@supabase/gotrue-js 2.119.0-canary.3 → 2.119.0-canary.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/main/GoTrueClient.d.ts +30 -5
- package/dist/main/GoTrueClient.d.ts.map +1 -1
- package/dist/main/GoTrueClient.js +80 -17
- package/dist/main/GoTrueClient.js.map +1 -1
- package/dist/main/lib/version.d.ts +1 -1
- package/dist/main/lib/version.js +1 -1
- package/dist/module/GoTrueClient.d.ts +30 -5
- package/dist/module/GoTrueClient.d.ts.map +1 -1
- package/dist/module/GoTrueClient.js +80 -17
- package/dist/module/GoTrueClient.js.map +1 -1
- package/dist/module/lib/version.d.ts +1 -1
- package/dist/module/lib/version.js +1 -1
- package/dist/tsconfig.module.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/migrations/sign-out-global-keeps-session.md +41 -0
- package/package.json +1 -1
- package/src/GoTrueClient.ts +87 -25
- package/src/lib/version.ts +1 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# `signOut` keeps the session when the server logout fails
|
|
2
|
+
|
|
3
|
+
**Since:** the first release after v2.118.0
|
|
4
|
+
**Action required by:** none in v2.x. See [Planned for v3](#planned-for-v3).
|
|
5
|
+
|
|
6
|
+
`signOut({ scope: 'global' })` and `signOut({ scope: 'others' })` now report a server or network failure without touching the session they were asked to revoke. `signOut({ scope: 'local' })` always signs out this device.
|
|
7
|
+
|
|
8
|
+
## What changed
|
|
9
|
+
|
|
10
|
+
- **`global` (the default scope):** the local session is cleared, and `SIGNED_OUT` fires, only after the server accepts the logout. If the request fails, for example because the device is offline, the local session is kept, no `SIGNED_OUT` event fires and the error is returned.
|
|
11
|
+
- **`local`:** the local session is removed and `SIGNED_OUT` fires before the request to revoke the refresh token is sent, so signing out of this device never depends on the network. The request's error, if any, is still returned.
|
|
12
|
+
- **`others`:** the local session is never changed. A failed request returns the error.
|
|
13
|
+
|
|
14
|
+
Previously, a failed `global` sign out still cleared the local session. The refresh token then stayed valid on the server and the client no longer had the credentials to retry the revoke.
|
|
15
|
+
|
|
16
|
+
## Who is affected
|
|
17
|
+
|
|
18
|
+
**Callers that use the default scope** (`signOut()` or `signOut({ scope: 'global' })`) and assume it always ends the local session. If the device is offline or the auth server returns an error, `signOut()` now returns `{ error }` and the user stays signed in.
|
|
19
|
+
|
|
20
|
+
To always sign out this device, either use the `local` scope, or retry with it when `global` fails:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
const { error } = await supabase.auth.signOut()
|
|
24
|
+
if (error) {
|
|
25
|
+
// The server could not revoke the sessions and the user is still signed in locally.
|
|
26
|
+
// Sign out this device regardless:
|
|
27
|
+
await supabase.auth.signOut({ scope: 'local' })
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If you only want to sign out the current tab or device, pass `{ scope: 'local' }` directly. This is usually what a "Sign out" button wants.
|
|
32
|
+
|
|
33
|
+
## Responses that still count as a successful sign out
|
|
34
|
+
|
|
35
|
+
A `401`, `403` or `404` response from the auth server is treated as "already signed out": the local session is cleared and `error` is `null`. Auth servers from v2.145.0 answer `403` with `user_not_found` (and from v2.150.0 `session_not_found`) when the user or session no longer exists, and servers before v2.145.0 answered `401` or `404`.
|
|
36
|
+
|
|
37
|
+
The status is also `403` for `bad_jwt` and `user_banned`, where nothing was revoked, so these are treated as a successful sign out too. This is known, and changing it would be a breaking change.
|
|
38
|
+
|
|
39
|
+
## Planned for v3
|
|
40
|
+
|
|
41
|
+
v3 is expected to match on the error code instead of the status: only `session_not_found` and `user_not_found` will count as already signed out, and every other error will be returned with the session kept. Callers that handle the `error` from `signOut()` as shown above need no change.
|
package/package.json
CHANGED
package/src/GoTrueClient.ts
CHANGED
|
@@ -4026,12 +4026,31 @@ export default class GoTrueClient {
|
|
|
4026
4026
|
}
|
|
4027
4027
|
|
|
4028
4028
|
/**
|
|
4029
|
-
* Inside a browser context, `signOut()` will remove the logged in user from the browser session
|
|
4029
|
+
* Inside a browser context, `signOut()` will remove the logged in user from the browser session
|
|
4030
|
+
* and log them out - removing all items from localstorage and then trigger a `"SIGNED_OUT"`
|
|
4031
|
+
* event.
|
|
4030
4032
|
*
|
|
4031
|
-
* For server-side management, you can revoke all refresh tokens for a user by passing a user's
|
|
4032
|
-
*
|
|
4033
|
+
* For server-side management, you can revoke all refresh tokens for a user by passing a user's
|
|
4034
|
+
* JWT through to `auth.api.signOut(JWT: string)`.
|
|
4033
4035
|
*
|
|
4034
|
-
*
|
|
4036
|
+
* There is no way to revoke a user's access token jwt until it expires. It is recommended to set
|
|
4037
|
+
* a shorter expiry on the jwt for this reason.
|
|
4038
|
+
*
|
|
4039
|
+
* `signOut` behavior depends on the selected scope:
|
|
4040
|
+
*
|
|
4041
|
+
* - `local`: The local session is removed first and a `SIGNED_OUT` event is fired, then the client
|
|
4042
|
+
* makes a best-effort request to revoke the refresh token on the server. If the request fails,
|
|
4043
|
+
* the client is still signed out locally and the error is returned, but the server may retain
|
|
4044
|
+
* the refresh token until it expires.
|
|
4045
|
+
*
|
|
4046
|
+
* - `global`: All of the user's sessions are first invalidated on the server. Once the server
|
|
4047
|
+
* acknowledges the request, the local session is cleared. If server logout fails, the local
|
|
4048
|
+
* session is retained, no `SIGNED_OUT` event is triggered, and the server or network error is
|
|
4049
|
+
* returned. A 401, 403, or 404 response counts as a successful sign-out.
|
|
4050
|
+
*
|
|
4051
|
+
* - `others`: All other sessions are invalidated when the server accepts the request; the
|
|
4052
|
+
* current local session remains unchanged. In case of server or network failures, the error is
|
|
4053
|
+
* returned. A `SIGNED_OUT` event is never triggered, regardless of the outcome.
|
|
4035
4054
|
*
|
|
4036
4055
|
* **Warning:** the default `scope` is `'global'`. This signs the user out of
|
|
4037
4056
|
* **every device they are currently signed in on**, not just the current
|
|
@@ -4049,7 +4068,13 @@ export default class GoTrueClient {
|
|
|
4049
4068
|
* usually what apps want on a "Sign out" button, especially when users
|
|
4050
4069
|
* sign in from multiple devices and do not expect signing out of one to
|
|
4051
4070
|
* terminate the others.
|
|
4052
|
-
* -
|
|
4071
|
+
* - With the default `global` scope, `signOut()` while offline returns an error and keeps the user
|
|
4072
|
+
* signed in. To sign out regardless, fall back to `signOut({ scope: 'local' })` when the call
|
|
4073
|
+
* returns an error.
|
|
4074
|
+
* - Since Supabase Auth uses JWTs for authentication, the access token JWT will be valid until
|
|
4075
|
+
* it's expired. When the user signs out, Supabase revokes the refresh token and deletes the
|
|
4076
|
+
* JWT from the client-side. This does not revoke the JWT and it will still be valid until it
|
|
4077
|
+
* expires.
|
|
4053
4078
|
*
|
|
4054
4079
|
* @example Sign out of every device (global – default)
|
|
4055
4080
|
* ```js
|
|
@@ -4079,41 +4104,78 @@ export default class GoTrueClient {
|
|
|
4079
4104
|
return await this._signOut(options)
|
|
4080
4105
|
}
|
|
4081
4106
|
|
|
4082
|
-
protected async _signOut(
|
|
4083
|
-
|
|
4084
|
-
|
|
4107
|
+
protected async _signOut({ scope = 'global' }: SignOut = {}): Promise<{
|
|
4108
|
+
error: AuthError | null
|
|
4109
|
+
}> {
|
|
4085
4110
|
return await this._useSession(async (result) => {
|
|
4086
4111
|
const removeCurrentSession = async () => {
|
|
4087
4112
|
await this._removeSession()
|
|
4088
4113
|
}
|
|
4089
4114
|
const { data, error: sessionError } = result
|
|
4090
4115
|
if (sessionError && !isAuthSessionMissingError(sessionError)) {
|
|
4116
|
+
// A retryable refresh failure (offline, 5xx) leaves the session in storage and no access
|
|
4117
|
+
// token to revoke with, so `local` still has to sign out this device. A rejected refresh
|
|
4118
|
+
// has already removed the session and fired `SIGNED_OUT`.
|
|
4119
|
+
if (scope === 'local' && isAuthRetryableFetchError(sessionError)) {
|
|
4120
|
+
await removeCurrentSession()
|
|
4121
|
+
}
|
|
4091
4122
|
return this._returnResult({ error: sessionError })
|
|
4092
4123
|
}
|
|
4093
4124
|
const accessToken = data.session?.access_token
|
|
4125
|
+
let removeError: { thrown: unknown } | null = null
|
|
4126
|
+
const finish = <T>(value: T): T => {
|
|
4127
|
+
if (removeError) {
|
|
4128
|
+
throw removeError.thrown
|
|
4129
|
+
}
|
|
4130
|
+
return value
|
|
4131
|
+
}
|
|
4132
|
+
if (scope === 'local') {
|
|
4133
|
+
// The local session is removed before the request is sent, so signing out of this device
|
|
4134
|
+
// never depends on the network. The request only revokes the refresh token on the server.
|
|
4135
|
+
// A throwing `SIGNED_OUT` subscriber must not skip that request, so its error is rethrown
|
|
4136
|
+
// once the request has been attempted.
|
|
4137
|
+
try {
|
|
4138
|
+
await removeCurrentSession()
|
|
4139
|
+
} catch (thrown) {
|
|
4140
|
+
removeError = { thrown }
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4094
4143
|
if (accessToken) {
|
|
4095
4144
|
const { error } = await this.admin.signOut(accessToken, scope)
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4145
|
+
// The current behavior is wrong for the current version of auth (v2.197.0). However,
|
|
4146
|
+
// changing it could be considered a breaking change and must wait for v3. Before modifying
|
|
4147
|
+
// this code we also need to consider which range of Auth versions we aim to support. Right
|
|
4148
|
+
// now this is not defined and thus undefined.
|
|
4149
|
+
//
|
|
4150
|
+
// Some constraints to be aware of:
|
|
4151
|
+
// - Since supabase/auth v2.145.0 a missing user answers 403 `user_not_found`.
|
|
4152
|
+
// - Since supabase/auth v2.150.0 a missing session answers 403 `session_not_found`.
|
|
4153
|
+
// - Before v2.145.0 an invalid JWT answered 401.
|
|
4154
|
+
// - Before v2.145.0 a missing user answers 404., so those statuses are treated the same way.
|
|
4155
|
+
//
|
|
4156
|
+
// TODO(v3): if we only support auth v2.150.0 or later, change this to:
|
|
4157
|
+
// - Match on `error.code` instead of `error.status`, and treat only 403 `session_not_found`
|
|
4158
|
+
// and `user_not_found` as already signed out. Drop the 401 and 404 handling.
|
|
4159
|
+
// - Return every other error, e.g. 403 `bad_jwt` or `user_banned`, or a 401 or 404 from a
|
|
4160
|
+
// gateway, instead of treating it as signed out. Today nothing was revoked, yet the
|
|
4161
|
+
// session is cleared and `error` is null. With `global` the session is then kept.
|
|
4162
|
+
// - For `others`, return `session_not_found` as well. It describes the current session, not
|
|
4163
|
+
// the sessions being revoked.
|
|
4164
|
+
if (
|
|
4165
|
+
error &&
|
|
4166
|
+
!(
|
|
4167
|
+
(isAuthApiError(error) &&
|
|
4168
|
+
(error.status === 404 || error.status === 401 || error.status === 403)) ||
|
|
4169
|
+
isAuthSessionMissingError(error)
|
|
4170
|
+
)
|
|
4171
|
+
) {
|
|
4172
|
+
return finish(this._returnResult({ error }))
|
|
4111
4173
|
}
|
|
4112
4174
|
}
|
|
4113
|
-
if (scope
|
|
4175
|
+
if (scope === 'global') {
|
|
4114
4176
|
await removeCurrentSession()
|
|
4115
4177
|
}
|
|
4116
|
-
return this._returnResult({ error: null })
|
|
4178
|
+
return finish(this._returnResult({ error: null }))
|
|
4117
4179
|
})
|
|
4118
4180
|
}
|
|
4119
4181
|
|
package/src/lib/version.ts
CHANGED
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
// - Debugging and support (identifying which version is running)
|
|
5
5
|
// - Telemetry and logging (version reporting in errors/analytics)
|
|
6
6
|
// - Ensuring build artifacts match the published package version
|
|
7
|
-
export const version = '2.119.0-canary.
|
|
7
|
+
export const version = '2.119.0-canary.4'
|