@lucaapp/service-utils 5.26.0 → 5.29.0
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.
- package/dist/lib/api/api.d.ts +1 -0
- package/dist/lib/api/api.js +12 -6
- package/dist/lib/api/index.d.ts +1 -0
- package/dist/lib/api/openapi.d.ts +1 -1
- package/dist/lib/api/openapi.js +25 -2
- package/dist/lib/api/security.d.ts +3 -0
- package/dist/lib/api/security.js +37 -0
- package/dist/lib/api/types/middleware.d.ts +2 -0
- package/package.json +3 -3
package/dist/lib/api/api.d.ts
CHANGED
package/dist/lib/api/api.js
CHANGED
|
@@ -10,6 +10,7 @@ const swagger_ui_express_1 = __importDefault(require("swagger-ui-express"));
|
|
|
10
10
|
const zod_to_openapi_1 = require("@asteasolutions/zod-to-openapi");
|
|
11
11
|
const endpoint_1 = require("./endpoint");
|
|
12
12
|
const openapi_1 = require("./openapi");
|
|
13
|
+
const security_1 = require("./security");
|
|
13
14
|
const body_parser_1 = __importDefault(require("body-parser"));
|
|
14
15
|
const rawBodySaver = function (req, res, buf, encoding) {
|
|
15
16
|
if (buf && buf.length) {
|
|
@@ -22,6 +23,7 @@ class Api {
|
|
|
22
23
|
this.router = options.router || (0, express_1.Router)();
|
|
23
24
|
this.registry = options.registry || new zod_to_openapi_1.OpenAPIRegistry();
|
|
24
25
|
this.prefixPath = options.prefixPath || '';
|
|
26
|
+
this.rootPrefixPath = this.prefixPath;
|
|
25
27
|
this.openApiVersion = options.openApiVersion || '3.0.0';
|
|
26
28
|
this.apiVersion = options.apiVersion || '1.0.0';
|
|
27
29
|
this.apiTitle = options.apiTitle || 'API';
|
|
@@ -31,33 +33,35 @@ class Api {
|
|
|
31
33
|
this.router.use(body_parser_1.default.json({ verify: rawBodySaver, limit: '2mb' }));
|
|
32
34
|
}
|
|
33
35
|
child(options) {
|
|
34
|
-
|
|
36
|
+
const child = new Api({
|
|
35
37
|
prefixPath: this.prefixPath + (options.prefixPath || ''),
|
|
36
38
|
debug: this.debug,
|
|
37
39
|
tags: [...this.tags, ...(options.tags || [])],
|
|
38
40
|
registry: this.registry,
|
|
39
41
|
router: this.router,
|
|
40
42
|
});
|
|
43
|
+
child.rootPrefixPath = this.rootPrefixPath;
|
|
44
|
+
return child;
|
|
41
45
|
}
|
|
42
46
|
get(path, summary, options, handler) {
|
|
43
47
|
(0, endpoint_1.mountEndpoint)(this.router, 'get', path, options, handler, this.debug);
|
|
44
|
-
(0, openapi_1.registerEndpoint)(this.registry, 'get', this.prefixPath, path, summary, options, this.tags, this.debug);
|
|
48
|
+
(0, openapi_1.registerEndpoint)(this.registry, 'get', this.prefixPath, this.rootPrefixPath, path, summary, options, this.tags, this.debug);
|
|
45
49
|
}
|
|
46
50
|
post(path, summary, options, handler) {
|
|
47
51
|
(0, endpoint_1.mountEndpoint)(this.router, 'post', path, options, handler, this.debug);
|
|
48
|
-
(0, openapi_1.registerEndpoint)(this.registry, 'post', this.prefixPath, path, summary, options, this.tags, this.debug);
|
|
52
|
+
(0, openapi_1.registerEndpoint)(this.registry, 'post', this.prefixPath, this.rootPrefixPath, path, summary, options, this.tags, this.debug);
|
|
49
53
|
}
|
|
50
54
|
patch(path, summary, options, handler) {
|
|
51
55
|
(0, endpoint_1.mountEndpoint)(this.router, 'patch', path, options, handler, this.debug);
|
|
52
|
-
(0, openapi_1.registerEndpoint)(this.registry, 'patch', this.prefixPath, path, summary, options, this.tags, this.debug);
|
|
56
|
+
(0, openapi_1.registerEndpoint)(this.registry, 'patch', this.prefixPath, this.rootPrefixPath, path, summary, options, this.tags, this.debug);
|
|
53
57
|
}
|
|
54
58
|
put(path, summary, options, handler) {
|
|
55
59
|
(0, endpoint_1.mountEndpoint)(this.router, 'put', path, options, handler, this.debug);
|
|
56
|
-
(0, openapi_1.registerEndpoint)(this.registry, 'put', this.prefixPath, path, summary, options, this.tags, this.debug);
|
|
60
|
+
(0, openapi_1.registerEndpoint)(this.registry, 'put', this.prefixPath, this.rootPrefixPath, path, summary, options, this.tags, this.debug);
|
|
57
61
|
}
|
|
58
62
|
delete(path, summary, options, handler) {
|
|
59
63
|
(0, endpoint_1.mountEndpoint)(this.router, 'delete', path, options, handler, this.debug);
|
|
60
|
-
(0, openapi_1.registerEndpoint)(this.registry, 'delete', this.prefixPath, path, summary, options, this.tags, this.debug);
|
|
64
|
+
(0, openapi_1.registerEndpoint)(this.registry, 'delete', this.prefixPath, this.rootPrefixPath, path, summary, options, this.tags, this.debug);
|
|
61
65
|
}
|
|
62
66
|
generateOpenAPISpec() {
|
|
63
67
|
let generator;
|
|
@@ -74,9 +78,11 @@ class Api {
|
|
|
74
78
|
title: this.apiTitle,
|
|
75
79
|
description: this.apiDescription,
|
|
76
80
|
},
|
|
81
|
+
servers: [{ url: '/' }],
|
|
77
82
|
});
|
|
78
83
|
(0, openapi_1.forcePathParamsRequired)(spec);
|
|
79
84
|
(0, openapi_1.hoistCombinatorRequiredFields)(spec);
|
|
85
|
+
spec.components = { ...spec.components, securitySchemes: security_1.SECURITY_SCHEMES };
|
|
80
86
|
return spec;
|
|
81
87
|
}
|
|
82
88
|
mountSwaggerMiddlewares() {
|
package/dist/lib/api/index.d.ts
CHANGED
|
@@ -25,5 +25,5 @@ export declare const hoistCombinatorRequiredFields: (spec: {
|
|
|
25
25
|
schemas?: Record<string, unknown>;
|
|
26
26
|
};
|
|
27
27
|
}) => void;
|
|
28
|
-
export declare const registerEndpoint: (registry: OpenAPIRegistry, method: HttpMethod, prefixPath: string, path: string, summary: string, options: EndpointOptions<any, any, any, any, any, any>, tags: string[], debug: boolean) => void;
|
|
28
|
+
export declare const registerEndpoint: (registry: OpenAPIRegistry, method: HttpMethod, prefixPath: string, rootPrefixPath: string, path: string, summary: string, options: EndpointOptions<any, any, any, any, any, any>, tags: string[], debug: boolean) => void;
|
|
29
29
|
export {};
|
package/dist/lib/api/openapi.js
CHANGED
|
@@ -4,6 +4,21 @@ exports.registerEndpoint = exports.hoistCombinatorRequiredFields = exports.force
|
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const http_1 = require("./types/http");
|
|
6
6
|
const mapExpressParamsToOpenAPIParams = (path) => path.replaceAll(/\:([^\:\/]+)/g, '{$1}');
|
|
7
|
+
const deriveOperationId = (registry, method, path) => {
|
|
8
|
+
const operationId = method +
|
|
9
|
+
path
|
|
10
|
+
.replaceAll(':', 'by-')
|
|
11
|
+
.split(/[^A-Za-z0-9]+/)
|
|
12
|
+
.filter(Boolean)
|
|
13
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
14
|
+
.join('');
|
|
15
|
+
const isTaken = registry.definitions.some(definition => definition.type === 'route' &&
|
|
16
|
+
definition.route.operationId === operationId);
|
|
17
|
+
if (isTaken) {
|
|
18
|
+
throw new Error(`Duplicate operationId ${operationId} for ${method.toUpperCase()} ${path}, set options.operationId`);
|
|
19
|
+
}
|
|
20
|
+
return operationId;
|
|
21
|
+
};
|
|
7
22
|
const buildResponseConfig = (responseOptions) => {
|
|
8
23
|
const responses = {};
|
|
9
24
|
responseOptions.forEach(response => {
|
|
@@ -210,7 +225,7 @@ const hoistCombinatorRequiredFields = (spec) => {
|
|
|
210
225
|
visit(schemas);
|
|
211
226
|
};
|
|
212
227
|
exports.hoistCombinatorRequiredFields = hoistCombinatorRequiredFields;
|
|
213
|
-
const registerEndpoint = (registry, method, prefixPath, path, summary,
|
|
228
|
+
const registerEndpoint = (registry, method, prefixPath, rootPrefixPath, path, summary,
|
|
214
229
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
215
230
|
options, tags, debug) => {
|
|
216
231
|
let bodySchema = options.schemas?.body || undefined;
|
|
@@ -218,6 +233,7 @@ options, tags, debug) => {
|
|
|
218
233
|
let querySchema = options.schemas?.query || undefined;
|
|
219
234
|
let headersSchema = options.schemas?.headers || undefined;
|
|
220
235
|
let responseSchemas = options.responses;
|
|
236
|
+
const securitySchemes = new Set();
|
|
221
237
|
if (options.errors) {
|
|
222
238
|
for (const errorType of Object.keys(options.errors)) {
|
|
223
239
|
const statusCode = options.errors[errorType];
|
|
@@ -278,6 +294,9 @@ options, tags, debug) => {
|
|
|
278
294
|
});
|
|
279
295
|
}
|
|
280
296
|
}
|
|
297
|
+
for (const scheme of middleware.options.security || []) {
|
|
298
|
+
securitySchemes.add(scheme);
|
|
299
|
+
}
|
|
281
300
|
}
|
|
282
301
|
// Add ZodError response
|
|
283
302
|
responseSchemas.push({
|
|
@@ -334,7 +353,11 @@ options, tags, debug) => {
|
|
|
334
353
|
headers: headersSchema ? [headersSchema] : [],
|
|
335
354
|
},
|
|
336
355
|
responses: responseMap,
|
|
337
|
-
|
|
356
|
+
operationId: options.operationId ||
|
|
357
|
+
deriveOperationId(registry, method, prefixPath.slice(rootPrefixPath.length) + path),
|
|
358
|
+
...(securitySchemes.size > 0 && {
|
|
359
|
+
security: [...securitySchemes].map(scheme => ({ [scheme]: [] })),
|
|
360
|
+
}),
|
|
338
361
|
});
|
|
339
362
|
};
|
|
340
363
|
exports.registerEndpoint = registerEndpoint;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { SecuritySchemeObject } from 'openapi3-ts/oas30';
|
|
2
|
+
export type SecurityScheme = 'jwt_operator' | 'jwt_support' | 'jwt_operator_device' | 'jwt_consumer' | 'api_key_operator_device' | 'api_key_atworld' | 'api_key_at_support' | 'basic_adyen_webhook' | 'hmac_adyen_webhook';
|
|
3
|
+
export declare const SECURITY_SCHEMES: Record<SecurityScheme, SecuritySchemeObject>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SECURITY_SCHEMES = void 0;
|
|
4
|
+
const xAuth = (description) => ({
|
|
5
|
+
type: 'apiKey',
|
|
6
|
+
in: 'header',
|
|
7
|
+
name: 'X-Auth',
|
|
8
|
+
description,
|
|
9
|
+
});
|
|
10
|
+
const xApiKey = (description) => ({
|
|
11
|
+
type: 'apiKey',
|
|
12
|
+
in: 'header',
|
|
13
|
+
name: 'X-Api-Key',
|
|
14
|
+
description,
|
|
15
|
+
});
|
|
16
|
+
const basicAuth = (description) => ({
|
|
17
|
+
type: 'http',
|
|
18
|
+
scheme: 'basic',
|
|
19
|
+
description,
|
|
20
|
+
});
|
|
21
|
+
const hmacHeader = (description) => ({
|
|
22
|
+
type: 'apiKey',
|
|
23
|
+
in: 'header',
|
|
24
|
+
name: 'hmacsignature',
|
|
25
|
+
description,
|
|
26
|
+
});
|
|
27
|
+
exports.SECURITY_SCHEMES = {
|
|
28
|
+
jwt_operator: xAuth('Operator session JWT issued by the luca backend'),
|
|
29
|
+
jwt_support: xAuth('Support session JWT issued by the luca backend'),
|
|
30
|
+
jwt_operator_device: xAuth('Operator device JWT issued by the luca backend (kiosk, terminal, link)'),
|
|
31
|
+
jwt_consumer: xAuth('Consumer JWT'),
|
|
32
|
+
api_key_operator_device: xApiKey('Operator device API key (sk_...) created by the operator for an external device'),
|
|
33
|
+
api_key_atworld: xApiKey('ATworld partner API key'),
|
|
34
|
+
api_key_at_support: xApiKey('AT support API key (partner or luca)'),
|
|
35
|
+
basic_adyen_webhook: basicAuth('Basic auth credential luca configures on the Adyen webhook endpoint'),
|
|
36
|
+
hmac_adyen_webhook: hmacHeader('Adyen HMAC-SHA256 signature over the raw request body, verified against the configured balance platform HMAC keys'),
|
|
37
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { SecurityScheme } from '../security';
|
|
2
3
|
import { ArrayToUnion, ExtractZodOutput, ZodSchemaOuput } from './utils';
|
|
3
4
|
export type EndpointResponseSchema = {
|
|
4
5
|
status: number;
|
|
@@ -45,6 +46,7 @@ export type MiddlewareOptions<TResponseSchemas extends ReadonlyArray<EndpointRes
|
|
|
45
46
|
returnRawBody?: boolean;
|
|
46
47
|
};
|
|
47
48
|
errors?: Record<string, number>;
|
|
49
|
+
security?: ReadonlyArray<SecurityScheme>;
|
|
48
50
|
};
|
|
49
51
|
export type Middleware<TResponseSchemas extends ReadonlyArray<EndpointResponseSchema>, TRequestBodySchema, TRequestParamsSchema, TRequestQuerySchema, TRequestHeadersSchema, TContextSchema> = {
|
|
50
52
|
options: MiddlewareOptions<TResponseSchemas, TRequestBodySchema, TRequestParamsSchema, TRequestQuerySchema, TRequestHeadersSchema, TContextSchema>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lucaapp/service-utils",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.29.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc -p tsconfig.build.json",
|
|
13
13
|
"ts:check": "tsc -p . --noEmit",
|
|
14
|
-
"lint": "eslint .",
|
|
14
|
+
"lint": "eslint --quiet .",
|
|
15
15
|
"test": "vitest run",
|
|
16
16
|
"test:coverage": "vitest run --coverage",
|
|
17
17
|
"test:ci": "VITEST_JUNIT_SUITE_NAME=service-utils CI=true vitest run --coverage",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"tar": "7.5.21",
|
|
89
89
|
"body-parser": "1.20.6",
|
|
90
90
|
"lodash": "^4.18.1",
|
|
91
|
-
"qs": "6.
|
|
91
|
+
"qs": "6.16.0",
|
|
92
92
|
"dottie": "2.0.7",
|
|
93
93
|
"fast-xml-parser": "^5.7.1",
|
|
94
94
|
"yaml": "^2.8.3",
|