@antelopejs/dms-frontend 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -252,7 +252,7 @@ It stays `false` on the server and during hydration, which render the page resol
|
|
|
252
252
|
|
|
253
253
|
### Interface language
|
|
254
254
|
|
|
255
|
-
The interface language follows the session (`useUserSession().user.language`), not the props of the page on screen: the first render takes it from the session the page arrived with, and it changes when the session's language does, after `useUserSession().fetch()` or a visit with a newer session. A page Inertia restores from history or serves from its prefetch cache carries the session it was fetched with: the frontend server stamps every session it writes (`session.updatedAt`), and a page carrying an older stamp of the same account updates neither the session nor the language. A session without a language, such as a signed-out one, keeps the language on screen; `$i18n.setLocale()` still switches it at any time.
|
|
255
|
+
The interface language follows the session (`useUserSession().user.language`), not the props of the page on screen: the first render takes it from the session the page arrived with, and it changes when the session's language does, after `useUserSession().fetch()` or a visit with a newer session. A page Inertia restores from history or serves from its prefetch cache carries the session it was fetched with: the frontend server stamps every session it writes (`session.updatedAt`), and a page carrying an older stamp of the same account updates neither the session nor the language. A session without a language, such as a signed-out one, keeps the language on screen; `$i18n.setLocale()` still switches it at any time. It also remembers a language the application offers in the `dms_locale` cookie (one year, `SameSite=Lax`), and a page without a signed-in user's language, such as a public sign-in page, first renders in that one: a visitor's choice survives a reload, while a signed-in user's own language still comes first.
|
|
256
256
|
|
|
257
257
|
### Declaring the loader releases a module supports
|
|
258
258
|
|
package/package.json
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
installDmsPlugins,
|
|
36
36
|
preloadDmsPage,
|
|
37
37
|
provideDmsFrontendRuntime,
|
|
38
|
+
readDmsLocaleCookie,
|
|
38
39
|
resolveDmsAsyncComponents,
|
|
39
40
|
resolveDmsComponent,
|
|
40
41
|
serializeDmsAsyncData,
|
|
@@ -260,7 +261,12 @@ export async function configureDmsApp(
|
|
|
260
261
|
options.serverFetch as typeof import("ofetch").ofetch,
|
|
261
262
|
readServerRendered<Record<string, unknown>>(SSR_ASYNC_DATA_ID, {}),
|
|
262
263
|
);
|
|
263
|
-
|
|
264
|
+
// The signed-in user's language, else the one this browser picked, which is
|
|
265
|
+
// how a visitor's choice on a public page survives a reload.
|
|
266
|
+
const locale =
|
|
267
|
+
userLocale(options.initialPageProps.user) ??
|
|
268
|
+
readDmsLocaleCookie(runtime, supportedLocales) ??
|
|
269
|
+
DEFAULT_LOCALE;
|
|
264
270
|
const messages = await loadLocaleMessages(locale);
|
|
265
271
|
const i18n = createI18n({
|
|
266
272
|
legacy: false,
|
|
@@ -537,11 +537,54 @@ function parseRoute(runtime: DmsFrontendRuntime, url: string): DmsRoute {
|
|
|
537
537
|
};
|
|
538
538
|
}
|
|
539
539
|
|
|
540
|
+
function hasSameEntries(
|
|
541
|
+
left: Record<string, string>,
|
|
542
|
+
right: Record<string, string>,
|
|
543
|
+
): boolean {
|
|
544
|
+
const keys = Object.keys(left);
|
|
545
|
+
return (
|
|
546
|
+
keys.length === Object.keys(right).length &&
|
|
547
|
+
keys.every((key) => right[key] === left[key])
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Every parse builds a new query, params and matched: assigning them as they
|
|
552
|
+
// come would re-run each watcher of the route on every call of useDmsRoute,
|
|
553
|
+
// though the URL did not change. They are only replaced when their content did.
|
|
540
554
|
function updateRoute(runtime: DmsFrontendRuntime, url: string): void {
|
|
541
555
|
// The meta is the page's, not the URL's: a page declares it, then reads its
|
|
542
556
|
// route, and the middleware the meta selected must still find it.
|
|
543
|
-
const {
|
|
544
|
-
|
|
557
|
+
const {
|
|
558
|
+
meta: _pageMeta,
|
|
559
|
+
query,
|
|
560
|
+
params,
|
|
561
|
+
matched,
|
|
562
|
+
...location
|
|
563
|
+
} = parseRoute(runtime, url);
|
|
564
|
+
const route = runtime.route;
|
|
565
|
+
if (!hasSameEntries(query, route.query)) route.query = query;
|
|
566
|
+
if (!hasSameEntries(params, route.params)) route.params = params;
|
|
567
|
+
if (matched.length !== route.matched.length) route.matched = matched;
|
|
568
|
+
Object.assign(route, location);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const URL_BASE = "http://frontend.local";
|
|
572
|
+
|
|
573
|
+
// The URL the browser shows: it may be ahead of the route, which follows
|
|
574
|
+
// Inertia's page, when a component wrote it in place (history.replaceState).
|
|
575
|
+
function currentUrl(runtime: DmsFrontendRuntime): string {
|
|
576
|
+
if (runtime.isServer || typeof window === "undefined")
|
|
577
|
+
return new URL(runtime.route.fullPath, URL_BASE).href;
|
|
578
|
+
return window.location.href;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// One spelling per URL: `a b`, `a+b` and `a%20b` are the same query, as are
|
|
582
|
+
// `:` and `%3A`. The origin is kept, so a URL of another site never matches
|
|
583
|
+
// the current one, whatever its path.
|
|
584
|
+
function normalizeUrl(url: string, base: string): string {
|
|
585
|
+
const parsed = new URL(url, base);
|
|
586
|
+
const search = parsed.searchParams.toString();
|
|
587
|
+
return `${parsed.origin}${parsed.pathname}${search ? `?${search}` : ""}${parsed.hash}`;
|
|
545
588
|
}
|
|
546
589
|
|
|
547
590
|
function locationToUrl(
|
|
@@ -645,10 +688,13 @@ export function useDmsRoute(pattern?: string): DmsRoute {
|
|
|
645
688
|
|
|
646
689
|
export function useDmsRouter(): DmsRouter {
|
|
647
690
|
const runtime = useDmsRuntime();
|
|
648
|
-
const navigate = (to: RouteLocationRaw, options?: DmsNavigationOptions) =>
|
|
649
|
-
|
|
691
|
+
const navigate = (to: RouteLocationRaw, options?: DmsNavigationOptions) => {
|
|
692
|
+
const current = currentUrl(runtime);
|
|
693
|
+
return normalizeUrl(locationToUrl(runtime, to), current) ===
|
|
694
|
+
normalizeUrl(current, current)
|
|
650
695
|
? Promise.resolve()
|
|
651
696
|
: visit(runtime, to, options);
|
|
697
|
+
};
|
|
652
698
|
return {
|
|
653
699
|
currentRoute: computed(() => runtime.route),
|
|
654
700
|
push: navigate,
|
|
@@ -1143,6 +1189,35 @@ function createCookieRef<T>(
|
|
|
1143
1189
|
});
|
|
1144
1190
|
}
|
|
1145
1191
|
|
|
1192
|
+
// The language a visitor picks, kept by the browser so that a reload renders
|
|
1193
|
+
// in it. A signed-in user's own language still comes first.
|
|
1194
|
+
const LOCALE_COOKIE = "dms_locale";
|
|
1195
|
+
const LOCALE_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
|
|
1196
|
+
|
|
1197
|
+
/**
|
|
1198
|
+
* The language this browser picked: read from `document.cookie`, or in a
|
|
1199
|
+
* server render from the request. Only one of `supportedLocales` counts.
|
|
1200
|
+
*/
|
|
1201
|
+
export function readDmsLocaleCookie(
|
|
1202
|
+
runtime: DmsFrontendRuntime,
|
|
1203
|
+
supportedLocales: string[],
|
|
1204
|
+
): string | undefined {
|
|
1205
|
+
const source =
|
|
1206
|
+
typeof document === "undefined" ? runtime.requestCookies : document.cookie;
|
|
1207
|
+
try {
|
|
1208
|
+
const locale = parseCookieValue<unknown>(
|
|
1209
|
+
readCookie(source, LOCALE_COOKIE),
|
|
1210
|
+
{},
|
|
1211
|
+
);
|
|
1212
|
+
return typeof locale === "string" && supportedLocales.includes(locale)
|
|
1213
|
+
? locale
|
|
1214
|
+
: undefined;
|
|
1215
|
+
} catch {
|
|
1216
|
+
// A value that is not URI-encoded: as if the browser had picked none.
|
|
1217
|
+
return undefined;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1146
1221
|
/**
|
|
1147
1222
|
* A cookie as a ref, shared by every caller of the same name — so a plugin and
|
|
1148
1223
|
* a page reading one preference see each other's writes — and read on the
|
|
@@ -1707,6 +1782,12 @@ export async function installDmsPlugins(
|
|
|
1707
1782
|
const messages = await loadLocaleMessages(locale);
|
|
1708
1783
|
i18n.setLocaleMessage(locale, messages);
|
|
1709
1784
|
i18n.locale.value = locale;
|
|
1785
|
+
if (typeof document !== "undefined" && supportedLocales.includes(locale))
|
|
1786
|
+
writeCookie(LOCALE_COOKIE, locale, {
|
|
1787
|
+
maxAge: LOCALE_COOKIE_MAX_AGE,
|
|
1788
|
+
sameSite: "lax",
|
|
1789
|
+
secure: globalThis.location?.protocol === "https:",
|
|
1790
|
+
});
|
|
1710
1791
|
};
|
|
1711
1792
|
dmsI18n.locales = computed(() =>
|
|
1712
1793
|
supportedLocales.map((code) => ({
|
|
@@ -197,7 +197,13 @@ export function oauthHandoff(request, response) {
|
|
|
197
197
|
json(response, 200, handoff);
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
/**
|
|
201
|
+
* The session a token pair opens.
|
|
202
|
+
*
|
|
203
|
+
* @param result Backend answer carrying the tokens and the user
|
|
204
|
+
* @param persistent Whether the session outlives the browser
|
|
205
|
+
*/
|
|
206
|
+
export function sessionFrom(result, persistent = true) {
|
|
201
207
|
const payload = JSON.parse(
|
|
202
208
|
Buffer.from(
|
|
203
209
|
result.access_token.split(".")[1] ?? "",
|
|
@@ -209,5 +215,6 @@ export function sessionFrom(result) {
|
|
|
209
215
|
accessToken: result.access_token,
|
|
210
216
|
refreshToken: result.refresh_token,
|
|
211
217
|
activeTenantId: payload.tenantId ?? "default",
|
|
218
|
+
persistent,
|
|
212
219
|
};
|
|
213
220
|
}
|
|
@@ -9,17 +9,28 @@ import {
|
|
|
9
9
|
sessionFrom,
|
|
10
10
|
} from "./oauth.mjs";
|
|
11
11
|
import {
|
|
12
|
+
clearSealedCookie,
|
|
12
13
|
clearSession,
|
|
14
|
+
isPersistent,
|
|
13
15
|
persistAccountSession,
|
|
14
16
|
readAccount,
|
|
17
|
+
readSealedCookie,
|
|
15
18
|
readSession,
|
|
16
19
|
removeAccount,
|
|
17
20
|
setRequestSession,
|
|
21
|
+
storeAccount,
|
|
22
|
+
writeSealedCookie,
|
|
18
23
|
} from "./session.mjs";
|
|
19
24
|
|
|
20
25
|
const REFRESH_TTL_MS = 15_000;
|
|
21
26
|
const refreshes = new Map();
|
|
22
27
|
|
|
28
|
+
// Carries the "keep me signed in" choice of a login across its 2FA step, whose
|
|
29
|
+
// request names only the challenge. Lives as long as the challenge token.
|
|
30
|
+
const PENDING_LOGIN = "dms_pending_login";
|
|
31
|
+
const PENDING_LOGIN_PATH = "/auth/verify-2fa";
|
|
32
|
+
const PENDING_LOGIN_LIFETIME_S = 300;
|
|
33
|
+
|
|
23
34
|
const PASSTHROUGH = new Map([
|
|
24
35
|
["/auth/request-2fa-email", "/api/auth/request-2fa-email"],
|
|
25
36
|
]);
|
|
@@ -101,14 +112,30 @@ function singleFlight(token, operation) {
|
|
|
101
112
|
return promise;
|
|
102
113
|
}
|
|
103
114
|
|
|
104
|
-
async function establishWith(
|
|
115
|
+
async function establishWith(
|
|
116
|
+
request,
|
|
117
|
+
response,
|
|
118
|
+
endpoint,
|
|
119
|
+
payload,
|
|
120
|
+
persistent = true,
|
|
121
|
+
) {
|
|
105
122
|
const result = await backend(endpoint, request, {
|
|
106
123
|
method: "POST",
|
|
107
124
|
body: payload,
|
|
108
125
|
});
|
|
126
|
+
if (result.requires_2fa && typeof result.two_factor_token === "string")
|
|
127
|
+
writeSealedCookie(
|
|
128
|
+
response,
|
|
129
|
+
PENDING_LOGIN,
|
|
130
|
+
{ token: result.two_factor_token, persistent },
|
|
131
|
+
PENDING_LOGIN_LIFETIME_S,
|
|
132
|
+
PENDING_LOGIN_PATH,
|
|
133
|
+
);
|
|
109
134
|
if (result.requires_2fa || result.requires_tenant_assignment)
|
|
110
135
|
return json(response, 200, result);
|
|
111
|
-
|
|
136
|
+
if (readSealedCookie(request, PENDING_LOGIN))
|
|
137
|
+
clearSealedCookie(response, PENDING_LOGIN, PENDING_LOGIN_PATH);
|
|
138
|
+
const session = sessionFrom(result, persistent);
|
|
112
139
|
const account = persistAccountSession(request, response, session);
|
|
113
140
|
json(response, 200, { user: result.user, account });
|
|
114
141
|
}
|
|
@@ -117,6 +144,42 @@ async function establish(request, response, endpoint) {
|
|
|
117
144
|
return establishWith(request, response, endpoint, await body(request));
|
|
118
145
|
}
|
|
119
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Sign in with a password. Only a login that asked to be kept signed in
|
|
149
|
+
* outlives the browser; any other ends when the browser closes.
|
|
150
|
+
*/
|
|
151
|
+
async function login(request, response) {
|
|
152
|
+
const input = await body(request);
|
|
153
|
+
return establishWith(
|
|
154
|
+
request,
|
|
155
|
+
response,
|
|
156
|
+
"/api/auth/login",
|
|
157
|
+
input,
|
|
158
|
+
input.keep_login === true,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Finish a login on its 2FA step, with the lifetime the login chose. A
|
|
164
|
+
* challenge that no login of this browser left a choice for, such as the one
|
|
165
|
+
* an OAuth sign-in hands over, keeps the default.
|
|
166
|
+
*/
|
|
167
|
+
async function verifyTwoFactor(request, response) {
|
|
168
|
+
const input = await body(request);
|
|
169
|
+
const pending = readSealedCookie(request, PENDING_LOGIN);
|
|
170
|
+
const persistent =
|
|
171
|
+
typeof input.token === "string" && pending?.token === input.token
|
|
172
|
+
? isPersistent(pending)
|
|
173
|
+
: true;
|
|
174
|
+
return establishWith(
|
|
175
|
+
request,
|
|
176
|
+
response,
|
|
177
|
+
"/api/auth/verify-2fa",
|
|
178
|
+
input,
|
|
179
|
+
persistent,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
120
183
|
/**
|
|
121
184
|
* Backend endpoints this deployment lets a module open a session from.
|
|
122
185
|
*
|
|
@@ -196,7 +259,7 @@ export async function refreshSession(request, response) {
|
|
|
196
259
|
const user = await backend("/api/auth/me", request, {
|
|
197
260
|
token: tokens.access_token,
|
|
198
261
|
});
|
|
199
|
-
const refreshed = sessionFrom({ ...tokens, user });
|
|
262
|
+
const refreshed = sessionFrom({ ...tokens, user }, isPersistent(session));
|
|
200
263
|
persistAccountSession(request, response, refreshed, session.accountId);
|
|
201
264
|
setRequestSession(request, refreshed);
|
|
202
265
|
return refreshed;
|
|
@@ -246,7 +309,7 @@ async function switchTenant(request, response) {
|
|
|
246
309
|
const user = await backend("/api/auth/me", request, {
|
|
247
310
|
token: tokens.access_token,
|
|
248
311
|
});
|
|
249
|
-
const switched = sessionFrom({ ...tokens, user });
|
|
312
|
+
const switched = sessionFrom({ ...tokens, user }, isPersistent(session));
|
|
250
313
|
persistAccountSession(request, response, switched, session.accountId);
|
|
251
314
|
json(response, 200, { success: true, user });
|
|
252
315
|
}
|
|
@@ -263,7 +326,7 @@ async function switchAccount(request, response) {
|
|
|
263
326
|
const user = await backend("/api/auth/me", request, {
|
|
264
327
|
token: tokens.access_token,
|
|
265
328
|
});
|
|
266
|
-
const session = sessionFrom({ ...tokens, user });
|
|
329
|
+
const session = sessionFrom({ ...tokens, user }, isPersistent(account));
|
|
267
330
|
const descriptor = persistAccountSession(
|
|
268
331
|
request,
|
|
269
332
|
response,
|
|
@@ -281,6 +344,36 @@ async function switchAccount(request, response) {
|
|
|
281
344
|
}
|
|
282
345
|
}
|
|
283
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Keep the tokens a validation refreshed, without changing which account is
|
|
349
|
+
* signed in. Another account only has its own cookie updated. The current one
|
|
350
|
+
* also has the session updated, since a refresh may have rotated the token the
|
|
351
|
+
* session holds; it keeps its user as the backend gave it.
|
|
352
|
+
*/
|
|
353
|
+
function storeValidatedAccount(request, response, account, accountId, tokens) {
|
|
354
|
+
const current = readSession(request);
|
|
355
|
+
if (current?.accountId === accountId) {
|
|
356
|
+
persistAccountSession(
|
|
357
|
+
request,
|
|
358
|
+
response,
|
|
359
|
+
sessionFrom({ ...tokens, user: current.user }, isPersistent(current)),
|
|
360
|
+
accountId,
|
|
361
|
+
);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const user = {
|
|
365
|
+
_id: account.userId,
|
|
366
|
+
email: account.email,
|
|
367
|
+
name: account.name,
|
|
368
|
+
};
|
|
369
|
+
storeAccount(
|
|
370
|
+
request,
|
|
371
|
+
response,
|
|
372
|
+
sessionFrom({ ...tokens, user }, isPersistent(account)),
|
|
373
|
+
accountId,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
284
377
|
async function validateAccount(request, response) {
|
|
285
378
|
const input = await body(request);
|
|
286
379
|
const account = readAccount(request, input.accountId);
|
|
@@ -290,15 +383,7 @@ async function validateAccount(request, response) {
|
|
|
290
383
|
method: "POST",
|
|
291
384
|
body: { token: account.refreshToken },
|
|
292
385
|
});
|
|
293
|
-
|
|
294
|
-
request,
|
|
295
|
-
response,
|
|
296
|
-
sessionFrom({
|
|
297
|
-
...tokens,
|
|
298
|
-
user: { id: account.userId, email: account.email, name: account.name },
|
|
299
|
-
}),
|
|
300
|
-
input.accountId,
|
|
301
|
-
);
|
|
386
|
+
storeValidatedAccount(request, response, account, input.accountId, tokens);
|
|
302
387
|
json(response, 200, { valid: true });
|
|
303
388
|
} catch (error) {
|
|
304
389
|
const rejected =
|
|
@@ -309,12 +394,10 @@ async function validateAccount(request, response) {
|
|
|
309
394
|
}
|
|
310
395
|
|
|
311
396
|
const actions = {
|
|
312
|
-
"/auth/login":
|
|
313
|
-
establish(request, response, "/api/auth/login"),
|
|
397
|
+
"/auth/login": login,
|
|
314
398
|
"/auth/signup": (request, response) =>
|
|
315
399
|
establish(request, response, "/api/auth/signup"),
|
|
316
|
-
"/auth/verify-2fa":
|
|
317
|
-
establish(request, response, "/api/auth/verify-2fa"),
|
|
400
|
+
"/auth/verify-2fa": verifyTwoFactor,
|
|
318
401
|
"/auth/establish": establishFromEndpoint,
|
|
319
402
|
"/auth/switch-account": switchAccount,
|
|
320
403
|
"/auth/switch-tenant": switchTenant,
|
|
@@ -60,9 +60,27 @@ function cookieValue(request, name) {
|
|
|
60
60
|
.find(([key]) => key === name)?.[1];
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Cookie attributes. Without a `maxAge` the cookie lasts as long as the
|
|
65
|
+
* browser session: it is gone once the browser is closed.
|
|
66
|
+
*/
|
|
63
67
|
function attributes(maxAge, path = "/") {
|
|
64
68
|
const secure = process.env.DMS_COOKIE_SECURE !== "false" ? "; Secure" : "";
|
|
65
|
-
|
|
69
|
+
const lifetime = maxAge === undefined ? "" : `; Max-Age=${maxAge}`;
|
|
70
|
+
return `Path=${path}; HttpOnly; SameSite=Lax${lifetime}${secure}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Whether a session, or a stored account, outlives the browser. Only a login
|
|
75
|
+
* that left "keep me signed in" unchecked says no; a cookie written before
|
|
76
|
+
* the flag existed carries none, and keeps the lifetime it was given then.
|
|
77
|
+
*/
|
|
78
|
+
export function isPersistent(session) {
|
|
79
|
+
return session?.persistent !== false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function lifetime(persistent) {
|
|
83
|
+
return persistent ? MAX_AGE : undefined;
|
|
66
84
|
}
|
|
67
85
|
|
|
68
86
|
function appendCookie(response, cookie) {
|
|
@@ -87,6 +105,7 @@ function descriptor(session, accountId) {
|
|
|
87
105
|
email: session.user?.email,
|
|
88
106
|
name: session.user?.name,
|
|
89
107
|
activeTenantId: session.activeTenantId,
|
|
108
|
+
persistent: isPersistent(session),
|
|
90
109
|
};
|
|
91
110
|
}
|
|
92
111
|
|
|
@@ -103,7 +122,7 @@ export function setRequestSession(request, session) {
|
|
|
103
122
|
export function writeSession(response, session) {
|
|
104
123
|
appendCookie(
|
|
105
124
|
response,
|
|
106
|
-
`${SESSION_COOKIE}=${encode(session)}; ${attributes(
|
|
125
|
+
`${SESSION_COOKIE}=${encode(session)}; ${attributes(lifetime(isPersistent(session)))}`,
|
|
107
126
|
);
|
|
108
127
|
}
|
|
109
128
|
|
|
@@ -117,9 +136,10 @@ export function readAccounts(request) {
|
|
|
117
136
|
}
|
|
118
137
|
|
|
119
138
|
function writeAccountIndex(response, accounts) {
|
|
139
|
+
const persistent = Object.values(accounts).some(isPersistent);
|
|
120
140
|
appendCookie(
|
|
121
141
|
response,
|
|
122
|
-
`${ACCOUNT_INDEX_COOKIE}=${encode(accounts)}; ${attributes(
|
|
142
|
+
`${ACCOUNT_INDEX_COOKIE}=${encode(accounts)}; ${attributes(lifetime(persistent))}`,
|
|
123
143
|
);
|
|
124
144
|
}
|
|
125
145
|
|
|
@@ -136,7 +156,7 @@ export function storeAccount(request, response, session, accountId) {
|
|
|
136
156
|
writeAccountIndex(response, accounts);
|
|
137
157
|
appendCookie(
|
|
138
158
|
response,
|
|
139
|
-
`${accountCookieName(id)}=${encode({ refreshToken: session.refreshToken, ...accountDescriptor })}; ${attributes(
|
|
159
|
+
`${accountCookieName(id)}=${encode({ refreshToken: session.refreshToken, ...accountDescriptor })}; ${attributes(lifetime(accountDescriptor.persistent))}`,
|
|
140
160
|
);
|
|
141
161
|
return accountDescriptor;
|
|
142
162
|
}
|
|
@@ -177,13 +197,19 @@ export function readSealedCookie(request, name) {
|
|
|
177
197
|
return value ? decode(value) : undefined;
|
|
178
198
|
}
|
|
179
199
|
|
|
180
|
-
export function writeSealedCookie(
|
|
200
|
+
export function writeSealedCookie(
|
|
201
|
+
response,
|
|
202
|
+
name,
|
|
203
|
+
value,
|
|
204
|
+
maxAge,
|
|
205
|
+
path = "/auth/oauth",
|
|
206
|
+
) {
|
|
181
207
|
appendCookie(
|
|
182
208
|
response,
|
|
183
|
-
`${name}=${encode(value)}; ${attributes(maxAge,
|
|
209
|
+
`${name}=${encode(value)}; ${attributes(maxAge, path)}`,
|
|
184
210
|
);
|
|
185
211
|
}
|
|
186
212
|
|
|
187
|
-
export function clearSealedCookie(response, name) {
|
|
188
|
-
appendCookie(response, `${name}=; ${attributes(0,
|
|
213
|
+
export function clearSealedCookie(response, name, path = "/auth/oauth") {
|
|
214
|
+
appendCookie(response, `${name}=; ${attributes(0, path)}`);
|
|
189
215
|
}
|