migrate specialusers
This commit is contained in:
parent
518391b497
commit
2ba9bb0ac2
|
@ -2,11 +2,6 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
import { plainToClass } from 'class-transformer';
|
import { plainToClass } from 'class-transformer';
|
||||||
import {
|
|
||||||
LockedLoginUsersList,
|
|
||||||
LockedPermsUsersList,
|
|
||||||
SystemUsersList
|
|
||||||
} from 'picsur-shared/dist/dto/specialusers.dto';
|
|
||||||
import {
|
import {
|
||||||
AsyncFailable,
|
AsyncFailable,
|
||||||
Fail,
|
Fail,
|
||||||
|
@ -19,6 +14,7 @@ import {
|
||||||
DefaultRolesList,
|
DefaultRolesList,
|
||||||
SoulBoundRolesList
|
SoulBoundRolesList
|
||||||
} from '../../models/dto/roles.dto';
|
} from '../../models/dto/roles.dto';
|
||||||
|
import { ImmutableUsersList, LockedLoginUsersList, UndeletableUsersList } from '../../models/dto/specialusers.dto';
|
||||||
import { EUserBackend } from '../../models/entities/user.entity';
|
import { EUserBackend } from '../../models/entities/user.entity';
|
||||||
import { GetCols } from '../collectionutils';
|
import { GetCols } from '../collectionutils';
|
||||||
import { RolesService } from '../roledb/roledb.service';
|
import { RolesService } from '../roledb/roledb.service';
|
||||||
|
@ -74,7 +70,7 @@ export class UsersService {
|
||||||
const userToModify = await this.resolve(user);
|
const userToModify = await this.resolve(user);
|
||||||
if (HasFailed(userToModify)) return userToModify;
|
if (HasFailed(userToModify)) return userToModify;
|
||||||
|
|
||||||
if (SystemUsersList.includes(userToModify.username)) {
|
if (UndeletableUsersList.includes(userToModify.username)) {
|
||||||
return Fail('Cannot delete system user');
|
return Fail('Cannot delete system user');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +90,7 @@ export class UsersService {
|
||||||
const userToModify = await this.resolve(user);
|
const userToModify = await this.resolve(user);
|
||||||
if (HasFailed(userToModify)) return userToModify;
|
if (HasFailed(userToModify)) return userToModify;
|
||||||
|
|
||||||
if (LockedPermsUsersList.includes(userToModify.username)) {
|
if (ImmutableUsersList.includes(userToModify.username)) {
|
||||||
// Just fail silently
|
// Just fail silently
|
||||||
return userToModify;
|
return userToModify;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// Cannot be deleted
|
// Cannot be deleted
|
||||||
export const SystemUsersList = ['guest', 'admin'];
|
export const UndeletableUsersList = ['guest', 'admin'];
|
||||||
|
|
||||||
// Cannot have different permissions
|
// Cannot have different permissions
|
||||||
export const LockedPermsUsersList = ['admin'];
|
export const ImmutableUsersList = ['admin'];
|
||||||
|
|
||||||
// Cannot login
|
// Cannot login
|
||||||
export const LockedLoginUsersList = ['guest'];
|
export const LockedLoginUsersList = ['guest'];
|
|
@ -6,7 +6,9 @@ import {
|
||||||
Logger,
|
Logger,
|
||||||
Post
|
Post
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import { plainToClass } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
|
GetSpecialUsersResponse,
|
||||||
UserCreateRequest,
|
UserCreateRequest,
|
||||||
UserCreateResponse,
|
UserCreateResponse,
|
||||||
UserDeleteRequest,
|
UserDeleteRequest,
|
||||||
|
@ -22,6 +24,7 @@ import { Permission } from 'picsur-shared/dist/dto/permissions';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { UsersService } from '../../../collections/userdb/userdb.service';
|
import { UsersService } from '../../../collections/userdb/userdb.service';
|
||||||
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
|
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
|
||||||
|
import { ImmutableUsersList, LockedLoginUsersList, UndeletableUsersList } from '../../../models/dto/specialusers.dto';
|
||||||
|
|
||||||
@Controller('api/user')
|
@Controller('api/user')
|
||||||
@RequiredPermissions(Permission.UserManage)
|
@RequiredPermissions(Permission.UserManage)
|
||||||
|
@ -125,4 +128,15 @@ export class UserManageController {
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('special')
|
||||||
|
async getSpecial(): Promise<GetSpecialUsersResponse> {
|
||||||
|
const result: GetSpecialUsersResponse = {
|
||||||
|
ImmutableUsersList,
|
||||||
|
LockedLoginUsersList,
|
||||||
|
UndeletableUsersList,
|
||||||
|
};
|
||||||
|
|
||||||
|
return plainToClass(GetSpecialUsersResponse, result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
||||||
import { MatChipInputEvent } from '@angular/material/chips';
|
import { MatChipInputEvent } from '@angular/material/chips';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { UIFriendlyPermissions } from 'picsur-shared/dist/dto/permissions';
|
import { UIFriendlyPermissions } from 'picsur-shared/dist/dto/permissions';
|
||||||
import { LockedPermsUsersList } from 'picsur-shared/dist/dto/specialusers.dto';
|
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { UpdateUserControl } from 'src/app/models/forms/updateuser.control';
|
import { UpdateUserControl } from 'src/app/models/forms/updateuser.control';
|
||||||
import { SnackBarType } from 'src/app/models/snack-bar-type';
|
import { SnackBarType } from 'src/app/models/snack-bar-type';
|
||||||
|
@ -23,6 +22,8 @@ enum EditMode {
|
||||||
styleUrls: ['./settings-users-edit.component.scss'],
|
styleUrls: ['./settings-users-edit.component.scss'],
|
||||||
})
|
})
|
||||||
export class SettingsUsersEditComponent implements OnInit {
|
export class SettingsUsersEditComponent implements OnInit {
|
||||||
|
private ImmutableUsersList: string[] = [];
|
||||||
|
|
||||||
readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE];
|
readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE];
|
||||||
|
|
||||||
private mode: EditMode = EditMode.edit;
|
private mode: EditMode = EditMode.edit;
|
||||||
|
@ -73,6 +74,10 @@ export class SettingsUsersEditComponent implements OnInit {
|
||||||
|
|
||||||
this.model.putUsername(user.username);
|
this.model.putUsername(user.username);
|
||||||
this.model.putRoles(user.roles);
|
this.model.putRoles(user.roles);
|
||||||
|
|
||||||
|
const { ImmutableUsersList } =
|
||||||
|
await this.userManageService.getSpecialRolesOptimistic();
|
||||||
|
this.ImmutableUsersList = ImmutableUsersList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initRoles() {
|
private async initRoles() {
|
||||||
|
@ -148,7 +153,7 @@ export class SettingsUsersEditComponent implements OnInit {
|
||||||
if (this.adding) {
|
if (this.adding) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return LockedPermsUsersList.includes(this.model.getData().username);
|
return this.ImmutableUsersList.includes(this.model.getData().username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
|
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
|
||||||
import { SystemUsersList } from 'picsur-shared/dist/dto/specialusers.dto';
|
|
||||||
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { BehaviorSubject, Subject, throttleTime } from 'rxjs';
|
import { BehaviorSubject, Subject, throttleTime } from 'rxjs';
|
||||||
|
@ -15,6 +14,8 @@ import { UtilService } from 'src/app/util/util.service';
|
||||||
styleUrls: ['./settings-users.component.scss'],
|
styleUrls: ['./settings-users.component.scss'],
|
||||||
})
|
})
|
||||||
export class SettingsUsersComponent implements OnInit {
|
export class SettingsUsersComponent implements OnInit {
|
||||||
|
private UndeletableUsersList: string[] = [];
|
||||||
|
|
||||||
public readonly displayedColumns: string[] = ['username', 'roles', 'actions'];
|
public readonly displayedColumns: string[] = ['username', 'roles', 'actions'];
|
||||||
public readonly pageSizeOptions: number[] = [5, 10, 25, 100];
|
public readonly pageSizeOptions: number[] = [5, 10, 25, 100];
|
||||||
public readonly startingPageSize = this.pageSizeOptions[2];
|
public readonly startingPageSize = this.pageSizeOptions[2];
|
||||||
|
@ -31,9 +32,13 @@ export class SettingsUsersComponent implements OnInit {
|
||||||
private router: Router
|
private router: Router
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
ngOnInit() {
|
||||||
this.subscribeToUpdate();
|
this.subscribeToUpdate();
|
||||||
this.fetchUsers(this.startingPageSize, 0);
|
|
||||||
|
Promise.all([
|
||||||
|
this.fetchUsers(this.startingPageSize, 0),
|
||||||
|
this.initSpecialUsers(),
|
||||||
|
]).catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addUser() {
|
public addUser() {
|
||||||
|
@ -123,7 +128,20 @@ export class SettingsUsersComponent implements OnInit {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async initSpecialUsers() {
|
||||||
|
const specialUsers = await this.userManageService.getSpecialUsers();
|
||||||
|
if (HasFailed(specialUsers)) {
|
||||||
|
this.utilService.showSnackBar(
|
||||||
|
'Failed to fetch special users',
|
||||||
|
SnackBarType.Error
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.UndeletableUsersList = specialUsers.UndeletableUsersList;
|
||||||
|
}
|
||||||
|
|
||||||
isSystem(user: EUser): boolean {
|
isSystem(user: EUser): boolean {
|
||||||
return SystemUsersList.includes(user.username);
|
return this.UndeletableUsersList.includes(user.username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
|
GetSpecialUsersResponse,
|
||||||
UserCreateRequest,
|
UserCreateRequest,
|
||||||
UserCreateResponse,
|
UserCreateResponse,
|
||||||
UserDeleteRequest,
|
UserDeleteRequest,
|
||||||
|
@ -15,12 +16,16 @@ import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||||
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
|
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { FullUserModel } from 'src/app/models/forms/fulluser.model';
|
import { FullUserModel } from 'src/app/models/forms/fulluser.model';
|
||||||
import { ApiService } from './api.service';
|
import { ApiService } from './api.service';
|
||||||
|
import { CacheService } from './cache.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class UserManageService {
|
export class UserManageService {
|
||||||
constructor(private apiService: ApiService) {}
|
constructor(
|
||||||
|
private apiService: ApiService,
|
||||||
|
private cacheService: CacheService
|
||||||
|
) {}
|
||||||
|
|
||||||
public async getUser(username: string): AsyncFailable<EUser> {
|
public async getUser(username: string): AsyncFailable<EUser> {
|
||||||
const body = {
|
const body = {
|
||||||
|
@ -93,4 +98,38 @@ export class UserManageService {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getSpecialUsers(): AsyncFailable<GetSpecialUsersResponse> {
|
||||||
|
const cached =
|
||||||
|
this.cacheService.get<GetSpecialUsersResponse>('specialUsers');
|
||||||
|
if (cached !== null) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.apiService.get(
|
||||||
|
GetSpecialUsersResponse,
|
||||||
|
'/api/user/special'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (HasFailed(result)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cacheService.set('specialRoles', result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getSpecialRolesOptimistic(): Promise<GetSpecialUsersResponse> {
|
||||||
|
const result = await this.getSpecialUsers();
|
||||||
|
if (HasFailed(result)) {
|
||||||
|
return {
|
||||||
|
ImmutableUsersList: [],
|
||||||
|
LockedLoginUsersList: [],
|
||||||
|
UndeletableUsersList: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,8 @@ import { Type } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
IsDefined,
|
IsDefined,
|
||||||
IsOptional, ValidateNested
|
IsOptional,
|
||||||
|
ValidateNested
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { EUser, NamePassUser, UsernameUser } from '../../entities/user.entity';
|
import { EUser, NamePassUser, UsernameUser } from '../../entities/user.entity';
|
||||||
import { IsPosInt } from '../../validators/positive-int.validator';
|
import { IsPosInt } from '../../validators/positive-int.validator';
|
||||||
|
@ -60,3 +61,18 @@ export class UserUpdateRequest extends UsernameUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UserUpdateResponse extends EUser {}
|
export class UserUpdateResponse extends EUser {}
|
||||||
|
|
||||||
|
// GetSpecialUsers
|
||||||
|
export class GetSpecialUsersResponse {
|
||||||
|
@IsDefined()
|
||||||
|
@IsStringList()
|
||||||
|
UndeletableUsersList: string[];
|
||||||
|
|
||||||
|
@IsDefined()
|
||||||
|
@IsStringList()
|
||||||
|
ImmutableUsersList: string[];
|
||||||
|
|
||||||
|
@IsDefined()
|
||||||
|
@IsStringList()
|
||||||
|
LockedLoginUsersList: string[];
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue