@hearth-auth/node 0.0.1

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 (85) hide show
  1. package/dist/admin.d.ts +83 -0
  2. package/dist/admin.d.ts.map +1 -0
  3. package/dist/admin.js +184 -0
  4. package/dist/admin.js.map +1 -0
  5. package/dist/admin.test.d.ts +2 -0
  6. package/dist/admin.test.d.ts.map +1 -0
  7. package/dist/admin.test.js +239 -0
  8. package/dist/admin.test.js.map +1 -0
  9. package/dist/authorize.d.ts +35 -0
  10. package/dist/authorize.d.ts.map +1 -0
  11. package/dist/authorize.js +68 -0
  12. package/dist/authorize.js.map +1 -0
  13. package/dist/authorize.test.d.ts +2 -0
  14. package/dist/authorize.test.d.ts.map +1 -0
  15. package/dist/authorize.test.js +93 -0
  16. package/dist/authorize.test.js.map +1 -0
  17. package/dist/client.d.ts +36 -0
  18. package/dist/client.d.ts.map +1 -0
  19. package/dist/client.js +51 -0
  20. package/dist/client.js.map +1 -0
  21. package/dist/config.d.ts +47 -0
  22. package/dist/config.d.ts.map +1 -0
  23. package/dist/config.js +33 -0
  24. package/dist/config.js.map +1 -0
  25. package/dist/config.test.d.ts +2 -0
  26. package/dist/config.test.d.ts.map +1 -0
  27. package/dist/config.test.js +36 -0
  28. package/dist/config.test.js.map +1 -0
  29. package/dist/discovery.d.ts +22 -0
  30. package/dist/discovery.d.ts.map +1 -0
  31. package/dist/discovery.js +60 -0
  32. package/dist/discovery.js.map +1 -0
  33. package/dist/discovery.test.d.ts +2 -0
  34. package/dist/discovery.test.d.ts.map +1 -0
  35. package/dist/discovery.test.js +77 -0
  36. package/dist/discovery.test.js.map +1 -0
  37. package/dist/errors.d.ts +120 -0
  38. package/dist/errors.d.ts.map +1 -0
  39. package/dist/errors.js +172 -0
  40. package/dist/errors.js.map +1 -0
  41. package/dist/errors.test.d.ts +2 -0
  42. package/dist/errors.test.d.ts.map +1 -0
  43. package/dist/errors.test.js +89 -0
  44. package/dist/errors.test.js.map +1 -0
  45. package/dist/index.d.ts +16 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +18 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/introspect.d.ts +37 -0
  50. package/dist/introspect.d.ts.map +1 -0
  51. package/dist/introspect.js +72 -0
  52. package/dist/introspect.js.map +1 -0
  53. package/dist/introspect.test.d.ts +2 -0
  54. package/dist/introspect.test.d.ts.map +1 -0
  55. package/dist/introspect.test.js +109 -0
  56. package/dist/introspect.test.js.map +1 -0
  57. package/dist/jwks.d.ts +26 -0
  58. package/dist/jwks.d.ts.map +1 -0
  59. package/dist/jwks.js +106 -0
  60. package/dist/jwks.js.map +1 -0
  61. package/dist/jwks.test.d.ts +7 -0
  62. package/dist/jwks.test.d.ts.map +1 -0
  63. package/dist/jwks.test.js +154 -0
  64. package/dist/jwks.test.js.map +1 -0
  65. package/dist/middleware.d.ts +61 -0
  66. package/dist/middleware.d.ts.map +1 -0
  67. package/dist/middleware.js +228 -0
  68. package/dist/middleware.js.map +1 -0
  69. package/dist/middleware.mode.test.d.ts +2 -0
  70. package/dist/middleware.mode.test.d.ts.map +1 -0
  71. package/dist/middleware.mode.test.js +203 -0
  72. package/dist/middleware.mode.test.js.map +1 -0
  73. package/dist/middleware.test.d.ts +2 -0
  74. package/dist/middleware.test.d.ts.map +1 -0
  75. package/dist/middleware.test.js +144 -0
  76. package/dist/middleware.test.js.map +1 -0
  77. package/dist/token.d.ts +68 -0
  78. package/dist/token.d.ts.map +1 -0
  79. package/dist/token.js +111 -0
  80. package/dist/token.js.map +1 -0
  81. package/dist/token.test.d.ts +2 -0
  82. package/dist/token.test.d.ts.map +1 -0
  83. package/dist/token.test.js +135 -0
  84. package/dist/token.test.js.map +1 -0
  85. package/package.json +40 -0
