diff --git a/.eslintrc.js b/.eslintrc.js index 16a57233e..0d821dc32 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, @@ -20,13 +21,37 @@ module.exports = { jest: true, }, rules: { - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/no-explicit-any': 'off', + 'func-style': ['error', 'declaration'], '@typescript-eslint/no-unused-vars': [ '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': [ + '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/package.json b/package.json index 84060db2a..e5a9a7421 100644 --- a/package.json +++ b/package.json @@ -58,9 +58,11 @@ "@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", + "@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/api/private/tokens/tokens.controller.ts b/src/api/private/tokens/tokens.controller.ts index 651982b13..d785e2ccf 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); + 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 89e8eef28..1169820ea 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'; @@ -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(@Request() req): Promise { + async getMe(@Req() req: Request): Promise { return this.usersService.toUserDto( await this.usersService.getUserByUsername(req.user.userName), ); @@ -51,19 +52,17 @@ export class MeController { @UseGuards(TokenAuthGuard) @Get('history') - async getUserHistory(@Request() req): Promise { + async getUserHistory(@Req() req: Request): Promise { const foundEntries = await this.historyService.getEntriesByUser(req.user); - return Promise.all( - foundEntries.map( - async (entry) => await this.historyService.toHistoryEntryDto(entry), - ), + return await Promise.all( + foundEntries.map((entry) => this.historyService.toHistoryEntryDto(entry)), ); } @UseGuards(TokenAuthGuard) @Put('history/:note') async updateHistoryEntry( - @Request() req, + @Req() req: Request, @Param('note') note: string, @Body() entryUpdateDto: HistoryEntryUpdateDto, ): Promise { @@ -87,7 +86,10 @@ export class MeController { @UseGuards(TokenAuthGuard) @Delete('history/:note') @HttpCode(204) - deleteHistoryEntry(@Request() req, @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); @@ -101,10 +103,10 @@ export class MeController { @UseGuards(TokenAuthGuard) @Get('notes') - async getMyNotes(@Request() req): Promise { - const notes = await this.notesService.getUserNotes(req.user); - return Promise.all( - notes.map((note) => this.notesService.toNoteMetadataDto(note)), + async getMyNotes(@Req() req: Request): Promise { + const notes = this.notesService.getUserNotes(req.user); + return await Promise.all( + (await 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..28fff33c3 100644 --- a/src/api/public/media/media.controller.ts +++ b/src/api/public/media/media.controller.ts @@ -13,13 +13,14 @@ import { NotFoundException, Param, Post, - Request, + Req, UnauthorizedException, UploadedFile, UseGuards, 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( - @Request() 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( - @Request() req, + @Req() req: Request, @Param('filename') filename: string, ): Promise { const username = req.user.userName; 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/public/notes/notes.controller.ts b/src/api/public/notes/notes.controller.ts index e7739529b..e641f17aa 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'; @@ -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( - @Request() req, + @Req() req: Request, @MarkdownBody() text: string, ): Promise { // ToDo: provide user for createNoteDto @@ -67,7 +68,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), ); } @@ -75,7 +76,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Get(':noteIdOrAlias') async getNote( - @Request() req, + @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, ): Promise { let note: Note; @@ -91,13 +92,13 @@ 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) @Post(':noteAlias') async createNamedNote( - @Request() req, + @Req() req: Request, @Param('noteAlias') noteAlias: string, @MarkdownBody() text: string, ): Promise { @@ -106,7 +107,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) { @@ -120,7 +121,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Delete(':noteIdOrAlias') async deleteNote( - @Request() req, + @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, ): Promise { try { @@ -143,7 +144,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Put(':noteIdOrAlias') async updateNote( - @Request() req, + @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, @MarkdownBody() text: string, ): Promise { @@ -153,7 +154,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) { @@ -168,7 +169,7 @@ export class NotesController { @Get(':noteIdOrAlias/content') @Header('content-type', 'text/markdown') async getNoteContent( - @Request() req, + @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, ): Promise { try { @@ -188,7 +189,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Get(':noteIdOrAlias/metadata') async getNoteMetadata( - @Request() req, + @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, ): Promise { try { @@ -196,7 +197,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); @@ -211,7 +212,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Put(':noteIdOrAlias/metadata/permissions') async updateNotePermissions( - @Request() 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( - @Request() req, + @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, ): Promise { try { @@ -243,7 +244,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), ), @@ -259,7 +260,7 @@ export class NotesController { @UseGuards(TokenAuthGuard) @Get(':noteIdOrAlias/revisions/:revisionId') async getNoteRevision( - @Request() req, + @Req() req: Request, @Param('noteIdOrAlias') noteIdOrAlias: string, @Param('revisionId') revisionId: number, ): Promise { diff --git a/src/api/utils/markdownbody-decorator.ts b/src/api/utils/markdownbody-decorator.ts index 1c81df3ea..98dcf0836 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 @@ -39,7 +41,7 @@ export const MarkdownBody = createParamDecorator( } }, [ - (target, key) => { + (target, key): void => { ApiConsumes('text/markdown')( target, key, diff --git a/src/auth/auth.service.spec.ts b/src/auth/auth.service.spec.ts index 8506b5070..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); }); @@ -277,10 +283,10 @@ describe('AuthService', () => { }); }); - describe('BufferToBase64Url', () => { + describe('bufferToBase64Url', () => { it('works', () => { expect( - service.BufferToBase64Url( + service.bufferToBase64Url( Buffer.from('testsentence is a test sentence'), ), ).toEqual('dGVzdHNlbnRlbmNlIGlzIGEgdGVzdCBzZW50ZW5jZQ'); @@ -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/auth/auth.service.ts b/src/auth/auth.service.ts index 25aaaec12..833c24232 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -49,27 +49,27 @@ 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 { + randomString(length: number): Buffer { if (length <= 0) { return null; } 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(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 @@ -117,11 +117,13 @@ 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}`); } - async setLastUsedToken(keyId: string) { + async setLastUsedToken(keyId: string): Promise { const accessToken = await this.authTokenRepository.findOne({ where: { keyId: keyId }, }); @@ -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; @@ -164,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 }, }); @@ -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 = { @@ -208,17 +213,17 @@ export class AuthService { // Delete all non valid tokens every sunday on 3:00 AM @Cron('0 0 3 * * 0') - async handleCron() { - return this.removeInvalidTokens(); + async handleCron(): Promise { + return await this.removeInvalidTokens(); } // Delete all non valid tokens 5 sec after startup @Timeout(5000) - async handleTimeout() { - return this.removeInvalidTokens(); + 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 5f1e40c6b..3b77dc9d6 100644 --- a/src/auth/mock-auth.guard.ts +++ b/src/auth/mock-auth.guard.ts @@ -7,14 +7,15 @@ 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 { private user: User; constructor(private usersService: UsersService) {} - async canActivate(context: ExecutionContext) { - const req = context.switchToHttp().getRequest(); + 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 // create them on the fly when the first call to the api is made 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..50a695f6e 100644 --- a/src/config/utils.ts +++ b/src/config/utils.ts @@ -4,19 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -export const toArrayConfig = (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 - '; @@ -25,19 +23,19 @@ 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( regex, - (_, index) => `"${replacement}${arrayOfNames[index]}.`, + (_, index: number) => `"${replacement}${arrayOfNames[index]}.`, ); message = message.replace('.providerName', '_PROVIDER_NAME'); message = message.replace('.baseURL', '_BASE_URL'); @@ -88,4 +86,4 @@ export const replaceAuthErrorsWithEnvironmentVariables = ( message = message.replace('.rolesClaim', '_ROLES_CLAIM'); message = message.replace('.accessRole', '_ACCESS_ROLE'); return message; -}; +} 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/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/history/history.service.spec.ts b/src/history/history.service.spec.ts index af4613dc1..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'; @@ -248,7 +254,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 +277,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 84583220e..aef7bc389 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 { @@ -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, diff --git a/src/logger/console-logger.service.ts b/src/logger/console-logger.service.ts index f280e6859..aeb1af10b 100644 --- a/src/logger/console-logger.service.ts +++ b/src/logger/console-logger.service.ts @@ -6,23 +6,23 @@ 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) { this.classContext = context; } - setContext(context: string) { + setContext(context: string): void { this.classContext = context; } - error(message: any, 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: any, functionContext?: string) { + log(message: unknown, functionContext?: string): void { this.printMessage( message, clc.green, @@ -41,7 +41,7 @@ export class ConsoleLoggerService { ); } - warn(message: any, functionContext?: string) { + warn(message: unknown, functionContext?: string): void { this.printMessage( message, clc.yellow, @@ -50,7 +50,7 @@ export class ConsoleLoggerService { ); } - debug(message: any, functionContext?: string) { + debug(message: unknown, functionContext?: string): void { this.printMessage( message, clc.magentaBright, @@ -59,7 +59,7 @@ export class ConsoleLoggerService { ); } - verbose(message: any, functionContext?: string) { + verbose(message: unknown, functionContext?: string): void { this.printMessage( message, clc.cyanBright, @@ -68,7 +68,7 @@ export class ConsoleLoggerService { ); } - private makeContextString(functionContext) { + private makeContextString(functionContext: string): string { let context = this.classContext; if (functionContext) { context += '.' + functionContext + '()'; @@ -77,14 +77,14 @@ export class ConsoleLoggerService { } private printMessage( - message: any, + message: unknown, color: (message: string) => string, context = '', isTimeDiffEnabled?: boolean, - ) { + ): void { const output = isObject(message) ? `${color('Object:')}\n${JSON.stringify(message, null, 2)}\n` - : color(message); + : color(message as string); const localeStringOptions: DateTimeFormatOptions = { year: 'numeric', @@ -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/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); } diff --git a/src/main.ts b/src/main.ts index 36a4f596d..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'); @@ -49,4 +49,4 @@ async function bootstrap() { logger.log(`Listening on port ${appConfig.port}`, 'AppBootstrap'); } -bootstrap(); +void bootstrap(); diff --git a/src/media/backends/filesystem-backend.ts b/src/media/backends/filesystem-backend.ts index cefc088c3..cf9ce7cce 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}'`); } } @@ -46,9 +46,9 @@ 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'); + this.logger.error((e as Error).message, (e as Error).stack, 'deleteFile'); throw new MediaBackendError(`Could not delete '${filePath}'`); } } @@ -57,7 +57,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) { @@ -67,7 +67,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}'`, ); 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..260c57312 100644 --- a/src/monitoring/monitoring.service.ts +++ b/src/monitoring/monitoring.service.ts @@ -5,23 +5,20 @@ */ 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() { +let versionCache: ServerVersion; + +async function getServerVersionFromPackageJson(): Promise { if (versionCache === null) { const rawFileContent: string = await fs.readFile( 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/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; 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/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/notes/notes.service.ts b/src/notes/notes.service.ts index 5b99e6f78..0ff2e7112 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); } /** @@ -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): NotePermissionsDto { 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( diff --git a/src/permissions/permissions.service.spec.ts b/src/permissions/permissions.service.spec.ts index 6396eb09c..a5bcde491 100644 --- a/src/permissions/permissions.service.spec.ts +++ b/src/permissions/permissions.service.spec.ts @@ -4,24 +4,30 @@ * 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'; -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; @@ -148,6 +154,7 @@ describe('PermissionsService', () => { noteEverybodyWrite, ]; } + const notes = createNoteUserPermissionNotes(); describe('mayRead works with', () => { @@ -336,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 */ @@ -350,7 +358,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 +416,10 @@ describe('PermissionsService', () => { ): NoteGroupPermission[][] { const results = []; - function permute(arr, memo) { + function permute( + arr: NoteGroupPermission[], + memo: NoteGroupPermission[], + ): NoteGroupPermission[][] { let cur; for (let i = 0; i < arr.length; i++) { @@ -456,15 +467,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, diff --git a/src/users/users.service.ts b/src/users/users.service.ts index b29b0e30e..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 }, @@ -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 { 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 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); 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; + } +} diff --git a/yarn.lock b/yarn.lock index 3e8f27d75..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" @@ -1095,6 +1100,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"