@hearth-auth/sdk 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -36
- package/dist/browser-auth.d.ts +40 -7
- package/dist/browser-auth.js +93 -58
- package/dist/browser-auth.js.map +1 -1
- package/dist/generated/admin/schema.d.ts +2 -0
- package/dist/hearth-client.d.ts +15 -3
- package/dist/hearth-client.js +14 -6
- package/dist/hearth-client.js.map +1 -1
- package/dist/hearth.d.ts +38 -24
- package/dist/hearth.js +45 -59
- package/dist/hearth.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/jwks-client.d.ts +24 -9
- package/dist/jwks-client.js +28 -7
- package/dist/jwks-client.js.map +1 -1
- package/dist/react.d.ts +6 -6
- package/dist/react.js +41 -14
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,23 +35,30 @@ const client = new HearthClient({
|
|
|
35
35
|
clientId: "<client-id>",
|
|
36
36
|
clientSecret: "<client-secret>", // confidential clients only
|
|
37
37
|
realmId: "<your-realm-id>",
|
|
38
|
+
audience: "https://api.example.com", // expected `aud`; default "hearth"
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
const claims = await client.verifyToken(accessToken); // throws on a bad token
|
|
41
42
|
claims.subject();
|
|
42
43
|
claims.hasPermission("docs.write");
|
|
43
44
|
|
|
44
|
-
// RBAC facade —
|
|
45
|
+
// RBAC facade — verifies the token, then checks its claims
|
|
45
46
|
const hearth = createHearth({
|
|
46
47
|
baseUrl: "https://hearth.example.com",
|
|
47
48
|
realmId: "<your-realm-id>",
|
|
48
|
-
|
|
49
|
+
issuerUrl: "https://hearth.example.com/realms/<realm-slug>",
|
|
50
|
+
getToken: () => currentAccessToken,
|
|
49
51
|
});
|
|
52
|
+
await hearth.hasPermission("docs.write");
|
|
50
53
|
```
|
|
51
54
|
|
|
52
55
|
`HearthClient` reads every endpoint URL from `{issuerUrl}/.well-known/openid-configuration` on first use and caches it. `httpTimeout` (default 10 000 ms) applies to every request it makes. Call `client.invalidateCache()` to drop the cached discovery document, JWKS and introspection client.
|
|
53
56
|
|
|
54
|
-
`createHearth` gives you
|
|
57
|
+
`createHearth` gives you an RBAC facade over the token `getToken()` returns. Each check verifies that token against the realm JWKS (fetched once, then cached) before it reads a claim.
|
|
58
|
+
|
|
59
|
+
### Audience
|
|
60
|
+
|
|
61
|
+
`verifyToken` always checks `aud` (RFC 9068 §4); the check cannot be turned off. The expected audience is the `audience` option, default `"hearth"` (exported as `DEFAULT_AUDIENCE`): the audience Hearth mints when a client names no resource. An API registered as a protected resource sets its resource URI. The client ID is not the audience: only ID tokens are checked against a client ID (OIDC Core). An empty `audience` throws `ConfigurationError`; a token whose `aud` does not contain the expected audience throws `TokenAudienceError`. `JwksClient` takes the same option, and `VerifyOptions.audience` overrides it for one `verify()` call.
|
|
55
62
|
|
|
56
63
|
---
|
|
57
64
|
|
|
@@ -106,69 +113,93 @@ Every token-endpoint failure throws `OAuthFlowError` with `statusCode` and the O
|
|
|
106
113
|
|
|
107
114
|
A single-page app has no server session to hold the verifier. Use `createHearthAuth`, or build the flow from `generateCodeVerifier`, `generateCodeChallenge` and `buildAuthorizationUrl`.
|
|
108
115
|
|
|
116
|
+
```typescript
|
|
117
|
+
import { createHearthAuth, HearthApiClient } from "@hearth-auth/sdk";
|
|
118
|
+
|
|
119
|
+
const auth = createHearthAuth(
|
|
120
|
+
new HearthApiClient({ baseUrl: "https://hearth.example.com", realmId: "<realm-id>" }),
|
|
121
|
+
{
|
|
122
|
+
clientId: "<client-id>",
|
|
123
|
+
redirectUri: "https://app.example.com/callback",
|
|
124
|
+
hearthUrl: "https://hearth.example.com",
|
|
125
|
+
realmSlug: "<realm-slug>",
|
|
126
|
+
// storage: "sessionStorage" (default) | "localStorage" | "memory" | { getItem, setItem, removeItem }
|
|
127
|
+
// storageKeyPrefix: "hearth_" (default)
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
await auth.startLogin();
|
|
132
|
+
// on the callback page: await auth.handleCallback(code, state);
|
|
133
|
+
auth.getAccessToken(); // also getRefreshToken(), getIdToken(), isAuthenticated(), clearTokens()
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The access token stays in memory. The refresh token, ID token and the PKCE verifier and `state` of a login in flight go to the configured `storage`, `sessionStorage` by default (scoped to one tab, cleared when it closes). `"memory"` does not survive the login redirect. At creation the facade removes `hearth_refresh_token` and `hearth_id_token` from `localStorage` unless `localStorage` is the configured store.
|
|
137
|
+
|
|
109
138
|
---
|
|
110
139
|
|
|
111
140
|
## RBAC capabilities
|
|
112
141
|
|
|
113
|
-
|
|
142
|
+
Every predicate verifies the token returned by `getToken()` before it reads a claim: the EdDSA signature against the realm JWKS (fetched from `issuerUrl` once, then cached), plus `exp`, `nbf`, `iat`, `iss` (must equal `issuerUrl`) and `aud` (must contain `audience`, default `"hearth"`). When the token is absent or does not verify, every predicate resolves `false`. With `sessionVersions` enabled, a predicate may reject with `SessionVersionRevokedError` or `SessionVersionCacheStaleError`.
|
|
114
143
|
|
|
115
144
|
```typescript
|
|
116
145
|
const hearth = createHearth({
|
|
117
146
|
baseUrl: "https://hearth.example.com",
|
|
118
147
|
realmId: "<your-realm-id>",
|
|
119
|
-
|
|
148
|
+
issuerUrl: "https://hearth.example.com/realms/<realm-slug>", // required: the realm issuer
|
|
149
|
+
audience: "hearth", // optional; this is the default
|
|
150
|
+
getToken: () => auth.getAccessToken(), // may also return a Promise
|
|
120
151
|
});
|
|
121
152
|
```
|
|
122
153
|
|
|
123
|
-
### `hasPermission(permission: string): boolean
|
|
154
|
+
### `hasPermission(permission: string): Promise<boolean>`
|
|
124
155
|
|
|
125
|
-
|
|
156
|
+
Resolves `true` iff the token verifies and its `permissions` claim contains `permission`. Use this for feature gates and API guards.
|
|
126
157
|
|
|
127
158
|
```typescript
|
|
128
|
-
if (hearth.hasPermission("docs.versions.read")) {
|
|
159
|
+
if (await hearth.hasPermission("docs.versions.read")) {
|
|
129
160
|
renderVersionHistory();
|
|
130
161
|
}
|
|
131
162
|
```
|
|
132
163
|
|
|
133
|
-
### `hasRole(role: string): boolean
|
|
164
|
+
### `hasRole(role: string): Promise<boolean>`
|
|
134
165
|
|
|
135
|
-
|
|
166
|
+
Resolves `true` iff the token verifies and its `roles` claim contains `role`. Useful for UI personalization and coarse-grained access.
|
|
136
167
|
|
|
137
168
|
```typescript
|
|
138
|
-
if (hearth.hasRole("billing-admin")) {
|
|
169
|
+
if (await hearth.hasRole("billing-admin")) {
|
|
139
170
|
renderBillingPanel();
|
|
140
171
|
}
|
|
141
172
|
```
|
|
142
173
|
|
|
143
|
-
### `inGroup(group: string): boolean
|
|
174
|
+
### `inGroup(group: string): Promise<boolean>`
|
|
144
175
|
|
|
145
|
-
|
|
176
|
+
Resolves `true` iff the token verifies and its `groups` claim contains the group slug.
|
|
146
177
|
|
|
147
178
|
```typescript
|
|
148
|
-
if (hearth.inGroup("engineering")) {
|
|
179
|
+
if (await hearth.inGroup("engineering")) {
|
|
149
180
|
renderInternalToolingLink();
|
|
150
181
|
}
|
|
151
182
|
```
|
|
152
183
|
|
|
153
|
-
### `inOrg(org: string): boolean
|
|
184
|
+
### `inOrg(org: string): Promise<boolean>`
|
|
154
185
|
|
|
155
|
-
|
|
186
|
+
Resolves `true` iff the token verifies and its `oid` claim equals the given org ID.
|
|
156
187
|
|
|
157
188
|
```typescript
|
|
158
|
-
if (hearth.inOrg("org_acme")) {
|
|
189
|
+
if (await hearth.inOrg("org_acme")) {
|
|
159
190
|
renderAcmeContent();
|
|
160
191
|
}
|
|
161
192
|
```
|
|
162
193
|
|
|
163
194
|
### `client.permissions(): Promise<MePermissionsResponse>`
|
|
164
195
|
|
|
165
|
-
Calls `GET /v1/me/permissions` and returns the **freshly-resolved** RBAC claim set from the server. Unlike the
|
|
196
|
+
Calls `GET /v1/me/permissions` and returns the **freshly-resolved** RBAC claim set from the server. Unlike the predicates above, this reflects any role/group assignments made since the JWT was issued.
|
|
166
197
|
|
|
167
198
|
```typescript
|
|
168
199
|
const { roles, groups, permissions } = await hearth.client.permissions();
|
|
169
200
|
```
|
|
170
201
|
|
|
171
|
-
Use `client.permissions()` when you need post-issuance accuracy (e.g., after an admin operation). For every other check, prefer the
|
|
202
|
+
Use `client.permissions()` when you need post-issuance accuracy (e.g., after an admin operation). For every other check, prefer the local predicates — once the JWKS is cached they make no network call.
|
|
172
203
|
|
|
173
204
|
---
|
|
174
205
|
|
|
@@ -190,7 +221,8 @@ import {
|
|
|
190
221
|
const hearth = createHearth({
|
|
191
222
|
baseUrl: "https://hearth.example.com",
|
|
192
223
|
realmId: "<your-realm-id>",
|
|
193
|
-
|
|
224
|
+
issuerUrl: "https://hearth.example.com/realms/<realm-slug>",
|
|
225
|
+
getToken: () => auth.getAccessToken(), // auth = createHearthAuth(...)
|
|
194
226
|
});
|
|
195
227
|
|
|
196
228
|
// 2. Mount the provider at the root of your React tree
|
|
@@ -220,7 +252,7 @@ function NavBar() {
|
|
|
220
252
|
}
|
|
221
253
|
```
|
|
222
254
|
|
|
223
|
-
All hooks return `false` when no `HearthProvider` is mounted, making them safe to call in tests without a provider.
|
|
255
|
+
The hooks return `boolean`. Each runs the facade's asynchronous check after every render and returns its last result: `false` until the check resolves, and `false` when it rejects. All hooks return `false` when no `HearthProvider` is mounted, making them safe to call in tests without a provider.
|
|
224
256
|
|
|
225
257
|
---
|
|
226
258
|
|
|
@@ -259,7 +291,8 @@ const delta = await client.svDelta(serviceToken, snap.current_seq, 500); // null
|
|
|
259
291
|
const discovery = await client.discover();
|
|
260
292
|
|
|
261
293
|
// Verify an access token: EdDSA signature against the realm JWKS, then exp,
|
|
262
|
-
// nbf, iss (must equal issuerUrl) and aud (must
|
|
294
|
+
// nbf, iat (not in the future), iss (must equal issuerUrl) and aud (must
|
|
295
|
+
// contain `audience`, default "hearth").
|
|
263
296
|
const claims = await client.verifyToken(accessToken);
|
|
264
297
|
claims.subject(); // sub
|
|
265
298
|
claims.scopes(); // scope split into an array
|
|
@@ -365,13 +398,13 @@ Errors raised by the SDK itself extend `HearthSdkError`:
|
|
|
365
398
|
|
|
366
399
|
| Error | When |
|
|
367
400
|
|---|---|
|
|
368
|
-
| `ConfigurationError` | A required setting is missing (`clientId`, `realmId`, a discovery endpoint) |
|
|
401
|
+
| `ConfigurationError` | A required setting is missing (`clientId`, `realmId`, a discovery endpoint) or `audience` is empty |
|
|
369
402
|
| `DiscoveryError` | The discovery document cannot be fetched or is invalid |
|
|
370
403
|
| `JWKSFetchError` | The JWKS cannot be fetched |
|
|
371
404
|
| `TokenVerificationError` | Base class of every token failure below |
|
|
372
|
-
| `TokenExpiredError`, `TokenNotYetValidError` | `exp` / `nbf` outside the clock-skew window |
|
|
405
|
+
| `TokenExpiredError`, `TokenNotYetValidError` | `exp` / `nbf` or `iat` outside the clock-skew window |
|
|
373
406
|
| `TokenInvalidError` | Bad signature, wrong algorithm, malformed JWT |
|
|
374
|
-
| `TokenIssuerError`, `TokenAudienceError` | `iss` / `aud`
|
|
407
|
+
| `TokenIssuerError`, `TokenAudienceError` | `iss` mismatch / `aud` does not contain the configured `audience` |
|
|
375
408
|
| `IntrospectionError` | The introspection request failed or returned non-JSON |
|
|
376
409
|
| `OAuthFlowError` | A token, userinfo, permissions or session-version request failed (`statusCode`, `errorCode`) |
|
|
377
410
|
| `AuthorizationModeMismatchError` | Introspection echoed a mode other than `expectedMode` |
|
|
@@ -416,7 +449,8 @@ const admin = new AdminClient("http://127.0.0.1:8420", realm_id, access_token);
|
|
|
416
449
|
// HearthClientConfig — constructor argument for HearthClient
|
|
417
450
|
interface HearthClientConfig {
|
|
418
451
|
issuerUrl: string; // e.g. "https://hearth.example.com"; endpoints are discovered from it
|
|
419
|
-
clientId?: string; // needed for login flows, introspection
|
|
452
|
+
clientId?: string; // needed for login flows, introspection
|
|
453
|
+
audience?: string; // expected `aud` on verifyToken; default "hearth"; always checked
|
|
420
454
|
clientSecret?: string; // confidential clients only
|
|
421
455
|
realmId?: string; // sent as X-Realm-ID; needed by authorize, mePermissions, sv feed, magic link
|
|
422
456
|
httpTimeout?: number; // ms, default 10 000
|
|
@@ -429,18 +463,47 @@ interface HearthClientConfig {
|
|
|
429
463
|
interface HearthOptions {
|
|
430
464
|
baseUrl: string;
|
|
431
465
|
realmId: string;
|
|
432
|
-
|
|
466
|
+
issuerUrl: string; // the realm issuer, e.g. "https://hearth.example.com/realms/acme"
|
|
467
|
+
audience?: string; // expected `aud`; default "hearth"
|
|
468
|
+
// called on every predicate check
|
|
469
|
+
getToken: () => string | null | undefined | Promise<string | null | undefined>;
|
|
470
|
+
sessionVersions?: SessionVersionConfig;
|
|
433
471
|
}
|
|
434
472
|
|
|
435
|
-
// HearthFacade — returned by createHearth()
|
|
473
|
+
// HearthFacade — returned by createHearth(); each predicate verifies the token first
|
|
436
474
|
interface HearthFacade {
|
|
437
|
-
hasPermission(permission: string): boolean
|
|
438
|
-
hasRole(role: string): boolean
|
|
439
|
-
inGroup(group: string): boolean
|
|
440
|
-
inOrg(org: string): boolean
|
|
475
|
+
hasPermission(permission: string): Promise<boolean>;
|
|
476
|
+
hasRole(role: string): Promise<boolean>;
|
|
477
|
+
inGroup(group: string): Promise<boolean>;
|
|
478
|
+
inOrg(org: string): Promise<boolean>;
|
|
479
|
+
sessionVersionCacheAge(): number;
|
|
480
|
+
stop(): void;
|
|
441
481
|
client: { permissions(): Promise<MePermissionsResponse> };
|
|
442
482
|
}
|
|
443
483
|
|
|
484
|
+
// AuthConfig — second argument to createHearthAuth(client, config)
|
|
485
|
+
interface AuthConfig {
|
|
486
|
+
clientId: string;
|
|
487
|
+
redirectUri: string;
|
|
488
|
+
hearthUrl: string; // e.g. "https://hearth.example.com"
|
|
489
|
+
realmSlug: string; // e.g. "acme"
|
|
490
|
+
storage?: "sessionStorage" | "localStorage" | "memory" | TokenStorage; // default "sessionStorage"
|
|
491
|
+
storageKeyPrefix?: string; // default "hearth_"
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// HearthBrowserAuth — returned by createHearthAuth()
|
|
495
|
+
interface HearthBrowserAuth {
|
|
496
|
+
startLogin(): Promise<void>;
|
|
497
|
+
handleCallback(code: string, state: string): Promise<void>;
|
|
498
|
+
refreshAccessToken(): Promise<void>;
|
|
499
|
+
logout(): Promise<void>;
|
|
500
|
+
getAccessToken(): string | null; // held in memory
|
|
501
|
+
getRefreshToken(): string | null;
|
|
502
|
+
getIdToken(): string | null;
|
|
503
|
+
isAuthenticated(): boolean;
|
|
504
|
+
clearTokens(): void;
|
|
505
|
+
}
|
|
506
|
+
|
|
444
507
|
// AuthorizeParams
|
|
445
508
|
interface AuthorizeParams {
|
|
446
509
|
clientId: string;
|
|
@@ -550,7 +613,7 @@ class HearthError extends Error {
|
|
|
550
613
|
|
|
551
614
|
**`TokenInvalidError`** — JWT signature does not match any key in the JWKS. If the server recently rotated keys the SDK will re-fetch once automatically; persistent failures indicate a key mismatch.
|
|
552
615
|
|
|
553
|
-
**`TokenAudienceError`** — the token's `aud` claim does not contain the configured audience.
|
|
616
|
+
**`TokenAudienceError`** — the token's `aud` claim does not contain the configured `audience` (default `"hearth"`). Set `audience` to the resource URI the token was issued for; the client ID is not the audience.
|
|
554
617
|
|
|
555
618
|
**`AuthorizationModeMismatchError`** — the server echoed an `access_token_authorization` mode
|
|
556
619
|
that differs from the SDK's `expectedMode` config or the `mode` passed to `requirePermission`.
|
|
@@ -569,7 +632,7 @@ registering the OAuth client; the SDK validates you stay consistent.
|
|
|
569
632
|
|
|
570
633
|
RBAC claims (`permissions`, `roles`, `groups`) are embedded in the JWT at issuance. The
|
|
571
634
|
checker verifies the token first — EdDSA signature against the realm's JWKS, plus `exp`,
|
|
572
|
-
`nbf`, `iss` and
|
|
635
|
+
`nbf`, `iat`, `iss` and `aud` — and only then reads the claim. The JWKS
|
|
573
636
|
is cached, so after the first request there is no network traffic per check.
|
|
574
637
|
|
|
575
638
|
A token that does not verify returns `false`; the checker never trusts an unverified
|
|
@@ -580,7 +643,7 @@ import { HearthClient, requirePermission } from "@hearth-auth/sdk";
|
|
|
580
643
|
|
|
581
644
|
const client = new HearthClient({
|
|
582
645
|
issuerUrl: "https://auth.example.com",
|
|
583
|
-
|
|
646
|
+
audience: "https://api.example.com", // expected `aud`; default "hearth"
|
|
584
647
|
});
|
|
585
648
|
|
|
586
649
|
const check = requirePermission("docs.write", { mode: "embedded", client });
|
|
@@ -659,7 +722,7 @@ const allowed = await check(accessToken);
|
|
|
659
722
|
import express from "express";
|
|
660
723
|
import { HearthClient, hearthMiddleware } from "@hearth-auth/sdk";
|
|
661
724
|
|
|
662
|
-
const client = new HearthClient({ issuerUrl: "https://hearth.example.com"
|
|
725
|
+
const client = new HearthClient({ issuerUrl: "https://hearth.example.com" });
|
|
663
726
|
const app = express();
|
|
664
727
|
|
|
665
728
|
app.get("/docs", hearthMiddleware({ client, requiredPermission: "docs.read" }), (req, res) => {
|
package/dist/browser-auth.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import { HearthApiClient } from "./client.js";
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import type { HearthApiClient } from "./client.js";
|
|
2
|
+
/** A key/value store for the SDK's browser state. `Storage` satisfies it. */
|
|
3
|
+
export interface TokenStorage {
|
|
4
|
+
getItem(key: string): string | null;
|
|
5
|
+
setItem(key: string, value: string): void;
|
|
6
|
+
removeItem(key: string): void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Where {@link createHearthAuth} keeps its state: `"sessionStorage"`
|
|
10
|
+
* (default), `"localStorage"` (survives the tab closing; readable by any
|
|
11
|
+
* script on the origin), `"memory"` (lost on reload or redirect), or a custom
|
|
12
|
+
* {@link TokenStorage}.
|
|
13
|
+
*/
|
|
14
|
+
export type AuthStorage = "sessionStorage" | "localStorage" | "memory" | TokenStorage;
|
|
8
15
|
/** Configuration for {@link createHearthAuth}. */
|
|
9
16
|
export interface AuthConfig {
|
|
10
17
|
/** OAuth 2.0 client ID. */
|
|
@@ -15,18 +22,44 @@ export interface AuthConfig {
|
|
|
15
22
|
hearthUrl: string;
|
|
16
23
|
/** Realm name (slug), e.g. `"demo"`. */
|
|
17
24
|
realmSlug: string;
|
|
25
|
+
/**
|
|
26
|
+
* Where to keep the refresh token, ID token and in-flight login state.
|
|
27
|
+
* Default `"sessionStorage"`. `"memory"` does not survive the login
|
|
28
|
+
* redirect, so use it only with a custom flow that stays on the page.
|
|
29
|
+
*/
|
|
30
|
+
storage?: AuthStorage;
|
|
31
|
+
/** Prefix of every storage key the SDK writes. Default `"hearth_"`. */
|
|
32
|
+
storageKeyPrefix?: string;
|
|
18
33
|
}
|
|
19
34
|
/** Auth facade returned by {@link createHearthAuth}. */
|
|
20
35
|
export interface HearthBrowserAuth {
|
|
36
|
+
/** Redirect to the authorization endpoint with a fresh PKCE challenge. */
|
|
21
37
|
startLogin(): Promise<void>;
|
|
38
|
+
/** Check `state`, then exchange the callback's code for tokens. */
|
|
22
39
|
handleCallback(code: string, state: string): Promise<void>;
|
|
40
|
+
/** Exchange the stored refresh token for a new access token. */
|
|
23
41
|
refreshAccessToken(): Promise<void>;
|
|
42
|
+
/** Clear the local session and redirect to the realm's end-session endpoint. */
|
|
24
43
|
logout(): Promise<void>;
|
|
44
|
+
/** The current access token (held in memory), or `null`. */
|
|
45
|
+
getAccessToken(): string | null;
|
|
46
|
+
/** The stored refresh token, or `null`. */
|
|
47
|
+
getRefreshToken(): string | null;
|
|
48
|
+
/** The stored ID token, or `null`. */
|
|
49
|
+
getIdToken(): string | null;
|
|
50
|
+
/** True iff an access token is present and not yet expired. */
|
|
51
|
+
isAuthenticated(): boolean;
|
|
52
|
+
/** Drop every token, and cancel the scheduled refresh. */
|
|
53
|
+
clearTokens(): void;
|
|
25
54
|
}
|
|
26
55
|
/**
|
|
27
56
|
* Create a browser-side Hearth auth facade backed entirely by the SDK.
|
|
28
57
|
*
|
|
29
58
|
* Handles the full PKCE login flow, token storage, silent refresh, and
|
|
30
59
|
* RP-initiated logout. No custom crypto or OIDC endpoint logic required.
|
|
60
|
+
*
|
|
61
|
+
* At creation it removes the refresh and ID tokens earlier SDK releases kept
|
|
62
|
+
* in `localStorage`, unless `localStorage` is the configured store and those
|
|
63
|
+
* are its own keys.
|
|
31
64
|
*/
|
|
32
65
|
export declare function createHearthAuth(client: HearthApiClient, config: AuthConfig): HearthBrowserAuth;
|
package/dist/browser-auth.js
CHANGED
|
@@ -1,70 +1,100 @@
|
|
|
1
1
|
import { startLogin } from "./pkce.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return localStorage.getItem(ID_KEY);
|
|
18
|
-
}
|
|
19
|
-
/** True iff an access token is present and not yet expired. */
|
|
20
|
-
export function isAuthenticated() {
|
|
21
|
-
return _accessToken !== null && _expiresAt !== null && Date.now() / 1000 < _expiresAt;
|
|
22
|
-
}
|
|
23
|
-
export function clearTokens() {
|
|
24
|
-
_accessToken = null;
|
|
25
|
-
_expiresAt = null;
|
|
26
|
-
localStorage.removeItem(REFRESH_KEY);
|
|
27
|
-
localStorage.removeItem(ID_KEY);
|
|
28
|
-
if (_refreshTimer !== null) {
|
|
29
|
-
clearTimeout(_refreshTimer);
|
|
30
|
-
_refreshTimer = null;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function storeTokens(tokens, fallbackRefresh) {
|
|
34
|
-
_accessToken = tokens.access_token;
|
|
35
|
-
_expiresAt = Date.now() / 1000 + (tokens.expires_in ?? 3600);
|
|
36
|
-
const rt = tokens.refresh_token ?? fallbackRefresh;
|
|
37
|
-
if (rt)
|
|
38
|
-
localStorage.setItem(REFRESH_KEY, rt);
|
|
39
|
-
if (tokens.id_token)
|
|
40
|
-
localStorage.setItem(ID_KEY, tokens.id_token);
|
|
2
|
+
/** Default prefix of every storage key the SDK writes. */
|
|
3
|
+
const DEFAULT_PREFIX = "hearth_";
|
|
4
|
+
/** Keys earlier SDK releases wrote to `localStorage`, removed at init. */
|
|
5
|
+
const LEGACY_LOCAL_STORAGE_KEYS = ["hearth_refresh_token", "hearth_id_token"];
|
|
6
|
+
function memoryStorage() {
|
|
7
|
+
const data = new Map();
|
|
8
|
+
return {
|
|
9
|
+
getItem: (key) => data.get(key) ?? null,
|
|
10
|
+
setItem: (key, value) => {
|
|
11
|
+
data.set(key, value);
|
|
12
|
+
},
|
|
13
|
+
removeItem: (key) => {
|
|
14
|
+
data.delete(key);
|
|
15
|
+
},
|
|
16
|
+
};
|
|
41
17
|
}
|
|
42
|
-
function
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}, delayMs);
|
|
18
|
+
function resolveStorage(option) {
|
|
19
|
+
if (option === undefined || option === "sessionStorage")
|
|
20
|
+
return sessionStorage;
|
|
21
|
+
if (option === "localStorage")
|
|
22
|
+
return localStorage;
|
|
23
|
+
if (option === "memory")
|
|
24
|
+
return memoryStorage();
|
|
25
|
+
return option;
|
|
51
26
|
}
|
|
52
|
-
const VERIFIER_KEY = "hearth_pkce_verifier";
|
|
53
|
-
const STATE_KEY = "hearth_oauth_state";
|
|
54
27
|
/**
|
|
55
28
|
* Create a browser-side Hearth auth facade backed entirely by the SDK.
|
|
56
29
|
*
|
|
57
30
|
* Handles the full PKCE login flow, token storage, silent refresh, and
|
|
58
31
|
* RP-initiated logout. No custom crypto or OIDC endpoint logic required.
|
|
32
|
+
*
|
|
33
|
+
* At creation it removes the refresh and ID tokens earlier SDK releases kept
|
|
34
|
+
* in `localStorage`, unless `localStorage` is the configured store and those
|
|
35
|
+
* are its own keys.
|
|
59
36
|
*/
|
|
60
37
|
export function createHearthAuth(client, config) {
|
|
38
|
+
const store = resolveStorage(config.storage);
|
|
39
|
+
const prefix = config.storageKeyPrefix ?? DEFAULT_PREFIX;
|
|
40
|
+
const key = {
|
|
41
|
+
refresh: `${prefix}refresh_token`,
|
|
42
|
+
id: `${prefix}id_token`,
|
|
43
|
+
verifier: `${prefix}pkce_verifier`,
|
|
44
|
+
state: `${prefix}oauth_state`,
|
|
45
|
+
};
|
|
46
|
+
if (typeof localStorage !== "undefined") {
|
|
47
|
+
const ownKeys = store === localStorage ? [key.refresh, key.id] : [];
|
|
48
|
+
for (const legacy of LEGACY_LOCAL_STORAGE_KEYS) {
|
|
49
|
+
if (!ownKeys.includes(legacy))
|
|
50
|
+
localStorage.removeItem(legacy);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
let accessToken = null;
|
|
54
|
+
let expiresAt = null;
|
|
55
|
+
let refreshTimer = null;
|
|
56
|
+
function getRefreshToken() {
|
|
57
|
+
return store.getItem(key.refresh);
|
|
58
|
+
}
|
|
59
|
+
function getIdToken() {
|
|
60
|
+
return store.getItem(key.id);
|
|
61
|
+
}
|
|
62
|
+
function clearTokens() {
|
|
63
|
+
accessToken = null;
|
|
64
|
+
expiresAt = null;
|
|
65
|
+
store.removeItem(key.refresh);
|
|
66
|
+
store.removeItem(key.id);
|
|
67
|
+
if (refreshTimer !== null) {
|
|
68
|
+
clearTimeout(refreshTimer);
|
|
69
|
+
refreshTimer = null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function storeTokens(tokens, fallbackRefresh) {
|
|
73
|
+
accessToken = tokens.access_token;
|
|
74
|
+
expiresAt = Date.now() / 1000 + (tokens.expires_in ?? 3600);
|
|
75
|
+
const rt = tokens.refresh_token ?? fallbackRefresh;
|
|
76
|
+
if (rt)
|
|
77
|
+
store.setItem(key.refresh, rt);
|
|
78
|
+
if (tokens.id_token)
|
|
79
|
+
store.setItem(key.id, tokens.id_token);
|
|
80
|
+
}
|
|
81
|
+
function scheduleRefresh(expiresIn) {
|
|
82
|
+
if (refreshTimer !== null)
|
|
83
|
+
clearTimeout(refreshTimer);
|
|
84
|
+
const delayMs = Math.max(expiresIn * 0.8, expiresIn - 60) * 1000;
|
|
85
|
+
refreshTimer = setTimeout(() => {
|
|
86
|
+
void refreshAccessToken().catch(() => {
|
|
87
|
+
/* re-auth on next action */
|
|
88
|
+
});
|
|
89
|
+
}, delayMs);
|
|
90
|
+
}
|
|
61
91
|
async function refreshAccessToken() {
|
|
62
92
|
const rt = getRefreshToken();
|
|
63
93
|
if (!rt)
|
|
64
94
|
throw new Error("No refresh token stored");
|
|
65
95
|
const tokens = await client.refreshTokens(config.clientId, rt);
|
|
66
96
|
storeTokens(tokens, rt);
|
|
67
|
-
scheduleRefresh(tokens.expires_in ?? 3600
|
|
97
|
+
scheduleRefresh(tokens.expires_in ?? 3600);
|
|
68
98
|
}
|
|
69
99
|
return {
|
|
70
100
|
async startLogin() {
|
|
@@ -72,15 +102,15 @@ export function createHearthAuth(client, config) {
|
|
|
72
102
|
clientId: config.clientId,
|
|
73
103
|
redirectUri: config.redirectUri,
|
|
74
104
|
});
|
|
75
|
-
|
|
76
|
-
|
|
105
|
+
store.setItem(key.verifier, codeVerifier);
|
|
106
|
+
store.setItem(key.state, state);
|
|
77
107
|
window.location.href = url;
|
|
78
108
|
},
|
|
79
109
|
async handleCallback(_code, state) {
|
|
80
|
-
const storedState =
|
|
81
|
-
const codeVerifier =
|
|
82
|
-
|
|
83
|
-
|
|
110
|
+
const storedState = store.getItem(key.state);
|
|
111
|
+
const codeVerifier = store.getItem(key.verifier) ?? undefined;
|
|
112
|
+
store.removeItem(key.state);
|
|
113
|
+
store.removeItem(key.verifier);
|
|
84
114
|
if (storedState !== state)
|
|
85
115
|
throw new Error("State mismatch — possible CSRF");
|
|
86
116
|
const tokens = await client.handleCallback({
|
|
@@ -90,7 +120,7 @@ export function createHearthAuth(client, config) {
|
|
|
90
120
|
codeVerifier,
|
|
91
121
|
});
|
|
92
122
|
storeTokens(tokens);
|
|
93
|
-
scheduleRefresh(tokens.expires_in ?? 3600
|
|
123
|
+
scheduleRefresh(tokens.expires_in ?? 3600);
|
|
94
124
|
},
|
|
95
125
|
refreshAccessToken,
|
|
96
126
|
async logout() {
|
|
@@ -104,6 +134,11 @@ export function createHearthAuth(client, config) {
|
|
|
104
134
|
params.set("id_token_hint", idToken);
|
|
105
135
|
window.location.href = `${end}?${params}`;
|
|
106
136
|
},
|
|
137
|
+
getAccessToken: () => accessToken,
|
|
138
|
+
getRefreshToken,
|
|
139
|
+
getIdToken,
|
|
140
|
+
isAuthenticated: () => accessToken !== null && expiresAt !== null && Date.now() / 1000 < expiresAt,
|
|
141
|
+
clearTokens,
|
|
107
142
|
};
|
|
108
143
|
}
|
|
109
144
|
//# sourceMappingURL=browser-auth.js.map
|
package/dist/browser-auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-auth.js","sourceRoot":"","sources":["../src/browser-auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"browser-auth.js","sourceRoot":"","sources":["../src/browser-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -1055,6 +1055,8 @@ export interface components {
|
|
|
1055
1055
|
name: string;
|
|
1056
1056
|
slug: string;
|
|
1057
1057
|
description: string | null;
|
|
1058
|
+
/** @description Declared in hearth.yaml; the admin API cannot change or delete it. */
|
|
1059
|
+
yaml_managed: boolean;
|
|
1058
1060
|
/**
|
|
1059
1061
|
* Format: int64
|
|
1060
1062
|
* @description Microseconds since the Unix epoch.
|
package/dist/hearth-client.d.ts
CHANGED
|
@@ -14,6 +14,14 @@ export interface HearthClientConfig {
|
|
|
14
14
|
* Required for flows that need a client identity (e.g. introspection).
|
|
15
15
|
*/
|
|
16
16
|
clientId?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Expected `aud` of the access tokens {@link HearthClient.verifyToken}
|
|
19
|
+
* accepts: the name of this API (RFC 9068 §4), not the client ID.
|
|
20
|
+
* Default: `"hearth"`, the audience Hearth mints when a client names no
|
|
21
|
+
* resource. An API registered as a protected resource sets its resource URI.
|
|
22
|
+
* The check is always on.
|
|
23
|
+
*/
|
|
24
|
+
audience?: string;
|
|
17
25
|
/**
|
|
18
26
|
* OAuth 2.0 client secret.
|
|
19
27
|
* Required for confidential client flows (e.g. introspection).
|
|
@@ -77,6 +85,8 @@ export declare class HearthClient {
|
|
|
77
85
|
readonly issuerUrl: string;
|
|
78
86
|
readonly clientId: string | undefined;
|
|
79
87
|
readonly clientSecret: string | undefined;
|
|
88
|
+
/** Expected `aud` of verified access tokens. Default `"hearth"`. */
|
|
89
|
+
readonly audience: string;
|
|
80
90
|
readonly jwksTtl: number | undefined;
|
|
81
91
|
readonly introspectionEndpointOverride: string | undefined;
|
|
82
92
|
/** HTTP timeout in milliseconds applied to all outbound fetch calls. */
|
|
@@ -160,14 +170,16 @@ export declare class HearthClient {
|
|
|
160
170
|
* 2. `exp` claim (rejects expired tokens).
|
|
161
171
|
* 3. `nbf` claim (rejects post-dated tokens).
|
|
162
172
|
* 4. `iss` claim (must match configured `issuerUrl`).
|
|
163
|
-
* 5. `aud` claim (
|
|
173
|
+
* 5. `aud` claim (must contain the configured `audience`, default `"hearth"`).
|
|
174
|
+
* 6. `iat` claim (rejects a token issued in the future).
|
|
164
175
|
*
|
|
165
|
-
* `exp` and `
|
|
176
|
+
* `exp`, `nbf` and `iat` allow a 5-second clock skew.
|
|
166
177
|
*
|
|
167
178
|
* @throws {@link TokenExpiredError} — token is expired.
|
|
179
|
+
* @throws {@link TokenNotYetValidError} — `nbf` or `iat` is in the future.
|
|
168
180
|
* @throws {@link TokenInvalidError} — signature invalid or JWT malformed.
|
|
169
181
|
* @throws {@link TokenIssuerError} — issuer does not match `issuerUrl`.
|
|
170
|
-
* @throws {@link TokenAudienceError} — audience does not include `
|
|
182
|
+
* @throws {@link TokenAudienceError} — audience does not include `audience`.
|
|
171
183
|
* @throws {@link JWKSFetchError} — JWKS endpoint unreachable.
|
|
172
184
|
*/
|
|
173
185
|
verifyToken(token: string): Promise<Claims>;
|