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
pull/67/head
Tzahi12345 5 years ago
parent e7b841c056
commit b685b955df

@ -91,7 +91,25 @@ db.defaults(
users_db.defaults( users_db.defaults(
{ {
users: [] users: [],
roles: {
"admin": {
"permissions": [
'filemanager',
'settings',
'subscriptions',
'sharing',
'advanced_download',
'downloads_manager'
]
}, "user": {
"permissions": [
'filemanager',
'subscriptions',
'sharing'
]
}
}
} }
).write(); ).write();
@ -2737,7 +2755,7 @@ app.post('/api/auth/jwtAuth'
); );
app.post('/api/auth/changePassword', optionalJwt, async (req, res) => { app.post('/api/auth/changePassword', optionalJwt, async (req, res) => {
let user_uid = req.user.uid; 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); let success = await auth_api.changeUserPassword(user_uid, password);
res.send({success: success}); res.send({success: success});
}); });
@ -2746,6 +2764,81 @@ app.post('/api/auth/adminExists', async (req, res) => {
res.send({exists: exists}); 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) { app.use(function(req, res, next) {
//if the request is not html then move along //if the request is not html then move along
var accept = req.accepts('html', 'json', 'xml'); var accept = req.accepts('html', 'json', 'xml');

@ -1,5 +1,6 @@
const path = require('path'); const path = require('path');
const config_api = require('../config'); const config_api = require('../config');
const consts = require('../consts');
var subscriptions_api = require('../subscriptions') var subscriptions_api = require('../subscriptions')
const fs = require('fs-extra'); const fs = require('fs-extra');
var jwt = require('jsonwebtoken'); var jwt = require('jsonwebtoken');
@ -97,7 +98,9 @@ exports.registerUser = function(req, res) {
}, },
subscriptions: [], subscriptions: [],
created: Date.now(), created: Date.now(),
role: userid === 'admin' ? 'admin' : 'user' role: userid === 'admin' ? 'admin' : 'user',
permissions: [],
permission_overrides: []
}; };
// check if user exists // check if user exists
if (users_db.get('users').find({uid: userid}).value()) { 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) { exports.generateJWT = function(req, res, next) {
var payload = { var payload = {
exp: Math.floor(Date.now() / 1000) + JWT_EXPIRATION exp: Math.floor(Date.now() / 1000) + JWT_EXPIRATION
, user: req.user, , user: req.user
// , role: role
}; };
req.token = jwt.sign(payload, SERVER_SECRET); req.token = jwt.sign(payload, SERVER_SECRET);
next(); next();
@ -210,7 +212,9 @@ exports.generateJWT = function(req, res, next) {
exports.returnAuthResponse = function(req, res) { exports.returnAuthResponse = function(req, res) {
res.status(200).json({ res.status(200).json({
user: req.user, 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() { exports.adminExists = function() {
return !!users_db.get('users').find({uid: 'admin'}).value(); 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; 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) { function getToken(queryParams) {
if (queryParams && queryParams.jwt) { if (queryParams && queryParams.jwt) {
var parted = queryParams.jwt.split(' '); var parted = queryParams.jwt.split(' ');

@ -142,7 +142,17 @@ let CONFIG_ITEMS = {
}, },
}; };
AVAILABLE_PERMISSIONS = [
'filemanager',
'settings',
'subscriptions',
'sharing',
'advanced_download',
'downloads_manager'
];
module.exports = { module.exports = {
CONFIG_ITEMS: CONFIG_ITEMS, CONFIG_ITEMS: CONFIG_ITEMS,
AVAILABLE_PERMISSIONS: AVAILABLE_PERMISSIONS,
CURRENT_VERSION: 'v3.6' CURRENT_VERSION: 'v3.6'
} }

@ -23,7 +23,7 @@
<span i18n="Dark mode toggle label">Dark</span> <span i18n="Dark mode toggle label">Dark</span>
<mat-slide-toggle class="theme-slide-toggle" [checked]="postsService.theme.key === 'dark'"></mat-slide-toggle> <mat-slide-toggle class="theme-slide-toggle" [checked]="postsService.theme.key === 'dark'"></mat-slide-toggle>
</button> </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> <mat-icon>settings</mat-icon>
<span i18n="Settings menu label">Settings</span> <span i18n="Settings menu label">Settings</span>
</button> </button>
@ -41,8 +41,8 @@
<mat-sidenav #sidenav> <mat-sidenav #sidenav>
<mat-nav-list> <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 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 *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 mat-list-item (click)="sidenav.close()" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</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-nav-list>
</mat-sidenav> </mat-sidenav>
<mat-sidenav-content [style.background]="postsService.theme ? postsService.theme.background_color : null"> <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 { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatToolbarModule } from '@angular/material/toolbar'; import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTabsModule } from '@angular/material/tabs'; 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 {DragDropModule} from '@angular/cdk/drag-drop';
import {ClipboardModule} from '@angular/cdk/clipboard'; import {ClipboardModule} from '@angular/cdk/clipboard';
import {FormsModule, ReactiveFormsModule} from '@angular/forms'; 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 { DownloadsComponent } from './components/downloads/downloads.component';
import { UserProfileDialogComponent } from './dialogs/user-profile-dialog/user-profile-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'; 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'); registerLocaleData(es, 'es');
export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) { export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) {
@ -93,7 +100,11 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
LoginComponent, LoginComponent,
DownloadsComponent, DownloadsComponent,
UserProfileDialogComponent, UserProfileDialogComponent,
SetDefaultAdminDialogComponent SetDefaultAdminDialogComponent,
ModifyUsersComponent,
AddUserDialogComponent,
ManageUserComponent,
ManageRoleComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -127,6 +138,9 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
MatAutocompleteModule, MatAutocompleteModule,
MatTabsModule, MatTabsModule,
MatTooltipModule, MatTooltipModule,
MatPaginatorModule,
MatSortModule,
MatTableModule,
DragDropModule, DragDropModule,
ClipboardModule, ClipboardModule,
VgCoreModule, VgCoreModule,

@ -8,7 +8,7 @@
</div> </div>
<div> <div>
<mat-form-field> <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> </mat-form-field>
</div> </div>
<div style="margin-bottom: 10px; margin-top: 10px;"> <div style="margin-bottom: 10px; margin-top: 10px;">

@ -40,6 +40,9 @@ export class LoginComponent implements OnInit {
} }
login() { login() {
if (this.loginPasswordInput === '') {
return;
}
this.loggingIn = true; this.loggingIn = true;
this.postsService.login(this.loginUsernameInput, this.loginPasswordInput).subscribe(res => { this.postsService.login(this.loginUsernameInput, this.loginPasswordInput).subscribe(res => {
this.loggingIn = false; this.loggingIn = false;

@ -0,0 +1,19 @@
<h4 *ngIf="role" mat-dialog-title><ng-container i18n="Manage role dialog title">Manage role</ng-container>&nbsp;-&nbsp;{{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;
}

@ -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();
});
});

@ -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';
});
}
}

@ -0,0 +1,31 @@
<h4 *ngIf="user" mat-dialog-title><ng-container i18n="Manage user dialog title">Manage user</ng-container>&nbsp;-&nbsp;{{user.name}}</h4>
<mat-dialog-content *ngIf="user">
<p><ng-container i18n="User UID">User UID:</ng-container>&nbsp;{{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;
}

@ -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();
});
});

@ -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;
});
}
}

@ -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();
});
});

