finished client endpoints

This commit is contained in:
faris Aljohari
2025-03-27 01:28:40 +03:00
parent c5dac6c564
commit cbd3f5028b
10 changed files with 169 additions and 21 deletions

View File

@ -77,21 +77,28 @@ export class AuthService {
return await this.sessionRepository.save(data); return await this.sessionRepository.save(data);
} }
async getTokens(payload) { async getTokens(
payload,
isRefreshToken = true,
accessTokenExpiry = '24h',
refreshTokenExpiry = '30d',
) {
const [accessToken, refreshToken] = await Promise.all([ const [accessToken, refreshToken] = await Promise.all([
this.jwtService.signAsync(payload, { this.jwtService.signAsync(payload, {
secret: this.configService.get<string>('JWT_SECRET'), secret: this.configService.get<string>('JWT_SECRET'),
expiresIn: '24h', expiresIn: accessTokenExpiry,
}),
this.jwtService.signAsync(payload, {
secret: this.configService.get<string>('JWT_SECRET'),
expiresIn: '7d',
}), }),
isRefreshToken
? this.jwtService.signAsync(payload, {
secret: this.configService.get<string>('JWT_SECRET'),
expiresIn: refreshTokenExpiry,
})
: null,
]); ]);
return { return {
accessToken, accessToken,
refreshToken, ...(isRefreshToken ? { refreshToken } : {}),
}; };
} }

View File

