Skip to content

Commit

Permalink
Add client id guard to auth request endpoints (#361)
Browse files Browse the repository at this point in the history
  • Loading branch information
Samuel authored Jun 26, 2024
1 parent 83a3606 commit 301486e
Show file tree
Hide file tree
Showing 19 changed files with 3,342 additions and 3,529 deletions.
2 changes: 1 addition & 1 deletion apps/armory/src/armory.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const REQUEST_HEADER_API_KEY = 'x-api-key'
//

export const ADMIN_SECURITY = adminApiKeySecurity(REQUEST_HEADER_API_KEY)
export const CLIENT_ID_SECURITY = clientIdSecurity(REQUEST_HEADER_CLIENT_SECRET)
export const CLIENT_ID_SECURITY = clientIdSecurity(REQUEST_HEADER_CLIENT_ID)
export const CLIENT_SECRET_SECURITY = clientSecretSecurity(REQUEST_HEADER_CLIENT_SECRET)

//
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Body, Controller, HttpStatus, Post } from '@nestjs/common'
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'
import { AdminGuard } from '../../../../shared/decorator/admin-guard.decorator'
import { ApiAdminGuard } from '../../../../shared/decorator/api-admin-guard.decorator'
import { ClientService } from '../../../core/service/client.service'
import { CreateClientRequestDto, CreateClientResponseDto } from '../dto/create-client.dto'

