@astram/guard 0.6.0 → 0.8.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/account.controller.d.ts +3 -1
- package/dist/auth/account.controller.d.ts.map +1 -1
- package/dist/auth/account.controller.js +12 -5
- package/dist/auth/auth.controller.d.ts +130 -21
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +537 -143
- package/dist/auth/auth.module.d.ts +15 -1
- package/dist/auth/auth.module.d.ts.map +1 -1
- package/dist/auth/auth.module.js +241 -30
- package/dist/auth/connections.d.ts +1 -1
- package/dist/auth/connections.d.ts.map +1 -1
- package/dist/auth/connections.js +14 -3
- package/dist/auth/default-session-facts-projector.d.ts +26 -0
- package/dist/auth/default-session-facts-projector.d.ts.map +1 -0
- package/dist/auth/default-session-facts-projector.js +21 -0
- package/dist/auth/mfa-step-up.d.ts +32 -0
- package/dist/auth/mfa-step-up.d.ts.map +1 -0
- package/dist/auth/mfa-step-up.js +109 -0
- package/dist/auth/pending-mfa-store.d.ts +50 -52
- package/dist/auth/pending-mfa-store.d.ts.map +1 -1
- package/dist/auth/pending-mfa-store.js +103 -61
- package/dist/auth/ports/session-facts-projector.d.ts +5 -0
- package/dist/auth/ports/session-facts-projector.d.ts.map +1 -1
- package/dist/auth/rate-limit/canonical-hint.d.ts +24 -0
- package/dist/auth/rate-limit/canonical-hint.d.ts.map +1 -0
- package/dist/auth/rate-limit/canonical-hint.js +63 -0
- package/dist/auth/rate-limit/trust-proxy-check.d.ts +46 -0
- package/dist/auth/rate-limit/trust-proxy-check.d.ts.map +1 -0
- package/dist/auth/rate-limit/trust-proxy-check.js +94 -0
- package/dist/auth/tokens.d.ts +70 -11
- package/dist/auth/tokens.d.ts.map +1 -1
- package/dist/auth/tokens.js +71 -12
- package/dist/authentication/log-email-sender.d.ts +9 -4
- package/dist/authentication/log-email-sender.d.ts.map +1 -1
- package/dist/authentication/mariadb-api-key-store.d.ts +46 -0
- package/dist/authentication/mariadb-api-key-store.d.ts.map +1 -0
- package/dist/authentication/mariadb-api-key-store.js +111 -0
- package/dist/authentication/mariadb-credential-resolver.d.ts +79 -65
- package/dist/authentication/mariadb-credential-resolver.d.ts.map +1 -1
- package/dist/authentication/mariadb-credential-resolver.js +116 -73
- package/dist/authentication/mariadb-credential-writer.d.ts.map +1 -1
- package/dist/authentication/mariadb-or-totp-verifier.d.ts +21 -3
- package/dist/authentication/mariadb-or-totp-verifier.d.ts.map +1 -1
- package/dist/authentication/mariadb-or-totp-verifier.js +37 -6
- package/dist/authentication/mariadb-profile-resolver.d.ts.map +1 -1
- package/dist/authentication/mariadb-profile-writer.d.ts.map +1 -1
- package/dist/authentication/mariadb-session-sweeper.d.ts +14 -1
- package/dist/authentication/mariadb-session-sweeper.d.ts.map +1 -1
- package/dist/authentication/mariadb-session-sweeper.js +29 -11
- package/dist/authentication/noop-email-sender.d.ts +12 -0
- package/dist/authentication/noop-email-sender.d.ts.map +1 -0
- package/dist/authentication/noop-email-sender.js +9 -0
- package/dist/authz/cross-tenant-grant-writer.d.ts.map +1 -1
- package/dist/authz/manifest-operation-authority-adapter.d.ts.map +1 -1
- package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts.map +1 -1
- package/dist/authz/mariadb-cross-tenant-grant-adapter.js +3 -1
- package/dist/authz/mariadb-policy-store.js +1 -1
- package/dist/authz/mariadb-record-rule-adapter.d.ts.map +1 -1
- package/dist/authz/mariadb-record-rule-adapter.js +1 -4
- package/dist/authz/permission-version-cache.d.ts +38 -0
- package/dist/authz/permission-version-cache.d.ts.map +1 -0
- package/dist/authz/permission-version-cache.js +81 -0
- package/dist/authz/wire-record-authorization.d.ts +25 -1
- package/dist/authz/wire-record-authorization.d.ts.map +1 -1
- package/dist/authz/wire-record-authorization.js +55 -2
- package/dist/completeness/catalog-reconciler.d.ts.map +1 -1
- package/dist/config/app-config.d.ts +52 -0
- package/dist/config/app-config.d.ts.map +1 -1
- package/dist/config/app-config.js +66 -4
- package/dist/encryption/local-kek-crypto.d.ts +15 -0
- package/dist/encryption/local-kek-crypto.d.ts.map +1 -0
- package/dist/encryption/local-kek-crypto.js +79 -0
- package/dist/enforcement/enforcement.module.d.ts.map +1 -1
- package/dist/enforcement/enforcement.module.js +9 -0
- package/dist/enforcement/engine-error.filter.d.ts +57 -0
- package/dist/enforcement/engine-error.filter.d.ts.map +1 -0
- package/dist/enforcement/engine-error.filter.js +118 -0
- package/dist/enforcement/global-enforcement.guard.d.ts +31 -2
- package/dist/enforcement/global-enforcement.guard.d.ts.map +1 -1
- package/dist/enforcement/global-enforcement.guard.js +148 -6
- package/dist/identity/auth-facts.controller.d.ts.map +1 -1
- package/dist/identity/identity-actions.d.ts +9 -0
- package/dist/identity/identity-actions.d.ts.map +1 -1
- package/dist/identity/identity-actions.js +16 -1
- package/dist/identity/permission-facts.adapter.d.ts.map +1 -1
- package/dist/identity/permission-facts.adapter.js +2 -3
- package/dist/index.d.ts +17 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +82 -25
- package/dist/module/shield.module.d.ts +9 -2
- package/dist/module/shield.module.d.ts.map +1 -1
- package/dist/module/shield.module.js +47 -38
- package/dist/request-auth/request-principal.d.ts +11 -3
- package/dist/request-auth/request-principal.d.ts.map +1 -1
- package/dist/request-auth/request-principal.js +58 -2
- package/dist/request-auth/session-facts-als.d.ts +16 -1
- package/dist/request-auth/session-facts-als.d.ts.map +1 -1
- package/dist/request-auth/session-facts-als.js +23 -0
- package/dist/request-auth/session-token-binder.d.ts +166 -10
- package/dist/request-auth/session-token-binder.d.ts.map +1 -1
- package/dist/request-auth/session-token-binder.js +408 -28
- package/dist/request-auth/tokens.d.ts +10 -0
- package/dist/request-auth/tokens.d.ts.map +1 -1
- package/dist/request-auth/tokens.js +11 -1
- package/dist/secrets/encrypted-file-secrets.d.ts +16 -0
- package/dist/secrets/encrypted-file-secrets.d.ts.map +1 -0
- package/dist/secrets/encrypted-file-secrets.js +98 -0
- package/dist/session/hot-revoking-session-record-store.d.ts +46 -0
- package/dist/session/hot-revoking-session-record-store.d.ts.map +1 -0
- package/dist/session/hot-revoking-session-record-store.js +58 -0
- package/dist/session/index.d.ts +3 -1
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +8 -1
- package/dist/session/mariadb-session-record-store.d.ts +6 -0
- package/dist/session/mariadb-session-record-store.d.ts.map +1 -1
- package/dist/session/mariadb-session-record-store.js +16 -1
- package/dist/session/session-lifetimes.d.ts +30 -0
- package/dist/session/session-lifetimes.d.ts.map +1 -0
- package/dist/session/session-lifetimes.js +21 -0
- package/dist/session/session-restriction-store.d.ts +43 -0
- package/dist/session/session-restriction-store.d.ts.map +1 -0
- package/dist/session/session-restriction-store.js +35 -0
- package/migrations/1710000000017-CreateServiceAccountKey.cjs +41 -0
- package/package.json +3 -3
|
@@ -105,7 +105,13 @@ export interface ShieldModuleOptions {
|
|
|
105
105
|
*/
|
|
106
106
|
readonly credentialWriter?: CredentialWriter;
|
|
107
107
|
readonly resetTokenStore?: ResetTokenStore;
|
|
108
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Password-reset email delivery — see {@link AuthModuleOptions.emailSender}. A real
|
|
110
|
+
* sender, or `'disabled'` (no-op, forgot-password still 204). REQUIRED under
|
|
111
|
+
* `auth: true` unless the raw `NODE_ENV` is `development`/`test` (where the dev
|
|
112
|
+
* `LogEmailSender` is the default); otherwise boot fails (security hardening #6).
|
|
113
|
+
*/
|
|
114
|
+
readonly emailSender?: EmailSender | 'disabled';
|
|
109
115
|
readonly recoveryCodeStore?: RecoveryCodeStore;
|
|
110
116
|
readonly profileWriter?: ProfileWriter;
|
|
111
117
|
readonly sessionSweeper?: SessionSweeper;
|
|
@@ -221,7 +227,8 @@ export interface ShieldModuleAsyncOptions {
|
|
|
221
227
|
*/
|
|
222
228
|
readonly credentialWriter?: CredentialWriter;
|
|
223
229
|
readonly resetTokenStore?: ResetTokenStore;
|
|
224
|
-
|
|
230
|
+
/** See {@link ShieldModuleOptions.emailSender} (a sender, or `'disabled'`). */
|
|
231
|
+
readonly emailSender?: EmailSender | 'disabled';
|
|
225
232
|
readonly recoveryCodeStore?: RecoveryCodeStore;
|
|
226
233
|
readonly profileWriter?: ProfileWriter;
|
|
227
234
|
readonly sessionSweeper?: SessionSweeper;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shield.module.d.ts","sourceRoot":"","sources":["../../src/module/shield.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AAOnF,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAQvE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAC;AACvF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACtF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yCAAyC,CAAC;AACpF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAKzE,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"shield.module.d.ts","sourceRoot":"","sources":["../../src/module/shield.module.ts"],"names":[],"mappings":"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"}
|
|
@@ -55,6 +55,9 @@ const app_config_js_1 = require("../config/app-config.js");
|
|
|
55
55
|
// the enforcement-only path never INSTANTIATES it (no live pool/Redis, no HMAC
|
|
56
56
|
// gate, no core load).
|
|
57
57
|
const auth_module_js_1 = require("../auth/auth.module.js");
|
|
58
|
+
// Core-free (type-only core imports): the deferred default projector + the version-cache token.
|
|
59
|
+
const default_session_facts_projector_js_1 = require("../auth/default-session-facts-projector.js");
|
|
60
|
+
const tokens_js_1 = require("../auth/tokens.js");
|
|
58
61
|
// The `SHIELD_DECISION_AUTHORITY` token now lives in `./shield-tokens.js` so the
|
|
59
62
|
// enforcement guard can import it without a cycle (guard → token → ShieldModule →
|
|
60
63
|
// EnforcementModule → guard). Re-exported here to keep the public import path stable.
|
|
@@ -87,7 +90,8 @@ let cachedRecordAuthApi;
|
|
|
87
90
|
async function loadBuildRecordAuthorization() {
|
|
88
91
|
if (cachedRecordAuthApi === undefined) {
|
|
89
92
|
try {
|
|
90
|
-
cachedRecordAuthApi =
|
|
93
|
+
cachedRecordAuthApi =
|
|
94
|
+
(await Promise.resolve().then(() => __importStar(require('../authz/wire-record-authorization.js'))));
|
|
91
95
|
}
|
|
92
96
|
catch {
|
|
93
97
|
// Built-dist fallback (the CJS dist path — see evalImport). `__dirname` is defined
|
|
@@ -97,7 +101,7 @@ async function loadBuildRecordAuthorization() {
|
|
|
97
101
|
cachedRecordAuthApi = (await evalImport(base));
|
|
98
102
|
}
|
|
99
103
|
}
|
|
100
|
-
return cachedRecordAuthApi
|
|
104
|
+
return cachedRecordAuthApi;
|
|
101
105
|
}
|
|
102
106
|
/**
|
|
103
107
|
* Resolve the `SHIELD_DECISION_AUTHORITY` VALUE from the caller's options (the ONE seam,
|
|
@@ -113,18 +117,33 @@ async function loadBuildRecordAuthorization() {
|
|
|
113
117
|
*
|
|
114
118
|
* Async because the `recordAuthorization` path lazy-loads the (core-touching) builder.
|
|
115
119
|
*/
|
|
116
|
-
async function resolveDecisionAuthority(options) {
|
|
120
|
+
async function resolveDecisionAuthority(options, extras = {}) {
|
|
117
121
|
if (options.recordAuthorization && options.decisionAuthority) {
|
|
118
122
|
throw new Error('ShieldModule: `recordAuthorization` and `decisionAuthority` are mutually exclusive — ' +
|
|
119
123
|
'supply the record-authorization inputs (pool + app ports + registry) so the framework ' +
|
|
120
124
|
'builds the decision authority, OR a pre-built `decisionAuthority`, but not both.');
|
|
121
125
|
}
|
|
122
126
|
if (options.recordAuthorization) {
|
|
123
|
-
const
|
|
124
|
-
|
|
127
|
+
const api = await loadBuildRecordAuthorization();
|
|
128
|
+
const versionCache = extras.versionCache ?? undefined;
|
|
129
|
+
// ADR-0035 default projector: cache the SAME facts the authority decides on (version-cache
|
|
130
|
+
// wrapped, so a login projection also warms the Redis version).
|
|
131
|
+
extras.defaultProjector?.attach(versionCache !== undefined
|
|
132
|
+
? api.withRedisPermissionVersion(options.recordAuthorization.permissionFacts, versionCache)
|
|
133
|
+
: options.recordAuthorization.permissionFacts);
|
|
134
|
+
return api.buildRecordAuthorization(options.recordAuthorization, versionCache !== undefined ? { versionCache } : {});
|
|
125
135
|
}
|
|
126
136
|
return options.decisionAuthority ?? core_seam_js_1.stubDecisionAuthority;
|
|
127
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* The deferred default projector for a registration (ADR-0035): only when auth is on and the
|
|
140
|
+
* consumer did not supply its own projector. `undefined` otherwise.
|
|
141
|
+
*/
|
|
142
|
+
function defaultProjectorFor(auth, explicit) {
|
|
143
|
+
return auth && explicit === undefined ? new default_session_facts_projector_js_1.DeferredSessionFactsProjector() : undefined;
|
|
144
|
+
}
|
|
145
|
+
/** Inject the Redis version cache when AuthModule provides it (`auth: true`); absent otherwise. */
|
|
146
|
+
const OPTIONAL_VERSION_CACHE = { token: tokens_js_1.SHIELD_PERMISSION_VERSION_CACHE, optional: true };
|
|
128
147
|
/**
|
|
129
148
|
* `ShieldModule` — the SINGLE registration seam (ADR-0023 §4).
|
|
130
149
|
*
|
|
@@ -153,6 +172,10 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
|
|
|
153
172
|
/** Register Shield with static options (the common path). */
|
|
154
173
|
static forRoot(options = {}) {
|
|
155
174
|
const config = options.config ?? (0, app_config_js_1.loadAppConfig)();
|
|
175
|
+
// ADR-0035: with auth on and no explicit projector, sessions cache the facts the framework-built
|
|
176
|
+
// authority decides on (attached when the authority is built; inactive until then).
|
|
177
|
+
const defaultProjector = defaultProjectorFor(options.auth, options.sessionFactsProjector);
|
|
178
|
+
const sessionFactsProjector = options.sessionFactsProjector ?? defaultProjector;
|
|
156
179
|
// Build the opt-in AuthModule ONCE so the SAME DynamicModule instance is both
|
|
157
180
|
// imported (composes) and re-exported (propagates its exported engine tokens
|
|
158
181
|
// app-wide through the @Global ShieldModule). Re-exporting the two Symbols
|
|
@@ -172,9 +195,7 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
|
|
|
172
195
|
...(options.credentialResolver
|
|
173
196
|
? { credentialResolver: options.credentialResolver }
|
|
174
197
|
: {}),
|
|
175
|
-
...(options.profileResolver
|
|
176
|
-
? { profileResolver: options.profileResolver }
|
|
177
|
-
: {}),
|
|
198
|
+
...(options.profileResolver ? { profileResolver: options.profileResolver } : {}),
|
|
178
199
|
// The account/credential-surface seams — same spread-when-present rule
|
|
179
200
|
// (an explicit `undefined` is not assignable under
|
|
180
201
|
// exactOptionalPropertyTypes; absent → AuthModule binds `null` → 501).
|
|
@@ -194,15 +215,11 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
|
|
|
194
215
|
...(options.identityControlPlaneKeys
|
|
195
216
|
? { identityControlPlaneKeys: options.identityControlPlaneKeys }
|
|
196
217
|
: {}),
|
|
197
|
-
...(
|
|
198
|
-
? { sessionFactsProjector: options.sessionFactsProjector }
|
|
199
|
-
: {}),
|
|
218
|
+
...(sessionFactsProjector ? { sessionFactsProjector } : {}),
|
|
200
219
|
...(options.scopeSubtreeResolver
|
|
201
220
|
? { scopeSubtreeResolver: options.scopeSubtreeResolver }
|
|
202
221
|
: {}),
|
|
203
|
-
...(options.membershipChecker
|
|
204
|
-
? { membershipChecker: options.membershipChecker }
|
|
205
|
-
: {}),
|
|
222
|
+
...(options.membershipChecker ? { membershipChecker: options.membershipChecker } : {}),
|
|
206
223
|
})
|
|
207
224
|
: undefined;
|
|
208
225
|
return {
|
|
@@ -221,24 +238,22 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
|
|
|
221
238
|
// stub paths resolve synchronously but flow through the same one seam. Both
|
|
222
239
|
// supplied → throws (mutual exclusion).
|
|
223
240
|
provide: shield_tokens_js_1.SHIELD_DECISION_AUTHORITY,
|
|
224
|
-
useFactory: () => resolveDecisionAuthority({
|
|
241
|
+
useFactory: (versionCache) => resolveDecisionAuthority({
|
|
225
242
|
...(options.recordAuthorization
|
|
226
243
|
? { recordAuthorization: options.recordAuthorization }
|
|
227
244
|
: {}),
|
|
228
245
|
...(options.decisionAuthority
|
|
229
246
|
? { decisionAuthority: options.decisionAuthority }
|
|
230
247
|
: {}),
|
|
231
|
-
}),
|
|
248
|
+
}, { versionCache, defaultProjector }),
|
|
249
|
+
// ADR-0035: the Redis version cache AuthModule provides under `auth: true`.
|
|
250
|
+
inject: [OPTIONAL_VERSION_CACHE],
|
|
232
251
|
},
|
|
233
252
|
],
|
|
234
253
|
// Re-export the composed AuthModule app-wide ONLY when auth is opted in — its
|
|
235
254
|
// own `exports` (SHIELD_AUTHENTICATOR / SHIELD_SESSION_AUTHORITY) then become
|
|
236
255
|
// visible to a consumer's controllers/services via the @Global seam.
|
|
237
|
-
exports: [
|
|
238
|
-
config_module_js_1.APP_CONFIG,
|
|
239
|
-
shield_tokens_js_1.SHIELD_DECISION_AUTHORITY,
|
|
240
|
-
...(authModule ? [authModule] : []),
|
|
241
|
-
],
|
|
256
|
+
exports: [config_module_js_1.APP_CONFIG, shield_tokens_js_1.SHIELD_DECISION_AUTHORITY, ...(authModule ? [authModule] : [])],
|
|
242
257
|
};
|
|
243
258
|
}
|
|
244
259
|
/**
|
|
@@ -263,6 +278,10 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
|
|
|
263
278
|
useFactory: (opts) => opts.config ?? (0, app_config_js_1.loadAppConfig)(),
|
|
264
279
|
inject: [optionsToken],
|
|
265
280
|
};
|
|
281
|
+
// ADR-0035: the deferred default projector (auth on, no explicit projector), attached when the
|
|
282
|
+
// async-resolved options yield `recordAuthorization`.
|
|
283
|
+
const defaultProjector = defaultProjectorFor(options.auth, options.sessionFactsProjector);
|
|
284
|
+
const sessionFactsProjector = options.sessionFactsProjector ?? defaultProjector;
|
|
266
285
|
const authorityProvider = {
|
|
267
286
|
provide: shield_tokens_js_1.SHIELD_DECISION_AUTHORITY,
|
|
268
287
|
// Same one-seam resolution as forRoot, over the async-resolved options: the
|
|
@@ -270,11 +289,11 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
|
|
|
270
289
|
// in its useFactory from the injected registrations) has the framework build the
|
|
271
290
|
// real wired authority; `decisionAuthority` is the legacy passthrough; both →
|
|
272
291
|
// throws; neither → stub. Async because the record path lazy-loads the builder.
|
|
273
|
-
useFactory: (opts) => resolveDecisionAuthority({
|
|
292
|
+
useFactory: (opts, versionCache) => resolveDecisionAuthority({
|
|
274
293
|
...(opts.recordAuthorization ? { recordAuthorization: opts.recordAuthorization } : {}),
|
|
275
294
|
...(opts.decisionAuthority ? { decisionAuthority: opts.decisionAuthority } : {}),
|
|
276
|
-
}),
|
|
277
|
-
inject: [optionsToken],
|
|
295
|
+
}, { versionCache, defaultProjector }),
|
|
296
|
+
inject: [optionsToken, OPTIONAL_VERSION_CACHE],
|
|
278
297
|
};
|
|
279
298
|
// Same one-instance opt-in AuthModule as forRoot (imported + re-exported).
|
|
280
299
|
// `credentialResolver` is STATIC on the async options (like `auth`) — a
|
|
@@ -289,9 +308,7 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
|
|
|
289
308
|
...(options.credentialResolver
|
|
290
309
|
? { credentialResolver: options.credentialResolver }
|
|
291
310
|
: {}),
|
|
292
|
-
...(options.profileResolver
|
|
293
|
-
? { profileResolver: options.profileResolver }
|
|
294
|
-
: {}),
|
|
311
|
+
...(options.profileResolver ? { profileResolver: options.profileResolver } : {}),
|
|
295
312
|
// The account/credential-surface seams — same spread-when-present rule
|
|
296
313
|
// (an explicit `undefined` is not assignable under
|
|
297
314
|
// exactOptionalPropertyTypes; absent → AuthModule binds `null` → 501).
|
|
@@ -311,15 +328,11 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
|
|
|
311
328
|
...(options.identityControlPlaneKeys
|
|
312
329
|
? { identityControlPlaneKeys: options.identityControlPlaneKeys }
|
|
313
330
|
: {}),
|
|
314
|
-
...(
|
|
315
|
-
? { sessionFactsProjector: options.sessionFactsProjector }
|
|
316
|
-
: {}),
|
|
331
|
+
...(sessionFactsProjector ? { sessionFactsProjector } : {}),
|
|
317
332
|
...(options.scopeSubtreeResolver
|
|
318
333
|
? { scopeSubtreeResolver: options.scopeSubtreeResolver }
|
|
319
334
|
: {}),
|
|
320
|
-
...(options.membershipChecker
|
|
321
|
-
? { membershipChecker: options.membershipChecker }
|
|
322
|
-
: {}),
|
|
335
|
+
...(options.membershipChecker ? { membershipChecker: options.membershipChecker } : {}),
|
|
323
336
|
})
|
|
324
337
|
: undefined;
|
|
325
338
|
return {
|
|
@@ -335,11 +348,7 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
|
|
|
335
348
|
...(options.imports ?? []),
|
|
336
349
|
],
|
|
337
350
|
providers: [asyncOptionsProvider, configProvider, authorityProvider],
|
|
338
|
-
exports: [
|
|
339
|
-
config_module_js_1.APP_CONFIG,
|
|
340
|
-
shield_tokens_js_1.SHIELD_DECISION_AUTHORITY,
|
|
341
|
-
...(authModule ? [authModule] : []),
|
|
342
|
-
],
|
|
351
|
+
exports: [config_module_js_1.APP_CONFIG, shield_tokens_js_1.SHIELD_DECISION_AUTHORITY, ...(authModule ? [authModule] : [])],
|
|
343
352
|
};
|
|
344
353
|
}
|
|
345
354
|
};
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* captured from the login-time Principal, so a genuinely-root login stays
|
|
13
13
|
* root; a normal login can never become one on a later request).
|
|
14
14
|
*/
|
|
15
|
-
import type { Principal } from '@astram/core';
|
|
15
|
+
import type { Principal, ServiceAuthenticator } from '@astram/core';
|
|
16
16
|
import type { SessionTokenBinder, BoundPrincipalClaims } from './session-token-binder.js';
|
|
17
17
|
/** Extract a bearer token from an `Authorization: Bearer <token>` header. */
|
|
18
18
|
export declare function extractBearerToken(authorizationHeader: unknown): string | null;
|
|
@@ -22,11 +22,19 @@ export declare function extractBearerToken(authorizationHeader: unknown): string
|
|
|
22
22
|
* a root on the request path.
|
|
23
23
|
*/
|
|
24
24
|
export declare function principalFromClaims(claims: BoundPrincipalClaims): Principal | null;
|
|
25
|
+
/**
|
|
26
|
+
* Is this bearer token a service-account API key (`ask_…`, ADR-0033) rather than a session
|
|
27
|
+
* access token? A cheap prefix check; the authenticator does the real (constant-time) verify.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isApiKey(token: string): boolean;
|
|
25
30
|
/**
|
|
26
31
|
* Resolve an HTTP request's bearer token to a `Principal`, or `null` (fail-closed)
|
|
27
32
|
* when there is no token, no binding, or the binding is malformed.
|
|
33
|
+
*
|
|
34
|
+
* When `serviceAuth` is provided and the bearer token is an API key (`ask_…`, ADR-0033), the token
|
|
35
|
+
* is authenticated as a service account instead of resolved against the session binder.
|
|
28
36
|
*/
|
|
29
|
-
export declare function resolveRequestPrincipal(authorizationHeader: unknown, binder: SessionTokenBinder): Promise<Principal | null>;
|
|
37
|
+
export declare function resolveRequestPrincipal(authorizationHeader: unknown, binder: SessionTokenBinder, serviceAuth?: ServiceAuthenticator): Promise<Principal | null>;
|
|
30
38
|
/**
|
|
31
39
|
* Resolve an HTTP request's bearer token to BOTH a `Principal` AND the raw
|
|
32
40
|
* {@link BoundPrincipalClaims}, or `null` (fail-closed) — the same read path as
|
|
@@ -35,7 +43,7 @@ export declare function resolveRequestPrincipal(authorizationHeader: unknown, bi
|
|
|
35
43
|
* uses this so it can read `claims.mustReenroll` to apply the enforced-re-enroll gate
|
|
36
44
|
* while still attaching the resolved `Principal` for `decide()`.
|
|
37
45
|
*/
|
|
38
|
-
export declare function resolveRequestAuth(authorizationHeader: unknown, binder: SessionTokenBinder): Promise<{
|
|
46
|
+
export declare function resolveRequestAuth(authorizationHeader: unknown, binder: SessionTokenBinder, serviceAuth?: ServiceAuthenticator): Promise<{
|
|
39
47
|
principal: Principal;
|
|
40
48
|
claims: BoundPrincipalClaims;
|
|
41
49
|
} | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-principal.d.ts","sourceRoot":"","sources":["../../src/request-auth/request-principal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"request-principal.d.ts","sourceRoot":"","sources":["../../src/request-auth/request-principal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAa1F,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAAC,mBAAmB,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAK9E;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,oBAAoB,GAAG,SAAS,GAAG,IAAI,CAiClF;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE/C;AA4BD;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAC3C,mBAAmB,EAAE,OAAO,EAC5B,MAAM,EAAE,kBAAkB,EAC1B,WAAW,CAAC,EAAE,oBAAoB,GACjC,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAU3B;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,mBAAmB,EAAE,OAAO,EAC5B,MAAM,EAAE,kBAAkB,EAC1B,WAAW,CAAC,EAAE,oBAAoB,GACjC,OAAO,CAAC;IAAE,SAAS,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,GAAG,IAAI,CAAC,CAgBxE"}
|
|
@@ -16,10 +16,18 @@
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.extractBearerToken = extractBearerToken;
|
|
18
18
|
exports.principalFromClaims = principalFromClaims;
|
|
19
|
+
exports.isApiKey = isApiKey;
|
|
19
20
|
exports.resolveRequestPrincipal = resolveRequestPrincipal;
|
|
20
21
|
exports.resolveRequestAuth = resolveRequestAuth;
|
|
21
22
|
/** Valid `Principal.kind` values (mirror of the Core union; validated at rebuild). */
|
|
22
23
|
const PRINCIPAL_KINDS = new Set(['user', 'role_group', 'service_account']);
|
|
24
|
+
/**
|
|
25
|
+
* The service-account API-key prefix (mirror of Core's `API_KEY_PREFIX = 'ask_'`, ADR-0033) —
|
|
26
|
+
* inlined as a literal so this file keeps its ZERO-runtime-`require('@astram/core')` discipline
|
|
27
|
+
* (importing the value would add a runtime dep; the type-only imports above are erased). Kept in
|
|
28
|
+
* sync with Core by the ADR + a guard test that asserts equality with the Core export.
|
|
29
|
+
*/
|
|
30
|
+
const API_KEY_PREFIX = 'ask_';
|
|
23
31
|
/** Extract a bearer token from an `Authorization: Bearer <token>` header. */
|
|
24
32
|
function extractBearerToken(authorizationHeader) {
|
|
25
33
|
if (typeof authorizationHeader !== 'string')
|
|
@@ -64,14 +72,53 @@ function principalFromClaims(claims) {
|
|
|
64
72
|
isRoot: claims.isRoot === true,
|
|
65
73
|
});
|
|
66
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Is this bearer token a service-account API key (`ask_…`, ADR-0033) rather than a session
|
|
77
|
+
* access token? A cheap prefix check; the authenticator does the real (constant-time) verify.
|
|
78
|
+
*/
|
|
79
|
+
function isApiKey(token) {
|
|
80
|
+
return token.startsWith(API_KEY_PREFIX);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Synthesize {@link BoundPrincipalClaims} for a machine (service-account) request from its resolved
|
|
84
|
+
* {@link Principal} (ADR-0033). A service account has NO interactive-session state — so the
|
|
85
|
+
* session-only gates are set to their benign defaults:
|
|
86
|
+
* - `mustReenroll: false` — re-enroll is a human recovery-code concern; a machine never re-enrolls.
|
|
87
|
+
* - `sessionId: ''` — there is no durable session row (the key IS the credential); the field is
|
|
88
|
+
* present only to satisfy the claims shape (no logout/liveness correlation applies to a key —
|
|
89
|
+
* revocation is via the key's `revokedAtMs`, enforced at authenticate time).
|
|
90
|
+
* - no `cachedFacts` (absent) — gate 5 DB-reloads the account's grants (fail-closed default; a
|
|
91
|
+
* machine has no login-time projection).
|
|
92
|
+
* The Principal's `scopeSubtreeNodeIds` is carried through so the tenant/scope gate reads the
|
|
93
|
+
* account's own subtree (least privilege).
|
|
94
|
+
*/
|
|
95
|
+
function claimsFromServicePrincipal(principal) {
|
|
96
|
+
return {
|
|
97
|
+
id: principal.id,
|
|
98
|
+
tenantId: principal.tenantId,
|
|
99
|
+
roleIds: [...principal.roleIds],
|
|
100
|
+
kind: principal.kind,
|
|
101
|
+
isRoot: false, // a service account is NEVER root (ADR-0033) — defense-in-depth on the claims too.
|
|
102
|
+
sessionId: '',
|
|
103
|
+
mustReenroll: false,
|
|
104
|
+
scopeSubtreeNodeIds: [...principal.scopeSubtreeNodeIds],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
67
107
|
/**
|
|
68
108
|
* Resolve an HTTP request's bearer token to a `Principal`, or `null` (fail-closed)
|
|
69
109
|
* when there is no token, no binding, or the binding is malformed.
|
|
110
|
+
*
|
|
111
|
+
* When `serviceAuth` is provided and the bearer token is an API key (`ask_…`, ADR-0033), the token
|
|
112
|
+
* is authenticated as a service account instead of resolved against the session binder.
|
|
70
113
|
*/
|
|
71
|
-
async function resolveRequestPrincipal(authorizationHeader, binder) {
|
|
114
|
+
async function resolveRequestPrincipal(authorizationHeader, binder, serviceAuth) {
|
|
72
115
|
const token = extractBearerToken(authorizationHeader);
|
|
73
116
|
if (token === null)
|
|
74
117
|
return null;
|
|
118
|
+
if (serviceAuth !== undefined && isApiKey(token)) {
|
|
119
|
+
const res = await serviceAuth.authenticateApiKey(token);
|
|
120
|
+
return res.ok ? res.principal : null; // fail-closed uniform on any bad key.
|
|
121
|
+
}
|
|
75
122
|
const claims = await binder.resolve(token);
|
|
76
123
|
if (claims === null)
|
|
77
124
|
return null;
|
|
@@ -85,10 +132,19 @@ async function resolveRequestPrincipal(authorizationHeader, binder) {
|
|
|
85
132
|
* uses this so it can read `claims.mustReenroll` to apply the enforced-re-enroll gate
|
|
86
133
|
* while still attaching the resolved `Principal` for `decide()`.
|
|
87
134
|
*/
|
|
88
|
-
async function resolveRequestAuth(authorizationHeader, binder) {
|
|
135
|
+
async function resolveRequestAuth(authorizationHeader, binder, serviceAuth) {
|
|
89
136
|
const token = extractBearerToken(authorizationHeader);
|
|
90
137
|
if (token === null)
|
|
91
138
|
return null;
|
|
139
|
+
// Machine path (ADR-0033): an `ask_…` bearer is a service-account API key. Authenticate it and
|
|
140
|
+
// synthesize the claims (benign session defaults) so ALL downstream enforcement — the re-enroll
|
|
141
|
+
// gate, the facts hot-path, decide() — is IDENTICAL to a human's (invariant #3, one authority).
|
|
142
|
+
if (serviceAuth !== undefined && isApiKey(token)) {
|
|
143
|
+
const res = await serviceAuth.authenticateApiKey(token);
|
|
144
|
+
if (!res.ok)
|
|
145
|
+
return null; // uniform fail-closed (malformed/unknown/expired/revoked/outage).
|
|
146
|
+
return { principal: res.principal, claims: claimsFromServicePrincipal(res.principal) };
|
|
147
|
+
}
|
|
92
148
|
const claims = await binder.resolve(token);
|
|
93
149
|
if (claims === null)
|
|
94
150
|
return null;
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
* (`PermissionFactsPort.loadCoarseGrants`). A cache miss NEVER fabricates a permit.
|
|
36
36
|
*/
|
|
37
37
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
38
|
-
import type { CoarseGrants } from '@astram/core';
|
|
38
|
+
import type { CoarseGrants, PermissionFactsPort } from '@astram/core';
|
|
39
39
|
/**
|
|
40
40
|
* The per-request session-facts store carried through the ALS. `cachedFacts` is the
|
|
41
41
|
* principal's coarse grants for THIS request (or `null` when none were projected →
|
|
@@ -47,7 +47,22 @@ export interface SessionFactsStore {
|
|
|
47
47
|
readonly cachedFacts: CoarseGrants | null;
|
|
48
48
|
/** The version `cachedFacts` was projected at (0 when absent). */
|
|
49
49
|
readonly permissionsVersion: number;
|
|
50
|
+
/**
|
|
51
|
+
* OPTIONAL reload signal (security hardening Phase 1C). The framework-built decision authority
|
|
52
|
+
* calls it when gate 5 RELOADS the coarse grants for a principal inside this request's scope
|
|
53
|
+
* (cache miss or stale version), passing the authoritative reloaded grants. The enforcement guard
|
|
54
|
+
* uses it to notice a permission-version move and re-derive the session's scope subtree. A pure
|
|
55
|
+
* notification: it never changes the decision, and readers ignore it.
|
|
56
|
+
*/
|
|
57
|
+
readonly onReload?: (principalId: string, tenantId: string, grants: CoarseGrants) => void;
|
|
50
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Wrap an app-owned `PermissionFactsPort`-shaped object so a gate-5 reload inside an enforcement
|
|
61
|
+
* request notifies that request's {@link SessionFactsStore.onReload}. Behaviour-identical
|
|
62
|
+
* otherwise: same results, same errors (a throwing reload propagates and notifies nothing).
|
|
63
|
+
* Outside a guard request (no ALS store, e.g. the engine's in-handler decide calls) it is a no-op.
|
|
64
|
+
*/
|
|
65
|
+
export declare function signalGateFiveReloads(port: PermissionFactsPort): PermissionFactsPort;
|
|
51
66
|
/**
|
|
52
67
|
* The ONE shared session-facts `AsyncLocalStorage` (see the file header). The
|
|
53
68
|
* enforcement guard imports this to `run()` the `decide()` call inside the request's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-facts-als.d.ts","sourceRoot":"","sources":["../../src/request-auth/session-facts-als.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAIrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"session-facts-als.d.ts","sourceRoot":"","sources":["../../src/request-auth/session-facts-als.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAIrD,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,wFAAwF;IACxF,QAAQ,CAAC,WAAW,EAAE,YAAY,GAAG,IAAI,CAAC;IAC1C,kEAAkE;IAClE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;CAC3F;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,GAAG,mBAAmB,CAepF;AAED;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,sCAA6C,CAAC;AAEnF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,iBAG7B,CAAC"}
|
|
@@ -37,7 +37,30 @@
|
|
|
37
37
|
*/
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.NO_SESSION_FACTS = exports.SHIELD_SESSION_FACTS_ALS = void 0;
|
|
40
|
+
exports.signalGateFiveReloads = signalGateFiveReloads;
|
|
40
41
|
const node_async_hooks_1 = require("node:async_hooks");
|
|
42
|
+
/**
|
|
43
|
+
* Wrap an app-owned `PermissionFactsPort`-shaped object so a gate-5 reload inside an enforcement
|
|
44
|
+
* request notifies that request's {@link SessionFactsStore.onReload}. Behaviour-identical
|
|
45
|
+
* otherwise: same results, same errors (a throwing reload propagates and notifies nothing).
|
|
46
|
+
* Outside a guard request (no ALS store, e.g. the engine's in-handler decide calls) it is a no-op.
|
|
47
|
+
*/
|
|
48
|
+
function signalGateFiveReloads(port) {
|
|
49
|
+
return {
|
|
50
|
+
currentPermissionsVersion: (principalId, tenantId) => port.currentPermissionsVersion(principalId, tenantId),
|
|
51
|
+
loadCoarseGrants: async (principalId, tenantId) => {
|
|
52
|
+
const grants = await port.loadCoarseGrants(principalId, tenantId);
|
|
53
|
+
const store = exports.SHIELD_SESSION_FACTS_ALS.getStore();
|
|
54
|
+
try {
|
|
55
|
+
store?.onReload?.(principalId, tenantId, grants);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// A notification must never turn a decision into an error.
|
|
59
|
+
}
|
|
60
|
+
return grants;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
41
64
|
/**
|
|
42
65
|
* The ONE shared session-facts `AsyncLocalStorage` (see the file header). The
|
|
43
66
|
* enforcement guard imports this to `run()` the `decide()` call inside the request's
|