From 6a6dc7ea2196b9b6260790503a226cb39217c1e6 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Wed, 24 Feb 2021 21:59:28 +0100 Subject: [PATCH 01/27] ESLint: Re-enable @typescript-eslint/no-explicit-any rule Signed-off-by: David Mehren --- .eslintrc.js | 1 - src/logger/console-logger.service.ts | 14 +++++++------- src/logger/nest-console-logger.service.ts | 10 +++++----- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 16a57233e..cf6e6e7fd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -22,7 +22,6 @@ module.exports = { rules: { '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-unused-vars': [ 'warn', { argsIgnorePattern: '^_+$' }, diff --git a/src/logger/console-logger.service.ts b/src/logger/console-logger.service.ts index f280e6859..d1d818611 100644 --- a/src/logger/console-logger.service.ts +++ b/src/logger/console-logger.service.ts @@ -22,7 +22,7 @@ export class ConsoleLoggerService { this.classContext = context; } - error(message: any, trace = '', functionContext?: string) { + error(message: unknown, trace = '', functionContext?: string) { this.printMessage( message, clc.red, @@ -32,7 +32,7 @@ export class ConsoleLoggerService { this.printStackTrace(trace); } - log(message: any, functionContext?: string) { + log(message: unknown, functionContext?: string) { this.printMessage( message, clc.green, @@ -41,7 +41,7 @@ export class ConsoleLoggerService { ); } - warn(message: any, functionContext?: string) { + warn(message: unknown, functionContext?: string) { this.printMessage( message, clc.yellow, @@ -50,7 +50,7 @@ export class ConsoleLoggerService { ); } - debug(message: any, functionContext?: string) { + debug(message: unknown, functionContext?: string) { this.printMessage( message, clc.magentaBright, @@ -59,7 +59,7 @@ export class ConsoleLoggerService { ); } - verbose(message: any, functionContext?: string) { + verbose(message: unknown, functionContext?: string) { this.printMessage( message, clc.cyanBright, @@ -77,14 +77,14 @@ export class ConsoleLoggerService { } private printMessage( - message: any, + message: unknown, color: (message: string) => string, context = '', isTimeDiffEnabled?: boolean, ) { const output = isObject(message) ? `${color('Object:')}\n${JSON.stringify(message, null, 2)}\n` - : color(message); + : color(message as string); const localeStringOptions: DateTimeFormatOptions = { year: 'numeric', diff --git a/src/logger/nest-console-logger.service.ts b/src/logger/nest-console-logger.service.ts index c31af1ef7..2fe5b62a4 100644 --- a/src/logger/nest-console-logger.service.ts +++ b/src/logger/nest-console-logger.service.ts @@ -12,27 +12,27 @@ Injectable(); export class NestConsoleLoggerService implements LoggerService { private consoleLoggerService = new ConsoleLoggerService(); - debug(message: any, context?: string): any { + debug(message: unknown, context?: string): void { this.consoleLoggerService.setContext(context); this.consoleLoggerService.debug(message); } - error(message: any, trace?: string, context?: string): any { + error(message: unknown, trace?: string, context?: string): void { this.consoleLoggerService.setContext(context); this.consoleLoggerService.error(message, trace); } - log(message: any, context?: string): any { + log(message: unknown, context?: string): void { this.consoleLoggerService.setContext(context); this.consoleLoggerService.log(message); } - verbose(message: any, context?: string): any { + verbose(message: unknown, context?: string): void { this.consoleLoggerService.setContext(context); this.consoleLoggerService.verbose(message); } - warn(message: any, context?: string): any { + warn(message: unknown, context?: string): void { this.consoleLoggerService.setContext(context); this.consoleLoggerService.warn(message); } From 6ffeb2e9c9e9d04e507b02592c435ec330f920a7 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sat, 20 Feb 2021 20:14:36 +0100 Subject: [PATCH 02/27] ESLint: Enable @typescript-eslint/return-await rule This ensures stack traces are helpful at the cost of a slightly lower performance (one more tick in the event loop). Fixes #838 Signed-off-by: David Mehren --- .eslintrc.js | 2 ++ src/api/private/tokens/tokens.controller.ts | 8 ++++++-- src/api/public/me/me.controller.ts | 6 +++--- src/api/public/notes/notes.controller.ts | 14 +++++++------- src/auth/auth.service.ts | 10 +++++----- src/history/history.service.ts | 2 +- src/media/backends/filesystem-backend.ts | 2 +- src/notes/notes.service.ts | 2 +- 8 files changed, 26 insertions(+), 20 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index cf6e6e7fd..8dd058c86 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -27,5 +27,7 @@ module.exports = { { argsIgnorePattern: '^_+$' }, ], '@typescript-eslint/explicit-module-boundary-types': 'off', + 'no-return-await': 'off', + '@typescript-eslint/return-await': ['error', 'always'], }, }; diff --git a/src/api/private/tokens/tokens.controller.ts b/src/api/private/tokens/tokens.controller.ts index 651982b13..cb44b93a8 100644 --- a/src/api/private/tokens/tokens.controller.ts +++ b/src/api/private/tokens/tokens.controller.ts @@ -44,12 +44,16 @@ export class TokensController { @Body('validUntil') validUntil: TimestampMillis, ): Promise { // ToDo: Get real userName - return this.authService.createTokenForUser('hardcoded', label, validUntil); + return await this.authService.createTokenForUser( + 'hardcoded', + label, + validUntil, + ); } @Delete('/:keyId') @HttpCode(204) async deleteToken(@Param('keyId') keyId: string) { - return this.authService.removeToken(keyId); + return await this.authService.removeToken(keyId); } } diff --git a/src/api/public/me/me.controller.ts b/src/api/public/me/me.controller.ts index 89e8eef28..dc46d647d 100644 --- a/src/api/public/me/me.controller.ts +++ b/src/api/public/me/me.controller.ts @@ -53,7 +53,7 @@ export class MeController { @Get('history') async getUserHistory(@Request() req): Promise { const foundEntries = await this.historyService.getEntriesByUser(req.user); - return Promise.all( + return await Promise.all( foundEntries.map( async (entry) => await this.historyService.toHistoryEntryDto(entry), ), @@ -69,7 +69,7 @@ export class MeController { ): Promise { // ToDo: Check if user is allowed to pin this history entry try { - return this.historyService.toHistoryEntryDto( + return await this.historyService.toHistoryEntryDto( await this.historyService.updateHistoryEntry( note, req.user, @@ -103,7 +103,7 @@ export class MeController { @Get('notes') async getMyNotes(@Request() req): Promise { const notes = await this.notesService.getUserNotes(req.user); - return Promise.all( + return await Promise.all( notes.map((note) => this.notesService.toNoteMetadataDto(note)), ); } diff --git a/src/api/public/notes/notes.controller.ts b/src/api/public/notes/notes.controller.ts index e7739529b..e94cb3a57 100644 --- a/src/api/public/notes/notes.controller.ts +++ b/src/api/public/notes/notes.controller.ts @@ -67,7 +67,7 @@ export class NotesController { throw new UnauthorizedException('Creating note denied!'); } this.logger.debug('Got raw markdown:\n' + text); - return this.noteService.toNoteDto( + return await this.noteService.toNoteDto( await this.noteService.createNote(text, undefined, req.user), ); } @@ -91,7 +91,7 @@ export class NotesController { throw new UnauthorizedException('Reading note denied!'); } await this.historyService.createOrUpdateHistoryEntry(note, req.user); - return this.noteService.toNoteDto(note); + return await this.noteService.toNoteDto(note); } @UseGuards(TokenAuthGuard) @@ -106,7 +106,7 @@ export class NotesController { } this.logger.debug('Got raw markdown:\n' + text, 'createNamedNote'); try { - return this.noteService.toNoteDto( + return await this.noteService.toNoteDto( await this.noteService.createNote(text, noteAlias, req.user), ); } catch (e) { @@ -153,7 +153,7 @@ export class NotesController { throw new UnauthorizedException('Updating note denied!'); } this.logger.debug('Got raw markdown:\n' + text, 'updateNote'); - return this.noteService.toNoteDto( + return await this.noteService.toNoteDto( await this.noteService.updateNote(note, text), ); } catch (e) { @@ -196,7 +196,7 @@ export class NotesController { if (!this.permissionsService.mayRead(req.user, note)) { throw new UnauthorizedException('Reading note denied!'); } - return this.noteService.toNoteMetadataDto(note); + return await this.noteService.toNoteMetadataDto(note); } catch (e) { if (e instanceof NotInDBError) { throw new NotFoundException(e.message); @@ -220,7 +220,7 @@ export class NotesController { if (!this.permissionsService.isOwner(req.user, note)) { throw new UnauthorizedException('Updating note denied!'); } - return this.noteService.toNotePermissionsDto( + return await this.noteService.toNotePermissionsDto( await this.noteService.updateNotePermissions(note, updateDto), ); } catch (e) { @@ -243,7 +243,7 @@ export class NotesController { throw new UnauthorizedException('Reading note denied!'); } const revisions = await this.revisionsService.getAllRevisions(note); - return Promise.all( + return await Promise.all( revisions.map((revision) => this.revisionsService.toRevisionMetadataDto(revision), ), diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 25aaaec12..ed3b36c43 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -49,17 +49,17 @@ export class AuthService { } const accessToken = await this.getAuthTokenAndValidate(keyId, secret); await this.setLastUsedToken(keyId); - return this.usersService.getUserByUsername(accessToken.user.userName); + return await this.usersService.getUserByUsername(accessToken.user.userName); } async hashPassword(cleartext: string): Promise { // hash the password with bcrypt and 2^12 iterations // this was decided on the basis of https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#bcrypt - return hash(cleartext, 12); + return await hash(cleartext, 12); } async checkPassword(cleartext: string, password: string): Promise { - return compare(cleartext, password); + return await compare(cleartext, password); } async randomString(length: number): Promise { @@ -209,13 +209,13 @@ export class AuthService { // Delete all non valid tokens every sunday on 3:00 AM @Cron('0 0 3 * * 0') async handleCron() { - return this.removeInvalidTokens(); + return await this.removeInvalidTokens(); } // Delete all non valid tokens 5 sec after startup @Timeout(5000) async handleTimeout() { - return this.removeInvalidTokens(); + return await this.removeInvalidTokens(); } async removeInvalidTokens() { diff --git a/src/history/history.service.ts b/src/history/history.service.ts index 84583220e..1d3d908ac 100644 --- a/src/history/history.service.ts +++ b/src/history/history.service.ts @@ -79,7 +79,7 @@ export class HistoryService { ); } entry.pinStatus = updateDto.pinStatus; - return this.historyEntryRepository.save(entry); + return await this.historyEntryRepository.save(entry); } async deleteHistoryEntry(noteIdOrAlias: string, user: User): Promise { diff --git a/src/media/backends/filesystem-backend.ts b/src/media/backends/filesystem-backend.ts index 918fd450b..93182a06f 100644 --- a/src/media/backends/filesystem-backend.ts +++ b/src/media/backends/filesystem-backend.ts @@ -46,7 +46,7 @@ export class FilesystemBackend implements MediaBackend { async deleteFile(fileName: string, _: BackendData): Promise { const filePath = this.getFilePath(fileName); try { - return fs.unlink(filePath); + return await fs.unlink(filePath); } catch (e) { this.logger.error(e.message, e.stack, 'deleteFile'); throw new MediaBackendError(`Could not delete '${filePath}'`); diff --git a/src/notes/notes.service.ts b/src/notes/notes.service.ts index 5b99e6f78..d278297a2 100644 --- a/src/notes/notes.service.ts +++ b/src/notes/notes.service.ts @@ -202,7 +202,7 @@ export class NotesService { //TODO: Calculate patch revisions.push(Revision.create(noteContent, noteContent)); note.revisions = Promise.resolve(revisions); - return this.noteRepository.save(note); + return await this.noteRepository.save(note); } /** From c3129d40e0cf4947db790c7b5a16e3adb49ee55c Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sat, 20 Feb 2021 20:41:42 +0100 Subject: [PATCH 03/27] ESLint: Remove @typescript-eslint/interface-name-prefix rule The check was removed from typescript-eslint and replaced by the naming-convention rule. Signed-off-by: David Mehren --- .eslintrc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 8dd058c86..3bb81225a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -20,7 +20,6 @@ module.exports = { jest: true, }, rules: { - '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/no-unused-vars': [ 'warn', From 3626ce9dff88ae7d3ef96b95daeb0b334c1b729d Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sat, 20 Feb 2021 21:15:45 +0100 Subject: [PATCH 04/27] ESLint: Enable @typescript-eslint/naming-convention rule This check enforces consistent variable naming. Signed-off-by: David Mehren --- .eslintrc.js | 24 ++++++++++++++++++++++++ src/api/utils/markdownbody-decorator.ts | 2 ++ src/auth/auth.service.spec.ts | 6 +++--- src/auth/auth.service.ts | 6 +++--- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 3bb81225a..35c52642b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -28,5 +28,29 @@ module.exports = { '@typescript-eslint/explicit-module-boundary-types': 'off', 'no-return-await': 'off', '@typescript-eslint/return-await': ['error', 'always'], + '@typescript-eslint/naming-convention': [ + 'error', + { + selector: 'default', + format: ['camelCase'], + leadingUnderscore: 'allow', + trailingUnderscore: 'allow', + }, + { + selector: 'enumMember', + format: ['UPPER_CASE'], + }, + { + selector: 'variable', + format: ['camelCase', 'UPPER_CASE'], + leadingUnderscore: 'allow', + trailingUnderscore: 'allow', + }, + + { + selector: 'typeLike', + format: ['PascalCase'], + }, + ], }, }; diff --git a/src/api/utils/markdownbody-decorator.ts b/src/api/utils/markdownbody-decorator.ts index 1c81df3ea..18f1d448b 100644 --- a/src/api/utils/markdownbody-decorator.ts +++ b/src/api/utils/markdownbody-decorator.ts @@ -18,6 +18,8 @@ import * as getRawBody from 'raw-body'; * * Implementation inspired by https://stackoverflow.com/questions/52283713/how-do-i-pass-plain-text-as-my-request-body-using-nestjs */ +// Override naming convention as decorators are in PascalCase +// eslint-disable-next-line @typescript-eslint/naming-convention export const MarkdownBody = createParamDecorator( async (_, context: ExecutionContext) => { // we have to check req.readable because of raw-body issue #57 diff --git a/src/auth/auth.service.spec.ts b/src/auth/auth.service.spec.ts index 8506b5070..356570fdd 100644 --- a/src/auth/auth.service.spec.ts +++ b/src/auth/auth.service.spec.ts @@ -69,7 +69,7 @@ describe('AuthService', () => { .then((result) => expect(result).toBeTruthy()); }); it('fails, if secret is too short', async () => { - const secret = service.BufferToBase64Url(await service.randomString(54)); + const secret = service.bufferToBase64Url(await service.randomString(54)); const hash = await service.hashPassword(secret); service .checkPassword(secret, hash) @@ -277,10 +277,10 @@ describe('AuthService', () => { }); }); - describe('BufferToBase64Url', () => { + describe('bufferToBase64Url', () => { it('works', () => { expect( - service.BufferToBase64Url( + service.bufferToBase64Url( Buffer.from('testsentence is a test sentence'), ), ).toEqual('dGVzdHNlbnRlbmNlIGlzIGEgdGVzdCBzZW50ZW5jZQ'); diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index ed3b36c43..5d746eb61 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -69,7 +69,7 @@ export class AuthService { return randomBytes(length); } - BufferToBase64Url(text: Buffer): string { + bufferToBase64Url(text: Buffer): string { // This is necessary as the is no base64url encoding in the toString method // but as can be seen on https://tools.ietf.org/html/rfc4648#page-7 // base64url is quite easy buildable from base64 @@ -93,8 +93,8 @@ export class AuthService { `User '${user.userName}' has already 200 tokens and can't have anymore`, ); } - const secret = this.BufferToBase64Url(await this.randomString(54)); - const keyId = this.BufferToBase64Url(await this.randomString(8)); + const secret = this.bufferToBase64Url(await this.randomString(54)); + const keyId = this.bufferToBase64Url(await this.randomString(8)); const accessToken = await this.hashPassword(secret); let token; // Tokens can only be valid for a maximum of 2 years From 0a337f03c5decedea08153ca1208e2058dd72349 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sat, 20 Feb 2021 21:27:37 +0100 Subject: [PATCH 05/27] ESLint: Remove @typescript-eslint/explicit-function-return-type rule The rule does not seem to be enabled at all, as it is not part of the recommended set. Signed-off-by: David Mehren --- .eslintrc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 35c52642b..931ba079b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -20,7 +20,6 @@ module.exports = { jest: true, }, rules: { - '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/no-unused-vars': [ 'warn', { argsIgnorePattern: '^_+$' }, From ba771fb7b6af28176bc46cbe6b4f15f0630e14d3 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Tue, 23 Feb 2021 21:15:10 +0100 Subject: [PATCH 06/27] Enable ESLint rules with type-checks Signed-off-by: David Mehren --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index 931ba079b..67d0a1004 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,6 +12,7 @@ module.exports = { extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', 'prettier', ], root: true, From f3f0360a95f4014e742a314fc9f48045863a27fe Mon Sep 17 00:00:00 2001 From: David Mehren Date: Tue, 23 Feb 2021 21:45:36 +0100 Subject: [PATCH 07/27] Add custom express types We add a custom User object into the request object. To be able to use that object in a strongly-typed situation, we need to tell TypeScript that it exists. Signed-off-by: David Mehren --- types/express/index.d.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 types/express/index.d.ts diff --git a/types/express/index.d.ts b/types/express/index.d.ts new file mode 100644 index 000000000..13bbf4862 --- /dev/null +++ b/types/express/index.d.ts @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { User} from '../../src/users/user.entity'; + +declare module 'express' { + export interface Request { + user?: User; + } +} From 2b14ad92cd49e5fa2e44193e3055d8edd1b1e70c Mon Sep 17 00:00:00 2001 From: David Mehren Date: Tue, 23 Feb 2021 21:14:39 +0100 Subject: [PATCH 08/27] Don't await non-Promises Signed-off-by: David Mehren --- src/api/public/me/me.controller.ts | 2 +- src/api/public/notes/notes.controller.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/public/me/me.controller.ts b/src/api/public/me/me.controller.ts index dc46d647d..db5d48626 100644 --- a/src/api/public/me/me.controller.ts +++ b/src/api/public/me/me.controller.ts @@ -102,7 +102,7 @@ export class MeController { @UseGuards(TokenAuthGuard) @Get('notes') async getMyNotes(@Request() req): Promise { - const notes = await this.notesService.getUserNotes(req.user); + const notes = this.notesService.getUserNotes(req.user); return await Promise.all( notes.map((note) => this.notesService.toNoteMetadataDto(note)), ); diff --git a/src/api/public/notes/notes.controller.ts b/src/api/public/notes/notes.controller.ts index e94cb3a57..da3baab93 100644 --- a/src/api/public/notes/notes.controller.ts +++ b/src/api/public/notes/notes.controller.ts @@ -221,7 +221,7 @@ export class NotesController { throw new UnauthorizedException('Updating note denied!'); } return await this.noteService.toNotePermissionsDto( - await this.noteService.updateNotePermissions(note, updateDto), + this.noteService.updateNotePermissions(note, updateDto), ); } catch (e) { if (e instanceof NotInDBError) { From b78c94c3a15db5db565aa533190df8b0c3b8a989 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Tue, 23 Feb 2021 21:20:01 +0100 Subject: [PATCH 09/27] Use Req decorator instead of Request This avoids a clash with the Request type from express Signed-off-by: David Mehren --- src/api/public/me/me.controller.ts | 12 ++++++------ src/api/public/media/media.controller.ts | 6 +++--- src/api/public/notes/notes.controller.ts | 22 +++++++++++----------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/api/public/me/me.controller.ts b/src/api/public/me/me.controller.ts index db5d48626..b445b0968 100644 --- a/src/api/public/me/me.controller.ts +++ b/src/api/public/me/me.controller.ts @@ -14,7 +14,7 @@ import { Param, Put, UseGuards, - Request, + Req, } from '@nestjs/common'; import { HistoryEntryUpdateDto } from '../../../history/history-entry-update.dto'; import { HistoryService } from '../../../history/history.service'; @@ -43,7 +43,7 @@ export class MeController { @UseGuards(TokenAuthGuard) @Get() - async getMe(@Request() req): Promise { + async getMe(@Req() req): Promise { return this.usersService.toUserDto( await this.usersService.getUserByUsername(req.user.userName), ); @@ -51,7 +51,7 @@ export class MeController { @UseGuards(TokenAuthGuard) @Get('history') - async getUserHistory(@Request() req): Promise { + async getUserHistory(@Req() req): Promise { const foundEntries = await this.historyService.getEntriesByUser(req.user); return await Promise.all( foundEntries.map( @@ -63,7 +63,7 @@ export class MeController { @UseGuards(TokenAuthGuard) @Put('history/:note') async updateHistoryEntry( - @Request() req, + @Req() req, @Param('note') note: string, @Body() entryUpdateDto: HistoryEntryUpdateDto, ): Promise { @@ -87,7 +87,7 @@ export class MeController { @UseGuards(TokenAuthGuard) @Delete('history/:note') @HttpCode(204) - deleteHistoryEntry(@Request() req, @Param('note') note: string) { + deleteHistoryEntry(@Req() req, @Param('note') note: string) { // ToDo: Check if user is allowed to delete note try { return this.historyService.deleteHistoryEntry(note, req.user); @@ -101,7 +101,7 @@ export class MeController { @UseGuards(TokenAuthGuard) @Get('notes') - async getMyNotes(@Request() req): Promise { + async getMyNotes(@Req() req): Promise { const notes = this.notesService.getUserNotes(req.user); return await Promise.all( notes.map((note) => this.notesService.toNoteMetadataDto(note)), diff --git a/src/api/public/media/media.controller.ts b/src/api/public/media/media.controller.ts index 4520e6b91..730f5c35a 100644 --- a/src/api/public/media/media.controller.ts +++ b/src/api/public/media/media.controller.ts @@ -13,7 +13,7 @@ import { NotFoundException, Param, Post, - Request, + Req, UnauthorizedException, UploadedFile, UseGuards, @@ -48,7 +48,7 @@ export class MediaController { @Post() @UseInterceptors(FileInterceptor('file')) async uploadMedia( - @Request() req, + @Req() req, @UploadedFile() file: MulterFile, @Headers('HedgeDoc-Note') noteId: string, ): Promise { @@ -80,7 +80,7 @@ export class MediaController { @UseGuards(TokenAuthGuard) @Delete(':filename') async deleteMedia( - @Request() req, + @Req() req, @Param('filename') filename: string, ): Promise { const username = req.user.userName; diff --git a/src/api/public/notes/notes.controller.ts b/src/api/public/notes/notes.controller.ts index da3baab93..dc110a288 100644 --- a/src/api/public/notes/notes.controller.ts +++ b/src/api/public/notes/notes.controller.ts @@ -15,7 +15,7 @@ import { Param, Post, Put, - Request, + Req, UnauthorizedException, UseGuards, } from '@nestjs/common'; @@ -59,7 +59,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Post() async createNote( - @Request() req, + @Req() req, @MarkdownBody() text: string, ): Promise { // ToDo: provide user for createNoteDto @@ -75,7 +75,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Get(':noteIdOrAlias') async getNote( - @Request() req, + @Req() req, @Param('noteIdOrAlias') noteIdOrAlias: string, ): Promise { let note: Note; @@ -97,7 +97,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Post(':noteAlias') async createNamedNote( - @Request() req, + @Req() req, @Param('noteAlias') noteAlias: string, @MarkdownBody() text: string, ): Promise { @@ -120,7 +120,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Delete(':noteIdOrAlias') async deleteNote( - @Request() req, + @Req() req, @Param('noteIdOrAlias') noteIdOrAlias: string, ): Promise { try { @@ -143,7 +143,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Put(':noteIdOrAlias') async updateNote( - @Request() req, + @Req() req, @Param('noteIdOrAlias') noteIdOrAlias: string, @MarkdownBody() text: string, ): Promise { @@ -168,7 +168,7 @@ export class NotesController { @Get(':noteIdOrAlias/content') @Header('content-type', 'text/markdown') async getNoteContent( - @Request() req, + @Req() req, @Param('noteIdOrAlias') noteIdOrAlias: string, ): Promise { try { @@ -188,7 +188,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Get(':noteIdOrAlias/metadata') async getNoteMetadata( - @Request() req, + @Req() req, @Param('noteIdOrAlias') noteIdOrAlias: string, ): Promise { try { @@ -211,7 +211,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Put(':noteIdOrAlias/metadata/permissions') async updateNotePermissions( - @Request() req, + @Req() req, @Param('noteIdOrAlias') noteIdOrAlias: string, @Body() updateDto: NotePermissionsUpdateDto, ): Promise { @@ -234,7 +234,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Get(':noteIdOrAlias/revisions') async getNoteRevisions( - @Request() req, + @Req() req, @Param('noteIdOrAlias') noteIdOrAlias: string, ): Promise { try { @@ -259,7 +259,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Get(':noteIdOrAlias/revisions/:revisionId') async getNoteRevision( - @Request() req, + @Req() req, @Param('noteIdOrAlias') noteIdOrAlias: string, @Param('revisionId') revisionId: number, ): Promise { From ba4825a99f9ed050ca634afd91b39e946691b688 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Tue, 23 Feb 2021 21:48:37 +0100 Subject: [PATCH 10/27] Add explicit Request type Signed-off-by: David Mehren --- src/api/public/me/me.controller.ts | 11 ++++++----- src/api/public/media/media.controller.ts | 5 +++-- src/api/public/notes/notes.controller.ts | 21 +++++++++++---------- src/auth/mock-auth.guard.ts | 3 ++- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/api/public/me/me.controller.ts b/src/api/public/me/me.controller.ts index b445b0968..0ba65b013 100644 --- a/src/api/public/me/me.controller.ts +++ b/src/api/public/me/me.controller.ts @@ -27,6 +27,7 @@ import { ApiSecurity, ApiTags } from '@nestjs/swagger'; import { HistoryEntryDto } from '../../../history/history-entry.dto'; import { UserInfoDto } from '../../../users/user-info.dto'; import { NotInDBError } from '../../../errors/errors'; +import { Request } from 'express'; @ApiTags('me') @ApiSecurity('token') @@ -43,7 +44,7 @@ export class MeController { @UseGuards(TokenAuthGuard) @Get() - async getMe(@Req() req): Promise { + async getMe(@Req() req: Request): Promise { return this.usersService.toUserDto( await this.usersService.getUserByUsername(req.user.userName), ); @@ -51,7 +52,7 @@ export class MeController { @UseGuards(TokenAuthGuard) @Get('history') - async getUserHistory(@Req() req): Promise { + async getUserHistory(@Req() req: Request): Promise { const foundEntries = await this.historyService.getEntriesByUser(req.user); return await Promise.all( foundEntries.map( @@ -63,7 +64,7 @@ export class MeController { @UseGuards(TokenAuthGuard) @Put('history/:note') async updateHistoryEntry( - @Req() req, + @Req() req: Request, @Param('note') note: string, @Body() entryUpdateDto: HistoryEntryUpdateDto, ): Promise { @@ -87,7 +88,7 @@ export class MeController { @UseGuards(TokenAuthGuard) @Delete('history/:note') @HttpCode(204) - deleteHistoryEntry(@Req() req, @Param('note') note: string) { + deleteHistoryEntry(@Req() req: Request, @Param('note') note: string) { // ToDo: Check if user is allowed to delete note try { return this.historyService.deleteHistoryEntry(note, req.user); @@ -101,7 +102,7 @@ export class MeController { @UseGuards(TokenAuthGuard) @Get('notes') - async getMyNotes(@Req() req): Promise { + async getMyNotes(@Req() req: Request): Promise { const notes = this.notesService.getUserNotes(req.user); return await Promise.all( notes.map((note) => this.notesService.toNoteMetadataDto(note)), diff --git a/src/api/public/media/media.controller.ts b/src/api/public/media/media.controller.ts index 730f5c35a..28fff33c3 100644 --- a/src/api/public/media/media.controller.ts +++ b/src/api/public/media/media.controller.ts @@ -20,6 +20,7 @@ import { UseInterceptors, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; +import { Request } from 'express'; import { ClientError, MediaBackendError, @@ -48,7 +49,7 @@ export class MediaController { @Post() @UseInterceptors(FileInterceptor('file')) async uploadMedia( - @Req() req, + @Req() req: Request, @UploadedFile() file: MulterFile, @Headers('HedgeDoc-Note') noteId: string, ): Promise { @@ -80,7 +81,7 @@ export class MediaController { @UseGuards(TokenAuthGuard) @Delete(':filename') async deleteMedia( - @Req() req, + @Req() req: Request, @Param('filename') filename: string, ): Promise { const username = req.user.userName; diff --git a/src/api/public/notes/notes.controller.ts b/src/api/public/notes/notes.controller.ts index dc110a288..1caa35d35 100644 --- a/src/api/public/notes/notes.controller.ts +++ b/src/api/public/notes/notes.controller.ts @@ -41,6 +41,7 @@ import { RevisionMetadataDto } from '../../../revisions/revision-metadata.dto'; import { RevisionDto } from '../../../revisions/revision.dto'; import { PermissionsService } from '../../../permissions/permissions.service'; import { Note } from '../../../notes/note.entity'; +import { Request } from 'express'; @ApiTags('notes') @ApiSecurity('token') @@ -59,7 +60,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Post() async createNote( - @Req() req, + @Req() req: Request, @MarkdownBody() text: string, ): Promise { // ToDo: provide user for createNoteDto @@ -75,7 +76,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Get(':noteIdOrAlias') async getNote( - @Req() req, + @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, ): Promise { let note: Note; @@ -97,7 +98,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Post(':noteAlias') async createNamedNote( - @Req() req, + @Req() req: Request, @Param('noteAlias') noteAlias: string, @MarkdownBody() text: string, ): Promise { @@ -120,7 +121,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Delete(':noteIdOrAlias') async deleteNote( - @Req() req, + @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, ): Promise { try { @@ -143,7 +144,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Put(':noteIdOrAlias') async updateNote( - @Req() req, + @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, @MarkdownBody() text: string, ): Promise { @@ -168,7 +169,7 @@ export class NotesController { @Get(':noteIdOrAlias/content') @Header('content-type', 'text/markdown') async getNoteContent( - @Req() req, + @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, ): Promise { try { @@ -188,7 +189,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Get(':noteIdOrAlias/metadata') async getNoteMetadata( - @Req() req, + @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, ): Promise { try { @@ -211,7 +212,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Put(':noteIdOrAlias/metadata/permissions') async updateNotePermissions( - @Req() req, + @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, @Body() updateDto: NotePermissionsUpdateDto, ): Promise { @@ -234,7 +235,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Get(':noteIdOrAlias/revisions') async getNoteRevisions( - @Req() req, + @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, ): Promise { try { @@ -259,7 +260,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Get(':noteIdOrAlias/revisions/:revisionId') async getNoteRevision( - @Req() req, + @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, @Param('revisionId') revisionId: number, ): Promise { diff --git a/src/auth/mock-auth.guard.ts b/src/auth/mock-auth.guard.ts index 5f1e40c6b..fc6d166ef 100644 --- a/src/auth/mock-auth.guard.ts +++ b/src/auth/mock-auth.guard.ts @@ -7,6 +7,7 @@ import { ExecutionContext, Injectable } from '@nestjs/common'; import { UsersService } from '../users/users.service'; import { User } from '../users/user.entity'; +import { Request } from 'express'; @Injectable() export class MockAuthGuard { @@ -14,7 +15,7 @@ export class MockAuthGuard { constructor(private usersService: UsersService) {} async canActivate(context: ExecutionContext) { - const req = context.switchToHttp().getRequest(); + const req: Request = context.switchToHttp().getRequest(); if (!this.user) { // this assures that we can create the user 'hardcoded', if we need them before any calls are made or // create them on the fly when the first call to the api is made From b5281991ef40c91b878979118cb6dfb57b376cca Mon Sep 17 00:00:00 2001 From: David Mehren Date: Tue, 23 Feb 2021 22:16:27 +0100 Subject: [PATCH 11/27] AuthService: randomString does not need to by async Signed-off-by: David Mehren --- src/auth/auth.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 5d746eb61..0439686bc 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -62,7 +62,7 @@ export class AuthService { return await compare(cleartext, password); } - async randomString(length: number): Promise { + randomString(length: number): Buffer { if (length <= 0) { return null; } @@ -93,8 +93,8 @@ export class AuthService { `User '${user.userName}' has already 200 tokens and can't have anymore`, ); } - const secret = this.bufferToBase64Url(await this.randomString(54)); - const keyId = this.bufferToBase64Url(await this.randomString(8)); + const secret = this.bufferToBase64Url(this.randomString(54)); + const keyId = this.bufferToBase64Url(this.randomString(8)); const accessToken = await this.hashPassword(secret); let token; // Tokens can only be valid for a maximum of 2 years From d8d105ed753eca148be21732649c0567dabfe012 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Wed, 24 Feb 2021 21:17:05 +0100 Subject: [PATCH 12/27] NotesService: toNotePermissionsDto does not need to be async Signed-off-by: David Mehren --- src/api/public/notes/notes.controller.ts | 2 +- src/notes/notes.service.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/api/public/notes/notes.controller.ts b/src/api/public/notes/notes.controller.ts index 1caa35d35..0960fb7b4 100644 --- a/src/api/public/notes/notes.controller.ts +++ b/src/api/public/notes/notes.controller.ts @@ -221,7 +221,7 @@ export class NotesController { if (!this.permissionsService.isOwner(req.user, note)) { throw new UnauthorizedException('Updating note denied!'); } - return await this.noteService.toNotePermissionsDto( + return this.noteService.toNotePermissionsDto( this.noteService.updateNotePermissions(note, updateDto), ); } catch (e) { diff --git a/src/notes/notes.service.ts b/src/notes/notes.service.ts index d278297a2..42ac32bd0 100644 --- a/src/notes/notes.service.ts +++ b/src/notes/notes.service.ts @@ -295,12 +295,11 @@ export class NotesService { } /** - * @async * Build NotePermissionsDto from a note. * @param {Note} note - the note to use * @return {NotePermissionsDto} the built NotePermissionDto */ - async toNotePermissionsDto(note: Note): Promise { + toNotePermissionsDto(note: Note): Promise { return { owner: this.usersService.toUserDto(note.owner), sharedToUsers: note.userPermissions.map((noteUserPermission) => ({ @@ -331,7 +330,7 @@ export class NotesService { editedBy: note.authorColors.map( (authorColor) => authorColor.user.userName, ), - permissions: await this.toNotePermissionsDto(note), + permissions: this.toNotePermissionsDto(note), tags: this.toTagList(note), updateTime: (await this.getLatestRevision(note)).createdAt, updateUser: this.usersService.toUserDto( From b37b2d10477d6b641af604c812157b4cbfe309a1 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Wed, 24 Feb 2021 21:19:48 +0100 Subject: [PATCH 13/27] HistoryService: toHistoryEntryDto does not need to be async Signed-off-by: David Mehren --- src/api/public/me/me.controller.ts | 6 ++---- src/history/history.service.spec.ts | 4 ++-- src/history/history.service.ts | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/api/public/me/me.controller.ts b/src/api/public/me/me.controller.ts index 0ba65b013..f68851a5b 100644 --- a/src/api/public/me/me.controller.ts +++ b/src/api/public/me/me.controller.ts @@ -55,9 +55,7 @@ export class MeController { async getUserHistory(@Req() req: Request): Promise { const foundEntries = await this.historyService.getEntriesByUser(req.user); return await Promise.all( - foundEntries.map( - async (entry) => await this.historyService.toHistoryEntryDto(entry), - ), + foundEntries.map((entry) => this.historyService.toHistoryEntryDto(entry)), ); } @@ -70,7 +68,7 @@ export class MeController { ): Promise { // ToDo: Check if user is allowed to pin this history entry try { - return await this.historyService.toHistoryEntryDto( + return this.historyService.toHistoryEntryDto( await this.historyService.updateHistoryEntry( note, req.user, diff --git a/src/history/history.service.spec.ts b/src/history/history.service.spec.ts index af4613dc1..bea7a67cd 100644 --- a/src/history/history.service.spec.ts +++ b/src/history/history.service.spec.ts @@ -248,7 +248,7 @@ describe('HistoryService', () => { const historyEntry = HistoryEntry.create(user, note); historyEntry.pinStatus = true; jest.spyOn(noteRepo, 'findOne').mockResolvedValueOnce(note); - const historyEntryDto = await service.toHistoryEntryDto(historyEntry); + const historyEntryDto = service.toHistoryEntryDto(historyEntry); expect(historyEntryDto.pinStatus).toEqual(true); expect(historyEntryDto.identifier).toEqual(alias); expect(historyEntryDto.tags).toEqual(tags); @@ -271,7 +271,7 @@ describe('HistoryService', () => { const historyEntry = HistoryEntry.create(user, note); historyEntry.pinStatus = true; jest.spyOn(noteRepo, 'findOne').mockResolvedValueOnce(note); - const historyEntryDto = await service.toHistoryEntryDto(historyEntry); + const historyEntryDto = service.toHistoryEntryDto(historyEntry); expect(historyEntryDto.pinStatus).toEqual(true); expect(historyEntryDto.identifier).toEqual(id); expect(historyEntryDto.tags).toEqual(tags); diff --git a/src/history/history.service.ts b/src/history/history.service.ts index 1d3d908ac..aef7bc389 100644 --- a/src/history/history.service.ts +++ b/src/history/history.service.ts @@ -93,7 +93,7 @@ export class HistoryService { return; } - async toHistoryEntryDto(entry: HistoryEntry): Promise { + toHistoryEntryDto(entry: HistoryEntry): HistoryEntryDto { return { identifier: entry.note.alias ? entry.note.alias : entry.note.id, lastVisited: entry.updatedAt, From c5fb87de050a41a9a9fe8673504011f81236fcb2 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Wed, 24 Feb 2021 20:29:39 +0100 Subject: [PATCH 14/27] Fix various ESLint errors in configs Signed-off-by: David Mehren --- src/config/app.config.ts | 6 +++--- src/config/auth.config.ts | 6 +++--- src/config/csp.config.ts | 6 +++--- src/config/database.config.ts | 6 +++--- src/config/hsts.config.ts | 6 +++--- src/config/media.config.ts | 6 +++--- src/config/utils.spec.ts | 6 ++++++ src/config/utils.ts | 2 +- 8 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/config/app.config.ts b/src/config/app.config.ts index 69573ecc2..85b52d70b 100644 --- a/src/config/app.config.ts +++ b/src/config/app.config.ts @@ -25,7 +25,7 @@ const schema = Joi.object({ .label('HD_LOGLEVEL'), }); -export default registerAs('appConfig', async () => { +export default registerAs('appConfig', () => { const appConfig = schema.validate( { domain: process.env.HD_DOMAIN, @@ -38,10 +38,10 @@ export default registerAs('appConfig', async () => { }, ); if (appConfig.error) { - const errorMessages = await appConfig.error.details.map( + const errorMessages = appConfig.error.details.map( (detail) => detail.message, ); throw new Error(buildErrorMessage(errorMessages)); } - return appConfig.value; + return appConfig.value as AppConfig; }); diff --git a/src/config/auth.config.ts b/src/config/auth.config.ts index 1d7ce0269..8cc4132e7 100644 --- a/src/config/auth.config.ts +++ b/src/config/auth.config.ts @@ -227,7 +227,7 @@ const authSchema = Joi.object({ .optional(), }); -export default registerAs('authConfig', async () => { +export default registerAs('authConfig', () => { // ToDo: Validate these with Joi to prevent duplicate entries? const gitlabNames = toArrayConfig( process.env.HD_AUTH_GITLABS, @@ -367,7 +367,7 @@ export default registerAs('authConfig', async () => { }, ); if (authConfig.error) { - const errorMessages = await authConfig.error.details + const errorMessages = authConfig.error.details .map((detail) => detail.message) .map((error) => { error = replaceAuthErrorsWithEnvironmentVariables( @@ -398,5 +398,5 @@ export default registerAs('authConfig', async () => { }); throw new Error(buildErrorMessage(errorMessages)); } - return authConfig.value; + return authConfig.value as AuthConfig; }); diff --git a/src/config/csp.config.ts b/src/config/csp.config.ts index 15da192c9..a3fe24fe8 100644 --- a/src/config/csp.config.ts +++ b/src/config/csp.config.ts @@ -18,7 +18,7 @@ const cspSchema = Joi.object({ reportURI: Joi.string().optional().label('HD_CSP_REPORT_URI'), }); -export default registerAs('cspConfig', async () => { +export default registerAs('cspConfig', () => { const cspConfig = cspSchema.validate( { enable: process.env.HD_CSP_ENABLE || true, @@ -30,10 +30,10 @@ export default registerAs('cspConfig', async () => { }, ); if (cspConfig.error) { - const errorMessages = await cspConfig.error.details.map( + const errorMessages = cspConfig.error.details.map( (detail) => detail.message, ); throw new Error(buildErrorMessage(errorMessages)); } - return cspConfig.value; + return cspConfig.value as CspConfig; }); diff --git a/src/config/database.config.ts b/src/config/database.config.ts index 652d74249..da437faf5 100644 --- a/src/config/database.config.ts +++ b/src/config/database.config.ts @@ -55,7 +55,7 @@ const databaseSchema = Joi.object({ .label('HD_DATABASE_DIALECT'), }); -export default registerAs('databaseConfig', async () => { +export default registerAs('databaseConfig', () => { const databaseConfig = databaseSchema.validate( { username: process.env.HD_DATABASE_USER, @@ -72,10 +72,10 @@ export default registerAs('databaseConfig', async () => { }, ); if (databaseConfig.error) { - const errorMessages = await databaseConfig.error.details.map( + const errorMessages = databaseConfig.error.details.map( (detail) => detail.message, ); throw new Error(buildErrorMessage(errorMessages)); } - return databaseConfig.value; + return databaseConfig.value as DatabaseConfig; }); diff --git a/src/config/hsts.config.ts b/src/config/hsts.config.ts index c64f3f416..0a1aa6916 100644 --- a/src/config/hsts.config.ts +++ b/src/config/hsts.config.ts @@ -28,7 +28,7 @@ const hstsSchema = Joi.object({ preload: Joi.boolean().default(true).optional().label('HD_HSTS_PRELOAD'), }); -export default registerAs('hstsConfig', async () => { +export default registerAs('hstsConfig', () => { const hstsConfig = hstsSchema.validate( { enable: process.env.HD_HSTS_ENABLE, @@ -42,10 +42,10 @@ export default registerAs('hstsConfig', async () => { }, ); if (hstsConfig.error) { - const errorMessages = await hstsConfig.error.details.map( + const errorMessages = hstsConfig.error.details.map( (detail) => detail.message, ); throw new Error(buildErrorMessage(errorMessages)); } - return hstsConfig.value; + return hstsConfig.value as HstsConfig; }); diff --git a/src/config/media.config.ts b/src/config/media.config.ts index 5f1d1e440..f06fc0791 100644 --- a/src/config/media.config.ts +++ b/src/config/media.config.ts @@ -75,7 +75,7 @@ const mediaSchema = Joi.object({ }, }); -export default registerAs('mediaConfig', async () => { +export default registerAs('mediaConfig', () => { const mediaConfig = mediaSchema.validate( { backend: { @@ -106,10 +106,10 @@ export default registerAs('mediaConfig', async () => { }, ); if (mediaConfig.error) { - const errorMessages = await mediaConfig.error.details.map( + const errorMessages = mediaConfig.error.details.map( (detail) => detail.message, ); throw new Error(buildErrorMessage(errorMessages)); } - return mediaConfig.value; + return mediaConfig.value as MediaConfig; }); diff --git a/src/config/utils.spec.ts b/src/config/utils.spec.ts index c611c53cf..72d5196a5 100644 --- a/src/config/utils.spec.ts +++ b/src/config/utils.spec.ts @@ -4,6 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +/* eslint-disable +@typescript-eslint/no-unsafe-call, +@typescript-eslint/no-unsafe-member-access, +@typescript-eslint/no-unsafe-return, +@typescript-eslint/require-await */ + import { replaceAuthErrorsWithEnvironmentVariables, toArrayConfig, diff --git a/src/config/utils.ts b/src/config/utils.ts index 041d91301..c0d62214c 100644 --- a/src/config/utils.ts +++ b/src/config/utils.ts @@ -37,7 +37,7 @@ export const replaceAuthErrorsWithEnvironmentVariables = ( const regex = new RegExp('"' + name + '\\[(\\d+)]\\.', 'g'); message = message.replace( regex, - (_, index) => `"${replacement}${arrayOfNames[index]}.`, + (_, index: number) => `"${replacement}${arrayOfNames[index]}.`, ); message = message.replace('.providerName', '_PROVIDER_NAME'); message = message.replace('.baseURL', '_BASE_URL'); From 8c3bf66469478b4d78e00ed46d624c0d44488911 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Wed, 24 Feb 2021 20:20:04 +0100 Subject: [PATCH 15/27] Fix various ESLint errors in unit tests Signed-off-by: David Mehren --- src/auth/auth.service.spec.ts | 20 +++++++++++++------- src/groups/groups.service.spec.ts | 2 +- src/history/history.service.spec.ts | 6 ++++++ src/media/media.service.spec.ts | 8 +++++++- src/monitoring/monitoring.service.ts | 2 ++ src/notes/notes.service.spec.ts | 20 +++++++++++++------- src/permissions/permissions.service.spec.ts | 16 +++++++++++----- 7 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/auth/auth.service.spec.ts b/src/auth/auth.service.spec.ts index 356570fdd..0aaa41ae3 100644 --- a/src/auth/auth.service.spec.ts +++ b/src/auth/auth.service.spec.ts @@ -4,6 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +/* eslint-disable +@typescript-eslint/no-unsafe-call, +@typescript-eslint/no-unsafe-member-access, +@typescript-eslint/no-unsafe-return, +@typescript-eslint/require-await */ + import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; import { PassportModule } from '@nestjs/passport'; @@ -64,17 +70,17 @@ describe('AuthService', () => { it('works', async () => { const testPassword = 'thisIsATestPassword'; const hash = await service.hashPassword(testPassword); - service + void service .checkPassword(testPassword, hash) .then((result) => expect(result).toBeTruthy()); }); it('fails, if secret is too short', async () => { - const secret = service.bufferToBase64Url(await service.randomString(54)); + const secret = service.bufferToBase64Url(service.randomString(54)); const hash = await service.hashPassword(secret); - service + void service .checkPassword(secret, hash) .then((result) => expect(result).toBeTruthy()); - service + void service .checkPassword(secret.substr(0, secret.length - 1), hash) .then((result) => expect(result).toBeFalsy()); }); @@ -194,12 +200,12 @@ describe('AuthService', () => { }); describe('fails:', () => { it('the secret is missing', () => { - expect( + void expect( service.validateToken(`${authToken.keyId}`), ).rejects.toBeInstanceOf(TokenNotValidError); }); it('the secret is too long', () => { - expect( + void expect( service.validateToken(`${authToken.keyId}.${'a'.repeat(73)}`), ).rejects.toBeInstanceOf(TokenNotValidError); }); @@ -293,7 +299,7 @@ describe('AuthService', () => { authToken.keyId = 'testKeyId'; authToken.label = 'testLabel'; authToken.createdAt = new Date(); - const tokenDto = await service.toAuthTokenDto(authToken); + const tokenDto = service.toAuthTokenDto(authToken); expect(tokenDto.keyId).toEqual(authToken.keyId); expect(tokenDto.lastUsed).toBeNull(); expect(tokenDto.label).toEqual(authToken.label); diff --git a/src/groups/groups.service.spec.ts b/src/groups/groups.service.spec.ts index 827d2d1f0..840fd2029 100644 --- a/src/groups/groups.service.spec.ts +++ b/src/groups/groups.service.spec.ts @@ -31,7 +31,7 @@ describe('GroupsService', () => { service = module.get(GroupsService); groupRepo = module.get>(getRepositoryToken(Group)); - group = Group.create('testGroup', 'Superheros') as Group; + group = Group.create('testGroup', 'Superheros'); }); it('should be defined', () => { diff --git a/src/history/history.service.spec.ts b/src/history/history.service.spec.ts index bea7a67cd..fe104eb1c 100644 --- a/src/history/history.service.spec.ts +++ b/src/history/history.service.spec.ts @@ -4,6 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +/* eslint-disable +@typescript-eslint/no-unsafe-call, +@typescript-eslint/no-unsafe-member-access, +@typescript-eslint/no-unsafe-return, +@typescript-eslint/require-await */ + import { Test, TestingModule } from '@nestjs/testing'; import { LoggerModule } from '../logger/logger.module'; import { HistoryService } from './history.service'; diff --git a/src/media/media.service.spec.ts b/src/media/media.service.spec.ts index 8773be3da..d84a98dba 100644 --- a/src/media/media.service.spec.ts +++ b/src/media/media.service.spec.ts @@ -4,6 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +/* eslint-disable +@typescript-eslint/no-unsafe-call, +@typescript-eslint/no-unsafe-member-access, +@typescript-eslint/no-unsafe-return, +@typescript-eslint/require-await */ + import { ConfigModule } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; @@ -96,7 +102,7 @@ describe('MediaService', () => { }); describe('saveFile', () => { - beforeEach(async () => { + beforeEach(() => { const user = User.create('hardcoded', 'Testy') as User; const alias = 'alias'; const note = Note.create(user, alias); diff --git a/src/monitoring/monitoring.service.ts b/src/monitoring/monitoring.service.ts index 92ef6452d..6c4f18287 100644 --- a/src/monitoring/monitoring.service.ts +++ b/src/monitoring/monitoring.service.ts @@ -22,6 +22,8 @@ async function getServerVersionFromPackageJson() { joinPath(__dirname, '../../package.json'), { encoding: 'utf8' }, ); + // TODO: Should this be validated in more detail? + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const packageInfo: { version: string } = JSON.parse(rawFileContent); const versionParts: number[] = packageInfo.version .split('.') diff --git a/src/notes/notes.service.spec.ts b/src/notes/notes.service.spec.ts index 18a02dba5..f1858f30b 100644 --- a/src/notes/notes.service.spec.ts +++ b/src/notes/notes.service.spec.ts @@ -4,6 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable +@typescript-eslint/no-unsafe-assignment, +@typescript-eslint/no-unsafe-member-access +*/ + import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { LoggerModule } from '../logger/logger.module'; @@ -189,7 +195,7 @@ describe('NotesService', () => { const newNote = await service.createNote(content); const revisions = await newNote.revisions; jest.spyOn(revisionRepo, 'findOne').mockResolvedValueOnce(revisions[0]); - service.getNoteContent(newNote).then((result) => { + void service.getNoteContent(newNote).then((result) => { expect(result).toEqual(content); }); }); @@ -204,7 +210,7 @@ describe('NotesService', () => { const newNote = await service.createNote(content); const revisions = await newNote.revisions; jest.spyOn(revisionRepo, 'findOne').mockResolvedValueOnce(revisions[0]); - service.getLatestRevision(newNote).then((result) => { + void service.getLatestRevision(newNote).then((result) => { expect(result).toEqual(revisions[0]); }); }); @@ -221,7 +227,7 @@ describe('NotesService', () => { const newNote = await service.createNote(content); const revisions = await newNote.revisions; jest.spyOn(revisionRepo, 'findOne').mockResolvedValueOnce(revisions[0]); - service.getLatestRevision(newNote).then((result) => { + void service.getLatestRevision(newNote).then((result) => { expect(result).toEqual(revisions[0]); }); }); @@ -594,7 +600,7 @@ describe('NotesService', () => { describe('toNotePermissionsDto', () => { it('works', async () => { const user = User.create('hardcoded', 'Testy') as User; - const group = Group.create('testGroup', 'testGroup') as Group; + const group = Group.create('testGroup', 'testGroup'); const note = Note.create(user); note.userPermissions = [ { @@ -610,7 +616,7 @@ describe('NotesService', () => { canEdit: true, }, ]; - const permissions = await service.toNotePermissionsDto(note); + const permissions = service.toNotePermissionsDto(note); expect(permissions.owner.userName).toEqual(user.userName); expect(permissions.sharedToUsers).toHaveLength(1); expect(permissions.sharedToUsers[0].user.userName).toEqual(user.userName); @@ -627,7 +633,7 @@ describe('NotesService', () => { it('works', async () => { const user = User.create('hardcoded', 'Testy') as User; const otherUser = User.create('other hardcoded', 'Testy2') as User; - const group = Group.create('testGroup', 'testGroup') as Group; + const group = Group.create('testGroup', 'testGroup'); const content = 'testContent'; jest .spyOn(noteRepo, 'save') @@ -718,7 +724,7 @@ describe('NotesService', () => { const user = User.create('hardcoded', 'Testy') as User; const otherUser = User.create('other hardcoded', 'Testy2') as User; otherUser.userName = 'other hardcoded user'; - const group = Group.create('testGroup', 'testGroup') as Group; + const group = Group.create('testGroup', 'testGroup'); const content = 'testContent'; jest .spyOn(noteRepo, 'save') diff --git a/src/permissions/permissions.service.spec.ts b/src/permissions/permissions.service.spec.ts index 6396eb09c..d123cdd2c 100644 --- a/src/permissions/permissions.service.spec.ts +++ b/src/permissions/permissions.service.spec.ts @@ -4,6 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +/* eslint-disable +@typescript-eslint/no-unsafe-call, +@typescript-eslint/no-unsafe-member-access, +@typescript-eslint/no-unsafe-return, +@typescript-eslint/require-await */ + import { Test, TestingModule } from '@nestjs/testing'; import { LoggerModule } from '../logger/logger.module'; import { GuestPermission, PermissionsService } from './permissions.service'; @@ -350,7 +356,7 @@ describe('PermissionsService', () => { for (const group2 of noteGroupPermissions[2]) { for (const group3 of noteGroupPermissions[3]) { for (const group4 of noteGroupPermissions[4]) { - const insert = []; + const insert: NoteGroupPermission[] = []; let readPermission = false; let writePermission = false; if (group0 !== null) { @@ -408,7 +414,7 @@ describe('PermissionsService', () => { ): NoteGroupPermission[][] { const results = []; - function permute(arr, memo) { + function permute(arr: NoteGroupPermission[], memo: NoteGroupPermission[]) { let cur; for (let i = 0; i < arr.length; i++) { @@ -456,15 +462,15 @@ describe('PermissionsService', () => { note.groupPermissions = permission.permissions; let permissionString = ''; for (const perm of permission.permissions) { - permissionString += ' ' + perm.group.name + ':' + perm.canEdit; + permissionString += ` ${perm.group.name}:${String(perm.canEdit)}`; } - it('mayWrite - test #' + i + ':' + permissionString, () => { + it(`mayWrite - test #${i}:${permissionString}`, () => { permissionsService.guestPermission = guestPermission; expect(permissionsService.mayWrite(user1, note)).toEqual( permission.allowsWrite, ); }); - it('mayRead - test #' + i + ':' + permissionString, () => { + it(`mayRead - test #${i}:${permissionString}`, () => { permissionsService.guestPermission = guestPermission; expect(permissionsService.mayRead(user1, note)).toEqual( permission.allowsRead, From 4d94739a9f7660a953a33e6900b227c0bbb3044f Mon Sep 17 00:00:00 2001 From: David Mehren Date: Wed, 24 Feb 2021 21:14:16 +0100 Subject: [PATCH 16/27] Fix various ESLint errors in E2E tests Signed-off-by: David Mehren --- test/public-api/me.e2e-spec.ts | 11 ++++++++--- test/public-api/media.e2e-spec.ts | 7 ++++++- test/public-api/notes.e2e-spec.ts | 9 +++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/test/public-api/me.e2e-spec.ts b/test/public-api/me.e2e-spec.ts index 42f73c940..aa4733080 100644 --- a/test/public-api/me.e2e-spec.ts +++ b/test/public-api/me.e2e-spec.ts @@ -4,6 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +/* eslint-disable +@typescript-eslint/no-unsafe-assignment, +@typescript-eslint/no-unsafe-member-access +*/ + import { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import * as request from 'supertest'; @@ -94,7 +99,7 @@ describe('Notes', () => { .expect(200); const history = response.body; for (const historyEntry of history) { - if ((historyEntry).identifier === 'testGetHistory') { + if (historyEntry.identifier === 'testGetHistory') { expect(historyEntry).toEqual(createdHistoryEntry); } } @@ -116,7 +121,7 @@ describe('Notes', () => { historyEntry = null; for (const e of history) { if (e.note.alias === noteName) { - historyEntry = await historyService.toHistoryEntryDto(e); + historyEntry = historyService.toHistoryEntryDto(e); } } expect(historyEntry.pinStatus).toEqual(true); @@ -133,7 +138,7 @@ describe('Notes', () => { const history = await historyService.getEntriesByUser(user); let historyEntry: HistoryEntry = null; for (const e of history) { - if ((e).note.alias === noteName) { + if (e.note.alias === noteName) { historyEntry = e; } } diff --git a/test/public-api/media.e2e-spec.ts b/test/public-api/media.e2e-spec.ts index f2a1cf8bd..89843b1cf 100644 --- a/test/public-api/media.e2e-spec.ts +++ b/test/public-api/media.e2e-spec.ts @@ -4,6 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +/* eslint-disable +@typescript-eslint/no-unsafe-assignment, +@typescript-eslint/no-unsafe-member-access +*/ + import { ConfigModule, ConfigService } from '@nestjs/config'; import { NestExpressApplication } from '@nestjs/platform-express'; import { Test } from '@nestjs/testing'; @@ -78,7 +83,7 @@ describe('Notes', () => { .set('HedgeDoc-Note', 'test_upload_media') .expect('Content-Type', /json/) .expect(201); - const path = uploadResponse.body.link; + const path: string = uploadResponse.body.link; const testImage = await fs.readFile('test/public-api/fixtures/test.png'); const downloadResponse = await request(app.getHttpServer()).get(path); expect(downloadResponse.body).toEqual(testImage); diff --git a/test/public-api/notes.e2e-spec.ts b/test/public-api/notes.e2e-spec.ts index f201f3d67..4aaa18ef1 100644 --- a/test/public-api/notes.e2e-spec.ts +++ b/test/public-api/notes.e2e-spec.ts @@ -4,6 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +/* eslint-disable +@typescript-eslint/no-unsafe-assignment, +@typescript-eslint/no-unsafe-member-access +*/ + import { INestApplication } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { Test } from '@nestjs/testing'; @@ -149,7 +154,7 @@ describe('Notes', () => { .set('Content-Type', 'text/markdown') .send(changedContent) .expect(200); - await expect( + expect( await notesService.getNoteContent( await notesService.getNoteByIdOrAlias('test4'), ), @@ -239,7 +244,7 @@ describe('Notes', () => { const note = await notesService.createNote(content, 'test7', user); const revision = await notesService.getLatestRevision(note); const response = await request(app.getHttpServer()) - .get('/notes/test7/revisions/' + revision.id) + .get(`/notes/test7/revisions/${revision.id}`) .expect('Content-Type', /json/) .expect(200); expect(response.body.content).toEqual(content); From 1cc86a728ab8027527e1af83711573000d589330 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Wed, 24 Feb 2021 22:35:06 +0100 Subject: [PATCH 17/27] Fix various ESLint errors in services Signed-off-by: David Mehren --- src/auth/auth.service.ts | 11 ++++++++--- src/groups/groups.service.ts | 2 +- src/notes/notes.service.ts | 2 +- src/users/users.service.ts | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 0439686bc..e078064de 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -117,7 +117,9 @@ export class AuthService { new Date(validUntil), ); } - const createdToken = await this.authTokenRepository.save(token); + const createdToken = (await this.authTokenRepository.save( + token, + )) as AuthToken; return this.toAuthTokenWithSecretDto(createdToken, `${keyId}.${secret}`); } @@ -150,7 +152,7 @@ export class AuthService { ) { // tokens validUntil Date lies in the past throw new TokenNotValidError( - `AuthToken '${token}' is not valid since ${accessToken.validUntil}.`, + `AuthToken '${token}' is not valid since ${accessToken.validUntil.toISOString()}.`, ); } return accessToken; @@ -173,7 +175,10 @@ export class AuthService { toAuthTokenDto(authToken: AuthToken): AuthTokenDto | null { if (!authToken) { - this.logger.warn(`Recieved ${authToken} argument!`, 'toAuthTokenDto'); + this.logger.warn( + `Recieved ${String(authToken)} argument!`, + 'toAuthTokenDto', + ); return null; } const tokenDto: AuthTokenDto = { diff --git a/src/groups/groups.service.ts b/src/groups/groups.service.ts index 042ef0996..ea2e7fd0d 100644 --- a/src/groups/groups.service.ts +++ b/src/groups/groups.service.ts @@ -45,7 +45,7 @@ export class GroupsService { */ toGroupDto(group: Group | null | undefined): GroupInfoDto | null { if (!group) { - this.logger.warn(`Recieved ${group} argument!`, 'toGroupDto'); + this.logger.warn(`Recieved ${String(group)} argument!`, 'toGroupDto'); return null; } return { diff --git a/src/notes/notes.service.ts b/src/notes/notes.service.ts index 42ac32bd0..0ff2e7112 100644 --- a/src/notes/notes.service.ts +++ b/src/notes/notes.service.ts @@ -299,7 +299,7 @@ export class NotesService { * @param {Note} note - the note to use * @return {NotePermissionsDto} the built NotePermissionDto */ - toNotePermissionsDto(note: Note): Promise { + toNotePermissionsDto(note: Note): NotePermissionsDto { return { owner: this.usersService.toUserDto(note.owner), sharedToUsers: note.userPermissions.map((noteUserPermission) => ({ diff --git a/src/users/users.service.ts b/src/users/users.service.ts index b29b0e30e..814743b95 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -56,7 +56,7 @@ export class UsersService { toUserDto(user: User | null | undefined): UserInfoDto | null { if (!user) { - this.logger.warn(`Recieved ${user} argument!`, 'toUserDto'); + this.logger.warn(`Recieved ${String(user)} argument!`, 'toUserDto'); return null; } return { From 2c841ae57879d64bf2672dcf9b93065ff8d2454f Mon Sep 17 00:00:00 2001 From: David Mehren Date: Wed, 24 Feb 2021 21:08:08 +0100 Subject: [PATCH 18/27] Fix ESLint errors in main.ts Signed-off-by: David Mehren --- src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index 36a4f596d..ccecea528 100644 --- a/src/main.ts +++ b/src/main.ts @@ -49,4 +49,4 @@ async function bootstrap() { logger.log(`Listening on port ${appConfig.port}`, 'AppBootstrap'); } -bootstrap(); +void bootstrap(); From a0ffa3be046deda8701297fcc8a2438b5033731e Mon Sep 17 00:00:00 2001 From: David Mehren Date: Wed, 24 Feb 2021 21:10:24 +0100 Subject: [PATCH 19/27] NoteEntity: Fix ESLint errors We now use @types/shortid to provide type information Signed-off-by: David Mehren --- package.json | 1 + src/notes/note.entity.ts | 2 +- yarn.lock | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 84060db2a..d69ac4275 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@types/express": "4.17.11", "@types/jest": "26.0.20", "@types/node": "13.13.45", + "@types/shortid": "^0.0.29", "@types/supertest": "2.0.10", "@typescript-eslint/eslint-plugin": "4.15.2", "@typescript-eslint/parser": "4.15.2", diff --git a/src/notes/note.entity.ts b/src/notes/note.entity.ts index dd1dba79d..1adcef9cb 100644 --- a/src/notes/note.entity.ts +++ b/src/notes/note.entity.ts @@ -85,7 +85,7 @@ export class Note { newNote.authorColors = []; newNote.userPermissions = []; newNote.groupPermissions = []; - newNote.revisions = Promise.resolve([]); + newNote.revisions = Promise.resolve([]) as Promise; newNote.description = null; newNote.title = null; newNote.tags = []; diff --git a/yarn.lock b/yarn.lock index 3e8f27d75..bba002f5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1095,6 +1095,11 @@ "@types/mime" "^1" "@types/node" "*" +"@types/shortid@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/shortid/-/shortid-0.0.29.tgz#8093ee0416a6e2bf2aa6338109114b3fbffa0e9b" + integrity sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps= + "@types/source-list-map@*": version "0.1.2" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" From b22d6414067719075f51a4dc4a6751e12f23002e Mon Sep 17 00:00:00 2001 From: David Mehren Date: Wed, 24 Feb 2021 20:39:25 +0100 Subject: [PATCH 20/27] ConsoleLoggerService: Fix ESLint errors We now use @types/cli-color to provide type information Signed-off-by: David Mehren --- package.json | 1 + src/logger/console-logger.service.ts | 6 +++--- yarn.lock | 5 +++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index d69ac4275..e5a9a7421 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@nestjs/cli": "7.5.6", "@nestjs/schematics": "7.2.8", "@nestjs/testing": "7.6.13", + "@types/cli-color": "^2.0.0", "@types/express": "4.17.11", "@types/jest": "26.0.20", "@types/node": "13.13.45", diff --git a/src/logger/console-logger.service.ts b/src/logger/console-logger.service.ts index d1d818611..a309111bf 100644 --- a/src/logger/console-logger.service.ts +++ b/src/logger/console-logger.service.ts @@ -6,12 +6,12 @@ import { Injectable, Optional, Scope } from '@nestjs/common'; import { isObject } from '@nestjs/common/utils/shared.utils'; -import * as clc from 'cli-color'; +import clc = require('cli-color'); import DateTimeFormatOptions = Intl.DateTimeFormatOptions; @Injectable({ scope: Scope.TRANSIENT }) export class ConsoleLoggerService { - private classContext; + private classContext: string; private lastTimestamp: number; constructor(@Optional() context?: string) { @@ -68,7 +68,7 @@ export class ConsoleLoggerService { ); } - private makeContextString(functionContext) { + private makeContextString(functionContext: string) { let context = this.classContext; if (functionContext) { context += '.' + functionContext + '()'; diff --git a/yarn.lock b/yarn.lock index bba002f5a..4374e2eb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -846,6 +846,11 @@ "@types/connect" "*" "@types/node" "*" +"@types/cli-color@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/cli-color/-/cli-color-2.0.0.tgz#dc64e32da0fb9ea1814300fb468a58e833ce71a6" + integrity sha512-E2Oisr73FjwxMHkYU6RcN9P9mmrbG4TNQMIebWhazYxOgWRzA7s4hM+DtAs6ZwiwKFbPst42v1XUAC1APIhRJA== + "@types/connect@*": version "3.4.34" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901" From 38b787fe25f636b9b3efd2e3a5d091d365133edd Mon Sep 17 00:00:00 2001 From: David Mehren Date: Wed, 24 Feb 2021 22:33:29 +0100 Subject: [PATCH 21/27] MeController: Fix ESLint errors Signed-off-by: David Mehren --- src/api/public/me/me.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/public/me/me.controller.ts b/src/api/public/me/me.controller.ts index f68851a5b..602efaf99 100644 --- a/src/api/public/me/me.controller.ts +++ b/src/api/public/me/me.controller.ts @@ -103,7 +103,7 @@ export class MeController { async getMyNotes(@Req() req: Request): Promise { const notes = this.notesService.getUserNotes(req.user); return await Promise.all( - notes.map((note) => this.notesService.toNoteMetadataDto(note)), + (await notes).map((note) => this.notesService.toNoteMetadataDto(note)), ); } } From 0a23538389f6d2bd34b343c41ac64e633497ff13 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Wed, 24 Feb 2021 22:33:47 +0100 Subject: [PATCH 22/27] NotesController: Fix ESLint errors Signed-off-by: David Mehren --- src/api/public/notes/notes.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/public/notes/notes.controller.ts b/src/api/public/notes/notes.controller.ts index 0960fb7b4..e641f17aa 100644 --- a/src/api/public/notes/notes.controller.ts +++ b/src/api/public/notes/notes.controller.ts @@ -222,7 +222,7 @@ export class NotesController { throw new UnauthorizedException('Updating note denied!'); } return this.noteService.toNotePermissionsDto( - this.noteService.updateNotePermissions(note, updateDto), + await this.noteService.updateNotePermissions(note, updateDto), ); } catch (e) { if (e instanceof NotInDBError) { From 73db82164913ef20affa69000504de45e6bf0d54 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Fri, 26 Feb 2021 12:24:35 +0100 Subject: [PATCH 23/27] FilesystemBackend: Fix ESLint errors Signed-off-by: David Mehren --- src/media/backends/filesystem-backend.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/media/backends/filesystem-backend.ts b/src/media/backends/filesystem-backend.ts index 93182a06f..47f554fd7 100644 --- a/src/media/backends/filesystem-backend.ts +++ b/src/media/backends/filesystem-backend.ts @@ -38,7 +38,7 @@ export class FilesystemBackend implements MediaBackend { await fs.writeFile(filePath, buffer, null); return ['/' + filePath, null]; } catch (e) { - this.logger.error(e.message, e.stack, 'saveFile'); + this.logger.error((e as Error).message, (e as Error).stack, 'saveFile'); throw new MediaBackendError(`Could not save '${filePath}'`); } } @@ -48,7 +48,7 @@ export class FilesystemBackend implements MediaBackend { try { return await fs.unlink(filePath); } catch (e) { - this.logger.error(e.message, e.stack, 'deleteFile'); + this.logger.error((e as Error).message, (e as Error).stack, 'deleteFile'); throw new MediaBackendError(`Could not delete '${filePath}'`); } } @@ -73,7 +73,11 @@ export class FilesystemBackend implements MediaBackend { ); await fs.mkdir(this.uploadDirectory); } catch (e) { - this.logger.error(e.message, e.stack, 'deleteFile'); + this.logger.error( + (e as Error).message, + (e as Error).stack, + 'deleteFile', + ); throw new MediaBackendError( `Could not create '${this.uploadDirectory}'`, ); From 9fcc3c6ceeafa2141925d09c72d15ea731977d60 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sat, 27 Feb 2021 17:41:32 +0100 Subject: [PATCH 24/27] Enforce explicit function return types This re-enables the `@typescript-eslint/explicit-module-boundary-types` check and also enables the `@typescript-eslint/explicit-function-return-type` check. Signed-off-by: David Mehren --- .eslintrc.js | 2 +- src/api/private/tokens/tokens.controller.ts | 2 +- src/api/public/me/me.controller.ts | 5 ++- .../monitoring/monitoring.controller.ts | 2 +- src/api/utils/markdownbody-decorator.ts | 2 +- src/auth/auth.service.ts | 10 +++--- src/auth/mock-auth.guard.ts | 2 +- src/config/utils.ts | 5 ++- src/logger/console-logger.service.ts | 18 +++++----- src/main.ts | 2 +- src/media/backends/filesystem-backend.ts | 2 +- src/monitoring/monitoring.service.ts | 8 ++++- src/permissions/permissions.service.spec.ts | 35 +++++++++++-------- src/users/users.service.ts | 2 +- src/utils/swagger.ts | 4 +-- 15 files changed, 59 insertions(+), 42 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 67d0a1004..fa37f4059 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -25,7 +25,7 @@ module.exports = { 'warn', { argsIgnorePattern: '^_+$' }, ], - '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/explicit-function-return-type': 'warn', 'no-return-await': 'off', '@typescript-eslint/return-await': ['error', 'always'], '@typescript-eslint/naming-convention': [ diff --git a/src/api/private/tokens/tokens.controller.ts b/src/api/private/tokens/tokens.controller.ts index cb44b93a8..d785e2ccf 100644 --- a/src/api/private/tokens/tokens.controller.ts +++ b/src/api/private/tokens/tokens.controller.ts @@ -53,7 +53,7 @@ export class TokensController { @Delete('/:keyId') @HttpCode(204) - async deleteToken(@Param('keyId') keyId: string) { + async deleteToken(@Param('keyId') keyId: string): Promise { return await this.authService.removeToken(keyId); } } diff --git a/src/api/public/me/me.controller.ts b/src/api/public/me/me.controller.ts index 602efaf99..1169820ea 100644 --- a/src/api/public/me/me.controller.ts +++ b/src/api/public/me/me.controller.ts @@ -86,7 +86,10 @@ export class MeController { @UseGuards(TokenAuthGuard) @Delete('history/:note') @HttpCode(204) - deleteHistoryEntry(@Req() req: Request, @Param('note') note: string) { + deleteHistoryEntry( + @Req() req: Request, + @Param('note') note: string, + ): Promise { // ToDo: Check if user is allowed to delete note try { return this.historyService.deleteHistoryEntry(note, req.user); diff --git a/src/api/public/monitoring/monitoring.controller.ts b/src/api/public/monitoring/monitoring.controller.ts index 4d561f0a3..1905adb08 100644 --- a/src/api/public/monitoring/monitoring.controller.ts +++ b/src/api/public/monitoring/monitoring.controller.ts @@ -25,7 +25,7 @@ export class MonitoringController { @UseGuards(TokenAuthGuard) @Get('prometheus') - getPrometheusStatus() { + getPrometheusStatus(): string { return ''; } } diff --git a/src/api/utils/markdownbody-decorator.ts b/src/api/utils/markdownbody-decorator.ts index 18f1d448b..98dcf0836 100644 --- a/src/api/utils/markdownbody-decorator.ts +++ b/src/api/utils/markdownbody-decorator.ts @@ -41,7 +41,7 @@ export const MarkdownBody = createParamDecorator( } }, [ - (target, key) => { + (target, key): void => { ApiConsumes('text/markdown')( target, key, diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index e078064de..833c24232 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -123,7 +123,7 @@ export class AuthService { return this.toAuthTokenWithSecretDto(createdToken, `${keyId}.${secret}`); } - async setLastUsedToken(keyId: string) { + async setLastUsedToken(keyId: string): Promise { const accessToken = await this.authTokenRepository.findOne({ where: { keyId: keyId }, }); @@ -166,7 +166,7 @@ export class AuthService { return user.authTokens; } - async removeToken(keyId: string) { + async removeToken(keyId: string): Promise { const token = await this.authTokenRepository.findOne({ where: { keyId: keyId }, }); @@ -213,17 +213,17 @@ export class AuthService { // Delete all non valid tokens every sunday on 3:00 AM @Cron('0 0 3 * * 0') - async handleCron() { + async handleCron(): Promise { return await this.removeInvalidTokens(); } // Delete all non valid tokens 5 sec after startup @Timeout(5000) - async handleTimeout() { + async handleTimeout(): Promise { return await this.removeInvalidTokens(); } - async removeInvalidTokens() { + async removeInvalidTokens(): Promise { const currentTime = new Date().getTime(); const tokens: AuthToken[] = await this.authTokenRepository.find(); let removedTokens = 0; diff --git a/src/auth/mock-auth.guard.ts b/src/auth/mock-auth.guard.ts index fc6d166ef..3b77dc9d6 100644 --- a/src/auth/mock-auth.guard.ts +++ b/src/auth/mock-auth.guard.ts @@ -14,7 +14,7 @@ export class MockAuthGuard { private user: User; constructor(private usersService: UsersService) {} - async canActivate(context: ExecutionContext) { + async canActivate(context: ExecutionContext): Promise { const req: Request = context.switchToHttp().getRequest(); if (!this.user) { // this assures that we can create the user 'hardcoded', if we need them before any calls are made or diff --git a/src/config/utils.ts b/src/config/utils.ts index c0d62214c..2064ce19f 100644 --- a/src/config/utils.ts +++ b/src/config/utils.ts @@ -4,7 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -export const toArrayConfig = (configValue: string, separator = ',') => { +export const toArrayConfig: ( + configValue: string, + separator?: string, +) => string[] = (configValue: string, separator = ',') => { if (!configValue) { return []; } diff --git a/src/logger/console-logger.service.ts b/src/logger/console-logger.service.ts index a309111bf..aeb1af10b 100644 --- a/src/logger/console-logger.service.ts +++ b/src/logger/console-logger.service.ts @@ -18,11 +18,11 @@ export class ConsoleLoggerService { this.classContext = context; } - setContext(context: string) { + setContext(context: string): void { this.classContext = context; } - error(message: unknown, trace = '', functionContext?: string) { + error(message: unknown, trace = '', functionContext?: string): void { this.printMessage( message, clc.red, @@ -32,7 +32,7 @@ export class ConsoleLoggerService { this.printStackTrace(trace); } - log(message: unknown, functionContext?: string) { + log(message: unknown, functionContext?: string): void { this.printMessage( message, clc.green, @@ -41,7 +41,7 @@ export class ConsoleLoggerService { ); } - warn(message: unknown, functionContext?: string) { + warn(message: unknown, functionContext?: string): void { this.printMessage( message, clc.yellow, @@ -50,7 +50,7 @@ export class ConsoleLoggerService { ); } - debug(message: unknown, functionContext?: string) { + debug(message: unknown, functionContext?: string): void { this.printMessage( message, clc.magentaBright, @@ -59,7 +59,7 @@ export class ConsoleLoggerService { ); } - verbose(message: unknown, functionContext?: string) { + verbose(message: unknown, functionContext?: string): void { this.printMessage( message, clc.cyanBright, @@ -68,7 +68,7 @@ export class ConsoleLoggerService { ); } - private makeContextString(functionContext: string) { + private makeContextString(functionContext: string): string { let context = this.classContext; if (functionContext) { context += '.' + functionContext + '()'; @@ -81,7 +81,7 @@ export class ConsoleLoggerService { color: (message: string) => string, context = '', isTimeDiffEnabled?: boolean, - ) { + ): void { const output = isObject(message) ? `${color('Object:')}\n${JSON.stringify(message, null, 2)}\n` : color(message as string); @@ -117,7 +117,7 @@ export class ConsoleLoggerService { return result; } - private printStackTrace(trace: string) { + private printStackTrace(trace: string): void { if (!trace) { return; } diff --git a/src/main.ts b/src/main.ts index ccecea528..e9c2bb5cd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -15,7 +15,7 @@ import { NestConsoleLoggerService } from './logger/nest-console-logger.service'; import { setupPrivateApiDocs, setupPublicApiDocs } from './utils/swagger'; import { BackendType } from './media/backends/backend-type.enum'; -async function bootstrap() { +async function bootstrap(): Promise { const app = await NestFactory.create(AppModule); const logger = await app.resolve(NestConsoleLoggerService); logger.log('Switching logger', 'AppBootstrap'); diff --git a/src/media/backends/filesystem-backend.ts b/src/media/backends/filesystem-backend.ts index 47f554fd7..70ceb4e79 100644 --- a/src/media/backends/filesystem-backend.ts +++ b/src/media/backends/filesystem-backend.ts @@ -63,7 +63,7 @@ export class FilesystemBackend implements MediaBackend { return join(this.uploadDirectory, fileName); } - private async ensureDirectory() { + private async ensureDirectory(): Promise { try { await fs.access(this.uploadDirectory); } catch (e) { diff --git a/src/monitoring/monitoring.service.ts b/src/monitoring/monitoring.service.ts index 6c4f18287..9755d9faf 100644 --- a/src/monitoring/monitoring.service.ts +++ b/src/monitoring/monitoring.service.ts @@ -16,7 +16,13 @@ let versionCache: null | { preRelease?: string; commit?: string; } = null; -async function getServerVersionFromPackageJson() { +async function getServerVersionFromPackageJson(): Promise<{ + major: number; + minor: number; + patch: number; + preRelease?: string; + commit?: string; +}> { if (versionCache === null) { const rawFileContent: string = await fs.readFile( joinPath(__dirname, '../../package.json'), diff --git a/src/permissions/permissions.service.spec.ts b/src/permissions/permissions.service.spec.ts index d123cdd2c..a5bcde491 100644 --- a/src/permissions/permissions.service.spec.ts +++ b/src/permissions/permissions.service.spec.ts @@ -11,23 +11,23 @@ @typescript-eslint/require-await */ import { Test, TestingModule } from '@nestjs/testing'; -import { LoggerModule } from '../logger/logger.module'; -import { GuestPermission, PermissionsService } from './permissions.service'; -import { User } from '../users/user.entity'; -import { Note } from '../notes/note.entity'; -import { UsersModule } from '../users/users.module'; -import { NotesModule } from '../notes/notes.module'; -import { PermissionsModule } from './permissions.module'; import { getRepositoryToken } from '@nestjs/typeorm'; +import { AuthToken } from '../auth/auth-token.entity'; +import { Group } from '../groups/group.entity'; +import { LoggerModule } from '../logger/logger.module'; +import { AuthorColor } from '../notes/author-color.entity'; +import { Note } from '../notes/note.entity'; +import { NotesModule } from '../notes/notes.module'; +import { Tag } from '../notes/tag.entity'; +import { Authorship } from '../revisions/authorship.entity'; +import { Revision } from '../revisions/revision.entity'; +import { Identity } from '../users/identity.entity'; +import { User } from '../users/user.entity'; +import { UsersModule } from '../users/users.module'; import { NoteGroupPermission } from './note-group-permission.entity'; import { NoteUserPermission } from './note-user-permission.entity'; -import { Identity } from '../users/identity.entity'; -import { AuthToken } from '../auth/auth-token.entity'; -import { Authorship } from '../revisions/authorship.entity'; -import { AuthorColor } from '../notes/author-color.entity'; -import { Revision } from '../revisions/revision.entity'; -import { Tag } from '../notes/tag.entity'; -import { Group } from '../groups/group.entity'; +import { PermissionsModule } from './permissions.module'; +import { GuestPermission, PermissionsService } from './permissions.service'; describe('PermissionsService', () => { let permissionsService: PermissionsService; @@ -154,6 +154,7 @@ describe('PermissionsService', () => { noteEverybodyWrite, ]; } + const notes = createNoteUserPermissionNotes(); describe('mayRead works with', () => { @@ -342,6 +343,7 @@ describe('PermissionsService', () => { [user2groupWrite, user2groupRead, null], // group4: don't allow user1 to read or write via group ]; } + /* * creates the matrix multiplication of group0 to group4 of createAllNoteGroupPermissions */ @@ -414,7 +416,10 @@ describe('PermissionsService', () => { ): NoteGroupPermission[][] { const results = []; - function permute(arr: NoteGroupPermission[], memo: NoteGroupPermission[]) { + function permute( + arr: NoteGroupPermission[], + memo: NoteGroupPermission[], + ): NoteGroupPermission[][] { let cur; for (let i = 0; i < arr.length; i++) { diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 814743b95..7714d0dd5 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -26,7 +26,7 @@ export class UsersService { return this.userRepository.save(user); } - async deleteUser(userName: string) { + async deleteUser(userName: string): Promise { // TODO: Handle owned notes and edits const user = await this.userRepository.findOne({ where: { userName: userName }, diff --git a/src/utils/swagger.ts b/src/utils/swagger.ts index f0167992c..e242d5397 100644 --- a/src/utils/swagger.ts +++ b/src/utils/swagger.ts @@ -9,7 +9,7 @@ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { PrivateApiModule } from '../api/private/private-api.module'; import { PublicApiModule } from '../api/public/public-api.module'; -export function setupPublicApiDocs(app: INestApplication) { +export function setupPublicApiDocs(app: INestApplication): void { const publicApiOptions = new DocumentBuilder() .setTitle('HedgeDoc Public API') // TODO: Use real version @@ -25,7 +25,7 @@ export function setupPublicApiDocs(app: INestApplication) { SwaggerModule.setup('apidoc', app, publicApi); } -export function setupPrivateApiDocs(app: INestApplication) { +export function setupPrivateApiDocs(app: INestApplication): void { const privateApiOptions = new DocumentBuilder() .setTitle('HedgeDoc Private API') // TODO: Use real version From 235d7efa19e9fffb3104d3300489878c9aaf4979 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sat, 27 Feb 2021 17:45:44 +0100 Subject: [PATCH 25/27] Refactor config utils to use functions instead of consts Signed-off-by: David Mehren --- src/config/utils.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/config/utils.ts b/src/config/utils.ts index 2064ce19f..50a695f6e 100644 --- a/src/config/utils.ts +++ b/src/config/utils.ts @@ -4,22 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -export const toArrayConfig: ( - configValue: string, - separator?: string, -) => string[] = (configValue: string, separator = ',') => { +export function toArrayConfig(configValue: string, separator = ','): string[] { if (!configValue) { return []; } - if (!configValue.includes(separator)) { return [configValue.trim()]; } - return configValue.split(separator).map((arrayItem) => arrayItem.trim()); -}; +} -export const buildErrorMessage = (errorMessages: string[]): string => { +export function buildErrorMessage(errorMessages: string[]): string { let totalErrorMessage = 'There were some errors with your configuration:'; for (const message of errorMessages) { totalErrorMessage += '\n - '; @@ -28,14 +23,14 @@ export const buildErrorMessage = (errorMessages: string[]): string => { totalErrorMessage += '\nFor further information, have a look at our configuration docs at https://docs.hedgedoc.org/configuration'; return totalErrorMessage; -}; +} -export const replaceAuthErrorsWithEnvironmentVariables = ( +export function replaceAuthErrorsWithEnvironmentVariables( message: string, name: string, replacement: string, arrayOfNames: string[], -): string => { +): string { // this builds a regex like /"gitlab\[(\d+)]\./ to extract the position in the arrayOfNames const regex = new RegExp('"' + name + '\\[(\\d+)]\\.', 'g'); message = message.replace( @@ -91,4 +86,4 @@ export const replaceAuthErrorsWithEnvironmentVariables = ( message = message.replace('.rolesClaim', '_ROLES_CLAIM'); message = message.replace('.accessRole', '_ACCESS_ROLE'); return message; -}; +} From fc1008e7736f68daf9a331e70782376275f57d21 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sat, 27 Feb 2021 17:54:24 +0100 Subject: [PATCH 26/27] Enforce the use of function declarations Signed-off-by: David Mehren --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index fa37f4059..0d821dc32 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,6 +21,7 @@ module.exports = { jest: true, }, rules: { + 'func-style': ['error', 'declaration'], '@typescript-eslint/no-unused-vars': [ 'warn', { argsIgnorePattern: '^_+$' }, From 609b1cf3a38084e4d6645739b75149bea7107802 Mon Sep 17 00:00:00 2001 From: David Mehren Date: Sat, 27 Feb 2021 21:21:41 +0100 Subject: [PATCH 27/27] Refactor server version object into own interface This makes the type of getServerVersionFromPackageJson() way easier to read. Signed-off-by: David Mehren --- src/monitoring/monitoring.service.ts | 19 ++++--------------- src/monitoring/server-status.dto.ts | 16 +++++++++------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/monitoring/monitoring.service.ts b/src/monitoring/monitoring.service.ts index 9755d9faf..260c57312 100644 --- a/src/monitoring/monitoring.service.ts +++ b/src/monitoring/monitoring.service.ts @@ -5,24 +5,13 @@ */ import { Injectable } from '@nestjs/common'; -import { ServerStatusDto } from './server-status.dto'; import { promises as fs } from 'fs'; import { join as joinPath } from 'path'; +import { ServerStatusDto, ServerVersion } from './server-status.dto'; -let versionCache: null | { - major: number; - minor: number; - patch: number; - preRelease?: string; - commit?: string; -} = null; -async function getServerVersionFromPackageJson(): Promise<{ - major: number; - minor: number; - patch: number; - preRelease?: string; - commit?: string; -}> { +let versionCache: ServerVersion; + +async function getServerVersionFromPackageJson(): Promise { if (versionCache === null) { const rawFileContent: string = await fs.readFile( joinPath(__dirname, '../../package.json'), diff --git a/src/monitoring/server-status.dto.ts b/src/monitoring/server-status.dto.ts index ac7f7407d..8bea15f1e 100644 --- a/src/monitoring/server-status.dto.ts +++ b/src/monitoring/server-status.dto.ts @@ -4,14 +4,16 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +export interface ServerVersion { + major: number; + minor: number; + patch: number; + preRelease?: string; + commit?: string; +} + export class ServerStatusDto { - serverVersion: { - major: number; - minor: number; - patch: number; - preRelease?: string; - commit?: string; - }; + serverVersion: ServerVersion; onlineNotes: number; onlineUsers: number; destictOnlineUsers: number;