refactor collections

This commit is contained in:
rubikscraft
2022-03-27 23:56:25 +02:00
parent 904ba2ee4b
commit 07ef1b1216
12 changed files with 147 additions and 113 deletions

View File

@@ -4,14 +4,12 @@ import { plainToClass } from 'class-transformer';
import Crypto from 'crypto'; import Crypto from 'crypto';
import { import {
AsyncFailable, AsyncFailable,
Fail, Fail, HasSuccess
HasFailed,
HasSuccess
} from 'picsur-shared/dist/types'; } from 'picsur-shared/dist/types';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { SupportedMime } from '../../models/dto/mimes.dto'; import { SupportedMime } from '../../models/dto/mimes.dto';
import { EImageBackend } from '../../models/entities/image.entity'; import { EImageBackend } from '../../models/entities/image.entity';
import { GetCols } from '../collectionutils'; import { GetCols } from '../../models/util/collection';
@Injectable() @Injectable()
export class ImageDBService { export class ImageDBService {
@@ -46,7 +44,9 @@ export class ImageDBService {
public async findOne<B extends true | undefined = undefined>( public async findOne<B extends true | undefined = undefined>(
hash: string, hash: string,
getPrivate?: B, getPrivate?: B,
): AsyncFailable<B extends undefined ? EImageBackend : Required<EImageBackend>> { ): AsyncFailable<
B extends undefined ? EImageBackend : Required<EImageBackend>
> {
try { try {
const found = await this.imageRepository.findOne({ const found = await this.imageRepository.findOne({
where: { hash }, where: { hash },
@@ -54,21 +54,27 @@ export class ImageDBService {
}); });
if (!found) return Fail('Image not found'); if (!found) return Fail('Image not found');
return found as B extends undefined ? EImageBackend : Required<EImageBackend>; return found as B extends undefined
? EImageBackend
: Required<EImageBackend>;
} catch (e: any) { } catch (e: any) {
return Fail(e?.message); return Fail(e?.message);
} }
} }
public async findMany( public async findMany(
startId: number, count: number,
limit: number, page: number,
): AsyncFailable<EImageBackend[]> { ): AsyncFailable<EImageBackend[]> {
if (count < 1 || page < 0) return Fail('Invalid page');
if (count > 100) return Fail('Too many results');
try { try {
const found = await this.imageRepository.find({ const found = await this.imageRepository.find({
where: { id: { gte: startId } }, skip: count * page,
take: limit, take: count,
}); });
if (found === undefined) return Fail('Images not found'); if (found === undefined) return Fail('Images not found');
return found; return found;
} catch (e: any) { } catch (e: any) {
@@ -77,18 +83,19 @@ export class ImageDBService {
} }
public async delete(hash: string): AsyncFailable<true> { public async delete(hash: string): AsyncFailable<true> {
const image = await this.findOne(hash);
if (HasFailed(image)) return image;
try { try {
await this.imageRepository.delete(image); const result = await this.imageRepository.delete({ hash });
if (result.affected === 0) return Fail('Image not found');
} catch (e: any) { } catch (e: any) {
return Fail(e?.message); return Fail(e?.message);
} }
return true; return true;
} }
public async deleteAll(): AsyncFailable<true> { public async deleteAll(IAmSure: boolean): AsyncFailable<true> {
if (!IAmSure)
return Fail('You must confirm that you want to delete all images');
try { try {
await this.imageRepository.delete({}); await this.imageRepository.delete({});
} catch (e: any) { } catch (e: any) {

View File

@@ -21,6 +21,8 @@ export class RolesModule implements OnModuleInit {
) {} ) {}
async onModuleInit() { async onModuleInit() {
// Nuking roles in dev environment makes testing easier
// This ensures that the roles are always started with their default permissions
if (!this.hostConfig.isProduction()) { if (!this.hostConfig.isProduction()) {
await this.nukeRoles(); await this.nukeRoles();
} }
@@ -38,6 +40,7 @@ export class RolesModule implements OnModuleInit {
} }
private async ensureSystemRolesExist() { private async ensureSystemRolesExist() {
// The UndeletableRolesList is also the list of systemroles
for (const systemRole of UndeletableRolesList) { for (const systemRole of UndeletableRolesList) {
this.logger.debug(`Ensuring system role "${systemRole}" exists`); this.logger.debug(`Ensuring system role "${systemRole}" exists`);
@@ -58,6 +61,9 @@ export class RolesModule implements OnModuleInit {
} }
private async updateImmutableRoles() { private async updateImmutableRoles() {
// Immutable roles can not be updated via the gui
// They therefore do have to be kept up to date from the backend
for (const immutableRole of ImmutableRolesList) { for (const immutableRole of ImmutableRolesList) {
this.logger.debug( this.logger.debug(
`Updating permissions for immutable role "${immutableRole}"`, `Updating permissions for immutable role "${immutableRole}"`,

View File

@@ -7,10 +7,14 @@ import {
HasFailed, HasFailed,
HasSuccess HasSuccess
} from 'picsur-shared/dist/types'; } from 'picsur-shared/dist/types';
import { makeUnique } from 'picsur-shared/dist/util/unique';
import { strictValidate } from 'picsur-shared/dist/util/validate'; import { strictValidate } from 'picsur-shared/dist/util/validate';
import { In, Repository } from 'typeorm'; import { In, Repository } from 'typeorm';
import { Permissions } from '../../models/dto/permissions.dto'; import { Permissions } from '../../models/dto/permissions.dto';
import { ImmutableRolesList, UndeletableRolesList } from '../../models/dto/roles.dto'; import {
ImmutableRolesList,
UndeletableRolesList
} from '../../models/dto/roles.dto';
import { ERoleBackend } from '../../models/entities/role.entity'; import { ERoleBackend } from '../../models/entities/role.entity';
@Injectable() @Injectable()
@@ -33,12 +37,10 @@ export class RolesService {
role.permissions = permissions; role.permissions = permissions;
try { try {
role = await this.rolesRepository.save(role, { reload: true }); return await this.rolesRepository.save(role, { reload: true });
} catch (e: any) { } catch (e: any) {
return Fail(e?.message); return Fail(e?.message);
} }
return plainToClass(ERoleBackend, role);
} }
public async delete( public async delete(
@@ -69,7 +71,7 @@ export class RolesService {
permissions.push(...foundRole.permissions); permissions.push(...foundRole.permissions);
} }
return [...new Set(...[permissions])]; return makeUnique(permissions);
} }
public async addPermissions( public async addPermissions(
@@ -79,10 +81,10 @@ export class RolesService {
const roleToModify = await this.resolve(role); const roleToModify = await this.resolve(role);
if (HasFailed(roleToModify)) return roleToModify; if (HasFailed(roleToModify)) return roleToModify;
// This is stupid const newPermissions = makeUnique([
const newPermissions = [ ...roleToModify.permissions,
...new Set([...roleToModify.permissions, ...permissions]), ...permissions,
]; ]);
return this.setPermissions(roleToModify, newPermissions); return this.setPermissions(roleToModify, newPermissions);
} }
@@ -101,9 +103,11 @@ export class RolesService {
return this.setPermissions(roleToModify, newPermissions); return this.setPermissions(roleToModify, newPermissions);
} }
// Permission specific validation is done here
public async setPermissions( public async setPermissions(
role: string | ERoleBackend, role: string | ERoleBackend,
permissions: Permissions, permissions: Permissions,
// Extra bypass for internal use
allowImmutable: boolean = false, allowImmutable: boolean = false,
): AsyncFailable<ERoleBackend> { ): AsyncFailable<ERoleBackend> {
const roleToModify = await this.resolve(role); const roleToModify = await this.resolve(role);
@@ -113,7 +117,7 @@ export class RolesService {
return Fail('Cannot modify immutable role'); return Fail('Cannot modify immutable role');
} }
roleToModify.permissions = [...new Set(permissions)]; roleToModify.permissions = makeUnique(permissions);
try { try {
return await this.rolesRepository.save(roleToModify); return await this.rolesRepository.save(roleToModify);
@@ -129,7 +133,7 @@ export class RolesService {
}); });
if (!found) return Fail('Role not found'); if (!found) return Fail('Role not found');
return found as ERoleBackend; return found;
} catch (e: any) { } catch (e: any) {
return Fail(e?.message); return Fail(e?.message);
} }
@@ -139,7 +143,7 @@ export class RolesService {
try { try {
const found = await this.rolesRepository.find(); const found = await this.rolesRepository.find();
if (!found) return Fail('No roles found'); if (!found) return Fail('No roles found');
return found as ERoleBackend[]; return found;
} catch (e: any) { } catch (e: any) {
return Fail(e?.message); return Fail(e?.message);
} }
@@ -149,8 +153,10 @@ export class RolesService {
return HasSuccess(await this.findOne(username)); return HasSuccess(await this.findOne(username));
} }
public async nukeSystemRoles(iamsure: boolean = false): AsyncFailable<true> { public async nukeSystemRoles(IAmSure: boolean = false): AsyncFailable<true> {
if (!iamsure) return Fail('Nuke aborted'); if (!IAmSure)
return Fail('You must confirm that you want to delete all roles');
try { try {
await this.rolesRepository.delete({ await this.rolesRepository.delete({
name: In(UndeletableRolesList), name: In(UndeletableRolesList),

View File

@@ -1,10 +1,10 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { plainToClass } from 'class-transformer';
import { import {
InternalSysprefRepresentation, InternalSysprefRepresentation,
SysPreference, SysPreference,
SysPrefValueType SysPrefValueType,
SysPrefValueTypeStrings
} from 'picsur-shared/dist/dto/syspreferences.dto'; } from 'picsur-shared/dist/dto/syspreferences.dto';
import { import {
AsyncFailable, AsyncFailable,
@@ -41,6 +41,7 @@ export class SysPreferenceService {
// Set // Set
try { try {
// Upsert here, because we want to create a new record if it does not exist
await this.sysPreferenceRepository.upsert(sysPreference, { await this.sysPreferenceRepository.upsert(sysPreference, {
conflictPaths: ['key'], conflictPaths: ['key'],
}); });
@@ -70,7 +71,7 @@ export class SysPreferenceService {
try { try {
foundSysPreference = await this.sysPreferenceRepository.findOne( foundSysPreference = await this.sysPreferenceRepository.findOne(
{ key: validatedKey }, { key: validatedKey },
{ cache: 60000 }, { cache: 60000 }, // Enable cache for 1 minute
); );
} catch (e: any) { } catch (e: any) {
this.logger.warn(e); this.logger.warn(e);
@@ -80,49 +81,46 @@ export class SysPreferenceService {
// Fallback // Fallback
if (!foundSysPreference) { if (!foundSysPreference) {
return this.saveDefault(validatedKey); return this.saveDefault(validatedKey);
} else { }
foundSysPreference = plainToClass(
ESysPreferenceBackend, // Validate
foundSysPreference,
);
const errors = await strictValidate(foundSysPreference); const errors = await strictValidate(foundSysPreference);
if (errors.length > 0) { if (errors.length > 0) {
this.logger.warn(errors); this.logger.warn(errors);
return Fail('Invalid preference'); return Fail('Invalid preference');
} }
}
// Return // Return
return this.retrieveConvertedValue(foundSysPreference); return this.retrieveConvertedValue(foundSysPreference);
} }
public async getStringPreference(key: string): AsyncFailable<string> { public async getStringPreference(key: string): AsyncFailable<string> {
const pref = await this.getPreference(key); return this.getPreferencePinned(key, 'string') as AsyncFailable<string>;
if (HasFailed(pref)) return pref;
if (pref.type !== 'string') return Fail('Invalid preference type');
return pref.value as string;
} }
public async getNumberPreference(key: string): AsyncFailable<number> { public async getNumberPreference(key: string): AsyncFailable<number> {
const pref = await this.getPreference(key); return this.getPreferencePinned(key, 'number') as AsyncFailable<number>;
if (HasFailed(pref)) return pref;
if (pref.type !== 'number') return Fail('Invalid preference type');
return pref.value as number;
} }
public async getBooleanPreference(key: string): AsyncFailable<boolean> { public async getBooleanPreference(key: string): AsyncFailable<boolean> {
const pref = await this.getPreference(key); return this.getPreferencePinned(key, 'boolean') as AsyncFailable<boolean>;
if (HasFailed(pref)) return pref; }
if (pref.type !== 'boolean') return Fail('Invalid preference type');
return pref.value as boolean; private async getPreferencePinned(
key: string,
type: SysPrefValueTypeStrings,
): AsyncFailable<SysPrefValueType> {
let pref = await this.getPreference(key);
if (HasFailed(pref)) return pref;
if (pref.type !== type) return Fail('Invalid preference type');
return pref.value;
} }
public async getAllPreferences(): AsyncFailable< public async getAllPreferences(): AsyncFailable<
InternalSysprefRepresentation[] InternalSysprefRepresentation[]
> { > {
// TODO: We are fetching each value invidually, we should fetch all at once
let internalSysPrefs = await Promise.all( let internalSysPrefs = await Promise.all(
SysPreferenceList.map((key) => this.getPreference(key)), SysPreferenceList.map((key) => this.getPreference(key)),
); );
@@ -132,6 +130,7 @@ export class SysPreferenceService {
return internalSysPrefs as InternalSysprefRepresentation[]; return internalSysPrefs as InternalSysprefRepresentation[];
} }
// Private // Private
private async saveDefault( private async saveDefault(
@@ -140,6 +139,7 @@ export class SysPreferenceService {
return this.setPreference(key, this.defaultsService.defaults[key]()); return this.setPreference(key, this.defaultsService.defaults[key]());
} }
// This converts the raw string representation of the value to the correct type
private retrieveConvertedValue( private retrieveConvertedValue(
preference: ESysPreferenceBackend, preference: ESysPreferenceBackend,
): Failable<InternalSysprefRepresentation> { ): Failable<InternalSysprefRepresentation> {
@@ -185,7 +185,7 @@ export class SysPreferenceService {
verifySysPreference.key = validatedKey; verifySysPreference.key = validatedKey;
verifySysPreference.value = validatedValue; verifySysPreference.value = validatedValue;
// Just to be sure // It should already be valid, but these two validators might go out of sync
const errors = await strictValidate(verifySysPreference); const errors = await strictValidate(verifySysPreference);
if (errors.length > 0) { if (errors.length > 0) {
this.logger.warn(errors); this.logger.warn(errors);
@@ -196,14 +196,13 @@ export class SysPreferenceService {
} }
private validatePrefKey(key: string): Failable<SysPreference> { private validatePrefKey(key: string): Failable<SysPreference> {
if (!SysPreferenceList.includes(key)) { if (!SysPreferenceList.includes(key)) return Fail('Invalid preference key');
return Fail('Invalid preference key');
}
return key as SysPreference; return key as SysPreference;
} }
private validatePrefValue( private validatePrefValue(
// Key is required, because the type of the value depends on the key
key: SysPreference, key: SysPreference,
value: SysPrefValueType, value: SysPrefValueType,
): Failable<string> { ): Failable<string> {

View File

@@ -6,6 +6,9 @@ import {
import { generateRandomString } from 'picsur-shared/dist/util/random'; import { generateRandomString } from 'picsur-shared/dist/util/random';
import { EnvJwtConfigService } from '../../config/jwt.config.service'; import { EnvJwtConfigService } from '../../config/jwt.config.service';
// This specific service is used to store default values for system preferences
// It needs to be in a service because the values depend on the environment
@Injectable() @Injectable()
export class SysPreferenceDefaultsService { export class SysPreferenceDefaultsService {
private readonly logger = new Logger('SysPreferenceDefaultsService'); private readonly logger = new Logger('SysPreferenceDefaultsService');
@@ -26,8 +29,8 @@ export class SysPreferenceDefaultsService {
return generateRandomString(64); return generateRandomString(64);
} }
}, },
[SysPreference.JwtExpiresIn]: () => this.jwtConfigService.getJwtExpiresIn() ?? '7d', [SysPreference.JwtExpiresIn]: () =>
this.jwtConfigService.getJwtExpiresIn() ?? '7d',
[SysPreference.TestString]: () => 'test_string', [SysPreference.TestString]: () => 'test_string',
[SysPreference.TestNumber]: () => 123, [SysPreference.TestNumber]: () => 123,
[SysPreference.TestBoolean]: () => true, [SysPreference.TestBoolean]: () => true,

View File

@@ -28,36 +28,27 @@ export class UsersModule implements OnModuleInit {
) {} ) {}
async onModuleInit() { async onModuleInit() {
await this.ensureGuestExists(); await this.ensureUserExists(
await this.ensureAdminExists(); 'guest',
} // Guest should never be able to login
// It should be prevented even if you know the password
private async ensureGuestExists() { // But to be sure, we set it to a random string
const username = 'guest'; generateRandomString(128),
const password = generateRandomString(128);
this.logger.debug(`Ensuring guest user exists`);
const exists = await this.usersService.exists(username);
if (exists) return;
const newUser = await this.usersService.create(
username,
password,
['guest'], ['guest'],
true,
); );
if (HasFailed(newUser)) { await this.ensureUserExists(
this.logger.error( 'admin',
`Failed to create guest user because: ${newUser.getReason()}`, this.authConfigService.getDefaultAdminPassword(),
['user', 'admin'],
); );
return;
}
} }
private async ensureAdminExists() { private async ensureUserExists(
const username = 'admin'; username: string,
const password = this.authConfigService.getDefaultAdminPassword(); password: string,
this.logger.debug(`Ensuring admin user exists`); roles: string[],
) {
this.logger.debug(`Ensuring user "${username}" exists`);
const exists = await this.usersService.exists(username); const exists = await this.usersService.exists(username);
if (exists) return; if (exists) return;
@@ -65,12 +56,12 @@ export class UsersModule implements OnModuleInit {
const newUser = await this.usersService.create( const newUser = await this.usersService.create(
username, username,
password, password,
['user', 'admin'], roles,
true, false,
); );
if (HasFailed(newUser)) { if (HasFailed(newUser)) {
this.logger.error( this.logger.error(
`Failed to create admin user because: ${newUser.getReason()}`, `Failed to create user "${username}" because: ${newUser.getReason()}`,
); );
return; return;
} }

View File

@@ -8,17 +8,22 @@ import {
HasFailed, HasFailed,
HasSuccess HasSuccess
} from 'picsur-shared/dist/types'; } from 'picsur-shared/dist/types';
import { makeUnique } from 'picsur-shared/dist/util/unique';
import { strictValidate } from 'picsur-shared/dist/util/validate'; import { strictValidate } from 'picsur-shared/dist/util/validate';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { 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 {
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 '../../models/util/collection';
import { RolesService } from '../roledb/roledb.service';
// TODO: make this a configurable value
const BCryptStrength = 12; const BCryptStrength = 12;
@Injectable() @Injectable()
@@ -28,7 +33,6 @@ export class UsersService {
constructor( constructor(
@InjectRepository(EUserBackend) @InjectRepository(EUserBackend)
private usersRepository: Repository<EUserBackend>, private usersRepository: Repository<EUserBackend>,
private rolesService: RolesService,
) {} ) {}
// Creation and deletion // Creation and deletion
@@ -37,6 +41,7 @@ export class UsersService {
username: string, username: string,
password: string, password: string,
roles?: string[], roles?: string[],
// Add option to create "invalid" users, should only be used by system
byPassRoleCheck?: boolean, byPassRoleCheck?: boolean,
): AsyncFailable<EUserBackend> { ): AsyncFailable<EUserBackend> {
if (await this.exists(username)) return Fail('User already exists'); if (await this.exists(username)) return Fail('User already exists');
@@ -48,10 +53,11 @@ export class UsersService {
user.password = hashedPassword; user.password = hashedPassword;
if (byPassRoleCheck) { if (byPassRoleCheck) {
const rolesToAdd = roles ?? []; const rolesToAdd = roles ?? [];
user.roles = [...new Set([...rolesToAdd])]; user.roles = makeUnique(rolesToAdd);
} else { } else {
// Strip soulbound roles and add default roles
const rolesToAdd = this.filterAddedRoles(roles ?? []); const rolesToAdd = this.filterAddedRoles(roles ?? []);
user.roles = [...new Set([...DefaultRolesList, ...rolesToAdd])]; user.roles = makeUnique([...DefaultRolesList, ...rolesToAdd]);
} }
try { try {
@@ -60,10 +66,10 @@ export class UsersService {
return Fail(e?.message); return Fail(e?.message);
} }
return plainToClass(EUserBackend, user); // Strips unwanted data // Strips unwanted data
return plainToClass(EUserBackend, user);
} }
// Returns user object without id
public async delete( public async delete(
user: string | EUserBackend, user: string | EUserBackend,
): AsyncFailable<EUserBackend> { ): AsyncFailable<EUserBackend> {
@@ -92,6 +98,7 @@ export class UsersService {
if (ImmutableUsersList.includes(userToModify.username)) { if (ImmutableUsersList.includes(userToModify.username)) {
// Just fail silently // Just fail silently
this.logger.log("Can't modify system user");
return userToModify; return userToModify;
} }
@@ -99,9 +106,7 @@ export class UsersService {
SoulBoundRolesList.includes(role), SoulBoundRolesList.includes(role),
); );
const rolesToAdd = this.filterAddedRoles(roles); const rolesToAdd = this.filterAddedRoles(roles);
const newRoles = makeUnique([...rolesToKeep, ...rolesToAdd]);
const newRoles = [...new Set([...rolesToKeep, ...rolesToAdd])];
userToModify.roles = newRoles; userToModify.roles = newRoles;
try { try {
@@ -115,19 +120,20 @@ export class UsersService {
user: string | EUserBackend, user: string | EUserBackend,
password: string, password: string,
): AsyncFailable<EUserBackend> { ): AsyncFailable<EUserBackend> {
const userToModify = await this.resolve(user); let userToModify = await this.resolve(user);
if (HasFailed(userToModify)) return userToModify; if (HasFailed(userToModify)) return userToModify;
const hashedPassword = await bcrypt.hash(password, BCryptStrength); const hashedPassword = await bcrypt.hash(password, BCryptStrength);
userToModify.password = hashedPassword; userToModify.password = hashedPassword;
try { try {
const fullUser = await this.usersRepository.save(userToModify); userToModify = await this.usersRepository.save(userToModify);
return plainToClass(EUserBackend, fullUser);
} catch (e: any) { } catch (e: any) {
return Fail(e?.message); return Fail(e?.message);
} }
// Strips unwanted data
return plainToClass(EUserBackend, userToModify);
} }
// Authentication // Authentication
@@ -140,7 +146,8 @@ export class UsersService {
if (HasFailed(user)) return user; if (HasFailed(user)) return user;
if (LockedLoginUsersList.includes(user.username)) { if (LockedLoginUsersList.includes(user.username)) {
return Fail('Wrong password'); // Error should be kept in backend
return Fail('Wrong username');
} }
if (!(await bcrypt.compare(password, user.password))) if (!(await bcrypt.compare(password, user.password)))
@@ -153,6 +160,8 @@ export class UsersService {
public async findOne<B extends true | undefined = undefined>( public async findOne<B extends true | undefined = undefined>(
username: string, username: string,
// Also fetch fields that aren't normally sent to the client
// (e.g. hashed password)
getPrivate?: B, getPrivate?: B,
): AsyncFailable< ): AsyncFailable<
B extends undefined ? EUserBackend : Required<EUserBackend> B extends undefined ? EUserBackend : Required<EUserBackend>

View File

@@ -1,16 +1,21 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types'; import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
import { makeUnique } from 'picsur-shared/dist/util/unique';
import { Permissions } from '../../models/dto/permissions.dto'; import { Permissions } from '../../models/dto/permissions.dto';
import { EUserBackend } from '../../models/entities/user.entity'; import { EUserBackend } from '../../models/entities/user.entity';
import { RolesService } from '../roledb/roledb.service'; import { RolesService } from '../roledb/roledb.service';
import { UsersService } from './userdb.service'; import { UsersService } from './userdb.service';
// Move some code here so it doesnt make the userdb service gigantic
@Injectable() @Injectable()
export class UserRolesService { export class UserRolesService {
constructor(private usersService: UsersService, private rolesService: RolesService){} constructor(
private usersService: UsersService,
private rolesService: RolesService,
) {}
// Permissions and roles // Permissions and roles
public async getPermissions( public async getPermissions(
user: string | EUserBackend, user: string | EUserBackend,
): AsyncFailable<Permissions> { ): AsyncFailable<Permissions> {
@@ -27,7 +32,7 @@ export class UserRolesService {
const userToModify = await this.usersService.resolve(user); const userToModify = await this.usersService.resolve(user);
if (HasFailed(userToModify)) return userToModify; if (HasFailed(userToModify)) return userToModify;
const newRoles = [...new Set([...userToModify.roles, ...roles])]; const newRoles = makeUnique([...userToModify.roles, ...roles]);
return this.usersService.setRoles(userToModify, newRoles); return this.usersService.setRoles(userToModify, newRoles);
} }

View File

@@ -41,13 +41,13 @@ export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) {
const permissions = this.extractPermissions(context); const permissions = this.extractPermissions(context);
if (HasFailed(permissions)) { if (HasFailed(permissions)) {
this.logger.warn('222' + permissions.getReason()); this.logger.warn('Route Permissions: ' + permissions.getReason());
throw new InternalServerErrorException(); throw new InternalServerErrorException();
} }
const userPermissions = await this.userRolesService.getPermissions(user); const userPermissions = await this.userRolesService.getPermissions(user);
if (HasFailed(userPermissions)) { if (HasFailed(userPermissions)) {
this.logger.warn('111' + userPermissions.getReason()); this.logger.warn('User Permissions: ' + userPermissions.getReason());
throw new InternalServerErrorException(); throw new InternalServerErrorException();
} }

View File

@@ -25,6 +25,6 @@ export class DemoManagerService {
private async executeAsync() { private async executeAsync() {
this.logger.log('Executing demo cleanup'); this.logger.log('Executing demo cleanup');
await this.imagesService.deleteAll(); await this.imagesService.deleteAll(true);
} }
} }

View File

@@ -0,0 +1,8 @@
export function makeUnique<T>(arr: T[]): T[] {
return arr.reduce(function (accum, current) {
if (accum.indexOf(current) < 0) {
accum.push(current);
}
return accum;
}, [] as T[]);
}