Skip to content

Commit dfe81f0

Browse files
authored
Merge pull request #16 from bonadocs/database-user-repositories
Database user repositories
2 parents 24ff864 + e42c5fa commit dfe81f0

File tree

8 files changed

+104
-8
lines changed

8 files changed

+104
-8
lines changed

src/middleware/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers';
55
import { Inject, Service } from 'typedi';
66

77
import { diConstants } from '@bonadocs/di';
8-
import { BonadocsLogger } from '@bonadocs/logger';
8+
import type { BonadocsLogger } from '@bonadocs/logger';
99

1010
import { AuthService } from '../modules/auth/auth.service';
1111
import { ApplicationError } from '../modules/errors/ApplicationError';

src/modules/auth/auth.controller.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,23 @@ import { Inject, Service } from 'typedi';
33

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

6-
import {
6+
import type {
77
LoginUserRequest,
88
LoginUserResponse,
99
RefreshTokenRequest,
1010
RefreshTokenResponse,
11+
WSTokenResponse,
1112
} from './auth.interface';
1213
import { AuthService } from './auth.service';
1314
import { LoginDto } from './dto/login.dto';
15+
import { WSLoginDto } from './dto/wslogin.dto';
1416

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

20-
@Post('/login')
22+
@Post('')
2123
async login(
2224
@Body({ validate: true }) payload: LoginDto,
2325
): Promise<JsonResponse<LoginUserResponse>> {
@@ -41,4 +43,14 @@ export class AuthController {
4143
message: 'Refresh successful',
4244
};
4345
}
46+
47+
@Post('/ws')
48+
async ws(@Body({ validate: true }) payload: WSLoginDto): Promise<JsonResponse<WSTokenResponse>> {
49+
const response = await this.authService.wsLogin(payload);
50+
return {
51+
data: response,
52+
status: 'successful',
53+
message: 'Refresh successful',
54+
};
55+
}
4456
}

src/modules/auth/auth.interface.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,11 @@ export interface RefreshTokenRequest {
1717
export interface RefreshTokenResponse {
1818
token: string;
1919
}
20+
21+
export interface WSTokenRequest {
22+
token: string;
23+
}
24+
25+
export interface WSTokenResponse {
26+
token: string;
27+
}

src/modules/auth/auth.service.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,5 +94,39 @@ describe('AuthService', () => {
9494
});
9595
}
9696
});
97+
98+
describe('wsLogin', () => {
99+
it('should generate a websocket token when given valid API token', async () => {
100+
// arrange
101+
const token = authService.generateJWT({
102+
userId: '123',
103+
authSource: 'firebase',
104+
});
105+
106+
// act
107+
const result = await authService.wsLogin({
108+
token,
109+
});
110+
111+
// assert
112+
expect(result).toBeDefined();
113+
expect(result.token).toEqual(expect.any(String));
114+
115+
const decoded = authService.validateJWT(result.token);
116+
expect(decoded).toMatchObject({
117+
purpose: 'ws-api',
118+
sub: '123',
119+
});
120+
});
121+
122+
it('should throw error when API token is invalid', async () => {
123+
// act & assert
124+
await expect(
125+
authService.wsLogin({
126+
token: 'invalid-token',
127+
}),
128+
).rejects.toHaveProperty('errorCode', 'UNAUTHORIZED');
129+
});
130+
});
97131
});
98132
});

src/modules/auth/auth.service.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { createHash } from 'crypto';
44
import { Inject, Service } from 'typedi';
55

66
import { diConstants } from '@bonadocs/di';
7-
import { BonadocsLogger } from '@bonadocs/logger';
7+
import type { BonadocsLogger } from '@bonadocs/logger';
88

99
import { ConfigService } from '../configuration/config.service';
1010
import { ApplicationError, applicationErrorCodes } from '../errors/ApplicationError';
@@ -16,6 +16,8 @@ import {
1616
LoginUserResponse,
1717
RefreshTokenRequest,
1818
RefreshTokenResponse,
19+
WSTokenRequest,
20+
WSTokenResponse,
1921
} from './auth.interface';
2022
import { AuthSource } from './auth.types';
2123
import { FirebaseJWTProvider } from './firebase';
@@ -87,6 +89,30 @@ export class AuthService {
8789
};
8890
}
8991

92+
async wsLogin(request: WSTokenRequest): Promise<WSTokenResponse> {
93+
const isValid = this.validateJWT(request.token);
94+
if (!isValid) {
95+
throw new ApplicationError({
96+
logger: this.logger,
97+
message: 'Api token provided not valid',
98+
errorCode: applicationErrorCodes.unauthorized,
99+
});
100+
}
101+
const payload = JSON.parse(Buffer.from(request.token.split('.')[1], 'base64url').toString());
102+
const validityPeriod = this.validityFromEnv ? Number(this.validityFromEnv) : 6 * 3600;
103+
const token = this.generateJWT(
104+
{
105+
purpose: 'ws-api',
106+
sub: payload.userId,
107+
},
108+
validityPeriod,
109+
);
110+
111+
return {
112+
token,
113+
};
114+
}
115+
90116
getHandler(logger: BonadocsLogger, authSource: AuthSource): AuthHandler {
91117
const func = this.authSourceHandlers[authSource];
92118
if (!func || typeof func !== 'function') {
@@ -206,6 +232,13 @@ export class AuthService {
206232
}
207233

208234
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString());
235+
if (payload.purpose === 'ws-api') {
236+
throw new ApplicationError({
237+
message: 'Invalid JWT - purpose ws server',
238+
logger: this.logger,
239+
errorCode: applicationErrorCodes.unauthorized,
240+
});
241+
}
209242
if (!payload.userId) {
210243
throw new ApplicationError({
211244
message: 'Invalid JWT - missing user ID',

src/modules/auth/dto/login.dto.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { IsIn, IsNotEmpty, IsNumber, IsString } from 'class-validator';
22

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

55
export class LoginDto {
66
@IsString()
77
@IsNotEmpty({ message: 'Auth source not provided' })
8-
@IsIn(authSources, { message: `authSource must be one of: ${authSources.join(', ')}` })
9-
authSource: AuthSource;
8+
@IsIn(authTypes.authSources, {
9+
message: `authSource must be one of: ${authTypes.authSources.join(', ')}`,
10+
})
11+
authSource: authTypes.AuthSource;
1012

1113
@IsNotEmpty({ message: 'Auth data not provided' })
1214
@IsString()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { IsNotEmpty, IsString } from 'class-validator';
2+
3+
export class WSLoginDto {
4+
@IsNotEmpty({ message: 'Token not provided' })
5+
@IsString()
6+
token: string;
7+
}

src/modules/tenderly/tenderly.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { TenderlyService } from './tenderly.service';
1212
export class TenderlyController {
1313
constructor(@Inject() private readonly tenderlyService: TenderlyService) {}
1414

15-
@Get('/stimulate')
15+
@Get('/simulate')
1616
async list(
1717
@QueryParam('chainId') chainId: number,
1818
@Body({ validate: true }) payload: SimulationsRequest,

0 commit comments

Comments
 (0)