@hearth-auth/sdk 0.0.1 → 1.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 (83) hide show
  1. package/dist/admin.d.ts +43 -0
  2. package/dist/admin.js +126 -0
  3. package/dist/admin.js.map +1 -0
  4. package/dist/browser-auth.d.ts +32 -0
  5. package/dist/browser-auth.js +99 -0
  6. package/dist/browser-auth.js.map +1 -0
  7. package/dist/claims.d.ts +86 -0
  8. package/dist/claims.js +137 -0
  9. package/dist/claims.js.map +1 -0
  10. package/dist/client.d.ts +77 -0
  11. package/dist/client.js +190 -0
  12. package/dist/client.js.map +1 -0
  13. package/dist/errors.d.ts +114 -0
  14. package/{src/errors.ts → dist/errors.js} +83 -97
  15. package/dist/errors.js.map +1 -0
  16. package/dist/hearth-client.d.ts +133 -0
  17. package/dist/hearth-client.js +192 -0
  18. package/dist/hearth-client.js.map +1 -0
  19. package/dist/hearth.d.ts +105 -0
  20. package/dist/hearth.js +109 -0
  21. package/dist/hearth.js.map +1 -0
  22. package/dist/index.d.ts +23 -0
  23. package/dist/index.js +22 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/introspection-client.d.ts +59 -0
  26. package/dist/introspection-client.js +36 -0
  27. package/dist/introspection-client.js.map +1 -0
  28. package/dist/jwks-client.d.ts +28 -0
  29. package/dist/jwks-client.js +28 -0
  30. package/dist/jwks-client.js.map +1 -0
  31. package/dist/middleware.d.ts +38 -0
  32. package/dist/middleware.js +51 -0
  33. package/dist/middleware.js.map +1 -0
  34. package/dist/pkce.d.ts +64 -0
  35. package/dist/pkce.js +64 -0
  36. package/dist/pkce.js.map +1 -0
  37. package/dist/react.d.ts +32 -0
  38. package/dist/react.js +41 -0
  39. package/dist/react.js.map +1 -0
  40. package/dist/session-version-cache.d.ts +50 -0
  41. package/dist/session-version-cache.js +129 -0
  42. package/dist/session-version-cache.js.map +1 -0
  43. package/dist/types.d.ts +168 -0
  44. package/dist/types.js +2 -0
  45. package/dist/types.js.map +1 -0
  46. package/package.json +13 -4
  47. package/CHANGELOG.md +0 -12
  48. package/src/admin.ts +0 -157
  49. package/src/browser-auth.ts +0 -130
  50. package/src/claims.ts +0 -180
  51. package/src/client.ts +0 -251
  52. package/src/generated/google/api/annotations_pb.ts +0 -44
  53. package/src/generated/google/api/http_pb.ts +0 -467
  54. package/src/generated/hearth/authz/v1/authz_pb.ts +0 -593
  55. package/src/generated/hearth/cluster/v1/raft_pb.ts +0 -183
  56. package/src/generated/hearth/events/v1/audit_pb.ts +0 -886
  57. package/src/generated/hearth/identity/v1/identity_pb.ts +0 -1673
  58. package/src/generated/hearth/identity/v1/oauth_pb.ts +0 -1138
  59. package/src/generated/hearth/rbac/v1/rbac_pb.ts +0 -2000
  60. package/src/hearth-client.ts +0 -288
  61. package/src/hearth.ts +0 -224
  62. package/src/index.ts +0 -106
  63. package/src/introspection-client.ts +0 -83
  64. package/src/jwks-client.ts +0 -45
  65. package/src/middleware.ts +0 -82
  66. package/src/pkce.ts +0 -129
  67. package/src/react.tsx +0 -57
  68. package/src/session-version-cache.ts +0 -167
  69. package/src/types.ts +0 -188
  70. package/tests/admin-crud.test.ts +0 -97
  71. package/tests/auth-flow.test.ts +0 -75
  72. package/tests/authorize.test.ts +0 -386
  73. package/tests/claims.test.ts +0 -159
  74. package/tests/hasPermission.test.ts +0 -152
  75. package/tests/hearth-client.test.ts +0 -243
  76. package/tests/helpers.ts +0 -90
  77. package/tests/jwks.test.ts +0 -62
  78. package/tests/pkce.test.ts +0 -210
  79. package/tests/react-useHasPermission.test.tsx +0 -92
  80. package/tests/required-action.test.ts +0 -276
  81. package/tests/session-version.test.ts +0 -391
  82. package/tsconfig.json +0 -16
  83. package/vitest.config.ts +0 -8
