@devmoods/express-extras 0.38.0 → 0.39.1
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/csrf.d.ts +9 -5
- package/dist/csrf.js +30 -20
- package/dist/logger.js +1 -1
- package/dist/manage.js +2 -1
- package/dist/middleware.d.ts +2 -1
- package/dist/middleware.js +6 -2
- package/dist/postgres.js +2 -1
- package/package.json +3 -3
package/dist/csrf.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { type RequestHandler, type Request, type Response } from 'express';
|
|
2
2
|
export interface CsrfMiddlewareOptions {
|
|
3
3
|
excempt?: boolean;
|
|
4
|
+
cookieName?: string;
|
|
5
|
+
sessionIdentifier?: (req: Request) => string;
|
|
4
6
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
interface CsrfOptions {
|
|
8
|
+
excempt: boolean;
|
|
9
|
+
cookieName: string;
|
|
10
|
+
sessionIdentifier: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function csrf({ excempt, sessionIdentifier, cookieName, }?: CsrfMiddlewareOptions): RequestHandler;
|
|
13
|
+
export declare function setCsrfOptions(options: CsrfOptions): void;
|
|
10
14
|
interface TokenOptions {
|
|
11
15
|
overwrite?: boolean;
|
|
12
16
|
}
|
package/dist/csrf.js
CHANGED
|
@@ -3,31 +3,39 @@ import {} from 'express';
|
|
|
3
3
|
import { setContext, useContext } from './context.js';
|
|
4
4
|
import { Forbidden } from './errors.js';
|
|
5
5
|
import { readEnv } from './utils.js';
|
|
6
|
-
|
|
6
|
+
const ignoredMethods = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
7
|
+
const defaultCookieName = () => {
|
|
8
|
+
return process.env.NODE_ENV === 'production' && !readEnv.boolean('CI', false)
|
|
9
|
+
? '__Host-devmoods.x-csrf-token'
|
|
10
|
+
: 'devmoods.x-csrf-token';
|
|
11
|
+
};
|
|
12
|
+
export function csrf({ excempt = false, sessionIdentifier, cookieName, } = {}) {
|
|
7
13
|
return (req, res, next) => {
|
|
8
|
-
|
|
14
|
+
const existingOptions = useCsrfOptions();
|
|
15
|
+
setCsrfOptions({
|
|
16
|
+
cookieName: cookieName ?? existingOptions.cookieName ?? defaultCookieName(),
|
|
17
|
+
sessionIdentifier: sessionIdentifier
|
|
18
|
+
? sessionIdentifier(req)
|
|
19
|
+
: existingOptions.sessionIdentifier,
|
|
20
|
+
excempt,
|
|
21
|
+
});
|
|
9
22
|
next();
|
|
10
23
|
};
|
|
11
24
|
}
|
|
12
|
-
const
|
|
13
|
-
export function
|
|
14
|
-
setContext(
|
|
15
|
-
}
|
|
16
|
-
export function useCsrfIdentifier() {
|
|
17
|
-
return useContext(CSRF_IDENTIFIER_CONTEXT_KEY) ?? '';
|
|
25
|
+
const CSRF_CONTEXT_KEY = '@csrfConfig';
|
|
26
|
+
export function setCsrfOptions(options) {
|
|
27
|
+
setContext(CSRF_CONTEXT_KEY, options);
|
|
18
28
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
function useCsrfOptions() {
|
|
30
|
+
return (useContext(CSRF_CONTEXT_KEY) || {
|
|
31
|
+
excempt: true,
|
|
32
|
+
cookieName: defaultCookieName(),
|
|
33
|
+
sessionIdentifier: '',
|
|
34
|
+
});
|
|
25
35
|
}
|
|
26
|
-
const ignoredMethods = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
27
|
-
const cookieName = '__Host-devmoods.x-csrf-token';
|
|
28
36
|
const generateTokenAndHash = (req, { overwrite = false } = {}) => {
|
|
29
37
|
const secret = readEnv('CSRF_SECRET', '');
|
|
30
|
-
const sessionIdentifier =
|
|
38
|
+
const { sessionIdentifier, cookieName } = useCsrfOptions();
|
|
31
39
|
const csrfCookie = req.cookies[cookieName];
|
|
32
40
|
if (typeof csrfCookie === 'string' && !overwrite) {
|
|
33
41
|
const [csrfToken, csrfTokenHash] = csrfCookie.split('|');
|
|
@@ -50,6 +58,7 @@ const generateTokenAndHash = (req, { overwrite = false } = {}) => {
|
|
|
50
58
|
export const generateCsrfToken = (req, res, options = {}) => {
|
|
51
59
|
const { csrfToken, csrfTokenHash } = generateTokenAndHash(req, options);
|
|
52
60
|
const cookieContent = `${csrfToken}|${csrfTokenHash}`;
|
|
61
|
+
const { cookieName } = useCsrfOptions();
|
|
53
62
|
res.cookie(cookieName, cookieContent, {
|
|
54
63
|
sameSite: 'lax',
|
|
55
64
|
path: '/',
|
|
@@ -68,6 +77,7 @@ const validateTokenHash = ({ csrfToken, csrfTokenHash, secret, sessionIdentifier
|
|
|
68
77
|
return csrfTokenHash === expectedHash;
|
|
69
78
|
};
|
|
70
79
|
const validateRequest = (req) => {
|
|
80
|
+
const { cookieName, sessionIdentifier } = useCsrfOptions();
|
|
71
81
|
const csrfCookie = req.cookies[cookieName];
|
|
72
82
|
if (typeof csrfCookie !== 'string') {
|
|
73
83
|
throw Forbidden('Missing CSRF cookie');
|
|
@@ -85,15 +95,15 @@ const validateRequest = (req) => {
|
|
|
85
95
|
csrfToken: csrfTokenFromRequest,
|
|
86
96
|
csrfTokenHash,
|
|
87
97
|
secret,
|
|
88
|
-
sessionIdentifier
|
|
98
|
+
sessionIdentifier,
|
|
89
99
|
})) {
|
|
90
100
|
throw Forbidden('Invalid CSRF token');
|
|
91
101
|
}
|
|
92
102
|
};
|
|
93
103
|
export function doubleCsrfProtection(req) {
|
|
94
104
|
return async () => {
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
105
|
+
const { excempt } = useCsrfOptions();
|
|
106
|
+
if (excempt) {
|
|
97
107
|
return;
|
|
98
108
|
}
|
|
99
109
|
if (ignoredMethods.has(req.method)) {
|
package/dist/logger.js
CHANGED
package/dist/manage.js
CHANGED
|
@@ -21,8 +21,9 @@ export function registerDefaultCommands(app) {
|
|
|
21
21
|
.option('--openapi', 'Output OpenAPI schema')
|
|
22
22
|
.option('--json', 'Output JSON')
|
|
23
23
|
.action(cli.wrap(async (options) => {
|
|
24
|
-
if (!app)
|
|
24
|
+
if (!app) {
|
|
25
25
|
return;
|
|
26
|
+
}
|
|
26
27
|
if (options.openapi) {
|
|
27
28
|
console.error('Not implemented yet');
|
|
28
29
|
return;
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import bodyParser from 'body-parser';
|
|
2
2
|
import { type NextFunction, type RequestHandler, type Request, type Response } from 'express';
|
|
3
|
+
import { type CsrfMiddlewareOptions } from './csrf.js';
|
|
3
4
|
type UrlencodedParser = typeof bodyParser.urlencoded;
|
|
4
5
|
type JsonParser = typeof bodyParser.json;
|
|
5
6
|
export declare const urlencodedParser: UrlencodedParser;
|
|
@@ -15,7 +16,7 @@ export interface BeforeMiddlewareOptions {
|
|
|
15
16
|
cookies?: boolean;
|
|
16
17
|
helmet?: boolean;
|
|
17
18
|
json?: boolean;
|
|
18
|
-
csrf?: boolean;
|
|
19
|
+
csrf?: boolean | CsrfMiddlewareOptions;
|
|
19
20
|
allowUnsafeInlineScripts?: boolean;
|
|
20
21
|
cspDirectives?: (defaults: BeforeMiddlewareCSPDirectives) => BeforeMiddlewareCSPDirectives;
|
|
21
22
|
}
|
package/dist/middleware.js
CHANGED
|
@@ -4,7 +4,7 @@ import cookieParser from 'cookie-parser';
|
|
|
4
4
|
import {} from 'express';
|
|
5
5
|
import helmet from 'helmet';
|
|
6
6
|
import { withRequestContext } from './context.js';
|
|
7
|
-
import { csrf, doubleCsrfProtection } from './csrf.js';
|
|
7
|
+
import { csrf, doubleCsrfProtection, } from './csrf.js';
|
|
8
8
|
import { NotFound } from './errors.js';
|
|
9
9
|
import { getLogger } from './logger.js';
|
|
10
10
|
import { startTimer } from './utils.js';
|
|
@@ -18,7 +18,11 @@ export { asyncMiddleware as route };
|
|
|
18
18
|
export function before({ cookies = false, helmet: useHelmet = true, json: useJson = true, csrf: useCsrf = false, allowUnsafeInlineScripts = false, cspDirectives = (x) => x, } = {}) {
|
|
19
19
|
return compose([
|
|
20
20
|
withRequestContext(),
|
|
21
|
-
useCsrf &&
|
|
21
|
+
typeof useCsrf === 'object' && useCsrf != null
|
|
22
|
+
? csrf(useCsrf)
|
|
23
|
+
: useCsrf
|
|
24
|
+
? csrf()
|
|
25
|
+
: undefined,
|
|
22
26
|
logRequestDuration(),
|
|
23
27
|
useHelmet &&
|
|
24
28
|
helmet({
|
package/dist/postgres.js
CHANGED
|
@@ -88,8 +88,9 @@ export function createPostgres({ onError, url = readEnv('POSTGRES_URL'), ...conf
|
|
|
88
88
|
* Wipe the database schema
|
|
89
89
|
*/
|
|
90
90
|
async function clear(force = false) {
|
|
91
|
-
if (!force)
|
|
91
|
+
if (!force) {
|
|
92
92
|
return;
|
|
93
|
+
}
|
|
93
94
|
return withConnection(async (connection) => {
|
|
94
95
|
await connection.query('DROP SCHEMA public CASCADE');
|
|
95
96
|
await connection.query(`CREATE SCHEMA public`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devmoods/express-extras",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.1",
|
|
4
4
|
"description": "Kubernetes-ready utilities for rapid Node.js application development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
],
|
|
19
19
|
"license": "ISC",
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@devmoods/eslint-config": "^1.
|
|
21
|
+
"@devmoods/eslint-config": "^1.15.0",
|
|
22
22
|
"@types/koa-compose": "^3.2.8",
|
|
23
23
|
"@types/node": "22.8.6",
|
|
24
24
|
"@types/supertest": "^6.0.2",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"node": ">=20.3.0 <23"
|
|
82
82
|
},
|
|
83
83
|
"scripts": {
|
|
84
|
-
"start": "pushd example; pnpm run start; popd example;",
|
|
84
|
+
"start": "pushd example; pnpm run start --watch; popd example;",
|
|
85
85
|
"start:worker": "pushd example; pnpm run start:worker; popd example;",
|
|
86
86
|
"clean": "rm -rf dist",
|
|
87
87
|
"lint": "eslint src example/src",
|