Skip to content

Commit f1937e6

Browse files
authored
Merge pull request #369 from red-kite-solutions/feature/openapi-specs
Feature/openapi specs
2 parents 0c5a453 + 8795f87 commit f1937e6

85 files changed

Lines changed: 3066 additions & 812 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

devspace.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,8 @@ deployments:
362362
repo: https://charts.bitnami.com/bitnami
363363
version: "13.9.1"
364364
values:
365+
image:
366+
repository: bitnamilegacy/mongodb
365367
architecture: replicaset
366368
nodeSelector: $(echo ${MONGO_K8S_NODE_SELECTOR})
367369
replicaCount: ${MONGO_REPLICA_SET_COUNT}
@@ -384,6 +386,10 @@ deployments:
384386
caKey: ${MONGO_CA_KEY}
385387
mTls:
386388
enabled: true
389+
image:
390+
registry: docker.io
391+
repository: bitnamilegacy/nginx
392+
387393
service:
388394
ports:
389395
mongodb: 27017
@@ -443,6 +449,8 @@ deployments:
443449
- --timeout
444450
- 1200s
445451
values:
452+
image:
453+
repository: bitnamilegacy/kafka
446454
controller:
447455
nodeSelector: $(echo ${KAFKA_K8S_NODE_SELECTOR})
448456
persistence:
@@ -688,6 +696,9 @@ dev:
688696
labelSelector:
689697
app.kubernetes.io/instance: kafka
690698

699+
# For devspace sync no space left on device error, if there is still space left
700+
# on your device, increase Linux inotify count
701+
# https://dev.to/rubiin/ubuntu-increase-inotify-watcher-file-watch-limit-kf4
691702
jobs-manager:
692703
labelSelector:
693704
app.kubernetes.io/component: jobs-manager

docs/docs/development/api.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,31 @@
11
# API
22

3-
Every frontend-available data and more is accessible through the API. Simply create your API key in your profile, and then add it as a header when querying the API.
3+
Every frontend-available data and more is accessible through the API. Simply create your API key in your profile, and then add it as a
4+
header when querying the API.
45

5-
The API is available at `/api/`. You can do an unauthenticated request at `/api/ping` that replies a simple string, and an authenticated request at `/api/` that gets the version.
6+
The API is available at `/api/`. You can do an unauthenticated request at `/api/ping` that replies a simple string, and an authenticated
7+
request at `/api/` that gets the version.
68

79
Unauthenticated `GET` request to `/api/ping`:
810

911
```bash
1012
curl https://your-red-kite-url/api/ping
1113
```
1214

13-
# API Key
15+
## API Key
1416

15-
Generate your API key in your profile page, giving it a meaningful name and an expiration date. Then, use it as a header in your following requests.
17+
Generate your API key in your profile page, giving it a meaningful name and an expiration date. Then, use it as a header in your following
18+
requests.
1619

1720
Authenticated `GET` request to `/api/`:
1821

1922
```bash
2023
export MY_KEY="my key value"
2124
curl -H "x-api-key: $MY_KEY" https://your-red-kite-url/api/
2225
```
26+
27+
## Open API Documentation
28+
29+
Your OpenAPI documentation is available in json format and through the swagger UI interface.
30+
31+
The swagger UI is available at `/api/docs/`, while the JSON format is available at `/api/docs-json`.

packages/backend/jobs-manager/service/nest-cli.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88
"include": "**/*.mjml",
99
"outDir": "dist/src"
1010
}
11+
],
12+
"plugins": [
13+
{
14+
"name": "@nestjs/swagger",
15+
"options": {
16+
"classValidatorShim": true,
17+
"introspectComments": true,
18+
"skipAutoHttpCode": true
19+
}
20+
}
1121
]
1222
}
1323
}