@@ -1,288 +0,0 @@
1
- import {
2
- AuthorizationModeMismatchError,
3
- ConfigurationError,
4
- DiscoveryError,
5
- } from "./errors.js";
6
- import { JwksClient } from "./jwks-client.js";
7
- import {
8
- IntrospectionClient,
9
- type IntrospectionResult,
10
- } from "./introspection-client.js";
11
- import type { AccessTokenAuthorizationMode, AuthorizePermissionOptions } from "./types.js";
12
-
13
- /** Configuration for {@link HearthClient}. */
14
- export interface HearthClientConfig {
15
- /**
16
- * Root URL of the Hearth instance, e.g. `https://auth.example.com`.
17
- * Required. Must be a valid HTTPS URL.
18
- */
19
- issuerUrl: string;
20
- /**
21
- * OAuth 2.0 client ID.
22
- * Required for flows that need a client identity (e.g. introspection).
23
- */
24
- clientId?: string;
25
- /**
26
- * OAuth 2.0 client secret.
27
- * Required for confidential client flows (e.g. introspection).
28
- */
29
- clientSecret?: string;
30
- /**
31
- * Override JWKS cache TTL in milliseconds.
32
- * Default: respect `Cache-Control: max-age` from the JWKS endpoint,
33
- * falling back to 5 minutes.
34
- */
35
- jwksTtl?: number;
36
- /**
37
- * Override the introspection endpoint URL discovered via OIDC discovery.
38
- * When absent, the URL is taken from `introspection_endpoint` in the
39
- * OIDC discovery document.
40
- */
41
- introspectionEndpoint?: string;
42
- /**
43
- * Timeout for all outbound HTTP calls in milliseconds.
44
- * Default: 10 000 (10 seconds).
45
- */
46
- httpTimeout?: number;
47
- /**
48
- * Realm ID sent as `X-Realm-ID` on realm-scoped requests.
49
- * Required for `authorize()` and the `requirePermission()` middleware in
50
- * `decision` mode.
51
- */
52
- realmId?: string;
53
- /**
54
- * Expected access-token authorization mode for this resource server.
55
- *
56
- * When set, `introspect()` validates the `mode` field echoed in the
57
- * introspection response and throws {@link AuthorizationModeMismatchError}
58
- * if they differ.
59
- */
60
- expectedMode?: AccessTokenAuthorizationMode;
61
- }
62
-
63
- interface OidcConfiguration {
64
- issuer: string;
65
- jwks_uri: string;
66
- introspection_endpoint?: string;
67
- [key: string]: unknown;
68
- }
69
-
70
- /**
71
- * Primary entry point for the Hearth Node.js SDK.
72
- *
73
- * Accepts a single configuration object, auto-discovers all endpoint URLs
74
- * from `{issuerUrl}/.well-known/openid-configuration` on first use, and
75
- * applies `httpTimeout` to every outbound fetch call.
76
- *
77
- * Lower-level access is available via {@link JwksClient} and
78
- * {@link IntrospectionClient}.
79
- */
80
- export class HearthClient {
81
- /** Issuer URL, trailing slash removed. */
82
- readonly issuerUrl: string;
83
- readonly clientId: string | undefined;
84
- readonly clientSecret: string | undefined;
85
- readonly jwksTtl: number | undefined;
86
- readonly introspectionEndpointOverride: string | undefined;
87
- /** HTTP timeout in milliseconds applied to all outbound fetch calls. */
88
- readonly httpTimeout: number;
89
- /** Realm ID for realm-scoped endpoints (e.g. `/oauth/authorize`). */
90
- readonly realmId: string | undefined;
91
- /** Expected authorization mode; validated on `introspect()` when present. */
92
- readonly expectedMode: AccessTokenAuthorizationMode | undefined;
93
-
94
- private _discovery: OidcConfiguration | null = null;
95
- private _jwksClient: JwksClient | null = null;
96
- private _introspectionClient: IntrospectionClient | null = null;
97
-
98
- constructor(config: HearthClientConfig) {
99
- if (!config.issuerUrl) {
100
- throw new ConfigurationError("issuerUrl is required");
101
- }
102
- try {
103
- new URL(config.issuerUrl);
104
- } catch {
105
- throw new ConfigurationError(
106
- `issuerUrl "${config.issuerUrl}" is not a valid URL`,
107
- );
108
- }
109
-
110
- this.issuerUrl = config.issuerUrl.replace(/\/$/, "");
111
- this.clientId = config.clientId;
112
- this.clientSecret = config.clientSecret;
113
- this.jwksTtl = config.jwksTtl;
114
- this.introspectionEndpointOverride = config.introspectionEndpoint;
115
- this.httpTimeout = config.httpTimeout ?? 10_000;
116
- this.realmId = config.realmId;
117
- this.expectedMode = config.expectedMode;
118
- }
119
-
120
- /**
121
- * Fetches and caches the OIDC discovery document from
122
- * `{issuerUrl}/.well-known/openid-configuration`.
123
- *
124
- * Throws {@link DiscoveryError} when the endpoint is unreachable,
125
- * returns a non-2xx status, or returns invalid JSON.
126
- */
127
- async discover(): Promise<OidcConfiguration> {
128
- if (this._discovery) return this._discovery;
129
-
130
- const url = `${this.issuerUrl}/.well-known/openid-configuration`;
131
- let resp: Response;
132
- try {
133
- resp = await fetch(url, {
134
- signal: AbortSignal.timeout(this.httpTimeout),
135
- });
136
- } catch (err) {
137
- throw new DiscoveryError(
138
- `OIDC discovery endpoint unreachable: ${url}`,
139
- { cause: err },
140
- );
141
- }
142
-
143
- if (!resp.ok) {
144
- throw new DiscoveryError(
145
- `OIDC discovery returned HTTP ${resp.status}`,
146
- );
147
- }
148
-
149
- let doc: OidcConfiguration;
150
- try {
151
- doc = (await resp.json()) as OidcConfiguration;
152
- } catch (err) {
153
- throw new DiscoveryError(`OIDC discovery returned invalid JSON`, {
154
- cause: err,
155
- });
156
- }
157
-
158
- if (!doc.jwks_uri) {
159
- throw new DiscoveryError(
160
- "OIDC discovery document is missing required field: jwks_uri",
161
- );
162
- }
163
-
164
- this._discovery = doc;
165
- return doc;
166
- }
167
-
168
- /**
169
- * Returns a {@link JwksClient} bound to the `jwks_uri` discovered from
170
- * the OIDC configuration. The client is created once and reused.
171
- */
172
- async jwksClient(): Promise<JwksClient> {
173
- if (this._jwksClient) return this._jwksClient;
174
- const doc = await this.discover();
175
- this._jwksClient = new JwksClient({
176
- jwksUri: doc.jwks_uri,
177
- ttl: this.jwksTtl,
178
- httpTimeout: this.httpTimeout,
179
- });
180
- return this._jwksClient;
181
- }
182
-
183
- /**
184
- * Returns an {@link IntrospectionClient} bound to the introspection
185
- * endpoint. The endpoint is taken from `introspectionEndpoint` config
186
- * (if provided) or from the OIDC discovery document.
187
- *
188
- * Throws {@link ConfigurationError} when:
189
- * - `clientId` or `clientSecret` are absent (required for introspection)
190
- * - No introspection endpoint is configured or discoverable
191
- */
192
- async introspectionClient(): Promise<IntrospectionClient> {
193
- if (this._introspectionClient) return this._introspectionClient;
194
-
195
- if (!this.clientId || !this.clientSecret) {
196
- throw new ConfigurationError(
197
- "clientId and clientSecret are required for token introspection",
198
- );
199
- }
200
-
201
- const endpoint =
202
- this.introspectionEndpointOverride ??
203
- (await this.discover()).introspection_endpoint;
204
-
205
- if (!endpoint) {
206
- throw new ConfigurationError(
207
- "introspection_endpoint is not present in the OIDC discovery document " +
208
- "and no introspectionEndpoint override was provided in config",
209
- );
210
- }
211
-
212
- this._introspectionClient = new IntrospectionClient({
213
- introspectionEndpoint: endpoint,
214
- clientId: this.clientId,
215
- clientSecret: this.clientSecret,
216
- httpTimeout: this.httpTimeout,
217
- });
218
- return this._introspectionClient;
219
- }
220
-
221
- /**
222
- * Calls `POST {issuerUrl}/oauth/authorize` to get a per-request permission
223
- * decision for the given bearer token (Decision mode, HEA-922).
224
- *
225
- * Requires `realmId` in config. Fail-closed: returns `false` on any network
226
- * or server error so authorization cannot be accidentally granted.
227
- *
228
- * @throws {@link ConfigurationError} when `realmId` is not configured.
229
- */
230
- async authorize(
231
- token: string,
232
- permission: string,
233
- opts?: AuthorizePermissionOptions,
234
- ): Promise<boolean> {
235
- if (!this.realmId) {
236
- throw new ConfigurationError("realmId is required for authorize()");
237
- }
238
- const body: Record<string, string> = { permission };
239
- if (opts?.organizationId) body["organization_id"] = opts.organizationId;
240
- if (opts?.resource) body["resource"] = opts.resource;
241
-
242
- try {
243
- const resp = await fetch(`${this.issuerUrl}/oauth/authorize`, {
244
- method: "POST",
245
- headers: {
246
- "Content-Type": "application/json",
247
- "X-Realm-ID": this.realmId,
248
- Authorization: `Bearer ${token}`,
249
- },
250
- body: JSON.stringify(body),
251
- signal: AbortSignal.timeout(this.httpTimeout),
252
- });
253
- if (!resp.ok) return false;
254
- const data = (await resp.json()) as { allowed?: boolean };
255
- return data.allowed === true;
256
- } catch {
257
- return false; // fail-closed on network/timeout errors
258
- }
259
- }
260
-
261
- /**
262
- * Introspects a token via RFC 7662 and optionally validates the echoed
263
- * `mode` field against `expectedMode` from config.
264
- *
265
- * Throws {@link AuthorizationModeMismatchError} when `expectedMode` is set
266
- * and the server echoes a different mode. This catches misconfigured
267
- * deployments where the resource server and the issuing client disagree on
268
- * the permission delivery strategy.
269
- *
270
- * @throws {@link ConfigurationError} when `clientId`/`clientSecret` are absent.
271
- * @throws {@link AuthorizationModeMismatchError} on mode echo mismatch.
272
- */
273
- async introspect(token: string): Promise<IntrospectionResult> {
274
- const ic = await this.introspectionClient();
275
- const result = await ic.introspect(token);
276
- if (
277
- this.expectedMode !== undefined &&
278
- result.mode !== undefined &&
279
- result.mode !== this.expectedMode
280
- ) {
281
- throw new AuthorizationModeMismatchError(
282
- this.expectedMode,
283
- String(result.mode),
284
- );
285
- }
286
- return result;
287
- }
288
- }
package/src/hearth.ts DELETED
@@ -1,224 +0,0 @@
1
- import { decodeJwt } from "jose";
2
- import { HearthApiClient } from "./client.js";
3
- import { SessionVersionCache } from "./session-version-cache.js";
4
- import type { MePermissionsResponse, SessionVersionConfig } from "./types.js";
5
-
6
- /** Options for creating a {@link HearthFacade} via {@link createHearth}. */
7
- export interface HearthOptions {
8
- /** Base URL of the Hearth server, e.g. `https://hearth.example.com`. */
9
- baseUrl: string;
10
- /** Realm ID to scope all requests to. */
11
- realmId: string;
12
- /**
13
- * Called synchronously on every `hasPermission` / `hasRole` /
14
- * `inGroup` / `inOrg` check. Return `null`/`undefined` when the
15
- * caller is unauthenticated.
16
- */
17
- getToken: () => string | null | undefined;
18
- /**
19
- * Optional session-version cache configuration (RFC HEA-930 § 13).
20
- *
21
- * When `enabled: true` the SDK fetches a session-version snapshot on
22
- * startup and polls the delta feed at `pollIntervalMs` intervals.
23
- * Every `hasPermission` / `hasRole` / `inGroup` / `inOrg` call then
24
- * validates the token's `sv` claim against the local cache — no
25
- * per-request network hop required.
26
- *
27
- * Tokens without an `sv` claim pass through unchanged (backward compat).
28
- */
29
- sessionVersions?: SessionVersionConfig;
30
- }
31
-
32
- /**
33
- * Minimum HTTP surface exposed by the facade.
34
- *
35
- * For the full API (auth code flow, admin, JWKS, etc.) construct a
36
- * {@link HearthClient} directly.
37
- */
38
- export interface HearthHttpClient {
39
- /**
40
- * Calls `GET /v1/me/permissions` and returns the freshly-resolved
41
- * RBAC claim set for the current bearer token.
42
- */
43
- permissions(): Promise<MePermissionsResponse>;
44
- }
45
-
46
- /**
47
- * RBAC claim-oriented facade over the Hearth SDK.
48
- *
49
- * When `sessionVersions` is not configured all boolean predicates are
50
- * synchronous, lock-free, and decode the JWT returned by `getToken()` on
51
- * every call. No network traffic, no cache. When the token is absent or
52
- * malformed every predicate returns `false`.
53
- *
54
- * When `sessionVersions.enabled` is `true`, the predicates additionally
55
- * validate the `sv` claim and may throw {@link SessionVersionRevokedError}
56
- * or {@link SessionVersionCacheStaleError} (see RFC HEA-930 § 8).
57
- */
58
- export interface HearthFacade {
59
- /**
60
- * Returns `true` iff the JWT `permissions` claim contains `permission`.
61
- *
62
- * May throw {@link SessionVersionRevokedError} or
63
- * {@link SessionVersionCacheStaleError} when session-version tracking
64
- * is enabled and the token's `sv` claim fails validation.
65
- */
66
- hasPermission(permission: string): boolean;
67
- /**
68
- * Returns `true` iff the JWT `roles` claim contains `role`.
69
- *
70
- * Same session-version throw semantics as {@link hasPermission}.
71
- */
72
- hasRole(role: string): boolean;
73
- /**
74
- * Returns `true` iff the JWT `groups` claim contains `group`.
75
- *
76
- * Same session-version throw semantics as {@link hasPermission}.
77
- */
78
- inGroup(group: string): boolean;
79
- /**
80
- * Returns `true` iff the JWT `oid` claim equals `org`.
81
- *
82
- * Same session-version throw semantics as {@link hasPermission}.
83
- */
84
- inOrg(org: string): boolean;
85
- /**
86
- * Returns the age of the session-version cache in milliseconds.
87
- *
88
- * Returns `Infinity` when session-version tracking is not configured or
89
- * the cache has never been successfully seeded. Use this in health-check
90
- * endpoints to confirm the cache is fresh before accepting requests.
91
- */
92
- sessionVersionCacheAge(): number;
93
- /**
94
- * Stops the background session-version poll loop.
95
- *
96
- * Call this when disposing the facade in long-running Node.js services
97
- * to avoid keeping the event loop alive.
98
- */
99
- stop(): void;
100
- /** Narrow HTTP surface for live RBAC resolution. */
101
- client: HearthHttpClient;
102
- }
103
-
104
- interface RbacJwtClaims {
105
- permissions?: unknown;
106
- roles?: unknown;
107
- groups?: unknown;
108
- oid?: unknown;
109
- /** Session version — `u64` emitted when session_version.enabled=true. */
110
- sv?: unknown;
111
- /** Session ID — present on all session-bearing access tokens. */
112
- sid?: unknown;
113
- }
114
-
115
- /**
116
- * Decode the middle JWT segment using `jose.decodeJwt`. Returns `null`
117
- * when the token is missing, malformed, or cannot be parsed as JSON.
118
- * Signature is NOT verified — the app trusts its own token.
119
- */
120
- function safeDecode(token: string | null | undefined): RbacJwtClaims | null {
121
- if (!token || typeof token !== "string") return null;
122
- try {
123
- return decodeJwt(token) as RbacJwtClaims;
124
- } catch {
125
- return null;
126
- }
127
- }
128
-
129
- function arrayContains(claim: unknown, value: string): boolean {
130
- return Array.isArray(claim) && claim.includes(value);
131
- }
132
-
133
- /** Extract the `sv` claim as `bigint`, or `undefined` if absent/non-numeric. */
134
- function extractSv(c: RbacJwtClaims): bigint | undefined {
135
- if (c.sv === undefined || c.sv === null) return undefined;
136
- if (typeof c.sv === "number") return BigInt(Math.trunc(c.sv));
137
- if (typeof c.sv === "bigint") return c.sv;
138
- return undefined;
139
- }
140
-
141
- /** Extract the `sid` claim as `string`, or `undefined` if absent. */
142
- function extractSid(c: RbacJwtClaims): string | undefined {
143
- return typeof c.sid === "string" ? c.sid : undefined;
144
- }
145
-
146
- /**
147
- * Create a {@link HearthFacade} over the RBAC claim set embedded in the
148
- * JWT returned by `opts.getToken()`.
149
- *
150
- * When `opts.sessionVersions.enabled` is `true` the facade additionally
151
- * starts a background session-version poll loop. Call `facade.stop()` to
152
- * tear it down.
153
- */
154
- export function createHearth(opts: HearthOptions): HearthFacade {
155
- const http = new HearthApiClient({
156
- baseUrl: opts.baseUrl,
157
- realmId: opts.realmId,
158
- });
159
-
160
- let svCache: SessionVersionCache | null = null;
161
- if (opts.sessionVersions?.enabled) {
162
- svCache = new SessionVersionCache(
163
- opts.baseUrl,
164
- opts.realmId,
165
- opts.sessionVersions,
166
- );
167
- svCache.start();
168
- }
169
-
170
- function claims(): RbacJwtClaims | null {
171
- return safeDecode(opts.getToken());
172
- }
173
-
174
- /** Runs the sv check; throws on revoked or stale. No-op when sv absent. */
175
- function assertSv(c: RbacJwtClaims): void {
176
- if (svCache !== null) {
177
- svCache.validateSv(extractSv(c), extractSid(c));
178
- }
179
- }
180
-
181
- return {
182
- hasPermission(permission: string): boolean {
183
- const c = claims();
184
- if (c === null) return false;
185
- assertSv(c);
186
- return arrayContains(c.permissions, permission);
187
- },
188
- hasRole(role: string): boolean {
189
- const c = claims();
190
- if (c === null) return false;
191
- assertSv(c);
192
- return arrayContains(c.roles, role);
193
- },
194
- inGroup(group: string): boolean {
195
- const c = claims();
196
- if (c === null) return false;
197
- assertSv(c);
198
- return arrayContains(c.groups, group);
199
- },
200
- inOrg(org: string): boolean {
201
- const c = claims();
202
- if (c === null) return false;
203
- assertSv(c);
204
- return typeof c.oid === "string" && c.oid === org;
205
- },
206
- sessionVersionCacheAge(): number {
207
- return svCache?.age() ?? Number.POSITIVE_INFINITY;
208
- },
209
- stop(): void {
210
- svCache?.stop();
211
- },
212
- client: {
213
- permissions(): Promise<MePermissionsResponse> {
214
- const token = opts.getToken();
215
- if (!token) {
216
- return Promise.reject(
217
- new Error("getToken() returned no token; cannot call permissions()"),
218
- );
219
- }
220
- return http.permissions(token);
221
- },
222
- },
223
- };
224
- }
package/src/index.ts DELETED
@@ -1,106 +0,0 @@
1
- // Primary entry point — recommended for all new integrations.
2
- export { HearthClient } from "./hearth-client.js";
3
-
4
- // PKCE browser utilities (RFC 7636).
5
- export {
6
- generateCodeVerifier,
7
- generateCodeChallenge,
8
- buildAuthorizationUrl,
9
- startLogin,
10
- } from "./pkce.js";
11
- export type {
12
- BuildAuthorizationUrlOptions,
13
- AuthorizationUrlResult,
14
- StartLoginOptions,
15
- StartLoginResult,
16
- } from "./pkce.js";
17
- export type { HearthClientConfig } from "./hearth-client.js";
18
-
19
- // Lower-level primitives (JWKS and introspection).
20
- export { JwksClient } from "./jwks-client.js";
21
- export type { JwksClientConfig } from "./jwks-client.js";
22
- export { IntrospectionClient } from "./introspection-client.js";
23
- export type {
24
- IntrospectionClientConfig,
25
- IntrospectionResult,
26
- } from "./introspection-client.js";
27
-
28
- // Error types (spec §5).
29
- export {
30
- AuthorizationModeMismatchError,
31
- ConfigurationError,
32
- DiscoveryError,
33
- HearthSdkError,
34
- IntrospectionError,
35
- JWKSFetchError,
36
- RequiredActionError,
37
- SessionVersionCacheStaleError,
38
- SessionVersionRevokedError,
39
- TokenAudienceError,
40
- TokenExpiredError,
41
- TokenInvalidError,
42
- TokenIssuerError,
43
- TokenNotYetValidError,
44
- } from "./errors.js";
45
-
46
- // Mode-aware middleware (HEA-923).
47
- export { requirePermission } from "./middleware.js";
48
- export type { PermissionChecker, RequirePermissionOptions } from "./middleware.js";
49
-
50
- // Claims API (spec §4).
51
- export { Claims } from "./claims.js";
52
-
53
- // Lower-level API client (kept for backwards-compatibility).
54
- export { HearthApiClient, HearthError } from "./client.js";
55
- export type { HearthApiClientConfig, HandleCallbackParams } from "./client.js";
56
- export { AdminClient } from "./admin.js";
57
- export { createHearth } from "./hearth.js";
58
- export type {
59
- HearthFacade,
60
- HearthHttpClient,
61
- HearthOptions,
62
- } from "./hearth.js";
63
- export {
64
- HearthContext,
65
- HearthProvider,
66
- useHasPermission,
67
- useHasRole,
68
- useInGroup,
69
- useInOrg,
70
- } from "./react.js";
71
- export type { HearthProviderProps } from "./react.js";
72
- export type {
73
- AccessTokenAuthorizationMode,
74
- AuthorizeParams,
75
- AuthorizePermissionOptions,
76
- AuthorizeResponse,
77
- BootstrapResponse,
78
- CreateRealmParams,
79
- CreateUserParams,
80
- JwksDocument,
81
- JsonWebKey,
82
- MePermissionsResponse,
83
- OAuthClient,
84
- PageResponse,
85
- RegisterClientParams,
86
- Realm,
87
- SessionVersionConfig,
88
- TokenExchangeParams,
89
- TokenResponse,
90
- UpdateRealmParams,
91
- UpdateUserParams,
92
- User,
93
- UserInfoResponse,
94
- } from "./types.js";
95
- export { SessionVersionCache } from "./session-version-cache.js";
96
-
97
- // Browser auth: token store + PKCE login facade for SPAs.
98
- export {
99
- getAccessToken,
100
- getRefreshToken,
101
- getIdToken,
102
- isAuthenticated,
103
- clearTokens,
104
- createHearthAuth,
105
- } from "./browser-auth.js";
106
- export type { AuthConfig, HearthBrowserAuth } from "./browser-auth.js";
@@ -1,83 +0,0 @@
1
- /** RFC 7662 §2.2 — result of a token introspection request. */
2
- export interface IntrospectionResult {
3
- /** Whether the token is currently active. */
4
- active: boolean;
5
- /** Subject identifier (when active). */
6
- sub?: string;
7
- /** Expiration time as Unix seconds (when active). */
8
- exp?: number;
9
- /** Issued-at time as Unix seconds (when active). */
10
- iat?: number;
11
- /** Issuer identifier (when active). */
12
- iss?: string;
13
- /** Intended audience (when active). */
14
- aud?: string | string[];
15
- /** Space-separated scope string (when active and present). */
16
- scope?: string;
17
- /** OAuth client that requested the token (when active and present). */
18
- client_id?: string;
19
- /**
20
- * Access-token authorization mode echoed from the issuing client config.
21
- * Present only when the Hearth server is HEA-922+.
22
- * Values: `"embedded"` | `"introspection"` | `"decision"`.
23
- */
24
- mode?: string;
25
- /** Live permission strings (present in introspection/decision mode). */
26
- permissions?: string[];
27
- /** Live role names (present in introspection/decision mode). */
28
- roles?: string[];
29
- /** Live group slugs (present in introspection/decision mode). */
30
- groups?: string[];
31
- /** All non-standard claims. */
32
- [key: string]: unknown;
33
- }
34
-
35
- /** Configuration for {@link IntrospectionClient}. */
36
- export interface IntrospectionClientConfig {
37
- /** RFC 7662 introspection endpoint URL. */
38
- introspectionEndpoint: string;
39
- /** Client ID used for HTTP Basic authentication. */
40
- clientId: string;
41
- /** Client secret used for HTTP Basic authentication. */
42
- clientSecret: string;
43
- /** Timeout for outbound HTTP calls in milliseconds. Default: 10 000. */
44
- httpTimeout?: number;
45
- }
46
-
47
- /**
48
- * Low-level RFC 7662 token introspection client.
49
- *
50
- * Results are never cached — per RFC 7662 §2.1, token state can change
51
- * at any time. Full error taxonomy will be added in §3.
52
- */
53
- export class IntrospectionClient {
54
- private readonly endpoint: string;
55
- private readonly clientId: string;
56
- private readonly clientSecret: string;
57
- readonly httpTimeout: number;
58
-
59
- constructor(config: IntrospectionClientConfig) {
60
- this.endpoint = config.introspectionEndpoint;
61
- this.clientId = config.clientId;
62
- this.clientSecret = config.clientSecret;
63
- this.httpTimeout = config.httpTimeout ?? 10_000;
64
- }
65
-
66
- /** Introspect a token. Never cached per RFC 7662 §2.1. */
67
- async introspect(token: string): Promise<IntrospectionResult> {
68
- const credentials = btoa(`${this.clientId}:${this.clientSecret}`);
69
- const resp = await fetch(this.endpoint, {
70
- method: "POST",
71
- headers: {
72
- Authorization: `Basic ${credentials}`,
73
- "Content-Type": "application/x-www-form-urlencoded",
74
- },
75
- body: new URLSearchParams({ token }),
76
- signal: AbortSignal.timeout(this.httpTimeout),
77
- });
78
- if (!resp.ok) {
79
- throw new Error(`Introspection endpoint returned HTTP ${resp.status}`);
80
- }
81
- return resp.json() as Promise<IntrospectionResult>;
82
- }
83
- }