trustflows-client 0.1.0-alpha.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SolidLab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # trustflows-client
2
+
3
+ Trustflows client helpers for Solid applications. This package provides a lightweight browser-first auth helper plus
4
+ UMA utilities for Trustflows-compatible services.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npm install trustflows-client
10
+ ```
11
+
12
+ ## Auth basics
13
+
14
+ Make sure a dereferenceable Client ID exists for this application. Make sure the resource server can get this JSON-LD
15
+ file, and that client_id is the same as the URL where this file is hosted.
16
+
17
+ ```json
18
+ {
19
+ "@context": "https://www.w3.org/ns/solid/oidc-context.jsonld",
20
+ "client_id": "http://localhost:8080/app/client-id.jsonld",
21
+ "client_name": "App Name",
22
+ "redirect_uris": [ "http://localhost:8080/app/logged-in-screen" ],
23
+ "post_logout_redirect_uris": [ "http://localhost:8080/app/logged-out-screen" ]
24
+ }
25
+ ```
26
+
27
+ This client ID file can then be used to log in a user, make sure to use the same redirect URI as in the file.
28
+
29
+ ```ts
30
+ import {
31
+ getDefaultAuth,
32
+ configureDefaultAuth
33
+ } from "trustflows-client";
34
+
35
+ configureDefaultAuth({
36
+ persistTokens: false,
37
+ });
38
+ const auth = getDefaultAuth();
39
+ await auth.login(
40
+ "https://idp.example",
41
+ "http://localhost:8080/app/client-id.jsonld",
42
+ "http://localhost:8080/app/logged-in-screen"
43
+ );
44
+
45
+ ```
46
+
47
+ `configureDefaultAuth()` and the `Auth` constructor accept these options:
48
+
49
+ - `fetch`: custom `fetch` implementation (useful for tests or custom networking).
50
+ - `storage`: storage provider (defaults to `sessionStorage` in the browser).
51
+ - `claimResolvers`: add or override UMA claim resolvers.
52
+ - `persistTokens`: whether to persist OIDC tokens in storage. Defaults to `true`.
53
+
54
+ Notes:
55
+ - `configureDefaultAuth()` must be called **before** `getDefaultAuth()`; after the default instance is created,
56
+ calling `configureDefaultAuth()` will throw.
57
+ - Token persistence stores an `oidc_tokens` JSON blob in `storage` and hydrates it on startup. Set
58
+ `persistTokens: false` to opt out.
59
+
60
+ After redirecting back to your application, you can handle the incoming redirect and create an authenticated fetch
61
+ function.
62
+
63
+ ```ts
64
+ import {
65
+ getDefaultAuth,
66
+ configureDefaultAuth
67
+ } from "trustflows-client";
68
+
69
+ configureDefaultAuth({
70
+ persistTokens: false,
71
+ });
72
+
73
+ const auth = getDefaultAuth();
74
+
75
+ await auth.handleIncomingRedirect();
76
+
77
+ const authFetch = auth.createAuthFetch();
78
+
79
+ const loggedIn = await auth.isLoggedIn();
80
+ ```
81
+
82
+ ## Custom UMA claim resolvers
83
+
84
+ You can add custom UMA claim resolvers in your application without modifying this package.
85
+
86
+ ```ts
87
+ import {
88
+ Auth,
89
+ type ClaimResolverDefinition,
90
+ } from "trustflows-client";
91
+
92
+ const myResolver: ClaimResolverDefinition = {
93
+ id: "custom-claim",
94
+ match: {
95
+ claim_type: "my-custom-claim",
96
+ issuer: "https://idp.example",
97
+ },
98
+ priority: 10,
99
+ resolve: async (requiredClaim, auth) => {
100
+ // Custom logic to resolve the claim
101
+ return {
102
+ claim_token: "custom-token",
103
+ claim_token_format: "custom-format",
104
+ };
105
+ },
106
+ };
107
+
108
+ auth.addClaimResolver(myResolver);
109
+ ```
110
+
111
+ The `id` field must be unique among all registered claim resolvers and is used for logging and debugging purposes.
112
+ `match` can use any of: `claim_token_format`, `claim_type`, `issuer`, `name`, `friendly_name`. Each field can be a
113
+ string, RegExp, or a predicate function. The `priority` field is used to determine the order in which resolvers are
114
+ tried (higher priority first). The `resolve` function is called when the resolver matches a claim request. It receives
115
+ the required claim and the auth entry that can be used to create the claim.
package/dist/auth.d.ts ADDED
@@ -0,0 +1,69 @@
1
+ import type { ClaimResolver, ClaimResolverDefinition, ClaimResolverRegistry } from './uma/claims/types';
2
+ export interface AuthOptions {
3
+ fetch?: typeof fetch;
4
+ storage?: Storage;
5
+ claimResolvers?: ClaimResolverRegistry;
6
+ persistTokens?: boolean;
7
+ }
8
+ interface OidcConfiguration {
9
+ authorization_endpoint?: string;
10
+ token_endpoint?: string;
11
+ end_session_endpoint?: string;
12
+ [key: string]: unknown;
13
+ }
14
+ interface UmaTokenCacheEntry {
15
+ token_type: string;
16
+ access_token: string;
17
+ expires_at?: number;
18
+ }
19
+ export declare class Auth {
20
+ oidcAccessToken?: string;
21
+ oidcToken?: string;
22
+ oidcRefreshToken?: string;
23
+ oidcTokenExpiry?: number;
24
+ webId?: string;
25
+ umaPermissionTokens: Map<string, UmaTokenCacheEntry>;
26
+ private readonly fetchFn;
27
+ private readonly storage?;
28
+ private readonly claimResolvers;
29
+ private readonly persistTokens;
30
+ private oidcIssuer?;
31
+ constructor(options?: AuthOptions);
32
+ addClaimResolver(format: string | ClaimResolverDefinition, resolver?: ClaimResolver): void;
33
+ get accessToken(): string | undefined;
34
+ set accessToken(value: string | undefined);
35
+ login(issuer: string, clientId: string, redirectUri: string, scope?: string): Promise<void>;
36
+ logout(postLogoutRedirectUri: string): Promise<void>;
37
+ handleIncomingRedirect(): Promise<boolean>;
38
+ isLoggedIn(): Promise<boolean>;
39
+ refreshTokens(tokenEndpoint: string, clientId: string): Promise<void>;
40
+ ensureValidToken(): Promise<void>;
41
+ getOidcConfig(issuer: string): Promise<OidcConfiguration>;
42
+ generateRandomString(bytes?: number): string;
43
+ pkceChallenge(verifier: string): Promise<string>;
44
+ createClaimToken(): Promise<string>;
45
+ createAuthFetch(): (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
46
+ clearUmaCache(): void;
47
+ clearOidcTokens(): void;
48
+ clearCache(): void;
49
+ extractWebId(idToken: string): string | undefined;
50
+ private buildUmaTokenKey;
51
+ private hydrateOidcTokens;
52
+ private persistOidcTokens;
53
+ private hydrateUmaTokens;
54
+ private persistUmaTokens;
55
+ getStoredUmaToken(resourceUrl: string, method?: string): UmaTokenCacheEntry | undefined;
56
+ storeUmaToken(resourceUrl: string, method: string, token: {
57
+ token_type: string;
58
+ access_token: string;
59
+ expires_in?: number;
60
+ }): void;
61
+ getClaimResolvers(): ClaimResolverRegistry;
62
+ getFetch(): typeof fetch;
63
+ private getStorage;
64
+ private getCrypto;
65
+ }
66
+ export declare function configureDefaultAuth(options: AuthOptions): void;
67
+ export declare function getDefaultAuth(): Auth;
68
+ export {};
69
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
package/dist/auth.js ADDED
@@ -0,0 +1,467 @@
1
+ import { createDefaultClaimResolvers } from './uma/claims/registry';
2
+ import { fetchWithUma, parseUmaAuthenticateHeader, } from './uma/utils';
3
+ export class Auth {
4
+ oidcAccessToken;
5
+ oidcToken;
6
+ oidcRefreshToken;
7
+ oidcTokenExpiry;
8
+ webId;
9
+ umaPermissionTokens = new Map();
10
+ fetchFn;
11
+ storage;
12
+ claimResolvers;
13
+ persistTokens;
14
+ oidcIssuer;
15
+ constructor(options = {}) {
16
+ this.fetchFn = options.fetch ?? fetch;
17
+ this.storage =
18
+ options.storage ??
19
+ (typeof sessionStorage === 'undefined' ? undefined : sessionStorage);
20
+ this.persistTokens = options.persistTokens ?? true;
21
+ this.claimResolvers = [
22
+ ...createDefaultClaimResolvers(),
23
+ ...options.claimResolvers ?? [],
24
+ ];
25
+ this.hydrateOidcTokens();
26
+ this.hydrateUmaTokens();
27
+ }
28
+ addClaimResolver(format, resolver) {
29
+ if (typeof format === 'string') {
30
+ if (!resolver) {
31
+ throw new Error('Claim resolver function is required.');
32
+ }
33
+ this.claimResolvers.push({
34
+ id: `custom:${format}`,
35
+ match: { claim_token_format: format },
36
+ resolve: resolver,
37
+ });
38
+ return;
39
+ }
40
+ this.claimResolvers.push(format);
41
+ }
42
+ get accessToken() {
43
+ return this.oidcAccessToken;
44
+ }
45
+ set accessToken(value) {
46
+ this.oidcAccessToken = value;
47
+ }
48
+ async login(issuer, clientId, redirectUri, scope = 'openid webid offline_access') {
49
+ const config = await this.getOidcConfig(issuer);
50
+ if (!config.authorization_endpoint) {
51
+ throw new Error('Missing authorization_endpoint in OIDC configuration');
52
+ }
53
+ const state = this.generateRandomString();
54
+ const codeVerifier = this.generateRandomString();
55
+ const codeChallenge = await this.pkceChallenge(codeVerifier);
56
+ const storage = this.getStorage();
57
+ storage.setItem('oidc_state', state);
58
+ storage.setItem('oidc_code_verifier', codeVerifier);
59
+ storage.setItem('oidc_issuer', issuer);
60
+ storage.setItem('oidc_client_id', clientId);
61
+ storage.setItem('oidc_redirect_uri', redirectUri);
62
+ this.oidcIssuer = issuer;
63
+ const params = new URLSearchParams({
64
+ response_type: 'code',
65
+ client_id: clientId,
66
+ redirect_uri: redirectUri,
67
+ scope,
68
+ state,
69
+ code_challenge: codeChallenge,
70
+ code_challenge_method: 'S256',
71
+ prompt: 'consent',
72
+ response_mode: 'query',
73
+ });
74
+ window.location.href = `${config.authorization_endpoint}?${params.toString()}`;
75
+ }
76
+ async logout(postLogoutRedirectUri) {
77
+ const resolvedIssuer = this.oidcIssuer ?? this.storage?.getItem('oidc_issuer') ?? undefined;
78
+ const idTokenHint = this.oidcToken;
79
+ this.clearCache();
80
+ if (!resolvedIssuer) {
81
+ return;
82
+ }
83
+ let config;
84
+ try {
85
+ config = await this.getOidcConfig(resolvedIssuer);
86
+ }
87
+ catch {
88
+ return;
89
+ }
90
+ if (!config.end_session_endpoint) {
91
+ return;
92
+ }
93
+ const params = new URLSearchParams();
94
+ if (idTokenHint) {
95
+ params.set('id_token_hint', idTokenHint);
96
+ }
97
+ params.set('post_logout_redirect_uri', postLogoutRedirectUri);
98
+ const logoutUrl = params.toString() ?
99
+ `${config.end_session_endpoint}?${params.toString()}` :
100
+ config.end_session_endpoint;
101
+ window.location.href = logoutUrl;
102
+ }
103
+ async handleIncomingRedirect() {
104
+ const url = new URL(window.location.href);
105
+ const code = url.searchParams.get('code');
106
+ const state = url.searchParams.get('state');
107
+ if (!code) {
108
+ return false;
109
+ }
110
+ const storage = this.getStorage();
111
+ const storedState = storage.getItem('oidc_state');
112
+ if (!state || state !== storedState) {
113
+ throw new Error('OIDC state mismatch');
114
+ }
115
+ const issuer = storage.getItem('oidc_issuer');
116
+ const clientId = storage.getItem('oidc_client_id');
117
+ const redirectUri = storage.getItem('oidc_redirect_uri');
118
+ const codeVerifier = storage.getItem('oidc_code_verifier');
119
+ if (!issuer || !clientId || !redirectUri || !codeVerifier) {
120
+ throw new Error('Missing stored OIDC parameters');
121
+ }
122
+ this.oidcIssuer = issuer;
123
+ const config = await this.getOidcConfig(issuer);
124
+ if (!config.token_endpoint) {
125
+ throw new Error('Missing token_endpoint in OIDC configuration');
126
+ }
127
+ const bodyParams = new URLSearchParams({
128
+ grant_type: 'authorization_code',
129
+ code,
130
+ redirect_uri: redirectUri,
131
+ client_id: clientId,
132
+ code_verifier: codeVerifier,
133
+ });
134
+ const tokenResp = await this.fetchFn(config.token_endpoint, {
135
+ method: 'POST',
136
+ headers: {
137
+ 'content-type': 'application/x-www-form-urlencoded',
138
+ },
139
+ body: bodyParams.toString(),
140
+ });
141
+ if (!tokenResp.ok) {
142
+ throw new Error(`Token endpoint error ${tokenResp.status}`);
143
+ }
144
+ const tokenJson = (await tokenResp.json());
145
+ this.oidcAccessToken = tokenJson.access_token;
146
+ this.oidcToken = tokenJson.id_token;
147
+ this.oidcRefreshToken = tokenJson.refresh_token;
148
+ if (tokenJson.expires_in) {
149
+ this.oidcTokenExpiry = Date.now() + tokenJson.expires_in * 1000;
150
+ }
151
+ if (tokenJson.id_token) {
152
+ this.webId = this.extractWebId(tokenJson.id_token);
153
+ }
154
+ this.persistOidcTokens();
155
+ window.history.replaceState({}, document.title, redirectUri);
156
+ return true;
157
+ }
158
+ async isLoggedIn() {
159
+ try {
160
+ await this.ensureValidToken();
161
+ }
162
+ catch {
163
+ return false;
164
+ }
165
+ return Boolean(this.oidcAccessToken ?? this.oidcToken);
166
+ }
167
+ async refreshTokens(tokenEndpoint, clientId) {
168
+ if (!this.oidcRefreshToken) {
169
+ return;
170
+ }
171
+ const bodyParams = new URLSearchParams({
172
+ grant_type: 'refresh_token',
173
+ refresh_token: this.oidcRefreshToken,
174
+ client_id: clientId,
175
+ });
176
+ const resp = await this.fetchFn(tokenEndpoint, {
177
+ method: 'POST',
178
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
179
+ body: bodyParams.toString(),
180
+ });
181
+ if (!resp.ok) {
182
+ throw new Error(`Refresh token endpoint error ${resp.status}`);
183
+ }
184
+ const json = (await resp.json());
185
+ if (json.access_token) {
186
+ this.oidcAccessToken = json.access_token;
187
+ }
188
+ if (json.id_token) {
189
+ this.oidcToken = json.id_token;
190
+ this.webId = this.extractWebId(json.id_token);
191
+ }
192
+ if (json.refresh_token) {
193
+ this.oidcRefreshToken = json.refresh_token;
194
+ }
195
+ if (json.expires_in) {
196
+ this.oidcTokenExpiry = Date.now() + json.expires_in * 1000;
197
+ }
198
+ this.persistOidcTokens();
199
+ }
200
+ async ensureValidToken() {
201
+ if (!this.oidcTokenExpiry || !this.oidcRefreshToken) {
202
+ return;
203
+ }
204
+ if (Date.now() < this.oidcTokenExpiry - 60_000) {
205
+ return;
206
+ }
207
+ const storage = this.getStorage();
208
+ const issuer = storage.getItem('oidc_issuer');
209
+ const clientId = storage.getItem('oidc_client_id');
210
+ if (!issuer || !clientId) {
211
+ return;
212
+ }
213
+ const config = await this.getOidcConfig(issuer);
214
+ if (!config.token_endpoint) {
215
+ return;
216
+ }
217
+ await this.refreshTokens(config.token_endpoint, clientId);
218
+ }
219
+ async getOidcConfig(issuer) {
220
+ const url = `${issuer.replace(/\/$/u, '')}/.well-known/openid-configuration`;
221
+ const res = await this.fetchFn(url);
222
+ if (!res.ok) {
223
+ throw new Error('Failed fetching OIDC configuration');
224
+ }
225
+ return (await res.json());
226
+ }
227
+ generateRandomString(bytes = 64) {
228
+ const cryptoObj = this.getCrypto();
229
+ const arr = new Uint8Array(bytes);
230
+ cryptoObj.getRandomValues(arr);
231
+ return [...arr]
232
+ .map((b) => `0${b.toString(16)}`.slice(-2))
233
+ .join('');
234
+ }
235
+ async pkceChallenge(verifier) {
236
+ const data = new TextEncoder().encode(verifier);
237
+ const digest = await this.getCrypto().subtle.digest('SHA-256', data);
238
+ const arr = new Uint8Array(digest);
239
+ const base64 = btoa(String.fromCodePoint(...arr));
240
+ return base64.replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/u, '');
241
+ }
242
+ async createClaimToken() {
243
+ await this.ensureValidToken();
244
+ if (!this.oidcToken) {
245
+ throw new Error('No OIDC ID token available for UMA claims.');
246
+ }
247
+ return this.oidcToken;
248
+ }
249
+ createAuthFetch() {
250
+ return async (input, init = {}) => {
251
+ const noTokenResponse = await this.fetchFn(input, init);
252
+ if (noTokenResponse.ok) {
253
+ return noTokenResponse;
254
+ }
255
+ if (noTokenResponse.status !== 401) {
256
+ return noTokenResponse;
257
+ }
258
+ const wwwAuthenticateHeader = noTokenResponse.headers.get('WWW-Authenticate');
259
+ const isUmaChallenge = wwwAuthenticateHeader
260
+ ?.trim()
261
+ .toLowerCase()
262
+ .startsWith('uma');
263
+ if (isUmaChallenge) {
264
+ const challenge = parseUmaAuthenticateHeader(noTokenResponse.headers);
265
+ if (!challenge) {
266
+ return noTokenResponse;
267
+ }
268
+ return fetchWithUma(input, init, {
269
+ auth: this,
270
+ challenge,
271
+ });
272
+ }
273
+ await this.ensureValidToken();
274
+ if (!this.oidcAccessToken) {
275
+ return noTokenResponse;
276
+ }
277
+ const headers = new Headers(init.headers);
278
+ headers.set('Authorization', `Bearer ${this.oidcAccessToken}`);
279
+ return this.fetchFn(input, { ...init, headers });
280
+ };
281
+ }
282
+ clearUmaCache() {
283
+ try {
284
+ this.umaPermissionTokens.clear();
285
+ this.storage?.removeItem('uma_permission_tokens');
286
+ }
287
+ catch {
288
+ /* Ignore */
289
+ }
290
+ }
291
+ clearOidcTokens() {
292
+ try {
293
+ this.oidcAccessToken = undefined;
294
+ this.oidcToken = undefined;
295
+ this.oidcRefreshToken = undefined;
296
+ this.oidcTokenExpiry = undefined;
297
+ this.webId = undefined;
298
+ this.oidcIssuer = undefined;
299
+ }
300
+ catch {
301
+ /* Ignore */
302
+ }
303
+ try {
304
+ const storage = this.storage;
305
+ storage?.removeItem('oidc_state');
306
+ storage?.removeItem('oidc_code_verifier');
307
+ storage?.removeItem('oidc_issuer');
308
+ storage?.removeItem('oidc_client_id');
309
+ storage?.removeItem('oidc_redirect_uri');
310
+ storage?.removeItem('oidc_tokens');
311
+ }
312
+ catch {
313
+ /* Ignore */
314
+ }
315
+ }
316
+ clearCache() {
317
+ this.clearUmaCache();
318
+ this.clearOidcTokens();
319
+ }
320
+ extractWebId(idToken) {
321
+ try {
322
+ const [, payload] = idToken.split('.');
323
+ if (!payload) {
324
+ return undefined;
325
+ }
326
+ const decoded = JSON.parse(atob(payload.replaceAll('-', '+').replaceAll('_', '/')));
327
+ return decoded.webid ?? decoded.sub;
328
+ }
329
+ catch {
330
+ return undefined;
331
+ }
332
+ }
333
+ buildUmaTokenKey(resourceUrl, method = 'GET') {
334
+ return `${method.toUpperCase()} ${resourceUrl}`;
335
+ }
336
+ hydrateOidcTokens() {
337
+ if (!this.persistTokens || !this.storage) {
338
+ return;
339
+ }
340
+ try {
341
+ const raw = this.storage.getItem('oidc_tokens');
342
+ if (!raw) {
343
+ return;
344
+ }
345
+ const data = JSON.parse(raw);
346
+ this.oidcAccessToken = data.access_token;
347
+ this.oidcToken = data.id_token;
348
+ this.oidcRefreshToken = data.refresh_token;
349
+ this.oidcTokenExpiry = data.expires_at;
350
+ this.webId = data.web_id ?? (data.id_token ? this.extractWebId(data.id_token) : undefined);
351
+ this.oidcIssuer = this.storage.getItem('oidc_issuer') ?? undefined;
352
+ }
353
+ catch {
354
+ /* Ignore */
355
+ }
356
+ }
357
+ persistOidcTokens() {
358
+ if (!this.persistTokens || !this.storage) {
359
+ return;
360
+ }
361
+ try {
362
+ const payload = {
363
+ access_token: this.oidcAccessToken,
364
+ id_token: this.oidcToken,
365
+ refresh_token: this.oidcRefreshToken,
366
+ expires_at: this.oidcTokenExpiry,
367
+ web_id: this.webId,
368
+ };
369
+ this.storage.setItem('oidc_tokens', JSON.stringify(payload));
370
+ }
371
+ catch {
372
+ /* Ignore */
373
+ }
374
+ }
375
+ hydrateUmaTokens() {
376
+ try {
377
+ const raw = this.storage?.getItem('uma_permission_tokens');
378
+ if (!raw) {
379
+ return;
380
+ }
381
+ const parsed = JSON.parse(raw);
382
+ const now = Date.now();
383
+ for (const [key, entry] of Object.entries(parsed)) {
384
+ if (!entry?.access_token) {
385
+ continue;
386
+ }
387
+ if (entry.expires_at && now > entry.expires_at) {
388
+ continue;
389
+ }
390
+ this.umaPermissionTokens.set(key, entry);
391
+ }
392
+ this.persistUmaTokens();
393
+ }
394
+ catch {
395
+ /* Ignore */
396
+ }
397
+ }
398
+ persistUmaTokens() {
399
+ const obj = {};
400
+ for (const [key, entry] of this.umaPermissionTokens.entries()) {
401
+ obj[key] = entry;
402
+ }
403
+ try {
404
+ this.storage?.setItem('uma_permission_tokens', JSON.stringify(obj));
405
+ }
406
+ catch {
407
+ /* Ignore */
408
+ }
409
+ }
410
+ getStoredUmaToken(resourceUrl, method = 'GET') {
411
+ const key = this.buildUmaTokenKey(resourceUrl, method);
412
+ const entry = this.umaPermissionTokens.get(key);
413
+ if (!entry) {
414
+ return undefined;
415
+ }
416
+ if (entry.expires_at && Date.now() > entry.expires_at) {
417
+ this.umaPermissionTokens.delete(key);
418
+ this.persistUmaTokens();
419
+ return undefined;
420
+ }
421
+ return entry;
422
+ }
423
+ storeUmaToken(resourceUrl, method, token) {
424
+ const key = this.buildUmaTokenKey(resourceUrl, method);
425
+ const expires_at = token.expires_in ?
426
+ Date.now() + token.expires_in * 1000 :
427
+ undefined;
428
+ this.umaPermissionTokens.set(key, {
429
+ token_type: token.token_type,
430
+ access_token: token.access_token,
431
+ expires_at,
432
+ });
433
+ this.persistUmaTokens();
434
+ }
435
+ getClaimResolvers() {
436
+ return [...this.claimResolvers];
437
+ }
438
+ getFetch() {
439
+ return this.fetchFn;
440
+ }
441
+ getStorage() {
442
+ if (!this.storage) {
443
+ throw new Error('Session storage is not available in this environment.');
444
+ }
445
+ return this.storage;
446
+ }
447
+ getCrypto() {
448
+ if (!globalThis.crypto) {
449
+ throw new Error('Web Crypto is not available in this environment.');
450
+ }
451
+ return globalThis.crypto;
452
+ }
453
+ }
454
+ let defaultAuth;
455
+ let defaultAuthOptions;
456
+ export function configureDefaultAuth(options) {
457
+ if (defaultAuth) {
458
+ throw new Error('Default Auth has already been created.');
459
+ }
460
+ defaultAuthOptions = options;
461
+ }
462
+ export function getDefaultAuth() {
463
+ if (!defaultAuth) {
464
+ defaultAuth = new Auth(defaultAuthOptions);
465
+ }
466
+ return defaultAuth;
467
+ }
@@ -0,0 +1,10 @@
1
+ export * from './auth';
2
+ export * from './types';
3
+ export * from './utils';
4
+ export * from './uma/claims/accessToken';
5
+ export * from './uma/claims/idToken';
6
+ export * from './uma/claims/registry';
7
+ export * from './uma/claims/types';
8
+ export * from './uma/types';
9
+ export * from './uma/utils';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AAExB,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}