reconsider logging levels
This commit is contained in:
parent
5ca8516560
commit
4612a9ea6f
|
@ -25,8 +25,8 @@ export class ImageDBService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
imageEntity = await this.imageRepository.save(imageEntity);
|
imageEntity = await this.imageRepository.save(imageEntity);
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return imageEntity;
|
return imageEntity;
|
||||||
|
@ -48,8 +48,8 @@ export class ImageDBService {
|
||||||
return found as B extends undefined
|
return found as B extends undefined
|
||||||
? EImageBackend
|
? EImageBackend
|
||||||
: Required<EImageBackend>;
|
: Required<EImageBackend>;
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,8 +68,8 @@ export class ImageDBService {
|
||||||
|
|
||||||
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) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,8 +77,8 @@ export class ImageDBService {
|
||||||
try {
|
try {
|
||||||
const result = await this.imageRepository.delete({ id });
|
const result = await this.imageRepository.delete({ id });
|
||||||
if (result.affected === 0) return Fail('Image not found');
|
if (result.affected === 0) return Fail('Image not found');
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -89,8 +89,8 @@ export class ImageDBService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.imageRepository.delete({});
|
await this.imageRepository.delete({});
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ export class PreferenceDefaultsService {
|
||||||
if (envSecret) {
|
if (envSecret) {
|
||||||
return envSecret;
|
return envSecret;
|
||||||
} else {
|
} else {
|
||||||
this.logger.warn(
|
this.logger.log(
|
||||||
'Since no JWT secret was provided, a random one will be generated and saved',
|
'Since no JWT secret was provided, a random one will be generated and saved',
|
||||||
);
|
);
|
||||||
return generateRandomString(64);
|
return generateRandomString(64);
|
||||||
|
|
|
@ -45,9 +45,8 @@ export class SysPreferenceService {
|
||||||
await this.sysPreferenceRepository.upsert(sysPreference, {
|
await this.sysPreferenceRepository.upsert(sysPreference, {
|
||||||
conflictPaths: ['key'],
|
conflictPaths: ['key'],
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
this.logger.warn(e);
|
return Fail(e);
|
||||||
return Fail('Could not save preference');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return
|
// Return
|
||||||
|
@ -75,16 +74,14 @@ export class SysPreferenceService {
|
||||||
}),
|
}),
|
||||||
() => this.saveDefault(validatedKey as SysPreference),
|
() => this.saveDefault(validatedKey as SysPreference),
|
||||||
);
|
);
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
this.logger.warn(e);
|
return Fail(e);
|
||||||
return Fail('Could not get preference');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
const result = ESysPreferenceSchema.safeParse(foundSysPreference);
|
const result = ESysPreferenceSchema.safeParse(foundSysPreference);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
this.logger.warn(result.error);
|
return Fail(result.error);
|
||||||
return Fail('Invalid preference');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return
|
// Return
|
||||||
|
@ -157,8 +154,7 @@ export class SysPreferenceService {
|
||||||
// It should already be valid, but these two validators might go out of sync
|
// It should already be valid, but these two validators might go out of sync
|
||||||
const result = ESysPreferenceSchema.safeParse(verifySysPreference);
|
const result = ESysPreferenceSchema.safeParse(verifySysPreference);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
this.logger.warn(result.error);
|
return Fail(result.error);
|
||||||
return Fail('Invalid preference');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.data;
|
return result.data;
|
||||||
|
|
|
@ -46,9 +46,8 @@ export class UsrPreferenceService {
|
||||||
await this.usrPreferenceRepository.upsert(usrPreference, {
|
await this.usrPreferenceRepository.upsert(usrPreference, {
|
||||||
conflictPaths: ['key', 'userId'],
|
conflictPaths: ['key', 'userId'],
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
this.logger.warn(e);
|
return Fail(e);
|
||||||
return Fail('Could not save preference');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return
|
// Return
|
||||||
|
@ -80,16 +79,14 @@ export class UsrPreferenceService {
|
||||||
}),
|
}),
|
||||||
() => this.saveDefault(userid, validatedKey as UsrPreference),
|
() => this.saveDefault(userid, validatedKey as UsrPreference),
|
||||||
);
|
);
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
this.logger.warn(e);
|
return Fail(e);
|
||||||
return Fail('Could not get preference');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
const result = EUsrPreferenceSchema.safeParse(foundUsrPreference);
|
const result = EUsrPreferenceSchema.safeParse(foundUsrPreference);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
this.logger.warn(result.error);
|
return Fail(result.error);
|
||||||
return Fail('Invalid preference');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return
|
// Return
|
||||||
|
@ -198,8 +195,7 @@ export class UsrPreferenceService {
|
||||||
// It should already be valid, but these two validators might go out of sync
|
// It should already be valid, but these two validators might go out of sync
|
||||||
const result = EUsrPreferenceSchema.safeParse(verifySysPreference);
|
const result = EUsrPreferenceSchema.safeParse(verifySysPreference);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
this.logger.warn(result.error);
|
return Fail(result.error);
|
||||||
return Fail('Invalid preference');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.data;
|
return result.data;
|
||||||
|
|
|
@ -3,7 +3,11 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { EarlyConfigModule } from '../../config/early/early-config.module';
|
import { EarlyConfigModule } from '../../config/early/early-config.module';
|
||||||
import { HostConfigService } from '../../config/early/host.config.service';
|
import { HostConfigService } from '../../config/early/host.config.service';
|
||||||
import { ImmutableRolesList, SystemRoleDefaults, UndeletableRolesList } from '../../models/constants/roles.const';
|
import {
|
||||||
|
ImmutableRolesList,
|
||||||
|
SystemRoleDefaults,
|
||||||
|
UndeletableRolesList
|
||||||
|
} from '../../models/constants/roles.const';
|
||||||
import { ERoleBackend } from '../../models/entities/role.entity';
|
import { ERoleBackend } from '../../models/entities/role.entity';
|
||||||
import { RolesService } from './role-db.service';
|
import { RolesService } from './role-db.service';
|
||||||
|
|
||||||
|
@ -42,7 +46,7 @@ export class RolesModule implements OnModuleInit {
|
||||||
private async ensureSystemRolesExist() {
|
private async ensureSystemRolesExist() {
|
||||||
// The UndeletableRolesList is also the list of systemroles
|
// 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.verbose(`Ensuring system role "${systemRole}" exists`);
|
||||||
|
|
||||||
const exists = await this.rolesService.exists(systemRole);
|
const exists = await this.rolesService.exists(systemRole);
|
||||||
if (exists) continue;
|
if (exists) continue;
|
||||||
|
@ -65,7 +69,7 @@ export class RolesModule implements OnModuleInit {
|
||||||
// They therefore do have to be kept up to date from the backend
|
// 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.verbose(
|
||||||
`Updating permissions for immutable role "${immutableRole}"`,
|
`Updating permissions for immutable role "${immutableRole}"`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,8 @@ export class RolesService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await this.rolesRepository.save(role, { reload: true });
|
return await this.rolesRepository.save(role, { reload: true });
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,8 +52,8 @@ export class RolesService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await this.rolesRepository.remove(roleToModify);
|
return await this.rolesRepository.remove(roleToModify);
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,8 +118,8 @@ export class RolesService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await this.rolesRepository.save(roleToModify);
|
return await this.rolesRepository.save(roleToModify);
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,8 +131,8 @@ export class RolesService {
|
||||||
|
|
||||||
if (!found) return Fail('Role not found');
|
if (!found) return Fail('Role not found');
|
||||||
return found;
|
return found;
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,8 +141,8 @@ export class RolesService {
|
||||||
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;
|
return found;
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,8 +158,8 @@ export class RolesService {
|
||||||
await this.rolesRepository.delete({
|
await this.rolesRepository.delete({
|
||||||
name: In(UndeletableRolesList),
|
name: In(UndeletableRolesList),
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -172,8 +172,7 @@ export class RolesService {
|
||||||
} else {
|
} else {
|
||||||
const result = ERoleSchema.safeParse(role);
|
const result = ERoleSchema.safeParse(role);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
this.logger.warn(result.error);
|
return Fail(result.error);
|
||||||
return Fail('Invalid role');
|
|
||||||
}
|
}
|
||||||
// This is safe
|
// This is safe
|
||||||
return result.data as ERoleBackend;
|
return result.data as ERoleBackend;
|
||||||
|
|
|
@ -48,7 +48,7 @@ export class UsersModule implements OnModuleInit {
|
||||||
password: string,
|
password: string,
|
||||||
roles: string[],
|
roles: string[],
|
||||||
) {
|
) {
|
||||||
this.logger.debug(`Ensuring user "${username}" exists`);
|
this.logger.verbose(`Ensuring user "${username}" exists`);
|
||||||
|
|
||||||
const exists = await this.usersService.exists(username);
|
const exists = await this.usersService.exists(username);
|
||||||
if (exists) return;
|
if (exists) return;
|
||||||
|
|
|
@ -64,8 +64,8 @@ export class UsersService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await this.usersRepository.save(user);
|
return await this.usersRepository.save(user);
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,8 +79,8 @@ export class UsersService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await this.usersRepository.remove(userToModify);
|
return await this.usersRepository.remove(userToModify);
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,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");
|
this.logger.verbose("User tried to modify system user, failed silently");
|
||||||
return userToModify;
|
return userToModify;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,8 +108,8 @@ export class UsersService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await this.usersRepository.save(userToModify);
|
return await this.usersRepository.save(userToModify);
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,8 +124,7 @@ export class UsersService {
|
||||||
.where('roles @> ARRAY[:role]', { role })
|
.where('roles @> ARRAY[:role]', { role })
|
||||||
.execute();
|
.execute();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error(e);
|
return Fail(e);
|
||||||
return Fail("Couldn't remove role from everyone");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -150,8 +149,8 @@ export class UsersService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
userToModify = await this.usersRepository.save(userToModify);
|
userToModify = await this.usersRepository.save(userToModify);
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return userToModify;
|
return userToModify;
|
||||||
|
@ -193,8 +192,8 @@ export class UsersService {
|
||||||
|
|
||||||
if (!found) return Fail('User not found');
|
if (!found) return Fail('User not found');
|
||||||
return found;
|
return found;
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,8 +205,8 @@ export class UsersService {
|
||||||
|
|
||||||
if (!found) return Fail('User not found');
|
if (!found) return Fail('User not found');
|
||||||
return found as EUserBackend;
|
return found as EUserBackend;
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,8 +222,8 @@ export class UsersService {
|
||||||
take: count,
|
take: count,
|
||||||
skip: count * page,
|
skip: count * page,
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Fail(e?.message);
|
return Fail(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,254 +0,0 @@
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
import * as bcrypt from 'bcrypt';
|
|
||||||
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.dto';
|
|
||||||
import {
|
|
||||||
AsyncFailable,
|
|
||||||
Fail,
|
|
||||||
HasFailed,
|
|
||||||
HasSuccess
|
|
||||||
} from 'picsur-shared/dist/types';
|
|
||||||
import { makeUnique } from 'picsur-shared/dist/util/unique';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { Permissions } from '../../models/constants/permissions.const';
|
|
||||||
import {
|
|
||||||
DefaultRolesList,
|
|
||||||
SoulBoundRolesList
|
|
||||||
} from '../../models/constants/roles.const';
|
|
||||||
import {
|
|
||||||
ImmutableUsersList,
|
|
||||||
LockedLoginUsersList,
|
|
||||||
UndeletableUsersList
|
|
||||||
} from '../../models/constants/special-users.const';
|
|
||||||
import { EUserBackend } from '../../models/entities/user.entity';
|
|
||||||
import { GetCols } from '../../models/util/collection';
|
|
||||||
import { SysPreferenceService } from '../preference-db/sys-preference-db.service';
|
|
||||||
import { RolesService } from '../role-db/role-db.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class UsersService {
|
|
||||||
private readonly logger = new Logger('UsersService');
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(EUserBackend)
|
|
||||||
private usersRepository: Repository<EUserBackend>,
|
|
||||||
private rolesService: RolesService,
|
|
||||||
private prefService: SysPreferenceService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
// Creation and deletion
|
|
||||||
|
|
||||||
public async create(
|
|
||||||
username: string,
|
|
||||||
password: string,
|
|
||||||
roles?: string[],
|
|
||||||
// Add option to create "invalid" users, should only be used by system
|
|
||||||
byPassRoleCheck?: boolean,
|
|
||||||
): AsyncFailable<EUserBackend> {
|
|
||||||
if (await this.exists(username)) return Fail('User already exists');
|
|
||||||
|
|
||||||
const strength = await this.getBCryptStrength();
|
|
||||||
const hashedPassword = await bcrypt.hash(password, strength);
|
|
||||||
|
|
||||||
let user = new EUserBackend();
|
|
||||||
user.username = username;
|
|
||||||
user.hashedPassword = hashedPassword;
|
|
||||||
if (byPassRoleCheck) {
|
|
||||||
const rolesToAdd = roles ?? [];
|
|
||||||
user.roles = makeUnique(rolesToAdd);
|
|
||||||
} else {
|
|
||||||
// Strip soulbound roles and add default roles
|
|
||||||
const rolesToAdd = this.filterAddedRoles(roles ?? []);
|
|
||||||
user.roles = makeUnique([...DefaultRolesList, ...rolesToAdd]);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await this.usersRepository.save(user);
|
|
||||||
} catch (e: any) {
|
|
||||||
return Fail(e?.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async delete(uuid: string): AsyncFailable<EUserBackend> {
|
|
||||||
const userToModify = await this.findOne(uuid);
|
|
||||||
if (HasFailed(userToModify)) return userToModify;
|
|
||||||
|
|
||||||
if (UndeletableUsersList.includes(userToModify.username)) {
|
|
||||||
return Fail('Cannot delete system user');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await this.usersRepository.remove(userToModify);
|
|
||||||
} catch (e: any) {
|
|
||||||
return Fail(e?.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updating
|
|
||||||
|
|
||||||
public async setRoles(
|
|
||||||
uuid: string,
|
|
||||||
roles: string[],
|
|
||||||
): AsyncFailable<EUserBackend> {
|
|
||||||
const userToModify = await this.findOne(uuid);
|
|
||||||
if (HasFailed(userToModify)) return userToModify;
|
|
||||||
|
|
||||||
if (ImmutableUsersList.includes(userToModify.username)) {
|
|
||||||
// Just fail silently
|
|
||||||
this.logger.log("Can't modify system user");
|
|
||||||
return userToModify;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rolesToKeep = userToModify.roles.filter((role) =>
|
|
||||||
SoulBoundRolesList.includes(role),
|
|
||||||
);
|
|
||||||
const rolesToAdd = this.filterAddedRoles(roles);
|
|
||||||
const newRoles = makeUnique([...rolesToKeep, ...rolesToAdd]);
|
|
||||||
userToModify.roles = newRoles;
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await this.usersRepository.save(userToModify);
|
|
||||||
} catch (e: any) {
|
|
||||||
return Fail(e?.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async removeRoleEveryone(role: string): AsyncFailable<true> {
|
|
||||||
try {
|
|
||||||
await this.usersRepository
|
|
||||||
.createQueryBuilder('user')
|
|
||||||
.update()
|
|
||||||
.set({
|
|
||||||
roles: () => 'ARRAY_REMOVE(roles, :role)',
|
|
||||||
})
|
|
||||||
.where('roles @> ARRAY[:role]', { role })
|
|
||||||
.execute();
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.error(e);
|
|
||||||
return Fail("Couldn't remove role from everyone");
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getPermissions(uuid: string): AsyncFailable<Permissions> {
|
|
||||||
const userToModify = await this.findOne(uuid);
|
|
||||||
if (HasFailed(userToModify)) return userToModify;
|
|
||||||
|
|
||||||
return await this.rolesService.getPermissions(userToModify.roles);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async updatePassword(
|
|
||||||
uuid: string,
|
|
||||||
password: string,
|
|
||||||
): AsyncFailable<EUserBackend> {
|
|
||||||
let userToModify = await this.findOne(uuid);
|
|
||||||
if (HasFailed(userToModify)) return userToModify;
|
|
||||||
|
|
||||||
const strength = await this.getBCryptStrength();
|
|
||||||
userToModify.hashedPassword = await bcrypt.hash(password, strength);
|
|
||||||
|
|
||||||
try {
|
|
||||||
userToModify = await this.usersRepository.save(userToModify);
|
|
||||||
} catch (e: any) {
|
|
||||||
return Fail(e?.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return userToModify;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authentication
|
|
||||||
|
|
||||||
async authenticate(
|
|
||||||
username: string,
|
|
||||||
password: string,
|
|
||||||
): AsyncFailable<EUserBackend> {
|
|
||||||
const user = await this.findByUsername(username, true);
|
|
||||||
if (HasFailed(user)) return user;
|
|
||||||
|
|
||||||
if (LockedLoginUsersList.includes(user.username)) {
|
|
||||||
// Error should be kept in backend
|
|
||||||
return Fail('Wrong username');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await bcrypt.compare(password, user.hashedPassword ?? '')))
|
|
||||||
return Fail('Wrong password');
|
|
||||||
|
|
||||||
return await this.findOne(user.id ?? '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listing
|
|
||||||
|
|
||||||
public async findByUsername(
|
|
||||||
username: string,
|
|
||||||
// Also fetch fields that aren't normally sent to the client
|
|
||||||
// (e.g. hashed password)
|
|
||||||
getPrivate: boolean = false,
|
|
||||||
): AsyncFailable<EUserBackend> {
|
|
||||||
try {
|
|
||||||
const found = await this.usersRepository.findOne({
|
|
||||||
where: { username },
|
|
||||||
select: getPrivate ? GetCols(this.usersRepository) : undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!found) return Fail('User not found');
|
|
||||||
return found;
|
|
||||||
} catch (e: any) {
|
|
||||||
return Fail(e?.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async findOne(uuid: string): AsyncFailable<EUserBackend> {
|
|
||||||
try {
|
|
||||||
const found = await this.usersRepository.findOne({
|
|
||||||
where: { id: uuid },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!found) return Fail('User not found');
|
|
||||||
return found as EUserBackend;
|
|
||||||
} catch (e: any) {
|
|
||||||
return Fail(e?.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async findMany(
|
|
||||||
count: number,
|
|
||||||
page: number,
|
|
||||||
): AsyncFailable<EUserBackend[]> {
|
|
||||||
if (count < 1 || page < 0) return Fail('Invalid page');
|
|
||||||
if (count > 100) return Fail('Too many results');
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await this.usersRepository.find({
|
|
||||||
take: count,
|
|
||||||
skip: count * page,
|
|
||||||
});
|
|
||||||
} catch (e: any) {
|
|
||||||
return Fail(e?.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async exists(username: string): Promise<boolean> {
|
|
||||||
return HasSuccess(await this.findByUsername(username));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal
|
|
||||||
|
|
||||||
private filterAddedRoles(roles: string[]): string[] {
|
|
||||||
const filteredRoles = roles.filter(
|
|
||||||
(role) => !SoulBoundRolesList.includes(role),
|
|
||||||
);
|
|
||||||
|
|
||||||
return filteredRoles;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getBCryptStrength(): Promise<number> {
|
|
||||||
const result = await this.prefService.getNumberPreference(
|
|
||||||
SysPreference.BCryptStrength,
|
|
||||||
);
|
|
||||||
if (HasFailed(result)) {
|
|
||||||
return 12;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,10 +7,10 @@ export class HostConfigService {
|
||||||
private readonly logger = new Logger('HostConfigService');
|
private readonly logger = new Logger('HostConfigService');
|
||||||
|
|
||||||
constructor(private configService: ConfigService) {
|
constructor(private configService: ConfigService) {
|
||||||
this.logger.debug('Host: ' + this.getHost());
|
this.logger.log('Host: ' + this.getHost());
|
||||||
this.logger.debug('Port: ' + this.getPort());
|
this.logger.log('Port: ' + this.getPort());
|
||||||
this.logger.debug('Demo: ' + this.isDemo());
|
this.logger.log('Demo: ' + this.isDemo());
|
||||||
this.logger.debug('Demo Interval: ' + this.getDemoInterval() / 1000 + 's');
|
this.logger.log('Demo Interval: ' + this.getDemoInterval() / 1000 + 's');
|
||||||
}
|
}
|
||||||
|
|
||||||
public getHost(): string {
|
public getHost(): string {
|
||||||
|
|
|
@ -7,7 +7,7 @@ export class MultipartConfigService {
|
||||||
private readonly logger = new Logger('MultipartConfigService');
|
private readonly logger = new Logger('MultipartConfigService');
|
||||||
|
|
||||||
constructor(private configService: ConfigService) {
|
constructor(private configService: ConfigService) {
|
||||||
this.logger.debug('Max file size: ' + this.getMaxFileSize());
|
this.logger.log('Max file size: ' + this.getMaxFileSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
public getMaxFileSize(): number {
|
public getMaxFileSize(): number {
|
||||||
|
|
|
@ -16,7 +16,7 @@ export class ServeStaticConfigService
|
||||||
private defaultLocation = join(PackageRoot, '../frontend/dist');
|
private defaultLocation = join(PackageRoot, '../frontend/dist');
|
||||||
|
|
||||||
constructor(private configService: ConfigService) {
|
constructor(private configService: ConfigService) {
|
||||||
this.logger.debug('Static directory: ' + this.getStaticDirectory());
|
this.logger.log('Static directory: ' + this.getStaticDirectory());
|
||||||
}
|
}
|
||||||
|
|
||||||
public getStaticDirectory(): string {
|
public getStaticDirectory(): string {
|
||||||
|
|
|
@ -15,11 +15,12 @@ export class TypeOrmConfigService implements TypeOrmOptionsFactory {
|
||||||
) {
|
) {
|
||||||
const varOptions = this.getTypeOrmServerOptions();
|
const varOptions = this.getTypeOrmServerOptions();
|
||||||
|
|
||||||
this.logger.debug('DB host: ' + varOptions.host);
|
this.logger.log('DB host: ' + varOptions.host);
|
||||||
this.logger.debug('DB port: ' + varOptions.port);
|
this.logger.log('DB port: ' + varOptions.port);
|
||||||
this.logger.debug('DB username: ' + varOptions.username);
|
this.logger.log('DB database: ' + varOptions.database);
|
||||||
this.logger.debug('DB password: ' + varOptions.password);
|
|
||||||
this.logger.debug('DB database: ' + varOptions.database);
|
this.logger.verbose('DB username: ' + varOptions.username);
|
||||||
|
this.logger.verbose('DB password: ' + varOptions.password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTypeOrmServerOptions() {
|
public getTypeOrmServerOptions() {
|
||||||
|
|
|
@ -14,8 +14,8 @@ export class JwtConfigService implements JwtOptionsFactory {
|
||||||
private async printDebug() {
|
private async printDebug() {
|
||||||
const secret = await this.getJwtSecret();
|
const secret = await this.getJwtSecret();
|
||||||
const expiresIn = await this.getJwtExpiresIn();
|
const expiresIn = await this.getJwtExpiresIn();
|
||||||
this.logger.debug('JWT secret: ' + secret);
|
this.logger.verbose('JWT secret: ' + secret);
|
||||||
this.logger.debug('JWT expiresIn: ' + expiresIn);
|
this.logger.verbose('JWT expiresIn: ' + expiresIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getJwtSecret(): Promise<string> {
|
public async getJwtSecret(): Promise<string> {
|
||||||
|
|
|
@ -28,7 +28,7 @@ export class MultiPartPipe implements PipeTransform {
|
||||||
) {
|
) {
|
||||||
let zodSchema = (metadata?.metatype as ZodDtoStatic)?.zodSchema;
|
let zodSchema = (metadata?.metatype as ZodDtoStatic)?.zodSchema;
|
||||||
if (!zodSchema) {
|
if (!zodSchema) {
|
||||||
this.logger.warn('Invalid scheme on multipart body');
|
this.logger.error('Invalid scheme on multipart body');
|
||||||
throw new InternalServerErrorException('Invalid scheme on backend');
|
throw new InternalServerErrorException('Invalid scheme on backend');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,14 +47,14 @@ export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) {
|
||||||
// These are the permissions required to access the route
|
// These are the permissions required to access the route
|
||||||
const permissions = this.extractPermissions(context);
|
const permissions = this.extractPermissions(context);
|
||||||
if (HasFailed(permissions)) {
|
if (HasFailed(permissions)) {
|
||||||
this.logger.warn('Route Permissions: ' + permissions.getReason());
|
this.logger.error('Fetching route permission failed: ' + permissions.getReason());
|
||||||
throw new InternalServerErrorException();
|
throw new InternalServerErrorException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// These are the permissions the user has
|
// These are the permissions the user has
|
||||||
const userPermissions = await this.usersService.getPermissions(user.id);
|
const userPermissions = await this.usersService.getPermissions(user.id);
|
||||||
if (HasFailed(userPermissions)) {
|
if (HasFailed(userPermissions)) {
|
||||||
this.logger.warn('User Permissions: ' + userPermissions.getReason());
|
this.logger.warn('Fetching user permissions failed: ' + userPermissions.getReason());
|
||||||
throw new InternalServerErrorException();
|
throw new InternalServerErrorException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ export class DemoManagerModule implements OnModuleInit, OnModuleDestroy {
|
||||||
|
|
||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
if (this.hostConfigService.isDemo()) {
|
if (this.hostConfigService.isDemo()) {
|
||||||
this.logger.log('Demo mode enabled');
|
this.logger.warn('Demo mode enabled, images are ephimeral');
|
||||||
await this.setupDemoMode();
|
await this.setupDemoMode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ export class DemoManagerService {
|
||||||
|
|
||||||
public async setupRoles() {
|
public async setupRoles() {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
'Modifying roles for demo mode, this will not be reverted automatically',
|
'Modifying roles for demo mode, this will have to be reverted manually',
|
||||||
);
|
);
|
||||||
// Could be done manually, but this makes settup up a demo instance quicker
|
// Could be done manually, but this makes settup up a demo instance quicker
|
||||||
this.rolesService.addPermissions('guest', [Permission.ImageUpload]);
|
this.rolesService.addPermissions('guest', [Permission.ImageUpload]);
|
||||||
|
@ -25,7 +25,7 @@ export class DemoManagerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async executeAsync() {
|
private async executeAsync() {
|
||||||
this.logger.debug('Executing demo cleanup');
|
this.logger.verbose('Executing demo cleanup');
|
||||||
await this.imagesService.deleteAll(true);
|
await this.imagesService.deleteAll(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,6 +123,7 @@ export class RolesController {
|
||||||
|
|
||||||
const success = await this.usersService.removeRoleEveryone(role.name);
|
const success = await this.usersService.removeRoleEveryone(role.name);
|
||||||
if (HasFailed(success)) {
|
if (HasFailed(success)) {
|
||||||
|
this.logger.warn(success.getReason());
|
||||||
throw new InternalServerErrorException(
|
throw new InternalServerErrorException(
|
||||||
'Could not remove role from users',
|
'Could not remove role from users',
|
||||||
);
|
);
|
||||||
|
|
|
@ -40,7 +40,7 @@ export class PicsurImgComponent implements OnChanges {
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
let url = this.imageURL ?? '';
|
let url = this.imageURL ?? '';
|
||||||
if (URLRegex.test(url)) {
|
if (URLRegex.test(url)) {
|
||||||
this.update(url).catch((err) => this.logger.error(err));
|
this.update(url).catch(this.logger.error);
|
||||||
} else {
|
} else {
|
||||||
this.state = PicsurImgState.Loading;
|
this.state = PicsurImgState.Loading;
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ export class LoginComponent implements OnInit {
|
||||||
|
|
||||||
const user = await this.userService.login(data.username, data.password);
|
const user = await this.userService.login(data.username, data.password);
|
||||||
if (HasFailed(user)) {
|
if (HasFailed(user)) {
|
||||||
this.logger.warn(user.getReason());
|
this.logger.error(user.getReason());
|
||||||
this.utilService.showSnackBar(
|
this.utilService.showSnackBar(
|
||||||
'Login failed, please try again',
|
'Login failed, please try again',
|
||||||
SnackBarType.Error
|
SnackBarType.Error
|
||||||
|
|
|
@ -54,7 +54,7 @@ export class RegisterComponent implements OnInit {
|
||||||
|
|
||||||
const user = await this.userService.register(data.username, data.password);
|
const user = await this.userService.register(data.username, data.password);
|
||||||
if (HasFailed(user)) {
|
if (HasFailed(user)) {
|
||||||
this.logger.warn(user.getReason());
|
this.logger.error(user.getReason());
|
||||||
this.utilService.showSnackBar(
|
this.utilService.showSnackBar(
|
||||||
'Register failed, please try again',
|
'Register failed, please try again',
|
||||||
SnackBarType.Error
|
SnackBarType.Error
|
||||||
|
@ -65,7 +65,7 @@ export class RegisterComponent implements OnInit {
|
||||||
if (!this.userService.isLoggedIn) {
|
if (!this.userService.isLoggedIn) {
|
||||||
const loginResult = await this.userService.login(data.username, data.password);
|
const loginResult = await this.userService.login(data.username, data.password);
|
||||||
if (HasFailed(loginResult)) {
|
if (HasFailed(loginResult)) {
|
||||||
this.logger.warn(loginResult.getReason());
|
this.logger.error(loginResult.getReason());
|
||||||
this.utilService.showSnackBar(
|
this.utilService.showSnackBar(
|
||||||
'Failed to login after register',
|
'Failed to login after register',
|
||||||
SnackBarType.Error
|
SnackBarType.Error
|
||||||
|
|
|
@ -44,7 +44,7 @@ export class ApiService {
|
||||||
|
|
||||||
const validateResult = sendSchema.safeParse(data);
|
const validateResult = sendSchema.safeParse(data);
|
||||||
if (!validateResult.success) {
|
if (!validateResult.success) {
|
||||||
this.logger.warn(validateResult.error);
|
this.logger.error(validateResult.error);
|
||||||
return Fail('Something went wrong');
|
return Fail('Something went wrong');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ export class ApiService {
|
||||||
|
|
||||||
const validateResult = resultSchema.safeParse(result);
|
const validateResult = resultSchema.safeParse(result);
|
||||||
if (!validateResult.success) {
|
if (!validateResult.success) {
|
||||||
this.logger.warn('result', validateResult.error);
|
this.logger.error(validateResult.error);
|
||||||
return Fail('Something went wrong');
|
return Fail('Something went wrong');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ export class ApiService {
|
||||||
try {
|
try {
|
||||||
return await response.json();
|
return await response.json();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.warn(e);
|
this.logger.error(e);
|
||||||
return Fail('Something went wrong');
|
return Fail('Something went wrong');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,7 @@ export class ApiService {
|
||||||
try {
|
try {
|
||||||
return await response.arrayBuffer();
|
return await response.arrayBuffer();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.warn(e);
|
this.logger.error(e);
|
||||||
return Fail('Something went wrong');
|
return Fail('Something went wrong');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,9 +133,9 @@ export class ApiService {
|
||||||
options.headers = headers;
|
options.headers = headers;
|
||||||
|
|
||||||
return await window.fetch(url, options);
|
return await window.fetch(url, options);
|
||||||
} catch (error: any) {
|
} catch (e) {
|
||||||
this.errorSubject.next({
|
this.errorSubject.next({
|
||||||
error,
|
error: e,
|
||||||
url,
|
url,
|
||||||
});
|
});
|
||||||
return Fail('Network Error');
|
return Fail('Network Error');
|
||||||
|
|
|
@ -59,7 +59,7 @@ export class PermissionService {
|
||||||
return this.userService.live.pipe(Throttle(300)).subscribe(async (user) => {
|
return this.userService.live.pipe(Throttle(300)).subscribe(async (user) => {
|
||||||
const permissions = await this.updatePermissions();
|
const permissions = await this.updatePermissions();
|
||||||
if (HasFailed(permissions)) {
|
if (HasFailed(permissions)) {
|
||||||
this.logger.warn(permissions.getReason());
|
this.logger.error(permissions.getReason());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -44,7 +44,7 @@ export class UserService {
|
||||||
|
|
||||||
const user = await this.extractUser(apikey);
|
const user = await this.extractUser(apikey);
|
||||||
if (HasFailed(user)) {
|
if (HasFailed(user)) {
|
||||||
this.logger.warn(user.getReason());
|
this.logger.error(user.getReason());
|
||||||
await this.logout();
|
await this.logout();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ export class UserService {
|
||||||
|
|
||||||
const fetchedUser = await this.fetchUser();
|
const fetchedUser = await this.fetchUser();
|
||||||
if (HasFailed(fetchedUser)) {
|
if (HasFailed(fetchedUser)) {
|
||||||
this.logger.warn(fetchedUser.getReason());
|
this.logger.error(fetchedUser.getReason());
|
||||||
await this.logout();
|
await this.logout();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ export class UserService {
|
||||||
|
|
||||||
const result = JwtDataSchema.safeParse(decoded);
|
const result = JwtDataSchema.safeParse(decoded);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
this.logger.warn(result.error);
|
this.logger.error(result.error);
|
||||||
return Fail('Invalid token data');
|
return Fail('Invalid token data');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,8 +66,18 @@ export class GlobalLogger {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const processedArgs = args.map(a => {
|
||||||
|
if (typeof a === 'string') {
|
||||||
|
return a;
|
||||||
|
} else if (a instanceof Error) {
|
||||||
|
return a.message;
|
||||||
|
} else {
|
||||||
|
return a.toString();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const styles = LoggerStyles[level];
|
const styles = LoggerStyles[level];
|
||||||
const message = ['%c' + context.source, styles, ...args];
|
const message = ['%c' + context.source, styles, ...processedArgs];
|
||||||
console[LoggerFunctions[level]](...message);
|
console[LoggerFunctions[level]](...message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ export class ApiErrorService {
|
||||||
this.utilService.showSnackBar('Network Error', SnackBarType.Error);
|
this.utilService.showSnackBar('Network Error', SnackBarType.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.warn(error.error);
|
this.logger.error(error.error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,27 @@
|
||||||
// -> Side effects go brrr
|
// -> Side effects go brrr
|
||||||
|
|
||||||
export class Failure {
|
export class Failure {
|
||||||
constructor(private readonly reason?: string) {}
|
constructor(private readonly reason?: string, private readonly stack?: string) {}
|
||||||
|
|
||||||
getReason(): string {
|
getReason(): string {
|
||||||
return this.reason ?? 'Unknown';
|
return this.reason ?? 'Unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStack(): string {
|
||||||
|
return this.stack ?? 'None';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Fail(reason?: string): Failure {
|
export function Fail(reason?: any): Failure {
|
||||||
|
if (typeof reason === 'string') {
|
||||||
return new Failure(reason);
|
return new Failure(reason);
|
||||||
|
} else if(reason instanceof Error) {
|
||||||
|
return new Failure(reason.message, reason.stack);
|
||||||
|
} else if(reason instanceof Failure) {
|
||||||
|
return reason;
|
||||||
|
} else {
|
||||||
|
return new Failure('Converted(' + reason + ')');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Failable<T> = T | Failure;
|
export type Failable<T> = T | Failure;
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"noImplicitThis": true,
|
"noImplicitThis": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
|
"useUnknownInCatchVariables": true,
|
||||||
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
|
|
Loading…
Reference in a new issue