@supabase/gotrue-js 2.0.1 → 2.2.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/README.md +1 -1
- package/dist/main/GoTrueAdminApi.d.ts +6 -1
- package/dist/main/GoTrueAdminApi.d.ts.map +1 -1
- package/dist/main/GoTrueAdminApi.js +40 -2
- package/dist/main/GoTrueAdminApi.js.map +1 -1
- package/dist/main/GoTrueClient.d.ts +43 -1
- package/dist/main/GoTrueClient.d.ts.map +1 -1
- package/dist/main/GoTrueClient.js +207 -3
- package/dist/main/GoTrueClient.js.map +1 -1
- package/dist/main/index.d.ts.map +1 -1
- package/dist/main/index.js.map +1 -1
- package/dist/main/lib/helpers.d.ts +1 -0
- package/dist/main/lib/helpers.d.ts.map +1 -1
- package/dist/main/lib/helpers.js +11 -1
- package/dist/main/lib/helpers.js.map +1 -1
- package/dist/main/lib/types.d.ts +378 -1
- package/dist/main/lib/types.d.ts.map +1 -1
- package/dist/main/lib/version.d.ts +1 -1
- package/dist/main/lib/version.js +1 -1
- package/dist/module/GoTrueAdminApi.d.ts +6 -1
- package/dist/module/GoTrueAdminApi.d.ts.map +1 -1
- package/dist/module/GoTrueAdminApi.js +40 -2
- package/dist/module/GoTrueAdminApi.js.map +1 -1
- package/dist/module/GoTrueClient.d.ts +43 -1
- package/dist/module/GoTrueClient.d.ts.map +1 -1
- package/dist/module/GoTrueClient.js +208 -4
- package/dist/module/GoTrueClient.js.map +1 -1
- package/dist/module/index.d.ts.map +1 -1
- package/dist/module/index.js.map +1 -1
- package/dist/module/lib/helpers.d.ts +1 -0
- package/dist/module/lib/helpers.d.ts.map +1 -1
- package/dist/module/lib/helpers.js +9 -0
- package/dist/module/lib/helpers.js.map +1 -1
- package/dist/module/lib/types.d.ts +378 -1
- package/dist/module/lib/types.d.ts.map +1 -1
- package/dist/module/lib/version.d.ts +1 -1
- package/dist/module/lib/version.js +1 -1
- package/package.json +2 -2
- package/src/GoTrueAdminApi.ts +63 -3
- package/src/GoTrueClient.ts +259 -4
- package/src/index.ts +0 -1
- package/src/lib/helpers.ts +11 -0
- package/src/lib/types.ts +425 -0
- package/src/lib/version.ts +1 -1
package/src/GoTrueClient.ts
CHANGED
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
} from './lib/errors'
|
|
18
18
|
import { Fetch, _request, _sessionResponse, _userResponse } from './lib/fetch'
|
|
19
19
|
import {
|
|
20
|
-
decodeBase64URL,
|
|
21
20
|
Deferred,
|
|
22
21
|
getItemAsync,
|
|
23
22
|
getParameterByName,
|
|
@@ -26,6 +25,7 @@ import {
|
|
|
26
25
|
resolveFetch,
|
|
27
26
|
setItemAsync,
|
|
28
27
|
uuid,
|
|
28
|
+
decodeJWTPayload,
|
|
29
29
|
} from './lib/helpers'
|
|
30
30
|
import localStorageAdapter from './lib/local-storage'
|
|
31
31
|
import { polyfillGlobalThis } from './lib/polyfills'
|
|
@@ -48,6 +48,21 @@ import type {
|
|
|
48
48
|
UserAttributes,
|
|
49
49
|
UserResponse,
|
|
50
50
|
VerifyOtpParams,
|
|
51
|
+
GoTrueMFAApi,
|
|
52
|
+
MFAEnrollParams,
|
|
53
|
+
AuthMFAEnrollResponse,
|
|
54
|
+
MFAChallengeParams,
|
|
55
|
+
AuthMFAChallengeResponse,
|
|
56
|
+
MFAUnenrollParams,
|
|
57
|
+
AuthMFAUnenrollResponse,
|
|
58
|
+
MFAVerifyParams,
|
|
59
|
+
AuthMFAVerifyResponse,
|
|
60
|
+
AuthMFAListFactorsResponse,
|
|
61
|
+
AMREntry,
|
|
62
|
+
AuthMFAGetAuthenticatorAssuranceLevelResponse,
|
|
63
|
+
AuthenticatorAssuranceLevels,
|
|
64
|
+
Factor,
|
|
65
|
+
MFAChallengeAndVerifyParams,
|
|
51
66
|
} from './lib/types'
|
|
52
67
|
|
|
53
68
|
polyfillGlobalThis() // Make "globalThis" available
|
|
@@ -67,6 +82,10 @@ export default class GoTrueClient {
|
|
|
67
82
|
* These methods should only be used in a trusted server-side environment.
|
|
68
83
|
*/
|
|
69
84
|
admin: GoTrueAdminApi
|
|
85
|
+
/**
|
|
86
|
+
* Namespace for the MFA methods.
|
|
87
|
+
*/
|
|
88
|
+
mfa: GoTrueMFAApi
|
|
70
89
|
/**
|
|
71
90
|
* The storage key used to identify the values saved in localStorage
|
|
72
91
|
*/
|
|
@@ -121,6 +140,15 @@ export default class GoTrueClient {
|
|
|
121
140
|
this.detectSessionInUrl = settings.detectSessionInUrl
|
|
122
141
|
|
|
123
142
|
this.initialize()
|
|
143
|
+
this.mfa = {
|
|
144
|
+
verify: this._verify.bind(this),
|
|
145
|
+
enroll: this._enroll.bind(this),
|
|
146
|
+
unenroll: this._unenroll.bind(this),
|
|
147
|
+
challenge: this._challenge.bind(this),
|
|
148
|
+
listFactors: this._listFactors.bind(this),
|
|
149
|
+
challengeAndVerify: this._challengeAndVerify.bind(this),
|
|
150
|
+
getAuthenticatorAssuranceLevel: this._getAuthenticatorAssuranceLevel.bind(this),
|
|
151
|
+
}
|
|
124
152
|
}
|
|
125
153
|
|
|
126
154
|
/**
|
|
@@ -204,7 +232,7 @@ export default class GoTrueClient {
|
|
|
204
232
|
body: {
|
|
205
233
|
email,
|
|
206
234
|
password,
|
|
207
|
-
data: options?.data,
|
|
235
|
+
data: options?.data ?? {},
|
|
208
236
|
gotrue_meta_security: { captcha_token: options?.captchaToken },
|
|
209
237
|
},
|
|
210
238
|
xform: _sessionResponse,
|
|
@@ -216,7 +244,7 @@ export default class GoTrueClient {
|
|
|
216
244
|
body: {
|
|
217
245
|
phone,
|
|
218
246
|
password,
|
|
219
|
-
data: options?.data,
|
|
247
|
+
data: options?.data ?? {},
|
|
220
248
|
gotrue_meta_security: { captcha_token: options?.captchaToken },
|
|
221
249
|
},
|
|
222
250
|
xform: _sessionResponse,
|
|
@@ -266,6 +294,7 @@ export default class GoTrueClient {
|
|
|
266
294
|
body: {
|
|
267
295
|
email,
|
|
268
296
|
password,
|
|
297
|
+
data: options?.data ?? {},
|
|
269
298
|
gotrue_meta_security: { captcha_token: options?.captchaToken },
|
|
270
299
|
},
|
|
271
300
|
xform: _sessionResponse,
|
|
@@ -277,6 +306,7 @@ export default class GoTrueClient {
|
|
|
277
306
|
body: {
|
|
278
307
|
phone,
|
|
279
308
|
password,
|
|
309
|
+
data: options?.data ?? {},
|
|
280
310
|
gotrue_meta_security: { captcha_token: options?.captchaToken },
|
|
281
311
|
},
|
|
282
312
|
xform: _sessionResponse,
|
|
@@ -530,6 +560,17 @@ export default class GoTrueClient {
|
|
|
530
560
|
}
|
|
531
561
|
}
|
|
532
562
|
|
|
563
|
+
/**
|
|
564
|
+
* Decodes a JWT (without performing any validation).
|
|
565
|
+
*/
|
|
566
|
+
private _decodeJWT(jwt: string): {
|
|
567
|
+
exp?: number
|
|
568
|
+
aal?: AuthenticatorAssuranceLevels | null
|
|
569
|
+
amr?: AMREntry[] | null
|
|
570
|
+
} {
|
|
571
|
+
return decodeJWTPayload(jwt)
|
|
572
|
+
}
|
|
573
|
+
|
|
533
574
|
/**
|
|
534
575
|
* Sets the session data from the current session. If the current session is expired, setSession will take care of refreshing it to obtain a new session.
|
|
535
576
|
* If the refresh token in the current session is invalid and the current session has expired, an error will be thrown.
|
|
@@ -545,7 +586,8 @@ export default class GoTrueClient {
|
|
|
545
586
|
let hasExpired = true
|
|
546
587
|
let session: Session | null = null
|
|
547
588
|
if (currentSession.access_token && currentSession.access_token.split('.')[1]) {
|
|
548
|
-
const payload =
|
|
589
|
+
const payload = this._decodeJWT(currentSession.access_token)
|
|
590
|
+
|
|
549
591
|
if (payload.exp) {
|
|
550
592
|
expiresAt = payload.exp
|
|
551
593
|
hasExpired = expiresAt <= timeNow
|
|
@@ -593,6 +635,46 @@ export default class GoTrueClient {
|
|
|
593
635
|
}
|
|
594
636
|
}
|
|
595
637
|
|
|
638
|
+
/**
|
|
639
|
+
* Returns a new session, regardless of expiry status.
|
|
640
|
+
* Takes in an optional current session. If not passed in, then refreshSession() will attempt to retrieve it from getSession().
|
|
641
|
+
* If the current session's refresh token is invalid, an error will be thrown.
|
|
642
|
+
* @param currentSession The current session. If passed in, it must contain a refresh token.
|
|
643
|
+
*/
|
|
644
|
+
async refreshSession(currentSession?: { refresh_token: string }): Promise<AuthResponse> {
|
|
645
|
+
try {
|
|
646
|
+
if (!currentSession) {
|
|
647
|
+
const { data, error } = await this.getSession()
|
|
648
|
+
if (error) {
|
|
649
|
+
throw error
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
currentSession = data.session ?? undefined
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (!currentSession?.refresh_token) {
|
|
656
|
+
throw new AuthSessionMissingError()
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const { session, error } = await this._callRefreshToken(currentSession.refresh_token)
|
|
660
|
+
if (error) {
|
|
661
|
+
return { data: { user: null, session: null }, error: error }
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (!session) {
|
|
665
|
+
return { data: { user: null, session: null }, error: null }
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return { data: { user: session.user, session }, error: null }
|
|
669
|
+
} catch (error) {
|
|
670
|
+
if (isAuthError(error)) {
|
|
671
|
+
return { data: { user: null, session: null }, error }
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
throw error
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
596
678
|
/**
|
|
597
679
|
* Gets the session data from a URL string
|
|
598
680
|
*/
|
|
@@ -1005,4 +1087,177 @@ export default class GoTrueClient {
|
|
|
1005
1087
|
}
|
|
1006
1088
|
return `${this.url}/authorize?${urlParams.join('&')}`
|
|
1007
1089
|
}
|
|
1090
|
+
|
|
1091
|
+
private async _unenroll(params: MFAUnenrollParams): Promise<AuthMFAUnenrollResponse> {
|
|
1092
|
+
const { data: sessionData, error: sessionError } = await this.getSession()
|
|
1093
|
+
if (sessionError) {
|
|
1094
|
+
return { data: null, error: sessionError }
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
return await _request(this.fetch, 'DELETE', `${this.url}/factors/${params.factorId}`, {
|
|
1098
|
+
headers: this.headers,
|
|
1099
|
+
jwt: sessionData?.session?.access_token,
|
|
1100
|
+
})
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* Deletes a registered factor from GoTrue
|
|
1105
|
+
* @param friendlyName Human readable name assigned to a device
|
|
1106
|
+
* @param factorType device which we're validating against. Can only be TOTP for now.
|
|
1107
|
+
* @param issuer domain which the user is enrolling with
|
|
1108
|
+
*/
|
|
1109
|
+
private async _enroll(params: MFAEnrollParams): Promise<AuthMFAEnrollResponse> {
|
|
1110
|
+
const { data: sessionData, error: sessionError } = await this.getSession()
|
|
1111
|
+
if (sessionError) {
|
|
1112
|
+
return { data: null, error: sessionError }
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
const { data, error } = await _request(this.fetch, 'POST', `${this.url}/factors`, {
|
|
1116
|
+
body: {
|
|
1117
|
+
friendly_name: params.friendlyName,
|
|
1118
|
+
factor_type: params.factorType,
|
|
1119
|
+
issuer: params.issuer,
|
|
1120
|
+
},
|
|
1121
|
+
headers: this.headers,
|
|
1122
|
+
jwt: sessionData?.session?.access_token,
|
|
1123
|
+
})
|
|
1124
|
+
|
|
1125
|
+
if (error) {
|
|
1126
|
+
return { data: null, error }
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
if (data?.totp?.qr_code) {
|
|
1130
|
+
data.totp.qr_code = `data:image/svg+xml;utf-8,${data.totp.qr_code}`
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
return { data, error: null }
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/**
|
|
1137
|
+
* Validates a device as part of the enrollment step.
|
|
1138
|
+
* @param factorID System assigned identifier for authenticator device as returned by enroll
|
|
1139
|
+
* @param code Code Generated by an authenticator device
|
|
1140
|
+
*/
|
|
1141
|
+
private async _verify(params: MFAVerifyParams): Promise<AuthMFAVerifyResponse> {
|
|
1142
|
+
const { data: sessionData, error: sessionError } = await this.getSession()
|
|
1143
|
+
if (sessionError) {
|
|
1144
|
+
return { data: null, error: sessionError }
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
const { data, error } = await _request(
|
|
1148
|
+
this.fetch,
|
|
1149
|
+
'POST',
|
|
1150
|
+
`${this.url}/factors/${params.factorId}/verify`,
|
|
1151
|
+
{
|
|
1152
|
+
body: { code: params.code, challenge_id: params.challengeId },
|
|
1153
|
+
headers: this.headers,
|
|
1154
|
+
jwt: sessionData?.session?.access_token,
|
|
1155
|
+
}
|
|
1156
|
+
)
|
|
1157
|
+
if (error) {
|
|
1158
|
+
return { data: null, error }
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
await this._saveSession({
|
|
1162
|
+
expires_at: Math.round(Date.now() / 1000) + data.expires_in,
|
|
1163
|
+
...data,
|
|
1164
|
+
})
|
|
1165
|
+
this._notifyAllSubscribers('MFA_CHALLENGE_VERIFIED', data)
|
|
1166
|
+
|
|
1167
|
+
return { data, error }
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* Creates a challenge which a user can verify against
|
|
1172
|
+
* @param factorID System assigned identifier for authenticator device as returned by enroll
|
|
1173
|
+
*/
|
|
1174
|
+
private async _challenge(params: MFAChallengeParams): Promise<AuthMFAChallengeResponse> {
|
|
1175
|
+
const { data: sessionData, error: sessionError } = await this.getSession()
|
|
1176
|
+
if (sessionError) {
|
|
1177
|
+
return { data: null, error: sessionError }
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
return await _request(this.fetch, 'POST', `${this.url}/factors/${params.factorId}/challenge`, {
|
|
1181
|
+
headers: this.headers,
|
|
1182
|
+
jwt: sessionData?.session?.access_token,
|
|
1183
|
+
})
|
|
1184
|
+
}
|
|
1185
|
+
private async _challengeAndVerify(
|
|
1186
|
+
params: MFAChallengeAndVerifyParams
|
|
1187
|
+
): Promise<AuthMFAVerifyResponse> {
|
|
1188
|
+
const { data: challengeData, error: challengeError } = await this._challenge({
|
|
1189
|
+
factorId: params.factorId,
|
|
1190
|
+
})
|
|
1191
|
+
if (challengeError) {
|
|
1192
|
+
return { data: null, error: challengeError }
|
|
1193
|
+
}
|
|
1194
|
+
return await this._verify({
|
|
1195
|
+
factorId: params.factorId,
|
|
1196
|
+
challengeId: challengeData.id,
|
|
1197
|
+
code: params.code,
|
|
1198
|
+
})
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* Displays all devices for a given user
|
|
1203
|
+
*/
|
|
1204
|
+
private async _listFactors(): Promise<AuthMFAListFactorsResponse> {
|
|
1205
|
+
const {
|
|
1206
|
+
data: { user },
|
|
1207
|
+
error: userError,
|
|
1208
|
+
} = await this.getUser()
|
|
1209
|
+
if (userError) {
|
|
1210
|
+
return { data: null, error: userError }
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const factors = user?.factors || []
|
|
1214
|
+
const totp = factors.filter(
|
|
1215
|
+
(factor) => factor.factor_type === 'totp' && factor.status === 'verified'
|
|
1216
|
+
)
|
|
1217
|
+
|
|
1218
|
+
return {
|
|
1219
|
+
data: {
|
|
1220
|
+
all: factors,
|
|
1221
|
+
totp,
|
|
1222
|
+
},
|
|
1223
|
+
error: null,
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
private async _getAuthenticatorAssuranceLevel(): Promise<AuthMFAGetAuthenticatorAssuranceLevelResponse> {
|
|
1228
|
+
const {
|
|
1229
|
+
data: { session },
|
|
1230
|
+
error: sessionError,
|
|
1231
|
+
} = await this.getSession()
|
|
1232
|
+
if (sessionError) {
|
|
1233
|
+
return { data: null, error: sessionError }
|
|
1234
|
+
}
|
|
1235
|
+
if (!session) {
|
|
1236
|
+
return {
|
|
1237
|
+
data: { currentLevel: null, nextLevel: null, currentAuthenticationMethods: [] },
|
|
1238
|
+
error: null,
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
const payload = this._decodeJWT(session.access_token)
|
|
1243
|
+
|
|
1244
|
+
let currentLevel: AuthenticatorAssuranceLevels | null = null
|
|
1245
|
+
|
|
1246
|
+
if (payload.aal) {
|
|
1247
|
+
currentLevel = payload.aal
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
let nextLevel: AuthenticatorAssuranceLevels | null = currentLevel
|
|
1251
|
+
|
|
1252
|
+
const verifiedFactors =
|
|
1253
|
+
session.user.factors?.filter((factor: Factor) => factor.status === 'verified') ?? []
|
|
1254
|
+
|
|
1255
|
+
if (verifiedFactors.length > 0) {
|
|
1256
|
+
nextLevel = 'aal2'
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
const currentAuthenticationMethods = payload.amr || []
|
|
1260
|
+
|
|
1261
|
+
return { data: { currentLevel, nextLevel, currentAuthenticationMethods }, error: null }
|
|
1262
|
+
}
|
|
1008
1263
|
}
|
package/src/index.ts
CHANGED
package/src/lib/helpers.ts
CHANGED
|
@@ -120,3 +120,14 @@ export class Deferred<T = any> {
|
|
|
120
120
|
})
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
+
// Taken from: https://stackoverflow.com/questions/38552003/how-to-decode-jwt-token-in-javascript-without-using-a-library
|
|
124
|
+
export function decodeJWTPayload(token: string) {
|
|
125
|
+
const parts = token.split('.')
|
|
126
|
+
|
|
127
|
+
if (parts.length !== 3) {
|
|
128
|
+
throw new Error('JWT is not valid: not a JWT structure')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const base64Url = parts[1]
|
|
132
|
+
return JSON.parse(decodeBase64URL(base64Url))
|
|
133
|
+
}
|