@stackframe/react 2.8.48 → 2.8.51
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 +30 -0
- package/dist/components-page/password-reset.js +6 -5
- package/dist/components-page/password-reset.js.map +1 -1
- package/dist/components-page/sign-out.js +2 -12
- package/dist/components-page/sign-out.js.map +1 -1
- package/dist/components-page/stack-handler-client.js +274 -0
- package/dist/components-page/stack-handler-client.js.map +1 -0
- package/dist/components-page/stack-handler.js +4 -239
- package/dist/components-page/stack-handler.js.map +1 -1
- package/dist/components-page/team-invitation.js +6 -5
- package/dist/components-page/team-invitation.js.map +1 -1
- package/dist/esm/components-page/password-reset.js +3 -2
- package/dist/esm/components-page/password-reset.js.map +1 -1
- package/dist/esm/components-page/sign-out.js +2 -2
- package/dist/esm/components-page/sign-out.js.map +1 -1
- package/dist/esm/components-page/stack-handler-client.js +250 -0
- package/dist/esm/components-page/stack-handler-client.js.map +1 -0
- package/dist/esm/components-page/stack-handler.js +4 -239
- package/dist/esm/components-page/stack-handler.js.map +1 -1
- package/dist/esm/components-page/team-invitation.js +3 -2
- package/dist/esm/components-page/team-invitation.js.map +1 -1
- package/dist/esm/lib/cookie.js +36 -7
- package/dist/esm/lib/cookie.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js +17 -0
- package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js +255 -56
- package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/common.js +3 -3
- package/dist/esm/lib/stack-app/apps/implementations/common.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js +39 -0
- package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/interfaces/admin-app.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/interfaces/client-app.js.map +1 -1
- package/dist/esm/lib/stack-app/common.js.map +1 -1
- package/dist/esm/lib/stack-app/users/index.js.map +1 -1
- package/dist/index.d.mts +124 -89
- package/dist/index.d.ts +124 -89
- package/dist/lib/cookie.js +38 -7
- package/dist/lib/cookie.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/admin-app-impl.js +17 -0
- package/dist/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/client-app-impl.js +254 -55
- package/dist/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/common.js +2 -2
- package/dist/lib/stack-app/apps/implementations/common.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/server-app-impl.js +49 -0
- package/dist/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
- package/dist/lib/stack-app/apps/interfaces/admin-app.js.map +1 -1
- package/dist/lib/stack-app/apps/interfaces/client-app.js.map +1 -1
- package/dist/lib/stack-app/common.js.map +1 -1
- package/dist/lib/stack-app/users/index.js.map +1 -1
- package/package.json +5 -5
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { WebAuthnError, startAuthentication, startRegistration } from "@simplewebauthn/browser";
|
|
3
3
|
import { KnownErrors, StackClientInterface } from "@stackframe/stack-shared";
|
|
4
4
|
import { InternalSession } from "@stackframe/stack-shared/dist/sessions";
|
|
5
|
+
import { encodeBase32 } from "@stackframe/stack-shared/dist/utils/bytes";
|
|
5
6
|
import { isBrowserLike } from "@stackframe/stack-shared/dist/utils/env";
|
|
6
7
|
import { StackAssertionError, captureError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
|
|
7
8
|
import { DependenciesMap } from "@stackframe/stack-shared/dist/utils/maps";
|
|
@@ -17,7 +18,7 @@ import * as cookie from "cookie";
|
|
|
17
18
|
import React, { useCallback, useMemo } from "react";
|
|
18
19
|
import { constructRedirectUrl } from "../../../../utils/url.js";
|
|
19
20
|
import { addNewOAuthProviderOrScope, callOAuthCallback, signInWithOAuth } from "../../../auth.js";
|
|
20
|
-
import { createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookieClient,
|
|
21
|
+
import { createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookieClient, isSecure as isSecureCookieContext, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie.js";
|
|
21
22
|
import { apiKeyCreationOptionsToCrud } from "../../api-keys/index.js";
|
|
22
23
|
import { stackAppInternalsSymbol } from "../../common.js";
|
|
23
24
|
import { contactChannelCreateOptionsToCrud, contactChannelUpdateOptionsToCrud } from "../../contact-channels/index.js";
|
|
@@ -25,6 +26,7 @@ import { adminProjectCreateOptionsToCrud } from "../../projects/index.js";
|
|
|
25
26
|
import { teamCreateOptionsToCrud, teamUpdateOptionsToCrud } from "../../teams/index.js";
|
|
26
27
|
import { attachUserDestructureGuard, userUpdateOptionsToCrud } from "../../users/index.js";
|
|
27
28
|
import { clientVersion, createCache, createCacheBySession, createEmptyTokenStore, getBaseUrl, getDefaultExtraRequestHeaders, getDefaultProjectId, getDefaultPublishableClientKey, getUrls, resolveConstructorOptions } from "./common.js";
|
|
29
|
+
import { parseJson } from "@stackframe/stack-shared/dist/utils/json";
|
|
28
30
|
import { useAsyncCache } from "./common.js";
|
|
29
31
|
var isReactServer = false;
|
|
30
32
|
var process = globalThis.process ?? { env: {} };
|
|
@@ -175,11 +177,15 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
|
|
|
175
177
|
this._convexPartialUserCache = createCache(
|
|
176
178
|
async ([ctx]) => await this._getPartialUserFromConvex(ctx)
|
|
177
179
|
);
|
|
180
|
+
this._trustedParentDomainCache = createCache(
|
|
181
|
+
async ([domain]) => await this._getTrustedParentDomain(domain)
|
|
182
|
+
);
|
|
178
183
|
this._anonymousSignUpInProgress = null;
|
|
179
184
|
this._memoryTokenStore = createEmptyTokenStore();
|
|
180
185
|
this._nextServerCookiesTokenStores = /* @__PURE__ */ new WeakMap();
|
|
181
186
|
this._requestTokenStores = /* @__PURE__ */ new WeakMap();
|
|
182
187
|
this._storedBrowserCookieTokenStore = null;
|
|
188
|
+
this._mostRecentQueuedCookieRefreshIndex = 0;
|
|
183
189
|
/**
|
|
184
190
|
* A map from token stores and session keys to sessions.
|
|
185
191
|
*
|
|
@@ -195,13 +201,17 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
|
|
|
195
201
|
}
|
|
196
202
|
this._options = resolvedOptions;
|
|
197
203
|
this._extraOptions = extraOptions;
|
|
204
|
+
const projectId = resolvedOptions.projectId ?? getDefaultProjectId();
|
|
205
|
+
if (projectId !== "internal" && !projectId.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i)) {
|
|
206
|
+
throw new Error(`Invalid project ID: ${projectId}. Project IDs must be UUIDs. Please check your environment variables and/or your StackApp.`);
|
|
207
|
+
}
|
|
198
208
|
if (extraOptions && extraOptions.interface) {
|
|
199
209
|
this._interface = extraOptions.interface;
|
|
200
210
|
} else {
|
|
201
211
|
this._interface = new StackClientInterface({
|
|
202
212
|
getBaseUrl: () => getBaseUrl(resolvedOptions.baseUrl),
|
|
203
213
|
extraRequestHeaders: resolvedOptions.extraRequestHeaders ?? getDefaultExtraRequestHeaders(),
|
|
204
|
-
projectId
|
|
214
|
+
projectId,
|
|
205
215
|
clientVersion,
|
|
206
216
|
publishableClientKey: resolvedOptions.publishableClientKey ?? getDefaultPublishableClientKey(),
|
|
207
217
|
prepareRequest: async () => {
|
|
@@ -302,13 +312,90 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
|
|
|
302
312
|
runAsynchronously(this._checkFeatureSupport(name, options));
|
|
303
313
|
throw new StackAssertionError(`${name} is not currently supported. Please reach out to Stack support for more information.`);
|
|
304
314
|
}
|
|
315
|
+
get _legacyRefreshTokenCookieName() {
|
|
316
|
+
return `stack-refresh-${this.projectId}`;
|
|
317
|
+
}
|
|
305
318
|
get _refreshTokenCookieName() {
|
|
306
319
|
return `stack-refresh-${this.projectId}`;
|
|
307
320
|
}
|
|
321
|
+
_getRefreshTokenDefaultCookieNameForSecure(secure) {
|
|
322
|
+
return `${secure ? "__Host-" : ""}${this._refreshTokenCookieName}--default`;
|
|
323
|
+
}
|
|
324
|
+
_getCustomRefreshCookieName(domain) {
|
|
325
|
+
const encoded = encodeBase32(new TextEncoder().encode(domain.toLowerCase()));
|
|
326
|
+
return `${this._refreshTokenCookieName}--custom-${encoded}`;
|
|
327
|
+
}
|
|
328
|
+
_formatRefreshCookieValue(refreshToken, updatedAt) {
|
|
329
|
+
return JSON.stringify({
|
|
330
|
+
refresh_token: refreshToken,
|
|
331
|
+
updated_at_millis: updatedAt
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
_formatAccessCookieValue(refreshToken, accessToken) {
|
|
335
|
+
return refreshToken && accessToken ? JSON.stringify([refreshToken, accessToken]) : null;
|
|
336
|
+
}
|
|
337
|
+
_parseStructuredRefreshCookie(value) {
|
|
338
|
+
if (!value) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
const parsed = parseJson(value);
|
|
342
|
+
if (parsed.status !== "ok" || typeof parsed.data !== "object" || parsed.data === null) {
|
|
343
|
+
console.warn("Failed to parse structured refresh cookie");
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
const data = parsed.data;
|
|
347
|
+
const refreshToken = "refresh_token" in data && typeof data.refresh_token === "string" ? data.refresh_token : null;
|
|
348
|
+
const updatedAt = "updated_at_millis" in data && typeof data.updated_at_millis === "number" ? data.updated_at_millis : null;
|
|
349
|
+
if (!refreshToken) {
|
|
350
|
+
console.warn("Refresh token not found in structured refresh cookie");
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
refreshToken,
|
|
355
|
+
updatedAt
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
_extractRefreshTokenFromCookieMap(cookies) {
|
|
359
|
+
const { legacyNames, structuredPrefixes } = this._getRefreshTokenCookieNamePatterns();
|
|
360
|
+
for (const name of legacyNames) {
|
|
361
|
+
const value = cookies[name];
|
|
362
|
+
if (value) {
|
|
363
|
+
return { refreshToken: value, updatedAt: null };
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
let selected = null;
|
|
367
|
+
for (const [name, value] of Object.entries(cookies)) {
|
|
368
|
+
if (!structuredPrefixes.some((prefix) => name.startsWith(prefix))) continue;
|
|
369
|
+
const parsed = this._parseStructuredRefreshCookie(value);
|
|
370
|
+
if (!parsed) continue;
|
|
371
|
+
const candidateUpdatedAt = parsed.updatedAt ?? Number.NEGATIVE_INFINITY;
|
|
372
|
+
const selectedUpdatedAt = selected?.updatedAt ?? Number.NEGATIVE_INFINITY;
|
|
373
|
+
if (!selected || candidateUpdatedAt > selectedUpdatedAt) {
|
|
374
|
+
selected = parsed;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (!selected) {
|
|
378
|
+
return { refreshToken: null, updatedAt: null };
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
refreshToken: selected.refreshToken,
|
|
382
|
+
updatedAt: selected.updatedAt ?? null
|
|
383
|
+
};
|
|
384
|
+
}
|
|
308
385
|
_getTokensFromCookies(cookies) {
|
|
309
|
-
const refreshToken = cookies
|
|
310
|
-
const
|
|
311
|
-
|
|
386
|
+
const { refreshToken } = this._extractRefreshTokenFromCookieMap(cookies);
|
|
387
|
+
const accessTokenCookie = cookies[this._accessTokenCookieName] ?? null;
|
|
388
|
+
let accessToken = null;
|
|
389
|
+
if (accessTokenCookie && accessTokenCookie.startsWith('["')) {
|
|
390
|
+
const parsed = parseJson(accessTokenCookie);
|
|
391
|
+
if (parsed.status === "ok" && typeof parsed.data === "object" && parsed.data !== null && Array.isArray(parsed.data) && parsed.data.length === 2 && typeof parsed.data[0] === "string" && typeof parsed.data[1] === "string") {
|
|
392
|
+
if (parsed.data[0] === refreshToken) {
|
|
393
|
+
accessToken = parsed.data[1];
|
|
394
|
+
}
|
|
395
|
+
} else {
|
|
396
|
+
console.warn("Access token cookie has invalid format");
|
|
397
|
+
}
|
|
398
|
+
}
|
|
312
399
|
return {
|
|
313
400
|
refreshToken,
|
|
314
401
|
accessToken
|
|
@@ -317,17 +404,98 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
|
|
|
317
404
|
get _accessTokenCookieName() {
|
|
318
405
|
return `stack-access`;
|
|
319
406
|
}
|
|
407
|
+
_getAllBrowserCookies() {
|
|
408
|
+
if (!isBrowserLike()) {
|
|
409
|
+
throw new StackAssertionError("Cannot get browser cookies on the server!");
|
|
410
|
+
}
|
|
411
|
+
return cookie.parse(document.cookie || "");
|
|
412
|
+
}
|
|
413
|
+
_getRefreshTokenCookieNamePatterns() {
|
|
414
|
+
return {
|
|
415
|
+
legacyNames: [this._legacyRefreshTokenCookieName, "stack-refresh"],
|
|
416
|
+
structuredPrefixes: [
|
|
417
|
+
`${this._refreshTokenCookieName}--`,
|
|
418
|
+
`__Host-${this._refreshTokenCookieName}--`
|
|
419
|
+
]
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
_collectRefreshTokenCookieNames(cookies) {
|
|
423
|
+
const { legacyNames, structuredPrefixes } = this._getRefreshTokenCookieNamePatterns();
|
|
424
|
+
const names = /* @__PURE__ */ new Set();
|
|
425
|
+
for (const name of legacyNames) {
|
|
426
|
+
if (cookies[name]) {
|
|
427
|
+
names.add(name);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
for (const name of Object.keys(cookies)) {
|
|
431
|
+
if (structuredPrefixes.some((prefix) => name.startsWith(prefix))) {
|
|
432
|
+
names.add(name);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return names;
|
|
436
|
+
}
|
|
437
|
+
_prepareRefreshCookieUpdate(existingCookies, refreshToken, accessToken, defaultCookieName) {
|
|
438
|
+
const cookieNames = this._collectRefreshTokenCookieNames(existingCookies);
|
|
439
|
+
cookieNames.delete(defaultCookieName);
|
|
440
|
+
const updatedAt = refreshToken ? Date.now() : null;
|
|
441
|
+
const refreshCookieValue = refreshToken && updatedAt !== null ? this._formatRefreshCookieValue(refreshToken, updatedAt) : null;
|
|
442
|
+
const accessTokenPayload = this._formatAccessCookieValue(refreshToken, accessToken);
|
|
443
|
+
return {
|
|
444
|
+
updatedAt,
|
|
445
|
+
refreshCookieValue,
|
|
446
|
+
accessTokenPayload,
|
|
447
|
+
cookieNamesToDelete: [...cookieNames]
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
_queueCustomRefreshCookieUpdate(refreshToken, updatedAt, context) {
|
|
451
|
+
runAsynchronously(async () => {
|
|
452
|
+
this._mostRecentQueuedCookieRefreshIndex++;
|
|
453
|
+
const updateIndex = this._mostRecentQueuedCookieRefreshIndex;
|
|
454
|
+
let hostname;
|
|
455
|
+
if (isBrowserLike()) {
|
|
456
|
+
hostname = window.location.hostname;
|
|
457
|
+
}
|
|
458
|
+
if (!hostname) {
|
|
459
|
+
console.warn("No hostname found when queueing custom refresh cookie update");
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const domain = await this._trustedParentDomainCache.getOrWait([hostname], "read-write");
|
|
463
|
+
const setCookie = async (targetDomain, value2) => {
|
|
464
|
+
const name = this._getCustomRefreshCookieName(targetDomain);
|
|
465
|
+
const options = { maxAge: 60 * 60 * 24 * 365, domain: targetDomain, noOpIfServerComponent: true };
|
|
466
|
+
if (context === "browser") {
|
|
467
|
+
setOrDeleteCookieClient(name, value2, options);
|
|
468
|
+
} else {
|
|
469
|
+
await setOrDeleteCookie(name, value2, options);
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
if (domain.status === "error" || !domain.data || updateIndex !== this._mostRecentQueuedCookieRefreshIndex) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const value = refreshToken && updatedAt ? this._formatRefreshCookieValue(refreshToken, updatedAt) : null;
|
|
476
|
+
await setCookie(domain.data, value);
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
async _getTrustedParentDomain(currentDomain) {
|
|
480
|
+
const project = Result.orThrow(await this._interface.getClientProject());
|
|
481
|
+
const domains = project.config.domains.map((d) => d.domain.trim().replace(/^https?:\/\//, "").split("/")[0]?.toLowerCase());
|
|
482
|
+
const trustedWildcards = domains.filter((d) => d.startsWith("**."));
|
|
483
|
+
const parts = currentDomain.split(".");
|
|
484
|
+
for (let i = parts.length - 2; i >= 0; i--) {
|
|
485
|
+
const parentDomain = parts.slice(i).join(".");
|
|
486
|
+
if (domains.includes(parentDomain) && trustedWildcards.includes("**." + parentDomain)) {
|
|
487
|
+
return parentDomain;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
320
492
|
_getBrowserCookieTokenStore() {
|
|
321
493
|
if (!isBrowserLike()) {
|
|
322
494
|
throw new Error("Cannot use cookie token store on the server!");
|
|
323
495
|
}
|
|
324
496
|
if (this._storedBrowserCookieTokenStore === null) {
|
|
325
497
|
const getCurrentValue = (old) => {
|
|
326
|
-
const tokens = this._getTokensFromCookies(
|
|
327
|
-
refreshTokenCookie: getCookieClient(this._refreshTokenCookieName) ?? getCookieClient("stack-refresh"),
|
|
328
|
-
// keep old cookie name for backwards-compatibility
|
|
329
|
-
accessTokenCookie: getCookieClient(this._accessTokenCookieName)
|
|
330
|
-
});
|
|
498
|
+
const tokens = this._getTokensFromCookies(this._getAllBrowserCookies());
|
|
331
499
|
return {
|
|
332
500
|
refreshToken: tokens.refreshToken,
|
|
333
501
|
accessToken: tokens.accessToken ?? (old?.refreshToken === tokens.refreshToken ? old.accessToken : null)
|
|
@@ -346,9 +514,19 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
|
|
|
346
514
|
}, 100);
|
|
347
515
|
this._storedBrowserCookieTokenStore.onChange((value) => {
|
|
348
516
|
try {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
517
|
+
const refreshToken = value.refreshToken;
|
|
518
|
+
const secure = window.location.protocol === "https:";
|
|
519
|
+
const defaultName = this._getRefreshTokenDefaultCookieNameForSecure(secure);
|
|
520
|
+
const { updatedAt, refreshCookieValue, accessTokenPayload, cookieNamesToDelete } = this._prepareRefreshCookieUpdate(
|
|
521
|
+
this._getAllBrowserCookies(),
|
|
522
|
+
refreshToken,
|
|
523
|
+
value.accessToken ?? null,
|
|
524
|
+
defaultName
|
|
525
|
+
);
|
|
526
|
+
setOrDeleteCookieClient(defaultName, refreshCookieValue, { maxAge: 60 * 60 * 24 * 365, secure });
|
|
527
|
+
setOrDeleteCookieClient(this._accessTokenCookieName, accessTokenPayload, { maxAge: 60 * 60 * 24 });
|
|
528
|
+
cookieNamesToDelete.forEach((name) => deleteCookieClient(name));
|
|
529
|
+
this._queueCustomRefreshCookieUpdate(refreshToken, updatedAt, "browser");
|
|
352
530
|
hasSucceededInWriting = true;
|
|
353
531
|
} catch (e) {
|
|
354
532
|
if (!isBrowserLike()) {
|
|
@@ -371,18 +549,31 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
|
|
|
371
549
|
if (isBrowserLike()) {
|
|
372
550
|
return this._getBrowserCookieTokenStore();
|
|
373
551
|
} else {
|
|
374
|
-
const tokens = this._getTokensFromCookies(
|
|
375
|
-
refreshTokenCookie: cookieHelper.get(this._refreshTokenCookieName) ?? cookieHelper.get("stack-refresh"),
|
|
376
|
-
// keep old cookie name for backwards-compatibility
|
|
377
|
-
accessTokenCookie: cookieHelper.get(this._accessTokenCookieName)
|
|
378
|
-
});
|
|
552
|
+
const tokens = this._getTokensFromCookies(cookieHelper.getAll());
|
|
379
553
|
const store = new Store(tokens);
|
|
380
554
|
store.onChange((value) => {
|
|
381
555
|
runAsynchronously(async () => {
|
|
556
|
+
const refreshToken = value.refreshToken;
|
|
557
|
+
const secure = await isSecureCookieContext();
|
|
558
|
+
const defaultName = this._getRefreshTokenDefaultCookieNameForSecure(secure);
|
|
559
|
+
const { updatedAt, refreshCookieValue, accessTokenPayload, cookieNamesToDelete } = this._prepareRefreshCookieUpdate(
|
|
560
|
+
cookieHelper.getAll(),
|
|
561
|
+
refreshToken,
|
|
562
|
+
value.accessToken ?? null,
|
|
563
|
+
defaultName
|
|
564
|
+
);
|
|
382
565
|
await Promise.all([
|
|
383
|
-
setOrDeleteCookie(
|
|
384
|
-
setOrDeleteCookie(this._accessTokenCookieName,
|
|
566
|
+
setOrDeleteCookie(defaultName, refreshCookieValue, { maxAge: 60 * 60 * 24 * 365, noOpIfServerComponent: true }),
|
|
567
|
+
setOrDeleteCookie(this._accessTokenCookieName, accessTokenPayload, { maxAge: 60 * 60 * 24, noOpIfServerComponent: true })
|
|
385
568
|
]);
|
|
569
|
+
if (cookieNamesToDelete.length > 0) {
|
|
570
|
+
await Promise.all(
|
|
571
|
+
cookieNamesToDelete.map(
|
|
572
|
+
(name) => setOrDeleteCookie(name, null, { noOpIfServerComponent: true })
|
|
573
|
+
)
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
this._queueCustomRefreshCookieUpdate(refreshToken, updatedAt, "server");
|
|
386
577
|
});
|
|
387
578
|
});
|
|
388
579
|
return store;
|
|
@@ -413,11 +604,7 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
|
|
|
413
604
|
}
|
|
414
605
|
const cookieHeader = tokenStoreInit.headers.get("cookie");
|
|
415
606
|
const parsed = cookie.parse(cookieHeader || "");
|
|
416
|
-
const res = new Store(
|
|
417
|
-
refreshToken: parsed[this._refreshTokenCookieName] || parsed["stack-refresh"] || null,
|
|
418
|
-
// keep old cookie name for backwards-compatibility
|
|
419
|
-
accessToken: parsed[this._accessTokenCookieName] || null
|
|
420
|
-
});
|
|
607
|
+
const res = new Store(this._getTokensFromCookies(parsed));
|
|
421
608
|
this._requestTokenStores.set(tokenStoreInit, res);
|
|
422
609
|
return res;
|
|
423
610
|
} else if ("accessToken" in tokenStoreInit || "refreshToken" in tokenStoreInit) {
|
|
@@ -763,35 +950,6 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
|
|
|
763
950
|
const tokens = await this.currentSession.getTokens();
|
|
764
951
|
return tokens;
|
|
765
952
|
},
|
|
766
|
-
async registerPasskey(options) {
|
|
767
|
-
const hostname = (await app._getCurrentUrl())?.hostname;
|
|
768
|
-
if (!hostname) {
|
|
769
|
-
throw new StackAssertionError("hostname must be provided if the Stack App does not have a redirect method");
|
|
770
|
-
}
|
|
771
|
-
const initiationResult = await app._interface.initiatePasskeyRegistration({}, session);
|
|
772
|
-
if (initiationResult.status !== "ok") {
|
|
773
|
-
return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to get initiation options for passkey registration"));
|
|
774
|
-
}
|
|
775
|
-
const { options_json, code } = initiationResult.data;
|
|
776
|
-
if (options_json.rp.id !== "THIS_VALUE_WILL_BE_REPLACED.example.com") {
|
|
777
|
-
throw new StackAssertionError(`Expected returned RP ID from server to equal sentinel, but found ${options_json.rp.id}`);
|
|
778
|
-
}
|
|
779
|
-
options_json.rp.id = hostname;
|
|
780
|
-
let attResp;
|
|
781
|
-
try {
|
|
782
|
-
attResp = await startRegistration({ optionsJSON: options_json });
|
|
783
|
-
} catch (error) {
|
|
784
|
-
if (error instanceof WebAuthnError) {
|
|
785
|
-
return Result.error(new KnownErrors.PasskeyWebAuthnError(error.message, error.name));
|
|
786
|
-
} else {
|
|
787
|
-
captureError("passkey-registration-failed", error);
|
|
788
|
-
return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to start passkey registration due to unknown error"));
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
const registrationResult = await app._interface.registerPasskey({ credential: attResp, code }, session);
|
|
792
|
-
await app._refreshUser(session);
|
|
793
|
-
return registrationResult;
|
|
794
|
-
},
|
|
795
953
|
signOut(options) {
|
|
796
954
|
return app._signOut(session, options);
|
|
797
955
|
}
|
|
@@ -1040,6 +1198,35 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
|
|
|
1040
1198
|
async getOAuthProvider(id) {
|
|
1041
1199
|
const providers = await this.listOAuthProviders();
|
|
1042
1200
|
return providers.find((p) => p.id === id) ?? null;
|
|
1201
|
+
},
|
|
1202
|
+
async registerPasskey(options) {
|
|
1203
|
+
const hostname = (await app._getCurrentUrl())?.hostname;
|
|
1204
|
+
if (!hostname) {
|
|
1205
|
+
throw new StackAssertionError("hostname must be provided if the Stack App does not have a redirect method");
|
|
1206
|
+
}
|
|
1207
|
+
const initiationResult = await app._interface.initiatePasskeyRegistration({}, session);
|
|
1208
|
+
if (initiationResult.status !== "ok") {
|
|
1209
|
+
return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to get initiation options for passkey registration"));
|
|
1210
|
+
}
|
|
1211
|
+
const { options_json, code } = initiationResult.data;
|
|
1212
|
+
if (options_json.rp.id !== "THIS_VALUE_WILL_BE_REPLACED.example.com") {
|
|
1213
|
+
throw new StackAssertionError(`Expected returned RP ID from server to equal sentinel, but found ${options_json.rp.id}`);
|
|
1214
|
+
}
|
|
1215
|
+
options_json.rp.id = hostname;
|
|
1216
|
+
let attResp;
|
|
1217
|
+
try {
|
|
1218
|
+
attResp = await startRegistration({ optionsJSON: options_json });
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
if (error instanceof WebAuthnError) {
|
|
1221
|
+
return Result.error(new KnownErrors.PasskeyWebAuthnError(error.message, error.name));
|
|
1222
|
+
} else {
|
|
1223
|
+
captureError("passkey-registration-failed", error);
|
|
1224
|
+
return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to start passkey registration due to unknown error"));
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
const registrationResult = await app._interface.registerPasskey({ credential: attResp, code }, session);
|
|
1228
|
+
await app._refreshUser(session);
|
|
1229
|
+
return registrationResult;
|
|
1043
1230
|
}
|
|
1044
1231
|
};
|
|
1045
1232
|
}
|
|
@@ -1808,10 +1995,22 @@ ${url}`);
|
|
|
1808
1995
|
});
|
|
1809
1996
|
}
|
|
1810
1997
|
async signOut(options) {
|
|
1811
|
-
const user = await this.getUser();
|
|
1998
|
+
const user = await this.getUser({ tokenStore: options?.tokenStore ?? void 0 });
|
|
1999
|
+
if (user) {
|
|
2000
|
+
await user.signOut({ redirectUrl: options?.redirectUrl });
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
async getAuthHeaders(options) {
|
|
2004
|
+
return {
|
|
2005
|
+
"x-stack-auth": JSON.stringify(await this.getAuthJson(options))
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
async getAuthJson(options) {
|
|
2009
|
+
const user = await this.getUser({ tokenStore: options?.tokenStore ?? void 0 });
|
|
1812
2010
|
if (user) {
|
|
1813
|
-
await user.
|
|
2011
|
+
return await user.getAuthJson();
|
|
1814
2012
|
}
|
|
2013
|
+
return { accessToken: null, refreshToken: null };
|
|
1815
2014
|
}
|
|
1816
2015
|
async getProject() {
|
|
1817
2016
|
const crud = Result.orThrow(await this._currentProjectCache.getOrWait([], "write-only"));
|