@pipeline-builder/api-core 4.1.2 → 4.1.4
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/README.md +3 -3
- package/lib/errors/app-errors.d.ts +0 -4
- package/lib/errors/app-errors.js +1 -8
- package/lib/helpers/restore-helpers.d.ts +3 -3
- package/lib/helpers/restore-helpers.js +4 -4
- package/lib/helpers/sse-helpers.d.ts +8 -1
- package/lib/helpers/sse-helpers.js +11 -3
- package/lib/middleware/auth.d.ts +174 -43
- package/lib/middleware/auth.js +384 -179
- package/lib/middleware/index.d.ts +1 -0
- package/lib/middleware/index.js +2 -1
- package/lib/middleware/route-table.d.ts +104 -0
- package/lib/middleware/route-table.js +230 -0
- package/lib/middleware/step-up.d.ts +48 -16
- package/lib/middleware/step-up.js +74 -36
- package/lib/services/api-key-exchange.d.ts +25 -0
- package/lib/services/api-key-exchange.js +219 -0
- package/lib/services/audit-spool.d.ts +2 -3
- package/lib/services/audit-spool.js +3 -4
- package/lib/services/cache-service.d.ts +65 -21
- package/lib/services/cache-service.js +173 -78
- package/lib/services/compliance-event-subscriber.d.ts +5 -4
- package/lib/services/compliance-event-subscriber.js +8 -6
- package/lib/services/durable-event-bus.d.ts +22 -39
- package/lib/services/durable-event-bus.js +109 -47
- package/lib/services/env-redis.d.ts +114 -35
- package/lib/services/env-redis.js +215 -73
- package/lib/services/http-client.d.ts +10 -15
- package/lib/services/http-client.js +32 -27
- package/lib/services/index.d.ts +3 -0
- package/lib/services/index.js +4 -1
- package/lib/services/jwks-cache.d.ts +102 -0
- package/lib/services/jwks-cache.js +222 -0
- package/lib/services/leader-lock.d.ts +6 -3
- package/lib/services/leader-lock.js +20 -5
- package/lib/services/quota.d.ts +44 -10
- package/lib/services/quota.js +95 -51
- package/lib/services/remote-audit-client.d.ts +1 -1
- package/lib/services/remote-audit-client.js +32 -1
- package/lib/services/scheduler.d.ts +2 -3
- package/lib/services/scheduler.js +2 -2
- package/lib/services/service-boot.d.ts +4 -2
- package/lib/services/service-boot.js +7 -3
- package/lib/services/service-keys.d.ts +83 -0
- package/lib/services/service-keys.js +354 -0
- package/lib/services/sse-ticket-store.d.ts +55 -22
- package/lib/services/sse-ticket-store.js +122 -79
- package/lib/services/token-revocation.d.ts +1 -1
- package/lib/services/token-revocation.js +31 -19
- package/lib/testing/route-coverage.d.ts +64 -0
- package/lib/testing/route-coverage.js +183 -0
- package/lib/testing/service-tokens.d.ts +66 -0
- package/lib/testing/service-tokens.js +137 -0
- package/lib/testing/tier-mock.d.ts +2 -2
- package/lib/testing/tier-mock.js +3 -3
- package/lib/testing/user-tokens.d.ts +75 -0
- package/lib/testing/user-tokens.js +115 -0
- package/lib/types/catalog-metadata.d.ts +0 -2
- package/lib/types/catalog-metadata.js +1 -3
- package/lib/types/common.d.ts +103 -4
- package/lib/types/common.js +11 -1
- package/lib/types/error-codes.d.ts +15 -0
- package/lib/types/error-codes.js +22 -1
- package/lib/types/permissions.d.ts +35 -1
- package/lib/types/permissions.js +70 -2
- package/lib/utils/api-key.d.ts +34 -0
- package/lib/utils/api-key.js +75 -0
- package/lib/utils/env.d.ts +0 -2
- package/lib/utils/env.js +1 -6
- package/lib/utils/index.d.ts +3 -0
- package/lib/utils/index.js +4 -1
- package/lib/utils/jwk.d.ts +98 -0
- package/lib/utils/jwk.js +139 -0
- package/lib/utils/logger.d.ts +4 -1
- package/lib/utils/logger.js +1 -22
- package/lib/utils/secret-encryption.d.ts +7 -2
- package/lib/utils/secret-encryption.js +47 -4
- package/lib/utils/secret-rotation.d.ts +17 -0
- package/lib/utils/secret-rotation.js +54 -0
- package/lib/validation/common-schemas.js +5 -4
- package/package.json +20 -1
package/lib/middleware/index.js
CHANGED
|
@@ -3,4 +3,5 @@
|
|
|
3
3
|
export * from './auth.js';
|
|
4
4
|
export * from './mongo-sanitize.js';
|
|
5
5
|
export * from './step-up.js';
|
|
6
|
-
|
|
6
|
+
export * from './route-table.js';
|
|
7
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbWlkZGxld2FyZS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLGNBQWMsV0FBVyxDQUFDO0FBQzFCLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxrQkFBa0IsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5leHBvcnQgKiBmcm9tICcuL2F1dGguanMnO1xuZXhwb3J0ICogZnJvbSAnLi9tb25nby1zYW5pdGl6ZS5qcyc7XG5leHBvcnQgKiBmcm9tICcuL3N0ZXAtdXAuanMnO1xuZXhwb3J0ICogZnJvbSAnLi9yb3V0ZS10YWJsZS5qcyc7XG4iXX0=
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { Permission } from '../types/permissions.js';
|
|
2
|
+
/** One requirement a gate middleware enforces. */
|
|
3
|
+
export type RouteGate = {
|
|
4
|
+
kind: 'auth';
|
|
5
|
+
} | {
|
|
6
|
+
kind: 'permission';
|
|
7
|
+
mode: 'any' | 'all';
|
|
8
|
+
permissions: readonly Permission[];
|
|
9
|
+
allowService?: boolean;
|
|
10
|
+
} | {
|
|
11
|
+
kind: 'systemAdmin';
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'servicePrincipal';
|
|
14
|
+
}
|
|
15
|
+
/** An INTERNAL route: user tokens refused outright, only these services admitted. */
|
|
16
|
+
| {
|
|
17
|
+
kind: 'internalService';
|
|
18
|
+
callers: readonly string[];
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'stepUp';
|
|
21
|
+
methods?: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
/** The whole SESSION must be MFA-grade (`requireAuth({ minAssurance })`), and
|
|
24
|
+
* optionally no older than `maxAge` seconds. */
|
|
25
|
+
| {
|
|
26
|
+
kind: 'assurance';
|
|
27
|
+
minAssurance: 1 | 2;
|
|
28
|
+
maxAge?: number;
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'feature';
|
|
31
|
+
feature: string;
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'scope';
|
|
34
|
+
scope: string;
|
|
35
|
+
} | {
|
|
36
|
+
kind: 'audit';
|
|
37
|
+
actions: readonly string[];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Attach gate metadata to a middleware function (appends to any existing tags)
|
|
41
|
+
* and return the same function. Use it for a service-local gate so the route
|
|
42
|
+
* table knows what it enforces — e.g. a handler-level `requireComplianceWrite`
|
|
43
|
+
* tagged `{ kind: 'permission', mode: 'any', permissions: ['compliance:write'] }`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function tagRouteGate<T extends object>(fn: T, ...gates: RouteGate[]): T;
|
|
46
|
+
/** The gate metadata attached to a middleware function (empty when untagged). */
|
|
47
|
+
export declare function getRouteGates(fn: unknown): readonly RouteGate[];
|
|
48
|
+
/**
|
|
49
|
+
* Declare the audit action(s) a route emits. A no-op middleware whose only job
|
|
50
|
+
* is to put the action on the route table, so the coverage test can require an
|
|
51
|
+
* audit declaration on every write route. Place it in the route's chain:
|
|
52
|
+
* `router.post('/', audited('pipeline.create'), handler)`. The handler still
|
|
53
|
+
* emits the event itself.
|
|
54
|
+
*/
|
|
55
|
+
export declare function audited(...actions: string[]): (req: unknown, res: unknown, next: () => void) => void;
|
|
56
|
+
/** Resolved requirements for one method + path. */
|
|
57
|
+
export interface RouteTableEntry {
|
|
58
|
+
method: string;
|
|
59
|
+
path: string;
|
|
60
|
+
/** Authentication runs before the handler (requireAuth). */
|
|
61
|
+
auth: boolean;
|
|
62
|
+
/** Each permission gate in the chain (all must pass; each is any-of or all-of). */
|
|
63
|
+
permissions: Array<{
|
|
64
|
+
mode: 'any' | 'all';
|
|
65
|
+
permissions: Permission[];
|
|
66
|
+
allowService: boolean;
|
|
67
|
+
}>;
|
|
68
|
+
systemAdmin: boolean;
|
|
69
|
+
servicePrincipal: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* The services allowed to call this route when it is INTERNAL (`requireInternalService`).
|
|
72
|
+
* An empty array means the route is not internal — a user token may reach it,
|
|
73
|
+
* subject to the other gates. Sorted, so the generated table is stable.
|
|
74
|
+
*/
|
|
75
|
+
internalCallers: string[];
|
|
76
|
+
stepUp: boolean;
|
|
77
|
+
/** When the step-up gate names the factors that may earn it (`['webauthn',
|
|
78
|
+
* 'totp']` on the most dangerous routes), the allowed methods. Empty means
|
|
79
|
+
* any factor satisfies it. Sorted, so the generated table is stable. */
|
|
80
|
+
stepUpMethods: string[];
|
|
81
|
+
/** Highest `minAssurance` any gate in the chain demands; 0 when none does. */
|
|
82
|
+
minAssurance: 0 | 1 | 2;
|
|
83
|
+
/** Tightest `maxAge` (seconds) any assurance gate in the chain demands. */
|
|
84
|
+
maxAge?: number;
|
|
85
|
+
features: string[];
|
|
86
|
+
scopes: string[];
|
|
87
|
+
audit: string[];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Build the route table of an assembled Express app (or router): one entry per
|
|
91
|
+
* reachable method + path with every gate that runs before its handler.
|
|
92
|
+
* HEAD is folded into GET, as Express does.
|
|
93
|
+
*/
|
|
94
|
+
export declare function buildRouteTable(app: unknown): RouteTableEntry[];
|
|
95
|
+
/** Whether a method mutates state (everything except GET/HEAD/OPTIONS). */
|
|
96
|
+
export declare function isWriteMethod(method: string): boolean;
|
|
97
|
+
/** Summary counts for a startup log line. */
|
|
98
|
+
export declare function summarizeRouteTable(table: readonly RouteTableEntry[]): {
|
|
99
|
+
routes: number;
|
|
100
|
+
writes: number;
|
|
101
|
+
ungated: number;
|
|
102
|
+
unaudited: number;
|
|
103
|
+
internal: number;
|
|
104
|
+
};
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Introspectable route table.
|
|
5
|
+
*
|
|
6
|
+
* Authorization is split between router mounts (`app.use('/x', gate, router)`)
|
|
7
|
+
* and individual routes, so grepping source can't prove that every route is
|
|
8
|
+
* gated. Instead, every gate middleware in this package carries METADATA (what it
|
|
9
|
+
* requires), and {@link buildRouteTable} walks an assembled Express app and
|
|
10
|
+
* resolves, per method + path, the full chain of gates Express would run before
|
|
11
|
+
* the handler: permissions, system-admin, service principal, step-up, feature,
|
|
12
|
+
* token scope, and the audit actions the route declares via {@link audited}.
|
|
13
|
+
*
|
|
14
|
+
* Express 5 (router@2) does not keep a mount's path string on its layer, so this
|
|
15
|
+
* module records it: on import it wraps `Router.prototype.use` to stamp each new
|
|
16
|
+
* layer with the path it was mounted at. api-core is imported before any route
|
|
17
|
+
* module registers a route, so every layer an app builds is stamped. The wrap
|
|
18
|
+
* only adds a symbol property; routing behaviour is unchanged.
|
|
19
|
+
*/
|
|
20
|
+
import express from 'express';
|
|
21
|
+
// Symbol.for, not Symbol(): a test that loads api-core twice (ESM + a
|
|
22
|
+
// requireActual copy) must still see tags set by the other copy.
|
|
23
|
+
const GATES = Symbol.for('pipeline-builder.route-gates');
|
|
24
|
+
const LAYER_PATH = Symbol.for('pipeline-builder.layer-path');
|
|
25
|
+
const RECORDER_INSTALLED = Symbol.for('pipeline-builder.route-recorder');
|
|
26
|
+
/**
|
|
27
|
+
* Attach gate metadata to a middleware function (appends to any existing tags)
|
|
28
|
+
* and return the same function. Use it for a service-local gate so the route
|
|
29
|
+
* table knows what it enforces — e.g. a handler-level `requireComplianceWrite`
|
|
30
|
+
* tagged `{ kind: 'permission', mode: 'any', permissions: ['compliance:write'] }`.
|
|
31
|
+
*/
|
|
32
|
+
export function tagRouteGate(fn, ...gates) {
|
|
33
|
+
const t = fn;
|
|
34
|
+
Object.defineProperty(t, GATES, { value: [...(t[GATES] ?? []), ...gates], configurable: true, enumerable: false, writable: true });
|
|
35
|
+
return fn;
|
|
36
|
+
}
|
|
37
|
+
/** The gate metadata attached to a middleware function (empty when untagged). */
|
|
38
|
+
export function getRouteGates(fn) {
|
|
39
|
+
if (typeof fn !== 'function')
|
|
40
|
+
return [];
|
|
41
|
+
return fn[GATES] ?? [];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Declare the audit action(s) a route emits. A no-op middleware whose only job
|
|
45
|
+
* is to put the action on the route table, so the coverage test can require an
|
|
46
|
+
* audit declaration on every write route. Place it in the route's chain:
|
|
47
|
+
* `router.post('/', audited('pipeline.create'), handler)`. The handler still
|
|
48
|
+
* emits the event itself.
|
|
49
|
+
*/
|
|
50
|
+
export function audited(...actions) {
|
|
51
|
+
return tagRouteGate(function auditedRoute(_req, _res, next) { next(); }, { kind: 'audit', actions });
|
|
52
|
+
}
|
|
53
|
+
function installRouteRecorder() {
|
|
54
|
+
const proto = express.Router.prototype;
|
|
55
|
+
if (proto[RECORDER_INSTALLED])
|
|
56
|
+
return;
|
|
57
|
+
const originalUse = proto.use;
|
|
58
|
+
proto.use = function recordingUse(...args) {
|
|
59
|
+
const before = this.stack.length;
|
|
60
|
+
const result = originalUse.apply(this, args);
|
|
61
|
+
// Same path disambiguation as router@2's `use`: a leading non-function
|
|
62
|
+
// (possibly nested in arrays) is the path; otherwise the mount is '/'.
|
|
63
|
+
let first = args[0];
|
|
64
|
+
while (Array.isArray(first) && first.length > 0)
|
|
65
|
+
first = first[0];
|
|
66
|
+
const path = typeof first === 'function' ? '/' : String(args[0]);
|
|
67
|
+
for (let i = before; i < this.stack.length; i++) {
|
|
68
|
+
Object.defineProperty(this.stack[i], LAYER_PATH, { value: path, enumerable: false, configurable: true });
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
};
|
|
72
|
+
Object.defineProperty(proto, RECORDER_INSTALLED, { value: true, enumerable: false });
|
|
73
|
+
}
|
|
74
|
+
installRouteRecorder();
|
|
75
|
+
function joinPath(prefix, path) {
|
|
76
|
+
const joined = `${prefix.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`;
|
|
77
|
+
return joined.length > 1 ? joined.replace(/\/+$/, '') : '/';
|
|
78
|
+
}
|
|
79
|
+
function isRouter(handle) {
|
|
80
|
+
return typeof handle === 'function' && Array.isArray(handle.stack);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Whether a middleware layer (mounted under `prefix`) runs for `fullPath`.
|
|
84
|
+
* Mirrors `Layer.prototype.match`, including its fast path: a layer added with
|
|
85
|
+
* no path (`router.use(gate)`) matches EVERY path under its router — which is
|
|
86
|
+
* exactly how the shared `requirePermission` gates between two `router.use(...)`
|
|
87
|
+
* mounts reach the routers registered after them.
|
|
88
|
+
*/
|
|
89
|
+
function applies(c, fullPath) {
|
|
90
|
+
const base = c.prefix === '/' ? '' : c.prefix;
|
|
91
|
+
if (base && fullPath !== base && !fullPath.startsWith(`${base}/`))
|
|
92
|
+
return false;
|
|
93
|
+
if (c.layer.slash)
|
|
94
|
+
return true;
|
|
95
|
+
const rel = fullPath.slice(base.length) || '/';
|
|
96
|
+
return c.layer.matchers.some((m) => m(rel) !== false);
|
|
97
|
+
}
|
|
98
|
+
function collectGates(fns) {
|
|
99
|
+
return fns.flatMap((fn) => getRouteGates(fn));
|
|
100
|
+
}
|
|
101
|
+
function toEntry(method, path, gates) {
|
|
102
|
+
const entry = {
|
|
103
|
+
method,
|
|
104
|
+
path,
|
|
105
|
+
auth: false,
|
|
106
|
+
permissions: [],
|
|
107
|
+
systemAdmin: false,
|
|
108
|
+
servicePrincipal: false,
|
|
109
|
+
internalCallers: [],
|
|
110
|
+
stepUp: false,
|
|
111
|
+
stepUpMethods: [],
|
|
112
|
+
minAssurance: 0,
|
|
113
|
+
features: [],
|
|
114
|
+
scopes: [],
|
|
115
|
+
audit: [],
|
|
116
|
+
};
|
|
117
|
+
for (const g of gates) {
|
|
118
|
+
switch (g.kind) {
|
|
119
|
+
case 'auth':
|
|
120
|
+
entry.auth = true;
|
|
121
|
+
break;
|
|
122
|
+
case 'permission':
|
|
123
|
+
entry.permissions.push({ mode: g.mode, permissions: [...g.permissions], allowService: g.allowService === true });
|
|
124
|
+
break;
|
|
125
|
+
case 'systemAdmin':
|
|
126
|
+
entry.systemAdmin = true;
|
|
127
|
+
break;
|
|
128
|
+
case 'servicePrincipal':
|
|
129
|
+
entry.servicePrincipal = true;
|
|
130
|
+
break;
|
|
131
|
+
case 'internalService':
|
|
132
|
+
for (const c of g.callers)
|
|
133
|
+
if (!entry.internalCallers.includes(c))
|
|
134
|
+
entry.internalCallers.push(c);
|
|
135
|
+
break;
|
|
136
|
+
case 'stepUp':
|
|
137
|
+
entry.stepUp = true;
|
|
138
|
+
for (const m of g.methods ?? [])
|
|
139
|
+
if (!entry.stepUpMethods.includes(m))
|
|
140
|
+
entry.stepUpMethods.push(m);
|
|
141
|
+
break;
|
|
142
|
+
case 'assurance':
|
|
143
|
+
// Several gates may apply; the STRICTEST wins, since every one of them runs.
|
|
144
|
+
if (g.minAssurance > entry.minAssurance)
|
|
145
|
+
entry.minAssurance = g.minAssurance;
|
|
146
|
+
if (g.maxAge !== undefined)
|
|
147
|
+
entry.maxAge = entry.maxAge === undefined ? g.maxAge : Math.min(entry.maxAge, g.maxAge);
|
|
148
|
+
break;
|
|
149
|
+
case 'feature':
|
|
150
|
+
if (!entry.features.includes(g.feature))
|
|
151
|
+
entry.features.push(g.feature);
|
|
152
|
+
break;
|
|
153
|
+
case 'scope':
|
|
154
|
+
if (!entry.scopes.includes(g.scope))
|
|
155
|
+
entry.scopes.push(g.scope);
|
|
156
|
+
break;
|
|
157
|
+
case 'audit':
|
|
158
|
+
for (const a of g.actions)
|
|
159
|
+
if (!entry.audit.includes(a))
|
|
160
|
+
entry.audit.push(a);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
entry.internalCallers.sort();
|
|
165
|
+
entry.stepUpMethods.sort();
|
|
166
|
+
return entry;
|
|
167
|
+
}
|
|
168
|
+
function walk(stack, prefix, inherited, out, seen) {
|
|
169
|
+
const local = [];
|
|
170
|
+
for (const layer of stack) {
|
|
171
|
+
if (layer.route) {
|
|
172
|
+
const paths = Array.isArray(layer.route.path) ? layer.route.path : [layer.route.path];
|
|
173
|
+
const methods = Object.keys(layer.route.methods).map((m) => (m === '_all' ? '*' : m.toUpperCase()));
|
|
174
|
+
for (const routePath of paths) {
|
|
175
|
+
const full = joinPath(prefix, String(routePath));
|
|
176
|
+
const chain = [...inherited, ...local].filter((c) => applies(c, full)).map((c) => c.layer.handle);
|
|
177
|
+
for (const method of methods) {
|
|
178
|
+
const key = `${method} ${full}`;
|
|
179
|
+
// First registration wins, as in Express: a later duplicate is unreachable.
|
|
180
|
+
if (seen.has(key))
|
|
181
|
+
continue;
|
|
182
|
+
seen.add(key);
|
|
183
|
+
const routeFns = layer.route.stack
|
|
184
|
+
.filter((l) => l.method === undefined || l.method.toUpperCase() === method || method === '*')
|
|
185
|
+
.map((l) => l.handle);
|
|
186
|
+
out.push(toEntry(method, full, collectGates([...chain, ...routeFns])));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else if (isRouter(layer.handle)) {
|
|
191
|
+
const mountPath = joinPath(prefix, layer[LAYER_PATH] ?? '/');
|
|
192
|
+
// Middleware that runs before this router (ancestors + earlier siblings)
|
|
193
|
+
// is inherited; applicability is re-checked against each full route path.
|
|
194
|
+
walk(layer.handle.stack, mountPath, [...inherited, ...local], out, seen);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
local.push({ layer, prefix });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Build the route table of an assembled Express app (or router): one entry per
|
|
203
|
+
* reachable method + path with every gate that runs before its handler.
|
|
204
|
+
* HEAD is folded into GET, as Express does.
|
|
205
|
+
*/
|
|
206
|
+
export function buildRouteTable(app) {
|
|
207
|
+
const root = app.router ?? app;
|
|
208
|
+
const out = [];
|
|
209
|
+
if (!root || !Array.isArray(root.stack))
|
|
210
|
+
return out;
|
|
211
|
+
walk(root.stack, '/', [], out, new Set());
|
|
212
|
+
return out.sort((a, b) => (a.path === b.path ? a.method.localeCompare(b.method) : a.path.localeCompare(b.path)));
|
|
213
|
+
}
|
|
214
|
+
/** Whether a method mutates state (everything except GET/HEAD/OPTIONS). */
|
|
215
|
+
export function isWriteMethod(method) {
|
|
216
|
+
return !['GET', 'HEAD', 'OPTIONS'].includes(method.toUpperCase());
|
|
217
|
+
}
|
|
218
|
+
/** Summary counts for a startup log line. */
|
|
219
|
+
export function summarizeRouteTable(table) {
|
|
220
|
+
const gated = (e) => e.permissions.length > 0 || e.systemAdmin || e.servicePrincipal;
|
|
221
|
+
const writes = table.filter((e) => isWriteMethod(e.method));
|
|
222
|
+
return {
|
|
223
|
+
routes: table.length,
|
|
224
|
+
writes: writes.length,
|
|
225
|
+
ungated: table.filter((e) => !gated(e)).length,
|
|
226
|
+
unaudited: writes.filter((e) => e.audit.length === 0).length,
|
|
227
|
+
internal: table.filter((e) => e.internalCallers.length > 0).length,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUtdGFibGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbWlkZGxld2FyZS9yb3V0ZS10YWJsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDOzs7Ozs7Ozs7Ozs7Ozs7O0dBZ0JHO0FBRUgsT0FBTyxPQUFPLE1BQU0sU0FBUyxDQUFDO0FBbUI5QixzRUFBc0U7QUFDdEUsaUVBQWlFO0FBQ2pFLE1BQU0sS0FBSyxHQUFHLE1BQU0sQ0FBQyxHQUFHLENBQUMsOEJBQThCLENBQUMsQ0FBQztBQUN6RCxNQUFNLFVBQVUsR0FBRyxNQUFNLENBQUMsR0FBRyxDQUFDLDZCQUE2QixDQUFDLENBQUM7QUFDN0QsTUFBTSxrQkFBa0IsR0FBRyxNQUFNLENBQUMsR0FBRyxDQUFDLGlDQUFpQyxDQUFDLENBQUM7QUFJekU7Ozs7O0dBS0c7QUFDSCxNQUFNLFVBQVUsWUFBWSxDQUFtQixFQUFLLEVBQUUsR0FBRyxLQUFrQjtJQUN6RSxNQUFNLENBQUMsR0FBRyxFQUFnQixDQUFDO0lBQzNCLE1BQU0sQ0FBQyxjQUFjLENBQUMsQ0FBQyxFQUFFLEtBQUssRUFBRSxFQUFFLEtBQUssRUFBRSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxDQUFDLEVBQUUsR0FBRyxLQUFLLENBQUMsRUFBRSxZQUFZLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7SUFDbkksT0FBTyxFQUFFLENBQUM7QUFDWixDQUFDO0FBRUQsaUZBQWlGO0FBQ2pGLE1BQU0sVUFBVSxhQUFhLENBQUMsRUFBVztJQUN2QyxJQUFJLE9BQU8sRUFBRSxLQUFLLFVBQVU7UUFBRSxPQUFPLEVBQUUsQ0FBQztJQUN4QyxPQUFRLEVBQWEsQ0FBQyxLQUFLLENBQUMsSUFBSSxFQUFFLENBQUM7QUFDckMsQ0FBQztBQUVEOzs7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SyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBQzVELE9BQU87UUFDTCxNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU07UUFDcEIsTUFBTSxFQUFFLE1BQU0sQ0FBQyxNQUFNO1FBQ3JCLE9BQU8sRUFBRSxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU07UUFDOUMsU0FBUyxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsTUFBTSxLQUFLLENBQUMsQ0FBQyxDQUFDLE1BQU07UUFDNUQsUUFBUSxFQUFFLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxlQUFlLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDLE1BQU07S0FDbkUsQ0FBQztBQUNKLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBJbnRyb3NwZWN0YWJsZSByb3V0ZSB0YWJsZS5cbiAqXG4gKiBBdXRob3JpemF0aW9uIGlzIHNwbGl0IGJldHdlZW4gcm91dGVyIG1vdW50cyAoYGFwcC51c2UoJy94JywgZ2F0ZSwgcm91dGVyKWApXG4gKiBhbmQgaW5kaXZpZHVhbCByb3V0ZXMsIHNvIGdyZXBwaW5nIHNvdXJjZSBjYW4ndCBwcm92ZSB0aGF0IGV2ZXJ5IHJvdXRlIGlzXG4gKiBnYXRlZC4gSW5zdGVhZCwgZXZlcnkgZ2F0ZSBtaWRkbGV3YXJlIGluIHRoaXMgcGFja2FnZSBjYXJyaWVzIE1FVEFEQVRBICh3aGF0IGl0XG4gKiByZXF1aXJlcyksIGFuZCB7QGxpbmsgYnVpbGRSb3V0ZVRhYmxlfSB3YWxrcyBhbiBhc3NlbWJsZWQgRXhwcmVzcyBhcHAgYW5kXG4gKiByZXNvbHZlcywgcGVyIG1ldGhvZCArIHBhdGgsIHRoZSBmdWxsIGNoYWluIG9mIGdhdGVzIEV4cHJlc3Mgd291bGQgcnVuIGJlZm9yZVxuICogdGhlIGhhbmRsZXI6IHBlcm1pc3Npb25zLCBzeXN0ZW0tYWRtaW4sIHNlcnZpY2UgcHJpbmNpcGFsLCBzdGVwLXVwLCBmZWF0dXJlLFxuICogdG9rZW4gc2NvcGUsIGFuZCB0aGUgYXVkaXQgYWN0aW9ucyB0aGUgcm91dGUgZGVjbGFyZXMgdmlhIHtAbGluayBhdWRpdGVkfS5cbiAqXG4gKiBFeHByZXNzIDUgKHJvdXRlckAyKSBkb2VzIG5vdCBrZWVwIGEgbW91bnQncyBwYXRoIHN0cmluZyBvbiBpdHMgbGF5ZXIsIHNvIHRoaXNcbiAqIG1vZHVsZSByZWNvcmRzIGl0OiBvbiBpbXBvcnQgaXQgd3JhcHMgYFJvdXRlci5wcm90b3R5cGUudXNlYCB0byBzdGFtcCBlYWNoIG5ld1xuICogbGF5ZXIgd2l0aCB0aGUgcGF0aCBpdCB3YXMgbW91bnRlZCBhdC4gYXBpLWNvcmUgaXMgaW1wb3J0ZWQgYmVmb3JlIGFueSByb3V0ZVxuICogbW9kdWxlIHJlZ2lzdGVycyBhIHJvdXRlLCBzbyBldmVyeSBsYXllciBhbiBhcHAgYnVpbGRzIGlzIHN0YW1wZWQuIFRoZSB3cmFwXG4gKiBvbmx5IGFkZHMgYSBzeW1ib2wgcHJvcGVydHk7IHJvdXRpbmcgYmVoYXZpb3VyIGlzIHVuY2hhbmdlZC5cbiAqL1xuXG5pbXBvcnQgZXhwcmVzcyBmcm9tICdleHByZXNzJztcbmltcG9ydCB0eXBlIHsgUGVybWlzc2lvbiB9IGZyb20gJy4uL3R5cGVzL3Blcm1pc3Npb25zLmpzJztcblxuLyoqIE9uZSByZXF1aXJlbWVudCBhIGdhdGUgbWlkZGxld2FyZSBlbmZvcmNlcy4gKi9cbmV4cG9ydCB0eXBlIFJvdXRlR2F0ZSA9XG4gIHwgeyBraW5kOiAnYXV0aCcgfVxuICB8IHsga2luZDogJ3Blcm1pc3Npb24nOyBtb2RlOiAnYW55JyB8ICdhbGwnOyBwZXJtaXNzaW9uczogcmVhZG9ubHkgUGVybWlzc2lvbltdOyBhbGxvd1NlcnZpY2U/OiBib29sZWFuIH1cbiAgfCB7IGtpbmQ6ICdzeXN0ZW1BZG1pbicgfVxuICB8IHsga2luZDogJ3NlcnZpY2VQcmluY2lwYWwnIH1cbiAgLyoqIEFuIElOVEVSTkFMIHJvdXRlOiB1c2VyIHRva2VucyByZWZ1c2VkIG91dHJpZ2h0LCBvbmx5IHRoZXNlIHNlcnZpY2VzIGFkbWl0dGVkLiAqL1xuICB8IHsga2luZDogJ2ludGVybmFsU2VydmljZSc7IGNhbGxlcnM6IHJlYWRvbmx5IHN0cmluZ1tdIH1cbiAgfCB7IGtpbmQ6ICdzdGVwVXAnOyBtZXRob2RzPzogcmVhZG9ubHkgc3RyaW5nW10gfVxuICAvKiogVGhlIHdob2xlIFNFU1NJT04gbXVzdCBiZSBNRkEtZ3JhZGUgKGByZXF1aXJlQXV0aCh7IG1pbkFzc3VyYW5jZSB9KWApLCBhbmRcbiAgICogIG9wdGlvbmFsbHkgbm8gb2xkZXIgdGhhbiBgbWF4QWdlYCBzZWNvbmRzLiAqL1xuICB8IHsga2luZDogJ2Fzc3VyYW5jZSc7IG1pbkFzc3VyYW5jZTogMSB8IDI7IG1heEFnZT86IG51bWJlciB9XG4gIHwgeyBraW5kOiAnZmVhdHVyZSc7IGZlYXR1cmU6IHN0cmluZyB9XG4gIHwgeyBraW5kOiAnc2NvcGUnOyBzY29wZTogc3RyaW5nIH1cbiAgfCB7IGtpbmQ6ICdhdWRpdCc7IGFjdGlvbnM6IHJlYWRvbmx5IHN0cmluZ1tdIH07XG5cbi8vIFN5bWJvbC5mb3IsIG5vdCBTeW1ib2woKTogYSB0ZXN0IHRoYXQgbG9hZHMgYXBpLWNvcmUgdHdpY2UgKEVTTSArIGFcbi8vIHJlcXVpcmVBY3R1YWwgY29weSkgbXVzdCBzdGlsbCBzZWUgdGFncyBzZXQgYnkgdGhlIG90aGVyIGNvcHkuXG5jb25zdCBHQVRFUyA9IFN5bWJvbC5mb3IoJ3BpcGVsaW5lLWJ1aWxkZXIucm91dGUtZ2F0ZXMnKTtcbmNvbnN0IExBWUVSX1BBVEggPSBTeW1ib2wuZm9yKCdwaXBlbGluZS1idWlsZGVyLmxheWVyLXBhdGgnKTtcbmNvbnN0IFJFQ09SREVSX0lOU1RBTExFRCA9IFN5bWJvbC5mb3IoJ3BpcGVsaW5lLWJ1aWxkZXIucm91dGUtcmVjb3JkZXInKTtcblxudHlwZSBUYWdnZWQgPSB7IFtHQVRFU10/OiBSb3V0ZUdhdGVbXSB9O1xuXG4vKipcbiAqIEF0dGFjaCBnYXRlIG1ldGFkYXRhIHRvIGEgbWlkZGxld2FyZSBmdW5jdGlvbiAoYXBwZW5kcyB0byBhbnkgZXhpc3RpbmcgdGFncylcbiAqIGFuZCByZXR1cm4gdGhlIHNhbWUgZnVuY3Rpb24uIFVzZSBpdCBmb3IgYSBzZXJ2aWNlLWxvY2FsIGdhdGUgc28gdGhlIHJvdXRlXG4gKiB0YWJsZSBrbm93cyB3aGF0IGl0IGVuZm9yY2VzIOKAlCBlLmcuIGEgaGFuZGxlci1sZXZlbCBgcmVxdWlyZUNvbXBsaWFuY2VXcml0ZWBcbiAqIHRhZ2dlZCBgeyBraW5kOiAncGVybWlzc2lvbicsIG1vZGU6ICdhbnknLCBwZXJtaXNzaW9uczogWydjb21wbGlhbmNlOndyaXRlJ10gfWAuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB0YWdSb3V0ZUdhdGU8VCBleHRlbmRzIG9iamVjdD4oZm46IFQsIC4uLmdhdGVzOiBSb3V0ZUdhdGVbXSk6IFQge1xuICBjb25zdCB0ID0gZm4gYXMgVCAmIFRhZ2dlZDtcbiAgT2JqZWN0LmRlZmluZVByb3BlcnR5KHQsIEdBVEVTLCB7IHZhbHVlOiBbLi4uKHRbR0FURVNdID8/IFtdKSwgLi4uZ2F0ZXNdLCBjb25maWd1cmFibGU6IHRydWUsIGVudW1lcmFibGU6IGZhbHNlLCB3cml0YWJsZTogdHJ1ZSB9KTtcbiAgcmV0dXJuIGZuO1xufVxuXG4vKiogVGhlIGdhdGUgbWV0YWRhdGEgYXR0YWNoZWQgdG8gYSBtaWRkbGV3YXJlIGZ1bmN0aW9uIChlbXB0eSB3aGVuIHVudGFnZ2VkKS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRSb3V0ZUdhdGVzKGZuOiB1bmtub3duKTogcmVhZG9ubHkgUm91dGVHYXRlW10ge1xuICBpZiAodHlwZW9mIGZuICE9PSAnZnVuY3Rpb24nKSByZXR1cm4gW107XG4gIHJldHVybiAoZm4gYXMgVGFnZ2VkKVtHQVRFU10gPz8gW107XG59XG5cbi8qKlxuICogRGVjbGFyZSB0aGUgYXVkaXQgYWN0aW9uKHMpIGEgcm91dGUgZW1pdHMuIEEgbm8tb3AgbWlkZGxld2FyZSB3aG9zZSBvbmx5IGpvYlxuICogaXMgdG8gcHV0IHRoZSBhY3Rpb24gb24gdGhlIHJvdXRlIHRhYmxlLCBzbyB0aGUgY292ZXJhZ2UgdGVzdCBjYW4gcmVxdWlyZSBhblxuICogYXVkaXQgZGVjbGFyYXRpb24gb24gZXZlcnkgd3JpdGUgcm91dGUuIFBsYWNlIGl0IGluIHRoZSByb3V0ZSdzIGNoYWluOlxuICogYHJvdXRlci5wb3N0KCcvJywgYXVkaXRlZCgncGlwZWxpbmUuY3JlYXRlJyksIGhhbmRsZXIpYC4gVGhlIGhhbmRsZXIgc3RpbGxcbiAqIGVtaXRzIHRoZSBldmVudCBpdHNlbGYuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhdWRpdGVkKC4uLmFjdGlvbnM6IHN0cmluZ1tdKTogKHJlcTogdW5rbm93biwgcmVzOiB1bmtub3duLCBuZXh0OiAoKSA9PiB2b2lkKSA9PiB2b2lkIHtcbiAgcmV0dXJuIHRhZ1JvdXRlR2F0ZShmdW5jdGlvbiBhdWRpdGVkUm91dGUoX3JlcTogdW5rbm93biwgX3JlczogdW5rbm93biwgbmV4dDogKCkgPT4gdm9pZCkgeyBuZXh0KCk7IH0sIHsga2luZDogJ2F1ZGl0JywgYWN0aW9ucyB9KTtcbn1cblxuLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4vLyBNb3VudC1wYXRoIHJlY29yZGluZ1xuLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbmludGVyZmFjZSBMYXllciB7XG4gIGhhbmRsZTogdW5rbm93bjtcbiAgcm91dGU/OiB7IHBhdGg6IHN0cmluZyB8IHN0cmluZ1tdOyBtZXRob2RzOiBSZWNvcmQ8c3RyaW5nLCBib29sZWFuPjsgc3RhY2s6IEFycmF5PHsgbWV0aG9kPzogc3RyaW5nOyBoYW5kbGU6IHVua25vd24gfT4gfTtcbiAgbWF0Y2hlcnM6IEFycmF5PChwYXRoOiBzdHJpbmcpID0+IHVua25vd24+O1xuICAvKiogcm91dGVyQDIncyBcIm1vdW50ZWQgYXQgJy8nIHdpdGggZW5kOmZhbHNlXCIgZmFzdCBwYXRoIOKAlCBtYXRjaGVzIEFOWSBwYXRoLiAqL1xuICBzbGFzaD86IGJvb2xlYW47XG4gIFtMQVlFUl9QQVRIXT86IHN0cmluZztcbn1cblxuaW50ZXJmYWNlIFJvdXRlckxpa2UgeyBzdGFjazogTGF5ZXJbXSB9XG5cbmZ1bmN0aW9uIGluc3RhbGxSb3V0ZVJlY29yZGVyKCk6IHZvaWQge1xuICBjb25zdCBwcm90byA9IChleHByZXNzLlJvdXRlciBhcyB1bmtub3duIGFzIHsgcHJvdG90eXBlOiBSZWNvcmQ8c3RyaW5nIHwgc3ltYm9sLCB1bmtub3duPiB9KS5wcm90b3R5cGU7XG4gIGlmIChwcm90b1tSRUNPUkRFUl9JTlNUQUxMRURdKSByZXR1cm47XG4gIGNvbnN0IG9yaWdpbmFsVXNlID0gcHJvdG8udXNlIGFzICh0aGlzOiBSb3V0ZXJMaWtlLCAuLi5hcmdzOiB1bmtub3duW10pID0+IHVua25vd247XG4gIHByb3RvLnVzZSA9IGZ1bmN0aW9uIHJlY29yZGluZ1VzZSh0aGlzOiBSb3V0ZXJMaWtlLCAuLi5hcmdzOiB1bmtub3duW10pOiB1bmtub3duIHtcbiAgICBjb25zdCBiZWZvcmUgPSB0aGlzLnN0YWNrLmxlbmd0aDtcbiAgICBjb25zdCByZXN1bHQgPSBvcmlnaW5hbFVzZS5hcHBseSh0aGlzLCBhcmdzKTtcbiAgICAvLyBTYW1lIHBhdGggZGlzYW1iaWd1YXRpb24gYXMgcm91dGVyQDIncyBgdXNlYDogYSBsZWFkaW5nIG5vbi1mdW5jdGlvblxuICAgIC8vIChwb3NzaWJseSBuZXN0ZWQgaW4gYXJyYXlzKSBpcyB0aGUgcGF0aDsgb3RoZXJ3aXNlIHRoZSBtb3VudCBpcyAnLycuXG4gICAgbGV0IGZpcnN0OiB1bmtub3duID0gYXJnc1swXTtcbiAgICB3aGlsZSAoQXJyYXkuaXNBcnJheShmaXJzdCkgJiYgZmlyc3QubGVuZ3RoID4gMCkgZmlyc3QgPSBmaXJzdFswXTtcbiAgICBjb25zdCBwYXRoID0gdHlwZW9mIGZpcnN0ID09PSAnZnVuY3Rpb24nID8gJy8nIDogU3RyaW5nKGFyZ3NbMF0pO1xuICAgIGZvciAobGV0IGkgPSBiZWZvcmU7IGkgPCB0aGlzLnN0YWNrLmxlbmd0aDsgaSsrKSB7XG4gICAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkodGhpcy5zdGFja1tpXSwgTEFZRVJfUEFUSCwgeyB2YWx1ZTogcGF0aCwgZW51bWVyYWJsZTogZmFsc2UsIGNvbmZpZ3VyYWJsZTogdHJ1ZSB9KTtcbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfTtcbiAgT2JqZWN0LmRlZmluZVByb3BlcnR5KHByb3RvLCBSRUNPUkRFUl9JTlNUQUxMRUQsIHsgdmFsdWU6IHRydWUsIGVudW1lcmFibGU6IGZhbHNlIH0pO1xufVxuXG5pbnN0YWxsUm91dGVSZWNvcmRlcigpO1xuXG4vLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbi8vIFRhYmxlIGNvbnN0cnVjdGlvblxuLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbi8qKiBSZXNvbHZlZCByZXF1aXJlbWVudHMgZm9yIG9uZSBtZXRob2QgKyBwYXRoLiAqL1xuZXhwb3J0IGludGVyZmFjZSBSb3V0ZVRhYmxlRW50cnkge1xuICBtZXRob2Q6IHN0cmluZztcbiAgcGF0aDogc3RyaW5nO1xuICAvKiogQXV0aGVudGljYXRpb24gcnVucyBiZWZvcmUgdGhlIGhhbmRsZXIgKHJlcXVpcmVBdXRoKS4gKi9cbiAgYXV0aDogYm9vbGVhbjtcbiAgLyoqIEVhY2ggcGVybWlzc2lvbiBnYXRlIGluIHRoZSBjaGFpbiAoYWxsIG11c3QgcGFzczsgZWFjaCBpcyBhbnktb2Ygb3IgYWxsLW9mKS4gKi9cbiAgcGVybWlzc2lvbnM6IEFycmF5PHsgbW9kZTogJ2FueScgfCAnYWxsJzsgcGVybWlzc2lvbnM6IFBlcm1pc3Npb25bXTsgYWxsb3dTZXJ2aWNlOiBib29sZWFuIH0+O1xuICBzeXN0ZW1BZG1pbjogYm9vbGVhbjtcbiAgc2VydmljZVByaW5jaXBhbDogYm9vbGVhbjtcbiAgLyoqXG4gICAqIFRoZSBzZXJ2aWNlcyBhbGxvd2VkIHRvIGNhbGwgdGhpcyByb3V0ZSB3aGVuIGl0IGlzIElOVEVSTkFMIChgcmVxdWlyZUludGVybmFsU2VydmljZWApLlxuICAgKiBBbiBlbXB0eSBhcnJheSBtZWFucyB0aGUgcm91dGUgaXMgbm90IGludGVybmFsIOKAlCBhIHVzZXIgdG9rZW4gbWF5IHJlYWNoIGl0LFxuICAgKiBzdWJqZWN0IHRvIHRoZSBvdGhlciBnYXRlcy4gU29ydGVkLCBzbyB0aGUgZ2VuZXJhdGVkIHRhYmxlIGlzIHN0YWJsZS5cbiAgICovXG4gIGludGVybmFsQ2FsbGVyczogc3RyaW5nW107XG4gIHN0ZXBVcDogYm9vbGVhbjtcbiAgLyoqIFdoZW4gdGhlIHN0ZXAtdXAgZ2F0ZSBuYW1lcyB0aGUgZmFjdG9ycyB0aGF0IG1heSBlYXJuIGl0IChgWyd3ZWJhdXRobicsXG4gICAqICAndG90cCddYCBvbiB0aGUgbW9zdCBkYW5nZXJvdXMgcm91dGVzKSwgdGhlIGFsbG93ZWQgbWV0aG9kcy4gRW1wdHkgbWVhbnNcbiAgICogIGFueSBmYWN0b3Igc2F0aXNmaWVzIGl0LiBTb3J0ZWQsIHNvIHRoZSBnZW5lcmF0ZWQgdGFibGUgaXMgc3RhYmxlLiAqL1xuICBzdGVwVXBNZXRob2RzOiBzdHJpbmdbXTtcbiAgLyoqIEhpZ2hlc3QgYG1pbkFzc3VyYW5jZWAgYW55IGdhdGUgaW4gdGhlIGNoYWluIGRlbWFuZHM7IDAgd2hlbiBub25lIGRvZXMuICovXG4gIG1pbkFzc3VyYW5jZTogMCB8IDEgfCAyO1xuICAvKiogVGlnaHRlc3QgYG1heEFnZWAgKHNlY29uZHMpIGFueSBhc3N1cmFuY2UgZ2F0ZSBpbiB0aGUgY2hhaW4gZGVtYW5kcy4gKi9cbiAgbWF4QWdlPzogbnVtYmVyO1xuICBmZWF0dXJlczogc3RyaW5nW107XG4gIHNjb3Blczogc3RyaW5nW107XG4gIGF1ZGl0OiBzdHJpbmdbXTtcbn1cblxuaW50ZXJmYWNlIENhbmRpZGF0ZSB7IGxheWVyOiBMYXllcjsgcHJlZml4OiBzdHJpbmcgfVxuXG5mdW5jdGlvbiBqb2luUGF0aChwcmVmaXg6IHN0cmluZywgcGF0aDogc3RyaW5nKTogc3RyaW5nIHtcbiAgY29uc3Qgam9pbmVkID0gYCR7cHJlZml4LnJlcGxhY2UoL1xcLyskLywgJycpfS8ke3BhdGgucmVwbGFjZSgvXlxcLysvLCAnJyl9YDtcbiAgcmV0dXJuIGpvaW5lZC5sZW5ndGggPiAxID8gam9pbmVkLnJlcGxhY2UoL1xcLyskLywgJycpIDogJy8nO1xufVxuXG5mdW5jdGlvbiBpc1JvdXRlcihoYW5kbGU6IHVua25vd24pOiBoYW5kbGUgaXMgUm91dGVyTGlrZSB7XG4gIHJldHVybiB0eXBlb2YgaGFuZGxlID09PSAnZnVuY3Rpb24nICYmIEFycmF5LmlzQXJyYXkoKGhhbmRsZSBhcyBQYXJ0aWFsPFJvdXRlckxpa2U+KS5zdGFjayk7XG59XG5cbi8qKlxuICogV2hldGhlciBhIG1pZGRsZXdhcmUgbGF5ZXIgKG1vdW50ZWQgdW5kZXIgYHByZWZpeGApIHJ1bnMgZm9yIGBmdWxsUGF0aGAuXG4gKiBNaXJyb3JzIGBMYXllci5wcm90b3R5cGUubWF0Y2hgLCBpbmNsdWRpbmcgaXRzIGZhc3QgcGF0aDogYSBsYXllciBhZGRlZCB3aXRoXG4gKiBubyBwYXRoIChgcm91dGVyLnVzZShnYXRlKWApIG1hdGNoZXMgRVZFUlkgcGF0aCB1bmRlciBpdHMgcm91dGVyIOKAlCB3aGljaCBpc1xuICogZXhhY3RseSBob3cgdGhlIHNoYXJlZCBgcmVxdWlyZVBlcm1pc3Npb25gIGdhdGVzIGJldHdlZW4gdHdvIGByb3V0ZXIudXNlKC4uLilgXG4gKiBtb3VudHMgcmVhY2ggdGhlIHJvdXRlcnMgcmVnaXN0ZXJlZCBhZnRlciB0aGVtLlxuICovXG5mdW5jdGlvbiBhcHBsaWVzKGM6IENhbmRpZGF0ZSwgZnVsbFBhdGg6IHN0cmluZyk6IGJvb2xlYW4ge1xuICBjb25zdCBiYXNlID0gYy5wcmVmaXggPT09ICcvJyA/ICcnIDogYy5wcmVmaXg7XG4gIGlmIChiYXNlICYmIGZ1bGxQYXRoICE9PSBiYXNlICYmICFmdWxsUGF0aC5zdGFydHNXaXRoKGAke2Jhc2V9L2ApKSByZXR1cm4gZmFsc2U7XG4gIGlmIChjLmxheWVyLnNsYXNoKSByZXR1cm4gdHJ1ZTtcbiAgY29uc3QgcmVsID0gZnVsbFBhdGguc2xpY2UoYmFzZS5sZW5ndGgpIHx8ICcvJztcbiAgcmV0dXJuIGMubGF5ZXIubWF0Y2hlcnMuc29tZSgobSkgPT4gbShyZWwpICE9PSBmYWxzZSk7XG59XG5cbmZ1bmN0aW9uIGNvbGxlY3RHYXRlcyhmbnM6IHVua25vd25bXSk6IFJvdXRlR2F0ZVtdIHtcbiAgcmV0dXJuIGZucy5mbGF0TWFwKChmbikgPT4gZ2V0Um91dGVHYXRlcyhmbikpO1xufVxuXG5mdW5jdGlvbiB0b0VudHJ5KG1ldGhvZDogc3RyaW5nLCBwYXRoOiBzdHJpbmcsIGdhdGVzOiBSb3V0ZUdhdGVbXSk6IFJvdXRlVGFibGVFbnRyeSB7XG4gIGNvbnN0IGVudHJ5OiBSb3V0ZVRhYmxlRW50cnkgPSB7XG4gICAgbWV0aG9kLFxuICAgIHBhdGgsXG4gICAgYXV0aDogZmFsc2UsXG4gICAgcGVybWlzc2lvbnM6IFtdLFxuICAgIHN5c3RlbUFkbWluOiBmYWxzZSxcbiAgICBzZXJ2aWNlUHJpbmNpcGFsOiBmYWxzZSxcbiAgICBpbnRlcm5hbENhbGxlcnM6IFtdLFxuICAgIHN0ZXBVcDogZmFsc2UsXG4gICAgc3RlcFVwTWV0aG9kczogW10sXG4gICAgbWluQXNzdXJhbmNlOiAwLFxuICAgIGZlYXR1cmVzOiBbXSxcbiAgICBzY29wZXM6IFtdLFxuICAgIGF1ZGl0OiBbXSxcbiAgfTtcbiAgZm9yIChjb25zdCBnIG9mIGdhdGVzKSB7XG4gICAgc3dpdGNoIChnLmtpbmQpIHtcbiAgICAgIGNhc2UgJ2F1dGgnOiBlbnRyeS5hdXRoID0gdHJ1ZTsgYnJlYWs7XG4gICAgICBjYXNlICdwZXJtaXNzaW9uJzogZW50cnkucGVybWlzc2lvbnMucHVzaCh7IG1vZGU6IGcubW9kZSwgcGVybWlzc2lvbnM6IFsuLi5nLnBlcm1pc3Npb25zXSwgYWxsb3dTZXJ2aWNlOiBnLmFsbG93U2VydmljZSA9PT0gdHJ1ZSB9KTsgYnJlYWs7XG4gICAgICBjYXNlICdzeXN0ZW1BZG1pbic6IGVudHJ5LnN5c3RlbUFkbWluID0gdHJ1ZTsgYnJlYWs7XG4gICAgICBjYXNlICdzZXJ2aWNlUHJpbmNpcGFsJzogZW50cnkuc2VydmljZVByaW5jaXBhbCA9IHRydWU7IGJyZWFrO1xuICAgICAgY2FzZSAnaW50ZXJuYWxTZXJ2aWNlJzogZm9yIChjb25zdCBjIG9mIGcuY2FsbGVycykgaWYgKCFlbnRyeS5pbnRlcm5hbENhbGxlcnMuaW5jbHVkZXMoYykpIGVudHJ5LmludGVybmFsQ2FsbGVycy5wdXNoKGMpOyBicmVhaztcbiAgICAgIGNhc2UgJ3N0ZXBVcCc6XG4gICAgICAgIGVudHJ5LnN0ZXBVcCA9IHRydWU7XG4gICAgICAgIGZvciAoY29uc3QgbSBvZiBnLm1ldGhvZHMgPz8gW10pIGlmICghZW50cnkuc3RlcFVwTWV0aG9kcy5pbmNsdWRlcyhtKSkgZW50cnkuc3RlcFVwTWV0aG9kcy5wdXNoKG0pO1xuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgJ2Fzc3VyYW5jZSc6XG4gICAgICAgIC8vIFNldmVyYWwgZ2F0ZXMgbWF5IGFwcGx5OyB0aGUgU1RSSUNURVNUIHdpbnMsIHNpbmNlIGV2ZXJ5IG9uZSBvZiB0aGVtIHJ1bnMuXG4gICAgICAgIGlmIChnLm1pbkFzc3VyYW5jZSA+IGVudHJ5Lm1pbkFzc3VyYW5jZSkgZW50cnkubWluQXNzdXJhbmNlID0gZy5taW5Bc3N1cmFuY2U7XG4gICAgICAgIGlmIChnLm1heEFnZSAhPT0gdW5kZWZpbmVkKSBlbnRyeS5tYXhBZ2UgPSBlbnRyeS5tYXhBZ2UgPT09IHVuZGVmaW5lZCA/IGcubWF4QWdlIDogTWF0aC5taW4oZW50cnkubWF4QWdlLCBnLm1heEFnZSk7XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSAnZmVhdHVyZSc6IGlmICghZW50cnkuZmVhdHVyZXMuaW5jbHVkZXMoZy5mZWF0dXJlKSkgZW50cnkuZmVhdHVyZXMucHVzaChnLmZlYXR1cmUpOyBicmVhaztcbiAgICAgIGNhc2UgJ3Njb3BlJzogaWYgKCFlbnRyeS5zY29wZXMuaW5jbHVkZXMoZy5zY29wZSkpIGVudHJ5LnNjb3Blcy5wdXNoKGcuc2NvcGUpOyBicmVhaztcbiAgICAgIGNhc2UgJ2F1ZGl0JzogZm9yIChjb25zdCBhIG9mIGcuYWN0aW9ucykgaWYgKCFlbnRyeS5hdWRpdC5pbmNsdWRlcyhhKSkgZW50cnkuYXVkaXQucHVzaChhKTsgYnJlYWs7XG4gICAgfVxuICB9XG4gIGVudHJ5LmludGVybmFsQ2FsbGVycy5zb3J0KCk7XG4gIGVudHJ5LnN0ZXBVcE1ldGhvZHMuc29ydCgpO1xuICByZXR1cm4gZW50cnk7XG59XG5cbmZ1bmN0aW9uIHdhbGsoc3RhY2s6IExheWVyW10sIHByZWZpeDogc3RyaW5nLCBpbmhlcml0ZWQ6IENhbmRpZGF0ZVtdLCBvdXQ6IFJvdXRlVGFibGVFbnRyeVtdLCBzZWVuOiBTZXQ8c3RyaW5nPik6IHZvaWQge1xuICBjb25zdCBsb2NhbDogQ2FuZGlkYXRlW10gPSBbXTtcbiAgZm9yIChjb25zdCBsYXllciBvZiBzdGFjaykge1xuICAgIGlmIChsYXllci5yb3V0ZSkge1xuICAgICAgY29uc3QgcGF0aHMgPSBBcnJheS5pc0FycmF5KGxheWVyLnJvdXRlLnBhdGgpID8gbGF5ZXIucm91dGUucGF0aCA6IFtsYXllci5yb3V0ZS5wYXRoXTtcbiAgICAgIGNvbnN0IG1ldGhvZHMgPSBPYmplY3Qua2V5cyhsYXllci5yb3V0ZS5tZXRob2RzKS5tYXAoKG0pID0+IChtID09PSAnX2FsbCcgPyAnKicgOiBtLnRvVXBwZXJDYXNlKCkpKTtcbiAgICAgIGZvciAoY29uc3Qgcm91dGVQYXRoIG9mIHBhdGhzKSB7XG4gICAgICAgIGNvbnN0IGZ1bGwgPSBqb2luUGF0aChwcmVmaXgsIFN0cmluZyhyb3V0ZVBhdGgpKTtcbiAgICAgICAgY29uc3QgY2hhaW4gPSBbLi4uaW5oZXJpdGVkLCAuLi5sb2NhbF0uZmlsdGVyKChjKSA9PiBhcHBsaWVzKGMsIGZ1bGwpKS5tYXAoKGMpID0+IGMubGF5ZXIuaGFuZGxlKTtcbiAgICAgICAgZm9yIChjb25zdCBtZXRob2Qgb2YgbWV0aG9kcykge1xuICAgICAgICAgIGNvbnN0IGtleSA9IGAke21ldGhvZH0gJHtmdWxsfWA7XG4gICAgICAgICAgLy8gRmlyc3QgcmVnaXN0cmF0aW9uIHdpbnMsIGFzIGluIEV4cHJlc3M6IGEgbGF0ZXIgZHVwbGljYXRlIGlzIHVucmVhY2hhYmxlLlxuICAgICAgICAgIGlmIChzZWVuLmhhcyhrZXkpKSBjb250aW51ZTtcbiAgICAgICAgICBzZWVuLmFkZChrZXkpO1xuICAgICAgICAgIGNvbnN0IHJvdXRlRm5zID0gbGF5ZXIucm91dGUuc3RhY2tcbiAgICAgICAgICAgIC5maWx0ZXIoKGwpID0+IGwubWV0aG9kID09PSB1bmRlZmluZWQgfHwgbC5tZXRob2QudG9VcHBlckNhc2UoKSA9PT0gbWV0aG9kIHx8IG1ldGhvZCA9PT0gJyonKVxuICAgICAgICAgICAgLm1hcCgobCkgPT4gbC5oYW5kbGUpO1xuICAgICAgICAgIG91dC5wdXNoKHRvRW50cnkobWV0aG9kLCBmdWxsLCBjb2xsZWN0R2F0ZXMoWy4uLmNoYWluLCAuLi5yb3V0ZUZuc10pKSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKGlzUm91dGVyKGxheWVyLmhhbmRsZSkpIHtcbiAgICAgIGNvbnN0IG1vdW50UGF0aCA9IGpvaW5QYXRoKHByZWZpeCwgbGF5ZXJbTEFZRVJfUEFUSF0gPz8gJy8nKTtcbiAgICAgIC8vIE1pZGRsZXdhcmUgdGhhdCBydW5zIGJlZm9yZSB0aGlzIHJvdXRlciAoYW5jZXN0b3JzICsgZWFybGllciBzaWJsaW5ncylcbiAgICAgIC8vIGlzIGluaGVyaXRlZDsgYXBwbGljYWJpbGl0eSBpcyByZS1jaGVja2VkIGFnYWluc3QgZWFjaCBmdWxsIHJvdXRlIHBhdGguXG4gICAgICB3YWxrKGxheWVyLmhhbmRsZS5zdGFjaywgbW91bnRQYXRoLCBbLi4uaW5oZXJpdGVkLCAuLi5sb2NhbF0sIG91dCwgc2Vlbik7XG4gICAgfSBlbHNlIHtcbiAgICAgIGxvY2FsLnB1c2goeyBsYXllciwgcHJlZml4IH0pO1xuICAgIH1cbiAgfVxufVxuXG4vKipcbiAqIEJ1aWxkIHRoZSByb3V0ZSB0YWJsZSBvZiBhbiBhc3NlbWJsZWQgRXhwcmVzcyBhcHAgKG9yIHJvdXRlcik6IG9uZSBlbnRyeSBwZXJcbiAqIHJlYWNoYWJsZSBtZXRob2QgKyBwYXRoIHdpdGggZXZlcnkgZ2F0ZSB0aGF0IHJ1bnMgYmVmb3JlIGl0cyBoYW5kbGVyLlxuICogSEVBRCBpcyBmb2xkZWQgaW50byBHRVQsIGFzIEV4cHJlc3MgZG9lcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGJ1aWxkUm91dGVUYWJsZShhcHA6IHVua25vd24pOiBSb3V0ZVRhYmxlRW50cnlbXSB7XG4gIGNvbnN0IHJvb3QgPSAoYXBwIGFzIHsgcm91dGVyPzogUm91dGVyTGlrZSB9KS5yb3V0ZXIgPz8gKGFwcCBhcyBSb3V0ZXJMaWtlKTtcbiAgY29uc3Qgb3V0OiBSb3V0ZVRhYmxlRW50cnlbXSA9IFtdO1xuICBpZiAoIXJvb3QgfHwgIUFycmF5LmlzQXJyYXkocm9vdC5zdGFjaykpIHJldHVybiBvdXQ7XG4gIHdhbGsocm9vdC5zdGFjaywgJy8nLCBbXSwgb3V0LCBuZXcgU2V0KCkpO1xuICByZXR1cm4gb3V0LnNvcnQoKGEsIGIpID0+IChhLnBhdGggPT09IGIucGF0aCA/IGEubWV0aG9kLmxvY2FsZUNvbXBhcmUoYi5tZXRob2QpIDogYS5wYXRoLmxvY2FsZUNvbXBhcmUoYi5wYXRoKSkpO1xufVxuXG4vKiogV2hldGhlciBhIG1ldGhvZCBtdXRhdGVzIHN0YXRlIChldmVyeXRoaW5nIGV4Y2VwdCBHRVQvSEVBRC9PUFRJT05TKS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpc1dyaXRlTWV0aG9kKG1ldGhvZDogc3RyaW5nKTogYm9vbGVhbiB7XG4gIHJldHVybiAhWydHRVQnLCAnSEVBRCcsICdPUFRJT05TJ10uaW5jbHVkZXMobWV0aG9kLnRvVXBwZXJDYXNlKCkpO1xufVxuXG4vKiogU3VtbWFyeSBjb3VudHMgZm9yIGEgc3RhcnR1cCBsb2cgbGluZS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzdW1tYXJpemVSb3V0ZVRhYmxlKHRhYmxlOiByZWFkb25seSBSb3V0ZVRhYmxlRW50cnlbXSk6IHsgcm91dGVzOiBudW1iZXI7IHdyaXRlczogbnVtYmVyOyB1bmdhdGVkOiBudW1iZXI7IHVuYXVkaXRlZDogbnVtYmVyOyBpbnRlcm5hbDogbnVtYmVyIH0ge1xuICBjb25zdCBnYXRlZCA9IChlOiBSb3V0ZVRhYmxlRW50cnkpOiBib29sZWFuID0+IGUucGVybWlzc2lvbnMubGVuZ3RoID4gMCB8fCBlLnN5c3RlbUFkbWluIHx8IGUuc2VydmljZVByaW5jaXBhbDtcbiAgY29uc3Qgd3JpdGVzID0gdGFibGUuZmlsdGVyKChlKSA9PiBpc1dyaXRlTWV0aG9kKGUubWV0aG9kKSk7XG4gIHJldHVybiB7XG4gICAgcm91dGVzOiB0YWJsZS5sZW5ndGgsXG4gICAgd3JpdGVzOiB3cml0ZXMubGVuZ3RoLFxuICAgIHVuZ2F0ZWQ6IHRhYmxlLmZpbHRlcigoZSkgPT4gIWdhdGVkKGUpKS5sZW5ndGgsXG4gICAgdW5hdWRpdGVkOiB3cml0ZXMuZmlsdGVyKChlKSA9PiBlLmF1ZGl0Lmxlbmd0aCA9PT0gMCkubGVuZ3RoLFxuICAgIGludGVybmFsOiB0YWJsZS5maWx0ZXIoKGUpID0+IGUuaW50ZXJuYWxDYWxsZXJzLmxlbmd0aCA+IDApLmxlbmd0aCxcbiAgfTtcbn1cbiJdfQ==
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* `requireStepUp` — a shared step-up (recent-password-reverify) gate for the
|
|
3
3
|
* stateless api services, mirroring the platform middleware of the same name.
|
|
4
4
|
*
|
|
5
|
-
* Flow: the UI hits `POST /api/auth/step-up` (platform) to re-verify the user
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Flow: the UI hits `POST /api/auth/step-up` (platform) to re-verify the user;
|
|
6
|
+
* platform mints a short-lived (60s) `step-up` JWT with the SAME ES256 signing
|
|
7
|
+
* key it mints access tokens with, so these services verify it through the same
|
|
8
|
+
* JWKS. The UI
|
|
8
9
|
* replays it as the `X-Step-Up-Token` header on the destructive/sensitive call;
|
|
9
10
|
* this middleware verifies it, binds it to the caller (`sub` match), and
|
|
10
11
|
* consumes its `jti` once (Redis-backed cross-instance when configured, else a
|
|
@@ -15,30 +16,54 @@
|
|
|
15
16
|
* verify + single-use jti into api-core so any service can require it.
|
|
16
17
|
*/
|
|
17
18
|
import type { Request, Response, NextFunction } from 'express';
|
|
19
|
+
/**
|
|
20
|
+
* How a step-up token was earned. Mirrors platform's `StepUpMethod`; kept here
|
|
21
|
+
* as its own union so a verifier can name the factors it accepts without
|
|
22
|
+
* importing platform.
|
|
23
|
+
*/
|
|
24
|
+
export type StepUpMethod = 'password' | 'webauthn' | 'totp' | 'reauth';
|
|
25
|
+
/**
|
|
26
|
+
* The factors that count as a SECOND FACTOR, for routes that may only be
|
|
27
|
+
* unlocked by one (#8: impersonation, KMS, IdP settings, platform admin).
|
|
28
|
+
*
|
|
29
|
+
* A password or a provider re-auth proves the same single factor the session was
|
|
30
|
+
* already opened with, so on those routes they are not enough: the point is that
|
|
31
|
+
* someone holding only a stolen session — or only a stolen password — cannot
|
|
32
|
+
* proceed.
|
|
33
|
+
*/
|
|
34
|
+
export declare const STRONG_STEP_UP_METHODS: readonly StepUpMethod[];
|
|
18
35
|
/** Payload of a platform-issued step-up token (see platform issueStepUpToken). */
|
|
19
36
|
export interface StepUpTokenPayload {
|
|
20
37
|
type: 'step-up';
|
|
21
38
|
sub: string;
|
|
22
39
|
jti: string;
|
|
40
|
+
/** Which factor earned it. Absent only on a token minted before the claim
|
|
41
|
+
* existed, which a method-restricted gate treats as "not proven". */
|
|
42
|
+
method?: StepUpMethod;
|
|
23
43
|
iat: number;
|
|
24
44
|
exp: number;
|
|
25
45
|
}
|
|
46
|
+
/** Options for {@link requireStepUp}. */
|
|
47
|
+
export interface RequireStepUpOptions {
|
|
48
|
+
/** Restrict which factors may earn the step-up (e.g. {@link STRONG_STEP_UP_METHODS}).
|
|
49
|
+
* Omitted means any factor the platform will issue a token for. */
|
|
50
|
+
methods?: readonly StepUpMethod[];
|
|
51
|
+
}
|
|
26
52
|
/**
|
|
27
|
-
* Verify a step-up token: valid signature
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
53
|
+
* Verify a step-up token: a valid ES256 signature from one of platform's
|
|
54
|
+
* published signing keys, issuer and audience when configured, AND the
|
|
55
|
+
* `type: 'step-up'` + `jti` claims — a plain access token is signed by the same
|
|
56
|
+
* key and carries the same `sub`, so without asserting the step-up shape it
|
|
57
|
+
* would bypass the gate. Throws on any failure (an unfetchable key set included:
|
|
58
|
+
* an unverifiable token is not a step-up). Does NOT bind to a caller —
|
|
59
|
+
* `requireStepUp` does the `sub` match and single-use consume.
|
|
33
60
|
*
|
|
34
|
-
* Delegates to `requireAuth`'s own
|
|
35
|
-
* The hand-rolled copy this replaced dropped TWO of
|
|
36
|
-
* pinned issuer/audience
|
|
37
|
-
*
|
|
38
|
-
* its previous-secret retry had no expiry/not-before carve-out, so an EXPIRED
|
|
39
|
-
* token was retried and surfaced as a signature error instead of an expiry one.
|
|
61
|
+
* Delegates to `requireAuth`'s own primitive (`verifyUserJwt`) rather than
|
|
62
|
+
* re-implementing it. The hand-rolled copy this replaced dropped TWO of its
|
|
63
|
+
* guards: it never pinned issuer/audience, and its previous-secret retry had no
|
|
64
|
+
* expiry/not-before carve-out, so an EXPIRED token surfaced as a signature error.
|
|
40
65
|
*/
|
|
41
|
-
export declare function verifyStepUpToken(token: string): StepUpTokenPayload
|
|
66
|
+
export declare function verifyStepUpToken(token: string): Promise<StepUpTokenPayload>;
|
|
42
67
|
/**
|
|
43
68
|
* Consume a step-up `jti` exactly once. Returns true if this call claimed it
|
|
44
69
|
* (first use), false if it was already consumed (replay). `expEpochSeconds` is
|
|
@@ -48,5 +73,12 @@ export declare function consumeStepUpJti(jti: string, expEpochSeconds: number):
|
|
|
48
73
|
/**
|
|
49
74
|
* Express middleware: require a valid, caller-bound, single-use step-up token in
|
|
50
75
|
* the `X-Step-Up-Token` header. Must run AFTER `requireAuth` (needs `req.user`).
|
|
76
|
+
*
|
|
77
|
+
* Use directly (`router.post('/x', requireAuth, requireStepUp, h)`) for the
|
|
78
|
+
* ordinary any-factor gate, or call it with `{ methods }` to demand that the
|
|
79
|
+
* confirmation was earned by a specific factor — `requireStepUp({ methods:
|
|
80
|
+
* STRONG_STEP_UP_METHODS })` on the routes where a password re-prompt is not a
|
|
81
|
+
* meaningful second proof.
|
|
51
82
|
*/
|
|
52
83
|
export declare function requireStepUp(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
84
|
+
export declare function requireStepUp(options: RequireStepUpOptions): (req: Request, res: Response, next: NextFunction) => void;
|