@nestia/sdk 1.4.12 → 1.4.13-dev.20230726

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/lib/INestiaConfig.d.ts +6 -15
  2. package/lib/analyses/ControllerAnalyzer.js +21 -2
  3. package/lib/analyses/ControllerAnalyzer.js.map +1 -1
  4. package/lib/analyses/ReflectAnalyzer.d.ts +1 -0
  5. package/lib/analyses/ReflectAnalyzer.js +8 -0
  6. package/lib/analyses/ReflectAnalyzer.js.map +1 -1
  7. package/lib/analyses/SecurityAnalyzer.d.ts +3 -0
  8. package/lib/analyses/SecurityAnalyzer.js +25 -0
  9. package/lib/analyses/SecurityAnalyzer.js.map +1 -0
  10. package/lib/executable/internal/NestiaSdkConfig.js +67 -67
  11. package/lib/executable/internal/NestiaSdkConfig.js.map +1 -1
  12. package/lib/generates/SwaggerGenerator.js +42 -0
  13. package/lib/generates/SwaggerGenerator.js.map +1 -1
  14. package/lib/structures/IController.d.ts +2 -0
  15. package/lib/structures/IRoute.d.ts +1 -0
  16. package/lib/structures/ISwaggerComponents.d.ts +26 -0
  17. package/lib/structures/ISwaggerComponents.js +3 -0
  18. package/lib/structures/ISwaggerComponents.js.map +1 -0
  19. package/lib/structures/ISwaggerDocument.d.ts +70 -86
  20. package/lib/structures/ISwaggerRoute.d.ts +43 -0
  21. package/lib/structures/ISwaggerRoute.js +3 -0
  22. package/lib/structures/ISwaggerRoute.js.map +1 -0
  23. package/lib/structures/ISwaggerSecurityScheme.d.ts +50 -0
  24. package/lib/structures/ISwaggerSecurityScheme.js +3 -0
  25. package/lib/structures/ISwaggerSecurityScheme.js.map +1 -0
  26. package/package.json +4 -3
  27. package/src/INestiaConfig.ts +6 -22
  28. package/src/analyses/ControllerAnalyzer.ts +24 -1
  29. package/src/analyses/ReflectAnalyzer.ts +10 -2
  30. package/src/analyses/SecurityAnalyzer.ts +20 -0
  31. package/src/generates/SwaggerGenerator.ts +90 -10
  32. package/src/structures/IController.ts +2 -0
  33. package/src/structures/IRoute.ts +1 -0
  34. package/src/structures/ISwaggerComponents.ts +29 -0
  35. package/src/structures/ISwaggerDocument.ts +77 -105
  36. package/src/structures/ISwaggerRoute.ts +47 -0
  37. package/src/structures/ISwaggerSecurityScheme.ts +57 -0
@@ -14,6 +14,8 @@ import { ApplicationProgrammer } from "typia/lib/programmers/ApplicationProgramm
14
14
  import { INestiaConfig } from "../INestiaConfig";
15
15
  import { IRoute } from "../structures/IRoute";
16
16
  import { ISwaggerDocument } from "../structures/ISwaggerDocument";
17
+ import { ISwaggerRoute } from "../structures/ISwaggerRoute";
18
+ import { ISwaggerSecurityScheme } from "../structures/ISwaggerSecurityScheme";
17
19
  import { FileRetriever } from "../utils/FileRetriever";
18
20
  import { MapUtil } from "../utils/MapUtil";
19
21
 
