add user role api
This commit is contained in:
parent
1febcd8147
commit
6d2e950230
|
@ -36,7 +36,7 @@ export class RolesModule implements OnModuleInit {
|
||||||
|
|
||||||
private async nukeRoles() {
|
private async nukeRoles() {
|
||||||
this.logger.error('Nuking system roles');
|
this.logger.error('Nuking system roles');
|
||||||
const result = this.rolesService.nukeSystemRoles(true);
|
const result = await this.rolesService.nukeSystemRoles(true);
|
||||||
if (HasFailed(result)) {
|
if (HasFailed(result)) {
|
||||||
this.logger.error(`Failed to nuke roles because: ${result.getReason()}`);
|
this.logger.error(`Failed to nuke roles because: ${result.getReason()}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,7 @@ export class RolesService {
|
||||||
where: { name },
|
where: { name },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!found) return Fail('User not found');
|
if (!found) return Fail('Role not found');
|
||||||
return found as ERoleBackend;
|
return found as ERoleBackend;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return Fail(e?.message);
|
return Fail(e?.message);
|
||||||
|
|
|
@ -88,43 +88,46 @@ export class UsersService {
|
||||||
public async addRoles(
|
public async addRoles(
|
||||||
user: string | EUserBackend,
|
user: string | EUserBackend,
|
||||||
roles: Roles,
|
roles: Roles,
|
||||||
): AsyncFailable<true> {
|
): AsyncFailable<EUserBackend> {
|
||||||
const userToModify = await this.resolve(user);
|
const userToModify = await this.resolve(user);
|
||||||
if (HasFailed(userToModify)) return userToModify;
|
if (HasFailed(userToModify)) return userToModify;
|
||||||
|
|
||||||
// This is stupid
|
const newRoles = [...new Set([...userToModify.roles, ...roles])];
|
||||||
userToModify.roles = [...new Set([...userToModify.roles, ...roles])];
|
|
||||||
|
|
||||||
try {
|
return this.setRoles(userToModify, newRoles);
|
||||||
await this.usersRepository.save(userToModify);
|
|
||||||
} catch (e: any) {
|
|
||||||
return Fail(e?.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async removeRoles(
|
public async removeRoles(
|
||||||
user: string | EUserBackend,
|
user: string | EUserBackend,
|
||||||
roles: Roles,
|
roles: Roles,
|
||||||
): AsyncFailable<true> {
|
): AsyncFailable<EUserBackend> {
|
||||||
const userToModify = await this.resolve(user);
|
const userToModify = await this.resolve(user);
|
||||||
if (HasFailed(userToModify)) return userToModify;
|
if (HasFailed(userToModify)) return userToModify;
|
||||||
|
|
||||||
// Make sure we don't remove unremovable roles
|
const newRoles = userToModify.roles.filter((role) => !roles.includes(role));
|
||||||
roles = roles.filter((role) => !PermanentRolesList.includes(role));
|
|
||||||
|
|
||||||
userToModify.roles = userToModify.roles.filter(
|
return this.setRoles(userToModify, newRoles);
|
||||||
(role) => !roles.includes(role),
|
}
|
||||||
|
|
||||||
|
public async setRoles(
|
||||||
|
user: string | EUserBackend,
|
||||||
|
roles: Roles,
|
||||||
|
): AsyncFailable<EUserBackend> {
|
||||||
|
const userToModify = await this.resolve(user);
|
||||||
|
if (HasFailed(userToModify)) return userToModify;
|
||||||
|
|
||||||
|
const rolesToKeep = userToModify.roles.filter((role) =>
|
||||||
|
PermanentRolesList.includes(role),
|
||||||
);
|
);
|
||||||
|
const newRoles = [...new Set([...rolesToKeep, ...roles])];
|
||||||
|
|
||||||
|
userToModify.roles = newRoles;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.usersRepository.save(userToModify);
|
return await this.usersRepository.save(userToModify);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return Fail(e?.message);
|
return Fail(e?.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findOne<B extends true | undefined = undefined>(
|
public async findOne<B extends true | undefined = undefined>(
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {
|
||||||
ArgumentsHost, Catch, ExceptionFilter, HttpException
|
ArgumentsHost, Catch, ExceptionFilter, HttpException
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { FastifyReply, FastifyRequest } from 'fastify';
|
import { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import { ApiErrorResponse } from 'picsur-shared/dist/dto/api.dto';
|
import { ApiErrorResponse } from 'picsur-shared/dist/dto/api';
|
||||||
|
|
||||||
@Catch(HttpException)
|
@Catch(HttpException)
|
||||||
export class MainExceptionFilter implements ExceptionFilter {
|
export class MainExceptionFilter implements ExceptionFilter {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {
|
||||||
CallHandler, ExecutionContext, Injectable,
|
CallHandler, ExecutionContext, Injectable,
|
||||||
NestInterceptor
|
NestInterceptor
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiResponse } from 'picsur-shared/dist/dto/api.dto';
|
import { ApiResponse } from 'picsur-shared/dist/dto/api';
|
||||||
import { map, Observable } from 'rxjs';
|
import { map, Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { instanceToPlain, plainToClass } from 'class-transformer';
|
import { instanceToPlain, plainToClass } from 'class-transformer';
|
||||||
import { validate } from 'class-validator';
|
import { validate } from 'class-validator';
|
||||||
import { JwtDataDto } from 'picsur-shared/dist/dto/auth.dto';
|
import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto';
|
||||||
import { EUserBackend } from '../../models/entities/user.entity';
|
import { EUserBackend } from '../../models/entities/user.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
|
@ -8,8 +8,9 @@ import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { plainToClass } from 'class-transformer';
|
import { plainToClass } from 'class-transformer';
|
||||||
import { validate } from 'class-validator';
|
import { validate } from 'class-validator';
|
||||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||||
import { JwtDataDto } from 'picsur-shared/dist/dto/auth.dto';
|
import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto';
|
||||||
import { EUserBackend } from '../../../models/entities/user.entity';
|
import { EUserBackend } from '../../../models/entities/user.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||||
private readonly logger = new Logger('JwtStrategy');
|
private readonly logger = new Logger('JwtStrategy');
|
||||||
|
|
|
@ -42,13 +42,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(permissions.getReason());
|
this.logger.warn("222"+permissions.getReason());
|
||||||
throw new InternalServerErrorException();
|
throw new InternalServerErrorException();
|
||||||
}
|
}
|
||||||
|
|
||||||
const userPermissions = await this.usersService.getPermissions(user);
|
const userPermissions = await this.usersService.getPermissions(user);
|
||||||
if (HasFailed(userPermissions)) {
|
if (HasFailed(userPermissions)) {
|
||||||
this.logger.warn(userPermissions.getReason());
|
this.logger.warn("111"+userPermissions.getReason());
|
||||||
throw new InternalServerErrorException();
|
throw new InternalServerErrorException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { AuthModule } from './auth/auth.module';
|
import { UserApiModule } from './auth/user.module';
|
||||||
import { ExperimentModule } from './experiment/experiment.module';
|
import { ExperimentModule } from './experiment/experiment.module';
|
||||||
import { InfoModule } from './info/info.module';
|
import { InfoModule } from './info/info.module';
|
||||||
import { PrefModule } from './pref/pref.module';
|
import { PrefModule } from './pref/pref.module';
|
||||||
|
@ -7,7 +7,7 @@ import { RolesApiModule } from './roles/roles.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
AuthModule,
|
UserApiModule,
|
||||||
PrefModule,
|
PrefModule,
|
||||||
ExperimentModule,
|
ExperimentModule,
|
||||||
InfoModule,
|
InfoModule,
|
||||||
|
|
|
@ -7,13 +7,19 @@ import {
|
||||||
Post,
|
Post,
|
||||||
Request
|
Request
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { AuthUserInfoRequest } from 'picsur-shared/dist/dto/api/auth.dto';
|
|
||||||
import {
|
import {
|
||||||
AuthDeleteRequest,
|
UserDeleteRequest,
|
||||||
AuthLoginResponse,
|
UserDeleteResponse,
|
||||||
AuthMeResponse,
|
UserInfoRequest,
|
||||||
AuthRegisterRequest
|
UserInfoResponse,
|
||||||
} from 'picsur-shared/dist/dto/auth.dto';
|
UserListResponse,
|
||||||
|
UserLoginResponse,
|
||||||
|
UserMeResponse,
|
||||||
|
UserRegisterRequest,
|
||||||
|
UserRegisterResponse,
|
||||||
|
UserUpdateRolesRequest,
|
||||||
|
UserUpdateRolesResponse
|
||||||
|
} from 'picsur-shared/dist/dto/api/user.dto';
|
||||||
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 {
|
import {
|
||||||
|
@ -23,8 +29,8 @@ import {
|
||||||
import { AuthManagerService } from '../../../managers/auth/auth.service';
|
import { AuthManagerService } from '../../../managers/auth/auth.service';
|
||||||
import AuthFasityRequest from '../../../models/dto/authrequest.dto';
|
import AuthFasityRequest from '../../../models/dto/authrequest.dto';
|
||||||
|
|
||||||
@Controller('api/auth')
|
@Controller('api/user')
|
||||||
export class AuthController {
|
export class UserController {
|
||||||
private readonly logger = new Logger('AuthController');
|
private readonly logger = new Logger('AuthController');
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -34,20 +40,17 @@ export class AuthController {
|
||||||
|
|
||||||
@Post('login')
|
@Post('login')
|
||||||
@UseLocalAuth('user-login')
|
@UseLocalAuth('user-login')
|
||||||
async login(@Request() req: AuthFasityRequest) {
|
async login(@Request() req: AuthFasityRequest): Promise<UserLoginResponse> {
|
||||||
const response: AuthLoginResponse = {
|
return {
|
||||||
jwt_token: await this.authService.createToken(req.user),
|
jwt_token: await this.authService.createToken(req.user),
|
||||||
};
|
};
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('register')
|
@Post('register')
|
||||||
@RequiredPermissions('user-register')
|
@RequiredPermissions('user-register')
|
||||||
async register(
|
async register(
|
||||||
@Request() req: AuthFasityRequest,
|
@Body() register: UserRegisterRequest,
|
||||||
@Body() register: AuthRegisterRequest,
|
): Promise<UserRegisterResponse> {
|
||||||
) {
|
|
||||||
const user = await this.usersService.create(
|
const user = await this.usersService.create(
|
||||||
register.username,
|
register.username,
|
||||||
register.password,
|
register.password,
|
||||||
|
@ -58,7 +61,11 @@ export class AuthController {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (register.isAdmin) {
|
if (register.isAdmin) {
|
||||||
await this.usersService.addRoles(user, ['admin']);
|
const result = await this.usersService.addRoles(user, ['admin']);
|
||||||
|
if (HasFailed(result)) {
|
||||||
|
this.logger.warn(result.getReason());
|
||||||
|
throw new InternalServerErrorException('Could not add admin role');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
|
@ -67,9 +74,8 @@ export class AuthController {
|
||||||
@Post('delete')
|
@Post('delete')
|
||||||
@RequiredPermissions('user-manage')
|
@RequiredPermissions('user-manage')
|
||||||
async delete(
|
async delete(
|
||||||
@Request() req: AuthFasityRequest,
|
@Body() deleteData: UserDeleteRequest,
|
||||||
@Body() deleteData: AuthDeleteRequest,
|
): Promise<UserDeleteResponse> {
|
||||||
) {
|
|
||||||
const user = await this.usersService.delete(deleteData.username);
|
const user = await this.usersService.delete(deleteData.username);
|
||||||
if (HasFailed(user)) {
|
if (HasFailed(user)) {
|
||||||
this.logger.warn(user.getReason());
|
this.logger.warn(user.getReason());
|
||||||
|
@ -79,9 +85,27 @@ export class AuthController {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('roles')
|
||||||
|
@RequiredPermissions('user-manage')
|
||||||
|
async setPermissions(
|
||||||
|
@Body() body: UserUpdateRolesRequest,
|
||||||
|
): Promise<UserUpdateRolesResponse> {
|
||||||
|
const updatedUser = await this.usersService.setRoles(
|
||||||
|
body.username,
|
||||||
|
body.roles,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (HasFailed(updatedUser)) {
|
||||||
|
this.logger.warn(updatedUser.getReason());
|
||||||
|
throw new InternalServerErrorException('Could not update user');
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedUser;
|
||||||
|
}
|
||||||
|
|
||||||
@Post('info')
|
@Post('info')
|
||||||
@RequiredPermissions('user-manage')
|
@RequiredPermissions('user-manage')
|
||||||
async getUser(@Body() body: AuthUserInfoRequest) {
|
async getUser(@Body() body: UserInfoRequest): Promise<UserInfoResponse> {
|
||||||
const user = await this.usersService.findOne(body.username);
|
const user = await this.usersService.findOne(body.username);
|
||||||
if (HasFailed(user)) {
|
if (HasFailed(user)) {
|
||||||
this.logger.warn(user.getReason());
|
this.logger.warn(user.getReason());
|
||||||
|
@ -93,30 +117,32 @@ export class AuthController {
|
||||||
|
|
||||||
@Get('list')
|
@Get('list')
|
||||||
@RequiredPermissions('user-manage')
|
@RequiredPermissions('user-manage')
|
||||||
async listUsers(@Request() req: AuthFasityRequest) {
|
async listUsers(): Promise<UserListResponse> {
|
||||||
const users = this.usersService.findAll();
|
const users = await this.usersService.findAll();
|
||||||
if (HasFailed(users)) {
|
if (HasFailed(users)) {
|
||||||
this.logger.warn(users.getReason());
|
this.logger.warn(users.getReason());
|
||||||
throw new InternalServerErrorException('Could not list users');
|
throw new InternalServerErrorException('Could not list users');
|
||||||
}
|
}
|
||||||
|
|
||||||
return users;
|
return {
|
||||||
|
users,
|
||||||
|
total: users.length,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('me')
|
@Get('me')
|
||||||
@RequiredPermissions('user-view')
|
@RequiredPermissions('user-view')
|
||||||
async me(@Request() req: AuthFasityRequest) {
|
async me(@Request() req: AuthFasityRequest): Promise<UserMeResponse> {
|
||||||
const permissions = await this.usersService.getPermissions(req.user);
|
const permissions = await this.usersService.getPermissions(req.user);
|
||||||
if (HasFailed(permissions)) {
|
if (HasFailed(permissions)) {
|
||||||
this.logger.warn(permissions.getReason());
|
this.logger.warn(permissions.getReason());
|
||||||
throw new InternalServerErrorException('Could not get permissions');
|
throw new InternalServerErrorException('Could not get permissions');
|
||||||
}
|
}
|
||||||
|
|
||||||
const meResponse: AuthMeResponse = new AuthMeResponse();
|
return {
|
||||||
meResponse.user = req.user;
|
user: req.user,
|
||||||
meResponse.permissions = permissions;
|
permissions,
|
||||||
meResponse.newJwtToken = await this.authService.createToken(req.user);
|
newJwtToken: await this.authService.createToken(req.user),
|
||||||
|
};
|
||||||
return meResponse;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { AuthManagerModule } from '../../../managers/auth/auth.module';
|
import { AuthManagerModule } from '../../../managers/auth/auth.module';
|
||||||
import { AuthController } from './auth.controller';
|
import { UserController } from './user.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [AuthManagerModule],
|
imports: [AuthManagerModule],
|
||||||
controllers: [AuthController],
|
controllers: [UserController],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class UserApiModule {}
|
|
@ -9,11 +9,10 @@ export class InfoController {
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@NoAuth()
|
@NoAuth()
|
||||||
getInfo() {
|
async getInfo(): Promise<InfoResponse> {
|
||||||
const response: InfoResponse = {
|
return {
|
||||||
demo: this.hostConfig.isDemo(),
|
demo: this.hostConfig.isDemo(),
|
||||||
production: this.hostConfig.isProduction(),
|
production: this.hostConfig.isProduction(),
|
||||||
};
|
};
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,10 @@ import {
|
||||||
Post
|
Post
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
SysPreferences,
|
SysPreferenceResponse,
|
||||||
UpdateSysPreferenceRequest
|
UpdateSysPreferenceRequest
|
||||||
} from 'picsur-shared/dist/dto/syspreferences.dto';
|
} from 'picsur-shared/dist/dto/api/pref.dto';
|
||||||
|
import { SysPreferences } from 'picsur-shared/dist/dto/syspreferences.dto';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { SysPreferenceService } from '../../../collections/syspreferencesdb/syspreferencedb.service';
|
import { SysPreferenceService } from '../../../collections/syspreferencesdb/syspreferencedb.service';
|
||||||
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
|
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
|
||||||
|
@ -23,7 +24,7 @@ export class PrefController {
|
||||||
constructor(private prefService: SysPreferenceService) {}
|
constructor(private prefService: SysPreferenceService) {}
|
||||||
|
|
||||||
@Get('sys/:key')
|
@Get('sys/:key')
|
||||||
async getSysPref(@Param('key') key: string) {
|
async getSysPref(@Param('key') key: string): Promise<SysPreferenceResponse> {
|
||||||
const returned = await this.prefService.getPreference(
|
const returned = await this.prefService.getPreference(
|
||||||
key as SysPreferences,
|
key as SysPreferences,
|
||||||
);
|
);
|
||||||
|
@ -39,7 +40,7 @@ export class PrefController {
|
||||||
async setSysPref(
|
async setSysPref(
|
||||||
@Param('key') key: string,
|
@Param('key') key: string,
|
||||||
@Body() body: UpdateSysPreferenceRequest,
|
@Body() body: UpdateSysPreferenceRequest,
|
||||||
) {
|
): Promise<SysPreferenceResponse> {
|
||||||
const value = body.value;
|
const value = body.value;
|
||||||
const returned = await this.prefService.setPreference(
|
const returned = await this.prefService.setPreference(
|
||||||
key as SysPreferences,
|
key as SysPreferences,
|
||||||
|
|
|
@ -8,9 +8,14 @@ import {
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
RoleCreateRequest,
|
RoleCreateRequest,
|
||||||
|
RoleCreateResponse,
|
||||||
RoleDeleteRequest,
|
RoleDeleteRequest,
|
||||||
|
RoleDeleteResponse,
|
||||||
RoleInfoRequest,
|
RoleInfoRequest,
|
||||||
RoleUpdateRequest
|
RoleInfoResponse,
|
||||||
|
RoleListResponse,
|
||||||
|
RoleUpdateRequest,
|
||||||
|
RoleUpdateResponse
|
||||||
} from 'picsur-shared/dist/dto/api/roles.dto';
|
} from 'picsur-shared/dist/dto/api/roles.dto';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { RolesService } from '../../../collections/roledb/roledb.service';
|
import { RolesService } from '../../../collections/roledb/roledb.service';
|
||||||
|
@ -24,19 +29,22 @@ export class RolesController {
|
||||||
constructor(private rolesService: RolesService) {}
|
constructor(private rolesService: RolesService) {}
|
||||||
|
|
||||||
@Get('/list')
|
@Get('/list')
|
||||||
getRoles() {
|
async getRoles(): Promise<RoleListResponse> {
|
||||||
const roles = this.rolesService.findAll();
|
const roles = await this.rolesService.findAll();
|
||||||
if (HasFailed(roles)) {
|
if (HasFailed(roles)) {
|
||||||
this.logger.warn(roles.getReason());
|
this.logger.warn(roles.getReason());
|
||||||
throw new InternalServerErrorException('Could not list roles');
|
throw new InternalServerErrorException('Could not list roles');
|
||||||
}
|
}
|
||||||
|
|
||||||
return roles;
|
return {
|
||||||
|
roles,
|
||||||
|
total: roles.length,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/info')
|
@Post('/info')
|
||||||
getRole(@Body() body: RoleInfoRequest) {
|
async getRole(@Body() body: RoleInfoRequest): Promise<RoleInfoResponse> {
|
||||||
const role = this.rolesService.findOne(body.name);
|
const role = await this.rolesService.findOne(body.name);
|
||||||
if (HasFailed(role)) {
|
if (HasFailed(role)) {
|
||||||
this.logger.warn(role.getReason());
|
this.logger.warn(role.getReason());
|
||||||
throw new InternalServerErrorException('Could not find role');
|
throw new InternalServerErrorException('Could not find role');
|
||||||
|
@ -45,9 +53,11 @@ export class RolesController {
|
||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/permissions/set')
|
@Post('/permissions')
|
||||||
updateRole(@Body() body: RoleUpdateRequest) {
|
async updateRole(
|
||||||
const updatedRole = this.rolesService.setPermissions(
|
@Body() body: RoleUpdateRequest,
|
||||||
|
): Promise<RoleUpdateResponse> {
|
||||||
|
const updatedRole = await this.rolesService.setPermissions(
|
||||||
body.name,
|
body.name,
|
||||||
body.permissions,
|
body.permissions,
|
||||||
);
|
);
|
||||||
|
@ -59,39 +69,11 @@ export class RolesController {
|
||||||
return updatedRole;
|
return updatedRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/permissions/add')
|
|
||||||
addPermissions(@Body() body: RoleUpdateRequest) {
|
|
||||||
const updatedRole = this.rolesService.addPermissions(
|
|
||||||
body.name,
|
|
||||||
body.permissions,
|
|
||||||
);
|
|
||||||
if (HasFailed(updatedRole)) {
|
|
||||||
this.logger.warn(updatedRole.getReason());
|
|
||||||
throw new InternalServerErrorException('Could not add role permissions');
|
|
||||||
}
|
|
||||||
|
|
||||||
return updatedRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('/permissions/remove')
|
|
||||||
removePermissions(@Body() body: RoleUpdateRequest) {
|
|
||||||
const updatedRole = this.rolesService.removePermissions(
|
|
||||||
body.name,
|
|
||||||
body.permissions,
|
|
||||||
);
|
|
||||||
if (HasFailed(updatedRole)) {
|
|
||||||
this.logger.warn(updatedRole.getReason());
|
|
||||||
throw new InternalServerErrorException(
|
|
||||||
'Could not remove role permissions',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return updatedRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('/create')
|
@Post('/create')
|
||||||
createRole(@Body() role: RoleCreateRequest) {
|
async createRole(
|
||||||
const newRole = this.rolesService.create(role.name, role.permissions);
|
@Body() role: RoleCreateRequest,
|
||||||
|
): Promise<RoleCreateResponse> {
|
||||||
|
const newRole = await this.rolesService.create(role.name, role.permissions);
|
||||||
if (HasFailed(newRole)) {
|
if (HasFailed(newRole)) {
|
||||||
this.logger.warn(newRole.getReason());
|
this.logger.warn(newRole.getReason());
|
||||||
throw new InternalServerErrorException('Could not create role');
|
throw new InternalServerErrorException('Could not create role');
|
||||||
|
@ -101,8 +83,10 @@ export class RolesController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/delete')
|
@Post('/delete')
|
||||||
deleteRole(@Body() role: RoleDeleteRequest) {
|
async deleteRole(
|
||||||
const deletedRole = this.rolesService.delete(role.name);
|
@Body() role: RoleDeleteRequest,
|
||||||
|
): Promise<RoleDeleteResponse> {
|
||||||
|
const deletedRole = await this.rolesService.delete(role.name);
|
||||||
if (HasFailed(deletedRole)) {
|
if (HasFailed(deletedRole)) {
|
||||||
this.logger.warn(deletedRole.getReason());
|
this.logger.warn(deletedRole.getReason());
|
||||||
throw new InternalServerErrorException('Could not delete role');
|
throw new InternalServerErrorException('Could not delete role');
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { isHash } from 'class-validator';
|
import { isHash } from 'class-validator';
|
||||||
import { FastifyReply, FastifyRequest } from 'fastify';
|
import { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
|
import { ImageMetaResponse } from 'picsur-shared/dist/dto/api/image.dto';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { MultiPart } from '../../decorators/multipart.decorator';
|
import { MultiPart } from '../../decorators/multipart.decorator';
|
||||||
import { RequiredPermissions } from '../../decorators/permissions.decorator';
|
import { RequiredPermissions } from '../../decorators/permissions.decorator';
|
||||||
|
@ -29,7 +30,7 @@ export class ImageController {
|
||||||
async getImage(
|
async getImage(
|
||||||
@Res({ passthrough: true }) res: FastifyReply,
|
@Res({ passthrough: true }) res: FastifyReply,
|
||||||
@Param('hash') hash: string,
|
@Param('hash') hash: string,
|
||||||
) {
|
): Promise<Buffer> {
|
||||||
if (!isHash(hash, 'sha256')) throw new BadRequestException('Invalid hash');
|
if (!isHash(hash, 'sha256')) throw new BadRequestException('Invalid hash');
|
||||||
|
|
||||||
const image = await this.imagesService.retrieveComplete(hash);
|
const image = await this.imagesService.retrieveComplete(hash);
|
||||||
|
@ -43,7 +44,7 @@ export class ImageController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('meta/:hash')
|
@Get('meta/:hash')
|
||||||
async getImageMeta(@Param('hash') hash: string) {
|
async getImageMeta(@Param('hash') hash: string): Promise<ImageMetaResponse> {
|
||||||
if (!isHash(hash, 'sha256')) throw new BadRequestException('Invalid hash');
|
if (!isHash(hash, 'sha256')) throw new BadRequestException('Invalid hash');
|
||||||
|
|
||||||
const image = await this.imagesService.retrieveInfo(hash);
|
const image = await this.imagesService.retrieveInfo(hash);
|
||||||
|
@ -60,7 +61,7 @@ export class ImageController {
|
||||||
async uploadImage(
|
async uploadImage(
|
||||||
@Req() req: FastifyRequest,
|
@Req() req: FastifyRequest,
|
||||||
@MultiPart(ImageUploadDto) multipart: ImageUploadDto,
|
@MultiPart(ImageUploadDto) multipart: ImageUploadDto,
|
||||||
) {
|
): Promise<ImageMetaResponse> {
|
||||||
const fileBuffer = await multipart.image.toBuffer();
|
const fileBuffer = await multipart.image.toBuffer();
|
||||||
const image = await this.imagesService.upload(fileBuffer);
|
const image = await this.imagesService.upload(fileBuffer);
|
||||||
if (HasFailed(image)) {
|
if (HasFailed(image)) {
|
||||||
|
|
|
@ -4,12 +4,11 @@ import { validate } from 'class-validator';
|
||||||
import {
|
import {
|
||||||
ApiResponse,
|
ApiResponse,
|
||||||
ApiSuccessResponse
|
ApiSuccessResponse
|
||||||
} from 'picsur-shared/dist/dto/api.dto';
|
} from 'picsur-shared/dist/dto/api';
|
||||||
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
|
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { MultiPartRequest } from '../models/multi-part-request';
|
import { MultiPartRequest } from '../models/multi-part-request';
|
||||||
import { KeyService } from './key.service';
|
import { KeyService } from './key.service';
|
||||||
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ImageMetaResponse } from 'picsur-shared/dist/dto/api/image.dto';
|
||||||
import { EImage } from 'picsur-shared/dist/entities/image.entity';
|
import { EImage } from 'picsur-shared/dist/entities/image.entity';
|
||||||
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
|
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { ImageUploadRequest } from '../models/image-upload-request';
|
import { ImageUploadRequest } from '../models/image-upload-request';
|
||||||
|
@ -12,7 +13,7 @@ export class ImageService {
|
||||||
|
|
||||||
public async UploadImage(image: File): AsyncFailable<string> {
|
public async UploadImage(image: File): AsyncFailable<string> {
|
||||||
const result = await this.api.postForm(
|
const result = await this.api.postForm(
|
||||||
EImage,
|
ImageMetaResponse,
|
||||||
'/i',
|
'/i',
|
||||||
new ImageUploadRequest(image)
|
new ImageUploadRequest(image)
|
||||||
);
|
);
|
||||||
|
@ -23,7 +24,7 @@ export class ImageService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async GetImageMeta(image: string): AsyncFailable<EImage> {
|
public async GetImageMeta(image: string): AsyncFailable<EImage> {
|
||||||
return await this.api.get(EImage, `/i/meta/${image}`);
|
return await this.api.get(ImageMetaResponse, `/i/meta/${image}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GetImageURL(image: string): string {
|
public GetImageURL(image: string): string {
|
||||||
|
|
|
@ -3,16 +3,13 @@ import { plainToClass } from 'class-transformer';
|
||||||
import { validate } from 'class-validator';
|
import { validate } from 'class-validator';
|
||||||
import jwt_decode from 'jwt-decode';
|
import jwt_decode from 'jwt-decode';
|
||||||
import {
|
import {
|
||||||
AuthLoginRequest,
|
UserLoginRequest,
|
||||||
AuthLoginResponse,
|
UserLoginResponse,
|
||||||
AuthMeResponse,
|
UserMeResponse
|
||||||
JwtDataDto
|
} from 'picsur-shared/dist/dto/api/user.dto';
|
||||||
} from 'picsur-shared/dist/dto/auth.dto';
|
import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto';
|
||||||
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||||
import {
|
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
|
||||||
AsyncFailable,
|
|
||||||
Fail, HasFailed
|
|
||||||
} from 'picsur-shared/dist/types';
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
import { ApiService } from './api.service';
|
import { ApiService } from './api.service';
|
||||||
import { KeyService } from './key.service';
|
import { KeyService } from './key.service';
|
||||||
|
@ -38,19 +35,19 @@ export class UserService {
|
||||||
private userSubject = new BehaviorSubject<EUser | null>(null);
|
private userSubject = new BehaviorSubject<EUser | null>(null);
|
||||||
|
|
||||||
constructor(private api: ApiService, private key: KeyService) {
|
constructor(private api: ApiService, private key: KeyService) {
|
||||||
this.init().catch(console.error);
|
this.init().catch(this.logger.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async login(username: string, password: string): AsyncFailable<EUser> {
|
public async login(username: string, password: string): AsyncFailable<EUser> {
|
||||||
const request: AuthLoginRequest = {
|
const request: UserLoginRequest = {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await this.api.post(
|
const response = await this.api.post(
|
||||||
AuthLoginRequest,
|
UserLoginRequest,
|
||||||
AuthLoginResponse,
|
UserLoginResponse,
|
||||||
'/api/auth/login',
|
'/api/user/login',
|
||||||
request
|
request
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -117,7 +114,7 @@ export class UserService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchUser(): AsyncFailable<EUser> {
|
private async fetchUser(): AsyncFailable<EUser> {
|
||||||
const got = await this.api.get(AuthMeResponse, '/api/auth/me');
|
const got = await this.api.get(UserMeResponse, '/api/user/me');
|
||||||
if (HasFailed(got)) return got;
|
if (HasFailed(got)) return got;
|
||||||
|
|
||||||
this.key.set(got.newJwtToken);
|
this.key.set(got.newJwtToken);
|
||||||
|
|
|
@ -13,6 +13,8 @@ import { UtilService } from 'src/app/util/util.service';
|
||||||
styleUrls: ['./header.component.scss'],
|
styleUrls: ['./header.component.scss'],
|
||||||
})
|
})
|
||||||
export class HeaderComponent implements OnInit {
|
export class HeaderComponent implements OnInit {
|
||||||
|
private readonly logger = console;
|
||||||
|
|
||||||
private currentUser: EUser | null = null;
|
private currentUser: EUser | null = null;
|
||||||
|
|
||||||
public get user() {
|
public get user() {
|
||||||
|
@ -36,7 +38,7 @@ export class HeaderComponent implements OnInit {
|
||||||
@AutoUnsubscribe()
|
@AutoUnsubscribe()
|
||||||
subscribeUser() {
|
subscribeUser() {
|
||||||
return this.userService.liveUser.subscribe((user) => {
|
return this.userService.liveUser.subscribe((user) => {
|
||||||
console.log('user', user);
|
this.logger.log('user', user);
|
||||||
this.currentUser = user;
|
this.currentUser = user;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
3
shared/src/dto/api/image.dto.ts
Normal file
3
shared/src/dto/api/image.dto.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import { EImage } from '../../entities/image.entity';
|
||||||
|
|
||||||
|
export class ImageMetaResponse extends EImage {}
|
9
shared/src/dto/api/pref.dto.ts
Normal file
9
shared/src/dto/api/pref.dto.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { IsNotEmpty } from 'class-validator';
|
||||||
|
import { ESysPreference } from '../../entities/syspreference.entity';
|
||||||
|
|
||||||
|
export class UpdateSysPreferenceRequest {
|
||||||
|
@IsNotEmpty()
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SysPreferenceResponse extends ESysPreference {}
|
|
@ -1,13 +1,32 @@
|
||||||
import { IsArray, IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
import { Type } from 'class-transformer';
|
||||||
|
import { IsArray, IsDefined, IsEnum, IsInt, IsNotEmpty, IsPositive, IsString, ValidateNested } from 'class-validator';
|
||||||
import { ERole } from '../../entities/role.entity';
|
import { ERole } from '../../entities/role.entity';
|
||||||
import { Permissions, PermissionsList } from '../permissions';
|
import { Permissions, PermissionsList } from '../permissions';
|
||||||
|
|
||||||
|
// RoleInfo
|
||||||
export class RoleInfoRequest {
|
export class RoleInfoRequest {
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class RoleInfoResponse extends ERole {}
|
||||||
|
|
||||||
|
// RoleList
|
||||||
|
export class RoleListResponse {
|
||||||
|
@IsArray()
|
||||||
|
@IsDefined()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => ERole)
|
||||||
|
roles: ERole[];
|
||||||
|
|
||||||
|
@IsInt()
|
||||||
|
@IsPositive()
|
||||||
|
@IsDefined()
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoleUpdate
|
||||||
export class RoleUpdateRequest {
|
export class RoleUpdateRequest {
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@ -17,10 +36,15 @@ export class RoleUpdateRequest {
|
||||||
@IsEnum(PermissionsList, { each: true })
|
@IsEnum(PermissionsList, { each: true })
|
||||||
permissions: Permissions;
|
permissions: Permissions;
|
||||||
}
|
}
|
||||||
|
export class RoleUpdateResponse extends ERole {}
|
||||||
|
|
||||||
|
// RoleCreate
|
||||||
export class RoleCreateRequest extends ERole {}
|
export class RoleCreateRequest extends ERole {}
|
||||||
|
export class RoleCreateResponse extends ERole {}
|
||||||
|
|
||||||
|
// RoleDelete
|
||||||
export class RoleDeleteRequest {
|
export class RoleDeleteRequest {
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
export class RoleDeleteResponse extends ERole {}
|
||||||
|
|
|
@ -3,17 +3,22 @@ import {
|
||||||
IsArray,
|
IsArray,
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
IsDefined,
|
IsDefined,
|
||||||
IsEnum, IsNotEmpty,
|
IsEnum,
|
||||||
|
IsInt,
|
||||||
|
IsNotEmpty,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
|
IsPositive,
|
||||||
IsString,
|
IsString,
|
||||||
ValidateNested
|
ValidateNested
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { EUser } from '../../entities/user.entity';
|
import { EUser } from '../../entities/user.entity';
|
||||||
import { Permissions, PermissionsList } from '../permissions';
|
import { Permissions, PermissionsList } from '../permissions';
|
||||||
|
import { Roles } from '../roles.dto';
|
||||||
|
|
||||||
// Api
|
// Api
|
||||||
|
|
||||||
export class AuthLoginRequest {
|
// UserLogin
|
||||||
|
export class UserLoginRequest {
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsString()
|
@IsString()
|
||||||
username: string;
|
username: string;
|
||||||
|
@ -23,13 +28,14 @@ export class AuthLoginRequest {
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AuthLoginResponse {
|
export class UserLoginResponse {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsDefined()
|
@IsDefined()
|
||||||
jwt_token: string;
|
jwt_token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AuthRegisterRequest {
|
// UserRegister
|
||||||
|
export class UserRegisterRequest {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
username: string;
|
username: string;
|
||||||
|
@ -43,15 +49,42 @@ export class AuthRegisterRequest {
|
||||||
isAdmin?: boolean;
|
isAdmin?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AuthDeleteRequest {
|
export class UserRegisterResponse extends EUser {}
|
||||||
|
|
||||||
|
// UserDelete
|
||||||
|
export class UserDeleteRequest {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
username: string;
|
username: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AuthDeleteResponse extends EUser {}
|
export class UserDeleteResponse extends EUser {}
|
||||||
|
|
||||||
export class AuthMeResponse {
|
// UserInfo
|
||||||
|
export class UserInfoRequest {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserInfoResponse extends EUser {}
|
||||||
|
|
||||||
|
// UserList
|
||||||
|
export class UserListResponse {
|
||||||
|
@IsArray()
|
||||||
|
@IsDefined()
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => EUser)
|
||||||
|
users: EUser[];
|
||||||
|
|
||||||
|
@IsInt()
|
||||||
|
@IsPositive()
|
||||||
|
@IsDefined()
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserMe
|
||||||
|
export class UserMeResponse {
|
||||||
@IsDefined()
|
@IsDefined()
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => EUser)
|
@Type(() => EUser)
|
||||||
|
@ -67,8 +100,16 @@ export class AuthMeResponse {
|
||||||
newJwtToken: string;
|
newJwtToken: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AuthUserInfoRequest {
|
// UserUpdateRoles
|
||||||
|
export class UserUpdateRolesRequest {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
username: string;
|
username: string;
|
||||||
|
|
||||||
|
@IsArray()
|
||||||
|
@IsDefined()
|
||||||
|
@IsString({ each: true })
|
||||||
|
roles: Roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UserUpdateRolesResponse extends EUser {}
|
|
@ -1,4 +1,3 @@
|
||||||
import { IsNotEmpty } from 'class-validator';
|
|
||||||
import tuple from '../types/tuple';
|
import tuple from '../types/tuple';
|
||||||
|
|
||||||
const SysPreferencesTuple = tuple(
|
const SysPreferencesTuple = tuple(
|
||||||
|
@ -9,8 +8,3 @@ const SysPreferencesTuple = tuple(
|
||||||
|
|
||||||
export const SysPreferences: string[] = SysPreferencesTuple;
|
export const SysPreferences: string[] = SysPreferencesTuple;
|
||||||
export type SysPreferences = typeof SysPreferencesTuple[number];
|
export type SysPreferences = typeof SysPreferencesTuple[number];
|
||||||
|
|
||||||
export class UpdateSysPreferenceRequest {
|
|
||||||
@IsNotEmpty()
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,6 +14,8 @@ export type Failable<T> = T | Failure;
|
||||||
|
|
||||||
export type AsyncFailable<T> = Promise<Failable<T>>;
|
export type AsyncFailable<T> = Promise<Failable<T>>;
|
||||||
|
|
||||||
|
// TODO: prevent promise from being allowed in these 2 functions
|
||||||
|
|
||||||
export function HasFailed<T>(failable: Failable<T>): failable is Failure {
|
export function HasFailed<T>(failable: Failable<T>): failable is Failure {
|
||||||
return failable instanceof Failure;
|
return failable instanceof Failure;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue