add permission endpoint

This commit is contained in:
rubikscraft
2022-03-12 21:13:58 +01:00
parent b463344ec7
commit 52434efdd7
8 changed files with 87 additions and 18 deletions

View File

@@ -8,7 +8,7 @@ export const RequiredPermissions = (...permissions: Permissions) => {
};
// Easy to read roles
export const NoAuth = () => RequiredPermissions();
export const NoPermissions = () => RequiredPermissions();
export const UseLocalAuth = (...permissions: Permissions) =>
CombineDecorators(

View File

@@ -14,6 +14,7 @@ import {
UserInfoResponse,
UserListResponse,
UserLoginResponse,
UserMePermissionsResponse,
UserMeResponse,
UserRegisterRequest,
UserRegisterResponse,
@@ -23,6 +24,7 @@ import {
import { HasFailed } from 'picsur-shared/dist/types';
import { UsersService } from '../../../collections/userdb/userdb.service';
import {
NoPermissions,
RequiredPermissions,
UseLocalAuth
} from '../../../decorators/permissions.decorator';
@@ -94,7 +96,7 @@ export class UserController {
body.username,
body.roles,
);
if (HasFailed(updatedUser)) {
this.logger.warn(updatedUser.getReason());
throw new InternalServerErrorException('Could not update user');
@@ -133,16 +135,24 @@ export class UserController {
@Get('me')
@RequiredPermissions('user-view')
async me(@Request() req: AuthFasityRequest): Promise<UserMeResponse> {
return {
user: req.user,
token: await this.authService.createToken(req.user),
};
}
// You can always check your permissions
@Get('me/permissions')
@NoPermissions()
async refresh(
@Request() req: AuthFasityRequest,
): Promise<UserMePermissionsResponse> {
const permissions = await this.usersService.getPermissions(req.user);
if (HasFailed(permissions)) {
this.logger.warn(permissions.getReason());
throw new InternalServerErrorException('Could not get permissions');
}
return {
user: req.user,
permissions,
newJwtToken: await this.authService.createToken(req.user),
};
return { permissions };
}
}

View File

@@ -1,14 +1,14 @@
import { Controller, Get } from '@nestjs/common';
import { InfoResponse } from 'picsur-shared/dist/dto/api/info.dto';
import { HostConfigService } from '../../../config/host.config.service';
import { NoAuth } from '../../../decorators/permissions.decorator';
import { NoPermissions } from '../../../decorators/permissions.decorator';
@Controller('api/info')
export class InfoController {
constructor(private hostConfig: HostConfigService) {}
@Get()
@NoAuth()
@NoPermissions()
async getInfo(): Promise<InfoResponse> {
return {
demo: this.hostConfig.isDemo(),

View File

@@ -3,10 +3,17 @@ import { NgModule } from '@angular/core';
import { ApiService } from './api.service';
import { ImageService } from './image.service';
import { KeyService } from './key.service';
import { PermissionService } from './permission.service';
import { UserService } from './user.service';
@NgModule({
providers: [ApiService, ImageService, UserService, KeyService],
providers: [
ApiService,
ImageService,
UserService,
PermissionService,
KeyService,
],
imports: [CommonModule],
})
export class ApiModule {}

View File

@@ -0,0 +1,50 @@
import { Injectable } from '@angular/core';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { UserMePermissionsResponse } from 'picsur-shared/dist/dto/api/user.dto';
import { Permissions } from 'picsur-shared/dist/dto/permissions';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
import { BehaviorSubject } from 'rxjs';
import { ApiService } from './api.service';
import { UserService } from './user.service';
@Injectable()
export class PermissionService {
private readonly logger = console;
constructor(private userService: UserService, private api: ApiService) {
this.onUser();
}
public get livePermissions() {
return this.permissionsSubject;
}
public get permissions() {
return this.permissionsSubject.getValue();
}
private permissionsSubject = new BehaviorSubject<Permissions>([]);
@AutoUnsubscribe()
private onUser() {
return this.userService.liveUser.subscribe(async (user) => {
const permissions = await this.fetchPermissions();
if (HasFailed(permissions)) {
this.logger.warn(permissions.getReason());
return;
}
this.permissionsSubject.next(permissions);
});
}
private async fetchPermissions(): AsyncFailable<Permissions> {
const got = await this.api.get(
UserMePermissionsResponse,
'/api/user/me/permissions'
);
if (HasFailed(got)) return got;
return got.permissions;
}
}

View File

@@ -4,8 +4,7 @@ import { validate } from 'class-validator';
import jwt_decode from 'jwt-decode';
import {
UserLoginRequest,
UserLoginResponse,
UserMeResponse
UserLoginResponse, UserMeResponse
} from 'picsur-shared/dist/dto/api/user.dto';
import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
@@ -117,7 +116,7 @@ export class UserService {
const got = await this.api.get(UserMeResponse, '/api/user/me');
if (HasFailed(got)) return got;
this.key.set(got.newJwtToken);
this.key.set(got.token);
return got.user;
}
}

View File

@@ -90,14 +90,17 @@ export class UserMeResponse {
@Type(() => EUser)
user: EUser;
@IsString()
@IsDefined()
token: string;
}
// UserMePermissions
export class UserMePermissionsResponse {
@IsDefined()
@IsArray()
@IsEnum(PermissionsList, { each: true })
permissions: Permissions;
@IsString()
@IsDefined()
newJwtToken: string;
}
// UserUpdateRoles

View File

@@ -7,8 +7,8 @@ const PermissionsTuple = tuple(
'image-upload',
'user-login', // Ability to log in
'user-register', // Ability to register
'user-view', // Ability to view user info, only granted if logged in
'user-manage',
'user-view', // Ability to view user details and refresh token
'role-manage',
'syspref-manage',
);