@c9up/warden 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/middleware.d.ts +71 -15
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +59 -14
- package/dist/middleware.js.map +1 -1
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +2 -2
- package/src/middleware.ts +132 -33
package/dist/middleware.d.ts
CHANGED
|
@@ -17,35 +17,81 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { type AuthResult } from "./AuthManager.js";
|
|
19
19
|
import type { BasePolicy } from "./bouncer/BasePolicy.js";
|
|
20
|
-
import { Bouncer } from "./bouncer/Bouncer.js";
|
|
21
20
|
import type { Ability } from "./bouncer/types.js";
|
|
22
21
|
import type { ScopeRequestContext } from "./config.js";
|
|
23
22
|
import type { Scope } from "./rights/types.js";
|
|
24
23
|
import type { SessionStore } from "./strategies/SessionStrategy.js";
|
|
24
|
+
/**
|
|
25
|
+
* Tokens warden resolves from the container: a service class, or a string/symbol
|
|
26
|
+
* alias (`"bouncer:registry"`). Mirrors Ream's `ServiceToken` without importing
|
|
27
|
+
* it — warden declares the shape so it stays framework-agnostic.
|
|
28
|
+
*/
|
|
29
|
+
type ResolvableToken = string | symbol | (abstract new (...args: never[]) => unknown);
|
|
30
|
+
/**
|
|
31
|
+
* Per-request IoC resolver Ream exposes as `ctx.containerResolver` (Adonis
|
|
32
|
+
* idiom). Warden resolves AuthManager / RightsResolver / the bouncer registry
|
|
33
|
+
* through this — reading from the context it is HANDED — so the package never
|
|
34
|
+
* imports `@c9up/ream` at runtime. A host that provides none (non-Ream, or a
|
|
35
|
+
* misconfigured kernel) yields no resolution: guarded routes fail CLOSED.
|
|
36
|
+
*/
|
|
37
|
+
interface ContainerResolver {
|
|
38
|
+
make(token: ResolvableToken): unknown;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Agnostic authorization slot — structurally Ream's `Authorizer` interface (and
|
|
42
|
+
* Adonis's bouncer contract): `allows` / `denies` / `authorize`. Typed as the
|
|
43
|
+
* interface, NOT the concrete `Bouncer`, so a real Ream `HttpContext` — whose
|
|
44
|
+
* `bouncer` is this same agnostic shape — is assignable to `WardenContext`
|
|
45
|
+
* without warden importing `@c9up/ream`. `initializeBouncer` fills the slot with
|
|
46
|
+
* a concrete `Bouncer`, which satisfies this contract.
|
|
47
|
+
*/
|
|
48
|
+
interface Authorizer {
|
|
49
|
+
allows(ability: string, ...args: unknown[]): Promise<boolean>;
|
|
50
|
+
denies(ability: string, ...args: unknown[]): Promise<boolean>;
|
|
51
|
+
authorize(ability: string, ...args: unknown[]): Promise<void>;
|
|
52
|
+
}
|
|
25
53
|
export interface WardenContext {
|
|
26
54
|
request: {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
headers: Record<string, string>;
|
|
55
|
+
/** Ream's `HttpContext` exposes headers as a METHOD, not a property. */
|
|
56
|
+
headers(): Record<string, string>;
|
|
30
57
|
};
|
|
58
|
+
/**
|
|
59
|
+
* Per-request IoC resolver (Ream's `ctx.containerResolver`). Warden resolves
|
|
60
|
+
* AuthManager + the bouncer registry through it — agnostic, no `@c9up/ream`
|
|
61
|
+
* import. Absent on a non-Ream host → guarded routes fail closed.
|
|
62
|
+
*/
|
|
63
|
+
containerResolver?: ContainerResolver;
|
|
31
64
|
response: {
|
|
32
|
-
status: (code: number) =>
|
|
65
|
+
status: (code: number) => unknown;
|
|
33
66
|
json: (data: unknown) => void;
|
|
34
67
|
};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
/** Set by `initializeBouncer` — the per-request authorization entry point. */
|
|
43
|
-
bouncer?: Bouncer;
|
|
44
|
-
/** The route handler metadata (decorators). */
|
|
68
|
+
/**
|
|
69
|
+
* Matched-route info — Ream (like Adonis) exposes the controller class +
|
|
70
|
+
* method under `ctx.route` (`ctx.route.controller` / `ctx.route.action`),
|
|
71
|
+
* NOT top-level. This is the primary source the guard-metadata lookup reads.
|
|
72
|
+
* Absent for inline-function routes. (context7 `/adonisjs/v7-docs`: "access
|
|
73
|
+
* the currently matched route via `ctx.route`".)
|
|
74
|
+
*/
|
|
45
75
|
route?: {
|
|
46
76
|
controller?: object;
|
|
47
77
|
action?: string | symbol;
|
|
48
78
|
};
|
|
79
|
+
/**
|
|
80
|
+
* Top-level controller/action — kept only as a FALLBACK for hosts that
|
|
81
|
+
* expose them flat. Ream uses `ctx.route` (above); the lookup prefers it.
|
|
82
|
+
*/
|
|
83
|
+
controller?: object;
|
|
84
|
+
action?: string | symbol;
|
|
85
|
+
/** Session store — set by a session middleware upstream. */
|
|
86
|
+
session?: SessionStore;
|
|
87
|
+
/** Set by the middleware after successful auth (Ream's `auth` slot). */
|
|
88
|
+
auth?: AuthResult;
|
|
89
|
+
/**
|
|
90
|
+
* Per-request authorization entry point — set by `initializeBouncer`. Typed
|
|
91
|
+
* as the agnostic {@link Authorizer} contract (Ream's `ctx.bouncer` slot),
|
|
92
|
+
* filled with a concrete `Bouncer` at runtime.
|
|
93
|
+
*/
|
|
94
|
+
bouncer?: Authorizer;
|
|
49
95
|
}
|
|
50
96
|
type WardenNext = () => Promise<void> | void;
|
|
51
97
|
/**
|
|
@@ -78,5 +124,15 @@ export interface BouncerRegistry {
|
|
|
78
124
|
* including for guests — matching AdonisJS's `initialize_bouncer_middleware`.
|
|
79
125
|
*/
|
|
80
126
|
export declare function initializeBouncer(ctx: WardenContext, next: WardenNext): Promise<void>;
|
|
127
|
+
/**
|
|
128
|
+
* Default export — the Adonis-style class form Ream's lazy middleware resolver
|
|
129
|
+
* expects (`new mod.default().handle(ctx, next)`). Without it the documented
|
|
130
|
+
* `router.use([() => import('@c9up/warden/middleware')])` crashes with
|
|
131
|
+
* `new undefined()`. The named `wardenMiddleware` / `initializeBouncer` stay for
|
|
132
|
+
* direct registration `router.use([wardenMiddleware])`.
|
|
133
|
+
*/
|
|
134
|
+
export default class WardenMiddleware {
|
|
135
|
+
handle(ctx: WardenContext, next: WardenNext): Promise<void>;
|
|
136
|
+
}
|
|
81
137
|
export {};
|
|
82
138
|
//# 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;AAC1B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|
package/dist/middleware.js
CHANGED
|
@@ -17,21 +17,56 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { AuthManager, sanitizePayload, } from "./AuthManager.js";
|
|
19
19
|
import { Bouncer } from "./bouncer/Bouncer.js";
|
|
20
|
+
import { WardenError } from "./errors.js";
|
|
20
21
|
import { getGuardMetadata, getPermissionMetadata, getRequireMfaMetadata, getRoleMetadata, } from "./Guard.js";
|
|
21
22
|
import { RightsResolver } from "./rights/RightsResolver.js";
|
|
22
23
|
function hasVerifyWithContext(strategy) {
|
|
23
24
|
return (typeof strategy.verifyWithContext === "function");
|
|
24
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve a token from the request's IoC resolver (`ctx.containerResolver`,
|
|
28
|
+
* Adonis idiom). Returns undefined when no resolver is present or the token is
|
|
29
|
+
* unbound — callers decide fail-open (the bouncer registry) vs fail-closed (the
|
|
30
|
+
* AuthManager gate in `wardenMiddleware`). No `@c9up/ream` import: warden reads
|
|
31
|
+
* only from the context Ream hands it.
|
|
32
|
+
*/
|
|
33
|
+
function resolveFromCtx(ctx, token) {
|
|
34
|
+
try {
|
|
35
|
+
return ctx.containerResolver?.make(token);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Bridge Ream's ctx to the `resolveScope` hook's `ScopeRequestContext`, which
|
|
43
|
+
* takes headers as a plain object (a snapshot) — keeps the app-facing hook
|
|
44
|
+
* contract stable while warden reads Ream's `headers()` method internally.
|
|
45
|
+
*/
|
|
46
|
+
function toScopeContext(ctx) {
|
|
47
|
+
return { request: { headers: ctx.request.headers() }, auth: ctx.auth };
|
|
48
|
+
}
|
|
25
49
|
/**
|
|
26
50
|
* Ream auth middleware — resolves AuthManager from the container,
|
|
27
51
|
* reads `@Guard`/`@Permission`/`@Role` metadata from the route handler,
|
|
28
52
|
* and authenticates + authorizes the request.
|
|
29
53
|
*/
|
|
30
54
|
export async function wardenMiddleware(ctx, next) {
|
|
31
|
-
const
|
|
55
|
+
const resolvedAuth = resolveFromCtx(ctx, AuthManager);
|
|
56
|
+
if (!(resolvedAuth instanceof AuthManager)) {
|
|
57
|
+
// Fail CLOSED — a missing AuthManager (WardenProvider not registered, or
|
|
58
|
+
// no `ctx.containerResolver` on a non-Ream host) must never silently let
|
|
59
|
+
// a guarded request through.
|
|
60
|
+
throw new WardenError("AUTH_NOT_REGISTERED", "AuthManager is not registered in the container — add WardenProvider to your providers list.");
|
|
61
|
+
}
|
|
62
|
+
const auth = resolvedAuth;
|
|
32
63
|
// Read guard metadata from the route handler (if declared via decorators).
|
|
33
|
-
|
|
34
|
-
|
|
64
|
+
// Ream (like Adonis) exposes the controller/method under `ctx.route`; fall
|
|
65
|
+
// back to top-level only for hosts that expose them flat. Reading the wrong
|
|
66
|
+
// place silently yields `[]` guards → guarded routes treated as public
|
|
67
|
+
// (fail-open) — the exact bug this precedence prevents.
|
|
68
|
+
const controller = ctx.route?.controller ?? ctx.controller;
|
|
69
|
+
const action = ctx.route?.action ?? ctx.action;
|
|
35
70
|
const strategies = controller && action ? getGuardMetadata(controller, action) : [];
|
|
36
71
|
// No guard on this route — pass through (public endpoint).
|
|
37
72
|
if (strategies.length === 0) {
|
|
@@ -43,7 +78,8 @@ export async function wardenMiddleware(ctx, next) {
|
|
|
43
78
|
// 2. API key header — reads the header name from the ApiKeyStrategy config
|
|
44
79
|
// (defaults to 'x-api-key' if no ApiKeyStrategy is registered)
|
|
45
80
|
// 3. Cookie/session (Session strategy — handled by the strategy itself via context)
|
|
46
|
-
const
|
|
81
|
+
const headers = ctx.request.headers();
|
|
82
|
+
const authHeader = headers.authorization ?? "";
|
|
47
83
|
const bearerToken = authHeader.startsWith("Bearer ")
|
|
48
84
|
? authHeader.slice(7)
|
|
49
85
|
: "";
|
|
@@ -61,7 +97,7 @@ export async function wardenMiddleware(ctx, next) {
|
|
|
61
97
|
// declared with `headerName: "X-Custom-Key"` would never match the
|
|
62
98
|
// incoming `x-custom-key` key without this normalisation — auth
|
|
63
99
|
// would silently fail even with the right header on the wire.
|
|
64
|
-
const apiKey =
|
|
100
|
+
const apiKey = headers[apiKeyHeader.toLowerCase()] ?? "";
|
|
65
101
|
const hasSessionStrategy = strategies.some((s) => s === "session");
|
|
66
102
|
if (!bearerToken && !apiKey && !hasSessionStrategy) {
|
|
67
103
|
ctx.response.status(401);
|
|
@@ -150,7 +186,7 @@ export async function initializeBouncer(ctx, next) {
|
|
|
150
186
|
const registry = isBouncerRegistry(candidate) ? candidate : undefined;
|
|
151
187
|
const user = ctx.auth?.user ?? null;
|
|
152
188
|
const scope = registry?.resolveScope
|
|
153
|
-
? await registry.resolveScope(ctx)
|
|
189
|
+
? await registry.resolveScope(toScopeContext(ctx))
|
|
154
190
|
: "global";
|
|
155
191
|
ctx.bouncer = new Bouncer(user, registry?.abilities, registry?.policies, {
|
|
156
192
|
scope,
|
|
@@ -158,14 +194,9 @@ export async function initializeBouncer(ctx, next) {
|
|
|
158
194
|
});
|
|
159
195
|
await next();
|
|
160
196
|
}
|
|
161
|
-
/** Resolve a container token, returning undefined instead of throwing when absent. */
|
|
197
|
+
/** Resolve a container token from the request resolver, returning undefined instead of throwing when absent. */
|
|
162
198
|
function tryResolve(ctx, token) {
|
|
163
|
-
|
|
164
|
-
return ctx.container.resolve(token);
|
|
165
|
-
}
|
|
166
|
-
catch {
|
|
167
|
-
return undefined;
|
|
168
|
-
}
|
|
199
|
+
return resolveFromCtx(ctx, token);
|
|
169
200
|
}
|
|
170
201
|
/** Structural guard — the registry is a plain object carrying ability/policy tables. */
|
|
171
202
|
function isBouncerRegistry(value) {
|
|
@@ -183,7 +214,9 @@ function isBouncerRegistry(value) {
|
|
|
183
214
|
async function resolveRequestScope(ctx) {
|
|
184
215
|
const candidate = tryResolve(ctx, "bouncer:registry");
|
|
185
216
|
const registry = isBouncerRegistry(candidate) ? candidate : undefined;
|
|
186
|
-
return registry?.resolveScope
|
|
217
|
+
return registry?.resolveScope
|
|
218
|
+
? await registry.resolveScope(toScopeContext(ctx))
|
|
219
|
+
: "global";
|
|
187
220
|
}
|
|
188
221
|
/**
|
|
189
222
|
* Try each declared strategy in order — session strategies via
|
|
@@ -271,4 +304,16 @@ async function checkAuthorization(auth, user, controller, action, scope) {
|
|
|
271
304
|
}
|
|
272
305
|
return null;
|
|
273
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* Default export — the Adonis-style class form Ream's lazy middleware resolver
|
|
309
|
+
* expects (`new mod.default().handle(ctx, next)`). Without it the documented
|
|
310
|
+
* `router.use([() => import('@c9up/warden/middleware')])` crashes with
|
|
311
|
+
* `new undefined()`. The named `wardenMiddleware` / `initializeBouncer` stay for
|
|
312
|
+
* direct registration `router.use([wardenMiddleware])`.
|
|
313
|
+
*/
|
|
314
|
+
export default class WardenMiddleware {
|
|
315
|
+
handle(ctx, next) {
|
|
316
|
+
return wardenMiddleware(ctx, next);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
274
319
|
//# sourceMappingURL=middleware.js.map
|
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;AAE1B,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAG/C,OAAO,EACN,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAQ5D,SAAS,oBAAoB,CAC5B,QAAsB;IAEtB,OAAO,CACN,OAAQ,QAAgC,CAAC,iBAAiB,KAAK,UAAU,CACzE,CAAC;AACH,CAAC;
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|
package/index.darwin-arm64.node
CHANGED
|
Binary file
|
package/index.darwin-x64.node
CHANGED
|
Binary file
|
|
Binary file
|
package/index.linux-x64-gnu.node
CHANGED
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c9up/warden",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Warden — Authentication & authorization for the Ream framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@c9up/ream": "^0.1.
|
|
48
|
+
"@c9up/ream": "^0.1.10"
|
|
49
49
|
},
|
|
50
50
|
"peerDependenciesMeta": {
|
|
51
51
|
"@c9up/ream": {
|
package/src/middleware.ts
CHANGED
|
@@ -26,6 +26,7 @@ import type { BasePolicy } from "./bouncer/BasePolicy.js";
|
|
|
26
26
|
import { Bouncer } from "./bouncer/Bouncer.js";
|
|
27
27
|
import type { Ability } from "./bouncer/types.js";
|
|
28
28
|
import type { ScopeRequestContext } from "./config.js";
|
|
29
|
+
import { WardenError } from "./errors.js";
|
|
29
30
|
import {
|
|
30
31
|
getGuardMetadata,
|
|
31
32
|
getPermissionMetadata,
|
|
@@ -48,32 +49,107 @@ function hasVerifyWithContext(
|
|
|
48
49
|
);
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Tokens warden resolves from the container: a service class, or a string/symbol
|
|
54
|
+
* alias (`"bouncer:registry"`). Mirrors Ream's `ServiceToken` without importing
|
|
55
|
+
* it — warden declares the shape so it stays framework-agnostic.
|
|
56
|
+
*/
|
|
57
|
+
type ResolvableToken =
|
|
58
|
+
| string
|
|
59
|
+
| symbol
|
|
60
|
+
| (abstract new (...args: never[]) => unknown);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Per-request IoC resolver Ream exposes as `ctx.containerResolver` (Adonis
|
|
64
|
+
* idiom). Warden resolves AuthManager / RightsResolver / the bouncer registry
|
|
65
|
+
* through this — reading from the context it is HANDED — so the package never
|
|
66
|
+
* imports `@c9up/ream` at runtime. A host that provides none (non-Ream, or a
|
|
67
|
+
* misconfigured kernel) yields no resolution: guarded routes fail CLOSED.
|
|
68
|
+
*/
|
|
69
|
+
interface ContainerResolver {
|
|
70
|
+
make(token: ResolvableToken): unknown;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Agnostic authorization slot — structurally Ream's `Authorizer` interface (and
|
|
75
|
+
* Adonis's bouncer contract): `allows` / `denies` / `authorize`. Typed as the
|
|
76
|
+
* interface, NOT the concrete `Bouncer`, so a real Ream `HttpContext` — whose
|
|
77
|
+
* `bouncer` is this same agnostic shape — is assignable to `WardenContext`
|
|
78
|
+
* without warden importing `@c9up/ream`. `initializeBouncer` fills the slot with
|
|
79
|
+
* a concrete `Bouncer`, which satisfies this contract.
|
|
80
|
+
*/
|
|
81
|
+
interface Authorizer {
|
|
82
|
+
allows(ability: string, ...args: unknown[]): Promise<boolean>;
|
|
83
|
+
denies(ability: string, ...args: unknown[]): Promise<boolean>;
|
|
84
|
+
authorize(ability: string, ...args: unknown[]): Promise<void>;
|
|
85
|
+
}
|
|
86
|
+
|
|
51
87
|
export interface WardenContext {
|
|
52
88
|
request: {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
headers: Record<string, string>;
|
|
89
|
+
/** Ream's `HttpContext` exposes headers as a METHOD, not a property. */
|
|
90
|
+
headers(): Record<string, string>;
|
|
56
91
|
};
|
|
92
|
+
/**
|
|
93
|
+
* Per-request IoC resolver (Ream's `ctx.containerResolver`). Warden resolves
|
|
94
|
+
* AuthManager + the bouncer registry through it — agnostic, no `@c9up/ream`
|
|
95
|
+
* import. Absent on a non-Ream host → guarded routes fail closed.
|
|
96
|
+
*/
|
|
97
|
+
containerResolver?: ContainerResolver;
|
|
57
98
|
response: {
|
|
58
|
-
status: (code: number) =>
|
|
99
|
+
status: (code: number) => unknown;
|
|
59
100
|
json: (data: unknown) => void;
|
|
60
101
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
/** Set by the middleware after successful auth. */
|
|
69
|
-
auth?: AuthResult;
|
|
70
|
-
/** Set by `initializeBouncer` — the per-request authorization entry point. */
|
|
71
|
-
bouncer?: Bouncer;
|
|
72
|
-
/** The route handler metadata (decorators). */
|
|
102
|
+
/**
|
|
103
|
+
* Matched-route info — Ream (like Adonis) exposes the controller class +
|
|
104
|
+
* method under `ctx.route` (`ctx.route.controller` / `ctx.route.action`),
|
|
105
|
+
* NOT top-level. This is the primary source the guard-metadata lookup reads.
|
|
106
|
+
* Absent for inline-function routes. (context7 `/adonisjs/v7-docs`: "access
|
|
107
|
+
* the currently matched route via `ctx.route`".)
|
|
108
|
+
*/
|
|
73
109
|
route?: {
|
|
74
110
|
controller?: object;
|
|
75
111
|
action?: string | symbol;
|
|
76
112
|
};
|
|
113
|
+
/**
|
|
114
|
+
* Top-level controller/action — kept only as a FALLBACK for hosts that
|
|
115
|
+
* expose them flat. Ream uses `ctx.route` (above); the lookup prefers it.
|
|
116
|
+
*/
|
|
117
|
+
controller?: object;
|
|
118
|
+
action?: string | symbol;
|
|
119
|
+
/** Session store — set by a session middleware upstream. */
|
|
120
|
+
session?: SessionStore;
|
|
121
|
+
/** Set by the middleware after successful auth (Ream's `auth` slot). */
|
|
122
|
+
auth?: AuthResult;
|
|
123
|
+
/**
|
|
124
|
+
* Per-request authorization entry point — set by `initializeBouncer`. Typed
|
|
125
|
+
* as the agnostic {@link Authorizer} contract (Ream's `ctx.bouncer` slot),
|
|
126
|
+
* filled with a concrete `Bouncer` at runtime.
|
|
127
|
+
*/
|
|
128
|
+
bouncer?: Authorizer;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Resolve a token from the request's IoC resolver (`ctx.containerResolver`,
|
|
133
|
+
* Adonis idiom). Returns undefined when no resolver is present or the token is
|
|
134
|
+
* unbound — callers decide fail-open (the bouncer registry) vs fail-closed (the
|
|
135
|
+
* AuthManager gate in `wardenMiddleware`). No `@c9up/ream` import: warden reads
|
|
136
|
+
* only from the context Ream hands it.
|
|
137
|
+
*/
|
|
138
|
+
function resolveFromCtx(ctx: WardenContext, token: ResolvableToken): unknown {
|
|
139
|
+
try {
|
|
140
|
+
return ctx.containerResolver?.make(token);
|
|
141
|
+
} catch {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Bridge Ream's ctx to the `resolveScope` hook's `ScopeRequestContext`, which
|
|
148
|
+
* takes headers as a plain object (a snapshot) — keeps the app-facing hook
|
|
149
|
+
* contract stable while warden reads Ream's `headers()` method internally.
|
|
150
|
+
*/
|
|
151
|
+
function toScopeContext(ctx: WardenContext): ScopeRequestContext {
|
|
152
|
+
return { request: { headers: ctx.request.headers() }, auth: ctx.auth };
|
|
77
153
|
}
|
|
78
154
|
|
|
79
155
|
type WardenNext = () => Promise<void> | void;
|
|
@@ -84,11 +160,25 @@ type WardenNext = () => Promise<void> | void;
|
|
|
84
160
|
* and authenticates + authorizes the request.
|
|
85
161
|
*/
|
|
86
162
|
export async function wardenMiddleware(ctx: WardenContext, next: WardenNext) {
|
|
87
|
-
const
|
|
163
|
+
const resolvedAuth = resolveFromCtx(ctx, AuthManager);
|
|
164
|
+
if (!(resolvedAuth instanceof AuthManager)) {
|
|
165
|
+
// Fail CLOSED — a missing AuthManager (WardenProvider not registered, or
|
|
166
|
+
// no `ctx.containerResolver` on a non-Ream host) must never silently let
|
|
167
|
+
// a guarded request through.
|
|
168
|
+
throw new WardenError(
|
|
169
|
+
"AUTH_NOT_REGISTERED",
|
|
170
|
+
"AuthManager is not registered in the container — add WardenProvider to your providers list.",
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
const auth = resolvedAuth;
|
|
88
174
|
|
|
89
175
|
// Read guard metadata from the route handler (if declared via decorators).
|
|
90
|
-
|
|
91
|
-
|
|
176
|
+
// Ream (like Adonis) exposes the controller/method under `ctx.route`; fall
|
|
177
|
+
// back to top-level only for hosts that expose them flat. Reading the wrong
|
|
178
|
+
// place silently yields `[]` guards → guarded routes treated as public
|
|
179
|
+
// (fail-open) — the exact bug this precedence prevents.
|
|
180
|
+
const controller = ctx.route?.controller ?? ctx.controller;
|
|
181
|
+
const action = ctx.route?.action ?? ctx.action;
|
|
92
182
|
const strategies =
|
|
93
183
|
controller && action ? getGuardMetadata(controller, action) : [];
|
|
94
184
|
|
|
@@ -103,7 +193,8 @@ export async function wardenMiddleware(ctx: WardenContext, next: WardenNext) {
|
|
|
103
193
|
// 2. API key header — reads the header name from the ApiKeyStrategy config
|
|
104
194
|
// (defaults to 'x-api-key' if no ApiKeyStrategy is registered)
|
|
105
195
|
// 3. Cookie/session (Session strategy — handled by the strategy itself via context)
|
|
106
|
-
const
|
|
196
|
+
const headers = ctx.request.headers();
|
|
197
|
+
const authHeader = headers.authorization ?? "";
|
|
107
198
|
const bearerToken = authHeader.startsWith("Bearer ")
|
|
108
199
|
? authHeader.slice(7)
|
|
109
200
|
: "";
|
|
@@ -120,7 +211,7 @@ export async function wardenMiddleware(ctx: WardenContext, next: WardenNext) {
|
|
|
120
211
|
// declared with `headerName: "X-Custom-Key"` would never match the
|
|
121
212
|
// incoming `x-custom-key` key without this normalisation — auth
|
|
122
213
|
// would silently fail even with the right header on the wire.
|
|
123
|
-
const apiKey =
|
|
214
|
+
const apiKey = headers[apiKeyHeader.toLowerCase()] ?? "";
|
|
124
215
|
|
|
125
216
|
const hasSessionStrategy = strategies.some((s) => s === "session");
|
|
126
217
|
if (!bearerToken && !apiKey && !hasSessionStrategy) {
|
|
@@ -240,7 +331,7 @@ export async function initializeBouncer(ctx: WardenContext, next: WardenNext) {
|
|
|
240
331
|
|
|
241
332
|
const user = ctx.auth?.user ?? null;
|
|
242
333
|
const scope: Scope = registry?.resolveScope
|
|
243
|
-
? await registry.resolveScope(ctx)
|
|
334
|
+
? await registry.resolveScope(toScopeContext(ctx))
|
|
244
335
|
: "global";
|
|
245
336
|
|
|
246
337
|
ctx.bouncer = new Bouncer(user, registry?.abilities, registry?.policies, {
|
|
@@ -250,16 +341,9 @@ export async function initializeBouncer(ctx: WardenContext, next: WardenNext) {
|
|
|
250
341
|
await next();
|
|
251
342
|
}
|
|
252
343
|
|
|
253
|
-
/** Resolve a container token, returning undefined instead of throwing when absent. */
|
|
254
|
-
function tryResolve(
|
|
255
|
-
ctx
|
|
256
|
-
token: string | (abstract new (...args: never[]) => unknown),
|
|
257
|
-
): unknown {
|
|
258
|
-
try {
|
|
259
|
-
return ctx.container.resolve(token);
|
|
260
|
-
} catch {
|
|
261
|
-
return undefined;
|
|
262
|
-
}
|
|
344
|
+
/** Resolve a container token from the request resolver, returning undefined instead of throwing when absent. */
|
|
345
|
+
function tryResolve(ctx: WardenContext, token: ResolvableToken): unknown {
|
|
346
|
+
return resolveFromCtx(ctx, token);
|
|
263
347
|
}
|
|
264
348
|
|
|
265
349
|
/** Structural guard — the registry is a plain object carrying ability/policy tables. */
|
|
@@ -281,7 +365,9 @@ function isBouncerRegistry(value: unknown): value is BouncerRegistry {
|
|
|
281
365
|
async function resolveRequestScope(ctx: WardenContext): Promise<Scope> {
|
|
282
366
|
const candidate = tryResolve(ctx, "bouncer:registry");
|
|
283
367
|
const registry = isBouncerRegistry(candidate) ? candidate : undefined;
|
|
284
|
-
return registry?.resolveScope
|
|
368
|
+
return registry?.resolveScope
|
|
369
|
+
? await registry.resolveScope(toScopeContext(ctx))
|
|
370
|
+
: "global";
|
|
285
371
|
}
|
|
286
372
|
|
|
287
373
|
/**
|
|
@@ -390,3 +476,16 @@ async function checkAuthorization(
|
|
|
390
476
|
}
|
|
391
477
|
return null;
|
|
392
478
|
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Default export — the Adonis-style class form Ream's lazy middleware resolver
|
|
482
|
+
* expects (`new mod.default().handle(ctx, next)`). Without it the documented
|
|
483
|
+
* `router.use([() => import('@c9up/warden/middleware')])` crashes with
|
|
484
|
+
* `new undefined()`. The named `wardenMiddleware` / `initializeBouncer` stay for
|
|
485
|
+
* direct registration `router.use([wardenMiddleware])`.
|
|
486
|
+
*/
|
|
487
|
+
export default class WardenMiddleware {
|
|
488
|
+
handle(ctx: WardenContext, next: WardenNext): Promise<void> {
|
|
489
|
+
return wardenMiddleware(ctx, next);
|
|
490
|
+
}
|
|
491
|
+
}
|