@@ -24,6 +26,9 @@ export namespace SwaggerGenerator {
24
26
  async (routeList: IRoute[]): Promise<void> => {
25
27
  console.log("Generating Swagger Documents");
26
28
 
29
+ // VALIDATE SECURITY
30
+ validate_security(config)(routeList);
31
+
27
32
  // PREPARE ASSETS
28
33
  const parsed: NodePath.ParsedPath = NodePath.parse(config.output);
29
34
  const directory: string = NodePath.dirname(parsed.dir);
@@ -50,12 +55,15 @@ export namespace SwaggerGenerator {
50
55
  // CONSTRUCT SWAGGER DOCUMENTS
51
56
  const tupleList: Array<ISchemaTuple> = [];
52
57
  const swagger: ISwaggerDocument = await initialize(config);
53
- const pathDict: Map<string, ISwaggerDocument.IPath> = new Map();
58
+ const pathDict: Map<
59
+ string,
60
+ Record<string, ISwaggerRoute>
61
+ > = new Map();
54
62
 
55
63
  for (const route of routeList) {
56
64
  if (route.tags.find((tag) => tag.name === "internal")) continue;
57
65
 
58
- const path: ISwaggerDocument.IPath = MapUtil.take(
66
+ const path: Record<string, ISwaggerRoute> = MapUtil.take(
59
67
  pathDict,
60
68
  get_path(route.path, route.parameters),
61
69
  () => ({}),
@@ -95,6 +103,76 @@ export namespace SwaggerGenerator {
95
103
  );
96
104
  };
97
105
 
106
+ const validate_security =
107
+ (config: INestiaConfig.ISwaggerConfig) =>
108
+ (routeList: IRoute[]): void | never => {
109
+ const securityMap: Map<
110
+ string,
111
+ { scheme: ISwaggerSecurityScheme; scopes: Set<string> }
112
+ > = new Map();
113
+ for (const [key, value] of Object.entries(config.security ?? {}))
114
+ securityMap.set(key, {
115
+ scheme: emend_security(value),
116
+ scopes:
117
+ value.type === "oauth2"
118
+ ? new Set([
119
+ ...Object.keys(
120
+ value.flows.authorizationCode?.scopes ??
121
+ {},
122
+ ),
123
+ ...Object.keys(
124
+ value.flows.implicit?.scopes ?? {},
125
+ ),
126
+ ...Object.keys(
127
+ value.flows.password?.scopes ?? {},
128
+ ),
129
+ ...Object.keys(
130
+ value.flows.clientCredentials?.scopes ??
131
+ {},
132
+ ),
133
+ ])
134
+ : new Set(),
135
+ });
136
+
137
+ const validate =
138
+ (reporter: (str: string) => void) =>
139
+ (key: string, scopes: string[]) => {
140
+ const security = securityMap.get(key);
141
+ if (security === undefined)
142
+ return reporter(
143
+ `target security "${key}" does not exists.`,
144
+ );
145
+ else if (scopes.length === 0) return;
146
+ else if (security.scheme.type !== "oauth2")
147
+ return reporter(
148
+ `target security "${key}" is not "oauth2" type, but you've configured the scopes.`,
149
+ );
150
+ for (const s of scopes)
151
+ if (security.scopes.has(s) === false)
152
+ reporter(
153
+ `target security ${key} does not have scope "${s}".`,
154
+ );
155
+ };
156
+
157
+ const violations: string[] = [];
158
+ for (const route of routeList)
159
+ for (const record of route.security)
160
+ for (const [key, scopes] of Object.entries(record))
161
+ validate((str) =>
162
+ violations.push(
163
+ ` - ${str} (${route.symbol} at "${route.location}")`,
164
+ ),
165
+ )(key, scopes);
166
+
167
+ if (violations.length)
168
+ throw new Error(
169
+ `Error on NestiaApplication.swagger(): invalid security configuration.\n` +
170
+ `\n` +
171
+ `List of violations:\n` +
172
+ violations.join("\n"),
173
+ );
174
+ };
175
+
98
176
  /* ---------------------------------------------------------
99
177
  INITIALIZERS
100
178
  --------------------------------------------------------- */
@@ -170,7 +248,7 @@ export namespace SwaggerGenerator {
170
248
  collection: MetadataCollection,
171
249
  tupleList: Array<ISchemaTuple>,
172
250
  route: IRoute,
173
- ): ISwaggerDocument.IRoute {
251
+ ): ISwaggerRoute {
174
252
  const bodyParam = route.parameters.find(
175
253
  (param) => param.category === "body",
176
254
  );
@@ -238,6 +316,7 @@ export namespace SwaggerGenerator {
238
316
  ),
239
317
  summary,
240
318
  description,
319
+ security: route.security.length ? route.security : undefined,
241
320
  "x-nestia-namespace": [
242
321
  ...route.path
243
322
  .split("/")
@@ -245,6 +324,7 @@ export namespace SwaggerGenerator {
245
324
  route.name,
246
325
  ].join("."),
247
326
  "x-nestia-jsDocTags": route.tags,
327
+ "x-nestia-method": route.method,
248
328
  };
249
329
  }
250
330
 
@@ -262,8 +342,8 @@ export namespace SwaggerGenerator {
262
342
  }
263
343
 
264
344
  function emend_security(
265
- input: INestiaConfig.ISwaggerConfig.ISecurityScheme,
266
- ): ISwaggerDocument.ISecurityScheme {
345
+ input: ISwaggerSecurityScheme,
346
+ ): ISwaggerSecurityScheme {
267
347
  if (input.type === "apiKey")
268
348
  return {
269
349
  ...input,
@@ -282,7 +362,7 @@ export namespace SwaggerGenerator {
282
362
  tupleList: Array<ISchemaTuple>,
283
363
  route: IRoute,
284
364
  parameter: IRoute.IParameter,
285
- ): ISwaggerDocument.IParameter {
365
+ ): ISwaggerRoute.IParameter {
286
366
  const schema: IJsonSchema | null = generate_schema(
287
367
  checker,
288
368
  collection,
@@ -322,7 +402,7 @@ export namespace SwaggerGenerator {
322
402
  tupleList: Array<ISchemaTuple>,
323
403
  route: IRoute,
324
404
  parameter: IRoute.IParameter,
325
- ): ISwaggerDocument.IRequestBody {
405
+ ): ISwaggerRoute.IRequestBody {
326
406
  const schema: IJsonSchema | null = generate_schema(
327
407
  checker,
328
408
  collection,
@@ -362,7 +442,7 @@ export namespace SwaggerGenerator {
362
442
  collection: MetadataCollection,
363
443
  tupleList: Array<ISchemaTuple>,
364
444
  route: IRoute,
365
- ): ISwaggerDocument.IResponseBody {
445
+ ): ISwaggerRoute.IResponseBody {
366
446
  // OUTPUT WITH SUCCESS STATUS
367
447
  const status: string =
368
448
  route.status !== undefined
@@ -376,7 +456,7 @@ export namespace SwaggerGenerator {
376
456
  tupleList,
377
457
  route.output.type,
378
458
  );
379
- const success: ISwaggerDocument.IResponseBody = {
459
+ const success: ISwaggerRoute.IResponseBody = {
380
460
  [status]: {
381
461
  description:
382
462
  warning.get(route.encrypted).get("response", route.method) +
@@ -396,7 +476,7 @@ export namespace SwaggerGenerator {
396
476
  };
397
477
 
398
478
  // EXCEPTION STATUSES
399
- const exceptions: ISwaggerDocument.IResponseBody = Object.fromEntries(
479
+ const exceptions: ISwaggerRoute.IResponseBody = Object.fromEntries(
400
480
  route.tags
401
481
  .filter(
402
482
  (tag) =>
@@ -5,6 +5,7 @@ export interface IController {
5
5
  name: string;
6
6
  paths: string[];
7
7
  functions: IController.IFunction[];
8
+ security: Record<string, string[]>[];
8
9
  }
9
10
 
10
11
  export namespace IController {
@@ -17,6 +18,7 @@ export namespace IController {
17
18
  status?: number;
18
19
  type?: string;
19
20
  contentType: "application/json" | "text/plain";
21
+ security: Record<string, string[]>[];
20
22
  }
21
23
 
22
24
  export type IParameter =
@@ -23,6 +23,7 @@ export interface IRoute {
23
23
  | { type: "setter"; source: string; target?: string }
24
24
  | { type: "assigner"; source: string }
25
25
  >;
26
+ security: Record<string, string[]>[];
26
27
  }
27
28
 
28
29
  export namespace IRoute {
@@ -0,0 +1,29 @@
1
+ import { IJsonComponents } from "typia";
2
+
3
+ import { ISwaggerSecurityScheme } from "./ISwaggerSecurityScheme";
4
+
5
+ /**
6
+ * Reusable components in Swagger.
7
+ *
8
+ * `ISwaggerComponents` is a data structure representing content of `components` object
9
+ * in `swagger.json` file generated by Nestia. Note that, this is not an universal
10
+ * structure, but a dedicated structure only for Nestia.
11
+ *
12
+ * @author Jeongho Nam - https://github.com/samchon
13
+ */
14
+ export interface ISwaggerComponents {
15
+ /**
16
+ * An object to hold reusable DTO schemas.
17
+ *
18
+ * For reference, `nestia` stores every object and alias types as reusable DTO
19
+ * schemas. The alias type means that defined by `type` keyword in TypeScript.
20
+ */
21
+ schemas?: Record<string, IJsonComponents.IObject | IJsonComponents.IAlias>;
22
+
23
+ /**
24
+ * An object to hold reusable security schemes.
25
+ *
26
+ * This property be configured by user in `nestia.config.ts` file.
27
+ */
28
+ securitySchemes?: Record<string, ISwaggerSecurityScheme>;
29
+ }
@@ -1,124 +1,96 @@
1
- import { IJsonComponents, IJsonSchema } from "typia";
2
- import { IJsDocTagInfo } from "typia/lib/metadata/IJsDocTagInfo";
1
+ import { ISwaggerComponents } from "./ISwaggerComponents";
2
+ import { ISwaggerRoute } from "./ISwaggerRoute";
3
3
 
4
+ /**
5
+ * Swagger Document.
6
+ *
7
+ * `ISwaggerDocument` is a data structure representing content of `swagger.json` file
8
+ * generated by Nestia. Note that, this is not an universal structure, but a dedicated
9
+ * structure only for Nestia.
10
+ *
11
+ * @author Jeongho Nam - https://github.com/samchon
12
+ */
4
13
  export interface ISwaggerDocument {
5
- openapi: "3.0.1";
14
+ /**
15
+ * The version of the OpenAPI document.
16
+ *
17
+ * Nestia always generate OpenAPI 3.0.x document.
18
+ */
19
+ openapi: `3.0.${number}`;
20
+
21
+ /**
22
+ * List of servers that provide the API.
23
+ */
6
24
  servers: ISwaggerDocument.IServer[];
25
+
26
+ /**
27
+ * Information about the API.
28
+ */
7
29
  info: ISwaggerDocument.IInfo;
8
- components: ISwaggerDocument.IComponents;
30
+
31
+ /**
32
+ * The available paths and operations for the API.
33
+ *
34
+ * The 1st key is the path, and the 2nd key is the HTTP method.
35
+ */
36
+ paths: Record<string, Record<string, ISwaggerRoute>>;
37
+
38
+ /**
39
+ * An object to hold reusable data structures.
40
+ *
41
+ * It stores both DTO schemas and security schemes.
42
+ *
43
+ * For reference, `nestia` defines every object and alias types as reusable DTO
44
+ * schemas. The alias type means that defined by `type` keyword in TypeScript.
45
+ */
46
+ components: ISwaggerComponents;
47
+
48
+ /**
49
+ * A declaration of which security mechanisms can be used across the API.
50
+ *
51
+ * When this property be configured, it would be overwritten in every API routes.
52
+ *
53
+ * For reference, key means the name of security scheme and value means the `scopes`.
54
+ * The `scopes` can be used only when target security scheme is `oauth2` type,
55
+ * especially for {@link ISwaggerSecurityScheme.IOAuth2.IFlow.scopes} property.
56
+ */
9
57
  security?: Record<string, string[]>[];
10
- paths: Record<string, ISwaggerDocument.IPath>;
11
58
  }
12
59
  export namespace ISwaggerDocument {
60
+ /**
61
+ * Remote server definition.
62
+ */
13
63
  export interface IServer {
64
+ /**
65
+ * A URL to the target host.
66
+ *
67
+ * @format url
68
+ */
14
69
  url: string;
70
+
71
+ /**
72
+ * An optional string describing the target server.
73
+ */
15
74
  description?: string;
16
75
  }
17
76
 
77
+ /**
78
+ * General information about the API.
79
+ */
18
80
  export interface IInfo {
81
+ /**
82
+ * Version of the API.
83
+ */
19
84
  version: string;
20
- title: string;
21
- description?: string;
22
- }
23
85
 
24
- export interface IComponents extends IJsonComponents {
25
- securitySchemes?: Record<string, ISecurityScheme>;
26
- }
27
-
28
- /* ---------------------------------------------------------
29
- SECURITY SCHEMES
30
- --------------------------------------------------------- */
31
- export type ISecurityScheme =
32
- | ISecurityScheme.IHttpBasic
33
- | ISecurityScheme.IHttpBearer
34
- | ISecurityScheme.IApiKey
35
- | ISecurityScheme.IOpenId
36
- | ISecurityScheme.IOAuth2;
37
- export namespace ISecurityScheme {
38
- export interface IHttpBasic {
39
- type: "http";
40
- schema: "basic";
41
- }
42
- export interface IHttpBearer {
43
- type: "http";
44
- scheme: "bearer";
45
- bearerFormat?: string;
46
- }
47
- export interface IApiKey {
48
- type: "apiKey";
49
- in: "header" | "query" | "cookie";
50
- name: string;
51
- }
52
-
53
- export interface IOpenId {
54
- type: "openIdConnect";
55
- openIdConnectUrl: string;
56
- }
57
-
58
- export interface IOAuth2 {
59
- type: "oauth2";
60
- flows: IOAuth2.IFlowSet;
61
- description?: string;
62
- }
63
- export namespace IOAuth2 {
64
- export interface IFlowSet {
65
- authorizationCode?: IFlow;
66
- implicit?: Omit<IFlow, "tokenUrl">;
67
- password?: Omit<IFlow, "authorizationUrl">;
68
- clientCredentials?: Omit<IFlow, "authorizationUrl">;
69
- }
70
- export interface IFlow {
71
- authorizationUrl: string;
72
- tokenUrl: string;
73
- refreshUrl: string;
74
- scopes?: Record<string, string>;
75
- }
76
- }
77
- }
86
+ /**
87
+ * The title of the API.
88
+ */
89
+ title: string;
78
90
 
79
- /* ---------------------------------------------------------
80
- ROUTE FUNCTIONS
81
- --------------------------------------------------------- */
82
- export type IPath = Record<string, IRoute>;
83
- export interface IRoute {
84
- deprecated?: boolean;
85
- tags: string[];
86
- parameters: IParameter[];
87
- requestBody?: IRequestBody;
88
- responses: IResponseBody;
89
- summary?: string;
91
+ /**
92
+ * A short description of the API.
93
+ */
90
94
  description?: string;
91
- "x-nestia-namespace": string;
92
- "x-nestia-jsDocTags": IJsDocTagInfo[];
93
- }
94
-
95
- export interface IParameter {
96
- name: string;
97
- in: string;
98
- schema: IJsonSchema;
99
- required: boolean;
100
- description: string;
101
- }
102
- export interface IRequestBody {
103
- description: string;
104
- content: IJsonContent;
105
- required: true;
106
- "x-nestia-encrypted": boolean;
107
- }
108
- export type IResponseBody = Record<
109
- string,
110
- {
111
- description: string;
112
- content?: IJsonContent;
113
- "x-nestia-encrypted"?: boolean;
114
- }
115
- >;
116
- export interface IJsonContent {
117
- "application/json"?: {
118
- schema: IJsonSchema;
119
- };
120
- "text/plain"?: {
121
- schema: IJsonSchema;
122
- };
123
95
  }
124
96
  }
@@ -0,0 +1,47 @@
1
+ import { IJsonSchema } from "typia";
2
+ import { IJsDocTagInfo } from "typia/lib/metadata/IJsDocTagInfo";
3
+
4
+ export interface ISwaggerRoute {
5
+ deprecated?: boolean;
6
+ security?: Record<string, string[]>[];
7
+ tags: string[];
8
+ parameters: ISwaggerRoute.IParameter[];
9
+ requestBody?: ISwaggerRoute.IRequestBody;
10
+ responses: ISwaggerRoute.IResponseBody;
11
+ summary?: string;
12
+ description?: string;
13
+ "x-nestia-method": string;
14
+ "x-nestia-namespace": string;
15
+ "x-nestia-jsDocTags": IJsDocTagInfo[];
16
+ }
17
+ export namespace ISwaggerRoute {
18
+ export interface IParameter {
19
+ name: string;
20
+ in: string;
21
+ schema: IJsonSchema;
22
+ required: boolean;
23
+ description: string;
24
+ }
25
+ export interface IRequestBody {
26
+ description: string;
27
+ content: IContent;
28
+ required: true;
29
+ "x-nestia-encrypted": boolean;
30
+ }
31
+ export type IResponseBody = Record<
32
+ string,
33
+ {
34
+ description: string;
35
+ content?: IContent;
36
+ "x-nestia-encrypted"?: boolean;
37
+ }
38
+ >;
39
+ export interface IContent {
40
+ "application/json"?: {
41
+ schema: IJsonSchema;
42
+ };
43
+ "text/plain"?: {
44
+ schema: IJsonSchema;
45
+ };
46
+ }
47
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Security schema of Swagger Documents.
3
+ *
4
+ * `ISwaggerSecurityScheme` is a data structure representing content of
5
+ * `securitySchemes` in `swagger.json` file. It is composed with 5 types of security
6
+ * schemes as an union type like below.
7
+ *
8
+ * @reference https://swagger.io/specification/#security-scheme-object
9
+ * @author Jeongho Nam - https://github.com/samchon
10
+ */
11
+ export type ISwaggerSecurityScheme =
12
+ | ISwaggerSecurityScheme.IHttpBasic
13
+ | ISwaggerSecurityScheme.IHttpBearer
14
+ | ISwaggerSecurityScheme.IApiKey
15
+ | ISwaggerSecurityScheme.IOpenId
16
+ | ISwaggerSecurityScheme.IOAuth2;
17
+ export namespace ISwaggerSecurityScheme {
18
+ export interface IHttpBasic {
19
+ type: "http";
20
+ schema: "basic";
21
+ }
22
+ export interface IHttpBearer {
23
+ type: "http";
24
+ scheme: "bearer";
25
+ bearerFormat?: string;
26
+ }
27
+ export interface IApiKey {
28
+ type: "apiKey";
29
+ in?: "header" | "query" | "cookie";
30
+ name?: string;
31
+ }
32
+
33
+ export interface IOpenId {
34
+ type: "openIdConnect";
35
+ openIdConnectUrl: string;
36
+ }
37
+
38
+ export interface IOAuth2 {
39
+ type: "oauth2";
40
+ flows: IOAuth2.IFlowSet;
41
+ description?: string;
42
+ }
43
+ export namespace IOAuth2 {
44
+ export interface IFlowSet {
45
+ authorizationCode?: IFlow;
46
+ implicit?: Omit<IFlow, "tokenUrl">;
47
+ password?: Omit<IFlow, "authorizationUrl">;
48
+ clientCredentials?: Omit<IFlow, "authorizationUrl">;
49
+ }
50
+ export interface IFlow {
51
+ authorizationUrl: string;
52
+ tokenUrl: string;
53
+ refreshUrl: string;
54
+ scopes?: Record<string, string>;
55
+ }
56
+ }
57
+ }