@cloudbase/auth 3.0.0 → 3.0.2
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/dist/cjs/adapter.js +19 -16
- package/dist/cjs/index.d.ts +107 -33
- package/dist/cjs/index.js +1529 -428
- package/dist/cjs/type.d.ts +242 -0
- package/dist/cjs/type.js +3 -0
- package/dist/cjs/utils.d.ts +5 -0
- package/dist/cjs/utils.js +59 -0
- package/dist/esm/adapter.js +19 -16
- package/dist/esm/index.d.ts +107 -33
- package/dist/esm/index.js +1527 -426
- package/dist/esm/type.d.ts +242 -0
- package/dist/esm/type.js +2 -0
- package/dist/esm/utils.d.ts +5 -0
- package/dist/esm/utils.js +51 -0
- package/dist/miniprogram/index.js +1 -1
- package/package.json +6 -6
- package/src/adapter.ts +13 -8
- package/src/index.ts +1606 -363
- package/src/type.ts +306 -0
- package/src/utils.ts +69 -0
- package/tsconfig.json +1 -1
package/src/index.ts
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import type { ICloudbase, ICloudbaseConfig, ICloudbasePlatformInfo } from '@cloudbase/types'
|
|
2
2
|
import type { ICloudbaseCache } from '@cloudbase/types/cache'
|
|
3
3
|
import type { ICloudbaseRequest } from '@cloudbase/types/request'
|
|
4
|
-
import type { ICloudbaseAuthConfig, IUser,
|
|
4
|
+
import type { ICloudbaseAuthConfig, IUser, ILoginState } from '@cloudbase/types/auth'
|
|
5
5
|
import type { ICloudbaseComponent } from '@cloudbase/types/component'
|
|
6
|
-
import type {
|
|
7
|
-
import
|
|
8
|
-
|
|
6
|
+
import type { AuthOptions, Credentials } from '@cloudbase/oauth'
|
|
7
|
+
import {
|
|
8
|
+
CloudbaseOAuth,
|
|
9
|
+
AUTH_API_PREFIX,
|
|
10
|
+
LOGIN_STATE_CHANGED_TYPE,
|
|
11
|
+
EVENTS,
|
|
12
|
+
AUTH_STATE_CHANGED_TYPE,
|
|
13
|
+
OAUTH_TYPE,
|
|
14
|
+
weAppJwtDecodeAll,
|
|
15
|
+
AuthError,
|
|
16
|
+
authModels,
|
|
17
|
+
} from '@cloudbase/oauth'
|
|
9
18
|
import { useAuthAdapter } from './adapter'
|
|
10
19
|
import {
|
|
11
20
|
printWarn,
|
|
@@ -18,23 +27,77 @@ import {
|
|
|
18
27
|
adapterForWxMp,
|
|
19
28
|
useDefaultAdapter,
|
|
20
29
|
} from './utilities'
|
|
30
|
+
import { saveToBrowserSession, getBrowserSession, removeBrowserSession, addUrlSearch } from './utils'
|
|
31
|
+
import { utils } from '@cloudbase/utilities'
|
|
32
|
+
import {
|
|
33
|
+
CommonRes,
|
|
34
|
+
DeleteMeReq,
|
|
35
|
+
GetClaimsRes,
|
|
36
|
+
GetUserIdentitiesRes,
|
|
37
|
+
GetUserRes,
|
|
38
|
+
LinkIdentityReq,
|
|
39
|
+
LinkIdentityRes,
|
|
40
|
+
OnAuthStateChangeCallback,
|
|
41
|
+
ReauthenticateRes,
|
|
42
|
+
ResendReq,
|
|
43
|
+
ResendRes,
|
|
44
|
+
ResetPasswordForEmailRes,
|
|
45
|
+
ResetPasswordForOldReq,
|
|
46
|
+
SetSessionReq,
|
|
47
|
+
SignInAnonymouslyReq,
|
|
48
|
+
SignInOAuthRes,
|
|
49
|
+
SignInRes,
|
|
50
|
+
SignInWithIdTokenReq,
|
|
51
|
+
SignInWithOAuthReq,
|
|
52
|
+
SignInWithOtpReq,
|
|
53
|
+
SignInWithOtpRes,
|
|
54
|
+
SignInWithPasswordReq,
|
|
55
|
+
SignUpRes,
|
|
56
|
+
UnlinkIdentityReq,
|
|
57
|
+
UpdateUserAttributes,
|
|
58
|
+
UpdateUserReq,
|
|
59
|
+
UpdateUserWithVerificationRes,
|
|
60
|
+
VerifyOAuthReq,
|
|
61
|
+
VerifyOtpReq,
|
|
62
|
+
} from './type'
|
|
63
|
+
|
|
64
|
+
const isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined'
|
|
65
|
+
|
|
66
|
+
export type {
|
|
67
|
+
SignInRes,
|
|
68
|
+
GetUserRes,
|
|
69
|
+
CommonRes,
|
|
70
|
+
SignInWithOtpRes,
|
|
71
|
+
SignInOAuthRes,
|
|
72
|
+
GetClaimsRes,
|
|
73
|
+
ResetPasswordForEmailRes,
|
|
74
|
+
GetUserIdentitiesRes,
|
|
75
|
+
LinkIdentityRes,
|
|
76
|
+
ReauthenticateRes,
|
|
77
|
+
ResendRes,
|
|
78
|
+
UpdateUserWithVerificationRes,
|
|
79
|
+
OnAuthStateChangeCallback,
|
|
80
|
+
SignInWithPasswordReq,
|
|
81
|
+
SignInWithIdTokenReq,
|
|
82
|
+
SignInWithOAuthReq,
|
|
83
|
+
VerifyOAuthReq,
|
|
84
|
+
VerifyOtpReq,
|
|
85
|
+
LinkIdentityReq,
|
|
86
|
+
UnlinkIdentityReq,
|
|
87
|
+
UpdateUserReq,
|
|
88
|
+
SignInWithOtpReq,
|
|
89
|
+
ResetPasswordForOldReq,
|
|
90
|
+
ResendReq,
|
|
91
|
+
SetSessionReq,
|
|
92
|
+
DeleteMeReq,
|
|
93
|
+
SignUpRes,
|
|
94
|
+
} from './type'
|
|
21
95
|
|
|
22
96
|
declare const cloudbase: ICloudbase
|
|
23
97
|
declare const wx: any
|
|
24
98
|
|
|
25
99
|
const COMPONENT_NAME = 'auth'
|
|
26
100
|
|
|
27
|
-
const LOGIN_STATE_CHANGED_TYPE = {
|
|
28
|
-
SIGN_OUT: 'sign_out',
|
|
29
|
-
SIGN_IN: 'sign_in',
|
|
30
|
-
CREDENTIALS_ERROR: 'credentials_error',
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const EVENTS = {
|
|
34
|
-
// 登录态改变后触发
|
|
35
|
-
LOGIN_STATE_CHANGED: 'loginStateChanged',
|
|
36
|
-
}
|
|
37
|
-
|
|
38
101
|
interface UserInfo {
|
|
39
102
|
uid?: string
|
|
40
103
|
gender?: string
|
|
@@ -51,9 +114,7 @@ interface UserInfo {
|
|
|
51
114
|
created_from?: string
|
|
52
115
|
}
|
|
53
116
|
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
const onCredentialsError = (params) => {
|
|
117
|
+
const onCredentialsError = eventBus => (params) => {
|
|
57
118
|
eventBus.fire(EVENTS.LOGIN_STATE_CHANGED, { ...params, eventType: LOGIN_STATE_CHANGED_TYPE.CREDENTIALS_ERROR })
|
|
58
119
|
}
|
|
59
120
|
|
|
@@ -62,14 +123,14 @@ interface IUserOptions {
|
|
|
62
123
|
oauthInstance: CloudbaseOAuth
|
|
63
124
|
}
|
|
64
125
|
|
|
65
|
-
class User implements IUser {
|
|
126
|
+
export class User implements IUser {
|
|
66
127
|
public uid?: string
|
|
67
128
|
public gender?: string
|
|
68
129
|
public picture?: string
|
|
69
130
|
public email?: string
|
|
70
131
|
public emailVerified?: boolean
|
|
71
132
|
public phoneNumber?: string
|
|
72
|
-
public username?: string
|
|
133
|
+
public username?: string // 用户名称,长度 5-24 位,支持字符中英文、数字、特殊字符(仅支持_-),不支持中文
|
|
73
134
|
public name?: string
|
|
74
135
|
public providers?: {
|
|
75
136
|
id?: string
|
|
@@ -153,7 +214,7 @@ class User implements IUser {
|
|
|
153
214
|
|
|
154
215
|
public async updateUserBasicInfo(params: authModels.ModifyUserBasicInfoRequest): Promise<void> {
|
|
155
216
|
await this.oauthInstance.authApi.updateUserBasicInfo({ ...params })
|
|
156
|
-
this.
|
|
217
|
+
await this.refresh()
|
|
157
218
|
}
|
|
158
219
|
|
|
159
220
|
/**
|
|
@@ -211,18 +272,31 @@ class User implements IUser {
|
|
|
211
272
|
`如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
|
|
212
273
|
],
|
|
213
274
|
})
|
|
214
|
-
public async refresh(params?: { version?: string; query?: any }): Promise<
|
|
275
|
+
public async refresh(params?: { version?: string; query?: any }): Promise<authModels.UserInfo> {
|
|
215
276
|
const newUserInfo = await this.oauthInstance.authApi.getUserInfo(params)
|
|
277
|
+
if ((newUserInfo as any).code === 'INVALID_ACCESS_TOKEN') {
|
|
278
|
+
this.setLocalUserInfo({})
|
|
279
|
+
throw newUserInfo
|
|
280
|
+
}
|
|
216
281
|
this.setLocalUserInfo(newUserInfo)
|
|
217
282
|
return newUserInfo
|
|
218
283
|
}
|
|
219
284
|
|
|
220
|
-
|
|
285
|
+
public getLocalUserInfo(key?: string): string | boolean | Record<string, any> {
|
|
221
286
|
const { userInfoKey } = this.cache.keys
|
|
222
287
|
const userInfo = this.cache.getStore(userInfoKey)
|
|
288
|
+
|
|
289
|
+
if (!key) return userInfo || {}
|
|
290
|
+
|
|
223
291
|
return userInfo[key]
|
|
224
292
|
}
|
|
225
293
|
|
|
294
|
+
public setLocalUserInfo(userInfo: any) {
|
|
295
|
+
const { userInfoKey } = this.cache.keys
|
|
296
|
+
this.cache.setStore(userInfoKey, userInfo)
|
|
297
|
+
this.setUserInfo()
|
|
298
|
+
}
|
|
299
|
+
|
|
226
300
|
private async getLocalUserInfoAsync(key: string): Promise<string> {
|
|
227
301
|
const { userInfoKey } = this.cache.keys
|
|
228
302
|
const userInfo = await this.cache.getStoreAsync(userInfoKey)
|
|
@@ -247,16 +321,11 @@ class User implements IUser {
|
|
|
247
321
|
'created_from',
|
|
248
322
|
'providers',
|
|
249
323
|
'username',
|
|
324
|
+
'created_at',
|
|
250
325
|
].forEach((infoKey) => {
|
|
251
326
|
this[infoKey] = userInfo[infoKey]
|
|
252
327
|
})
|
|
253
328
|
}
|
|
254
|
-
|
|
255
|
-
private setLocalUserInfo(userInfo: any) {
|
|
256
|
-
const { userInfoKey } = this.cache.keys
|
|
257
|
-
this.cache.setStore(userInfoKey, userInfo)
|
|
258
|
-
this.setUserInfo()
|
|
259
|
-
}
|
|
260
329
|
}
|
|
261
330
|
interface ILoginStateOptions extends IUserOptions {
|
|
262
331
|
envId: string
|
|
@@ -294,19 +363,22 @@ export class LoginState implements ILoginState {
|
|
|
294
363
|
}
|
|
295
364
|
|
|
296
365
|
class Auth {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
private
|
|
301
|
-
|
|
302
|
-
|
|
366
|
+
readonly config: ICloudbaseAuthConfig
|
|
367
|
+
oauthInstance: CloudbaseOAuth
|
|
368
|
+
readonly cache: ICloudbaseCache
|
|
369
|
+
private listeners: Map<string, Set<OnAuthStateChangeCallback>> = new Map()
|
|
370
|
+
private hasListenerSetUp = false
|
|
371
|
+
|
|
372
|
+
constructor(config: ICloudbaseAuthConfig & {
|
|
373
|
+
cache: ICloudbaseCache
|
|
374
|
+
request?: ICloudbaseRequest
|
|
375
|
+
runtime?: string
|
|
376
|
+
},) {
|
|
303
377
|
this.config = config
|
|
304
|
-
this.cache = config.cache
|
|
305
378
|
this.oauthInstance = config.oauthInstance
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
379
|
+
this.cache = config.cache
|
|
380
|
+
this.init()
|
|
381
|
+
this.setAccessKey()
|
|
310
382
|
}
|
|
311
383
|
|
|
312
384
|
/**
|
|
@@ -412,7 +484,7 @@ class Auth {
|
|
|
412
484
|
// async storage的平台调用此API提示
|
|
413
485
|
printWarn(
|
|
414
486
|
ERRORS.INVALID_OPERATION,
|
|
415
|
-
'current platform\'s storage is asynchronous, please use getCurrentUser
|
|
487
|
+
'current platform\'s storage is asynchronous, please use getCurrentUser instead',
|
|
416
488
|
)
|
|
417
489
|
return
|
|
418
490
|
}
|
|
@@ -436,35 +508,36 @@ class Auth {
|
|
|
436
508
|
`如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
|
|
437
509
|
],
|
|
438
510
|
})
|
|
439
|
-
public async getCurrentUser() {
|
|
511
|
+
public async getCurrentUser(): Promise<(authModels.UserInfo & Partial<User>) | null> {
|
|
440
512
|
const loginState = await this.getLoginState()
|
|
441
513
|
if (loginState) {
|
|
514
|
+
const userInfo = loginState.user.getLocalUserInfo() as authModels.UserInfo
|
|
442
515
|
await loginState.user.checkLocalInfoAsync()
|
|
443
|
-
return loginState.user
|
|
516
|
+
return { ...loginState.user, ...userInfo } as unknown as authModels.UserInfo & Partial<User>
|
|
444
517
|
}
|
|
445
518
|
return null
|
|
446
519
|
}
|
|
447
520
|
|
|
448
|
-
/**
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
@catchErrorsDecorator({
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
})
|
|
462
|
-
public async signInAnonymously(data: {
|
|
463
|
-
|
|
464
|
-
} = {},): Promise<LoginState> {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
521
|
+
// /**
|
|
522
|
+
// * 匿名登录
|
|
523
|
+
// * @returns {Promise<LoginState>}
|
|
524
|
+
// * @memberof Auth
|
|
525
|
+
// */
|
|
526
|
+
// @catchErrorsDecorator({
|
|
527
|
+
// title: '匿名登录失败',
|
|
528
|
+
// messages: [
|
|
529
|
+
// '请确认以下各项:',
|
|
530
|
+
// ' 1 - 当前环境是否开启了匿名登录',
|
|
531
|
+
// ' 2 - 调用 auth().signInAnonymously() 的语法或参数是否正确',
|
|
532
|
+
// `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
|
|
533
|
+
// ],
|
|
534
|
+
// })
|
|
535
|
+
// public async signInAnonymously(data: {
|
|
536
|
+
// provider_token?: string
|
|
537
|
+
// } = {},): Promise<LoginState> {
|
|
538
|
+
// await this.oauthInstance.authApi.signInAnonymously(data)
|
|
539
|
+
// return this.createLoginState()
|
|
540
|
+
// }
|
|
468
541
|
|
|
469
542
|
/**
|
|
470
543
|
* 匿名登录
|
|
@@ -491,7 +564,7 @@ class Auth {
|
|
|
491
564
|
const wxInfo = wx.getAccountInfoSync().miniProgram
|
|
492
565
|
|
|
493
566
|
const mainFunc = async (code) => {
|
|
494
|
-
let result: GrantProviderTokenResponse | undefined = undefined
|
|
567
|
+
let result: authModels.GrantProviderTokenResponse | undefined = undefined
|
|
495
568
|
let credentials: Credentials | undefined = undefined
|
|
496
569
|
|
|
497
570
|
try {
|
|
@@ -566,7 +639,7 @@ class Auth {
|
|
|
566
639
|
const wxInfo = wx.getAccountInfoSync().miniProgram
|
|
567
640
|
|
|
568
641
|
const mainFunc = async (code) => {
|
|
569
|
-
let result: GrantProviderTokenResponse | undefined = undefined
|
|
642
|
+
let result: authModels.GrantProviderTokenResponse | undefined = undefined
|
|
570
643
|
|
|
571
644
|
try {
|
|
572
645
|
result = await this.oauthInstance.authApi.grantProviderToken({
|
|
@@ -609,82 +682,6 @@ class Auth {
|
|
|
609
682
|
return
|
|
610
683
|
}
|
|
611
684
|
|
|
612
|
-
/**
|
|
613
|
-
* 小程序openId静默登录
|
|
614
|
-
* @returns {Promise<LoginState>}
|
|
615
|
-
* @memberof Auth
|
|
616
|
-
*/
|
|
617
|
-
@catchErrorsDecorator({
|
|
618
|
-
title: '小程序openId静默登录失败',
|
|
619
|
-
messages: [
|
|
620
|
-
'请确认以下各项:',
|
|
621
|
-
' 1 - 当前环境是否开启了小程序openId静默登录',
|
|
622
|
-
' 2 - 调用 auth().signInWithOpenId() 的语法或参数是否正确',
|
|
623
|
-
`如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
|
|
624
|
-
],
|
|
625
|
-
})
|
|
626
|
-
public async signInWithOpenId({ useWxCloud = true } = {}): Promise<LoginState> {
|
|
627
|
-
if (!adapterForWxMp.isMatch()) {
|
|
628
|
-
throw Error('wx api undefined')
|
|
629
|
-
}
|
|
630
|
-
const wxInfo = wx.getAccountInfoSync().miniProgram
|
|
631
|
-
|
|
632
|
-
const mainFunc = async (code) => {
|
|
633
|
-
let result: GrantProviderTokenResponse | undefined = undefined
|
|
634
|
-
let credentials: Credentials | undefined = undefined
|
|
635
|
-
|
|
636
|
-
try {
|
|
637
|
-
result = await this.oauthInstance.authApi.grantProviderToken(
|
|
638
|
-
{
|
|
639
|
-
provider_id: wxInfo?.appId,
|
|
640
|
-
provider_code: code,
|
|
641
|
-
provider_params: {
|
|
642
|
-
provider_code_type: 'open_id',
|
|
643
|
-
appid: wxInfo?.appId,
|
|
644
|
-
},
|
|
645
|
-
},
|
|
646
|
-
useWxCloud,
|
|
647
|
-
)
|
|
648
|
-
|
|
649
|
-
if ((result as any)?.error_code || !result.provider_token) {
|
|
650
|
-
throw result
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
credentials = await this.oauthInstance.authApi.signInWithProvider(
|
|
654
|
-
{ provider_token: result.provider_token },
|
|
655
|
-
useWxCloud,
|
|
656
|
-
)
|
|
657
|
-
|
|
658
|
-
if ((credentials as any)?.error_code) {
|
|
659
|
-
throw credentials
|
|
660
|
-
}
|
|
661
|
-
} catch (error) {
|
|
662
|
-
throw error
|
|
663
|
-
}
|
|
664
|
-
await this.oauthInstance.oauth2client.setCredentials(credentials as Credentials)
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
await new Promise((resolve, reject) => {
|
|
668
|
-
wx.login({
|
|
669
|
-
success: async (res: { code: string }) => {
|
|
670
|
-
try {
|
|
671
|
-
await mainFunc(res.code)
|
|
672
|
-
resolve(true)
|
|
673
|
-
} catch (error) {
|
|
674
|
-
reject(error)
|
|
675
|
-
}
|
|
676
|
-
},
|
|
677
|
-
fail: (res: any) => {
|
|
678
|
-
const error = new Error(res?.errMsg)
|
|
679
|
-
;(error as any).code = res?.errno
|
|
680
|
-
reject(error)
|
|
681
|
-
},
|
|
682
|
-
})
|
|
683
|
-
})
|
|
684
|
-
|
|
685
|
-
return this.createLoginState()
|
|
686
|
-
}
|
|
687
|
-
|
|
688
685
|
/**
|
|
689
686
|
* 小程序unionId静默登录
|
|
690
687
|
* @returns {Promise<LoginState>}
|
|
@@ -755,63 +752,63 @@ class Auth {
|
|
|
755
752
|
}
|
|
756
753
|
|
|
757
754
|
/**
|
|
758
|
-
*
|
|
755
|
+
* 小程序手机号授权登录,目前只支持全托管手机号授权登录
|
|
759
756
|
* @returns {Promise<LoginState>}
|
|
760
757
|
* @memberof Auth
|
|
761
758
|
*/
|
|
762
|
-
@catchErrorsDecorator({
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
})
|
|
771
|
-
public async signInWithPhoneAuth({ phoneCode = '' }): Promise<LoginState> {
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
}
|
|
759
|
+
// @catchErrorsDecorator({
|
|
760
|
+
// title: '小程序手机号授权登录失败',
|
|
761
|
+
// messages: [
|
|
762
|
+
// '请确认以下各项:',
|
|
763
|
+
// ' 1 - 当前环境是否开启了小程序手机号授权登录',
|
|
764
|
+
// ' 2 - 调用 auth().signInWithPhoneAuth() 的语法或参数是否正确',
|
|
765
|
+
// `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
|
|
766
|
+
// ],
|
|
767
|
+
// })
|
|
768
|
+
// public async signInWithPhoneAuth({ phoneCode = '' }): Promise<LoginState> {
|
|
769
|
+
// if (!adapterForWxMp.isMatch()) {
|
|
770
|
+
// throw Error('wx api undefined')
|
|
771
|
+
// }
|
|
772
|
+
// const wxInfo = wx.getAccountInfoSync().miniProgram
|
|
773
|
+
// const providerInfo = {
|
|
774
|
+
// provider_params: { provider_code_type: 'phone' },
|
|
775
|
+
// provider_id: wxInfo.appId,
|
|
776
|
+
// }
|
|
777
|
+
|
|
778
|
+
// const { code } = await wx.login()
|
|
779
|
+
// ;(providerInfo as any).provider_code = code
|
|
780
|
+
|
|
781
|
+
// try {
|
|
782
|
+
// let providerToken = await this.oauthInstance.authApi.grantProviderToken(providerInfo)
|
|
783
|
+
// if (providerToken.error_code) {
|
|
784
|
+
// throw providerToken
|
|
785
|
+
// }
|
|
786
|
+
|
|
787
|
+
// providerToken = await this.oauthInstance.authApi.patchProviderToken({
|
|
788
|
+
// provider_token: providerToken.provider_token,
|
|
789
|
+
// provider_id: wxInfo.appId,
|
|
790
|
+
// provider_params: {
|
|
791
|
+
// code: phoneCode,
|
|
792
|
+
// provider_code_type: 'phone',
|
|
793
|
+
// },
|
|
794
|
+
// })
|
|
795
|
+
// if (providerToken.error_code) {
|
|
796
|
+
// throw providerToken
|
|
797
|
+
// }
|
|
798
|
+
|
|
799
|
+
// const signInRes = await this.oauthInstance.authApi.signInWithProvider({
|
|
800
|
+
// provider_token: providerToken.provider_token,
|
|
801
|
+
// })
|
|
802
|
+
|
|
803
|
+
// if ((signInRes as any)?.error_code) {
|
|
804
|
+
// throw signInRes
|
|
805
|
+
// }
|
|
806
|
+
// } catch (error) {
|
|
807
|
+
// throw error
|
|
808
|
+
// }
|
|
809
|
+
|
|
810
|
+
// return this.createLoginState()
|
|
811
|
+
// }
|
|
815
812
|
|
|
816
813
|
/**
|
|
817
814
|
* 小程序短信验证码登陆
|
|
@@ -893,21 +890,21 @@ class Auth {
|
|
|
893
890
|
* @returns {Promise<LoginState>}
|
|
894
891
|
* @memberof Auth
|
|
895
892
|
*/
|
|
896
|
-
@catchErrorsDecorator({
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
})
|
|
907
|
-
public async signInWithCustomTicket(): Promise<LoginState> {
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
}
|
|
893
|
+
// @catchErrorsDecorator({
|
|
894
|
+
// title: '自定义登录失败',
|
|
895
|
+
// messages: [
|
|
896
|
+
// '请确认以下各项:',
|
|
897
|
+
// ' 1 - 当前环境是否开启了自定义登录',
|
|
898
|
+
// ' 2 - 调用 auth().signInWithCustomTicket() 的语法或参数是否正确',
|
|
899
|
+
// ' 3 - ticket 是否归属于当前环境',
|
|
900
|
+
// ' 4 - 创建 ticket 的自定义登录私钥是否过期',
|
|
901
|
+
// `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
|
|
902
|
+
// ],
|
|
903
|
+
// })
|
|
904
|
+
// public async signInWithCustomTicket(): Promise<LoginState> {
|
|
905
|
+
// await this.oauthInstance.authApi.signInWithCustomTicket()
|
|
906
|
+
// return this.createLoginState()
|
|
907
|
+
// }
|
|
911
908
|
|
|
912
909
|
/**
|
|
913
910
|
*
|
|
@@ -920,25 +917,25 @@ class Auth {
|
|
|
920
917
|
return this.createLoginState(params)
|
|
921
918
|
}
|
|
922
919
|
|
|
923
|
-
/**
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
@catchErrorsDecorator({
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
})
|
|
938
|
-
public async signUp(params: authModels.SignUpRequest): Promise<
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
}
|
|
920
|
+
// /**
|
|
921
|
+
// *
|
|
922
|
+
// * @param {authModels.SignUpRequest} params
|
|
923
|
+
// * @returns {Promise<LoginState>}
|
|
924
|
+
// * @memberof Auth
|
|
925
|
+
// */
|
|
926
|
+
// @catchErrorsDecorator({
|
|
927
|
+
// title: '注册失败',
|
|
928
|
+
// messages: [
|
|
929
|
+
// '请确认以下各项:',
|
|
930
|
+
// ' 1 - 当前环境是否开启了指定登录方式',
|
|
931
|
+
// ' 2 - 调用 auth().signUp() 的语法或参数是否正确',
|
|
932
|
+
// `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
|
|
933
|
+
// ],
|
|
934
|
+
// })
|
|
935
|
+
// public async signUp(params: authModels.SignUpRequest): Promise<any> {
|
|
936
|
+
// await this.oauthInstance.authApi.signUp(params)
|
|
937
|
+
// return this.createLoginState()
|
|
938
|
+
// }
|
|
942
939
|
|
|
943
940
|
/**
|
|
944
941
|
* 设置密码
|
|
@@ -971,26 +968,6 @@ class Auth {
|
|
|
971
968
|
return exist
|
|
972
969
|
}
|
|
973
970
|
|
|
974
|
-
/**
|
|
975
|
-
* 登出
|
|
976
|
-
*/
|
|
977
|
-
@catchErrorsDecorator({
|
|
978
|
-
title: '用户登出失败',
|
|
979
|
-
messages: [
|
|
980
|
-
'请确认以下各项:',
|
|
981
|
-
' 1 - 调用 auth().signOut() 的语法或参数是否正确',
|
|
982
|
-
' 2 - 当前用户是否为匿名登录(匿名登录不支持signOut)',
|
|
983
|
-
`如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
|
|
984
|
-
],
|
|
985
|
-
})
|
|
986
|
-
public async signOut(params?: authModels.SignoutRequest): Promise<void | authModels.SignoutReponse> {
|
|
987
|
-
const { userInfoKey } = this.cache.keys
|
|
988
|
-
const res = await this.oauthInstance.authApi.signOut(params)
|
|
989
|
-
await this.cache.removeStoreAsync(userInfoKey)
|
|
990
|
-
eventBus.fire(EVENTS.LOGIN_STATE_CHANGED, { eventType: LOGIN_STATE_CHANGED_TYPE.SIGN_OUT })
|
|
991
|
-
return res
|
|
992
|
-
}
|
|
993
|
-
|
|
994
971
|
/**
|
|
995
972
|
* 获取本地登录态-同步
|
|
996
973
|
*/
|
|
@@ -999,7 +976,7 @@ class Auth {
|
|
|
999
976
|
// async storage的平台调用此API提示
|
|
1000
977
|
printWarn(
|
|
1001
978
|
ERRORS.INVALID_OPERATION,
|
|
1002
|
-
'current platform\'s storage is asynchronous, please use getLoginState
|
|
979
|
+
'current platform\'s storage is asynchronous, please use getLoginState instead',
|
|
1003
980
|
)
|
|
1004
981
|
return
|
|
1005
982
|
}
|
|
@@ -1058,8 +1035,8 @@ class Auth {
|
|
|
1058
1035
|
`如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
|
|
1059
1036
|
],
|
|
1060
1037
|
})
|
|
1061
|
-
public async getUserInfo(
|
|
1062
|
-
return this.
|
|
1038
|
+
public async getUserInfo(): Promise<(authModels.UserInfo & Partial<User>) | null> {
|
|
1039
|
+
return this.getCurrentUser()
|
|
1063
1040
|
}
|
|
1064
1041
|
|
|
1065
1042
|
@catchErrorsDecorator({
|
|
@@ -1185,7 +1162,7 @@ class Auth {
|
|
|
1185
1162
|
}
|
|
1186
1163
|
|
|
1187
1164
|
public async onLoginStateChanged(callback: Function) {
|
|
1188
|
-
eventBus
|
|
1165
|
+
this.config.eventBus?.on(EVENTS.LOGIN_STATE_CHANGED, async (params) => {
|
|
1189
1166
|
// getLoginState会重复触发getCredentials,导致死循环,所以getCredentials出错不再出发getLoginState
|
|
1190
1167
|
const loginState = params?.data?.eventType !== LOGIN_STATE_CHANGED_TYPE.CREDENTIALS_ERROR ? await this.getLoginState() : {}
|
|
1191
1168
|
callback.call(this, { ...params, ...loginState })
|
|
@@ -1226,7 +1203,7 @@ class Auth {
|
|
|
1226
1203
|
`如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
|
|
1227
1204
|
],
|
|
1228
1205
|
})
|
|
1229
|
-
public async getProviderSubType(): Promise<ProviderSubType> {
|
|
1206
|
+
public async getProviderSubType(): Promise<authModels.ProviderSubType> {
|
|
1230
1207
|
return this.oauthInstance.authApi.getProviderSubType()
|
|
1231
1208
|
}
|
|
1232
1209
|
|
|
@@ -1258,55 +1235,21 @@ class Auth {
|
|
|
1258
1235
|
return this.oauthInstance.authApi.getUserBehaviorLog(params)
|
|
1259
1236
|
}
|
|
1260
1237
|
|
|
1261
|
-
/**
|
|
1262
|
-
* 跳转系统默认登录页
|
|
1263
|
-
* @returns {Promise<authModels.ToDefaultLoginPage>}
|
|
1264
|
-
* @memberof Auth
|
|
1265
|
-
*/
|
|
1266
|
-
public async toDefaultLoginPage(params: authModels.ToDefaultLoginPage = {}): Promise<void> {
|
|
1267
|
-
const configVersion = params.config_version || 'env'
|
|
1268
|
-
|
|
1269
|
-
if (adapterForWxMp.isMatch()) {
|
|
1270
|
-
wx.navigateTo({ url: '/packages/$wd_system/pages/login/index' })
|
|
1271
|
-
} else {
|
|
1272
|
-
const redirectUri = params.redirect_uri || window.location.href
|
|
1273
|
-
const urlObj = new URL(redirectUri)
|
|
1274
|
-
const loginPage = `${urlObj.origin}/__auth/?app_id=${params.app_id || ''}&env_id=${this.config.env}&client_id=${
|
|
1275
|
-
this.config.clientId
|
|
1276
|
-
}&config_version=${configVersion}&redirect_uri=${encodeURIComponent(redirectUri)}`
|
|
1277
|
-
window.location.href = loginPage
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
private async createLoginState(
|
|
1282
|
-
params?: { version?: string; query?: any },
|
|
1283
|
-
options?: { asyncRefreshUser?: boolean },
|
|
1284
|
-
): Promise<LoginState> {
|
|
1285
|
-
const loginState = new LoginState({
|
|
1286
|
-
envId: this.config.env,
|
|
1287
|
-
cache: this.cache,
|
|
1288
|
-
oauthInstance: this.oauthInstance,
|
|
1289
|
-
})
|
|
1290
|
-
|
|
1291
|
-
await loginState.checkLocalStateAsync()
|
|
1292
|
-
if (options?.asyncRefreshUser) {
|
|
1293
|
-
loginState.user.refresh(params)
|
|
1294
|
-
} else {
|
|
1295
|
-
await loginState.user.refresh(params)
|
|
1296
|
-
}
|
|
1297
|
-
eventBus.fire(EVENTS.LOGIN_STATE_CHANGED, { eventType: LOGIN_STATE_CHANGED_TYPE.SIGN_IN })
|
|
1298
|
-
return loginState
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
1238
|
/**
|
|
1302
1239
|
* sms/email 验证码登录/注册,逻辑一致收敛
|
|
1303
1240
|
*/
|
|
1304
|
-
|
|
1241
|
+
public async signInWithUsername({
|
|
1305
1242
|
verificationInfo = { verification_id: '', is_user: false },
|
|
1306
1243
|
verificationCode = '',
|
|
1307
1244
|
username: rawUsername = '',
|
|
1308
1245
|
bindInfo = undefined,
|
|
1309
1246
|
loginType = '',
|
|
1247
|
+
}: {
|
|
1248
|
+
verificationInfo?: authModels.GetVerificationResponse
|
|
1249
|
+
verificationCode?: string
|
|
1250
|
+
username?: string
|
|
1251
|
+
bindInfo?: any
|
|
1252
|
+
loginType?: string
|
|
1310
1253
|
}): Promise<LoginState> {
|
|
1311
1254
|
try {
|
|
1312
1255
|
// 1. 验证验证码
|
|
@@ -1319,10 +1262,11 @@ class Auth {
|
|
|
1319
1262
|
throw verifyRes
|
|
1320
1263
|
}
|
|
1321
1264
|
|
|
1265
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
1322
1266
|
const { verification_token } = verifyRes
|
|
1323
1267
|
|
|
1324
1268
|
// 手机登录参数
|
|
1325
|
-
let username = `+86 ${rawUsername}`
|
|
1269
|
+
let username = /^\+\d{1,3}\s+/.test(rawUsername) ? rawUsername : `+86 ${rawUsername}`
|
|
1326
1270
|
let signUpParam: any = { phone_number: username }
|
|
1327
1271
|
|
|
1328
1272
|
// 邮箱登录参数
|
|
@@ -1370,37 +1314,1296 @@ class Auth {
|
|
|
1370
1314
|
throw error
|
|
1371
1315
|
}
|
|
1372
1316
|
}
|
|
1373
|
-
}
|
|
1374
1317
|
|
|
1375
|
-
|
|
1318
|
+
async createLoginState(
|
|
1319
|
+
params?: { version?: string; query?: any },
|
|
1320
|
+
options?: { asyncRefreshUser?: boolean; userInfo?: any },
|
|
1321
|
+
): Promise<LoginState> {
|
|
1322
|
+
const loginState = new LoginState({
|
|
1323
|
+
envId: this.config.env,
|
|
1324
|
+
cache: this.cache,
|
|
1325
|
+
oauthInstance: this.oauthInstance,
|
|
1326
|
+
})
|
|
1327
|
+
|
|
1328
|
+
await loginState.checkLocalStateAsync()
|
|
1376
1329
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
debug?: ICloudbaseAuthConfig['debug']
|
|
1387
|
-
},
|
|
1388
|
-
) {
|
|
1389
|
-
const { region = 'ap-shanghai', i18n, publishable_key } = config
|
|
1390
|
-
const platform = options?.platform || (useDefaultAdapter() as ICloudbasePlatformInfo)
|
|
1391
|
-
const { runtime, adapter } = platform
|
|
1330
|
+
if (options?.userInfo) {
|
|
1331
|
+
loginState.user.setLocalUserInfo(options.userInfo)
|
|
1332
|
+
} else {
|
|
1333
|
+
if (options?.asyncRefreshUser) {
|
|
1334
|
+
loginState.user.refresh(params)
|
|
1335
|
+
} else {
|
|
1336
|
+
await loginState.user.refresh(params)
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1392
1339
|
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1340
|
+
this.config.eventBus?.fire(EVENTS.LOGIN_STATE_CHANGED, { eventType: LOGIN_STATE_CHANGED_TYPE.SIGN_IN })
|
|
1341
|
+
|
|
1342
|
+
this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.SIGNED_IN })
|
|
1343
|
+
return loginState
|
|
1397
1344
|
}
|
|
1398
1345
|
|
|
1399
|
-
|
|
1400
|
-
|
|
1346
|
+
async setAccessKey() {
|
|
1347
|
+
if (this.config.accessKey) {
|
|
1348
|
+
try {
|
|
1349
|
+
this.oauthInstance.oauth2client.setAccessKeyCredentials({
|
|
1350
|
+
access_token: this.config.accessKey,
|
|
1351
|
+
token_type: 'Bearer',
|
|
1352
|
+
scope: 'accessKey',
|
|
1353
|
+
expires_at: new Date(+new Date() + +new Date()),
|
|
1354
|
+
expires_in: +new Date() + +new Date(),
|
|
1355
|
+
})
|
|
1356
|
+
} catch (error) {
|
|
1357
|
+
console.warn('accessKey error: ', error)
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// ========== new auth api methods merged below ==========
|
|
1363
|
+
|
|
1364
|
+
/**
|
|
1365
|
+
* https://supabase.com/docs/reference/javascript/auth-signinanonymously
|
|
1366
|
+
* Sign in a user anonymously.
|
|
1367
|
+
* const { data, error } = await auth.signInAnonymously();
|
|
1368
|
+
* @param params
|
|
1369
|
+
* @returns Promise<SignInRes>
|
|
1370
|
+
*/
|
|
1371
|
+
async signInAnonymously(params: SignInAnonymouslyReq): Promise<SignInRes> {
|
|
1372
|
+
try {
|
|
1373
|
+
await this.oauthInstance.authApi.signInAnonymously(params)
|
|
1374
|
+
const loginState = await this.createLoginState()
|
|
1375
|
+
|
|
1376
|
+
const { data: { session } = {} } = await this.getSession()
|
|
1377
|
+
|
|
1378
|
+
// loginState返回是为了兼容v2版本
|
|
1379
|
+
return { ...(loginState as any), data: { user: session.user, session }, error: null }
|
|
1380
|
+
} catch (error) {
|
|
1381
|
+
return { data: {}, error: new AuthError(error) }
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
/**
|
|
1386
|
+
* https://supabase.com/docs/reference/javascript/auth-signup
|
|
1387
|
+
* Sign up a new user with email or phone using a one-time password (OTP). If the account not exist, a new account will be created.
|
|
1388
|
+
* @param params
|
|
1389
|
+
* @returns Promise<SignUpRes>
|
|
1390
|
+
*/
|
|
1391
|
+
async signUp(params: authModels.SignUpRequest & { phone?: string }): Promise<SignUpRes> {
|
|
1392
|
+
if (params.phone_number || params.verification_code || params.verification_token || params.provider_token) {
|
|
1393
|
+
await this.oauthInstance.authApi.signUp(params)
|
|
1394
|
+
return this.createLoginState() as any
|
|
1395
|
+
}
|
|
1396
|
+
try {
|
|
1397
|
+
// 参数校验:email或phone必填其一
|
|
1398
|
+
this.validateAtLeastOne(params, [['email'], ['phone']], 'You must provide either an email or phone number')
|
|
1399
|
+
|
|
1400
|
+
// 第一步:发送验证码并存储 verificationInfo
|
|
1401
|
+
const verificationInfo = await this.getVerification(params.email ? { email: params.email } : { phone_number: this.formatPhone(params.phone) },)
|
|
1402
|
+
|
|
1403
|
+
return {
|
|
1404
|
+
data: {
|
|
1405
|
+
// 第二步:等待用户输入验证码(通过 Promise 包装用户输入事件)
|
|
1406
|
+
verifyOtp: async ({ token, messageId = verificationInfo.verification_id }): Promise<SignInRes> => {
|
|
1407
|
+
try {
|
|
1408
|
+
// 第三步:待用户输入完验证码之后,验证短信验证码
|
|
1409
|
+
const verificationTokenRes = await this.verify({
|
|
1410
|
+
verification_id: messageId || verificationInfo.verification_id,
|
|
1411
|
+
verification_code: token,
|
|
1412
|
+
})
|
|
1413
|
+
|
|
1414
|
+
// 第四步:注册并登录或直接登录
|
|
1415
|
+
// 如果用户已经存在,直接登录
|
|
1416
|
+
if (verificationInfo.is_user) {
|
|
1417
|
+
await this.signIn({
|
|
1418
|
+
username: params.email || this.formatPhone(params.phone),
|
|
1419
|
+
verification_token: verificationTokenRes.verification_token,
|
|
1420
|
+
})
|
|
1421
|
+
} else {
|
|
1422
|
+
// 如果用户不存在,注册用户
|
|
1423
|
+
const data = JSON.parse(JSON.stringify(params))
|
|
1424
|
+
delete data.email
|
|
1425
|
+
delete data.phone
|
|
1426
|
+
|
|
1427
|
+
await this.oauthInstance.authApi.signUp({
|
|
1428
|
+
...data,
|
|
1429
|
+
...(params.email ? { email: params.email } : { phone_number: this.formatPhone(params.phone) }),
|
|
1430
|
+
verification_token: verificationTokenRes.verification_token,
|
|
1431
|
+
verification_code: token,
|
|
1432
|
+
})
|
|
1433
|
+
await this.createLoginState()
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
const { data: { session } = {} } = await this.getSession()
|
|
1437
|
+
|
|
1438
|
+
return { data: { user: session.user, session }, error: null }
|
|
1439
|
+
} catch (error) {
|
|
1440
|
+
return { data: {}, error: new AuthError(error) }
|
|
1441
|
+
}
|
|
1442
|
+
},
|
|
1443
|
+
},
|
|
1444
|
+
error: null,
|
|
1445
|
+
}
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
return { data: {}, error: new AuthError(error) }
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* https://supabase.com/docs/reference/javascript/auth-signout
|
|
1453
|
+
* const result = await auth.signOut();
|
|
1454
|
+
*
|
|
1455
|
+
* @param params
|
|
1456
|
+
*/
|
|
1457
|
+
async signOut(params?: authModels.SignoutRequest,): Promise<authModels.SignoutResponse & { data: Object; error: AuthError }> {
|
|
1458
|
+
try {
|
|
1459
|
+
const { userInfoKey } = this.cache.keys
|
|
1460
|
+
const res = await this.oauthInstance.authApi.signOut(params)
|
|
1461
|
+
await this.cache.removeStoreAsync(userInfoKey)
|
|
1462
|
+
this.setAccessKey()
|
|
1463
|
+
|
|
1464
|
+
this.config.eventBus?.fire(EVENTS.LOGIN_STATE_CHANGED, { eventType: LOGIN_STATE_CHANGED_TYPE.SIGN_OUT })
|
|
1465
|
+
|
|
1466
|
+
this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.SIGNED_OUT })
|
|
1467
|
+
|
|
1468
|
+
// res返回是为了兼容v2版本
|
|
1469
|
+
return { ...res, data: {}, error: null }
|
|
1470
|
+
} catch (error) {
|
|
1471
|
+
return { data: {}, error: new AuthError(error) }
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
/**
|
|
1476
|
+
* https://supabase.com/docs/reference/javascript/auth-onauthstatechange
|
|
1477
|
+
* Receive a notification every time an auth event happens.
|
|
1478
|
+
* @param callback
|
|
1479
|
+
* @returns Promise<{ data: { subscription: Subscription }, error: Error | null }>
|
|
1480
|
+
*/
|
|
1481
|
+
onAuthStateChange(callback: OnAuthStateChangeCallback) {
|
|
1482
|
+
if (!this.hasListenerSetUp) {
|
|
1483
|
+
this.setupListeners()
|
|
1484
|
+
this.hasListenerSetUp = true
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
const id = Math.random().toString(36)
|
|
1488
|
+
|
|
1489
|
+
if (!this.listeners.has(id)) {
|
|
1490
|
+
this.listeners.set(id, new Set())
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
this.listeners.get(id)!.add(callback)
|
|
1494
|
+
|
|
1495
|
+
// 返回 Subscription 对象
|
|
1496
|
+
const subscription = {
|
|
1497
|
+
id,
|
|
1498
|
+
callback,
|
|
1499
|
+
unsubscribe: () => {
|
|
1500
|
+
const callbacks = this.listeners.get(id)
|
|
1501
|
+
if (callbacks) {
|
|
1502
|
+
callbacks.delete(callback)
|
|
1503
|
+
if (callbacks.size === 0) {
|
|
1504
|
+
this.listeners.delete(id)
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
},
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
return {
|
|
1511
|
+
data: { subscription },
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
/**
|
|
1516
|
+
* https://supabase.com/docs/reference/javascript/auth-signinwithpassword
|
|
1517
|
+
* Log in an existing user with an email and password or phone and password or username and password.
|
|
1518
|
+
* @param params
|
|
1519
|
+
* @returns Promise<SignInRes>
|
|
1520
|
+
*/
|
|
1521
|
+
async signInWithPassword(params: SignInWithPasswordReq): Promise<SignInRes> {
|
|
1522
|
+
try {
|
|
1523
|
+
// 参数校验:username/email/phone三选一,password必填
|
|
1524
|
+
this.validateAtLeastOne(
|
|
1525
|
+
params,
|
|
1526
|
+
[['username'], ['email'], ['phone']],
|
|
1527
|
+
'You must provide either username, email, or phone',
|
|
1528
|
+
)
|
|
1529
|
+
this.validateParams(params, {
|
|
1530
|
+
password: { required: true, message: 'Password is required' },
|
|
1531
|
+
})
|
|
1532
|
+
|
|
1533
|
+
await this.signIn({
|
|
1534
|
+
username: params.username || params.email || this.formatPhone(params.phone),
|
|
1535
|
+
password: params.password,
|
|
1536
|
+
...(params.is_encrypt ? { isEncrypt: true, version: 'v2' } : {}),
|
|
1537
|
+
})
|
|
1538
|
+
const { data: { session } = {} } = await this.getSession()
|
|
1539
|
+
|
|
1540
|
+
return { data: { user: session.user, session }, error: null }
|
|
1541
|
+
} catch (error) {
|
|
1542
|
+
return { data: {}, error: new AuthError(error) }
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
/**
|
|
1547
|
+
* https://supabase.com/docs/reference/javascript/auth-signinwithidtoken
|
|
1548
|
+
* 第三方平台登录。如果用户不存在,会根据云开发平台-登录方式中对应身份源的登录模式配置,判断是否自动注册
|
|
1549
|
+
* @param params
|
|
1550
|
+
* @returns Promise<SignInRes>
|
|
1551
|
+
*/
|
|
1552
|
+
async signInWithIdToken(params: SignInWithIdTokenReq): Promise<SignInRes> {
|
|
1553
|
+
try {
|
|
1554
|
+
// 参数校验:token必填
|
|
1555
|
+
this.validateParams(params, {
|
|
1556
|
+
token: { required: true, message: 'Token is required' },
|
|
1557
|
+
})
|
|
1558
|
+
|
|
1559
|
+
await this.signInWithProvider({
|
|
1560
|
+
provider_token: params.token,
|
|
1561
|
+
})
|
|
1562
|
+
const { data: { session } = {} } = await this.getSession()
|
|
1563
|
+
|
|
1564
|
+
return { data: { user: session.user, session }, error: null }
|
|
1565
|
+
} catch (error) {
|
|
1566
|
+
return { data: {}, error: new AuthError(error) }
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
/**
|
|
1571
|
+
* https://supabase.com/docs/reference/javascript/auth-signinwithotp
|
|
1572
|
+
* Log in a user using a one-time password (OTP).
|
|
1573
|
+
* @param params
|
|
1574
|
+
* @returns Promise<SignInWithOtpRes>
|
|
1575
|
+
*/
|
|
1576
|
+
async signInWithOtp(params: SignInWithOtpReq): Promise<SignInWithOtpRes> {
|
|
1577
|
+
try {
|
|
1578
|
+
// 参数校验:email或phone必填其一
|
|
1579
|
+
this.validateAtLeastOne(params, [['email'], ['phone']], 'You must provide either an email or phone number')
|
|
1580
|
+
|
|
1581
|
+
// 第一步:发送验证码并存储 verificationInfo
|
|
1582
|
+
const verificationInfo = await this.getVerification(params.email ? { email: params.email } : { phone_number: this.formatPhone(params.phone) },)
|
|
1583
|
+
|
|
1584
|
+
return {
|
|
1585
|
+
data: {
|
|
1586
|
+
user: null,
|
|
1587
|
+
session: null,
|
|
1588
|
+
// 第二步:等待用户输入验证码(通过 Promise 包装用户输入事件)
|
|
1589
|
+
verifyOtp: async ({ token, messageId = verificationInfo.verification_id }): Promise<SignInRes> => this.verifyOtp({
|
|
1590
|
+
email: params.email,
|
|
1591
|
+
phone: params.phone,
|
|
1592
|
+
token,
|
|
1593
|
+
messageId,
|
|
1594
|
+
}),
|
|
1595
|
+
},
|
|
1596
|
+
error: null,
|
|
1597
|
+
}
|
|
1598
|
+
} catch (error) {
|
|
1599
|
+
return { data: {}, error: new AuthError(error) }
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
/**
|
|
1604
|
+
* 校验第三方平台授权登录回调
|
|
1605
|
+
* @param params
|
|
1606
|
+
* @returns Promise<SignInRes>
|
|
1607
|
+
*/
|
|
1608
|
+
async verifyOAuth(params?: VerifyOAuthReq): Promise<SignInRes | LinkIdentityRes> {
|
|
1609
|
+
const data: any = {}
|
|
1610
|
+
try {
|
|
1611
|
+
// 回调至 provider_redirect_uri 地址(url query中携带 授权code,state等参数),此时检查 state 是否符合预期(如 自己设置的 wx_open)
|
|
1612
|
+
const code = params?.code || utils.getQuery('code')
|
|
1613
|
+
const state = params?.state || utils.getQuery('state')
|
|
1614
|
+
|
|
1615
|
+
// 参数校验:code和state必填
|
|
1616
|
+
if (!code) {
|
|
1617
|
+
return { data: {}, error: new AuthError({ message: 'Code is required' }) }
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
if (!state) {
|
|
1621
|
+
return { data: {}, error: new AuthError({ message: 'State is required' }) }
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
const cacheData = getBrowserSession(state)
|
|
1625
|
+
data.type = cacheData?.type
|
|
1626
|
+
|
|
1627
|
+
const provider = params?.provider || cacheData?.provider || utils.getQuery('provider')
|
|
1628
|
+
|
|
1629
|
+
if (!provider) {
|
|
1630
|
+
return { data, error: new AuthError({ message: 'Provider is required' }) }
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// state符合预期,则获取该三方平台token
|
|
1634
|
+
const { provider_token: token } = await this.grantProviderToken({
|
|
1635
|
+
provider_id: provider,
|
|
1636
|
+
provider_redirect_uri: location.origin + location.pathname, // 指定三方平台跳回的 url 地址
|
|
1637
|
+
provider_code: code, // 第三方平台跳转回页面时,url param 中携带的 code 参数
|
|
1638
|
+
})
|
|
1639
|
+
|
|
1640
|
+
let res: SignInRes | LinkIdentityRes
|
|
1641
|
+
|
|
1642
|
+
if (cacheData.type === OAUTH_TYPE.BIND_IDENTITY) {
|
|
1643
|
+
res = await this.oauthInstance.authApi.toBindIdentity({ provider_token: token, provider, fireEvent: true })
|
|
1644
|
+
} else {
|
|
1645
|
+
// 通过 provider_token 仅登录或登录并注册(与云开发平台-登录方式-身份源登录模式配置有关)
|
|
1646
|
+
res = await this.signInWithIdToken({
|
|
1647
|
+
token,
|
|
1648
|
+
})
|
|
1649
|
+
res.data = { ...data, ...res.data }
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
const localSearch = new URLSearchParams(location?.search)
|
|
1653
|
+
localSearch.delete('code')
|
|
1654
|
+
localSearch.delete('state')
|
|
1655
|
+
addUrlSearch(
|
|
1656
|
+
cacheData?.search === undefined ? `?${localSearch.toString()}` : cacheData?.search,
|
|
1657
|
+
cacheData?.hash || location.hash,
|
|
1658
|
+
)
|
|
1659
|
+
removeBrowserSession(state)
|
|
1660
|
+
|
|
1661
|
+
return res
|
|
1662
|
+
} catch (error) {
|
|
1663
|
+
return { data, error: new AuthError(error) }
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
/**
|
|
1668
|
+
* https://supabase.com/docs/reference/javascript/auth-signinwithoauth
|
|
1669
|
+
* 生成第三方平台授权 Uri (如微信二维码扫码授权网页)
|
|
1670
|
+
* @param params
|
|
1671
|
+
* @returns Promise<SignInOAuthRes>
|
|
1672
|
+
*/
|
|
1673
|
+
async signInWithOAuth(params: SignInWithOAuthReq): Promise<SignInOAuthRes> {
|
|
1674
|
+
try {
|
|
1675
|
+
// 参数校验:provider必填
|
|
1676
|
+
this.validateParams(params, {
|
|
1677
|
+
provider: { required: true, message: 'Provider is required' },
|
|
1678
|
+
})
|
|
1679
|
+
|
|
1680
|
+
const href = params.options?.redirectTo || location.href
|
|
1681
|
+
|
|
1682
|
+
const urlObject = new URL(href)
|
|
1683
|
+
|
|
1684
|
+
const provider_redirect_uri = urlObject.origin + urlObject.pathname
|
|
1685
|
+
|
|
1686
|
+
const state = params.options?.state || `prd-${params.provider}-${Math.random().toString(36)
|
|
1687
|
+
.slice(2)}`
|
|
1688
|
+
|
|
1689
|
+
const { uri } = await this.genProviderRedirectUri({
|
|
1690
|
+
provider_id: params.provider,
|
|
1691
|
+
provider_redirect_uri,
|
|
1692
|
+
state,
|
|
1693
|
+
})
|
|
1694
|
+
|
|
1695
|
+
// 对 URL 进行解码
|
|
1696
|
+
const decodedUri = decodeURIComponent(uri)
|
|
1697
|
+
|
|
1698
|
+
// 合并额外的查询参数
|
|
1699
|
+
let finalUri = decodedUri
|
|
1700
|
+
|
|
1701
|
+
if (params.options?.queryParams) {
|
|
1702
|
+
const url = new URL(decodedUri)
|
|
1703
|
+
Object.entries(params.options.queryParams).forEach(([key, value]) => {
|
|
1704
|
+
url.searchParams.set(key, value)
|
|
1705
|
+
})
|
|
1706
|
+
finalUri = url.toString()
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
saveToBrowserSession(state, {
|
|
1710
|
+
provider: params.provider,
|
|
1711
|
+
search: urlObject.search,
|
|
1712
|
+
hash: urlObject.hash,
|
|
1713
|
+
type: params.options?.type || OAUTH_TYPE.SIGN_IN,
|
|
1714
|
+
})
|
|
1715
|
+
|
|
1716
|
+
if (isBrowser() && !params.options?.skipBrowserRedirect) {
|
|
1717
|
+
window.location.assign(finalUri)
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
return { data: { url: finalUri, provider: params.provider }, error: null }
|
|
1721
|
+
} catch (error) {
|
|
1722
|
+
return { data: {}, error: new AuthError(error) }
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// https://supabase.com/docs/reference/javascript/auth-getclaims
|
|
1727
|
+
async getClaims(): Promise<GetClaimsRes> {
|
|
1728
|
+
try {
|
|
1729
|
+
const { accessToken } = await this.getAccessToken()
|
|
1730
|
+
const parsedToken = weAppJwtDecodeAll(accessToken)
|
|
1731
|
+
return { data: parsedToken, error: null }
|
|
1732
|
+
} catch (error) {
|
|
1733
|
+
return { data: {}, error: new AuthError(error) }
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
/**
|
|
1738
|
+
* https://supabase.com/docs/reference/javascript/auth-resetpasswordforemail
|
|
1739
|
+
* 通过 email 或手机号重置密码
|
|
1740
|
+
* @param emailOrPhone 邮箱或手机号
|
|
1741
|
+
* @returns Promise<ResetPasswordForEmailRes>
|
|
1742
|
+
*/
|
|
1743
|
+
async resetPasswordForEmail(
|
|
1744
|
+
emailOrPhone: string,
|
|
1745
|
+
options?: { redirectTo?: string },
|
|
1746
|
+
): Promise<ResetPasswordForEmailRes> {
|
|
1747
|
+
try {
|
|
1748
|
+
// 参数校验:emailOrPhone必填
|
|
1749
|
+
this.validateParams(
|
|
1750
|
+
{ emailOrPhone },
|
|
1751
|
+
{
|
|
1752
|
+
emailOrPhone: { required: true, message: 'Email or phone is required' },
|
|
1753
|
+
},
|
|
1754
|
+
)
|
|
1755
|
+
|
|
1756
|
+
const { redirectTo } = options || {}
|
|
1757
|
+
|
|
1758
|
+
// 判断是邮箱还是手机号
|
|
1759
|
+
const isEmail = emailOrPhone.includes('@')
|
|
1760
|
+
let verificationParams: { email?: string; phone_number?: string }
|
|
1761
|
+
|
|
1762
|
+
if (isEmail) {
|
|
1763
|
+
verificationParams = { email: emailOrPhone }
|
|
1764
|
+
} else {
|
|
1765
|
+
// 正规化手机号
|
|
1766
|
+
const formattedPhone = this.formatPhone(emailOrPhone)
|
|
1767
|
+
verificationParams = { phone_number: formattedPhone }
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// 第一步:发送验证码并存储 verificationInfo
|
|
1771
|
+
const verificationInfo = await this.getVerification(verificationParams)
|
|
1772
|
+
|
|
1773
|
+
return {
|
|
1774
|
+
data: {
|
|
1775
|
+
// 第二步:等待用户输入验证码(通过 Promise 包装用户输入事件)
|
|
1776
|
+
updateUser: async (attributes: UpdateUserAttributes): Promise<SignInRes> => {
|
|
1777
|
+
this.validateParams(attributes, {
|
|
1778
|
+
nonce: { required: true, message: 'Nonce is required' },
|
|
1779
|
+
password: { required: true, message: 'Password is required' },
|
|
1780
|
+
})
|
|
1781
|
+
try {
|
|
1782
|
+
// 第三步:待用户输入完验证码之后,验证验证码
|
|
1783
|
+
const verificationTokenRes = await this.verify({
|
|
1784
|
+
verification_id: verificationInfo.verification_id,
|
|
1785
|
+
verification_code: attributes.nonce,
|
|
1786
|
+
})
|
|
1787
|
+
|
|
1788
|
+
await this.oauthInstance.authApi.resetPassword({
|
|
1789
|
+
email: isEmail ? emailOrPhone : undefined,
|
|
1790
|
+
phone_number: !isEmail ? emailOrPhone : undefined,
|
|
1791
|
+
new_password: attributes.password,
|
|
1792
|
+
verification_token: verificationTokenRes.verification_token,
|
|
1793
|
+
})
|
|
1794
|
+
|
|
1795
|
+
this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, {
|
|
1796
|
+
event: AUTH_STATE_CHANGED_TYPE.PASSWORD_RECOVERY,
|
|
1797
|
+
})
|
|
1798
|
+
|
|
1799
|
+
const res = await this.signInWithPassword({
|
|
1800
|
+
email: isEmail ? emailOrPhone : undefined,
|
|
1801
|
+
phone: !isEmail ? emailOrPhone : undefined,
|
|
1802
|
+
password: attributes.password,
|
|
1803
|
+
})
|
|
1804
|
+
|
|
1805
|
+
if (redirectTo && isBrowser()) {
|
|
1806
|
+
window.location.assign(redirectTo)
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
return res
|
|
1810
|
+
} catch (error) {
|
|
1811
|
+
return { data: {}, error: new AuthError(error) }
|
|
1812
|
+
}
|
|
1813
|
+
},
|
|
1814
|
+
},
|
|
1815
|
+
error: null,
|
|
1816
|
+
}
|
|
1817
|
+
} catch (error) {
|
|
1818
|
+
return { data: {}, error: new AuthError(error) }
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
/**
|
|
1823
|
+
* 通过旧密码重置密码
|
|
1824
|
+
* @param new_password
|
|
1825
|
+
* @param old_password
|
|
1826
|
+
* @returns
|
|
1827
|
+
*/
|
|
1828
|
+
async resetPasswordForOld(params: ResetPasswordForOldReq) {
|
|
1829
|
+
try {
|
|
1830
|
+
await this.oauthInstance.authApi.updatePasswordByOld({
|
|
1831
|
+
old_password: params.old_password,
|
|
1832
|
+
new_password: params.new_password,
|
|
1833
|
+
})
|
|
1834
|
+
|
|
1835
|
+
const { data: { session } = {} } = await this.getSession()
|
|
1836
|
+
|
|
1837
|
+
return { data: { user: session.user, session }, error: null }
|
|
1838
|
+
} catch (error) {
|
|
1839
|
+
return { data: {}, error: new AuthError(error) }
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
/**
|
|
1844
|
+
* https://supabase.com/docs/reference/javascript/auth-verifyotp
|
|
1845
|
+
* Log in a user given a User supplied OTP and verificationId received through mobile or email.
|
|
1846
|
+
* @param params
|
|
1847
|
+
* @returns Promise<SignInRes>
|
|
1848
|
+
*/
|
|
1849
|
+
async verifyOtp(params: VerifyOtpReq): Promise<SignInRes> {
|
|
1850
|
+
try {
|
|
1851
|
+
const { type } = params
|
|
1852
|
+
// 参数校验:token和verificationInfo必填
|
|
1853
|
+
this.validateParams(params, {
|
|
1854
|
+
token: { required: true, message: 'Token is required' },
|
|
1855
|
+
messageId: { required: true, message: 'messageId is required' },
|
|
1856
|
+
})
|
|
1857
|
+
|
|
1858
|
+
if (['phone_change', 'email_change'].includes(type)) {
|
|
1859
|
+
await this.verify({
|
|
1860
|
+
verification_id: params.messageId,
|
|
1861
|
+
verification_code: params.token,
|
|
1862
|
+
})
|
|
1863
|
+
} else {
|
|
1864
|
+
await this.signInWithUsername({
|
|
1865
|
+
verificationInfo: { verification_id: params.messageId, is_user: true },
|
|
1866
|
+
verificationCode: params.token,
|
|
1867
|
+
username: params.email || this.formatPhone(params.phone) || '',
|
|
1868
|
+
loginType: params.email ? 'email' : 'phone',
|
|
1869
|
+
})
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
const { data: { session } = {} } = await this.getSession()
|
|
1873
|
+
|
|
1874
|
+
return { data: { user: session.user, session }, error: null }
|
|
1875
|
+
} catch (error) {
|
|
1876
|
+
return { data: {}, error: new AuthError(error) }
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
/**
|
|
1881
|
+
* https://supabase.com/docs/reference/javascript/auth-getSession
|
|
1882
|
+
* Returns the session, refreshing it if necessary.
|
|
1883
|
+
* @returns Promise<SignInRes>
|
|
1884
|
+
*/
|
|
1885
|
+
async getSession(): Promise<SignInRes> {
|
|
1886
|
+
try {
|
|
1887
|
+
const credentials: Credentials = await this.oauthInstance.oauth2client.getCredentials()
|
|
1888
|
+
|
|
1889
|
+
if (!credentials || credentials.scope === 'accessKey') {
|
|
1890
|
+
return { data: { session: null }, error: null }
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
const { data: { user } = {} } = await this.getUser()
|
|
1894
|
+
|
|
1895
|
+
return { data: { session: { ...credentials, user }, user }, error: null }
|
|
1896
|
+
} catch (error) {
|
|
1897
|
+
return { data: {}, error: new AuthError(error) }
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
/**
|
|
1902
|
+
* https://supabase.com/docs/reference/javascript/auth-refreshsession
|
|
1903
|
+
* 无论过期状态如何,都返回一个新的会话
|
|
1904
|
+
* @param refresh_token
|
|
1905
|
+
* @returns Promise<SignInRes>
|
|
1906
|
+
*/
|
|
1907
|
+
async refreshSession(refresh_token?: string): Promise<SignInRes> {
|
|
1908
|
+
try {
|
|
1909
|
+
const credentials: Credentials = await this.oauthInstance.oauth2client.localCredentials.getCredentials()
|
|
1910
|
+
credentials.refresh_token = refresh_token || credentials.refresh_token
|
|
1911
|
+
const newTokens = await this.oauthInstance.oauth2client.refreshToken(credentials)
|
|
1912
|
+
const { data: { user } = {} } = await this.getUser()
|
|
1913
|
+
|
|
1914
|
+
return { data: { user, session: { ...newTokens, user } }, error: null }
|
|
1915
|
+
} catch (error) {
|
|
1916
|
+
return { data: {}, error: new AuthError(error) }
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
/**
|
|
1921
|
+
* https://supabase.com/docs/reference/javascript/auth-getuser
|
|
1922
|
+
* 如果存在现有会话,则获取当前用户详细信息
|
|
1923
|
+
* @returns Promise<GetUserRes>
|
|
1924
|
+
*/
|
|
1925
|
+
async getUser(): Promise<GetUserRes> {
|
|
1926
|
+
try {
|
|
1927
|
+
const user = this.convertToUser(await this.getUserInfo())
|
|
1928
|
+
return { data: { user }, error: null }
|
|
1929
|
+
} catch (error) {
|
|
1930
|
+
return { data: {}, error: new AuthError(error) }
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
/**
|
|
1935
|
+
* 刷新用户信息
|
|
1936
|
+
* @returns Promise<CommonRes>
|
|
1937
|
+
*/
|
|
1938
|
+
async refreshUser(): Promise<CommonRes> {
|
|
1939
|
+
try {
|
|
1940
|
+
await this.currentUser.refresh()
|
|
1941
|
+
|
|
1942
|
+
const { data: { session } = {} } = await this.getSession()
|
|
1943
|
+
return { data: { user: session.user, session }, error: null }
|
|
1944
|
+
} catch (error) {
|
|
1945
|
+
return { data: {}, error: new AuthError(error) }
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
/**
|
|
1950
|
+
* https://supabase.com/docs/reference/javascript/auth-updateuser
|
|
1951
|
+
* 更新用户信息
|
|
1952
|
+
* @param params
|
|
1953
|
+
* @returns Promise<GetUserRes | UpdateUserWithVerificationRes>
|
|
1954
|
+
*/
|
|
1955
|
+
async updateUser(params: UpdateUserReq): Promise<GetUserRes | UpdateUserWithVerificationRes> {
|
|
1956
|
+
try {
|
|
1957
|
+
// 参数校验:至少有一个更新字段被提供
|
|
1958
|
+
const hasValue = Object.keys(params).some(key => params[key] !== undefined && params[key] !== null && params[key] !== '',)
|
|
1959
|
+
if (!hasValue) {
|
|
1960
|
+
throw new AuthError({ message: 'At least one field must be provided for update' })
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
const { email, phone, ...restParams } = params
|
|
1964
|
+
|
|
1965
|
+
// 检查是否需要更新 email 或 phone
|
|
1966
|
+
const needsEmailVerification = email !== undefined
|
|
1967
|
+
const needsPhoneVerification = phone !== undefined
|
|
1968
|
+
|
|
1969
|
+
let extraRes = {}
|
|
1970
|
+
|
|
1971
|
+
if (needsEmailVerification || needsPhoneVerification) {
|
|
1972
|
+
// 需要发送验证码
|
|
1973
|
+
let verificationParams: { email?: string; phone_number?: string }
|
|
1974
|
+
let verificationType: 'email_change' | 'phone_change'
|
|
1975
|
+
|
|
1976
|
+
if (needsEmailVerification) {
|
|
1977
|
+
verificationParams = { email: params.email }
|
|
1978
|
+
verificationType = 'email_change'
|
|
1979
|
+
} else {
|
|
1980
|
+
// 正规化手机号
|
|
1981
|
+
const formattedPhone = this.formatPhone(params.phone)
|
|
1982
|
+
verificationParams = { phone_number: formattedPhone }
|
|
1983
|
+
verificationType = 'phone_change'
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
// 发送验证码
|
|
1987
|
+
const verificationInfo = await this.getVerification(verificationParams)
|
|
1988
|
+
|
|
1989
|
+
Object.keys(restParams).length > 0 && (await this.updateUserBasicInfo(restParams))
|
|
1990
|
+
|
|
1991
|
+
extraRes = {
|
|
1992
|
+
messageId: verificationInfo.verification_id,
|
|
1993
|
+
verifyOtp: async (verifyParams: { email?: string; phone?: string; token: string }): Promise<GetUserRes> => {
|
|
1994
|
+
try {
|
|
1995
|
+
if (verifyParams.email && params.email === verifyParams.email) {
|
|
1996
|
+
// 验证码验证
|
|
1997
|
+
await this.verifyOtp({
|
|
1998
|
+
type: 'email_change',
|
|
1999
|
+
email: params.email,
|
|
2000
|
+
token: verifyParams.token,
|
|
2001
|
+
messageId: verificationInfo.verification_id,
|
|
2002
|
+
})
|
|
2003
|
+
await this.updateUserBasicInfo({ email: params.email })
|
|
2004
|
+
} else if (verifyParams.phone && params.phone === verifyParams.phone) {
|
|
2005
|
+
// 验证码验证
|
|
2006
|
+
await this.verifyOtp({
|
|
2007
|
+
type: 'phone_change',
|
|
2008
|
+
phone: params.phone,
|
|
2009
|
+
token: verifyParams.token,
|
|
2010
|
+
messageId: verificationInfo.verification_id,
|
|
2011
|
+
})
|
|
2012
|
+
await this.updateUserBasicInfo({ phone: this.formatPhone(params.phone) })
|
|
2013
|
+
} else {
|
|
2014
|
+
await this.verifyOtp({
|
|
2015
|
+
type: verificationType,
|
|
2016
|
+
email: needsEmailVerification ? params.email : undefined,
|
|
2017
|
+
phone: !needsEmailVerification ? params.phone : undefined,
|
|
2018
|
+
token: verifyParams.token,
|
|
2019
|
+
messageId: verificationInfo.verification_id,
|
|
2020
|
+
})
|
|
2021
|
+
// 验证成功后更新用户信息
|
|
2022
|
+
await this.updateUserBasicInfo(params)
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
const {
|
|
2026
|
+
data: { user },
|
|
2027
|
+
} = await this.getUser()
|
|
2028
|
+
this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.USER_UPDATED })
|
|
2029
|
+
|
|
2030
|
+
return { data: { user }, error: null }
|
|
2031
|
+
} catch (error) {
|
|
2032
|
+
return { data: {}, error: new AuthError(error) }
|
|
2033
|
+
}
|
|
2034
|
+
},
|
|
2035
|
+
}
|
|
2036
|
+
} else {
|
|
2037
|
+
// 不需要验证,直接更新
|
|
2038
|
+
await this.updateUserBasicInfo(params)
|
|
2039
|
+
}
|
|
2040
|
+
const {
|
|
2041
|
+
data: { user },
|
|
2042
|
+
} = await this.getUser()
|
|
2043
|
+
this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.USER_UPDATED })
|
|
2044
|
+
|
|
2045
|
+
return { data: { user, ...extraRes }, error: null }
|
|
2046
|
+
} catch (error) {
|
|
2047
|
+
return { data: {}, error: new AuthError(error) }
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
/**
|
|
2052
|
+
* https://supabase.com/docs/reference/javascript/auth-getuseridentities
|
|
2053
|
+
* 获取所有身份源
|
|
2054
|
+
* @returns Promise<GetUserIdentitiesRes>
|
|
2055
|
+
*/
|
|
2056
|
+
async getUserIdentities(): Promise<GetUserIdentitiesRes> {
|
|
2057
|
+
try {
|
|
2058
|
+
const providers = await this.oauthInstance.authApi.getProviders()
|
|
2059
|
+
|
|
2060
|
+
return { data: { identities: providers?.data?.filter(v => !!v.bind) as unknown as GetUserIdentitiesRes['data']['identities'] }, error: null }
|
|
2061
|
+
} catch (error) {
|
|
2062
|
+
return { data: {}, error: new AuthError(error) }
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
/**
|
|
2067
|
+
* https://supabase.com/docs/reference/javascript/auth-linkidentity
|
|
2068
|
+
* 绑定身份源到当前用户
|
|
2069
|
+
* @param params
|
|
2070
|
+
* @returns Promise<LinkIdentityRes>
|
|
2071
|
+
*/
|
|
2072
|
+
async linkIdentity(params: LinkIdentityReq): Promise<LinkIdentityRes> {
|
|
2073
|
+
try {
|
|
2074
|
+
// 参数校验:provider必填
|
|
2075
|
+
this.validateParams(params, {
|
|
2076
|
+
provider: { required: true, message: 'Provider is required' },
|
|
2077
|
+
})
|
|
2078
|
+
|
|
2079
|
+
await this.signInWithOAuth({
|
|
2080
|
+
provider: params.provider,
|
|
2081
|
+
options: {
|
|
2082
|
+
type: OAUTH_TYPE.BIND_IDENTITY,
|
|
2083
|
+
},
|
|
2084
|
+
})
|
|
2085
|
+
|
|
2086
|
+
return { data: { provider: params.provider }, error: null }
|
|
2087
|
+
} catch (error) {
|
|
2088
|
+
return { data: {}, error: new AuthError(error) }
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
/**
|
|
2093
|
+
* https://supabase.com/docs/reference/javascript/auth-unlinkidentity
|
|
2094
|
+
* 解绑身份源
|
|
2095
|
+
* @param params
|
|
2096
|
+
* @returns Promise<CommonRes>
|
|
2097
|
+
*/
|
|
2098
|
+
async unlinkIdentity(params: UnlinkIdentityReq): Promise<CommonRes> {
|
|
2099
|
+
try {
|
|
2100
|
+
// 参数校验:provider必填
|
|
2101
|
+
this.validateParams(params, {
|
|
2102
|
+
provider: { required: true, message: 'Provider is required' },
|
|
2103
|
+
})
|
|
2104
|
+
|
|
2105
|
+
await this.oauthInstance.authApi.unbindProvider({ provider_id: params.provider })
|
|
2106
|
+
|
|
2107
|
+
return { data: {}, error: null }
|
|
2108
|
+
} catch (error) {
|
|
2109
|
+
return { data: {}, error: new AuthError(error) }
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
/**
|
|
2114
|
+
* https://supabase.com/docs/reference/javascript/auth-reauthentication
|
|
2115
|
+
* 重新认证
|
|
2116
|
+
* @returns Promise<ReauthenticateRes>
|
|
2117
|
+
*/
|
|
2118
|
+
async reauthenticate(): Promise<ReauthenticateRes> {
|
|
2119
|
+
try {
|
|
2120
|
+
const {
|
|
2121
|
+
data: { user },
|
|
2122
|
+
} = await this.getUser()
|
|
2123
|
+
|
|
2124
|
+
this.validateAtLeastOne(user, [['email', 'phone']], 'You must provide either an email or phone number')
|
|
2125
|
+
const userInfo = user.email ? { email: user.email } : { phone_number: this.formatPhone(user.phone) }
|
|
2126
|
+
|
|
2127
|
+
// 第一步:发送验证码并存储 verificationInfo
|
|
2128
|
+
const verificationInfo = await this.getVerification(userInfo)
|
|
2129
|
+
|
|
2130
|
+
return {
|
|
2131
|
+
data: {
|
|
2132
|
+
// 第二步:等待用户输入验证码(通过 Promise 包装用户输入事件)
|
|
2133
|
+
updateUser: async (attributes: UpdateUserAttributes): Promise<SignInRes> => {
|
|
2134
|
+
this.validateParams(attributes, {
|
|
2135
|
+
nonce: { required: true, message: 'Nonce is required' },
|
|
2136
|
+
})
|
|
2137
|
+
try {
|
|
2138
|
+
if (attributes.password) {
|
|
2139
|
+
// 第三步:待用户输入完验证码之后,验证验证码
|
|
2140
|
+
const verificationTokenRes = await this.verify({
|
|
2141
|
+
verification_id: verificationInfo.verification_id,
|
|
2142
|
+
verification_code: attributes.nonce,
|
|
2143
|
+
})
|
|
2144
|
+
|
|
2145
|
+
// 第四步:获取 sudo_token
|
|
2146
|
+
const sudoRes = await this.oauthInstance.authApi.sudo({
|
|
2147
|
+
verification_token: verificationTokenRes.verification_token,
|
|
2148
|
+
})
|
|
2149
|
+
|
|
2150
|
+
await this.oauthInstance.authApi.setPassword({
|
|
2151
|
+
new_password: attributes.password,
|
|
2152
|
+
sudo_token: sudoRes.sudo_token,
|
|
2153
|
+
})
|
|
2154
|
+
} else {
|
|
2155
|
+
await this.signInWithUsername({
|
|
2156
|
+
verificationInfo,
|
|
2157
|
+
verificationCode: attributes.nonce,
|
|
2158
|
+
...userInfo,
|
|
2159
|
+
loginType: userInfo.email ? 'email' : 'phone',
|
|
2160
|
+
})
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
const { data: { session } = {} } = await this.getSession()
|
|
2164
|
+
|
|
2165
|
+
return { data: { user: session.user, session }, error: null }
|
|
2166
|
+
} catch (error) {
|
|
2167
|
+
return { data: {}, error: new AuthError(error) }
|
|
2168
|
+
}
|
|
2169
|
+
},
|
|
2170
|
+
},
|
|
2171
|
+
error: null,
|
|
2172
|
+
}
|
|
2173
|
+
} catch (error) {
|
|
2174
|
+
return { data: {}, error: new AuthError(error) }
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
/**
|
|
2179
|
+
* https://supabase.com/docs/reference/javascript/auth-resend
|
|
2180
|
+
* 重新发送验证码
|
|
2181
|
+
* @param params
|
|
2182
|
+
* @returns Promise<ResendRes>
|
|
2183
|
+
*/
|
|
2184
|
+
async resend(params: ResendReq): Promise<ResendRes> {
|
|
2185
|
+
try {
|
|
2186
|
+
// 参数校验:email或phone必填其一
|
|
2187
|
+
this.validateAtLeastOne(params, [['email'], ['phone']], 'You must provide either an email or phone number')
|
|
2188
|
+
|
|
2189
|
+
const target = params.type === 'signup' ? 'ANY' : 'USER'
|
|
2190
|
+
const data: { email?: string; phone_number?: string; target: 'USER' | 'ANY' } = { target }
|
|
2191
|
+
if ('email' in params) {
|
|
2192
|
+
data.email = params.email
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
if ('phone' in params) {
|
|
2196
|
+
data.phone_number = this.formatPhone(params.phone)
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
// 重新发送验证码
|
|
2200
|
+
const { verification_id: verificationId } = await this.oauthInstance.authApi.getVerification(data)
|
|
2201
|
+
|
|
2202
|
+
return {
|
|
2203
|
+
data: { messageId: verificationId },
|
|
2204
|
+
error: null,
|
|
2205
|
+
}
|
|
2206
|
+
} catch (error: any) {
|
|
2207
|
+
return {
|
|
2208
|
+
data: {},
|
|
2209
|
+
error: new AuthError(error),
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
/**
|
|
2215
|
+
* https://supabase.com/docs/reference/javascript/auth-setsession
|
|
2216
|
+
* 使用access_token和refresh_token来设置会话
|
|
2217
|
+
* @param params
|
|
2218
|
+
* @returns Promise<SignInRes>
|
|
2219
|
+
*/
|
|
2220
|
+
async setSession(params: SetSessionReq): Promise<SignInRes> {
|
|
2221
|
+
try {
|
|
2222
|
+
this.validateParams(params, {
|
|
2223
|
+
access_token: { required: true, message: 'Access token is required' },
|
|
2224
|
+
refresh_token: { required: true, message: 'Refresh token is required' },
|
|
2225
|
+
})
|
|
2226
|
+
|
|
2227
|
+
await this.oauthInstance.oauth2client.refreshToken(params, { throwError: true })
|
|
2228
|
+
|
|
2229
|
+
const { data: { session } = {} } = await this.getSession()
|
|
2230
|
+
|
|
2231
|
+
this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.SIGNED_IN })
|
|
2232
|
+
|
|
2233
|
+
return { data: { user: session.user, session }, error: null }
|
|
2234
|
+
} catch (error) {
|
|
2235
|
+
return { data: {}, error: new AuthError(error) }
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
// https://supabase.com/docs/reference/javascript/auth-exchangecodeforsession
|
|
2240
|
+
async exchangeCodeForSession() {
|
|
2241
|
+
//
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
/**
|
|
2245
|
+
* 删除当前用户
|
|
2246
|
+
* @param params
|
|
2247
|
+
* @returns
|
|
2248
|
+
*/
|
|
2249
|
+
async deleteUser(params: DeleteMeReq): Promise<CommonRes> {
|
|
2250
|
+
try {
|
|
2251
|
+
this.validateParams(params, {
|
|
2252
|
+
password: { required: true, message: 'Password is required' },
|
|
2253
|
+
})
|
|
2254
|
+
|
|
2255
|
+
const { sudo_token } = await this.oauthInstance.authApi.sudo(params)
|
|
2256
|
+
|
|
2257
|
+
await this.oauthInstance.authApi.deleteMe({ sudo_token })
|
|
2258
|
+
|
|
2259
|
+
return { data: {}, error: null }
|
|
2260
|
+
} catch (error) {
|
|
2261
|
+
return { data: {}, error: new AuthError(error) }
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
/**
|
|
2266
|
+
* 跳转系统默认登录页
|
|
2267
|
+
* @returns {Promise<authModels.ToDefaultLoginPage>}
|
|
2268
|
+
* @memberof Auth
|
|
2269
|
+
*/
|
|
2270
|
+
async toDefaultLoginPage(params: authModels.ToDefaultLoginPage = {}): Promise<CommonRes> {
|
|
2271
|
+
try {
|
|
2272
|
+
const configVersion = params.config_version || 'env'
|
|
2273
|
+
const query = Object.keys(params.query || {})
|
|
2274
|
+
.map(key => `${key}=${params.query[key]}`)
|
|
2275
|
+
.join('&')
|
|
2276
|
+
|
|
2277
|
+
if (adapterForWxMp.isMatch()) {
|
|
2278
|
+
wx.navigateTo({ url: `/packages/$wd_system/pages/login/index${query ? `?${query}` : ''}` })
|
|
2279
|
+
} else {
|
|
2280
|
+
const redirectUri = params.redirect_uri || window.location.href
|
|
2281
|
+
const urlObj = new URL(redirectUri)
|
|
2282
|
+
const loginPage = `${urlObj.origin}/__auth/?app_id=${params.app_id || ''}&env_id=${this.config.env}&client_id=${
|
|
2283
|
+
this.config.clientId || this.config.env
|
|
2284
|
+
}&config_version=${configVersion}&redirect_uri=${encodeURIComponent(redirectUri)}${query ? `&${query}` : ''}`
|
|
2285
|
+
window.location.href = loginPage
|
|
2286
|
+
}
|
|
2287
|
+
return { data: {}, error: null }
|
|
2288
|
+
} catch (error) {
|
|
2289
|
+
return { data: {}, error: new AuthError(error) }
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
/**
|
|
2294
|
+
* 自定义登录
|
|
2295
|
+
* @param getTickFn () => Promise<string>, 获取自定义登录 ticket 的函数
|
|
2296
|
+
* @returns
|
|
2297
|
+
*/
|
|
2298
|
+
async signInWithCustomTicket(getTickFn?: authModels.GetCustomSignTicketFn): Promise<SignInRes> {
|
|
2299
|
+
if (getTickFn) {
|
|
2300
|
+
this.setCustomSignFunc(getTickFn)
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
try {
|
|
2304
|
+
await this.oauthInstance.authApi.signInWithCustomTicket()
|
|
2305
|
+
const loginState = await this.createLoginState()
|
|
2306
|
+
|
|
2307
|
+
const { data: { session } = {} } = await this.getSession()
|
|
2308
|
+
|
|
2309
|
+
// loginState返回是为了兼容v2版本
|
|
2310
|
+
return { ...(loginState as any), data: { user: session.user, session }, error: null }
|
|
2311
|
+
} catch (error) {
|
|
2312
|
+
return { data: {}, error: new AuthError(error) }
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
/**
|
|
2317
|
+
* 小程序openId静默登录
|
|
2318
|
+
* @param params
|
|
2319
|
+
* @returns Promise<SignInRes>
|
|
2320
|
+
*/
|
|
2321
|
+
async signInWithOpenId({ useWxCloud = true } = {}): Promise<SignInRes> {
|
|
2322
|
+
if (!adapterForWxMp.isMatch()) {
|
|
2323
|
+
throw Error('wx api undefined')
|
|
2324
|
+
}
|
|
2325
|
+
const wxInfo = wx.getAccountInfoSync().miniProgram
|
|
2326
|
+
|
|
2327
|
+
const mainFunc = async (code) => {
|
|
2328
|
+
let result: authModels.GrantProviderTokenResponse | undefined = undefined
|
|
2329
|
+
let credentials: Credentials | undefined = undefined
|
|
2330
|
+
|
|
2331
|
+
try {
|
|
2332
|
+
result = await this.oauthInstance.authApi.grantProviderToken(
|
|
2333
|
+
{
|
|
2334
|
+
provider_id: wxInfo?.appId,
|
|
2335
|
+
provider_code: code,
|
|
2336
|
+
provider_params: {
|
|
2337
|
+
provider_code_type: 'open_id',
|
|
2338
|
+
appid: wxInfo?.appId,
|
|
2339
|
+
},
|
|
2340
|
+
},
|
|
2341
|
+
useWxCloud,
|
|
2342
|
+
)
|
|
2343
|
+
|
|
2344
|
+
if ((result as any)?.error_code || !result.provider_token) {
|
|
2345
|
+
throw result
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
credentials = await this.oauthInstance.authApi.signInWithProvider(
|
|
2349
|
+
{ provider_token: result.provider_token },
|
|
2350
|
+
useWxCloud,
|
|
2351
|
+
)
|
|
2352
|
+
|
|
2353
|
+
if ((credentials as any)?.error_code) {
|
|
2354
|
+
throw credentials
|
|
2355
|
+
}
|
|
2356
|
+
} catch (error) {
|
|
2357
|
+
throw error
|
|
2358
|
+
}
|
|
2359
|
+
await this.oauthInstance.oauth2client.setCredentials(credentials as Credentials)
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
try {
|
|
2363
|
+
await new Promise((resolve, reject) => {
|
|
2364
|
+
wx.login({
|
|
2365
|
+
success: async (res: { code: string }) => {
|
|
2366
|
+
try {
|
|
2367
|
+
await mainFunc(res.code)
|
|
2368
|
+
resolve(true)
|
|
2369
|
+
} catch (error) {
|
|
2370
|
+
reject(error)
|
|
2371
|
+
}
|
|
2372
|
+
},
|
|
2373
|
+
fail: (res: any) => {
|
|
2374
|
+
const error = new Error(res?.errMsg)
|
|
2375
|
+
;(error as any).code = res?.errno
|
|
2376
|
+
reject(error)
|
|
2377
|
+
},
|
|
2378
|
+
})
|
|
2379
|
+
})
|
|
2380
|
+
|
|
2381
|
+
const loginState = await this.createLoginState()
|
|
2382
|
+
|
|
2383
|
+
const { data: { session } = {} } = await this.getSession()
|
|
2384
|
+
|
|
2385
|
+
// loginState返回是为了兼容v2版本
|
|
2386
|
+
return { ...(loginState as any), data: { user: session.user, session }, error: null }
|
|
2387
|
+
} catch (error) {
|
|
2388
|
+
return { data: {}, error: new AuthError(error) }
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
/**
|
|
2393
|
+
* 小程序手机号授权登录,目前只支持全托管手机号授权登录
|
|
2394
|
+
* @param params
|
|
2395
|
+
* @returns Promise<SignInRes>
|
|
2396
|
+
*/
|
|
2397
|
+
async signInWithPhoneAuth({ phoneCode = '' }): Promise<SignInRes> {
|
|
2398
|
+
if (!adapterForWxMp.isMatch()) {
|
|
2399
|
+
return { data: {}, error: new AuthError({ message: 'wx api undefined' }) }
|
|
2400
|
+
}
|
|
2401
|
+
const wxInfo = wx.getAccountInfoSync().miniProgram
|
|
2402
|
+
const providerInfo = {
|
|
2403
|
+
provider_params: { provider_code_type: 'phone' },
|
|
2404
|
+
provider_id: wxInfo.appId,
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
const { code } = await wx.login()
|
|
2408
|
+
;(providerInfo as any).provider_code = code
|
|
2409
|
+
|
|
2410
|
+
try {
|
|
2411
|
+
let providerToken = await this.oauthInstance.authApi.grantProviderToken(providerInfo)
|
|
2412
|
+
if (providerToken.error_code) {
|
|
2413
|
+
throw providerToken
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
providerToken = await this.oauthInstance.authApi.patchProviderToken({
|
|
2417
|
+
provider_token: providerToken.provider_token,
|
|
2418
|
+
provider_id: wxInfo.appId,
|
|
2419
|
+
provider_params: {
|
|
2420
|
+
code: phoneCode,
|
|
2421
|
+
provider_code_type: 'phone',
|
|
2422
|
+
},
|
|
2423
|
+
})
|
|
2424
|
+
if (providerToken.error_code) {
|
|
2425
|
+
throw providerToken
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
const signInRes = await this.oauthInstance.authApi.signInWithProvider({
|
|
2429
|
+
provider_token: providerToken.provider_token,
|
|
2430
|
+
})
|
|
2431
|
+
|
|
2432
|
+
if ((signInRes as any)?.error_code) {
|
|
2433
|
+
throw signInRes
|
|
2434
|
+
}
|
|
2435
|
+
} catch (error) {
|
|
2436
|
+
return { data: {}, error: new AuthError(error) }
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
const loginState = await this.createLoginState()
|
|
2440
|
+
|
|
2441
|
+
const { data: { session } = {} } = await this.getSession()
|
|
2442
|
+
|
|
2443
|
+
// loginState返回是为了兼容v2版本
|
|
2444
|
+
return { ...(loginState as any), data: { user: session.user, session }, error: null }
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
private formatPhone(phone: string) {
|
|
2448
|
+
if (!/\s+/.test(phone) && /^\+\d{1,3}\d+/.test(phone)) {
|
|
2449
|
+
return phone.replace(/^(\+\d{1,2})(\d+)$/, '$1 $2')
|
|
2450
|
+
}
|
|
2451
|
+
return /^\+\d{1,3}\s+/.test(phone) ? phone : `+86 ${phone}`
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
private notifyListeners(event, session, info): OnAuthStateChangeCallback {
|
|
2455
|
+
this.listeners.forEach((callbacks) => {
|
|
2456
|
+
callbacks.forEach((callback) => {
|
|
2457
|
+
try {
|
|
2458
|
+
callback(event, session, info)
|
|
2459
|
+
} catch (error) {
|
|
2460
|
+
console.error('Error in auth state change callback:', error)
|
|
2461
|
+
}
|
|
2462
|
+
})
|
|
2463
|
+
})
|
|
2464
|
+
return
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
private setupListeners() {
|
|
2468
|
+
this.config.eventBus?.on(EVENTS.AUTH_STATE_CHANGED, async (params) => {
|
|
2469
|
+
const event = params?.data?.event
|
|
2470
|
+
const info = params?.data?.info
|
|
2471
|
+
const {
|
|
2472
|
+
data: { session },
|
|
2473
|
+
} = await this.getSession()
|
|
2474
|
+
|
|
2475
|
+
this.notifyListeners(event, session, info)
|
|
2476
|
+
})
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
private convertToUser(userInfo: authModels.UserInfo & Partial<User>) {
|
|
2480
|
+
if (!userInfo) return null
|
|
2481
|
+
|
|
2482
|
+
// 优先使用 userInfo 中的数据(V3 API)
|
|
2483
|
+
const email = userInfo?.email || ''
|
|
2484
|
+
const phone = userInfo?.phone_number || ''
|
|
2485
|
+
const userId = userInfo?.sub || userInfo?.uid || ''
|
|
2486
|
+
|
|
2487
|
+
return {
|
|
2488
|
+
id: userId,
|
|
2489
|
+
aud: 'authenticated',
|
|
2490
|
+
role: userInfo.groups?.map?.(group => (typeof group === 'string' ? group : group.id)),
|
|
2491
|
+
email: email || '',
|
|
2492
|
+
email_confirmed_at: userInfo?.email_verified ? userInfo.created_at : userInfo.created_at,
|
|
2493
|
+
phone,
|
|
2494
|
+
phone_confirmed_at: phone ? userInfo.created_at : undefined,
|
|
2495
|
+
confirmed_at: userInfo.created_at,
|
|
2496
|
+
last_sign_in_at: (userInfo as any).last_sign_in_at,
|
|
2497
|
+
app_metadata: {
|
|
2498
|
+
provider: userInfo.loginType?.toLowerCase() || 'cloudbase',
|
|
2499
|
+
providers: [userInfo.loginType?.toLowerCase() || 'cloudbase'],
|
|
2500
|
+
},
|
|
2501
|
+
user_metadata: {
|
|
2502
|
+
// V3 API 用户信息
|
|
2503
|
+
name: userInfo?.name,
|
|
2504
|
+
picture: userInfo?.picture,
|
|
2505
|
+
username: userInfo?.username, // 用户名称,长度 5-24 位,支持字符中英文、数字、特殊字符(仅支持_-),不支持中文
|
|
2506
|
+
gender: userInfo?.gender,
|
|
2507
|
+
locale: userInfo?.locale,
|
|
2508
|
+
// V2 API 兼容(使用 any 避免类型错误)
|
|
2509
|
+
uid: userInfo.uid,
|
|
2510
|
+
nickName: userInfo.nickName,
|
|
2511
|
+
avatarUrl: userInfo.avatarUrl || userInfo.picture,
|
|
2512
|
+
location: userInfo.location,
|
|
2513
|
+
hasPassword: userInfo.hasPassword,
|
|
2514
|
+
},
|
|
2515
|
+
identities:
|
|
2516
|
+
userInfo?.providers?.map(p => ({
|
|
2517
|
+
id: p.id || '',
|
|
2518
|
+
identity_id: p.id || '',
|
|
2519
|
+
user_id: userId,
|
|
2520
|
+
identity_data: {
|
|
2521
|
+
provider_id: p.id,
|
|
2522
|
+
provider_user_id: p.provider_user_id,
|
|
2523
|
+
name: p.name,
|
|
2524
|
+
},
|
|
2525
|
+
provider: p.id || 'cloudbase',
|
|
2526
|
+
created_at: userInfo.created_at,
|
|
2527
|
+
updated_at: userInfo.updated_at,
|
|
2528
|
+
last_sign_in_at: (userInfo as any).last_sign_in_at,
|
|
2529
|
+
})) || [],
|
|
2530
|
+
created_at: userInfo.created_at,
|
|
2531
|
+
updated_at: userInfo.updated_at,
|
|
2532
|
+
is_anonymous: userInfo.name === 'anonymous',
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
/**
|
|
2537
|
+
* 参数校验辅助方法
|
|
2538
|
+
*/
|
|
2539
|
+
private validateParams(params: any, rules: { [key: string]: { required?: boolean; message: string } }): void {
|
|
2540
|
+
for (const [key, rule] of Object.entries(rules)) {
|
|
2541
|
+
if (rule.required && (params?.[key] === undefined || params?.[key] === null || params?.[key] === '')) {
|
|
2542
|
+
throw new AuthError({ message: rule.message })
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
/**
|
|
2548
|
+
* 校验必填参数组(至少有一个参数必须有值)
|
|
2549
|
+
*/
|
|
2550
|
+
private validateAtLeastOne(params: any, fieldGroups: string[][], message: string): void {
|
|
2551
|
+
const hasValue = fieldGroups.some(group => group.some(field => params?.[field] !== undefined && params?.[field] !== null && params?.[field] !== ''),)
|
|
2552
|
+
|
|
2553
|
+
if (!hasValue) {
|
|
2554
|
+
throw new AuthError({ message })
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
private async init(): Promise<{ error: Error | null }> {
|
|
2559
|
+
try {
|
|
2560
|
+
const credentials: Credentials = await this.oauthInstance.oauth2client.localCredentials.getCredentials()
|
|
2561
|
+
if (credentials) {
|
|
2562
|
+
this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.INITIAL_SESSION })
|
|
2563
|
+
}
|
|
2564
|
+
} catch (error) {
|
|
2565
|
+
// Ignore errors when checking for existing credentials
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
return { error: null }
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
type TInitAuthOptions = Pick<ICloudbaseAuthConfig, 'region' | 'persistence' | 'i18n' | 'accessKey' | 'useWxCloud'> &
|
|
2573
|
+
Partial<AuthOptions> & {
|
|
2574
|
+
detectSessionInUrl?: boolean
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
export function generateAuthInstance(
|
|
2578
|
+
config: TInitAuthOptions & { sdkVersion?: string },
|
|
2579
|
+
options?: {
|
|
2580
|
+
clientId: ICloudbaseConfig['clientId']
|
|
2581
|
+
env: ICloudbaseConfig['env']
|
|
2582
|
+
apiOrigin: string
|
|
2583
|
+
cache?: ICloudbaseCache
|
|
2584
|
+
platform?: ICloudbase['platform']
|
|
2585
|
+
app?: ICloudbase
|
|
2586
|
+
debug?: ICloudbaseAuthConfig['debug']
|
|
2587
|
+
detectSessionInUrl?: ICloudbaseAuthConfig['detectSessionInUrl']
|
|
2588
|
+
},
|
|
2589
|
+
) {
|
|
2590
|
+
const { region = 'ap-shanghai', i18n, accessKey, useWxCloud } = config
|
|
2591
|
+
const platform = options?.platform || (useDefaultAdapter.bind(options)() as ICloudbasePlatformInfo)
|
|
2592
|
+
const { runtime, adapter } = platform
|
|
2593
|
+
|
|
2594
|
+
const { env, clientId, debug, cache, app: cloudbase } = options || {}
|
|
2595
|
+
let { apiOrigin } = options || {}
|
|
2596
|
+
if (!apiOrigin) {
|
|
2597
|
+
apiOrigin = `https://${env}.${region}.tcb-api.tencentcloudapi.com`
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
const commonOpts = {
|
|
2601
|
+
env,
|
|
1401
2602
|
clientId,
|
|
1402
2603
|
i18n,
|
|
1403
|
-
|
|
2604
|
+
accessKey,
|
|
2605
|
+
useWxCloud,
|
|
2606
|
+
eventBus: new CloudbaseEventEmitter(),
|
|
1404
2607
|
}
|
|
1405
2608
|
|
|
1406
2609
|
const oauthInstance = new CloudbaseOAuth(useAuthAdapter({
|
|
@@ -1415,8 +2618,10 @@ export function generateAuthInstance(
|
|
|
1415
2618
|
captchaOptions: config?.captchaOptions,
|
|
1416
2619
|
wxCloud: config?.wxCloud,
|
|
1417
2620
|
adapter,
|
|
1418
|
-
onCredentialsError,
|
|
1419
|
-
headers: config.headers || {},
|
|
2621
|
+
onCredentialsError: onCredentialsError(commonOpts.eventBus),
|
|
2622
|
+
headers: { 'X-SDK-Version': `@cloudbase/js-sdk/${config.sdkVersion}`, ...(config.headers || {}) },
|
|
2623
|
+
detectSessionInUrl: config.detectSessionInUrl,
|
|
2624
|
+
debug,
|
|
1420
2625
|
}),)
|
|
1421
2626
|
|
|
1422
2627
|
const authInstance = new Auth({
|
|
@@ -1425,8 +2630,7 @@ export function generateAuthInstance(
|
|
|
1425
2630
|
persistence: config.persistence,
|
|
1426
2631
|
debug,
|
|
1427
2632
|
cache:
|
|
1428
|
-
cache
|
|
1429
|
-
|| new CloudbaseCache({
|
|
2633
|
+
cache || new CloudbaseCache({
|
|
1430
2634
|
persistence: config.persistence,
|
|
1431
2635
|
keys: { userInfoKey: `user_info_${env}` },
|
|
1432
2636
|
platformInfo: platform,
|
|
@@ -1436,53 +2640,92 @@ export function generateAuthInstance(
|
|
|
1436
2640
|
oauthInstance,
|
|
1437
2641
|
})
|
|
1438
2642
|
|
|
2643
|
+
// Initialize session with user info callback
|
|
2644
|
+
// This handles OAuth callback URL detection and creates login state atomically
|
|
2645
|
+
oauthInstance.initializeSession(async (data, error) => {
|
|
2646
|
+
if (!data) return
|
|
2647
|
+
|
|
2648
|
+
if (data.type === OAUTH_TYPE.SIGN_IN) {
|
|
2649
|
+
if (error) {
|
|
2650
|
+
commonOpts.eventBus.fire(EVENTS.AUTH_STATE_CHANGED, {
|
|
2651
|
+
event: AUTH_STATE_CHANGED_TYPE.SIGNED_IN,
|
|
2652
|
+
info: { ...data, error },
|
|
2653
|
+
})
|
|
2654
|
+
} else if (data.user) {
|
|
2655
|
+
// 使用已获取的 user 信息创建 LoginState,复用 createLoginState 逻辑
|
|
2656
|
+
// 但跳过 refresh() 避免再次请求 API 导致死锁
|
|
2657
|
+
authInstance.createLoginState({}, { userInfo: data.user })
|
|
2658
|
+
}
|
|
2659
|
+
} else if (data.type === OAUTH_TYPE.BIND_IDENTITY) {
|
|
2660
|
+
commonOpts.eventBus.fire(EVENTS.AUTH_STATE_CHANGED, {
|
|
2661
|
+
event: AUTH_STATE_CHANGED_TYPE.BIND_IDENTITY,
|
|
2662
|
+
info: { ...data, error },
|
|
2663
|
+
})
|
|
2664
|
+
}
|
|
2665
|
+
})
|
|
2666
|
+
|
|
1439
2667
|
return { authInstance, oauthInstance }
|
|
1440
2668
|
}
|
|
1441
2669
|
|
|
2670
|
+
const NAMESPACE = 'auth'
|
|
2671
|
+
|
|
1442
2672
|
const component: ICloudbaseComponent = {
|
|
1443
2673
|
name: COMPONENT_NAME,
|
|
1444
|
-
namespace:
|
|
1445
|
-
entity(config
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
printWarn(ERRORS.INVALID_OPERATION, 'every cloudbase instance should has only one auth object')
|
|
1452
|
-
return this.authInstance
|
|
1453
|
-
}
|
|
1454
|
-
const { adapter } = this.platform
|
|
1455
|
-
// 如不明确指定persistence则优先取各平台adapter首选,其次localStorage
|
|
1456
|
-
const newPersistence = config.persistence || adapter.primaryStorage
|
|
1457
|
-
if (newPersistence && newPersistence !== this.config.persistence) {
|
|
1458
|
-
this.updateConfig({ persistence: newPersistence })
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
const { authInstance, oauthInstance } = generateAuthInstance(
|
|
1462
|
-
{
|
|
1463
|
-
wxCloud: this.config.wxCloud,
|
|
1464
|
-
storage: this.config.storage,
|
|
1465
|
-
...config,
|
|
1466
|
-
persistence: this.config.persistence,
|
|
1467
|
-
i18n: this.config.i18n,
|
|
1468
|
-
publishable_key: this.config.publishable_key,
|
|
1469
|
-
},
|
|
1470
|
-
{
|
|
1471
|
-
env: this.config.env,
|
|
1472
|
-
clientId: this.config.clientId,
|
|
1473
|
-
apiOrigin: this.request.getBaseEndPoint(),
|
|
1474
|
-
platform: this.platform,
|
|
1475
|
-
cache: this.cache,
|
|
1476
|
-
app: this,
|
|
1477
|
-
debug: this.config.debug,
|
|
1478
|
-
},
|
|
1479
|
-
)
|
|
2674
|
+
namespace: NAMESPACE,
|
|
2675
|
+
entity(config?: TInitAuthOptions) {
|
|
2676
|
+
const auth = function (config?: TInitAuthOptions) {
|
|
2677
|
+
if (this.authInstance && !config) {
|
|
2678
|
+
// printWarn(ERRORS.INVALID_OPERATION, 'every cloudbase instance should has only one auth object')
|
|
2679
|
+
return this.authInstance
|
|
2680
|
+
}
|
|
1480
2681
|
|
|
1481
|
-
|
|
2682
|
+
config = config || {
|
|
2683
|
+
region: '',
|
|
2684
|
+
persistence: 'local',
|
|
2685
|
+
apiPath: AUTH_API_PREFIX,
|
|
2686
|
+
}
|
|
2687
|
+
const { adapter } = this.platform
|
|
2688
|
+
// 如不明确指定persistence则优先取各平台adapter首选,其次localStorage
|
|
2689
|
+
const newPersistence = config.persistence || adapter.primaryStorage
|
|
2690
|
+
if (newPersistence && newPersistence !== this.config.persistence) {
|
|
2691
|
+
this.updateConfig({ persistence: newPersistence })
|
|
2692
|
+
}
|
|
1482
2693
|
|
|
1483
|
-
|
|
2694
|
+
const { authInstance, oauthInstance } = generateAuthInstance(
|
|
2695
|
+
{
|
|
2696
|
+
wxCloud: this.config.wxCloud,
|
|
2697
|
+
storage: this.config.storage,
|
|
2698
|
+
...config,
|
|
2699
|
+
persistence: this.config.persistence,
|
|
2700
|
+
i18n: this.config.i18n,
|
|
2701
|
+
accessKey: this.config.accessKey,
|
|
2702
|
+
useWxCloud: this.config.useWxCloud,
|
|
2703
|
+
sdkVersion: this.version,
|
|
2704
|
+
detectSessionInUrl: this.config.auth?.detectSessionInUrl,
|
|
2705
|
+
},
|
|
2706
|
+
{
|
|
2707
|
+
env: this.config.env,
|
|
2708
|
+
clientId: this.config.clientId,
|
|
2709
|
+
apiOrigin: this.request.getBaseEndPoint(this.config.endPointMode || 'CLOUD_API'),
|
|
2710
|
+
platform: this.platform,
|
|
2711
|
+
cache: this.cache,
|
|
2712
|
+
app: this,
|
|
2713
|
+
debug: this.config.debug,
|
|
2714
|
+
},
|
|
2715
|
+
)
|
|
2716
|
+
|
|
2717
|
+
this.oauthInstance = oauthInstance
|
|
2718
|
+
|
|
2719
|
+
this.authInstance = authInstance
|
|
2720
|
+
|
|
2721
|
+
return this.authInstance
|
|
2722
|
+
}
|
|
1484
2723
|
|
|
1485
|
-
|
|
2724
|
+
const authProto = auth.call(this, config)
|
|
2725
|
+
Object.assign(auth, authProto)
|
|
2726
|
+
Object.setPrototypeOf(auth, Object.getPrototypeOf(authProto))
|
|
2727
|
+
this[NAMESPACE] = auth
|
|
2728
|
+
return auth
|
|
1486
2729
|
},
|
|
1487
2730
|
}
|
|
1488
2731
|
|