@flow-industries/id 0.7.0 → 0.8.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.
|
@@ -164,11 +164,24 @@ export function createFlow(options = {}) {
|
|
|
164
164
|
return false;
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
|
|
167
|
+
// Every refresh-token POST — boot, ensureGuest, and getToken — funnels through
|
|
168
|
+
// this one lock so two callers can't present the same stored token at once.
|
|
169
|
+
// Rotation is single-use: a token replayed after it rotated looks like theft,
|
|
170
|
+
// tripping server-side reuse detection and revoking the whole lineage (which
|
|
171
|
+
// would then 401 → clear → re-guest). The lock serializes refreshes across
|
|
172
|
+
// tabs of the same origin, and the guard short-circuits a caller that arrives
|
|
173
|
+
// after a concurrent holder already minted a fresh JWT, so no one re-presents
|
|
174
|
+
// an already-rotated token. refreshViaToken re-reads the token fresh from
|
|
175
|
+
// storage inside the lock, so a genuinely-second refresh uses the rotated one.
|
|
176
|
+
async function doRefresh() {
|
|
177
|
+
return withGuestLock(`flow.id.refresh:${host}`, async () => {
|
|
178
|
+
const current = store.getSnapshot().jwt;
|
|
179
|
+
if (current && !isExpiring(current))
|
|
180
|
+
return true;
|
|
181
|
+
return refreshViaToken();
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const refreshJwt = doRefresh;
|
|
172
185
|
// Dedupe concurrent refreshes: a burst of getToken() calls that all find the
|
|
173
186
|
// cached token expired should trigger one mint, not one per call.
|
|
174
187
|
let refreshInFlight = null;
|
|
@@ -220,7 +233,7 @@ export function createFlow(options = {}) {
|
|
|
220
233
|
// existing session instead of minting a duplicate guest.
|
|
221
234
|
if (store.getSnapshot().user)
|
|
222
235
|
return true;
|
|
223
|
-
if (await
|
|
236
|
+
if (await doRefresh())
|
|
224
237
|
return true;
|
|
225
238
|
try {
|
|
226
239
|
const result = await getDialog().requestSilent(METHODS.guest, []);
|
|
@@ -249,7 +262,7 @@ export function createFlow(options = {}) {
|
|
|
249
262
|
void (async () => {
|
|
250
263
|
await restoreCredential(store);
|
|
251
264
|
if (options.autoRestore !== false) {
|
|
252
|
-
const restored = await
|
|
265
|
+
const restored = await doRefresh();
|
|
253
266
|
// ensureGuest re-checks (refresh-first) under a cross-tab lock before
|
|
254
267
|
// minting, so calling it after a failed refresh can't fork a visitor into
|
|
255
268
|
// duplicate guests across tabs — the extra refresh is the dedup.
|
|
@@ -294,6 +307,17 @@ export function createFlow(options = {}) {
|
|
|
294
307
|
accessKeyPrep = await accessKeyModule.prepareAccessKey(accessKeyOptions, chain.id);
|
|
295
308
|
extraCapabilities = { accessKeyHash: accessKeyPrep.accessKeyHash };
|
|
296
309
|
}
|
|
310
|
+
// When the current session is a guest, forward its access token so a
|
|
311
|
+
// sign-up upgrades that guest in place even where the guest cookie is
|
|
312
|
+
// unreadable (the iOS top-level sign-up popup). The server verifies it
|
|
313
|
+
// before honoring it, so an app that never becomes a guest sends nothing.
|
|
314
|
+
const snapshot = store.getSnapshot();
|
|
315
|
+
if (snapshot.user?.isGuest && snapshot.jwt) {
|
|
316
|
+
extraCapabilities = {
|
|
317
|
+
...(extraCapabilities ?? {}),
|
|
318
|
+
guestToken: snapshot.jwt,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
297
321
|
const { session, webauthn, refreshToken } = await runLogin({
|
|
298
322
|
dialog: dialogHost,
|
|
299
323
|
store,
|
|
@@ -7,6 +7,13 @@ export type ConnectCapabilities = {
|
|
|
7
7
|
accessKeyHash?: string;
|
|
8
8
|
signInHeadless?: boolean;
|
|
9
9
|
credentialId?: string;
|
|
10
|
+
/**
|
|
11
|
+
* The current guest's access JWT, forwarded so a sign-up upgrades that guest
|
|
12
|
+
* in place even where the guest cookie is unreadable (the iOS top-level
|
|
13
|
+
* sign-up popup). The server verifies it before letting it stand in for the
|
|
14
|
+
* cookie session; see the register endpoint.
|
|
15
|
+
*/
|
|
16
|
+
guestToken?: string;
|
|
10
17
|
};
|
|
11
18
|
export type ConnectRequest = [{
|
|
12
19
|
capabilities?: ConnectCapabilities;
|