@devmoods/express-extras 0.66.1 → 0.68.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.js +0 -1
- package/dist/auth/google.js +0 -1
- package/dist/auth/passwordless.js +0 -1
- package/dist/auth/token.d.ts +13 -13
- package/dist/auth/token.js +12 -12
- package/dist/auth.js +2 -2
- package/dist/faktory.js +8 -0
- package/dist/middleware.d.ts +2 -1
- package/dist/middleware.js +2 -1
- package/dist/router.d.ts +1 -1
- package/dist/vite/ssr.js +11 -20
- package/dist/webapi.d.ts +2 -2
- package/dist/webapi.js +20 -28
- package/package.json +11 -12
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { startTimer } from '@devmoods/observability';
|
|
2
2
|
import argon2 from 'argon2';
|
|
3
3
|
import bcrypt from 'bcrypt';
|
|
4
|
-
import {} from 'express';
|
|
5
4
|
import { createTokenResponse, tokenSchema } from './token.js';
|
|
6
5
|
import {} from './types.js';
|
|
7
6
|
import { Unauthorized } from '../errors.js';
|
package/dist/auth/google.js
CHANGED
package/dist/auth/token.d.ts
CHANGED
|
@@ -6,28 +6,28 @@ export declare function createTokenResponse(accessToken: AccessToken): readonly
|
|
|
6
6
|
}, {
|
|
7
7
|
readonly 'Cache-Control': "no-store";
|
|
8
8
|
}];
|
|
9
|
-
export type AccessToken = ReturnType<typeof createAccessToken
|
|
9
|
+
export type AccessToken = Awaited<ReturnType<typeof createAccessToken>>;
|
|
10
10
|
/**
|
|
11
11
|
* Returns a signed JWT token.
|
|
12
12
|
*/
|
|
13
|
-
export declare function createAccessToken(payload: any, version?: string, secret?: string): {
|
|
13
|
+
export declare function createAccessToken(payload: any, version?: string, secret?: string): Promise<{
|
|
14
14
|
token: string;
|
|
15
15
|
tokenType: "Bearer";
|
|
16
16
|
expiresIn: number;
|
|
17
|
-
}
|
|
17
|
+
}>;
|
|
18
18
|
/**
|
|
19
19
|
* Decode the given JWT token.
|
|
20
20
|
*/
|
|
21
|
-
export declare function decodeAccessToken(token: string, secret?: string): {
|
|
22
|
-
[
|
|
23
|
-
iss?: string
|
|
24
|
-
sub?: string
|
|
25
|
-
aud?: string | string[]
|
|
26
|
-
|
|
27
|
-
nbf?: number
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
21
|
+
export declare function decodeAccessToken(token: string, secret?: string): Promise<{
|
|
22
|
+
[propName: string]: unknown;
|
|
23
|
+
iss?: string;
|
|
24
|
+
sub?: string;
|
|
25
|
+
aud?: string | string[];
|
|
26
|
+
jti?: string;
|
|
27
|
+
nbf?: number;
|
|
28
|
+
exp?: number;
|
|
29
|
+
iat?: number;
|
|
30
|
+
}>;
|
|
31
31
|
/**
|
|
32
32
|
* Extract a Bearer token from the HTTP Authorization header
|
|
33
33
|
* of `req`.
|
package/dist/auth/token.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { config } from '@devmoods/config';
|
|
2
2
|
import {} from 'express';
|
|
3
|
-
import
|
|
3
|
+
import { SignJWT, jwtVerify } from 'jose';
|
|
4
4
|
import { v4 } from 'uuid';
|
|
5
5
|
import { InvalidToken } from './errors.js';
|
|
6
6
|
import {} from '../validate.js';
|
|
@@ -24,24 +24,24 @@ export function createTokenResponse(accessToken) {
|
|
|
24
24
|
/**
|
|
25
25
|
* Returns a signed JWT token.
|
|
26
26
|
*/
|
|
27
|
-
export function createAccessToken(payload, version = '1', secret = config.AUTH_JWT_SECRET) {
|
|
27
|
+
export async function createAccessToken(payload, version = '1', secret = config.AUTH_JWT_SECRET) {
|
|
28
28
|
const expiresIn = config.AUTH_TOKEN_EXPIRES;
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
const secretKey = new TextEncoder().encode(secret);
|
|
30
|
+
const token = await new SignJWT({ v: version, ...payload })
|
|
31
|
+
.setProtectedHeader({ alg: 'HS256' })
|
|
32
|
+
.setExpirationTime(`${expiresIn}s`)
|
|
33
|
+
.setIssuedAt(new Date())
|
|
34
|
+
.sign(secretKey);
|
|
32
35
|
return { token, tokenType: 'Bearer', expiresIn };
|
|
33
36
|
}
|
|
34
37
|
/**
|
|
35
38
|
* Decode the given JWT token.
|
|
36
39
|
*/
|
|
37
|
-
export function decodeAccessToken(token, secret = config.AUTH_JWT_SECRET) {
|
|
40
|
+
export async function decodeAccessToken(token, secret = config.AUTH_JWT_SECRET) {
|
|
38
41
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
throw new Error('decoded value not an object');
|
|
43
|
-
}
|
|
44
|
-
return { ...decoded };
|
|
42
|
+
const secretKey = new TextEncoder().encode(secret);
|
|
43
|
+
const { payload } = await jwtVerify(token.trim(), secretKey);
|
|
44
|
+
return { ...payload };
|
|
45
45
|
}
|
|
46
46
|
catch (error) {
|
|
47
47
|
throw new InvalidToken(`Failed to decode auth token: ${error.message}`, {
|
package/dist/auth.js
CHANGED
|
@@ -22,7 +22,7 @@ export function createAuth({ getUserById, filterUser, tokenStorage, providers, l
|
|
|
22
22
|
const useCurrentUser = () => useRequestContext('currentUser');
|
|
23
23
|
const setCurrentUser = (user) => setRequestContext('currentUser', user);
|
|
24
24
|
const login = async (req, res, user, isRefresh = false) => {
|
|
25
|
-
const accessToken = createAccessToken({ uid: user.id }, sessionTokenVersion);
|
|
25
|
+
const accessToken = await createAccessToken({ uid: user.id }, sessionTokenVersion);
|
|
26
26
|
const sessionId = sha256(accessToken.token);
|
|
27
27
|
await tokenStorage.set(sessionId, String(user.id), accessToken.expiresIn);
|
|
28
28
|
setAccessTokenCookie(res, accessToken.token);
|
|
@@ -138,7 +138,7 @@ export function createAuth({ getUserById, filterUser, tokenStorage, providers, l
|
|
|
138
138
|
await logout(req, res);
|
|
139
139
|
throw new InvalidToken('session token expired');
|
|
140
140
|
}
|
|
141
|
-
const decoded = decodeAccessToken(token);
|
|
141
|
+
const decoded = await decodeAccessToken(token);
|
|
142
142
|
if (decoded.v !== sessionTokenVersion) {
|
|
143
143
|
await logout(req, res);
|
|
144
144
|
throw new InvalidToken('Invalid token version');
|
package/dist/faktory.js
CHANGED
|
@@ -65,6 +65,14 @@ export async function createFaktoryWorker({ queues, jobs, faktory: createFactory
|
|
|
65
65
|
return job.execute(...args, jobContext);
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
|
+
/* c8 ignore start */
|
|
69
|
+
const onSignal = async (signal) => {
|
|
70
|
+
logger.info('faktory worker received signal', { signal });
|
|
71
|
+
};
|
|
72
|
+
for (const signal of ['SIGTERM', 'SIGINT']) {
|
|
73
|
+
process.once(signal, onSignal);
|
|
74
|
+
}
|
|
75
|
+
/* c8 ignore end */
|
|
68
76
|
return {
|
|
69
77
|
async start(workerOptions = {}) {
|
|
70
78
|
try {
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ServerResponse, type IncomingMessage } from 'node:http';
|
|
2
|
-
import { type NextFunction, type RequestHandler, type Request, type Response, json as jsonParser, urlencoded as urlencodedParser } from 'express';
|
|
2
|
+
import { type NextFunction, type RequestHandler, type Request, type Response, json as jsonParser, urlencoded as urlencodedParser, static as staticMiddleware } from 'express';
|
|
3
3
|
import { type createAuth } from './auth.js';
|
|
4
4
|
import { compose } from './composeMiddleware.js';
|
|
5
5
|
import { type CsrfMiddlewareOptions } from './csrf.js';
|
|
@@ -82,3 +82,4 @@ export declare function asyncMiddleware<RequestBody, RequestQuery extends Record
|
|
|
82
82
|
export declare function withTransaction(wrapper: {
|
|
83
83
|
transaction: (fn: () => Promise<void>) => Promise<void>;
|
|
84
84
|
}): RequestHandler;
|
|
85
|
+
export { staticMiddleware };
|
package/dist/middleware.js
CHANGED
|
@@ -3,7 +3,7 @@ import {} from 'node:http';
|
|
|
3
3
|
import { config } from '@devmoods/config';
|
|
4
4
|
import { startTimer } from '@devmoods/observability';
|
|
5
5
|
import cookieParser from 'cookie-parser';
|
|
6
|
-
import { json as jsonParser, urlencoded as urlencodedParser, } from 'express';
|
|
6
|
+
import { json as jsonParser, urlencoded as urlencodedParser, static as staticMiddleware, } from 'express';
|
|
7
7
|
import helmet from 'helmet';
|
|
8
8
|
import {} from './auth.js';
|
|
9
9
|
import { compose } from './composeMiddleware.js';
|
|
@@ -236,3 +236,4 @@ export function withTransaction(wrapper) {
|
|
|
236
236
|
}
|
|
237
237
|
};
|
|
238
238
|
}
|
|
239
|
+
export { staticMiddleware };
|
package/dist/router.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type ExtractParams<Path extends string> = Prettify<Path extends `${string
|
|
|
12
12
|
} & ExtractParams<`/${Rest}`> : Path extends `${string}:${infer Param}` ? {
|
|
13
13
|
[K in Param extends `${infer P}?` ? P : Param]: string;
|
|
14
14
|
} : {}>;
|
|
15
|
-
export type TypedRequestHandler<Params, ResponseBodySchema extends StandardSchemaV1 | undefined, RequestBodySchema extends StandardSchemaV1 | undefined, RequestQuery extends StandardSchemaV1 | undefined, RequestParamsSchema extends StandardSchemaV1 | undefined = undefined> = (req: Request<Params,
|
|
15
|
+
export type TypedRequestHandler<Params, ResponseBodySchema extends StandardSchemaV1 | undefined, RequestBodySchema extends StandardSchemaV1 | undefined, RequestQuery extends StandardSchemaV1 | undefined, RequestParamsSchema extends StandardSchemaV1 | undefined = undefined> = (req: Request<Params, any, RequestBodySchema extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<RequestBodySchema> : unknown, RequestQuery extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<RequestQuery> : Record<string, string | string[]>>, res: Response<ResponseBodySchema extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<ResponseBodySchema> : unknown>, next: NextFunction) => ResponseWrapper<ResponseBodySchema extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<ResponseBodySchema> : unknown> | Promise<ResponseWrapper<ResponseBodySchema extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<ResponseBodySchema> : unknown>>;
|
|
16
16
|
export declare class TypedRouter {
|
|
17
17
|
#private;
|
|
18
18
|
router: import("express-serve-static-core").Router;
|
package/dist/vite/ssr.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from 'node:fs/promises';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { config } from '@devmoods/config';
|
|
4
4
|
import express, {} from 'express';
|
|
5
|
-
import {
|
|
5
|
+
import {} from 'vite';
|
|
6
6
|
import {} from '../composeMiddleware.js';
|
|
7
7
|
import {} from './types.js';
|
|
8
8
|
import { generateCsrfToken } from '../csrf.js';
|
|
@@ -16,30 +16,28 @@ import { createFetchRequest } from '../webapi.js';
|
|
|
16
16
|
export async function createSsrMiddleware({ root = process.cwd(), getInitialData, useDevServer = config.VITE_USE_DEV_SERVER, } = {}) {
|
|
17
17
|
const resolve = (p) => path.resolve(root, p);
|
|
18
18
|
let vite;
|
|
19
|
+
let render;
|
|
20
|
+
let assetLinks = { preload: '', css: '', js: '' };
|
|
19
21
|
const middlewares = [];
|
|
20
22
|
if (useDevServer) {
|
|
21
|
-
vite = await
|
|
23
|
+
vite = await import('vite').then((mod) => mod.createServer({
|
|
22
24
|
root,
|
|
23
25
|
server: { middlewareMode: true },
|
|
24
26
|
appType: 'custom',
|
|
25
|
-
});
|
|
27
|
+
}));
|
|
28
|
+
render = (await vite.ssrLoadModule('/src/entry-server.tsx')).render;
|
|
26
29
|
middlewares.push(vite.middlewares);
|
|
27
30
|
}
|
|
28
31
|
else {
|
|
32
|
+
const result = await import(path.join(root, 'dist/server/entry-server.js'));
|
|
33
|
+
render = result.render;
|
|
34
|
+
assetLinks = await getAssetLinks(resolve('dist/client/.vite/manifest.json'), 'src/entry-client.tsx');
|
|
29
35
|
middlewares.push(express.static(resolve('dist/client'), { index: false }));
|
|
30
36
|
}
|
|
31
37
|
middlewares.push(route(async (req, res) => {
|
|
32
38
|
try {
|
|
33
|
-
let render;
|
|
34
|
-
if (useDevServer) {
|
|
35
|
-
render = (await vite.ssrLoadModule('/src/entry-server.tsx')).render;
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
const result = await import(path.join(root, 'dist/server/entry-server.js'));
|
|
39
|
-
render = result.render;
|
|
40
|
-
}
|
|
41
39
|
const nonce = useCspNonce();
|
|
42
|
-
const rendered = await render(createFetchRequest(req), {
|
|
40
|
+
const rendered = await render(createFetchRequest(req, res), {
|
|
43
41
|
useDevServer,
|
|
44
42
|
getInitialData: getInitialData || (async () => ({})),
|
|
45
43
|
placeholders: `
|
|
@@ -61,13 +59,6 @@ export async function createSsrMiddleware({ root = process.cwd(), getInitialData
|
|
|
61
59
|
publicUrl: new URL(config.PUBLIC_URL),
|
|
62
60
|
csrfToken: generateCsrfToken(req, res),
|
|
63
61
|
});
|
|
64
|
-
const readManifest = async () => {
|
|
65
|
-
if (useDevServer) {
|
|
66
|
-
return { preload: '', css: '', js: '' };
|
|
67
|
-
}
|
|
68
|
-
const manifestPath = path.resolve(root, 'dist/client/.vite/manifest.json');
|
|
69
|
-
return getAssetLinks(manifestPath, 'src/entry-client.tsx');
|
|
70
|
-
};
|
|
71
62
|
let html = rendered.html;
|
|
72
63
|
const context = Object.assign({
|
|
73
64
|
fouc: `
|
|
@@ -76,7 +67,7 @@ export async function createSsrMiddleware({ root = process.cwd(), getInitialData
|
|
|
76
67
|
setTimeout(() => { document.getElementById('fouc').remove(); }, 2000);
|
|
77
68
|
</script>
|
|
78
69
|
`,
|
|
79
|
-
}, rendered.context,
|
|
70
|
+
}, rendered.context, assetLinks);
|
|
80
71
|
const ssrPlaceholders = Object.fromEntries(Object.entries(context).map(([k, v]) => [`ssr-${k}`, v.toString()]));
|
|
81
72
|
// Add nonce to all bare script tags
|
|
82
73
|
html = html.replace(/<script>/g, `<script nonce="${nonce}">`);
|
package/dist/webapi.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { type Request as ExpressRequest } from 'express';
|
|
1
|
+
import { type Request as ExpressRequest, type Response as ExpressResponse } from 'express';
|
|
2
2
|
export declare function createFetchHeaders(requestHeaders: ExpressRequest['headers']): Headers;
|
|
3
|
-
export declare function createFetchRequest(req: ExpressRequest): Request;
|
|
3
|
+
export declare function createFetchRequest(req: ExpressRequest, res: ExpressResponse): Request;
|
package/dist/webapi.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {} from 'express';
|
|
2
|
-
import { BadRequest } from './errors.js';
|
|
3
2
|
export function createFetchHeaders(requestHeaders) {
|
|
4
3
|
const headers = new Headers();
|
|
5
4
|
for (const [key, values] of Object.entries(requestHeaders)) {
|
|
@@ -17,32 +16,25 @@ export function createFetchHeaders(requestHeaders) {
|
|
|
17
16
|
}
|
|
18
17
|
return headers;
|
|
19
18
|
}
|
|
20
|
-
export function createFetchRequest(req) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return new Request(url.href, init);
|
|
40
|
-
/* c8 ignore next 7 */
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
if (error instanceof TypeError) {
|
|
44
|
-
throw BadRequest('Invalid request');
|
|
45
|
-
}
|
|
46
|
-
throw error;
|
|
19
|
+
export function createFetchRequest(req, res) {
|
|
20
|
+
const origin = `${req.protocol}://${req.get('host')}`;
|
|
21
|
+
/* c8 ignore next 1 */
|
|
22
|
+
const url = new URL(req.originalUrl || req.url, origin);
|
|
23
|
+
let controller = new AbortController();
|
|
24
|
+
const init = {
|
|
25
|
+
method: req.method,
|
|
26
|
+
headers: createFetchHeaders(req.headers),
|
|
27
|
+
signal: controller.signal,
|
|
28
|
+
};
|
|
29
|
+
/* c8 ignore next 6 */
|
|
30
|
+
res.on('finish', () => {
|
|
31
|
+
controller = null;
|
|
32
|
+
});
|
|
33
|
+
res.on('close', () => {
|
|
34
|
+
controller?.abort();
|
|
35
|
+
});
|
|
36
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
37
|
+
init.body = req.body;
|
|
47
38
|
}
|
|
39
|
+
return new Request(url.href, init);
|
|
48
40
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devmoods/express-extras",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.68.0",
|
|
4
4
|
"description": "Kubernetes-ready utilities for rapid Node.js application development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -61,26 +61,25 @@
|
|
|
61
61
|
"license": "ISC",
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@vitejs/plugin-react": "^5.0.4",
|
|
64
|
-
"arktype": "^2.1.
|
|
64
|
+
"arktype": "^2.1.23",
|
|
65
65
|
"faktory-worker": "^4.7.1",
|
|
66
66
|
"react": "^19.2.0",
|
|
67
67
|
"react-dom": "^19.2.0",
|
|
68
|
-
"vite": "^7.1.
|
|
68
|
+
"vite": "^7.1.11"
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
71
|
"@devmoods/config": "0.2.0",
|
|
72
72
|
"@devmoods/fetch": "^4.2.0",
|
|
73
73
|
"@devmoods/observability": "0.1.1",
|
|
74
|
-
"@devmoods/postgres": "0.2.
|
|
75
|
-
"@hey-api/openapi-ts": "^0.
|
|
76
|
-
"@sentry/node": "^10.
|
|
77
|
-
"@sentry/node-native": "^10.
|
|
78
|
-
"@sentry/profiling-node": "^10.
|
|
74
|
+
"@devmoods/postgres": "0.2.1",
|
|
75
|
+
"@hey-api/openapi-ts": "^0.86.1",
|
|
76
|
+
"@sentry/node": "^10.21.0",
|
|
77
|
+
"@sentry/node-native": "^10.21.0",
|
|
78
|
+
"@sentry/profiling-node": "^10.21.0",
|
|
79
79
|
"@standard-schema/spec": "^1.0.0",
|
|
80
80
|
"@types/bcrypt": "^6.0.0",
|
|
81
81
|
"@types/cookie-parser": "^1.4.9",
|
|
82
82
|
"@types/express": "^5.0.3",
|
|
83
|
-
"@types/jsonwebtoken": "^9.0.10",
|
|
84
83
|
"@types/koa-compose": "^3.2.8",
|
|
85
84
|
"@types/nodemailer": "^7.0.2",
|
|
86
85
|
"@types/qs": "^6.14.0",
|
|
@@ -92,9 +91,9 @@
|
|
|
92
91
|
"cookie-parser": "^1.4.7",
|
|
93
92
|
"dotenv": "^17.2.3",
|
|
94
93
|
"express": "^4.21.2",
|
|
95
|
-
"google-auth-library": "^10.4.
|
|
94
|
+
"google-auth-library": "^10.4.1",
|
|
96
95
|
"helmet": "^8.1.0",
|
|
97
|
-
"
|
|
96
|
+
"jose": "^6.1.0",
|
|
98
97
|
"nodemailer": "^7.0.9",
|
|
99
98
|
"redis": "^5.8.3",
|
|
100
99
|
"sonda": "^0.9.0",
|
|
@@ -112,5 +111,5 @@
|
|
|
112
111
|
"engines": {
|
|
113
112
|
"node": ">=20 <25"
|
|
114
113
|
},
|
|
115
|
-
"gitHead": "
|
|
114
|
+
"gitHead": "721a709c087904a5cc1332d28a62857a82295fea"
|
|
116
115
|
}
|