@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.
- package/dist/admin.d.ts +83 -0
- package/dist/admin.d.ts.map +1 -0
- package/dist/admin.js +184 -0
- package/dist/admin.js.map +1 -0
- package/dist/admin.test.d.ts +2 -0
- package/dist/admin.test.d.ts.map +1 -0
- package/dist/admin.test.js +239 -0
- package/dist/admin.test.js.map +1 -0
- package/dist/authorize.d.ts +35 -0
- package/dist/authorize.d.ts.map +1 -0
- package/dist/authorize.js +68 -0
- package/dist/authorize.js.map +1 -0
- package/dist/authorize.test.d.ts +2 -0
- package/dist/authorize.test.d.ts.map +1 -0
- package/dist/authorize.test.js +93 -0
- package/dist/authorize.test.js.map +1 -0
- package/dist/client.d.ts +36 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +51 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +33 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +2 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/config.test.js +36 -0
- package/dist/config.test.js.map +1 -0
- package/dist/discovery.d.ts +22 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +60 -0
- package/dist/discovery.js.map +1 -0
- package/dist/discovery.test.d.ts +2 -0
- package/dist/discovery.test.d.ts.map +1 -0
- package/dist/discovery.test.js +77 -0
- package/dist/discovery.test.js.map +1 -0
- package/dist/errors.d.ts +120 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +172 -0
- package/dist/errors.js.map +1 -0
- package/dist/errors.test.d.ts +2 -0
- package/dist/errors.test.d.ts.map +1 -0
- package/dist/errors.test.js +89 -0
- package/dist/errors.test.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/introspect.d.ts +37 -0
- package/dist/introspect.d.ts.map +1 -0
- package/dist/introspect.js +72 -0
- package/dist/introspect.js.map +1 -0
- package/dist/introspect.test.d.ts +2 -0
- package/dist/introspect.test.d.ts.map +1 -0
- package/dist/introspect.test.js +109 -0
- package/dist/introspect.test.js.map +1 -0
- package/dist/jwks.d.ts +26 -0
- package/dist/jwks.d.ts.map +1 -0
- package/dist/jwks.js +106 -0
- package/dist/jwks.js.map +1 -0
- package/dist/jwks.test.d.ts +7 -0
- package/dist/jwks.test.d.ts.map +1 -0
- package/dist/jwks.test.js +154 -0
- package/dist/jwks.test.js.map +1 -0
- package/dist/middleware.d.ts +61 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +228 -0
- package/dist/middleware.js.map +1 -0
- package/dist/middleware.mode.test.d.ts +2 -0
- package/dist/middleware.mode.test.d.ts.map +1 -0
- package/dist/middleware.mode.test.js +203 -0
- package/dist/middleware.mode.test.js.map +1 -0
- package/dist/middleware.test.d.ts +2 -0
- package/dist/middleware.test.d.ts.map +1 -0
- package/dist/middleware.test.js +144 -0
- package/dist/middleware.test.js.map +1 -0
- package/dist/token.d.ts +68 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +111 -0
- package/dist/token.js.map +1 -0
- package/dist/token.test.d.ts +2 -0
- package/dist/token.test.d.ts.map +1 -0
- package/dist/token.test.js +135 -0
- package/dist/token.test.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** §7 — AuthorizeClient: per-request permission decisions via POST /oauth/authorize. */
|
|
2
|
+
import { AuthorizeError } from "./errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Calls `POST /oauth/authorize` to make a per-request permission decision.
|
|
5
|
+
*
|
|
6
|
+
* This client is fail-closed: network errors and non-OK responses return
|
|
7
|
+
* `{ allowed: false }` rather than throwing, so middleware cannot accidentally
|
|
8
|
+
* grant access on infrastructure failures.
|
|
9
|
+
*
|
|
10
|
+
* Misconfiguration (no endpoint) throws `AuthorizeError` before any network call.
|
|
11
|
+
*/
|
|
12
|
+
export class AuthorizeClient {
|
|
13
|
+
endpoint;
|
|
14
|
+
realmId;
|
|
15
|
+
timeout;
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.endpoint = config.authorize_endpoint ?? `${config.issuer_url}/oauth/authorize`;
|
|
18
|
+
// If authorize_endpoint was explicitly set to null (no issuer_url fallback possible),
|
|
19
|
+
// keep null so decide() can throw a typed error.
|
|
20
|
+
if (config.authorize_endpoint === null && !config.issuer_url) {
|
|
21
|
+
this.endpoint = null;
|
|
22
|
+
}
|
|
23
|
+
this.realmId = config.realm_id;
|
|
24
|
+
this.timeout = config.http_timeout;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Check whether the bearer token holder has `permission`.
|
|
28
|
+
*
|
|
29
|
+
* Fail-closed: returns `{ allowed: false }` on any network or server error.
|
|
30
|
+
* Throws `AuthorizeError` only when the endpoint is not configured.
|
|
31
|
+
*/
|
|
32
|
+
async decide(token, permission, opts) {
|
|
33
|
+
if (!this.endpoint) {
|
|
34
|
+
throw new AuthorizeError("authorize_endpoint is not configured and issuer_url is unavailable");
|
|
35
|
+
}
|
|
36
|
+
const headers = {
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
"Authorization": `Bearer ${token}`,
|
|
39
|
+
};
|
|
40
|
+
if (this.realmId)
|
|
41
|
+
headers["X-Realm-ID"] = this.realmId;
|
|
42
|
+
const body = { permission };
|
|
43
|
+
if (opts?.organization_id)
|
|
44
|
+
body["organization_id"] = opts.organization_id;
|
|
45
|
+
if (opts?.resource)
|
|
46
|
+
body["resource"] = opts.resource;
|
|
47
|
+
try {
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
50
|
+
const res = await fetch(this.endpoint, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers,
|
|
53
|
+
body: JSON.stringify(body),
|
|
54
|
+
signal: controller.signal,
|
|
55
|
+
});
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
if (!res.ok)
|
|
58
|
+
return { allowed: false };
|
|
59
|
+
const json = await res.json();
|
|
60
|
+
return { allowed: json["allowed"] === true };
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Fail-closed: any network error → deny
|
|
64
|
+
return { allowed: false };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=authorize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorize.js","sourceRoot":"","sources":["../src/authorize.ts"],"names":[],"mappings":"AAAA,wFAAwF;AAExF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAe7C;;;;;;;;GAQG;AACH,MAAM,OAAO,eAAe;IACT,QAAQ,CAAgB;IACxB,OAAO,CAAgB;IACvB,OAAO,CAAS;IAEjC,YAAY,MAAsB;QAChC,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,kBAAkB,IAAI,GAAG,MAAM,CAAC,UAAU,kBAAkB,CAAC;QACpF,sFAAsF;QACtF,iDAAiD;QACjD,IAAI,MAAM,CAAC,kBAAkB,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YAC7D,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACvB,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC;IACrC,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,UAAkB,EAAE,IAAuB;QACrE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,IAAI,cAAc,CACtB,oEAAoE,CACrE,CAAC;QACJ,CAAC;QAED,MAAM,OAAO,GAA2B;YACtC,cAAc,EAAE,kBAAkB;YAClC,eAAe,EAAE,UAAU,KAAK,EAAE;SACnC,CAAC;QACF,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC;QAEvD,MAAM,IAAI,GAA2B,EAAE,UAAU,EAAE,CAAC;QACpD,IAAI,IAAI,EAAE,eAAe;YAAE,IAAI,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC,eAAe,CAAC;QAC1E,IAAI,IAAI,EAAE,QAAQ;YAAE,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;QAErD,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;YACjE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE;gBACrC,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;gBAC1B,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;YACH,YAAY,CAAC,KAAK,CAAC,CAAC;YAEpB,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;YAEvC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAA6B,CAAC;YACzD,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,wCAAwC;YACxC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC5B,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorize.test.d.ts","sourceRoot":"","sources":["../src/authorize.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { AuthorizeClient } from "./authorize.js";
|
|
3
|
+
const CONFIG = {
|
|
4
|
+
issuer_url: "https://auth.example.com",
|
|
5
|
+
client_id: "client1",
|
|
6
|
+
client_secret: "secret1",
|
|
7
|
+
audience: [],
|
|
8
|
+
jwks_ttl: 300_000,
|
|
9
|
+
introspection_endpoint: "https://auth.example.com/introspect",
|
|
10
|
+
authorize_endpoint: "https://auth.example.com/oauth/authorize",
|
|
11
|
+
realm_id: "11111111-1111-1111-1111-111111111111",
|
|
12
|
+
http_timeout: 10_000,
|
|
13
|
+
clock_skew_seconds: 60,
|
|
14
|
+
};
|
|
15
|
+
function makeClient(overrides = {}) {
|
|
16
|
+
return new AuthorizeClient({ ...CONFIG, ...overrides });
|
|
17
|
+
}
|
|
18
|
+
describe("AuthorizeClient", () => {
|
|
19
|
+
afterEach(() => vi.restoreAllMocks());
|
|
20
|
+
it("returns allowed=true when server grants permission", async () => {
|
|
21
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
|
22
|
+
ok: true,
|
|
23
|
+
json: async () => ({ allowed: true }),
|
|
24
|
+
});
|
|
25
|
+
const result = await makeClient().decide("tok123", "docs.write");
|
|
26
|
+
expect(result.allowed).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
it("returns allowed=false when server denies permission", async () => {
|
|
29
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
|
30
|
+
ok: true,
|
|
31
|
+
json: async () => ({ allowed: false }),
|
|
32
|
+
});
|
|
33
|
+
const result = await makeClient().decide("tok123", "docs.write");
|
|
34
|
+
expect(result.allowed).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
it("sends Authorization header with Bearer token and X-Realm-ID", async () => {
|
|
37
|
+
const spy = vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
|
38
|
+
ok: true,
|
|
39
|
+
json: async () => ({ allowed: true }),
|
|
40
|
+
});
|
|
41
|
+
await makeClient().decide("mytoken", "files.delete");
|
|
42
|
+
const [url, init] = spy.mock.calls[0];
|
|
43
|
+
expect(url).toBe("https://auth.example.com/oauth/authorize");
|
|
44
|
+
const headers = init?.headers;
|
|
45
|
+
expect(headers["Authorization"]).toBe("Bearer mytoken");
|
|
46
|
+
expect(headers["X-Realm-ID"]).toBe("11111111-1111-1111-1111-111111111111");
|
|
47
|
+
});
|
|
48
|
+
it("sends permission in request body", async () => {
|
|
49
|
+
const spy = vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
|
50
|
+
ok: true,
|
|
51
|
+
json: async () => ({ allowed: false }),
|
|
52
|
+
});
|
|
53
|
+
await makeClient().decide("tok", "users.admin");
|
|
54
|
+
const body = JSON.parse(spy.mock.calls[0][1]?.body);
|
|
55
|
+
expect(body.permission).toBe("users.admin");
|
|
56
|
+
});
|
|
57
|
+
it("passes optional organization_id and resource", async () => {
|
|
58
|
+
const spy = vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
|
59
|
+
ok: true,
|
|
60
|
+
json: async () => ({ allowed: true }),
|
|
61
|
+
});
|
|
62
|
+
await makeClient().decide("tok", "docs.read", {
|
|
63
|
+
organization_id: "org_abc",
|
|
64
|
+
resource: "https://api.example.com",
|
|
65
|
+
});
|
|
66
|
+
const body = JSON.parse(spy.mock.calls[0][1]?.body);
|
|
67
|
+
expect(body.organization_id).toBe("org_abc");
|
|
68
|
+
expect(body.resource).toBe("https://api.example.com");
|
|
69
|
+
});
|
|
70
|
+
it("fail-closed: returns allowed=false on network error (does not throw)", async () => {
|
|
71
|
+
vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("ECONNREFUSED"));
|
|
72
|
+
const result = await makeClient().decide("tok", "docs.write");
|
|
73
|
+
expect(result.allowed).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
it("fail-closed: returns allowed=false on non-OK HTTP response", async () => {
|
|
76
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
|
77
|
+
ok: false,
|
|
78
|
+
status: 502,
|
|
79
|
+
});
|
|
80
|
+
const result = await makeClient().decide("tok", "docs.write");
|
|
81
|
+
expect(result.allowed).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
it("falls back to {issuer_url}/oauth/authorize when authorize_endpoint is null", async () => {
|
|
84
|
+
const spy = vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
|
85
|
+
ok: true,
|
|
86
|
+
json: async () => ({ allowed: true }),
|
|
87
|
+
});
|
|
88
|
+
const client = makeClient({ authorize_endpoint: null });
|
|
89
|
+
await client.decide("tok", "perm");
|
|
90
|
+
expect(spy.mock.calls[0][0]).toBe("https://auth.example.com/oauth/authorize");
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=authorize.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorize.test.js","sourceRoot":"","sources":["../src/authorize.test.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** §1 — HearthClient: unified entry point for the @hearth-auth/node SDK. */
|
|
2
|
+
import type { HearthConfig } from "./config.js";
|
|
3
|
+
import type { IntrospectionResult } from "./introspect.js";
|
|
4
|
+
import type { AuthorizeOptions, AuthorizeResult } from "./authorize.js";
|
|
5
|
+
import type { VerifiedToken } from "./token.js";
|
|
6
|
+
export declare class HearthClient {
|
|
7
|
+
private readonly verifier;
|
|
8
|
+
private readonly introspectionClient;
|
|
9
|
+
private readonly authorizeClient;
|
|
10
|
+
private readonly discovery;
|
|
11
|
+
constructor(config: HearthConfig);
|
|
12
|
+
/**
|
|
13
|
+
* Verify a JWT using JWKS.
|
|
14
|
+
* Supports RS256 and ES256. Validates exp, iss, aud, iat (with clock skew tolerance).
|
|
15
|
+
* On key miss, re-fetches the JWKS once before failing (handles key rotation).
|
|
16
|
+
*/
|
|
17
|
+
verifyToken(token: string): Promise<VerifiedToken>;
|
|
18
|
+
/**
|
|
19
|
+
* Introspect a token per RFC 7662.
|
|
20
|
+
* Returns IntrospectionResult with active, sub, iss, aud, exp, iat, scope, extra.
|
|
21
|
+
*/
|
|
22
|
+
introspect(token: string, tokenTypeHint?: "access_token" | "refresh_token"): Promise<IntrospectionResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Per-request permission decision via `POST /oauth/authorize` (Decision mode).
|
|
25
|
+
*
|
|
26
|
+
* Fail-closed: returns `{ allowed: false }` on network errors.
|
|
27
|
+
* Throws `AuthorizeError` when the endpoint is misconfigured.
|
|
28
|
+
*/
|
|
29
|
+
authorize(token: string, permission: string, opts?: AuthorizeOptions): Promise<AuthorizeResult>;
|
|
30
|
+
/**
|
|
31
|
+
* Force eviction of the JWKS and discovery caches.
|
|
32
|
+
* Call this after receiving a 401 from a resource server protected by the same issuer.
|
|
33
|
+
*/
|
|
34
|
+
invalidateCache(): void;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAE5E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAe;IACxC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAsB;IAC1D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkB;gBAEhC,MAAM,EAAE,YAAY;IAQhC;;;;OAIG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAIxD;;;OAGG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,cAAc,GAAG,eAAe,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAI/G;;;;;OAKG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAIrG;;;OAGG;IACH,eAAe,IAAI,IAAI;CAGxB"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** §1 — HearthClient: unified entry point for the @hearth-auth/node SDK. */
|
|
2
|
+
import { resolveConfig } from "./config.js";
|
|
3
|
+
import { DiscoveryClient } from "./discovery.js";
|
|
4
|
+
import { JwksVerifier } from "./jwks.js";
|
|
5
|
+
import { IntrospectionClient } from "./introspect.js";
|
|
6
|
+
import { AuthorizeClient } from "./authorize.js";
|
|
7
|
+
export class HearthClient {
|
|
8
|
+
verifier;
|
|
9
|
+
introspectionClient;
|
|
10
|
+
authorizeClient;
|
|
11
|
+
discovery;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
const resolved = resolveConfig(config);
|
|
14
|
+
this.discovery = new DiscoveryClient(resolved.issuer_url, resolved.http_timeout);
|
|
15
|
+
this.verifier = new JwksVerifier(resolved, this.discovery);
|
|
16
|
+
this.introspectionClient = new IntrospectionClient(resolved, () => this.discovery.discover());
|
|
17
|
+
this.authorizeClient = new AuthorizeClient(resolved);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Verify a JWT using JWKS.
|
|
21
|
+
* Supports RS256 and ES256. Validates exp, iss, aud, iat (with clock skew tolerance).
|
|
22
|
+
* On key miss, re-fetches the JWKS once before failing (handles key rotation).
|
|
23
|
+
*/
|
|
24
|
+
async verifyToken(token) {
|
|
25
|
+
return this.verifier.verifyToken(token);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Introspect a token per RFC 7662.
|
|
29
|
+
* Returns IntrospectionResult with active, sub, iss, aud, exp, iat, scope, extra.
|
|
30
|
+
*/
|
|
31
|
+
async introspect(token, tokenTypeHint) {
|
|
32
|
+
return this.introspectionClient.introspect(token, tokenTypeHint);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Per-request permission decision via `POST /oauth/authorize` (Decision mode).
|
|
36
|
+
*
|
|
37
|
+
* Fail-closed: returns `{ allowed: false }` on network errors.
|
|
38
|
+
* Throws `AuthorizeError` when the endpoint is misconfigured.
|
|
39
|
+
*/
|
|
40
|
+
async authorize(token, permission, opts) {
|
|
41
|
+
return this.authorizeClient.decide(token, permission, opts);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Force eviction of the JWKS and discovery caches.
|
|
45
|
+
* Call this after receiving a 401 from a resource server protected by the same issuer.
|
|
46
|
+
*/
|
|
47
|
+
invalidateCache() {
|
|
48
|
+
this.verifier.invalidateCache();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAG5E,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAIjD,MAAM,OAAO,YAAY;IACN,QAAQ,CAAe;IACvB,mBAAmB,CAAsB;IACzC,eAAe,CAAkB;IACjC,SAAS,CAAkB;IAE5C,YAAY,MAAoB;QAC9B,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,CAAC,SAAS,GAAG,IAAI,eAAe,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;QACjF,IAAI,CAAC,QAAQ,GAAG,IAAI,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAC3D,IAAI,CAAC,mBAAmB,GAAG,IAAI,mBAAmB,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC9F,IAAI,CAAC,eAAe,GAAG,IAAI,eAAe,CAAC,QAAQ,CAAC,CAAC;IACvD,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,WAAW,CAAC,KAAa;QAC7B,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAC1C,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,UAAU,CAAC,KAAa,EAAE,aAAgD;QAC9E,OAAO,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IACnE,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,SAAS,CAAC,KAAa,EAAE,UAAkB,EAAE,IAAuB;QACxE,OAAO,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;IAC9D,CAAC;IAED;;;OAGG;IACH,eAAe;QACb,IAAI,CAAC,QAAQ,CAAC,eAAe,EAAE,CAAC;IAClC,CAAC;CACF"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** §1 — HearthConfig: spec-compliant configuration for HearthClient. */
|
|
2
|
+
export interface HearthConfig {
|
|
3
|
+
/** OIDC issuer URL. JWKS and introspection endpoints are auto-discovered from here. */
|
|
4
|
+
issuer_url: string;
|
|
5
|
+
/** OAuth2 client ID used for introspection. */
|
|
6
|
+
client_id: string;
|
|
7
|
+
/** OAuth2 client secret used for introspection. */
|
|
8
|
+
client_secret: string;
|
|
9
|
+
/** Optional accepted audience (string or array). Omit to skip audience validation. */
|
|
10
|
+
audience?: string | string[];
|
|
11
|
+
/** JWKS cache TTL in milliseconds. Defaults to 5 minutes; hard cap 24 hours. */
|
|
12
|
+
jwks_ttl?: number;
|
|
13
|
+
/** Override introspection endpoint URL (auto-discovered if omitted). */
|
|
14
|
+
introspection_endpoint?: string;
|
|
15
|
+
/** HTTP request timeout in milliseconds. Default: 10 000. */
|
|
16
|
+
http_timeout?: number;
|
|
17
|
+
/** Clock skew tolerance in seconds for exp/iat validation. Default: 60. */
|
|
18
|
+
clock_skew_seconds?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Realm UUID. Required for endpoints that need `X-Realm-ID` (introspection mode,
|
|
21
|
+
* decision mode via `POST /oauth/authorize`).
|
|
22
|
+
*/
|
|
23
|
+
realm_id?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Override the decision endpoint URL. Defaults to `{issuer_url}/oauth/authorize`.
|
|
26
|
+
* Only used when `expectedMode` is `"decision"`.
|
|
27
|
+
*/
|
|
28
|
+
authorize_endpoint?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare const JWKS_TTL_DEFAULT_MS: number;
|
|
31
|
+
export declare const JWKS_TTL_MAX_MS: number;
|
|
32
|
+
export declare const HTTP_TIMEOUT_DEFAULT_MS = 10000;
|
|
33
|
+
export declare const CLOCK_SKEW_DEFAULT_S = 60;
|
|
34
|
+
export interface ResolvedConfig {
|
|
35
|
+
issuer_url: string;
|
|
36
|
+
client_id: string;
|
|
37
|
+
client_secret: string;
|
|
38
|
+
audience: string[];
|
|
39
|
+
jwks_ttl: number;
|
|
40
|
+
introspection_endpoint: string | null;
|
|
41
|
+
http_timeout: number;
|
|
42
|
+
clock_skew_seconds: number;
|
|
43
|
+
realm_id: string | null;
|
|
44
|
+
authorize_endpoint: string | null;
|
|
45
|
+
}
|
|
46
|
+
export declare function resolveConfig(config: HearthConfig): ResolvedConfig;
|
|
47
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,wEAAwE;AAIxE,MAAM,WAAW,YAAY;IAC3B,uFAAuF;IACvF,UAAU,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,eAAO,MAAM,mBAAmB,QAAgB,CAAC;AACjD,eAAO,MAAM,eAAe,QAAsB,CAAC;AACnD,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAC9C,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc,CAwBlE"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** §1 — HearthConfig: spec-compliant configuration for HearthClient. */
|
|
2
|
+
import { ConfigurationError } from "./errors.js";
|
|
3
|
+
export const JWKS_TTL_DEFAULT_MS = 5 * 60 * 1000; // 5 min
|
|
4
|
+
export const JWKS_TTL_MAX_MS = 24 * 60 * 60 * 1000; // 24 h
|
|
5
|
+
export const HTTP_TIMEOUT_DEFAULT_MS = 10_000;
|
|
6
|
+
export const CLOCK_SKEW_DEFAULT_S = 60;
|
|
7
|
+
export function resolveConfig(config) {
|
|
8
|
+
if (!config.issuer_url)
|
|
9
|
+
throw new ConfigurationError("issuer_url is required");
|
|
10
|
+
if (!config.client_id)
|
|
11
|
+
throw new ConfigurationError("client_id is required");
|
|
12
|
+
if (!config.client_secret)
|
|
13
|
+
throw new ConfigurationError("client_secret is required");
|
|
14
|
+
let jwks_ttl = config.jwks_ttl ?? JWKS_TTL_DEFAULT_MS;
|
|
15
|
+
if (jwks_ttl > JWKS_TTL_MAX_MS)
|
|
16
|
+
jwks_ttl = JWKS_TTL_MAX_MS;
|
|
17
|
+
const audience = config.audience
|
|
18
|
+
? Array.isArray(config.audience) ? config.audience : [config.audience]
|
|
19
|
+
: [];
|
|
20
|
+
return {
|
|
21
|
+
issuer_url: config.issuer_url.replace(/\/$/, ""),
|
|
22
|
+
client_id: config.client_id,
|
|
23
|
+
client_secret: config.client_secret,
|
|
24
|
+
audience,
|
|
25
|
+
jwks_ttl,
|
|
26
|
+
introspection_endpoint: config.introspection_endpoint ?? null,
|
|
27
|
+
http_timeout: config.http_timeout ?? HTTP_TIMEOUT_DEFAULT_MS,
|
|
28
|
+
clock_skew_seconds: config.clock_skew_seconds ?? CLOCK_SKEW_DEFAULT_S,
|
|
29
|
+
realm_id: config.realm_id ?? null,
|
|
30
|
+
authorize_endpoint: config.authorize_endpoint ?? null,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,wEAAwE;AAExE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AA+BjD,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAI,QAAQ;AAC7D,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAE,OAAO;AAC5D,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAC9C,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAevC,MAAM,UAAU,aAAa,CAAC,MAAoB;IAChD,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,CAAC,CAAC;IAC/E,IAAI,CAAC,MAAM,CAAC,SAAS;QAAE,MAAM,IAAI,kBAAkB,CAAC,uBAAuB,CAAC,CAAC;IAC7E,IAAI,CAAC,MAAM,CAAC,aAAa;QAAE,MAAM,IAAI,kBAAkB,CAAC,2BAA2B,CAAC,CAAC;IAErF,IAAI,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,mBAAmB,CAAC;IACtD,IAAI,QAAQ,GAAG,eAAe;QAAE,QAAQ,GAAG,eAAe,CAAC;IAE3D,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ;QAC9B,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;QACtE,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;QAChD,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,QAAQ;QACR,QAAQ;QACR,sBAAsB,EAAE,MAAM,CAAC,sBAAsB,IAAI,IAAI;QAC7D,YAAY,EAAE,MAAM,CAAC,YAAY,IAAI,uBAAuB;QAC5D,kBAAkB,EAAE,MAAM,CAAC,kBAAkB,IAAI,oBAAoB;QACrE,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,IAAI;QACjC,kBAAkB,EAAE,MAAM,CAAC,kBAAkB,IAAI,IAAI;KACtD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { resolveConfig, JWKS_TTL_MAX_MS, JWKS_TTL_DEFAULT_MS, CLOCK_SKEW_DEFAULT_S, HTTP_TIMEOUT_DEFAULT_MS } from "./config.js";
|
|
3
|
+
import { ConfigurationError } from "./errors.js";
|
|
4
|
+
describe("resolveConfig", () => {
|
|
5
|
+
const base = { issuer_url: "https://auth.example.com", client_id: "cid", client_secret: "sec" };
|
|
6
|
+
it("applies defaults", () => {
|
|
7
|
+
const r = resolveConfig(base);
|
|
8
|
+
expect(r.jwks_ttl).toBe(JWKS_TTL_DEFAULT_MS);
|
|
9
|
+
expect(r.http_timeout).toBe(HTTP_TIMEOUT_DEFAULT_MS);
|
|
10
|
+
expect(r.clock_skew_seconds).toBe(CLOCK_SKEW_DEFAULT_S);
|
|
11
|
+
expect(r.audience).toEqual([]);
|
|
12
|
+
expect(r.introspection_endpoint).toBeNull();
|
|
13
|
+
});
|
|
14
|
+
it("strips trailing slash from issuer_url", () => {
|
|
15
|
+
const r = resolveConfig({ ...base, issuer_url: "https://auth.example.com/" });
|
|
16
|
+
expect(r.issuer_url).toBe("https://auth.example.com");
|
|
17
|
+
});
|
|
18
|
+
it("caps jwks_ttl at 24h", () => {
|
|
19
|
+
const r = resolveConfig({ ...base, jwks_ttl: JWKS_TTL_MAX_MS + 1 });
|
|
20
|
+
expect(r.jwks_ttl).toBe(JWKS_TTL_MAX_MS);
|
|
21
|
+
});
|
|
22
|
+
it("normalizes audience string to array", () => {
|
|
23
|
+
const r = resolveConfig({ ...base, audience: "api.example.com" });
|
|
24
|
+
expect(r.audience).toEqual(["api.example.com"]);
|
|
25
|
+
});
|
|
26
|
+
it("throws ConfigurationError on missing issuer_url", () => {
|
|
27
|
+
expect(() => resolveConfig({ ...base, issuer_url: "" })).toThrow(ConfigurationError);
|
|
28
|
+
});
|
|
29
|
+
it("throws ConfigurationError on missing client_id", () => {
|
|
30
|
+
expect(() => resolveConfig({ ...base, client_id: "" })).toThrow(ConfigurationError);
|
|
31
|
+
});
|
|
32
|
+
it("throws ConfigurationError on missing client_secret", () => {
|
|
33
|
+
expect(() => resolveConfig({ ...base, client_secret: "" })).toThrow(ConfigurationError);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
//# sourceMappingURL=config.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.js","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACjI,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,MAAM,IAAI,GAAG,EAAE,UAAU,EAAE,0BAA0B,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;IAEhG,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAC1B,MAAM,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAC7C,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QACrD,MAAM,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QACxD,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC/B,MAAM,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,GAAG,aAAa,CAAC,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,2BAA2B,EAAE,CAAC,CAAC;QAC9E,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,CAAC,GAAG,aAAa,CAAC,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,eAAe,GAAG,CAAC,EAAE,CAAC,CAAC;QACpE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,GAAG,aAAa,CAAC,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,iBAAiB,EAAE,CAAC,CAAC;QAClE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACvF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACtF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,EAAE,GAAG,IAAI,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** §1 — OIDC auto-discovery from {issuer_url}/.well-known/openid-configuration */
|
|
2
|
+
export interface OidcDiscovery {
|
|
3
|
+
issuer: string;
|
|
4
|
+
jwks_uri: string;
|
|
5
|
+
introspection_endpoint?: string;
|
|
6
|
+
authorization_endpoint?: string;
|
|
7
|
+
token_endpoint?: string;
|
|
8
|
+
userinfo_endpoint?: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export declare class DiscoveryClient {
|
|
12
|
+
private readonly issuerUrl;
|
|
13
|
+
private readonly httpTimeout;
|
|
14
|
+
private cache;
|
|
15
|
+
private fetchPromise;
|
|
16
|
+
constructor(issuerUrl: string, httpTimeout: number);
|
|
17
|
+
discover(): Promise<OidcDiscovery>;
|
|
18
|
+
private fetchDiscovery;
|
|
19
|
+
/** Reset cached discovery document (e.g. for testing or after key rotation). */
|
|
20
|
+
reset(): void;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAIlF,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,eAAe;IAKxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAL9B,OAAO,CAAC,KAAK,CAA8B;IAC3C,OAAO,CAAC,YAAY,CAAuC;gBAGxC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM;IAGhC,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC;YAgB1B,cAAc;IA0B5B,gFAAgF;IAChF,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** §1 — OIDC auto-discovery from {issuer_url}/.well-known/openid-configuration */
|
|
2
|
+
import { DiscoveryError } from "./errors.js";
|
|
3
|
+
export class DiscoveryClient {
|
|
4
|
+
issuerUrl;
|
|
5
|
+
httpTimeout;
|
|
6
|
+
cache = null;
|
|
7
|
+
fetchPromise = null;
|
|
8
|
+
constructor(issuerUrl, httpTimeout) {
|
|
9
|
+
this.issuerUrl = issuerUrl;
|
|
10
|
+
this.httpTimeout = httpTimeout;
|
|
11
|
+
}
|
|
12
|
+
async discover() {
|
|
13
|
+
if (this.cache)
|
|
14
|
+
return this.cache;
|
|
15
|
+
// Deduplicate concurrent calls
|
|
16
|
+
if (!this.fetchPromise) {
|
|
17
|
+
this.fetchPromise = this.fetchDiscovery().then((doc) => {
|
|
18
|
+
this.cache = doc;
|
|
19
|
+
this.fetchPromise = null;
|
|
20
|
+
return doc;
|
|
21
|
+
}).catch((err) => {
|
|
22
|
+
this.fetchPromise = null;
|
|
23
|
+
throw err;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return this.fetchPromise;
|
|
27
|
+
}
|
|
28
|
+
async fetchDiscovery() {
|
|
29
|
+
const url = `${this.issuerUrl}/.well-known/openid-configuration`;
|
|
30
|
+
let res;
|
|
31
|
+
try {
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
const timer = setTimeout(() => controller.abort(), this.httpTimeout);
|
|
34
|
+
res = await fetch(url, { signal: controller.signal });
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
throw new DiscoveryError(`OIDC discovery fetch failed: ${url}`, { cause: err });
|
|
39
|
+
}
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
throw new DiscoveryError(`OIDC discovery returned HTTP ${res.status}: ${url}`);
|
|
42
|
+
}
|
|
43
|
+
let doc;
|
|
44
|
+
try {
|
|
45
|
+
doc = await res.json();
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
throw new DiscoveryError("OIDC discovery response is not valid JSON", { cause: err });
|
|
49
|
+
}
|
|
50
|
+
if (!doc.jwks_uri) {
|
|
51
|
+
throw new DiscoveryError("OIDC discovery document missing required field: jwks_uri");
|
|
52
|
+
}
|
|
53
|
+
return doc;
|
|
54
|
+
}
|
|
55
|
+
/** Reset cached discovery document (e.g. for testing or after key rotation). */
|
|
56
|
+
reset() {
|
|
57
|
+
this.cache = null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAElF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAY7C,MAAM,OAAO,eAAe;IAKP;IACA;IALX,KAAK,GAAyB,IAAI,CAAC;IACnC,YAAY,GAAkC,IAAI,CAAC;IAE3D,YACmB,SAAiB,EACjB,WAAmB;QADnB,cAAS,GAAT,SAAS,CAAQ;QACjB,gBAAW,GAAX,WAAW,CAAQ;IACnC,CAAC;IAEJ,KAAK,CAAC,QAAQ;QACZ,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC;QAClC,+BAA+B;QAC/B,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;gBACrD,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC;gBACjB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;gBACzB,OAAO,GAAG,CAAC;YACb,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBACf,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;gBACzB,MAAM,GAAG,CAAC;YACZ,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAEO,KAAK,CAAC,cAAc;QAC1B,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,SAAS,mCAAmC,CAAC;QACjE,IAAI,GAAa,CAAC;QAClB,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;YACrE,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;YACtD,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,cAAc,CAAC,gCAAgC,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;QAClF,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,cAAc,CAAC,gCAAgC,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC,CAAC;QACjF,CAAC;QACD,IAAI,GAAkB,CAAC;QACvB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,EAAmB,CAAC;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,cAAc,CAAC,2CAA2C,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;QACxF,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,IAAI,cAAc,CAAC,0DAA0D,CAAC,CAAC;QACvF,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,gFAAgF;IAChF,KAAK;QACH,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.test.d.ts","sourceRoot":"","sources":["../src/discovery.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { DiscoveryClient } from "./discovery.js";
|
|
3
|
+
import { DiscoveryError } from "./errors.js";
|
|
4
|
+
const ISSUER = "https://auth.example.com";
|
|
5
|
+
const DISCOVERY_DOC = {
|
|
6
|
+
issuer: ISSUER,
|
|
7
|
+
jwks_uri: `${ISSUER}/jwks`,
|
|
8
|
+
authorization_endpoint: `${ISSUER}/authorize`,
|
|
9
|
+
token_endpoint: `${ISSUER}/token`,
|
|
10
|
+
introspection_endpoint: `${ISSUER}/introspect`,
|
|
11
|
+
};
|
|
12
|
+
function mockFetch(response) {
|
|
13
|
+
return vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
|
14
|
+
ok: true,
|
|
15
|
+
status: 200,
|
|
16
|
+
json: async () => DISCOVERY_DOC,
|
|
17
|
+
...response,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
describe("DiscoveryClient", () => {
|
|
21
|
+
afterEach(() => vi.restoreAllMocks());
|
|
22
|
+
it("fetches and returns discovery document", async () => {
|
|
23
|
+
mockFetch({});
|
|
24
|
+
const client = new DiscoveryClient(ISSUER, 10_000);
|
|
25
|
+
const doc = await client.discover();
|
|
26
|
+
expect(doc.jwks_uri).toBe(`${ISSUER}/jwks`);
|
|
27
|
+
expect(doc.introspection_endpoint).toBe(`${ISSUER}/introspect`);
|
|
28
|
+
});
|
|
29
|
+
it("caches discovery document on second call (no re-fetch)", async () => {
|
|
30
|
+
const spy = mockFetch({});
|
|
31
|
+
const client = new DiscoveryClient(ISSUER, 10_000);
|
|
32
|
+
await client.discover();
|
|
33
|
+
await client.discover();
|
|
34
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
35
|
+
});
|
|
36
|
+
it("deduplicates concurrent discover() calls", async () => {
|
|
37
|
+
const spy = mockFetch({});
|
|
38
|
+
const client = new DiscoveryClient(ISSUER, 10_000);
|
|
39
|
+
await Promise.all([client.discover(), client.discover(), client.discover()]);
|
|
40
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
41
|
+
});
|
|
42
|
+
it("re-fetches after reset()", async () => {
|
|
43
|
+
const spy = mockFetch({});
|
|
44
|
+
const client = new DiscoveryClient(ISSUER, 10_000);
|
|
45
|
+
await client.discover();
|
|
46
|
+
client.reset();
|
|
47
|
+
await client.discover();
|
|
48
|
+
expect(spy).toHaveBeenCalledTimes(2);
|
|
49
|
+
});
|
|
50
|
+
it("throws DiscoveryError on non-OK response", async () => {
|
|
51
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue({ ok: false, status: 500 });
|
|
52
|
+
const client = new DiscoveryClient(ISSUER, 10_000);
|
|
53
|
+
await expect(client.discover()).rejects.toBeInstanceOf(DiscoveryError);
|
|
54
|
+
});
|
|
55
|
+
it("throws DiscoveryError on network failure", async () => {
|
|
56
|
+
vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("ECONNREFUSED"));
|
|
57
|
+
const client = new DiscoveryClient(ISSUER, 10_000);
|
|
58
|
+
await expect(client.discover()).rejects.toBeInstanceOf(DiscoveryError);
|
|
59
|
+
});
|
|
60
|
+
it("throws DiscoveryError when jwks_uri is missing from document", async () => {
|
|
61
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
|
62
|
+
ok: true,
|
|
63
|
+
json: async () => ({ issuer: ISSUER }),
|
|
64
|
+
});
|
|
65
|
+
const client = new DiscoveryClient(ISSUER, 10_000);
|
|
66
|
+
await expect(client.discover()).rejects.toBeInstanceOf(DiscoveryError);
|
|
67
|
+
});
|
|
68
|
+
it("throws DiscoveryError on invalid JSON response", async () => {
|
|
69
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
|
70
|
+
ok: true,
|
|
71
|
+
json: async () => { throw new Error("invalid json"); },
|
|
72
|
+
});
|
|
73
|
+
const client = new DiscoveryClient(ISSUER, 10_000);
|
|
74
|
+
await expect(client.discover()).rejects.toBeInstanceOf(DiscoveryError);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
//# sourceMappingURL=discovery.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.test.js","sourceRoot":"","sources":["../src/discovery.test.ts"],"names":[],"mappings":"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"}
|