Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement oauth2 client credentials flow to access api #35

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8d979ed
chore: update Hydra config to send webhooks
getlarge Apr 9, 2024
daa0221
chore: update Ory wrappers
getlarge Apr 9, 2024
7552efb
feat(shared-models): create client model
getlarge Apr 9, 2024
7d2789a
refactor(microservices-shared-guards): move Ory guards mixin in share…
getlarge Apr 9, 2024
96fdd61
feat(auth): create models for Clients module
getlarge Apr 9, 2024
123bcc1
feat(auth): create initial ClientsModule
getlarge Apr 9, 2024
081d3a3
chore: regenerate OpenAPI specs
getlarge Apr 9, 2024
e513a30
chore: replace internal Ory CLIs
getlarge Apr 9, 2024
0fb77dd
chore: add nginx endpoints
getlarge Apr 9, 2024
0d1ec62
fix(auth): refine OAuth2 client creation
getlarge Apr 9, 2024
f27aade
fix(auth): update OryOAuth2WebhookPayload validators
getlarge Apr 9, 2024
6cf0585
chore: update Nestjs Ory libs
getlarge Apr 15, 2024
2aee923
feat(tickets): enable client to access Tickets API
getlarge Apr 15, 2024
3d9136b
refactor(microservices-shared-events): create separate event data cla…
getlarge Apr 15, 2024
4b3e228
fix(auth): create proper Ory exceptions in webhook responses
getlarge Apr 15, 2024
7bf9358
fix(tickets): move combined guards in providers to use `OrGuard`
getlarge Apr 15, 2024
b52964c
refactor: use classes explictly to enable validation pipe transformation
getlarge Apr 15, 2024
c38650d
feat(microservices-shared-filters): create message ack interceptor
getlarge Apr 15, 2024
e6134f9
fix(microservices-shared-filters): correclty handle ErrorResponse
getlarge Apr 15, 2024
e5d9811
refactor: integrate MessageAckInterceptor and GlobalErrorFilter in RM…
getlarge Apr 15, 2024
04c5d04
fix(tickets): correctly handle ticket creation failures
getlarge Apr 15, 2024
9b53ffa
test: remove obsolete assertions
getlarge Apr 15, 2024
aa72877
test: remove obsolete assertions
getlarge Apr 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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