@devmoods/express-extras 0.49.3 → 0.50.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/auth/emailAndPassword.d.ts +5 -1
- package/dist/auth/emailAndPassword.js +4 -1
- package/dist/faktory.d.ts +0 -1
- package/dist/faktory.js +0 -5
- package/dist/listen.js +2 -2
- package/dist/manage.js +10 -2
- package/dist/middleware.d.ts +26 -2
- package/dist/middleware.js +12 -6
- package/dist/validate.d.ts +7 -4
- package/dist/validate.js +11 -0
- package/dist/vite/defineViteConfig.js +4 -4
- package/dist/vite/ssr.js +1 -0
- package/dist/vite/types.d.ts +1 -0
- package/package.json +8 -7
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { type AnyUser, type AuthProvider } from './types.js';
|
|
2
|
+
export interface AuthPasswordChangedMetadata {
|
|
3
|
+
ip: string;
|
|
4
|
+
userAgent: string;
|
|
5
|
+
}
|
|
2
6
|
export interface EmailAndPasswordOptions<UserModel = AnyUser> {
|
|
3
7
|
login: (email: string, verifyPassword: (realPassword: string) => Promise<string>) => Promise<UserModel>;
|
|
4
8
|
onPasswordResetRequest?(user: UserModel, temporaryToken: string): void | Promise<void>;
|
|
5
|
-
onPasswordChanged?(user: UserModel, newPassword: string): Promise<void>;
|
|
9
|
+
onPasswordChanged?(user: UserModel, newPassword: string, metadata: AuthPasswordChangedMetadata): Promise<void>;
|
|
6
10
|
onEmailVerificationRequest?(user: UserModel, temporaryToken: string): Promise<void>;
|
|
7
11
|
onEmailVerified?(user: UserModel): Promise<void>;
|
|
8
12
|
}
|
|
@@ -52,7 +52,10 @@ export function EmailAndPasswordAuthProvider(options) {
|
|
|
52
52
|
throw Unauthorized('Invalid token');
|
|
53
53
|
}
|
|
54
54
|
context.setCurrentUser(user);
|
|
55
|
-
await options.onPasswordChanged?.(user, await hashPassword(password)
|
|
55
|
+
await options.onPasswordChanged?.(user, await hashPassword(password), {
|
|
56
|
+
ip: req.ip ?? '',
|
|
57
|
+
userAgent: req.headers['user-agent'] ?? '',
|
|
58
|
+
});
|
|
56
59
|
logger.info('user changed password');
|
|
57
60
|
return 200;
|
|
58
61
|
}));
|
package/dist/faktory.d.ts
CHANGED
|
@@ -14,7 +14,6 @@ export declare function createJobs<GlobalContext extends AnyJobContext>({ queueN
|
|
|
14
14
|
$job: boolean;
|
|
15
15
|
jobName: string;
|
|
16
16
|
options: JobOptions;
|
|
17
|
-
readonly executionCount: number;
|
|
18
17
|
delay(args?: Parameters<Fn>[0]): Promise<`${string}-${string}-${string}-${string}-${string}`>;
|
|
19
18
|
execute(args: Parameters<Fn>[0], context: GlobalContext): Promise<any>;
|
|
20
19
|
};
|
package/dist/faktory.js
CHANGED
|
@@ -14,7 +14,6 @@ export function createJobs({ queueName, faktory: createFactory = create, }) {
|
|
|
14
14
|
throw new Error('job() must get a function as the first or second argument');
|
|
15
15
|
}
|
|
16
16
|
const jobName = fn.name;
|
|
17
|
-
let executionCount = 0;
|
|
18
17
|
if (!jobName) {
|
|
19
18
|
throw new Error('Can not create job without a name');
|
|
20
19
|
}
|
|
@@ -22,9 +21,6 @@ export function createJobs({ queueName, faktory: createFactory = create, }) {
|
|
|
22
21
|
$job: true,
|
|
23
22
|
jobName,
|
|
24
23
|
options,
|
|
25
|
-
get executionCount() {
|
|
26
|
-
return executionCount;
|
|
27
|
-
},
|
|
28
24
|
async delay(args) {
|
|
29
25
|
const jid = randomUUID();
|
|
30
26
|
try {
|
|
@@ -49,7 +45,6 @@ export function createJobs({ queueName, faktory: createFactory = create, }) {
|
|
|
49
45
|
async execute(args, context) {
|
|
50
46
|
try {
|
|
51
47
|
const result = await fn(args, context);
|
|
52
|
-
executionCount += 1;
|
|
53
48
|
return result;
|
|
54
49
|
}
|
|
55
50
|
catch (error) {
|
package/dist/listen.js
CHANGED
|
@@ -6,7 +6,7 @@ import {} from 'express';
|
|
|
6
6
|
import { config } from './config.js';
|
|
7
7
|
import { BadRequest } from './errors.js';
|
|
8
8
|
import { getLogger } from './logger.js';
|
|
9
|
-
import {
|
|
9
|
+
import { route } from './middleware.js';
|
|
10
10
|
import { envPort, startTimer } from './utils.js';
|
|
11
11
|
import { validateQuery } from './validate.js';
|
|
12
12
|
const logger = getLogger('http');
|
|
@@ -103,7 +103,7 @@ export function createExpressServer(app, options = {}) {
|
|
|
103
103
|
}
|
|
104
104
|
const healthTypes = ['live', 'ready'];
|
|
105
105
|
if (!app.get('__healthAttached')) {
|
|
106
|
-
app.get('/health/:type?',
|
|
106
|
+
app.get('/health/:type?', validateQuery({
|
|
107
107
|
type: 'object',
|
|
108
108
|
properties: {
|
|
109
109
|
type: { type: 'string', enum: ['live', 'ready'] },
|
package/dist/manage.js
CHANGED
|
@@ -46,8 +46,16 @@ export function registerDefaultCommands(app) {
|
|
|
46
46
|
}));
|
|
47
47
|
});
|
|
48
48
|
registerManagementCommand((command, cli) => {
|
|
49
|
-
command('show-config')
|
|
50
|
-
|
|
49
|
+
command('show-config')
|
|
50
|
+
.option('--json', 'Output JSON')
|
|
51
|
+
.action(cli.wrap(async (options) => {
|
|
52
|
+
if (options.json) {
|
|
53
|
+
console.log(JSON.stringify(config.value, null, 2));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
for (const [key, value] of Object.entries(config.value)) {
|
|
57
|
+
cli.writeLine(`${key}=${value}\n`);
|
|
58
|
+
}
|
|
51
59
|
}));
|
|
52
60
|
});
|
|
53
61
|
}
|
package/dist/middleware.d.ts
CHANGED
|
@@ -13,18 +13,42 @@ export interface BeforeMiddlewareCSPDirectives {
|
|
|
13
13
|
[key: string]: ContentSecurityPolicyDirectiveValue[];
|
|
14
14
|
}
|
|
15
15
|
export interface BeforeMiddlewareOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Enable cookie parsing
|
|
18
|
+
*/
|
|
16
19
|
cookies?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Enable helmet security headers
|
|
22
|
+
*
|
|
23
|
+
* https://github.com/helmetjs/helmet
|
|
24
|
+
*/
|
|
17
25
|
helmet?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Enable JSON body parsing
|
|
28
|
+
*/
|
|
18
29
|
json?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Enable URL encoded body parsing
|
|
32
|
+
*/
|
|
33
|
+
urlencoded?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Enable CSRF protection
|
|
36
|
+
*/
|
|
19
37
|
csrf?: boolean | CsrfMiddlewareOptions;
|
|
38
|
+
/**
|
|
39
|
+
* Allow unsafe inline scripts
|
|
40
|
+
*/
|
|
20
41
|
allowUnsafeInlineScripts?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Custom CSP directives
|
|
44
|
+
*/
|
|
21
45
|
cspDirectives?: (defaults: BeforeMiddlewareCSPDirectives) => BeforeMiddlewareCSPDirectives;
|
|
22
46
|
}
|
|
23
47
|
export declare function useCspNonce(): string;
|
|
24
48
|
/**
|
|
25
49
|
* Register middleware that must come before the app routes.
|
|
26
50
|
*/
|
|
27
|
-
export declare function before({ cookies, helmet: useHelmet, json: useJson, csrf: useCsrf, allowUnsafeInlineScripts, cspDirectives, }?: BeforeMiddlewareOptions): import("compose-middleware").RequestHandler<unknown, unknown, void>;
|
|
51
|
+
export declare function before({ cookies, helmet: useHelmet, json: useJson, csrf: useCsrf, urlencoded: useUrlencoded, allowUnsafeInlineScripts, cspDirectives, }?: BeforeMiddlewareOptions): import("compose-middleware").RequestHandler<unknown, unknown, void>;
|
|
28
52
|
/**
|
|
29
53
|
* Middleware that sends a 404
|
|
30
54
|
*
|
|
@@ -43,4 +67,4 @@ type ResponseWrapper<TResponseBody extends AnyResponseBody> = TResponseBody | TR
|
|
|
43
67
|
/**
|
|
44
68
|
* Alias of `route()`
|
|
45
69
|
*/
|
|
46
|
-
export declare function asyncMiddleware<RequestBody, RequestQuery extends Record<string, any> = Record<string, any>, ResponseBody extends AnyResponseBody = Record<string, any
|
|
70
|
+
export declare function asyncMiddleware<RequestBody, RequestQuery extends Record<string, any> = Record<string, any>, ResponseBody extends AnyResponseBody = Record<string, any>, ParamKeys extends string = string>(fn: (req: Request<Record<ParamKeys, string>, ResponseBody, RequestBody, RequestQuery, Record<string, any>>, res: Response<ResponseBody, object>, next: NextFunction) => ResponseWrapper<ResponseBody> | Promise<ResponseWrapper<ResponseBody>>): RequestHandler<Record<string, string>, ResponseBody, RequestBody, RequestQuery>;
|
package/dist/middleware.js
CHANGED
|
@@ -20,7 +20,7 @@ export function useCspNonce() {
|
|
|
20
20
|
/**
|
|
21
21
|
* Register middleware that must come before the app routes.
|
|
22
22
|
*/
|
|
23
|
-
export function before({ cookies = false, helmet: useHelmet = true, json: useJson = true, csrf: useCsrf = false, allowUnsafeInlineScripts = false, cspDirectives = (x) => x, } = {}) {
|
|
23
|
+
export function before({ cookies = false, helmet: useHelmet = true, json: useJson = true, csrf: useCsrf = false, urlencoded: useUrlencoded = false, allowUnsafeInlineScripts = false, cspDirectives = (x) => x, } = {}) {
|
|
24
24
|
return compose([
|
|
25
25
|
withRequestContext(),
|
|
26
26
|
typeof useCsrf === 'object' && useCsrf != null
|
|
@@ -29,10 +29,7 @@ export function before({ cookies = false, helmet: useHelmet = true, json: useJso
|
|
|
29
29
|
? csrf()
|
|
30
30
|
: undefined,
|
|
31
31
|
logRequestDuration(),
|
|
32
|
-
(
|
|
33
|
-
setContext(CSP_NONCE_KEY, crypto.randomBytes(16).toString('base64'));
|
|
34
|
-
next();
|
|
35
|
-
}),
|
|
32
|
+
setCspNonce(),
|
|
36
33
|
useHelmet &&
|
|
37
34
|
helmet({
|
|
38
35
|
contentSecurityPolicy: {
|
|
@@ -65,7 +62,7 @@ export function before({ cookies = false, helmet: useHelmet = true, json: useJso
|
|
|
65
62
|
},
|
|
66
63
|
}),
|
|
67
64
|
useJson && jsonParser(),
|
|
68
|
-
urlencodedParser({ extended: true }),
|
|
65
|
+
useUrlencoded && urlencodedParser({ extended: true }),
|
|
69
66
|
(cookies || useCsrf) && cookieParser(),
|
|
70
67
|
analyticsProxy(),
|
|
71
68
|
].filter(Boolean));
|
|
@@ -106,6 +103,15 @@ export function notFound() {
|
|
|
106
103
|
next(NotFound(`Could not ${req.method} ${req.path}`));
|
|
107
104
|
};
|
|
108
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Set a CSP nonce for the request
|
|
108
|
+
*/
|
|
109
|
+
function setCspNonce() {
|
|
110
|
+
return (req, res, next) => {
|
|
111
|
+
setContext(CSP_NONCE_KEY, crypto.randomBytes(16).toString('base64'));
|
|
112
|
+
next();
|
|
113
|
+
};
|
|
114
|
+
}
|
|
109
115
|
/**
|
|
110
116
|
* In production, we could directly proxy the url
|
|
111
117
|
* to an analytics service from nginx, but in dev
|
package/dist/validate.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Options } from 'ajv';
|
|
2
|
+
import { Type } from 'arktype';
|
|
2
3
|
import { type RequestHandler } from 'express';
|
|
3
4
|
export type ValidationSchema = {
|
|
4
5
|
type?: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' | 'null';
|
|
@@ -27,9 +28,10 @@ export type ValidationSchema = {
|
|
|
27
28
|
title?: string;
|
|
28
29
|
description?: string;
|
|
29
30
|
} | boolean;
|
|
30
|
-
|
|
31
|
-
export declare function
|
|
32
|
-
export declare function
|
|
31
|
+
type AnySchema = Type | ValidationSchema;
|
|
32
|
+
export declare function validateBody(schema: AnySchema): RequestHandler;
|
|
33
|
+
export declare function validateQuery(schema: AnySchema): RequestHandler;
|
|
34
|
+
export declare function validateParams(schema: AnySchema): RequestHandler;
|
|
33
35
|
export type ValidationResult = {
|
|
34
36
|
isValid: true;
|
|
35
37
|
errors: null;
|
|
@@ -48,4 +50,5 @@ export declare function compileValidateSchema(schema: ValidationSchema, options?
|
|
|
48
50
|
[key: string]: any[];
|
|
49
51
|
};
|
|
50
52
|
};
|
|
51
|
-
export declare function validateSchema(data: any, schema:
|
|
53
|
+
export declare function validateSchema(data: any, schema: AnySchema, options?: Options): ValidationResult;
|
|
54
|
+
export {};
|
package/dist/validate.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import _Ajv, {} from 'ajv';
|
|
2
|
+
import { Type } from 'arktype';
|
|
2
3
|
import {} from 'express';
|
|
3
4
|
import { BadRequest } from './errors.js';
|
|
4
5
|
const Ajv = _Ajv;
|
|
6
|
+
const toValidationSchema = (schema) => {
|
|
7
|
+
if (schema instanceof Type) {
|
|
8
|
+
return schema.toJsonSchema();
|
|
9
|
+
}
|
|
10
|
+
return schema;
|
|
11
|
+
};
|
|
5
12
|
export function validateBody(schema) {
|
|
13
|
+
schema = toValidationSchema(schema);
|
|
6
14
|
const validate = compileValidateSchema(schema);
|
|
7
15
|
const middleware = (req, res, next) => {
|
|
8
16
|
const result = validate(req.body);
|
|
@@ -15,6 +23,7 @@ export function validateBody(schema) {
|
|
|
15
23
|
return middleware;
|
|
16
24
|
}
|
|
17
25
|
export function validateQuery(schema) {
|
|
26
|
+
schema = toValidationSchema(schema);
|
|
18
27
|
const validate = compileValidateSchema(schema, { coerceTypes: true });
|
|
19
28
|
const middleware = (req, res, next) => {
|
|
20
29
|
const result = validate(req.query);
|
|
@@ -27,6 +36,7 @@ export function validateQuery(schema) {
|
|
|
27
36
|
return middleware;
|
|
28
37
|
}
|
|
29
38
|
export function validateParams(schema) {
|
|
39
|
+
schema = toValidationSchema(schema);
|
|
30
40
|
const validate = compileValidateSchema(schema, { coerceTypes: true });
|
|
31
41
|
const middleware = (req, res, next) => {
|
|
32
42
|
const result = validate(req.params);
|
|
@@ -68,6 +78,7 @@ export function compileValidateSchema(schema, options = {}) {
|
|
|
68
78
|
};
|
|
69
79
|
}
|
|
70
80
|
export function validateSchema(data, schema, options = {}) {
|
|
81
|
+
schema = toValidationSchema(schema);
|
|
71
82
|
const ajv = createAjv(options);
|
|
72
83
|
const valid = ajv.validate(schema, data);
|
|
73
84
|
if (valid) {
|
|
@@ -39,7 +39,7 @@ export function defineViteConfig({ base = '', reactCompiler = false, ssr = false
|
|
|
39
39
|
}
|
|
40
40
|
: {
|
|
41
41
|
input: {
|
|
42
|
-
index: path.resolve(process.cwd(), '
|
|
42
|
+
index: path.resolve(process.cwd(), 'index.html'),
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
45
|
},
|
|
@@ -49,12 +49,12 @@ export function defineViteConfig({ base = '', reactCompiler = false, ssr = false
|
|
|
49
49
|
port,
|
|
50
50
|
strictPort: true,
|
|
51
51
|
origin: readEnv('VITE_ORIGIN', `http://localhost:${port}`),
|
|
52
|
-
}
|
|
53
|
-
: {
|
|
54
52
|
proxy: {
|
|
55
53
|
'/api': `http://localhost:${readEnv.number('PORT', 4000)}`,
|
|
54
|
+
'/analytics.js': `http://localhost:${readEnv.number('PORT', 4000)}`,
|
|
56
55
|
},
|
|
57
|
-
}
|
|
56
|
+
}
|
|
57
|
+
: {},
|
|
58
58
|
ssr: ssr
|
|
59
59
|
? {
|
|
60
60
|
noExternal: ['react-helmet-async'],
|
package/dist/vite/ssr.js
CHANGED
|
@@ -49,6 +49,7 @@ export async function createSsrMiddleware({ root = process.cwd(), getInitialData
|
|
|
49
49
|
window.$RefreshSig$ = () => (type) => type
|
|
50
50
|
window.__vite_plugin_react_preamble_installed__ = true
|
|
51
51
|
`,
|
|
52
|
+
publicUrl: config.value.PUBLIC_URL,
|
|
52
53
|
});
|
|
53
54
|
const readManifest = async () => {
|
|
54
55
|
if (useDevServer) {
|
package/dist/vite/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devmoods/express-extras",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.0",
|
|
4
4
|
"description": "Kubernetes-ready utilities for rapid Node.js application development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -44,12 +44,12 @@
|
|
|
44
44
|
],
|
|
45
45
|
"license": "ISC",
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@devmoods/eslint-config": "^2.
|
|
47
|
+
"@devmoods/eslint-config": "^2.6.0",
|
|
48
48
|
"@types/koa-compose": "^3.2.8",
|
|
49
|
-
"@types/node": "22.10.
|
|
49
|
+
"@types/node": "22.10.7",
|
|
50
50
|
"@types/supertest": "^6.0.2",
|
|
51
51
|
"@vitejs/plugin-react": "^4.3.4",
|
|
52
|
-
"@vitest/coverage-v8": "^
|
|
52
|
+
"@vitest/coverage-v8": "^3.0.2",
|
|
53
53
|
"cookiejar": "^2.1.4",
|
|
54
54
|
"eslint": "^9.18.0",
|
|
55
55
|
"faktory-worker": "^4.6.0",
|
|
@@ -59,13 +59,13 @@
|
|
|
59
59
|
"tsx": "^4.19.2",
|
|
60
60
|
"typescript": "5.7.3",
|
|
61
61
|
"vite": "^6.0.7",
|
|
62
|
-
"vitest": "^
|
|
62
|
+
"vitest": "^3.0.2",
|
|
63
63
|
"vitest-fail-on-console": "^0.7.1"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@devmoods/fetch": "^4.0.1",
|
|
67
|
-
"@sentry/node": "^8.
|
|
68
|
-
"@sentry/profiling-node": "^8.
|
|
67
|
+
"@sentry/node": "^8.50.0",
|
|
68
|
+
"@sentry/profiling-node": "^8.50.0",
|
|
69
69
|
"@types/bcrypt": "^5.0.2",
|
|
70
70
|
"@types/cookie-parser": "^1.4.8",
|
|
71
71
|
"@types/express": "^5.0.0",
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"@types/uuid": "^10.0.0",
|
|
77
77
|
"ajv": "^8.17.1",
|
|
78
78
|
"argon2": "^0.41.1",
|
|
79
|
+
"arktype": "^2.0.0",
|
|
79
80
|
"bcrypt": "^5.1.1",
|
|
80
81
|
"chalk": "^5.4.1",
|
|
81
82
|
"commander": "^13.0.0",
|