Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers';
import { Inject, Service } from 'typedi';

import { diConstants } from '@bonadocs/di';
import { BonadocsLogger } from '@bonadocs/logger';
import type { BonadocsLogger } from '@bonadocs/logger';

import { AuthService } from '../modules/auth/auth.service';
import { ApplicationError } from '../modules/errors/ApplicationError';
Expand Down
16 changes: 14 additions & 2 deletions src/modules/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,23 @@ import { Inject, Service } from 'typedi';

import { JsonResponse } from '../shared';

import {
import type {
LoginUserRequest,
LoginUserResponse,
RefreshTokenRequest,
RefreshTokenResponse,
WSTokenResponse,
} from './auth.interface';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
import { WSLoginDto } from './dto/wslogin.dto';

@Service()
@JsonController('/auth')
export class AuthController {
constructor(@Inject() private readonly authService: AuthService) {}

@Post('/login')
@Post('')
async login(
@Body({ validate: true }) payload: LoginDto,
): Promise<JsonResponse<LoginUserResponse>> {
Expand All @@ -41,4 +43,14 @@ export class AuthController {
message: 'Refresh successful',
};
}

@Post('/ws')
async ws(@Body({ validate: true }) payload: WSLoginDto): Promise<JsonResponse<WSTokenResponse>> {
const response = await this.authService.wsLogin(payload);
return {
data: response,
status: 'successful',
message: 'Refresh successful',
};
}
}
8 changes: 8 additions & 0 deletions src/modules/auth/auth.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ export interface RefreshTokenRequest {
export interface RefreshTokenResponse {
token: string;
}

export interface WSTokenRequest {
token: string;
}

export interface WSTokenResponse {
token: string;
}
34 changes: 34 additions & 0 deletions src/modules/auth/auth.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,39 @@ describe('AuthService', () => {
});
}
});

describe('wsLogin', () => {
it('should generate a websocket token when given valid API token', async () => {
// arrange
const token = authService.generateJWT({
userId: '123',
authSource: 'firebase',
});

// act
const result = await authService.wsLogin({
token,
});

// assert
expect(result).toBeDefined();
expect(result.token).toEqual(expect.any(String));

const decoded = authService.validateJWT(result.token);
expect(decoded).toMatchObject({
purpose: 'ws-api',
sub: '123',
});
});

it('should throw error when API token is invalid', async () => {
// act & assert
await expect(
authService.wsLogin({
token: 'invalid-token',
}),
).rejects.toHaveProperty('errorCode', 'UNAUTHORIZED');
});
});
});
});
35 changes: 34 additions & 1 deletion src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createHash } from 'crypto';
import { Inject, Service } from 'typedi';

import { diConstants } from '@bonadocs/di';
import { BonadocsLogger } from '@bonadocs/logger';
import type { BonadocsLogger } from '@bonadocs/logger';

import { ConfigService } from '../configuration/config.service';
import { ApplicationError, applicationErrorCodes } from '../errors/ApplicationError';
Expand All @@ -16,6 +16,8 @@ import {
LoginUserResponse,
RefreshTokenRequest,
RefreshTokenResponse,
WSTokenRequest,
WSTokenResponse,
} from './auth.interface';
import { AuthSource } from './auth.types';
import { FirebaseJWTProvider } from './firebase';
Expand Down Expand Up @@ -87,6 +89,30 @@ export class AuthService {
};
}

async wsLogin(request: WSTokenRequest): Promise<WSTokenResponse> {
const isValid = this.validateJWT(request.token);
if (!isValid) {
throw new ApplicationError({
logger: this.logger,
message: 'Api token provided not valid',
errorCode: applicationErrorCodes.unauthorized,
});
}
const payload = JSON.parse(Buffer.from(request.token.split('.')[1], 'base64url').toString());
const validityPeriod = this.validityFromEnv ? Number(this.validityFromEnv) : 6 * 3600;
const token = this.generateJWT(
{
purpose: 'ws-api',
sub: payload.userId,
},
validityPeriod,
);

return {
token,
};
}

getHandler(logger: BonadocsLogger, authSource: AuthSource): AuthHandler {
const func = this.authSourceHandlers[authSource];
if (!func || typeof func !== 'function') {
Expand Down Expand Up @@ -206,6 +232,13 @@ export class AuthService {
}

const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString());
if (payload.purpose === 'ws-api') {
throw new ApplicationError({
message: 'Invalid JWT - purpose ws server',
logger: this.logger,
errorCode: applicationErrorCodes.unauthorized,
});
}
if (!payload.userId) {
throw new ApplicationError({
message: 'Invalid JWT - missing user ID',
Expand Down
8 changes: 5 additions & 3 deletions src/modules/auth/dto/login.dto.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { IsIn, IsNotEmpty, IsNumber, IsString } from 'class-validator';

import { AuthSource, authSources } from '../auth.types';
import * as authTypes from '../auth.types';

export class LoginDto {
@IsString()
@IsNotEmpty({ message: 'Auth source not provided' })
@IsIn(authSources, { message: `authSource must be one of: ${authSources.join(', ')}` })
authSource: AuthSource;
@IsIn(authTypes.authSources, {
message: `authSource must be one of: ${authTypes.authSources.join(', ')}`,
})
authSource: authTypes.AuthSource;

@IsNotEmpty({ message: 'Auth data not provided' })
@IsString()
Expand Down
7 changes: 7 additions & 0 deletions src/modules/auth/dto/wslogin.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { IsNotEmpty, IsString } from 'class-validator';

export class WSLoginDto {
@IsNotEmpty({ message: 'Token not provided' })
@IsString()
token: string;
}
2 changes: 1 addition & 1 deletion src/modules/tenderly/tenderly.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { TenderlyService } from './tenderly.service';
export class TenderlyController {
constructor(@Inject() private readonly tenderlyService: TenderlyService) {}

@Get('/stimulate')
@Get('/simulate')
async list(
@QueryParam('chainId') chainId: number,
@Body({ validate: true }) payload: SimulationsRequest,
Expand Down
Loading