From 28266bca0b2734a6423a3558362d4e20ed073fe8 Mon Sep 17 00:00:00 2001 From: Yannick Bungers Date: Thu, 23 Sep 2021 22:44:34 +0200 Subject: [PATCH 1/3] Get user from Session instead of hardcoded value Signed-off-by: Yannick Bungers --- src/api/private/alias/alias.controller.ts | 20 ++++------ .../private/me/history/history.controller.ts | 24 +++++------- src/api/private/me/me.controller.ts | 34 ++++++++++------- src/api/private/media/media.controller.ts | 7 +++- src/api/private/notes/notes.controller.ts | 34 ++++++++--------- .../private/tokens/tokens.controller.spec.ts | 2 + src/api/private/tokens/tokens.controller.ts | 38 +++++++++++++++---- 7 files changed, 92 insertions(+), 67 deletions(-) diff --git a/src/api/private/alias/alias.controller.ts b/src/api/private/alias/alias.controller.ts index a359728e0..d32cc21cf 100644 --- a/src/api/private/alias/alias.controller.ts +++ b/src/api/private/alias/alias.controller.ts @@ -13,10 +13,9 @@ import { Param, Post, Put, - Req, UnauthorizedException, + UseGuards, } from '@nestjs/common'; -import { Request } from 'express'; import { AlreadyInDBError, @@ -24,6 +23,7 @@ import { NotInDBError, PrimaryAliasDeletionForbiddenError, } from '../../../errors/errors'; +import { SessionGuard } from '../../../identity/session.guard'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; import { AliasCreateDto } from '../../../notes/alias-create.dto'; import { AliasUpdateDto } from '../../../notes/alias-update.dto'; @@ -31,8 +31,11 @@ import { AliasDto } from '../../../notes/alias.dto'; import { AliasService } from '../../../notes/alias.service'; import { NotesService } from '../../../notes/notes.service'; import { PermissionsService } from '../../../permissions/permissions.service'; +import { User } from '../../../users/user.entity'; import { UsersService } from '../../../users/users.service'; +import { RequestUser } from '../../utils/request-user.decorator'; +@UseGuards(SessionGuard) @Controller('alias') export class AliasController { constructor( @@ -44,15 +47,12 @@ export class AliasController { ) { this.logger.setContext(AliasController.name); } - @Post() async addAlias( - @Req() req: Request, + @RequestUser() user: User, @Body() newAliasDto: AliasCreateDto, ): Promise { try { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); const note = await this.noteService.getNoteByIdOrAlias( newAliasDto.noteIdOrAlias, ); @@ -77,7 +77,7 @@ export class AliasController { @Put(':alias') async makeAliasPrimary( - @Req() req: Request, + @RequestUser() user: User, @Param('alias') alias: string, @Body() changeAliasDto: AliasUpdateDto, ): Promise { @@ -87,8 +87,6 @@ export class AliasController { ); } try { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); const note = await this.noteService.getNoteByIdOrAlias(alias); if (!this.permissionsService.isOwner(user, note)) { throw new UnauthorizedException('Reading note denied!'); @@ -112,12 +110,10 @@ export class AliasController { @Delete(':alias') @HttpCode(204) async removeAlias( - @Req() req: Request, + @RequestUser() user: User, @Param('alias') alias: string, ): Promise { try { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); const note = await this.noteService.getNoteByIdOrAlias(alias); if (!this.permissionsService.isOwner(user, note)) { throw new UnauthorizedException('Reading note denied!'); diff --git a/src/api/private/me/history/history.controller.ts b/src/api/private/me/history/history.controller.ts index fdb098b4c..2bdedab36 100644 --- a/src/api/private/me/history/history.controller.ts +++ b/src/api/private/me/history/history.controller.ts @@ -13,6 +13,7 @@ import { Param, Post, Put, + UseGuards, } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; @@ -21,27 +22,27 @@ import { HistoryEntryImportDto } from '../../../../history/history-entry-import. import { HistoryEntryUpdateDto } from '../../../../history/history-entry-update.dto'; import { HistoryEntryDto } from '../../../../history/history-entry.dto'; import { HistoryService } from '../../../../history/history.service'; +import { SessionGuard } from '../../../../identity/session.guard'; import { ConsoleLoggerService } from '../../../../logger/console-logger.service'; import { GetNotePipe } from '../../../../notes/get-note.pipe'; import { Note } from '../../../../notes/note.entity'; -import { UsersService } from '../../../../users/users.service'; +import { User } from '../../../../users/user.entity'; +import { RequestUser } from '../../../utils/request-user.decorator'; +@UseGuards(SessionGuard) @ApiTags('history') @Controller('/me/history') export class HistoryController { constructor( private readonly logger: ConsoleLoggerService, private historyService: HistoryService, - private userService: UsersService, ) { this.logger.setContext(HistoryController.name); } @Get() - async getHistory(): Promise { - // ToDo: use actual user here + async getHistory(@RequestUser() user: User): Promise { try { - const user = await this.userService.getUserByUsername('hardcoded'); const foundEntries = await this.historyService.getEntriesByUser(user); return foundEntries.map((entry) => this.historyService.toHistoryEntryDto(entry), @@ -56,11 +57,10 @@ export class HistoryController { @Post() async setHistory( + @RequestUser() user: User, @Body('history') history: HistoryEntryImportDto[], ): Promise { try { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); await this.historyService.setHistory(user, history); } catch (e) { if (e instanceof NotInDBError || e instanceof ForbiddenIdError) { @@ -71,10 +71,8 @@ export class HistoryController { } @Delete() - async deleteHistory(): Promise { + async deleteHistory(@RequestUser() user: User): Promise { try { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); await this.historyService.deleteHistory(user); } catch (e) { if (e instanceof NotInDBError) { @@ -87,11 +85,10 @@ export class HistoryController { @Put(':note') async updateHistoryEntry( @Param('note', GetNotePipe) note: Note, + @RequestUser() user: User, @Body() entryUpdateDto: HistoryEntryUpdateDto, ): Promise { try { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); const newEntry = await this.historyService.updateHistoryEntry( note, user, @@ -109,10 +106,9 @@ export class HistoryController { @Delete(':note') async deleteHistoryEntry( @Param('note', GetNotePipe) note: Note, + @RequestUser() user: User, ): Promise { try { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); await this.historyService.deleteHistoryEntry(note, user); } catch (e) { if (e instanceof NotInDBError) { diff --git a/src/api/private/me/me.controller.ts b/src/api/private/me/me.controller.ts index 78f090cc7..240d5b72b 100644 --- a/src/api/private/me/me.controller.ts +++ b/src/api/private/me/me.controller.ts @@ -3,14 +3,26 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { Body, Controller, Delete, Get, HttpCode, Post } from '@nestjs/common'; +import { + Body, + Controller, + Delete, + Get, + HttpCode, + Post, + UseGuards, +} from '@nestjs/common'; +import { SessionGuard } from '../../../identity/session.guard'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; import { MediaUploadDto } from '../../../media/media-upload.dto'; import { MediaService } from '../../../media/media.service'; import { UserInfoDto } from '../../../users/user-info.dto'; +import { User } from '../../../users/user.entity'; import { UsersService } from '../../../users/users.service'; +import { RequestUser } from '../../utils/request-user.decorator'; +@UseGuards(SessionGuard) @Controller('me') export class MeController { constructor( @@ -20,27 +32,20 @@ export class MeController { ) { this.logger.setContext(MeController.name); } - @Get() - async getMe(): Promise { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); + getMe(@RequestUser() user: User): UserInfoDto { return this.userService.toUserDto(user); } @Get('media') - async getMyMedia(): Promise { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); + async getMyMedia(@RequestUser() user: User): Promise { const media = await this.mediaService.listUploadsByUser(user); return media.map((media) => this.mediaService.toMediaUploadDto(media)); } @Delete() @HttpCode(204) - async deleteUser(): Promise { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); + async deleteUser(@RequestUser() user: User): Promise { const mediaUploads = await this.mediaService.listUploadsByUser(user); for (const mediaUpload of mediaUploads) { await this.mediaService.deleteFile(mediaUpload); @@ -52,9 +57,10 @@ export class MeController { @Post('profile') @HttpCode(200) - async updateDisplayName(@Body('name') newDisplayName: string): Promise { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); + async updateDisplayName( + @RequestUser() user: User, + @Body('name') newDisplayName: string, + ): Promise { await this.userService.changeDisplayName(user, newDisplayName); } } diff --git a/src/api/private/media/media.controller.ts b/src/api/private/media/media.controller.ts index 8db4e26f4..9c89eac31 100644 --- a/src/api/private/media/media.controller.ts +++ b/src/api/private/media/media.controller.ts @@ -11,6 +11,7 @@ import { InternalServerErrorException, Post, UploadedFile, + UseGuards, UseInterceptors, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; @@ -20,6 +21,7 @@ import { MediaBackendError, NotInDBError, } from '../../../errors/errors'; +import { SessionGuard } from '../../../identity/session.guard'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; import { MediaUploadUrlDto } from '../../../media/media-upload-url.dto'; import { MediaService } from '../../../media/media.service'; @@ -28,7 +30,9 @@ import { Note } from '../../../notes/note.entity'; import { NotesService } from '../../../notes/notes.service'; import { User } from '../../../users/user.entity'; import { UsersService } from '../../../users/users.service'; +import { RequestUser } from '../../utils/request-user.decorator'; +@UseGuards(SessionGuard) @Controller('media') export class MediaController { constructor( @@ -46,9 +50,8 @@ export class MediaController { async uploadMedia( @UploadedFile() file: MulterFile, @Headers('HedgeDoc-Note') noteId: string, + @RequestUser() user: User, ): Promise { - // ToDo: Get real userName - const user: User = await this.userService.getUserByUsername('hardcoded'); try { // TODO: Move getting the Note object into a decorator const note: Note = await this.noteService.getNoteByIdOrAlias(noteId); diff --git a/src/api/private/notes/notes.controller.ts b/src/api/private/notes/notes.controller.ts index 6ab130701..6d087895b 100644 --- a/src/api/private/notes/notes.controller.ts +++ b/src/api/private/notes/notes.controller.ts @@ -14,6 +14,7 @@ import { Param, Post, UnauthorizedException, + UseGuards, } from '@nestjs/common'; import { @@ -22,6 +23,7 @@ import { NotInDBError, } from '../../../errors/errors'; import { HistoryService } from '../../../history/history.service'; +import { SessionGuard } from '../../../identity/session.guard'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; import { MediaUploadDto } from '../../../media/media-upload.dto'; import { MediaService } from '../../../media/media.service'; @@ -34,9 +36,12 @@ import { PermissionsService } from '../../../permissions/permissions.service'; import { RevisionMetadataDto } from '../../../revisions/revision-metadata.dto'; import { RevisionDto } from '../../../revisions/revision.dto'; import { RevisionsService } from '../../../revisions/revisions.service'; +import { User } from '../../../users/user.entity'; import { UsersService } from '../../../users/users.service'; import { MarkdownBody } from '../../utils/markdownbody-decorator'; +import { RequestUser } from '../../utils/request-user.decorator'; +@UseGuards(SessionGuard) @Controller('notes') export class NotesController { constructor( @@ -53,10 +58,9 @@ export class NotesController { @Get(':noteIdOrAlias') async getNote( + @RequestUser() user: User, @Param('noteIdOrAlias', GetNotePipe) note: Note, ): Promise { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); if (!this.permissionsService.mayRead(user, note)) { throw new UnauthorizedException('Reading note denied!'); } @@ -67,10 +71,9 @@ export class NotesController { @Get(':noteIdOrAlias/media') async getNotesMedia( @Param('noteIdOrAlias', GetNotePipe) note: Note, + @RequestUser() user: User, ): Promise { try { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); if (!this.permissionsService.mayRead(user, note)) { throw new UnauthorizedException('Reading note denied!'); } @@ -86,10 +89,10 @@ export class NotesController { @Post() @HttpCode(201) - async createNote(@MarkdownBody() text: string): Promise { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); - // ToDo: provide user for createNoteDto + async createNote( + @RequestUser() user: User, + @MarkdownBody() text: string, + ): Promise { if (!this.permissionsService.mayCreate(user)) { throw new UnauthorizedException('Creating note denied!'); } @@ -102,11 +105,10 @@ export class NotesController { @Post(':noteAlias') @HttpCode(201) async createNamedNote( + @RequestUser() user: User, @Param('noteAlias') noteAlias: string, @MarkdownBody() text: string, ): Promise { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); if (!this.permissionsService.mayCreate(user)) { throw new UnauthorizedException('Creating note denied!'); } @@ -129,12 +131,11 @@ export class NotesController { @Delete(':noteIdOrAlias') @HttpCode(204) async deleteNote( + @RequestUser() user: User, @Param('noteIdOrAlias', GetNotePipe) note: Note, @Body() noteMediaDeletionDto: NoteMediaDeletionDto, ): Promise { try { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); if (!this.permissionsService.isOwner(user, note)) { throw new UnauthorizedException('Deleting note denied!'); } @@ -160,11 +161,10 @@ export class NotesController { @Get(':noteIdOrAlias/revisions') async getNoteRevisions( + @RequestUser() user: User, @Param('noteIdOrAlias', GetNotePipe) note: Note, ): Promise { try { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); if (!this.permissionsService.mayRead(user, note)) { throw new UnauthorizedException('Reading note denied!'); } @@ -185,11 +185,10 @@ export class NotesController { @Delete(':noteIdOrAlias/revisions') @HttpCode(204) async purgeNoteRevisions( + @RequestUser() user: User, @Param('noteIdOrAlias') noteIdOrAlias: string, ): Promise { try { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); const note = await this.noteService.getNoteByIdOrAlias(noteIdOrAlias); if (!this.permissionsService.mayRead(user, note)) { throw new UnauthorizedException('Reading note denied!'); @@ -217,12 +216,11 @@ export class NotesController { @Get(':noteIdOrAlias/revisions/:revisionId') async getNoteRevision( + @RequestUser() user: User, @Param('noteIdOrAlias', GetNotePipe) note: Note, @Param('revisionId') revisionId: number, ): Promise { try { - // ToDo: use actual user here - const user = await this.userService.getUserByUsername('hardcoded'); if (!this.permissionsService.mayRead(user, note)) { throw new UnauthorizedException('Reading note denied!'); } diff --git a/src/api/private/tokens/tokens.controller.spec.ts b/src/api/private/tokens/tokens.controller.spec.ts index 56ecfd57e..3d2118860 100644 --- a/src/api/private/tokens/tokens.controller.spec.ts +++ b/src/api/private/tokens/tokens.controller.spec.ts @@ -14,6 +14,7 @@ import { Identity } from '../../../identity/identity.entity'; import { LoggerModule } from '../../../logger/logger.module'; import { Session } from '../../../users/session.entity'; import { User } from '../../../users/user.entity'; +import { UsersModule } from '../../../users/users.module'; import { TokensController } from './tokens.controller'; describe('TokensController', () => { @@ -29,6 +30,7 @@ describe('TokensController', () => { }), LoggerModule, AuthModule, + UsersModule, ], }) .overrideProvider(getRepositoryToken(User)) diff --git a/src/api/private/tokens/tokens.controller.ts b/src/api/private/tokens/tokens.controller.ts index 9271953bc..d634be650 100644 --- a/src/api/private/tokens/tokens.controller.ts +++ b/src/api/private/tokens/tokens.controller.ts @@ -9,17 +9,25 @@ import { Delete, Get, HttpCode, + NotFoundException, Param, Post, + UnauthorizedException, + UseGuards, } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { AuthTokenWithSecretDto } from '../../../auth/auth-token-with-secret.dto'; import { AuthTokenDto } from '../../../auth/auth-token.dto'; import { AuthService } from '../../../auth/auth.service'; +import { NotInDBError } from '../../../errors/errors'; +import { SessionGuard } from '../../../identity/session.guard'; import { ConsoleLoggerService } from '../../../logger/console-logger.service'; +import { User } from '../../../users/user.entity'; import { TimestampMillis } from '../../../utils/timestamp'; +import { RequestUser } from '../../utils/request-user.decorator'; +@UseGuards(SessionGuard) @ApiTags('tokens') @Controller('tokens') export class TokensController { @@ -31,9 +39,8 @@ export class TokensController { } @Get() - async getUserTokens(): Promise { - // ToDo: Get real userName - return (await this.authService.getTokensByUsername('hardcoded')).map( + async getUserTokens(@RequestUser() user: User): Promise { + return (await this.authService.getTokensByUsername(user.userName)).map( (token) => this.authService.toAuthTokenDto(token), ); } @@ -42,10 +49,10 @@ export class TokensController { async postTokenRequest( @Body('label') label: string, @Body('validUntil') validUntil: TimestampMillis, + @RequestUser() user: User, ): Promise { - // ToDo: Get real userName return await this.authService.createTokenForUser( - 'hardcoded', + user.userName, label, validUntil, ); @@ -53,7 +60,24 @@ export class TokensController { @Delete('/:keyId') @HttpCode(204) - async deleteToken(@Param('keyId') keyId: string): Promise { - return await this.authService.removeToken(keyId); + async deleteToken( + @RequestUser() user: User, + @Param('keyId') keyId: string, + ): Promise { + const tokens = await this.authService.getTokensByUsername(user.userName); + try { + for (const token of tokens) { + if (token.keyId == keyId) { + return await this.authService.removeToken(keyId); + } + } + } catch (e) { + if (e instanceof NotInDBError) { + throw new NotFoundException(e.message); + } + } + throw new UnauthorizedException( + 'User is not authorized to delete this token', + ); } } From 276d423ee2acfce4d20bd87e50533c98e94110db Mon Sep 17 00:00:00 2001 From: Yannick Bungers Date: Wed, 13 Oct 2021 20:43:56 +0200 Subject: [PATCH 2/3] Fix tests with using sessions in e2e tests of private api Signed-off-by: Yannick Bungers --- .eslintrc.js | 2 +- test/private-api/alias.e2e-spec.ts | 58 ++++++++++----------- test/private-api/history.e2e-spec.ts | 32 ++++++++---- test/private-api/me.e2e-spec.ts | 26 +++++++--- test/private-api/media.e2e-spec.ts | 26 +++++++--- test/private-api/notes.e2e-spec.ts | 76 +++++++++++++++------------- 6 files changed, 132 insertions(+), 88 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index b268f3de2..305fdd6dc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,7 +21,7 @@ module.exports = { 'jest/expect-expect': [ 'error', { - assertFunctionNames: ['expect', 'request.**.expect'], + assertFunctionNames: ['expect', 'request.**.expect', 'agent.**.expect'], }, ], 'jest/no-standalone-expect': [ diff --git a/test/private-api/alias.e2e-spec.ts b/test/private-api/alias.e2e-spec.ts index 9b620b04d..d05242734 100644 --- a/test/private-api/alias.e2e-spec.ts +++ b/test/private-api/alias.e2e-spec.ts @@ -11,12 +11,14 @@ import request from 'supertest'; import { PrivateApiModule } from '../../src/api/private/private-api.module'; import { AuthModule } from '../../src/auth/auth.module'; +import { AuthConfig } from '../../src/config/auth.config'; import appConfigMock from '../../src/config/mock/app.config.mock'; import authConfigMock from '../../src/config/mock/auth.config.mock'; import customizationConfigMock from '../../src/config/mock/customization.config.mock'; import externalConfigMock from '../../src/config/mock/external-services.config.mock'; import mediaConfigMock from '../../src/config/mock/media.config.mock'; import { GroupsModule } from '../../src/groups/groups.module'; +import { IdentityService } from '../../src/identity/identity.service'; import { LoggerModule } from '../../src/logger/logger.module'; import { AliasCreateDto } from '../../src/notes/alias-create.dto'; import { AliasUpdateDto } from '../../src/notes/alias-update.dto'; @@ -27,14 +29,17 @@ import { PermissionsModule } from '../../src/permissions/permissions.module'; import { User } from '../../src/users/user.entity'; import { UsersModule } from '../../src/users/users.module'; import { UsersService } from '../../src/users/users.service'; +import { setupSessionMiddleware } from '../../src/utils/session'; describe('Alias', () => { let app: INestApplication; let aliasService: AliasService; let notesService: NotesService; + let identityService: IdentityService; let user: User; let content: string; let forbiddenNoteId: string; + let agent: request.SuperAgentTest; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ @@ -69,12 +74,21 @@ describe('Alias', () => { const config = moduleRef.get(ConfigService); forbiddenNoteId = config.get('appConfig').forbiddenNoteIds[0]; app = moduleRef.createNestApplication(); + const authConfig = config.get('authConfig') as AuthConfig; + setupSessionMiddleware(app, authConfig); await app.init(); aliasService = moduleRef.get(AliasService); notesService = moduleRef.get(NotesService); + identityService = moduleRef.get(IdentityService); const userService = moduleRef.get(UsersService); user = await userService.createUser('hardcoded', 'Testy'); + await identityService.createLocalIdentity(user, 'test'); content = 'This is a test note.'; + agent = request.agent(app.getHttpServer()); + await agent + .post('/auth/local/login') + .send({ username: 'hardcoded', password: 'test' }) + .expect(201); }); describe('POST /alias', () => { @@ -92,7 +106,7 @@ describe('Alias', () => { it('create with normal alias', async () => { const newAlias = 'normalAlias'; newAliasDto.newAlias = newAlias; - const metadata = await request(app.getHttpServer()) + const metadata = await agent .post(`/alias`) .set('Content-Type', 'application/json') .send(newAliasDto) @@ -100,9 +114,7 @@ describe('Alias', () => { expect(metadata.body.name).toEqual(newAlias); expect(metadata.body.primaryAlias).toBeFalsy(); expect(metadata.body.noteId).toEqual(publicId); - const note = await request(app.getHttpServer()) - .get(`/notes/${newAlias}`) - .expect(200); + const note = await agent.get(`/notes/${newAlias}`).expect(200); expect(note.body.metadata.aliases).toContain(newAlias); expect(note.body.metadata.primaryAlias).toBeTruthy(); expect(note.body.metadata.id).toEqual(publicId); @@ -111,7 +123,7 @@ describe('Alias', () => { describe('does not create an alias', () => { it('because of a forbidden alias', async () => { newAliasDto.newAlias = forbiddenNoteId; - await request(app.getHttpServer()) + await agent .post(`/alias`) .set('Content-Type', 'application/json') .send(newAliasDto) @@ -119,7 +131,7 @@ describe('Alias', () => { }); it('because of a alias that is a public id', async () => { newAliasDto.newAlias = publicId; - await request(app.getHttpServer()) + await agent .post(`/alias`) .set('Content-Type', 'application/json') .send(newAliasDto) @@ -142,7 +154,7 @@ describe('Alias', () => { }); it('updates a note with a normal alias', async () => { - const metadata = await request(app.getHttpServer()) + const metadata = await agent .put(`/alias/${newAlias}`) .set('Content-Type', 'application/json') .send(changeAliasDto) @@ -150,9 +162,7 @@ describe('Alias', () => { expect(metadata.body.name).toEqual(newAlias); expect(metadata.body.primaryAlias).toBeTruthy(); expect(metadata.body.noteId).toEqual(publicId); - const note = await request(app.getHttpServer()) - .get(`/notes/${newAlias}`) - .expect(200); + const note = await agent.get(`/notes/${newAlias}`).expect(200); expect(note.body.metadata.aliases).toContain(newAlias); expect(note.body.metadata.primaryAlias).toBeTruthy(); expect(note.body.metadata.id).toEqual(publicId); @@ -160,7 +170,7 @@ describe('Alias', () => { describe('does not update', () => { it('a note with unknown alias', async () => { - await request(app.getHttpServer()) + await agent .put(`/alias/i_dont_exist`) .set('Content-Type', 'application/json') .send(changeAliasDto) @@ -168,7 +178,7 @@ describe('Alias', () => { }); it('if the property primaryAlias is false', async () => { changeAliasDto.primaryAlias = false; - await request(app.getHttpServer()) + await agent .put(`/alias/${newAlias}`) .set('Content-Type', 'application/json') .send(changeAliasDto) @@ -186,34 +196,24 @@ describe('Alias', () => { }); it('deletes a normal alias', async () => { - await request(app.getHttpServer()) - .delete(`/alias/${newAlias}`) - .expect(204); - await request(app.getHttpServer()).get(`/notes/${newAlias}`).expect(404); + await agent.delete(`/alias/${newAlias}`).expect(204); + await agent.get(`/notes/${newAlias}`).expect(404); }); it('does not delete an unknown alias', async () => { - await request(app.getHttpServer()) - .delete(`/alias/i_dont_exist`) - .expect(404); + await agent.delete(`/alias/i_dont_exist`).expect(404); }); it('does not delete an primary alias (if it is not the only one)', async () => { const note = await notesService.getNoteByIdOrAlias(testAlias); await aliasService.addAlias(note, newAlias); - await request(app.getHttpServer()) - .delete(`/alias/${testAlias}`) - .expect(400); - await request(app.getHttpServer()).get(`/notes/${newAlias}`).expect(200); + await agent.delete(`/alias/${testAlias}`).expect(400); + await agent.get(`/notes/${newAlias}`).expect(200); }); it('deletes a primary alias (if it is the only one)', async () => { - await request(app.getHttpServer()) - .delete(`/alias/${newAlias}`) - .expect(204); - await request(app.getHttpServer()) - .delete(`/alias/${testAlias}`) - .expect(204); + await agent.delete(`/alias/${newAlias}`).expect(204); + await agent.delete(`/alias/${testAlias}`).expect(204); }); }); }); diff --git a/test/private-api/history.e2e-spec.ts b/test/private-api/history.e2e-spec.ts index 4a6b2878a..b3d253a42 100644 --- a/test/private-api/history.e2e-spec.ts +++ b/test/private-api/history.e2e-spec.ts @@ -11,6 +11,7 @@ import request from 'supertest'; import { PrivateApiModule } from '../../src/api/private/private-api.module'; import { AuthModule } from '../../src/auth/auth.module'; +import { AuthConfig } from '../../src/config/auth.config'; import appConfigMock from '../../src/config/mock/app.config.mock'; import authConfigMock from '../../src/config/mock/auth.config.mock'; import customizationConfigMock from '../../src/config/mock/customization.config.mock'; @@ -20,6 +21,7 @@ import { GroupsModule } from '../../src/groups/groups.module'; import { HistoryEntryImportDto } from '../../src/history/history-entry-import.dto'; import { HistoryEntry } from '../../src/history/history-entry.entity'; import { HistoryService } from '../../src/history/history.service'; +import { IdentityService } from '../../src/identity/identity.service'; import { LoggerModule } from '../../src/logger/logger.module'; import { Note } from '../../src/notes/note.entity'; import { NotesModule } from '../../src/notes/notes.module'; @@ -28,15 +30,18 @@ import { PermissionsModule } from '../../src/permissions/permissions.module'; import { User } from '../../src/users/user.entity'; import { UsersModule } from '../../src/users/users.module'; import { UsersService } from '../../src/users/users.service'; +import { setupSessionMiddleware } from '../../src/utils/session'; describe('History', () => { let app: INestApplication; let historyService: HistoryService; + let identityService: IdentityService; let user: User; let note: Note; let note2: Note; let forbiddenNoteId: string; let content: string; + let agent: request.SuperAgentTest; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ @@ -71,25 +76,34 @@ describe('History', () => { const config = moduleRef.get(ConfigService); forbiddenNoteId = config.get('appConfig').forbiddenNoteIds[0]; app = moduleRef.createNestApplication(); + const authConfig = config.get('authConfig') as AuthConfig; + setupSessionMiddleware(app, authConfig); await app.init(); content = 'This is a test note.'; historyService = moduleRef.get(HistoryService); const userService = moduleRef.get(UsersService); + identityService = moduleRef.get(IdentityService); user = await userService.createUser('hardcoded', 'Testy'); + await identityService.createLocalIdentity(user, 'test'); const notesService = moduleRef.get(NotesService); note = await notesService.createNote(content, 'note', user); note2 = await notesService.createNote(content, 'note2', user); + agent = request.agent(app.getHttpServer()); + await agent + .post('/auth/local/login') + .send({ username: 'hardcoded', password: 'test' }) + .expect(201); }); it('GET /me/history', async () => { - const emptyResponse = await request(app.getHttpServer()) + const emptyResponse = await agent .get('/me/history') .expect('Content-Type', /json/) .expect(200); expect(emptyResponse.body.length).toEqual(0); const entry = await historyService.updateHistoryEntryTimestamp(note, user); const entryDto = historyService.toHistoryEntryDto(entry); - const response = await request(app.getHttpServer()) + const response = await agent .get('/me/history') .expect('Content-Type', /json/) .expect(200); @@ -114,7 +128,7 @@ describe('History', () => { )[0].name; postEntryDto.pinStatus = pinStatus; postEntryDto.lastVisited = lastVisited; - await request(app.getHttpServer()) + await agent .post('/me/history') .set('Content-Type', 'application/json') .send(JSON.stringify({ history: [postEntryDto] })) @@ -149,7 +163,7 @@ describe('History', () => { brokenEntryDto.note = forbiddenNoteId; brokenEntryDto.pinStatus = pinStatus; brokenEntryDto.lastVisited = lastVisited; - await request(app.getHttpServer()) + await agent .post('/me/history') .set('Content-Type', 'application/json') .send(JSON.stringify({ history: [brokenEntryDto] })) @@ -160,7 +174,7 @@ describe('History', () => { brokenEntryDto.note = 'i_dont_exist'; brokenEntryDto.pinStatus = pinStatus; brokenEntryDto.lastVisited = lastVisited; - await request(app.getHttpServer()) + await agent .post('/me/history') .set('Content-Type', 'application/json') .send(JSON.stringify({ history: [brokenEntryDto] })) @@ -181,7 +195,7 @@ describe('History', () => { it('DELETE /me/history', async () => { expect((await historyService.getEntriesByUser(user)).length).toEqual(1); - await request(app.getHttpServer()).delete('/me/history').expect(200); + await agent.delete('/me/history').expect(200); expect((await historyService.getEntriesByUser(user)).length).toEqual(0); }); @@ -189,7 +203,7 @@ describe('History', () => { const entry = await historyService.updateHistoryEntryTimestamp(note2, user); expect(entry.pinStatus).toBeFalsy(); const alias = entry.note.aliases.filter((alias) => alias.primary)[0].name; - await request(app.getHttpServer()) + await agent .put(`/me/history/${alias || 'undefined'}`) .send({ pinStatus: true }) .expect(200); @@ -204,9 +218,7 @@ describe('History', () => { const alias = entry.note.aliases.filter((alias) => alias.primary)[0].name; const entry2 = await historyService.updateHistoryEntryTimestamp(note, user); const entryDto = historyService.toHistoryEntryDto(entry2); - await request(app.getHttpServer()) - .delete(`/me/history/${alias || 'undefined'}`) - .expect(200); + await agent.delete(`/me/history/${alias || 'undefined'}`).expect(200); const userEntries = await historyService.getEntriesByUser(user); expect(userEntries.length).toEqual(1); const userEntryDto = historyService.toHistoryEntryDto(userEntries[0]); diff --git a/test/private-api/me.e2e-spec.ts b/test/private-api/me.e2e-spec.ts index b138bab47..f10768cda 100644 --- a/test/private-api/me.e2e-spec.ts +++ b/test/private-api/me.e2e-spec.ts @@ -17,6 +17,7 @@ import request from 'supertest'; import { PrivateApiModule } from '../../src/api/private/private-api.module'; import { AuthModule } from '../../src/auth/auth.module'; +import { AuthConfig } from '../../src/config/auth.config'; import appConfigMock from '../../src/config/mock/app.config.mock'; import authConfigMock from '../../src/config/mock/auth.config.mock'; import customizationConfigMock from '../../src/config/mock/customization.config.mock'; @@ -25,6 +26,7 @@ import mediaConfigMock from '../../src/config/mock/media.config.mock'; import { NotInDBError } from '../../src/errors/errors'; import { GroupsModule } from '../../src/groups/groups.module'; import { HistoryModule } from '../../src/history/history.module'; +import { IdentityService } from '../../src/identity/identity.service'; import { LoggerModule } from '../../src/logger/logger.module'; import { MediaModule } from '../../src/media/media.module'; import { MediaService } from '../../src/media/media.service'; @@ -36,17 +38,20 @@ import { UserInfoDto } from '../../src/users/user-info.dto'; import { User } from '../../src/users/user.entity'; import { UsersModule } from '../../src/users/users.module'; import { UsersService } from '../../src/users/users.service'; +import { setupSessionMiddleware } from '../../src/utils/session'; describe('Me', () => { let app: INestApplication; let userService: UsersService; let mediaService: MediaService; + let identityService: IdentityService; let uploadPath: string; let user: User; let content: string; let note1: Note; let alias2: string; let note2: Note; + let agent: request.SuperAgentTest; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ @@ -82,21 +87,31 @@ describe('Me', () => { const config = moduleRef.get(ConfigService); uploadPath = config.get('mediaConfig').backend.filesystem.uploadPath; app = moduleRef.createNestApplication(); + const authConfig = config.get('authConfig') as AuthConfig; + setupSessionMiddleware(app, authConfig); await app.init(); //historyService = moduleRef.get(); userService = moduleRef.get(UsersService); mediaService = moduleRef.get(MediaService); + identityService = moduleRef.get(IdentityService); user = await userService.createUser('hardcoded', 'Testy'); + await identityService.createLocalIdentity(user, 'test'); + const notesService = moduleRef.get(NotesService); content = 'This is a test note.'; alias2 = 'note2'; note1 = await notesService.createNote(content, undefined, user); note2 = await notesService.createNote(content, alias2, user); + agent = request.agent(app.getHttpServer()); + await agent + .post('/auth/local/login') + .send({ username: 'hardcoded', password: 'test' }) + .expect(201); }); it('GET /me', async () => { const userInfo = userService.toUserDto(user); - const response = await request(app.getHttpServer()) + const response = await agent .get('/me') .expect('Content-Type', /json/) .expect(200); @@ -105,8 +120,7 @@ describe('Me', () => { }); it('GET /me/media', async () => { - const httpServer = app.getHttpServer(); - const responseBefore = await request(httpServer) + const responseBefore = await agent .get('/me/media/') .expect('Content-Type', /json/) .expect(200); @@ -118,7 +132,7 @@ describe('Me', () => { const url2 = await mediaService.saveFile(testImage, user, note2); const url3 = await mediaService.saveFile(testImage, user, note2); - const response = await request(httpServer) + const response = await agent .get('/me/media/') .expect('Content-Type', /json/) .expect(200); @@ -137,7 +151,7 @@ describe('Me', () => { it('POST /me/profile', async () => { const newDisplayName = 'Another name'; expect(user.displayName).not.toEqual(newDisplayName); - await request(app.getHttpServer()) + await agent .post('/me/profile') .send({ name: newDisplayName, @@ -155,7 +169,7 @@ describe('Me', () => { const mediaUploads = await mediaService.listUploadsByUser(dbUser); expect(mediaUploads).toHaveLength(1); expect(mediaUploads[0].fileUrl).toEqual(url0); - await request(app.getHttpServer()).delete('/me').expect(204); + await agent.delete('/me').expect(204); await expect(userService.getUserByUsername('hardcoded')).rejects.toThrow( NotInDBError, ); diff --git a/test/private-api/media.e2e-spec.ts b/test/private-api/media.e2e-spec.ts index 78c930516..18a51d697 100644 --- a/test/private-api/media.e2e-spec.ts +++ b/test/private-api/media.e2e-spec.ts @@ -13,12 +13,14 @@ import request from 'supertest'; import { PrivateApiModule } from '../../src/api/private/private-api.module'; import { AuthModule } from '../../src/auth/auth.module'; +import { AuthConfig } from '../../src/config/auth.config'; import appConfigMock from '../../src/config/mock/app.config.mock'; import authConfigMock from '../../src/config/mock/auth.config.mock'; import customizationConfigMock from '../../src/config/mock/customization.config.mock'; import externalConfigMock from '../../src/config/mock/external-services.config.mock'; import mediaConfigMock from '../../src/config/mock/media.config.mock'; import { GroupsModule } from '../../src/groups/groups.module'; +import { IdentityService } from '../../src/identity/identity.service'; import { ConsoleLoggerService } from '../../src/logger/console-logger.service'; import { LoggerModule } from '../../src/logger/logger.module'; import { MediaModule } from '../../src/media/media.module'; @@ -26,11 +28,14 @@ import { NotesModule } from '../../src/notes/notes.module'; import { NotesService } from '../../src/notes/notes.service'; import { PermissionsModule } from '../../src/permissions/permissions.module'; import { UsersService } from '../../src/users/users.service'; +import { setupSessionMiddleware } from '../../src/utils/session'; import { ensureDeleted } from '../utils'; describe('Media', () => { + let identityService: IdentityService; let app: NestExpressApplication; let uploadPath: string; + let agent: request.SuperAgentTest; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ @@ -67,19 +72,28 @@ describe('Media', () => { app.useStaticAssets(uploadPath, { prefix: '/uploads', }); + const authConfig = config.get('authConfig') as AuthConfig; + setupSessionMiddleware(app, authConfig); await app.init(); const logger = await app.resolve(ConsoleLoggerService); logger.log('Switching logger', 'AppBootstrap'); app.useLogger(logger); + identityService = moduleRef.get(IdentityService); const notesService: NotesService = moduleRef.get(NotesService); await notesService.createNote('test content', 'test_upload_media'); const userService: UsersService = moduleRef.get(UsersService); - await userService.createUser('hardcoded', 'Testy'); + const user = await userService.createUser('hardcoded', 'Testy'); + await identityService.createLocalIdentity(user, 'test'); + agent = request.agent(app.getHttpServer()); + await agent + .post('/auth/local/login') + .send({ username: 'hardcoded', password: 'test' }) + .expect(201); }); describe('POST /media', () => { it('works', async () => { - const uploadResponse = await request(app.getHttpServer()) + const uploadResponse = await agent .post('/media') .attach('file', 'test/private-api/fixtures/test.png') .set('HedgeDoc-Note', 'test_upload_media') @@ -87,7 +101,7 @@ describe('Media', () => { .expect(201); const path: string = uploadResponse.body.link; const testImage = await fs.readFile('test/private-api/fixtures/test.png'); - const downloadResponse = await request(app.getHttpServer()).get(path); + const downloadResponse = await agent.get(path); expect(downloadResponse.body).toEqual(testImage); // Remove /uploads/ from path as we just need the filename. const fileName = path.replace('/uploads/', ''); @@ -99,7 +113,7 @@ describe('Media', () => { await ensureDeleted(uploadPath); }); it('MIME type not supported', async () => { - await request(app.getHttpServer()) + await agent .post('/media') .attach('file', 'test/private-api/fixtures/test.zip') .set('HedgeDoc-Note', 'test_upload_media') @@ -107,7 +121,7 @@ describe('Media', () => { await expect(fs.access(uploadPath)).rejects.toBeDefined(); }); it('note does not exist', async () => { - await request(app.getHttpServer()) + await agent .post('/media') .attach('file', 'test/private-api/fixtures/test.zip') .set('HedgeDoc-Note', 'i_dont_exist') @@ -118,7 +132,7 @@ describe('Media', () => { await fs.mkdir(uploadPath, { mode: '444', }); - await request(app.getHttpServer()) + await agent .post('/media') .attach('file', 'test/private-api/fixtures/test.png') .set('HedgeDoc-Note', 'test_upload_media') diff --git a/test/private-api/notes.e2e-spec.ts b/test/private-api/notes.e2e-spec.ts index ce77abb74..75cdc5313 100644 --- a/test/private-api/notes.e2e-spec.ts +++ b/test/private-api/notes.e2e-spec.ts @@ -13,6 +13,7 @@ import request from 'supertest'; import { PrivateApiModule } from '../../src/api/private/private-api.module'; import { AuthModule } from '../../src/auth/auth.module'; +import { AuthConfig } from '../../src/config/auth.config'; import appConfigMock from '../../src/config/mock/app.config.mock'; import authConfigMock from '../../src/config/mock/auth.config.mock'; import customizationConfigMock from '../../src/config/mock/customization.config.mock'; @@ -20,6 +21,7 @@ import externalConfigMock from '../../src/config/mock/external-services.config.m import mediaConfigMock from '../../src/config/mock/media.config.mock'; import { NotInDBError } from '../../src/errors/errors'; import { GroupsModule } from '../../src/groups/groups.module'; +import { IdentityService } from '../../src/identity/identity.service'; import { LoggerModule } from '../../src/logger/logger.module'; import { MediaService } from '../../src/media/media.service'; import { NotesModule } from '../../src/notes/notes.module'; @@ -28,17 +30,20 @@ import { PermissionsModule } from '../../src/permissions/permissions.module'; import { User } from '../../src/users/user.entity'; import { UsersModule } from '../../src/users/users.module'; import { UsersService } from '../../src/users/users.service'; +import { setupSessionMiddleware } from '../../src/utils/session'; describe('Notes', () => { let app: INestApplication; let notesService: NotesService; let mediaService: MediaService; + let identityService: IdentityService; let user: User; let user2: User; let content: string; let forbiddenNoteId: string; let uploadPath: string; let testImage: Buffer; + let agent: request.SuperAgentTest; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ @@ -74,18 +79,28 @@ describe('Notes', () => { forbiddenNoteId = config.get('appConfig').forbiddenNoteIds[0]; uploadPath = config.get('mediaConfig').backend.filesystem.uploadPath; app = moduleRef.createNestApplication(); + const authConfig = config.get('authConfig') as AuthConfig; + setupSessionMiddleware(app, authConfig); await app.init(); notesService = moduleRef.get(NotesService); mediaService = moduleRef.get(MediaService); + identityService = moduleRef.get(IdentityService); const userService = moduleRef.get(UsersService); user = await userService.createUser('hardcoded', 'Testy'); + await identityService.createLocalIdentity(user, 'test'); user2 = await userService.createUser('hardcoded2', 'Max Mustermann'); + await identityService.createLocalIdentity(user2, 'test'); content = 'This is a test note.'; testImage = await fs.readFile('test/public-api/fixtures/test.png'); + agent = request.agent(app.getHttpServer()); + await agent + .post('/auth/local/login') + .send({ username: 'hardcoded', password: 'test' }) + .expect(201); }); it('POST /notes', async () => { - const response = await request(app.getHttpServer()) + const response = await agent .post('/notes') .set('Content-Type', 'text/markdown') .send(content) @@ -103,7 +118,7 @@ describe('Notes', () => { it('works with an existing note', async () => { // check if we can succefully get a note that exists await notesService.createNote(content, 'test1', user); - const response = await request(app.getHttpServer()) + const response = await agent .get('/notes/test1') .expect('Content-Type', /json/) .expect(200); @@ -111,7 +126,7 @@ describe('Notes', () => { }); it('fails with an non-existing note', async () => { // check if a missing note correctly returns 404 - await request(app.getHttpServer()) + await agent .get('/notes/i_dont_exist') .expect('Content-Type', /json/) .expect(404); @@ -120,7 +135,7 @@ describe('Notes', () => { describe('POST /notes/{note}', () => { it('works with a non-existing alias', async () => { - const response = await request(app.getHttpServer()) + const response = await agent .post('/notes/test2') .set('Content-Type', 'text/markdown') .send(content) @@ -135,7 +150,7 @@ describe('Notes', () => { }); it('fails with a forbidden alias', async () => { - await request(app.getHttpServer()) + await agent .post(`/notes/${forbiddenNoteId}`) .set('Content-Type', 'text/markdown') .send(content) @@ -144,7 +159,7 @@ describe('Notes', () => { }); it('fails with a existing alias', async () => { - await request(app.getHttpServer()) + await agent .post('/notes/test2') .set('Content-Type', 'text/markdown') .send(content) @@ -159,7 +174,7 @@ describe('Notes', () => { const noteId = 'test3'; const note = await notesService.createNote(content, noteId, user); await mediaService.saveFile(testImage, user, note); - await request(app.getHttpServer()) + await agent .delete(`/notes/${noteId}`) .set('Content-Type', 'application/json') .send({ @@ -176,7 +191,7 @@ describe('Notes', () => { const noteId = 'test3a'; const note = await notesService.createNote(content, noteId, user); const url = await mediaService.saveFile(testImage, user, note); - await request(app.getHttpServer()) + await agent .delete(`/notes/${noteId}`) .set('Content-Type', 'application/json') .send({ @@ -195,21 +210,17 @@ describe('Notes', () => { }); }); it('fails with a forbidden alias', async () => { - await request(app.getHttpServer()) - .delete(`/notes/${forbiddenNoteId}`) - .expect(400); + await agent.delete(`/notes/${forbiddenNoteId}`).expect(400); }); it('fails with a non-existing alias', async () => { - await request(app.getHttpServer()) - .delete('/notes/i_dont_exist') - .expect(404); + await agent.delete('/notes/i_dont_exist').expect(404); }); }); describe('GET /notes/{note}/revisions', () => { it('works with existing alias', async () => { await notesService.createNote(content, 'test4', user); - const response = await request(app.getHttpServer()) + const response = await agent .get('/notes/test4/revisions') .expect('Content-Type', /json/) .expect(200); @@ -217,14 +228,12 @@ describe('Notes', () => { }); it('fails with a forbidden alias', async () => { - await request(app.getHttpServer()) - .get(`/notes/${forbiddenNoteId}/revisions`) - .expect(400); + await agent.get(`/notes/${forbiddenNoteId}/revisions`).expect(400); }); it('fails with non-existing alias', async () => { // check if a missing note correctly returns 404 - await request(app.getHttpServer()) + await agent .get('/notes/i_dont_exist/revisions') .expect('Content-Type', /json/) .expect(404); @@ -236,29 +245,27 @@ describe('Notes', () => { const noteId = 'test8'; const note = await notesService.createNote(content, noteId, user); await notesService.updateNote(note, 'update'); - const responseBeforeDeleting = await request(app.getHttpServer()) + const responseBeforeDeleting = await agent .get('/notes/test8/revisions') .expect('Content-Type', /json/) .expect(200); expect(responseBeforeDeleting.body).toHaveLength(2); - await request(app.getHttpServer()) + await agent .delete(`/notes/${noteId}/revisions`) .set('Content-Type', 'application/json') .expect(204); - const responseAfterDeleting = await request(app.getHttpServer()) + const responseAfterDeleting = await agent .get('/notes/test8/revisions') .expect('Content-Type', /json/) .expect(200); expect(responseAfterDeleting.body).toHaveLength(1); }); it('fails with a forbidden alias', async () => { - await request(app.getHttpServer()) - .delete(`/notes/${forbiddenNoteId}/revisions`) - .expect(400); + await agent.delete(`/notes/${forbiddenNoteId}/revisions`).expect(400); }); it('fails with non-existing alias', async () => { // check if a missing note correctly returns 404 - await request(app.getHttpServer()) + await agent .delete('/notes/i_dont_exist/revisions') .expect('Content-Type', /json/) .expect(404); @@ -269,20 +276,18 @@ describe('Notes', () => { it('works with an existing alias', async () => { const note = await notesService.createNote(content, 'test5', user); const revision = await notesService.getLatestRevision(note); - const response = await request(app.getHttpServer()) + const response = await agent .get(`/notes/test5/revisions/${revision.id}`) .expect('Content-Type', /json/) .expect(200); expect(response.body.content).toEqual(content); }); it('fails with a forbidden alias', async () => { - await request(app.getHttpServer()) - .get(`/notes/${forbiddenNoteId}/revisions/1`) - .expect(400); + await agent.get(`/notes/${forbiddenNoteId}/revisions/1`).expect(400); }); it('fails with non-existing alias', async () => { // check if a missing note correctly returns 404 - await request(app.getHttpServer()) + await agent .get('/notes/i_dont_exist/revisions/1') .expect('Content-Type', /json/) .expect(404); @@ -295,8 +300,7 @@ describe('Notes', () => { const extraAlias = 'test7'; const note1 = await notesService.createNote(content, alias, user); const note2 = await notesService.createNote(content, extraAlias, user); - const httpServer = app.getHttpServer(); - const response = await request(httpServer) + const response = await agent .get(`/notes/${alias}/media/`) .expect('Content-Type', /json/) .expect(200); @@ -306,7 +310,7 @@ describe('Notes', () => { const url0 = await mediaService.saveFile(testImage, user, note1); const url1 = await mediaService.saveFile(testImage, user, note2); - const responseAfter = await request(httpServer) + const responseAfter = await agent .get(`/notes/${alias}/media/`) .expect('Content-Type', /json/) .expect(200); @@ -321,7 +325,7 @@ describe('Notes', () => { await fs.rmdir(uploadPath, { recursive: true }); }); it('fails, when note does not exist', async () => { - await request(app.getHttpServer()) + await agent .get(`/notes/i_dont_exist/media/`) .expect('Content-Type', /json/) .expect(404); @@ -329,7 +333,7 @@ describe('Notes', () => { it("fails, when user can't read note", async () => { const alias = 'test11'; await notesService.createNote('This is a test note.', alias, user2); - await request(app.getHttpServer()) + await agent .get(`/notes/${alias}/media/`) .expect('Content-Type', /json/) .expect(401); From 141b1bf5e8b2f6c6c0c4edfa17196726e56eeac4 Mon Sep 17 00:00:00 2001 From: Yannick Bungers Date: Fri, 8 Oct 2021 13:06:44 +0200 Subject: [PATCH 3/3] Add e2e tests for tokens Signed-off-by: Yannick Bungers --- test/public-api/tokens.e2e-spec.ts | 130 +++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 test/public-api/tokens.e2e-spec.ts diff --git a/test/public-api/tokens.e2e-spec.ts b/test/public-api/tokens.e2e-spec.ts new file mode 100644 index 000000000..6ffb3e9e5 --- /dev/null +++ b/test/public-api/tokens.e2e-spec.ts @@ -0,0 +1,130 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { INestApplication } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { Test } from '@nestjs/testing'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import request from 'supertest'; + +import { PrivateApiModule } from '../../src/api/private/private-api.module'; +import { AuthModule } from '../../src/auth/auth.module'; +import { MockAuthGuard } from '../../src/auth/mock-auth.guard'; +import { TokenAuthGuard } from '../../src/auth/token.strategy'; +import { AuthConfig } from '../../src/config/auth.config'; +import appConfigMock from '../../src/config/mock/app.config.mock'; +import authConfigMock from '../../src/config/mock/auth.config.mock'; +import customizationConfigMock from '../../src/config/mock/customization.config.mock'; +import externalServicesConfigMock from '../../src/config/mock/external-services.config.mock'; +import mediaConfigMock from '../../src/config/mock/media.config.mock'; +import { GroupsModule } from '../../src/groups/groups.module'; +import { HistoryModule } from '../../src/history/history.module'; +import { IdentityService } from '../../src/identity/identity.service'; +import { LoggerModule } from '../../src/logger/logger.module'; +import { MediaModule } from '../../src/media/media.module'; +import { NotesModule } from '../../src/notes/notes.module'; +import { PermissionsModule } from '../../src/permissions/permissions.module'; +import { User } from '../../src/users/user.entity'; +import { UsersModule } from '../../src/users/users.module'; +import { UsersService } from '../../src/users/users.service'; +import { setupSessionMiddleware } from '../../src/utils/session'; + +describe('Tokens', () => { + let app: INestApplication; + let userService: UsersService; + let identityService: IdentityService; + let user: User; + let agent: request.SuperAgentTest; + let keyId: string; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + load: [ + appConfigMock, + authConfigMock, + mediaConfigMock, + customizationConfigMock, + externalServicesConfigMock, + ], + }), + PrivateApiModule, + NotesModule, + PermissionsModule, + GroupsModule, + TypeOrmModule.forRoot({ + type: 'sqlite', + database: './hedgedoc-e2e-private-me.sqlite', + autoLoadEntities: true, + synchronize: true, + dropSchema: true, + }), + LoggerModule, + AuthModule, + UsersModule, + MediaModule, + HistoryModule, + ], + }) + .overrideGuard(TokenAuthGuard) + .useClass(MockAuthGuard) + .compile(); + const config = moduleRef.get(ConfigService); + identityService = moduleRef.get(IdentityService); + app = moduleRef.createNestApplication(); + userService = moduleRef.get(UsersService); + user = await userService.createUser('hardcoded', 'Testy'); + await identityService.createLocalIdentity(user, 'test'); + const authConfig = config.get('authConfig') as AuthConfig; + setupSessionMiddleware(app, authConfig); + await app.init(); + agent = request.agent(app.getHttpServer()); + await agent + .post('/auth/local/login') + .send({ username: 'hardcoded', password: 'test' }) + .expect(201); + }); + + it(`POST /tokens`, async () => { + const tokenName = 'testToken'; + const response = await agent + .post('/tokens') + .send({ + label: tokenName, + }) + .expect('Content-Type', /json/) + .expect(201); + keyId = response.body.keyId; + expect(response.body.label).toBe(tokenName); + expect(response.body.validUntil).toBe(null); + expect(response.body.lastUsed).toBe(null); + expect(response.body.secret.length).toBe(84); + }); + + it(`GET /tokens`, async () => { + const tokenName = 'testToken'; + const response = await agent + .get('/tokens/') + .expect('Content-Type', /json/) + .expect(200); + expect(response.body[0].label).toBe(tokenName); + expect(response.body[0].validUntil).toBe(null); + expect(response.body[0].lastUsed).toBe(null); + expect(response.body[0].secret).not.toBeDefined(); + }); + it(`DELETE /tokens/:keyid`, async () => { + const response = await agent.delete('/tokens/' + keyId).expect(204); + expect(response.body).toStrictEqual({}); + }); + it(`GET /tokens 2`, async () => { + const response = await agent + .get('/tokens/') + .expect('Content-Type', /json/) + .expect(200); + expect(response.body).toStrictEqual([]); + }); +});