@c9up/warden 0.1.3 → 0.1.5

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.
Files changed (185) hide show
  1. package/README.md +57 -0
  2. package/dist/AuthManager.d.ts +98 -0
  3. package/dist/AuthManager.d.ts.map +1 -0
  4. package/dist/AuthManager.js +147 -0
  5. package/dist/AuthManager.js.map +1 -0
  6. package/dist/AuthRateLimiter.d.ts +29 -0
  7. package/dist/AuthRateLimiter.d.ts.map +1 -0
  8. package/dist/AuthRateLimiter.js +88 -0
  9. package/dist/AuthRateLimiter.js.map +1 -0
  10. package/dist/Guard.d.ts +35 -0
  11. package/dist/Guard.d.ts.map +1 -0
  12. package/dist/Guard.js +68 -0
  13. package/dist/Guard.js.map +1 -0
  14. package/dist/RedisBlacklistDriver.d.ts +32 -0
  15. package/dist/RedisBlacklistDriver.d.ts.map +1 -0
  16. package/dist/RedisBlacklistDriver.js +37 -0
  17. package/dist/RedisBlacklistDriver.js.map +1 -0
  18. package/dist/RefreshTokenStore.d.ts +34 -0
  19. package/dist/RefreshTokenStore.d.ts.map +1 -0
  20. package/dist/RefreshTokenStore.js +48 -0
  21. package/dist/RefreshTokenStore.js.map +1 -0
  22. package/dist/ResilientBlacklistDriver.d.ts +35 -0
  23. package/dist/ResilientBlacklistDriver.d.ts.map +1 -0
  24. package/dist/ResilientBlacklistDriver.js +61 -0
  25. package/dist/ResilientBlacklistDriver.js.map +1 -0
  26. package/dist/TokenBlacklist.d.ts +30 -0
  27. package/dist/TokenBlacklist.d.ts.map +1 -0
  28. package/dist/TokenBlacklist.js +50 -0
  29. package/dist/TokenBlacklist.js.map +1 -0
  30. package/dist/WardenProvider.d.ts +19 -0
  31. package/dist/WardenProvider.d.ts.map +1 -0
  32. package/dist/WardenProvider.js +71 -0
  33. package/dist/WardenProvider.js.map +1 -0
  34. package/dist/bouncer/AuthorizationResponse.d.ts +25 -0
  35. package/dist/bouncer/AuthorizationResponse.d.ts.map +1 -0
  36. package/dist/bouncer/AuthorizationResponse.js +31 -0
  37. package/dist/bouncer/AuthorizationResponse.js.map +1 -0
  38. package/dist/bouncer/BasePolicy.d.ts +47 -0
  39. package/dist/bouncer/BasePolicy.d.ts.map +1 -0
  40. package/dist/bouncer/BasePolicy.js +36 -0
  41. package/dist/bouncer/BasePolicy.js.map +1 -0
  42. package/dist/bouncer/Bouncer.d.ts +47 -0
  43. package/dist/bouncer/Bouncer.d.ts.map +1 -0
  44. package/dist/bouncer/Bouncer.js +134 -0
  45. package/dist/bouncer/Bouncer.js.map +1 -0
  46. package/dist/bouncer/PolicyAuthorizer.d.ts +24 -0
  47. package/dist/bouncer/PolicyAuthorizer.d.ts.map +1 -0
  48. package/dist/bouncer/PolicyAuthorizer.js +92 -0
  49. package/dist/bouncer/PolicyAuthorizer.js.map +1 -0
  50. package/dist/bouncer/decorators.d.ts +25 -0
  51. package/dist/bouncer/decorators.d.ts.map +1 -0
  52. package/dist/bouncer/decorators.js +36 -0
  53. package/dist/bouncer/decorators.js.map +1 -0
  54. package/dist/bouncer/evaluate.d.ts +36 -0
  55. package/dist/bouncer/evaluate.d.ts.map +1 -0
  56. package/dist/bouncer/evaluate.js +64 -0
  57. package/dist/bouncer/evaluate.js.map +1 -0
  58. package/dist/bouncer/policyContext.d.ts +29 -0
  59. package/dist/bouncer/policyContext.d.ts.map +1 -0
  60. package/dist/bouncer/policyContext.js +36 -0
  61. package/dist/bouncer/policyContext.js.map +1 -0
  62. package/dist/bouncer/types.d.ts +52 -0
  63. package/dist/bouncer/types.d.ts.map +1 -0
  64. package/dist/bouncer/types.js +9 -0
  65. package/dist/bouncer/types.js.map +1 -0
  66. package/dist/config.d.ts +51 -0
  67. package/dist/config.d.ts.map +1 -0
  68. package/dist/config.js +21 -0
  69. package/dist/config.js.map +1 -0
  70. package/dist/configure.d.ts +10 -0
  71. package/dist/configure.d.ts.map +1 -0
  72. package/dist/configure.js +44 -0
  73. package/dist/configure.js.map +1 -0
  74. package/dist/errors.d.ts +18 -0
  75. package/dist/errors.d.ts.map +1 -0
  76. package/dist/errors.js +21 -0
  77. package/dist/errors.js.map +1 -0
  78. package/dist/firstcontact/FirstContactManager.d.ts +25 -0
  79. package/dist/firstcontact/FirstContactManager.d.ts.map +1 -0
  80. package/dist/firstcontact/FirstContactManager.js +38 -0
  81. package/dist/firstcontact/FirstContactManager.js.map +1 -0
  82. package/dist/firstcontact/drivers/GitHubDriver.d.ts +14 -0
  83. package/dist/firstcontact/drivers/GitHubDriver.d.ts.map +1 -0
  84. package/dist/firstcontact/drivers/GitHubDriver.js +61 -0
  85. package/dist/firstcontact/drivers/GitHubDriver.js.map +1 -0
  86. package/dist/firstcontact/drivers/GoogleDriver.d.ts +14 -0
  87. package/dist/firstcontact/drivers/GoogleDriver.d.ts.map +1 -0
  88. package/dist/firstcontact/drivers/GoogleDriver.js +64 -0
  89. package/dist/firstcontact/drivers/GoogleDriver.js.map +1 -0
  90. package/dist/firstcontact/types.d.ts +34 -0
  91. package/dist/firstcontact/types.d.ts.map +1 -0
  92. package/dist/firstcontact/types.js +5 -0
  93. package/dist/firstcontact/types.js.map +1 -0
  94. package/dist/index.d.ts +52 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +34 -0
  97. package/dist/index.js.map +1 -0
  98. package/dist/mfa/BackupCodesProvider.d.ts +39 -0
  99. package/dist/mfa/BackupCodesProvider.d.ts.map +1 -0
  100. package/dist/mfa/BackupCodesProvider.js +87 -0
  101. package/dist/mfa/BackupCodesProvider.js.map +1 -0
  102. package/dist/mfa/MfaManager.d.ts +107 -0
  103. package/dist/mfa/MfaManager.d.ts.map +1 -0
  104. package/dist/mfa/MfaManager.js +219 -0
  105. package/dist/mfa/MfaManager.js.map +1 -0
  106. package/dist/mfa/OtpProvider.d.ts +67 -0
  107. package/dist/mfa/OtpProvider.d.ts.map +1 -0
  108. package/dist/mfa/OtpProvider.js +104 -0
  109. package/dist/mfa/OtpProvider.js.map +1 -0
  110. package/dist/mfa/TotpProvider.d.ts +52 -0
  111. package/dist/mfa/TotpProvider.d.ts.map +1 -0
  112. package/dist/mfa/TotpProvider.js +103 -0
  113. package/dist/mfa/TotpProvider.js.map +1 -0
  114. package/dist/mfa/WebauthnProvider.d.ts +175 -0
  115. package/dist/mfa/WebauthnProvider.d.ts.map +1 -0
  116. package/dist/mfa/WebauthnProvider.js +239 -0
  117. package/dist/mfa/WebauthnProvider.js.map +1 -0
  118. package/dist/mfa/base32.d.ts +13 -0
  119. package/dist/mfa/base32.d.ts.map +1 -0
  120. package/dist/mfa/base32.js +52 -0
  121. package/dist/mfa/base32.js.map +1 -0
  122. package/dist/mfa/webauthn-codec.d.ts +58 -0
  123. package/dist/mfa/webauthn-codec.d.ts.map +1 -0
  124. package/dist/mfa/webauthn-codec.js +221 -0
  125. package/dist/mfa/webauthn-codec.js.map +1 -0
  126. package/dist/middleware.d.ts +51 -0
  127. package/dist/middleware.d.ts.map +1 -0
  128. package/dist/middleware.js +213 -0
  129. package/dist/middleware.js.map +1 -0
  130. package/dist/native.d.ts +14 -0
  131. package/dist/native.d.ts.map +1 -0
  132. package/dist/native.js +39 -0
  133. package/dist/native.js.map +1 -0
  134. package/dist/rights/MemoryRightsStore.d.ts +29 -0
  135. package/dist/rights/MemoryRightsStore.d.ts.map +1 -0
  136. package/dist/rights/MemoryRightsStore.js +70 -0
  137. package/dist/rights/MemoryRightsStore.js.map +1 -0
  138. package/dist/rights/RightsResolver.d.ts +29 -0
  139. package/dist/rights/RightsResolver.d.ts.map +1 -0
  140. package/dist/rights/RightsResolver.js +73 -0
  141. package/dist/rights/RightsResolver.js.map +1 -0
  142. package/dist/rights/types.d.ts +55 -0
  143. package/dist/rights/types.d.ts.map +1 -0
  144. package/dist/rights/types.js +18 -0
  145. package/dist/rights/types.js.map +1 -0
  146. package/dist/services/main.d.ts +19 -0
  147. package/dist/services/main.d.ts.map +1 -0
  148. package/dist/services/main.js +32 -0
  149. package/dist/services/main.js.map +1 -0
  150. package/dist/standalone.d.ts +37 -0
  151. package/dist/standalone.d.ts.map +1 -0
  152. package/dist/standalone.js +92 -0
  153. package/dist/standalone.js.map +1 -0
  154. package/dist/strategies/ApiKeyStrategy.d.ts +28 -0
  155. package/dist/strategies/ApiKeyStrategy.d.ts.map +1 -0
  156. package/dist/strategies/ApiKeyStrategy.js +36 -0
  157. package/dist/strategies/ApiKeyStrategy.js.map +1 -0
  158. package/dist/strategies/JwtStrategy.d.ts +51 -0
  159. package/dist/strategies/JwtStrategy.d.ts.map +1 -0
  160. package/dist/strategies/JwtStrategy.js +150 -0
  161. package/dist/strategies/JwtStrategy.js.map +1 -0
  162. package/dist/strategies/SessionStrategy.d.ts +58 -0
  163. package/dist/strategies/SessionStrategy.d.ts.map +1 -0
  164. package/dist/strategies/SessionStrategy.js +70 -0
  165. package/dist/strategies/SessionStrategy.js.map +1 -0
  166. package/index.darwin-arm64.node +0 -0
  167. package/index.darwin-x64.node +0 -0
  168. package/index.linux-arm64-gnu.node +0 -0
  169. package/index.linux-x64-gnu.node +0 -0
  170. package/index.win32-x64-msvc.node +0 -0
  171. package/package.json +1 -1
  172. package/src/Guard.ts +21 -0
  173. package/src/RedisBlacklistDriver.ts +60 -0
  174. package/src/ResilientBlacklistDriver.ts +84 -0
  175. package/src/WardenProvider.ts +11 -0
  176. package/src/config.ts +7 -0
  177. package/src/index.ts +58 -0
  178. package/src/mfa/BackupCodesProvider.ts +125 -0
  179. package/src/mfa/MfaManager.ts +307 -0
  180. package/src/mfa/OtpProvider.ts +177 -0
  181. package/src/mfa/TotpProvider.ts +140 -0
  182. package/src/mfa/WebauthnProvider.ts +416 -0
  183. package/src/mfa/base32.ts +54 -0
  184. package/src/mfa/webauthn-codec.ts +264 -0
  185. package/src/middleware.ts +14 -0
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Bouncer — Layer 2 (EVALUATION) of Warden's unified authorization (Epic 56).
3
+ *
4
+ * A Bouncer-faithful evaluation core (verified context7 `/adonisjs/bouncer`,
5
+ * 2026-06-01): standalone abilities via `Bouncer.ability`, class-based policies
6
+ * via `with(Policy)`, the four verbs `allows`/`denies`/`authorize`/`execute`,
7
+ * and guest-denied-by-default. The user is `UserPayload | null` (guest = null,
8
+ * D3); `authorize` throws `WARDEN_AUTHORIZATION_FAILURE` carrying `status: 403`
9
+ * (D2). 56.2 ships PURE mechanics — it does NOT consult the Layer-1
10
+ * `RightsResolver` (D4); abilities/policy methods receive `(user, ...args)` and
11
+ * the developer writes the predicate, exactly as Adonis does.
12
+ */
13
+ import { WardenError } from "../errors.js";
14
+ import { AuthorizationResponse } from "./AuthorizationResponse.js";
15
+ import { evaluate, isAction, throwAuthorizationFailure } from "./evaluate.js";
16
+ import { PolicyAuthorizer } from "./PolicyAuthorizer.js";
17
+ import { emptyPermissions } from "./policyContext.js";
18
+ export class Bouncer {
19
+ #user;
20
+ #abilities;
21
+ #policies;
22
+ #scope;
23
+ #resolver;
24
+ /** Memoized resolution — a Bouncer is fixed per `(user, scope)`, so resolve once (D3). */
25
+ #resolved;
26
+ constructor(user, abilities, policies, context) {
27
+ this.#user = user;
28
+ this.#abilities = abilities ?? {};
29
+ this.#policies = policies ?? {};
30
+ this.#scope = context?.scope ?? "global";
31
+ this.#resolver = context?.resolver;
32
+ }
33
+ /** The active resolution scope (default `"global"`). */
34
+ get scope() {
35
+ return this.#scope;
36
+ }
37
+ /**
38
+ * Resolve this Bouncer's `(user, scope)` to `EffectivePermissions`, memoized
39
+ * once per instance (D3). Guest (`user === null`) or no resolver ⇒ empty
40
+ * permissions — `resolve()` is never called for a guest (D9). 56.6 relocates
41
+ * this cache to the request context without changing the policy-facing API.
42
+ */
43
+ #resolvePermissions() {
44
+ if (this.#resolved === undefined) {
45
+ this.#resolved =
46
+ this.#resolver !== undefined && this.#user !== null
47
+ ? this.#resolver.resolve(this.#user, this.#scope)
48
+ : Promise.resolve(emptyPermissions(this.#scope));
49
+ }
50
+ return this.#resolved;
51
+ }
52
+ static ability(optionsOrCallback, maybeCallback) {
53
+ let allowGuest = false;
54
+ let raw;
55
+ if (typeof optionsOrCallback === "function") {
56
+ raw = optionsOrCallback;
57
+ }
58
+ else {
59
+ allowGuest = optionsOrCallback.allowGuest ?? false;
60
+ raw = maybeCallback;
61
+ }
62
+ // The typed overloads above carry the real callback signature for the
63
+ // caller; the impl param is the universal-function supertype so both
64
+ // overloads are accepted. `isAction` reinterprets it as the invokable
65
+ // `Action` shape (a type guard, not a cast — a function's parameter types
66
+ // are not observable at runtime).
67
+ if (!isAction(raw)) {
68
+ throw new WardenError("INVALID_ABILITY", "Bouncer.ability requires a callback alongside the options object.", { hint: "Bouncer.ability({ allowGuest: true }, (user) => ...)" });
69
+ }
70
+ const callback = raw;
71
+ const execute = (user, ...args) => callback(user, ...args);
72
+ return { allowGuest, execute };
73
+ }
74
+ /** Convenience — equivalent to `AuthorizationResponse.deny` (Adonis `bouncer.deny`). */
75
+ deny(message, status) {
76
+ return AuthorizationResponse.deny(message, status);
77
+ }
78
+ /** Open a policy for checks (D8 — fresh `new PolicyClass()` per check). */
79
+ with(policy) {
80
+ const factory = typeof policy === "string"
81
+ ? this.#resolvePolicyClass(policy)
82
+ : () => new policy();
83
+ // The PolicyAuthorizer inherits the Bouncer's scope + the shared memoized
84
+ // resolve so every policy check sees the active `(user, scope)` (AC1/AC3).
85
+ return new PolicyAuthorizer(this.#user, factory, this.#scope, () => this.#resolvePermissions());
86
+ }
87
+ execute(ability, ...args) {
88
+ return this.#evaluateAbility(ability, args);
89
+ }
90
+ async allows(ability, ...args) {
91
+ return (await this.#evaluateAbility(ability, args)).authorized;
92
+ }
93
+ async denies(ability, ...args) {
94
+ return !(await this.#evaluateAbility(ability, args)).authorized;
95
+ }
96
+ async authorize(ability, ...args) {
97
+ const response = await this.#evaluateAbility(ability, args);
98
+ if (!response.authorized) {
99
+ throwAuthorizationFailure(response);
100
+ }
101
+ }
102
+ async #evaluateAbility(ability, args) {
103
+ const resolved = typeof ability === "string" ? this.#resolveAbility(ability) : ability;
104
+ return evaluate({
105
+ user: this.#user,
106
+ action: typeof ability === "string" ? ability : "(ability)",
107
+ allowGuest: resolved.allowGuest,
108
+ run: (user) => resolved.execute(user, ...args),
109
+ args,
110
+ });
111
+ }
112
+ #resolveAbility(name) {
113
+ const found = this.#abilities[name];
114
+ if (found === undefined) {
115
+ throw new WardenError("UNKNOWN_ABILITY", `No ability "${name}" is registered on this Bouncer.`, { hint: "Register it via new Bouncer(user, { [name]: ability })." });
116
+ }
117
+ return found;
118
+ }
119
+ #resolvePolicyClass(name) {
120
+ // Lazy: the lookup runs when a verb constructs the policy, so an unknown
121
+ // name surfaces as a promise rejection (like every other verb error),
122
+ // not a synchronous throw at `with()` time.
123
+ return () => {
124
+ const ctor = this.#policies[name];
125
+ if (ctor === undefined) {
126
+ throw new WardenError("UNKNOWN_POLICY", `No policy "${name}" is registered on this Bouncer.`, {
127
+ hint: "Register it via new Bouncer(user, abilities, { [name]: Policy }).",
128
+ });
129
+ }
130
+ return new ctor();
131
+ };
132
+ }
133
+ }
134
+ //# sourceMappingURL=Bouncer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Bouncer.js","sourceRoot":"","sources":["../../src/bouncer/Bouncer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * PolicyAuthorizer — the object returned by `bouncer.with(Policy)`. Exposes the
3
+ * same four verbs as the Bouncer, dispatched to a named action method on a
4
+ * freshly-constructed policy instance (D8 — `new PolicyClass()` per check). The
5
+ * `before`/`after` hooks and the per-method `@allowGuest` rule run through the
6
+ * shared D5 pipeline.
7
+ */
8
+ import type { UserPayload } from "../AuthManager.js";
9
+ import type { EffectivePermissions, Scope } from "../rights/types.js";
10
+ import type { AuthorizationResponse } from "./AuthorizationResponse.js";
11
+ import { BasePolicy } from "./BasePolicy.js";
12
+ export declare class PolicyAuthorizer {
13
+ #private;
14
+ constructor(user: UserPayload | null, factory: () => BasePolicy, scope?: Scope, resolvePermissions?: () => Promise<EffectivePermissions>);
15
+ /** Run a check and resolve to the full response (D8 — fresh policy per check). */
16
+ execute(action: string, ...args: unknown[]): Promise<AuthorizationResponse>;
17
+ /** True iff the action is authorized. Never throws on denial. */
18
+ allows(action: string, ...args: unknown[]): Promise<boolean>;
19
+ /** Boolean negation of {@link allows}. Never throws on denial. */
20
+ denies(action: string, ...args: unknown[]): Promise<boolean>;
21
+ /** Resolves on allow; throws `WARDEN_AUTHORIZATION_FAILURE` on deny (D2). */
22
+ authorize(action: string, ...args: unknown[]): Promise<void>;
23
+ }
24
+ //# sourceMappingURL=PolicyAuthorizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PolicyAuthorizer.d.ts","sourceRoot":"","sources":["../../src/bouncer/PolicyAuthorizer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,oBAAoB,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAqD7C,qBAAa,gBAAgB;;gBAO3B,IAAI,EAAE,WAAW,GAAG,IAAI,EACxB,OAAO,EAAE,MAAM,UAAU,EACzB,KAAK,GAAE,KAAgB,EACvB,kBAAkB,GAAE,MAAM,OAAO,CAAC,oBAAoB,CACb;IAQ1C,kFAAkF;IAC5E,OAAO,CACZ,MAAM,EAAE,MAAM,EACd,GAAG,IAAI,EAAE,OAAO,EAAE,GAChB,OAAO,CAAC,qBAAqB,CAAC;IAmBjC,iEAAiE;IAC3D,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAIlE,kEAAkE;IAC5D,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAIlE,6EAA6E;IACvE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAMlE"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * PolicyAuthorizer — the object returned by `bouncer.with(Policy)`. Exposes the
3
+ * same four verbs as the Bouncer, dispatched to a named action method on a
4
+ * freshly-constructed policy instance (D8 — `new PolicyClass()` per check). The
5
+ * `before`/`after` hooks and the per-method `@allowGuest` rule run through the
6
+ * shared D5 pipeline.
7
+ */
8
+ import { WardenError } from "../errors.js";
9
+ import { BasePolicy } from "./BasePolicy.js";
10
+ import { getActionMetadata } from "./decorators.js";
11
+ import { evaluate, isAction, throwAuthorizationFailure, } from "./evaluate.js";
12
+ import { emptyPermissions, setPolicyContext } from "./policyContext.js";
13
+ /**
14
+ * Resolve a named action method declared anywhere on the policy's own subclass
15
+ * chain (so an action inherited from an intermediate policy base class still
16
+ * dispatches). Rejects `constructor`, the `before`/`after` hooks, and any
17
+ * `BasePolicy`/`Object.prototype` member so a verb call can never dispatch to a
18
+ * non-action method — a guard the adversarial review specifically probes.
19
+ */
20
+ function resolveActionMethod(policy, action) {
21
+ // Reject the hooks + constructor by name, then accept an action declared
22
+ // anywhere on the policy's own subclass chain — the immediate prototype up to
23
+ // (but not including) BasePolicy.prototype / Object.prototype — so a method
24
+ // inherited from an intermediate policy base class (`class A extends B`) still
25
+ // dispatches, while BasePolicy's own members (scope/permissions getters) and
26
+ // Object.prototype members stay unreachable.
27
+ const isHookOrCtor = action === "constructor" || action === "before" || action === "after";
28
+ let declaredHere = false;
29
+ for (let proto = Object.getPrototypeOf(policy); proto !== null &&
30
+ proto !== BasePolicy.prototype &&
31
+ proto !== Object.prototype; proto = Object.getPrototypeOf(proto)) {
32
+ if (Object.hasOwn(proto, action)) {
33
+ declaredHere = true;
34
+ break;
35
+ }
36
+ }
37
+ const candidate = Reflect.get(policy, action);
38
+ if (isHookOrCtor) {
39
+ declaredHere = false;
40
+ }
41
+ if (!declaredHere || !isAction(candidate)) {
42
+ throw new WardenError("UNKNOWN_POLICY_ACTION", `Policy "${policy.constructor.name}" has no action "${action}"`, { hint: "Declare the action as a method on the policy class." });
43
+ }
44
+ return candidate.bind(policy);
45
+ }
46
+ export class PolicyAuthorizer {
47
+ #user;
48
+ #factory;
49
+ #scope;
50
+ #resolvePermissions;
51
+ constructor(user, factory, scope = "global", resolvePermissions = () => Promise.resolve(emptyPermissions(scope))) {
52
+ this.#user = user;
53
+ this.#factory = factory;
54
+ this.#scope = scope;
55
+ this.#resolvePermissions = resolvePermissions;
56
+ }
57
+ /** Run a check and resolve to the full response (D8 — fresh policy per check). */
58
+ async execute(action, ...args) {
59
+ const policy = this.#factory();
60
+ // Attach the active scope + resolved permissions BEFORE dispatch so the
61
+ // before/method/after pipeline all read `this.scope` / `this.permissions`.
62
+ const permissions = await this.#resolvePermissions();
63
+ setPolicyContext(policy, { scope: this.#scope, permissions });
64
+ const method = resolveActionMethod(policy, action);
65
+ const options = getActionMetadata(policy, action);
66
+ return evaluate({
67
+ user: this.#user,
68
+ action,
69
+ allowGuest: options.allowGuest ?? false,
70
+ run: (user) => method(user, ...args),
71
+ args,
72
+ before: policy.before?.bind(policy),
73
+ after: policy.after?.bind(policy),
74
+ });
75
+ }
76
+ /** True iff the action is authorized. Never throws on denial. */
77
+ async allows(action, ...args) {
78
+ return (await this.execute(action, ...args)).authorized;
79
+ }
80
+ /** Boolean negation of {@link allows}. Never throws on denial. */
81
+ async denies(action, ...args) {
82
+ return !(await this.execute(action, ...args)).authorized;
83
+ }
84
+ /** Resolves on allow; throws `WARDEN_AUTHORIZATION_FAILURE` on deny (D2). */
85
+ async authorize(action, ...args) {
86
+ const response = await this.execute(action, ...args);
87
+ if (!response.authorized) {
88
+ throwAuthorizationFailure(response);
89
+ }
90
+ }
91
+ }
92
+ //# sourceMappingURL=PolicyAuthorizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PolicyAuthorizer.js","sourceRoot":"","sources":["../../src/bouncer/PolicyAuthorizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Bouncer policy decorators — `@allowGuest()` / `@action()` mark policy methods
3
+ * that a guest (null user) may reach. Mirrors the `Guard.ts` idiom (D9):
4
+ * `reflect-metadata` side-effect import, a `Symbol.for` key, the legacy
5
+ * `MethodDecorator` signature, and a `getActionMetadata` reader.
6
+ */
7
+ import "reflect-metadata";
8
+ import type { AbilityOptions } from "./types.js";
9
+ /**
10
+ * `@action({ allowGuest })` — configure a policy method's evaluation options.
11
+ */
12
+ export declare function action(options: AbilityOptions): MethodDecorator;
13
+ /**
14
+ * `@allowGuest()` — let a guest (null user) reach this policy method.
15
+ * Equivalent to `@action({ allowGuest: true })`.
16
+ */
17
+ export declare function allowGuest(): MethodDecorator;
18
+ /**
19
+ * Read a policy method's action metadata. Walks the prototype chain
20
+ * (`Reflect.getMetadata`) so it resolves from a policy INSTANCE — legacy
21
+ * decorators write metadata onto the class prototype, where the instance
22
+ * inherits it. Absent ⇒ `{}` (⇒ `allowGuest` falsy ⇒ guest denied).
23
+ */
24
+ export declare function getActionMetadata(target: object, propertyKey: string | symbol): AbilityOptions;
25
+ //# sourceMappingURL=decorators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../../src/bouncer/decorators.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAKjD;;GAEG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,cAAc,GAAG,eAAe,CAI/D;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,eAAe,CAE5C;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAChC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,MAAM,GAC1B,cAAc,CAEhB"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Bouncer policy decorators — `@allowGuest()` / `@action()` mark policy methods
3
+ * that a guest (null user) may reach. Mirrors the `Guard.ts` idiom (D9):
4
+ * `reflect-metadata` side-effect import, a `Symbol.for` key, the legacy
5
+ * `MethodDecorator` signature, and a `getActionMetadata` reader.
6
+ */
7
+ // Side-effect import: registers `Reflect.defineMetadata` / `getMetadata`. Pulled
8
+ // in here (not transitively) so warden stays self-sufficient when published.
9
+ import "reflect-metadata";
10
+ /** Action metadata key — Symbol.for ensures cross-module accessibility. */
11
+ const ACTION_KEY = Symbol.for("warden:bouncer:action");
12
+ /**
13
+ * `@action({ allowGuest })` — configure a policy method's evaluation options.
14
+ */
15
+ export function action(options) {
16
+ return (target, propertyKey) => {
17
+ Reflect.defineMetadata(ACTION_KEY, options, target, propertyKey);
18
+ };
19
+ }
20
+ /**
21
+ * `@allowGuest()` — let a guest (null user) reach this policy method.
22
+ * Equivalent to `@action({ allowGuest: true })`.
23
+ */
24
+ export function allowGuest() {
25
+ return action({ allowGuest: true });
26
+ }
27
+ /**
28
+ * Read a policy method's action metadata. Walks the prototype chain
29
+ * (`Reflect.getMetadata`) so it resolves from a policy INSTANCE — legacy
30
+ * decorators write metadata onto the class prototype, where the instance
31
+ * inherits it. Absent ⇒ `{}` (⇒ `allowGuest` falsy ⇒ guest denied).
32
+ */
33
+ export function getActionMetadata(target, propertyKey) {
34
+ return Reflect.getMetadata(ACTION_KEY, target, propertyKey) ?? {};
35
+ }
36
+ //# sourceMappingURL=decorators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../src/bouncer/decorators.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,iFAAiF;AACjF,6EAA6E;AAC7E,OAAO,kBAAkB,CAAC;AAG1B,2EAA2E;AAC3E,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;AAEvD;;GAEG;AACH,MAAM,UAAU,MAAM,CAAC,OAAuB;IAC7C,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE;QAC9B,OAAO,CAAC,cAAc,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAClE,CAAC,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU;IACzB,OAAO,MAAM,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;AACrC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAChC,MAAc,EACd,WAA4B;IAE5B,OAAO,OAAO,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,CAAC,IAAI,EAAE,CAAC;AACnE,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Shared evaluation pipeline — the single place that encodes the Adonis Bouncer
3
+ * evaluation order (D5), used by BOTH standalone abilities (no hooks) and policy
4
+ * methods (with `before`/`after`). Pinned by `bouncer-parity.test.ts` (AC-E2).
5
+ *
6
+ * Order: (1) `before` — a non-`undefined` return short-circuits the action;
7
+ * (2) guest-deny — `user === null` && !allowGuest ⇒ auto-deny WITHOUT running the
8
+ * action; (3) run the action; (4) `after` — a non-`undefined` return overrides.
9
+ */
10
+ import type { UserPayload } from "../AuthManager.js";
11
+ import { AuthorizationResponse } from "./AuthorizationResponse.js";
12
+ import type { AuthorizerResponse, HookResponse } from "./types.js";
13
+ /** A policy action method / ability callback, structurally. */
14
+ export type Action = (user: UserPayload | null, ...args: unknown[]) => AuthorizerResponse;
15
+ /** Boolean sugar → response; an explicit response passes through (D7). */
16
+ export declare function normalizeResponse(value: boolean | AuthorizationResponse): AuthorizationResponse;
17
+ /**
18
+ * Type guard for a callable action. A function's parameter/return types are not
19
+ * observable at runtime, so this asserts the structural `Action` shape from a
20
+ * `typeof === "function"` check — a type guard (not an `as` cast), the standard
21
+ * escape for dynamic dispatch.
22
+ */
23
+ export declare function isAction(value: unknown): value is Action;
24
+ /** Throw the canonical authorization failure for a denied response (D2). */
25
+ export declare function throwAuthorizationFailure(response: AuthorizationResponse): never;
26
+ /** Run the D5 evaluation pipeline and resolve to the final response. */
27
+ export declare function evaluate(params: {
28
+ user: UserPayload | null;
29
+ action: string;
30
+ allowGuest: boolean;
31
+ run: (user: UserPayload | null) => AuthorizerResponse;
32
+ args: unknown[];
33
+ before?: (user: UserPayload | null, action: string, ...args: unknown[]) => HookResponse;
34
+ after?: (user: UserPayload | null, action: string, result: AuthorizationResponse) => HookResponse;
35
+ }): Promise<AuthorizationResponse>;
36
+ //# sourceMappingURL=evaluate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../../src/bouncer/evaluate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEnE,+DAA+D;AAC/D,MAAM,MAAM,MAAM,GAAG,CACpB,IAAI,EAAE,WAAW,GAAG,IAAI,EACxB,GAAG,IAAI,EAAE,OAAO,EAAE,KACd,kBAAkB,CAAC;AAExB,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,OAAO,GAAG,qBAAqB,GACpC,qBAAqB,CAKvB;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAExD;AAED,4EAA4E;AAC5E,wBAAgB,yBAAyB,CACxC,QAAQ,EAAE,qBAAqB,GAC7B,KAAK,CASP;AAED,wEAAwE;AACxE,wBAAsB,QAAQ,CAAC,MAAM,EAAE;IACtC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;IACpB,GAAG,EAAE,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,KAAK,kBAAkB,CAAC;IACtD,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,CACR,IAAI,EAAE,WAAW,GAAG,IAAI,EACxB,MAAM,EAAE,MAAM,EACd,GAAG,IAAI,EAAE,OAAO,EAAE,KACd,YAAY,CAAC;IAClB,KAAK,CAAC,EAAE,CACP,IAAI,EAAE,WAAW,GAAG,IAAI,EACxB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,qBAAqB,KACzB,YAAY,CAAC;CAClB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA+BjC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Shared evaluation pipeline — the single place that encodes the Adonis Bouncer
3
+ * evaluation order (D5), used by BOTH standalone abilities (no hooks) and policy
4
+ * methods (with `before`/`after`). Pinned by `bouncer-parity.test.ts` (AC-E2).
5
+ *
6
+ * Order: (1) `before` — a non-`undefined` return short-circuits the action;
7
+ * (2) guest-deny — `user === null` && !allowGuest ⇒ auto-deny WITHOUT running the
8
+ * action; (3) run the action; (4) `after` — a non-`undefined` return overrides.
9
+ */
10
+ import { WardenError } from "../errors.js";
11
+ import { AuthorizationResponse } from "./AuthorizationResponse.js";
12
+ /** Boolean sugar → response; an explicit response passes through (D7). */
13
+ export function normalizeResponse(value) {
14
+ if (value instanceof AuthorizationResponse) {
15
+ return value;
16
+ }
17
+ return value ? AuthorizationResponse.allow() : AuthorizationResponse.deny();
18
+ }
19
+ /**
20
+ * Type guard for a callable action. A function's parameter/return types are not
21
+ * observable at runtime, so this asserts the structural `Action` shape from a
22
+ * `typeof === "function"` check — a type guard (not an `as` cast), the standard
23
+ * escape for dynamic dispatch.
24
+ */
25
+ export function isAction(value) {
26
+ return typeof value === "function";
27
+ }
28
+ /** Throw the canonical authorization failure for a denied response (D2). */
29
+ export function throwAuthorizationFailure(response) {
30
+ throw new WardenError("AUTHORIZATION_FAILURE", response.message ?? "Authorization failed", {
31
+ hint: "The current user is not authorized for this action.",
32
+ status: response.status ?? 403,
33
+ });
34
+ }
35
+ /** Run the D5 evaluation pipeline and resolve to the final response. */
36
+ export async function evaluate(params) {
37
+ const { user, action, allowGuest, run, args, before, after } = params;
38
+ let response;
39
+ // (1) before — non-undefined short-circuits the action.
40
+ if (before) {
41
+ const early = await before(user, action, ...args);
42
+ if (early !== undefined) {
43
+ response = normalizeResponse(early);
44
+ }
45
+ }
46
+ // (2) guest-deny + (3) action — only if before did not short-circuit.
47
+ if (response === undefined) {
48
+ if (user === null && !allowGuest) {
49
+ response = AuthorizationResponse.deny();
50
+ }
51
+ else {
52
+ response = normalizeResponse(await run(user));
53
+ }
54
+ }
55
+ // (4) after — non-undefined overrides.
56
+ if (after) {
57
+ const override = await after(user, action, response);
58
+ if (override !== undefined) {
59
+ response = normalizeResponse(override);
60
+ }
61
+ }
62
+ return response;
63
+ }
64
+ //# sourceMappingURL=evaluate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../../src/bouncer/evaluate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AASnE,0EAA0E;AAC1E,MAAM,UAAU,iBAAiB,CAChC,KAAsC;IAEtC,IAAI,KAAK,YAAY,qBAAqB,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,CAAC,qBAAqB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,qBAAqB,CAAC,IAAI,EAAE,CAAC;AAC7E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAc;IACtC,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC;AACpC,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,yBAAyB,CACxC,QAA+B;IAE/B,MAAM,IAAI,WAAW,CACpB,uBAAuB,EACvB,QAAQ,CAAC,OAAO,IAAI,sBAAsB,EAC1C;QACC,IAAI,EAAE,qDAAqD;QAC3D,MAAM,EAAE,QAAQ,CAAC,MAAM,IAAI,GAAG;KAC9B,CACD,CAAC;AACH,CAAC;AAED,wEAAwE;AACxE,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,MAgB9B;IACA,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC;IAEtE,IAAI,QAA2C,CAAC;IAEhD,wDAAwD;IACxD,IAAI,MAAM,EAAE,CAAC;QACZ,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;QAClD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACzB,QAAQ,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;QACrC,CAAC;IACF,CAAC;IAED,sEAAsE;IACtE,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC5B,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAClC,QAAQ,GAAG,qBAAqB,CAAC,IAAI,EAAE,CAAC;QACzC,CAAC;aAAM,CAAC;YACP,QAAQ,GAAG,iBAAiB,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QAC/C,CAAC;IACF,CAAC;IAED,uCAAuC;IACvC,IAAI,KAAK,EAAE,CAAC;QACX,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;QACrD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC5B,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACxC,CAAC;IACF,CAAC;IAED,OAAO,QAAQ,CAAC;AACjB,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Internal policy context attach — Layer-2 ⨯ Layer-1 wiring (Epic 56, story 56.3).
3
+ *
4
+ * The active `scope` and the resolved `EffectivePermissions` for the in-flight
5
+ * `(user, scope)` are attached to a freshly-constructed policy via a package-
6
+ * internal `WeakMap` (D2) — NOT a public setter on `BasePolicy` and NOT barrelled.
7
+ * `BasePolicy`'s `this.scope` / `this.permissions` getters read it back; the
8
+ * `PolicyAuthorizer` writes it before dispatch. A policy used without a Bouncer
9
+ * has no entry, so the getters fall back to `global` + {@link emptyPermissions}.
10
+ */
11
+ import type { EffectivePermissions, Scope } from "../rights/types.js";
12
+ import type { BasePolicy } from "./BasePolicy.js";
13
+ /** What the authorizer attaches to a policy instance for one check. */
14
+ export interface PolicyContext {
15
+ readonly scope: Scope;
16
+ readonly permissions: EffectivePermissions;
17
+ }
18
+ /** Attach the active scope + resolved permissions to a policy instance (internal). */
19
+ export declare function setPolicyContext(policy: BasePolicy, context: PolicyContext): void;
20
+ /** Read back a policy's attached context, or `undefined` when used standalone. */
21
+ export declare function getPolicyContext(policy: BasePolicy): PolicyContext | undefined;
22
+ /**
23
+ * The empty `EffectivePermissions` for a guest / no-resolver Bouncer (D9). Built
24
+ * locally — never a `resolve()` call (the resolver requires a non-null user) and
25
+ * never importing 56.1's non-exported `ResolvedPermissions`. `has` → false;
26
+ * `hasAll([])` → true (vacuous); `hasAny` → false; empty sets; `scope` = active.
27
+ */
28
+ export declare function emptyPermissions(scope: Scope): EffectivePermissions;
29
+ //# sourceMappingURL=policyContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policyContext.d.ts","sourceRoot":"","sources":["../../src/bouncer/policyContext.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;CAC3C;AAID,sFAAsF;AACtF,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,aAAa,GACpB,IAAI,CAEN;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,UAAU,GAChB,aAAa,GAAG,SAAS,CAE3B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,GAAG,oBAAoB,CASnE"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Internal policy context attach — Layer-2 ⨯ Layer-1 wiring (Epic 56, story 56.3).
3
+ *
4
+ * The active `scope` and the resolved `EffectivePermissions` for the in-flight
5
+ * `(user, scope)` are attached to a freshly-constructed policy via a package-
6
+ * internal `WeakMap` (D2) — NOT a public setter on `BasePolicy` and NOT barrelled.
7
+ * `BasePolicy`'s `this.scope` / `this.permissions` getters read it back; the
8
+ * `PolicyAuthorizer` writes it before dispatch. A policy used without a Bouncer
9
+ * has no entry, so the getters fall back to `global` + {@link emptyPermissions}.
10
+ */
11
+ const contexts = new WeakMap();
12
+ /** Attach the active scope + resolved permissions to a policy instance (internal). */
13
+ export function setPolicyContext(policy, context) {
14
+ contexts.set(policy, context);
15
+ }
16
+ /** Read back a policy's attached context, or `undefined` when used standalone. */
17
+ export function getPolicyContext(policy) {
18
+ return contexts.get(policy);
19
+ }
20
+ /**
21
+ * The empty `EffectivePermissions` for a guest / no-resolver Bouncer (D9). Built
22
+ * locally — never a `resolve()` call (the resolver requires a non-null user) and
23
+ * never importing 56.1's non-exported `ResolvedPermissions`. `has` → false;
24
+ * `hasAll([])` → true (vacuous); `hasAny` → false; empty sets; `scope` = active.
25
+ */
26
+ export function emptyPermissions(scope) {
27
+ return {
28
+ has: () => false,
29
+ hasAll: (permissions) => permissions.length === 0,
30
+ hasAny: () => false,
31
+ permissions: new Set(),
32
+ roles: new Set(),
33
+ scope,
34
+ };
35
+ }
36
+ //# sourceMappingURL=policyContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policyContext.js","sourceRoot":"","sources":["../../src/bouncer/policyContext.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAWH,MAAM,QAAQ,GAAG,IAAI,OAAO,EAA6B,CAAC;AAE1D,sFAAsF;AACtF,MAAM,UAAU,gBAAgB,CAC/B,MAAkB,EAClB,OAAsB;IAEtB,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC/B,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,gBAAgB,CAC/B,MAAkB;IAElB,OAAO,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAY;IAC5C,OAAO;QACN,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK;QAChB,MAAM,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC;QACjD,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK;QACnB,WAAW,EAAE,IAAI,GAAG,EAAU;QAC9B,KAAK,EAAE,IAAI,GAAG,EAAU;QACxB,KAAK;KACL,CAAC;AACH,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Bouncer evaluation contract — Layer 2 of Warden's unified authorization.
3
+ *
4
+ * The shapes here are faithful to AdonisJS Bouncer (verified context7
5
+ * `/adonisjs/bouncer`, 2026-06-01): a predicate returns a boolean (sugar for
6
+ * allow / deny) or an explicit `AuthorizationResponse`, sync or async.
7
+ */
8
+ import type { UserPayload } from "../AuthManager.js";
9
+ import type { RightsResolver } from "../rights/RightsResolver.js";
10
+ import type { Scope } from "../rights/types.js";
11
+ import type { AuthorizationResponse } from "./AuthorizationResponse.js";
12
+ /**
13
+ * Optional 4th `Bouncer` ctor argument (56.3, D1) — the scope dimension + the
14
+ * Layer-1 resolver. Both optional; omitting it ⇒ the implicit `global` scope
15
+ * with no resolver (single-tenant zero-config, D7). Additive: every 56.2 call
16
+ * site keeps compiling.
17
+ */
18
+ export interface BouncerContext {
19
+ /** Active resolution scope. Defaults to `"global"`. */
20
+ readonly scope?: Scope;
21
+ /** Layer-1 resolver consulted for this Bouncer's `(user, scope)`. */
22
+ readonly resolver?: RightsResolver;
23
+ }
24
+ /** A predicate's return — bool sugar or an explicit response, sync or async (D7). */
25
+ export type AuthorizerResponse = boolean | AuthorizationResponse | Promise<boolean | AuthorizationResponse>;
26
+ /** A `before`/`after` hook return — like {@link AuthorizerResponse} plus `undefined` (fall-through). */
27
+ export type HookResponse = boolean | AuthorizationResponse | undefined | Promise<boolean | AuthorizationResponse | undefined>;
28
+ /** Options accepted by `Bouncer.ability` and the `@action` decorator. */
29
+ export interface AbilityOptions {
30
+ allowGuest?: boolean;
31
+ }
32
+ /**
33
+ * An opaque ability reference produced by `Bouncer.ability`. Usable by-reference
34
+ * (typed call args via the `Args` phantom) AND by-name (registered in the
35
+ * Bouncer ctor's abilities record).
36
+ *
37
+ * `Args` is carried by the contravariant `__args` phantom — never read at
38
+ * runtime — so a concrete `Ability<[Post]>` is assignable to the universal
39
+ * `Ability<never[]>` storage type while `execute` stays invokable with the real
40
+ * args. This is a branding pattern (carry the type parameter in the signature),
41
+ * not a cast: `execute` itself takes `unknown[]` and the call sites validate
42
+ * args via the phantom at the verb boundary.
43
+ */
44
+ export interface Ability<Args extends unknown[] = unknown[]> {
45
+ /** Whether a guest (null user) may invoke the callback (D5 step 2). */
46
+ readonly allowGuest: boolean;
47
+ /** Runs the ability callback. Guest-deny is applied by the evaluator, not here. */
48
+ readonly execute: (user: UserPayload | null, ...args: unknown[]) => AuthorizerResponse;
49
+ /** Phantom — ties the by-reference call args to `Args`; never read at runtime. */
50
+ readonly __args?: (...args: Args) => void;
51
+ }
52
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/bouncer/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAExE;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC9B,uDAAuD;IACvD,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;CACnC;AAED,qFAAqF;AACrF,MAAM,MAAM,kBAAkB,GAC3B,OAAO,GACP,qBAAqB,GACrB,OAAO,CAAC,OAAO,GAAG,qBAAqB,CAAC,CAAC;AAE5C,wGAAwG;AACxG,MAAM,MAAM,YAAY,GACrB,OAAO,GACP,qBAAqB,GACrB,SAAS,GACT,OAAO,CAAC,OAAO,GAAG,qBAAqB,GAAG,SAAS,CAAC,CAAC;AAExD,yEAAyE;AACzE,MAAM,WAAW,cAAc;IAC9B,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,OAAO,CAAC,IAAI,SAAS,OAAO,EAAE,GAAG,OAAO,EAAE;IAC1D,uEAAuE;IACvE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,mFAAmF;IACnF,QAAQ,CAAC,OAAO,EAAE,CACjB,IAAI,EAAE,WAAW,GAAG,IAAI,EACxB,GAAG,IAAI,EAAE,OAAO,EAAE,KACd,kBAAkB,CAAC;IACxB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;CAC1C"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Bouncer evaluation contract — Layer 2 of Warden's unified authorization.
3
+ *
4
+ * The shapes here are faithful to AdonisJS Bouncer (verified context7
5
+ * `/adonisjs/bouncer`, 2026-06-01): a predicate returns a boolean (sugar for
6
+ * allow / deny) or an explicit `AuthorizationResponse`, sync or async.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/bouncer/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}