@depup/supabase__auth-js 2.103.0-depup.0 → 2.110.8-depup.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/AGENTS.md +11 -0
- package/README.md +2 -2
- package/changes.json +1 -1
- package/dist/main/GoTrueAdminApi.d.ts +45 -8
- package/dist/main/GoTrueAdminApi.d.ts.map +1 -1
- package/dist/main/GoTrueAdminApi.js +67 -7
- package/dist/main/GoTrueAdminApi.js.map +1 -1
- package/dist/main/GoTrueClient.d.ts +239 -42
- package/dist/main/GoTrueClient.d.ts.map +1 -1
- package/dist/main/GoTrueClient.js +946 -164
- package/dist/main/GoTrueClient.js.map +1 -1
- package/dist/main/lib/constants.d.ts +8 -0
- package/dist/main/lib/constants.d.ts.map +1 -1
- package/dist/main/lib/constants.js +9 -1
- package/dist/main/lib/constants.js.map +1 -1
- package/dist/main/lib/errors.d.ts +42 -3
- package/dist/main/lib/errors.d.ts.map +1 -1
- package/dist/main/lib/errors.js +44 -13
- package/dist/main/lib/errors.js.map +1 -1
- package/dist/main/lib/fetch.d.ts +28 -8
- package/dist/main/lib/fetch.d.ts.map +1 -1
- package/dist/main/lib/fetch.js +28 -6
- package/dist/main/lib/fetch.js.map +1 -1
- package/dist/main/lib/helpers.d.ts +4 -1
- package/dist/main/lib/helpers.d.ts.map +1 -1
- package/dist/main/lib/helpers.js +15 -4
- package/dist/main/lib/helpers.js.map +1 -1
- package/dist/main/lib/locks.d.ts +29 -35
- package/dist/main/lib/locks.d.ts.map +1 -1
- package/dist/main/lib/locks.js +37 -38
- package/dist/main/lib/locks.js.map +1 -1
- package/dist/main/lib/types.d.ts +305 -64
- package/dist/main/lib/types.d.ts.map +1 -1
- package/dist/main/lib/types.js.map +1 -1
- package/dist/main/lib/version.d.ts +1 -1
- package/dist/main/lib/version.js +1 -1
- package/dist/main/lib/webauthn.d.ts +8 -0
- package/dist/main/lib/webauthn.d.ts.map +1 -1
- package/dist/main/lib/webauthn.dom.d.ts +3 -3
- package/dist/main/lib/webauthn.dom.d.ts.map +1 -1
- package/dist/main/lib/webauthn.errors.d.ts +5 -0
- package/dist/main/lib/webauthn.errors.d.ts.map +1 -1
- package/dist/main/lib/webauthn.errors.js +7 -0
- package/dist/main/lib/webauthn.errors.js.map +1 -1
- package/dist/main/lib/webauthn.js +1 -0
- package/dist/main/lib/webauthn.js.map +1 -1
- package/dist/module/GoTrueAdminApi.d.ts +45 -8
- package/dist/module/GoTrueAdminApi.d.ts.map +1 -1
- package/dist/module/GoTrueAdminApi.js +68 -8
- package/dist/module/GoTrueAdminApi.js.map +1 -1
- package/dist/module/GoTrueClient.d.ts +239 -42
- package/dist/module/GoTrueClient.d.ts.map +1 -1
- package/dist/module/GoTrueClient.js +951 -169
- package/dist/module/GoTrueClient.js.map +1 -1
- package/dist/module/lib/constants.d.ts +8 -0
- package/dist/module/lib/constants.d.ts.map +1 -1
- package/dist/module/lib/constants.js +8 -0
- package/dist/module/lib/constants.js.map +1 -1
- package/dist/module/lib/errors.d.ts +42 -3
- package/dist/module/lib/errors.d.ts.map +1 -1
- package/dist/module/lib/errors.js +41 -12
- package/dist/module/lib/errors.js.map +1 -1
- package/dist/module/lib/fetch.d.ts +28 -8
- package/dist/module/lib/fetch.d.ts.map +1 -1
- package/dist/module/lib/fetch.js +28 -6
- package/dist/module/lib/fetch.js.map +1 -1
- package/dist/module/lib/helpers.d.ts +4 -1
- package/dist/module/lib/helpers.d.ts.map +1 -1
- package/dist/module/lib/helpers.js +14 -4
- package/dist/module/lib/helpers.js.map +1 -1
- package/dist/module/lib/locks.d.ts +29 -35
- package/dist/module/lib/locks.d.ts.map +1 -1
- package/dist/module/lib/locks.js +37 -38
- package/dist/module/lib/locks.js.map +1 -1
- package/dist/module/lib/types.d.ts +305 -64
- package/dist/module/lib/types.d.ts.map +1 -1
- package/dist/module/lib/types.js.map +1 -1
- package/dist/module/lib/version.d.ts +1 -1
- package/dist/module/lib/version.js +1 -1
- package/dist/module/lib/webauthn.d.ts +8 -0
- package/dist/module/lib/webauthn.d.ts.map +1 -1
- package/dist/module/lib/webauthn.dom.d.ts +3 -3
- package/dist/module/lib/webauthn.dom.d.ts.map +1 -1
- package/dist/module/lib/webauthn.errors.d.ts +5 -0
- package/dist/module/lib/webauthn.errors.d.ts.map +1 -1
- package/dist/module/lib/webauthn.errors.js +7 -0
- package/dist/module/lib/webauthn.errors.js.map +1 -1
- package/dist/module/lib/webauthn.js +1 -1
- package/dist/module/lib/webauthn.js.map +1 -1
- package/dist/tsconfig.module.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/migrations/README.md +25 -0
- package/migrations/lockless-coordination.md +155 -0
- package/package.json +25 -12
- package/src/GoTrueAdminApi.ts +99 -7
- package/src/GoTrueClient.ts +1117 -181
- package/src/lib/constants.ts +9 -0
- package/src/lib/errors.ts +76 -9
- package/src/lib/fetch.ts +66 -24
- package/src/lib/helpers.ts +19 -6
- package/src/lib/locks.ts +44 -42
- package/src/lib/types.ts +374 -82
- package/src/lib/version.ts +1 -1
- package/src/lib/webauthn.dom.ts +3 -3
- package/src/lib/webauthn.errors.ts +12 -0
- package/src/lib/webauthn.ts +1 -1
|
@@ -25,9 +25,16 @@ const DEFAULT_OPTIONS = {
|
|
|
25
25
|
debug: false,
|
|
26
26
|
hasCustomAuthorizationHeader: false,
|
|
27
27
|
throwOnError: false,
|
|
28
|
-
lockAcquireTimeout: 5000, // 5 seconds
|
|
28
|
+
lockAcquireTimeout: 5000, // 5 seconds. Only used when a custom `lock` is supplied. TODO(v3): remove.
|
|
29
29
|
skipAutoInitialize: false,
|
|
30
|
+
experimental: {},
|
|
30
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* No-op lock used internally as a placeholder. Kept so older test setups that
|
|
34
|
+
* inject this exact reference do not break; new code never sees it because
|
|
35
|
+
* `this.lock` stays `null` when no custom lock is supplied (lockless path).
|
|
36
|
+
* TODO(v3): remove with the legacy lock path.
|
|
37
|
+
*/
|
|
31
38
|
async function lockNoOp(name, acquireTimeout, fn) {
|
|
32
39
|
return await fn();
|
|
33
40
|
}
|
|
@@ -61,13 +68,21 @@ class GoTrueClient {
|
|
|
61
68
|
/**
|
|
62
69
|
* Create a new client for use in the browser.
|
|
63
70
|
*
|
|
64
|
-
* @example
|
|
71
|
+
* @example Using supabase-js (recommended)
|
|
72
|
+
* ```ts
|
|
73
|
+
* import { createClient } from '@supabase/supabase-js'
|
|
74
|
+
*
|
|
75
|
+
* const supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key')
|
|
76
|
+
* const { data, error } = await supabase.auth.getUser()
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @example Standalone import for bundle-sensitive environments
|
|
65
80
|
* ```ts
|
|
66
81
|
* import { GoTrueClient } from '@supabase/auth-js'
|
|
67
82
|
*
|
|
68
83
|
* const auth = new GoTrueClient({
|
|
69
84
|
* url: 'https://xyzcompany.supabase.co/auth/v1',
|
|
70
|
-
* headers: { apikey: '
|
|
85
|
+
* headers: { apikey: 'your-publishable-key' },
|
|
71
86
|
* storageKey: 'supabase-auth',
|
|
72
87
|
* })
|
|
73
88
|
* ```
|
|
@@ -84,6 +99,29 @@ class GoTrueClient {
|
|
|
84
99
|
this.autoRefreshTickTimeout = null;
|
|
85
100
|
this.visibilityChangedCallback = null;
|
|
86
101
|
this.refreshingDeferred = null;
|
|
102
|
+
/**
|
|
103
|
+
* Cache of the most recent refresh failure, keyed by the refresh token
|
|
104
|
+
* that failed. Serial callers passing the *same* token within
|
|
105
|
+
* `REFRESH_FAILURE_COOLDOWN_MS` (including subsequent auto-refresh ticks)
|
|
106
|
+
* receive this cached result instead of firing another `/token` request.
|
|
107
|
+
* Callers passing a *different* token (token rotation pickup, explicit
|
|
108
|
+
* `setSession`/`refreshSession({ refresh_token })`, multi-account switch)
|
|
109
|
+
* bypass the cache and attempt a fresh refresh as they should.
|
|
110
|
+
* Cleared on any successful refresh (locally or via BroadcastChannel from
|
|
111
|
+
* another tab) and on `_removeSession`.
|
|
112
|
+
*
|
|
113
|
+
* Pairs with `refreshingDeferred`: concurrent callers share the in-flight
|
|
114
|
+
* promise, serial callers within the cooldown share the failure result.
|
|
115
|
+
*/
|
|
116
|
+
this.lastRefreshFailure = null;
|
|
117
|
+
/**
|
|
118
|
+
* Monotonic counter incremented at the top of `_removeSession`, before any
|
|
119
|
+
* `await`. The commit guard inside `_callRefreshToken` captures this value
|
|
120
|
+
* before `_saveSession` and re-checks it after, so a `signOut` that
|
|
121
|
+
* interleaves inside `_saveSession`'s storage-write awaits is still caught
|
|
122
|
+
* (the post-fetch storage snapshot alone misses that window).
|
|
123
|
+
*/
|
|
124
|
+
this._sessionRemovalEpoch = 0;
|
|
87
125
|
/**
|
|
88
126
|
* Keeps track of the async client initialization.
|
|
89
127
|
* When null or not yet resolved the auth state is `unknown`
|
|
@@ -91,9 +129,29 @@ class GoTrueClient {
|
|
|
91
129
|
* Keep extra care to never reject or throw uncaught errors
|
|
92
130
|
*/
|
|
93
131
|
this.initializePromise = null;
|
|
132
|
+
/**
|
|
133
|
+
* Non-null only while `initialize()` is running. While open,
|
|
134
|
+
* `_notifyAllSubscribers` enqueues init-chain notifications (those fired with
|
|
135
|
+
* `broadcast = true`, i.e. from `_recoverAndRefresh`) into this array instead
|
|
136
|
+
* of firing directly, so that `initializePromise` is guaranteed to be
|
|
137
|
+
* resolved before any subscriber callback runs. Callbacks that call
|
|
138
|
+
* `getSession()` / `getUser()` etc. would otherwise deadlock because those
|
|
139
|
+
* methods await `initializePromise`. Notifications from the incoming
|
|
140
|
+
* BroadcastChannel handler (`broadcast = false`) are not enqueued — they fire
|
|
141
|
+
* immediately. Flushed (in order) by `initialize()` after `initializePromise`
|
|
142
|
+
* settles.
|
|
143
|
+
*/
|
|
144
|
+
this._pendingInitNotifications = null;
|
|
94
145
|
this.detectSessionInUrl = true;
|
|
95
146
|
this.hasCustomAuthorizationHeader = false;
|
|
96
147
|
this.suppressGetSessionWarning = false;
|
|
148
|
+
/**
|
|
149
|
+
* Custom lock function passed via `settings.lock`. When non-null, every auth
|
|
150
|
+
* operation runs inside `_acquireLock`. When null (the default), the client
|
|
151
|
+
* uses its lockless coordination (refresh single-flight + commit guard).
|
|
152
|
+
* TODO(v3): remove along with the legacy lock path.
|
|
153
|
+
*/
|
|
154
|
+
this.lock = null;
|
|
97
155
|
this.lockAcquired = false;
|
|
98
156
|
this.pendingInLock = [];
|
|
99
157
|
/**
|
|
@@ -118,29 +176,32 @@ class GoTrueClient {
|
|
|
118
176
|
}
|
|
119
177
|
this.persistSession = settings.persistSession;
|
|
120
178
|
this.autoRefreshToken = settings.autoRefreshToken;
|
|
179
|
+
this.experimental = (_b = settings.experimental) !== null && _b !== void 0 ? _b : {};
|
|
121
180
|
this.admin = new GoTrueAdminApi_1.default({
|
|
122
181
|
url: settings.url,
|
|
123
182
|
headers: settings.headers,
|
|
124
183
|
fetch: settings.fetch,
|
|
184
|
+
experimental: this.experimental,
|
|
125
185
|
});
|
|
126
186
|
this.url = settings.url;
|
|
127
187
|
this.headers = settings.headers;
|
|
128
188
|
this.fetch = (0, helpers_1.resolveFetch)(settings.fetch);
|
|
129
|
-
this.lock = settings.lock || lockNoOp;
|
|
130
189
|
this.detectSessionInUrl = settings.detectSessionInUrl;
|
|
131
190
|
this.flowType = settings.flowType;
|
|
132
191
|
this.hasCustomAuthorizationHeader = settings.hasCustomAuthorizationHeader;
|
|
133
192
|
this.throwOnError = settings.throwOnError;
|
|
193
|
+
// Always wire `lockAcquireTimeout` even on the lockless path: consumers
|
|
194
|
+
// (including supabase-js tests) read it off the client to verify option
|
|
195
|
+
// flow-through.
|
|
134
196
|
this.lockAcquireTimeout = settings.lockAcquireTimeout;
|
|
135
|
-
|
|
197
|
+
// TODO(v3): remove. Legacy opt-in path preserved for backwards
|
|
198
|
+
// compatibility with callers passing a custom `lock` (typically React
|
|
199
|
+
// Native `processLock` or Node multi-process setups). When `settings.lock`
|
|
200
|
+
// is null the client uses its lockless coordination — no `navigator.locks`
|
|
201
|
+
// by default, no implicit `processLock`.
|
|
202
|
+
if (settings.lock != null) {
|
|
136
203
|
this.lock = settings.lock;
|
|
137
204
|
}
|
|
138
|
-
else if (this.persistSession && (0, helpers_1.isBrowser)() && ((_b = globalThis === null || globalThis === void 0 ? void 0 : globalThis.navigator) === null || _b === void 0 ? void 0 : _b.locks)) {
|
|
139
|
-
this.lock = locks_1.navigatorLock;
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
this.lock = lockNoOp;
|
|
143
|
-
}
|
|
144
205
|
if (!this.jwks) {
|
|
145
206
|
this.jwks = { keys: [] };
|
|
146
207
|
this.jwks_cached_at = Number.MIN_SAFE_INTEGER;
|
|
@@ -162,6 +223,15 @@ class GoTrueClient {
|
|
|
162
223
|
listGrants: this._listOAuthGrants.bind(this),
|
|
163
224
|
revokeGrant: this._revokeOAuthGrant.bind(this),
|
|
164
225
|
};
|
|
226
|
+
this.passkey = {
|
|
227
|
+
startRegistration: this._startPasskeyRegistration.bind(this),
|
|
228
|
+
verifyRegistration: this._verifyPasskeyRegistration.bind(this),
|
|
229
|
+
startAuthentication: this._startPasskeyAuthentication.bind(this),
|
|
230
|
+
verifyAuthentication: this._verifyPasskeyAuthentication.bind(this),
|
|
231
|
+
list: this._listPasskeys.bind(this),
|
|
232
|
+
update: this._updatePasskey.bind(this),
|
|
233
|
+
delete: this._deletePasskey.bind(this),
|
|
234
|
+
};
|
|
165
235
|
if (this.persistSession) {
|
|
166
236
|
if (settings.storage) {
|
|
167
237
|
this.storage = settings.storage;
|
|
@@ -192,6 +262,12 @@ class GoTrueClient {
|
|
|
192
262
|
}
|
|
193
263
|
(_c = this.broadcastChannel) === null || _c === void 0 ? void 0 : _c.addEventListener('message', async (event) => {
|
|
194
264
|
this._debug('received broadcast notification from other tab or client', event);
|
|
265
|
+
// Another tab successfully refreshed or signed in — any cached
|
|
266
|
+
// failure in this tab is stale and should not block the next
|
|
267
|
+
// refresh attempt.
|
|
268
|
+
if (event.data.event === 'TOKEN_REFRESHED' || event.data.event === 'SIGNED_IN') {
|
|
269
|
+
this.lastRefreshFailure = null;
|
|
270
|
+
}
|
|
195
271
|
try {
|
|
196
272
|
await this._notifyAllSubscribers(event.data.event, event.data.session, false); // broadcast = false so we don't get an endless loop of messages
|
|
197
273
|
}
|
|
@@ -237,22 +313,55 @@ class GoTrueClient {
|
|
|
237
313
|
return this;
|
|
238
314
|
}
|
|
239
315
|
/**
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
316
|
+
* Initialize the auth client by loading the session from storage or
|
|
317
|
+
* detecting it from the URL after an OAuth, magic-link, or password-recovery
|
|
318
|
+
* redirect.
|
|
319
|
+
*
|
|
320
|
+
* **Most callers do not need to invoke this directly.** The client calls it
|
|
321
|
+
* automatically during construction, and to react to sign-in events (including
|
|
322
|
+
* post-redirect events) you should subscribe to `onAuthStateChange` rather
|
|
323
|
+
* than awaiting `initialize()`.
|
|
324
|
+
*
|
|
325
|
+
* You only need to call it manually when you have opted out of the automatic
|
|
326
|
+
* call by passing `skipAutoInitialize: true` — for example, in an SSR context
|
|
327
|
+
* where you need to control initialization timing. In that case, awaiting
|
|
328
|
+
* `initialize()` returns the resolved session result (or any error encountered
|
|
329
|
+
* while detecting it from the URL).
|
|
243
330
|
*
|
|
244
331
|
* @category Auth
|
|
245
332
|
*/
|
|
246
333
|
async initialize() {
|
|
334
|
+
var _a;
|
|
247
335
|
if (this.initializePromise) {
|
|
248
336
|
return await this.initializePromise;
|
|
249
337
|
}
|
|
338
|
+
// Open the notification queue before _initialize() runs so that every
|
|
339
|
+
// _notifyAllSubscribers call inside the init chain enqueues instead of
|
|
340
|
+
// firing. Without this, a callback receiving SIGNED_IN (or TOKEN_REFRESHED
|
|
341
|
+
// / SIGNED_OUT) during _recoverAndRefresh would deadlock if it called
|
|
342
|
+
// getSession() / getUser() — those methods await initializePromise, which
|
|
343
|
+
// can only resolve after the callback returns, which can only return after
|
|
344
|
+
// getSession() resolves. The queue is flushed below after initializePromise
|
|
345
|
+
// has settled, so callbacks run with a fully resolved initializePromise.
|
|
346
|
+
this._pendingInitNotifications = [];
|
|
250
347
|
this.initializePromise = (async () => {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
348
|
+
if (this.lock != null) {
|
|
349
|
+
// TODO(v3): remove legacy lock path
|
|
350
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
351
|
+
return await this._initialize();
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
return await this._initialize();
|
|
254
355
|
})();
|
|
255
|
-
|
|
356
|
+
const result = await this.initializePromise;
|
|
357
|
+
// initializePromise is now resolved — flush queued notifications in order.
|
|
358
|
+
// Callbacks can safely call getSession() / getUser() / signOut() etc.
|
|
359
|
+
const queue = (_a = this._pendingInitNotifications) !== null && _a !== void 0 ? _a : [];
|
|
360
|
+
this._pendingInitNotifications = null;
|
|
361
|
+
for (const n of queue) {
|
|
362
|
+
await this._notifyAllSubscribers(n.event, n.session, n.broadcast);
|
|
363
|
+
}
|
|
364
|
+
return result;
|
|
256
365
|
}
|
|
257
366
|
/**
|
|
258
367
|
* IMPORTANT:
|
|
@@ -786,6 +895,21 @@ class GoTrueClient {
|
|
|
786
895
|
* password: 'some-password',
|
|
787
896
|
* })
|
|
788
897
|
* ```
|
|
898
|
+
*
|
|
899
|
+
* @exampleDescription Handling errors
|
|
900
|
+
* Log the full `error` object so fields like `code`, `status`, and `name` aren't hidden. The `error.code` (e.g. `'invalid_credentials'`, `'email_not_confirmed'`) is often more useful for branching than `error.message`, and the full object surfaces both.
|
|
901
|
+
*
|
|
902
|
+
* @example Handling errors
|
|
903
|
+
* ```js
|
|
904
|
+
* const { data, error } = await supabase.auth.signInWithPassword({
|
|
905
|
+
* email: 'example@email.com',
|
|
906
|
+
* password: 'example-password',
|
|
907
|
+
* })
|
|
908
|
+
* if (error) {
|
|
909
|
+
* console.error(error)
|
|
910
|
+
* return
|
|
911
|
+
* }
|
|
912
|
+
* ```
|
|
789
913
|
*/
|
|
790
914
|
async signInWithPassword(credentials) {
|
|
791
915
|
try {
|
|
@@ -1101,9 +1225,13 @@ class GoTrueClient {
|
|
|
1101
1225
|
*/
|
|
1102
1226
|
async exchangeCodeForSession(authCode) {
|
|
1103
1227
|
await this.initializePromise;
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1228
|
+
if (this.lock != null) {
|
|
1229
|
+
// TODO(v3): remove legacy lock path
|
|
1230
|
+
return this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
1231
|
+
return this._exchangeCodeForSession(authCode);
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
return this._exchangeCodeForSession(authCode);
|
|
1107
1235
|
}
|
|
1108
1236
|
/**
|
|
1109
1237
|
* Signs in a user by verifying a message signed by the user's private key.
|
|
@@ -1205,7 +1333,7 @@ class GoTrueClient {
|
|
|
1205
1333
|
}
|
|
1206
1334
|
}
|
|
1207
1335
|
async signInWithEthereum(credentials) {
|
|
1208
|
-
var _a, _b, _c, _d,
|
|
1336
|
+
var _a, _b, _c, _d, _f, _g, _h, _j, _k, _l, _m;
|
|
1209
1337
|
// TODO: flatten type
|
|
1210
1338
|
let message;
|
|
1211
1339
|
let signature;
|
|
@@ -1265,11 +1393,11 @@ class GoTrueClient {
|
|
|
1265
1393
|
version: '1',
|
|
1266
1394
|
chainId: chainId,
|
|
1267
1395
|
nonce: (_c = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _c === void 0 ? void 0 : _c.nonce,
|
|
1268
|
-
issuedAt: (
|
|
1269
|
-
expirationTime: (
|
|
1270
|
-
notBefore: (
|
|
1271
|
-
requestId: (
|
|
1272
|
-
resources: (
|
|
1396
|
+
issuedAt: (_f = (_d = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _d === void 0 ? void 0 : _d.issuedAt) !== null && _f !== void 0 ? _f : new Date(),
|
|
1397
|
+
expirationTime: (_g = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _g === void 0 ? void 0 : _g.expirationTime,
|
|
1398
|
+
notBefore: (_h = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _h === void 0 ? void 0 : _h.notBefore,
|
|
1399
|
+
requestId: (_j = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _j === void 0 ? void 0 : _j.requestId,
|
|
1400
|
+
resources: (_k = options === null || options === void 0 ? void 0 : options.signInWithEthereum) === null || _k === void 0 ? void 0 : _k.resources,
|
|
1273
1401
|
};
|
|
1274
1402
|
message = (0, ethereum_1.createSiweMessage)(siweMessage);
|
|
1275
1403
|
// Sign message
|
|
@@ -1282,8 +1410,8 @@ class GoTrueClient {
|
|
|
1282
1410
|
const { data, error } = await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/token?grant_type=web3`, {
|
|
1283
1411
|
headers: this.headers,
|
|
1284
1412
|
body: Object.assign({ chain: 'ethereum', message,
|
|
1285
|
-
signature }, (((
|
|
1286
|
-
? { gotrue_meta_security: { captcha_token: (
|
|
1413
|
+
signature }, (((_l = credentials.options) === null || _l === void 0 ? void 0 : _l.captchaToken)
|
|
1414
|
+
? { gotrue_meta_security: { captcha_token: (_m = credentials.options) === null || _m === void 0 ? void 0 : _m.captchaToken } }
|
|
1287
1415
|
: null)),
|
|
1288
1416
|
xform: fetch_1._sessionResponse,
|
|
1289
1417
|
});
|
|
@@ -1308,7 +1436,7 @@ class GoTrueClient {
|
|
|
1308
1436
|
}
|
|
1309
1437
|
}
|
|
1310
1438
|
async signInWithSolana(credentials) {
|
|
1311
|
-
var _a, _b, _c, _d,
|
|
1439
|
+
var _a, _b, _c, _d, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
1312
1440
|
let message;
|
|
1313
1441
|
let signature;
|
|
1314
1442
|
if ('message' in credentials) {
|
|
@@ -1393,17 +1521,17 @@ class GoTrueClient {
|
|
|
1393
1521
|
...(((_d = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _d === void 0 ? void 0 : _d.notBefore)
|
|
1394
1522
|
? [`Not Before: ${options.signInWithSolana.notBefore}`]
|
|
1395
1523
|
: []),
|
|
1396
|
-
...(((
|
|
1524
|
+
...(((_f = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _f === void 0 ? void 0 : _f.expirationTime)
|
|
1397
1525
|
? [`Expiration Time: ${options.signInWithSolana.expirationTime}`]
|
|
1398
1526
|
: []),
|
|
1399
|
-
...(((
|
|
1527
|
+
...(((_g = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _g === void 0 ? void 0 : _g.chainId)
|
|
1400
1528
|
? [`Chain ID: ${options.signInWithSolana.chainId}`]
|
|
1401
1529
|
: []),
|
|
1402
|
-
...(((
|
|
1403
|
-
...(((
|
|
1530
|
+
...(((_h = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _h === void 0 ? void 0 : _h.nonce) ? [`Nonce: ${options.signInWithSolana.nonce}`] : []),
|
|
1531
|
+
...(((_j = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _j === void 0 ? void 0 : _j.requestId)
|
|
1404
1532
|
? [`Request ID: ${options.signInWithSolana.requestId}`]
|
|
1405
1533
|
: []),
|
|
1406
|
-
...(((
|
|
1534
|
+
...(((_l = (_k = options === null || options === void 0 ? void 0 : options.signInWithSolana) === null || _k === void 0 ? void 0 : _k.resources) === null || _l === void 0 ? void 0 : _l.length)
|
|
1407
1535
|
? [
|
|
1408
1536
|
'Resources',
|
|
1409
1537
|
...options.signInWithSolana.resources.map((resource) => `- ${resource}`),
|
|
@@ -1420,8 +1548,8 @@ class GoTrueClient {
|
|
|
1420
1548
|
try {
|
|
1421
1549
|
const { data, error } = await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/token?grant_type=web3`, {
|
|
1422
1550
|
headers: this.headers,
|
|
1423
|
-
body: Object.assign({ chain: 'solana', message, signature: (0, base64url_1.bytesToBase64URL)(signature) }, (((
|
|
1424
|
-
? { gotrue_meta_security: { captcha_token: (
|
|
1551
|
+
body: Object.assign({ chain: 'solana', message, signature: (0, base64url_1.bytesToBase64URL)(signature) }, (((_m = credentials.options) === null || _m === void 0 ? void 0 : _m.captchaToken)
|
|
1552
|
+
? { gotrue_meta_security: { captcha_token: (_o = credentials.options) === null || _o === void 0 ? void 0 : _o.captchaToken } }
|
|
1425
1553
|
: null)),
|
|
1426
1554
|
xform: fetch_1._sessionResponse,
|
|
1427
1555
|
});
|
|
@@ -1473,7 +1601,7 @@ class GoTrueClient {
|
|
|
1473
1601
|
}
|
|
1474
1602
|
if (data.session) {
|
|
1475
1603
|
await this._saveSession(data.session);
|
|
1476
|
-
await this._notifyAllSubscribers('SIGNED_IN', data.session);
|
|
1604
|
+
await this._notifyAllSubscribers(redirectType === 'recovery' ? 'PASSWORD_RECOVERY' : 'SIGNED_IN', data.session);
|
|
1477
1605
|
}
|
|
1478
1606
|
return this._returnResult({ data: Object.assign(Object.assign({}, data), { redirectType: redirectType !== null && redirectType !== void 0 ? redirectType : null }), error });
|
|
1479
1607
|
}
|
|
@@ -1676,7 +1804,7 @@ class GoTrueClient {
|
|
|
1676
1804
|
* ```
|
|
1677
1805
|
*/
|
|
1678
1806
|
async signInWithOtp(credentials) {
|
|
1679
|
-
var _a, _b, _c, _d,
|
|
1807
|
+
var _a, _b, _c, _d, _f;
|
|
1680
1808
|
try {
|
|
1681
1809
|
if ('email' in credentials) {
|
|
1682
1810
|
const { email, options } = credentials;
|
|
@@ -1709,7 +1837,7 @@ class GoTrueClient {
|
|
|
1709
1837
|
data: (_c = options === null || options === void 0 ? void 0 : options.data) !== null && _c !== void 0 ? _c : {},
|
|
1710
1838
|
create_user: (_d = options === null || options === void 0 ? void 0 : options.shouldCreateUser) !== null && _d !== void 0 ? _d : true,
|
|
1711
1839
|
gotrue_meta_security: { captcha_token: options === null || options === void 0 ? void 0 : options.captchaToken },
|
|
1712
|
-
channel: (
|
|
1840
|
+
channel: (_f = options === null || options === void 0 ? void 0 : options.channel) !== null && _f !== void 0 ? _f : 'sms',
|
|
1713
1841
|
},
|
|
1714
1842
|
});
|
|
1715
1843
|
return this._returnResult({
|
|
@@ -1955,7 +2083,7 @@ class GoTrueClient {
|
|
|
1955
2083
|
* ```
|
|
1956
2084
|
*/
|
|
1957
2085
|
async signInWithSSO(params) {
|
|
1958
|
-
var _a, _b, _c, _d,
|
|
2086
|
+
var _a, _b, _c, _d, _f;
|
|
1959
2087
|
try {
|
|
1960
2088
|
let codeChallenge = null;
|
|
1961
2089
|
let codeChallengeMethod = null;
|
|
@@ -1971,7 +2099,7 @@ class GoTrueClient {
|
|
|
1971
2099
|
xform: fetch_1._ssoResponse,
|
|
1972
2100
|
});
|
|
1973
2101
|
// Automatically redirect in browser unless skipBrowserRedirect is true
|
|
1974
|
-
if (((_d = result.data) === null || _d === void 0 ? void 0 : _d.url) && (0, helpers_1.isBrowser)() && !((
|
|
2102
|
+
if (((_d = result.data) === null || _d === void 0 ? void 0 : _d.url) && (0, helpers_1.isBrowser)() && !((_f = params.options) === null || _f === void 0 ? void 0 : _f.skipBrowserRedirect)) {
|
|
1975
2103
|
window.location.assign(result.data.url);
|
|
1976
2104
|
}
|
|
1977
2105
|
return this._returnResult(result);
|
|
@@ -2007,9 +2135,13 @@ class GoTrueClient {
|
|
|
2007
2135
|
*/
|
|
2008
2136
|
async reauthenticate() {
|
|
2009
2137
|
await this.initializePromise;
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2138
|
+
if (this.lock != null) {
|
|
2139
|
+
// TODO(v3): remove legacy lock path
|
|
2140
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
2141
|
+
return await this._reauthenticate();
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
return await this._reauthenticate();
|
|
2013
2145
|
}
|
|
2014
2146
|
async _reauthenticate() {
|
|
2015
2147
|
try {
|
|
@@ -2097,15 +2229,26 @@ class GoTrueClient {
|
|
|
2097
2229
|
const endpoint = `${this.url}/resend`;
|
|
2098
2230
|
if ('email' in credentials) {
|
|
2099
2231
|
const { email, type, options } = credentials;
|
|
2232
|
+
let codeChallenge = null;
|
|
2233
|
+
let codeChallengeMethod = null;
|
|
2234
|
+
if (this.flowType === 'pkce') {
|
|
2235
|
+
;
|
|
2236
|
+
[codeChallenge, codeChallengeMethod] = await (0, helpers_1.getCodeChallengeAndMethod)(this.storage, this.storageKey);
|
|
2237
|
+
}
|
|
2100
2238
|
const { error } = await (0, fetch_1._request)(this.fetch, 'POST', endpoint, {
|
|
2101
2239
|
headers: this.headers,
|
|
2102
2240
|
body: {
|
|
2103
2241
|
email,
|
|
2104
2242
|
type,
|
|
2105
2243
|
gotrue_meta_security: { captcha_token: options === null || options === void 0 ? void 0 : options.captchaToken },
|
|
2244
|
+
code_challenge: codeChallenge,
|
|
2245
|
+
code_challenge_method: codeChallengeMethod,
|
|
2106
2246
|
},
|
|
2107
2247
|
redirectTo: options === null || options === void 0 ? void 0 : options.emailRedirectTo,
|
|
2108
2248
|
});
|
|
2249
|
+
if (error) {
|
|
2250
|
+
await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
|
|
2251
|
+
}
|
|
2109
2252
|
return this._returnResult({ data: { user: null, session: null }, error });
|
|
2110
2253
|
}
|
|
2111
2254
|
else if ('phone' in credentials) {
|
|
@@ -2126,6 +2269,7 @@ class GoTrueClient {
|
|
|
2126
2269
|
throw new errors_1.AuthInvalidCredentialsError('You must provide either an email or phone number and a type');
|
|
2127
2270
|
}
|
|
2128
2271
|
catch (error) {
|
|
2272
|
+
await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
|
|
2129
2273
|
if ((0, errors_1.isAuthError)(error)) {
|
|
2130
2274
|
return this._returnResult({ data: { user: null, session: null }, error });
|
|
2131
2275
|
}
|
|
@@ -2141,7 +2285,7 @@ class GoTrueClient {
|
|
|
2141
2285
|
* to the client. If that storage is based on request cookies for example,
|
|
2142
2286
|
* the values in it may not be authentic and therefore it's strongly advised
|
|
2143
2287
|
* against using this method and its results in such circumstances. A warning
|
|
2144
|
-
* will be emitted if this is detected. Use {@link
|
|
2288
|
+
* will be emitted if this is detected. Use {@link GoTrueClient.getUser} instead.
|
|
2145
2289
|
*
|
|
2146
2290
|
* @category Auth
|
|
2147
2291
|
*
|
|
@@ -2152,7 +2296,7 @@ class GoTrueClient {
|
|
|
2152
2296
|
* - If the session's access token is expired or is about to expire, this method will use the refresh token to refresh the session.
|
|
2153
2297
|
* - When using in a browser, or you've called `startAutoRefresh()` in your environment (React Native, etc.) this function always returns a valid access token without refreshing the session itself, as this is done in the background. This function returns very fast.
|
|
2154
2298
|
* - **IMPORTANT SECURITY NOTICE:** If using an insecure storage medium, such as cookies or request headers, the user object returned by this function **must not be trusted**. Always verify the JWT using `getClaims()` or your own JWT verification library to securely establish the user's identity and access. You can also use `getUser()` to fetch the user object directly from the Auth server for this purpose.
|
|
2155
|
-
* -
|
|
2299
|
+
* - Cross-tab refresh races are handled by the GoTrue server (the rotated token from the first tab is returned to subsequent tabs via the parent-of-active mechanism), so no client-side serialization is needed.
|
|
2156
2300
|
*
|
|
2157
2301
|
* @example Get the session data
|
|
2158
2302
|
* ```js
|
|
@@ -2219,15 +2363,24 @@ class GoTrueClient {
|
|
|
2219
2363
|
*/
|
|
2220
2364
|
async getSession() {
|
|
2221
2365
|
await this.initializePromise;
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2366
|
+
if (this.lock != null) {
|
|
2367
|
+
// TODO(v3): remove legacy lock path
|
|
2368
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
2369
|
+
return this._useSession(async (result) => {
|
|
2370
|
+
return result;
|
|
2371
|
+
});
|
|
2225
2372
|
});
|
|
2373
|
+
}
|
|
2374
|
+
return await this._useSession(async (result) => {
|
|
2375
|
+
return result;
|
|
2226
2376
|
});
|
|
2227
|
-
return result;
|
|
2228
2377
|
}
|
|
2229
2378
|
/**
|
|
2230
2379
|
* Acquires a global lock based on the storage key.
|
|
2380
|
+
*
|
|
2381
|
+
* TODO(v3): remove along with the legacy lock path. Only called when
|
|
2382
|
+
* `this.lock` is non-null (custom lock supplied via constructor). The
|
|
2383
|
+
* default lockless path bypasses this entirely.
|
|
2231
2384
|
*/
|
|
2232
2385
|
async _acquireLock(acquireTimeout, fn) {
|
|
2233
2386
|
this._debug('#_acquireLock', 'begin', acquireTimeout);
|
|
@@ -2244,7 +2397,7 @@ class GoTrueClient {
|
|
|
2244
2397
|
try {
|
|
2245
2398
|
await result;
|
|
2246
2399
|
}
|
|
2247
|
-
catch (
|
|
2400
|
+
catch (_e) {
|
|
2248
2401
|
// we just care if it finished
|
|
2249
2402
|
}
|
|
2250
2403
|
})());
|
|
@@ -2283,15 +2436,17 @@ class GoTrueClient {
|
|
|
2283
2436
|
}
|
|
2284
2437
|
}
|
|
2285
2438
|
/**
|
|
2286
|
-
* Use instead of {@link
|
|
2287
|
-
*
|
|
2288
|
-
*
|
|
2289
|
-
* session at once across multiple tabs or processes.
|
|
2439
|
+
* Use instead of {@link GoTrueClient.getSession} inside the library. Loads the session
|
|
2440
|
+
* via `__loadSession` (which may trigger a refresh if the access token is
|
|
2441
|
+
* within the expiry margin) and runs `fn` with the result.
|
|
2290
2442
|
*/
|
|
2291
2443
|
async _useSession(fn) {
|
|
2292
2444
|
this._debug('#_useSession', 'begin');
|
|
2293
2445
|
try {
|
|
2294
|
-
//
|
|
2446
|
+
// Concurrent callers may both reach __loadSession; storage reads are
|
|
2447
|
+
// idempotent, and the only write path inside it (refresh) is
|
|
2448
|
+
// single-flighted downstream by `refreshingDeferred` in
|
|
2449
|
+
// `_callRefreshToken`. No serialization is needed at this layer.
|
|
2295
2450
|
const result = await this.__loadSession();
|
|
2296
2451
|
return await fn(result);
|
|
2297
2452
|
}
|
|
@@ -2302,11 +2457,12 @@ class GoTrueClient {
|
|
|
2302
2457
|
/**
|
|
2303
2458
|
* NEVER USE DIRECTLY!
|
|
2304
2459
|
*
|
|
2305
|
-
* Always use
|
|
2460
|
+
* Always use `_useSession`.
|
|
2306
2461
|
*/
|
|
2307
2462
|
async __loadSession() {
|
|
2308
2463
|
this._debug('#__loadSession()', 'begin');
|
|
2309
|
-
if (!this.lockAcquired) {
|
|
2464
|
+
if (this.lock != null && !this.lockAcquired) {
|
|
2465
|
+
// TODO(v3): remove. Only meaningful on the legacy lock path.
|
|
2310
2466
|
this._debug('#__loadSession()', 'used outside of an acquired lock!', new Error().stack);
|
|
2311
2467
|
}
|
|
2312
2468
|
try {
|
|
@@ -2360,6 +2516,25 @@ class GoTrueClient {
|
|
|
2360
2516
|
}
|
|
2361
2517
|
const { data: session, error } = await this._callRefreshToken(currentSession.refresh_token);
|
|
2362
2518
|
if (error) {
|
|
2519
|
+
// Proactive-preserve mirror: `_callRefreshToken` keeps the session
|
|
2520
|
+
// in storage when refresh fails non-retryably but the access token
|
|
2521
|
+
// is still inside its real expiry window. Hand the caller the
|
|
2522
|
+
// still-valid session instead of translating the refresh error
|
|
2523
|
+
// into `session: null`. If the access token has actually expired,
|
|
2524
|
+
// the session is genuinely dead and the error stands. Explicit
|
|
2525
|
+
// refresh entry points (`refreshSession`, `setSession`)
|
|
2526
|
+
// intentionally bypass this fallback — they want to know the
|
|
2527
|
+
// refresh failed.
|
|
2528
|
+
const accessTokenStillValid = !!(currentSession.expires_at && currentSession.expires_at * 1000 > Date.now());
|
|
2529
|
+
if (accessTokenStillValid) {
|
|
2530
|
+
// Race guard: a concurrent `signOut` may have cleared storage
|
|
2531
|
+
// during the refresh attempt. Don't hand back a session that no
|
|
2532
|
+
// longer exists on disk.
|
|
2533
|
+
const stillStored = (await (0, helpers_1.getItemAsync)(this.storage, this.storageKey));
|
|
2534
|
+
if (stillStored && stillStored.refresh_token === currentSession.refresh_token) {
|
|
2535
|
+
return this._returnResult({ data: { session: currentSession }, error: null });
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2363
2538
|
return this._returnResult({ data: { session: null }, error });
|
|
2364
2539
|
}
|
|
2365
2540
|
return this._returnResult({ data: { session }, error: null });
|
|
@@ -2449,9 +2624,16 @@ class GoTrueClient {
|
|
|
2449
2624
|
return await this._getUser(jwt);
|
|
2450
2625
|
}
|
|
2451
2626
|
await this.initializePromise;
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2627
|
+
let result;
|
|
2628
|
+
if (this.lock != null) {
|
|
2629
|
+
// TODO(v3): remove legacy lock path
|
|
2630
|
+
result = await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
2631
|
+
return await this._getUser();
|
|
2632
|
+
});
|
|
2633
|
+
}
|
|
2634
|
+
else {
|
|
2635
|
+
result = await this._getUser();
|
|
2636
|
+
}
|
|
2455
2637
|
if (result.data.user) {
|
|
2456
2638
|
this.suppressGetSessionWarning = true;
|
|
2457
2639
|
}
|
|
@@ -2612,9 +2794,13 @@ class GoTrueClient {
|
|
|
2612
2794
|
*/
|
|
2613
2795
|
async updateUser(attributes, options = {}) {
|
|
2614
2796
|
await this.initializePromise;
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2797
|
+
if (this.lock != null) {
|
|
2798
|
+
// TODO(v3): remove legacy lock path
|
|
2799
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
2800
|
+
return await this._updateUser(attributes, options);
|
|
2801
|
+
});
|
|
2802
|
+
}
|
|
2803
|
+
return await this._updateUser(attributes, options);
|
|
2618
2804
|
}
|
|
2619
2805
|
async _updateUser(attributes, options = {}) {
|
|
2620
2806
|
try {
|
|
@@ -2783,9 +2969,13 @@ class GoTrueClient {
|
|
|
2783
2969
|
*/
|
|
2784
2970
|
async setSession(currentSession) {
|
|
2785
2971
|
await this.initializePromise;
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2972
|
+
if (this.lock != null) {
|
|
2973
|
+
// TODO(v3): remove legacy lock path
|
|
2974
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
2975
|
+
return await this._setSession(currentSession);
|
|
2976
|
+
});
|
|
2977
|
+
}
|
|
2978
|
+
return await this._setSession(currentSession);
|
|
2789
2979
|
}
|
|
2790
2980
|
async _setSession(currentSession) {
|
|
2791
2981
|
try {
|
|
@@ -2963,9 +3153,13 @@ class GoTrueClient {
|
|
|
2963
3153
|
*/
|
|
2964
3154
|
async refreshSession(currentSession) {
|
|
2965
3155
|
await this.initializePromise;
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
3156
|
+
if (this.lock != null) {
|
|
3157
|
+
// TODO(v3): remove legacy lock path
|
|
3158
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
3159
|
+
return await this._refreshSession(currentSession);
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
return await this._refreshSession(currentSession);
|
|
2969
3163
|
}
|
|
2970
3164
|
async _refreshSession(currentSession) {
|
|
2971
3165
|
try {
|
|
@@ -3002,6 +3196,7 @@ class GoTrueClient {
|
|
|
3002
3196
|
* Gets the session data from a URL string
|
|
3003
3197
|
*/
|
|
3004
3198
|
async _getSessionFromURL(params, callbackUrlType) {
|
|
3199
|
+
var _a;
|
|
3005
3200
|
try {
|
|
3006
3201
|
if (!(0, helpers_1.isBrowser)())
|
|
3007
3202
|
throw new errors_1.AuthImplicitGrantRedirectError('No browser detected.');
|
|
@@ -3040,7 +3235,10 @@ class GoTrueClient {
|
|
|
3040
3235
|
const url = new URL(window.location.href);
|
|
3041
3236
|
url.searchParams.delete('code');
|
|
3042
3237
|
window.history.replaceState(window.history.state, '', url.toString());
|
|
3043
|
-
return {
|
|
3238
|
+
return {
|
|
3239
|
+
data: { session: data.session, redirectType: (_a = data.redirectType) !== null && _a !== void 0 ? _a : null },
|
|
3240
|
+
error: null,
|
|
3241
|
+
};
|
|
3044
3242
|
}
|
|
3045
3243
|
const { provider_token, provider_refresh_token, access_token, refresh_token, expires_in, expires_at, token_type, } = params;
|
|
3046
3244
|
if (!access_token || !expires_in || !refresh_token || !token_type) {
|
|
@@ -3099,7 +3297,7 @@ class GoTrueClient {
|
|
|
3099
3297
|
if (typeof this.detectSessionInUrl === 'function') {
|
|
3100
3298
|
return this.detectSessionInUrl(new URL(window.location.href), params);
|
|
3101
3299
|
}
|
|
3102
|
-
return Boolean(params.access_token || params.error_description);
|
|
3300
|
+
return Boolean(params.access_token || params.error || params.error_description || params.error_code);
|
|
3103
3301
|
}
|
|
3104
3302
|
/**
|
|
3105
3303
|
* Checks if the current URL and backing storage contain parameters given by a PKCE flow
|
|
@@ -3116,37 +3314,56 @@ class GoTrueClient {
|
|
|
3116
3314
|
*
|
|
3117
3315
|
* If using `others` scope, no `SIGNED_OUT` event is fired!
|
|
3118
3316
|
*
|
|
3317
|
+
* **Warning:** the default `scope` is `'global'`. This signs the user out of
|
|
3318
|
+
* **every device they are currently signed in on**, not just the current
|
|
3319
|
+
* tab/session. If you only want to sign the user out of the current session
|
|
3320
|
+
* (the behavior most other auth libraries default to), pass
|
|
3321
|
+
* `{ scope: 'local' }` explicitly.
|
|
3322
|
+
*
|
|
3119
3323
|
* @category Auth
|
|
3120
3324
|
*
|
|
3121
3325
|
* @remarks
|
|
3122
3326
|
* - In order to use the `signOut()` method, the user needs to be signed in first.
|
|
3123
|
-
* - By default, `signOut()` uses the global scope, which signs out
|
|
3327
|
+
* - By default, `signOut()` uses the **global** scope, which signs out the user
|
|
3328
|
+
* on every device they are signed in on (not just the current one). Pass
|
|
3329
|
+
* `{ scope: 'local' }` to only sign out the current session. This is
|
|
3330
|
+
* usually what apps want on a "Sign out" button, especially when users
|
|
3331
|
+
* sign in from multiple devices and do not expect signing out of one to
|
|
3332
|
+
* terminate the others.
|
|
3124
3333
|
* - Since Supabase Auth uses JWTs for authentication, the access token JWT will be valid until it's expired. When the user signs out, Supabase revokes the refresh token and deletes the JWT from the client-side. This does not revoke the JWT and it will still be valid until it expires.
|
|
3125
3334
|
*
|
|
3126
|
-
* @example Sign out (
|
|
3335
|
+
* @example Sign out of every device (global – default)
|
|
3127
3336
|
* ```js
|
|
3128
3337
|
* const { error } = await supabase.auth.signOut()
|
|
3129
3338
|
* ```
|
|
3130
3339
|
*
|
|
3131
|
-
* @example Sign out
|
|
3340
|
+
* @example Sign out only the current session (recommended for most apps)
|
|
3132
3341
|
* ```js
|
|
3133
3342
|
* const { error } = await supabase.auth.signOut({ scope: 'local' })
|
|
3134
3343
|
* ```
|
|
3135
3344
|
*
|
|
3136
|
-
* @example Sign out
|
|
3345
|
+
* @example Sign out of all other sessions, keep the current one
|
|
3137
3346
|
* ```js
|
|
3138
3347
|
* const { error } = await supabase.auth.signOut({ scope: 'others' })
|
|
3139
3348
|
* ```
|
|
3140
3349
|
*/
|
|
3141
3350
|
async signOut(options = { scope: 'global' }) {
|
|
3142
3351
|
await this.initializePromise;
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3352
|
+
if (this.lock != null) {
|
|
3353
|
+
// TODO(v3): remove legacy lock path
|
|
3354
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
3355
|
+
return await this._signOut(options);
|
|
3356
|
+
});
|
|
3357
|
+
}
|
|
3358
|
+
return await this._signOut(options);
|
|
3146
3359
|
}
|
|
3147
3360
|
async _signOut({ scope } = { scope: 'global' }) {
|
|
3148
3361
|
return await this._useSession(async (result) => {
|
|
3149
3362
|
var _a;
|
|
3363
|
+
const removeCurrentSession = async () => {
|
|
3364
|
+
await this._removeSession();
|
|
3365
|
+
await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
|
|
3366
|
+
};
|
|
3150
3367
|
const { data, error: sessionError } = result;
|
|
3151
3368
|
if (sessionError && !(0, errors_1.isAuthSessionMissingError)(sessionError)) {
|
|
3152
3369
|
return this._returnResult({ error: sessionError });
|
|
@@ -3160,13 +3377,15 @@ class GoTrueClient {
|
|
|
3160
3377
|
if (!(((0, errors_1.isAuthApiError)(error) &&
|
|
3161
3378
|
(error.status === 404 || error.status === 401 || error.status === 403)) ||
|
|
3162
3379
|
(0, errors_1.isAuthSessionMissingError)(error))) {
|
|
3380
|
+
if (scope !== 'others') {
|
|
3381
|
+
await removeCurrentSession();
|
|
3382
|
+
}
|
|
3163
3383
|
return this._returnResult({ error });
|
|
3164
3384
|
}
|
|
3165
3385
|
}
|
|
3166
3386
|
}
|
|
3167
3387
|
if (scope !== 'others') {
|
|
3168
|
-
await
|
|
3169
|
-
await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
|
|
3388
|
+
await removeCurrentSession();
|
|
3170
3389
|
}
|
|
3171
3390
|
return this._returnResult({ error: null });
|
|
3172
3391
|
});
|
|
@@ -3178,18 +3397,8 @@ class GoTrueClient {
|
|
|
3178
3397
|
* - Subscribes to important events occurring on the user's session.
|
|
3179
3398
|
* - Use on the frontend/client. It is less useful on the server.
|
|
3180
3399
|
* - Events are emitted across tabs to keep your application's UI up-to-date. Some events can fire very frequently, based on the number of tabs open. Use a quick and efficient callback function, and defer or debounce as many operations as you can to be performed outside of the callback.
|
|
3181
|
-
* -
|
|
3182
|
-
*
|
|
3183
|
-
* - Limit the number of `await` calls in `async` callbacks.
|
|
3184
|
-
* - Do not use other Supabase functions in the callback function. If you must, dispatch the functions once the callback has finished executing. Use this as a quick way to achieve this:
|
|
3185
|
-
* ```js
|
|
3186
|
-
* supabase.auth.onAuthStateChange((event, session) => {
|
|
3187
|
-
* setTimeout(async () => {
|
|
3188
|
-
* // await on other Supabase function here
|
|
3189
|
-
* // this runs right after the callback has finished
|
|
3190
|
-
* }, 0)
|
|
3191
|
-
* })
|
|
3192
|
-
* ```
|
|
3400
|
+
* - Callbacks can be `async` and can safely call other Supabase auth methods (`getUser`, `setSession`, etc.) from inside the callback.
|
|
3401
|
+
* - Keep callbacks quick. Events are awaited in order, so a slow callback delays subsequent events to subscribers in this tab.
|
|
3193
3402
|
* - Emitted events:
|
|
3194
3403
|
* - `INITIAL_SESSION`
|
|
3195
3404
|
* - Emitted right after the Supabase client is constructed and the initial session from storage is loaded.
|
|
@@ -3372,9 +3581,15 @@ class GoTrueClient {
|
|
|
3372
3581
|
this.stateChangeEmitters.set(id, subscription);
|
|
3373
3582
|
(async () => {
|
|
3374
3583
|
await this.initializePromise;
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3584
|
+
if (this.lock != null) {
|
|
3585
|
+
// TODO(v3): remove legacy lock path
|
|
3586
|
+
await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
3587
|
+
this._emitInitialSession(id);
|
|
3588
|
+
});
|
|
3589
|
+
}
|
|
3590
|
+
else {
|
|
3591
|
+
await this._emitInitialSession(id);
|
|
3592
|
+
}
|
|
3378
3593
|
})();
|
|
3379
3594
|
return { data: { subscription } };
|
|
3380
3595
|
}
|
|
@@ -3391,7 +3606,10 @@ class GoTrueClient {
|
|
|
3391
3606
|
catch (err) {
|
|
3392
3607
|
await ((_b = this.stateChangeEmitters.get(id)) === null || _b === void 0 ? void 0 : _b.callback('INITIAL_SESSION', null));
|
|
3393
3608
|
this._debug('INITIAL_SESSION', 'callback id', id, 'error', err);
|
|
3394
|
-
if ((0, errors_1.isAuthSessionMissingError)(err)) {
|
|
3609
|
+
if ((0, errors_1.isAuthSessionMissingError)(err) || (0, errors_1.isAuthRetryableFetchError)(err)) {
|
|
3610
|
+
// A missing session or a transient/aborted network failure (e.g. a
|
|
3611
|
+
// superseded page navigation cancelling the in-flight request) is
|
|
3612
|
+
// not an application error — warn rather than surface it raw.
|
|
3395
3613
|
console.warn(err);
|
|
3396
3614
|
}
|
|
3397
3615
|
else {
|
|
@@ -3587,7 +3805,7 @@ class GoTrueClient {
|
|
|
3587
3805
|
var _a;
|
|
3588
3806
|
try {
|
|
3589
3807
|
const { data, error } = await this._useSession(async (result) => {
|
|
3590
|
-
var _a, _b, _c, _d,
|
|
3808
|
+
var _a, _b, _c, _d, _f;
|
|
3591
3809
|
const { data, error } = result;
|
|
3592
3810
|
if (error)
|
|
3593
3811
|
throw error;
|
|
@@ -3599,7 +3817,7 @@ class GoTrueClient {
|
|
|
3599
3817
|
});
|
|
3600
3818
|
return await (0, fetch_1._request)(this.fetch, 'GET', url, {
|
|
3601
3819
|
headers: this.headers,
|
|
3602
|
-
jwt: (
|
|
3820
|
+
jwt: (_f = (_d = data.session) === null || _d === void 0 ? void 0 : _d.access_token) !== null && _f !== void 0 ? _f : undefined,
|
|
3603
3821
|
});
|
|
3604
3822
|
});
|
|
3605
3823
|
if (error)
|
|
@@ -3716,7 +3934,10 @@ class GoTrueClient {
|
|
|
3716
3934
|
* @param refreshToken A valid refresh token that was returned on login.
|
|
3717
3935
|
*/
|
|
3718
3936
|
async _refreshAccessToken(refreshToken) {
|
|
3719
|
-
|
|
3937
|
+
// Refresh tokens are long-lived bearer credentials; do NOT include any
|
|
3938
|
+
// fragment of the token in the debug tag, even when `debug: true` is
|
|
3939
|
+
// enabled (logs may be forwarded to third-party services).
|
|
3940
|
+
const debugName = `#_refreshAccessToken()`;
|
|
3720
3941
|
this._debug(debugName, 'begin');
|
|
3721
3942
|
try {
|
|
3722
3943
|
const startedAt = Date.now();
|
|
@@ -3823,10 +4044,17 @@ class GoTrueClient {
|
|
|
3823
4044
|
if (this.autoRefreshToken && currentSession.refresh_token) {
|
|
3824
4045
|
const { error } = await this._callRefreshToken(currentSession.refresh_token);
|
|
3825
4046
|
if (error) {
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
4047
|
+
// `_callRefreshToken` is the single source of truth for refresh
|
|
4048
|
+
// outcomes: it removes the session itself when the access token
|
|
4049
|
+
// is actually expired, and preserves it when the token is still
|
|
4050
|
+
// valid (proactive-preserve). Don't second-guess that here — a
|
|
4051
|
+
// local `_removeSession` would emit a duplicate `SIGNED_OUT` on
|
|
4052
|
+
// genuine failures and undo the proactive-preserve at init time.
|
|
4053
|
+
if ((0, errors_1.isAuthRefreshDiscardedError)(error)) {
|
|
4054
|
+
this._debug(debugName, 'refresh discarded by commit guard', error);
|
|
4055
|
+
}
|
|
4056
|
+
else {
|
|
4057
|
+
this._debug(debugName, 'refresh failed', error);
|
|
3830
4058
|
}
|
|
3831
4059
|
}
|
|
3832
4060
|
}
|
|
@@ -3859,7 +4087,16 @@ class GoTrueClient {
|
|
|
3859
4087
|
}
|
|
3860
4088
|
catch (err) {
|
|
3861
4089
|
this._debug(debugName, 'error', err);
|
|
3862
|
-
|
|
4090
|
+
if ((0, errors_1.isAuthRetryableFetchError)(err)) {
|
|
4091
|
+
// Transient/aborted network failure during session recovery (e.g. a
|
|
4092
|
+
// superseded page navigation cancelling the in-flight request). Warn
|
|
4093
|
+
// rather than surface it raw; the session is untouched and recovery
|
|
4094
|
+
// will run again on the next load.
|
|
4095
|
+
console.warn(err);
|
|
4096
|
+
}
|
|
4097
|
+
else {
|
|
4098
|
+
console.error(err);
|
|
4099
|
+
}
|
|
3863
4100
|
return;
|
|
3864
4101
|
}
|
|
3865
4102
|
finally {
|
|
@@ -3875,18 +4112,91 @@ class GoTrueClient {
|
|
|
3875
4112
|
if (this.refreshingDeferred) {
|
|
3876
4113
|
return this.refreshingDeferred.promise;
|
|
3877
4114
|
}
|
|
3878
|
-
|
|
4115
|
+
// Serial failure cooldown: callers passing the *same* refresh token
|
|
4116
|
+
// after a recent failure receive the cached result instead of firing
|
|
4117
|
+
// another `/token` request. This caps the proactive-refresh storm
|
|
4118
|
+
// where every `getSession()` call inside the 90s EXPIRY_MARGIN_MS
|
|
4119
|
+
// window kept re-firing against the same broken refresh token during
|
|
4120
|
+
// outages. Concurrent callers already share `refreshingDeferred`; this
|
|
4121
|
+
// cache covers serial callers spaced across cooldown windows.
|
|
4122
|
+
//
|
|
4123
|
+
// Token-keyed so callers with a fresh refresh token (rotation pickup
|
|
4124
|
+
// from another tab, explicit `setSession`/`refreshSession({ refresh_token })`,
|
|
4125
|
+
// multi-account switch) bypass the cache and attempt a real refresh.
|
|
4126
|
+
if (this.lastRefreshFailure &&
|
|
4127
|
+
this.lastRefreshFailure.refreshToken === refreshToken &&
|
|
4128
|
+
Date.now() < this.lastRefreshFailure.expiresAt) {
|
|
4129
|
+
this._debug('#_callRefreshToken()', 'returning cached failure (cooldown active)');
|
|
4130
|
+
return this.lastRefreshFailure.result;
|
|
4131
|
+
}
|
|
4132
|
+
// Refresh tokens are long-lived bearer credentials; do NOT include any
|
|
4133
|
+
// fragment of the token in the debug tag, even when `debug: true` is
|
|
4134
|
+
// enabled (logs may be forwarded to third-party services).
|
|
4135
|
+
const debugName = `#_callRefreshToken()`;
|
|
3879
4136
|
this._debug(debugName, 'begin');
|
|
3880
4137
|
try {
|
|
3881
4138
|
this.refreshingDeferred = new helpers_1.Deferred();
|
|
4139
|
+
// Snapshot storage before the fetch. The commit guard discards the
|
|
4140
|
+
// rotated tokens only when a non-null pre-fetch snapshot changed under
|
|
4141
|
+
// us — typical case: a concurrent `signOut` ran `_removeSession`, or
|
|
4142
|
+
// another tab's refresh rewrote the slot. Callers passing
|
|
4143
|
+
// externally-sourced tokens (SSR cookie handoff, multi-account
|
|
4144
|
+
// switching, `setSession`/`refreshSession({ refresh_token })`) may
|
|
4145
|
+
// start from a null snapshot OR from a non-null snapshot whose
|
|
4146
|
+
// refresh_token differs from the one they're hydrating; in both
|
|
4147
|
+
// cases the guard fires only when storage was *modified between
|
|
4148
|
+
// snapshots*, not when the input token disagrees with what's stored.
|
|
4149
|
+
const storedAtStart = (await (0, helpers_1.getItemAsync)(this.storage, this.storageKey));
|
|
3882
4150
|
const { data, error } = await this._refreshAccessToken(refreshToken);
|
|
3883
4151
|
if (error)
|
|
3884
4152
|
throw error;
|
|
3885
4153
|
if (!data.session)
|
|
3886
4154
|
throw new errors_1.AuthSessionMissingError();
|
|
4155
|
+
const storedAfter = (await (0, helpers_1.getItemAsync)(this.storage, this.storageKey));
|
|
4156
|
+
const storageChangedUnderUs = storedAtStart !== null &&
|
|
4157
|
+
(storedAfter === null || storedAfter.refresh_token !== storedAtStart.refresh_token);
|
|
4158
|
+
if (storageChangedUnderUs) {
|
|
4159
|
+
this._debug(debugName, 'commit guard: storage changed since refresh started, discarding rotated tokens', {
|
|
4160
|
+
// Presence indicators only — never log refresh token fragments,
|
|
4161
|
+
// even partial. Logs may be forwarded to third-party services.
|
|
4162
|
+
startedWith: 'present',
|
|
4163
|
+
nowHolds: storedAfter ? 'replaced' : 'cleared',
|
|
4164
|
+
});
|
|
4165
|
+
const discarded = {
|
|
4166
|
+
data: null,
|
|
4167
|
+
error: new errors_1.AuthRefreshDiscardedError(),
|
|
4168
|
+
};
|
|
4169
|
+
this.refreshingDeferred.resolve(discarded);
|
|
4170
|
+
return discarded;
|
|
4171
|
+
}
|
|
4172
|
+
// Second leg of the commit guard: close the TOCTOU window between the
|
|
4173
|
+
// synchronous `storageChangedUnderUs` check and the actual storage
|
|
4174
|
+
// writes inside `_saveSession`. A concurrent `signOut → _removeSession`
|
|
4175
|
+
// can land inside `_saveSession`'s `await setItemAsync(...)` yields and
|
|
4176
|
+
// clear storage just before we overwrite it. Capture the epoch BEFORE
|
|
4177
|
+
// the save and re-check after; if it advanced, undo the write directly
|
|
4178
|
+
// (do NOT call `_removeSession` — that would emit a duplicate
|
|
4179
|
+
// SIGNED_OUT for the concurrent signOut that already fired one).
|
|
4180
|
+
const epochBeforeSave = this._sessionRemovalEpoch;
|
|
3887
4181
|
await this._saveSession(data.session);
|
|
4182
|
+
if (this._sessionRemovalEpoch !== epochBeforeSave) {
|
|
4183
|
+
this._debug(debugName, 'commit guard (post-save): _removeSession ran during _saveSession, undoing write');
|
|
4184
|
+
await (0, helpers_1.removeItemAsync)(this.storage, this.storageKey);
|
|
4185
|
+
if (this.userStorage) {
|
|
4186
|
+
await (0, helpers_1.removeItemAsync)(this.userStorage, this.storageKey + '-user');
|
|
4187
|
+
}
|
|
4188
|
+
const discarded = {
|
|
4189
|
+
data: null,
|
|
4190
|
+
error: new errors_1.AuthRefreshDiscardedError(),
|
|
4191
|
+
};
|
|
4192
|
+
this.refreshingDeferred.resolve(discarded);
|
|
4193
|
+
return discarded;
|
|
4194
|
+
}
|
|
3888
4195
|
await this._notifyAllSubscribers('TOKEN_REFRESHED', data.session);
|
|
3889
4196
|
const result = { data: data.session, error: null };
|
|
4197
|
+
// Refresh succeeded — clear any cached failure so the next caller
|
|
4198
|
+
// (including the auto-refresh ticker) attempts a real refresh again.
|
|
4199
|
+
this.lastRefreshFailure = null;
|
|
3890
4200
|
this.refreshingDeferred.resolve(result);
|
|
3891
4201
|
return result;
|
|
3892
4202
|
}
|
|
@@ -3895,8 +4205,35 @@ class GoTrueClient {
|
|
|
3895
4205
|
if ((0, errors_1.isAuthError)(error)) {
|
|
3896
4206
|
const result = { data: null, error };
|
|
3897
4207
|
if (!(0, errors_1.isAuthRetryableFetchError)(error)) {
|
|
3898
|
-
|
|
4208
|
+
// Proactive vs reactive distinction: a refresh fires whenever
|
|
4209
|
+
// the access token is within EXPIRY_MARGIN_MS of expiry. If the
|
|
4210
|
+
// access token is *still valid* at this moment, the refresh was
|
|
4211
|
+
// proactive and the existing session is still usable until its
|
|
4212
|
+
// real expiry — destroying it now would log out a user whose
|
|
4213
|
+
// access token works. If the access token has actually expired,
|
|
4214
|
+
// the refresh token is the only credential left and it just got
|
|
4215
|
+
// rejected — the session is genuinely dead. `__loadSession`
|
|
4216
|
+
// mirrors this distinction on the read path so callers see the
|
|
4217
|
+
// preserved session instead of `session: null`.
|
|
4218
|
+
const storedNow = (await (0, helpers_1.getItemAsync)(this.storage, this.storageKey));
|
|
4219
|
+
const accessTokenStillValid = !!((storedNow === null || storedNow === void 0 ? void 0 : storedNow.expires_at) && storedNow.expires_at * 1000 > Date.now());
|
|
4220
|
+
if (accessTokenStillValid) {
|
|
4221
|
+
this._debug(debugName, 'proactive refresh failed, access token still valid — preserving session');
|
|
4222
|
+
}
|
|
4223
|
+
else {
|
|
4224
|
+
await this._removeSession();
|
|
4225
|
+
}
|
|
3899
4226
|
}
|
|
4227
|
+
// Cache the failure so serial callers (and the next auto-refresh
|
|
4228
|
+
// tick) passing the same refresh token within the cooldown window
|
|
4229
|
+
// receive it synchronously instead of firing another `/token`
|
|
4230
|
+
// call. Set after the optional `_removeSession` above (which
|
|
4231
|
+
// clears the cache as part of teardown) so the cache survives.
|
|
4232
|
+
this.lastRefreshFailure = {
|
|
4233
|
+
refreshToken,
|
|
4234
|
+
result,
|
|
4235
|
+
expiresAt: Date.now() + constants_1.REFRESH_FAILURE_COOLDOWN_MS,
|
|
4236
|
+
};
|
|
3900
4237
|
(_a = this.refreshingDeferred) === null || _a === void 0 ? void 0 : _a.resolve(result);
|
|
3901
4238
|
return result;
|
|
3902
4239
|
}
|
|
@@ -3909,6 +4246,19 @@ class GoTrueClient {
|
|
|
3909
4246
|
}
|
|
3910
4247
|
}
|
|
3911
4248
|
async _notifyAllSubscribers(event, session, broadcast = true) {
|
|
4249
|
+
if (this._pendingInitNotifications !== null && broadcast) {
|
|
4250
|
+
// We're inside initialize() before initializePromise has resolved, and
|
|
4251
|
+
// this notification originates from the init chain (_recoverAndRefresh),
|
|
4252
|
+
// which always fires with broadcast = true. Enqueue instead of firing so
|
|
4253
|
+
// that callbacks can safely call getSession() / getUser() without
|
|
4254
|
+
// deadlocking on initializePromise.
|
|
4255
|
+
//
|
|
4256
|
+
// Notifications with broadcast = false come from the incoming
|
|
4257
|
+
// BroadcastChannel handler (a cross-tab event), not the init chain, so
|
|
4258
|
+
// they are fired immediately to preserve multi-tab ordering.
|
|
4259
|
+
this._pendingInitNotifications.push({ event, session, broadcast });
|
|
4260
|
+
return;
|
|
4261
|
+
}
|
|
3912
4262
|
const debugName = `#_notifyAllSubscribers(${event})`;
|
|
3913
4263
|
this._debug(debugName, 'begin', session, `broadcast = ${broadcast}`);
|
|
3914
4264
|
try {
|
|
@@ -3945,7 +4295,6 @@ class GoTrueClient {
|
|
|
3945
4295
|
// _saveSession is always called whenever a new session has been acquired
|
|
3946
4296
|
// so we can safely suppress the warning returned by future getSession calls
|
|
3947
4297
|
this.suppressGetSessionWarning = true;
|
|
3948
|
-
await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
|
|
3949
4298
|
// Create a shallow copy to work with, to avoid mutating the original session object if it's used elsewhere
|
|
3950
4299
|
const sessionToProcess = Object.assign({}, session);
|
|
3951
4300
|
const userIsProxy = sessionToProcess.user && sessionToProcess.user.__isUserNotAvailableProxy === true;
|
|
@@ -3978,7 +4327,15 @@ class GoTrueClient {
|
|
|
3978
4327
|
}
|
|
3979
4328
|
}
|
|
3980
4329
|
async _removeSession() {
|
|
4330
|
+
// Bump synchronously, BEFORE any `await`, so that `_callRefreshToken`'s
|
|
4331
|
+
// post-save check sees the increment whenever this method has started —
|
|
4332
|
+
// even if it hasn't finished. Pairs with the epoch check in
|
|
4333
|
+
// `_callRefreshToken`. See `_sessionRemovalEpoch` field doc.
|
|
4334
|
+
this._sessionRemovalEpoch += 1;
|
|
3981
4335
|
this._debug('#_removeSession()');
|
|
4336
|
+
// The session is gone — no point holding on to a cached refresh failure
|
|
4337
|
+
// for a token that no longer exists. Synchronous, before any `await`.
|
|
4338
|
+
this.lastRefreshFailure = null;
|
|
3982
4339
|
this.suppressGetSessionWarning = false;
|
|
3983
4340
|
await (0, helpers_1.removeItemAsync)(this.storage, this.storageKey);
|
|
3984
4341
|
await (0, helpers_1.removeItemAsync)(this.storage, this.storageKey + '-code-verifier');
|
|
@@ -3991,8 +4348,8 @@ class GoTrueClient {
|
|
|
3991
4348
|
/**
|
|
3992
4349
|
* Removes any registered visibilitychange callback.
|
|
3993
4350
|
*
|
|
3994
|
-
* {@
|
|
3995
|
-
* {@
|
|
4351
|
+
* {@link GoTrueClient.startAutoRefresh}
|
|
4352
|
+
* {@link GoTrueClient.stopAutoRefresh}
|
|
3996
4353
|
*/
|
|
3997
4354
|
_removeVisibilityChangedCallback() {
|
|
3998
4355
|
this._debug('#_removeVisibilityChangedCallback()');
|
|
@@ -4008,7 +4365,7 @@ class GoTrueClient {
|
|
|
4008
4365
|
}
|
|
4009
4366
|
}
|
|
4010
4367
|
/**
|
|
4011
|
-
* This is the private implementation of {@link
|
|
4368
|
+
* This is the private implementation of {@link GoTrueClient.startAutoRefresh}. Use this
|
|
4012
4369
|
* within the library.
|
|
4013
4370
|
*/
|
|
4014
4371
|
async _startAutoRefresh() {
|
|
@@ -4050,7 +4407,7 @@ class GoTrueClient {
|
|
|
4050
4407
|
}
|
|
4051
4408
|
}
|
|
4052
4409
|
/**
|
|
4053
|
-
* This is the private implementation of {@link
|
|
4410
|
+
* This is the private implementation of {@link GoTrueClient.stopAutoRefresh}. Use this
|
|
4054
4411
|
* within the library.
|
|
4055
4412
|
*/
|
|
4056
4413
|
async _stopAutoRefresh() {
|
|
@@ -4086,7 +4443,7 @@ class GoTrueClient {
|
|
|
4086
4443
|
* platform's foreground indication mechanism and call these methods
|
|
4087
4444
|
* appropriately to conserve resources.
|
|
4088
4445
|
*
|
|
4089
|
-
* {@
|
|
4446
|
+
* {@link GoTrueClient.stopAutoRefresh}
|
|
4090
4447
|
*
|
|
4091
4448
|
* @category Auth
|
|
4092
4449
|
*
|
|
@@ -4120,7 +4477,7 @@ class GoTrueClient {
|
|
|
4120
4477
|
* If you call this method any managed visibility change callback will be
|
|
4121
4478
|
* removed and you must manage visibility changes on your own.
|
|
4122
4479
|
*
|
|
4123
|
-
* See {@link
|
|
4480
|
+
* See {@link GoTrueClient.startAutoRefresh} for more details.
|
|
4124
4481
|
*
|
|
4125
4482
|
* @category Auth
|
|
4126
4483
|
*
|
|
@@ -4148,47 +4505,122 @@ class GoTrueClient {
|
|
|
4148
4505
|
this._removeVisibilityChangedCallback();
|
|
4149
4506
|
await this._stopAutoRefresh();
|
|
4150
4507
|
}
|
|
4508
|
+
/**
|
|
4509
|
+
* Tears down the client's background work: stops the auto-refresh interval,
|
|
4510
|
+
* removes the `visibilitychange` listener, closes the cross-tab
|
|
4511
|
+
* `BroadcastChannel`, and clears registered `onAuthStateChange` subscribers.
|
|
4512
|
+
*
|
|
4513
|
+
* Call this from cleanup hooks when the client is being replaced before
|
|
4514
|
+
* its JS realm is destroyed. React Strict Mode and HMR are the common
|
|
4515
|
+
* cases. Any in-flight `fetch` calls continue to completion and may still
|
|
4516
|
+
* write to storage; dispose doesn't abort them or erase storage.
|
|
4517
|
+
*
|
|
4518
|
+
* Lifecycle caveat: because in-flight refreshes are not aborted, a
|
|
4519
|
+
* disposed instance can still persist a rotated session to storage after
|
|
4520
|
+
* `dispose()` returns. A subsequent `createClient` against the same
|
|
4521
|
+
* `storageKey` will pick up that session on its next read. If you need
|
|
4522
|
+
* strict isolation between client lifecycles, await any pending auth
|
|
4523
|
+
* operation before calling `dispose()` (or change the `storageKey` for
|
|
4524
|
+
* the replacement client).
|
|
4525
|
+
*
|
|
4526
|
+
* Safe to call repeatedly.
|
|
4527
|
+
*
|
|
4528
|
+
* @category Auth
|
|
4529
|
+
*
|
|
4530
|
+
* @example Cleanup on React unmount
|
|
4531
|
+
* ```ts
|
|
4532
|
+
* useEffect(() => {
|
|
4533
|
+
* const client = createClient(...)
|
|
4534
|
+
* return () => { client.auth.dispose() }
|
|
4535
|
+
* }, [])
|
|
4536
|
+
* ```
|
|
4537
|
+
*/
|
|
4538
|
+
async dispose() {
|
|
4539
|
+
var _a;
|
|
4540
|
+
this._removeVisibilityChangedCallback();
|
|
4541
|
+
await this._stopAutoRefresh();
|
|
4542
|
+
(_a = this.broadcastChannel) === null || _a === void 0 ? void 0 : _a.close();
|
|
4543
|
+
this.broadcastChannel = null;
|
|
4544
|
+
this.stateChangeEmitters.clear();
|
|
4545
|
+
}
|
|
4151
4546
|
/**
|
|
4152
4547
|
* Runs the auto refresh token tick.
|
|
4153
4548
|
*/
|
|
4154
4549
|
async _autoRefreshTokenTick() {
|
|
4155
4550
|
this._debug('#_autoRefreshTokenTick()', 'begin');
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4551
|
+
if (this.lock != null) {
|
|
4552
|
+
// TODO(v3): remove legacy lock path. Uses `_acquireLock(0, ...)` which
|
|
4553
|
+
// throws `LockAcquireTimeoutError` immediately if the lock is held —
|
|
4554
|
+
// that's the fail-fast skip path that lets the tick bail out instead
|
|
4555
|
+
// of queuing behind a long-running operation.
|
|
4556
|
+
try {
|
|
4557
|
+
await this._acquireLock(0, async () => {
|
|
4160
4558
|
try {
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4559
|
+
const now = Date.now();
|
|
4560
|
+
try {
|
|
4561
|
+
return await this._useSession(async (result) => {
|
|
4562
|
+
const { data: { session }, } = result;
|
|
4563
|
+
if (!session || !session.refresh_token || !session.expires_at) {
|
|
4564
|
+
this._debug('#_autoRefreshTokenTick()', 'no session');
|
|
4565
|
+
return;
|
|
4566
|
+
}
|
|
4567
|
+
const expiresInTicks = Math.floor((session.expires_at * 1000 - now) / constants_1.AUTO_REFRESH_TICK_DURATION_MS);
|
|
4568
|
+
this._debug('#_autoRefreshTokenTick()', `access token expires in ${expiresInTicks} ticks, a tick lasts ${constants_1.AUTO_REFRESH_TICK_DURATION_MS}ms, refresh threshold is ${constants_1.AUTO_REFRESH_TICK_THRESHOLD} ticks`);
|
|
4569
|
+
if (expiresInTicks <= constants_1.AUTO_REFRESH_TICK_THRESHOLD) {
|
|
4570
|
+
await this._callRefreshToken(session.refresh_token);
|
|
4571
|
+
}
|
|
4572
|
+
});
|
|
4573
|
+
}
|
|
4574
|
+
catch (e) {
|
|
4575
|
+
console.error('Auto refresh tick failed with error. This is likely a transient error.', e);
|
|
4576
|
+
}
|
|
4174
4577
|
}
|
|
4175
|
-
|
|
4176
|
-
|
|
4578
|
+
finally {
|
|
4579
|
+
this._debug('#_autoRefreshTokenTick()', 'end');
|
|
4177
4580
|
}
|
|
4581
|
+
});
|
|
4582
|
+
}
|
|
4583
|
+
catch (e) {
|
|
4584
|
+
if (e instanceof locks_1.LockAcquireTimeoutError) {
|
|
4585
|
+
this._debug('auto refresh token tick lock not available');
|
|
4178
4586
|
}
|
|
4179
|
-
|
|
4180
|
-
|
|
4587
|
+
else {
|
|
4588
|
+
throw e;
|
|
4181
4589
|
}
|
|
4182
|
-
}
|
|
4590
|
+
}
|
|
4591
|
+
return;
|
|
4183
4592
|
}
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4593
|
+
// Lockless default: skip if a refresh is already in flight.
|
|
4594
|
+
// `_callRefreshToken` also dedupes via the same field; this is just a
|
|
4595
|
+
// fast-path skip to avoid an unnecessary storage read.
|
|
4596
|
+
if (this.refreshingDeferred !== null) {
|
|
4597
|
+
this._debug('#_autoRefreshTokenTick()', 'refresh already in flight, skipping');
|
|
4598
|
+
return;
|
|
4599
|
+
}
|
|
4600
|
+
try {
|
|
4601
|
+
const now = Date.now();
|
|
4602
|
+
try {
|
|
4603
|
+
await this._useSession(async (result) => {
|
|
4604
|
+
const { data: { session }, } = result;
|
|
4605
|
+
if (!session || !session.refresh_token || !session.expires_at) {
|
|
4606
|
+
this._debug('#_autoRefreshTokenTick()', 'no session');
|
|
4607
|
+
return;
|
|
4608
|
+
}
|
|
4609
|
+
// session will expire in this many ticks (or has already expired if <= 0)
|
|
4610
|
+
const expiresInTicks = Math.floor((session.expires_at * 1000 - now) / constants_1.AUTO_REFRESH_TICK_DURATION_MS);
|
|
4611
|
+
this._debug('#_autoRefreshTokenTick()', `access token expires in ${expiresInTicks} ticks, a tick lasts ${constants_1.AUTO_REFRESH_TICK_DURATION_MS}ms, refresh threshold is ${constants_1.AUTO_REFRESH_TICK_THRESHOLD} ticks`);
|
|
4612
|
+
if (expiresInTicks <= constants_1.AUTO_REFRESH_TICK_THRESHOLD) {
|
|
4613
|
+
await this._callRefreshToken(session.refresh_token);
|
|
4614
|
+
}
|
|
4615
|
+
});
|
|
4187
4616
|
}
|
|
4188
|
-
|
|
4189
|
-
|
|
4617
|
+
catch (e) {
|
|
4618
|
+
console.error('Auto refresh tick failed with error. This is likely a transient error.', e);
|
|
4190
4619
|
}
|
|
4191
4620
|
}
|
|
4621
|
+
finally {
|
|
4622
|
+
this._debug('#_autoRefreshTokenTick()', 'end');
|
|
4623
|
+
}
|
|
4192
4624
|
}
|
|
4193
4625
|
/**
|
|
4194
4626
|
* Registers callbacks on the browser / platform, which in-turn run
|
|
@@ -4237,18 +4669,26 @@ class GoTrueClient {
|
|
|
4237
4669
|
if (!calledFromInitialize) {
|
|
4238
4670
|
// called when the visibility has changed, i.e. the browser
|
|
4239
4671
|
// transitioned from hidden -> visible so we need to see if the session
|
|
4240
|
-
// should be recovered
|
|
4241
|
-
// the lock first asynchronously
|
|
4672
|
+
// should be recovered
|
|
4242
4673
|
await this.initializePromise;
|
|
4243
|
-
|
|
4674
|
+
if (this.lock != null) {
|
|
4675
|
+
// TODO(v3): remove legacy lock path
|
|
4676
|
+
await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
4677
|
+
if (document.visibilityState !== 'visible') {
|
|
4678
|
+
this._debug(methodName, 'acquired the lock to recover the session, but the browser visibilityState is no longer visible, aborting');
|
|
4679
|
+
return;
|
|
4680
|
+
}
|
|
4681
|
+
await this._recoverAndRefresh();
|
|
4682
|
+
});
|
|
4683
|
+
}
|
|
4684
|
+
else {
|
|
4244
4685
|
if (document.visibilityState !== 'visible') {
|
|
4245
|
-
this._debug(methodName, '
|
|
4246
|
-
// visibility has changed while waiting for the lock, abort
|
|
4686
|
+
this._debug(methodName, 'visibilityState is no longer visible, skipping recovery');
|
|
4247
4687
|
return;
|
|
4248
4688
|
}
|
|
4249
4689
|
// recover the session
|
|
4250
4690
|
await this._recoverAndRefresh();
|
|
4251
|
-
}
|
|
4691
|
+
}
|
|
4252
4692
|
}
|
|
4253
4693
|
}
|
|
4254
4694
|
else if (document.visibilityState === 'hidden') {
|
|
@@ -4344,7 +4784,7 @@ class GoTrueClient {
|
|
|
4344
4784
|
}
|
|
4345
4785
|
}
|
|
4346
4786
|
async _verify(params) {
|
|
4347
|
-
|
|
4787
|
+
const run = async () => {
|
|
4348
4788
|
try {
|
|
4349
4789
|
return await this._useSession(async (result) => {
|
|
4350
4790
|
var _a;
|
|
@@ -4378,10 +4818,15 @@ class GoTrueClient {
|
|
|
4378
4818
|
}
|
|
4379
4819
|
throw error;
|
|
4380
4820
|
}
|
|
4381
|
-
}
|
|
4821
|
+
};
|
|
4822
|
+
if (this.lock != null) {
|
|
4823
|
+
// TODO(v3): remove legacy lock path
|
|
4824
|
+
return this._acquireLock(this.lockAcquireTimeout, run);
|
|
4825
|
+
}
|
|
4826
|
+
return run();
|
|
4382
4827
|
}
|
|
4383
4828
|
async _challenge(params) {
|
|
4384
|
-
|
|
4829
|
+
const run = async () => {
|
|
4385
4830
|
try {
|
|
4386
4831
|
return await this._useSession(async (result) => {
|
|
4387
4832
|
var _a;
|
|
@@ -4421,14 +4866,17 @@ class GoTrueClient {
|
|
|
4421
4866
|
}
|
|
4422
4867
|
throw error;
|
|
4423
4868
|
}
|
|
4424
|
-
}
|
|
4869
|
+
};
|
|
4870
|
+
if (this.lock != null) {
|
|
4871
|
+
// TODO(v3): remove legacy lock path
|
|
4872
|
+
return this._acquireLock(this.lockAcquireTimeout, run);
|
|
4873
|
+
}
|
|
4874
|
+
return run();
|
|
4425
4875
|
}
|
|
4426
4876
|
/**
|
|
4427
|
-
* {@
|
|
4877
|
+
* {@link GoTrueMFAApi#challengeAndVerify}
|
|
4428
4878
|
*/
|
|
4429
4879
|
async _challengeAndVerify(params) {
|
|
4430
|
-
// both _challenge and _verify independently acquire the lock, so no need
|
|
4431
|
-
// to acquire it here
|
|
4432
4880
|
const { data: challengeData, error: challengeError } = await this._challenge({
|
|
4433
4881
|
factorId: params.factorId,
|
|
4434
4882
|
});
|
|
@@ -4442,11 +4890,10 @@ class GoTrueClient {
|
|
|
4442
4890
|
});
|
|
4443
4891
|
}
|
|
4444
4892
|
/**
|
|
4445
|
-
* {@
|
|
4893
|
+
* {@link GoTrueMFAApi#listFactors}
|
|
4446
4894
|
*/
|
|
4447
4895
|
async _listFactors() {
|
|
4448
4896
|
var _a;
|
|
4449
|
-
// use #getUser instead of #_getUser as the former acquires a lock
|
|
4450
4897
|
const { data: { user }, error: userError, } = await this.getUser();
|
|
4451
4898
|
if (userError) {
|
|
4452
4899
|
return { data: null, error: userError };
|
|
@@ -4471,7 +4918,7 @@ class GoTrueClient {
|
|
|
4471
4918
|
};
|
|
4472
4919
|
}
|
|
4473
4920
|
/**
|
|
4474
|
-
* {@
|
|
4921
|
+
* {@link GoTrueMFAApi#getAuthenticatorAssuranceLevel}
|
|
4475
4922
|
*/
|
|
4476
4923
|
async _getAuthenticatorAssuranceLevel(jwt) {
|
|
4477
4924
|
var _a, _b, _c, _d;
|
|
@@ -4722,15 +5169,15 @@ class GoTrueClient {
|
|
|
4722
5169
|
* Extracts the JWT claims present in the access token by first verifying the
|
|
4723
5170
|
* JWT against the server's JSON Web Key Set endpoint
|
|
4724
5171
|
* `/.well-known/jwks.json` which is often cached, resulting in significantly
|
|
4725
|
-
* faster responses. Prefer this method over {@link
|
|
5172
|
+
* faster responses. Prefer this method over {@link GoTrueClient.getUser} which always
|
|
4726
5173
|
* sends a request to the Auth server for each JWT.
|
|
4727
5174
|
*
|
|
4728
5175
|
* If the project is not using an asymmetric JWT signing key (like ECC or
|
|
4729
|
-
* RSA) it always sends a request to the Auth server (similar to
|
|
4730
|
-
*
|
|
5176
|
+
* RSA) it always sends a request to the Auth server (similar to
|
|
5177
|
+
* {@link GoTrueClient.getUser}) to verify the JWT.
|
|
4731
5178
|
*
|
|
4732
5179
|
* @param jwt An optional specific JWT you wish to verify, not the one you
|
|
4733
|
-
* can obtain from {@link
|
|
5180
|
+
* can obtain from {@link GoTrueClient.getSession}.
|
|
4734
5181
|
* @param options Various additional options that allow you to customize the
|
|
4735
5182
|
* behavior of this method.
|
|
4736
5183
|
*
|
|
@@ -4795,8 +5242,14 @@ class GoTrueClient {
|
|
|
4795
5242
|
}
|
|
4796
5243
|
const { header, payload, signature, raw: { header: rawHeader, payload: rawPayload }, } = (0, helpers_1.decodeJWT)(token);
|
|
4797
5244
|
if (!(options === null || options === void 0 ? void 0 : options.allowExpired)) {
|
|
4798
|
-
// Reject expired JWTs should only happen if jwt argument was passed
|
|
4799
|
-
|
|
5245
|
+
// Reject expired JWTs should only happen if jwt argument was passed.
|
|
5246
|
+
// Rethrow as AuthInvalidJwtError so the outer catch converts it to { data, error }.
|
|
5247
|
+
try {
|
|
5248
|
+
(0, helpers_1.validateExp)(payload.exp);
|
|
5249
|
+
}
|
|
5250
|
+
catch (e) {
|
|
5251
|
+
throw new errors_1.AuthInvalidJwtError(e instanceof Error ? e.message : 'JWT validation failed');
|
|
5252
|
+
}
|
|
4800
5253
|
}
|
|
4801
5254
|
const signingKey = !header.alg ||
|
|
4802
5255
|
header.alg.startsWith('HS') ||
|
|
@@ -4847,6 +5300,335 @@ class GoTrueClient {
|
|
|
4847
5300
|
throw error;
|
|
4848
5301
|
}
|
|
4849
5302
|
}
|
|
5303
|
+
// --- Passkey Methods ---
|
|
5304
|
+
/**
|
|
5305
|
+
* Sign in with a passkey. Handles the full WebAuthn ceremony:
|
|
5306
|
+
* 1. Fetches authentication challenge from server
|
|
5307
|
+
* 2. Prompts user via navigator.credentials.get()
|
|
5308
|
+
* 3. Verifies credential with server and creates session
|
|
5309
|
+
*
|
|
5310
|
+
* Requires `auth.experimental.passkey: true`.
|
|
5311
|
+
*
|
|
5312
|
+
* @category Auth
|
|
5313
|
+
*/
|
|
5314
|
+
async signInWithPasskey(credentials) {
|
|
5315
|
+
var _a, _b, _c;
|
|
5316
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5317
|
+
try {
|
|
5318
|
+
if (!(0, webauthn_1.browserSupportsWebAuthn)()) {
|
|
5319
|
+
return this._returnResult({
|
|
5320
|
+
data: null,
|
|
5321
|
+
error: new errors_1.AuthUnknownError('Browser does not support WebAuthn', null),
|
|
5322
|
+
});
|
|
5323
|
+
}
|
|
5324
|
+
// 1. Get challenge options from server
|
|
5325
|
+
const { data: options, error: optionsError } = await this._startPasskeyAuthentication({
|
|
5326
|
+
options: { captchaToken: (_a = credentials === null || credentials === void 0 ? void 0 : credentials.options) === null || _a === void 0 ? void 0 : _a.captchaToken },
|
|
5327
|
+
});
|
|
5328
|
+
if (optionsError || !options) {
|
|
5329
|
+
return this._returnResult({ data: null, error: optionsError });
|
|
5330
|
+
}
|
|
5331
|
+
// 2. Deserialize and prompt user via browser WebAuthn API
|
|
5332
|
+
const publicKeyOptions = (0, webauthn_1.deserializeCredentialRequestOptions)(options.options);
|
|
5333
|
+
const signal = (_c = (_b = credentials === null || credentials === void 0 ? void 0 : credentials.options) === null || _b === void 0 ? void 0 : _b.signal) !== null && _c !== void 0 ? _c : webauthn_1.webAuthnAbortService.createNewAbortSignal();
|
|
5334
|
+
const { data: credential, error: credentialError } = await (0, webauthn_1.getCredential)({
|
|
5335
|
+
publicKey: publicKeyOptions,
|
|
5336
|
+
signal,
|
|
5337
|
+
});
|
|
5338
|
+
if (credentialError || !credential) {
|
|
5339
|
+
return this._returnResult({
|
|
5340
|
+
data: null,
|
|
5341
|
+
error: credentialError !== null && credentialError !== void 0 ? credentialError : new errors_1.AuthUnknownError('WebAuthn ceremony failed', null),
|
|
5342
|
+
});
|
|
5343
|
+
}
|
|
5344
|
+
// 3. Serialize and verify with server
|
|
5345
|
+
const serialized = (0, webauthn_1.serializeCredentialRequestResponse)(credential);
|
|
5346
|
+
return this._verifyPasskeyAuthentication({
|
|
5347
|
+
challengeId: options.challenge_id,
|
|
5348
|
+
credential: serialized,
|
|
5349
|
+
});
|
|
5350
|
+
}
|
|
5351
|
+
catch (error) {
|
|
5352
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5353
|
+
return this._returnResult({ data: null, error });
|
|
5354
|
+
}
|
|
5355
|
+
throw error;
|
|
5356
|
+
}
|
|
5357
|
+
}
|
|
5358
|
+
/**
|
|
5359
|
+
* Register a passkey for the current authenticated user. Handles the full WebAuthn ceremony:
|
|
5360
|
+
* 1. Fetches registration challenge from server
|
|
5361
|
+
* 2. Prompts user via navigator.credentials.create()
|
|
5362
|
+
* 3. Verifies credential with server
|
|
5363
|
+
*
|
|
5364
|
+
* Requires an active session. Requires `auth.experimental.passkey: true`.
|
|
5365
|
+
*
|
|
5366
|
+
* @category Auth
|
|
5367
|
+
*/
|
|
5368
|
+
async registerPasskey(credentials) {
|
|
5369
|
+
var _a, _b;
|
|
5370
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5371
|
+
try {
|
|
5372
|
+
if (!(0, webauthn_1.browserSupportsWebAuthn)()) {
|
|
5373
|
+
return this._returnResult({
|
|
5374
|
+
data: null,
|
|
5375
|
+
error: new errors_1.AuthUnknownError('Browser does not support WebAuthn', null),
|
|
5376
|
+
});
|
|
5377
|
+
}
|
|
5378
|
+
// 1. Get challenge options from server
|
|
5379
|
+
const { data: options, error: optionsError } = await this._startPasskeyRegistration();
|
|
5380
|
+
if (optionsError || !options) {
|
|
5381
|
+
return this._returnResult({ data: null, error: optionsError });
|
|
5382
|
+
}
|
|
5383
|
+
// 2. Deserialize and prompt user via browser WebAuthn API
|
|
5384
|
+
const publicKeyOptions = (0, webauthn_1.deserializeCredentialCreationOptions)(options.options);
|
|
5385
|
+
const signal = (_b = (_a = credentials === null || credentials === void 0 ? void 0 : credentials.options) === null || _a === void 0 ? void 0 : _a.signal) !== null && _b !== void 0 ? _b : webauthn_1.webAuthnAbortService.createNewAbortSignal();
|
|
5386
|
+
const { data: credential, error: credentialError } = await (0, webauthn_1.createCredential)({
|
|
5387
|
+
publicKey: publicKeyOptions,
|
|
5388
|
+
signal,
|
|
5389
|
+
});
|
|
5390
|
+
if (credentialError || !credential) {
|
|
5391
|
+
return this._returnResult({
|
|
5392
|
+
data: null,
|
|
5393
|
+
error: credentialError !== null && credentialError !== void 0 ? credentialError : new errors_1.AuthUnknownError('WebAuthn ceremony failed', null),
|
|
5394
|
+
});
|
|
5395
|
+
}
|
|
5396
|
+
// 3. Serialize and verify with server
|
|
5397
|
+
const serialized = (0, webauthn_1.serializeCredentialCreationResponse)(credential);
|
|
5398
|
+
return this._verifyPasskeyRegistration({
|
|
5399
|
+
challengeId: options.challenge_id,
|
|
5400
|
+
credential: serialized,
|
|
5401
|
+
});
|
|
5402
|
+
}
|
|
5403
|
+
catch (error) {
|
|
5404
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5405
|
+
return this._returnResult({ data: null, error });
|
|
5406
|
+
}
|
|
5407
|
+
throw error;
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5410
|
+
/**
|
|
5411
|
+
* Start passkey registration for the current authenticated user.
|
|
5412
|
+
* Returns WebAuthn credential creation options to pass to navigator.credentials.create().
|
|
5413
|
+
*/
|
|
5414
|
+
async _startPasskeyRegistration() {
|
|
5415
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5416
|
+
try {
|
|
5417
|
+
return await this._useSession(async (result) => {
|
|
5418
|
+
const { data: { session }, error: sessionError, } = result;
|
|
5419
|
+
if (sessionError) {
|
|
5420
|
+
return this._returnResult({ data: null, error: sessionError });
|
|
5421
|
+
}
|
|
5422
|
+
if (!session) {
|
|
5423
|
+
return this._returnResult({ data: null, error: new errors_1.AuthSessionMissingError() });
|
|
5424
|
+
}
|
|
5425
|
+
const { data, error } = await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/passkeys/registration/options`, {
|
|
5426
|
+
headers: this.headers,
|
|
5427
|
+
jwt: session.access_token,
|
|
5428
|
+
body: {},
|
|
5429
|
+
});
|
|
5430
|
+
if (error) {
|
|
5431
|
+
return this._returnResult({ data: null, error });
|
|
5432
|
+
}
|
|
5433
|
+
return this._returnResult({ data, error: null });
|
|
5434
|
+
});
|
|
5435
|
+
}
|
|
5436
|
+
catch (error) {
|
|
5437
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5438
|
+
return this._returnResult({ data: null, error });
|
|
5439
|
+
}
|
|
5440
|
+
throw error;
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5443
|
+
/**
|
|
5444
|
+
* Verify passkey registration with the credential response.
|
|
5445
|
+
* The credentialResponse should be the serialized output of navigator.credentials.create().
|
|
5446
|
+
*/
|
|
5447
|
+
async _verifyPasskeyRegistration(params) {
|
|
5448
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5449
|
+
try {
|
|
5450
|
+
return await this._useSession(async (result) => {
|
|
5451
|
+
const { data: { session }, error: sessionError, } = result;
|
|
5452
|
+
if (sessionError) {
|
|
5453
|
+
return this._returnResult({ data: null, error: sessionError });
|
|
5454
|
+
}
|
|
5455
|
+
if (!session) {
|
|
5456
|
+
return this._returnResult({ data: null, error: new errors_1.AuthSessionMissingError() });
|
|
5457
|
+
}
|
|
5458
|
+
const { data, error } = await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/passkeys/registration/verify`, {
|
|
5459
|
+
headers: this.headers,
|
|
5460
|
+
jwt: session.access_token,
|
|
5461
|
+
body: {
|
|
5462
|
+
challenge_id: params.challengeId,
|
|
5463
|
+
credential: params.credential,
|
|
5464
|
+
},
|
|
5465
|
+
});
|
|
5466
|
+
if (error) {
|
|
5467
|
+
return this._returnResult({ data: null, error });
|
|
5468
|
+
}
|
|
5469
|
+
return this._returnResult({ data, error: null });
|
|
5470
|
+
});
|
|
5471
|
+
}
|
|
5472
|
+
catch (error) {
|
|
5473
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5474
|
+
return this._returnResult({ data: null, error });
|
|
5475
|
+
}
|
|
5476
|
+
throw error;
|
|
5477
|
+
}
|
|
5478
|
+
}
|
|
5479
|
+
/**
|
|
5480
|
+
* Start passkey authentication.
|
|
5481
|
+
* Returns WebAuthn credential request options to pass to navigator.credentials.get().
|
|
5482
|
+
*/
|
|
5483
|
+
async _startPasskeyAuthentication(params) {
|
|
5484
|
+
var _a;
|
|
5485
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5486
|
+
try {
|
|
5487
|
+
const { data, error } = await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/passkeys/authentication/options`, {
|
|
5488
|
+
headers: this.headers,
|
|
5489
|
+
body: {
|
|
5490
|
+
gotrue_meta_security: { captcha_token: (_a = params === null || params === void 0 ? void 0 : params.options) === null || _a === void 0 ? void 0 : _a.captchaToken },
|
|
5491
|
+
},
|
|
5492
|
+
});
|
|
5493
|
+
if (error) {
|
|
5494
|
+
return this._returnResult({ data: null, error });
|
|
5495
|
+
}
|
|
5496
|
+
return this._returnResult({ data, error: null });
|
|
5497
|
+
}
|
|
5498
|
+
catch (error) {
|
|
5499
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5500
|
+
return this._returnResult({ data: null, error });
|
|
5501
|
+
}
|
|
5502
|
+
throw error;
|
|
5503
|
+
}
|
|
5504
|
+
}
|
|
5505
|
+
/**
|
|
5506
|
+
* Verify passkey authentication and create a session.
|
|
5507
|
+
* The credential should be the serialized output of navigator.credentials.get().
|
|
5508
|
+
*/
|
|
5509
|
+
async _verifyPasskeyAuthentication(params) {
|
|
5510
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5511
|
+
try {
|
|
5512
|
+
const { data, error } = await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/passkeys/authentication/verify`, {
|
|
5513
|
+
headers: this.headers,
|
|
5514
|
+
body: {
|
|
5515
|
+
challenge_id: params.challengeId,
|
|
5516
|
+
credential: params.credential,
|
|
5517
|
+
},
|
|
5518
|
+
xform: fetch_1._sessionResponse,
|
|
5519
|
+
});
|
|
5520
|
+
if (error) {
|
|
5521
|
+
return this._returnResult({ data: null, error });
|
|
5522
|
+
}
|
|
5523
|
+
if (data.session) {
|
|
5524
|
+
await this._saveSession(data.session);
|
|
5525
|
+
await this._notifyAllSubscribers('SIGNED_IN', data.session);
|
|
5526
|
+
}
|
|
5527
|
+
return this._returnResult({ data, error: null });
|
|
5528
|
+
}
|
|
5529
|
+
catch (error) {
|
|
5530
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5531
|
+
return this._returnResult({ data: null, error });
|
|
5532
|
+
}
|
|
5533
|
+
throw error;
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
/**
|
|
5537
|
+
* List all passkeys for the current user.
|
|
5538
|
+
*/
|
|
5539
|
+
async _listPasskeys() {
|
|
5540
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5541
|
+
try {
|
|
5542
|
+
return await this._useSession(async (result) => {
|
|
5543
|
+
const { data: { session }, error: sessionError, } = result;
|
|
5544
|
+
if (sessionError) {
|
|
5545
|
+
return this._returnResult({ data: null, error: sessionError });
|
|
5546
|
+
}
|
|
5547
|
+
if (!session) {
|
|
5548
|
+
return this._returnResult({ data: null, error: new errors_1.AuthSessionMissingError() });
|
|
5549
|
+
}
|
|
5550
|
+
const { data, error } = await (0, fetch_1._request)(this.fetch, 'GET', `${this.url}/passkeys`, {
|
|
5551
|
+
headers: this.headers,
|
|
5552
|
+
jwt: session.access_token,
|
|
5553
|
+
xform: (data) => ({ data, error: null }),
|
|
5554
|
+
});
|
|
5555
|
+
if (error) {
|
|
5556
|
+
return this._returnResult({ data: null, error });
|
|
5557
|
+
}
|
|
5558
|
+
return this._returnResult({ data, error: null });
|
|
5559
|
+
});
|
|
5560
|
+
}
|
|
5561
|
+
catch (error) {
|
|
5562
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5563
|
+
return this._returnResult({ data: null, error });
|
|
5564
|
+
}
|
|
5565
|
+
throw error;
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
/**
|
|
5569
|
+
* Update a passkey.
|
|
5570
|
+
*/
|
|
5571
|
+
async _updatePasskey(params) {
|
|
5572
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5573
|
+
try {
|
|
5574
|
+
return await this._useSession(async (result) => {
|
|
5575
|
+
const { data: { session }, error: sessionError, } = result;
|
|
5576
|
+
if (sessionError) {
|
|
5577
|
+
return this._returnResult({ data: null, error: sessionError });
|
|
5578
|
+
}
|
|
5579
|
+
if (!session) {
|
|
5580
|
+
return this._returnResult({ data: null, error: new errors_1.AuthSessionMissingError() });
|
|
5581
|
+
}
|
|
5582
|
+
const { data, error } = await (0, fetch_1._request)(this.fetch, 'PATCH', `${this.url}/passkeys/${params.passkeyId}`, {
|
|
5583
|
+
headers: this.headers,
|
|
5584
|
+
jwt: session.access_token,
|
|
5585
|
+
body: { friendly_name: params.friendlyName },
|
|
5586
|
+
});
|
|
5587
|
+
if (error) {
|
|
5588
|
+
return this._returnResult({ data: null, error });
|
|
5589
|
+
}
|
|
5590
|
+
return this._returnResult({ data, error: null });
|
|
5591
|
+
});
|
|
5592
|
+
}
|
|
5593
|
+
catch (error) {
|
|
5594
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5595
|
+
return this._returnResult({ data: null, error });
|
|
5596
|
+
}
|
|
5597
|
+
throw error;
|
|
5598
|
+
}
|
|
5599
|
+
}
|
|
5600
|
+
/**
|
|
5601
|
+
* Delete a passkey.
|
|
5602
|
+
*/
|
|
5603
|
+
async _deletePasskey(params) {
|
|
5604
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5605
|
+
try {
|
|
5606
|
+
return await this._useSession(async (result) => {
|
|
5607
|
+
const { data: { session }, error: sessionError, } = result;
|
|
5608
|
+
if (sessionError) {
|
|
5609
|
+
return this._returnResult({ data: null, error: sessionError });
|
|
5610
|
+
}
|
|
5611
|
+
if (!session) {
|
|
5612
|
+
return this._returnResult({ data: null, error: new errors_1.AuthSessionMissingError() });
|
|
5613
|
+
}
|
|
5614
|
+
const { error } = await (0, fetch_1._request)(this.fetch, 'DELETE', `${this.url}/passkeys/${params.passkeyId}`, {
|
|
5615
|
+
headers: this.headers,
|
|
5616
|
+
jwt: session.access_token,
|
|
5617
|
+
noResolveJson: true,
|
|
5618
|
+
});
|
|
5619
|
+
if (error) {
|
|
5620
|
+
return this._returnResult({ data: null, error });
|
|
5621
|
+
}
|
|
5622
|
+
return this._returnResult({ data: null, error: null });
|
|
5623
|
+
});
|
|
5624
|
+
}
|
|
5625
|
+
catch (error) {
|
|
5626
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5627
|
+
return this._returnResult({ data: null, error });
|
|
5628
|
+
}
|
|
5629
|
+
throw error;
|
|
5630
|
+
}
|
|
5631
|
+
}
|
|
4850
5632
|
}
|
|
4851
5633
|
GoTrueClient.nextInstanceID = {};
|
|
4852
5634
|
exports.default = GoTrueClient;
|