@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.
- package/dist/admin.d.ts +43 -0
- package/dist/admin.js +126 -0
- package/dist/admin.js.map +1 -0
- package/dist/browser-auth.d.ts +32 -0
- package/dist/browser-auth.js +99 -0
- package/dist/browser-auth.js.map +1 -0
- package/dist/claims.d.ts +86 -0
- package/dist/claims.js +137 -0
- package/dist/claims.js.map +1 -0
- package/dist/client.d.ts +77 -0
- package/dist/client.js +190 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +114 -0
- package/{src/errors.ts → dist/errors.js} +83 -97
- package/dist/errors.js.map +1 -0
- package/dist/hearth-client.d.ts +133 -0
- package/dist/hearth-client.js +192 -0
- package/dist/hearth-client.js.map +1 -0
- package/dist/hearth.d.ts +105 -0
- package/dist/hearth.js +109 -0
- package/dist/hearth.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/introspection-client.d.ts +59 -0
- package/dist/introspection-client.js +36 -0
- package/dist/introspection-client.js.map +1 -0
- package/dist/jwks-client.d.ts +28 -0
- package/dist/jwks-client.js +28 -0
- package/dist/jwks-client.js.map +1 -0
- package/dist/middleware.d.ts +38 -0
- package/dist/middleware.js +51 -0
- package/dist/middleware.js.map +1 -0
- package/dist/pkce.d.ts +64 -0
- package/dist/pkce.js +64 -0
- package/dist/pkce.js.map +1 -0
- package/dist/react.d.ts +32 -0
- package/dist/react.js +41 -0
- package/dist/react.js.map +1 -0
- package/dist/session-version-cache.d.ts +50 -0
- package/dist/session-version-cache.js +129 -0
- package/dist/session-version-cache.js.map +1 -0
- package/dist/types.d.ts +168 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +13 -4
- package/CHANGELOG.md +0 -12
- package/src/admin.ts +0 -157
- package/src/browser-auth.ts +0 -130
- package/src/claims.ts +0 -180
- package/src/client.ts +0 -251
- package/src/generated/google/api/annotations_pb.ts +0 -44
- package/src/generated/google/api/http_pb.ts +0 -467
- package/src/generated/hearth/authz/v1/authz_pb.ts +0 -593
- package/src/generated/hearth/cluster/v1/raft_pb.ts +0 -183
- package/src/generated/hearth/events/v1/audit_pb.ts +0 -886
- package/src/generated/hearth/identity/v1/identity_pb.ts +0 -1673
- package/src/generated/hearth/identity/v1/oauth_pb.ts +0 -1138
- package/src/generated/hearth/rbac/v1/rbac_pb.ts +0 -2000
- package/src/hearth-client.ts +0 -288
- package/src/hearth.ts +0 -224
- package/src/index.ts +0 -106
- package/src/introspection-client.ts +0 -83
- package/src/jwks-client.ts +0 -45
- package/src/middleware.ts +0 -82
- package/src/pkce.ts +0 -129
- package/src/react.tsx +0 -57
- package/src/session-version-cache.ts +0 -167
- package/src/types.ts +0 -188
- package/tests/admin-crud.test.ts +0 -97
- package/tests/auth-flow.test.ts +0 -75
- package/tests/authorize.test.ts +0 -386
- package/tests/claims.test.ts +0 -159
- package/tests/hasPermission.test.ts +0 -152
- package/tests/hearth-client.test.ts +0 -243
- package/tests/helpers.ts +0 -90
- package/tests/jwks.test.ts +0 -62
- package/tests/pkce.test.ts +0 -210
- package/tests/react-useHasPermission.test.tsx +0 -92
- package/tests/required-action.test.ts +0 -276
- package/tests/session-version.test.ts +0 -391
- package/tsconfig.json +0 -16
- package/vitest.config.ts +0 -8
package/dist/admin.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { CreateRealmParams, CreateUserParams, PageResponse, Realm, UpdateRealmParams, UpdateUserParams, User } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Admin API client for Hearth.
|
|
4
|
+
*
|
|
5
|
+
* Requires a valid admin access token. All operations go through
|
|
6
|
+
* the /admin/* endpoints which enforce RBAC admin role checks.
|
|
7
|
+
*/
|
|
8
|
+
export declare class AdminClient {
|
|
9
|
+
private readonly baseUrl;
|
|
10
|
+
private readonly realmId;
|
|
11
|
+
private readonly accessToken;
|
|
12
|
+
constructor(baseUrl: string, realmId: string, accessToken: string);
|
|
13
|
+
/** POST /admin/users — create a user. */
|
|
14
|
+
createUser(params: CreateUserParams): Promise<User>;
|
|
15
|
+
/** GET /admin/users — list users with pagination. */
|
|
16
|
+
listUsers(options?: {
|
|
17
|
+
limit?: number;
|
|
18
|
+
cursor?: string;
|
|
19
|
+
}): Promise<PageResponse<User>>;
|
|
20
|
+
/** GET /admin/users/:id — get a user by ID. */
|
|
21
|
+
getUser(userId: string): Promise<User>;
|
|
22
|
+
/** PUT /admin/users/:id — update a user. */
|
|
23
|
+
updateUser(userId: string, params: UpdateUserParams): Promise<User>;
|
|
24
|
+
/** DELETE /admin/users/:id — delete a user. */
|
|
25
|
+
deleteUser(userId: string): Promise<void>;
|
|
26
|
+
/** POST /admin/realms — create a realm. */
|
|
27
|
+
createRealm(params: CreateRealmParams): Promise<Realm>;
|
|
28
|
+
/** GET /admin/realms — list realms with pagination. */
|
|
29
|
+
listRealms(options?: {
|
|
30
|
+
limit?: number;
|
|
31
|
+
cursor?: string;
|
|
32
|
+
}): Promise<PageResponse<Realm>>;
|
|
33
|
+
/** GET /admin/realms/:id — get a realm by ID. */
|
|
34
|
+
getRealm(realmId: string): Promise<Realm>;
|
|
35
|
+
/** PUT /admin/realms/:id — update a realm. */
|
|
36
|
+
updateRealm(realmId: string, params: UpdateRealmParams): Promise<Realm>;
|
|
37
|
+
/** DELETE /admin/realms/:id — delete a realm. */
|
|
38
|
+
deleteRealm(realmId: string): Promise<void>;
|
|
39
|
+
private headers;
|
|
40
|
+
private get;
|
|
41
|
+
private post;
|
|
42
|
+
private request;
|
|
43
|
+
}
|
package/dist/admin.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { HearthError } from "./client.js";
|
|
2
|
+
/**
|
|
3
|
+
* Admin API client for Hearth.
|
|
4
|
+
*
|
|
5
|
+
* Requires a valid admin access token. All operations go through
|
|
6
|
+
* the /admin/* endpoints which enforce RBAC admin role checks.
|
|
7
|
+
*/
|
|
8
|
+
export class AdminClient {
|
|
9
|
+
baseUrl;
|
|
10
|
+
realmId;
|
|
11
|
+
accessToken;
|
|
12
|
+
constructor(baseUrl, realmId, accessToken) {
|
|
13
|
+
this.baseUrl = baseUrl;
|
|
14
|
+
this.realmId = realmId;
|
|
15
|
+
this.accessToken = accessToken;
|
|
16
|
+
}
|
|
17
|
+
// === Users ===
|
|
18
|
+
/** POST /admin/users — create a user. */
|
|
19
|
+
async createUser(params) {
|
|
20
|
+
return this.post("/admin/users", {
|
|
21
|
+
email: params.email,
|
|
22
|
+
display_name: params.displayName,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/** GET /admin/users — list users with pagination. */
|
|
26
|
+
async listUsers(options) {
|
|
27
|
+
const q = new URLSearchParams();
|
|
28
|
+
if (options?.limit)
|
|
29
|
+
q.set("limit", String(options.limit));
|
|
30
|
+
if (options?.cursor)
|
|
31
|
+
q.set("cursor", options.cursor);
|
|
32
|
+
return this.get(`/admin/users?${q}`);
|
|
33
|
+
}
|
|
34
|
+
/** GET /admin/users/:id — get a user by ID. */
|
|
35
|
+
async getUser(userId) {
|
|
36
|
+
return this.get(`/admin/users/${userId}`);
|
|
37
|
+
}
|
|
38
|
+
/** PUT /admin/users/:id — update a user. */
|
|
39
|
+
async updateUser(userId, params) {
|
|
40
|
+
return this.request("PATCH", `/admin/users/${userId}`, {
|
|
41
|
+
email: params.email,
|
|
42
|
+
display_name: params.displayName,
|
|
43
|
+
status: params.status,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** DELETE /admin/users/:id — delete a user. */
|
|
47
|
+
async deleteUser(userId) {
|
|
48
|
+
const resp = await fetch(`${this.baseUrl}/admin/users/${userId}`, {
|
|
49
|
+
method: "DELETE",
|
|
50
|
+
headers: this.headers(),
|
|
51
|
+
});
|
|
52
|
+
if (!resp.ok) {
|
|
53
|
+
throw new HearthError(resp.status, await resp.json());
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// === Realms ===
|
|
57
|
+
/** POST /admin/realms — create a realm. */
|
|
58
|
+
async createRealm(params) {
|
|
59
|
+
return this.post("/admin/realms", {
|
|
60
|
+
name: params.name,
|
|
61
|
+
config: params.config,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/** GET /admin/realms — list realms with pagination. */
|
|
65
|
+
async listRealms(options) {
|
|
66
|
+
const q = new URLSearchParams();
|
|
67
|
+
if (options?.limit)
|
|
68
|
+
q.set("limit", String(options.limit));
|
|
69
|
+
if (options?.cursor)
|
|
70
|
+
q.set("cursor", options.cursor);
|
|
71
|
+
return this.get(`/admin/realms?${q}`);
|
|
72
|
+
}
|
|
73
|
+
/** GET /admin/realms/:id — get a realm by ID. */
|
|
74
|
+
async getRealm(realmId) {
|
|
75
|
+
return this.get(`/admin/realms/${realmId}`);
|
|
76
|
+
}
|
|
77
|
+
/** PUT /admin/realms/:id — update a realm. */
|
|
78
|
+
async updateRealm(realmId, params) {
|
|
79
|
+
return this.request("PATCH", `/admin/realms/${realmId}`, {
|
|
80
|
+
name: params.name,
|
|
81
|
+
status: params.status,
|
|
82
|
+
config: params.config,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/** DELETE /admin/realms/:id — delete a realm. */
|
|
86
|
+
async deleteRealm(realmId) {
|
|
87
|
+
const resp = await fetch(`${this.baseUrl}/admin/realms/${realmId}`, {
|
|
88
|
+
method: "DELETE",
|
|
89
|
+
headers: this.headers(),
|
|
90
|
+
});
|
|
91
|
+
if (!resp.ok) {
|
|
92
|
+
throw new HearthError(resp.status, await resp.json());
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
headers() {
|
|
96
|
+
return {
|
|
97
|
+
"X-Realm-ID": this.realmId,
|
|
98
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
99
|
+
"Content-Type": "application/json",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
async get(path) {
|
|
103
|
+
const resp = await fetch(`${this.baseUrl}${path}`, {
|
|
104
|
+
headers: this.headers(),
|
|
105
|
+
});
|
|
106
|
+
if (!resp.ok) {
|
|
107
|
+
throw new HearthError(resp.status, await resp.json());
|
|
108
|
+
}
|
|
109
|
+
return resp.json();
|
|
110
|
+
}
|
|
111
|
+
async post(path, body) {
|
|
112
|
+
return this.request("POST", path, body);
|
|
113
|
+
}
|
|
114
|
+
async request(method, path, body) {
|
|
115
|
+
const resp = await fetch(`${this.baseUrl}${path}`, {
|
|
116
|
+
method,
|
|
117
|
+
headers: this.headers(),
|
|
118
|
+
body: JSON.stringify(body),
|
|
119
|
+
});
|
|
120
|
+
if (!resp.ok) {
|
|
121
|
+
throw new HearthError(resp.status, await resp.json());
|
|
122
|
+
}
|
|
123
|
+
return resp.json();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=admin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { HearthApiClient } from "./client.js";
|
|
2
|
+
export declare function getAccessToken(): string | null;
|
|
3
|
+
export declare function getRefreshToken(): string | null;
|
|
4
|
+
export declare function getIdToken(): string | null;
|
|
5
|
+
/** True iff an access token is present and not yet expired. */
|
|
6
|
+
export declare function isAuthenticated(): boolean;
|
|
7
|
+
export declare function clearTokens(): void;
|
|
8
|
+
/** Configuration for {@link createHearthAuth}. */
|
|
9
|
+
export interface AuthConfig {
|
|
10
|
+
/** OAuth 2.0 client ID. */
|
|
11
|
+
clientId: string;
|
|
12
|
+
/** Redirect URI registered for this client. */
|
|
13
|
+
redirectUri: string;
|
|
14
|
+
/** Hearth server base URL, e.g. `http://localhost:8420`. */
|
|
15
|
+
hearthUrl: string;
|
|
16
|
+
/** Realm name (slug), e.g. `"demo"`. */
|
|
17
|
+
realmSlug: string;
|
|
18
|
+
}
|
|
19
|
+
/** Auth facade returned by {@link createHearthAuth}. */
|
|
20
|
+
export interface HearthBrowserAuth {
|
|
21
|
+
startLogin(): Promise<void>;
|
|
22
|
+
handleCallback(code: string, state: string): Promise<void>;
|
|
23
|
+
refreshAccessToken(): Promise<void>;
|
|
24
|
+
logout(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Create a browser-side Hearth auth facade backed entirely by the SDK.
|
|
28
|
+
*
|
|
29
|
+
* Handles the full PKCE login flow, token storage, silent refresh, and
|
|
30
|
+
* RP-initiated logout. No custom crypto or OIDC endpoint logic required.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createHearthAuth(client: HearthApiClient, config: AuthConfig): HearthBrowserAuth;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { startLogin } from "./pkce.js";
|
|
2
|
+
// ── Token store ─────────────────────────────────────────────────────────────
|
|
3
|
+
// Access token lives in memory only. Refresh + ID tokens survive page reloads
|
|
4
|
+
// via localStorage. For stricter XSS safety, swap for an HttpOnly-cookie BFF.
|
|
5
|
+
const REFRESH_KEY = "hearth_refresh_token";
|
|
6
|
+
const ID_KEY = "hearth_id_token";
|
|
7
|
+
let _accessToken = null;
|
|
8
|
+
let _expiresAt = null;
|
|
9
|
+
let _refreshTimer = null;
|
|
10
|
+
export function getAccessToken() { return _accessToken; }
|
|
11
|
+
export function getRefreshToken() { return localStorage.getItem(REFRESH_KEY); }
|
|
12
|
+
export function getIdToken() { return localStorage.getItem(ID_KEY); }
|
|
13
|
+
/** True iff an access token is present and not yet expired. */
|
|
14
|
+
export function isAuthenticated() {
|
|
15
|
+
return _accessToken !== null && _expiresAt !== null && Date.now() / 1000 < _expiresAt;
|
|
16
|
+
}
|
|
17
|
+
export function clearTokens() {
|
|
18
|
+
_accessToken = null;
|
|
19
|
+
_expiresAt = null;
|
|
20
|
+
localStorage.removeItem(REFRESH_KEY);
|
|
21
|
+
localStorage.removeItem(ID_KEY);
|
|
22
|
+
if (_refreshTimer !== null) {
|
|
23
|
+
clearTimeout(_refreshTimer);
|
|
24
|
+
_refreshTimer = null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function storeTokens(tokens, fallbackRefresh) {
|
|
28
|
+
_accessToken = tokens.access_token;
|
|
29
|
+
_expiresAt = Date.now() / 1000 + (tokens.expires_in ?? 3600);
|
|
30
|
+
const rt = tokens.refresh_token ?? fallbackRefresh;
|
|
31
|
+
if (rt)
|
|
32
|
+
localStorage.setItem(REFRESH_KEY, rt);
|
|
33
|
+
if (tokens.id_token)
|
|
34
|
+
localStorage.setItem(ID_KEY, tokens.id_token);
|
|
35
|
+
}
|
|
36
|
+
function scheduleRefresh(expiresIn, doRefresh) {
|
|
37
|
+
if (_refreshTimer !== null)
|
|
38
|
+
clearTimeout(_refreshTimer);
|
|
39
|
+
const delayMs = Math.max(expiresIn * 0.8, expiresIn - 60) * 1000;
|
|
40
|
+
_refreshTimer = setTimeout(() => { void doRefresh().catch(() => { }); }, delayMs);
|
|
41
|
+
}
|
|
42
|
+
const VERIFIER_KEY = "hearth_pkce_verifier";
|
|
43
|
+
const STATE_KEY = "hearth_oauth_state";
|
|
44
|
+
/**
|
|
45
|
+
* Create a browser-side Hearth auth facade backed entirely by the SDK.
|
|
46
|
+
*
|
|
47
|
+
* Handles the full PKCE login flow, token storage, silent refresh, and
|
|
48
|
+
* RP-initiated logout. No custom crypto or OIDC endpoint logic required.
|
|
49
|
+
*/
|
|
50
|
+
export function createHearthAuth(client, config) {
|
|
51
|
+
async function refreshAccessToken() {
|
|
52
|
+
const rt = getRefreshToken();
|
|
53
|
+
if (!rt)
|
|
54
|
+
throw new Error("No refresh token stored");
|
|
55
|
+
const tokens = await client.refreshTokens(config.clientId, rt);
|
|
56
|
+
storeTokens(tokens, rt);
|
|
57
|
+
scheduleRefresh(tokens.expires_in ?? 3600, refreshAccessToken);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
async startLogin() {
|
|
61
|
+
const { url, state, codeVerifier } = await startLogin(client, {
|
|
62
|
+
clientId: config.clientId,
|
|
63
|
+
redirectUri: config.redirectUri,
|
|
64
|
+
});
|
|
65
|
+
sessionStorage.setItem(VERIFIER_KEY, codeVerifier);
|
|
66
|
+
sessionStorage.setItem(STATE_KEY, state);
|
|
67
|
+
window.location.href = url;
|
|
68
|
+
},
|
|
69
|
+
async handleCallback(_code, state) {
|
|
70
|
+
const storedState = sessionStorage.getItem(STATE_KEY);
|
|
71
|
+
const codeVerifier = sessionStorage.getItem(VERIFIER_KEY) ?? undefined;
|
|
72
|
+
sessionStorage.removeItem(STATE_KEY);
|
|
73
|
+
sessionStorage.removeItem(VERIFIER_KEY);
|
|
74
|
+
if (storedState !== state)
|
|
75
|
+
throw new Error("State mismatch — possible CSRF");
|
|
76
|
+
const tokens = await client.handleCallback({
|
|
77
|
+
callbackUrl: window.location.href,
|
|
78
|
+
clientId: config.clientId,
|
|
79
|
+
redirectUri: config.redirectUri,
|
|
80
|
+
codeVerifier,
|
|
81
|
+
});
|
|
82
|
+
storeTokens(tokens);
|
|
83
|
+
scheduleRefresh(tokens.expires_in ?? 3600, refreshAccessToken);
|
|
84
|
+
},
|
|
85
|
+
refreshAccessToken,
|
|
86
|
+
async logout() {
|
|
87
|
+
const idToken = getIdToken();
|
|
88
|
+
clearTokens();
|
|
89
|
+
const doc = await client.discovery().catch(() => null);
|
|
90
|
+
const end = doc?.["end_session_endpoint"]
|
|
91
|
+
?? `${config.hearthUrl}/realms/${config.realmSlug}/end_session`;
|
|
92
|
+
const params = new URLSearchParams({ post_logout_redirect_uri: window.location.origin });
|
|
93
|
+
if (idToken)
|
|
94
|
+
params.set("id_token_hint", idToken);
|
|
95
|
+
window.location.href = `${end}?${params}`;
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=browser-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-auth.js","sourceRoot":"","sources":["../src/browser-auth.ts"],"names":[],"mappings":"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"}
|
package/dist/claims.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec §4 — Claims API.
|
|
3
|
+
*
|
|
4
|
+
* {@link Claims} wraps a decoded JWT payload and exposes typed accessors
|
|
5
|
+
* for standard OIDC and Hearth-specific claims. All reads are local —
|
|
6
|
+
* no network call is made. Signature verification is the caller's
|
|
7
|
+
* responsibility (e.g. via the JWKS endpoint before constructing Claims).
|
|
8
|
+
*/
|
|
9
|
+
/** Raw JWT payload shape used internally. */
|
|
10
|
+
interface RawPayload {
|
|
11
|
+
sub?: string;
|
|
12
|
+
iss?: string;
|
|
13
|
+
aud?: string | string[];
|
|
14
|
+
exp?: number;
|
|
15
|
+
nbf?: number;
|
|
16
|
+
iat?: number;
|
|
17
|
+
jti?: string;
|
|
18
|
+
scope?: string;
|
|
19
|
+
scopes?: string[];
|
|
20
|
+
roles?: string[];
|
|
21
|
+
permissions?: string[];
|
|
22
|
+
groups?: string[];
|
|
23
|
+
oid?: string;
|
|
24
|
+
org_groups?: string[];
|
|
25
|
+
token_type?: string;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Typed accessor for a decoded JWT's claims.
|
|
30
|
+
*
|
|
31
|
+
* Construct via {@link Claims.decode} (decodes without verifying signature)
|
|
32
|
+
* or pass a pre-decoded payload to the constructor.
|
|
33
|
+
*/
|
|
34
|
+
export declare class Claims {
|
|
35
|
+
private readonly payload;
|
|
36
|
+
constructor(payload: RawPayload);
|
|
37
|
+
/**
|
|
38
|
+
* Decode a JWT string into a {@link Claims} object.
|
|
39
|
+
* The signature is NOT verified — the caller must verify it separately.
|
|
40
|
+
*
|
|
41
|
+
* @throws {TokenInvalidError} if the string is not a valid JWT.
|
|
42
|
+
*/
|
|
43
|
+
static decode(token: string): Claims;
|
|
44
|
+
/**
|
|
45
|
+
* Assert the token is temporally valid (not expired, past nbf).
|
|
46
|
+
*
|
|
47
|
+
* @throws {TokenExpiredError} if exp is in the past.
|
|
48
|
+
* @throws {TokenNotYetValidError} if nbf is in the future.
|
|
49
|
+
*/
|
|
50
|
+
assertValid(clockSkewSeconds?: number): void;
|
|
51
|
+
/** The `sub` (subject) claim — identifies the principal that is the subject of the JWT. */
|
|
52
|
+
subject(): string;
|
|
53
|
+
/** The `iss` (issuer) claim — identifies the principal that issued the JWT. */
|
|
54
|
+
issuer(): string;
|
|
55
|
+
/** The `aud` (audiences) claim — normalized to an array. */
|
|
56
|
+
audiences(): string[];
|
|
57
|
+
/** The `exp` (expiry) claim as a Date, or null if absent. */
|
|
58
|
+
expiry(): Date | null;
|
|
59
|
+
/** The `iat` (issuedAt) claim as a Date, or null if absent. */
|
|
60
|
+
issuedAt(): Date | null;
|
|
61
|
+
/** The `jti` (JWT ID) claim, or null if absent. */
|
|
62
|
+
jwtID(): string | null;
|
|
63
|
+
/** The `scope` claim split into individual scopes (or `scopes` array if present). */
|
|
64
|
+
scopes(): string[];
|
|
65
|
+
/** Returns true iff the token contains the given scope. */
|
|
66
|
+
hasScope(scope: string): boolean;
|
|
67
|
+
/** Returns true iff the token's `roles` claim contains the given role. */
|
|
68
|
+
hasRole(role: string): boolean;
|
|
69
|
+
/** Returns true iff the token's `permissions` claim contains the given permission. */
|
|
70
|
+
hasPermission(permission: string): boolean;
|
|
71
|
+
/** The raw `scope` claim (space-delimited string), or empty string if absent. */
|
|
72
|
+
scope(): string;
|
|
73
|
+
/** Returns true iff the token's `groups` claim contains the given group. */
|
|
74
|
+
inGroup(groupId: string): boolean;
|
|
75
|
+
/** Returns true iff the token's `oid` claim exactly matches the given org ID. */
|
|
76
|
+
inOrg(orgId: string): boolean;
|
|
77
|
+
/** The `token_type` claim (`"access"`, `"refresh"`, `"required_action"`), or empty string. */
|
|
78
|
+
tokenType(): string;
|
|
79
|
+
/** The `oid` (organization ID) claim, or `undefined` if absent. */
|
|
80
|
+
organizationId(): string | undefined;
|
|
81
|
+
/** The `org_groups` claim (Keycloak-style org-scoped group paths), or empty array. */
|
|
82
|
+
orgGroups(): string[];
|
|
83
|
+
/** Access an arbitrary claim by key. */
|
|
84
|
+
get(key: string): unknown;
|
|
85
|
+
}
|
|
86
|
+
export {};
|
package/dist/claims.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec §4 — Claims API.
|
|
3
|
+
*
|
|
4
|
+
* {@link Claims} wraps a decoded JWT payload and exposes typed accessors
|
|
5
|
+
* for standard OIDC and Hearth-specific claims. All reads are local —
|
|
6
|
+
* no network call is made. Signature verification is the caller's
|
|
7
|
+
* responsibility (e.g. via the JWKS endpoint before constructing Claims).
|
|
8
|
+
*/
|
|
9
|
+
import { decodeJwt } from "jose";
|
|
10
|
+
import { TokenExpiredError, TokenInvalidError, TokenNotYetValidError, } from "./errors.js";
|
|
11
|
+
/**
|
|
12
|
+
* Typed accessor for a decoded JWT's claims.
|
|
13
|
+
*
|
|
14
|
+
* Construct via {@link Claims.decode} (decodes without verifying signature)
|
|
15
|
+
* or pass a pre-decoded payload to the constructor.
|
|
16
|
+
*/
|
|
17
|
+
export class Claims {
|
|
18
|
+
payload;
|
|
19
|
+
constructor(payload) {
|
|
20
|
+
this.payload = payload;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Decode a JWT string into a {@link Claims} object.
|
|
24
|
+
* The signature is NOT verified — the caller must verify it separately.
|
|
25
|
+
*
|
|
26
|
+
* @throws {TokenInvalidError} if the string is not a valid JWT.
|
|
27
|
+
*/
|
|
28
|
+
static decode(token) {
|
|
29
|
+
try {
|
|
30
|
+
const payload = decodeJwt(token);
|
|
31
|
+
return new Claims(payload);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
throw new TokenInvalidError(`Failed to decode JWT: ${err instanceof Error ? err.message : String(err)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Assert the token is temporally valid (not expired, past nbf).
|
|
39
|
+
*
|
|
40
|
+
* @throws {TokenExpiredError} if exp is in the past.
|
|
41
|
+
* @throws {TokenNotYetValidError} if nbf is in the future.
|
|
42
|
+
*/
|
|
43
|
+
assertValid(clockSkewSeconds = 0) {
|
|
44
|
+
const now = Math.floor(Date.now() / 1000);
|
|
45
|
+
const exp = this.payload.exp;
|
|
46
|
+
if (exp !== undefined && now > exp + clockSkewSeconds) {
|
|
47
|
+
throw new TokenExpiredError(new Date(exp * 1000));
|
|
48
|
+
}
|
|
49
|
+
const nbf = this.payload.nbf;
|
|
50
|
+
if (nbf !== undefined && now < nbf - clockSkewSeconds) {
|
|
51
|
+
throw new TokenNotYetValidError(new Date(nbf * 1000));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** The `sub` (subject) claim — identifies the principal that is the subject of the JWT. */
|
|
55
|
+
subject() {
|
|
56
|
+
return this.payload.sub ?? "";
|
|
57
|
+
}
|
|
58
|
+
/** The `iss` (issuer) claim — identifies the principal that issued the JWT. */
|
|
59
|
+
issuer() {
|
|
60
|
+
return this.payload.iss ?? "";
|
|
61
|
+
}
|
|
62
|
+
/** The `aud` (audiences) claim — normalized to an array. */
|
|
63
|
+
audiences() {
|
|
64
|
+
const aud = this.payload.aud;
|
|
65
|
+
if (!aud)
|
|
66
|
+
return [];
|
|
67
|
+
return Array.isArray(aud) ? aud : [aud];
|
|
68
|
+
}
|
|
69
|
+
/** The `exp` (expiry) claim as a Date, or null if absent. */
|
|
70
|
+
expiry() {
|
|
71
|
+
return this.payload.exp !== undefined
|
|
72
|
+
? new Date(this.payload.exp * 1000)
|
|
73
|
+
: null;
|
|
74
|
+
}
|
|
75
|
+
/** The `iat` (issuedAt) claim as a Date, or null if absent. */
|
|
76
|
+
issuedAt() {
|
|
77
|
+
return this.payload.iat !== undefined
|
|
78
|
+
? new Date(this.payload.iat * 1000)
|
|
79
|
+
: null;
|
|
80
|
+
}
|
|
81
|
+
/** The `jti` (JWT ID) claim, or null if absent. */
|
|
82
|
+
jwtID() {
|
|
83
|
+
return this.payload.jti ?? null;
|
|
84
|
+
}
|
|
85
|
+
/** The `scope` claim split into individual scopes (or `scopes` array if present). */
|
|
86
|
+
scopes() {
|
|
87
|
+
if (this.payload.scopes)
|
|
88
|
+
return this.payload.scopes;
|
|
89
|
+
const scope = this.payload.scope;
|
|
90
|
+
if (!scope)
|
|
91
|
+
return [];
|
|
92
|
+
return scope.split(/\s+/).filter(Boolean);
|
|
93
|
+
}
|
|
94
|
+
/** Returns true iff the token contains the given scope. */
|
|
95
|
+
hasScope(scope) {
|
|
96
|
+
return this.scopes().includes(scope);
|
|
97
|
+
}
|
|
98
|
+
/** Returns true iff the token's `roles` claim contains the given role. */
|
|
99
|
+
hasRole(role) {
|
|
100
|
+
return (this.payload.roles ?? []).includes(role);
|
|
101
|
+
}
|
|
102
|
+
/** Returns true iff the token's `permissions` claim contains the given permission. */
|
|
103
|
+
hasPermission(permission) {
|
|
104
|
+
return (this.payload.permissions ?? []).includes(permission);
|
|
105
|
+
}
|
|
106
|
+
/** The raw `scope` claim (space-delimited string), or empty string if absent. */
|
|
107
|
+
scope() {
|
|
108
|
+
return this.payload.scope ?? "";
|
|
109
|
+
}
|
|
110
|
+
/** Returns true iff the token's `groups` claim contains the given group. */
|
|
111
|
+
inGroup(groupId) {
|
|
112
|
+
const groups = this.payload.groups;
|
|
113
|
+
return Array.isArray(groups) && groups.includes(groupId);
|
|
114
|
+
}
|
|
115
|
+
/** Returns true iff the token's `oid` claim exactly matches the given org ID. */
|
|
116
|
+
inOrg(orgId) {
|
|
117
|
+
return typeof this.payload.oid === "string" && this.payload.oid === orgId;
|
|
118
|
+
}
|
|
119
|
+
/** The `token_type` claim (`"access"`, `"refresh"`, `"required_action"`), or empty string. */
|
|
120
|
+
tokenType() {
|
|
121
|
+
return this.payload.token_type ?? "";
|
|
122
|
+
}
|
|
123
|
+
/** The `oid` (organization ID) claim, or `undefined` if absent. */
|
|
124
|
+
organizationId() {
|
|
125
|
+
return this.payload.oid;
|
|
126
|
+
}
|
|
127
|
+
/** The `org_groups` claim (Keycloak-style org-scoped group paths), or empty array. */
|
|
128
|
+
orgGroups() {
|
|
129
|
+
const og = this.payload.org_groups;
|
|
130
|
+
return Array.isArray(og) ? og : [];
|
|
131
|
+
}
|
|
132
|
+
/** Access an arbitrary claim by key. */
|
|
133
|
+
get(key) {
|
|
134
|
+
return this.payload[key];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=claims.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claims.js","sourceRoot":"","sources":["../src/claims.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { AuthorizeParams, AuthorizeResponse, BootstrapResponse, JwksDocument, MePermissionsResponse, RegisterClientParams, OAuthClient, TokenExchangeParams, TokenResponse, UserInfoResponse } from "./types.js";
|
|
2
|
+
/** Parameters for the PKCE authorization-code callback handler (spec §7). */
|
|
3
|
+
export interface HandleCallbackParams {
|
|
4
|
+
/** Full callback URL including query parameters (`code`, `state`, etc.). */
|
|
5
|
+
callbackUrl: string;
|
|
6
|
+
/** OAuth 2.0 client ID. */
|
|
7
|
+
clientId: string;
|
|
8
|
+
/** Redirect URI registered for this client. */
|
|
9
|
+
redirectUri: string;
|
|
10
|
+
/** PKCE code verifier generated during `login()` (RFC 7636). */
|
|
11
|
+
codeVerifier?: string;
|
|
12
|
+
}
|
|
13
|
+
/** Error thrown when the Hearth API returns an error. */
|
|
14
|
+
export declare class HearthError extends Error {
|
|
15
|
+
readonly status: number;
|
|
16
|
+
readonly body: unknown;
|
|
17
|
+
constructor(status: number, body: unknown);
|
|
18
|
+
}
|
|
19
|
+
/** Configuration for HearthApiClient. */
|
|
20
|
+
export interface HearthApiClientConfig {
|
|
21
|
+
baseUrl: string;
|
|
22
|
+
realmId: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Low-level Hearth HTTP API client for auth code flows, token management,
|
|
26
|
+
* JWKS retrieval, and live RBAC claim resolution.
|
|
27
|
+
*
|
|
28
|
+
* @deprecated Use {@link HearthClient} from `hearth-client.js` as the
|
|
29
|
+
* recommended entry point. This class is kept as a lower-level primitive.
|
|
30
|
+
*/
|
|
31
|
+
export declare class HearthApiClient {
|
|
32
|
+
private readonly baseUrl;
|
|
33
|
+
private readonly realmId;
|
|
34
|
+
constructor(config: HearthApiClientConfig);
|
|
35
|
+
/** POST /admin/bootstrap — create realm, admin user, tokens (dev mode only). */
|
|
36
|
+
static bootstrap(baseUrl: string): Promise<BootstrapResponse>;
|
|
37
|
+
/** POST /clients — register an OAuth 2.0 client. */
|
|
38
|
+
registerClient(params: RegisterClientParams): Promise<OAuthClient>;
|
|
39
|
+
/** POST /authorize — initiate an authorization code flow. */
|
|
40
|
+
authorize(params: AuthorizeParams): Promise<AuthorizeResponse>;
|
|
41
|
+
/** POST /token — exchange an authorization code for tokens. */
|
|
42
|
+
exchangeCode(params: TokenExchangeParams): Promise<TokenResponse>;
|
|
43
|
+
/**
|
|
44
|
+
* Handle a PKCE authorization-code callback (spec §7).
|
|
45
|
+
*
|
|
46
|
+
* Extracts the `code` from `callbackUrl`, exchanges it for tokens, then
|
|
47
|
+
* inspects the JWT's `token_type` claim before returning:
|
|
48
|
+
*
|
|
49
|
+
* - If `token_type === "required_action"`: throws {@link RequiredActionError}
|
|
50
|
+
* with `requiredActions` populated from the JWT's `required_actions` claim.
|
|
51
|
+
* - If the callback URL contains `required_action_redirect_uri`: throws
|
|
52
|
+
* {@link RequiredActionError} with `redirectUri` set to that value.
|
|
53
|
+
* - Otherwise: returns the token response normally.
|
|
54
|
+
*/
|
|
55
|
+
handleCallback(params: HandleCallbackParams): Promise<TokenResponse>;
|
|
56
|
+
/** POST /token — refresh tokens using a refresh token. */
|
|
57
|
+
refreshTokens(clientId: string, refreshToken: string): Promise<TokenResponse>;
|
|
58
|
+
/**
|
|
59
|
+
* GET /v1/me/permissions — fetch the freshly-resolved RBAC claim set
|
|
60
|
+
* for the bearer-token user.
|
|
61
|
+
*
|
|
62
|
+
* Unlike `hasPermission()` on a `createHearth()` client (which reads
|
|
63
|
+
* the cached set from the JWT), this call queries the server and
|
|
64
|
+
* reflects any role/group assignments made since the token was issued.
|
|
65
|
+
*/
|
|
66
|
+
permissions(accessToken: string): Promise<MePermissionsResponse>;
|
|
67
|
+
/** GET /userinfo — retrieve user claims using an access token. */
|
|
68
|
+
userinfo(accessToken: string): Promise<UserInfoResponse>;
|
|
69
|
+
/** GET /jwks — retrieve the JWKS document. */
|
|
70
|
+
jwks(): Promise<JwksDocument>;
|
|
71
|
+
/** GET /.well-known/openid-configuration — OIDC discovery document. */
|
|
72
|
+
discovery(): Promise<Record<string, unknown>>;
|
|
73
|
+
/** Creates an AdminClient using the given access token. */
|
|
74
|
+
admin(accessToken: string): AdminClient;
|
|
75
|
+
private post;
|
|
76
|
+
}
|
|
77
|
+
import { AdminClient } from "./admin.js";
|