packages/backend/jobs-manager/service/package.json

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,18 @@
2424
"test:cov": "jest --coverage"
2525
},
2626
"dependencies": {
27-
"@nestjs/common": "^9.2.1",
28-
"@nestjs/core": "^9.2.1",
29-
"@nestjs/cqrs": "^9.0.1",
30-
"@nestjs/jwt": "^10.0.1",
31-
"@nestjs/microservices": "^9.2.1",
32-
"@nestjs/mongoose": "^9.2.2",
33-
"@nestjs/platform-express": "^9.2.1",
34-
"@nestjs/platform-socket.io": "^9.2.1",
35-
"@nestjs/schedule": "^2.1.0",
36-
"@nestjs/swagger": "^8.1.0",
37-
"@nestjs/websockets": "^9.2.1",
38-
"@typegoose/typegoose": "^10.3.3",
27+
"@nestjs/common": "^11.1.1",
28+
"@nestjs/core": "^11.1.1",
29+
"@nestjs/cqrs": "^11.0.3",
30+
"@nestjs/jwt": "^11.0.0",
31+
"@nestjs/microservices": "^11.1.1",
32+
"@nestjs/mongoose": "^11.0.3",
33+
"@nestjs/platform-express": "^11.1.1",
34+
"@nestjs/platform-socket.io": "^11.1.1",
35+
"@nestjs/schedule": "^6.0.0",
36+
"@nestjs/swagger": "^11.2.0",
37+
"@nestjs/websockets": "^11.1.1",
38+
"@typegoose/typegoose": "^12.16.0",
3939
"class-transformer": "^0.5.1",
4040
"class-validator": "^0.14.0",
4141
"cron-validator": "^1.3.1",
@@ -62,10 +62,10 @@
6262
"webpack": "^5.90.3"
6363
},
6464
"devDependencies": {
65-
"@nestjs/cli": "^9.1.9",
66-
"@nestjs/passport": "^9.0.0",
67-
"@nestjs/schematics": "^9.0.4",
68-
"@nestjs/testing": "^9.2.1",
65+
"@nestjs/cli": "^11.0.7",
66+
"@nestjs/passport": "^11.0.5",
67+
"@nestjs/schematics": "^11.0.5",
68+
"@nestjs/testing": "^11.1.1",
6969
"@types/express": "^4.17.16",
7070
"@types/jest": "^29.5.1",
7171
"@types/mjml": "^4",

packages/backend/jobs-manager/service/src/main.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import { NestApplicationOptions, ValidationPipe } from '@nestjs/common';
22
import { NestFactory } from '@nestjs/core';
33
import { ExpressAdapter } from '@nestjs/platform-express';
4+
import {
5+
DocumentBuilder,
6+
SwaggerDocumentOptions,
7+
SwaggerModule,
8+
} from '@nestjs/swagger';
49
import { useContainer } from 'class-validator';
510
import { json } from 'express';
611
import { readFileSync } from 'node:fs';
712
import { KeepAliveStrategy } from './keep-alive-strategy';
813
import { isConsumerMode } from './modules/app.constants';
914
import { AppModule } from './modules/app.module';
15+
import { DataSource } from './modules/database/data-source/data-source.model';
16+
import { JobParameter } from './modules/database/subscriptions/subscriptions.type';
17+
import { mapSecurityScopesToDescription } from './utils/swagger.utils';
1018

1119
async function bootstrap() {
1220
const adapter = new ExpressAdapter();
@@ -37,6 +45,7 @@ async function bootstrap() {
3745
await workerApp.listen();
3846
} else {
3947
const app = await NestFactory.create(AppModule, adapter, options);
48+
4049
app.enableCors({
4150
origin: process.env.RK_URL,
4251
});
@@ -48,6 +57,43 @@ async function bootstrap() {
4857
forbidNonWhitelisted: true,
4958
}),
5059
);
60+
61+
// OpenAPI Setup
62+
const config = new DocumentBuilder()
63+
.setTitle('Red Kite API')
64+
.setDescription('The Red Kite external attack surface management API.')
65+
.addApiKey(
66+
{
67+
type: 'apiKey',
68+
name: 'x-api-key',
69+
in: 'header',
70+
},
71+
'apiKey',
72+
)
73+
.addBearerAuth()
74+
.setVersion(process.env.RK_VERSION)
75+
.build();
76+
77+
const documentFactory = () => {
78+
const options: SwaggerDocumentOptions = {
79+
deepScanRoutes: true,
80+
extraModels: [JobParameter, DataSource],
81+
};
82+
const doc = SwaggerModule.createDocument(app, config, options);
83+
84+
// Setting up ObjectIds in the OpenAPI documentation to string
85+
doc.components = doc.components || {};
86+
doc.components.schemas = doc.components.schemas || {};
87+
doc.components.schemas.ObjectId = {
88+
type: 'string',
89+
example: '507f1f77bcf86cd799439011',
90+
description: 'MongoDB ObjectId represented as string',
91+
};
92+
93+
return mapSecurityScopesToDescription(doc);
94+
};
95+
SwaggerModule.setup('docs', app, documentFactory);
96+
5197
useContainer(app.select(AppModule), { fallbackOnErrors: true });
5298
await app.listen(3000);
5399
}
Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Controller, Get, UseGuards } from '@nestjs/common';
22
import { AuthGuard } from '@nestjs/passport';
3+
import { ApiDefaultResponse } from '@nestjs/swagger';
34
import { AppService } from './app.service';
4-
import { Role } from './auth/constants';
55
import { Scopes } from './auth/decorators/scopes.decorator';
66
import { ScopesGuard } from './auth/guards/scope.guard';
77
import { ApiKeyStrategy } from './auth/strategies/api-key.strategy';
@@ -11,15 +11,29 @@ import { JwtStrategy } from './auth/strategies/jwt.strategy';
1111
export class AppController {
1212
constructor(private readonly appService: AppService) {}
1313

14+
/**
15+
* Get the application version
16+
*
17+
* @remarks
18+
* Get the application version from the `RK_VERSION` environment variable.
19+
*/
1420
@Scopes('manage:health:version')
1521
@UseGuards(AuthGuard([JwtStrategy.name, ApiKeyStrategy.name]), ScopesGuard)
1622
@Get()
1723
getVersion(): string {
1824
return this.appService.getVersion();
1925
}
2026

27+
/**
28+
* Application health check
29+
*
30+
* @remarks
31+
* Know if the application is running. This call is unauthenticated.
32+
*
33+
*/
34+
@ApiDefaultResponse({ example: 'pong' })
2135
@Get('ping')
22-
ping(): string {
36+
ping() {
2337
return 'pong';
2438
}
2539
}

packages/backend/jobs-manager/service/src/modules/auth/auth.controller.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,36 @@ interface RequestWithUser extends Request {
2424
export class AuthController {
2525
constructor(private readonly authService: AuthService) {}
2626

27+
/**
28+
* Log in with credentials and get an access token.
29+
*
30+
* @remarks
31+
* Log in to the API with your local credentials. After logging in, an access token will be issued.
32+
*/
2733
@UseGuards(LocalAuthGuard)
2834
@Post('login')
2935
async login(@Request() req) {
3036
return await this.loginCore(req);
3137
}
3238

39+
/**
40+
* Log in with unique link.
41+
*
42+
* @remarks
43+
* Log in to the API with your unique link. Used to reset your password.
44+
*/
3345
@UseGuards(MagicLinkAuthGuard)
3446
@Post('login-magic-link')
3547
async loginMagicLink(@Request() req: RequestWithUser) {
3648
return await this.loginCore(req);
3749
}
3850

51+
/**
52+
* Refresh your access token with your refresh token.
53+
*
54+
* @remarks
55+
* Use this call to get a new access token. This
56+
*/
3957
@UseGuards(JwtRefreshGuard)
4058
@Put('refresh')
4159
async refresh(@Request() req: RequestWithUser) {
@@ -44,6 +62,12 @@ export class AuthController {
4462
return { access_token: accessToken };
4563
}
4664

65+
/**
66+
* Logout the current user.
67+
*
68+
* @remarks
69+
* Logout the current user, deleting their refresh token.
70+
*/
4771
@UseGuards(JwtAuthGuard)
4872
@Delete('logout')
4973
async logOut(@Request() request: RequestWithUser, @Body() dto: LogoutDto) {
@@ -58,8 +82,10 @@ export class AuthController {
5882
}
5983

6084
/**
61-
* This function is left without authorizations on purpose
62-
* It is used to anonymously know if the platform was properly initialized
85+
* Anonymously know if the platform was properly initialized.
86+
*
87+
* @remarks
88+
* Anonymous API call to check if the local authentication was setup.
6389
*/
6490
@Get('setup')
6591
async getIsSetup(): Promise<any> {

packages/backend/jobs-manager/service/src/modules/auth/auth.dto.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@ export class LogoutDto {
44
@IsOptional()
55
refresh_token: string;
66
}
7+
8+
export class LocalAuthDto {
9+
email: string;
10+
password: string;
11+
}

packages/backend/jobs-manager/service/src/modules/auth/decorators/scopes.decorator.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { SetMetadata } from '@nestjs/common';
1+
import { applyDecorators, SetMetadata } from '@nestjs/common';
2+
import { ApiSecurity } from '@nestjs/swagger';
23
import { ScopeActivationContext } from '../guards/scope-guard.utils';
34
import { ApiScope, ExtendedScope, ScopeOptions } from '../scopes.constants';
45

@@ -20,5 +21,9 @@ export const Scopes = (
2021
scopeOptions: options,
2122
};
2223

23-
return SetMetadata(SCOPE_INFO_KEY, scopeInfo);
24+
return applyDecorators(
25+
ApiSecurity('apiKey', scopeInfo.scopes),
26+
ApiSecurity('bearer', scopeInfo.scopes),
27+
SetMetadata(SCOPE_INFO_KEY, scopeInfo),
28+
);
2429
};
Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Injectable } from '@nestjs/common';
1+
import { Injectable, UnauthorizedException } from '@nestjs/common';
22
import { PassportStrategy } from '@nestjs/passport';
33
import { HeaderAPIKeyStrategy } from 'passport-headerapikey';
44
import { ApiKeyDocument } from '../../database/api-key/api-key.model';
@@ -11,23 +11,21 @@ export class ApiKeyStrategy extends PassportStrategy(
1111
'ApiKeyStrategy',
1212
) {
1313
constructor(private authService: AuthService) {
14-
super(
15-
{ header: 'x-api-key', prefix: '' },
16-
true,
17-
async (apikey, done, req) => {
18-
const apiKeyDocument: ApiKeyDocument =
19-
await authService.findValidApiKey(apikey);
14+
super({ header: 'x-api-key', prefix: '' }, false);
15+
}
16+
17+
async validate(apikey: string) {
18+
const apiKeyDocument: ApiKeyDocument =
19+
await this.authService.findValidApiKey(apikey);
2020

21-
if (!apiKeyDocument) return done(null, false);
21+
if (!apiKeyDocument) throw new UnauthorizedException();
2222

23-
const user: UserAuthContext = {
24-
id: apiKeyDocument.userId.toString(),
25-
apiKeyId: apiKeyDocument._id.toString(),
26-
scopes: apiKeyDocument.scopes,
27-
};
23+
const user: UserAuthContext = {
24+
id: apiKeyDocument.userId.toString(),
25+
apiKeyId: apiKeyDocument._id.toString(),
26+
scopes: apiKeyDocument.scopes,
27+
};
2828

29-
return done(null, user);
30-
},
31-
);
29+
return user;
3230
}
3331
}

0 commit comments

Comments
 (0)