@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,45 +0,0 @@
1
- import type { JsonWebKey } from "./types.js";
2
-
3
- /** Configuration for {@link JwksClient}. */
4
- export interface JwksClientConfig {
5
- /** URL of the JWKS endpoint (e.g. from OIDC discovery `jwks_uri`). */
6
- jwksUri: string;
7
- /**
8
- * Override cache TTL in milliseconds.
9
- * When absent, the client respects `Cache-Control: max-age` from the JWKS
10
- * response and falls back to 5 minutes.
11
- */
12
- ttl?: number;
13
- /** Timeout for outbound HTTP calls in milliseconds. Default: 10 000. */
14
- httpTimeout?: number;
15
- }
16
-
17
- /**
18
- * Low-level JWKS fetcher.
19
- *
20
- * Fetches the JSON Web Key Set from the configured endpoint.
21
- * Full caching and rotation logic will be added in §2.
22
- */
23
- export class JwksClient {
24
- private readonly jwksUri: string;
25
- readonly ttl: number | undefined;
26
- readonly httpTimeout: number;
27
-
28
- constructor(config: JwksClientConfig) {
29
- this.jwksUri = config.jwksUri;
30
- this.ttl = config.ttl;
31
- this.httpTimeout = config.httpTimeout ?? 10_000;
32
- }
33
-
34
- /** Fetch the current JWKS keys from the endpoint. */
35
- async fetchKeys(): Promise<JsonWebKey[]> {
36
- const resp = await fetch(this.jwksUri, {
37
- signal: AbortSignal.timeout(this.httpTimeout),
38
- });
39
- if (!resp.ok) {
40
- throw new Error(`JWKS fetch failed with HTTP ${resp.status}`);
41
- }
42
- const doc = (await resp.json()) as { keys: JsonWebKey[] };
43
- return doc.keys;
44
- }
45
- }
package/src/middleware.ts DELETED
@@ -1,82 +0,0 @@
1
- import { decodeJwt } from "jose";
2
- import { AuthorizationModeMismatchError } from "./errors.js";
3
- import type { HearthClient } from "./hearth-client.js";
4
- import type { AccessTokenAuthorizationMode, AuthorizePermissionOptions } from "./types.js";
5
-
6
- /** Options for {@link requirePermission}. */
7
- export interface RequirePermissionOptions extends AuthorizePermissionOptions {
8
- /**
9
- * Which permission delivery mode the resource server expects.
10
- *
11
- * MUST be set explicitly — the middleware MUST NOT auto-detect the mode from
12
- * JWT claim presence. Absence of a `permissions` claim in the token does not
13
- * change behavior (per HEA-923 design constraint).
14
- */
15
- mode: AccessTokenAuthorizationMode;
16
- /** HearthClient instance used for network calls in decision/introspection modes. */
17
- client: HearthClient;
18
- }
19
-
20
- /**
21
- * A synchronous-or-async gate that returns `true` iff the token holder has
22
- * the given permission under the configured mode.
23
- */
24
- export type PermissionChecker = (token: string) => Promise<boolean>;
25
-
26
- /**
27
- * Returns a mode-aware permission checker for the given `permission`.
28
- *
29
- * Behaviour by mode:
30
- * - **embedded** — decodes the JWT locally and checks the `permissions` claim.
31
- * No network traffic. Returns `false` when the claim is absent; DOES NOT
32
- * fall back to network (design constraint: absence of claims ≠ switch mode).
33
- * - **decision** — calls `client.authorize(token, permission, opts)` which
34
- * POSTs to `POST /oauth/authorize`. Fail-closed on network/server errors.
35
- * - **introspection** — calls `client.introspectionClient().introspect(token)`,
36
- * validates the echoed `mode` field if present, then checks the returned
37
- * `permissions` array. Throws {@link AuthorizationModeMismatchError} if the
38
- * server echoes a mode that differs from `opts.mode`.
39
- *
40
- * @param permission - The permission string to check (e.g. `"docs.write"`).
41
- * @param opts - Mode, client reference, and optional scoping parameters.
42
- */
43
- export function requirePermission(
44
- permission: string,
45
- opts: RequirePermissionOptions,
46
- ): PermissionChecker {
47
- const { mode, client, organizationId, resource } = opts;
48
-
49
- switch (mode) {
50
- case "embedded":
51
- return async (token: string): Promise<boolean> => {
52
- let claims: Record<string, unknown> | null = null;
53
- try {
54
- claims = decodeJwt(token) as Record<string, unknown>;
55
- } catch {
56
- return false;
57
- }
58
- const perms = claims["permissions"];
59
- return Array.isArray(perms) && perms.includes(permission);
60
- };
61
-
62
- case "decision":
63
- return async (token: string): Promise<boolean> =>
64
- client.authorize(token, permission, { organizationId, resource });
65
-
66
- case "introspection":
67
- return async (token: string): Promise<boolean> => {
68
- const ic = await client.introspectionClient();
69
- const result = await ic.introspect(token);
70
-
71
- // Validate mode echo when present — catches misconfigured deployments.
72
- if (result.mode !== undefined && result.mode !== "introspection") {
73
- throw new AuthorizationModeMismatchError("introspection", String(result.mode));
74
- }
75
-
76
- if (!result.active) return false;
77
- return (
78
- Array.isArray(result.permissions) && result.permissions.includes(permission)
79
- );
80
- };
81
- }
82
- }
package/src/pkce.ts DELETED
@@ -1,129 +0,0 @@
1
- /** Minimal interface required by {@link startLogin} — satisfied by {@link HearthApiClient}. */
2
- interface DiscoverySource {
3
- discovery(): Promise<Record<string, unknown>>;
4
- }
5
-
6
- /** Generate a cryptographically random RFC 7636 code verifier (256-bit / 32 bytes). */
7
- export function generateCodeVerifier(): string {
8
- const bytes = new Uint8Array(32);
9
- crypto.getRandomValues(bytes);
10
- return base64urlEncode(bytes);
11
- }
12
-
13
- /** Derive the S256 code challenge from a verifier (RFC 7636 §4.2). */
14
- export async function generateCodeChallenge(verifier: string): Promise<string> {
15
- const data = new TextEncoder().encode(verifier);
16
- const hash = await crypto.subtle.digest("SHA-256", data);
17
- return base64urlEncode(new Uint8Array(hash));
18
- }
19
-
20
- /** Options for {@link buildAuthorizationUrl}. */
21
- export interface BuildAuthorizationUrlOptions {
22
- /** OIDC `authorization_endpoint` from the discovery document. */
23
- authorizationEndpoint: string;
24
- /** OAuth 2.0 client ID. */
25
- clientId: string;
26
- /** Redirect URI registered for this client. */
27
- redirectUri: string;
28
- /** Base64url-encoded S256 code challenge (from {@link generateCodeChallenge}). */
29
- codeChallenge: string;
30
- /** OAuth 2.0 scope string. Default: `"openid profile email"`. */
31
- scope?: string;
32
- /** CSRF state token. Auto-generated (16 random bytes) when absent. */
33
- state?: string;
34
- }
35
-
36
- /** Return value of {@link buildAuthorizationUrl}. */
37
- export interface AuthorizationUrlResult {
38
- /** Full authorization redirect URL to navigate the browser to. */
39
- url: string;
40
- /** State value embedded in the URL — persist for CSRF validation in the callback. */
41
- state: string;
42
- }
43
-
44
- /** Build the full authorization redirect URL for an RFC 7636 PKCE flow. */
45
- export function buildAuthorizationUrl(
46
- opts: BuildAuthorizationUrlOptions,
47
- ): AuthorizationUrlResult {
48
- const state = opts.state ?? generateState();
49
- const params = new URLSearchParams({
50
- response_type: "code",
51
- client_id: opts.clientId,
52
- redirect_uri: opts.redirectUri,
53
- code_challenge: opts.codeChallenge,
54
- code_challenge_method: "S256",
55
- scope: opts.scope ?? "openid profile email",
56
- state,
57
- });
58
- return { url: `${opts.authorizationEndpoint}?${params.toString()}`, state };
59
- }
60
-
61
- /** Options for {@link startLogin}. */
62
- export interface StartLoginOptions {
63
- /** OAuth 2.0 client ID. */
64
- clientId: string;
65
- /** Redirect URI registered for this client. */
66
- redirectUri: string;
67
- /** OAuth 2.0 scope string. Default: `"openid profile email"`. */
68
- scope?: string;
69
- /** CSRF state token. Auto-generated when absent. */
70
- state?: string;
71
- }
72
-
73
- /** Return value of {@link startLogin}. */
74
- export interface StartLoginResult {
75
- /** Full authorization URL — redirect the browser here to begin login. */
76
- url: string;
77
- /** OAuth 2.0 state value — persist for CSRF validation in the callback. */
78
- state: string;
79
- /**
80
- * RFC 7636 code verifier — persist (e.g. `sessionStorage`) and pass as
81
- * `codeVerifier` to `handleCallback()` during the token exchange step.
82
- */
83
- codeVerifier: string;
84
- }
85
-
86
- /**
87
- * One-shot PKCE login initiation: discovers the authorization endpoint,
88
- * generates a code verifier/challenge, and builds the redirect URL.
89
- *
90
- * The caller MUST persist `codeVerifier` and `state` (e.g. in `sessionStorage`)
91
- * before navigating to `url`, and pass them to `handleCallback()` on return.
92
- */
93
- export async function startLogin(
94
- client: DiscoverySource,
95
- opts: StartLoginOptions,
96
- ): Promise<StartLoginResult> {
97
- const doc = await client.discovery();
98
- const authorizationEndpoint = doc["authorization_endpoint"] as string | undefined;
99
- if (!authorizationEndpoint) {
100
- throw new Error(
101
- "startLogin: authorization_endpoint not found in OIDC discovery document",
102
- );
103
- }
104
- const codeVerifier = generateCodeVerifier();
105
- const codeChallenge = await generateCodeChallenge(codeVerifier);
106
- const { url, state } = buildAuthorizationUrl({
107
- authorizationEndpoint,
108
- clientId: opts.clientId,
109
- redirectUri: opts.redirectUri,
110
- codeChallenge,
111
- scope: opts.scope,
112
- state: opts.state,
113
- });
114
- return { url, state, codeVerifier };
115
- }
116
-
117
- function generateState(): string {
118
- const bytes = new Uint8Array(16);
119
- crypto.getRandomValues(bytes);
120
- return base64urlEncode(bytes);
121
- }
122
-
123
- function base64urlEncode(input: Uint8Array): string {
124
- let binary = "";
125
- for (const byte of input) {
126
- binary += String.fromCharCode(byte);
127
- }
128
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
129
- }
package/src/react.tsx DELETED
@@ -1,57 +0,0 @@
1
- import * as React from "react";
2
- import type { HearthFacade } from "./hearth.js";
3
-
4
- /**
5
- * React context carrying a {@link HearthFacade} down the tree.
6
- *
7
- * The default value is `null`; the hooks treat a `null` context as
8
- * unauthenticated and return `false`.
9
- */
10
- export const HearthContext = React.createContext<HearthFacade | null>(null);
11
-
12
- /** Props for {@link HearthProvider}. */
13
- export interface HearthProviderProps {
14
- client: HearthFacade;
15
- children: React.ReactNode;
16
- }
17
-
18
- /**
19
- * Provides a {@link HearthFacade} to descendants via {@link HearthContext}.
20
- *
21
- * Wrap your React tree once with this after calling `createHearth(...)`.
22
- */
23
- export function HearthProvider(props: HearthProviderProps): React.ReactElement {
24
- return React.createElement(
25
- HearthContext.Provider,
26
- { value: props.client },
27
- props.children,
28
- );
29
- }
30
-
31
- /**
32
- * Returns `true` iff the nearest {@link HearthProvider} client reports
33
- * the permission as present in the JWT claim set. Returns `false`
34
- * when no provider is mounted.
35
- */
36
- export function useHasPermission(permission: string): boolean {
37
- const client = React.useContext(HearthContext);
38
- return client !== null && client.hasPermission(permission);
39
- }
40
-
41
- /** Returns `true` iff the JWT `roles` claim contains `role`. */
42
- export function useHasRole(role: string): boolean {
43
- const client = React.useContext(HearthContext);
44
- return client !== null && client.hasRole(role);
45
- }
46
-
47
- /** Returns `true` iff the JWT `groups` claim contains `group`. */
48
- export function useInGroup(group: string): boolean {
49
- const client = React.useContext(HearthContext);
50
- return client !== null && client.inGroup(group);
51
- }
52
-
53
- /** Returns `true` iff the JWT `oid` claim equals `org`. */
54
- export function useInOrg(org: string): boolean {
55
- const client = React.useContext(HearthContext);
56
- return client !== null && client.inOrg(org);
57
- }
@@ -1,167 +0,0 @@
1
- import type { SessionVersionConfig } from "./types.js";
2
- import {
3
- SessionVersionCacheStaleError,
4
- SessionVersionRevokedError,
5
- } from "./errors.js";
6
-
7
- interface SnapshotResponse {
8
- realm: string;
9
- current_seq: number;
10
- versions: Record<string, number>;
11
- }
12
-
13
- interface DeltaEntry {
14
- seq: number;
15
- session_id: string;
16
- min_sv: number;
17
- bumped_at: number;
18
- }
19
-
20
- interface DeltaFeedResponse {
21
- realm: string;
22
- next_seq: number;
23
- deltas: DeltaEntry[];
24
- }
25
-
26
- /**
27
- * Client-side cache of per-session minimum accepted `sv` values.
28
- *
29
- * Polls `GET /oauth/session-versions` at `cfg.pollIntervalMs` intervals and
30
- * applies delta entries to an in-memory `Map<sessionId, bigint>`. Used by
31
- * `createHearth()` to validate the `sv` claim in access tokens without any
32
- * per-request network call.
33
- *
34
- * Background poll errors are swallowed; the cache age then grows and eventually
35
- * trips the stale threshold, triggering fail-closed behaviour (§ 8.1).
36
- */
37
- export class SessionVersionCache {
38
- private readonly baseUrl: string;
39
- private readonly realmId: string;
40
- private readonly cfg: SessionVersionConfig;
41
- private readonly versions = new Map<string, bigint>();
42
- private lastRefreshed = 0;
43
- private seq = 0;
44
- private pollTimer: ReturnType<typeof setTimeout> | null = null;
45
-
46
- constructor(baseUrl: string, realmId: string, cfg: SessionVersionConfig) {
47
- this.baseUrl = baseUrl.replace(/\/$/, "");
48
- this.realmId = realmId;
49
- this.cfg = cfg;
50
- }
51
-
52
- /**
53
- * Kicks off the initial snapshot fetch (async, non-blocking) and starts the
54
- * background poll loop. Call once after construction.
55
- *
56
- * If `staleThresholdMs <= pollIntervalMs` a console warning is emitted.
57
- * Until the first snapshot completes, `age()` returns `Infinity` which
58
- * will trip the stale threshold if `staleThresholdMs` is finite.
59
- */
60
- start(): void {
61
- if (this.cfg.staleThresholdMs <= this.cfg.pollIntervalMs) {
62
- console.warn(
63
- "[hearth] sessionVersions.staleThresholdMs must be > pollIntervalMs " +
64
- `(stale=${this.cfg.staleThresholdMs}ms, poll=${this.cfg.pollIntervalMs}ms). ` +
65
- "Recommended: staleThresholdMs = pollIntervalMs × 3.",
66
- );
67
- }
68
- void this.fetchSnapshot().catch(() => undefined);
69
- this.schedulePoll();
70
- }
71
-
72
- /** Stops the background poll timer. Call when disposing the Hearth facade. */
73
- stop(): void {
74
- if (this.pollTimer !== null) {
75
- clearTimeout(this.pollTimer);
76
- this.pollTimer = null;
77
- }
78
- }
79
-
80
- /** Returns milliseconds since the cache was last successfully refreshed. */
81
- age(): number {
82
- if (this.lastRefreshed === 0) return Number.POSITIVE_INFINITY;
83
- return Date.now() - this.lastRefreshed;
84
- }
85
-
86
- /**
87
- * Validates the `sv` claim against the local cache.
88
- *
89
- * - Absent `sv` or absent `sid` → no-op (backward compat, RFC § 8.2).
90
- * - Cache age > `staleThresholdMs` → throws {@link SessionVersionCacheStaleError}.
91
- * - `sv < minSv` → throws {@link SessionVersionRevokedError}.
92
- *
93
- * When `onStale` is `"introspect"`, callers should catch
94
- * {@link SessionVersionCacheStaleError} and fall back to the introspection
95
- * endpoint, which performs a fresh server-side check.
96
- */
97
- validateSv(sv: bigint | undefined, sessionId: string | undefined): void {
98
- if (sv === undefined || sessionId === undefined) return;
99
-
100
- const ageMs = this.age();
101
- if (ageMs > this.cfg.staleThresholdMs) {
102
- throw new SessionVersionCacheStaleError(
103
- isFinite(ageMs) ? ageMs : -1,
104
- this.cfg.onStale,
105
- );
106
- }
107
-
108
- const minSv = this.versions.get(sessionId) ?? 1n;
109
- if (sv < minSv) {
110
- throw new SessionVersionRevokedError(sessionId, sv, minSv);
111
- }
112
- }
113
-
114
- // ── Private ─────────────────────────────────────────────────────────────────
115
-
116
- private async fetchSnapshot(): Promise<void> {
117
- const url = `${this.baseUrl}/oauth/session-versions/snapshot?realm=${encodeURIComponent(this.realmId)}`;
118
- const resp = await fetch(url, {
119
- headers: { Authorization: `Bearer ${this.cfg.serviceToken}` },
120
- });
121
- if (!resp.ok) {
122
- throw new Error(`SV snapshot fetch failed: HTTP ${resp.status}`);
123
- }
124
- const data = (await resp.json()) as SnapshotResponse;
125
- this.versions.clear();
126
- for (const [sid, minSv] of Object.entries(data.versions)) {
127
- this.versions.set(sid, BigInt(minSv));
128
- }
129
- this.seq = data.current_seq;
130
- this.lastRefreshed = Date.now();
131
- }
132
-
133
- private schedulePoll(): void {
134
- this.pollTimer = setTimeout(() => {
135
- void this.poll()
136
- .catch(() => undefined)
137
- .finally(() => this.schedulePoll());
138
- }, this.cfg.pollIntervalMs);
139
- }
140
-
141
- private async poll(): Promise<void> {
142
- const url =
143
- `${this.baseUrl}/oauth/session-versions?since=${this.seq}` +
144
- `&realm=${encodeURIComponent(this.realmId)}`;
145
- const resp = await fetch(url, {
146
- headers: { Authorization: `Bearer ${this.cfg.serviceToken}` },
147
- });
148
- if (resp.status === 204) {
149
- this.lastRefreshed = Date.now();
150
- return;
151
- }
152
- if (resp.status === 400) {
153
- // Sequence predates retention window — must re-seed from snapshot.
154
- await this.fetchSnapshot();
155
- return;
156
- }
157
- if (!resp.ok) {
158
- throw new Error(`SV delta poll failed: HTTP ${resp.status}`);
159
- }
160
- const data = (await resp.json()) as DeltaFeedResponse;
161
- for (const delta of data.deltas) {
162
- this.versions.set(delta.session_id, BigInt(delta.min_sv));
163
- }
164
- this.seq = data.next_seq;
165
- this.lastRefreshed = Date.now();
166
- }
167
- }
package/src/types.ts DELETED
@@ -1,188 +0,0 @@
1
- /** Response from the dev bootstrap endpoint. */
2
- export interface BootstrapResponse {
3
- realm_id: string;
4
- user_id: string;
5
- access_token: string;
6
- refresh_token: string;
7
- }
8
-
9
- /** Parameters for initiating an authorization code flow. */
10
- export interface AuthorizeParams {
11
- clientId: string;
12
- redirectUri: string;
13
- scope: string;
14
- state: string;
15
- responseType?: string;
16
- userId: string;
17
- codeChallenge?: string;
18
- codeChallengeMethod?: string;
19
- nonce?: string;
20
- }
21
-
22
- /** Response from the authorize endpoint. */
23
- export interface AuthorizeResponse {
24
- code: string;
25
- state: string;
26
- }
27
-
28
- /** Parameters for exchanging an authorization code. */
29
- export interface TokenExchangeParams {
30
- clientId: string;
31
- code: string;
32
- redirectUri: string;
33
- codeVerifier?: string;
34
- }
35
-
36
- /** Response from the token exchange endpoint. */
37
- export interface TokenResponse {
38
- access_token: string;
39
- id_token: string;
40
- token_type: string;
41
- expires_in: number;
42
- refresh_token: string;
43
- }
44
-
45
- /** UserInfo response from the OIDC UserInfo endpoint. */
46
- export interface UserInfoResponse {
47
- sub: string;
48
- name?: string;
49
- email?: string;
50
- email_verified?: boolean;
51
- }
52
-
53
- /** Parameters for creating a user. */
54
- export interface CreateUserParams {
55
- email: string;
56
- displayName: string;
57
- }
58
-
59
- /** User record from the API. */
60
- export interface User {
61
- id: string;
62
- email: string;
63
- display_name: string;
64
- status: string;
65
- created_at?: number;
66
- updated_at?: number;
67
- }
68
-
69
- /** Parameters for updating a user. */
70
- export interface UpdateUserParams {
71
- email?: string;
72
- displayName?: string;
73
- status?: string;
74
- }
75
-
76
- /** Parameters for creating a realm. */
77
- export interface CreateRealmParams {
78
- name: string;
79
- config?: Record<string, unknown>;
80
- }
81
-
82
- /** Realm record from the API. */
83
- export interface Realm {
84
- id: string;
85
- name: string;
86
- status: string;
87
- config: Record<string, unknown> | null;
88
- created_at?: number;
89
- updated_at?: number;
90
- }
91
-
92
- /** Parameters for updating a realm. */
93
- export interface UpdateRealmParams {
94
- name?: string;
95
- status?: string;
96
- config?: Record<string, unknown>;
97
- }
98
-
99
- /** Paginated list response. */
100
- export interface PageResponse<T> {
101
- items: T[];
102
- next_cursor: string | null;
103
- }
104
-
105
- /** Parameters for registering an OAuth client. */
106
- export interface RegisterClientParams {
107
- clientName: string;
108
- redirectUris: string[];
109
- }
110
-
111
- /** OAuth client record from the API. */
112
- export interface OAuthClient {
113
- client_id: string;
114
- client_name: string;
115
- redirect_uris: string[];
116
- grant_types: string[];
117
- created_at?: number;
118
- }
119
-
120
- /** JWKS document containing public keys. */
121
- export interface JwksDocument {
122
- keys: JsonWebKey[];
123
- }
124
-
125
- /** A single JWK entry. */
126
- export interface JsonWebKey {
127
- kty: string;
128
- crv?: string;
129
- x?: string;
130
- kid?: string;
131
- use?: string;
132
- alg?: string;
133
- }
134
-
135
- /**
136
- * Response from `GET /v1/me/permissions`.
137
- *
138
- * Returns the freshly-resolved RBAC claim set for the bearer-token user.
139
- */
140
- export interface MePermissionsResponse {
141
- roles: string[];
142
- groups: string[];
143
- permissions: string[];
144
- scope: string;
145
- }
146
-
147
- /** The three permission delivery modes introduced in HEA-922. */
148
- export type AccessTokenAuthorizationMode = "embedded" | "introspection" | "decision";
149
-
150
- /** Options for a per-request permission decision call to `POST /oauth/authorize`. */
151
- export interface AuthorizePermissionOptions {
152
- /** Constrain the decision to a specific organization. */
153
- organizationId?: string;
154
- /** Constrain the decision to a specific resource. */
155
- resource?: string;
156
- }
157
-
158
- /**
159
- * Configuration for the client-side session-version cache (RFC HEA-930 § 13).
160
- *
161
- * When enabled, the SDK fetches a snapshot of `{sessionId → minSv}` on startup,
162
- * polls `GET /oauth/session-versions` for deltas at `pollIntervalMs` intervals,
163
- * and validates the `sv` claim on every `hasPermission` / `hasRole` / `inGroup`
164
- * / `inOrg` call without any per-request network hop.
165
- */
166
- export interface SessionVersionConfig {
167
- /** Whether session-version validation is enabled. */
168
- enabled: boolean;
169
- /** Delta feed poll interval in milliseconds. Recommended: 5 000. */
170
- pollIntervalMs: number;
171
- /**
172
- * Maximum cache age before the cache is considered stale, in milliseconds.
173
- * MUST be greater than `pollIntervalMs`. Recommended: `pollIntervalMs × 3`.
174
- */
175
- staleThresholdMs: number;
176
- /**
177
- * Action when the cache exceeds `staleThresholdMs`:
178
- * - `"reject"` — throw {@link SessionVersionCacheStaleError} (fail-closed).
179
- * - `"introspect"` — caller should catch {@link SessionVersionCacheStaleError}
180
- * and fall back to the introspection endpoint.
181
- */
182
- onStale: "reject" | "introspect";
183
- /**
184
- * Service-to-service access token with `hearth.sv_feed` scope.
185
- * Required when `enabled` is `true`.
186
- */
187
- serviceToken: string;
188
- }