@dloizides/auth-client 1.0.0 → 2.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/CHANGELOG.md +176 -0
- package/README.md +138 -42
- package/dist/AuthClient-BGr8L03W.d.mts +460 -0
- package/dist/AuthClient-D95OMajD.d.ts +460 -0
- package/dist/TokenResponse-CY1CaU2l.d.mts +59 -0
- package/dist/TokenResponse-CY1CaU2l.d.ts +59 -0
- package/dist/index.d.mts +207 -134
- package/dist/index.d.ts +207 -134
- package/dist/index.js +799 -52
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +785 -53
- package/dist/index.mjs.map +1 -1
- package/dist/oidc/index.d.mts +127 -0
- package/dist/oidc/index.d.ts +127 -0
- package/dist/oidc/index.js +192 -0
- package/dist/oidc/index.js.map +1 -0
- package/dist/oidc/index.mjs +184 -0
- package/dist/oidc/index.mjs.map +1 -0
- package/dist/react.d.mts +63 -0
- package/dist/react.d.ts +63 -0
- package/dist/react.js +65 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +58 -0
- package/dist/react.mjs.map +1 -0
- package/package.json +53 -5
package/dist/index.js
CHANGED
|
@@ -81,19 +81,141 @@ function parseBaseUrlFromIssuer(issuerUrl) {
|
|
|
81
81
|
return issuerUrl.substring(0, idx).replace(/\/$/, "");
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
// src/utils/normalizeTokenResponse.ts
|
|
85
|
+
function asString(value) {
|
|
86
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
87
|
+
}
|
|
88
|
+
function asNumber(value) {
|
|
89
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
90
|
+
}
|
|
91
|
+
function normalizeTokenResponse(raw) {
|
|
92
|
+
const accessToken = asString(raw.access_token);
|
|
93
|
+
if (accessToken === void 0) {
|
|
94
|
+
throw new Error("Token response missing access_token");
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
accessToken,
|
|
98
|
+
refreshToken: asString(raw.refresh_token),
|
|
99
|
+
idToken: asString(raw.id_token),
|
|
100
|
+
expiresIn: asNumber(raw.expires_in),
|
|
101
|
+
tokenType: asString(raw.token_type),
|
|
102
|
+
scope: asString(raw.scope)
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function tokenResponseToAuthTokens(response, now = Date.now()) {
|
|
106
|
+
return {
|
|
107
|
+
accessToken: response.accessToken,
|
|
108
|
+
refreshToken: response.refreshToken,
|
|
109
|
+
idToken: response.idToken,
|
|
110
|
+
expiresAt: computeExpiresAt(response.expiresIn, now)
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/events/AuthEventEmitter.ts
|
|
115
|
+
var AuthEventEmitter = class {
|
|
116
|
+
constructor() {
|
|
117
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
118
|
+
}
|
|
119
|
+
on(event, listener) {
|
|
120
|
+
let bucket = this.listeners.get(event);
|
|
121
|
+
if (bucket === void 0) {
|
|
122
|
+
bucket = /* @__PURE__ */ new Set();
|
|
123
|
+
this.listeners.set(event, bucket);
|
|
124
|
+
}
|
|
125
|
+
bucket.add(listener);
|
|
126
|
+
return () => {
|
|
127
|
+
const current = this.listeners.get(event);
|
|
128
|
+
if (current !== void 0) {
|
|
129
|
+
current.delete(listener);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
emit(event) {
|
|
134
|
+
const bucket = this.listeners.get(event);
|
|
135
|
+
if (bucket === void 0) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const snapshot = Array.from(bucket);
|
|
139
|
+
for (const listener of snapshot) {
|
|
140
|
+
listener();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** Remove all listeners. Useful for `AuthClient.dispose()` and tests. */
|
|
144
|
+
clear() {
|
|
145
|
+
this.listeners.clear();
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
84
149
|
// src/AuthClient.ts
|
|
85
150
|
var DEFAULT_SCOPE = "openid profile email";
|
|
151
|
+
var OFFLINE_ACCESS_SCOPE = "offline_access";
|
|
86
152
|
var AuthClient = class _AuthClient {
|
|
87
153
|
/**
|
|
88
154
|
* @throws Error when `baseUrl`, `realm`, or `clientId` is missing or empty.
|
|
89
155
|
*/
|
|
90
|
-
constructor(config, storage) {
|
|
156
|
+
constructor(config, storage, collaborators = {}) {
|
|
91
157
|
_AuthClient.validateConfig(config);
|
|
92
158
|
this.config = {
|
|
93
159
|
...config,
|
|
94
160
|
scope: config.scope ?? DEFAULT_SCOPE
|
|
95
161
|
};
|
|
162
|
+
this.directKcAuth = config.useDirectKcAuth === true;
|
|
96
163
|
this.tokenStorage = storage;
|
|
164
|
+
this.api = collaborators.api;
|
|
165
|
+
this.interceptor = collaborators.interceptor;
|
|
166
|
+
this.inactivityTracker = collaborators.inactivityTracker;
|
|
167
|
+
this.events = collaborators.events ?? new AuthEventEmitter();
|
|
168
|
+
this.onTokenAcquired = collaborators.onTokenAcquired;
|
|
169
|
+
this.onTokenRefreshed = collaborators.onTokenRefreshed;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Whether this client is configured to route auth flows directly to
|
|
173
|
+
* Keycloak (v2.1.0 direct-KC path) instead of through the proxied
|
|
174
|
+
* identity-api `/auth/*` endpoints.
|
|
175
|
+
*
|
|
176
|
+
* Apps can render conditionally on this — e.g. to swap a login form for
|
|
177
|
+
* a "Sign in with Keycloak" redirect button.
|
|
178
|
+
*/
|
|
179
|
+
isDirectMode() {
|
|
180
|
+
return this.directKcAuth;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Persist a token bundle produced by an external flow (e.g. the
|
|
184
|
+
* app-side `useKeycloakExchange` hook that consumes the shared
|
|
185
|
+
* `exchangeAuthorizationCode` primitive). Fires `onTokenAcquired` after
|
|
186
|
+
* persistence and marks the inactivity tracker active.
|
|
187
|
+
*
|
|
188
|
+
* Designed for the v2.1.0 direct-KC path where the PKCE code exchange
|
|
189
|
+
* happens in the app's React-Query hook (which needs `useDispatch`/etc.)
|
|
190
|
+
* but the token persistence + observability should still flow through
|
|
191
|
+
* the shared client.
|
|
192
|
+
*/
|
|
193
|
+
async acceptDirectKcTokens(response) {
|
|
194
|
+
const tokens = tokenResponseToAuthTokens(response);
|
|
195
|
+
await this.tokenStorage.write(tokens);
|
|
196
|
+
if (this.inactivityTracker !== void 0) {
|
|
197
|
+
await this.inactivityTracker.markActive();
|
|
198
|
+
}
|
|
199
|
+
if (this.onTokenAcquired !== void 0) {
|
|
200
|
+
this.onTokenAcquired(tokens);
|
|
201
|
+
}
|
|
202
|
+
return tokens;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Same as {@link acceptDirectKcTokens} but fires `onTokenRefreshed`.
|
|
206
|
+
* Use after a `refreshAccessToken()` swap to keep observability counts
|
|
207
|
+
* separated between "fresh login" and "silent refresh".
|
|
208
|
+
*/
|
|
209
|
+
async acceptDirectKcRefresh(response) {
|
|
210
|
+
const tokens = tokenResponseToAuthTokens(response);
|
|
211
|
+
await this.tokenStorage.write(tokens);
|
|
212
|
+
if (this.inactivityTracker !== void 0) {
|
|
213
|
+
await this.inactivityTracker.markActive();
|
|
214
|
+
}
|
|
215
|
+
if (this.onTokenRefreshed !== void 0) {
|
|
216
|
+
this.onTokenRefreshed(tokens);
|
|
217
|
+
}
|
|
218
|
+
return tokens;
|
|
97
219
|
}
|
|
98
220
|
/**
|
|
99
221
|
* Build an {@link AuthClient} from a standalone issuer URL by parsing the
|
|
@@ -102,7 +224,7 @@ var AuthClient = class _AuthClient {
|
|
|
102
224
|
*
|
|
103
225
|
* @throws Error when the issuer URL doesn't match `{base}/realms/{realm}`.
|
|
104
226
|
*/
|
|
105
|
-
static fromIssuerUrl(input, storage) {
|
|
227
|
+
static fromIssuerUrl(input, storage, collaborators = {}) {
|
|
106
228
|
const realm = parseRealmFromIssuer(input.issuerUrl);
|
|
107
229
|
const baseUrl = parseBaseUrlFromIssuer(input.issuerUrl);
|
|
108
230
|
if (realm === null || baseUrl === null || baseUrl === "") {
|
|
@@ -116,7 +238,8 @@ var AuthClient = class _AuthClient {
|
|
|
116
238
|
redirectUri: input.redirectUri,
|
|
117
239
|
scope: input.scope
|
|
118
240
|
},
|
|
119
|
-
storage
|
|
241
|
+
storage,
|
|
242
|
+
collaborators
|
|
120
243
|
);
|
|
121
244
|
}
|
|
122
245
|
static validateConfig(config) {
|
|
@@ -175,7 +298,7 @@ var AuthClient = class _AuthClient {
|
|
|
175
298
|
realm: this.realm,
|
|
176
299
|
clientId: this.clientId,
|
|
177
300
|
redirectUri: this.config.redirectUri,
|
|
178
|
-
scope: this.
|
|
301
|
+
scope: this.resolveScope(input.offlineAccess),
|
|
179
302
|
state: input.state,
|
|
180
303
|
codeChallenge: input.codeChallenge,
|
|
181
304
|
codeChallengeMethod: input.codeChallengeMethod
|
|
@@ -204,8 +327,258 @@ var AuthClient = class _AuthClient {
|
|
|
204
327
|
}
|
|
205
328
|
return tokens.accessToken;
|
|
206
329
|
}
|
|
330
|
+
/** Subscribe to lifecycle events (currently `sessionExpired` only). */
|
|
331
|
+
on(event, listener) {
|
|
332
|
+
return this.events.on(event, listener);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Boot-time wiring. Checks the inactivity tracker; if expired, clears
|
|
336
|
+
* tokens and emits `sessionExpired`. Returns whether a usable session
|
|
337
|
+
* survived.
|
|
338
|
+
*/
|
|
339
|
+
async init() {
|
|
340
|
+
if (this.inactivityTracker !== void 0) {
|
|
341
|
+
const expired = await this.inactivityTracker.isExpired();
|
|
342
|
+
if (expired) {
|
|
343
|
+
await this.tokenStorage.clear();
|
|
344
|
+
await this.inactivityTracker.clear();
|
|
345
|
+
this.events.emit("sessionExpired");
|
|
346
|
+
return { hasSession: false };
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const tokens = await this.tokenStorage.read();
|
|
350
|
+
return { hasSession: tokens !== null };
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Trigger a refresh via the configured interceptor. Returns the new tokens
|
|
354
|
+
* or `null` when the refresh failed (in which case `sessionExpired` has
|
|
355
|
+
* already fired).
|
|
356
|
+
*
|
|
357
|
+
* @throws Error when no interceptor is configured.
|
|
358
|
+
*/
|
|
359
|
+
async refresh() {
|
|
360
|
+
if (this.interceptor === void 0) {
|
|
361
|
+
throw new Error("AuthClient.refresh: no RefreshInterceptor configured");
|
|
362
|
+
}
|
|
363
|
+
const tokens = await this.interceptor.refreshTokens();
|
|
364
|
+
if (tokens !== null && this.onTokenRefreshed !== void 0) {
|
|
365
|
+
this.onTokenRefreshed(tokens);
|
|
366
|
+
}
|
|
367
|
+
return tokens;
|
|
368
|
+
}
|
|
369
|
+
async loginWithOtp(input) {
|
|
370
|
+
return this.runLogin(this.requireApi().loginWithOtp({
|
|
371
|
+
email: input.email,
|
|
372
|
+
otp: input.otp,
|
|
373
|
+
tenantId: input.tenantId,
|
|
374
|
+
offlineAccess: input.offlineAccess ?? false
|
|
375
|
+
}));
|
|
376
|
+
}
|
|
377
|
+
async loginWithPassword(input) {
|
|
378
|
+
return this.runLogin(this.requireApi().loginWithPassword({
|
|
379
|
+
email: input.email,
|
|
380
|
+
password: input.password,
|
|
381
|
+
tenantId: input.tenantId,
|
|
382
|
+
offlineAccess: input.offlineAccess ?? false
|
|
383
|
+
}));
|
|
384
|
+
}
|
|
385
|
+
async logout(options = {}) {
|
|
386
|
+
const api = this.requireApi();
|
|
387
|
+
try {
|
|
388
|
+
await api.logout(options.everywhere ?? false);
|
|
389
|
+
} finally {
|
|
390
|
+
await this.tokenStorage.clear();
|
|
391
|
+
if (this.inactivityTracker !== void 0) {
|
|
392
|
+
await this.inactivityTracker.clear();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
async requestPasswordReset(input) {
|
|
397
|
+
return this.requireApi().forgotPassword({ email: input.email, tenantId: input.tenantId });
|
|
398
|
+
}
|
|
399
|
+
async confirmPasswordReset(input) {
|
|
400
|
+
return this.requireApi().resetPassword({ token: input.token, newPassword: input.newPassword });
|
|
401
|
+
}
|
|
402
|
+
/** Internal: run a login HTTP call, persist tokens, mark inactivity-active. */
|
|
403
|
+
async runLogin(promise) {
|
|
404
|
+
const raw = await promise;
|
|
405
|
+
if (typeof raw.access_token !== "string" || raw.access_token === "") {
|
|
406
|
+
throw new Error("AuthClient: login response missing access_token");
|
|
407
|
+
}
|
|
408
|
+
const normalized = normalizeTokenResponse({ ...raw, access_token: raw.access_token });
|
|
409
|
+
const tokens = tokenResponseToAuthTokens(normalized);
|
|
410
|
+
await this.tokenStorage.write(tokens);
|
|
411
|
+
if (this.inactivityTracker !== void 0) {
|
|
412
|
+
await this.inactivityTracker.markActive();
|
|
413
|
+
}
|
|
414
|
+
if (this.onTokenAcquired !== void 0) {
|
|
415
|
+
this.onTokenAcquired(tokens);
|
|
416
|
+
}
|
|
417
|
+
return tokens;
|
|
418
|
+
}
|
|
419
|
+
requireApi() {
|
|
420
|
+
if (this.api === void 0) {
|
|
421
|
+
throw new Error("AuthClient: no AuthApiClient configured");
|
|
422
|
+
}
|
|
423
|
+
return this.api;
|
|
424
|
+
}
|
|
425
|
+
resolveScope(offlineAccess) {
|
|
426
|
+
if (offlineAccess !== true) {
|
|
427
|
+
return this.scope;
|
|
428
|
+
}
|
|
429
|
+
if (this.scope.includes(OFFLINE_ACCESS_SCOPE)) {
|
|
430
|
+
return this.scope;
|
|
431
|
+
}
|
|
432
|
+
return `${this.scope} ${OFFLINE_ACCESS_SCOPE}`.trim();
|
|
433
|
+
}
|
|
207
434
|
};
|
|
208
435
|
|
|
436
|
+
// src/oidc/discovery.ts
|
|
437
|
+
var cache = /* @__PURE__ */ new Map();
|
|
438
|
+
function normalizeIssuer(issuerUrl) {
|
|
439
|
+
return issuerUrl.replace(/\/$/, "");
|
|
440
|
+
}
|
|
441
|
+
function isOidcDiscoveryDocument(data) {
|
|
442
|
+
if (data === null || typeof data !== "object") {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
const d = data;
|
|
446
|
+
return typeof d.issuer === "string" && d.issuer !== "" && typeof d.authorization_endpoint === "string" && d.authorization_endpoint !== "" && typeof d.token_endpoint === "string" && d.token_endpoint !== "";
|
|
447
|
+
}
|
|
448
|
+
async function fetchDiscoveryDocument(input) {
|
|
449
|
+
const key = normalizeIssuer(input.issuerUrl);
|
|
450
|
+
const cached = cache.get(key);
|
|
451
|
+
if (cached !== void 0) {
|
|
452
|
+
return cached;
|
|
453
|
+
}
|
|
454
|
+
const response = await input.http({
|
|
455
|
+
url: `${key}/.well-known/openid-configuration`,
|
|
456
|
+
method: "GET"
|
|
457
|
+
});
|
|
458
|
+
if (!response.ok) {
|
|
459
|
+
throw new Error(
|
|
460
|
+
`OIDC discovery failed: ${String(response.status)} for ${key}`
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
if (!isOidcDiscoveryDocument(response.data)) {
|
|
464
|
+
throw new Error(`OIDC discovery returned invalid metadata for ${key}`);
|
|
465
|
+
}
|
|
466
|
+
cache.set(key, response.data);
|
|
467
|
+
return response.data;
|
|
468
|
+
}
|
|
469
|
+
function clearDiscoveryCache() {
|
|
470
|
+
cache.clear();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// src/oidc/pkce.ts
|
|
474
|
+
var VERIFIER_MIN_LENGTH = 43;
|
|
475
|
+
var VERIFIER_MAX_LENGTH = 128;
|
|
476
|
+
var DEFAULT_VERIFIER_LENGTH = 64;
|
|
477
|
+
var RANDOM_BYTES_PER_CHAR = 1;
|
|
478
|
+
var UNRESERVED_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
479
|
+
function getCrypto() {
|
|
480
|
+
const c = globalThis.crypto;
|
|
481
|
+
if (c === void 0 || c.subtle === void 0) {
|
|
482
|
+
throw new Error("pkce: globalThis.crypto.subtle is required (Node 16+ / modern browser)");
|
|
483
|
+
}
|
|
484
|
+
return c;
|
|
485
|
+
}
|
|
486
|
+
function assertVerifierLength(length) {
|
|
487
|
+
if (length < VERIFIER_MIN_LENGTH || length > VERIFIER_MAX_LENGTH) {
|
|
488
|
+
throw new Error(`pkce: code_verifier length must be ${String(VERIFIER_MIN_LENGTH)}-${String(VERIFIER_MAX_LENGTH)} chars (RFC 7636)`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function base64UrlEncode(buffer) {
|
|
492
|
+
const bytes = new Uint8Array(buffer);
|
|
493
|
+
let binary = "";
|
|
494
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
495
|
+
binary += String.fromCharCode(bytes[i]);
|
|
496
|
+
}
|
|
497
|
+
const b64 = globalThis.btoa?.(binary) ?? Buffer.from(binary, "binary").toString("base64");
|
|
498
|
+
let end = b64.length;
|
|
499
|
+
while (end > 0 && b64.charCodeAt(end - 1) === "=".charCodeAt(0)) {
|
|
500
|
+
end -= 1;
|
|
501
|
+
}
|
|
502
|
+
return b64.slice(0, end).replace(/\+/g, "-").replace(/\//g, "_");
|
|
503
|
+
}
|
|
504
|
+
function generateCodeVerifier(length = DEFAULT_VERIFIER_LENGTH) {
|
|
505
|
+
assertVerifierLength(length);
|
|
506
|
+
const crypto = getCrypto();
|
|
507
|
+
const bytes = new Uint8Array(length * RANDOM_BYTES_PER_CHAR);
|
|
508
|
+
crypto.getRandomValues(bytes);
|
|
509
|
+
let out = "";
|
|
510
|
+
for (let i = 0; i < length; i++) {
|
|
511
|
+
const byte = bytes[i];
|
|
512
|
+
out += UNRESERVED_CHARS[byte % UNRESERVED_CHARS.length];
|
|
513
|
+
}
|
|
514
|
+
return out;
|
|
515
|
+
}
|
|
516
|
+
async function deriveCodeChallenge(verifier) {
|
|
517
|
+
assertVerifierLength(verifier.length);
|
|
518
|
+
const crypto = getCrypto();
|
|
519
|
+
const data = new TextEncoder().encode(verifier);
|
|
520
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
521
|
+
return base64UrlEncode(digest);
|
|
522
|
+
}
|
|
523
|
+
async function generatePkcePair(length) {
|
|
524
|
+
const codeVerifier = generateCodeVerifier(length);
|
|
525
|
+
const codeChallenge = await deriveCodeChallenge(codeVerifier);
|
|
526
|
+
return { codeVerifier, codeChallenge, codeChallengeMethod: "S256" };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// src/utils/buildTokenRequestBody.ts
|
|
530
|
+
function buildAuthorizationCodeBody(input) {
|
|
531
|
+
return new URLSearchParams({
|
|
532
|
+
client_id: input.clientId,
|
|
533
|
+
grant_type: "authorization_code",
|
|
534
|
+
code: input.code,
|
|
535
|
+
redirect_uri: input.redirectUri,
|
|
536
|
+
code_verifier: input.codeVerifier
|
|
537
|
+
}).toString();
|
|
538
|
+
}
|
|
539
|
+
function buildRefreshTokenBody(input) {
|
|
540
|
+
return new URLSearchParams({
|
|
541
|
+
client_id: input.clientId,
|
|
542
|
+
grant_type: "refresh_token",
|
|
543
|
+
refresh_token: input.refreshToken
|
|
544
|
+
}).toString();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// src/oidc/tokenExchange.ts
|
|
548
|
+
var FORM_HEADERS = {
|
|
549
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
550
|
+
};
|
|
551
|
+
async function postTokenEndpoint(http, url, body) {
|
|
552
|
+
const response = await http({
|
|
553
|
+
url,
|
|
554
|
+
method: "POST",
|
|
555
|
+
headers: FORM_HEADERS,
|
|
556
|
+
body
|
|
557
|
+
});
|
|
558
|
+
if (!response.ok) {
|
|
559
|
+
throw new Error(`token endpoint POST failed: ${String(response.status)}`);
|
|
560
|
+
}
|
|
561
|
+
return normalizeTokenResponse(response.data);
|
|
562
|
+
}
|
|
563
|
+
async function exchangeAuthorizationCode(input) {
|
|
564
|
+
const url = buildTokenEndpoint(input.baseUrl, input.realm);
|
|
565
|
+
const body = buildAuthorizationCodeBody({
|
|
566
|
+
clientId: input.clientId,
|
|
567
|
+
code: input.code,
|
|
568
|
+
redirectUri: input.redirectUri,
|
|
569
|
+
codeVerifier: input.codeVerifier
|
|
570
|
+
});
|
|
571
|
+
return postTokenEndpoint(input.http, url, body);
|
|
572
|
+
}
|
|
573
|
+
async function refreshAccessToken(input) {
|
|
574
|
+
const url = buildTokenEndpoint(input.baseUrl, input.realm);
|
|
575
|
+
const body = buildRefreshTokenBody({
|
|
576
|
+
clientId: input.clientId,
|
|
577
|
+
refreshToken: input.refreshToken
|
|
578
|
+
});
|
|
579
|
+
return postTokenEndpoint(input.http, url, body);
|
|
580
|
+
}
|
|
581
|
+
|
|
209
582
|
// src/types/KeycloakRoles.ts
|
|
210
583
|
var KeycloakRoles = /* @__PURE__ */ ((KeycloakRoles2) => {
|
|
211
584
|
KeycloakRoles2["SuperUser"] = "superUser";
|
|
@@ -279,6 +652,413 @@ var InMemoryTokenStorage = class {
|
|
|
279
652
|
}
|
|
280
653
|
};
|
|
281
654
|
|
|
655
|
+
// src/storage/CookieTokenStorage.ts
|
|
656
|
+
var CookieTokenStorage = class {
|
|
657
|
+
constructor() {
|
|
658
|
+
this.accessToken = null;
|
|
659
|
+
this.idToken = void 0;
|
|
660
|
+
this.expiresAt = 0;
|
|
661
|
+
}
|
|
662
|
+
read() {
|
|
663
|
+
if (this.accessToken === null) {
|
|
664
|
+
return Promise.resolve(null);
|
|
665
|
+
}
|
|
666
|
+
const tokens = {
|
|
667
|
+
accessToken: this.accessToken,
|
|
668
|
+
idToken: this.idToken,
|
|
669
|
+
expiresAt: this.expiresAt
|
|
670
|
+
};
|
|
671
|
+
return Promise.resolve(tokens);
|
|
672
|
+
}
|
|
673
|
+
write(tokens) {
|
|
674
|
+
this.accessToken = tokens.accessToken;
|
|
675
|
+
this.idToken = tokens.idToken;
|
|
676
|
+
this.expiresAt = tokens.expiresAt;
|
|
677
|
+
return Promise.resolve();
|
|
678
|
+
}
|
|
679
|
+
clear() {
|
|
680
|
+
this.accessToken = null;
|
|
681
|
+
this.idToken = void 0;
|
|
682
|
+
this.expiresAt = 0;
|
|
683
|
+
return Promise.resolve();
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
// src/storage/SecureStoreTokenStorage.ts
|
|
688
|
+
var DEFAULT_PREFIX = "auth";
|
|
689
|
+
var ACCESS_KEY = "access";
|
|
690
|
+
var REFRESH_KEY = "refresh";
|
|
691
|
+
var ID_KEY = "id";
|
|
692
|
+
var EXPIRES_KEY = "expiresAt";
|
|
693
|
+
var SecureStoreTokenStorage = class {
|
|
694
|
+
constructor(options) {
|
|
695
|
+
this.secureStore = options.secureStore;
|
|
696
|
+
this.prefix = options.keyPrefix ?? DEFAULT_PREFIX;
|
|
697
|
+
this.requireAuthentication = options.requireAuthentication ?? false;
|
|
698
|
+
this.biometricGate = options.biometricGate;
|
|
699
|
+
}
|
|
700
|
+
async read() {
|
|
701
|
+
if (this.shouldRunBiometricGate()) {
|
|
702
|
+
await this.biometricGate.unlock();
|
|
703
|
+
}
|
|
704
|
+
const readOptions = this.requireAuthentication ? { requireAuthentication: true } : void 0;
|
|
705
|
+
const accessToken = await this.secureStore.getItemAsync(this.fullKey(ACCESS_KEY), readOptions);
|
|
706
|
+
if (accessToken === null) {
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
const refreshTokenRaw = await this.secureStore.getItemAsync(this.fullKey(REFRESH_KEY), readOptions);
|
|
710
|
+
const idTokenRaw = await this.secureStore.getItemAsync(this.fullKey(ID_KEY));
|
|
711
|
+
const expiresAtRaw = await this.secureStore.getItemAsync(this.fullKey(EXPIRES_KEY));
|
|
712
|
+
const expiresAt = parseExpiresAt(expiresAtRaw);
|
|
713
|
+
return {
|
|
714
|
+
accessToken,
|
|
715
|
+
refreshToken: refreshTokenRaw === null ? void 0 : refreshTokenRaw,
|
|
716
|
+
idToken: idTokenRaw === null ? void 0 : idTokenRaw,
|
|
717
|
+
expiresAt
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
async write(tokens) {
|
|
721
|
+
await this.secureStore.setItemAsync(this.fullKey(ACCESS_KEY), tokens.accessToken);
|
|
722
|
+
if (tokens.refreshToken !== void 0 && tokens.refreshToken !== "") {
|
|
723
|
+
await this.secureStore.setItemAsync(this.fullKey(REFRESH_KEY), tokens.refreshToken);
|
|
724
|
+
} else {
|
|
725
|
+
await this.secureStore.deleteItemAsync(this.fullKey(REFRESH_KEY));
|
|
726
|
+
}
|
|
727
|
+
if (tokens.idToken !== void 0 && tokens.idToken !== "") {
|
|
728
|
+
await this.secureStore.setItemAsync(this.fullKey(ID_KEY), tokens.idToken);
|
|
729
|
+
} else {
|
|
730
|
+
await this.secureStore.deleteItemAsync(this.fullKey(ID_KEY));
|
|
731
|
+
}
|
|
732
|
+
await this.secureStore.setItemAsync(this.fullKey(EXPIRES_KEY), String(tokens.expiresAt));
|
|
733
|
+
}
|
|
734
|
+
async clear() {
|
|
735
|
+
await this.secureStore.deleteItemAsync(this.fullKey(ACCESS_KEY));
|
|
736
|
+
await this.secureStore.deleteItemAsync(this.fullKey(REFRESH_KEY));
|
|
737
|
+
await this.secureStore.deleteItemAsync(this.fullKey(ID_KEY));
|
|
738
|
+
await this.secureStore.deleteItemAsync(this.fullKey(EXPIRES_KEY));
|
|
739
|
+
}
|
|
740
|
+
shouldRunBiometricGate() {
|
|
741
|
+
return this.biometricGate !== void 0 && this.biometricGate.isEnabled();
|
|
742
|
+
}
|
|
743
|
+
fullKey(slot) {
|
|
744
|
+
return `${this.prefix}.${slot}`;
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
function parseExpiresAt(raw) {
|
|
748
|
+
if (raw === null || raw === "") {
|
|
749
|
+
return 0;
|
|
750
|
+
}
|
|
751
|
+
const parsed = Number(raw);
|
|
752
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// src/biometric/BiometricGate.ts
|
|
756
|
+
var DEFAULT_PROMPT = "Unlock to continue";
|
|
757
|
+
var DEFAULT_MAX_FAILURES = 3;
|
|
758
|
+
var BiometricGate = class {
|
|
759
|
+
constructor(options) {
|
|
760
|
+
this.enabled = false;
|
|
761
|
+
this.failureCount = 0;
|
|
762
|
+
this.hydrated = false;
|
|
763
|
+
this.localAuth = options.localAuth;
|
|
764
|
+
this.flagStore = options.flagStore;
|
|
765
|
+
this.promptMessage = options.promptMessage ?? DEFAULT_PROMPT;
|
|
766
|
+
this.maxFailures = options.maxFailures ?? DEFAULT_MAX_FAILURES;
|
|
767
|
+
}
|
|
768
|
+
/** Hardware present AND a fingerprint/face ID is enrolled. */
|
|
769
|
+
async isAvailable() {
|
|
770
|
+
const hasHardware = await this.localAuth.hasHardwareAsync();
|
|
771
|
+
if (!hasHardware) {
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
return this.localAuth.isEnrolledAsync();
|
|
775
|
+
}
|
|
776
|
+
/** Synchronous read of the current enabled flag (post-hydration). */
|
|
777
|
+
isEnabled() {
|
|
778
|
+
return this.enabled;
|
|
779
|
+
}
|
|
780
|
+
/** Read the persisted opt-in flag once at app boot. Idempotent. */
|
|
781
|
+
async hydrate() {
|
|
782
|
+
if (this.hydrated) {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
this.hydrated = true;
|
|
786
|
+
if (this.flagStore !== void 0) {
|
|
787
|
+
this.enabled = await this.flagStore.read();
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Toggle biometric requirement. Persists via {@link BiometricFlagStore} when
|
|
792
|
+
* configured. Resets the failure counter so a re-enable starts fresh.
|
|
793
|
+
*/
|
|
794
|
+
async setEnabled(enabled) {
|
|
795
|
+
this.enabled = enabled;
|
|
796
|
+
this.failureCount = 0;
|
|
797
|
+
if (this.flagStore !== void 0) {
|
|
798
|
+
await this.flagStore.write(enabled);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
/** Reset the failure counter. Tests + consumer recovery flows. */
|
|
802
|
+
resetFailures() {
|
|
803
|
+
this.failureCount = 0;
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* One-shot biometric prompt. Returns `true` on success. Does NOT throw on
|
|
807
|
+
* failure or update the failure counter — useful for action confirmation.
|
|
808
|
+
*/
|
|
809
|
+
async prompt() {
|
|
810
|
+
const result = await this.localAuth.authenticateAsync({
|
|
811
|
+
promptMessage: this.promptMessage
|
|
812
|
+
});
|
|
813
|
+
return result.success;
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Required pre-condition for sensitive token reads. No-op when disabled.
|
|
817
|
+
*
|
|
818
|
+
* @throws Error after {@link maxFailures} consecutive failures.
|
|
819
|
+
* @throws Error on a single failure (lower in the count, but still throws so
|
|
820
|
+
* `SecureStoreTokenStorage.read()` short-circuits).
|
|
821
|
+
*/
|
|
822
|
+
async unlock() {
|
|
823
|
+
if (!this.enabled) {
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
const result = await this.localAuth.authenticateAsync({
|
|
827
|
+
promptMessage: this.promptMessage
|
|
828
|
+
});
|
|
829
|
+
if (result.success) {
|
|
830
|
+
this.failureCount = 0;
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
this.failureCount += 1;
|
|
834
|
+
if (this.failureCount >= this.maxFailures) {
|
|
835
|
+
throw new Error("Biometric authentication failed; locked out");
|
|
836
|
+
}
|
|
837
|
+
throw new Error("Biometric authentication failed");
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
// src/interceptor/RefreshInterceptor.ts
|
|
842
|
+
var RefreshInterceptor = class {
|
|
843
|
+
constructor(options) {
|
|
844
|
+
this.inflight = null;
|
|
845
|
+
this.storage = options.storage;
|
|
846
|
+
this.refresh = options.refresh;
|
|
847
|
+
this.events = options.events;
|
|
848
|
+
this.onRefreshSuccess = options.onRefreshSuccess;
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Trigger (or join) a refresh. Returns the new tokens, or `null` if the
|
|
852
|
+
* refresh failed — in which case storage has already been cleared and
|
|
853
|
+
* `sessionExpired` already fired.
|
|
854
|
+
*/
|
|
855
|
+
async refreshTokens() {
|
|
856
|
+
if (this.inflight !== null) {
|
|
857
|
+
return this.inflight;
|
|
858
|
+
}
|
|
859
|
+
this.inflight = this.runRefresh();
|
|
860
|
+
try {
|
|
861
|
+
return await this.inflight;
|
|
862
|
+
} finally {
|
|
863
|
+
this.inflight = null;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Whether a refresh is currently in flight. Exposed for tests / debug.
|
|
868
|
+
*/
|
|
869
|
+
get isRefreshing() {
|
|
870
|
+
return this.inflight !== null;
|
|
871
|
+
}
|
|
872
|
+
async runRefresh() {
|
|
873
|
+
const current = await this.storage.read();
|
|
874
|
+
let next;
|
|
875
|
+
try {
|
|
876
|
+
next = await this.refresh(current);
|
|
877
|
+
} catch {
|
|
878
|
+
await this.failHard();
|
|
879
|
+
return null;
|
|
880
|
+
}
|
|
881
|
+
if (next === null) {
|
|
882
|
+
await this.failHard();
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
await this.storage.write(next);
|
|
886
|
+
if (this.onRefreshSuccess !== void 0) {
|
|
887
|
+
await this.onRefreshSuccess(next);
|
|
888
|
+
}
|
|
889
|
+
return next;
|
|
890
|
+
}
|
|
891
|
+
async failHard() {
|
|
892
|
+
await this.storage.clear();
|
|
893
|
+
this.events.emit("sessionExpired");
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
// src/inactivity/InactivityTracker.ts
|
|
898
|
+
var DEFAULT_MAX_DAYS = 90;
|
|
899
|
+
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
900
|
+
var InactivityTracker = class {
|
|
901
|
+
constructor(options) {
|
|
902
|
+
this.store = options.store;
|
|
903
|
+
const days = options.maxInactivityDays ?? DEFAULT_MAX_DAYS;
|
|
904
|
+
this.maxInactivityMs = days * MS_PER_DAY;
|
|
905
|
+
this.now = options.now ?? Date.now;
|
|
906
|
+
}
|
|
907
|
+
async markActive(timestamp) {
|
|
908
|
+
await this.store.write(timestamp ?? this.now());
|
|
909
|
+
}
|
|
910
|
+
async getLastActive() {
|
|
911
|
+
return this.store.read();
|
|
912
|
+
}
|
|
913
|
+
async isExpired() {
|
|
914
|
+
const last = await this.store.read();
|
|
915
|
+
if (last === null) {
|
|
916
|
+
return false;
|
|
917
|
+
}
|
|
918
|
+
return this.now() - last > this.maxInactivityMs;
|
|
919
|
+
}
|
|
920
|
+
async clear() {
|
|
921
|
+
await this.store.clear();
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
// src/http/HttpClient.ts
|
|
926
|
+
function createFetchHttpClient(fetchImpl) {
|
|
927
|
+
return async (request) => {
|
|
928
|
+
const init = {
|
|
929
|
+
method: request.method,
|
|
930
|
+
headers: request.headers,
|
|
931
|
+
body: request.body
|
|
932
|
+
};
|
|
933
|
+
if (request.credentials !== void 0) {
|
|
934
|
+
init.credentials = request.credentials;
|
|
935
|
+
}
|
|
936
|
+
const response = await fetchImpl(request.url, init);
|
|
937
|
+
let data;
|
|
938
|
+
if (response.status !== 204) {
|
|
939
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
940
|
+
if (contentType.includes("application/json")) {
|
|
941
|
+
data = await response.json();
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return {
|
|
945
|
+
status: response.status,
|
|
946
|
+
ok: response.ok,
|
|
947
|
+
data
|
|
948
|
+
};
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// src/api/AuthApiClient.ts
|
|
953
|
+
var AuthApiClient = class {
|
|
954
|
+
constructor(options) {
|
|
955
|
+
this.http = options.http;
|
|
956
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
957
|
+
this.getAccessToken = options.getAccessToken;
|
|
958
|
+
this.useCredentials = options.useCredentials ?? false;
|
|
959
|
+
}
|
|
960
|
+
loginWithOtp(request) {
|
|
961
|
+
return this.postLogin("/auth/verify-otp", request);
|
|
962
|
+
}
|
|
963
|
+
loginWithPassword(request) {
|
|
964
|
+
return this.postLogin("/auth/login", request);
|
|
965
|
+
}
|
|
966
|
+
/** Web cookie-flow refresh. Sends no body; cookie travels via `credentials`. */
|
|
967
|
+
async refreshCookie() {
|
|
968
|
+
const response = await this.http({
|
|
969
|
+
url: `${this.baseUrl}/auth/refresh-cookie`,
|
|
970
|
+
method: "POST",
|
|
971
|
+
headers: { "content-type": "application/json" },
|
|
972
|
+
credentials: this.useCredentials ? "include" : void 0
|
|
973
|
+
});
|
|
974
|
+
if (!response.ok) {
|
|
975
|
+
throw new Error(`refresh-cookie failed with status ${response.status}`);
|
|
976
|
+
}
|
|
977
|
+
return response.data ?? {};
|
|
978
|
+
}
|
|
979
|
+
async logout(everywhere = false) {
|
|
980
|
+
const url = everywhere ? `${this.baseUrl}/auth/logout?everywhere=true` : `${this.baseUrl}/auth/logout`;
|
|
981
|
+
const response = await this.http({
|
|
982
|
+
url,
|
|
983
|
+
method: "POST",
|
|
984
|
+
headers: await this.authHeaders(),
|
|
985
|
+
credentials: this.useCredentials ? "include" : void 0
|
|
986
|
+
});
|
|
987
|
+
if (!response.ok) {
|
|
988
|
+
throw new Error(`logout failed with status ${response.status}`);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
async forgotPassword(request) {
|
|
992
|
+
const response = await this.http({
|
|
993
|
+
url: `${this.baseUrl}/auth/forgot-password`,
|
|
994
|
+
method: "POST",
|
|
995
|
+
headers: { "content-type": "application/json" },
|
|
996
|
+
body: JSON.stringify(request)
|
|
997
|
+
});
|
|
998
|
+
if (!response.ok) {
|
|
999
|
+
throw new Error(`forgot-password failed with status ${response.status}`);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
async resetPassword(request) {
|
|
1003
|
+
const response = await this.http({
|
|
1004
|
+
url: `${this.baseUrl}/auth/reset-password`,
|
|
1005
|
+
method: "POST",
|
|
1006
|
+
headers: { "content-type": "application/json" },
|
|
1007
|
+
body: JSON.stringify(request)
|
|
1008
|
+
});
|
|
1009
|
+
if (!response.ok) {
|
|
1010
|
+
throw new Error(`reset-password failed with status ${response.status}`);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
async listSessions() {
|
|
1014
|
+
const response = await this.http({
|
|
1015
|
+
url: `${this.baseUrl}/me/sessions`,
|
|
1016
|
+
method: "GET",
|
|
1017
|
+
headers: await this.authHeaders(),
|
|
1018
|
+
credentials: this.useCredentials ? "include" : void 0
|
|
1019
|
+
});
|
|
1020
|
+
if (!response.ok) {
|
|
1021
|
+
throw new Error(`listSessions failed with status ${response.status}`);
|
|
1022
|
+
}
|
|
1023
|
+
return Array.isArray(response.data) ? response.data : [];
|
|
1024
|
+
}
|
|
1025
|
+
async revokeSession(sessionId) {
|
|
1026
|
+
const response = await this.http({
|
|
1027
|
+
url: `${this.baseUrl}/me/sessions/${encodeURIComponent(sessionId)}/revoke`,
|
|
1028
|
+
method: "POST",
|
|
1029
|
+
headers: await this.authHeaders(),
|
|
1030
|
+
credentials: this.useCredentials ? "include" : void 0
|
|
1031
|
+
});
|
|
1032
|
+
if (!response.ok) {
|
|
1033
|
+
throw new Error(`revokeSession failed with status ${response.status}`);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
async postLogin(path, body) {
|
|
1037
|
+
const response = await this.http({
|
|
1038
|
+
url: `${this.baseUrl}${path}`,
|
|
1039
|
+
method: "POST",
|
|
1040
|
+
headers: { "content-type": "application/json" },
|
|
1041
|
+
body: JSON.stringify(body),
|
|
1042
|
+
credentials: this.useCredentials ? "include" : void 0
|
|
1043
|
+
});
|
|
1044
|
+
if (!response.ok) {
|
|
1045
|
+
throw new Error(`login failed with status ${response.status}`);
|
|
1046
|
+
}
|
|
1047
|
+
return response.data ?? {};
|
|
1048
|
+
}
|
|
1049
|
+
async authHeaders() {
|
|
1050
|
+
const headers = { "content-type": "application/json" };
|
|
1051
|
+
if (this.getAccessToken === void 0) {
|
|
1052
|
+
return headers;
|
|
1053
|
+
}
|
|
1054
|
+
const token = await this.getAccessToken();
|
|
1055
|
+
if (token !== null && token !== "") {
|
|
1056
|
+
headers.authorization = `Bearer ${token}`;
|
|
1057
|
+
}
|
|
1058
|
+
return headers;
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
|
|
282
1062
|
// src/utils/normalizeKeycloakUser.ts
|
|
283
1063
|
function isNonEmptyString(value) {
|
|
284
1064
|
return typeof value === "string" && value !== "";
|
|
@@ -329,24 +1109,6 @@ function normalizeKeycloakUser(u) {
|
|
|
329
1109
|
};
|
|
330
1110
|
}
|
|
331
1111
|
|
|
332
|
-
// src/utils/buildTokenRequestBody.ts
|
|
333
|
-
function buildAuthorizationCodeBody(input) {
|
|
334
|
-
return new URLSearchParams({
|
|
335
|
-
client_id: input.clientId,
|
|
336
|
-
grant_type: "authorization_code",
|
|
337
|
-
code: input.code,
|
|
338
|
-
redirect_uri: input.redirectUri,
|
|
339
|
-
code_verifier: input.codeVerifier
|
|
340
|
-
}).toString();
|
|
341
|
-
}
|
|
342
|
-
function buildRefreshTokenBody(input) {
|
|
343
|
-
return new URLSearchParams({
|
|
344
|
-
client_id: input.clientId,
|
|
345
|
-
grant_type: "refresh_token",
|
|
346
|
-
refresh_token: input.refreshToken
|
|
347
|
-
}).toString();
|
|
348
|
-
}
|
|
349
|
-
|
|
350
1112
|
// src/utils/extractAuthCode.ts
|
|
351
1113
|
function extractAuthCode(response) {
|
|
352
1114
|
if (!response) {
|
|
@@ -407,40 +1169,17 @@ function decodeUtf8(binary) {
|
|
|
407
1169
|
return new TextDecoder("utf-8").decode(bytes);
|
|
408
1170
|
}
|
|
409
1171
|
|
|
410
|
-
|
|
411
|
-
function asString(value) {
|
|
412
|
-
return typeof value === "string" && value !== "" ? value : void 0;
|
|
413
|
-
}
|
|
414
|
-
function asNumber(value) {
|
|
415
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
416
|
-
}
|
|
417
|
-
function normalizeTokenResponse(raw) {
|
|
418
|
-
const accessToken = asString(raw.access_token);
|
|
419
|
-
if (accessToken === void 0) {
|
|
420
|
-
throw new Error("Token response missing access_token");
|
|
421
|
-
}
|
|
422
|
-
return {
|
|
423
|
-
accessToken,
|
|
424
|
-
refreshToken: asString(raw.refresh_token),
|
|
425
|
-
idToken: asString(raw.id_token),
|
|
426
|
-
expiresIn: asNumber(raw.expires_in),
|
|
427
|
-
tokenType: asString(raw.token_type),
|
|
428
|
-
scope: asString(raw.scope)
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
function tokenResponseToAuthTokens(response, now = Date.now()) {
|
|
432
|
-
return {
|
|
433
|
-
accessToken: response.accessToken,
|
|
434
|
-
refreshToken: response.refreshToken,
|
|
435
|
-
idToken: response.idToken,
|
|
436
|
-
expiresAt: computeExpiresAt(response.expiresIn, now)
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
|
|
1172
|
+
exports.AuthApiClient = AuthApiClient;
|
|
440
1173
|
exports.AuthClient = AuthClient;
|
|
1174
|
+
exports.AuthEventEmitter = AuthEventEmitter;
|
|
1175
|
+
exports.BiometricGate = BiometricGate;
|
|
441
1176
|
exports.BrowserStorageTokenStorage = BrowserStorageTokenStorage;
|
|
1177
|
+
exports.CookieTokenStorage = CookieTokenStorage;
|
|
442
1178
|
exports.InMemoryTokenStorage = InMemoryTokenStorage;
|
|
1179
|
+
exports.InactivityTracker = InactivityTracker;
|
|
443
1180
|
exports.KeycloakRoles = KeycloakRoles;
|
|
1181
|
+
exports.RefreshInterceptor = RefreshInterceptor;
|
|
1182
|
+
exports.SecureStoreTokenStorage = SecureStoreTokenStorage;
|
|
444
1183
|
exports.buildAuthorizationCodeBody = buildAuthorizationCodeBody;
|
|
445
1184
|
exports.buildAuthorizationEndpoint = buildAuthorizationEndpoint;
|
|
446
1185
|
exports.buildAuthorizationUrl = buildAuthorizationUrl;
|
|
@@ -449,15 +1188,23 @@ exports.buildLogoutEndpoint = buildLogoutEndpoint;
|
|
|
449
1188
|
exports.buildRefreshTokenBody = buildRefreshTokenBody;
|
|
450
1189
|
exports.buildTokenEndpoint = buildTokenEndpoint;
|
|
451
1190
|
exports.buildUserInfoEndpoint = buildUserInfoEndpoint;
|
|
1191
|
+
exports.clearDiscoveryCache = clearDiscoveryCache;
|
|
452
1192
|
exports.computeExpiresAt = computeExpiresAt;
|
|
1193
|
+
exports.createFetchHttpClient = createFetchHttpClient;
|
|
453
1194
|
exports.decodeJwt = decodeJwt;
|
|
1195
|
+
exports.deriveCodeChallenge = deriveCodeChallenge;
|
|
1196
|
+
exports.exchangeAuthorizationCode = exchangeAuthorizationCode;
|
|
454
1197
|
exports.extractAuthCode = extractAuthCode;
|
|
1198
|
+
exports.fetchDiscoveryDocument = fetchDiscoveryDocument;
|
|
1199
|
+
exports.generateCodeVerifier = generateCodeVerifier;
|
|
1200
|
+
exports.generatePkcePair = generatePkcePair;
|
|
455
1201
|
exports.isKeycloakRole = isKeycloakRole;
|
|
456
1202
|
exports.isTokenExpired = isTokenExpired;
|
|
457
1203
|
exports.normalizeKeycloakUser = normalizeKeycloakUser;
|
|
458
1204
|
exports.normalizeTokenResponse = normalizeTokenResponse;
|
|
459
1205
|
exports.parseBaseUrlFromIssuer = parseBaseUrlFromIssuer;
|
|
460
1206
|
exports.parseRealmFromIssuer = parseRealmFromIssuer;
|
|
1207
|
+
exports.refreshAccessToken = refreshAccessToken;
|
|
461
1208
|
exports.tokenResponseToAuthTokens = tokenResponseToAuthTokens;
|
|
462
1209
|
//# sourceMappingURL=index.js.map
|
|
463
1210
|
//# sourceMappingURL=index.js.map
|