feat: generate secret function and replaced few instances (#7810)

This PR fixes #4588

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
ZiaCodes
2024-10-30 16:07:11 +05:00
committed by GitHub
parent 1782865ff8
commit 57d9b8e8b4
75 changed files with 2860 additions and 1531 deletions

View File

@ -2,14 +2,14 @@
import { Module } from '@nestjs/common';
import { JwtModule as NestJwtModule } from '@nestjs/jwt';
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
import { EnvironmentModule } from 'src/engine/core-modules/environment/environment.module';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
const InternalJwtModule = NestJwtModule.registerAsync({
useFactory: async (environmentService: EnvironmentService) => {
return {
secret: environmentService.get('ACCESS_TOKEN_SECRET'),
secret: environmentService.get('APP_SECRET'),
signOptions: {
expiresIn: environmentService.get('ACCESS_TOKEN_EXPIRES_IN'),
},

View File

@ -1,11 +1,30 @@
import { Injectable } from '@nestjs/common';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService, JwtSignOptions, JwtVerifyOptions } from '@nestjs/jwt';
import { createHash } from 'crypto';
import * as jwt from 'jsonwebtoken';
import {
AuthException,
AuthExceptionCode,
} from 'src/engine/core-modules/auth/auth.exception';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
type WorkspaceTokenType =
| 'ACCESS'
| 'LOGIN'
| 'REFRESH'
| 'FILE'
| 'POSTGRES_PROXY'
| 'REMOTE_SERVER';
@Injectable()
export class JwtWrapperService {
constructor(private readonly jwtService: JwtService) {}
constructor(
private readonly jwtService: JwtService,
private readonly environmentService: EnvironmentService,
) {}
sign(payload: string | object, options?: JwtSignOptions): string {
// Typescript does not handle well the overloads of the sign method, helping it a little bit
@ -20,7 +39,58 @@ export class JwtWrapperService {
return this.jwtService.verify(token, options);
}
decode<T = any>(payload: string, options: jwt.DecodeOptions): T {
decode<T = any>(payload: string, options?: jwt.DecodeOptions): T {
return this.jwtService.decode(payload, options);
}
verifyWorkspaceToken(
token: string,
type: WorkspaceTokenType,
options?: JwtVerifyOptions,
) {
const payload = this.decode(token, {
json: true,
});
// TODO: check if this is really needed
if (type !== 'FILE' && !payload.sub) {
throw new UnauthorizedException('No payload sub');
}
try {
return this.jwtService.verify(token, {
...options,
secret: this.generateAppSecret(type, payload.workspaceId),
});
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
throw new AuthException(
'Token has expired.',
AuthExceptionCode.UNAUTHENTICATED,
);
} else if (error instanceof jwt.JsonWebTokenError) {
throw new AuthException(
'Token invalid.',
AuthExceptionCode.UNAUTHENTICATED,
);
} else {
throw new AuthException(
'Unknown token error.',
AuthExceptionCode.INVALID_INPUT,
);
}
}
}
generateAppSecret(type: WorkspaceTokenType, workspaceId?: string): string {
const appSecret = this.environmentService.get('APP_SECRET');
if (!appSecret) {
throw new Error('APP_SECRET is not set');
}
return createHash('sha256')
.update(`${appSecret}${workspaceId}${type}`)
.digest('hex');
}
}