logging
This commit is contained in:
@@ -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 { AppService } from './app.service';
|
||||||
import { AuthModule } from './auth/auth.module';
|
import { AuthModule } from './auth/auth.module';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||||
import path, { join } from 'path';
|
import { join } from 'path';
|
||||||
import { AppController } from './app.controller';
|
import { LoggerModule } from './core/logger.module';
|
||||||
import * as express from 'express';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
AuthModule,
|
AuthModule,
|
||||||
@@ -15,6 +13,7 @@ import * as express from 'express';
|
|||||||
envFilePath: ['.env'],
|
envFilePath: ['.env'],
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
}),
|
}),
|
||||||
|
LoggerModule,
|
||||||
ServeStaticModule.forRoot({
|
ServeStaticModule.forRoot({
|
||||||
rootPath: join(__dirname, '../client'),
|
rootPath: join(__dirname, '../client'),
|
||||||
exclude: ['*/api*'],
|
exclude: ['*/api*'],
|
||||||
@@ -46,5 +45,6 @@ import * as express from 'express';
|
|||||||
],
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
|
exports: [],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import { UsersService } from 'src/users/users.service';
|
|||||||
import { ClientService } from 'src/client/client.service';
|
import { ClientService } from 'src/client/client.service';
|
||||||
import { FormDataRequest } from 'nestjs-form-data';
|
import { FormDataRequest } from 'nestjs-form-data';
|
||||||
import { Client } from 'src/model/client.entity';
|
import { Client } from 'src/model/client.entity';
|
||||||
|
import { CustomLogger } from 'src/core/custom.logger';
|
||||||
|
|
||||||
@Controller('auth')
|
@Controller('auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
constructor(
|
constructor(
|
||||||
private usersService: UsersService,
|
private usersService: UsersService,
|
||||||
private clientService: ClientService,
|
private clientService: ClientService,
|
||||||
|
private logger: CustomLogger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post('register')
|
@Post('register')
|
||||||
@@ -31,7 +33,7 @@ export class AuthController {
|
|||||||
password,
|
password,
|
||||||
clientId,
|
clientId,
|
||||||
);
|
);
|
||||||
|
this.logger.log(`User ${username} logged in on client ${clientId}`);
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,14 +59,12 @@ export class AuthController {
|
|||||||
@Body('client_secret') clientSecret: string,
|
@Body('client_secret') clientSecret: string,
|
||||||
@Body('code') code: string,
|
@Body('code') code: string,
|
||||||
@Body('grant_type') grantType: string,
|
@Body('grant_type') grantType: string,
|
||||||
@Body('redirect_uri') redirectUri: string,
|
|
||||||
) {
|
) {
|
||||||
return this.usersService.verifyToken({
|
return this.usersService.verifyToken({
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
code,
|
code,
|
||||||
grantType,
|
grantType,
|
||||||
redirectUri,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
AuthorizationCode,
|
AuthorizationCode,
|
||||||
AuthorizationCodeRepository,
|
AuthorizationCodeRepository,
|
||||||
} from 'src/model/auth-code.entity';
|
} from 'src/model/auth-code.entity';
|
||||||
|
import { LoggerModule } from 'src/core/logger.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [
|
providers: [
|
||||||
@@ -25,10 +26,11 @@ import {
|
|||||||
imports: [
|
imports: [
|
||||||
JwtModule.register({
|
JwtModule.register({
|
||||||
secret: 'SECRET_KEY', // Ändere dies zu einer Umgebungsvariablen in einer realen Anwendung
|
secret: 'SECRET_KEY', // Ändere dies zu einer Umgebungsvariablen in einer realen Anwendung
|
||||||
signOptions: { expiresIn: '60s' },
|
signOptions: { expiresIn: '15m' },
|
||||||
}),
|
}),
|
||||||
NestjsFormDataModule,
|
NestjsFormDataModule,
|
||||||
TypeOrmModule.forFeature([User, Client, RedirectUri, AuthorizationCode]),
|
TypeOrmModule.forFeature([User, Client, RedirectUri, AuthorizationCode]),
|
||||||
|
LoggerModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
|||||||
42
idp/src/core/custom.logger.ts
Normal file
42
idp/src/core/custom.logger.ts
Normal file
@@ -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<Log>) {}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
idp/src/core/logger.module.ts
Normal file
12
idp/src/core/logger.module.ts
Normal file
@@ -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 {}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { NestFactory, Reflector } from '@nestjs/core';
|
import { NestFactory, Reflector } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { ValidationPipe } from '@nestjs/common';
|
import { Logger, ValidationPipe } from '@nestjs/common';
|
||||||
import { ClassSerializerInterceptor } from '@nestjs/common';
|
import { ClassSerializerInterceptor } from '@nestjs/common';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
@@ -19,6 +19,7 @@ async function bootstrap() {
|
|||||||
);
|
);
|
||||||
const port = parseInt(process.env.PORT) || 5000;
|
const port = parseInt(process.env.PORT) || 5000;
|
||||||
await app.listen(port);
|
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();
|
bootstrap();
|
||||||
|
|||||||
7
idp/src/model/interface/logger.interface.ts
Normal file
7
idp/src/model/interface/logger.interface.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
16
idp/src/model/log.entity.ts
Normal file
16
idp/src/model/log.entity.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
DataSource,
|
DataSource,
|
||||||
Repository,
|
Repository,
|
||||||
OneToMany,
|
OneToMany,
|
||||||
|
CreateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { AuthorizationCode } from './auth-code.entity';
|
import { AuthorizationCode } from './auth-code.entity';
|
||||||
|
|
||||||
@@ -27,6 +28,16 @@ export class User {
|
|||||||
@Column()
|
@Column()
|
||||||
password: string;
|
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 })
|
@Column({ default: true })
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
AuthorizationCodeRepository,
|
AuthorizationCodeRepository,
|
||||||
} from 'src/model/auth-code.entity';
|
} from 'src/model/auth-code.entity';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { CustomLogger } from 'src/core/custom.logger';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersService {
|
export class UsersService {
|
||||||
@@ -16,6 +17,7 @@ export class UsersService {
|
|||||||
private readonly jwtService: JwtService,
|
private readonly jwtService: JwtService,
|
||||||
private userRepo: UserRepository,
|
private userRepo: UserRepository,
|
||||||
private tokenRepo: AuthorizationCodeRepository,
|
private tokenRepo: AuthorizationCodeRepository,
|
||||||
|
private logger: CustomLogger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createUser(username: string, password: string): Promise<User> {
|
async createUser(username: string, password: string): Promise<User> {
|
||||||
@@ -39,17 +41,20 @@ export class UsersService {
|
|||||||
): Promise<{ code: string }> {
|
): Promise<{ code: string }> {
|
||||||
const user = await this.userRepo.findByUsername(username);
|
const user = await this.userRepo.findByUsername(username);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
this.logger.error(`User ${username} not found`);
|
||||||
throw new Error('User not found');
|
throw new Error('User not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const valid = await bcrypt.compare(password, user.password);
|
const valid = await bcrypt.compare(password, user.password);
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
|
this.logger.error(`User ${username} provided invalid credentials`);
|
||||||
throw new HttpException('Invalid credentials', 401);
|
throw new HttpException('Invalid credentials', 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = await this.clientService.getClientById(clientId);
|
const client = await this.clientService.getClientById(clientId);
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
|
this.logger.error(`Client ${clientId} not found`);
|
||||||
throw new HttpException('Invalid client', 401);
|
throw new HttpException('Invalid client', 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,18 +68,10 @@ export class UsersService {
|
|||||||
return { code: token.code };
|
return { code: token.code };
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyToken({
|
async verifyToken({ clientId, clientSecret, code, grantType }: any) {
|
||||||
clientId,
|
|
||||||
clientSecret,
|
|
||||||
code,
|
|
||||||
grantType,
|
|
||||||
redirectUri,
|
|
||||||
}: any) {
|
|
||||||
if (grantType !== 'authorization_code') {
|
|
||||||
throw new HttpException('Invalid grant type', 401);
|
|
||||||
}
|
|
||||||
const client = await this.clientService.getClientById(clientId);
|
const client = await this.clientService.getClientById(clientId);
|
||||||
if (!client) {
|
if (!client) {
|
||||||
|
this.logger.error(`Client ${clientId} not found for verification`);
|
||||||
throw new HttpException('Invalid client', 401);
|
throw new HttpException('Invalid client', 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,8 +79,12 @@ export class UsersService {
|
|||||||
throw new HttpException('Invalid client', 401);
|
throw new HttpException('Invalid client', 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!client.redirectUris.some((uri) => uri.uri === redirectUri)) {
|
if (grantType == 'refresh_token') {
|
||||||
throw new HttpException('Invalid redirect', 401);
|
return this.getNewAccessToken(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grantType !== 'authorization_code') {
|
||||||
|
throw new HttpException('Invalid grant type', 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = await this.tokenRepo.findByCode(code);
|
const token = await this.tokenRepo.findByCode(code);
|
||||||
@@ -103,25 +104,53 @@ export class UsersService {
|
|||||||
|
|
||||||
this.tokenRepo.removeCode(token);
|
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 = {
|
const payload = {
|
||||||
username: user.username,
|
username: user.username,
|
||||||
id: user.id,
|
id: user.id,
|
||||||
iss: 'http://localhost:3000',
|
firstName: user.firstName,
|
||||||
aud: 'http://localhost:3000',
|
lastName: user.lastName,
|
||||||
|
iss: 'sso.beantastic.de',
|
||||||
|
aud: 'not set',
|
||||||
};
|
};
|
||||||
|
return this.jwtService.sign(payload);
|
||||||
|
}
|
||||||
|
|
||||||
const access_token = this.jwtService.sign(payload);
|
private createRefreshToken(user: User) {
|
||||||
const refresh_token = this.jwtService.sign(
|
this.jwtService.sign(
|
||||||
{
|
{
|
||||||
type: 'refresh',
|
type: 'refresh',
|
||||||
id: user.id,
|
id: user.id,
|
||||||
},
|
},
|
||||||
{ expiresIn: '365d' },
|
{ 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) {
|
verifyAccessToken(token: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user