feat(auth): add guest login

Co-authored-by: Philip Molares <philip.molares@udo.edu>
Signed-off-by: Philip Molares <philip.molares@udo.edu>
Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson 2025-05-28 21:53:35 +02:00
parent 04d19ebfbc
commit 167135a8d0
No known key found for this signature in database
GPG key ID: DB99ADDDC5C0AF82
9 changed files with 87 additions and 19 deletions

View file

@ -40,7 +40,9 @@ export class ApiTokensController {
@Get()
@OpenApi(200)
async getUserTokens(@RequestUserId() userId: number): Promise<ApiTokenDto[]> {
async getUserTokens(
@RequestUserId({ forbidGuests: true }) userId: number,
): Promise<ApiTokenDto[]> {
return (await this.apiTokenService.getTokensOfUserById(userId)).map(
(token) => this.apiTokenService.toAuthTokenDto(token),
);
@ -50,7 +52,7 @@ export class ApiTokensController {
@OpenApi(201)
async postTokenRequest(
@Body() createDto: ApiTokenCreateDto,
@RequestUserId() userId: User[FieldNameUser.id],
@RequestUserId({ forbidGuests: true }) userId: User[FieldNameUser.id],
): Promise<ApiTokenWithSecretDto> {
return await this.apiTokenService.createToken(
userId,
@ -62,7 +64,7 @@ export class ApiTokensController {
@Delete('/:keyId')
@OpenApi(204, 404)
async deleteToken(
@RequestUserId() userId: number,
@RequestUserId({ forbidGuests: true }) userId: number,
@Param('keyId') keyId: string,
): Promise<void> {
await this.apiTokenService.removeToken(keyId, userId);

View file

@ -66,7 +66,7 @@ export class MeController {
@Put('profile')
@OpenApi(200)
async updateProfile(
@RequestUserId() userId: number,
@RequestUserId({ forbidGuests: true }) userId: number,
@Body('displayName') newDisplayName: string,
): Promise<void> {
await this.userService.updateUser(

View file

@ -32,7 +32,7 @@ import {
import { ApiTags } from '@nestjs/swagger';
import { SessionGuard } from '../../../auth/session.guard';
import { NotInDBError } from '../../../errors/errors';
import { NotInDBError, PermissionError } from '../../../errors/errors';
import { GroupsService } from '../../../groups/groups.service';
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
import { MediaService } from '../../../media/media.service';
@ -70,10 +70,7 @@ export class NotesController {
@OpenApi(200)
@RequirePermission(RequiredPermission.READ)
@UseInterceptors(GetNoteIdInterceptor)
async getNote(
@RequestUserId({ guestsAllowed: true }) userId: number,
@RequestNoteId() noteId: number,
): Promise<NoteDto> {
async getNote(@RequestNoteId() noteId: number): Promise<NoteDto> {
return await this.noteService.toNoteDto(noteId);
}
@ -92,7 +89,7 @@ export class NotesController {
@OpenApi(201, 413)
@RequirePermission(RequiredPermission.CREATE)
async createNote(
@RequestUserId({ guestsAllowed: true }) userId: number,
@RequestUserId() userId: number,
@MarkdownBody() text: string,
): Promise<NoteDto> {
const createdNoteId = await this.noteService.createNote(text, userId);
@ -103,7 +100,7 @@ export class NotesController {
@OpenApi(201, 400, 404, 409, 413)
@RequirePermission(RequiredPermission.CREATE)
async createNamedNote(
@RequestUserId({ guestsAllowed: true }) userId: number,
@RequestUserId() userId: number,
@Param('noteAlias') noteAlias: string,
@MarkdownBody() text: string,
): Promise<NoteDto> {
@ -124,6 +121,12 @@ export class NotesController {
@RequestNoteId() noteId: number,
@Body() noteMediaDeletionDto: NoteMediaDeletionDto,
): Promise<void> {
const isOwner = await this.permissionService.isOwner(userId, noteId);
if (!isOwner) {
throw new PermissionError(
'You do not have the permission to delete this note.',
);
}
const mediaUploads =
await this.mediaService.getMediaUploadUuidsByNoteId(noteId);
for (const mediaUpload of mediaUploads) {
@ -141,7 +144,6 @@ export class NotesController {
@RequirePermission(RequiredPermission.READ)
@Get(':noteAlias/metadata')
async getNoteMetadata(
@RequestUserId({ guestsAllowed: true }) userId: number,
@RequestNoteId() noteId: number,
): Promise<NoteMetadataDto> {
return await this.noteService.toNoteMetadataDto(noteId);
@ -152,7 +154,6 @@ export class NotesController {
@RequirePermission(RequiredPermission.READ)
@UseInterceptors(GetNoteIdInterceptor)
async getNoteRevisions(
@RequestUserId({ guestsAllowed: true }) userId: number,
@RequestNoteId() noteId: number,
): Promise<RevisionMetadataDto[]> {
return await this.revisionsService.getAllRevisionMetadataDto(noteId);

View file

@ -13,7 +13,7 @@ import {
import { CompleteRequest } from '../request.type';
type RequestUserIdParameter = {
guestsAllowed: boolean;
forbidGuests: boolean;
};
/**
@ -26,14 +26,13 @@ type RequestUserIdParameter = {
// eslint-disable-next-line @typescript-eslint/naming-convention
export const RequestUserId = createParamDecorator(
(
data: RequestUserIdParameter = { guestsAllowed: false },
data: RequestUserIdParameter = { forbidGuests: false },
ctx: ExecutionContext,
) => {
const request: CompleteRequest = ctx.switchToHttp().getRequest();
if (
!request.authProviderType ||
(request.authProviderType === AuthProviderType.GUEST &&
!data.guestsAllowed)
(request.authProviderType === AuthProviderType.GUEST && data.forbidGuests)
) {
throw new UnauthorizedException("You're not logged in");
}

View file

@ -19,7 +19,7 @@ export const forbiddenDescription =
'Access to the requested resource is not permitted';
export const notFoundDescription = 'The requested resource was not found';
export const successfullyDeletedDescription =
'The requested resource was sucessfully deleted';
'The requested resource was successfully deleted';
export const unprocessableEntityDescription =
"The request change can't be processed";
export const conflictDescription =