From 5a916628652eda6a2892332c58efe5f7c5e0c48d Mon Sep 17 00:00:00 2001 From: Philip Molares Date: Tue, 31 Aug 2021 13:36:13 +0200 Subject: [PATCH] feat: add session handling Signed-off-by: Philip Molares --- package.json | 2 ++ src/identity/session.guard.ts | 49 +++++++++++++++++++++++++++++++++++ src/main.ts | 7 ++++- src/utils/session.ts | 39 ++++++++++++++++++++++++++++ yarn.lock | 4 +-- 5 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 src/identity/session.guard.ts create mode 100644 src/utils/session.ts diff --git a/package.json b/package.json index 7c76edcda..834f68862 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "connect-typeorm": "1.1.4", "eslint-plugin-jest": "24.4.0", "eslint-plugin-local-rules": "1.1.0", + "express-session": "1.17.2", "file-type": "16.5.3", "joi": "17.4.2", "minio": "7.0.19", @@ -71,6 +72,7 @@ "@tsconfig/node12": "1.0.9", "@types/cli-color": "2.0.1", "@types/express": "4.17.13", + "@types/express-session": "^1.17.4", "@types/jest": "27.0.1", "@types/node": "14.17.16", "@types/passport-local": "^1.0.34", diff --git a/src/identity/session.guard.ts b/src/identity/session.guard.ts new file mode 100644 index 000000000..c263596b2 --- /dev/null +++ b/src/identity/session.guard.ts @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { + CanActivate, + ExecutionContext, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; + +import { NotInDBError } from '../errors/errors'; +import { ConsoleLoggerService } from '../logger/console-logger.service'; +import { User } from '../users/user.entity'; +import { UsersService } from '../users/users.service'; + +@Injectable() +export class SessionGuard implements CanActivate { + constructor( + private readonly logger: ConsoleLoggerService, + private userService: UsersService, + ) { + this.logger.setContext(SessionGuard.name); + } + + async canActivate(context: ExecutionContext): Promise { + const request: Request & { session?: { user: string }; user?: User } = + context.switchToHttp().getRequest(); + if (!request.session) { + this.logger.debug('The user has no session.'); + throw new UnauthorizedException("You're not logged in"); + } + try { + request.user = await this.userService.getUserByUsername( + request.session.user, + ); + return true; + } catch (e) { + if (e instanceof NotInDBError) { + this.logger.debug( + `The user '${request.session.user}' does not exist, but has a session.`, + ); + throw new UnauthorizedException("You're not logged in"); + } + throw e; + } + } +} diff --git a/src/main.ts b/src/main.ts index d0d883dbb..289138a13 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,9 +10,11 @@ import { NestExpressApplication } from '@nestjs/platform-express'; import { AppModule } from './app.module'; import { AppConfig } from './config/app.config'; +import { AuthConfig } from './config/auth.config'; import { MediaConfig } from './config/media.config'; import { ConsoleLoggerService } from './logger/console-logger.service'; import { BackendType } from './media/backends/backend-type.enum'; +import { setupSessionMiddleware } from './utils/session'; import { setupPrivateApiDocs, setupPublicApiDocs } from './utils/swagger'; async function bootstrap(): Promise { @@ -25,9 +27,10 @@ async function bootstrap(): Promise { app.useLogger(logger); const configService = app.get(ConfigService); const appConfig = configService.get('appConfig'); + const authConfig = configService.get('authConfig'); const mediaConfig = configService.get('mediaConfig'); - if (!appConfig || !mediaConfig) { + if (!appConfig || !authConfig || !mediaConfig) { logger.error('Could not initialize config, aborting.', 'AppBootstrap'); process.exit(1); } @@ -45,6 +48,8 @@ async function bootstrap(): Promise { ); } + setupSessionMiddleware(app, authConfig); + app.enableCors({ origin: appConfig.rendererOrigin, }); diff --git a/src/utils/session.ts b/src/utils/session.ts new file mode 100644 index 000000000..d8b10fd2c --- /dev/null +++ b/src/utils/session.ts @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { INestApplication } from '@nestjs/common'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { TypeormStore } from 'connect-typeorm'; +import session from 'express-session'; +import { Repository } from 'typeorm'; + +import { AuthConfig } from '../config/auth.config'; +import { Session } from '../users/session.entity'; + +/** + * Setup the session middleware via the given authConfig. + * @param {INestApplication} app - the nest application to configure the middleware for. + * @param {AuthConfig} authConfig - the authConfig to configure the middleware with. + */ +export function setupSessionMiddleware( + app: INestApplication, + authConfig: AuthConfig, +): void { + app.use( + session({ + name: 'hedgedoc-session', + secret: authConfig.session.secret, + cookie: { + maxAge: authConfig.session.lifetime, + }, + resave: false, + saveUninitialized: false, + store: new TypeormStore({ + cleanupLimit: 2, + ttl: 86400, + }).connect(app.get>(getRepositoryToken(Session))), + }), + ); +} diff --git a/yarn.lock b/yarn.lock index 98d1c4f74..325f58c14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1147,7 +1147,7 @@ "@types/qs" "*" "@types/range-parser" "*" -"@types/express-session@^1.15.5": +"@types/express-session@^1.15.5", "@types/express-session@^1.17.4": version "1.17.4" resolved "https://registry.yarnpkg.com/@types/express-session/-/express-session-1.17.4.tgz#97a30a35e853a61bdd26e727453b8ed314d6166b" integrity sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg== @@ -3175,7 +3175,7 @@ expect@^27.2.0: jest-message-util "^27.2.0" jest-regex-util "^27.0.6" -express-session@^1.15.6: +express-session@1.17.2, express-session@^1.15.6: version "1.17.2" resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.2.tgz#397020374f9bf7997f891b85ea338767b30d0efd" integrity sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==