diff --git a/libs/common/src/modules/client/client.repository.module.ts b/libs/common/src/modules/client/client.repository.module.ts new file mode 100644 index 0000000..af1f3b0 --- /dev/null +++ b/libs/common/src/modules/client/client.repository.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ClientEntity } from './entities'; + +@Module({ + imports: [TypeOrmModule.forFeature([ClientEntity])], + exports: [TypeOrmModule], +}) +export class ClientRepositoryModule {} diff --git a/src/app.module.ts b/src/app.module.ts index f2a5bd8..3187210 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -29,11 +29,13 @@ import { RoleModule } from './role/role.module'; import { TermsConditionsModule } from './terms-conditions/terms-conditions.module'; import { PrivacyPolicyModule } from './privacy-policy/privacy-policy.module'; import { TagModule } from './tags/tags.module'; +import { ClientModule } from './client/client.module'; @Module({ imports: [ ConfigModule.forRoot({ load: config, }), + ClientModule, AuthenticationModule, UserModule, InviteUserModule, diff --git a/src/client/client.module.ts b/src/client/client.module.ts new file mode 100644 index 0000000..84bc096 --- /dev/null +++ b/src/client/client.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { ClientController } from './controllers'; +import { ClientService } from './services'; +import { ClientRepositoryModule } from '@app/common/modules/client/client.repository.module'; +import { ClientRepository } from '@app/common/modules/client/repositories'; + +@Module({ + imports: [ClientRepositoryModule], + controllers: [ClientController], + providers: [ClientService, ClientRepository], + exports: [ClientService], +}) +export class ClientModule {} diff --git a/src/client/controllers/client.controller.ts b/src/client/controllers/client.controller.ts new file mode 100644 index 0000000..edf22ea --- /dev/null +++ b/src/client/controllers/client.controller.ts @@ -0,0 +1,15 @@ +import { Controller, Post, Body } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { RegisterClientDto } from '../dtos/register-client.dto'; +import { ClientService } from '../services'; + +@ApiTags('OAuth Clients') +@Controller('clients') +export class ClientController { + constructor(private readonly clientService: ClientService) {} + + @Post('register') + async registerClient(@Body() dto: RegisterClientDto) { + return this.clientService.registerClient(dto); + } +} diff --git a/src/client/controllers/index.ts b/src/client/controllers/index.ts new file mode 100644 index 0000000..a83bab5 --- /dev/null +++ b/src/client/controllers/index.ts @@ -0,0 +1 @@ +export * from './client.controller'; diff --git a/src/client/dtos/index.ts b/src/client/dtos/index.ts new file mode 100644 index 0000000..34d8bbb --- /dev/null +++ b/src/client/dtos/index.ts @@ -0,0 +1,3 @@ +export * from './add.community.dto'; +export * from './project.param.dto'; +export * from './get.community.dto'; diff --git a/src/client/dtos/register-client.dto.ts b/src/client/dtos/register-client.dto.ts new file mode 100644 index 0000000..bdcc573 --- /dev/null +++ b/src/client/dtos/register-client.dto.ts @@ -0,0 +1,31 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class RegisterClientDto { + @ApiProperty({ + example: 'SmartHomeApp', + description: 'The name of the client', + required: true, + }) + @IsString() + @IsNotEmpty() + name: string; + + @ApiProperty({ + example: 'https://client-app.com/callback', + description: 'The redirect URI of the client', + required: true, + }) + @IsString() + @IsNotEmpty() + redirectUri: string; + + @ApiProperty({ + example: ['DEVICE_SINGLE_CONTROL', 'DEVICE_VIEW'], + description: 'The scopes of the client', + required: true, + }) + @IsString({ each: true }) + @IsNotEmpty({ each: true }) + scopes: string[]; +} diff --git a/src/client/services/client.service.ts b/src/client/services/client.service.ts new file mode 100644 index 0000000..795e33a --- /dev/null +++ b/src/client/services/client.service.ts @@ -0,0 +1,45 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import * as crypto from 'crypto'; +import { RegisterClientDto } from '../dtos/register-client.dto'; +import { ClientEntity } from '@app/common/modules/client/entities'; + +@Injectable() +export class ClientService { + constructor( + @InjectRepository(ClientEntity) + private clientRepository: Repository, + ) {} + + async registerClient(dto: RegisterClientDto) { + const clientId = crypto.randomBytes(16).toString('hex'); + const clientSecret = crypto.randomBytes(32).toString('hex'); + + const client = this.clientRepository.create({ + name: dto.name, + clientId, + clientSecret, + redirectUri: dto.redirectUri, + scopes: dto.scopes, + }); + + await this.clientRepository.save(client); + + return { clientId, clientSecret }; + } + + async validateClient( + clientId: string, + clientSecret: string, + ): Promise { + const client = await this.clientRepository.findOne({ + where: { clientId, clientSecret }, + }); + if (!client) { + throw new NotFoundException('Invalid client credentials'); + } + return client; + } +} diff --git a/src/client/services/index.ts b/src/client/services/index.ts new file mode 100644 index 0000000..b58570a --- /dev/null +++ b/src/client/services/index.ts @@ -0,0 +1 @@ +export * from './client.service';