@terreno/api 0.11.7 → 0.11.9
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/betterAuthSetup.js +10 -3
- package/dist/configurationPlugin.d.ts +2 -1
- package/dist/configurationPlugin.js +16 -9
- package/dist/errors.d.ts +6 -6
- package/dist/errors.js +22 -22
- package/dist/errors.test.d.ts +1 -0
- package/dist/errors.test.js +280 -0
- package/dist/githubAuth.d.ts +3 -3
- package/dist/githubAuth.js +16 -16
- package/dist/middleware.d.ts +1 -1
- package/dist/middleware.js +4 -3
- package/dist/middleware.test.d.ts +1 -0
- package/dist/middleware.test.js +82 -0
- package/dist/notifiers/googleChatNotifier.js +4 -3
- package/dist/notifiers/zoomNotifier.js +12 -11
- package/dist/openApiCompat.js +2 -1
- package/dist/openApiEtag.d.ts +1 -1
- package/dist/openApiEtag.js +4 -3
- package/dist/openApiValidator.d.ts +12 -12
- package/dist/openApiValidator.js +59 -58
- package/dist/plugins.d.ts +12 -1
- package/dist/plugins.js +34 -1
- package/dist/plugins.test.js +212 -8
- package/dist/scriptRunner.d.ts +8 -7
- package/dist/secretProviders.js +17 -7
- package/dist/types/consentForm.d.ts +4 -2
- package/package.json +1 -1
- package/src/betterAuthSetup.ts +10 -3
- package/src/configurationPlugin.ts +18 -9
- package/src/errors.test.ts +302 -0
- package/src/errors.ts +18 -13
- package/src/githubAuth.ts +11 -10
- package/src/middleware.test.ts +71 -0
- package/src/middleware.ts +6 -2
- package/src/notifiers/googleChatNotifier.ts +4 -3
- package/src/notifiers/zoomNotifier.ts +4 -3
- package/src/openApiCompat.ts +2 -1
- package/src/openApiEtag.ts +2 -2
- package/src/openApiValidator.ts +46 -46
- package/src/plugins.test.ts +130 -0
- package/src/plugins.ts +35 -0
- package/src/scriptRunner.ts +23 -27
- package/src/secretProviders.ts +27 -9
- package/src/types/consentForm.ts +6 -4
package/src/githubAuth.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {generateTokens, type UserModel} from "./auth";
|
|
|
5
5
|
import {APIError} from "./errors";
|
|
6
6
|
import type {AuthOptions} from "./expressServer";
|
|
7
7
|
import {logger} from "./logger";
|
|
8
|
+
import {findOneOrNoneFor} from "./plugins";
|
|
8
9
|
|
|
9
10
|
/** Options for configuring GitHub OAuth authentication */
|
|
10
11
|
export interface GitHubAuthOptions {
|
|
@@ -57,24 +58,24 @@ export interface GitHubUserFields {
|
|
|
57
58
|
* userSchema.plugin(githubUserPlugin);
|
|
58
59
|
* ```
|
|
59
60
|
*/
|
|
60
|
-
export
|
|
61
|
+
export const githubUserPlugin = (schema: any): void => {
|
|
61
62
|
schema.add({
|
|
62
63
|
githubAvatarUrl: {type: String},
|
|
63
64
|
githubId: {index: true, sparse: true, type: String, unique: true},
|
|
64
65
|
githubProfileUrl: {type: String},
|
|
65
66
|
githubUsername: {type: String},
|
|
66
67
|
});
|
|
67
|
-
}
|
|
68
|
+
};
|
|
68
69
|
|
|
69
70
|
/**
|
|
70
71
|
* Sets up GitHub OAuth authentication strategy.
|
|
71
72
|
* Call this after setupAuth() in your server initialization.
|
|
72
73
|
*/
|
|
73
|
-
export
|
|
74
|
+
export const setupGitHubAuth = (
|
|
74
75
|
_app: express.Application,
|
|
75
76
|
userModel: UserModel,
|
|
76
77
|
githubOptions: GitHubAuthOptions
|
|
77
|
-
) {
|
|
78
|
+
): void => {
|
|
78
79
|
const scope = githubOptions.scope ?? ["user:email"];
|
|
79
80
|
|
|
80
81
|
passport.use(
|
|
@@ -112,7 +113,7 @@ export function setupGitHubAuth(
|
|
|
112
113
|
const githubId = profile.id;
|
|
113
114
|
|
|
114
115
|
// Check if user with this GitHub ID already exists
|
|
115
|
-
const existingGitHubUser = await userModel
|
|
116
|
+
const existingGitHubUser = await findOneOrNoneFor(userModel, {githubId});
|
|
116
117
|
|
|
117
118
|
// Case 1: User is authenticated and wants to link GitHub account
|
|
118
119
|
if (existingUser) {
|
|
@@ -155,7 +156,7 @@ export function setupGitHubAuth(
|
|
|
155
156
|
|
|
156
157
|
// Check if user with this email already exists
|
|
157
158
|
if (email) {
|
|
158
|
-
const existingEmailUser = await userModel
|
|
159
|
+
const existingEmailUser = await findOneOrNoneFor(userModel, {email});
|
|
159
160
|
if (existingEmailUser) {
|
|
160
161
|
// If account linking is allowed, link GitHub to existing email account
|
|
161
162
|
if (githubOptions.allowAccountLinking !== false) {
|
|
@@ -195,7 +196,7 @@ export function setupGitHubAuth(
|
|
|
195
196
|
}) as any
|
|
196
197
|
) as passport.Strategy
|
|
197
198
|
);
|
|
198
|
-
}
|
|
199
|
+
};
|
|
199
200
|
|
|
200
201
|
/**
|
|
201
202
|
* Adds GitHub OAuth routes to the Express application.
|
|
@@ -206,12 +207,12 @@ export function setupGitHubAuth(
|
|
|
206
207
|
* - POST /auth/github/link - Links GitHub account to authenticated user (requires JWT auth)
|
|
207
208
|
* - DELETE /auth/github/unlink - Unlinks GitHub account from authenticated user (requires JWT auth)
|
|
208
209
|
*/
|
|
209
|
-
export
|
|
210
|
+
export const addGitHubAuthRoutes = (
|
|
210
211
|
app: express.Application,
|
|
211
212
|
userModel: UserModel,
|
|
212
213
|
githubOptions: GitHubAuthOptions,
|
|
213
214
|
authOptions?: AuthOptions
|
|
214
|
-
): void {
|
|
215
|
+
): void => {
|
|
215
216
|
const router = require("express").Router();
|
|
216
217
|
|
|
217
218
|
// Initiate GitHub OAuth flow
|
|
@@ -332,4 +333,4 @@ export function addGitHubAuthRoutes(
|
|
|
332
333
|
}
|
|
333
334
|
|
|
334
335
|
app.use("/auth", router);
|
|
335
|
-
}
|
|
336
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {beforeEach, describe, expect, it, type Mock, mock} from "bun:test";
|
|
2
|
+
import * as Sentry from "@sentry/bun";
|
|
3
|
+
import type {NextFunction, Request, Response} from "express";
|
|
4
|
+
|
|
5
|
+
import {sentryAppVersionMiddleware} from "./middleware";
|
|
6
|
+
|
|
7
|
+
const buildReq = (headers: Record<string, string | undefined>): Request => {
|
|
8
|
+
return {
|
|
9
|
+
get: (name: string) => headers[name],
|
|
10
|
+
} as unknown as Request;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const buildNext = (): Mock<() => void> => mock(() => {});
|
|
14
|
+
|
|
15
|
+
describe("sentryAppVersionMiddleware", () => {
|
|
16
|
+
let setTagMock: Mock<(key: string, value: string) => void>;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
// bunSetup.ts mocks @sentry/bun so that getCurrentScope() returns a scope
|
|
20
|
+
// with a Bun mock setTag. Clear that mock between tests so each assertion
|
|
21
|
+
// sees only its own calls.
|
|
22
|
+
setTagMock = Sentry.getCurrentScope().setTag as unknown as Mock<
|
|
23
|
+
(key: string, value: string) => void
|
|
24
|
+
>;
|
|
25
|
+
setTagMock.mockClear();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("sets the app_version tag when the App-Version header is present", () => {
|
|
29
|
+
const next = buildNext();
|
|
30
|
+
const req = buildReq({"App-Version": "1.2.3"});
|
|
31
|
+
|
|
32
|
+
sentryAppVersionMiddleware(req, {} as Response, next as unknown as NextFunction);
|
|
33
|
+
|
|
34
|
+
expect(setTagMock).toHaveBeenCalledTimes(1);
|
|
35
|
+
expect(setTagMock.mock.calls[0]).toEqual(["app_version", "1.2.3"]);
|
|
36
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("does not set a tag when the App-Version header is missing", () => {
|
|
40
|
+
const next = buildNext();
|
|
41
|
+
const req = buildReq({});
|
|
42
|
+
|
|
43
|
+
sentryAppVersionMiddleware(req, {} as Response, next as unknown as NextFunction);
|
|
44
|
+
|
|
45
|
+
expect(setTagMock).not.toHaveBeenCalled();
|
|
46
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("does not set a tag when the App-Version header is an empty string", () => {
|
|
50
|
+
const next = buildNext();
|
|
51
|
+
const req = buildReq({"App-Version": ""});
|
|
52
|
+
|
|
53
|
+
sentryAppVersionMiddleware(req, {} as Response, next as unknown as NextFunction);
|
|
54
|
+
|
|
55
|
+
expect(setTagMock).not.toHaveBeenCalled();
|
|
56
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("calls next exactly once with no arguments when the header is present", () => {
|
|
60
|
+
const next = buildNext();
|
|
61
|
+
|
|
62
|
+
sentryAppVersionMiddleware(
|
|
63
|
+
buildReq({"App-Version": "9.9.9"}),
|
|
64
|
+
{} as Response,
|
|
65
|
+
next as unknown as NextFunction
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
69
|
+
expect(next.mock.calls[0]).toHaveLength(0);
|
|
70
|
+
});
|
|
71
|
+
});
|
package/src/middleware.ts
CHANGED
|
@@ -9,10 +9,14 @@ import type {NextFunction, Request, Response} from "express";
|
|
|
9
9
|
*
|
|
10
10
|
* Expected header: `App-Version`
|
|
11
11
|
*/
|
|
12
|
-
export
|
|
12
|
+
export const sentryAppVersionMiddleware = (
|
|
13
|
+
req: Request,
|
|
14
|
+
_res: Response,
|
|
15
|
+
next: NextFunction
|
|
16
|
+
): void => {
|
|
13
17
|
const appVersion = req.get("App-Version");
|
|
14
18
|
if (appVersion) {
|
|
15
19
|
Sentry.getCurrentScope().setTag("app_version", appVersion);
|
|
16
20
|
}
|
|
17
21
|
next();
|
|
18
|
-
}
|
|
22
|
+
};
|
|
@@ -34,13 +34,14 @@ export const sendToGoogleChat = async (
|
|
|
34
34
|
|
|
35
35
|
try {
|
|
36
36
|
await axios.post(chatWebhookUrl, {text: formattedMessageText});
|
|
37
|
-
} catch (error:
|
|
38
|
-
|
|
37
|
+
} catch (error: unknown) {
|
|
38
|
+
const errorObj = error as {text?: string; message?: string};
|
|
39
|
+
logger.error(`Error posting to Google Chat: ${errorObj.text ?? errorObj.message}`);
|
|
39
40
|
Sentry.captureException(error);
|
|
40
41
|
if (shouldThrow) {
|
|
41
42
|
throw new APIError({
|
|
42
43
|
status: 500,
|
|
43
|
-
title: `Error posting to Google Chat: ${
|
|
44
|
+
title: `Error posting to Google Chat: ${errorObj.text ?? errorObj.message}`,
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
47
|
}
|
|
@@ -95,13 +95,14 @@ export const sendToZoom = async (
|
|
|
95
95
|
},
|
|
96
96
|
}
|
|
97
97
|
);
|
|
98
|
-
} catch (error:
|
|
99
|
-
|
|
98
|
+
} catch (error: unknown) {
|
|
99
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
100
|
+
logger.error(`Error posting to Zoom: ${errorMessage}`);
|
|
100
101
|
Sentry.captureException(error);
|
|
101
102
|
if (shouldThrow) {
|
|
102
103
|
throw new APIError({
|
|
103
104
|
status: 500,
|
|
104
|
-
title: `Error posting to Zoom: ${
|
|
105
|
+
title: `Error posting to Zoom: ${errorMessage}`,
|
|
105
106
|
});
|
|
106
107
|
}
|
|
107
108
|
}
|
package/src/openApiCompat.ts
CHANGED
|
@@ -110,7 +110,7 @@ const patchRouterStack = (stack: any[]): void => {
|
|
|
110
110
|
*/
|
|
111
111
|
export const patchAppUse = (app: any): void => {
|
|
112
112
|
const originalUse = app.use.bind(app);
|
|
113
|
-
|
|
113
|
+
const patchedUse = (...args: any[]): unknown => {
|
|
114
114
|
// Track stack length before the call
|
|
115
115
|
const router = app._router || app.router;
|
|
116
116
|
const stackBefore = router?.stack?.length ?? 0;
|
|
@@ -132,6 +132,7 @@ export const patchAppUse = (app: any): void => {
|
|
|
132
132
|
|
|
133
133
|
return result;
|
|
134
134
|
};
|
|
135
|
+
app.use = patchedUse;
|
|
135
136
|
};
|
|
136
137
|
|
|
137
138
|
/**
|
package/src/openApiEtag.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type {NextFunction, Request, Response} from "express";
|
|
|
6
6
|
* This middleware should be added before the @wesleytodd/openapi middleware
|
|
7
7
|
* to intercept requests to /openapi.json and add conditional request support.
|
|
8
8
|
*/
|
|
9
|
-
export
|
|
9
|
+
export const openApiEtagMiddleware = (req: Request, res: Response, next: NextFunction): void => {
|
|
10
10
|
// Only handle GET requests to /openapi.json
|
|
11
11
|
if (req.method !== "GET" || req.path !== "/openapi.json") {
|
|
12
12
|
next();
|
|
@@ -37,4 +37,4 @@ export function openApiEtagMiddleware(req: Request, res: Response, next: NextFun
|
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
next();
|
|
40
|
-
}
|
|
40
|
+
};
|
package/src/openApiValidator.ts
CHANGED
|
@@ -110,9 +110,9 @@ let globalConfig: OpenApiValidatorConfig = {
|
|
|
110
110
|
* Check whether `configureOpenApiValidator()` has been called.
|
|
111
111
|
* Validation middleware is a no-op when this returns false.
|
|
112
112
|
*/
|
|
113
|
-
export
|
|
113
|
+
export const isOpenApiValidatorConfigured = (): boolean => {
|
|
114
114
|
return isConfigured;
|
|
115
|
-
}
|
|
115
|
+
};
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
118
|
* Configure the global OpenAPI validator settings.
|
|
@@ -131,28 +131,28 @@ export function isOpenApiValidatorConfigured(): boolean {
|
|
|
131
131
|
* });
|
|
132
132
|
* ```
|
|
133
133
|
*/
|
|
134
|
-
export
|
|
134
|
+
export const configureOpenApiValidator = (config: Partial<OpenApiValidatorConfig> = {}): void => {
|
|
135
135
|
isConfigured = true;
|
|
136
136
|
globalConfig = {...globalConfig, ...config};
|
|
137
137
|
// Clear cached AJV instances so new config takes effect
|
|
138
138
|
ajvCache.clear();
|
|
139
139
|
validatorCache.clear();
|
|
140
140
|
logger.debug(`OpenAPI validator configured: ${JSON.stringify(globalConfig)}`);
|
|
141
|
-
}
|
|
141
|
+
};
|
|
142
142
|
|
|
143
143
|
/**
|
|
144
144
|
* Get the current global validator configuration.
|
|
145
145
|
*/
|
|
146
|
-
export
|
|
146
|
+
export const getOpenApiValidatorConfig = (): OpenApiValidatorConfig => {
|
|
147
147
|
return {...globalConfig};
|
|
148
|
-
}
|
|
148
|
+
};
|
|
149
149
|
|
|
150
150
|
/**
|
|
151
151
|
* Reset the global validator configuration to defaults.
|
|
152
152
|
* Also resets `isConfigured` to false.
|
|
153
153
|
* Useful for testing.
|
|
154
154
|
*/
|
|
155
|
-
export
|
|
155
|
+
export const resetOpenApiValidatorConfig = (): void => {
|
|
156
156
|
isConfigured = false;
|
|
157
157
|
globalConfig = {
|
|
158
158
|
coerceTypes: true,
|
|
@@ -163,7 +163,7 @@ export function resetOpenApiValidatorConfig(): void {
|
|
|
163
163
|
};
|
|
164
164
|
ajvCache.clear();
|
|
165
165
|
validatorCache.clear();
|
|
166
|
-
}
|
|
166
|
+
};
|
|
167
167
|
|
|
168
168
|
// Lazy AJV instance cache keyed by coerceTypes + removeAdditional
|
|
169
169
|
const ajvCache = new Map<string, Ajv>();
|
|
@@ -171,7 +171,7 @@ const ajvCache = new Map<string, Ajv>();
|
|
|
171
171
|
/**
|
|
172
172
|
* Get or create an AJV instance with the current config settings.
|
|
173
173
|
*/
|
|
174
|
-
|
|
174
|
+
const getAjvInstance = (): Ajv => {
|
|
175
175
|
const key = `coerce:${globalConfig.coerceTypes ?? true},remove:${globalConfig.removeAdditional ?? true}`;
|
|
176
176
|
let instance = ajvCache.get(key);
|
|
177
177
|
|
|
@@ -189,7 +189,7 @@ function getAjvInstance(): Ajv {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
return instance;
|
|
192
|
-
}
|
|
192
|
+
};
|
|
193
193
|
|
|
194
194
|
// Cache compiled validators by schema hash + config key
|
|
195
195
|
const validatorCache = new Map<string, ValidateFunction>();
|
|
@@ -197,9 +197,9 @@ const validatorCache = new Map<string, ValidateFunction>();
|
|
|
197
197
|
/**
|
|
198
198
|
* Generate a simple hash for a schema to use as a cache key.
|
|
199
199
|
*/
|
|
200
|
-
|
|
200
|
+
const hashSchema = (schema: OpenApiSchema): string => {
|
|
201
201
|
return JSON.stringify(schema);
|
|
202
|
-
}
|
|
202
|
+
};
|
|
203
203
|
|
|
204
204
|
const VALID_JSON_SCHEMA_TYPES = new Set([
|
|
205
205
|
"string",
|
|
@@ -221,7 +221,7 @@ const MONGOOSE_TYPE_MAP: Record<string, {type: string; format?: string}> = {
|
|
|
221
221
|
* Recursively replace non-standard mongoose-to-swagger types with valid JSON Schema types
|
|
222
222
|
* so AJV can compile the schema.
|
|
223
223
|
*/
|
|
224
|
-
|
|
224
|
+
const sanitizeSchemaForAjv = (schema: Record<string, unknown>): Record<string, unknown> => {
|
|
225
225
|
if (!schema || typeof schema !== "object") {
|
|
226
226
|
return schema;
|
|
227
227
|
}
|
|
@@ -262,7 +262,7 @@ function sanitizeSchemaForAjv(schema: Record<string, unknown>): Record<string, u
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
return result;
|
|
265
|
-
}
|
|
265
|
+
};
|
|
266
266
|
|
|
267
267
|
/**
|
|
268
268
|
* Get or create a compiled validator for a schema.
|
|
@@ -270,7 +270,7 @@ function sanitizeSchemaForAjv(schema: Record<string, unknown>): Record<string, u
|
|
|
270
270
|
* Sanitizes non-standard mongoose-to-swagger types before compilation.
|
|
271
271
|
* Returns null if the schema still cannot be compiled after sanitization.
|
|
272
272
|
*/
|
|
273
|
-
|
|
273
|
+
const getValidator = (schema: OpenApiSchema): ValidateFunction | null => {
|
|
274
274
|
const ajv = getAjvInstance();
|
|
275
275
|
const configKey = `coerce:${globalConfig.coerceTypes ?? true},remove:${globalConfig.removeAdditional ?? true}`;
|
|
276
276
|
const hash = `${configKey}:${hashSchema(schema)}`;
|
|
@@ -293,12 +293,12 @@ function getValidator(schema: OpenApiSchema): ValidateFunction | null {
|
|
|
293
293
|
validatorCache.set(hash, null as unknown as ValidateFunction);
|
|
294
294
|
return null;
|
|
295
295
|
}
|
|
296
|
-
}
|
|
296
|
+
};
|
|
297
297
|
|
|
298
298
|
/**
|
|
299
299
|
* Format AJV errors into a human-readable string.
|
|
300
300
|
*/
|
|
301
|
-
|
|
301
|
+
const formatValidationErrors = (errors: ErrorObject[]): string => {
|
|
302
302
|
return errors
|
|
303
303
|
.map((err) => {
|
|
304
304
|
const path = err.instancePath || "/";
|
|
@@ -306,17 +306,17 @@ function formatValidationErrors(errors: ErrorObject[]): string {
|
|
|
306
306
|
return `${path}: ${message}`;
|
|
307
307
|
})
|
|
308
308
|
.join("; ");
|
|
309
|
-
}
|
|
309
|
+
};
|
|
310
310
|
|
|
311
311
|
/**
|
|
312
312
|
* Convert OpenApiSchemaProperty to a full OpenApiSchema suitable for AJV.
|
|
313
313
|
* Strips `required` from individual properties (OpenAPI-style) and moves it
|
|
314
314
|
* to the schema-level `required` array (JSON Schema-style) for AJV compatibility.
|
|
315
315
|
*/
|
|
316
|
-
|
|
316
|
+
const propertiesToSchema = (
|
|
317
317
|
properties: Record<string, OpenApiSchemaProperty>,
|
|
318
318
|
requiredFields?: string[]
|
|
319
|
-
): OpenApiSchema {
|
|
319
|
+
): OpenApiSchema => {
|
|
320
320
|
// Extract required fields from properties that have required: true
|
|
321
321
|
const autoRequired = Object.entries(properties)
|
|
322
322
|
.filter(([_, prop]) => prop.required)
|
|
@@ -344,7 +344,7 @@ function propertiesToSchema(
|
|
|
344
344
|
}
|
|
345
345
|
|
|
346
346
|
return schema;
|
|
347
|
-
}
|
|
347
|
+
};
|
|
348
348
|
|
|
349
349
|
/**
|
|
350
350
|
* Options for the request body validator middleware.
|
|
@@ -388,10 +388,10 @@ export interface RequestBodyValidatorOptions {
|
|
|
388
388
|
* @param options - Optional configuration for this validator
|
|
389
389
|
* @returns Express middleware function
|
|
390
390
|
*/
|
|
391
|
-
export
|
|
391
|
+
export const validateRequestBody = (
|
|
392
392
|
schema: Record<string, OpenApiSchemaProperty>,
|
|
393
393
|
options?: RequestBodyValidatorOptions
|
|
394
|
-
): (req: Request, res: Response, next: NextFunction) => void {
|
|
394
|
+
): ((req: Request, res: Response, next: NextFunction) => void) => {
|
|
395
395
|
const fullSchema = propertiesToSchema(schema, options?.required);
|
|
396
396
|
|
|
397
397
|
return (req: Request, _res: Response, next: NextFunction): void => {
|
|
@@ -491,7 +491,7 @@ export function validateRequestBody(
|
|
|
491
491
|
|
|
492
492
|
next();
|
|
493
493
|
};
|
|
494
|
-
}
|
|
494
|
+
};
|
|
495
495
|
|
|
496
496
|
/**
|
|
497
497
|
* Options for the query parameter validator middleware.
|
|
@@ -515,10 +515,10 @@ export interface QueryValidatorOptions {
|
|
|
515
515
|
* @param options - Optional configuration for this validator
|
|
516
516
|
* @returns Express middleware function
|
|
517
517
|
*/
|
|
518
|
-
export
|
|
518
|
+
export const validateQueryParams = (
|
|
519
519
|
schema: Record<string, OpenApiSchemaProperty>,
|
|
520
520
|
options?: QueryValidatorOptions
|
|
521
|
-
): (req: Request, res: Response, next: NextFunction) => void {
|
|
521
|
+
): ((req: Request, res: Response, next: NextFunction) => void) => {
|
|
522
522
|
const fullSchema = propertiesToSchema(schema);
|
|
523
523
|
|
|
524
524
|
return (req: Request, _res: Response, next: NextFunction): void => {
|
|
@@ -591,7 +591,7 @@ export function validateQueryParams(
|
|
|
591
591
|
|
|
592
592
|
next();
|
|
593
593
|
};
|
|
594
|
-
}
|
|
594
|
+
};
|
|
595
595
|
|
|
596
596
|
/**
|
|
597
597
|
* Options for creating a combined validation middleware.
|
|
@@ -630,9 +630,9 @@ export interface CreateValidatorOptions {
|
|
|
630
630
|
* ], handler);
|
|
631
631
|
* ```
|
|
632
632
|
*/
|
|
633
|
-
export
|
|
633
|
+
export const createValidator = (
|
|
634
634
|
options: CreateValidatorOptions
|
|
635
|
-
): (req: Request, res: Response, next: NextFunction) => void {
|
|
635
|
+
): ((req: Request, res: Response, next: NextFunction) => void) => {
|
|
636
636
|
const bodyValidator = options.body
|
|
637
637
|
? validateRequestBody(options.body, {enabled: options.enabled})
|
|
638
638
|
: null;
|
|
@@ -663,7 +663,7 @@ export function createValidator(
|
|
|
663
663
|
next();
|
|
664
664
|
}
|
|
665
665
|
};
|
|
666
|
-
}
|
|
666
|
+
};
|
|
667
667
|
|
|
668
668
|
/**
|
|
669
669
|
* Validates response data against a schema.
|
|
@@ -673,10 +673,10 @@ export function createValidator(
|
|
|
673
673
|
* @param schema - The expected schema
|
|
674
674
|
* @returns Object with valid flag and any errors
|
|
675
675
|
*/
|
|
676
|
-
export
|
|
676
|
+
export const validateResponseData = (
|
|
677
677
|
data: unknown,
|
|
678
678
|
schema: Record<string, OpenApiSchemaProperty>
|
|
679
|
-
): {valid: boolean; errors?: ErrorObject[]} {
|
|
679
|
+
): {valid: boolean; errors?: ErrorObject[]} => {
|
|
680
680
|
if (!globalConfig.validateResponses) {
|
|
681
681
|
return {valid: true};
|
|
682
682
|
}
|
|
@@ -698,7 +698,7 @@ export function validateResponseData(
|
|
|
698
698
|
}
|
|
699
699
|
|
|
700
700
|
return {valid: true};
|
|
701
|
-
}
|
|
701
|
+
};
|
|
702
702
|
|
|
703
703
|
const m2sOptions = {
|
|
704
704
|
props: ["readOnly", "required", "enum", "default"],
|
|
@@ -712,19 +712,19 @@ const m2sOptions = {
|
|
|
712
712
|
* @param model - A Mongoose model
|
|
713
713
|
* @returns Schema properties suitable for validation
|
|
714
714
|
*/
|
|
715
|
-
export
|
|
715
|
+
export const getSchemaFromModel = <T>(model: Model<T>): Record<string, OpenApiSchemaProperty> => {
|
|
716
716
|
const modelSwagger = m2s(model, m2sOptions);
|
|
717
717
|
fixMixedFields((model as any).schema, modelSwagger.properties);
|
|
718
718
|
return modelSwagger.properties as Record<string, OpenApiSchemaProperty>;
|
|
719
|
-
}
|
|
719
|
+
};
|
|
720
720
|
|
|
721
721
|
/**
|
|
722
722
|
* Extract required field names from a Mongoose model's swagger schema.
|
|
723
723
|
*/
|
|
724
|
-
|
|
724
|
+
const getRequiredFieldsFromModel = <T>(model: Model<T>): string[] => {
|
|
725
725
|
const modelSwagger = m2s(model, m2sOptions);
|
|
726
726
|
return (modelSwagger.required as string[]) ?? [];
|
|
727
|
-
}
|
|
727
|
+
};
|
|
728
728
|
|
|
729
729
|
/**
|
|
730
730
|
* Creates a request body validator middleware from a Mongoose model.
|
|
@@ -734,10 +734,10 @@ function getRequiredFieldsFromModel<T>(model: Model<T>): string[] {
|
|
|
734
734
|
* @param options - Optional configuration for the validator
|
|
735
735
|
* @returns Express middleware function
|
|
736
736
|
*/
|
|
737
|
-
export
|
|
737
|
+
export const validateModelRequestBody = <T>(
|
|
738
738
|
model: Model<T>,
|
|
739
739
|
options?: RequestBodyValidatorOptions
|
|
740
|
-
): (req: Request, res: Response, next: NextFunction) => void {
|
|
740
|
+
): ((req: Request, res: Response, next: NextFunction) => void) => {
|
|
741
741
|
let schema = getSchemaFromModel(model);
|
|
742
742
|
let requiredFields = getRequiredFieldsFromModel(model);
|
|
743
743
|
|
|
@@ -751,7 +751,7 @@ export function validateModelRequestBody<T>(
|
|
|
751
751
|
...options,
|
|
752
752
|
required: [...(options?.required ?? []), ...requiredFields],
|
|
753
753
|
});
|
|
754
|
-
}
|
|
754
|
+
};
|
|
755
755
|
|
|
756
756
|
/**
|
|
757
757
|
* Options for creating validation middleware for a modelRouter.
|
|
@@ -805,13 +805,13 @@ export interface ModelRouterValidationOptions {
|
|
|
805
805
|
* @param options - Configuration options
|
|
806
806
|
* @returns Object with create and update validation middleware
|
|
807
807
|
*/
|
|
808
|
-
export
|
|
808
|
+
export const createModelValidators = <T>(
|
|
809
809
|
model: Model<T>,
|
|
810
810
|
options?: ModelRouterValidationOptions
|
|
811
811
|
): {
|
|
812
812
|
create: (req: Request, res: Response, next: NextFunction) => void;
|
|
813
813
|
update: (req: Request, res: Response, next: NextFunction) => void;
|
|
814
|
-
} {
|
|
814
|
+
} => {
|
|
815
815
|
const schema = getSchemaFromModel(model);
|
|
816
816
|
|
|
817
817
|
return {
|
|
@@ -826,7 +826,7 @@ export function createModelValidators<T>(
|
|
|
826
826
|
onError: options?.onError,
|
|
827
827
|
}),
|
|
828
828
|
};
|
|
829
|
-
}
|
|
829
|
+
};
|
|
830
830
|
|
|
831
831
|
/**
|
|
832
832
|
* Build a query parameter schema from a model's Mongoose schema and queryFields array.
|
|
@@ -836,10 +836,10 @@ export function createModelValidators<T>(
|
|
|
836
836
|
* @param queryFields - Array of field names allowed for querying
|
|
837
837
|
* @returns Schema properties suitable for query validation
|
|
838
838
|
*/
|
|
839
|
-
export
|
|
839
|
+
export const buildQuerySchemaFromFields = <T>(
|
|
840
840
|
model: Model<T>,
|
|
841
841
|
queryFields: string[] = []
|
|
842
|
-
): Record<string, OpenApiSchemaProperty> {
|
|
842
|
+
): Record<string, OpenApiSchemaProperty> => {
|
|
843
843
|
const modelSchema = getSchemaFromModel(model);
|
|
844
844
|
const querySchema: Record<string, OpenApiSchemaProperty> = {
|
|
845
845
|
limit: {type: "number"},
|
|
@@ -859,4 +859,4 @@ export function buildQuerySchemaFromFields<T>(
|
|
|
859
859
|
}
|
|
860
860
|
|
|
861
861
|
return querySchema;
|
|
862
|
-
}
|
|
862
|
+
};
|