mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-11 07:10:56 +03:00
Added roles and permissions system, as well as the ability to modify users and their roles
Downloads manager now uses device fingerprint as identifier rather than a randomly generated sessionID
This commit is contained in:
@@ -91,7 +91,25 @@ db.defaults(
|
||||
|
||||
users_db.defaults(
|
||||
{
|
||||
users: []
|
||||
users: [],
|
||||
roles: {
|
||||
"admin": {
|
||||
"permissions": [
|
||||
'filemanager',
|
||||
'settings',
|
||||
'subscriptions',
|
||||
'sharing',
|
||||
'advanced_download',
|
||||
'downloads_manager'
|
||||
]
|
||||
}, "user": {
|
||||
"permissions": [
|
||||
'filemanager',
|
||||
'subscriptions',
|
||||
'sharing'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
).write();
|
||||
|
||||
@@ -2737,7 +2755,7 @@ app.post('/api/auth/jwtAuth'
|
||||
);
|
||||
app.post('/api/auth/changePassword', optionalJwt, async (req, res) => {
|
||||
let user_uid = req.user.uid;
|
||||
let password = req.body.password;
|
||||
let password = req.body.new_password;
|
||||
let success = await auth_api.changeUserPassword(user_uid, password);
|
||||
res.send({success: success});
|
||||
});
|
||||
@@ -2746,6 +2764,81 @@ app.post('/api/auth/adminExists', async (req, res) => {
|
||||
res.send({exists: exists});
|
||||
});
|
||||
|
||||
// user management
|
||||
app.post('/api/getUsers', optionalJwt, async (req, res) => {
|
||||
let users = users_db.get('users').value();
|
||||
res.send({users: users});
|
||||
});
|
||||
app.post('/api/getRoles', optionalJwt, async (req, res) => {
|
||||
let roles = users_db.get('roles').value();
|
||||
res.send({roles: roles});
|
||||
});
|
||||
|
||||
app.post('/api/changeUser', optionalJwt, async (req, res) => {
|
||||
let change_obj = req.body.change_object;
|
||||
try {
|
||||
const user_db_obj = users_db.get('users').find({uid: change_obj.uid});
|
||||
if (change_obj.name) {
|
||||
user_db_obj.assign({name: change_obj.name}).write();
|
||||
}
|
||||
if (change_obj.role) {
|
||||
user_db_obj.assign({role: change_obj.role}).write();
|
||||
}
|
||||
res.send({success: true});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
res.send({success: false});
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/deleteUser', optionalJwt, async (req, res) => {
|
||||
let uid = req.body.uid;
|
||||
try {
|
||||
let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
||||
const user_folder = path.join(__dirname, usersFileFolder, uid);
|
||||
const user_db_obj = users_db.get('users').find({uid: uid});
|
||||
if (user_db_obj.value()) {
|
||||
// user exists, let's delete
|
||||
deleteFolderRecursive(user_folder);
|
||||
users_db.get('users').remove({uid: uid}).write();
|
||||
}
|
||||
res.send({success: true});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
res.send({success: false});
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/changeUserPermissions', optionalJwt, async (req, res) => {
|
||||
const user_uid = req.body.user_uid;
|
||||
const permission = req.body.permission;
|
||||
const new_value = req.body.new_value;
|
||||
|
||||
if (!permission || !new_value) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
const success = auth_api.changeUserPermissions(user_uid, permission, new_value);
|
||||
|
||||
res.send({success: success});
|
||||
});
|
||||
|
||||
app.post('/api/changeRolePermissions', optionalJwt, async (req, res) => {
|
||||
const role = req.body.role;
|
||||
const permission = req.body.permission;
|
||||
const new_value = req.body.new_value;
|
||||
|
||||
if (!permission || !new_value) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
const success = auth_api.changeRolePermissions(role, permission, new_value);
|
||||
|
||||
res.send({success: success});
|
||||
});
|
||||
|
||||
app.use(function(req, res, next) {
|
||||
//if the request is not html then move along
|
||||
var accept = req.accepts('html', 'json', 'xml');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const path = require('path');
|
||||
const config_api = require('../config');
|
||||
const consts = require('../consts');
|
||||
var subscriptions_api = require('../subscriptions')
|
||||
const fs = require('fs-extra');
|
||||
var jwt = require('jsonwebtoken');
|
||||
@@ -97,7 +98,9 @@ exports.registerUser = function(req, res) {
|
||||
},
|
||||
subscriptions: [],
|
||||
created: Date.now(),
|
||||
role: userid === 'admin' ? 'admin' : 'user'
|
||||
role: userid === 'admin' ? 'admin' : 'user',
|
||||
permissions: [],
|
||||
permission_overrides: []
|
||||
};
|
||||
// check if user exists
|
||||
if (users_db.get('users').find({uid: userid}).value()) {
|
||||
@@ -200,8 +203,7 @@ exports.authenticateViaPassport = function(req, res, next) {
|
||||
exports.generateJWT = function(req, res, next) {
|
||||
var payload = {
|
||||
exp: Math.floor(Date.now() / 1000) + JWT_EXPIRATION
|
||||
, user: req.user,
|
||||
// , role: role
|
||||
, user: req.user
|
||||
};
|
||||
req.token = jwt.sign(payload, SERVER_SECRET);
|
||||
next();
|
||||
@@ -210,7 +212,9 @@ exports.generateJWT = function(req, res, next) {
|
||||
exports.returnAuthResponse = function(req, res) {
|
||||
res.status(200).json({
|
||||
user: req.user,
|
||||
token: req.token
|
||||
token: req.token,
|
||||
permissions: exports.userPermissions(req.user.uid),
|
||||
available_permissions: consts['AVAILABLE_PERMISSIONS']
|
||||
});
|
||||
}
|
||||
|
||||
@@ -252,6 +256,40 @@ exports.changeUserPassword = async function(user_uid, new_pass) {
|
||||
});
|
||||
}
|
||||
|
||||
// change user permissions
|
||||
exports.changeUserPermissions = function(user_uid, permission, new_value) {
|
||||
try {
|
||||
const user_db_obj = users_db.get('users').find({uid: user_uid});
|
||||
user_db_obj.get('permissions').pull(permission).write();
|
||||
user_db_obj.get('permission_overrides').pull(permission).write();
|
||||
if (new_value === 'yes') {
|
||||
user_db_obj.get('permissions').push(permission).write();
|
||||
user_db_obj.get('permission_overrides').push(permission).write();
|
||||
} else if (new_value === 'no') {
|
||||
user_db_obj.get('permission_overrides').push(permission).write();
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// change role permissions
|
||||
exports.changeRolePermissions = function(role, permission, new_value) {
|
||||
try {
|
||||
const role_db_obj = users_db.get('roles').get(role);
|
||||
role_db_obj.get('permissions').pull(permission).write();
|
||||
if (new_value === 'yes') {
|
||||
role_db_obj.get('permissions').push(permission).write();
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
exports.adminExists = function() {
|
||||
return !!users_db.get('users').find({uid: 'admin'}).value();
|
||||
}
|
||||
@@ -410,6 +448,74 @@ exports.changeSharingMode = function(user_uid, file_uid, type, is_playlist, enab
|
||||
return success;
|
||||
}
|
||||
|
||||
exports.userHasPermission = function(user_uid, permission) {
|
||||
const user_obj = users_db.get('users').find({uid: user_uid}).value();
|
||||
const role = user_obj['role'];
|
||||
if (!role) {
|
||||
// role doesn't exist
|
||||
logger.error('Invalid role ' + role);
|
||||
return false;
|
||||
}
|
||||
const role_permissions = (users_db.get('roles').value())['permissions'];
|
||||
|
||||
const user_has_explicit_permission = user_obj['permissions'].includes(permission);
|
||||
const permission_in_overrides = user_obj['permission_overrides'].includes(permission);
|
||||
|
||||
// check if user has a negative/positive override
|
||||
if (user_has_explicit_permission && permission_in_overrides) {
|
||||
// positive override
|
||||
return true;
|
||||
} else if (!user_has_explicit_permission && permission_in_overrides) {
|
||||
// negative override
|
||||
return false;
|
||||
}
|
||||
|
||||
// no overrides, let's check if the role has the permission
|
||||
if (role_permissions.includes(permission)) {
|
||||
return true;
|
||||
} else {
|
||||
logger.verbose(`User ${user_uid} failed to get permission ${permission}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
exports.userPermissions = function(user_uid) {
|
||||
let user_permissions = [];
|
||||
const user_obj = users_db.get('users').find({uid: user_uid}).value();
|
||||
const role = user_obj['role'];
|
||||
if (!role) {
|
||||
// role doesn't exist
|
||||
logger.error('Invalid role ' + role);
|
||||
return null;
|
||||
}
|
||||
const role_permissions = users_db.get('roles').get(role).get('permissions').value()
|
||||
|
||||
for (let i = 0; i < consts['AVAILABLE_PERMISSIONS'].length; i++) {
|
||||
let permission = consts['AVAILABLE_PERMISSIONS'][i];
|
||||
|
||||
const user_has_explicit_permission = user_obj['permissions'].includes(permission);
|
||||
const permission_in_overrides = user_obj['permission_overrides'].includes(permission);
|
||||
|
||||
// check if user has a negative/positive override
|
||||
if (user_has_explicit_permission && permission_in_overrides) {
|
||||
// positive override
|
||||
user_permissions.push(permission);
|
||||
} else if (!user_has_explicit_permission && permission_in_overrides) {
|
||||
// negative override
|
||||
continue;
|
||||
}
|
||||
|
||||
// no overrides, let's check if the role has the permission
|
||||
if (role_permissions.includes(permission)) {
|
||||
user_permissions.push(permission);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return user_permissions;
|
||||
}
|
||||
|
||||
function getToken(queryParams) {
|
||||
if (queryParams && queryParams.jwt) {
|
||||
var parted = queryParams.jwt.split(' ');
|
||||
|
||||
@@ -142,7 +142,17 @@ let CONFIG_ITEMS = {
|
||||
},
|
||||
};
|
||||
|
||||
AVAILABLE_PERMISSIONS = [
|
||||
'filemanager',
|
||||
'settings',
|
||||
'subscriptions',
|
||||
'sharing',
|
||||
'advanced_download',
|
||||
'downloads_manager'
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
CONFIG_ITEMS: CONFIG_ITEMS,
|
||||
AVAILABLE_PERMISSIONS: AVAILABLE_PERMISSIONS,
|
||||
CURRENT_VERSION: 'v3.6'
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
<span i18n="Dark mode toggle label">Dark</span>
|
||||
<mat-slide-toggle class="theme-slide-toggle" [checked]="postsService.theme.key === 'dark'"></mat-slide-toggle>
|
||||
</button>
|
||||
<button (click)="openSettingsDialog()" mat-menu-item>
|
||||
<button *ngIf="!postsService.isLoggedIn || postsService.permissions.includes('settings')" (click)="openSettingsDialog()" mat-menu-item>
|
||||
<mat-icon>settings</mat-icon>
|
||||
<span i18n="Settings menu label">Settings</span>
|
||||
</button>
|
||||
@@ -41,8 +41,8 @@
|
||||
<mat-sidenav #sidenav>
|
||||
<mat-nav-list>
|
||||
<a mat-list-item (click)="sidenav.close()" routerLink='/home'><ng-container i18n="Navigation menu Home Page title">Home</ng-container></a>
|
||||
<a *ngIf="allowSubscriptions" mat-list-item (click)="sidenav.close()" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
|
||||
<a mat-list-item (click)="sidenav.close()" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a>
|
||||
<a *ngIf="allowSubscriptions && (!postsService.isLoggedIn || postsService.permissions.includes('subscriptions'))" mat-list-item (click)="sidenav.close()" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
|
||||
<a *ngIf="!postsService.isLoggedIn || postsService.permissions.includes('downloads_manager')" mat-list-item (click)="sidenav.close()" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a>
|
||||
</mat-nav-list>
|
||||
</mat-sidenav>
|
||||
<mat-sidenav-content [style.background]="postsService.theme ? postsService.theme.background_color : null">
|
||||
|
||||
@@ -25,6 +25,9 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import {MatPaginatorModule} from '@angular/material/paginator';
|
||||
import {MatSortModule} from '@angular/material/sort';
|
||||
import {MatTableModule} from '@angular/material/table';
|
||||
import {DragDropModule} from '@angular/cdk/drag-drop';
|
||||
import {ClipboardModule} from '@angular/cdk/clipboard';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
@@ -61,6 +64,10 @@ 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';
|
||||
import { ModifyUsersComponent } from './components/modify-users/modify-users.component';
|
||||
import { AddUserDialogComponent } from './dialogs/add-user-dialog/add-user-dialog.component';
|
||||
import { ManageUserComponent } from './components/manage-user/manage-user.component';
|
||||
import { ManageRoleComponent } from './components/manage-role/manage-role.component';
|
||||
registerLocaleData(es, 'es');
|
||||
|
||||
export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) {
|
||||
@@ -93,7 +100,11 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
||||
LoginComponent,
|
||||
DownloadsComponent,
|
||||
UserProfileDialogComponent,
|
||||
SetDefaultAdminDialogComponent
|
||||
SetDefaultAdminDialogComponent,
|
||||
ModifyUsersComponent,
|
||||
AddUserDialogComponent,
|
||||
ManageUserComponent,
|
||||
ManageRoleComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@@ -127,6 +138,9 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
||||
MatAutocompleteModule,
|
||||
MatTabsModule,
|
||||
MatTooltipModule,
|
||||
MatPaginatorModule,
|
||||
MatSortModule,
|
||||
MatTableModule,
|
||||
DragDropModule,
|
||||
ClipboardModule,
|
||||
VgCoreModule,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<input [(ngModel)]="loginPasswordInput" type="password" matInput placeholder="Password">
|
||||
<input [(ngModel)]="loginPasswordInput" (keyup.enter)="login()" type="password" matInput placeholder="Password">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px; margin-top: 10px;">
|
||||
|
||||
@@ -40,6 +40,9 @@ export class LoginComponent implements OnInit {
|
||||
}
|
||||
|
||||
login() {
|
||||
if (this.loginPasswordInput === '') {
|
||||
return;
|
||||
}
|
||||
this.loggingIn = true;
|
||||
this.postsService.login(this.loginUsernameInput, this.loginPasswordInput).subscribe(res => {
|
||||
this.loggingIn = false;
|
||||
|
||||
19
src/app/components/manage-role/manage-role.component.html
Normal file
19
src/app/components/manage-role/manage-role.component.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<h4 *ngIf="role" mat-dialog-title><ng-container i18n="Manage role dialog title">Manage role</ng-container> - {{role.name}}</h4>
|
||||
|
||||
<mat-dialog-content *ngIf="role">
|
||||
<mat-list>
|
||||
<mat-list-item role="listitem" *ngFor="let permission of available_permissions">
|
||||
<h3 matLine>{{permissionToLabel[permission] ? permissionToLabel[permission] : permission}}</h3>
|
||||
<span matLine>
|
||||
<mat-radio-group [disabled]="permission === 'settings' && role.name === 'admin'" (change)="changeRolePermissions($event, permission, permissions[permission])" [(ngModel)]="permissions[permission]" [attr.aria-label]="'Give role permission for ' + permission">
|
||||
<mat-radio-button value="yes"><ng-container i18n="Yes">Yes</ng-container></mat-radio-button>
|
||||
<mat-radio-button value="no"><ng-container i18n="No">No</ng-container></mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<button mat-button mat-dialog-close><ng-container i18n="Close">Close</ng-container></button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,4 @@
|
||||
.mat-radio-button {
|
||||
margin-right: 10px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
25
src/app/components/manage-role/manage-role.component.spec.ts
Normal file
25
src/app/components/manage-role/manage-role.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ManageRoleComponent } from './manage-role.component';
|
||||
|
||||
describe('ManageRoleComponent', () => {
|
||||
let component: ManageRoleComponent;
|
||||
let fixture: ComponentFixture<ManageRoleComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ManageRoleComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ManageRoleComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
61
src/app/components/manage-role/manage-role.component.ts
Normal file
61
src/app/components/manage-role/manage-role.component.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Component, OnInit, Inject } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-manage-role',
|
||||
templateUrl: './manage-role.component.html',
|
||||
styleUrls: ['./manage-role.component.scss']
|
||||
})
|
||||
export class ManageRoleComponent implements OnInit {
|
||||
|
||||
role = null;
|
||||
available_permissions = null;
|
||||
permissions = null;
|
||||
|
||||
permissionToLabel = {
|
||||
'filemanager': 'File manager',
|
||||
'settings': 'Settings access',
|
||||
'subscriptions': 'Subscriptions',
|
||||
'sharing': 'Share files',
|
||||
'advanced_download': 'Use advanced download mode',
|
||||
'downloads_manager': 'Use downloads manager'
|
||||
}
|
||||
|
||||
constructor(public postsService: PostsService, private dialogRef: MatDialogRef<ManageRoleComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any) {
|
||||
if (this.data) {
|
||||
this.role = this.data.role;
|
||||
this.available_permissions = this.postsService.available_permissions;
|
||||
this.parsePermissions();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
parsePermissions() {
|
||||
this.permissions = {};
|
||||
for (let i = 0; i < this.available_permissions.length; i++) {
|
||||
const permission = this.available_permissions[i];
|
||||
if (this.role.permissions.includes(permission)) {
|
||||
this.permissions[permission] = 'yes';
|
||||
} else {
|
||||
this.permissions[permission] = 'no';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeRolePermissions(change, permission) {
|
||||
this.postsService.setRolePermission(this.role.name, permission, change.value).subscribe(res => {
|
||||
if (res['success']) {
|
||||
|
||||
} else {
|
||||
this.permissions[permission] = this.permissions[permission] === 'yes' ? 'no' : 'yes';
|
||||
}
|
||||
}, err => {
|
||||
this.permissions[permission] = this.permissions[permission] === 'yes' ? 'no' : 'yes';
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
31
src/app/components/manage-user/manage-user.component.html
Normal file
31
src/app/components/manage-user/manage-user.component.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<h4 *ngIf="user" mat-dialog-title><ng-container i18n="Manage user dialog title">Manage user</ng-container> - {{user.name}}</h4>
|
||||
|
||||
<mat-dialog-content *ngIf="user">
|
||||
<p><ng-container i18n="User UID">User UID:</ng-container> {{user.uid}}</p>
|
||||
|
||||
<div>
|
||||
<mat-form-field style="margin-right: 15px;">
|
||||
<input matInput [(ngModel)]="newPasswordInput" type="password" placeholder="New password" i18n-placeholder="New password placeholder">
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="accent" (click)="setNewPassword()" [disabled]="newPasswordInput.length === 0"><ng-container i18n="Set new password">Set new password</ng-container></button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mat-list>
|
||||
<mat-list-item role="listitem" *ngFor="let permission of available_permissions">
|
||||
<h3 matLine>{{permissionToLabel[permission] ? permissionToLabel[permission] : permission}}</h3>
|
||||
<span matLine>
|
||||
<mat-radio-group [disabled]="permission === 'settings' && postsService.user.uid === user.uid" (change)="changeUserPermissions($event, permission)" [(ngModel)]="permissions[permission]" [attr.aria-label]="'Give user permission for ' + permission">
|
||||
<mat-radio-button value="default"><ng-container i18n="Use default">Use default</ng-container></mat-radio-button>
|
||||
<mat-radio-button value="yes"><ng-container i18n="Yes">Yes</ng-container></mat-radio-button>
|
||||
<mat-radio-button value="no"><ng-container i18n="No">No</ng-container></mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<button mat-button mat-dialog-close><ng-container i18n="Close">Close</ng-container></button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,4 @@
|
||||
.mat-radio-button {
|
||||
margin-right: 10px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
25
src/app/components/manage-user/manage-user.component.spec.ts
Normal file
25
src/app/components/manage-user/manage-user.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ManageUserComponent } from './manage-user.component';
|
||||
|
||||
describe('ManageUserComponent', () => {
|
||||
let component: ManageUserComponent;
|
||||
let fixture: ComponentFixture<ManageUserComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ManageUserComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ManageUserComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
69
src/app/components/manage-user/manage-user.component.ts
Normal file
69
src/app/components/manage-user/manage-user.component.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Component, OnInit, Inject } from '@angular/core';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-manage-user',
|
||||
templateUrl: './manage-user.component.html',
|
||||
styleUrls: ['./manage-user.component.scss']
|
||||
})
|
||||
export class ManageUserComponent implements OnInit {
|
||||
|
||||
user = null;
|
||||
newPasswordInput = '';
|
||||
available_permissions = null;
|
||||
permissions = null;
|
||||
|
||||
permissionToLabel = {
|
||||
'filemanager': 'File manager',
|
||||
'settings': 'Settings access',
|
||||
'subscriptions': 'Subscriptions',
|
||||
'sharing': 'Share files',
|
||||
'advanced_download': 'Use advanced download mode',
|
||||
'downloads_manager': 'Use downloads manager'
|
||||
}
|
||||
|
||||
settingNewPassword = false;
|
||||
|
||||
constructor(public postsService: PostsService, @Inject(MAT_DIALOG_DATA) public data: any) {
|
||||
if (this.data) {
|
||||
this.user = this.data.user;
|
||||
this.available_permissions = this.postsService.available_permissions;
|
||||
this.parsePermissions();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
parsePermissions() {
|
||||
this.permissions = {};
|
||||
for (let i = 0; i < this.available_permissions.length; i++) {
|
||||
const permission = this.available_permissions[i];
|
||||
if (this.user.permission_overrides.includes(permission)) {
|
||||
if (this.user.permissions.includes(permission)) {
|
||||
this.permissions[permission] = 'yes';
|
||||
} else {
|
||||
this.permissions[permission] = 'no';
|
||||
}
|
||||
} else {
|
||||
this.permissions[permission] = 'default';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeUserPermissions(change, permission) {
|
||||
this.postsService.setUserPermission(this.user.uid, permission, change.value).subscribe(res => {
|
||||
// console.log(res);
|
||||
});
|
||||
}
|
||||
|
||||
setNewPassword() {
|
||||
this.settingNewPassword = true;
|
||||
this.postsService.changeUserPassword(this.user.uid, this.newPasswordInput).subscribe(res => {
|
||||
this.newPasswordInput = '';
|
||||
this.settingNewPassword = false;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
107
src/app/components/modify-users/modify-users.component.html
Normal file
107
src/app/components/modify-users/modify-users.component.html
Normal file
@@ -0,0 +1,107 @@
|
||||
<div *ngIf="dataSource; else loading">
|
||||
<div style="padding: 15px">
|
||||
<div class="row">
|
||||
<div class="table table-responsive p-5">
|
||||
<div class="example-header">
|
||||
<mat-form-field>
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Search">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="example-container mat-elevation-z8">
|
||||
|
||||
<mat-table #table [dataSource]="dataSource" matSort>
|
||||
|
||||
<!-- Name Column -->
|
||||
<ng-container matColumnDef="name">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header><ng-container i18n="Username users table header"> User name </ng-container></mat-header-cell>
|
||||
|
||||
<mat-cell *matCellDef="let row">
|
||||
<span *ngIf="editObject && editObject.uid === row.uid; else noteditingname">
|
||||
<span style="width: 80%;">
|
||||
<mat-form-field>
|
||||
<input matInput [(ngModel)]="constructedObject['name']" type="text" style="font-size: 12px">
|
||||
</mat-form-field>
|
||||
</span>
|
||||
</span>
|
||||
<ng-template #noteditingname>
|
||||
{{row.name}}
|
||||
</ng-template>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Email Column -->
|
||||
<ng-container matColumnDef="role">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header><ng-container i18n="Role users table header"> Role </ng-container></mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
<span *ngIf="editObject && editObject.uid === row.uid; else noteditingemail">
|
||||
<span style="width: 80%;">
|
||||
<mat-form-field>
|
||||
<mat-select [(ngModel)]="constructedObject['role']">
|
||||
<mat-option value="admin">Admin</mat-option>
|
||||
<mat-option value="user">User</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</span>
|
||||
</span>
|
||||
<ng-template #noteditingemail>
|
||||
{{row.role}}
|
||||
</ng-template>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Actions Column -->
|
||||
<ng-container matColumnDef="actions">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header><ng-container i18n="Actions users table header"> Actions </ng-container></mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
<span *ngIf="editObject && editObject.uid === row.uid; else notediting">
|
||||
<button mat-icon-button color="primary" (click)="finishEditing(row.uid)" matTooltip="Finish editing user">
|
||||
<mat-icon>done</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button (click)="disableEditMode()" matTooltip="Cancel editing user">
|
||||
<mat-icon>cancel</mat-icon>
|
||||
</button>
|
||||
</span>
|
||||
<ng-template #notediting>
|
||||
<button mat-icon-button (click)="enableEditMode(row.uid)" matTooltip="Edit user">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
</ng-template>
|
||||
<button (click)="manageUser(row.uid)" mat-icon-button [disabled]="editObject && editObject.uid === row.uid" matTooltip="Manage user">
|
||||
<mat-icon>settings</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button [disabled]="editObject && editObject.uid === row.uid || row.uid === postsService.user.uid" (click)="removeUser(row.uid)" matTooltip="Delete user">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns;">
|
||||
</mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator #paginator [length]="length"
|
||||
[pageSize]="pageSize"
|
||||
[pageSizeOptions]="pageSizeOptions">
|
||||
</mat-paginator>
|
||||
|
||||
<button color="primary" [disabled]="!this.users" mat-raised-button (click)="openAddUserDialog()" style="float: left; top: -45px; left: 15px">
|
||||
<ng-container i18n="Add users button">Add Users</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button color="primary" [matMenuTriggerFor]="edit_roles_menu" class="edit-role" mat-raised-button>Edit Role</button>
|
||||
<mat-menu #edit_roles_menu="matMenu">
|
||||
<button (click)="openModifyRole(role)" mat-menu-item *ngFor="let role of roles">{{role.name}}</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="position: absolute" class="centered">
|
||||
<ng-template #loading>
|
||||
<mat-spinner></mat-spinner>
|
||||
</ng-template>
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
.edit-role {
|
||||
position: relative;
|
||||
top: -80px;
|
||||
left: 35px;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ModifyUsersComponent } from './modify-users.component';
|
||||
|
||||
describe('ModifyUsersComponent', () => {
|
||||
let component: ModifyUsersComponent;
|
||||
let fixture: ComponentFixture<ModifyUsersComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ModifyUsersComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ModifyUsersComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
219
src/app/components/modify-users/modify-users.component.ts
Normal file
219
src/app/components/modify-users/modify-users.component.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
import { Component, OnInit, Input, ViewChild, AfterViewInit } from '@angular/core';
|
||||
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||
import { AddUserDialogComponent } from 'app/dialogs/add-user-dialog/add-user-dialog.component';
|
||||
import { ManageUserComponent } from '../manage-user/manage-user.component';
|
||||
import { ManageRoleComponent } from '../manage-role/manage-role.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-modify-users',
|
||||
templateUrl: './modify-users.component.html',
|
||||
styleUrls: ['./modify-users.component.scss']
|
||||
})
|
||||
export class ModifyUsersComponent implements OnInit, AfterViewInit {
|
||||
|
||||
displayedColumns = ['name', 'role', 'actions'];
|
||||
dataSource = new MatTableDataSource();
|
||||
|
||||
deleteDialogContentSubstring = 'Are you sure you want delete user ';
|
||||
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
|
||||
// MatPaginator Inputs
|
||||
length = 100;
|
||||
@Input() pageSize = 5;
|
||||
pageSizeOptions: number[] = [5, 10, 25, 100];
|
||||
|
||||
// MatPaginator Output
|
||||
pageEvent: PageEvent;
|
||||
users: any;
|
||||
editObject = null;
|
||||
constructedObject = {};
|
||||
roles = null;
|
||||
|
||||
|
||||
constructor(public postsService: PostsService, public snackBar: MatSnackBar, public dialog: MatDialog,
|
||||
private dialogRef: MatDialogRef<ModifyUsersComponent>) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.getArray();
|
||||
this.getRoles();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the paginator and sort after the view init since this component will
|
||||
* be able to query its view for the initialized paginator and sort.
|
||||
*/
|
||||
afterGetData() {
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
|
||||
setPageSizeOptions(setPageSizeOptionsInput: string) {
|
||||
this.pageSizeOptions = setPageSizeOptionsInput.split(',').map(str => +str);
|
||||
}
|
||||
|
||||
applyFilter(filterValue: string) {
|
||||
filterValue = filterValue.trim(); // Remove whitespace
|
||||
filterValue = filterValue.toLowerCase(); // Datasource defaults to lowercase matches
|
||||
this.dataSource.filter = filterValue;
|
||||
}
|
||||
|
||||
private getArray() {
|
||||
this.postsService.getUsers().subscribe(res => {
|
||||
this.users = res['users'];
|
||||
this.createAndSortData();
|
||||
this.afterGetData();
|
||||
});
|
||||
}
|
||||
|
||||
getRoles() {
|
||||
this.postsService.getRoles().subscribe(res => {
|
||||
this.roles = [];
|
||||
const roles = res['roles'];
|
||||
const role_names = Object.keys(roles);
|
||||
for (let i = 0; i < role_names.length; i++) {
|
||||
const role_name = role_names[i];
|
||||
this.roles.push({
|
||||
name: role_name,
|
||||
permissions: roles[role_name]['permissions']
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openAddUserDialog() {
|
||||
const dialogRef = this.dialog.open(AddUserDialogComponent);
|
||||
dialogRef.afterClosed().subscribe(user => {
|
||||
if (user && !user.error) {
|
||||
this.openSnackBar('Successfully added user ' + user.name);
|
||||
this.getArray();
|
||||
} else if (user && user.error) {
|
||||
this.openSnackBar('Failed to add user');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
finishEditing(user_uid) {
|
||||
let has_finished = false;
|
||||
if (this.constructedObject && this.constructedObject['name'] && this.constructedObject['role']) {
|
||||
if (!isEmptyOrSpaces(this.constructedObject['name']) && !isEmptyOrSpaces(this.constructedObject['role'])) {
|
||||
has_finished = true;
|
||||
const index_of_object = this.indexOfUser(user_uid);
|
||||
this.users[index_of_object] = this.constructedObject;
|
||||
this.constructedObject = {};
|
||||
this.editObject = null;
|
||||
this.setUser(this.users[index_of_object]);
|
||||
this.createAndSortData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enableEditMode(user_uid) {
|
||||
if (this.uidInUserList(user_uid) && this.indexOfUser(user_uid) > -1) {
|
||||
const users_index = this.indexOfUser(user_uid);
|
||||
this.editObject = this.users[users_index];
|
||||
this.constructedObject['name'] = this.users[users_index].name;
|
||||
this.constructedObject['uid'] = this.users[users_index].uid;
|
||||
this.constructedObject['role'] = this.users[users_index].role;
|
||||
}
|
||||
}
|
||||
|
||||
disableEditMode() {
|
||||
this.editObject = null;
|
||||
}
|
||||
|
||||
// checks if user is in users array by name
|
||||
uidInUserList(user_uid) {
|
||||
for (let i = 0; i < this.users.length; i++) {
|
||||
if (this.users[i].uid === user_uid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// gets index of user in users array by name
|
||||
indexOfUser(user_uid) {
|
||||
for (let i = 0; i < this.users.length; i++) {
|
||||
if (this.users[i].uid === user_uid) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
setUser(change_obj) {
|
||||
this.postsService.changeUser(change_obj).subscribe(res => {
|
||||
this.getArray();
|
||||
});
|
||||
}
|
||||
|
||||
manageUser(user_uid) {
|
||||
const index_of_object = this.indexOfUser(user_uid);
|
||||
const user_obj = this.users[index_of_object];
|
||||
this.dialog.open(ManageUserComponent, {
|
||||
data: {
|
||||
user: user_obj
|
||||
},
|
||||
width: '65vw'
|
||||
});
|
||||
}
|
||||
|
||||
removeUser(user_uid) {
|
||||
this.postsService.deleteUser(user_uid).subscribe(res => {
|
||||
this.getArray();
|
||||
}, err => {
|
||||
this.getArray();
|
||||
});
|
||||
}
|
||||
|
||||
createAndSortData() {
|
||||
// Sorts the data by last finished
|
||||
this.users.sort((a, b) => b.name > a.name);
|
||||
|
||||
const filteredData = [];
|
||||
for (let i = 0; i < this.users.length; i++) {
|
||||
filteredData.push(JSON.parse(JSON.stringify(this.users[i])));
|
||||
}
|
||||
|
||||
// Assign the data to the data source for the table to render
|
||||
this.dataSource.data = filteredData;
|
||||
}
|
||||
|
||||
openModifyRole(role) {
|
||||
const dialogRef = this.dialog.open(ManageRoleComponent, {
|
||||
data: {
|
||||
role: role
|
||||
}
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(success => {
|
||||
this.getRoles();
|
||||
});
|
||||
}
|
||||
|
||||
closeDialog() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
public openSnackBar(message: string, action: string = '') {
|
||||
this.snackBar.open(message, action, {
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function isEmptyOrSpaces(str){
|
||||
return str === null || str.match(/^ *$/) !== null;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<h4 mat-dialog-title i18n="Register user dialog title">Register a user</h4>
|
||||
|
||||
<mat-dialog-content>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="User name" [(ngModel)]="usernameInput">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Password" [(ngModel)]="passwordInput" type="password">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<button color="accent" (click)="createUser()" mat-raised-button><ng-container i18n="Register user button">Register</ng-container></button>
|
||||
<button mat-dialog-close mat-button><ng-container i18n="Close button">Close</ng-container></button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AddUserDialogComponent } from './add-user-dialog.component';
|
||||
|
||||
describe('AddUserDialogComponent', () => {
|
||||
let component: AddUserDialogComponent;
|
||||
let fixture: ComponentFixture<AddUserDialogComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AddUserDialogComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AddUserDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
32
src/app/dialogs/add-user-dialog/add-user-dialog.component.ts
Normal file
32
src/app/dialogs/add-user-dialog/add-user-dialog.component.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-add-user-dialog',
|
||||
templateUrl: './add-user-dialog.component.html',
|
||||
styleUrls: ['./add-user-dialog.component.scss']
|
||||
})
|
||||
export class AddUserDialogComponent implements OnInit {
|
||||
|
||||
usernameInput = '';
|
||||
passwordInput = '';
|
||||
|
||||
constructor(private postsService: PostsService, public dialogRef: MatDialogRef<AddUserDialogComponent>) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
createUser() {
|
||||
this.postsService.register(this.usernameInput, this.passwordInput).subscribe(res => {
|
||||
if (res['user']) {
|
||||
this.dialogRef.close(res['user']);
|
||||
} else {
|
||||
this.dialogRef.close({error: 'Unknown error'});
|
||||
}
|
||||
}, err => {
|
||||
this.dialogRef.close({error: err});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<div style="position: relative">
|
||||
<div>
|
||||
<mat-form-field color="accent">
|
||||
<input type="password" (keyup.enter)="create()" matInput [(ngModel)]="input" placeholder="Password" placeholder-i18n="Password">
|
||||
<input type="password" (keyup.enter)="create()" matInput [(ngModel)]="input" placeholder="Password" i18n-placeholder="Password">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
<ng-template #nofile>
|
||||
|
||||
</ng-template>
|
||||
<div style="margin: 20px" *ngIf="fileManagerEnabled">
|
||||
<div style="margin: 20px" *ngIf="fileManagerEnabled && (!postsService.isLoggedIn || postsService.permissions.includes('filemanager'))">
|
||||
<mat-accordion>
|
||||
<mat-expansion-panel (opened)="accordionOpened('audio')" (closed)="accordionClosed('audio')" (mouseleave)="accordionLeft('audio')" (mouseenter)="accordionEntered('audio')" class="big">
|
||||
<mat-expansion-panel-header>
|
||||
|
||||
@@ -3,7 +3,6 @@ import {PostsService} from '../posts.services';
|
||||
import {FileCardComponent} from '../file-card/file-card.component';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import {FormControl, Validators} from '@angular/forms';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { saveAs } from 'file-saver';
|
||||
@@ -215,7 +214,7 @@ export class MainComponent implements OnInit {
|
||||
|
||||
simulatedOutput = '';
|
||||
|
||||
constructor(private postsService: PostsService, private youtubeSearch: YoutubeSearchService, public snackBar: MatSnackBar,
|
||||
constructor(public postsService: PostsService, private youtubeSearch: YoutubeSearchService, public snackBar: MatSnackBar,
|
||||
private router: Router, public dialog: MatDialog, private platform: Platform, private route: ActivatedRoute) {
|
||||
this.audioOnly = false;
|
||||
}
|
||||
@@ -242,7 +241,8 @@ export class MainComponent implements OnInit {
|
||||
this.postsService.config['API']['youtube_API_key'];
|
||||
this.youtubeAPIKey = this.youtubeSearchEnabled ? this.postsService.config['API']['youtube_API_key'] : null;
|
||||
this.allowQualitySelect = this.postsService.config['Extra']['allow_quality_select'];
|
||||
this.allowAdvancedDownload = this.postsService.config['Advanced']['allow_advanced_download'];
|
||||
this.allowAdvancedDownload = this.postsService.config['Advanced']['allow_advanced_download']
|
||||
&& (!this.postsService.isLoggedIn || this.postsService.permissions.includes('advanced_download'));
|
||||
this.useDefaultDownloadingAgent = this.postsService.config['Advanced']['use_default_downloading_agent'];
|
||||
this.customDownloadingAgent = this.postsService.config['Advanced']['custom_downloading_agent'];
|
||||
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
<div *ngIf="playlist.length > 1">
|
||||
<button class="save-button" color="primary" (click)="downloadContent()" [disabled]="downloading" mat-fab><mat-icon class="save-icon">save</mat-icon><mat-spinner *ngIf="downloading" class="spinner" [diameter]="50"></mat-spinner></button>
|
||||
<button *ngIf="!id" color="accent" class="favorite-button" color="primary" (click)="namePlaylistDialog()" mat-fab><mat-icon class="save-icon">favorite</mat-icon></button>
|
||||
<button *ngIf="!is_shared && id" class="share-button" color="primary" (click)="openShareDialog()" mat-fab><mat-icon class="save-icon">share</mat-icon></button>
|
||||
<button *ngIf="!is_shared && id && (!postsService.isLoggedIn || postsService.permissions.includes('sharing'))" class="share-button" color="primary" (click)="openShareDialog()" mat-fab><mat-icon class="save-icon">share</mat-icon></button>
|
||||
</div>
|
||||
<div *ngIf="playlist.length === 1">
|
||||
<button class="save-button" color="primary" (click)="downloadFile()" [disabled]="downloading" mat-fab><mat-icon class="save-icon">save</mat-icon><mat-spinner *ngIf="downloading" class="spinner" [diameter]="50"></mat-spinner></button>
|
||||
<button *ngIf="!is_shared && uid && uid !== 'false' && type !== 'subscription'" class="share-button" color="primary" (click)="openShareDialog()" mat-fab><mat-icon class="save-icon">share</mat-icon></button>
|
||||
<button *ngIf="!is_shared && uid && uid !== 'false' && type !== 'subscription' && (!postsService.isLoggedIn || postsService.permissions.includes('sharing'))" class="share-button" color="primary" (click)="openShareDialog()" mat-fab><mat-icon class="save-icon">share</mat-icon></button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,7 +90,7 @@ export class PlayerComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private postsService: PostsService, private route: ActivatedRoute, private dialog: MatDialog, private router: Router,
|
||||
constructor(public postsService: PostsService, private route: ActivatedRoute, private dialog: MatDialog, private router: Router,
|
||||
public snackBar: MatSnackBar) {
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { DOCUMENT } from '@angular/common';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import * as Fingerprint2 from 'fingerprintjs2';
|
||||
|
||||
@Injectable()
|
||||
export class PostsService implements CanActivate {
|
||||
@@ -30,9 +31,13 @@ export class PostsService implements CanActivate {
|
||||
|
||||
debugMode = false;
|
||||
|
||||
// must be reset after logout
|
||||
isLoggedIn = false;
|
||||
token = null;
|
||||
user = null;
|
||||
permissions = null;
|
||||
|
||||
available_permissions = null;
|
||||
|
||||
reload_config = new BehaviorSubject<boolean>(false);
|
||||
config_reloaded = new BehaviorSubject<boolean>(false);
|
||||
@@ -48,13 +53,13 @@ export class PostsService implements CanActivate {
|
||||
// this.startPath = window.location.href + '/api/';
|
||||
// this.startPathSSL = window.location.href + '/api/';
|
||||
this.path = this.document.location.origin + '/api/';
|
||||
this.session_id = uuid();
|
||||
|
||||
if (isDevMode()) {
|
||||
this.debugMode = true;
|
||||
this.path = 'http://localhost:17442/api/';
|
||||
}
|
||||
|
||||
this.http_params = `apiKey=${this.auth_token}&sessionID=${this.session_id}`
|
||||
this.http_params = `apiKey=${this.auth_token}`
|
||||
|
||||
this.httpOptions = {
|
||||
params: new HttpParams({
|
||||
@@ -62,6 +67,12 @@ export class PostsService implements CanActivate {
|
||||
}),
|
||||
};
|
||||
|
||||
Fingerprint2.get(components => {
|
||||
// set identity as user id doesn't necessarily exist
|
||||
this.session_id = Fingerprint2.x64hash128(components.map(function (pair) { return pair.value; }).join(), 31);
|
||||
this.httpOptions.params = this.httpOptions.params.set('sessionID', this.session_id);
|
||||
});
|
||||
|
||||
// get config
|
||||
this.loadNavItems().subscribe(res => {
|
||||
const result = !this.debugMode ? res['config_file'] : res;
|
||||
@@ -71,11 +82,8 @@ export class PostsService implements CanActivate {
|
||||
// login stuff
|
||||
if (localStorage.getItem('jwt_token')) {
|
||||
this.token = localStorage.getItem('jwt_token');
|
||||
this.httpOptions = {
|
||||
params: new HttpParams({
|
||||
fromString: `apiKey=${this.auth_token}&sessionID=${this.session_id}&jwt=${this.token}`
|
||||
}),
|
||||
};
|
||||
this.httpOptions.params = this.httpOptions.params.set('jwt', this.token);
|
||||
|
||||
this.jwtAuth();
|
||||
} else {
|
||||
this.sendToLogin();
|
||||
@@ -321,18 +329,16 @@ export class PostsService implements CanActivate {
|
||||
return this.http.get('https://api.github.com/repos/tzahi12345/youtubedl-material/releases');
|
||||
}
|
||||
|
||||
afterLogin(user, token) {
|
||||
afterLogin(user, token, permissions, available_permissions) {
|
||||
this.isLoggedIn = true;
|
||||
this.user = user;
|
||||
this.permissions = permissions;
|
||||
this.available_permissions = available_permissions;
|
||||
this.token = token;
|
||||
|
||||
localStorage.setItem('jwt_token', this.token);
|
||||
|
||||
this.httpOptions = {
|
||||
params: new HttpParams({
|
||||
fromString: `apiKey=${this.auth_token}&sessionID=${this.session_id}&jwt=${this.token}`
|
||||
}),
|
||||
};
|
||||
this.httpOptions.params = this.httpOptions.params.set('jwt', this.token);
|
||||
|
||||
// needed to re-initialize parts of app after login
|
||||
this.config_reloaded.next(true);
|
||||
@@ -347,7 +353,7 @@ export class PostsService implements CanActivate {
|
||||
const call = this.http.post(this.path + 'auth/login', {userid: username, password: password}, this.httpOptions);
|
||||
call.subscribe(res => {
|
||||
if (res['token']) {
|
||||
this.afterLogin(res['user'], res['token']);
|
||||
this.afterLogin(res['user'], res['token'], res['permissions'], res['available_permissions']);
|
||||
}
|
||||
});
|
||||
return call;
|
||||
@@ -358,7 +364,7 @@ export class PostsService implements CanActivate {
|
||||
const call = this.http.post(this.path + 'auth/jwtAuth', {}, this.httpOptions);
|
||||
call.subscribe(res => {
|
||||
if (res['token']) {
|
||||
this.afterLogin(res['user'], res['token']);
|
||||
this.afterLogin(res['user'], res['token'], res['permissions'], res['available_permissions']);
|
||||
this.setInitialized();
|
||||
}
|
||||
}, err => {
|
||||
@@ -371,6 +377,7 @@ export class PostsService implements CanActivate {
|
||||
|
||||
logout() {
|
||||
this.user = null;
|
||||
this.permissions = null;
|
||||
this.isLoggedIn = false;
|
||||
localStorage.setItem('jwt_token', null);
|
||||
if (this.router.url !== '/login') {
|
||||
@@ -430,7 +437,9 @@ export class PostsService implements CanActivate {
|
||||
}
|
||||
|
||||
checkAdminCreationStatus() {
|
||||
console.log('checking c stat');
|
||||
if (!this.config['Advanced']['multi_user_mode']) {
|
||||
return;
|
||||
}
|
||||
this.adminExists().subscribe(res => {
|
||||
if (!res['exists']) {
|
||||
// must create admin account
|
||||
@@ -439,6 +448,36 @@ export class PostsService implements CanActivate {
|
||||
});
|
||||
}
|
||||
|
||||
changeUser(change_obj) {
|
||||
return this.http.post(this.path + 'changeUser', {change_object: change_obj}, this.httpOptions);
|
||||
}
|
||||
|
||||
deleteUser(uid) {
|
||||
return this.http.post(this.path + 'deleteUser', {uid: uid}, this.httpOptions);
|
||||
}
|
||||
|
||||
changeUserPassword(user_uid, new_password) {
|
||||
return this.http.post(this.path + 'auth/changePassword', {user_uid: user_uid, new_password: new_password}, this.httpOptions);
|
||||
}
|
||||
|
||||
getUsers() {
|
||||
return this.http.post(this.path + 'getUsers', {}, this.httpOptions);
|
||||
}
|
||||
|
||||
getRoles() {
|
||||
return this.http.post(this.path + 'getRoles', {}, this.httpOptions);
|
||||
}
|
||||
|
||||
setUserPermission(user_uid, permission, new_value) {
|
||||
return this.http.post(this.path + 'changeUserPermissions', {user_uid: user_uid, permission: permission, new_value: new_value},
|
||||
this.httpOptions);
|
||||
}
|
||||
|
||||
setRolePermission(role_name, permission, new_value) {
|
||||
return this.http.post(this.path + 'changeRolePermissions', {role: role_name, permission: permission, new_value: new_value},
|
||||
this.httpOptions);
|
||||
}
|
||||
|
||||
public openSnackBar(message: string, action: string = '') {
|
||||
this.snackBar.open(message, action, {
|
||||
duration: 2000,
|
||||
|
||||
@@ -266,6 +266,9 @@
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-tab>
|
||||
<mat-tab *ngIf="postsService.config && postsService.config.Advanced.multi_user_mode" label="Users" i18n-label="Users settings label">
|
||||
<app-modify-users></app-modify-users>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</mat-dialog-content>
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ export class SettingsComponent implements OnInit {
|
||||
this._settingsSame = val;
|
||||
}
|
||||
|
||||
constructor(private postsService: PostsService, private snackBar: MatSnackBar, private sanitizer: DomSanitizer,
|
||||
constructor(public postsService: PostsService, private snackBar: MatSnackBar, private sanitizer: DomSanitizer,
|
||||
private dialog: MatDialog) { }
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
Reference in New Issue
Block a user