@ -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();
});
});

@ -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 style="position: relative">
<div> <div>
<mat-form-field color="accent"> <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> </mat-form-field>
</div> </div>
</div> </div>

@ -186,7 +186,7 @@
<ng-template #nofile> <ng-template #nofile>
</ng-template> </ng-template>
<div style="margin: 20px" *ngIf="fileManagerEnabled"> <div style="margin: 20px" *ngIf="fileManagerEnabled && (!postsService.isLoggedIn || postsService.permissions.includes('filemanager'))">
<mat-accordion> <mat-accordion>
<mat-expansion-panel (opened)="accordionOpened('audio')" (closed)="accordionClosed('audio')" (mouseleave)="accordionLeft('audio')" (mouseenter)="accordionEntered('audio')" class="big"> <mat-expansion-panel (opened)="accordionOpened('audio')" (closed)="accordionClosed('audio')" (mouseleave)="accordionLeft('audio')" (mouseenter)="accordionEntered('audio')" class="big">
<mat-expansion-panel-header> <mat-expansion-panel-header>

@ -3,7 +3,6 @@ import {PostsService} from '../posts.services';
import {FileCardComponent} from '../file-card/file-card.component'; import {FileCardComponent} from '../file-card/file-card.component';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import {FormControl, Validators} from '@angular/forms'; import {FormControl, Validators} from '@angular/forms';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
@ -215,7 +214,7 @@ export class MainComponent implements OnInit {
simulatedOutput = ''; 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) { private router: Router, public dialog: MatDialog, private platform: Platform, private route: ActivatedRoute) {
this.audioOnly = false; this.audioOnly = false;
} }
@ -242,7 +241,8 @@ export class MainComponent implements OnInit {
this.postsService.config['API']['youtube_API_key']; this.postsService.config['API']['youtube_API_key'];
this.youtubeAPIKey = this.youtubeSearchEnabled ? this.postsService.config['API']['youtube_API_key'] : null; this.youtubeAPIKey = this.youtubeSearchEnabled ? this.postsService.config['API']['youtube_API_key'] : null;
this.allowQualitySelect = this.postsService.config['Extra']['allow_quality_select']; 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.useDefaultDownloadingAgent = this.postsService.config['Advanced']['use_default_downloading_agent'];
this.customDownloadingAgent = this.postsService.config['Advanced']['custom_downloading_agent']; this.customDownloadingAgent = this.postsService.config['Advanced']['custom_downloading_agent'];

@ -26,10 +26,10 @@
<div *ngIf="playlist.length > 1"> <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 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="!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>
<div *ngIf="playlist.length === 1"> <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 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>
</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) { public snackBar: MatSnackBar) {
} }

