diff --git a/backend/app.js b/backend/app.js index dfad780..0a7fbec 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1,7 +1,7 @@ var async = require('async'); const { uuid } = require('uuidv4'); var fs = require('fs-extra'); -var auth = require('./authentication/auth'); +var auth_api = require('./authentication/auth'); var winston = require('winston'); var path = require('path'); var youtubedl = require('youtube-dl'); @@ -63,6 +63,7 @@ const logger = winston.createLogger({ config_api.setLogger(logger); subscriptions_api.setLogger(logger); +auth_api.setLogger(logger); // var GithubContent = require('github-content'); @@ -154,7 +155,7 @@ app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); // use passport -app.use(auth.passport.initialize()); +app.use(auth_api.passport.initialize()); // objects @@ -218,6 +219,7 @@ async function runFilesToDBMigration() { db.set('files_to_db_migration_complete', true).write(); resolve(true); } catch(err) { + logger.error(err); resolve(false); } }); @@ -635,7 +637,7 @@ function getMp3s() { var url = jsonobj.webpage_url; var uploader = jsonobj.uploader; 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; @@ -664,7 +666,7 @@ function getMp4s(relative_path = true) { var url = jsonobj.webpage_url; var uploader = jsonobj.uploader; 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 duration = jsonobj.duration; @@ -1659,6 +1661,14 @@ app.use(function(req, res, next) { 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) { let config_file = config_api.getConfigFile(); res.send({ @@ -1781,19 +1791,21 @@ app.post('/api/fileStatusMp4', function(req, res) { }); // gets all download mp3s -app.get('/api/getMp3s', function(req, res) { - const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode'); +app.get('/api/getMp3s', optionalJwt, function(req, res) { var mp3s = db.get('files.audio').value(); // getMp3s(); var playlists = db.get('playlists.audio').value(); - - if (req.query.jwt && multiUserMode) { + const is_authenticated = req.isAuthenticated(); + if (is_authenticated) { // 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"); }); @@ -2537,11 +2549,18 @@ app.get('/api/audio/:id', function(req , res){ // user authentication app.post('/api/auth/register' - , auth.registerUser); + , auth_api.registerUser); app.post('/api/auth/login' - , auth.passport.authenticate('local', {}) - , auth.generateJWT - , auth.returnAuthResponse + , auth_api.passport.authenticate('local', {}) + , auth_api.passport.authorize('local') + , 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) { diff --git a/backend/authentication/auth.js b/backend/authentication/auth.js index 81392b8..b46d191 100644 --- a/backend/authentication/auth.js +++ b/backend/authentication/auth.js @@ -11,11 +11,13 @@ db.defaults( 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'; + +// other required vars +let logger = null; + +exports.setLogger = function(input_logger) { + logger = input_logger; +} /************************* * Authentication module @@ -27,7 +29,19 @@ var jwt = require('jsonwebtoken'); const JWT_EXPIRATION = (60 * 60); // one hour 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'); var BasicStrategy = require('passport-http').BasicStrategy; @@ -50,33 +64,42 @@ exports.registerUser = function(req, res) { bcrypt.hash(plaintextPassword, saltRounds) .then(function(hash) { + let new_user = { + name: username, + uid: userid, + passhash: hash, + files: { + audio: [], + video: [] + } + }; // check if user exists if (db.get('users').find({uid: userid}).value()) { // user id is taken! + logger.error('Registration failed: UID is already taken!'); res.status(409).send('UID is already taken!'); } else if (db.get('users').find({name: username}).value()) { // user name is taken! + logger.error('Registration failed: User name is already taken!'); res.status(409).send('User name is already taken!'); } else { // add to db - db.get('users').push({ - name: username, - uid: userid, - passhash: hash - }).write(); + db.get('users').push(new_user).write(); + logger.verbose(`New user created: ${new_user.name}`); + res.send({ + user: new_user + }); } }) .then(function(result) { - res.send('registered'); + }) .catch(function(err) { + logger.error(err); if( err.code == 'ER_DUP_ENTRY' ) { res.status(409).send('UserId already taken'); } else { - console.log('failed TO register User'); - - // res.writeHead(500, {'Content-Type':'text/plain'}); - res.end(err); + res.sendStatus(409); } }); } @@ -93,7 +116,7 @@ exports.registerUser = function(req, res) { * If so, passes the user info to the next middleware. ************************************************/ 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) { return done(null, user); } else { @@ -107,7 +130,7 @@ exports.passport.use(new LocalStrategy({ passwordField: 'password'}, function(username, password, done) { 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) { return done(null, bcrypt.compareSync(password, user.passhash) ? user : false); } @@ -200,8 +223,9 @@ exports.ensureAuthenticatedElseError = function(req, res, next) { // 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) { diff --git a/src/app/components/login/login.component.html b/src/app/components/login/login.component.html index f7e6b93..3833599 100644 --- a/src/app/components/login/login.component.html +++ b/src/app/components/login/login.component.html @@ -1,14 +1,14 @@ - +
- +
- +
@@ -16,7 +16,24 @@
- +
+ + + +
+
+ + + +
+
+ + + +
+
+ +
\ No newline at end of file diff --git a/src/app/components/login/login.component.ts b/src/app/components/login/login.component.ts index eef1015..0bb8c04 100644 --- a/src/app/components/login/login.component.ts +++ b/src/app/components/login/login.component.ts @@ -1,5 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { PostsService } from 'app/posts.services'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { Router } from '@angular/router'; @Component({ selector: 'app-login', @@ -8,24 +10,82 @@ import { PostsService } from 'app/posts.services'; }) export class LoginComponent implements OnInit { - usernameInput = ''; - passwordInput = ''; - registrationEnabled = true; + selectedTabIndex = 0; + + // login + loginUsernameInput = ''; + loginPasswordInput = ''; 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 { + if (this.postsService.isLoggedIn) { + this.router.navigate(['/home']); + } } login() { this.loggingIn = true; - this.postsService.login(this.usernameInput, this.passwordInput).subscribe(res => { + this.postsService.login(this.loginUsernameInput, this.loginPasswordInput).subscribe(res => { this.loggingIn = false; - console.log(res); }, err => { 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, + }); + } + } diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index c766ae6..fcbba9b 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -9,6 +9,7 @@ import { Router, CanActivate } from '@angular/router'; import { DOCUMENT } from '@angular/common'; import { BehaviorSubject } from 'rxjs'; import { v4 as uuid } from 'uuid'; +import { MatSnackBar } from '@angular/material/snack-bar'; @Injectable() export class PostsService implements CanActivate { @@ -25,13 +26,15 @@ export class PostsService implements CanActivate { session_id = null; httpOptions = null; http_params: string = null; + unauthorized = 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, + public snackBar: MatSnackBar) { console.log('PostsService Initialized...'); // this.startPath = window.location.href + '/api/'; // this.startPathSSL = window.location.href + '/api/'; @@ -49,10 +52,24 @@ export class PostsService implements CanActivate { 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 { + return new Promise(resolve => { + resolve(true); + }) console.log(route); - return true; throw new Error('Method not implemented.'); } @@ -271,11 +288,17 @@ export class PostsService implements CanActivate { this.user = user; this.token = token; + localStorage.setItem('jwt_token', this.token); + this.httpOptions = { params: new HttpParams({ fromString: `apiKey=${this.auth_token}&jwt=${this.token}` }), }; + + if (this.router.url === '/login') { + this.router.navigate(['/home']); + } } // user methods @@ -289,4 +312,56 @@ export class PostsService implements CanActivate { 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, + }); + } + }