@ -1,4 +1,17 @@
export class ControllerRoute { export class ControllerRoute {
static CLIENT = class {
public static readonly ROUTE = 'client';
static ACTIONS = class {
public static readonly REGISTER_NEW_CLIENT_SUMMARY =
'Register a new client';
public static readonly REGISTER_NEW_CLIENT_DESCRIPTION =
'This endpoint registers a new client in the system.';
public static readonly LOGIN_CLIENT_SUMMARY = 'Login a client';
public static readonly LOGIN_CLIENT_DESCRIPTION =
'This endpoint allows a client to log in to the system.';
};
};
static PROJECT = class { static PROJECT = class {
public static readonly ROUTE = 'projects'; public static readonly ROUTE = 'projects';
static ACTIONS = class { static ACTIONS = class {

View File

@ -1,17 +1,18 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module';
import { CommonModule } from '../../libs/common/src';
import { UserAuthController } from './controllers';
import { UserAuthService } from './services';
import { UserRepository } from '@app/common/modules/user/repositories'; import { UserRepository } from '@app/common/modules/user/repositories';
import { UserSessionRepository } from '@app/common/modules/session/repositories/session.repository'; import { UserSessionRepository } from '@app/common/modules/session/repositories/session.repository';
import { UserOtpRepository } from '@app/common/modules/user/repositories'; import { UserOtpRepository } from '@app/common/modules/user/repositories';
import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; import { RoleTypeRepository } from '@app/common/modules/role-type/repositories';
import { RoleService } from 'src/role/services'; import { RoleService } from 'src/role/services';
import { UserAuthController } from './controllers';
import { UserAuthService } from './services';
import { AuthService } from '@app/common/auth/services/auth.service';
import { EmailService } from '@app/common/util/email.service';
import { JwtService } from '@nestjs/jwt';
@Module({ @Module({
imports: [ConfigModule, UserRepositoryModule, CommonModule], imports: [ConfigModule],
controllers: [UserAuthController], controllers: [UserAuthController],
providers: [ providers: [
UserAuthService, UserAuthService,
@ -20,6 +21,9 @@ import { RoleService } from 'src/role/services';
UserOtpRepository, UserOtpRepository,
RoleTypeRepository, RoleTypeRepository,
RoleService, RoleService,
AuthService,
EmailService,
JwtService,
], ],
exports: [UserAuthService], exports: [UserAuthService],
}) })

View File

@ -19,6 +19,7 @@ import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard';
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
import { OtpType } from '@app/common/constants/otp-type.enum'; import { OtpType } from '@app/common/constants/otp-type.enum';
import { ControllerRoute } from '@app/common/constants/controller-route'; import { ControllerRoute } from '@app/common/constants/controller-route';
import { ClientGuard } from 'src/guards/client.guard';
@Controller({ @Controller({
version: EnableDisableStatusEnum.ENABLED, version: EnableDisableStatusEnum.ENABLED,
@ -29,6 +30,8 @@ export class UserAuthController {
constructor(private readonly userAuthService: UserAuthService) {} constructor(private readonly userAuthService: UserAuthService) {}
@ResponseMessage('User Registered Successfully') @ResponseMessage('User Registered Successfully')
@ApiBearerAuth()
@UseGuards(ClientGuard)
@Post('user/signup') @Post('user/signup')
@ApiOperation({ @ApiOperation({
summary: ControllerRoute.AUTHENTICATION.ACTIONS.SIGN_UP_SUMMARY, summary: ControllerRoute.AUTHENTICATION.ACTIONS.SIGN_UP_SUMMARY,

View File

@ -3,11 +3,22 @@ import { ClientController } from './controllers';
import { ClientService } from './services'; import { ClientService } from './services';
import { ClientRepositoryModule } from '@app/common/modules/client/client.repository.module'; import { ClientRepositoryModule } from '@app/common/modules/client/client.repository.module';
import { ClientRepository } from '@app/common/modules/client/repositories'; import { ClientRepository } from '@app/common/modules/client/repositories';
import { AuthService } from '@app/common/auth/services/auth.service';
import { JwtService } from '@nestjs/jwt';
import { UserRepository } from '@app/common/modules/user/repositories';
import { UserSessionRepository } from '@app/common/modules/session/repositories/session.repository';
@Module({ @Module({
imports: [ClientRepositoryModule], imports: [ClientRepositoryModule],
controllers: [ClientController], controllers: [ClientController],
providers: [ClientService, ClientRepository], providers: [
ClientService,
ClientRepository,
AuthService,
JwtService,
UserRepository,
UserSessionRepository,
],
exports: [ClientService], exports: [ClientService],
}) })
export class ClientModule {} export class ClientModule {}

View File

@ -1,15 +1,33 @@
import { Controller, Post, Body } from '@nestjs/common'; import { Controller, Post, Body } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { RegisterClientDto } from '../dtos/register-client.dto'; import { RegisterClientDto } from '../dtos/register-client.dto';
import { ClientService } from '../services'; import { ClientService } from '../services';
import { ControllerRoute } from '@app/common/constants/controller-route';
import { ClientTokenDto } from '../dtos/token-client.dto';
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
@ApiTags('OAuth Clients') @ApiTags('OAuth Clients')
@Controller('clients') @Controller({
version: EnableDisableStatusEnum.ENABLED,
path: ControllerRoute.CLIENT.ROUTE,
})
export class ClientController { export class ClientController {
constructor(private readonly clientService: ClientService) {} constructor(private readonly clientService: ClientService) {}
@Post('register') @Post('register')
@ApiOperation({
summary: ControllerRoute.CLIENT.ACTIONS.REGISTER_NEW_CLIENT_SUMMARY,
description: ControllerRoute.CLIENT.ACTIONS.REGISTER_NEW_CLIENT_DESCRIPTION,
})
async registerClient(@Body() dto: RegisterClientDto) { async registerClient(@Body() dto: RegisterClientDto) {
return this.clientService.registerClient(dto); return this.clientService.registerClient(dto);
} }
@Post('token')
@ApiOperation({
summary: ControllerRoute.CLIENT.ACTIONS.LOGIN_CLIENT_SUMMARY,
description: ControllerRoute.CLIENT.ACTIONS.LOGIN_CLIENT_DESCRIPTION,
})
async login(@Body() dto: ClientTokenDto) {
return this.clientService.loginWithClientCredentials(dto);
}
} }

View File

@ -1,3 +1 @@
export * from './add.community.dto'; export * from './register-client.dto';
export * from './project.param.dto';
export * from './get.community.dto';

View File

@ -0,0 +1,22 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class ClientTokenDto {
@ApiProperty({
example: 'abcd1234xyz',
description: 'The client ID of the client',
required: true,
})
@IsString()
@IsNotEmpty()
clientId: string;
@ApiProperty({
example: 'secureSecret123',
description: 'The client secret of the client',
required: true,
})
@IsString()
@IsNotEmpty()
clientSecret: string;
}

View File

@ -1,18 +1,37 @@
import { Injectable, NotFoundException } from '@nestjs/common'; import { HttpStatus, Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import { RegisterClientDto } from '../dtos/register-client.dto'; import { RegisterClientDto } from '../dtos/register-client.dto';
import { ClientEntity } from '@app/common/modules/client/entities'; import { ClientEntity } from '@app/common/modules/client/entities';
import { ClientTokenDto } from '../dtos/token-client.dto';
import { AuthService } from '@app/common/auth/services/auth.service';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
@Injectable() @Injectable()
export class ClientService { export class ClientService {
constructor( constructor(
@InjectRepository(ClientEntity) @InjectRepository(ClientEntity)
private clientRepository: Repository<ClientEntity>, private clientRepository: Repository<ClientEntity>,
private readonly authService: AuthService,
) {} ) {}
async loginWithClientCredentials(dto: ClientTokenDto) {
const client = await this.validateClient(dto.clientId, dto.clientSecret);
const payload = {
client: {
clientId: client.clientId,
uuid: client.uuid,
scopes: client.scopes,
},
};
const tokens = await this.authService.getTokens(payload, false, '5m');
return new SuccessResponseDto({
message: `Client logged in successfully`,
data: tokens,
statusCode: HttpStatus.CREATED,
});
}
async registerClient(dto: RegisterClientDto) { async registerClient(dto: RegisterClientDto) {
const clientId = crypto.randomBytes(16).toString('hex'); const clientId = crypto.randomBytes(16).toString('hex');
const clientSecret = crypto.randomBytes(32).toString('hex'); const clientSecret = crypto.randomBytes(32).toString('hex');
@ -27,7 +46,11 @@ export class ClientService {
await this.clientRepository.save(client); await this.clientRepository.save(client);
return { clientId, clientSecret }; return new SuccessResponseDto({
message: `Client registered successfully`,
data: { clientId, clientSecret },
statusCode: HttpStatus.CREATED,
});
} }
async validateClient( async validateClient(

View File

@ -0,0 +1,49 @@
import { UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import * as jwt from 'jsonwebtoken';
export class ClientGuard extends AuthGuard('jwt') {
handleRequest(err, user, info, context) {
if (err || !user) {
throw err || new UnauthorizedException();
}
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException('Token not found');
}
try {
const decoded = jwt.decode(token);
if (!this.validateToken(decoded)) {
throw new UnauthorizedException('Invalid token');
}
} catch (err) {
throw new UnauthorizedException('Invalid token');
}
return user;
}
private extractTokenFromHeader(request): string | null {
const authHeader = request.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
return authHeader.split(' ')[1];
}
return null;
}
private validateToken(decoded: any): boolean {
if (
decoded &&
decoded.client &&
decoded.client.clientId &&
decoded.client.uuid
) {
return true;
}
return false;
}
}