added ability to set pin for settings. pin is md5'd by the backend

pull/36/head
Isaac Grynsztein 6 years ago
parent c752b13732
commit 91f6dbcb5b

@ -9,6 +9,7 @@ var express = require("express");
var bodyParser = require("body-parser");
var archiver = require('archiver');
const low = require('lowdb')
var md5 = require('md5');
var URL = require('url').URL;
const shortid = require('shortid')
const url_api = require('url');
@ -29,7 +30,8 @@ db.defaults(
video: []
},
configWriteFlag: false,
subscriptions: []
subscriptions: [],
pin_md5: ''
}).write();
// config values
@ -1176,6 +1178,47 @@ app.post('/api/downloadArchive', async (req, res) => {
});
app.post('/api/isPinSet', async (req, res) => {
let stored_pin = db.get('pin_md5').value();
let is_set = false;
if (!stored_pin || stored_pin.length === 0) {
} else {
is_set = true;
}
res.send({
is_set: is_set
});
});
app.post('/api/setPin', async (req, res) => {
let unhashed_pin = req.body.pin;
let hashed_pin = md5(unhashed_pin);
db.set('pin_md5', hashed_pin).write();
res.send({
success: true
});
});
app.post('/api/checkPin', async (req, res) => {
let input_pin = req.body.input_pin;
let input_pin_md5 = md5(input_pin);
let stored_pin = db.get('pin_md5').value();
let successful = false;
if (input_pin_md5 === stored_pin) {
successful = true;
}
res.send({
success: successful
});
});
app.get('/api/video/:id', function(req , res){
var head;
let optionalParams = url_api.parse(req.url,true).query;

@ -19,7 +19,8 @@
"file_manager_enabled": true,
"allow_quality_select": true,
"download_only_mode": false,
"allow_multi_download_mode": true
"allow_multi_download_mode": true,
"settings_pin_required": false
},
"API": {
"use_youtube_API": false,

@ -19,7 +19,8 @@
"file_manager_enabled": true,
"allow_quality_select": true,
"download_only_mode": false,
"allow_multi_download_mode": true
"allow_multi_download_mode": true,
"settings_pin_required": false
},
"API": {
"use_youtube_API": false,

@ -60,6 +60,10 @@ let CONFIG_ITEMS = {
'key': 'ytdl_allow_multi_download_mode',
'path': 'YoutubeDLMaterial.Extra.allow_multi_download_mode'
},
'ytdl_settings_pin_required': {
'key': 'ytdl_settings_pin_required',
'path': 'YoutubeDLMaterial.Extra.settings_pin_required'
},
// API
'ytdl_use_youtube_api': {

@ -25,6 +25,7 @@
"exe": "^1.0.2",
"express": "^4.17.1",
"lowdb": "^1.0.0",
"md5": "^2.2.1",
"shortid": "^2.2.15",
"uuidv4": "^6.0.6",
"youtube-dl": "^3.0.2"

@ -18,6 +18,7 @@ services:
ytdl_allow_quality_select: 'true'
ytdl_download_only_mode: 'false'
ytdl_allow_multi_download_mode: 'true'
ytdl_settings_pin_required: 'false'
ytdl_use_youtube_api: 'false'
ytdl_youtube_api_key: 'false'
ytdl_default_theme: default

@ -19,6 +19,7 @@ import { Router, NavigationStart, NavigationEnd } from '@angular/router';
import { OverlayContainer } from '@angular/cdk/overlay';
import { THEMES_CONFIG } from '../themes';
import { SettingsComponent } from './settings/settings.component';
import { CheckOrSetPinDialogComponent } from './dialogs/check-or-set-pin-dialog/check-or-set-pin-dialog.component';
@Component({
selector: 'app-root',
@ -35,6 +36,8 @@ export class AppComponent implements OnInit {
defaultTheme = null;
allowThemeChange = null;
allowSubscriptions = false;
// defaults to true to prevent attack
settingsPinRequired = true;
@ViewChild('sidenav', {static: false}) sidenav: MatSidenav;
@ViewChild('hamburgerMenu', {static: false, read: ElementRef}) hamburgerMenuButton: ElementRef;
@ -73,6 +76,7 @@ export class AppComponent implements OnInit {
this.postsService.loadNavItems().subscribe(res => { // loads settings
const result = !this.postsService.debugMode ? res['config_file'] : res;
this.topBarTitle = result['YoutubeDLMaterial']['Extra']['title_top'];
this.settingsPinRequired = result['YoutubeDLMaterial']['Extra']['settings_pin_required'];
const themingExists = result['YoutubeDLMaterial']['Themes'];
this.defaultTheme = themingExists ? result['YoutubeDLMaterial']['Themes']['default_theme'] : 'default';
this.allowThemeChange = themingExists ? result['YoutubeDLMaterial']['Themes']['allow_theme_change'] : true;
@ -158,10 +162,29 @@ onSetTheme(theme, old_theme) {
}
openSettingsDialog() {
if (this.settingsPinRequired) {
this.openPinDialog();
} else {
this.actuallyOpenSettingsDialog();
}
}
actuallyOpenSettingsDialog() {
const dialogRef = this.dialog.open(SettingsComponent, {
width: '80vw'
});
}
openPinDialog() {
const dialogRef = this.dialog.open(CheckOrSetPinDialogComponent, {
});
dialogRef.afterClosed().subscribe(res => {
if (res) {
this.actuallyOpenSettingsDialog();
}
})
}
}

@ -37,6 +37,7 @@ import { SubscriptionComponent } from './subscription//subscription/subscription
import { SubscriptionFileCardComponent } from './subscription/subscription-file-card/subscription-file-card.component';
import { SubscriptionInfoDialogComponent } from './dialogs/subscription-info-dialog/subscription-info-dialog.component';
import { SettingsComponent } from './settings/settings.component';
import { CheckOrSetPinDialogComponent } from './dialogs/check-or-set-pin-dialog/check-or-set-pin-dialog.component';
export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) {
return (element.id === 'video' ? videoFilesMouseHovering || videoFilesOpened : audioFilesMouseHovering || audioFilesOpened);
@ -56,7 +57,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
SubscriptionComponent,
SubscriptionFileCardComponent,
SubscriptionInfoDialogComponent,
SettingsComponent
SettingsComponent,
CheckOrSetPinDialogComponent
],
imports: [
BrowserModule,
@ -102,7 +104,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
CreatePlaylistComponent,
SubscribeDialogComponent,
SubscriptionInfoDialogComponent,
SettingsComponent
SettingsComponent,
CheckOrSetPinDialogComponent
],
providers: [PostsService],
bootstrap: [AppComponent]

@ -0,0 +1,18 @@
<h4 *ngIf="pinSetChecked" mat-dialog-title>{{dialog_title}}</h4>
<mat-dialog-content>
<div style="position: relative">
<div *ngIf="pinSetChecked">
<mat-form-field color="accent">
<input type="password" (keyup.enter)="doAction()" matInput [(ngModel)]="input" [placeholder]="input_placeholder">
</mat-form-field>
</div>
<div class="spinner-div" *ngIf="!pinSetChecked">
<mat-spinner [diameter]="25"></mat-spinner>
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button [disabled]="input.length === 0" color="accent" style="margin-bottom: 12px;" (click)="doAction()" mat-raised-button>{{button_label}}</button>
</mat-dialog-actions>

@ -0,0 +1,6 @@
.spinner-div {
position: absolute;
margin: 0 auto;
top: 30%;
left: 42%;
}

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

@ -0,0 +1,96 @@
import { Component, OnInit, Inject } from '@angular/core';
import { PostsService } from 'app/posts.services';
import { MAT_DIALOG_DATA, MatDialogRef, MatSnackBar } from '@angular/material';
@Component({
selector: 'app-check-or-set-pin-dialog',
templateUrl: './check-or-set-pin-dialog.component.html',
styleUrls: ['./check-or-set-pin-dialog.component.scss']
})
export class CheckOrSetPinDialogComponent implements OnInit {
pinSetChecked = false;
pinSet = true;
resetMode = false;
dialog_title = '';
input_placeholder = null;
input = '';
button_label = '';
constructor(private postsService: PostsService, @Inject(MAT_DIALOG_DATA) public data: any,
public dialogRef: MatDialogRef<CheckOrSetPinDialogComponent>, private snackBar: MatSnackBar) { }
ngOnInit() {
if (this.data) {
console.log('is reset mode');
this.resetMode = this.data.resetMode;
}
if (this.resetMode) {
this.pinSetChecked = true;
this.notSetLogic();
} else {
this.isPinSet();
}
}
isPinSet() {
this.postsService.isPinSet().subscribe(res => {
this.pinSetChecked = true;
if (res['is_set']) {
this.isSetLogic();
} else {
this.notSetLogic();
}
});
}
isSetLogic() {
this.pinSet = true;
this.dialog_title = 'Pin Required';
this.input_placeholder = 'Pin';
this.button_label = 'Submit'
}
notSetLogic() {
this.pinSet = false;
this.dialog_title = 'Set your pin';
this.input_placeholder = 'New pin';
this.button_label = 'Set Pin'
}
doAction() {
// pin set must have been checked, and input must not be empty
if (!this.pinSetChecked || this.input.length === 0) {
return;
}
if (this.pinSet) {
this.postsService.checkPin(this.input).subscribe(res => {
if (res['success']) {
this.dialogRef.close(true);
} else {
this.dialogRef.close(false);
this.openSnackBar('Pin is incorrect!');
}
});
} else {
this.postsService.setPin(this.input).subscribe(res => {
if (res['success']) {
this.dialogRef.close(true);
this.openSnackBar('Pin successfully set!');
} else {
this.dialogRef.close(false);
this.openSnackBar('Failed to set pin!');
}
});
}
}
public openSnackBar(message: string, action: string = '') {
this.snackBar.open(message, action, {
duration: 2000,
});
}
}

@ -130,6 +130,18 @@ export class PostsService {
return this.http.post(this.path + 'getVideoInfos', {fileNames: fileNames, type: type, urlMode: urlMode});
}
isPinSet() {
return this.http.post(this.path + 'isPinSet', {});
}
setPin(unhashed_pin) {
return this.http.post(this.path + 'setPin', {pin: unhashed_pin});
}
checkPin(unhashed_pin) {
return this.http.post(this.path + 'checkPin', {input_pin: unhashed_pin});
}
createPlaylist(playlistName, fileNames, type, thumbnailURL) {
return this.http.post(this.path + 'createPlaylist', {playlistName: playlistName,
fileNames: fileNames,

@ -115,6 +115,10 @@
<div class="col-12">
<mat-checkbox color="accent" [(ngModel)]="new_config['Extra']['allow_multi_download_mode']">Allow multi-download mode</mat-checkbox>
</div>
<div class="col-12">
<mat-checkbox color="accent" [(ngModel)]="new_config['Extra']['settings_pin_required']">Require pin for settings</mat-checkbox>
<button mat-stroked-button (click)="setNewPin()" [disabled]="!new_config['Extra']['settings_pin_required']">Set New Pin</button>
</div>
</div>
</div>
</mat-expansion-panel>

@ -1,5 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { PostsService } from 'app/posts.services';
import { MatDialog } from '@angular/material';
import { CheckOrSetPinDialogComponent } from 'app/dialogs/check-or-set-pin-dialog/check-or-set-pin-dialog.component';
@Component({
selector: 'app-settings',
@ -12,7 +14,7 @@ export class SettingsComponent implements OnInit {
new_config = null
loading_config = false;
constructor(private postsService: PostsService) { }
constructor(private postsService: PostsService, private dialog: MatDialog) { }
ngOnInit() {
this.getConfig();
@ -46,4 +48,12 @@ export class SettingsComponent implements OnInit {
})
}
setNewPin() {
const dialogRef = this.dialog.open(CheckOrSetPinDialogComponent, {
data: {
resetMode: true
}
});
}
}

@ -1,44 +1,45 @@
{
"YoutubeDLMaterial": {
"Host": {
"url": "http://localhost",
"port": "17442"
},
"Encryption": {
"use-encryption": false,
"cert-file-path": "/etc/letsencrypt/live/example.com/fullchain.pem",
"key-file-path": "/etc/letsencrypt/live/example.com/privkey.pem"
},
"Downloader": {
"path-audio": "audio/",
"path-video": "video/",
"custom_args": ""
},
"Extra": {
"title_top": "Youtube Downloader",
"file_manager_enabled": true,
"allow_quality_select": true,
"download_only_mode": false,
"allow_multi_download_mode": true
},
"API": {
"use_youtube_API": false,
"youtube_API_key": ""
},
"Themes": {
"default_theme": "default",
"allow_theme_change": true
},
"Subscriptions": {
"allow_subscriptions": true,
"subscriptions_base_path": "subscriptions/",
"subscriptions_check_interval": "300",
"subscriptions_use_youtubedl_archive": true
},
"Advanced": {
"use_default_downloading_agent": true,
"custom_downloading_agent": "",
"allow_advanced_download": true
}
"YoutubeDLMaterial": {
"Host": {
"url": "http://localhost",
"port": "17442"
},
"Encryption": {
"use-encryption": false,
"cert-file-path": "/etc/letsencrypt/live/example.com/fullchain.pem",
"key-file-path": "/etc/letsencrypt/live/example.com/privkey.pem"
},
"Downloader": {
"path-audio": "audio/",
"path-video": "video/",
"custom_args": ""
},
"Extra": {
"title_top": "Youtube Downloader",
"file_manager_enabled": true,
"allow_quality_select": true,
"download_only_mode": false,
"allow_multi_download_mode": true,
"settings_pin_required": false
},
"API": {
"use_youtube_API": false,
"youtube_API_key": ""
},
"Themes": {
"default_theme": "default",
"allow_theme_change": true
},
"Subscriptions": {
"allow_subscriptions": true,
"subscriptions_base_path": "subscriptions/",
"subscriptions_check_interval": "300",
"subscriptions_use_youtubedl_archive": true
},
"Advanced": {
"use_default_downloading_agent": true,
"custom_downloading_agent": "",
"allow_advanced_download": true
}
}
}
Loading…
Cancel
Save