@@ -0,0 +1,228 @@
1
+ /** §6 — Framework-decoupled Express and Fastify middleware for JWT verification. */
2
+ import { HearthClient } from "./client.js";
3
+ import { resolveConfig } from "./config.js";
4
+ import { IntrospectionClient } from "./introspect.js";
5
+ import { AuthorizeClient } from "./authorize.js";
6
+ import { TokenVerificationError, AuthorizationModeError, RequiredActionError } from "./errors.js";
7
+ const WWW_AUTHENTICATE = 'Bearer realm="hearth"';
8
+ function extractBearer(headers) {
9
+ const raw = headers["authorization"];
10
+ const header = Array.isArray(raw) ? raw[0] : raw;
11
+ if (!header?.startsWith("Bearer "))
12
+ return null;
13
+ return header.slice(7);
14
+ }
15
+ function sendUnauthorized(res, description) {
16
+ if (res.setHeader)
17
+ res.setHeader("WWW-Authenticate", WWW_AUTHENTICATE);
18
+ if (res.header)
19
+ res.header("WWW-Authenticate", WWW_AUTHENTICATE);
20
+ res.status(401);
21
+ const body = { error: "unauthorized", error_description: description };
22
+ if (res.json)
23
+ res.json(body);
24
+ else if (res.send)
25
+ res.send(body);
26
+ }
27
+ function sendForbidden(res) {
28
+ res.status(403);
29
+ const body = { error: "forbidden", error_description: "Insufficient scope, role, or permission" };
30
+ if (res.json)
31
+ res.json(body);
32
+ else if (res.send)
33
+ res.send(body);
34
+ }
35
+ /** Check scope and role from JWT claims — always used for embedded mode. */
36
+ function checkScopeAndRole(token, opts) {
37
+ if (opts.requiredScope && !token.hasScope(opts.requiredScope))
38
+ return false;
39
+ if (opts.requiredRole && !token.hasRole(opts.requiredRole))
40
+ return false;
41
+ return true;
42
+ }
43
+ /** Embedded: all checks from JWT claims — no network calls. */
44
+ function checkEmbedded(token, opts) {
45
+ if (!checkScopeAndRole(token, opts))
46
+ return "deny_forbidden";
47
+ if (opts.requiredPermission && !token.hasPermission(opts.requiredPermission))
48
+ return "deny_forbidden";
49
+ return "allow";
50
+ }
51
+ /** Introspection: call /introspect for live RBAC; enforce mode echo match. */
52
+ async function checkIntrospection(token, introspectionClient, opts, verifiedToken) {
53
+ // Scope/role are still checked from JWT (lightweight, no extra roundtrip)
54
+ if (!checkScopeAndRole(verifiedToken, opts))
55
+ return "deny_forbidden";
56
+ let result;
57
+ try {
58
+ result = await introspectionClient.introspect(token, "access_token");
59
+ }
60
+ catch {
61
+ return "deny_forbidden";
62
+ }
63
+ if (!result.active)
64
+ return "deny_unauthorized";
65
+ // Mode-echo check: server must confirm the token was issued for introspection mode.
66
+ if (result.mode !== undefined && result.mode !== opts.expectedMode) {
67
+ // Typed error for callers who inspect the cause, but middleware is fail-closed.
68
+ const _ = new AuthorizationModeError(opts.expectedMode, result.mode);
69
+ void _;
70
+ return "deny_forbidden";
71
+ }
72
+ if (opts.requiredPermission) {
73
+ const livePermissions = result.permissions ?? [];
74
+ if (!livePermissions.includes(opts.requiredPermission))
75
+ return "deny_forbidden";
76
+ }
77
+ return "allow";
78
+ }
79
+ /** Decision: per-request POST /oauth/authorize — fail-closed. */
80
+ async function checkDecision(token, authorizeClient, opts, verifiedToken) {
81
+ if (!checkScopeAndRole(verifiedToken, opts))
82
+ return "deny_forbidden";
83
+ if (opts.requiredPermission) {
84
+ const result = await authorizeClient.decide(token, opts.requiredPermission);
85
+ if (!result.allowed)
86
+ return "deny_forbidden";
87
+ }
88
+ return "allow";
89
+ }
90
+ /** Express-compatible middleware factory. Attaches verified token to `req.hearthToken`. */
91
+ export function hearthMiddleware(options) {
92
+ const resolved = resolveConfig(options);
93
+ const client = new HearthClient(options);
94
+ const introspectionClient = new IntrospectionClient(resolved, async () => {
95
+ throw new Error("Discovery not available in middleware context; configure introspection_endpoint explicitly");
96
+ });
97
+ const authorizeClient = new AuthorizeClient(resolved);
98
+ const required = options.required !== false;
99
+ const mode = options.expectedMode ?? "embedded";
100
+ return async (req, res, next) => {
101
+ const rawToken = extractBearer(req.headers);
102
+ if (!rawToken) {
103
+ if (required) {
104
+ sendUnauthorized(res, "Bearer token required");
105
+ return;
106
+ }
107
+ next();
108
+ return;
109
+ }
110
+ let verified;
111
+ try {
112
+ verified = await client.verifyToken(rawToken);
113
+ }
114
+ catch (err) {
115
+ if (required) {
116
+ const desc = err instanceof TokenVerificationError ? err.message : "Token verification failed";
117
+ sendUnauthorized(res, desc);
118
+ return;
119
+ }
120
+ next();
121
+ return;
122
+ }
123
+ // §6 Rule 6: required_action tokens must never be accepted for general API access
124
+ if (verified.tokenType() === "required_action") {
125
+ const err = new RequiredActionError(verified.requiredActions());
126
+ sendUnauthorized(res, "Token requires completion of required actions");
127
+ throw err;
128
+ }
129
+ let decision;
130
+ if (mode === "introspection") {
131
+ decision = await checkIntrospection(rawToken, introspectionClient, options, verified);
132
+ }
133
+ else if (mode === "decision") {
134
+ decision = await checkDecision(rawToken, authorizeClient, options, verified);
135
+ }
136
+ else {
137
+ decision = checkEmbedded(verified, options);
138
+ }
139
+ if (decision === "deny_unauthorized") {
140
+ sendUnauthorized(res, "Token is no longer active");
141
+ return;
142
+ }
143
+ if (decision === "deny_forbidden") {
144
+ sendForbidden(res);
145
+ return;
146
+ }
147
+ req.hearthToken = verified;
148
+ next();
149
+ };
150
+ }
151
+ /** Fastify hook/plugin factory. Attaches verified token to `request.hearthToken`. */
152
+ export function hearthFastifyHook(options) {
153
+ const resolved = resolveConfig(options);
154
+ const client = new HearthClient(options);
155
+ const introspectionClient = new IntrospectionClient(resolved, async () => {
156
+ throw new Error("Discovery not available in middleware context; configure introspection_endpoint explicitly");
157
+ });
158
+ const authorizeClient = new AuthorizeClient(resolved);
159
+ const required = options.required !== false;
160
+ const mode = options.expectedMode ?? "embedded";
161
+ return async (request, reply) => {
162
+ const authHeader = request.headers["authorization"];
163
+ if (!authHeader?.startsWith("Bearer ")) {
164
+ if (required) {
165
+ reply.header("WWW-Authenticate", WWW_AUTHENTICATE).code(401).send({
166
+ error: "unauthorized",
167
+ error_description: "Bearer token required",
168
+ });
169
+ return;
170
+ }
171
+ return;
172
+ }
173
+ const rawToken = authHeader.slice(7);
174
+ let verified;
175
+ try {
176
+ verified = await client.verifyToken(rawToken);
177
+ }
178
+ catch (err) {
179
+ if (required) {
180
+ const desc = err instanceof TokenVerificationError ? err.message : "Token verification failed";
181
+ reply.header("WWW-Authenticate", WWW_AUTHENTICATE).code(401).send({
182
+ error: "unauthorized",
183
+ error_description: desc,
184
+ });
185
+ return;
186
+ }
187
+ return;
188
+ }
189
+ // §6 Rule 6: required_action tokens must never be accepted for general API access
190
+ if (verified.tokenType() === "required_action") {
191
+ const err = new RequiredActionError(verified.requiredActions());
192
+ reply.header("WWW-Authenticate", WWW_AUTHENTICATE).code(401).send({
193
+ error: "unauthorized",
194
+ error_description: "Token requires completion of required actions",
195
+ });
196
+ throw err;
197
+ }
198
+ // Reuse Express-side request wrapper for auth-options check
199
+ const minimalReq = { headers: request.headers };
200
+ let decision;
201
+ if (mode === "introspection") {
202
+ decision = await checkIntrospection(rawToken, introspectionClient, options, verified);
203
+ }
204
+ else if (mode === "decision") {
205
+ decision = await checkDecision(rawToken, authorizeClient, options, verified);
206
+ }
207
+ else {
208
+ decision = checkEmbedded(verified, options);
209
+ }
210
+ void minimalReq;
211
+ if (decision === "deny_unauthorized") {
212
+ reply.header("WWW-Authenticate", WWW_AUTHENTICATE).code(401).send({
213
+ error: "unauthorized",
214
+ error_description: "Token is no longer active",
215
+ });
216
+ return;
217
+ }
218
+ if (decision === "deny_forbidden") {
219
+ reply.code(403).send({
220
+ error: "forbidden",
221
+ error_description: "Insufficient scope, role, or permission",
222
+ });
223
+ return;
224
+ }
225
+ request.hearthToken = verified;
226
+ };
227
+ }
228
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=middleware.mode.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.mode.test.d.ts","sourceRoot":"","sources":["../src/middleware.mode.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Tests for mode-aware middleware — covers the expectedMode contract from HEA-924.
3
+ *
4
+ * Design constraint: absence of `permissions` in a token MUST NOT silently
5
+ * change authorization behavior. Only explicit `expectedMode` drives the check path.
6
+ */
7
+ import { describe, it, expect, vi, afterEach } from "vitest";
8
+ import { hearthMiddleware, hearthFastifyHook } from "./middleware.js";
9
+ import { HearthClient } from "./client.js";
10
+ import { AuthorizeClient } from "./authorize.js";
11
+ import { IntrospectionClient } from "./introspect.js";
12
+ import { AuthorizationModeError } from "./errors.js";
13
+ import { VerifiedToken } from "./token.js";
14
+ const BASE_CONFIG = {
15
+ issuer_url: "https://auth.example.com",
16
+ client_id: "app",
17
+ client_secret: "secret",
18
+ realm_id: "11111111-1111-1111-1111-111111111111",
19
+ };
20
+ function makeToken(payload = {}) {
21
+ return new VerifiedToken({ sub: "u1", iss: "https://auth.example.com", exp: 9_999_999_999, iat: 1_700_000_000, ...payload }, { alg: "RS256" });
22
+ }
23
+ function makeReqRes(authHeader = "Bearer tok") {
24
+ const req = { headers: { authorization: authHeader }, hearthToken: undefined };
25
+ const res = {
26
+ statusCode: 200,
27
+ body: undefined,
28
+ headers: {},
29
+ status(code) { this.statusCode = code; return this; },
30
+ json(body) { this.body = body; return this; },
31
+ setHeader(name, value) { this.headers[name] = value; return this; },
32
+ };
33
+ return { req, res, next: vi.fn() };
34
+ }
35
+ describe("hearthMiddleware — embedded mode (default)", () => {
36
+ afterEach(() => vi.restoreAllMocks());
37
+ it("embedded: checks permissions from JWT claims when token has them", async () => {
38
+ const token = makeToken({ permissions: ["docs.write"] });
39
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(token);
40
+ const mw = hearthMiddleware({ ...BASE_CONFIG, expectedMode: "embedded", requiredPermission: "docs.write" });
41
+ const { req, res, next } = makeReqRes();
42
+ await mw(req, res, next);
43
+ expect(res.statusCode).toBe(200);
44
+ expect(next).toHaveBeenCalled();
45
+ });
46
+ it("embedded: returns 403 when JWT has no matching permission — does NOT fall back to remote", async () => {
47
+ const token = makeToken({ permissions: [] });
48
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(token);
49
+ const authorizeSpy = vi.spyOn(AuthorizeClient.prototype, "decide");
50
+ const mw = hearthMiddleware({ ...BASE_CONFIG, expectedMode: "embedded", requiredPermission: "docs.write" });
51
+ const { req, res, next } = makeReqRes();
52
+ await mw(req, res, next);
53
+ expect(res.statusCode).toBe(403);
54
+ expect(authorizeSpy).not.toHaveBeenCalled();
55
+ expect(next).not.toHaveBeenCalled();
56
+ });
57
+ it("embedded: absent permissions claim is NOT treated as decision-mode fallback", async () => {
58
+ // Token has no permissions field at all — must still check local and return 403
59
+ const token = makeToken({});
60
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(token);
61
+ const authorizeSpy = vi.spyOn(AuthorizeClient.prototype, "decide");
62
+ const mw = hearthMiddleware({ ...BASE_CONFIG, expectedMode: "embedded", requiredPermission: "admin.read" });
63
+ const { req, res, next } = makeReqRes();
64
+ await mw(req, res, next);
65
+ expect(res.statusCode).toBe(403);
66
+ expect(authorizeSpy).not.toHaveBeenCalled();
67
+ });
68
+ });
69
+ describe("hearthMiddleware — introspection mode", () => {
70
+ afterEach(() => vi.restoreAllMocks());
71
+ it("introspection: allows when live permission is present", async () => {
72
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(makeToken());
73
+ vi.spyOn(IntrospectionClient.prototype, "introspect").mockResolvedValue({
74
+ active: true, extra: {}, mode: "introspection", permissions: ["docs.write"], roles: [], groups: [],
75
+ });
76
+ const mw = hearthMiddleware({ ...BASE_CONFIG, expectedMode: "introspection", requiredPermission: "docs.write" });
77
+ const { req, res, next } = makeReqRes();
78
+ await mw(req, res, next);
79
+ expect(res.statusCode).toBe(200);
80
+ expect(next).toHaveBeenCalled();
81
+ });
82
+ it("introspection: returns 403 when live permission is absent", async () => {
83
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(makeToken());
84
+ vi.spyOn(IntrospectionClient.prototype, "introspect").mockResolvedValue({
85
+ active: true, extra: {}, mode: "introspection", permissions: ["docs.read"], roles: [], groups: [],
86
+ });
87
+ const mw = hearthMiddleware({ ...BASE_CONFIG, expectedMode: "introspection", requiredPermission: "docs.write" });
88
+ const { req, res, next } = makeReqRes();
89
+ await mw(req, res, next);
90
+ expect(res.statusCode).toBe(403);
91
+ expect(next).not.toHaveBeenCalled();
92
+ });
93
+ it("introspection: returns 401 when token is inactive", async () => {
94
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(makeToken());
95
+ vi.spyOn(IntrospectionClient.prototype, "introspect").mockResolvedValue({
96
+ active: false, extra: {},
97
+ });
98
+ const mw = hearthMiddleware({ ...BASE_CONFIG, expectedMode: "introspection" });
99
+ const { req, res, next } = makeReqRes();
100
+ await mw(req, res, next);
101
+ expect(res.statusCode).toBe(401);
102
+ expect(next).not.toHaveBeenCalled();
103
+ });
104
+ it("introspection: mode-mismatch returns 403 with AuthorizationModeError cause", async () => {
105
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(makeToken());
106
+ vi.spyOn(IntrospectionClient.prototype, "introspect").mockResolvedValue({
107
+ active: true, extra: {}, mode: "embedded", permissions: ["docs.write"], roles: [], groups: [],
108
+ });
109
+ const mw = hearthMiddleware({ ...BASE_CONFIG, expectedMode: "introspection", requiredPermission: "docs.write" });
110
+ const { req, res, next } = makeReqRes();
111
+ await mw(req, res, next);
112
+ // Mode mismatch → fail-closed 403
113
+ expect(res.statusCode).toBe(403);
114
+ expect(next).not.toHaveBeenCalled();
115
+ });
116
+ it("introspection: fail-closed on network error (introspect throws)", async () => {
117
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(makeToken());
118
+ vi.spyOn(IntrospectionClient.prototype, "introspect").mockRejectedValue(new Error("network"));
119
+ const mw = hearthMiddleware({ ...BASE_CONFIG, expectedMode: "introspection", requiredPermission: "perm" });
120
+ const { req, res, next } = makeReqRes();
121
+ await mw(req, res, next);
122
+ expect(res.statusCode).toBe(403);
123
+ expect(next).not.toHaveBeenCalled();
124
+ });
125
+ });
126
+ describe("hearthMiddleware — decision mode", () => {
127
+ afterEach(() => vi.restoreAllMocks());
128
+ it("decision: calls /oauth/authorize and allows when server grants", async () => {
129
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(makeToken());
130
+ vi.spyOn(AuthorizeClient.prototype, "decide").mockResolvedValue({ allowed: true });
131
+ const mw = hearthMiddleware({ ...BASE_CONFIG, expectedMode: "decision", requiredPermission: "docs.write" });
132
+ const { req, res, next } = makeReqRes();
133
+ await mw(req, res, next);
134
+ expect(res.statusCode).toBe(200);
135
+ expect(next).toHaveBeenCalled();
136
+ });
137
+ it("decision: returns 403 when server denies", async () => {
138
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(makeToken());
139
+ vi.spyOn(AuthorizeClient.prototype, "decide").mockResolvedValue({ allowed: false });
140
+ const mw = hearthMiddleware({ ...BASE_CONFIG, expectedMode: "decision", requiredPermission: "docs.write" });
141
+ const { req, res, next } = makeReqRes();
142
+ await mw(req, res, next);
143
+ expect(res.statusCode).toBe(403);
144
+ expect(next).not.toHaveBeenCalled();
145
+ });
146
+ it("decision: fail-closed on network error — allowed=false means 403", async () => {
147
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(makeToken());
148
+ // decide() is fail-closed internally; simulate it returning false
149
+ vi.spyOn(AuthorizeClient.prototype, "decide").mockResolvedValue({ allowed: false });
150
+ const mw = hearthMiddleware({ ...BASE_CONFIG, expectedMode: "decision", requiredPermission: "perm" });
151
+ const { req, res, next } = makeReqRes();
152
+ await mw(req, res, next);
153
+ expect(res.statusCode).toBe(403);
154
+ });
155
+ it("decision: does NOT check JWT permissions claim — only uses /oauth/authorize result", async () => {
156
+ // Token has the permission embedded — but in decision mode we must call the server
157
+ const token = makeToken({ permissions: ["docs.write"] });
158
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(token);
159
+ const decideSpy = vi.spyOn(AuthorizeClient.prototype, "decide").mockResolvedValue({ allowed: false });
160
+ const mw = hearthMiddleware({ ...BASE_CONFIG, expectedMode: "decision", requiredPermission: "docs.write" });
161
+ const { req, res, next } = makeReqRes();
162
+ await mw(req, res, next);
163
+ // Server said no → 403 even though JWT has the perm
164
+ expect(decideSpy).toHaveBeenCalled();
165
+ expect(res.statusCode).toBe(403);
166
+ });
167
+ });
168
+ describe("hearthMiddleware — defaults to embedded when expectedMode omitted", () => {
169
+ afterEach(() => vi.restoreAllMocks());
170
+ it("no expectedMode → behaves as embedded", async () => {
171
+ const token = makeToken({ permissions: ["x.read"] });
172
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(token);
173
+ const mw = hearthMiddleware({ ...BASE_CONFIG, requiredPermission: "x.read" });
174
+ const { req, res, next } = makeReqRes();
175
+ await mw(req, res, next);
176
+ expect(res.statusCode).toBe(200);
177
+ expect(next).toHaveBeenCalled();
178
+ });
179
+ });
180
+ describe("AuthorizationModeError", () => {
181
+ it("carries expected and actual mode fields", () => {
182
+ const err = new AuthorizationModeError("introspection", "embedded");
183
+ expect(err).toBeInstanceOf(AuthorizationModeError);
184
+ expect(err.expected).toBe("introspection");
185
+ expect(err.actual).toBe("embedded");
186
+ expect(err.message).toMatch(/introspection/);
187
+ expect(err.message).toMatch(/embedded/);
188
+ });
189
+ });
190
+ describe("hearthFastifyHook — decision mode", () => {
191
+ afterEach(() => vi.restoreAllMocks());
192
+ it("decision: allows when server grants via fastify hook", async () => {
193
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(makeToken());
194
+ vi.spyOn(AuthorizeClient.prototype, "decide").mockResolvedValue({ allowed: true });
195
+ const hook = hearthFastifyHook({ ...BASE_CONFIG, expectedMode: "decision", requiredPermission: "docs.write" });
196
+ const request = { headers: { authorization: "Bearer tok" }, hearthToken: undefined };
197
+ const reply = { statusCode: 200, _headers: {}, _body: undefined, code(c) { this.statusCode = c; return this; }, header(n, v) { this._headers[n] = v; return this; }, send(b) { this._body = b; } };
198
+ await hook(request, reply);
199
+ expect(reply.statusCode).toBe(200);
200
+ expect(request.hearthToken).toBeDefined();
201
+ });
202
+ });
203
+ //# sourceMappingURL=middleware.mode.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.mode.test.js","sourceRoot":"","sources":["../src/middleware.mode.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=middleware.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.test.d.ts","sourceRoot":"","sources":["../src/middleware.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,144 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { hearthMiddleware } from "./middleware.js";
3
+ import { HearthClient } from "./client.js";
4
+ import { TokenExpiredError, RequiredActionError } from "./errors.js";
5
+ import { VerifiedToken } from "./token.js";
6
+ const BASE_CONFIG = { issuer_url: "https://auth.example.com", client_id: "app", client_secret: "secret" };
7
+ function makeReqRes(authHeader) {
8
+ const req = { headers: { authorization: authHeader }, hearthToken: undefined };
9
+ const res = {
10
+ statusCode: 200,
11
+ body: undefined,
12
+ headers: {},
13
+ status(code) { this.statusCode = code; return this; },
14
+ json(body) { this.body = body; return this; },
15
+ setHeader(name, value) { this.headers[name] = value; return this; },
16
+ };
17
+ const next = vi.fn();
18
+ return { req, res, next };
19
+ }
20
+ function makeVerifiedToken(payload = {}) {
21
+ return new VerifiedToken({
22
+ sub: "user1",
23
+ iss: "https://auth.example.com",
24
+ exp: 9_999_999_999,
25
+ iat: 1_700_000_000,
26
+ ...payload,
27
+ }, { alg: "RS256" });
28
+ }
29
+ describe("hearthMiddleware", () => {
30
+ beforeEach(() => {
31
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockReset();
32
+ });
33
+ it("calls next with req.hearthToken populated when token is valid", async () => {
34
+ const token = makeVerifiedToken();
35
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(token);
36
+ const mw = hearthMiddleware(BASE_CONFIG);
37
+ const { req, res, next } = makeReqRes("Bearer valid-token");
38
+ await mw(req, res, next);
39
+ expect(next).toHaveBeenCalledWith();
40
+ expect(req.hearthToken).toBe(token);
41
+ });
42
+ it("returns 401 with WWW-Authenticate header when no token (required=true)", async () => {
43
+ const mw = hearthMiddleware(BASE_CONFIG);
44
+ const { req, res, next } = makeReqRes();
45
+ await mw(req, res, next);
46
+ expect(res.statusCode).toBe(401);
47
+ expect(res.headers["WWW-Authenticate"]).toBe('Bearer realm="hearth"');
48
+ expect(next).not.toHaveBeenCalled();
49
+ });
50
+ it("calls next without hearthToken when token missing and required=false", async () => {
51
+ const mw = hearthMiddleware({ ...BASE_CONFIG, required: false });
52
+ const { req, res, next } = makeReqRes();
53
+ await mw(req, res, next);
54
+ expect(next).toHaveBeenCalled();
55
+ expect(req.hearthToken).toBeUndefined();
56
+ });
57
+ it("returns 401 with WWW-Authenticate when verification fails", async () => {
58
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockRejectedValue(new TokenExpiredError(new Date()));
59
+ const mw = hearthMiddleware(BASE_CONFIG);
60
+ const { req, res, next } = makeReqRes("Bearer bad-token");
61
+ await mw(req, res, next);
62
+ expect(res.statusCode).toBe(401);
63
+ expect(res.headers["WWW-Authenticate"]).toBe('Bearer realm="hearth"');
64
+ expect(next).not.toHaveBeenCalled();
65
+ });
66
+ it("returns 403 when token valid but required scope is missing", async () => {
67
+ const token = makeVerifiedToken({ scope: "openid" });
68
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(token);
69
+ const mw = hearthMiddleware({ ...BASE_CONFIG, requiredScope: "admin" });
70
+ const { req, res, next } = makeReqRes("Bearer valid-token");
71
+ await mw(req, res, next);
72
+ expect(res.statusCode).toBe(403);
73
+ expect(next).not.toHaveBeenCalled();
74
+ });
75
+ it("returns 403 when token valid but required role is missing", async () => {
76
+ const token = makeVerifiedToken({ roles: ["viewer"] });
77
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(token);
78
+ const mw = hearthMiddleware({ ...BASE_CONFIG, requiredRole: "admin" });
79
+ const { req, res, next } = makeReqRes("Bearer valid-token");
80
+ await mw(req, res, next);
81
+ expect(res.statusCode).toBe(403);
82
+ });
83
+ it("returns 403 when token valid but required permission is missing", async () => {
84
+ const token = makeVerifiedToken({ permissions: ["read"] });
85
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(token);
86
+ const mw = hearthMiddleware({ ...BASE_CONFIG, requiredPermission: "delete" });
87
+ const { req, res, next } = makeReqRes("Bearer valid-token");
88
+ await mw(req, res, next);
89
+ expect(res.statusCode).toBe(403);
90
+ });
91
+ it("allows request with all required scope/role/permission", async () => {
92
+ const token = makeVerifiedToken({
93
+ scope: "openid admin",
94
+ roles: ["superuser"],
95
+ permissions: ["delete"],
96
+ });
97
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(token);
98
+ const mw = hearthMiddleware({ ...BASE_CONFIG, requiredScope: "admin", requiredRole: "superuser", requiredPermission: "delete" });
99
+ const { req, res, next } = makeReqRes("Bearer valid-token");
100
+ await mw(req, res, next);
101
+ expect(res.statusCode).toBe(200);
102
+ expect(next).toHaveBeenCalled();
103
+ });
104
+ it("returns 401 and throws RequiredActionError when token_type is required_action", async () => {
105
+ const token = makeVerifiedToken({
106
+ token_type: "required_action",
107
+ required_actions: ["VERIFY_EMAIL", "UPDATE_PASSWORD"],
108
+ });
109
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(token);
110
+ const mw = hearthMiddleware(BASE_CONFIG);
111
+ const { req, res, next } = makeReqRes("Bearer required-action-token");
112
+ let thrownError;
113
+ try {
114
+ await mw(req, res, next);
115
+ }
116
+ catch (err) {
117
+ thrownError = err;
118
+ }
119
+ expect(res.statusCode).toBe(401);
120
+ expect(next).not.toHaveBeenCalled();
121
+ expect(thrownError).toBeInstanceOf(RequiredActionError);
122
+ const reqActionErr = thrownError;
123
+ expect(reqActionErr.requiredActions).toEqual(["VERIFY_EMAIL", "UPDATE_PASSWORD"]);
124
+ });
125
+ it("RequiredActionError includes empty requiredActions when required_actions claim is absent", async () => {
126
+ const token = makeVerifiedToken({
127
+ token_type: "required_action",
128
+ });
129
+ vi.spyOn(HearthClient.prototype, "verifyToken").mockResolvedValue(token);
130
+ const mw = hearthMiddleware(BASE_CONFIG);
131
+ const { req, res, next } = makeReqRes("Bearer required-action-token");
132
+ let thrownError;
133
+ try {
134
+ await mw(req, res, next);
135
+ }
136
+ catch (err) {
137
+ thrownError = err;
138
+ }
139
+ expect(res.statusCode).toBe(401);
140
+ expect(thrownError).toBeInstanceOf(RequiredActionError);
141
+ expect(thrownError.requiredActions).toEqual([]);
142
+ });
143
+ });
144
+ //# sourceMappingURL=middleware.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.test.js","sourceRoot":"","sources":["../src/middleware.test.ts"],"names":[],"mappings":"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"}