Skip to content

Commit

Permalink
Merge pull request #35 from getlarge/34-feat-implement-oauth2-client-…
Browse files Browse the repository at this point in the history
…credentials-flow-to-access-api

feat: implement oauth2 client credentials flow to access api
  • Loading branch information
getlarge authored Apr 15, 2024
2 parents 3eaf98a + aa72877 commit ad67709
Show file tree
Hide file tree
Showing 93 changed files with 1,896 additions and 1,755 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,5 @@ urls_identity_provider_publicUrl="http://127.0.0.1:4433"
urls_identity_provider_url="http://kratos:4434"
secrets_system="system_secret_not_good_not_secure"
oidc_subject_identifiers_pairwise_salt="not_secure_salt"
oauth2_token_hook_url="http://host.docker.internal:8080/api/clients/on-token-request"
oauth2_token_hook_auth_config_value="unsecure_api_key"
158 changes: 158 additions & 0 deletions apps/auth/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,100 @@
}
]
}
},
"/api/clients": {
"post": {
"operationId": "ClientsController_create",
"summary": "Register a new client - Scope : clients:create_one",
"description": "Register a new client",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateClientDto"
}
}
}
},
"responses": {
"200": {
"description": "Client created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreatedClientDto"
}
}
}
}
},
"tags": [
"clients"
]
}
},
"/api/clients/on-token-request": {
"post": {
"operationId": "ClientsController_onSignUp",
"summary": "",
"description": "Triggered when a client request an OAuth2 token",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OryOAuth2WebhookPayloadDto"
}
}
}
},
"responses": {
"200": {
"description": "Token session update",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OryOAuth2WebhookResponseDto"
}
}
}
}
},
"tags": [
"clients"
]
}
},
"/api/clients/current-client": {
"get": {
"operationId": "ClientsController_getCurrentClient",
"summary": "Get current client - Scope : clients:read_one",
"description": "Get details about currently authenticated client",
"parameters": [],
"responses": {
"201": {
"description": "Current client authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ClientDto"
}
}
}
}
},
"tags": [
"clients"
],
"security": [
{
"bearer": []
}
]
}
}
},
"info": {
Expand All @@ -142,6 +236,10 @@
{
"name": "users",
"description": ""
},
{
"name": "clients",
"description": ""
}
],
"servers": [
Expand Down Expand Up @@ -308,6 +406,66 @@
"id",
"identityId"
]
},
"CreateClientDto": {
"type": "object",
"properties": {}
},
"CreatedClientDto": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"clientId": {
"type": "string",
"description": "Ory client id",
"format": "uuid"
},
"userId": {
"type": "string",
"description": "Owner id"
},
"clientSecret": {
"type": "string"
}
},
"required": [
"id",
"clientId",
"userId",
"clientSecret"
]
},
"OryOAuth2WebhookPayloadDto": {
"type": "object",
"properties": {}
},
"OryOAuth2WebhookResponseDto": {
"type": "object",
"properties": {}
},
"ClientDto": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"clientId": {
"type": "string",
"description": "Ory client id",
"format": "uuid"
},
"userId": {
"type": "string",
"description": "Owner id"
}
},
"required": [
"id",
"clientId",
"userId"
]
}
}
},
Expand Down
2 changes: 2 additions & 0 deletions apps/auth/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { LoggerModule } from 'nestjs-pino';

