diff --git a/backend/app.js b/backend/app.js index 803b674..0b69026 100644 --- a/backend/app.js +++ b/backend/app.js @@ -2735,6 +2735,16 @@ app.post('/api/auth/jwtAuth' , auth_api.generateJWT , auth_api.returnAuthResponse ); +app.post('/api/auth/changePassword', optionalJwt, async (req, res) => { + let user_uid = req.user.uid; + let password = req.body.password; + let success = await auth_api.changeUserPassword(user_uid, password); + res.send({success: success}); +}); +app.post('/api/auth/adminExists', async (req, res) => { + let exists = auth_api.adminExists(); + res.send({exists: exists}); +}); app.use(function(req, res, next) { //if the request is not html then move along diff --git a/backend/authentication/auth.js b/backend/authentication/auth.js index 5b5d0a6..b8930a8 100644 --- a/backend/authentication/auth.js +++ b/backend/authentication/auth.js @@ -96,7 +96,8 @@ exports.registerUser = function(req, res) { video: [] }, subscriptions: [], - created: Date.now() + created: Date.now(), + role: userid === 'admin' ? 'admin' : 'user' }; // check if user exists if (users_db.get('users').find({uid: userid}).value()) { @@ -238,6 +239,23 @@ exports.ensureAuthenticatedElseError = function(req, res, next) { } } +// change password +exports.changeUserPassword = async function(user_uid, new_pass) { + return new Promise(resolve => { + bcrypt.hash(new_pass, saltRounds) + .then(function(hash) { + users_db.get('users').find({uid: user_uid}).assign({passhash: hash}).write(); + resolve(true); + }).catch(err => { + resolve(false); + }); + }); +} + +exports.adminExists = function() { + return !!users_db.get('users').find({uid: 'admin'}).value(); +} + // video stuff exports.getUserVideos = function(user_uid, type) { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 0bf3165..3fa9207 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -24,6 +24,7 @@ import { SettingsComponent } from './settings/settings.component'; import { CheckOrSetPinDialogComponent } from './dialogs/check-or-set-pin-dialog/check-or-set-pin-dialog.component'; import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component'; import { UserProfileDialogComponent } from './dialogs/user-profile-dialog/user-profile-dialog.component'; +import { SetDefaultAdminDialogComponent } from './dialogs/set-default-admin-dialog/set-default-admin-dialog.component'; @Component({ selector: 'app-root', @@ -148,6 +149,18 @@ onSetTheme(theme, old_theme) { } else { // } + this.postsService.open_create_default_admin_dialog.subscribe(open => { + if (open) { + const dialogRef = this.dialog.open(SetDefaultAdminDialogComponent); + dialogRef.afterClosed().subscribe(success => { + if (success) { + if (this.router.url !== '/login') { this.router.navigate(['/login']); } + } else { + console.error('Failed to create default admin account. See logs for details.'); + } + }); + } + }); } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 0334c6a..4d4a46d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -60,6 +60,7 @@ import { ShareMediaDialogComponent } from './dialogs/share-media-dialog/share-me import { LoginComponent } from './components/login/login.component'; import { DownloadsComponent } from './components/downloads/downloads.component'; import { UserProfileDialogComponent } from './dialogs/user-profile-dialog/user-profile-dialog.component'; +import { SetDefaultAdminDialogComponent } from './dialogs/set-default-admin-dialog/set-default-admin-dialog.component'; registerLocaleData(es, 'es'); export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps) { @@ -91,7 +92,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible ShareMediaDialogComponent, LoginComponent, DownloadsComponent, - UserProfileDialogComponent + UserProfileDialogComponent, + SetDefaultAdminDialogComponent ], imports: [ CommonModule, diff --git a/src/app/components/login/login.component.ts b/src/app/components/login/login.component.ts index 0bb8c04..a592302 100644 --- a/src/app/components/login/login.component.ts +++ b/src/app/components/login/login.component.ts @@ -30,6 +30,13 @@ export class LoginComponent implements OnInit { if (this.postsService.isLoggedIn) { this.router.navigate(['/home']); } + this.postsService.service_initialized.subscribe(init => { + if (init) { + if (!this.postsService.config['Advanced']['multi_user_mode']) { + this.router.navigate(['/home']); + } + } + }); } login() { diff --git a/src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html b/src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html new file mode 100644 index 0000000..1d800b9 --- /dev/null +++ b/src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.html @@ -0,0 +1,19 @@ +

Create admin account

+ + +
+

No default admin account detected. This will create and set the password for an admin account with the user name as 'admin'.

+
+
+
+ + + +
+
+
+ + + +
+
\ No newline at end of file diff --git a/src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.scss b/src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.scss new file mode 100644 index 0000000..efe2b9e --- /dev/null +++ b/src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.scss @@ -0,0 +1,5 @@ +.spinner-div { + position: relative; + left: 10px; + bottom: 5px; +} \ No newline at end of file diff --git a/src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.spec.ts b/src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.spec.ts new file mode 100644 index 0000000..cc37170 --- /dev/null +++ b/src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SetDefaultAdminDialogComponent } from './set-default-admin-dialog.component'; + +describe('SetDefaultAdminDialogComponent', () => { + let component: SetDefaultAdminDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SetDefaultAdminDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SetDefaultAdminDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.ts b/src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.ts new file mode 100644 index 0000000..eec524a --- /dev/null +++ b/src/app/dialogs/set-default-admin-dialog/set-default-admin-dialog.component.ts @@ -0,0 +1,33 @@ +import { Component, OnInit } from '@angular/core'; +import { PostsService } from 'app/posts.services'; +import { MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'app-set-default-admin-dialog', + templateUrl: './set-default-admin-dialog.component.html', + styleUrls: ['./set-default-admin-dialog.component.scss'] +}) +export class SetDefaultAdminDialogComponent implements OnInit { + creating = false; + input = ''; + constructor(private postsService: PostsService, public dialogRef: MatDialogRef) { } + + ngOnInit(): void { + } + + create() { + this.creating = true; + this.postsService.createAdminAccount(this.input).subscribe(res => { + this.creating = false; + if (res['success']) { + this.dialogRef.close(true); + } else { + this.dialogRef.close(false); + } + }, err => { + console.log(err); + this.dialogRef.close(false); + }); + } + +} diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index ea47db8..42c6168 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -39,6 +39,8 @@ export class PostsService implements CanActivate { service_initialized = new BehaviorSubject(false); initialized = false; + open_create_default_admin_dialog = new BehaviorSubject(false); + config = null; constructor(private http: HttpClient, private router: Router, @Inject(DOCUMENT) private document: Document, public snackBar: MatSnackBar) { @@ -75,6 +77,8 @@ export class PostsService implements CanActivate { }), }; this.jwtAuth(); + } else { + this.sendToLogin(); } } else { this.setInitialized(); @@ -398,6 +402,7 @@ export class PostsService implements CanActivate { } sendToLogin() { + this.checkAdminCreationStatus(); if (this.router.url === '/login') { return; } @@ -414,6 +419,26 @@ export class PostsService implements CanActivate { this.config_reloaded.next(true); } + adminExists() { + return this.http.post(this.path + 'auth/adminExists', {}, this.httpOptions); + } + + createAdminAccount(password) { + return this.http.post(this.path + 'auth/register', {userid: 'admin', + username: 'admin', + password: password}, this.httpOptions); + } + + checkAdminCreationStatus() { + console.log('checking c stat'); + this.adminExists().subscribe(res => { + if (!res['exists']) { + // must create admin account + this.open_create_default_admin_dialog.next(true); + } + }); + } + public openSnackBar(message: string, action: string = '') { this.snackBar.open(message, action, { duration: 2000, diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts index b8fc2b9..c3021a3 100644 --- a/src/app/settings/settings.component.ts +++ b/src/app/settings/settings.component.ts @@ -63,6 +63,10 @@ export class SettingsComponent implements OnInit { const settingsToSave = {'YoutubeDLMaterial': this.new_config}; this.postsService.setConfig(settingsToSave).subscribe(res => { if (res['success']) { + if (!this.initial_config['Advanced']['multi_user_mode'] && this.new_config['Advanced']['multi_user_mode']) { + // multi user mode was enabled, let's check if default admin account exists + this.postsService.checkAdminCreationStatus(); + } // sets new config as old config this.initial_config = JSON.parse(JSON.stringify(this.new_config)); this.postsService.reload_config.next(true);