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,
+ });
+ }
+
}