@c9up/warden 0.1.5 → 0.1.7
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 +1 -0
- package/dist/ResilientBlacklistDriver.js +2 -2
- package/dist/ResilientBlacklistDriver.js.map +1 -1
- package/dist/TokenBlacklist.d.ts +6 -2
- package/dist/TokenBlacklist.d.ts.map +1 -1
- package/dist/TokenBlacklist.js +2 -2
- package/dist/TokenBlacklist.js.map +1 -1
- package/dist/WardenProvider.d.ts.map +1 -1
- package/dist/WardenProvider.js +37 -6
- package/dist/WardenProvider.js.map +1 -1
- package/dist/config.d.ts +56 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/middleware.d.ts +31 -0
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +64 -3
- package/dist/middleware.js.map +1 -1
- package/dist/standalone.d.ts.map +1 -1
- package/dist/standalone.js +12 -0
- package/dist/standalone.js.map +1 -1
- package/dist/strategies/ApiKeyStrategy.d.ts.map +1 -1
- package/dist/strategies/ApiKeyStrategy.js +14 -1
- package/dist/strategies/ApiKeyStrategy.js.map +1 -1
- package/dist/strategies/JwtStrategy.d.ts +25 -1
- package/dist/strategies/JwtStrategy.d.ts.map +1 -1
- package/dist/strategies/JwtStrategy.js +4 -1
- package/dist/strategies/JwtStrategy.js.map +1 -1
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +2 -2
- package/src/ResilientBlacklistDriver.ts +2 -2
- package/src/TokenBlacklist.ts +7 -3
- package/src/WardenProvider.ts +42 -7
- package/src/config.ts +53 -1
- package/src/index.ts +1 -1
- package/src/middleware.ts +97 -2
- package/src/standalone.ts +14 -1
- package/src/strategies/ApiKeyStrategy.ts +14 -1
- package/src/strategies/JwtStrategy.ts +24 -7
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ class OrderController {
|
|
|
27
27
|
- Multi-strategy AuthManager (JWT, session, API key, OAuth)
|
|
28
28
|
- `@Guard()`, `@Permission()`, `@Role()` decorators
|
|
29
29
|
- RBAC: `hasRole`, `hasPermission`, `hasAllPermissions`
|
|
30
|
+
- Unified authorization: Bouncer-shaped abilities & policies over one resolver — RBAC + ACL + ownership + multi-tenant scope, zero `@adonisjs/bouncer` dependency
|
|
30
31
|
- Strategy exception safety (catch → AuthResult)
|
|
31
32
|
- Runtime strategy registration
|
|
32
33
|
- **Multi-factor authentication** — TOTP, backup codes, email/SMS OTP, WebAuthn/passkeys, all dependency-free
|
|
@@ -50,12 +50,12 @@ export class ResilientBlacklistDriver {
|
|
|
50
50
|
}
|
|
51
51
|
async cleanup() {
|
|
52
52
|
try {
|
|
53
|
-
await this.#primary.cleanup();
|
|
53
|
+
await this.#primary.cleanup?.();
|
|
54
54
|
}
|
|
55
55
|
catch (error) {
|
|
56
56
|
this.#onDegrade({ op: "cleanup", error });
|
|
57
57
|
}
|
|
58
|
-
await this.#fallback.cleanup();
|
|
58
|
+
await this.#fallback.cleanup?.();
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
//# sourceMappingURL=ResilientBlacklistDriver.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResilientBlacklistDriver.js","sourceRoot":"","sources":["../src/ResilientBlacklistDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAEN,qBAAqB,GACrB,MAAM,qBAAqB,CAAC;AAY7B,SAAS,gBAAgB,CAAC,EAAE,EAAE,EAAE,KAAK,EAAyB;IAC7D,OAAO,CAAC,IAAI,CACX,wCAAwC,EAAE,6BAA6B,EACvE,KAAK,CACL,CAAC;AACH,CAAC;AAED,MAAM,OAAO,wBAAwB;IAC3B,QAAQ,CAAkB;IAC1B,SAAS,CAAkB;IAC3B,UAAU,CAAyC;IAE5D,YACC,OAAwB,EACxB,WAA4B,IAAI,qBAAqB,EAAE,EACvD,SAAmC,EAAE;QAErC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,SAAS,IAAI,gBAAgB,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,SAAiB;QACvC,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACtC,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC1C,CAAC;IACF,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACpB,IAAI,CAAC;YACJ,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACtC,wEAAwE;YACxE,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;IACF,CAAC;IAED,KAAK,CAAC,OAAO;QACZ,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"ResilientBlacklistDriver.js","sourceRoot":"","sources":["../src/ResilientBlacklistDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAEN,qBAAqB,GACrB,MAAM,qBAAqB,CAAC;AAY7B,SAAS,gBAAgB,CAAC,EAAE,EAAE,EAAE,KAAK,EAAyB;IAC7D,OAAO,CAAC,IAAI,CACX,wCAAwC,EAAE,6BAA6B,EACvE,KAAK,CACL,CAAC;AACH,CAAC;AAED,MAAM,OAAO,wBAAwB;IAC3B,QAAQ,CAAkB;IAC1B,SAAS,CAAkB;IAC3B,UAAU,CAAyC;IAE5D,YACC,OAAwB,EACxB,WAA4B,IAAI,qBAAqB,EAAE,EACvD,SAAmC,EAAE;QAErC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,SAAS,IAAI,gBAAgB,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,SAAiB;QACvC,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACtC,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC1C,CAAC;IACF,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACpB,IAAI,CAAC;YACJ,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACtC,wEAAwE;YACxE,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;IACF,CAAC;IAED,KAAK,CAAC,OAAO;QACZ,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QACjC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3C,CAAC;QACD,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,CAAC;IAClC,CAAC;CACD"}
|
package/dist/TokenBlacklist.d.ts
CHANGED
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
export interface BlacklistDriver {
|
|
9
9
|
add(jti: string, expiresAt: number): Promise<void>;
|
|
10
10
|
has(jti: string): Promise<boolean>;
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Reap expired entries. Optional: TTL-backed stores (Redis/KeyDB) expire keys
|
|
13
|
+
* themselves and have nothing to sweep, so they may omit this.
|
|
14
|
+
*/
|
|
15
|
+
cleanup?(): Promise<void>;
|
|
12
16
|
}
|
|
13
17
|
/** In-memory blacklist for development. */
|
|
14
18
|
export declare class MemoryBlacklistDriver implements BlacklistDriver {
|
|
@@ -24,7 +28,7 @@ export declare class TokenBlacklist {
|
|
|
24
28
|
revoke(jti: string, expiresAt: number): Promise<void>;
|
|
25
29
|
/** Check if a token JTI is blacklisted. */
|
|
26
30
|
isRevoked(jti: string): Promise<boolean>;
|
|
27
|
-
/** Remove expired entries. */
|
|
31
|
+
/** Remove expired entries (no-op when the driver self-expires via TTL). */
|
|
28
32
|
cleanup(): Promise<void>;
|
|
29
33
|
}
|
|
30
34
|
//# sourceMappingURL=TokenBlacklist.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenBlacklist.d.ts","sourceRoot":"","sources":["../src/TokenBlacklist.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,eAAe;IAC/B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"TokenBlacklist.d.ts","sourceRoot":"","sources":["../src/TokenBlacklist.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,eAAe;IAC/B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC;;;OAGG;IACH,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,2CAA2C;AAC3C,qBAAa,qBAAsB,YAAW,eAAe;IAC5D,OAAO,CAAC,OAAO,CAAkC;IAE3C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUlC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAM9B;AAED,qBAAa,cAAc;IACd,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,eAAe;IAE3C,uCAAuC;IACjC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3D,2CAA2C;IACrC,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI9C,2EAA2E;IACrE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG9B"}
|
package/dist/TokenBlacklist.js
CHANGED
|
@@ -42,9 +42,9 @@ export class TokenBlacklist {
|
|
|
42
42
|
async isRevoked(jti) {
|
|
43
43
|
return this.driver.has(jti);
|
|
44
44
|
}
|
|
45
|
-
/** Remove expired entries. */
|
|
45
|
+
/** Remove expired entries (no-op when the driver self-expires via TTL). */
|
|
46
46
|
async cleanup() {
|
|
47
|
-
|
|
47
|
+
await this.driver.cleanup?.();
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
//# sourceMappingURL=TokenBlacklist.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenBlacklist.js","sourceRoot":"","sources":["../src/TokenBlacklist.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"TokenBlacklist.js","sourceRoot":"","sources":["../src/TokenBlacklist.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,2CAA2C;AAC3C,MAAM,OAAO,qBAAqB;IACzB,OAAO,GAAwB,IAAI,GAAG,EAAE,CAAC;IAEjD,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,SAAiB;QACvC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACpB,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,SAAS,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QAC1C,IAAI,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAC5B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACzB,OAAO,KAAK,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC;IACb,CAAC;IAED,KAAK,CAAC,OAAO;QACZ,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACvC,IAAI,GAAG,GAAG,GAAG;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACzC,CAAC;IACF,CAAC;CACD;AAED,MAAM,OAAO,cAAc;IACN;IAApB,YAAoB,MAAuB;QAAvB,WAAM,GAAN,MAAM,CAAiB;IAAG,CAAC;IAE/C,uCAAuC;IACvC,KAAK,CAAC,MAAM,CAAC,GAAW,EAAE,SAAiB;QAC1C,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACvC,CAAC;IAED,2CAA2C;IAC3C,KAAK,CAAC,SAAS,CAAC,GAAW;QAC1B,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,2EAA2E;IAC3E,KAAK,CAAC,OAAO;QACZ,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;IAC/B,CAAC;CACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WardenProvider.d.ts","sourceRoot":"","sources":["../src/WardenProvider.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"WardenProvider.d.ts","sourceRoot":"","sources":["../src/WardenProvider.ts"],"names":[],"mappings":"AAYA,UAAU,eAAe;IACxB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,GAAG,IAAI,CAAC;IACxD,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;CACjC;AAED,UAAU,iBAAiB;IAC1B,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;CAC7C;AAED,MAAM,WAAW,gBAAgB;IAChC,SAAS,EAAE,eAAe,CAAC;IAC3B,MAAM,EAAE,iBAAiB,CAAC;CAC1B;AAED,MAAM,CAAC,OAAO,OAAO,cAAc;IACtB,SAAS,CAAC,GAAG,EAAE,gBAAgB;gBAArB,GAAG,EAAE,gBAAgB;IAE3C,QAAQ;IAiGF,IAAI;CAGV"}
|
package/dist/WardenProvider.js
CHANGED
|
@@ -4,7 +4,9 @@ import { MfaManager } from "./mfa/MfaManager.js";
|
|
|
4
4
|
import { MemoryRightsStore } from "./rights/MemoryRightsStore.js";
|
|
5
5
|
import { RightsResolver } from "./rights/RightsResolver.js";
|
|
6
6
|
import { setAuth } from "./services/main.js";
|
|
7
|
+
import { ApiKeyStrategy } from "./strategies/ApiKeyStrategy.js";
|
|
7
8
|
import { JwtStrategy } from "./strategies/JwtStrategy.js";
|
|
9
|
+
import { SessionStrategy } from "./strategies/SessionStrategy.js";
|
|
8
10
|
export default class WardenProvider {
|
|
9
11
|
app;
|
|
10
12
|
constructor(app) {
|
|
@@ -21,11 +23,29 @@ export default class WardenProvider {
|
|
|
21
23
|
// AuthManager with `strategies: {}` and `defaultStrategy: 'jwt'`,
|
|
22
24
|
// passing the (then-permissive) constructor and erroring deep in the
|
|
23
25
|
// middleware loop.
|
|
24
|
-
if (!config?.jwt) {
|
|
25
|
-
throw new WardenError("WARDEN_NO_AUTH_CONFIG", `@c9up/warden: no authentication strategies configured. Set config.warden.auth.jwt
|
|
26
|
+
if (!config?.jwt && !config?.session && !config?.apiKey) {
|
|
27
|
+
throw new WardenError("WARDEN_NO_AUTH_CONFIG", `@c9up/warden: no authentication strategies configured. Set at least one of config.warden.auth.jwt / .session / .apiKey in your reamrc.ts before registering WardenProvider.`);
|
|
28
|
+
}
|
|
29
|
+
// Build the strategy table from whatever the app configured. Each strategy
|
|
30
|
+
// is also exposed by its class token (mirrors the jwt convention) so apps
|
|
31
|
+
// can resolve a specific one. The `revoke()` blacklist flows through
|
|
32
|
+
// `config.jwt.blacklist` into JwtStrategy.
|
|
33
|
+
const strategies = {};
|
|
34
|
+
if (config.jwt) {
|
|
35
|
+
const jwt = new JwtStrategy(config.jwt);
|
|
36
|
+
this.app.container.singleton(JwtStrategy, () => jwt);
|
|
37
|
+
strategies.jwt = jwt;
|
|
38
|
+
}
|
|
39
|
+
if (config.session) {
|
|
40
|
+
const session = new SessionStrategy(config.session);
|
|
41
|
+
this.app.container.singleton(SessionStrategy, () => session);
|
|
42
|
+
strategies.session = session;
|
|
43
|
+
}
|
|
44
|
+
if (config.apiKey) {
|
|
45
|
+
const apiKey = new ApiKeyStrategy(config.apiKey);
|
|
46
|
+
this.app.container.singleton(ApiKeyStrategy, () => apiKey);
|
|
47
|
+
strategies["api-key"] = apiKey;
|
|
26
48
|
}
|
|
27
|
-
const jwt = new JwtStrategy(config.jwt);
|
|
28
|
-
this.app.container.singleton(JwtStrategy, () => jwt);
|
|
29
49
|
// Rights layer (Epic 56): one resolver singleton backs BOTH the coarse
|
|
30
50
|
// RBAC helpers (injected into AuthManager below) and — once 56.6 lands —
|
|
31
51
|
// the Bouncer construction, so a coarse question and a policy question
|
|
@@ -39,10 +59,21 @@ export default class WardenProvider {
|
|
|
39
59
|
if (rightsStore instanceof MemoryRightsStore) {
|
|
40
60
|
this.app.container.singleton(MemoryRightsStore, () => rightsStore);
|
|
41
61
|
}
|
|
62
|
+
// Bouncer registry (Epic 56.6): the abilities/policies/scope-resolver the
|
|
63
|
+
// per-request `initializeBouncer` middleware builds each `ctx.bouncer`
|
|
64
|
+
// from. Registered (even when empty) under a string token so the agnostic
|
|
65
|
+
// middleware resolves it by name, mirroring the "auth" alias above.
|
|
66
|
+
const bouncerRegistry = {
|
|
67
|
+
abilities: config.abilities ?? {},
|
|
68
|
+
policies: config.policies ?? {},
|
|
69
|
+
resolveScope: config.resolveScope,
|
|
70
|
+
};
|
|
71
|
+
this.app.container.singleton("bouncer:registry", () => bouncerRegistry);
|
|
72
|
+
// Default to the configured strategy, else the first one registered.
|
|
73
|
+
const defaultStrategy = config.defaultStrategy ?? Object.keys(strategies)[0];
|
|
42
74
|
this.app.container.singleton(AuthManager, () => {
|
|
43
|
-
const strategies = { jwt };
|
|
44
75
|
return new AuthManager({
|
|
45
|
-
defaultStrategy
|
|
76
|
+
defaultStrategy,
|
|
46
77
|
strategies,
|
|
47
78
|
rights: rightsResolver,
|
|
48
79
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WardenProvider.js","sourceRoot":"","sources":["../src/WardenProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"WardenProvider.js","sourceRoot":"","sources":["../src/WardenProvider.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
|
@@ -15,19 +15,55 @@
|
|
|
15
15
|
* })
|
|
16
16
|
*/
|
|
17
17
|
import type { UserPayload } from "./AuthManager.js";
|
|
18
|
+
import type { BasePolicy } from "./bouncer/BasePolicy.js";
|
|
19
|
+
import type { Ability } from "./bouncer/types.js";
|
|
18
20
|
import type { MfaManager } from "./mfa/MfaManager.js";
|
|
19
|
-
import type { RightsStore } from "./rights/types.js";
|
|
21
|
+
import type { RightsStore, Scope } from "./rights/types.js";
|
|
22
|
+
import type { ApiKeyConfig } from "./strategies/ApiKeyStrategy.js";
|
|
23
|
+
import type { SessionStrategyConfig } from "./strategies/SessionStrategy.js";
|
|
24
|
+
import type { TokenBlacklist } from "./TokenBlacklist.js";
|
|
25
|
+
/**
|
|
26
|
+
* Minimal request shape passed to `resolveScope` — enough to derive a tenant
|
|
27
|
+
* from a header or the authenticated user, without coupling config to the full
|
|
28
|
+
* HTTP context type (keeps Warden agnostic of the host framework).
|
|
29
|
+
*/
|
|
30
|
+
export interface ScopeRequestContext {
|
|
31
|
+
request: {
|
|
32
|
+
headers: Record<string, string>;
|
|
33
|
+
};
|
|
34
|
+
auth?: {
|
|
35
|
+
user?: UserPayload | null;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
20
38
|
export interface JwtConfig {
|
|
21
39
|
secret: string;
|
|
22
40
|
expiresInSeconds?: number;
|
|
23
41
|
findUser: (id: string) => Promise<UserPayload | null>;
|
|
24
42
|
verifyCredentials: (email: string, password: string) => Promise<UserPayload | null>;
|
|
43
|
+
/**
|
|
44
|
+
* Optional revocation list. Supply a `TokenBlacklist` (Memory/Redis-backed) to
|
|
45
|
+
* enable `revoke()`/early-rejection of JWTs before expiry. Without it, `revoke()`
|
|
46
|
+
* throws (the call would be a silent no-op).
|
|
47
|
+
*/
|
|
48
|
+
blacklist?: TokenBlacklist;
|
|
25
49
|
}
|
|
26
50
|
export interface WardenConfig {
|
|
27
51
|
/** Default auth strategy (default: 'jwt'). */
|
|
28
52
|
defaultStrategy?: string;
|
|
29
53
|
/** JWT strategy configuration. */
|
|
30
54
|
jwt?: JwtConfig;
|
|
55
|
+
/**
|
|
56
|
+
* Session strategy configuration. Supply `findUser` so `@Guard('session')`
|
|
57
|
+
* routes resolve the authenticated user from the session id. Without this,
|
|
58
|
+
* the `session` strategy is unregistered and `@Guard('session')` throws.
|
|
59
|
+
*/
|
|
60
|
+
session?: SessionStrategyConfig;
|
|
61
|
+
/**
|
|
62
|
+
* API-key strategy configuration. Supply `findByKey` so `@Guard('api-key')`
|
|
63
|
+
* routes resolve the user (and scopes) from the request header. Without this,
|
|
64
|
+
* the `api-key` strategy is unregistered and `@Guard('api-key')` throws.
|
|
65
|
+
*/
|
|
66
|
+
apiKey?: ApiKeyConfig;
|
|
31
67
|
/**
|
|
32
68
|
* Rights layer configuration (Epic 56). Supply a custom `store` to back the
|
|
33
69
|
* unified resolver with a DB-backed driver; when omitted, an in-memory
|
|
@@ -37,6 +73,25 @@ export interface WardenConfig {
|
|
|
37
73
|
rights?: {
|
|
38
74
|
store?: RightsStore;
|
|
39
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* Standalone abilities the per-request Bouncer knows by name (Epic 56.6).
|
|
78
|
+
* Keyed by the string passed to `ctx.bouncer.authorize('post.edit', …)`.
|
|
79
|
+
* Define with `Bouncer.ability((user, post) => …)`.
|
|
80
|
+
*/
|
|
81
|
+
abilities?: Record<string, Ability<never[]>>;
|
|
82
|
+
/**
|
|
83
|
+
* Class-based policies the per-request Bouncer knows by name (Epic 56.6).
|
|
84
|
+
* Reachable via `ctx.bouncer.with('PostPolicy')`. Each entry is the policy
|
|
85
|
+
* constructor; a fresh instance is created per check.
|
|
86
|
+
*/
|
|
87
|
+
policies?: Record<string, new () => BasePolicy>;
|
|
88
|
+
/**
|
|
89
|
+
* Derive the authorization scope for a request (AD4 — scope-first). Return
|
|
90
|
+
* `{ tenant }` to scope rights to a tenant (e.g. from a subdomain or header),
|
|
91
|
+
* or `"global"`. Omitted ⇒ every request runs in the implicit `global` scope
|
|
92
|
+
* (zero config for single-tenant apps).
|
|
93
|
+
*/
|
|
94
|
+
resolveScope?: (ctx: ScopeRequestContext) => Scope | Promise<Scope>;
|
|
40
95
|
/**
|
|
41
96
|
* Multi-factor authentication. Supply a configured `MfaManager` (built with
|
|
42
97
|
* your persistent stores + providers) to register it in the container as
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IAC7C,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;KAAE,CAAC;CACrC;AAED,MAAM,WAAW,SAAS;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACtD,iBAAiB,EAAE,CAClB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,KACZ,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACjC;;;;OAIG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC5B,8CAA8C;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kCAAkC;IAClC,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB;;;;OAIG;IACH,OAAO,CAAC,EAAE,qBAAqB,CAAC;IAChC;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;;;OAKG;IACH,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,WAAW,CAAA;KAAE,CAAC;IACjC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,UAAU,CAAC,CAAC;IAChD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACpE;;;;OAIG;IACH,GAAG,CAAC,EAAE;QAAE,OAAO,EAAE,UAAU,CAAA;KAAE,CAAC;CAC9B;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAE/D"}
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAwFH,oEAAoE;AACpE,MAAM,UAAU,YAAY,CAAC,MAAoB;IAChD,OAAO,MAAM,CAAC;AACf,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export type { EffectivePermissions, RightsStore, Scope, } from "./rights/types.j
|
|
|
43
43
|
export { scopeKey } from "./rights/types.js";
|
|
44
44
|
export type { ApiKeyConfig } from "./strategies/ApiKeyStrategy.js";
|
|
45
45
|
export { ApiKeyStrategy } from "./strategies/ApiKeyStrategy.js";
|
|
46
|
-
export type { JwtStrategyConfig } from "./strategies/JwtStrategy.js";
|
|
46
|
+
export type { JwtClaims, JwtStrategyConfig } from "./strategies/JwtStrategy.js";
|
|
47
47
|
export { generateJwtSecret, JwtStrategy } from "./strategies/JwtStrategy.js";
|
|
48
48
|
export type { SessionStore, SessionStrategyConfig, } from "./strategies/SessionStrategy.js";
|
|
49
49
|
export { SessionStrategy } from "./strategies/SessionStrategy.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/middleware.d.ts
CHANGED
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
* }
|
|
17
17
|
*/
|
|
18
18
|
import { type AuthResult } from "./AuthManager.js";
|
|
19
|
+
import type { BasePolicy } from "./bouncer/BasePolicy.js";
|
|
20
|
+
import { Bouncer } from "./bouncer/Bouncer.js";
|
|
21
|
+
import type { Ability } from "./bouncer/types.js";
|
|
22
|
+
import type { ScopeRequestContext } from "./config.js";
|
|
23
|
+
import type { Scope } from "./rights/types.js";
|
|
19
24
|
import type { SessionStore } from "./strategies/SessionStrategy.js";
|
|
20
25
|
export interface WardenContext {
|
|
21
26
|
request: {
|
|
@@ -34,6 +39,8 @@ export interface WardenContext {
|
|
|
34
39
|
session?: SessionStore;
|
|
35
40
|
/** Set by the middleware after successful auth. */
|
|
36
41
|
auth?: AuthResult;
|
|
42
|
+
/** Set by `initializeBouncer` — the per-request authorization entry point. */
|
|
43
|
+
bouncer?: Bouncer;
|
|
37
44
|
/** The route handler metadata (decorators). */
|
|
38
45
|
route?: {
|
|
39
46
|
controller?: object;
|
|
@@ -47,5 +54,29 @@ type WardenNext = () => Promise<void> | void;
|
|
|
47
54
|
* and authenticates + authorizes the request.
|
|
48
55
|
*/
|
|
49
56
|
export declare function wardenMiddleware(ctx: WardenContext, next: WardenNext): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* The abilities/policies/scope-resolver a per-request Bouncer is built from.
|
|
59
|
+
* Registered once by `WardenProvider` (from `config.auth`) under the container
|
|
60
|
+
* token `"bouncer:registry"`; read fresh by `initializeBouncer` per request.
|
|
61
|
+
*/
|
|
62
|
+
export interface BouncerRegistry {
|
|
63
|
+
abilities: Record<string, Ability<never[]>>;
|
|
64
|
+
policies: Record<string, new () => BasePolicy>;
|
|
65
|
+
resolveScope?: (ctx: ScopeRequestContext) => Scope | Promise<Scope>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Bouncer initializer (Epic 56.6) — a GLOBAL middleware (register it for every
|
|
69
|
+
* route, after `wardenMiddleware`). It builds the per-request {@link Bouncer}
|
|
70
|
+
* from the authenticated user (`ctx.auth?.user`, or `null` for a guest), the
|
|
71
|
+
* shared `RightsResolver`, and the registered abilities/policies, then attaches
|
|
72
|
+
* it as `ctx.bouncer`. Handlers authorize via `await ctx.bouncer.authorize(...)`
|
|
73
|
+
* (throws `WARDEN_AUTHORIZATION_FAILURE` carrying `status: 403`, which the host's
|
|
74
|
+
* ExceptionHandler maps to a 403 response) or branch on `ctx.bouncer.allows(...)`.
|
|
75
|
+
*
|
|
76
|
+
* Unlike `wardenMiddleware` (a per-route GUARD that short-circuits public
|
|
77
|
+
* routes), this runs on EVERY request so `ctx.bouncer` is always available —
|
|
78
|
+
* including for guests — matching AdonisJS's `initialize_bouncer_middleware`.
|
|
79
|
+
*/
|
|
80
|
+
export declare function initializeBouncer(ctx: WardenContext, next: WardenNext): Promise<void>;
|
|
50
81
|
export {};
|
|
51
82
|
//# sourceMappingURL=middleware.d.ts.map
|
package/dist/middleware.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAEN,KAAK,UAAU,EAGf,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAEN,KAAK,UAAU,EAGf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAQvD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAcpE,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE;QACR,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAChC,CAAC;IACF,QAAQ,EAAE;QACT,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAC/B,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;KAC9B,CAAC;IACF,SAAS,EAAE;QACV,OAAO,EAAE,CACR,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,KAC/D,OAAO,CAAC;KACb,CAAC;IACF,4DAA4D;IAC5D,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,mDAAmD;IACnD,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+CAA+C;IAC/C,KAAK,CAAC,EAAE;QACP,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;KACzB,CAAC;CACF;AAED,KAAK,UAAU,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAE7C;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,iBAwH1E;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC5C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,UAAU,CAAC,CAAC;IAC/C,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;CACpE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,iBAmB3E"}
|
package/dist/middleware.js
CHANGED
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
* }
|
|
17
17
|
*/
|
|
18
18
|
import { AuthManager, sanitizePayload, } from "./AuthManager.js";
|
|
19
|
+
import { Bouncer } from "./bouncer/Bouncer.js";
|
|
19
20
|
import { getGuardMetadata, getPermissionMetadata, getRequireMfaMetadata, getRoleMetadata, } from "./Guard.js";
|
|
21
|
+
import { RightsResolver } from "./rights/RightsResolver.js";
|
|
20
22
|
function hasVerifyWithContext(strategy) {
|
|
21
23
|
return (typeof strategy.verifyWithContext === "function");
|
|
22
24
|
}
|
|
@@ -101,7 +103,8 @@ export async function wardenMiddleware(ctx, next) {
|
|
|
101
103
|
// bypass permission checks. This is the strictest model — callers who want
|
|
102
104
|
// role-OR-permission semantics should use a custom guard instead.
|
|
103
105
|
if (controller && action) {
|
|
104
|
-
const
|
|
106
|
+
const scope = await resolveRequestScope(ctx);
|
|
107
|
+
const denied = await checkAuthorization(auth, user, controller, action, scope);
|
|
105
108
|
if (denied) {
|
|
106
109
|
ctx.response.status(403);
|
|
107
110
|
ctx.response.json({ error: { code: "FORBIDDEN", message: denied } });
|
|
@@ -124,6 +127,64 @@ export async function wardenMiddleware(ctx, next) {
|
|
|
124
127
|
ctx.auth = result;
|
|
125
128
|
await next();
|
|
126
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Bouncer initializer (Epic 56.6) — a GLOBAL middleware (register it for every
|
|
132
|
+
* route, after `wardenMiddleware`). It builds the per-request {@link Bouncer}
|
|
133
|
+
* from the authenticated user (`ctx.auth?.user`, or `null` for a guest), the
|
|
134
|
+
* shared `RightsResolver`, and the registered abilities/policies, then attaches
|
|
135
|
+
* it as `ctx.bouncer`. Handlers authorize via `await ctx.bouncer.authorize(...)`
|
|
136
|
+
* (throws `WARDEN_AUTHORIZATION_FAILURE` carrying `status: 403`, which the host's
|
|
137
|
+
* ExceptionHandler maps to a 403 response) or branch on `ctx.bouncer.allows(...)`.
|
|
138
|
+
*
|
|
139
|
+
* Unlike `wardenMiddleware` (a per-route GUARD that short-circuits public
|
|
140
|
+
* routes), this runs on EVERY request so `ctx.bouncer` is always available —
|
|
141
|
+
* including for guests — matching AdonisJS's `initialize_bouncer_middleware`.
|
|
142
|
+
*/
|
|
143
|
+
export async function initializeBouncer(ctx, next) {
|
|
144
|
+
// Both dependencies are registered by WardenProvider. Resolve defensively:
|
|
145
|
+
// a host that wired the middleware but not the provider gets an empty Bouncer
|
|
146
|
+
// (no abilities/resolver) rather than a crash on every request.
|
|
147
|
+
const resolved = tryResolve(ctx, RightsResolver);
|
|
148
|
+
const resolver = resolved instanceof RightsResolver ? resolved : undefined;
|
|
149
|
+
const candidate = tryResolve(ctx, "bouncer:registry");
|
|
150
|
+
const registry = isBouncerRegistry(candidate) ? candidate : undefined;
|
|
151
|
+
const user = ctx.auth?.user ?? null;
|
|
152
|
+
const scope = registry?.resolveScope
|
|
153
|
+
? await registry.resolveScope(ctx)
|
|
154
|
+
: "global";
|
|
155
|
+
ctx.bouncer = new Bouncer(user, registry?.abilities, registry?.policies, {
|
|
156
|
+
scope,
|
|
157
|
+
resolver,
|
|
158
|
+
});
|
|
159
|
+
await next();
|
|
160
|
+
}
|
|
161
|
+
/** Resolve a container token, returning undefined instead of throwing when absent. */
|
|
162
|
+
function tryResolve(ctx, token) {
|
|
163
|
+
try {
|
|
164
|
+
return ctx.container.resolve(token);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/** Structural guard — the registry is a plain object carrying ability/policy tables. */
|
|
171
|
+
function isBouncerRegistry(value) {
|
|
172
|
+
return (typeof value === "object" &&
|
|
173
|
+
value !== null &&
|
|
174
|
+
"abilities" in value &&
|
|
175
|
+
"policies" in value);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Resolve the request's authorization scope from the registry's `resolveScope`
|
|
179
|
+
* hook (default `"global"`). Shared so the @Role/@Permission decorator gate and
|
|
180
|
+
* the Bouncer path evaluate in the SAME scope — without this the decorator path
|
|
181
|
+
* hardcoded "global" and tenant-scoped roles/grants never satisfied @Role/@Permission.
|
|
182
|
+
*/
|
|
183
|
+
async function resolveRequestScope(ctx) {
|
|
184
|
+
const candidate = tryResolve(ctx, "bouncer:registry");
|
|
185
|
+
const registry = isBouncerRegistry(candidate) ? candidate : undefined;
|
|
186
|
+
return registry?.resolveScope ? await registry.resolveScope(ctx) : "global";
|
|
187
|
+
}
|
|
127
188
|
/**
|
|
128
189
|
* Try each declared strategy in order — session strategies via
|
|
129
190
|
* `verifyWithContext()`, others via `verify(token)` with native-first credential
|
|
@@ -191,13 +252,13 @@ async function tryAuthenticate(auth, strategies, creds) {
|
|
|
191
252
|
* EffectivePermissions set (Epic 56, D7). Returns the FORBIDDEN message, or null
|
|
192
253
|
* when authorised (and zero-cost when neither decorator is present).
|
|
193
254
|
*/
|
|
194
|
-
async function checkAuthorization(auth, user, controller, action) {
|
|
255
|
+
async function checkAuthorization(auth, user, controller, action, scope) {
|
|
195
256
|
const requiredPermissions = getPermissionMetadata(controller, action);
|
|
196
257
|
const requiredRoles = getRoleMetadata(controller, action);
|
|
197
258
|
if (requiredPermissions.length === 0 && requiredRoles.length === 0) {
|
|
198
259
|
return null;
|
|
199
260
|
}
|
|
200
|
-
const effective = await auth.resolvePermissions(user,
|
|
261
|
+
const effective = await auth.resolvePermissions(user, scope);
|
|
201
262
|
if (requiredPermissions.length > 0) {
|
|
202
263
|
const missing = requiredPermissions.filter((p) => !effective.has(p));
|
|
203
264
|
if (missing.length > 0)
|
package/dist/middleware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACN,WAAW,EAGX,eAAe,GACf,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|
package/dist/standalone.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../src/standalone.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EACN,WAAW,EACX,KAAK,UAAU,
|
|
1
|
+
{"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../src/standalone.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EACN,WAAW,EACX,KAAK,UAAU,EAEf,KAAK,WAAW,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,MAAM,WAAW,MAAM;IACtB,oEAAoE;IACpE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC9D,wDAAwD;IACxD,YAAY,CACX,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,QAAQ,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,CAAC,CAAC;IACvB,qDAAqD;IACrD,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,mDAAmD;IACnD,OAAO,EAAE,WAAW,CAAC;IACrB,iFAAiF;IACjF,iBAAiB,IAAI,CACpB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,IAAI,EAAE,MAAM,IAAI,KACZ,IAAI,CAAC;CACV;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAuFzD"}
|
package/dist/standalone.js
CHANGED
|
@@ -17,15 +17,27 @@
|
|
|
17
17
|
* if (!result.authenticated) return res.status(401).json(result)
|
|
18
18
|
*/
|
|
19
19
|
import { AuthManager, } from "./AuthManager.js";
|
|
20
|
+
import { ApiKeyStrategy } from "./strategies/ApiKeyStrategy.js";
|
|
20
21
|
import { JwtStrategy } from "./strategies/JwtStrategy.js";
|
|
22
|
+
import { SessionStrategy } from "./strategies/SessionStrategy.js";
|
|
21
23
|
/**
|
|
22
24
|
* Create a standalone Warden instance — no Ream container needed.
|
|
23
25
|
*/
|
|
24
26
|
export function createWarden(config) {
|
|
27
|
+
// Mirror WardenProvider: build EVERY configured strategy, not just jwt —
|
|
28
|
+
// otherwise config.session / config.apiKey were silently dropped and a
|
|
29
|
+
// jwt-less config (e.g. { defaultStrategy: 'api-key', apiKey }) threw
|
|
30
|
+
// INVALID_CONFIG at boot (audit 2026-06-13). Keys match the @Guard names.
|
|
25
31
|
const strategies = {};
|
|
26
32
|
if (config.jwt) {
|
|
27
33
|
strategies.jwt = new JwtStrategy(config.jwt);
|
|
28
34
|
}
|
|
35
|
+
if (config.session) {
|
|
36
|
+
strategies.session = new SessionStrategy(config.session);
|
|
37
|
+
}
|
|
38
|
+
if (config.apiKey) {
|
|
39
|
+
strategies["api-key"] = new ApiKeyStrategy(config.apiKey);
|
|
40
|
+
}
|
|
29
41
|
const manager = new AuthManager({
|
|
30
42
|
defaultStrategy: config.defaultStrategy ?? "jwt",
|
|
31
43
|
strategies,
|
package/dist/standalone.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standalone.js","sourceRoot":"","sources":["../src/standalone.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EACN,WAAW,
|
|
1
|
+
{"version":3,"file":"standalone.js","sourceRoot":"","sources":["../src/standalone.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiKeyStrategy.d.ts","sourceRoot":"","sources":["../../src/strategies/ApiKeyStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE/E,MAAM,WAAW,YAAY;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,CACV,GAAG,EAAE,MAAM,KACP,OAAO,CAAC;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;CAC9D;AAED,qBAAa,cAAe,YAAW,YAAY;;IAClD,IAAI,SAAa;gBAIL,MAAM,EAAE,YAAY;IAKhC,wDAAwD;IACxD,IAAI,UAAU,IAAI,MAAM,CAEvB;IAEK,YAAY,CAAC,YAAY,EAAE;QAChC,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,UAAU,CAAC;IAMjB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"ApiKeyStrategy.d.ts","sourceRoot":"","sources":["../../src/strategies/ApiKeyStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE/E,MAAM,WAAW,YAAY;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,CACV,GAAG,EAAE,MAAM,KACP,OAAO,CAAC;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;CAC9D;AAED,qBAAa,cAAe,YAAW,YAAY;;IAClD,IAAI,SAAa;gBAIL,MAAM,EAAE,YAAY;IAKhC,wDAAwD;IACxD,IAAI,UAAU,IAAI,MAAM,CAEvB;IAEK,YAAY,CAAC,YAAY,EAAE;QAChC,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,UAAU,CAAC;IAMjB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;CAyBhD"}
|
|
@@ -26,7 +26,20 @@ export class ApiKeyStrategy {
|
|
|
26
26
|
return { authenticated: false, error: "Invalid API key" };
|
|
27
27
|
const user = { ...result.user };
|
|
28
28
|
if (result.scopes && result.scopes.length > 0) {
|
|
29
|
-
//
|
|
29
|
+
// Expose the API key's scopes on `user.permissions` (without mutating
|
|
30
|
+
// the source object) so APP code can read them via
|
|
31
|
+
// `ctx.auth.user.permissions`.
|
|
32
|
+
//
|
|
33
|
+
// IMPORTANT — these scopes are a SEPARATE axis from the `@Permission`
|
|
34
|
+
// route gate, by design (Adonis Bouncer parity): the gate flows through
|
|
35
|
+
// RightsResolver, which resolves grants from the rights STORE and
|
|
36
|
+
// deliberately ignores payload-carried permissions (see RightsResolver
|
|
37
|
+
// D1). This mirrors AdonisJS, where token abilities are checked via
|
|
38
|
+
// `currentAccessToken.allows(scope)` and are NOT consulted by Bouncer
|
|
39
|
+
// ability/policy checks. So an API key carrying `orders.create` is NOT
|
|
40
|
+
// auto-granted by `@Permission('orders.create')` — that's intentional,
|
|
41
|
+
// not a bug. Gate API-key routes on scopes with an explicit app-level
|
|
42
|
+
// check against `ctx.auth.user.permissions`.
|
|
30
43
|
const merged = new Set([...(user.permissions ?? []), ...result.scopes]);
|
|
31
44
|
user.permissions = [...merged];
|
|
32
45
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiKeyStrategy.js","sourceRoot":"","sources":["../../src/strategies/ApiKeyStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,MAAM,OAAO,cAAc;IAC1B,IAAI,GAAG,SAAS,CAAC;IACjB,OAAO,CAAe;IACtB,WAAW,CAAS;IAEpB,YAAY,MAAoB;QAC/B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,UAAU,IAAI,WAAW,CAAC;IACrD,CAAC;IAED,wDAAwD;IACxD,IAAI,UAAU;QACb,OAAO,IAAI,CAAC,WAAW,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,YAGlB;QACA,MAAM,IAAI,KAAK,CACd,uFAAuF,CACvF,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAa;QACzB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACnD,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;QAEvE,MAAM,IAAI,GAAgB,EAAE,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QAC7C,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/C,
|
|
1
|
+
{"version":3,"file":"ApiKeyStrategy.js","sourceRoot":"","sources":["../../src/strategies/ApiKeyStrategy.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,MAAM,OAAO,cAAc;IAC1B,IAAI,GAAG,SAAS,CAAC;IACjB,OAAO,CAAe;IACtB,WAAW,CAAS;IAEpB,YAAY,MAAoB;QAC/B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,UAAU,IAAI,WAAW,CAAC;IACrD,CAAC;IAED,wDAAwD;IACxD,IAAI,UAAU;QACb,OAAO,IAAI,CAAC,WAAW,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,YAGlB;QACA,MAAM,IAAI,KAAK,CACd,uFAAuF,CACvF,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAa;QACzB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACnD,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;QAEvE,MAAM,IAAI,GAAgB,EAAE,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QAC7C,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/C,sEAAsE;YACtE,mDAAmD;YACnD,+BAA+B;YAC/B,EAAE;YACF,sEAAsE;YACtE,wEAAwE;YACxE,kEAAkE;YAClE,uEAAuE;YACvE,oEAAoE;YACpE,sEAAsE;YACtE,uEAAuE;YACvE,uEAAuE;YACvE,sEAAsE;YACtE,6CAA6C;YAC7C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YACxE,IAAI,CAAC,WAAW,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACtC,CAAC;CACD"}
|
|
@@ -4,6 +4,22 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { AuthResult, AuthStrategy, UserPayload } from "../AuthManager.js";
|
|
6
6
|
import type { TokenBlacklist } from "../TokenBlacklist.js";
|
|
7
|
+
/**
|
|
8
|
+
* The verified JWT claims. Passed to `findUser` as the second argument so an app
|
|
9
|
+
* can reject a token on more than `sub` — e.g. compare `iat` to a stored
|
|
10
|
+
* `passwordChangedAt` (kill sessions on reset), check a `tokenVersion` custom
|
|
11
|
+
* claim, or honour the `jti` directly. Return `null` from `findUser` to reject.
|
|
12
|
+
*/
|
|
13
|
+
export interface JwtClaims {
|
|
14
|
+
sub: string;
|
|
15
|
+
roles?: string[];
|
|
16
|
+
permissions?: string[];
|
|
17
|
+
iat: number;
|
|
18
|
+
exp: number;
|
|
19
|
+
/** RFC 7519 JWT ID — populated at sign time so revocation can target a single token. */
|
|
20
|
+
jti: string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
7
23
|
export interface JwtStrategyConfig {
|
|
8
24
|
/**
|
|
9
25
|
* Active signing secret. New tokens are always signed with this key.
|
|
@@ -19,7 +35,15 @@ export interface JwtStrategyConfig {
|
|
|
19
35
|
*/
|
|
20
36
|
previousSecrets?: readonly string[];
|
|
21
37
|
expiresInSeconds?: number;
|
|
22
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the user for a verified token. Receives the token `sub` AND the
|
|
40
|
+
* full verified `claims` — use the claims to enforce session invalidation
|
|
41
|
+
* the strategy can't know about (e.g. reject when `claims.iat` predates the
|
|
42
|
+
* user's `passwordChangedAt`, or a `claims.tokenVersion` is stale) by
|
|
43
|
+
* returning `null`. The second arg is optional for callers that only need
|
|
44
|
+
* the id (existing one-arg `findUser` functions stay valid).
|
|
45
|
+
*/
|
|
46
|
+
findUser: (id: string, claims: JwtClaims) => Promise<UserPayload | null>;
|
|
23
47
|
verifyCredentials: (email: string, password: string) => Promise<UserPayload | null>;
|
|
24
48
|
/**
|
|
25
49
|
* Optional blacklist for revocation. When supplied, `verify` rejects
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JwtStrategy.d.ts","sourceRoot":"","sources":["../../src/strategies/JwtStrategy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE/E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"JwtStrategy.d.ts","sourceRoot":"","sources":["../../src/strategies/JwtStrategy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE/E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,wFAAwF;IACxF,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AA6BD,MAAM,WAAW,iBAAiB;IACjC;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;OAOG;IACH,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACzE,iBAAiB,EAAE,CAClB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,KACZ,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACjC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,qBAAa,WAAY,YAAW,YAAY;;IAC/C,IAAI,SAAS;gBAaD,MAAM,EAAE,iBAAiB;IAgC/B,YAAY,CACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,OAAO,CAAC,UAAU,CAAC;IAkBhB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAmChD,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM;IAepC;;;;;;;;OAQG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAa7C;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
|
|
@@ -104,7 +104,10 @@ export class JwtStrategy {
|
|
|
104
104
|
return { authenticated: false, error: "Token revoked" };
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
-
|
|
107
|
+
// Pass the FULL verified claims (not just `sub`) so the app can reject
|
|
108
|
+
// stale tokens it alone can judge — iat vs passwordChangedAt, tokenVersion,
|
|
109
|
+
// etc. — by returning null. This is the session-invalidation seam.
|
|
110
|
+
const user = await this.#findUser(payload.sub, payload);
|
|
108
111
|
if (!user) {
|
|
109
112
|
return { authenticated: false, error: "User not found" };
|
|
110
113
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JwtStrategy.js","sourceRoot":"","sources":["../../src/strategies/JwtStrategy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"JwtStrategy.js","sourceRoot":"","sources":["../../src/strategies/JwtStrategy.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c9up/warden",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Warden — Authentication for the Ream framework",
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "Warden — Authentication & authorization for the Ream framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -75,10 +75,10 @@ export class ResilientBlacklistDriver implements BlacklistDriver {
|
|
|
75
75
|
|
|
76
76
|
async cleanup(): Promise<void> {
|
|
77
77
|
try {
|
|
78
|
-
await this.#primary.cleanup();
|
|
78
|
+
await this.#primary.cleanup?.();
|
|
79
79
|
} catch (error) {
|
|
80
80
|
this.#onDegrade({ op: "cleanup", error });
|
|
81
81
|
}
|
|
82
|
-
await this.#fallback.cleanup();
|
|
82
|
+
await this.#fallback.cleanup?.();
|
|
83
83
|
}
|
|
84
84
|
}
|
package/src/TokenBlacklist.ts
CHANGED
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
export interface BlacklistDriver {
|
|
10
10
|
add(jti: string, expiresAt: number): Promise<void>;
|
|
11
11
|
has(jti: string): Promise<boolean>;
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Reap expired entries. Optional: TTL-backed stores (Redis/KeyDB) expire keys
|
|
14
|
+
* themselves and have nothing to sweep, so they may omit this.
|
|
15
|
+
*/
|
|
16
|
+
cleanup?(): Promise<void>;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
/** In-memory blacklist for development. */
|
|
@@ -51,8 +55,8 @@ export class TokenBlacklist {
|
|
|
51
55
|
return this.driver.has(jti);
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
/** Remove expired entries. */
|
|
58
|
+
/** Remove expired entries (no-op when the driver self-expires via TTL). */
|
|
55
59
|
async cleanup(): Promise<void> {
|
|
56
|
-
|
|
60
|
+
await this.driver.cleanup?.();
|
|
57
61
|
}
|
|
58
62
|
}
|
package/src/WardenProvider.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { AuthManager } from "./AuthManager.js";
|
|
1
|
+
import { AuthManager, type AuthStrategy } from "./AuthManager.js";
|
|
2
2
|
import type { WardenConfig } from "./config.js";
|
|
3
3
|
import { WardenError } from "./errors.js";
|
|
4
4
|
import { MfaManager } from "./mfa/MfaManager.js";
|
|
5
|
+
import type { BouncerRegistry } from "./middleware.js";
|
|
5
6
|
import { MemoryRightsStore } from "./rights/MemoryRightsStore.js";
|
|
6
7
|
import { RightsResolver } from "./rights/RightsResolver.js";
|
|
7
8
|
import { setAuth } from "./services/main.js";
|
|
9
|
+
import { ApiKeyStrategy } from "./strategies/ApiKeyStrategy.js";
|
|
8
10
|
import { JwtStrategy } from "./strategies/JwtStrategy.js";
|
|
11
|
+
import { SessionStrategy } from "./strategies/SessionStrategy.js";
|
|
9
12
|
|
|
10
13
|
interface WardenContainer {
|
|
11
14
|
singleton(token: unknown, factory: () => unknown): void;
|
|
@@ -36,15 +39,34 @@ export default class WardenProvider {
|
|
|
36
39
|
// AuthManager with `strategies: {}` and `defaultStrategy: 'jwt'`,
|
|
37
40
|
// passing the (then-permissive) constructor and erroring deep in the
|
|
38
41
|
// middleware loop.
|
|
39
|
-
if (!config?.jwt) {
|
|
42
|
+
if (!config?.jwt && !config?.session && !config?.apiKey) {
|
|
40
43
|
throw new WardenError(
|
|
41
44
|
"WARDEN_NO_AUTH_CONFIG",
|
|
42
|
-
`@c9up/warden: no authentication strategies configured. Set config.warden.auth.jwt
|
|
45
|
+
`@c9up/warden: no authentication strategies configured. Set at least one of config.warden.auth.jwt / .session / .apiKey in your reamrc.ts before registering WardenProvider.`,
|
|
43
46
|
);
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
// Build the strategy table from whatever the app configured. Each strategy
|
|
50
|
+
// is also exposed by its class token (mirrors the jwt convention) so apps
|
|
51
|
+
// can resolve a specific one. The `revoke()` blacklist flows through
|
|
52
|
+
// `config.jwt.blacklist` into JwtStrategy.
|
|
53
|
+
const strategies: Record<string, AuthStrategy> = {};
|
|
54
|
+
|
|
55
|
+
if (config.jwt) {
|
|
56
|
+
const jwt = new JwtStrategy(config.jwt);
|
|
57
|
+
this.app.container.singleton(JwtStrategy, () => jwt);
|
|
58
|
+
strategies.jwt = jwt;
|
|
59
|
+
}
|
|
60
|
+
if (config.session) {
|
|
61
|
+
const session = new SessionStrategy(config.session);
|
|
62
|
+
this.app.container.singleton(SessionStrategy, () => session);
|
|
63
|
+
strategies.session = session;
|
|
64
|
+
}
|
|
65
|
+
if (config.apiKey) {
|
|
66
|
+
const apiKey = new ApiKeyStrategy(config.apiKey);
|
|
67
|
+
this.app.container.singleton(ApiKeyStrategy, () => apiKey);
|
|
68
|
+
strategies["api-key"] = apiKey;
|
|
69
|
+
}
|
|
48
70
|
|
|
49
71
|
// Rights layer (Epic 56): one resolver singleton backs BOTH the coarse
|
|
50
72
|
// RBAC helpers (injected into AuthManager below) and — once 56.6 lands —
|
|
@@ -60,10 +82,23 @@ export default class WardenProvider {
|
|
|
60
82
|
this.app.container.singleton(MemoryRightsStore, () => rightsStore);
|
|
61
83
|
}
|
|
62
84
|
|
|
85
|
+
// Bouncer registry (Epic 56.6): the abilities/policies/scope-resolver the
|
|
86
|
+
// per-request `initializeBouncer` middleware builds each `ctx.bouncer`
|
|
87
|
+
// from. Registered (even when empty) under a string token so the agnostic
|
|
88
|
+
// middleware resolves it by name, mirroring the "auth" alias above.
|
|
89
|
+
const bouncerRegistry: BouncerRegistry = {
|
|
90
|
+
abilities: config.abilities ?? {},
|
|
91
|
+
policies: config.policies ?? {},
|
|
92
|
+
resolveScope: config.resolveScope,
|
|
93
|
+
};
|
|
94
|
+
this.app.container.singleton("bouncer:registry", () => bouncerRegistry);
|
|
95
|
+
|
|
96
|
+
// Default to the configured strategy, else the first one registered.
|
|
97
|
+
const defaultStrategy =
|
|
98
|
+
config.defaultStrategy ?? Object.keys(strategies)[0];
|
|
63
99
|
this.app.container.singleton(AuthManager, () => {
|
|
64
|
-
const strategies: Record<string, JwtStrategy> = { jwt };
|
|
65
100
|
return new AuthManager({
|
|
66
|
-
defaultStrategy
|
|
101
|
+
defaultStrategy,
|
|
67
102
|
strategies,
|
|
68
103
|
rights: rightsResolver,
|
|
69
104
|
});
|
package/src/config.ts
CHANGED
|
@@ -16,8 +16,23 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { UserPayload } from "./AuthManager.js";
|
|
19
|
+
import type { BasePolicy } from "./bouncer/BasePolicy.js";
|
|
20
|
+
import type { Ability } from "./bouncer/types.js";
|
|
19
21
|
import type { MfaManager } from "./mfa/MfaManager.js";
|
|
20
|
-
import type { RightsStore } from "./rights/types.js";
|
|
22
|
+
import type { RightsStore, Scope } from "./rights/types.js";
|
|
23
|
+
import type { ApiKeyConfig } from "./strategies/ApiKeyStrategy.js";
|
|
24
|
+
import type { SessionStrategyConfig } from "./strategies/SessionStrategy.js";
|
|
25
|
+
import type { TokenBlacklist } from "./TokenBlacklist.js";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Minimal request shape passed to `resolveScope` — enough to derive a tenant
|
|
29
|
+
* from a header or the authenticated user, without coupling config to the full
|
|
30
|
+
* HTTP context type (keeps Warden agnostic of the host framework).
|
|
31
|
+
*/
|
|
32
|
+
export interface ScopeRequestContext {
|
|
33
|
+
request: { headers: Record<string, string> };
|
|
34
|
+
auth?: { user?: UserPayload | null };
|
|
35
|
+
}
|
|
21
36
|
|
|
22
37
|
export interface JwtConfig {
|
|
23
38
|
secret: string;
|
|
@@ -27,6 +42,12 @@ export interface JwtConfig {
|
|
|
27
42
|
email: string,
|
|
28
43
|
password: string,
|
|
29
44
|
) => Promise<UserPayload | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Optional revocation list. Supply a `TokenBlacklist` (Memory/Redis-backed) to
|
|
47
|
+
* enable `revoke()`/early-rejection of JWTs before expiry. Without it, `revoke()`
|
|
48
|
+
* throws (the call would be a silent no-op).
|
|
49
|
+
*/
|
|
50
|
+
blacklist?: TokenBlacklist;
|
|
30
51
|
}
|
|
31
52
|
|
|
32
53
|
export interface WardenConfig {
|
|
@@ -34,6 +55,18 @@ export interface WardenConfig {
|
|
|
34
55
|
defaultStrategy?: string;
|
|
35
56
|
/** JWT strategy configuration. */
|
|
36
57
|
jwt?: JwtConfig;
|
|
58
|
+
/**
|
|
59
|
+
* Session strategy configuration. Supply `findUser` so `@Guard('session')`
|
|
60
|
+
* routes resolve the authenticated user from the session id. Without this,
|
|
61
|
+
* the `session` strategy is unregistered and `@Guard('session')` throws.
|
|
62
|
+
*/
|
|
63
|
+
session?: SessionStrategyConfig;
|
|
64
|
+
/**
|
|
65
|
+
* API-key strategy configuration. Supply `findByKey` so `@Guard('api-key')`
|
|
66
|
+
* routes resolve the user (and scopes) from the request header. Without this,
|
|
67
|
+
* the `api-key` strategy is unregistered and `@Guard('api-key')` throws.
|
|
68
|
+
*/
|
|
69
|
+
apiKey?: ApiKeyConfig;
|
|
37
70
|
/**
|
|
38
71
|
* Rights layer configuration (Epic 56). Supply a custom `store` to back the
|
|
39
72
|
* unified resolver with a DB-backed driver; when omitted, an in-memory
|
|
@@ -41,6 +74,25 @@ export interface WardenConfig {
|
|
|
41
74
|
* in-memory shipped).
|
|
42
75
|
*/
|
|
43
76
|
rights?: { store?: RightsStore };
|
|
77
|
+
/**
|
|
78
|
+
* Standalone abilities the per-request Bouncer knows by name (Epic 56.6).
|
|
79
|
+
* Keyed by the string passed to `ctx.bouncer.authorize('post.edit', …)`.
|
|
80
|
+
* Define with `Bouncer.ability((user, post) => …)`.
|
|
81
|
+
*/
|
|
82
|
+
abilities?: Record<string, Ability<never[]>>;
|
|
83
|
+
/**
|
|
84
|
+
* Class-based policies the per-request Bouncer knows by name (Epic 56.6).
|
|
85
|
+
* Reachable via `ctx.bouncer.with('PostPolicy')`. Each entry is the policy
|
|
86
|
+
* constructor; a fresh instance is created per check.
|
|
87
|
+
*/
|
|
88
|
+
policies?: Record<string, new () => BasePolicy>;
|
|
89
|
+
/**
|
|
90
|
+
* Derive the authorization scope for a request (AD4 — scope-first). Return
|
|
91
|
+
* `{ tenant }` to scope rights to a tenant (e.g. from a subdomain or header),
|
|
92
|
+
* or `"global"`. Omitted ⇒ every request runs in the implicit `global` scope
|
|
93
|
+
* (zero config for single-tenant apps).
|
|
94
|
+
*/
|
|
95
|
+
resolveScope?: (ctx: ScopeRequestContext) => Scope | Promise<Scope>;
|
|
44
96
|
/**
|
|
45
97
|
* Multi-factor authentication. Supply a configured `MfaManager` (built with
|
|
46
98
|
* your persistent stores + providers) to register it in the container as
|
package/src/index.ts
CHANGED
|
@@ -124,7 +124,7 @@ export type {
|
|
|
124
124
|
export { scopeKey } from "./rights/types.js";
|
|
125
125
|
export type { ApiKeyConfig } from "./strategies/ApiKeyStrategy.js";
|
|
126
126
|
export { ApiKeyStrategy } from "./strategies/ApiKeyStrategy.js";
|
|
127
|
-
export type { JwtStrategyConfig } from "./strategies/JwtStrategy.js";
|
|
127
|
+
export type { JwtClaims, JwtStrategyConfig } from "./strategies/JwtStrategy.js";
|
|
128
128
|
export { generateJwtSecret, JwtStrategy } from "./strategies/JwtStrategy.js";
|
|
129
129
|
export type {
|
|
130
130
|
SessionStore,
|
package/src/middleware.ts
CHANGED
|
@@ -22,12 +22,18 @@ import {
|
|
|
22
22
|
type AuthStrategy,
|
|
23
23
|
sanitizePayload,
|
|
24
24
|
} from "./AuthManager.js";
|
|
25
|
+
import type { BasePolicy } from "./bouncer/BasePolicy.js";
|
|
26
|
+
import { Bouncer } from "./bouncer/Bouncer.js";
|
|
27
|
+
import type { Ability } from "./bouncer/types.js";
|
|
28
|
+
import type { ScopeRequestContext } from "./config.js";
|
|
25
29
|
import {
|
|
26
30
|
getGuardMetadata,
|
|
27
31
|
getPermissionMetadata,
|
|
28
32
|
getRequireMfaMetadata,
|
|
29
33
|
getRoleMetadata,
|
|
30
34
|
} from "./Guard.js";
|
|
35
|
+
import { RightsResolver } from "./rights/RightsResolver.js";
|
|
36
|
+
import type { Scope } from "./rights/types.js";
|
|
31
37
|
import type { SessionStore } from "./strategies/SessionStrategy.js";
|
|
32
38
|
|
|
33
39
|
interface StrategyWithContext extends AuthStrategy {
|
|
@@ -61,6 +67,8 @@ export interface WardenContext {
|
|
|
61
67
|
session?: SessionStore;
|
|
62
68
|
/** Set by the middleware after successful auth. */
|
|
63
69
|
auth?: AuthResult;
|
|
70
|
+
/** Set by `initializeBouncer` — the per-request authorization entry point. */
|
|
71
|
+
bouncer?: Bouncer;
|
|
64
72
|
/** The route handler metadata (decorators). */
|
|
65
73
|
route?: {
|
|
66
74
|
controller?: object;
|
|
@@ -164,7 +172,14 @@ export async function wardenMiddleware(ctx: WardenContext, next: WardenNext) {
|
|
|
164
172
|
// bypass permission checks. This is the strictest model — callers who want
|
|
165
173
|
// role-OR-permission semantics should use a custom guard instead.
|
|
166
174
|
if (controller && action) {
|
|
167
|
-
const
|
|
175
|
+
const scope = await resolveRequestScope(ctx);
|
|
176
|
+
const denied = await checkAuthorization(
|
|
177
|
+
auth,
|
|
178
|
+
user,
|
|
179
|
+
controller,
|
|
180
|
+
action,
|
|
181
|
+
scope,
|
|
182
|
+
);
|
|
168
183
|
if (denied) {
|
|
169
184
|
ctx.response.status(403);
|
|
170
185
|
ctx.response.json({ error: { code: "FORBIDDEN", message: denied } });
|
|
@@ -190,6 +205,85 @@ export async function wardenMiddleware(ctx: WardenContext, next: WardenNext) {
|
|
|
190
205
|
await next();
|
|
191
206
|
}
|
|
192
207
|
|
|
208
|
+
/**
|
|
209
|
+
* The abilities/policies/scope-resolver a per-request Bouncer is built from.
|
|
210
|
+
* Registered once by `WardenProvider` (from `config.auth`) under the container
|
|
211
|
+
* token `"bouncer:registry"`; read fresh by `initializeBouncer` per request.
|
|
212
|
+
*/
|
|
213
|
+
export interface BouncerRegistry {
|
|
214
|
+
abilities: Record<string, Ability<never[]>>;
|
|
215
|
+
policies: Record<string, new () => BasePolicy>;
|
|
216
|
+
resolveScope?: (ctx: ScopeRequestContext) => Scope | Promise<Scope>;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Bouncer initializer (Epic 56.6) — a GLOBAL middleware (register it for every
|
|
221
|
+
* route, after `wardenMiddleware`). It builds the per-request {@link Bouncer}
|
|
222
|
+
* from the authenticated user (`ctx.auth?.user`, or `null` for a guest), the
|
|
223
|
+
* shared `RightsResolver`, and the registered abilities/policies, then attaches
|
|
224
|
+
* it as `ctx.bouncer`. Handlers authorize via `await ctx.bouncer.authorize(...)`
|
|
225
|
+
* (throws `WARDEN_AUTHORIZATION_FAILURE` carrying `status: 403`, which the host's
|
|
226
|
+
* ExceptionHandler maps to a 403 response) or branch on `ctx.bouncer.allows(...)`.
|
|
227
|
+
*
|
|
228
|
+
* Unlike `wardenMiddleware` (a per-route GUARD that short-circuits public
|
|
229
|
+
* routes), this runs on EVERY request so `ctx.bouncer` is always available —
|
|
230
|
+
* including for guests — matching AdonisJS's `initialize_bouncer_middleware`.
|
|
231
|
+
*/
|
|
232
|
+
export async function initializeBouncer(ctx: WardenContext, next: WardenNext) {
|
|
233
|
+
// Both dependencies are registered by WardenProvider. Resolve defensively:
|
|
234
|
+
// a host that wired the middleware but not the provider gets an empty Bouncer
|
|
235
|
+
// (no abilities/resolver) rather than a crash on every request.
|
|
236
|
+
const resolved = tryResolve(ctx, RightsResolver);
|
|
237
|
+
const resolver = resolved instanceof RightsResolver ? resolved : undefined;
|
|
238
|
+
const candidate = tryResolve(ctx, "bouncer:registry");
|
|
239
|
+
const registry = isBouncerRegistry(candidate) ? candidate : undefined;
|
|
240
|
+
|
|
241
|
+
const user = ctx.auth?.user ?? null;
|
|
242
|
+
const scope: Scope = registry?.resolveScope
|
|
243
|
+
? await registry.resolveScope(ctx)
|
|
244
|
+
: "global";
|
|
245
|
+
|
|
246
|
+
ctx.bouncer = new Bouncer(user, registry?.abilities, registry?.policies, {
|
|
247
|
+
scope,
|
|
248
|
+
resolver,
|
|
249
|
+
});
|
|
250
|
+
await next();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Resolve a container token, returning undefined instead of throwing when absent. */
|
|
254
|
+
function tryResolve(
|
|
255
|
+
ctx: WardenContext,
|
|
256
|
+
token: string | (abstract new (...args: never[]) => unknown),
|
|
257
|
+
): unknown {
|
|
258
|
+
try {
|
|
259
|
+
return ctx.container.resolve(token);
|
|
260
|
+
} catch {
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Structural guard — the registry is a plain object carrying ability/policy tables. */
|
|
266
|
+
function isBouncerRegistry(value: unknown): value is BouncerRegistry {
|
|
267
|
+
return (
|
|
268
|
+
typeof value === "object" &&
|
|
269
|
+
value !== null &&
|
|
270
|
+
"abilities" in value &&
|
|
271
|
+
"policies" in value
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Resolve the request's authorization scope from the registry's `resolveScope`
|
|
277
|
+
* hook (default `"global"`). Shared so the @Role/@Permission decorator gate and
|
|
278
|
+
* the Bouncer path evaluate in the SAME scope — without this the decorator path
|
|
279
|
+
* hardcoded "global" and tenant-scoped roles/grants never satisfied @Role/@Permission.
|
|
280
|
+
*/
|
|
281
|
+
async function resolveRequestScope(ctx: WardenContext): Promise<Scope> {
|
|
282
|
+
const candidate = tryResolve(ctx, "bouncer:registry");
|
|
283
|
+
const registry = isBouncerRegistry(candidate) ? candidate : undefined;
|
|
284
|
+
return registry?.resolveScope ? await registry.resolveScope(ctx) : "global";
|
|
285
|
+
}
|
|
286
|
+
|
|
193
287
|
/**
|
|
194
288
|
* Try each declared strategy in order — session strategies via
|
|
195
289
|
* `verifyWithContext()`, others via `verify(token)` with native-first credential
|
|
@@ -278,13 +372,14 @@ async function checkAuthorization(
|
|
|
278
372
|
user: NonNullable<AuthResult["user"]>,
|
|
279
373
|
controller: object,
|
|
280
374
|
action: string | symbol,
|
|
375
|
+
scope: Scope,
|
|
281
376
|
): Promise<string | null> {
|
|
282
377
|
const requiredPermissions = getPermissionMetadata(controller, action);
|
|
283
378
|
const requiredRoles = getRoleMetadata(controller, action);
|
|
284
379
|
if (requiredPermissions.length === 0 && requiredRoles.length === 0) {
|
|
285
380
|
return null;
|
|
286
381
|
}
|
|
287
|
-
const effective = await auth.resolvePermissions(user,
|
|
382
|
+
const effective = await auth.resolvePermissions(user, scope);
|
|
288
383
|
if (requiredPermissions.length > 0) {
|
|
289
384
|
const missing = requiredPermissions.filter((p) => !effective.has(p));
|
|
290
385
|
if (missing.length > 0) return `Missing permissions: ${missing.join(", ")}`;
|
package/src/standalone.ts
CHANGED
|
@@ -20,10 +20,13 @@
|
|
|
20
20
|
import {
|
|
21
21
|
AuthManager,
|
|
22
22
|
type AuthResult,
|
|
23
|
+
type AuthStrategy,
|
|
23
24
|
type UserPayload,
|
|
24
25
|
} from "./AuthManager.js";
|
|
25
26
|
import type { WardenConfig } from "./config.js";
|
|
27
|
+
import { ApiKeyStrategy } from "./strategies/ApiKeyStrategy.js";
|
|
26
28
|
import { JwtStrategy } from "./strategies/JwtStrategy.js";
|
|
29
|
+
import { SessionStrategy } from "./strategies/SessionStrategy.js";
|
|
27
30
|
|
|
28
31
|
export interface Warden {
|
|
29
32
|
/** Verify a bearer token. Returns auth result with user payload. */
|
|
@@ -49,11 +52,21 @@ export interface Warden {
|
|
|
49
52
|
* Create a standalone Warden instance — no Ream container needed.
|
|
50
53
|
*/
|
|
51
54
|
export function createWarden(config: WardenConfig): Warden {
|
|
52
|
-
|
|
55
|
+
// Mirror WardenProvider: build EVERY configured strategy, not just jwt —
|
|
56
|
+
// otherwise config.session / config.apiKey were silently dropped and a
|
|
57
|
+
// jwt-less config (e.g. { defaultStrategy: 'api-key', apiKey }) threw
|
|
58
|
+
// INVALID_CONFIG at boot (audit 2026-06-13). Keys match the @Guard names.
|
|
59
|
+
const strategies: Record<string, AuthStrategy> = {};
|
|
53
60
|
|
|
54
61
|
if (config.jwt) {
|
|
55
62
|
strategies.jwt = new JwtStrategy(config.jwt);
|
|
56
63
|
}
|
|
64
|
+
if (config.session) {
|
|
65
|
+
strategies.session = new SessionStrategy(config.session);
|
|
66
|
+
}
|
|
67
|
+
if (config.apiKey) {
|
|
68
|
+
strategies["api-key"] = new ApiKeyStrategy(config.apiKey);
|
|
69
|
+
}
|
|
57
70
|
|
|
58
71
|
const manager = new AuthManager({
|
|
59
72
|
defaultStrategy: config.defaultStrategy ?? "jwt",
|
|
@@ -45,7 +45,20 @@ export class ApiKeyStrategy implements AuthStrategy {
|
|
|
45
45
|
|
|
46
46
|
const user: UserPayload = { ...result.user };
|
|
47
47
|
if (result.scopes && result.scopes.length > 0) {
|
|
48
|
-
//
|
|
48
|
+
// Expose the API key's scopes on `user.permissions` (without mutating
|
|
49
|
+
// the source object) so APP code can read them via
|
|
50
|
+
// `ctx.auth.user.permissions`.
|
|
51
|
+
//
|
|
52
|
+
// IMPORTANT — these scopes are a SEPARATE axis from the `@Permission`
|
|
53
|
+
// route gate, by design (Adonis Bouncer parity): the gate flows through
|
|
54
|
+
// RightsResolver, which resolves grants from the rights STORE and
|
|
55
|
+
// deliberately ignores payload-carried permissions (see RightsResolver
|
|
56
|
+
// D1). This mirrors AdonisJS, where token abilities are checked via
|
|
57
|
+
// `currentAccessToken.allows(scope)` and are NOT consulted by Bouncer
|
|
58
|
+
// ability/policy checks. So an API key carrying `orders.create` is NOT
|
|
59
|
+
// auto-granted by `@Permission('orders.create')` — that's intentional,
|
|
60
|
+
// not a bug. Gate API-key routes on scopes with an explicit app-level
|
|
61
|
+
// check against `ctx.auth.user.permissions`.
|
|
49
62
|
const merged = new Set([...(user.permissions ?? []), ...result.scopes]);
|
|
50
63
|
user.permissions = [...merged];
|
|
51
64
|
}
|
|
@@ -8,7 +8,13 @@ import type { AuthResult, AuthStrategy, UserPayload } from "../AuthManager.js";
|
|
|
8
8
|
import { nativeWarden } from "../native.js";
|
|
9
9
|
import type { TokenBlacklist } from "../TokenBlacklist.js";
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The verified JWT claims. Passed to `findUser` as the second argument so an app
|
|
13
|
+
* can reject a token on more than `sub` — e.g. compare `iat` to a stored
|
|
14
|
+
* `passwordChangedAt` (kill sessions on reset), check a `tokenVersion` custom
|
|
15
|
+
* claim, or honour the `jti` directly. Return `null` from `findUser` to reject.
|
|
16
|
+
*/
|
|
17
|
+
export interface JwtClaims {
|
|
12
18
|
sub: string;
|
|
13
19
|
roles?: string[];
|
|
14
20
|
permissions?: string[];
|
|
@@ -19,7 +25,7 @@ interface JwtPayloadData {
|
|
|
19
25
|
[key: string]: unknown;
|
|
20
26
|
}
|
|
21
27
|
|
|
22
|
-
function sign(payload:
|
|
28
|
+
function sign(payload: JwtClaims, secret: string): string {
|
|
23
29
|
const rust = nativeWarden();
|
|
24
30
|
if (!rust) {
|
|
25
31
|
throw new Error(
|
|
@@ -29,7 +35,7 @@ function sign(payload: JwtPayloadData, secret: string): string {
|
|
|
29
35
|
return rust.jwtSign(JSON.stringify(payload), secret);
|
|
30
36
|
}
|
|
31
37
|
|
|
32
|
-
function verify(token: string, secret: string):
|
|
38
|
+
function verify(token: string, secret: string): JwtClaims | null {
|
|
33
39
|
const rust = nativeWarden();
|
|
34
40
|
if (!rust) {
|
|
35
41
|
throw new Error(
|
|
@@ -38,7 +44,7 @@ function verify(token: string, secret: string): JwtPayloadData | null {
|
|
|
38
44
|
}
|
|
39
45
|
try {
|
|
40
46
|
const payloadJson = rust.jwtVerify(token, secret);
|
|
41
|
-
const payload = JSON.parse(payloadJson) as
|
|
47
|
+
const payload = JSON.parse(payloadJson) as JwtClaims;
|
|
42
48
|
if (typeof payload.sub !== "string" || !payload.sub) return null;
|
|
43
49
|
return payload;
|
|
44
50
|
} catch {
|
|
@@ -61,7 +67,15 @@ export interface JwtStrategyConfig {
|
|
|
61
67
|
*/
|
|
62
68
|
previousSecrets?: readonly string[];
|
|
63
69
|
expiresInSeconds?: number;
|
|
64
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the user for a verified token. Receives the token `sub` AND the
|
|
72
|
+
* full verified `claims` — use the claims to enforce session invalidation
|
|
73
|
+
* the strategy can't know about (e.g. reject when `claims.iat` predates the
|
|
74
|
+
* user's `passwordChangedAt`, or a `claims.tokenVersion` is stale) by
|
|
75
|
+
* returning `null`. The second arg is optional for callers that only need
|
|
76
|
+
* the id (existing one-arg `findUser` functions stay valid).
|
|
77
|
+
*/
|
|
78
|
+
findUser: (id: string, claims: JwtClaims) => Promise<UserPayload | null>;
|
|
65
79
|
verifyCredentials: (
|
|
66
80
|
email: string,
|
|
67
81
|
password: string,
|
|
@@ -113,7 +127,7 @@ export class JwtStrategy implements AuthStrategy {
|
|
|
113
127
|
* Returns `null` when no secret accepts the token — same semantics as
|
|
114
128
|
* the single-secret path so callers see "invalid or expired token".
|
|
115
129
|
*/
|
|
116
|
-
#verifyWithRotation(token: string):
|
|
130
|
+
#verifyWithRotation(token: string): JwtClaims | null {
|
|
117
131
|
for (const secret of this.#verifySecrets) {
|
|
118
132
|
const payload = verify(token, secret);
|
|
119
133
|
if (payload) return payload;
|
|
@@ -165,7 +179,10 @@ export class JwtStrategy implements AuthStrategy {
|
|
|
165
179
|
}
|
|
166
180
|
}
|
|
167
181
|
|
|
168
|
-
|
|
182
|
+
// Pass the FULL verified claims (not just `sub`) so the app can reject
|
|
183
|
+
// stale tokens it alone can judge — iat vs passwordChangedAt, tokenVersion,
|
|
184
|
+
// etc. — by returning null. This is the session-invalidation seam.
|
|
185
|
+
const user = await this.#findUser(payload.sub, payload);
|
|
169
186
|
if (!user) {
|
|
170
187
|
return { authenticated: false, error: "User not found" };
|
|
171
188
|
}
|