@hearth-auth/sdk 2.0.3 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +295 -101
- package/dist/admin.d.ts +82 -47
- package/dist/admin.js +179 -132
- package/dist/admin.js.map +1 -1
- package/dist/claims.d.ts +10 -0
- package/dist/claims.js +16 -0
- package/dist/claims.js.map +1 -1
- package/dist/errors.d.ts +18 -6
- package/dist/errors.js +70 -7
- package/dist/errors.js.map +1 -1
- package/dist/generated/admin/schema.d.ts +3648 -0
- package/dist/generated/admin/schema.js +6 -0
- package/dist/generated/admin/schema.js.map +1 -0
- package/dist/hearth-client.d.ts +129 -10
- package/dist/hearth-client.js +278 -83
- package/dist/hearth-client.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/introspection-client.d.ts +9 -3
- package/dist/introspection-client.js +35 -14
- package/dist/introspection-client.js.map +1 -1
- package/dist/jwks-client.d.ts +8 -2
- package/dist/jwks-client.js +20 -10
- package/dist/jwks-client.js.map +1 -1
- package/dist/middleware.d.ts +117 -0
- package/dist/middleware.js +171 -1
- package/dist/middleware.js.map +1 -1
- package/dist/nextjs/edge.d.ts +26 -0
- package/dist/nextjs/edge.js +31 -0
- package/dist/nextjs/edge.js.map +1 -0
- package/dist/nextjs/index.d.ts +50 -0
- package/dist/nextjs/index.js +55 -0
- package/dist/nextjs/index.js.map +1 -0
- package/dist/session-version-cache.js.map +1 -1
- package/dist/types.d.ts +62 -4
- package/package.json +20 -3
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { IntrospectionError } from "./errors.js";
|
|
1
2
|
/**
|
|
2
3
|
* Low-level RFC 7662 token introspection client.
|
|
3
4
|
*
|
|
4
5
|
* Results are never cached — per RFC 7662 §2.1, token state can change
|
|
5
|
-
* at any time.
|
|
6
|
+
* at any time.
|
|
6
7
|
*/
|
|
7
8
|
export class IntrospectionClient {
|
|
8
9
|
endpoint;
|
|
@@ -15,22 +16,42 @@ export class IntrospectionClient {
|
|
|
15
16
|
this.clientSecret = config.clientSecret;
|
|
16
17
|
this.httpTimeout = config.httpTimeout ?? 10_000;
|
|
17
18
|
}
|
|
18
|
-
/**
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Introspect a token. Never cached per RFC 7662 §2.1.
|
|
21
|
+
*
|
|
22
|
+
* @param tokenTypeHint - Optional RFC 7662 `token_type_hint`.
|
|
23
|
+
* @throws {@link IntrospectionError} when the request fails, the endpoint
|
|
24
|
+
* answers non-2xx, or the response is not JSON.
|
|
25
|
+
*/
|
|
26
|
+
async introspect(token, tokenTypeHint) {
|
|
20
27
|
const credentials = btoa(`${this.clientId}:${this.clientSecret}`);
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const body = new URLSearchParams({ token });
|
|
29
|
+
if (tokenTypeHint)
|
|
30
|
+
body.set("token_type_hint", tokenTypeHint);
|
|
31
|
+
let resp;
|
|
32
|
+
try {
|
|
33
|
+
resp = await fetch(this.endpoint, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: {
|
|
36
|
+
Authorization: `Basic ${credentials}`,
|
|
37
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
38
|
+
},
|
|
39
|
+
body,
|
|
40
|
+
signal: AbortSignal.timeout(this.httpTimeout),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
throw new IntrospectionError("Introspection request failed", err);
|
|
45
|
+
}
|
|
30
46
|
if (!resp.ok) {
|
|
31
|
-
throw new
|
|
47
|
+
throw new IntrospectionError(`Introspection endpoint returned HTTP ${resp.status}`);
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return (await resp.json());
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
throw new IntrospectionError("Introspection response is not valid JSON", err);
|
|
32
54
|
}
|
|
33
|
-
return resp.json();
|
|
34
55
|
}
|
|
35
56
|
}
|
|
36
57
|
//# sourceMappingURL=introspection-client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"introspection-client.js","sourceRoot":"","sources":["../src/introspection-client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"introspection-client.js","sourceRoot":"","sources":["../src/introspection-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAgDjD;;;;;GAKG;AACH,MAAM,OAAO,mBAAmB;IACb,QAAQ,CAAS;IACjB,QAAQ,CAAS;IACjB,YAAY,CAAS;IAC7B,WAAW,CAAS;IAE7B,YAAY,MAAiC;QAC3C,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,qBAAqB,CAAC;QAC7C,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAChC,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC;QACxC,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC;IAClD,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,UAAU,CACd,KAAa,EACb,aAAgD;QAEhD,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;QAClE,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5C,IAAI,aAAa;YAAE,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,aAAa,CAAC,CAAC;QAE9D,IAAI,IAAc,CAAC;QACnB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE;gBAChC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,aAAa,EAAE,SAAS,WAAW,EAAE;oBACrC,cAAc,EAAE,mCAAmC;iBACpD;gBACD,IAAI;gBACJ,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC;aAC9C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,kBAAkB,CAAC,8BAA8B,EAAE,GAAG,CAAC,CAAC;QACpE,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,MAAM,IAAI,kBAAkB,CAAC,wCAAwC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACtF,CAAC;QACD,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAwB,CAAC;QACpD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,kBAAkB,CAAC,0CAA0C,EAAE,GAAG,CAAC,CAAC;QAChF,CAAC;IACH,CAAC;CACF"}
|
package/dist/jwks-client.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface VerifyOptions {
|
|
|
6
6
|
issuer?: string;
|
|
7
7
|
/** Expected `aud` claim(s). Skipped when absent. */
|
|
8
8
|
audience?: string | string[];
|
|
9
|
-
/** Clock skew tolerance in seconds
|
|
9
|
+
/** Clock skew tolerance in seconds, applied to `exp` and `nbf`. Default: 5. */
|
|
10
10
|
clockSkewSeconds?: number;
|
|
11
11
|
}
|
|
12
12
|
/** Configuration for {@link JwksClient}. */
|
|
@@ -63,7 +63,9 @@ export declare class JwksClient {
|
|
|
63
63
|
* 3. `iss` — always, against `options.issuer` or the client's configured
|
|
64
64
|
* `issuer`. Throws {@link ConfigurationError} when neither is set.
|
|
65
65
|
* 4. `aud` — when `options.audience` or the client's configured `audience` is set.
|
|
66
|
-
* 5. `
|
|
66
|
+
* 5. `nbf` — when present, within clock skew tolerance.
|
|
67
|
+
*
|
|
68
|
+
* `jose` performs every check above; this method only maps its errors.
|
|
67
69
|
*
|
|
68
70
|
* @throws {@link TokenExpiredError} when the token is expired.
|
|
69
71
|
* @throws {@link TokenInvalidError} when the signature or structure is invalid.
|
|
@@ -73,6 +75,10 @@ export declare class JwksClient {
|
|
|
73
75
|
* @throws {@link ConfigurationError} when no expected issuer is configured.
|
|
74
76
|
*/
|
|
75
77
|
verify(token: string, options?: VerifyOptions): Promise<Claims>;
|
|
78
|
+
/**
|
|
79
|
+
* Map a `jose` error onto the SDK error taxonomy (openspec/specs/sdk-support-contract/spec.md).
|
|
80
|
+
* `issuer` and `audience` are the values the check actually used.
|
|
81
|
+
*/
|
|
76
82
|
private mapJoseError;
|
|
77
83
|
/** Fetch the current JWKS keys from the endpoint. */
|
|
78
84
|
fetchKeys(): Promise<JsonWebKey[]>;
|
package/dist/jwks-client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createLocalJWKSet, jwtVerify, errors as joseErrors } from "jose";
|
|
2
2
|
import { Claims } from "./claims.js";
|
|
3
|
-
import { ConfigurationError, JWKSFetchError, TokenExpiredError, TokenInvalidError, TokenIssuerError, TokenAudienceError, } from "./errors.js";
|
|
3
|
+
import { ConfigurationError, JWKSFetchError, TokenExpiredError, TokenInvalidError, TokenIssuerError, TokenAudienceError, TokenNotYetValidError, } from "./errors.js";
|
|
4
4
|
/**
|
|
5
5
|
* JWKS-backed JWT verifier with key caching, automatic key rotation,
|
|
6
6
|
* and full EdDSA / Ed25519 signature verification (spec §2).
|
|
@@ -46,7 +46,9 @@ export class JwksClient {
|
|
|
46
46
|
* 3. `iss` — always, against `options.issuer` or the client's configured
|
|
47
47
|
* `issuer`. Throws {@link ConfigurationError} when neither is set.
|
|
48
48
|
* 4. `aud` — when `options.audience` or the client's configured `audience` is set.
|
|
49
|
-
* 5. `
|
|
49
|
+
* 5. `nbf` — when present, within clock skew tolerance.
|
|
50
|
+
*
|
|
51
|
+
* `jose` performs every check above; this method only maps its errors.
|
|
50
52
|
*
|
|
51
53
|
* @throws {@link TokenExpiredError} when the token is expired.
|
|
52
54
|
* @throws {@link TokenInvalidError} when the signature or structure is invalid.
|
|
@@ -56,7 +58,7 @@ export class JwksClient {
|
|
|
56
58
|
* @throws {@link ConfigurationError} when no expected issuer is configured.
|
|
57
59
|
*/
|
|
58
60
|
async verify(token, options) {
|
|
59
|
-
const clockTolerance = options?.clockSkewSeconds ??
|
|
61
|
+
const clockTolerance = options?.clockSkewSeconds ?? 5;
|
|
60
62
|
// Pin the issuer: fall back to the one this client was constructed with,
|
|
61
63
|
// and refuse outright when neither is available. Verifying a signature
|
|
62
64
|
// without checking `iss` accepts any token from any realm that shares this
|
|
@@ -77,6 +79,8 @@ export class JwksClient {
|
|
|
77
79
|
// the RS256 key a realm JWKS may carry signs ID tokens only, and an ID
|
|
78
80
|
// token must never pass as an access token (task 26.55).
|
|
79
81
|
algorithms: ["EdDSA"],
|
|
82
|
+
// Hearth always sets `exp`; a token without one would never expire.
|
|
83
|
+
requiredClaims: ["exp"],
|
|
80
84
|
clockTolerance,
|
|
81
85
|
});
|
|
82
86
|
return new Claims(payload);
|
|
@@ -92,29 +96,35 @@ export class JwksClient {
|
|
|
92
96
|
return await doVerify(keySet);
|
|
93
97
|
}
|
|
94
98
|
catch (retryErr) {
|
|
95
|
-
return this.mapJoseError(retryErr,
|
|
99
|
+
return this.mapJoseError(retryErr, issuer, audience);
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
|
-
return this.mapJoseError(firstErr,
|
|
102
|
+
return this.mapJoseError(firstErr, issuer, audience);
|
|
99
103
|
}
|
|
100
104
|
}
|
|
101
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Map a `jose` error onto the SDK error taxonomy (openspec/specs/sdk-support-contract/spec.md).
|
|
107
|
+
* `issuer` and `audience` are the values the check actually used.
|
|
108
|
+
*/
|
|
109
|
+
mapJoseError(err, issuer, audience) {
|
|
102
110
|
if (err instanceof joseErrors.JWTExpired) {
|
|
103
111
|
const exp = err.payload?.exp;
|
|
104
112
|
throw new TokenExpiredError(exp ? new Date(exp * 1000) : new Date(0));
|
|
105
113
|
}
|
|
106
114
|
if (err instanceof joseErrors.JWTClaimValidationFailed) {
|
|
107
115
|
const claim = err.claim;
|
|
116
|
+
if (claim === "nbf" && err.reason === "check_failed") {
|
|
117
|
+
const nbf = err.payload?.nbf;
|
|
118
|
+
throw new TokenNotYetValidError(new Date((nbf ?? 0) * 1000));
|
|
119
|
+
}
|
|
108
120
|
if (claim === "iss") {
|
|
109
121
|
const actual = err.payload?.["iss"] ?? "";
|
|
110
|
-
throw new TokenIssuerError(
|
|
122
|
+
throw new TokenIssuerError(issuer, actual);
|
|
111
123
|
}
|
|
112
124
|
if (claim === "aud") {
|
|
113
125
|
const raw = err.payload?.["aud"];
|
|
114
126
|
const actual = Array.isArray(raw) ? raw : [String(raw ?? "")];
|
|
115
|
-
const expected = Array.isArray(
|
|
116
|
-
? options.audience[0]
|
|
117
|
-
: (options?.audience ?? "");
|
|
127
|
+
const expected = Array.isArray(audience) ? (audience[0] ?? "") : (audience ?? "");
|
|
118
128
|
throw new TokenAudienceError(expected, actual);
|
|
119
129
|
}
|
|
120
130
|
throw new TokenInvalidError(`JWT claim validation failed (${claim}): ${err.message}`);
|
package/dist/jwks-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwks-client.js","sourceRoot":"","sources":["../src/jwks-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;AAG1E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,
|
|
1
|
+
{"version":3,"file":"jwks-client.js","sourceRoot":"","sources":["../src/jwks-client.ts"],"names":[],"mappings":"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"}
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Claims } from "./claims.js";
|
|
1
2
|
import type { HearthClient } from "./hearth-client.js";
|
|
2
3
|
import type { AccessTokenAuthorizationMode, AuthorizePermissionOptions } from "./types.js";
|
|
3
4
|
/** Options for {@link requirePermission}. */
|
|
@@ -39,3 +40,119 @@ export type PermissionChecker = (token: string) => Promise<boolean>;
|
|
|
39
40
|
* @param opts - Mode, client reference, and optional scoping parameters.
|
|
40
41
|
*/
|
|
41
42
|
export declare function requirePermission(permission: string, opts: RequirePermissionOptions): PermissionChecker;
|
|
43
|
+
/** Options for {@link hearthMiddleware}, {@link hearthFastifyHook} and the Next.js helpers. */
|
|
44
|
+
export interface HearthMiddlewareOptions extends AuthorizePermissionOptions {
|
|
45
|
+
/** Client used to verify tokens and, in non-embedded modes, to call the server. */
|
|
46
|
+
client: HearthClient;
|
|
47
|
+
/**
|
|
48
|
+
* How the permission check is made. Defaults to the client's `expectedMode`,
|
|
49
|
+
* then to `"embedded"`.
|
|
50
|
+
*
|
|
51
|
+
* - `"embedded"` — read `permissions` from the verified JWT. No network call.
|
|
52
|
+
* - `"introspection"` — introspect the token on every request. An inactive
|
|
53
|
+
* token answers 401; a missing live permission, a different echoed mode or
|
|
54
|
+
* a failed introspection call answers 403.
|
|
55
|
+
* - `"decision"` — ask `POST /oauth/authorize` for `requiredPermission` on
|
|
56
|
+
* every request. Any failure counts as a denial (403).
|
|
57
|
+
*
|
|
58
|
+
* A token without a `permissions` claim never changes the mode (HEA-923).
|
|
59
|
+
*/
|
|
60
|
+
mode?: AccessTokenAuthorizationMode;
|
|
61
|
+
/** When `true` (default), a request without a usable bearer token answers 401. */
|
|
62
|
+
required?: boolean;
|
|
63
|
+
/** Answer 403 unless the token's `scope` contains this value. */
|
|
64
|
+
requiredScope?: string;
|
|
65
|
+
/** Answer 403 unless the token's `roles` claim contains this value. */
|
|
66
|
+
requiredRole?: string;
|
|
67
|
+
/** Answer 403 unless the token holder has this permission (checked per `mode`). */
|
|
68
|
+
requiredPermission?: string;
|
|
69
|
+
}
|
|
70
|
+
/** JSON error body sent with a 401 or 403. */
|
|
71
|
+
export interface HearthAuthErrorBody {
|
|
72
|
+
error: "unauthorized" | "forbidden";
|
|
73
|
+
error_description: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Outcome of {@link authenticateRequest}.
|
|
77
|
+
*
|
|
78
|
+
* `ok: true` means the request may proceed; `claims` is `null` when no token
|
|
79
|
+
* was sent and `required` is `false`. `ok: false` carries the response to send.
|
|
80
|
+
*/
|
|
81
|
+
export type HearthAuthResult = {
|
|
82
|
+
ok: true;
|
|
83
|
+
claims: Claims | null;
|
|
84
|
+
} | {
|
|
85
|
+
ok: false;
|
|
86
|
+
status: 401 | 403;
|
|
87
|
+
body: HearthAuthErrorBody;
|
|
88
|
+
headers: Record<string, string>;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Check at setup time that the client can serve the configured mode, so a
|
|
92
|
+
* misconfiguration fails at startup rather than on the first request. The
|
|
93
|
+
* middleware factories call it; call it yourself when building on
|
|
94
|
+
* {@link authenticateRequest}.
|
|
95
|
+
*
|
|
96
|
+
* @throws {@link ConfigurationError}
|
|
97
|
+
*/
|
|
98
|
+
export declare function assertMiddlewareOptions(opts: HearthMiddlewareOptions): void;
|
|
99
|
+
/**
|
|
100
|
+
* Authenticate and authorize one request from its `Authorization` header.
|
|
101
|
+
*
|
|
102
|
+
* Framework-neutral core of {@link hearthMiddleware}, {@link hearthFastifyHook}
|
|
103
|
+
* and the Next.js helpers; use it to adapt Hearth to another framework.
|
|
104
|
+
*
|
|
105
|
+
* Steps: extract the bearer token, verify it (EdDSA signature, `exp`, `nbf`,
|
|
106
|
+
* `iss`, `aud`), refuse `required_action` tokens, check `requiredScope` and
|
|
107
|
+
* `requiredRole` against the JWT, then check `requiredPermission` per `mode`.
|
|
108
|
+
*/
|
|
109
|
+
export declare function authenticateRequest(authorization: string | null | undefined, opts: HearthMiddlewareOptions): Promise<HearthAuthResult>;
|
|
110
|
+
declare global {
|
|
111
|
+
namespace Express {
|
|
112
|
+
interface Request {
|
|
113
|
+
/** Verified claims, set by `hearthMiddleware()`. */
|
|
114
|
+
hearthClaims?: Claims;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** The request fields {@link hearthMiddleware} reads and writes (Express-compatible). */
|
|
119
|
+
export interface MiddlewareRequest {
|
|
120
|
+
headers: Record<string, string | string[] | undefined>;
|
|
121
|
+
hearthClaims?: Claims;
|
|
122
|
+
}
|
|
123
|
+
/** The response methods {@link hearthMiddleware} calls (Express-compatible). */
|
|
124
|
+
export interface MiddlewareResponse {
|
|
125
|
+
status(code: number): unknown;
|
|
126
|
+
setHeader(name: string, value: string): unknown;
|
|
127
|
+
json(body: unknown): unknown;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Express (or Connect-style) middleware.
|
|
131
|
+
*
|
|
132
|
+
* On success it sets `req.hearthClaims` and calls `next()`. Otherwise it
|
|
133
|
+
* answers 401 (with `WWW-Authenticate: Bearer realm="hearth"`) or 403 with a
|
|
134
|
+
* JSON body and does not call `next`. An unexpected error goes to `next(err)`.
|
|
135
|
+
*
|
|
136
|
+
* @throws {@link ConfigurationError} when the client cannot serve the mode.
|
|
137
|
+
*/
|
|
138
|
+
export declare function hearthMiddleware(opts: HearthMiddlewareOptions): (req: MiddlewareRequest, res: MiddlewareResponse, next: (err?: unknown) => void) => Promise<void>;
|
|
139
|
+
/** The request fields {@link hearthFastifyHook} reads and writes. */
|
|
140
|
+
export interface FastifyRequestLike {
|
|
141
|
+
headers: Record<string, string | string[] | undefined>;
|
|
142
|
+
hearthClaims?: Claims;
|
|
143
|
+
}
|
|
144
|
+
/** The reply methods {@link hearthFastifyHook} calls. */
|
|
145
|
+
export interface FastifyReplyLike {
|
|
146
|
+
code(statusCode: number): FastifyReplyLike;
|
|
147
|
+
header(name: string, value: string): FastifyReplyLike;
|
|
148
|
+
send(body: unknown): unknown;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Fastify `onRequest` / `preHandler` hook.
|
|
152
|
+
*
|
|
153
|
+
* On success it sets `request.hearthClaims`. Otherwise it sends 401 or 403
|
|
154
|
+
* with a JSON body, which ends the request.
|
|
155
|
+
*
|
|
156
|
+
* @throws {@link ConfigurationError} when the client cannot serve the mode.
|
|
157
|
+
*/
|
|
158
|
+
export declare function hearthFastifyHook(opts: HearthMiddlewareOptions): (request: FastifyRequestLike, reply: FastifyReplyLike) => Promise<void>;
|
package/dist/middleware.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AuthorizationModeMismatchError } from "./errors.js";
|
|
1
|
+
import { AuthorizationModeMismatchError, ConfigurationError, TokenVerificationError, } from "./errors.js";
|
|
2
2
|
/**
|
|
3
3
|
* Returns a mode-aware permission checker for the given `permission`.
|
|
4
4
|
*
|
|
@@ -49,4 +49,174 @@ export function requirePermission(permission, opts) {
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
const WWW_AUTHENTICATE = 'Bearer realm="hearth"';
|
|
53
|
+
function unauthorized(description) {
|
|
54
|
+
return {
|
|
55
|
+
ok: false,
|
|
56
|
+
status: 401,
|
|
57
|
+
body: { error: "unauthorized", error_description: description },
|
|
58
|
+
headers: { "WWW-Authenticate": WWW_AUTHENTICATE },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const FORBIDDEN = {
|
|
62
|
+
ok: false,
|
|
63
|
+
status: 403,
|
|
64
|
+
body: { error: "forbidden", error_description: "Insufficient scope, role, or permission" },
|
|
65
|
+
headers: {},
|
|
66
|
+
};
|
|
67
|
+
/** The token from an `Authorization: Bearer <token>` header, or `null`. */
|
|
68
|
+
function bearerToken(header) {
|
|
69
|
+
const match = /^Bearer[ ]+(\S+)\s*$/i.exec(header ?? "");
|
|
70
|
+
return match ? match[1] : null;
|
|
71
|
+
}
|
|
72
|
+
/** The mode in force for `opts`, after defaults. */
|
|
73
|
+
function resolveMode(opts) {
|
|
74
|
+
return opts.mode ?? opts.client.expectedMode ?? "embedded";
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check at setup time that the client can serve the configured mode, so a
|
|
78
|
+
* misconfiguration fails at startup rather than on the first request. The
|
|
79
|
+
* middleware factories call it; call it yourself when building on
|
|
80
|
+
* {@link authenticateRequest}.
|
|
81
|
+
*
|
|
82
|
+
* @throws {@link ConfigurationError}
|
|
83
|
+
*/
|
|
84
|
+
export function assertMiddlewareOptions(opts) {
|
|
85
|
+
const mode = resolveMode(opts);
|
|
86
|
+
const { client } = opts;
|
|
87
|
+
if (mode === "introspection" && (!client.clientId || !client.clientSecret)) {
|
|
88
|
+
throw new ConfigurationError("introspection mode needs a client with clientId and clientSecret");
|
|
89
|
+
}
|
|
90
|
+
if (mode === "decision" && opts.requiredPermission && !client.realmId) {
|
|
91
|
+
throw new ConfigurationError("decision mode needs a client with realmId");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Authenticate and authorize one request from its `Authorization` header.
|
|
96
|
+
*
|
|
97
|
+
* Framework-neutral core of {@link hearthMiddleware}, {@link hearthFastifyHook}
|
|
98
|
+
* and the Next.js helpers; use it to adapt Hearth to another framework.
|
|
99
|
+
*
|
|
100
|
+
* Steps: extract the bearer token, verify it (EdDSA signature, `exp`, `nbf`,
|
|
101
|
+
* `iss`, `aud`), refuse `required_action` tokens, check `requiredScope` and
|
|
102
|
+
* `requiredRole` against the JWT, then check `requiredPermission` per `mode`.
|
|
103
|
+
*/
|
|
104
|
+
export async function authenticateRequest(authorization, opts) {
|
|
105
|
+
const required = opts.required !== false;
|
|
106
|
+
const token = bearerToken(authorization);
|
|
107
|
+
if (!token) {
|
|
108
|
+
return required ? unauthorized("Bearer token required") : { ok: true, claims: null };
|
|
109
|
+
}
|
|
110
|
+
let claims;
|
|
111
|
+
try {
|
|
112
|
+
claims = await opts.client.verifyToken(token);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
if (!required)
|
|
116
|
+
return { ok: true, claims: null };
|
|
117
|
+
return unauthorized(err instanceof TokenVerificationError ? err.message : "Token verification failed");
|
|
118
|
+
}
|
|
119
|
+
// A required_action token is only good for completing the pending actions,
|
|
120
|
+
// never for general API access (spec §6 rule 6) — even on optional routes.
|
|
121
|
+
if (claims.tokenType() === "required_action") {
|
|
122
|
+
return unauthorized("Token requires completion of required actions");
|
|
123
|
+
}
|
|
124
|
+
if (opts.requiredScope && !claims.hasScope(opts.requiredScope))
|
|
125
|
+
return FORBIDDEN;
|
|
126
|
+
if (opts.requiredRole && !claims.hasRole(opts.requiredRole))
|
|
127
|
+
return FORBIDDEN;
|
|
128
|
+
switch (resolveMode(opts)) {
|
|
129
|
+
case "embedded":
|
|
130
|
+
if (opts.requiredPermission && !claims.hasPermission(opts.requiredPermission)) {
|
|
131
|
+
return FORBIDDEN;
|
|
132
|
+
}
|
|
133
|
+
return { ok: true, claims };
|
|
134
|
+
case "decision":
|
|
135
|
+
if (opts.requiredPermission) {
|
|
136
|
+
const allowed = await opts.client.authorize(token, opts.requiredPermission, {
|
|
137
|
+
organizationId: opts.organizationId,
|
|
138
|
+
resource: opts.resource,
|
|
139
|
+
});
|
|
140
|
+
if (!allowed)
|
|
141
|
+
return FORBIDDEN;
|
|
142
|
+
}
|
|
143
|
+
return { ok: true, claims };
|
|
144
|
+
case "introspection": {
|
|
145
|
+
let result;
|
|
146
|
+
try {
|
|
147
|
+
const ic = await opts.client.introspectionClient();
|
|
148
|
+
result = await ic.introspect(token, "access_token");
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return FORBIDDEN; // fail closed
|
|
152
|
+
}
|
|
153
|
+
if (!result.active)
|
|
154
|
+
return unauthorized("Token is no longer active");
|
|
155
|
+
if (result.mode !== undefined && result.mode !== "introspection")
|
|
156
|
+
return FORBIDDEN;
|
|
157
|
+
if (opts.requiredPermission &&
|
|
158
|
+
!(Array.isArray(result.permissions) && result.permissions.includes(opts.requiredPermission))) {
|
|
159
|
+
return FORBIDDEN;
|
|
160
|
+
}
|
|
161
|
+
return { ok: true, claims };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function headerValue(value) {
|
|
166
|
+
return Array.isArray(value) ? value[0] : value;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Express (or Connect-style) middleware.
|
|
170
|
+
*
|
|
171
|
+
* On success it sets `req.hearthClaims` and calls `next()`. Otherwise it
|
|
172
|
+
* answers 401 (with `WWW-Authenticate: Bearer realm="hearth"`) or 403 with a
|
|
173
|
+
* JSON body and does not call `next`. An unexpected error goes to `next(err)`.
|
|
174
|
+
*
|
|
175
|
+
* @throws {@link ConfigurationError} when the client cannot serve the mode.
|
|
176
|
+
*/
|
|
177
|
+
export function hearthMiddleware(opts) {
|
|
178
|
+
assertMiddlewareOptions(opts);
|
|
179
|
+
return async (req, res, next) => {
|
|
180
|
+
let result;
|
|
181
|
+
try {
|
|
182
|
+
result = await authenticateRequest(headerValue(req.headers["authorization"]), opts);
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
next(err);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!result.ok) {
|
|
189
|
+
for (const [name, value] of Object.entries(result.headers))
|
|
190
|
+
res.setHeader(name, value);
|
|
191
|
+
res.status(result.status);
|
|
192
|
+
res.json(result.body);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (result.claims)
|
|
196
|
+
req.hearthClaims = result.claims;
|
|
197
|
+
next();
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Fastify `onRequest` / `preHandler` hook.
|
|
202
|
+
*
|
|
203
|
+
* On success it sets `request.hearthClaims`. Otherwise it sends 401 or 403
|
|
204
|
+
* with a JSON body, which ends the request.
|
|
205
|
+
*
|
|
206
|
+
* @throws {@link ConfigurationError} when the client cannot serve the mode.
|
|
207
|
+
*/
|
|
208
|
+
export function hearthFastifyHook(opts) {
|
|
209
|
+
assertMiddlewareOptions(opts);
|
|
210
|
+
return async (request, reply) => {
|
|
211
|
+
const result = await authenticateRequest(headerValue(request.headers["authorization"]), opts);
|
|
212
|
+
if (!result.ok) {
|
|
213
|
+
for (const [name, value] of Object.entries(result.headers))
|
|
214
|
+
reply.header(name, value);
|
|
215
|
+
reply.code(result.status).send(result.body);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (result.claims)
|
|
219
|
+
request.hearthClaims = result.claims;
|
|
220
|
+
};
|
|
221
|
+
}
|
|
52
222
|
//# 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,OAAO,
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@hearth-auth/sdk/nextjs/edge` — Next.js `middleware.ts` guard for the Edge
|
|
3
|
+
* Runtime.
|
|
4
|
+
*
|
|
5
|
+
* Uses only `fetch` and Web Crypto (through `jose`); it imports no `node:`
|
|
6
|
+
* module, so it runs in a V8 isolate.
|
|
7
|
+
*/
|
|
8
|
+
import type { HearthMiddlewareOptions } from "../middleware.js";
|
|
9
|
+
/** A request whose headers have a `get()` accessor (`NextRequest`, `Request`). */
|
|
10
|
+
export interface EdgeRequestLike {
|
|
11
|
+
headers: {
|
|
12
|
+
get(name: string): string | null;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build a guard for Next.js `middleware.ts`.
|
|
17
|
+
*
|
|
18
|
+
* The guard resolves to `undefined` when the request may proceed (return
|
|
19
|
+
* `NextResponse.next()`), or to a 401/403 JSON `Response` to return as is.
|
|
20
|
+
* Create it once at module scope so the client's discovery and JWKS caches
|
|
21
|
+
* live as long as the isolate.
|
|
22
|
+
*
|
|
23
|
+
* @throws {@link ConfigurationError} when the client cannot serve the mode.
|
|
24
|
+
*/
|
|
25
|
+
export declare function hearthEdgeMiddleware(options: HearthMiddlewareOptions): (req: EdgeRequestLike) => Promise<Response | undefined>;
|
|
26
|
+
export type { HearthMiddlewareOptions } from "../middleware.js";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@hearth-auth/sdk/nextjs/edge` — Next.js `middleware.ts` guard for the Edge
|
|
3
|
+
* Runtime.
|
|
4
|
+
*
|
|
5
|
+
* Uses only `fetch` and Web Crypto (through `jose`); it imports no `node:`
|
|
6
|
+
* module, so it runs in a V8 isolate.
|
|
7
|
+
*/
|
|
8
|
+
import { assertMiddlewareOptions, authenticateRequest } from "../middleware.js";
|
|
9
|
+
/**
|
|
10
|
+
* Build a guard for Next.js `middleware.ts`.
|
|
11
|
+
*
|
|
12
|
+
* The guard resolves to `undefined` when the request may proceed (return
|
|
13
|
+
* `NextResponse.next()`), or to a 401/403 JSON `Response` to return as is.
|
|
14
|
+
* Create it once at module scope so the client's discovery and JWKS caches
|
|
15
|
+
* live as long as the isolate.
|
|
16
|
+
*
|
|
17
|
+
* @throws {@link ConfigurationError} when the client cannot serve the mode.
|
|
18
|
+
*/
|
|
19
|
+
export function hearthEdgeMiddleware(options) {
|
|
20
|
+
assertMiddlewareOptions(options);
|
|
21
|
+
return async (req) => {
|
|
22
|
+
const result = await authenticateRequest(req.headers.get("authorization"), options);
|
|
23
|
+
if (result.ok)
|
|
24
|
+
return undefined;
|
|
25
|
+
return new Response(JSON.stringify(result.body), {
|
|
26
|
+
status: result.status,
|
|
27
|
+
headers: { ...result.headers, "Content-Type": "application/json" },
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=edge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge.js","sourceRoot":"","sources":["../../src/nextjs/edge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAQhF;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAgC;IAEhC,uBAAuB,CAAC,OAAO,CAAC,CAAC;IACjC,OAAO,KAAK,EAAE,GAAoB,EAAiC,EAAE;QACnE,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,CAAC;QACpF,IAAI,MAAM,CAAC,EAAE;YAAE,OAAO,SAAS,CAAC;QAChC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;YAC/C,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,OAAO,EAAE,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE;SACnE,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@hearth-auth/sdk/nextjs` — Next.js helpers for the Node.js runtime
|
|
3
|
+
* (Pages Router API routes and App Router Route Handlers).
|
|
4
|
+
*
|
|
5
|
+
* For `middleware.ts`, which runs on the Edge Runtime, import
|
|
6
|
+
* `hearthEdgeMiddleware` from `@hearth-auth/sdk/nextjs/edge`.
|
|
7
|
+
*/
|
|
8
|
+
import type { Claims } from "../claims.js";
|
|
9
|
+
import type { HearthClient } from "../hearth-client.js";
|
|
10
|
+
import type { HearthMiddlewareOptions } from "../middleware.js";
|
|
11
|
+
/** A request whose headers have a `get()` accessor (`NextRequest`, `Request`). */
|
|
12
|
+
export interface RequestLike {
|
|
13
|
+
headers: {
|
|
14
|
+
get(name: string): string | null;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** The fields of `NextApiRequest` that {@link withHearthAuth} uses. */
|
|
18
|
+
export interface ApiRequest {
|
|
19
|
+
headers: Record<string, string | string[] | undefined>;
|
|
20
|
+
/** Verified claims, set by {@link withHearthAuth} before the handler runs. */
|
|
21
|
+
hearthClaims?: Claims;
|
|
22
|
+
}
|
|
23
|
+
/** The methods of `NextApiResponse` that {@link withHearthAuth} uses. */
|
|
24
|
+
export interface ApiResponse {
|
|
25
|
+
status(code: number): unknown;
|
|
26
|
+
setHeader(name: string, value: string): unknown;
|
|
27
|
+
json(body: unknown): unknown;
|
|
28
|
+
}
|
|
29
|
+
/** A Pages Router API route handler. */
|
|
30
|
+
export type ApiHandler<Req extends ApiRequest = ApiRequest, Res extends ApiResponse = ApiResponse> = (req: Req, res: Res) => unknown | Promise<unknown>;
|
|
31
|
+
/**
|
|
32
|
+
* Wrap a Pages Router API route with Hearth authentication.
|
|
33
|
+
*
|
|
34
|
+
* Verifies the `Authorization: Bearer` token, applies the guards in `options`,
|
|
35
|
+
* sets `req.hearthClaims` and calls `handler`. When the request fails it
|
|
36
|
+
* answers 401 or 403 and `handler` is not called.
|
|
37
|
+
*
|
|
38
|
+
* @throws {@link ConfigurationError} when the client cannot serve the mode.
|
|
39
|
+
*/
|
|
40
|
+
export declare function withHearthAuth<Req extends ApiRequest, Res extends ApiResponse>(handler: ApiHandler<Req, Res>, options: HearthMiddlewareOptions): (req: Req, res: Res) => Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Verify the bearer token on an App Router Route Handler request.
|
|
43
|
+
*
|
|
44
|
+
* Returns the verified {@link Claims}, or `null` when the `Authorization`
|
|
45
|
+
* header is missing or not a bearer token, the token does not verify, or the
|
|
46
|
+
* token is a `required_action` token. Create the {@link HearthClient} once at
|
|
47
|
+
* module scope so its JWKS cache is shared across requests.
|
|
48
|
+
*/
|
|
49
|
+
export declare function getHearthClaims(req: RequestLike, client: HearthClient): Promise<Claims | null>;
|
|
50
|
+
export type { HearthMiddlewareOptions } from "../middleware.js";
|