Fixed bug that prevented migrations from succeeding

Added scaffolding required for jwt authentication for certain routes

Added logger to auth_api

Added necessary routing rules for multi-user mode

Registration is now possible
pull/67/head
Isaac Grynsztein 5 years ago
parent c3cc28540f
commit 98f1d003c3

@ -1,7 +1,7 @@
var async = require('async'); var async = require('async');
const { uuid } = require('uuidv4'); const { uuid } = require('uuidv4');
var fs = require('fs-extra'); var fs = require('fs-extra');
var auth = require('./authentication/auth'); var auth_api = require('./authentication/auth');
var winston = require('winston'); var winston = require('winston');
var path = require('path'); var path = require('path');
var youtubedl = require('youtube-dl'); var youtubedl = require('youtube-dl');
@ -63,6 +63,7 @@ const logger = winston.createLogger({
config_api.setLogger(logger); config_api.setLogger(logger);
subscriptions_api.setLogger(logger); subscriptions_api.setLogger(logger);
auth_api.setLogger(logger);
// var GithubContent = require('github-content'); // var GithubContent = require('github-content');
@ -154,7 +155,7 @@ app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json()); app.use(bodyParser.json());
// use passport // use passport
app.use(auth.passport.initialize()); app.use(auth_api.passport.initialize());
// objects // objects
@ -218,6 +219,7 @@ async function runFilesToDBMigration() {
db.set('files_to_db_migration_complete', true).write(); db.set('files_to_db_migration_complete', true).write();
resolve(true); resolve(true);
} catch(err) { } catch(err) {
logger.error(err);
resolve(false); resolve(false);
} }
}); });
@ -635,7 +637,7 @@ function getMp3s() {
var url = jsonobj.webpage_url; var url = jsonobj.webpage_url;
var uploader = jsonobj.uploader; var uploader = jsonobj.uploader;
var upload_date = jsonobj.upload_date; var upload_date = jsonobj.upload_date;
upload_date = `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}`; upload_date = upload_date ? `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}` : null;
var size = stats.size; var size = stats.size;
@ -664,7 +666,7 @@ function getMp4s(relative_path = true) {
var url = jsonobj.webpage_url; var url = jsonobj.webpage_url;
var uploader = jsonobj.uploader; var uploader = jsonobj.uploader;
var upload_date = jsonobj.upload_date; var upload_date = jsonobj.upload_date;
upload_date = `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}`; upload_date = upload_date ? `${upload_date.substring(0, 4)}-${upload_date.substring(4, 6)}-${upload_date.substring(6, 8)}` : null;
var thumbnail = jsonobj.thumbnail; var thumbnail = jsonobj.thumbnail;
var duration = jsonobj.duration; var duration = jsonobj.duration;
@ -1659,6 +1661,14 @@ app.use(function(req, res, next) {
app.use(compression()); app.use(compression());
const optionalJwt = function (req, res, next) {
const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode');
if (multiUserMode && req.query.jwt) {
return auth_api.passport.authenticate('jwt', { session: false })(req, res, next);
}
return next();
};
app.get('/api/config', function(req, res) { app.get('/api/config', function(req, res) {
let config_file = config_api.getConfigFile(); let config_file = config_api.getConfigFile();
res.send({ res.send({
@ -1781,19 +1791,21 @@ 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', optionalJwt, 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();
const is_authenticated = req.isAuthenticated();
if (req.query.jwt && multiUserMode) { if (is_authenticated) {
// mp3s = db.get // mp3s = db.get
auth_api.passport.authenticate('jwt')
mp3s = auth_api.getUserVideos()
} else {
res.send({
mp3s: mp3s,
playlists: playlists
});
} }
res.send({
mp3s: mp3s,
playlists: playlists
});
res.end("yes"); res.end("yes");
}); });
@ -2537,11 +2549,18 @@ app.get('/api/audio/:id', function(req , res){
// user authentication // user authentication
app.post('/api/auth/register' app.post('/api/auth/register'
, auth.registerUser); , auth_api.registerUser);
app.post('/api/auth/login' app.post('/api/auth/login'
, auth.passport.authenticate('local', {}) , auth_api.passport.authenticate('local', {})
, auth.generateJWT , auth_api.passport.authorize('local')
, auth.returnAuthResponse , auth_api.generateJWT
, auth_api.returnAuthResponse
);
app.post('/api/auth/jwtAuth'
, auth_api.passport.authenticate('jwt', { session: false })
, auth_api.passport.authorize('jwt')
, auth_api.generateJWT
, auth_api.returnAuthResponse
); );
app.use(function(req, res, next) { app.use(function(req, res, next) {

@ -11,11 +11,13 @@ db.defaults(
var LocalStrategy = require('passport-local').Strategy; var LocalStrategy = require('passport-local').Strategy;
var JwtStrategy = require('passport-jwt').Strategy, var JwtStrategy = require('passport-jwt').Strategy,
ExtractJwt = require('passport-jwt').ExtractJwt; ExtractJwt = require('passport-jwt').ExtractJwt;
var opts = {}
opts.jwtFromRequest = ExtractJwt.fromUrlQueryParameter('jwt'); // other required vars
opts.secretOrKey = 'secret'; let logger = null;
opts.issuer = 'example.com';
opts.audience = 'example.com'; exports.setLogger = function(input_logger) {
logger = input_logger;
}
/************************* /*************************
* Authentication module * Authentication module
@ -27,7 +29,19 @@ var jwt = require('jsonwebtoken');
const JWT_EXPIRATION = (60 * 60); // one hour const JWT_EXPIRATION = (60 * 60); // one hour
const { uuid } = require('uuidv4'); const { uuid } = require('uuidv4');
const SERVER_SECRET = uuid(); let SERVER_SECRET = null;
if (db.get('jwt_secret').value()) {
SERVER_SECRET = db.get('jwt_secret').value();
} else {
SERVER_SECRET = uuid();
db.set('jwt_secret', SERVER_SECRET).write();
}
var opts = {}
opts.jwtFromRequest = ExtractJwt.fromUrlQueryParameter('jwt');
opts.secretOrKey = SERVER_SECRET;
/*opts.issuer = 'example.com';
opts.audience = 'example.com';*/
exports.passport = require('passport'); exports.passport = require('passport');
var BasicStrategy = require('passport-http').BasicStrategy; var BasicStrategy = require('passport-http').BasicStrategy;
@ -50,33 +64,42 @@ exports.registerUser = function(req, res) {
bcrypt.hash(plaintextPassword, saltRounds) bcrypt.hash(plaintextPassword, saltRounds)
.then(function(hash) { .then(function(hash) {
let new_user = {
name: username,
uid: userid,
passhash: hash,
files: {
audio: [],
video: []
}
};
// check if user exists // check if user exists
if (db.get('users').find({uid: userid}).value()) { if (db.get('users').find({uid: userid}).value()) {
// user id is taken! // user id is taken!
logger.error('Registration failed: UID is already taken!');
res.status(409).send('UID is already taken!'); res.status(409).send('UID is already taken!');
} else if (db.get('users').find({name: username}).value()) { } else if (db.get('users').find({name: username}).value()) {
// user name is taken! // user name is taken!
logger.error('Registration failed: User name is already taken!');
res.status(409).send('User name is already taken!'); res.status(409).send('User name is already taken!');
} else { } else {
// add to db // add to db
db.get('users').push({ db.get('users').push(new_user).write();
name: username, logger.verbose(`New user created: ${new_user.name}`);
uid: userid, res.send({
passhash: hash user: new_user
}).write(); });
} }
}) })
.then(function(result) { .then(function(result) {
res.send('registered');
}) })
.catch(function(err) { .catch(function(err) {
logger.error(err);
if( err.code == 'ER_DUP_ENTRY' ) { if( err.code == 'ER_DUP_ENTRY' ) {
res.status(409).send('UserId already taken'); res.status(409).send('UserId already taken');
} else { } else {
console.log('failed TO register User'); res.sendStatus(409);
// res.writeHead(500, {'Content-Type':'text/plain'});
res.end(err);
} }
}); });
} }
@ -93,7 +116,7 @@ exports.registerUser = function(req, res) {
* If so, passes the user info to the next middleware. * If so, passes the user info to the next middleware.
************************************************/ ************************************************/
exports.passport.use(new JwtStrategy(opts, function(jwt_payload, done) { exports.passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
const user = db.get('users').find({uid: jwt_payload.sub}).value(); const user = db.get('users').find({uid: jwt_payload.user.uid}).value();
if (user) { if (user) {
return done(null, user); return done(null, user);
} else { } else {
@ -107,7 +130,7 @@ exports.passport.use(new LocalStrategy({
passwordField: 'password'}, passwordField: 'password'},
function(username, password, done) { function(username, password, done) {
const user = db.get('users').find({name: username}).value(); const user = db.get('users').find({name: username}).value();
if (!user) { return done(null, false); } if (!user) { console.log('user not found'); return done(null, false); }
if (user) { if (user) {
return done(null, bcrypt.compareSync(password, user.passhash) ? user : false); return done(null, bcrypt.compareSync(password, user.passhash) ? user : false);
} }
@ -200,8 +223,9 @@ exports.ensureAuthenticatedElseError = function(req, res, next) {
// video stuff // video stuff
exports.getUserVideos(type) { exports.getUserVideos = function(uid, type) {
const user = db.get('users').find({uid: uid}).value();
return user['files'][type];
} }
function getToken(queryParams) { function getToken(queryParams) {

@ -1,14 +1,14 @@
<mat-card class="login-card"> <mat-card class="login-card">
<mat-tab-group> <mat-tab-group [(selectedIndex)]="selectedTabIndex">
<mat-tab label="Login"> <mat-tab label="Login">
<div style="margin-top: 10px;"> <div style="margin-top: 10px;">
<mat-form-field> <mat-form-field>
<input [(ngModel)]="usernameInput" matInput placeholder="User name"> <input [(ngModel)]="loginUsernameInput" matInput placeholder="User name">
</mat-form-field> </mat-form-field>
</div> </div>
<div> <div>
<mat-form-field> <mat-form-field>
<input [(ngModel)]="passwordInput" type="password" matInput placeholder="Password"> <input [(ngModel)]="loginPasswordInput" type="password" matInput placeholder="Password">
</mat-form-field> </mat-form-field>
</div> </div>
<div style="margin-bottom: 10px; margin-top: 10px;"> <div style="margin-bottom: 10px; margin-top: 10px;">
@ -16,7 +16,24 @@
</div> </div>
</mat-tab> </mat-tab>
<mat-tab *ngIf="registrationEnabled" label="Register"> <mat-tab *ngIf="registrationEnabled" label="Register">
<div style="margin-top: 10px;">
<mat-form-field>
<input [(ngModel)]="registrationUsernameInput" matInput placeholder="User name">
</mat-form-field>
</div>
<div>
<mat-form-field>
<input [(ngModel)]="registrationPasswordInput" type="password" matInput placeholder="Password">
</mat-form-field>
</div>
<div>
<mat-form-field>
<input [(ngModel)]="registrationPasswordConfirmationInput" type="password" matInput placeholder="Confirm Password">
</mat-form-field>
</div>
<div style="margin-bottom: 10px; margin-top: 10px;">
<button [disabled]="registering" color="primary" (click)="register()" mat-raised-button>Register</button>
</div>
</mat-tab> </mat-tab>
</mat-tab-group> </mat-tab-group>
</mat-card> </mat-card>

@ -1,5 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { PostsService } from 'app/posts.services'; import { PostsService } from 'app/posts.services';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Router } from '@angular/router';
@Component({ @Component({
selector: 'app-login', selector: 'app-login',
@ -8,24 +10,82 @@ import { PostsService } from 'app/posts.services';
}) })
export class LoginComponent implements OnInit { export class LoginComponent implements OnInit {
usernameInput = ''; selectedTabIndex = 0;
passwordInput = '';
registrationEnabled = true; // login
loginUsernameInput = '';
loginPasswordInput = '';
loggingIn = false; loggingIn = false;
constructor(private postsService: PostsService) { } // registration
registrationEnabled = true;
registrationUsernameInput = '';
registrationPasswordInput = '';
registrationPasswordConfirmationInput = '';
registering = false;
constructor(private postsService: PostsService, private snackBar: MatSnackBar, private router: Router) { }
ngOnInit(): void { ngOnInit(): void {
if (this.postsService.isLoggedIn) {
this.router.navigate(['/home']);
}
} }
login() { login() {
this.loggingIn = true; this.loggingIn = true;
this.postsService.login(this.usernameInput, this.passwordInput).subscribe(res => { this.postsService.login(this.loginUsernameInput, this.loginPasswordInput).subscribe(res => {
this.loggingIn = false; this.loggingIn = false;
console.log(res);
}, err => { }, err => {
this.loggingIn = false; this.loggingIn = false;
}); });
} }
register() {
if (!this.registrationUsernameInput || this.registrationUsernameInput === '') {
this.openSnackBar('User name is required!');
return;
}
if (!this.registrationPasswordInput || this.registrationPasswordInput === '') {
this.openSnackBar('Password is required!');
return;
}
if (!this.registrationPasswordConfirmationInput || this.registrationPasswordConfirmationInput === '') {
this.openSnackBar('Password confirmation is required!');
return;
}
if (this.registrationPasswordInput !== this.registrationPasswordConfirmationInput) {
this.openSnackBar('Password confirmation is incorrect!');
return;
}
this.registering = true;
this.postsService.register(this.registrationUsernameInput, this.registrationPasswordInput).subscribe(res => {
this.registering = false;
if (res && res['user']) {
this.openSnackBar(`User ${res['user']['name']} successfully registered.`);
this.loginUsernameInput = res['user']['name'];
this.selectedTabIndex = 0;
} else {
}
}, err => {
this.registering = false;
if (err && err.error && typeof err.error === 'string') {
this.openSnackBar(err.error);
} else {
console.log(err);
}
});
}
public openSnackBar(message: string, action: string = '') {
this.snackBar.open(message, action, {
duration: 2000,
});
}
} }

@ -9,6 +9,7 @@ import { Router, CanActivate } from '@angular/router';
import { DOCUMENT } from '@angular/common'; import { DOCUMENT } from '@angular/common';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { MatSnackBar } from '@angular/material/snack-bar';
@Injectable() @Injectable()
export class PostsService implements CanActivate { export class PostsService implements CanActivate {
@ -25,13 +26,15 @@ export class PostsService implements CanActivate {
session_id = null; session_id = null;
httpOptions = null; httpOptions = null;
http_params: string = null; http_params: string = null;
unauthorized = false;
debugMode = false; debugMode = false;
isLoggedIn = false; isLoggedIn = false;
token = null; token = null;
user = 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,
public snackBar: MatSnackBar) {
console.log('PostsService Initialized...'); console.log('PostsService Initialized...');
// this.startPath = window.location.href + '/api/'; // this.startPath = window.location.href + '/api/';
// this.startPathSSL = window.location.href + '/api/'; // this.startPathSSL = window.location.href + '/api/';
@ -49,10 +52,24 @@ export class PostsService implements CanActivate {
fromString: this.http_params fromString: this.http_params
}), }),
}; };
// login stuff
if (localStorage.getItem('jwt_token')) {
this.token = localStorage.getItem('jwt_token');
this.httpOptions = {
params: new HttpParams({
fromString: `apiKey=${this.auth_token}&jwt=${this.token}`
}),
};
this.jwtAuth();
}
} }
canActivate(route, state): boolean { canActivate(route, state): Promise<boolean> {
return new Promise(resolve => {
resolve(true);
})
console.log(route); console.log(route);
return true;
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
@ -271,11 +288,17 @@ export class PostsService implements CanActivate {
this.user = user; this.user = user;
this.token = token; this.token = token;
localStorage.setItem('jwt_token', this.token);
this.httpOptions = { this.httpOptions = {
params: new HttpParams({ params: new HttpParams({
fromString: `apiKey=${this.auth_token}&jwt=${this.token}` fromString: `apiKey=${this.auth_token}&jwt=${this.token}`
}), }),
}; };
if (this.router.url === '/login') {
this.router.navigate(['/home']);
}
} }
// user methods // user methods
@ -289,4 +312,56 @@ export class PostsService implements CanActivate {
return call; return call;
} }
// user methods
jwtAuth() {
const call = this.http.post(this.path + 'auth/jwtAuth', {}, this.httpOptions);
call.subscribe(res => {
if (res['token']) {
this.afterLogin(res['user'], res['token']);
}
}, err => {
if (err.status === 401) {
this.sendToLogin();
}
});
return call;
}
logout() {
this.user = null;
this.isLoggedIn = false;
localStorage.setItem('jwt_token', null);
}
// user methods
register(username, password) {
const call = this.http.post(this.path + 'auth/register', {userid: username,
username: username,
password: password}, this.httpOptions);
/*call.subscribe(res => {
console.log(res['user']);
if (res['user']) {
// this.afterRegistration(res['user']);
}
});*/
return call;
}
sendToLogin() {
if (this.router.url === '/login') {
return;
}
this.router.navigate(['/login']);
// send login notification
this.openSnackBar('You must log in to access this page!');
}
public openSnackBar(message: string, action: string = '') {
this.snackBar.open(message, action, {
duration: 2000,
});
}
} }

Loading…
Cancel
Save