jwt auth scaffolding

logging in now works

UI login component created
This commit is contained in:
Isaac Grynsztein
2020-04-16 22:35:34 -04:00
parent da8571fb1a
commit 1f3572a630
9 changed files with 185 additions and 13 deletions

View File

@@ -1629,9 +1629,14 @@ app.post('/api/fileStatusMp4', function(req, res) {
// gets all download mp3s // gets all download mp3s
app.get('/api/getMp3s', function(req, res) { app.get('/api/getMp3s', function(req, res) {
const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode');
var mp3s = db.get('files.audio').value(); // getMp3s(); var mp3s = db.get('files.audio').value(); // getMp3s();
var playlists = db.get('playlists.audio').value(); var playlists = db.get('playlists.audio').value();
if (req.query.jwt && multiUserMode) {
// mp3s = db.get
}
res.send({ res.send({
mp3s: mp3s, mp3s: mp3s,
playlists: playlists playlists: playlists
@@ -2313,8 +2318,7 @@ app.get('/api/audio/:id', function(req , res){
app.post('/api/auth/register' app.post('/api/auth/register'
, auth.registerUser); , auth.registerUser);
app.post('/api/auth/login' app.post('/api/auth/login'
// , auth.passport.authenticate('basic',{session:false}) // causes challenge pop-up on 401 , auth.passport.authenticate('local', {})
, auth.authenticateViaPassport
, auth.generateJWT , auth.generateJWT
, auth.returnAuthResponse , auth.returnAuthResponse
); );

View File

@@ -8,6 +8,15 @@ db.defaults(
} }
).write(); ).write();
var LocalStrategy = require('passport-local').Strategy;
var JwtStrategy = require('passport-jwt').Strategy,
ExtractJwt = require('passport-jwt').ExtractJwt;
var opts = {}
opts.jwtFromRequest = ExtractJwt.fromUrlQueryParameter('jwt');
opts.secretOrKey = 'secret';
opts.issuer = 'example.com';
opts.audience = 'example.com';
/************************* /*************************
* Authentication module * Authentication module
************************/ ************************/
@@ -23,11 +32,18 @@ const SERVER_SECRET = uuid();
exports.passport = require('passport'); exports.passport = require('passport');
var BasicStrategy = require('passport-http').BasicStrategy; var BasicStrategy = require('passport-http').BasicStrategy;
exports.passport.serializeUser(function(user, done) {
done(null, user);
});
exports.passport.deserializeUser(function(user, done) {
done(null, user);
});
/*************************************** /***************************************
* Register user with hashed password * Register user with hashed password
**************************************/ **************************************/
exports.registerUser = function(req, res) { exports.registerUser = function(req, res) {
console.log('got here');
var userid = req.body.userid; var userid = req.body.userid;
var username = req.body.username; var username = req.body.username;
var plaintextPassword = req.body.password; var plaintextPassword = req.body.password;
@@ -76,10 +92,31 @@ exports.registerUser = function(req, res) {
* This checks that the credentials are valid. * This checks that the credentials are valid.
* If so, passes the user info to the next middleware. * If so, passes the user info to the next middleware.
************************************************/ ************************************************/
exports.passport.use(new BasicStrategy( exports.passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
const user = db.get('users').find({uid: jwt_payload.sub}).value();
if (user) {
return done(null, user);
} else {
return done(null, false);
// or you could create a new account
}
}));
exports.passport.use(new LocalStrategy({
usernameField: 'userid',
passwordField: 'password'},
function(username, password, done) {
const user = db.get('users').find({name: username}).value();
if (!user) { return done(null, false); }
if (user) {
return done(null, bcrypt.compareSync(password, user.passhash) ? user : false);
}
}
));
/*passport.use(new BasicStrategy(
function(userid, plainTextPassword, done) { function(userid, plainTextPassword, done) {
// console.log('BasicStrategy: verifying credentials'); const user = db.get('users').find({name: userid}).value();
const user = db.get('users').find({uid: userid}).value();
if (user) { if (user) {
var hashedPwd = user.passhash; var hashedPwd = user.passhash;
return bcrypt.compare(plainTextPassword, hashedPwd); return bcrypt.compare(plainTextPassword, hashedPwd);
@@ -88,6 +125,7 @@ exports.passport.use(new BasicStrategy(
} }
} }
)); ));
*/
/************************************************************* /*************************************************************
* This is a wrapper for auth.passport.authenticate(). * This is a wrapper for auth.passport.authenticate().
@@ -96,6 +134,7 @@ exports.passport.use(new BasicStrategy(
* Browser's will pop-up up dialog when status is 401 and * Browser's will pop-up up dialog when status is 401 and
* "WWW-Authenticate:Basic..." * "WWW-Authenticate:Basic..."
*************************************************************/ *************************************************************/
/*
exports.authenticateViaPassport = function(req, res, next) { exports.authenticateViaPassport = function(req, res, next) {
exports.passport.authenticate('basic',{session:false}, exports.passport.authenticate('basic',{session:false},
function(err, user, info) { function(err, user, info) {
@@ -109,6 +148,7 @@ exports.authenticateViaPassport = function(req, res, next) {
} }
)(req, res, next); )(req, res, next);
}; };
*/
/********************************** /**********************************
* Generating/Signing a JWT token * Generating/Signing a JWT token
@@ -158,6 +198,12 @@ exports.ensureAuthenticatedElseError = function(req, res, next) {
} }
} }
// video stuff
exports.getUserVideos(type) {
}
function getToken(queryParams) { function getToken(queryParams) {
if (queryParams && queryParams.jwt) { if (queryParams && queryParams.jwt) {
var parted = queryParams.jwt.split(' '); var parted = queryParams.jwt.split(' ');

View File

@@ -4,11 +4,15 @@ import { MainComponent } from './main/main.component';
import { PlayerComponent } from './player/player.component'; import { PlayerComponent } from './player/player.component';
import { SubscriptionsComponent } from './subscriptions/subscriptions.component'; import { SubscriptionsComponent } from './subscriptions/subscriptions.component';
import { SubscriptionComponent } from './subscription/subscription/subscription.component'; import { SubscriptionComponent } from './subscription/subscription/subscription.component';
import { PostsService } from './posts.services';
import { LoginComponent } from './components/login/login.component';
const routes: Routes = [ const routes: Routes = [
{ path: 'home', component: MainComponent }, { path: 'home', component: MainComponent, canActivate: [PostsService] },
{ path: 'player', component: PlayerComponent}, { path: 'player', component: PlayerComponent, canActivate: [PostsService]},
{ path: 'subscriptions', component: SubscriptionsComponent }, { path: 'subscriptions', component: SubscriptionsComponent, canActivate: [PostsService] },
{ path: 'subscription', component: SubscriptionComponent }, { path: 'subscription', component: SubscriptionComponent, canActivate: [PostsService] },
{ path: 'login', component: LoginComponent },
{ path: '', redirectTo: '/home', pathMatch: 'full' }, { path: '', redirectTo: '/home', pathMatch: 'full' },
]; ];

View File

@@ -57,6 +57,7 @@ import { ArgModifierDialogComponent, HighlightPipe } from './dialogs/arg-modifie
import { UpdaterComponent } from './updater/updater.component'; import { UpdaterComponent } from './updater/updater.component';
import { UpdateProgressDialogComponent } from './dialogs/update-progress-dialog/update-progress-dialog.component'; import { UpdateProgressDialogComponent } from './dialogs/update-progress-dialog/update-progress-dialog.component';
import { ShareMediaDialogComponent } from './dialogs/share-media-dialog/share-media-dialog.component'; import { ShareMediaDialogComponent } from './dialogs/share-media-dialog/share-media-dialog.component';
import { LoginComponent } from './components/login/login.component';
registerLocaleData(es, 'es'); registerLocaleData(es, 'es');
export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) { export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) {
@@ -85,7 +86,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
HighlightPipe, HighlightPipe,
UpdaterComponent, UpdaterComponent,
UpdateProgressDialogComponent, UpdateProgressDialogComponent,
ShareMediaDialogComponent ShareMediaDialogComponent,
LoginComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,

View File

@@ -0,0 +1,22 @@
<mat-card class="login-card">
<mat-tab-group>
<mat-tab label="Login">
<div style="margin-top: 10px;">
<mat-form-field>
<input [(ngModel)]="usernameInput" matInput placeholder="User name">
</mat-form-field>
</div>
<div>
<mat-form-field>
<input [(ngModel)]="passwordInput" type="password" matInput placeholder="Password">
</mat-form-field>
</div>
<div style="margin-bottom: 10px; margin-top: 10px;">
<button [disabled]="loggingIn" color="primary" (click)="login()" mat-raised-button>Login</button>
</div>
</mat-tab>
<mat-tab *ngIf="registrationEnabled" label="Register">
</mat-tab>
</mat-tab-group>
</mat-card>

View File

@@ -0,0 +1,6 @@
.login-card {
max-width: 600px;
width: 80%;
margin: 0 auto;
margin-top: 20px;
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,31 @@
import { Component, OnInit } from '@angular/core';
import { PostsService } from 'app/posts.services';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
usernameInput = '';
passwordInput = '';
registrationEnabled = true;
loggingIn = false;
constructor(private postsService: PostsService) { }
ngOnInit(): void {
}
login() {
this.loggingIn = true;
this.postsService.login(this.usernameInput, this.passwordInput).subscribe(res => {
this.loggingIn = false;
console.log(res);
}, err => {
this.loggingIn = false;
});
}
}

View File

@@ -5,12 +5,12 @@ import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw'; import 'rxjs/add/observable/throw';
import { THEMES_CONFIG } from '../themes'; import { THEMES_CONFIG } from '../themes';
import { Router } from '@angular/router'; import { Router, CanActivate } from '@angular/router';
import { DOCUMENT } from '@angular/common'; import { DOCUMENT } from '@angular/common';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
@Injectable() @Injectable()
export class PostsService { export class PostsService implements CanActivate {
path = ''; path = '';
audioFolder = ''; audioFolder = '';
videoFolder = ''; videoFolder = '';
@@ -24,6 +24,10 @@ export class PostsService {
httpOptions = null; httpOptions = null;
debugMode = false; debugMode = false;
isLoggedIn = false;
token = null;
user = null;
constructor(private http: HttpClient, private router: Router, @Inject(DOCUMENT) private document: Document) { constructor(private http: HttpClient, private router: Router, @Inject(DOCUMENT) private document: Document) {
console.log('PostsService Initialized...'); console.log('PostsService Initialized...');
// this.startPath = window.location.href + '/api/'; // this.startPath = window.location.href + '/api/';
@@ -41,6 +45,11 @@ export class PostsService {
}), }),
}; };
} }
canActivate(route, state): boolean {
console.log(route);
return true;
throw new Error('Method not implemented.');
}
setTheme(theme) { setTheme(theme) {
this.theme = this.THEMES_CONFIG[theme]; this.theme = this.THEMES_CONFIG[theme];
@@ -233,4 +242,27 @@ export class PostsService {
return this.http.get('https://api.github.com/repos/tzahi12345/youtubedl-material/releases'); return this.http.get('https://api.github.com/repos/tzahi12345/youtubedl-material/releases');
} }
afterLogin(user, token) {
this.isLoggedIn = true;
this.user = user;
this.token = token;
this.httpOptions = {
params: new HttpParams({
fromString: `apiKey=${this.auth_token}&jwt=${this.token}`
}),
};
}
// user methods
login(username, password) {
const call = this.http.post(this.path + 'auth/login', {userid: username, password: password}, this.httpOptions);
call.subscribe(res => {
if (res['token']) {
this.afterLogin(res['user'], res['token']);
}
});
return call;
}
} }