Expand All @@ -10,7 +10,7 @@ export class ClientController {
constructor(private clientService: ClientService) {}

@Post()
@AdminGuard()
@ApiAdminGuard()
@ApiOperation({
summary: 'Creates a new client'
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Criterion, EntityUtil, Then, UserRole } from '@narval/policy-engine-sha
import { Body, Controller, Get, HttpCode, HttpStatus, Post, Query, UseGuards } from '@nestjs/common'
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'
import { ClusterService } from '../../../../policy-engine/core/service/cluster.service'
import { ApiClientSecretGuard } from '../../../../shared/decorator/api-client-secret-guard.decorator'
import { ClientId } from '../../../../shared/decorator/client-id.decorator'
import { ClientSecretGuard } from '../../../../shared/guard/client-secret.guard'
import { EntityDataStoreService } from '../../../core/service/entity-data-store.service'
import { PolicyDataStoreService } from '../../../core/service/policy-data-store.service'
import { DataStoreGuard } from '../../../shared/guard/data-store.guard'
Expand All @@ -23,14 +23,14 @@ export class DataStoreController {
) {}

@Get('/entities')
@UseGuards(DataStoreGuard)
@ApiOperation({
summary: 'Gets the client entities'
})
@ApiResponse({
status: HttpStatus.OK,
type: EntityDataStoreDto
})
@UseGuards(DataStoreGuard)
async getEntities(@Query('clientId') clientId: string): Promise<EntityDataStoreDto> {
const entity = await this.entityDataStoreService.getEntities(clientId)

Expand All @@ -47,14 +47,14 @@ export class DataStoreController {
}

@Get('/policies')
@UseGuards(DataStoreGuard)
@ApiOperation({
summary: 'Gets the client policies'
})
@ApiResponse({
status: HttpStatus.OK,
type: PolicyDataStoreDto
})
@UseGuards(DataStoreGuard)
async getPolicies(@Query('clientId') clientId: string): Promise<PolicyDataStoreDto> {
const policy = await this.policyDataStoreService.getPolicies(clientId)

Expand Down Expand Up @@ -110,8 +110,8 @@ export class DataStoreController {
}

@Post('/sync')
@ApiClientSecretGuard()
@HttpCode(HttpStatus.OK)
@UseGuards(ClientSecretGuard)
@ApiOperation({
summary: 'Sync the client data store with the engine cluster'
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Body, Controller, Get, HttpStatus, NotFoundException, Param, Post } from '@nestjs/common'
import { ApiHeader, ApiOperation, ApiResponse, ApiSecurity, ApiTags } from '@nestjs/swagger'
import { REQUEST_HEADER_CLIENT_ID } from '../../../../../src/armory.constant'
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'
import { ApiClientIdGuard } from '../../../../shared/decorator/api-client-id-guard.decorator'
import { ClientId } from '../../../../shared/decorator/client-id.decorator'
import { ErrorResponseDto } from '../../../../shared/dto/error-response.dto'
import { AuthorizationRequestService } from '../../../core/service/authorization-request.service'
Expand All @@ -14,14 +14,10 @@ export class AuthorizationRequestController {
constructor(private authorizationRequestService: AuthorizationRequestService) {}

@Post('/')
@ApiSecurity('CLIENT_ID')
@ApiClientIdGuard()
@ApiOperation({
summary: 'Submits a new authorization request for evaluation by the policy engine'
})
@ApiHeader({
name: REQUEST_HEADER_CLIENT_ID,
required: true
})
@ApiResponse({
description: 'The authorization request has been successfully submitted for evaluation',
status: HttpStatus.CREATED,
Expand All @@ -37,6 +33,7 @@ export class AuthorizationRequestController {
}

@Get('/:id')
@ApiClientIdGuard()
@ApiOperation({
summary: 'Gets an authorization request by ID'
})
Expand All @@ -61,6 +58,7 @@ export class AuthorizationRequestController {
}

@Post('/:id/approvals')
@ApiClientIdGuard()
@ApiOperation({
summary: 'Approves an authorization request'
})
Expand Down
7 changes: 6 additions & 1 deletion apps/armory/src/orchestration/orchestration.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import { BullModule } from '@nestjs/bull'
import { Module } from '@nestjs/common'
import { load } from '../armory.config'
import { AUTHORIZATION_REQUEST_PROCESSING_QUEUE, DEFAULT_HTTP_MODULE_PROVIDERS } from '../armory.constant'
import { ClientModule } from '../client/client.module'
import { DataFeedModule } from '../data-feed/data-feed.module'
import { PolicyEngineModule } from '../policy-engine/policy-engine.module'
import { PriceModule } from '../price/price.module'
import { ClientIdGuard } from '../shared/guard/client-id.guard'
import { ClientSecretGuard } from '../shared/guard/client-secret.guard'
import { PersistenceModule } from '../shared/module/persistence/persistence.module'
import { TransferTrackingModule } from '../transfer-tracking/transfer-tracking.module'
import { AuthorizationRequestService } from './core/service/authorization-request.service'
Expand All @@ -34,13 +37,15 @@ const INFRASTRUCTURE_MODULES = [
})
]

const DOMAIN_MODULES = [TransferTrackingModule, PriceModule, DataFeedModule, PolicyEngineModule]
const DOMAIN_MODULES = [ClientModule, TransferTrackingModule, PriceModule, DataFeedModule, PolicyEngineModule]

@Module({
imports: [...INFRASTRUCTURE_MODULES, ...DOMAIN_MODULES],
controllers: [AuthorizationRequestController],
providers: [
...DEFAULT_HTTP_MODULE_PROVIDERS,
ClientIdGuard,
ClientSecretGuard,
AuthorizationRequestService,
AuthorizationRequestRepository,
AuthorizationRequestProcessingConsumer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ApiHeader, ApiSecurity } from '@nestjs/swagger'
import { ADMIN_SECURITY, REQUEST_HEADER_API_KEY } from '../../armory.constant'
import { AdminApiKeyGuard } from '../guard/admin-api-key.guard'

export function AdminGuard() {
export function ApiAdminGuard() {
return applyDecorators(
UseGuards(AdminApiKeyGuard),
ApiSecurity(ADMIN_SECURITY.name),
Expand Down
15 changes: 15 additions & 0 deletions apps/armory/src/shared/decorator/api-client-id-guard.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { UseGuards, applyDecorators } from '@nestjs/common'
import { ApiHeader, ApiSecurity } from '@nestjs/swagger'
import { CLIENT_ID_SECURITY, REQUEST_HEADER_CLIENT_ID } from '../../armory.constant'
import { ClientIdGuard } from '../guard/client-id.guard'

export function ApiClientIdGuard() {
return applyDecorators(
UseGuards(ClientIdGuard),
ApiSecurity(CLIENT_ID_SECURITY.name),
ApiHeader({
name: REQUEST_HEADER_CLIENT_ID,
required: true
})
)
}
13 changes: 0 additions & 13 deletions apps/armory/src/shared/decorator/api-client-id.decorator.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ import { UseGuards, applyDecorators } from '@nestjs/common'
import { ApiHeader, ApiSecurity } from '@nestjs/swagger'
import { CLIENT_SECRET_SECURITY, REQUEST_HEADER_CLIENT_SECRET } from '../../armory.constant'
import { ClientSecretGuard } from '../guard/client-secret.guard'
import { ApiClientId } from './api-client-id.decorator'

export function ClientGuard() {
export function ApiClientSecretGuard() {
return applyDecorators(
UseGuards(ClientSecretGuard),
ApiClientId(),
ApiSecurity(CLIENT_SECRET_SECURITY.name),
ApiHeader({
required: true,
Expand Down
33 changes: 33 additions & 0 deletions apps/armory/src/shared/guard/client-id.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { CanActivate, ExecutionContext, HttpStatus, Injectable } from '@nestjs/common'
import { REQUEST_HEADER_CLIENT_ID } from '../../armory.constant'
import { ClientService } from '../../client/core/service/client.service'
import { ApplicationException } from '../exception/application.exception'

@Injectable()
export class ClientIdGuard implements CanActivate {
constructor(private clientService: ClientService) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest()

const clientId = req.headers[REQUEST_HEADER_CLIENT_ID]

if (!clientId) {
throw new ApplicationException({
message: `Missing or invalid ${REQUEST_HEADER_CLIENT_ID} header`,
suggestedHttpStatusCode: HttpStatus.UNAUTHORIZED
})
}

const client = await this.clientService.findById(clientId)

if (!client) {
throw new ApplicationException({
message: `Client not found for ${REQUEST_HEADER_CLIENT_ID} header`,
suggestedHttpStatusCode: HttpStatus.NOT_FOUND
})
}

return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class AccountController {
@ApiOperation({
summary: 'Lists the client accounts'
})
@PermissionGuard(Permission.WALLET_READ)
@ApiResponse({
status: HttpStatus.CREATED,
type: AccountsDto
Expand Down
2 changes: 1 addition & 1 deletion packages/armory-sdk/src/lib/auth/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export class AuthClient {
* @returns A Promise that resolves to the retrieved AuthorizationResponseDto.
*/
async getAuthorizationById(id: string): Promise<AuthorizationResponseDto> {
const { data } = await this.authorizationHttp.getById(id)
const { data } = await this.authorizationHttp.getById(this.config.clientId, id)

return data
}
Expand Down
2 changes: 1 addition & 1 deletion packages/armory-sdk/src/lib/auth/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export type AuthorizationHttp = {
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getById(id: string, options?: RawAxiosRequestConfig): AxiosPromise<AuthorizationResponseDto>
getById(id: string, clientId: string, options?: RawAxiosRequestConfig): AxiosPromise<AuthorizationResponseDto>
}

export type AuthClientHttp = {
Expand Down
4 changes: 2 additions & 2 deletions packages/armory-sdk/src/lib/data-store/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export class EntityStoreClient {
async sync(): Promise<boolean> {
assert(this.config.clientSecret !== undefined, 'Missing clientSecret')

const { data } = await this.dataStoreHttp.sync({
const { data } = await this.dataStoreHttp.sync(this.config.clientSecret, {
headers: {
[REQUEST_HEADER_CLIENT_ID]: this.config.clientId,
[REQUEST_HEADER_CLIENT_SECRET]: this.config.clientSecret
Expand Down Expand Up @@ -205,7 +205,7 @@ export class PolicyStoreClient {
async sync(): Promise<boolean> {
assert(this.config.clientSecret !== undefined, 'Missing clientSecret')

const { data } = await this.dataStoreHttp.sync({
const { data } = await this.dataStoreHttp.sync(this.config.clientSecret, {
headers: {
[REQUEST_HEADER_CLIENT_ID]: this.config.clientId,
[REQUEST_HEADER_CLIENT_SECRET]: this.config.clientSecret
Expand Down
2 changes: 1 addition & 1 deletion packages/armory-sdk/src/lib/data-store/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,5 @@ export type DataStoreHttp = {
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
sync(options?: RawAxiosRequestConfig): AxiosPromise<SyncResponse>
sync(clientSecret: string, options?: RawAxiosRequestConfig): AxiosPromise<SyncResponse>
}
4 changes: 4 additions & 0 deletions packages/armory-sdk/src/lib/http/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ servers.
The generate targets uses `openapi-generator-cli` that depends on a running
server hosting the Swagger documentation.

It requires JAVA to be installed so make sure it's installed by running `java -version` in your terminal.

You can install it by running `brew install java` or by downloading it directly from [this website](https://www.oracle.com/java/technologies/downloads/#jdk22-mac).

```bash
# Start the server
make armory/start/dev
Expand Down
Loading

0 comments on commit 301486e

Please sign in to comment.