From 328062eaa05c6bcc269c1ddde7fb776a83dc2060 Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Sun, 25 Aug 2024 16:54:55 +0200 Subject: [PATCH] logging --- idp/src/app.module.ts | 10 +-- idp/src/auth/auth.controller.ts | 6 +- idp/src/auth/auth.module.ts | 4 +- idp/src/core/custom.logger.ts | 42 +++++++++++++ idp/src/core/logger.module.ts | 12 ++++ idp/src/main.ts | 5 +- idp/src/model/interface/logger.interface.ts | 7 +++ idp/src/model/log.entity.ts | 16 +++++ idp/src/model/user.entity.ts | 11 ++++ idp/src/users/users.service.ts | 67 +++++++++++++++------ 10 files changed, 150 insertions(+), 30 deletions(-) create mode 100644 idp/src/core/custom.logger.ts create mode 100644 idp/src/core/logger.module.ts create mode 100644 idp/src/model/interface/logger.interface.ts create mode 100644 idp/src/model/log.entity.ts diff --git a/idp/src/app.module.ts b/idp/src/app.module.ts index d3f819f..3246054 100644 --- a/idp/src/app.module.ts +++ b/idp/src/app.module.ts @@ -1,13 +1,11 @@ -import { Injectable, MiddlewareConsumer, Module, NestMiddleware, NestModule, RequestMethod } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { AppService } from './app.service'; import { AuthModule } from './auth/auth.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule } from '@nestjs/config'; import { ServeStaticModule } from '@nestjs/serve-static'; -import path, { join } from 'path'; -import { AppController } from './app.controller'; -import * as express from 'express'; - +import { join } from 'path'; +import { LoggerModule } from './core/logger.module'; @Module({ imports: [ AuthModule, @@ -15,6 +13,7 @@ import * as express from 'express'; envFilePath: ['.env'], isGlobal: true, }), + LoggerModule, ServeStaticModule.forRoot({ rootPath: join(__dirname, '../client'), exclude: ['*/api*'], @@ -46,5 +45,6 @@ import * as express from 'express'; ], controllers: [], providers: [AppService], + exports: [], }) export class AppModule {} diff --git a/idp/src/auth/auth.controller.ts b/idp/src/auth/auth.controller.ts index e5ae27b..213ad66 100644 --- a/idp/src/auth/auth.controller.ts +++ b/idp/src/auth/auth.controller.ts @@ -3,12 +3,14 @@ import { UsersService } from 'src/users/users.service'; import { ClientService } from 'src/client/client.service'; import { FormDataRequest } from 'nestjs-form-data'; import { Client } from 'src/model/client.entity'; +import { CustomLogger } from 'src/core/custom.logger'; @Controller('auth') export class AuthController { constructor( private usersService: UsersService, private clientService: ClientService, + private logger: CustomLogger, ) {} @Post('register') @@ -31,7 +33,7 @@ export class AuthController { password, clientId, ); - + this.logger.log(`User ${username} logged in on client ${clientId}`); return token; } @@ -57,14 +59,12 @@ export class AuthController { @Body('client_secret') clientSecret: string, @Body('code') code: string, @Body('grant_type') grantType: string, - @Body('redirect_uri') redirectUri: string, ) { return this.usersService.verifyToken({ clientId, clientSecret, code, grantType, - redirectUri, }); } diff --git a/idp/src/auth/auth.module.ts b/idp/src/auth/auth.module.ts index 6bee175..e077629 100644 --- a/idp/src/auth/auth.module.ts +++ b/idp/src/auth/auth.module.ts @@ -12,6 +12,7 @@ import { AuthorizationCode, AuthorizationCodeRepository, } from 'src/model/auth-code.entity'; +import { LoggerModule } from 'src/core/logger.module'; @Module({ providers: [ @@ -25,10 +26,11 @@ import { imports: [ JwtModule.register({ secret: 'SECRET_KEY', // Ändere dies zu einer Umgebungsvariablen in einer realen Anwendung - signOptions: { expiresIn: '60s' }, + signOptions: { expiresIn: '15m' }, }), NestjsFormDataModule, TypeOrmModule.forFeature([User, Client, RedirectUri, AuthorizationCode]), + LoggerModule, ], }) export class AuthModule {} diff --git a/idp/src/core/custom.logger.ts b/idp/src/core/custom.logger.ts new file mode 100644 index 0000000..4899cbc --- /dev/null +++ b/idp/src/core/custom.logger.ts @@ -0,0 +1,42 @@ +import { LoggerService, Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Log } from 'src/model/log.entity'; +import { Repository } from 'typeorm'; + +@Injectable() +export class CustomLogger implements LoggerService { + private readonly logger = new Logger('CustomLogger'); + + // constructor(private readonly connection: Connection) {} + constructor(@InjectRepository(Log) private logRepository: Repository) {} + + private async logToDatabase(level: string, message: string) { + const logEntry = new Log(); + logEntry.level = level; + logEntry.message = message; + logEntry.timestamp = new Date(); + await this.logRepository.save(logEntry); + } + + private logToConsole(level: string, message: string) { + this.logger[level](message); + } + + async error(message: any) { + await this.logToDatabase('error', message.toString()); + this.logToConsole('error', message); + } + + async warn(message: any) { + await this.logToDatabase('warn', message.toString()); + this.logToConsole('warn', message); + } + + async log(message: any) { + this.logToDatabase('log', message); + } + + async debug(message: any) { + this.logToConsole('debug', message); + } +} diff --git a/idp/src/core/logger.module.ts b/idp/src/core/logger.module.ts new file mode 100644 index 0000000..6ca95e9 --- /dev/null +++ b/idp/src/core/logger.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { Log } from 'src/model/log.entity'; +import { CustomLogger } from './custom.logger'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +@Module({ + providers: [CustomLogger], + controllers: [], + imports: [TypeOrmModule.forFeature([Log])], + exports: [CustomLogger], +}) +export class LoggerModule {} diff --git a/idp/src/main.ts b/idp/src/main.ts index 02951e7..d3cabef 100644 --- a/idp/src/main.ts +++ b/idp/src/main.ts @@ -1,6 +1,6 @@ import { NestFactory, Reflector } from '@nestjs/core'; import { AppModule } from './app.module'; -import { ValidationPipe } from '@nestjs/common'; +import { Logger, ValidationPipe } from '@nestjs/common'; import { ClassSerializerInterceptor } from '@nestjs/common'; async function bootstrap() { @@ -19,6 +19,7 @@ async function bootstrap() { ); const port = parseInt(process.env.PORT) || 5000; await app.listen(port); - console.log(`Application is running on: ${await app.getUrl()}`); + const logger = new Logger('bootstrap'); + logger.log(`Application is running on: ${await app.getUrl()}\n`); } bootstrap(); diff --git a/idp/src/model/interface/logger.interface.ts b/idp/src/model/interface/logger.interface.ts new file mode 100644 index 0000000..2fe3bf6 --- /dev/null +++ b/idp/src/model/interface/logger.interface.ts @@ -0,0 +1,7 @@ +export interface CustomLogger { + log(message: string, context?: string): void; + error(message: string, trace?: string, context?: string): void; + warn(message: string, context?: string): void; + debug(message: string, context?: string): void; + verbose(message: string, context?: string): void; +} diff --git a/idp/src/model/log.entity.ts b/idp/src/model/log.entity.ts new file mode 100644 index 0000000..804a69a --- /dev/null +++ b/idp/src/model/log.entity.ts @@ -0,0 +1,16 @@ +import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; + +@Entity() +export class Log { + @PrimaryGeneratedColumn() + id: number; + + @Column() + level: string; + + @Column() + message: string; + + @Column() + timestamp: Date; +} diff --git a/idp/src/model/user.entity.ts b/idp/src/model/user.entity.ts index 3662016..82ac885 100644 --- a/idp/src/model/user.entity.ts +++ b/idp/src/model/user.entity.ts @@ -12,6 +12,7 @@ import { DataSource, Repository, OneToMany, + CreateDateColumn, } from 'typeorm'; import { AuthorizationCode } from './auth-code.entity'; @@ -27,6 +28,16 @@ export class User { @Column() password: string; + @Column({ name: 'first_name', nullable: true }) + firstName?: string; + + @Column({ name: 'last_name', nullable: true }) + lastName?: string; + + @CreateDateColumn({ name: 'created_at' }) + createdAt?: Date; + + @Exclude() @Column({ default: true }) isActive: boolean; diff --git a/idp/src/users/users.service.ts b/idp/src/users/users.service.ts index 3fe13bf..ccef5f1 100644 --- a/idp/src/users/users.service.ts +++ b/idp/src/users/users.service.ts @@ -8,6 +8,7 @@ import { AuthorizationCodeRepository, } from 'src/model/auth-code.entity'; import { JwtService } from '@nestjs/jwt'; +import { CustomLogger } from 'src/core/custom.logger'; @Injectable() export class UsersService { @@ -16,6 +17,7 @@ export class UsersService { private readonly jwtService: JwtService, private userRepo: UserRepository, private tokenRepo: AuthorizationCodeRepository, + private logger: CustomLogger, ) {} async createUser(username: string, password: string): Promise { @@ -39,17 +41,20 @@ export class UsersService { ): Promise<{ code: string }> { const user = await this.userRepo.findByUsername(username); if (!user) { + this.logger.error(`User ${username} not found`); throw new Error('User not found'); } const valid = await bcrypt.compare(password, user.password); if (!valid) { + this.logger.error(`User ${username} provided invalid credentials`); throw new HttpException('Invalid credentials', 401); } const client = await this.clientService.getClientById(clientId); if (!client) { + this.logger.error(`Client ${clientId} not found`); throw new HttpException('Invalid client', 401); } @@ -63,18 +68,10 @@ export class UsersService { return { code: token.code }; } - async verifyToken({ - clientId, - clientSecret, - code, - grantType, - redirectUri, - }: any) { - if (grantType !== 'authorization_code') { - throw new HttpException('Invalid grant type', 401); - } + async verifyToken({ clientId, clientSecret, code, grantType }: any) { const client = await this.clientService.getClientById(clientId); if (!client) { + this.logger.error(`Client ${clientId} not found for verification`); throw new HttpException('Invalid client', 401); } @@ -82,8 +79,12 @@ export class UsersService { throw new HttpException('Invalid client', 401); } - if (!client.redirectUris.some((uri) => uri.uri === redirectUri)) { - throw new HttpException('Invalid redirect', 401); + if (grantType == 'refresh_token') { + return this.getNewAccessToken(code); + } + + if (grantType !== 'authorization_code') { + throw new HttpException('Invalid grant type', 401); } const token = await this.tokenRepo.findByCode(code); @@ -103,25 +104,53 @@ export class UsersService { this.tokenRepo.removeCode(token); + const access_token = this.createAccessToken(user); + const refresh_token = this.createRefreshToken(user); + return { + access_token, + refresh_token, + }; + } + + private createAccessToken(user: User) { const payload = { username: user.username, id: user.id, - iss: 'http://localhost:3000', - aud: 'http://localhost:3000', + firstName: user.firstName, + lastName: user.lastName, + iss: 'sso.beantastic.de', + aud: 'not set', }; + return this.jwtService.sign(payload); + } - const access_token = this.jwtService.sign(payload); - const refresh_token = this.jwtService.sign( + private createRefreshToken(user: User) { + this.jwtService.sign( { type: 'refresh', id: user.id, }, { expiresIn: '365d' }, ); - return { - access_token, - refresh_token, + } + + async getNewAccessToken(refreshToken: string) { + const payload = this.jwtService.verify(refreshToken); + if (payload.type !== 'refresh') { + throw new HttpException('Invalid token', 401); + } + const user = await this.userRepo.findById(payload.id); + if (!user) { + throw new HttpException('Invalid token', 401); + } + + const token = this.createAccessToken(user); + const pay = this.jwtService.decode(token); + const result = { + access_token: token, + expires_in: pay.exp - pay.iat, }; + return result; } verifyAccessToken(token: string) {