@ -10,6 +10,7 @@ import { DOCUMENT } from '@angular/common';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
import * as Fingerprint2 from 'fingerprintjs2';
@Injectable() @Injectable()
export class PostsService implements CanActivate { export class PostsService implements CanActivate {
@ -30,9 +31,13 @@ export class PostsService implements CanActivate {
debugMode = false; debugMode = false;
// must be reset after logout
isLoggedIn = false; isLoggedIn = false;
token = null; token = null;
user = null; user = null;
permissions = null;
available_permissions = null;
reload_config = new BehaviorSubject<boolean>(false); reload_config = new BehaviorSubject<boolean>(false);
config_reloaded = 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.startPath = window.location.href + '/api/';
// this.startPathSSL = window.location.href + '/api/'; // this.startPathSSL = window.location.href + '/api/';
this.path = this.document.location.origin + '/api/'; this.path = this.document.location.origin + '/api/';
this.session_id = uuid();
if (isDevMode()) { if (isDevMode()) {
this.debugMode = true; this.debugMode = true;
this.path = 'http://localhost:17442/api/'; 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 = { this.httpOptions = {
params: new HttpParams({ 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 // get config
this.loadNavItems().subscribe(res => { this.loadNavItems().subscribe(res => {
const result = !this.debugMode ? res['config_file'] : res; const result = !this.debugMode ? res['config_file'] : res;
@ -71,11 +82,8 @@ export class PostsService implements CanActivate {
// login stuff // login stuff
if (localStorage.getItem('jwt_token')) { if (localStorage.getItem('jwt_token')) {
this.token = localStorage.getItem('jwt_token'); this.token = localStorage.getItem('jwt_token');
this.httpOptions = { this.httpOptions.params = this.httpOptions.params.set('jwt', this.token);
params: new HttpParams({
fromString: `apiKey=${this.auth_token}&sessionID=${this.session_id}&jwt=${this.token}`
}),
};
this.jwtAuth(); this.jwtAuth();
} else { } else {
this.sendToLogin(); this.sendToLogin();
@ -321,18 +329,16 @@ export class PostsService implements CanActivate {
return this.http.get('https://api.github.com/repos/tzahi12345/youtubedl-material/releases'); 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.isLoggedIn = true;
this.user = user; this.user = user;
this.permissions = permissions;
this.available_permissions = available_permissions;
this.token = token; this.token = token;
localStorage.setItem('jwt_token', this.token); localStorage.setItem('jwt_token', this.token);
this.httpOptions = { this.httpOptions.params = this.httpOptions.params.set('jwt', this.token);
params: new HttpParams({
fromString: `apiKey=${this.auth_token}&sessionID=${this.session_id}&jwt=${this.token}`
}),
};
// needed to re-initialize parts of app after login // needed to re-initialize parts of app after login
this.config_reloaded.next(true); 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); const call = this.http.post(this.path + 'auth/login', {userid: username, password: password}, this.httpOptions);
call.subscribe(res => { call.subscribe(res => {
if (res['token']) { if (res['token']) {
this.afterLogin(res['user'], res['token']); this.afterLogin(res['user'], res['token'], res['permissions'], res['available_permissions']);
} }
}); });
return call; return call;
@ -358,7 +364,7 @@ export class PostsService implements CanActivate {
const call = this.http.post(this.path + 'auth/jwtAuth', {}, this.httpOptions); const call = this.http.post(this.path + 'auth/jwtAuth', {}, this.httpOptions);
call.subscribe(res => { call.subscribe(res => {
if (res['token']) { if (res['token']) {
this.afterLogin(res['user'], res['token']); this.afterLogin(res['user'], res['token'], res['permissions'], res['available_permissions']);
this.setInitialized(); this.setInitialized();
} }
}, err => { }, err => {
@ -371,6 +377,7 @@ export class PostsService implements CanActivate {
logout() { logout() {
this.user = null; this.user = null;
this.permissions = null;
this.isLoggedIn = false; this.isLoggedIn = false;
localStorage.setItem('jwt_token', null); localStorage.setItem('jwt_token', null);
if (this.router.url !== '/login') { if (this.router.url !== '/login') {
@ -430,7 +437,9 @@ export class PostsService implements CanActivate {
} }
checkAdminCreationStatus() { checkAdminCreationStatus() {
console.log('checking c stat'); if (!this.config['Advanced']['multi_user_mode']) {
return;
}
this.adminExists().subscribe(res => { this.adminExists().subscribe(res => {
if (!res['exists']) { if (!res['exists']) {
// must create admin account // 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 = '') { public openSnackBar(message: string, action: string = '') {
this.snackBar.open(message, action, { this.snackBar.open(message, action, {
duration: 2000, duration: 2000,

@ -266,6 +266,9 @@
</div> </div>
</ng-template> </ng-template>
</mat-tab> </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-tab-group>
</mat-dialog-content> </mat-dialog-content>

@ -39,7 +39,7 @@ export class SettingsComponent implements OnInit {
this._settingsSame = val; 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) { } private dialog: MatDialog) { }
ngOnInit() { ngOnInit() {

Loading…
Cancel
Save