import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ClientsModule } from './clients/clients.module';
import { EnvironmentVariables } from './env';
import { HealthModule } from './health/health.module';
import { UsersModule } from './users/users.module';
Expand Down Expand Up @@ -48,6 +49,7 @@ import { UsersModule } from './users/users.module';
}),
HealthModule,
UsersModule,
ClientsModule,
],
controllers: [AppController],
providers: [
Expand Down
115 changes: 115 additions & 0 deletions apps/auth/src/app/clients/clients.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {
Body,
Controller,
Get,
HttpCode,
HttpStatus,
Post,
UseGuards,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import {
ApiBearerAuth,
ApiBody,
ApiOperation,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import { SecurityRequirements } from '@ticketing/microservices/shared/constants';
import {
CurrentClient,
CurrentUser,
} from '@ticketing/microservices/shared/decorators';
import {
OryActionAuthGuard,
OryAuthenticationGuard,
OryOAuth2AuthenticationGuard,
} from '@ticketing/microservices/shared/guards';
import { Actions, Resources } from '@ticketing/shared/constants';
import { requestValidationErrorFactory } from '@ticketing/shared/errors';

import { User } from '../users/models';
import { ClientsService } from './clients.service';
import {
Client,
ClientDto,
CreateClientDto,
CreatedClientDto,
OryOAuth2WebhookPayloadDto,
OryOAuth2WebhookResponseDto,
} from './models';

@Controller(Resources.CLIENTS)
@ApiTags(Resources.CLIENTS)
export class ClientsController {
constructor(private readonly clientsService: ClientsService) {}

@UseGuards(OryAuthenticationGuard())
@UsePipes(
new ValidationPipe({
transform: true,
exceptionFactory: requestValidationErrorFactory,
forbidUnknownValues: true,
}),
)
@ApiOperation({
description: 'Register a new client',
summary: `Register a new client - Scope : ${Resources.CLIENTS}:${Actions.CREATE_ONE}`,
})
@ApiBody({ type: CreateClientDto })
@ApiResponse({
status: HttpStatus.OK,
description: 'Client created',
type: CreatedClientDto,
})
@Post('')
@HttpCode(HttpStatus.CREATED)
create(
@CurrentUser() user: User,
@Body() body: CreateClientDto,
): Promise<CreatedClientDto> {
return this.clientsService.create(body, user);
}

@UseGuards(OryActionAuthGuard)
@UsePipes(
new ValidationPipe({
transform: true,
exceptionFactory: requestValidationErrorFactory,
forbidUnknownValues: true,
}),
)
@ApiOperation({
description: 'Triggered when a client request an OAuth2 token',
})
@ApiBody({ type: OryOAuth2WebhookPayloadDto })
@ApiResponse({
status: HttpStatus.OK,
description: 'Token session update',
type: OryOAuth2WebhookResponseDto,
})
@Post('on-token-request')
@HttpCode(HttpStatus.OK)
onSignUp(
@Body() body: OryOAuth2WebhookPayloadDto,
): Promise<OryOAuth2WebhookResponseDto> {
return this.clientsService.onTokenRequest(body);
}

@UseGuards(OryOAuth2AuthenticationGuard())
@ApiOperation({
description: 'Get details about currently authenticated client',
summary: `Get current client - Scope : ${Resources.CLIENTS}:${Actions.READ_ONE}`,
})
@ApiBearerAuth(SecurityRequirements.Bearer)
@ApiResponse({
status: HttpStatus.CREATED,
description: 'Current client authenticated',
type: ClientDto,
})
@Get('current-client')
getCurrentClient(@CurrentClient() client: Client): Client {
return client;
}
}
41 changes: 41 additions & 0 deletions apps/auth/src/app/clients/clients.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { OryOAuth2Module } from '@getlarge/hydra-client-wrapper';
import { OryFrontendModule } from '@getlarge/kratos-client-wrapper';
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';

import { EnvironmentVariables } from '../env';
import { ClientsController } from './clients.controller';
import { ClientsService } from './clients.service';
import { Client, ClientSchema } from './schemas/client.schema';

@Module({
imports: [
MongooseModule.forFeature([
{
name: Client.name,
schema: ClientSchema,
},
]),
OryFrontendModule.forRootAsync({
inject: [ConfigService],
useFactory: (
configService: ConfigService<EnvironmentVariables, true>,
) => ({
basePath: configService.get('ORY_KRATOS_PUBLIC_URL'),
}),
}),
OryOAuth2Module.forRootAsync({
inject: [ConfigService],
useFactory: (
configService: ConfigService<EnvironmentVariables, true>,
) => ({
basePath: configService.get('ORY_HYDRA_ADMIN_URL'),
accessToken: configService.get('ORY_HYDRA_API_KEY'),
}),
}),
],
controllers: [ClientsController],
providers: [ClientsService],
})
export class ClientsModule {}
Loading

0 comments on commit ad67709

Please sign in to comment.