Skip to content

Commit

Permalink
feat: progressive auth service
Browse files Browse the repository at this point in the history
  • Loading branch information
okjodom committed Jan 15, 2025
1 parent 06cb4b8 commit a120a80
Show file tree
Hide file tree
Showing 16 changed files with 236 additions and 40 deletions.
1 change: 1 addition & 0 deletions apps/api/.dev.env
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
PORT=4000
NODE_ENV='development'
AUTH_GRPC_URL='auth:4010'
SWAP_GRPC_URL='swap:4040'
NOSTR_GRPC_URL='nostr:4050'
SMS_GRPC_URL='sms:4060'
Expand Down
25 changes: 22 additions & 3 deletions apps/api/src/api.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { join } from 'path';
import * as Joi from 'joi';
import { join } from 'path';
import { JwtModule } from '@nestjs/jwt';
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { ClientsModule, Transport } from '@nestjs/microservices';
import {
AUTH_SERVICE_NAME,
EVENTS_SERVICE_BUS,
LoggerModule,
NOSTR_SERVICE_NAME,
Expand All @@ -22,9 +24,12 @@ import { AdminController } from './admin/admin.controller';
import { AdminService } from './admin/admin.service';
import { SolowalletService } from './solowallet/solowallet.service';
import { SolowalletController } from './solowallet/solowallet.controller';
import { AuthService } from './auth/auth.service';
import { AuthController } from './auth/auth.controller';

@Module({
imports: [
JwtModule,
LoggerModule,
ConfigModule.forRoot({
isGlobal: true,
Expand All @@ -41,6 +46,18 @@ import { SolowalletController } from './solowallet/solowallet.controller';
}),
}),
ClientsModule.registerAsync([
{
name: AUTH_SERVICE_NAME,
useFactory: (configService: ConfigService) => ({
transport: Transport.GRPC,
options: {
package: 'auth',
protoPath: join(__dirname, '../../../proto/auth.proto'),
url: configService.getOrThrow<string>('AUTH_GRPC_URL'),
},
}),
inject: [ConfigService],
},
{
name: SWAP_SERVICE_NAME,
useFactory: (configService: ConfigService) => ({
Expand Down Expand Up @@ -115,20 +132,22 @@ import { SolowalletController } from './solowallet/solowallet.controller';
]),
],
controllers: [
AdminController,
AuthController,
SwapController,
NostrController,
SmsController,
SharesController,
SolowalletController,
AdminController,
],
providers: [
AuthService,
SwapService,
NostrService,
SmsService,
SharesService,
AdminService,
SolowalletService,
AdminService,
],
})
export class ApiModule {}
39 changes: 39 additions & 0 deletions apps/api/src/auth/auth.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { TestingModule } from '@nestjs/testing';
import { createTestingModuleWithValidation } from '@bitsacco/testing';

import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';

describe('AuthController', () => {
let controller: AuthController;
let authService: AuthService;

beforeEach(async () => {
const module: TestingModule = await createTestingModuleWithValidation({
controllers: [AuthController],
providers: [
ConfigService,
{
provide: AuthService,
useValue: {
loginUser: jest.fn(),
registerUser: jest.fn(),
verifyUser: jest.fn(),
authenticate: jest.fn(),
},
},
JwtService,
],
});

controller = module.get<AuthController>(AuthController);
authService = module.get<AuthService>(AuthService);
});

it('should be defined', () => {
expect(controller).toBeDefined();
expect(authService).toBeDefined();
});
});
48 changes: 48 additions & 0 deletions apps/api/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { type Response } from 'express';
import { firstValueFrom } from 'rxjs';
import { JwtService } from '@nestjs/jwt';
import { Body, Controller, Logger, Post, Res } from '@nestjs/common';
import { ApiBody, ApiOperation } from '@nestjs/swagger';
import { AuthTokenPayload, LoginUserRequestDto } from '@bitsacco/common';
import { AuthService } from './auth.service';
import { ConfigService } from '@nestjs/config';

@Controller('auth')
export class AuthController {
private readonly logger = new Logger(AuthController.name);

constructor(
private readonly configService: ConfigService,
private readonly authService: AuthService,
private readonly jwtService: JwtService,
) {
this.logger.log('AuthController initialized');
}

@Post('login')
@ApiOperation({ summary: 'Login user' })
@ApiBody({
type: LoginUserRequestDto,
})
async login(
@Body() req: LoginUserRequestDto,
@Res({ passthrough: true }) res: Response,
) {
return firstValueFrom(this.authService.loginUser(req)).then(
(authResponse) => {
const { expires } = this.jwtService.decode<AuthTokenPayload>(
authResponse.token,
);

const env = this.configService.get('NODE_ENV');
res.cookie('Authentication', authResponse.token, {
httpOnly: true,
secure: env === 'production',
expires,
});

return authResponse;
},
);
}
}
34 changes: 34 additions & 0 deletions apps/api/src/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { TestingModule } from '@nestjs/testing';
import { AuthServiceClient } from '@bitsacco/common';
import { createTestingModuleWithValidation } from '@bitsacco/testing';
import { ClientGrpc } from '@nestjs/microservices';
import { AuthService } from './auth.service';

describe('AuthService', () => {
let service: AuthService;
let serviceGenerator: ClientGrpc;
let mockAuthServiceClient: Partial<AuthServiceClient>;

beforeEach(async () => {
serviceGenerator = {
getService: jest.fn().mockReturnValue(mockAuthServiceClient),
getClientByServiceName: jest.fn().mockReturnValue(mockAuthServiceClient),
};

const module: TestingModule = await createTestingModuleWithValidation({
providers: [
{
provide: AuthService,
useFactory: () => {
return new AuthService(serviceGenerator);
},
},
],
});
service = module.get<AuthService>(AuthService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
37 changes: 37 additions & 0 deletions apps/api/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {
AUTH_SERVICE_NAME,
AuthRequest,
AuthServiceClient,
LoginUserRequest,
RegisterUserRequest,
VerifyUserRequest,
} from '@bitsacco/common';
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { type ClientGrpc } from '@nestjs/microservices';

@Injectable()
export class AuthService implements OnModuleInit {
private client: AuthServiceClient;

constructor(@Inject(AUTH_SERVICE_NAME) private readonly grpc: ClientGrpc) {}

onModuleInit() {
this.client = this.grpc.getService<AuthServiceClient>(AUTH_SERVICE_NAME);
}

loginUser(req: LoginUserRequest) {
return this.client.loginUser(req);
}

registerUser(req: RegisterUserRequest) {
this.client.registerUser(req);
}

verifyUser(req: VerifyUserRequest) {
this.client.verifyUser(req);
}

authenticate(req: AuthRequest) {
this.client.authenticate(req);
}
}
2 changes: 1 addition & 1 deletion apps/auth/src/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class AuthController {
}

@GrpcMethod()
verifyuser(req: VerifyUserRequestDto) {
verifyUser(req: VerifyUserRequestDto) {
return this.authService.verifyUser(req);
}

Expand Down
15 changes: 7 additions & 8 deletions apps/auth/src/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,14 @@ import { ConfigService } from '@nestjs/config';
import { Injectable, Logger } from '@nestjs/common';
import {
AuthRequest,
AuthTokenPayload,
LoginUserRequestDto,
RegisterUserRequestDto,
User,
VerifyUserRequestDto,
} from '@bitsacco/common';
import { UsersService } from './users';

interface AuthTokenPayload {
user: User;
expires: Date;
}

@Injectable()
export class AuthService {
private readonly logger = new Logger(AuthService.name);
Expand All @@ -29,8 +25,9 @@ export class AuthService {

async loginUser(req: LoginUserRequestDto) {
const user = await this.userService.validateUser(req);

return this.createAuthToken(user);
return {
token: this.createAuthToken(user),
};
}

async registerUser(req: RegisterUserRequestDto) {
Expand Down Expand Up @@ -66,6 +63,8 @@ export class AuthService {
expires,
};

return this.jwtService.sign(payload);
return {
token: this.createAuthToken(user),
};
}
}
1 change: 0 additions & 1 deletion libs/common/src/auth/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion libs/common/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './auth';
export * from './types';
export * from './logger';
export * from './utils';
Expand Down
6 changes: 6 additions & 0 deletions libs/common/src/types/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { User } from './proto/auth';

export interface AuthTokenPayload {
user: User;
expires: Date;
}
1 change: 1 addition & 0 deletions libs/common/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from './proto/solowallet';
export * from './api';
export * from './validator';
export * from './fedimint';
export * from './auth';
27 changes: 17 additions & 10 deletions libs/common/src/types/proto/auth.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions libs/common/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './currency';
export { CustomStore } from './cache';
export { JwtAuthGuard } from './jwt-auth.guard';
Loading

0 comments on commit a120a80

Please sign in to comment.