@depup/supabase__auth-js 2.103.0-depup.0 → 2.110.9-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 +952 -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 +957 -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 +1125 -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,16 @@ 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) ||
|
|
3610
|
+
(0, errors_1.isAuthRetryableFetchError)(err) ||
|
|
3611
|
+
((0, errors_1.isAuthApiError)(err) &&
|
|
3612
|
+
(err.code === 'refresh_token_not_found' ||
|
|
3613
|
+
err.code === 'refresh_token_already_used' ||
|
|
3614
|
+
err.code === 'session_expired'))) {
|
|
3615
|
+
// A missing session, a transient/aborted network failure (e.g. a
|
|
3616
|
+
// superseded page navigation cancelling the in-flight request), or
|
|
3617
|
+
// a dead refresh token (e.g. stale SSR cookies) is not an
|
|
3618
|
+
// application error — warn rather than surface it raw.
|
|
3395
3619
|
console.warn(err);
|
|
3396
3620
|
}
|
|
3397
3621
|
else {
|
|
@@ -3587,7 +3811,7 @@ class GoTrueClient {
|
|
|
3587
3811
|
var _a;
|
|
3588
3812
|
try {
|
|
3589
3813
|
const { data, error } = await this._useSession(async (result) => {
|
|
3590
|
-
var _a, _b, _c, _d,
|
|
3814
|
+
var _a, _b, _c, _d, _f;
|
|
3591
3815
|
const { data, error } = result;
|
|
3592
3816
|
if (error)
|
|
3593
3817
|
throw error;
|
|
@@ -3599,7 +3823,7 @@ class GoTrueClient {
|
|
|
3599
3823
|
});
|
|
3600
3824
|
return await (0, fetch_1._request)(this.fetch, 'GET', url, {
|
|
3601
3825
|
headers: this.headers,
|
|
3602
|
-
jwt: (
|
|
3826
|
+
jwt: (_f = (_d = data.session) === null || _d === void 0 ? void 0 : _d.access_token) !== null && _f !== void 0 ? _f : undefined,
|
|
3603
3827
|
});
|
|
3604
3828
|
});
|
|
3605
3829
|
if (error)
|
|
@@ -3716,7 +3940,10 @@ class GoTrueClient {
|
|
|
3716
3940
|
* @param refreshToken A valid refresh token that was returned on login.
|
|
3717
3941
|
*/
|
|
3718
3942
|
async _refreshAccessToken(refreshToken) {
|
|
3719
|
-
|
|
3943
|
+
// Refresh tokens are long-lived bearer credentials; do NOT include any
|
|
3944
|
+
// fragment of the token in the debug tag, even when `debug: true` is
|
|
3945
|
+
// enabled (logs may be forwarded to third-party services).
|
|
3946
|
+
const debugName = `#_refreshAccessToken()`;
|
|
3720
3947
|
this._debug(debugName, 'begin');
|
|
3721
3948
|
try {
|
|
3722
3949
|
const startedAt = Date.now();
|
|
@@ -3823,10 +4050,17 @@ class GoTrueClient {
|
|
|
3823
4050
|
if (this.autoRefreshToken && currentSession.refresh_token) {
|
|
3824
4051
|
const { error } = await this._callRefreshToken(currentSession.refresh_token);
|
|
3825
4052
|
if (error) {
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
4053
|
+
// `_callRefreshToken` is the single source of truth for refresh
|
|
4054
|
+
// outcomes: it removes the session itself when the access token
|
|
4055
|
+
// is actually expired, and preserves it when the token is still
|
|
4056
|
+
// valid (proactive-preserve). Don't second-guess that here — a
|
|
4057
|
+
// local `_removeSession` would emit a duplicate `SIGNED_OUT` on
|
|
4058
|
+
// genuine failures and undo the proactive-preserve at init time.
|
|
4059
|
+
if ((0, errors_1.isAuthRefreshDiscardedError)(error)) {
|
|
4060
|
+
this._debug(debugName, 'refresh discarded by commit guard', error);
|
|
4061
|
+
}
|
|
4062
|
+
else {
|
|
4063
|
+
this._debug(debugName, 'refresh failed', error);
|
|
3830
4064
|
}
|
|
3831
4065
|
}
|
|
3832
4066
|
}
|
|
@@ -3859,7 +4093,16 @@ class GoTrueClient {
|
|
|
3859
4093
|
}
|
|
3860
4094
|
catch (err) {
|
|
3861
4095
|
this._debug(debugName, 'error', err);
|
|
3862
|
-
|
|
4096
|
+
if ((0, errors_1.isAuthRetryableFetchError)(err)) {
|
|
4097
|
+
// Transient/aborted network failure during session recovery (e.g. a
|
|
4098
|
+
// superseded page navigation cancelling the in-flight request). Warn
|
|
4099
|
+
// rather than surface it raw; the session is untouched and recovery
|
|
4100
|
+
// will run again on the next load.
|
|
4101
|
+
console.warn(err);
|
|
4102
|
+
}
|
|
4103
|
+
else {
|
|
4104
|
+
console.error(err);
|
|
4105
|
+
}
|
|
3863
4106
|
return;
|
|
3864
4107
|
}
|
|
3865
4108
|
finally {
|
|
@@ -3875,18 +4118,91 @@ class GoTrueClient {
|
|
|
3875
4118
|
if (this.refreshingDeferred) {
|
|
3876
4119
|
return this.refreshingDeferred.promise;
|
|
3877
4120
|
}
|
|
3878
|
-
|
|
4121
|
+
// Serial failure cooldown: callers passing the *same* refresh token
|
|
4122
|
+
// after a recent failure receive the cached result instead of firing
|
|
4123
|
+
// another `/token` request. This caps the proactive-refresh storm
|
|
4124
|
+
// where every `getSession()` call inside the 90s EXPIRY_MARGIN_MS
|
|
4125
|
+
// window kept re-firing against the same broken refresh token during
|
|
4126
|
+
// outages. Concurrent callers already share `refreshingDeferred`; this
|
|
4127
|
+
// cache covers serial callers spaced across cooldown windows.
|
|
4128
|
+
//
|
|
4129
|
+
// Token-keyed so callers with a fresh refresh token (rotation pickup
|
|
4130
|
+
// from another tab, explicit `setSession`/`refreshSession({ refresh_token })`,
|
|
4131
|
+
// multi-account switch) bypass the cache and attempt a real refresh.
|
|
4132
|
+
if (this.lastRefreshFailure &&
|
|
4133
|
+
this.lastRefreshFailure.refreshToken === refreshToken &&
|
|
4134
|
+
Date.now() < this.lastRefreshFailure.expiresAt) {
|
|
4135
|
+
this._debug('#_callRefreshToken()', 'returning cached failure (cooldown active)');
|
|
4136
|
+
return this.lastRefreshFailure.result;
|
|
4137
|
+
}
|
|
4138
|
+
// Refresh tokens are long-lived bearer credentials; do NOT include any
|
|
4139
|
+
// fragment of the token in the debug tag, even when `debug: true` is
|
|
4140
|
+
// enabled (logs may be forwarded to third-party services).
|
|
4141
|
+
const debugName = `#_callRefreshToken()`;
|
|
3879
4142
|
this._debug(debugName, 'begin');
|
|
3880
4143
|
try {
|
|
3881
4144
|
this.refreshingDeferred = new helpers_1.Deferred();
|
|
4145
|
+
// Snapshot storage before the fetch. The commit guard discards the
|
|
4146
|
+
// rotated tokens only when a non-null pre-fetch snapshot changed under
|
|
4147
|
+
// us — typical case: a concurrent `signOut` ran `_removeSession`, or
|
|
4148
|
+
// another tab's refresh rewrote the slot. Callers passing
|
|
4149
|
+
// externally-sourced tokens (SSR cookie handoff, multi-account
|
|
4150
|
+
// switching, `setSession`/`refreshSession({ refresh_token })`) may
|
|
4151
|
+
// start from a null snapshot OR from a non-null snapshot whose
|
|
4152
|
+
// refresh_token differs from the one they're hydrating; in both
|
|
4153
|
+
// cases the guard fires only when storage was *modified between
|
|
4154
|
+
// snapshots*, not when the input token disagrees with what's stored.
|
|
4155
|
+
const storedAtStart = (await (0, helpers_1.getItemAsync)(this.storage, this.storageKey));
|
|
3882
4156
|
const { data, error } = await this._refreshAccessToken(refreshToken);
|
|
3883
4157
|
if (error)
|
|
3884
4158
|
throw error;
|
|
3885
4159
|
if (!data.session)
|
|
3886
4160
|
throw new errors_1.AuthSessionMissingError();
|
|
4161
|
+
const storedAfter = (await (0, helpers_1.getItemAsync)(this.storage, this.storageKey));
|
|
4162
|
+
const storageChangedUnderUs = storedAtStart !== null &&
|
|
4163
|
+
(storedAfter === null || storedAfter.refresh_token !== storedAtStart.refresh_token);
|
|
4164
|
+
if (storageChangedUnderUs) {
|
|
4165
|
+
this._debug(debugName, 'commit guard: storage changed since refresh started, discarding rotated tokens', {
|
|
4166
|
+
// Presence indicators only — never log refresh token fragments,
|
|
4167
|
+
// even partial. Logs may be forwarded to third-party services.
|
|
4168
|
+
startedWith: 'present',
|
|
4169
|
+
nowHolds: storedAfter ? 'replaced' : 'cleared',
|
|
4170
|
+
});
|
|
4171
|
+
const discarded = {
|
|
4172
|
+
data: null,
|
|
4173
|
+
error: new errors_1.AuthRefreshDiscardedError(),
|
|
4174
|
+
};
|
|
4175
|
+
this.refreshingDeferred.resolve(discarded);
|
|
4176
|
+
return discarded;
|
|
4177
|
+
}
|
|
4178
|
+
// Second leg of the commit guard: close the TOCTOU window between the
|
|
4179
|
+
// synchronous `storageChangedUnderUs` check and the actual storage
|
|
4180
|
+
// writes inside `_saveSession`. A concurrent `signOut → _removeSession`
|
|
4181
|
+
// can land inside `_saveSession`'s `await setItemAsync(...)` yields and
|
|
4182
|
+
// clear storage just before we overwrite it. Capture the epoch BEFORE
|
|
4183
|
+
// the save and re-check after; if it advanced, undo the write directly
|
|
4184
|
+
// (do NOT call `_removeSession` — that would emit a duplicate
|
|
4185
|
+
// SIGNED_OUT for the concurrent signOut that already fired one).
|
|
4186
|
+
const epochBeforeSave = this._sessionRemovalEpoch;
|
|
3887
4187
|
await this._saveSession(data.session);
|
|
4188
|
+
if (this._sessionRemovalEpoch !== epochBeforeSave) {
|
|
4189
|
+
this._debug(debugName, 'commit guard (post-save): _removeSession ran during _saveSession, undoing write');
|
|
4190
|
+
await (0, helpers_1.removeItemAsync)(this.storage, this.storageKey);
|
|
4191
|
+
if (this.userStorage) {
|
|
4192
|
+
await (0, helpers_1.removeItemAsync)(this.userStorage, this.storageKey + '-user');
|
|
4193
|
+
}
|
|
4194
|
+
const discarded = {
|
|
4195
|
+
data: null,
|
|
4196
|
+
error: new errors_1.AuthRefreshDiscardedError(),
|
|
4197
|
+
};
|
|
4198
|
+
this.refreshingDeferred.resolve(discarded);
|
|
4199
|
+
return discarded;
|
|
4200
|
+
}
|
|
3888
4201
|
await this._notifyAllSubscribers('TOKEN_REFRESHED', data.session);
|
|
3889
4202
|
const result = { data: data.session, error: null };
|
|
4203
|
+
// Refresh succeeded — clear any cached failure so the next caller
|
|
4204
|
+
// (including the auto-refresh ticker) attempts a real refresh again.
|
|
4205
|
+
this.lastRefreshFailure = null;
|
|
3890
4206
|
this.refreshingDeferred.resolve(result);
|
|
3891
4207
|
return result;
|
|
3892
4208
|
}
|
|
@@ -3895,8 +4211,35 @@ class GoTrueClient {
|
|
|
3895
4211
|
if ((0, errors_1.isAuthError)(error)) {
|
|
3896
4212
|
const result = { data: null, error };
|
|
3897
4213
|
if (!(0, errors_1.isAuthRetryableFetchError)(error)) {
|
|
3898
|
-
|
|
4214
|
+
// Proactive vs reactive distinction: a refresh fires whenever
|
|
4215
|
+
// the access token is within EXPIRY_MARGIN_MS of expiry. If the
|
|
4216
|
+
// access token is *still valid* at this moment, the refresh was
|
|
4217
|
+
// proactive and the existing session is still usable until its
|
|
4218
|
+
// real expiry — destroying it now would log out a user whose
|
|
4219
|
+
// access token works. If the access token has actually expired,
|
|
4220
|
+
// the refresh token is the only credential left and it just got
|
|
4221
|
+
// rejected — the session is genuinely dead. `__loadSession`
|
|
4222
|
+
// mirrors this distinction on the read path so callers see the
|
|
4223
|
+
// preserved session instead of `session: null`.
|
|
4224
|
+
const storedNow = (await (0, helpers_1.getItemAsync)(this.storage, this.storageKey));
|
|
4225
|
+
const accessTokenStillValid = !!((storedNow === null || storedNow === void 0 ? void 0 : storedNow.expires_at) && storedNow.expires_at * 1000 > Date.now());
|
|
4226
|
+
if (accessTokenStillValid) {
|
|
4227
|
+
this._debug(debugName, 'proactive refresh failed, access token still valid — preserving session');
|
|
4228
|
+
}
|
|
4229
|
+
else {
|
|
4230
|
+
await this._removeSession();
|
|
4231
|
+
}
|
|
3899
4232
|
}
|
|
4233
|
+
// Cache the failure so serial callers (and the next auto-refresh
|
|
4234
|
+
// tick) passing the same refresh token within the cooldown window
|
|
4235
|
+
// receive it synchronously instead of firing another `/token`
|
|
4236
|
+
// call. Set after the optional `_removeSession` above (which
|
|
4237
|
+
// clears the cache as part of teardown) so the cache survives.
|
|
4238
|
+
this.lastRefreshFailure = {
|
|
4239
|
+
refreshToken,
|
|
4240
|
+
result,
|
|
4241
|
+
expiresAt: Date.now() + constants_1.REFRESH_FAILURE_COOLDOWN_MS,
|
|
4242
|
+
};
|
|
3900
4243
|
(_a = this.refreshingDeferred) === null || _a === void 0 ? void 0 : _a.resolve(result);
|
|
3901
4244
|
return result;
|
|
3902
4245
|
}
|
|
@@ -3909,6 +4252,19 @@ class GoTrueClient {
|
|
|
3909
4252
|
}
|
|
3910
4253
|
}
|
|
3911
4254
|
async _notifyAllSubscribers(event, session, broadcast = true) {
|
|
4255
|
+
if (this._pendingInitNotifications !== null && broadcast) {
|
|
4256
|
+
// We're inside initialize() before initializePromise has resolved, and
|
|
4257
|
+
// this notification originates from the init chain (_recoverAndRefresh),
|
|
4258
|
+
// which always fires with broadcast = true. Enqueue instead of firing so
|
|
4259
|
+
// that callbacks can safely call getSession() / getUser() without
|
|
4260
|
+
// deadlocking on initializePromise.
|
|
4261
|
+
//
|
|
4262
|
+
// Notifications with broadcast = false come from the incoming
|
|
4263
|
+
// BroadcastChannel handler (a cross-tab event), not the init chain, so
|
|
4264
|
+
// they are fired immediately to preserve multi-tab ordering.
|
|
4265
|
+
this._pendingInitNotifications.push({ event, session, broadcast });
|
|
4266
|
+
return;
|
|
4267
|
+
}
|
|
3912
4268
|
const debugName = `#_notifyAllSubscribers(${event})`;
|
|
3913
4269
|
this._debug(debugName, 'begin', session, `broadcast = ${broadcast}`);
|
|
3914
4270
|
try {
|
|
@@ -3945,7 +4301,6 @@ class GoTrueClient {
|
|
|
3945
4301
|
// _saveSession is always called whenever a new session has been acquired
|
|
3946
4302
|
// so we can safely suppress the warning returned by future getSession calls
|
|
3947
4303
|
this.suppressGetSessionWarning = true;
|
|
3948
|
-
await (0, helpers_1.removeItemAsync)(this.storage, `${this.storageKey}-code-verifier`);
|
|
3949
4304
|
// Create a shallow copy to work with, to avoid mutating the original session object if it's used elsewhere
|
|
3950
4305
|
const sessionToProcess = Object.assign({}, session);
|
|
3951
4306
|
const userIsProxy = sessionToProcess.user && sessionToProcess.user.__isUserNotAvailableProxy === true;
|
|
@@ -3978,7 +4333,15 @@ class GoTrueClient {
|
|
|
3978
4333
|
}
|
|
3979
4334
|
}
|
|
3980
4335
|
async _removeSession() {
|
|
4336
|
+
// Bump synchronously, BEFORE any `await`, so that `_callRefreshToken`'s
|
|
4337
|
+
// post-save check sees the increment whenever this method has started —
|
|
4338
|
+
// even if it hasn't finished. Pairs with the epoch check in
|
|
4339
|
+
// `_callRefreshToken`. See `_sessionRemovalEpoch` field doc.
|
|
4340
|
+
this._sessionRemovalEpoch += 1;
|
|
3981
4341
|
this._debug('#_removeSession()');
|
|
4342
|
+
// The session is gone — no point holding on to a cached refresh failure
|
|
4343
|
+
// for a token that no longer exists. Synchronous, before any `await`.
|
|
4344
|
+
this.lastRefreshFailure = null;
|
|
3982
4345
|
this.suppressGetSessionWarning = false;
|
|
3983
4346
|
await (0, helpers_1.removeItemAsync)(this.storage, this.storageKey);
|
|
3984
4347
|
await (0, helpers_1.removeItemAsync)(this.storage, this.storageKey + '-code-verifier');
|
|
@@ -3991,8 +4354,8 @@ class GoTrueClient {
|
|
|
3991
4354
|
/**
|
|
3992
4355
|
* Removes any registered visibilitychange callback.
|
|
3993
4356
|
*
|
|
3994
|
-
* {@
|
|
3995
|
-
* {@
|
|
4357
|
+
* {@link GoTrueClient.startAutoRefresh}
|
|
4358
|
+
* {@link GoTrueClient.stopAutoRefresh}
|
|
3996
4359
|
*/
|
|
3997
4360
|
_removeVisibilityChangedCallback() {
|
|
3998
4361
|
this._debug('#_removeVisibilityChangedCallback()');
|
|
@@ -4008,7 +4371,7 @@ class GoTrueClient {
|
|
|
4008
4371
|
}
|
|
4009
4372
|
}
|
|
4010
4373
|
/**
|
|
4011
|
-
* This is the private implementation of {@link
|
|
4374
|
+
* This is the private implementation of {@link GoTrueClient.startAutoRefresh}. Use this
|
|
4012
4375
|
* within the library.
|
|
4013
4376
|
*/
|
|
4014
4377
|
async _startAutoRefresh() {
|
|
@@ -4050,7 +4413,7 @@ class GoTrueClient {
|
|
|
4050
4413
|
}
|
|
4051
4414
|
}
|
|
4052
4415
|
/**
|
|
4053
|
-
* This is the private implementation of {@link
|
|
4416
|
+
* This is the private implementation of {@link GoTrueClient.stopAutoRefresh}. Use this
|
|
4054
4417
|
* within the library.
|
|
4055
4418
|
*/
|
|
4056
4419
|
async _stopAutoRefresh() {
|
|
@@ -4086,7 +4449,7 @@ class GoTrueClient {
|
|
|
4086
4449
|
* platform's foreground indication mechanism and call these methods
|
|
4087
4450
|
* appropriately to conserve resources.
|
|
4088
4451
|
*
|
|
4089
|
-
* {@
|
|
4452
|
+
* {@link GoTrueClient.stopAutoRefresh}
|
|
4090
4453
|
*
|
|
4091
4454
|
* @category Auth
|
|
4092
4455
|
*
|
|
@@ -4120,7 +4483,7 @@ class GoTrueClient {
|
|
|
4120
4483
|
* If you call this method any managed visibility change callback will be
|
|
4121
4484
|
* removed and you must manage visibility changes on your own.
|
|
4122
4485
|
*
|
|
4123
|
-
* See {@link
|
|
4486
|
+
* See {@link GoTrueClient.startAutoRefresh} for more details.
|
|
4124
4487
|
*
|
|
4125
4488
|
* @category Auth
|
|
4126
4489
|
*
|
|
@@ -4148,47 +4511,122 @@ class GoTrueClient {
|
|
|
4148
4511
|
this._removeVisibilityChangedCallback();
|
|
4149
4512
|
await this._stopAutoRefresh();
|
|
4150
4513
|
}
|
|
4514
|
+
/**
|
|
4515
|
+
* Tears down the client's background work: stops the auto-refresh interval,
|
|
4516
|
+
* removes the `visibilitychange` listener, closes the cross-tab
|
|
4517
|
+
* `BroadcastChannel`, and clears registered `onAuthStateChange` subscribers.
|
|
4518
|
+
*
|
|
4519
|
+
* Call this from cleanup hooks when the client is being replaced before
|
|
4520
|
+
* its JS realm is destroyed. React Strict Mode and HMR are the common
|
|
4521
|
+
* cases. Any in-flight `fetch` calls continue to completion and may still
|
|
4522
|
+
* write to storage; dispose doesn't abort them or erase storage.
|
|
4523
|
+
*
|
|
4524
|
+
* Lifecycle caveat: because in-flight refreshes are not aborted, a
|
|
4525
|
+
* disposed instance can still persist a rotated session to storage after
|
|
4526
|
+
* `dispose()` returns. A subsequent `createClient` against the same
|
|
4527
|
+
* `storageKey` will pick up that session on its next read. If you need
|
|
4528
|
+
* strict isolation between client lifecycles, await any pending auth
|
|
4529
|
+
* operation before calling `dispose()` (or change the `storageKey` for
|
|
4530
|
+
* the replacement client).
|
|
4531
|
+
*
|
|
4532
|
+
* Safe to call repeatedly.
|
|
4533
|
+
*
|
|
4534
|
+
* @category Auth
|
|
4535
|
+
*
|
|
4536
|
+
* @example Cleanup on React unmount
|
|
4537
|
+
* ```ts
|
|
4538
|
+
* useEffect(() => {
|
|
4539
|
+
* const client = createClient(...)
|
|
4540
|
+
* return () => { client.auth.dispose() }
|
|
4541
|
+
* }, [])
|
|
4542
|
+
* ```
|
|
4543
|
+
*/
|
|
4544
|
+
async dispose() {
|
|
4545
|
+
var _a;
|
|
4546
|
+
this._removeVisibilityChangedCallback();
|
|
4547
|
+
await this._stopAutoRefresh();
|
|
4548
|
+
(_a = this.broadcastChannel) === null || _a === void 0 ? void 0 : _a.close();
|
|
4549
|
+
this.broadcastChannel = null;
|
|
4550
|
+
this.stateChangeEmitters.clear();
|
|
4551
|
+
}
|
|
4151
4552
|
/**
|
|
4152
4553
|
* Runs the auto refresh token tick.
|
|
4153
4554
|
*/
|
|
4154
4555
|
async _autoRefreshTokenTick() {
|
|
4155
4556
|
this._debug('#_autoRefreshTokenTick()', 'begin');
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4557
|
+
if (this.lock != null) {
|
|
4558
|
+
// TODO(v3): remove legacy lock path. Uses `_acquireLock(0, ...)` which
|
|
4559
|
+
// throws `LockAcquireTimeoutError` immediately if the lock is held —
|
|
4560
|
+
// that's the fail-fast skip path that lets the tick bail out instead
|
|
4561
|
+
// of queuing behind a long-running operation.
|
|
4562
|
+
try {
|
|
4563
|
+
await this._acquireLock(0, async () => {
|
|
4160
4564
|
try {
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4565
|
+
const now = Date.now();
|
|
4566
|
+
try {
|
|
4567
|
+
return await this._useSession(async (result) => {
|
|
4568
|
+
const { data: { session }, } = result;
|
|
4569
|
+
if (!session || !session.refresh_token || !session.expires_at) {
|
|
4570
|
+
this._debug('#_autoRefreshTokenTick()', 'no session');
|
|
4571
|
+
return;
|
|
4572
|
+
}
|
|
4573
|
+
const expiresInTicks = Math.floor((session.expires_at * 1000 - now) / constants_1.AUTO_REFRESH_TICK_DURATION_MS);
|
|
4574
|
+
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`);
|
|
4575
|
+
if (expiresInTicks <= constants_1.AUTO_REFRESH_TICK_THRESHOLD) {
|
|
4576
|
+
await this._callRefreshToken(session.refresh_token);
|
|
4577
|
+
}
|
|
4578
|
+
});
|
|
4579
|
+
}
|
|
4580
|
+
catch (e) {
|
|
4581
|
+
console.error('Auto refresh tick failed with error. This is likely a transient error.', e);
|
|
4582
|
+
}
|
|
4174
4583
|
}
|
|
4175
|
-
|
|
4176
|
-
|
|
4584
|
+
finally {
|
|
4585
|
+
this._debug('#_autoRefreshTokenTick()', 'end');
|
|
4177
4586
|
}
|
|
4587
|
+
});
|
|
4588
|
+
}
|
|
4589
|
+
catch (e) {
|
|
4590
|
+
if (e instanceof locks_1.LockAcquireTimeoutError) {
|
|
4591
|
+
this._debug('auto refresh token tick lock not available');
|
|
4178
4592
|
}
|
|
4179
|
-
|
|
4180
|
-
|
|
4593
|
+
else {
|
|
4594
|
+
throw e;
|
|
4181
4595
|
}
|
|
4182
|
-
}
|
|
4596
|
+
}
|
|
4597
|
+
return;
|
|
4183
4598
|
}
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4599
|
+
// Lockless default: skip if a refresh is already in flight.
|
|
4600
|
+
// `_callRefreshToken` also dedupes via the same field; this is just a
|
|
4601
|
+
// fast-path skip to avoid an unnecessary storage read.
|
|
4602
|
+
if (this.refreshingDeferred !== null) {
|
|
4603
|
+
this._debug('#_autoRefreshTokenTick()', 'refresh already in flight, skipping');
|
|
4604
|
+
return;
|
|
4605
|
+
}
|
|
4606
|
+
try {
|
|
4607
|
+
const now = Date.now();
|
|
4608
|
+
try {
|
|
4609
|
+
await this._useSession(async (result) => {
|
|
4610
|
+
const { data: { session }, } = result;
|
|
4611
|
+
if (!session || !session.refresh_token || !session.expires_at) {
|
|
4612
|
+
this._debug('#_autoRefreshTokenTick()', 'no session');
|
|
4613
|
+
return;
|
|
4614
|
+
}
|
|
4615
|
+
// session will expire in this many ticks (or has already expired if <= 0)
|
|
4616
|
+
const expiresInTicks = Math.floor((session.expires_at * 1000 - now) / constants_1.AUTO_REFRESH_TICK_DURATION_MS);
|
|
4617
|
+
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`);
|
|
4618
|
+
if (expiresInTicks <= constants_1.AUTO_REFRESH_TICK_THRESHOLD) {
|
|
4619
|
+
await this._callRefreshToken(session.refresh_token);
|
|
4620
|
+
}
|
|
4621
|
+
});
|
|
4187
4622
|
}
|
|
4188
|
-
|
|
4189
|
-
|
|
4623
|
+
catch (e) {
|
|
4624
|
+
console.error('Auto refresh tick failed with error. This is likely a transient error.', e);
|
|
4190
4625
|
}
|
|
4191
4626
|
}
|
|
4627
|
+
finally {
|
|
4628
|
+
this._debug('#_autoRefreshTokenTick()', 'end');
|
|
4629
|
+
}
|
|
4192
4630
|
}
|
|
4193
4631
|
/**
|
|
4194
4632
|
* Registers callbacks on the browser / platform, which in-turn run
|
|
@@ -4237,18 +4675,26 @@ class GoTrueClient {
|
|
|
4237
4675
|
if (!calledFromInitialize) {
|
|
4238
4676
|
// called when the visibility has changed, i.e. the browser
|
|
4239
4677
|
// transitioned from hidden -> visible so we need to see if the session
|
|
4240
|
-
// should be recovered
|
|
4241
|
-
// the lock first asynchronously
|
|
4678
|
+
// should be recovered
|
|
4242
4679
|
await this.initializePromise;
|
|
4243
|
-
|
|
4680
|
+
if (this.lock != null) {
|
|
4681
|
+
// TODO(v3): remove legacy lock path
|
|
4682
|
+
await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
4683
|
+
if (document.visibilityState !== 'visible') {
|
|
4684
|
+
this._debug(methodName, 'acquired the lock to recover the session, but the browser visibilityState is no longer visible, aborting');
|
|
4685
|
+
return;
|
|
4686
|
+
}
|
|
4687
|
+
await this._recoverAndRefresh();
|
|
4688
|
+
});
|
|
4689
|
+
}
|
|
4690
|
+
else {
|
|
4244
4691
|
if (document.visibilityState !== 'visible') {
|
|
4245
|
-
this._debug(methodName, '
|
|
4246
|
-
// visibility has changed while waiting for the lock, abort
|
|
4692
|
+
this._debug(methodName, 'visibilityState is no longer visible, skipping recovery');
|
|
4247
4693
|
return;
|
|
4248
4694
|
}
|
|
4249
4695
|
// recover the session
|
|
4250
4696
|
await this._recoverAndRefresh();
|
|
4251
|
-
}
|
|
4697
|
+
}
|
|
4252
4698
|
}
|
|
4253
4699
|
}
|
|
4254
4700
|
else if (document.visibilityState === 'hidden') {
|
|
@@ -4344,7 +4790,7 @@ class GoTrueClient {
|
|
|
4344
4790
|
}
|
|
4345
4791
|
}
|
|
4346
4792
|
async _verify(params) {
|
|
4347
|
-
|
|
4793
|
+
const run = async () => {
|
|
4348
4794
|
try {
|
|
4349
4795
|
return await this._useSession(async (result) => {
|
|
4350
4796
|
var _a;
|
|
@@ -4378,10 +4824,15 @@ class GoTrueClient {
|
|
|
4378
4824
|
}
|
|
4379
4825
|
throw error;
|
|
4380
4826
|
}
|
|
4381
|
-
}
|
|
4827
|
+
};
|
|
4828
|
+
if (this.lock != null) {
|
|
4829
|
+
// TODO(v3): remove legacy lock path
|
|
4830
|
+
return this._acquireLock(this.lockAcquireTimeout, run);
|
|
4831
|
+
}
|
|
4832
|
+
return run();
|
|
4382
4833
|
}
|
|
4383
4834
|
async _challenge(params) {
|
|
4384
|
-
|
|
4835
|
+
const run = async () => {
|
|
4385
4836
|
try {
|
|
4386
4837
|
return await this._useSession(async (result) => {
|
|
4387
4838
|
var _a;
|
|
@@ -4421,14 +4872,17 @@ class GoTrueClient {
|
|
|
4421
4872
|
}
|
|
4422
4873
|
throw error;
|
|
4423
4874
|
}
|
|
4424
|
-
}
|
|
4875
|
+
};
|
|
4876
|
+
if (this.lock != null) {
|
|
4877
|
+
// TODO(v3): remove legacy lock path
|
|
4878
|
+
return this._acquireLock(this.lockAcquireTimeout, run);
|
|
4879
|
+
}
|
|
4880
|
+
return run();
|
|
4425
4881
|
}
|
|
4426
4882
|
/**
|
|
4427
|
-
* {@
|
|
4883
|
+
* {@link GoTrueMFAApi#challengeAndVerify}
|
|
4428
4884
|
*/
|
|
4429
4885
|
async _challengeAndVerify(params) {
|
|
4430
|
-
// both _challenge and _verify independently acquire the lock, so no need
|
|
4431
|
-
// to acquire it here
|
|
4432
4886
|
const { data: challengeData, error: challengeError } = await this._challenge({
|
|
4433
4887
|
factorId: params.factorId,
|
|
4434
4888
|
});
|
|
@@ -4442,11 +4896,10 @@ class GoTrueClient {
|
|
|
4442
4896
|
});
|
|
4443
4897
|
}
|
|
4444
4898
|
/**
|
|
4445
|
-
* {@
|
|
4899
|
+
* {@link GoTrueMFAApi#listFactors}
|
|
4446
4900
|
*/
|
|
4447
4901
|
async _listFactors() {
|
|
4448
4902
|
var _a;
|
|
4449
|
-
// use #getUser instead of #_getUser as the former acquires a lock
|
|
4450
4903
|
const { data: { user }, error: userError, } = await this.getUser();
|
|
4451
4904
|
if (userError) {
|
|
4452
4905
|
return { data: null, error: userError };
|
|
@@ -4471,7 +4924,7 @@ class GoTrueClient {
|
|
|
4471
4924
|
};
|
|
4472
4925
|
}
|
|
4473
4926
|
/**
|
|
4474
|
-
* {@
|
|
4927
|
+
* {@link GoTrueMFAApi#getAuthenticatorAssuranceLevel}
|
|
4475
4928
|
*/
|
|
4476
4929
|
async _getAuthenticatorAssuranceLevel(jwt) {
|
|
4477
4930
|
var _a, _b, _c, _d;
|
|
@@ -4722,15 +5175,15 @@ class GoTrueClient {
|
|
|
4722
5175
|
* Extracts the JWT claims present in the access token by first verifying the
|
|
4723
5176
|
* JWT against the server's JSON Web Key Set endpoint
|
|
4724
5177
|
* `/.well-known/jwks.json` which is often cached, resulting in significantly
|
|
4725
|
-
* faster responses. Prefer this method over {@link
|
|
5178
|
+
* faster responses. Prefer this method over {@link GoTrueClient.getUser} which always
|
|
4726
5179
|
* sends a request to the Auth server for each JWT.
|
|
4727
5180
|
*
|
|
4728
5181
|
* 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
|
-
*
|
|
5182
|
+
* RSA) it always sends a request to the Auth server (similar to
|
|
5183
|
+
* {@link GoTrueClient.getUser}) to verify the JWT.
|
|
4731
5184
|
*
|
|
4732
5185
|
* @param jwt An optional specific JWT you wish to verify, not the one you
|
|
4733
|
-
* can obtain from {@link
|
|
5186
|
+
* can obtain from {@link GoTrueClient.getSession}.
|
|
4734
5187
|
* @param options Various additional options that allow you to customize the
|
|
4735
5188
|
* behavior of this method.
|
|
4736
5189
|
*
|
|
@@ -4795,8 +5248,14 @@ class GoTrueClient {
|
|
|
4795
5248
|
}
|
|
4796
5249
|
const { header, payload, signature, raw: { header: rawHeader, payload: rawPayload }, } = (0, helpers_1.decodeJWT)(token);
|
|
4797
5250
|
if (!(options === null || options === void 0 ? void 0 : options.allowExpired)) {
|
|
4798
|
-
// Reject expired JWTs should only happen if jwt argument was passed
|
|
4799
|
-
|
|
5251
|
+
// Reject expired JWTs should only happen if jwt argument was passed.
|
|
5252
|
+
// Rethrow as AuthInvalidJwtError so the outer catch converts it to { data, error }.
|
|
5253
|
+
try {
|
|
5254
|
+
(0, helpers_1.validateExp)(payload.exp);
|
|
5255
|
+
}
|
|
5256
|
+
catch (e) {
|
|
5257
|
+
throw new errors_1.AuthInvalidJwtError(e instanceof Error ? e.message : 'JWT validation failed');
|
|
5258
|
+
}
|
|
4800
5259
|
}
|
|
4801
5260
|
const signingKey = !header.alg ||
|
|
4802
5261
|
header.alg.startsWith('HS') ||
|
|
@@ -4847,6 +5306,335 @@ class GoTrueClient {
|
|
|
4847
5306
|
throw error;
|
|
4848
5307
|
}
|
|
4849
5308
|
}
|
|
5309
|
+
// --- Passkey Methods ---
|
|
5310
|
+
/**
|
|
5311
|
+
* Sign in with a passkey. Handles the full WebAuthn ceremony:
|
|
5312
|
+
* 1. Fetches authentication challenge from server
|
|
5313
|
+
* 2. Prompts user via navigator.credentials.get()
|
|
5314
|
+
* 3. Verifies credential with server and creates session
|
|
5315
|
+
*
|
|
5316
|
+
* Requires `auth.experimental.passkey: true`.
|
|
5317
|
+
*
|
|
5318
|
+
* @category Auth
|
|
5319
|
+
*/
|
|
5320
|
+
async signInWithPasskey(credentials) {
|
|
5321
|
+
var _a, _b, _c;
|
|
5322
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5323
|
+
try {
|
|
5324
|
+
if (!(0, webauthn_1.browserSupportsWebAuthn)()) {
|
|
5325
|
+
return this._returnResult({
|
|
5326
|
+
data: null,
|
|
5327
|
+
error: new errors_1.AuthUnknownError('Browser does not support WebAuthn', null),
|
|
5328
|
+
});
|
|
5329
|
+
}
|
|
5330
|
+
// 1. Get challenge options from server
|
|
5331
|
+
const { data: options, error: optionsError } = await this._startPasskeyAuthentication({
|
|
5332
|
+
options: { captchaToken: (_a = credentials === null || credentials === void 0 ? void 0 : credentials.options) === null || _a === void 0 ? void 0 : _a.captchaToken },
|
|
5333
|
+
});
|
|
5334
|
+
if (optionsError || !options) {
|
|
5335
|
+
return this._returnResult({ data: null, error: optionsError });
|
|
5336
|
+
}
|
|
5337
|
+
// 2. Deserialize and prompt user via browser WebAuthn API
|
|
5338
|
+
const publicKeyOptions = (0, webauthn_1.deserializeCredentialRequestOptions)(options.options);
|
|
5339
|
+
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();
|
|
5340
|
+
const { data: credential, error: credentialError } = await (0, webauthn_1.getCredential)({
|
|
5341
|
+
publicKey: publicKeyOptions,
|
|
5342
|
+
signal,
|
|
5343
|
+
});
|
|
5344
|
+
if (credentialError || !credential) {
|
|
5345
|
+
return this._returnResult({
|
|
5346
|
+
data: null,
|
|
5347
|
+
error: credentialError !== null && credentialError !== void 0 ? credentialError : new errors_1.AuthUnknownError('WebAuthn ceremony failed', null),
|
|
5348
|
+
});
|
|
5349
|
+
}
|
|
5350
|
+
// 3. Serialize and verify with server
|
|
5351
|
+
const serialized = (0, webauthn_1.serializeCredentialRequestResponse)(credential);
|
|
5352
|
+
return this._verifyPasskeyAuthentication({
|
|
5353
|
+
challengeId: options.challenge_id,
|
|
5354
|
+
credential: serialized,
|
|
5355
|
+
});
|
|
5356
|
+
}
|
|
5357
|
+
catch (error) {
|
|
5358
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5359
|
+
return this._returnResult({ data: null, error });
|
|
5360
|
+
}
|
|
5361
|
+
throw error;
|
|
5362
|
+
}
|
|
5363
|
+
}
|
|
5364
|
+
/**
|
|
5365
|
+
* Register a passkey for the current authenticated user. Handles the full WebAuthn ceremony:
|
|
5366
|
+
* 1. Fetches registration challenge from server
|
|
5367
|
+
* 2. Prompts user via navigator.credentials.create()
|
|
5368
|
+
* 3. Verifies credential with server
|
|
5369
|
+
*
|
|
5370
|
+
* Requires an active session. Requires `auth.experimental.passkey: true`.
|
|
5371
|
+
*
|
|
5372
|
+
* @category Auth
|
|
5373
|
+
*/
|
|
5374
|
+
async registerPasskey(credentials) {
|
|
5375
|
+
var _a, _b;
|
|
5376
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5377
|
+
try {
|
|
5378
|
+
if (!(0, webauthn_1.browserSupportsWebAuthn)()) {
|
|
5379
|
+
return this._returnResult({
|
|
5380
|
+
data: null,
|
|
5381
|
+
error: new errors_1.AuthUnknownError('Browser does not support WebAuthn', null),
|
|
5382
|
+
});
|
|
5383
|
+
}
|
|
5384
|
+
// 1. Get challenge options from server
|
|
5385
|
+
const { data: options, error: optionsError } = await this._startPasskeyRegistration();
|
|
5386
|
+
if (optionsError || !options) {
|
|
5387
|
+
return this._returnResult({ data: null, error: optionsError });
|
|
5388
|
+
}
|
|
5389
|
+
// 2. Deserialize and prompt user via browser WebAuthn API
|
|
5390
|
+
const publicKeyOptions = (0, webauthn_1.deserializeCredentialCreationOptions)(options.options);
|
|
5391
|
+
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();
|
|
5392
|
+
const { data: credential, error: credentialError } = await (0, webauthn_1.createCredential)({
|
|
5393
|
+
publicKey: publicKeyOptions,
|
|
5394
|
+
signal,
|
|
5395
|
+
});
|
|
5396
|
+
if (credentialError || !credential) {
|
|
5397
|
+
return this._returnResult({
|
|
5398
|
+
data: null,
|
|
5399
|
+
error: credentialError !== null && credentialError !== void 0 ? credentialError : new errors_1.AuthUnknownError('WebAuthn ceremony failed', null),
|
|
5400
|
+
});
|
|
5401
|
+
}
|
|
5402
|
+
// 3. Serialize and verify with server
|
|
5403
|
+
const serialized = (0, webauthn_1.serializeCredentialCreationResponse)(credential);
|
|
5404
|
+
return this._verifyPasskeyRegistration({
|
|
5405
|
+
challengeId: options.challenge_id,
|
|
5406
|
+
credential: serialized,
|
|
5407
|
+
});
|
|
5408
|
+
}
|
|
5409
|
+
catch (error) {
|
|
5410
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5411
|
+
return this._returnResult({ data: null, error });
|
|
5412
|
+
}
|
|
5413
|
+
throw error;
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
/**
|
|
5417
|
+
* Start passkey registration for the current authenticated user.
|
|
5418
|
+
* Returns WebAuthn credential creation options to pass to navigator.credentials.create().
|
|
5419
|
+
*/
|
|
5420
|
+
async _startPasskeyRegistration() {
|
|
5421
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5422
|
+
try {
|
|
5423
|
+
return await this._useSession(async (result) => {
|
|
5424
|
+
const { data: { session }, error: sessionError, } = result;
|
|
5425
|
+
if (sessionError) {
|
|
5426
|
+
return this._returnResult({ data: null, error: sessionError });
|
|
5427
|
+
}
|
|
5428
|
+
if (!session) {
|
|
5429
|
+
return this._returnResult({ data: null, error: new errors_1.AuthSessionMissingError() });
|
|
5430
|
+
}
|
|
5431
|
+
const { data, error } = await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/passkeys/registration/options`, {
|
|
5432
|
+
headers: this.headers,
|
|
5433
|
+
jwt: session.access_token,
|
|
5434
|
+
body: {},
|
|
5435
|
+
});
|
|
5436
|
+
if (error) {
|
|
5437
|
+
return this._returnResult({ data: null, error });
|
|
5438
|
+
}
|
|
5439
|
+
return this._returnResult({ data, error: null });
|
|
5440
|
+
});
|
|
5441
|
+
}
|
|
5442
|
+
catch (error) {
|
|
5443
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5444
|
+
return this._returnResult({ data: null, error });
|
|
5445
|
+
}
|
|
5446
|
+
throw error;
|
|
5447
|
+
}
|
|
5448
|
+
}
|
|
5449
|
+
/**
|
|
5450
|
+
* Verify passkey registration with the credential response.
|
|
5451
|
+
* The credentialResponse should be the serialized output of navigator.credentials.create().
|
|
5452
|
+
*/
|
|
5453
|
+
async _verifyPasskeyRegistration(params) {
|
|
5454
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5455
|
+
try {
|
|
5456
|
+
return await this._useSession(async (result) => {
|
|
5457
|
+
const { data: { session }, error: sessionError, } = result;
|
|
5458
|
+
if (sessionError) {
|
|
5459
|
+
return this._returnResult({ data: null, error: sessionError });
|
|
5460
|
+
}
|
|
5461
|
+
if (!session) {
|
|
5462
|
+
return this._returnResult({ data: null, error: new errors_1.AuthSessionMissingError() });
|
|
5463
|
+
}
|
|
5464
|
+
const { data, error } = await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/passkeys/registration/verify`, {
|
|
5465
|
+
headers: this.headers,
|
|
5466
|
+
jwt: session.access_token,
|
|
5467
|
+
body: {
|
|
5468
|
+
challenge_id: params.challengeId,
|
|
5469
|
+
credential: params.credential,
|
|
5470
|
+
},
|
|
5471
|
+
});
|
|
5472
|
+
if (error) {
|
|
5473
|
+
return this._returnResult({ data: null, error });
|
|
5474
|
+
}
|
|
5475
|
+
return this._returnResult({ data, error: null });
|
|
5476
|
+
});
|
|
5477
|
+
}
|
|
5478
|
+
catch (error) {
|
|
5479
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5480
|
+
return this._returnResult({ data: null, error });
|
|
5481
|
+
}
|
|
5482
|
+
throw error;
|
|
5483
|
+
}
|
|
5484
|
+
}
|
|
5485
|
+
/**
|
|
5486
|
+
* Start passkey authentication.
|
|
5487
|
+
* Returns WebAuthn credential request options to pass to navigator.credentials.get().
|
|
5488
|
+
*/
|
|
5489
|
+
async _startPasskeyAuthentication(params) {
|
|
5490
|
+
var _a;
|
|
5491
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5492
|
+
try {
|
|
5493
|
+
const { data, error } = await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/passkeys/authentication/options`, {
|
|
5494
|
+
headers: this.headers,
|
|
5495
|
+
body: {
|
|
5496
|
+
gotrue_meta_security: { captcha_token: (_a = params === null || params === void 0 ? void 0 : params.options) === null || _a === void 0 ? void 0 : _a.captchaToken },
|
|
5497
|
+
},
|
|
5498
|
+
});
|
|
5499
|
+
if (error) {
|
|
5500
|
+
return this._returnResult({ data: null, error });
|
|
5501
|
+
}
|
|
5502
|
+
return this._returnResult({ data, error: null });
|
|
5503
|
+
}
|
|
5504
|
+
catch (error) {
|
|
5505
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5506
|
+
return this._returnResult({ data: null, error });
|
|
5507
|
+
}
|
|
5508
|
+
throw error;
|
|
5509
|
+
}
|
|
5510
|
+
}
|
|
5511
|
+
/**
|
|
5512
|
+
* Verify passkey authentication and create a session.
|
|
5513
|
+
* The credential should be the serialized output of navigator.credentials.get().
|
|
5514
|
+
*/
|
|
5515
|
+
async _verifyPasskeyAuthentication(params) {
|
|
5516
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5517
|
+
try {
|
|
5518
|
+
const { data, error } = await (0, fetch_1._request)(this.fetch, 'POST', `${this.url}/passkeys/authentication/verify`, {
|
|
5519
|
+
headers: this.headers,
|
|
5520
|
+
body: {
|
|
5521
|
+
challenge_id: params.challengeId,
|
|
5522
|
+
credential: params.credential,
|
|
5523
|
+
},
|
|
5524
|
+
xform: fetch_1._sessionResponse,
|
|
5525
|
+
});
|
|
5526
|
+
if (error) {
|
|
5527
|
+
return this._returnResult({ data: null, error });
|
|
5528
|
+
}
|
|
5529
|
+
if (data.session) {
|
|
5530
|
+
await this._saveSession(data.session);
|
|
5531
|
+
await this._notifyAllSubscribers('SIGNED_IN', data.session);
|
|
5532
|
+
}
|
|
5533
|
+
return this._returnResult({ data, error: null });
|
|
5534
|
+
}
|
|
5535
|
+
catch (error) {
|
|
5536
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5537
|
+
return this._returnResult({ data: null, error });
|
|
5538
|
+
}
|
|
5539
|
+
throw error;
|
|
5540
|
+
}
|
|
5541
|
+
}
|
|
5542
|
+
/**
|
|
5543
|
+
* List all passkeys for the current user.
|
|
5544
|
+
*/
|
|
5545
|
+
async _listPasskeys() {
|
|
5546
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5547
|
+
try {
|
|
5548
|
+
return await this._useSession(async (result) => {
|
|
5549
|
+
const { data: { session }, error: sessionError, } = result;
|
|
5550
|
+
if (sessionError) {
|
|
5551
|
+
return this._returnResult({ data: null, error: sessionError });
|
|
5552
|
+
}
|
|
5553
|
+
if (!session) {
|
|
5554
|
+
return this._returnResult({ data: null, error: new errors_1.AuthSessionMissingError() });
|
|
5555
|
+
}
|
|
5556
|
+
const { data, error } = await (0, fetch_1._request)(this.fetch, 'GET', `${this.url}/passkeys`, {
|
|
5557
|
+
headers: this.headers,
|
|
5558
|
+
jwt: session.access_token,
|
|
5559
|
+
xform: (data) => ({ data, error: null }),
|
|
5560
|
+
});
|
|
5561
|
+
if (error) {
|
|
5562
|
+
return this._returnResult({ data: null, error });
|
|
5563
|
+
}
|
|
5564
|
+
return this._returnResult({ data, error: null });
|
|
5565
|
+
});
|
|
5566
|
+
}
|
|
5567
|
+
catch (error) {
|
|
5568
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5569
|
+
return this._returnResult({ data: null, error });
|
|
5570
|
+
}
|
|
5571
|
+
throw error;
|
|
5572
|
+
}
|
|
5573
|
+
}
|
|
5574
|
+
/**
|
|
5575
|
+
* Update a passkey.
|
|
5576
|
+
*/
|
|
5577
|
+
async _updatePasskey(params) {
|
|
5578
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5579
|
+
try {
|
|
5580
|
+
return await this._useSession(async (result) => {
|
|
5581
|
+
const { data: { session }, error: sessionError, } = result;
|
|
5582
|
+
if (sessionError) {
|
|
5583
|
+
return this._returnResult({ data: null, error: sessionError });
|
|
5584
|
+
}
|
|
5585
|
+
if (!session) {
|
|
5586
|
+
return this._returnResult({ data: null, error: new errors_1.AuthSessionMissingError() });
|
|
5587
|
+
}
|
|
5588
|
+
const { data, error } = await (0, fetch_1._request)(this.fetch, 'PATCH', `${this.url}/passkeys/${params.passkeyId}`, {
|
|
5589
|
+
headers: this.headers,
|
|
5590
|
+
jwt: session.access_token,
|
|
5591
|
+
body: { friendly_name: params.friendlyName },
|
|
5592
|
+
});
|
|
5593
|
+
if (error) {
|
|
5594
|
+
return this._returnResult({ data: null, error });
|
|
5595
|
+
}
|
|
5596
|
+
return this._returnResult({ data, error: null });
|
|
5597
|
+
});
|
|
5598
|
+
}
|
|
5599
|
+
catch (error) {
|
|
5600
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5601
|
+
return this._returnResult({ data: null, error });
|
|
5602
|
+
}
|
|
5603
|
+
throw error;
|
|
5604
|
+
}
|
|
5605
|
+
}
|
|
5606
|
+
/**
|
|
5607
|
+
* Delete a passkey.
|
|
5608
|
+
*/
|
|
5609
|
+
async _deletePasskey(params) {
|
|
5610
|
+
(0, helpers_1.assertPasskeyExperimentalEnabled)(this.experimental);
|
|
5611
|
+
try {
|
|
5612
|
+
return await this._useSession(async (result) => {
|
|
5613
|
+
const { data: { session }, error: sessionError, } = result;
|
|
5614
|
+
if (sessionError) {
|
|
5615
|
+
return this._returnResult({ data: null, error: sessionError });
|
|
5616
|
+
}
|
|
5617
|
+
if (!session) {
|
|
5618
|
+
return this._returnResult({ data: null, error: new errors_1.AuthSessionMissingError() });
|
|
5619
|
+
}
|
|
5620
|
+
const { error } = await (0, fetch_1._request)(this.fetch, 'DELETE', `${this.url}/passkeys/${params.passkeyId}`, {
|
|
5621
|
+
headers: this.headers,
|
|
5622
|
+
jwt: session.access_token,
|
|
5623
|
+
noResolveJson: true,
|
|
5624
|
+
});
|
|
5625
|
+
if (error) {
|
|
5626
|
+
return this._returnResult({ data: null, error });
|
|
5627
|
+
}
|
|
5628
|
+
return this._returnResult({ data: null, error: null });
|
|
5629
|
+
});
|
|
5630
|
+
}
|
|
5631
|
+
catch (error) {
|
|
5632
|
+
if ((0, errors_1.isAuthError)(error)) {
|
|
5633
|
+
return this._returnResult({ data: null, error });
|
|
5634
|
+
}
|
|
5635
|
+
throw error;
|
|
5636
|
+
}
|
|
5637
|
+
}
|
|
4850
5638
|
}
|
|
4851
5639
|
GoTrueClient.nextInstanceID = {};
|
|
4852
5640
|
exports.default = GoTrueClient;
|