@cloudbase/auth 2.26.1 → 2.26.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/src/index.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  weAppJwtDecodeAll,
15
15
  AuthError,
16
16
  authModels,
17
+ DEFAULT_NODE_ACCESS_SCOPE,
17
18
  } from '@cloudbase/oauth'
18
19
  import { useAuthAdapter } from './adapter'
19
20
  import {
@@ -28,6 +29,13 @@ import {
28
29
  useDefaultAdapter,
29
30
  } from './utilities'
30
31
  import { saveToBrowserSession, getBrowserSession, removeBrowserSession, addUrlSearch } from './utils'
32
+ import {
33
+ AuthV1Compat,
34
+ WeixinAuthProvider,
35
+ CustomAuthProvider,
36
+ AnonymousAuthProvider,
37
+ applyUserV1Compat,
38
+ } from './v1-compat'
31
39
  import { utils } from '@cloudbase/utilities'
32
40
  import {
33
41
  CommonRes,
@@ -58,6 +66,7 @@ import {
58
66
  UpdateUserReq,
59
67
  UpdateUserWithVerificationRes,
60
68
  VerifyOAuthReq,
69
+ VerifyOAuthRes,
61
70
  VerifyOtpReq,
62
71
  } from './type'
63
72
 
@@ -327,6 +336,10 @@ export class User implements IUser {
327
336
  })
328
337
  }
329
338
  }
339
+
340
+ // 应用 v1 兼容方法到 User 类
341
+ applyUserV1Compat(User)
342
+
330
343
  interface ILoginStateOptions extends IUserOptions {
331
344
  envId: string
332
345
  }
@@ -362,18 +375,21 @@ export class LoginState implements ILoginState {
362
375
  }
363
376
  }
364
377
 
365
- class Auth {
366
- readonly config: ICloudbaseAuthConfig
378
+ interface IAuthConfig extends ICloudbaseAuthConfig {
379
+ cache: ICloudbaseCache
380
+ request?: ICloudbaseRequest
381
+ runtime?: string
382
+ }
383
+
384
+ class Auth extends AuthV1Compat {
385
+ readonly config: IAuthConfig
367
386
  oauthInstance: CloudbaseOAuth
368
387
  readonly cache: ICloudbaseCache
369
388
  private listeners: Map<string, Set<OnAuthStateChangeCallback>> = new Map()
370
389
  private hasListenerSetUp = false
371
390
 
372
- constructor(config: ICloudbaseAuthConfig & {
373
- cache: ICloudbaseCache
374
- request?: ICloudbaseRequest
375
- runtime?: string
376
- },) {
391
+ constructor(config: IAuthConfig) {
392
+ super()
377
393
  this.config = config
378
394
  this.oauthInstance = config.oauthInstance
379
395
  this.cache = config.cache
@@ -381,1060 +397,827 @@ class Auth {
381
397
  this.setAccessKey()
382
398
  }
383
399
 
384
- /**
385
- * 绑定手机号
386
- * @param phoneNumber
387
- * @param phoneCode
388
- */
389
- @catchErrorsDecorator({
390
- title: '绑定手机号失败',
391
- messages: [
392
- '请确认以下各项:',
393
- ' 1 - 调用 auth().bindPhoneNumber() 的语法或参数是否正确',
394
- ' 2 - 当前环境是否开通了短信验证码登录',
395
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
396
- ],
397
- })
398
- public async bindPhoneNumber(params: authModels.BindPhoneRequest) {
399
- return this.oauthInstance.authApi.editContact(params)
400
- }
400
+ // ========== new auth api methods merged below ==========
401
401
 
402
402
  /**
403
- * 解绑三方绑定
404
- * @param loginType
403
+ * https://supabase.com/docs/reference/javascript/auth-signinanonymously
404
+ * Sign in a user anonymously.
405
+ * const { data, error } = await auth.signInAnonymously();
406
+ * @param params
407
+ * @returns Promise<SignInRes>
405
408
  */
406
- @catchErrorsDecorator({
407
- title: '解除三方绑定失败',
408
- messages: [
409
- '请确认以下各项:',
410
- ' 1 - 调用 auth().unbindProvider() 的语法或参数是否正确',
411
- ' 2 - 当前账户是否已经与此登录方式解绑',
412
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
413
- ],
414
- })
415
- public async unbindProvider(params: authModels.UnbindProviderRequest): Promise<void> {
416
- return this.oauthInstance.authApi.unbindProvider(params)
417
- }
409
+ async signInAnonymously(params: SignInAnonymouslyReq): Promise<SignInRes> {
410
+ try {
411
+ await this.oauthInstance.authApi.signInAnonymously(params)
412
+ const loginState = await this.createLoginState()
418
413
 
419
- /**
420
- * 更新邮箱地址
421
- * @param email
422
- * @param sudo_token
423
- * @param verification_token
424
- */
425
- @catchErrorsDecorator({
426
- title: '绑定邮箱地址失败',
427
- messages: [
428
- '请确认以下各项:',
429
- ' 1 - 调用 auth().bindEmail() 的语法或参数是否正确',
430
- ' 2 - 当前环境是否开通了邮箱密码登录',
431
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
432
- ],
433
- })
434
- public bindEmail(params: authModels.BindEmailRequest) {
435
- return this.oauthInstance.authApi.editContact(params)
436
- }
414
+ const { data: { session } = {} } = await this.getSession()
437
415
 
438
- /**
439
- * verify
440
- * @param {authModels.VerifyRequest} params
441
- * @returns {Promise<authModels.VerifyResponse>}
442
- * @memberof User
443
- */
444
- @catchErrorsDecorator({
445
- title: '验证码验证失败',
446
- messages: [
447
- '请确认以下各项:',
448
- ' 1 - 调用 auth().verify() 的语法或参数是否正确',
449
- ' 2 - 当前环境是否开通了手机验证码/邮箱登录',
450
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
451
- ],
452
- })
453
- public async verify(params: authModels.VerifyRequest): Promise<authModels.VerifyResponse> {
454
- return this.oauthInstance.authApi.verify(params)
416
+ // loginState返回是为了兼容v2版本
417
+ return { ...(loginState as any), data: { user: session.user, session }, error: null }
418
+ } catch (error) {
419
+ return { data: {}, error: new AuthError(error) }
420
+ }
455
421
  }
456
422
 
457
423
  /**
458
- * 获取验证码
459
- * @param {authModels.GetVerificationRequest} params
460
- * @returns {Promise<authModels.GetVerificationResponse>}
461
- * @memberof User
424
+ * https://supabase.com/docs/reference/javascript/auth-signup
425
+ * 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.
426
+ * @param params
427
+ * @returns Promise<SignUpRes>
462
428
  */
463
- @catchErrorsDecorator({
464
- title: '获取验证码失败',
465
- messages: [
466
- '请确认以下各项:',
467
- ' 1 - 调用 auth().getVerification() 的语法或参数是否正确',
468
- ' 2 - 当前环境是否开通了手机验证码/邮箱登录',
469
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
470
- ],
471
- })
472
- public async getVerification(
473
- params: authModels.GetVerificationRequest,
474
- options?: { withCaptcha: boolean },
475
- ): Promise<authModels.GetVerificationResponse> {
476
- if (params.phone_number) {
429
+ async signUp(params: authModels.SignUpRequest & { phone?: string }): Promise<SignUpRes> {
430
+ if (params.phone_number || params.verification_code || params.verification_token || params.provider_token) {
477
431
  params.phone_number = this.formatPhone(params.phone_number)
432
+ await this.oauthInstance.authApi.signUp(params)
433
+ return this.createLoginState() as any
478
434
  }
479
- return this.oauthInstance.authApi.getVerification(params, options)
480
- }
435
+ try {
436
+ // 参数校验:email或phone必填其一
437
+ this.validateAtLeastOne(params, [['email'], ['phone']], 'You must provide either an email or phone number')
481
438
 
482
- /**
483
- * 获取当前登录的用户信息-同步
484
- */
485
- get currentUser() {
486
- if (this.cache.mode === 'async') {
487
- // async storage的平台调用此API提示
488
- printWarn(
489
- ERRORS.INVALID_OPERATION,
490
- 'current platform\'s storage is asynchronous, please use getCurrentUser instead',
491
- )
492
- return
493
- }
439
+ // 第一步:发送验证码并存储 verificationInfo
440
+ const verificationInfo = await this.getVerification(params.email ? { email: params.email } : { phone_number: this.formatPhone(params.phone) },)
494
441
 
495
- const loginState = this.hasLoginState()
442
+ return {
443
+ data: {
444
+ // 第二步:等待用户输入验证码(通过 Promise 包装用户输入事件)
445
+ verifyOtp: async ({ token, messageId = verificationInfo.verification_id }): Promise<SignInRes> => {
446
+ try {
447
+ // 第三步:待用户输入完验证码之后,验证短信验证码
448
+ const verificationTokenRes = await this.verify({
449
+ verification_id: messageId || verificationInfo.verification_id,
450
+ verification_code: token,
451
+ })
496
452
 
497
- if (loginState) {
498
- return loginState.user || null
453
+ // 第四步:注册并登录或直接登录
454
+ // 如果用户已经存在,直接登录
455
+ if (verificationInfo.is_user) {
456
+ await this.signIn({
457
+ username: params.email || this.formatPhone(params.phone),
458
+ verification_token: verificationTokenRes.verification_token,
459
+ })
460
+ } else {
461
+ // 如果用户不存在,注册用户
462
+ const data = JSON.parse(JSON.stringify(params))
463
+ delete data.email
464
+ delete data.phone
465
+
466
+ await this.oauthInstance.authApi.signUp({
467
+ ...data,
468
+ ...(params.email ? { email: params.email } : { phone_number: this.formatPhone(params.phone) }),
469
+ verification_token: verificationTokenRes.verification_token,
470
+ verification_code: token,
471
+ })
472
+ await this.createLoginState()
473
+ }
474
+
475
+ const { data: { session } = {} } = await this.getSession()
476
+
477
+ return { data: { user: session.user, session }, error: null }
478
+ } catch (error) {
479
+ return { data: {}, error: new AuthError(error) }
480
+ }
481
+ },
482
+ },
483
+ error: null,
484
+ }
485
+ } catch (error) {
486
+ return { data: {}, error: new AuthError(error) }
499
487
  }
500
- return null
501
488
  }
502
489
 
503
490
  /**
504
- * 获取当前登录的用户信息-异步
491
+ * https://supabase.com/docs/reference/javascript/auth-signout
492
+ * const result = await auth.signOut();
493
+ *
494
+ * @param params
505
495
  */
506
- @catchErrorsDecorator({
507
- title: '获取用户信息失败',
508
- messages: [
509
- '请确认以下各项:',
510
- ' 1 - 调用 auth().getCurrentUser() 的语法或参数是否正确',
511
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
512
- ],
513
- })
514
- public async getCurrentUser(): Promise<(authModels.UserInfo & Partial<User>) | null> {
515
- const loginState = await this.getLoginState()
516
- if (loginState) {
517
- const userInfo = loginState.user.getLocalUserInfo() as authModels.UserInfo
518
- await loginState.user.checkLocalInfoAsync()
519
- return { ...loginState.user, ...userInfo } as unknown as authModels.UserInfo & Partial<User>
496
+ async signOut(params?: authModels.SignoutRequest,): Promise<authModels.SignoutResponse & { data: Object; error: AuthError }> {
497
+ try {
498
+ const { userInfoKey } = this.cache.keys
499
+ const res = await this.oauthInstance.authApi.signOut(params)
500
+ await this.cache.removeStoreAsync(userInfoKey)
501
+ this.setAccessKey()
502
+
503
+ this.config.eventBus?.fire(EVENTS.LOGIN_STATE_CHANGED, { eventType: LOGIN_STATE_CHANGED_TYPE.SIGN_OUT })
504
+
505
+ this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.SIGNED_OUT })
506
+
507
+ // res返回是为了兼容v2版本
508
+ return { ...res, data: {}, error: null }
509
+ } catch (error) {
510
+ return { data: {}, error: new AuthError(error) }
520
511
  }
521
- return null
522
512
  }
523
513
 
524
- // /**
525
- // * 匿名登录
526
- // * @returns {Promise<LoginState>}
527
- // * @memberof Auth
528
- // */
529
- // @catchErrorsDecorator({
530
- // title: '匿名登录失败',
531
- // messages: [
532
- // '请确认以下各项:',
533
- // ' 1 - 当前环境是否开启了匿名登录',
534
- // ' 2 - 调用 auth().signInAnonymously() 的语法或参数是否正确',
535
- // `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
536
- // ],
537
- // })
538
- // public async signInAnonymously(data: {
539
- // provider_token?: string
540
- // } = {},): Promise<LoginState> {
541
- // await this.oauthInstance.authApi.signInAnonymously(data)
542
- // return this.createLoginState()
543
- // }
544
-
545
514
  /**
546
- * 匿名登录
547
- * @returns {Promise<LoginState>}
548
- * @memberof Auth
515
+ * https://supabase.com/docs/reference/javascript/auth-onauthstatechange
516
+ * Receive a notification every time an auth event happens.
517
+ * @param callback
518
+ * @returns Promise<{ data: { subscription: Subscription }, error: Error | null }>
549
519
  */
550
- @catchErrorsDecorator({
551
- title: '小程序匿名登录失败',
552
- messages: [
553
- '请确认以下各项:',
554
- ' 1 - 当前环境是否开启了匿名登录',
555
- ' 2 - 调用 auth().signInAnonymouslyInWx() 的语法或参数是否正确',
556
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
557
- ],
558
- })
559
- public async signInAnonymouslyInWx({
560
- useWxCloud,
561
- }: {
562
- useWxCloud?: boolean
563
- } = {}): Promise<LoginState> {
564
- if (!adapterForWxMp.isMatch()) {
565
- throw Error('wx api undefined')
520
+ onAuthStateChange(callback: OnAuthStateChangeCallback) {
521
+ if (!this.hasListenerSetUp) {
522
+ this.setupListeners()
523
+ this.hasListenerSetUp = true
566
524
  }
567
- const wxInfo = wx.getAccountInfoSync().miniProgram
568
525
 
569
- const mainFunc = async (code) => {
570
- let result: authModels.GrantProviderTokenResponse | undefined = undefined
571
- let credentials: Credentials | undefined = undefined
572
-
573
- try {
574
- result = await this.oauthInstance.authApi.grantProviderToken(
575
- {
576
- provider_id: wxInfo?.appId,
577
- provider_code: code,
578
- provider_params: {
579
- provider_code_type: 'open_id',
580
- appid: wxInfo?.appId,
581
- },
582
- },
583
- useWxCloud,
584
- )
526
+ const id = Math.random().toString(36)
585
527
 
586
- if ((result as any)?.error_code || !result.provider_token) {
587
- throw result
588
- }
528
+ if (!this.listeners.has(id)) {
529
+ this.listeners.set(id, new Set())
530
+ }
589
531
 
590
- credentials = await this.oauthInstance.authApi.signInAnonymously(
591
- { provider_token: result.provider_token },
592
- useWxCloud,
593
- )
532
+ this.listeners.get(id)!.add(callback)
594
533
 
595
- if ((credentials as any)?.error_code) {
596
- throw credentials
534
+ // 返回 Subscription 对象
535
+ const subscription = {
536
+ id,
537
+ callback,
538
+ unsubscribe: () => {
539
+ const callbacks = this.listeners.get(id)
540
+ if (callbacks) {
541
+ callbacks.delete(callback)
542
+ if (callbacks.size === 0) {
543
+ this.listeners.delete(id)
544
+ }
597
545
  }
598
- } catch (error) {
599
- throw error
600
- }
546
+ },
601
547
  }
602
548
 
603
- await new Promise((resolve, reject) => {
604
- wx.login({
605
- success: async (res: { code: string }) => {
606
- try {
607
- await mainFunc(res.code)
608
- resolve(true)
609
- } catch (error) {
610
- reject(error)
611
- }
612
- },
613
- fail: (res: any) => {
614
- const error = new Error(res?.errMsg)
615
- ;(error as any).code = res?.errno
616
- reject(error)
617
- },
618
- })
619
- })
620
-
621
- return this.createLoginState(undefined, { asyncRefreshUser: true })
549
+ return {
550
+ data: { subscription },
551
+ }
622
552
  }
623
553
 
624
554
  /**
625
- * 小程序绑定OpenID
626
- * @returns {Promise<LoginState>}
627
- * @memberof Auth
555
+ * https://supabase.com/docs/reference/javascript/auth-signinwithpassword
556
+ * Log in an existing user with an email and password or phone and password or username and password.
557
+ * @param params
558
+ * @returns Promise<SignInRes>
628
559
  */
629
- @catchErrorsDecorator({
630
- title: '小程序绑定OpenID失败',
631
- messages: [
632
- '请确认以下各项:',
633
- ' 1 - 当前环境是否开启了小程序openId静默登录',
634
- ' 2 - 调用 auth().bindOpenId() 的语法或参数是否正确',
635
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
636
- ],
637
- })
638
- public async bindOpenId(): Promise<void> {
639
- if (!adapterForWxMp.isMatch()) {
640
- throw Error('wx api undefined')
641
- }
642
- const wxInfo = wx.getAccountInfoSync().miniProgram
643
-
644
- const mainFunc = async (code) => {
645
- let result: authModels.GrantProviderTokenResponse | undefined = undefined
646
-
647
- try {
648
- result = await this.oauthInstance.authApi.grantProviderToken({
649
- provider_id: wxInfo?.appId,
650
- provider_code: code,
651
- provider_params: {
652
- provider_code_type: 'open_id',
653
- appid: wxInfo?.appId,
654
- },
655
- })
560
+ async signInWithPassword(params: SignInWithPasswordReq): Promise<SignInRes> {
561
+ try {
562
+ // 参数校验:username/email/phone三选一,password必填
563
+ this.validateAtLeastOne(
564
+ params,
565
+ [['username'], ['email'], ['phone']],
566
+ 'You must provide either username, email, or phone',
567
+ )
568
+ this.validateParams(params, {
569
+ password: { required: true, message: 'Password is required' },
570
+ })
656
571
 
657
- if ((result as any)?.error_code || !result.provider_token) {
658
- throw result
659
- }
572
+ await this.signIn({
573
+ username: params.username || params.email || this.formatPhone(params.phone),
574
+ password: params.password,
575
+ ...(params.is_encrypt ? { isEncrypt: true, version: 'v2' } : {}),
576
+ })
577
+ const { data: { session } = {} } = await this.getSession()
660
578
 
661
- await this.oauthInstance.authApi.bindWithProvider({ provider_token: result.provider_token })
662
- } catch (error) {
663
- throw error
579
+ return { data: { user: session.user, session }, error: null }
580
+ } catch (error) {
581
+ const authError = new AuthError(error)
582
+ // 优化错误提示:登录失败时提供更友好的排查指引
583
+ if (authError.message?.includes('密码不正确') || authError.message?.includes('password')) {
584
+ console.warn('[CloudBase Auth] 登录失败提示:\n'
585
+ + ' 1. 请确认用户名/邮箱/手机号是否正确\n'
586
+ + ' 2. 请确认密码是否正确\n'
587
+ + ' 3. 如果用户不存在,请先通过 auth.signUp() 注册用户,或在云开发控制台手动创建用户\n'
588
+ + ' 4. 确认当前环境已开启对应的登录方式(控制台 → 环境 → 登录授权)',)
664
589
  }
590
+ return { data: {}, error: authError }
665
591
  }
592
+ }
666
593
 
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
- },
594
+ /**
595
+ * https://supabase.com/docs/reference/javascript/auth-signinwithidtoken
596
+ * 第三方平台登录。如果用户不存在,会根据云开发平台-登录方式中对应身份源的登录模式配置,判断是否自动注册
597
+ * @param params
598
+ * @returns Promise<SignInRes>
599
+ */
600
+ async signInWithIdToken(params: SignInWithIdTokenReq): Promise<SignInRes> {
601
+ try {
602
+ // 参数校验:token必填
603
+ this.validateParams(params, {
604
+ token: { required: true, message: 'Token is required' },
682
605
  })
683
- })
684
606
 
685
- return
607
+ await this.signInWithProvider({
608
+ provider_token: params.token,
609
+ })
610
+ const { data: { session } = {} } = await this.getSession()
611
+
612
+ return { data: { user: session.user, session }, error: null }
613
+ } catch (error) {
614
+ return { data: {}, error: new AuthError(error) }
615
+ }
686
616
  }
687
617
 
688
618
  /**
689
- * 小程序unionId静默登录
690
- * @returns {Promise<LoginState>}
691
- * @memberof Auth
619
+ * https://supabase.com/docs/reference/javascript/auth-signinwithotp
620
+ * Log in a user using a one-time password (OTP).
621
+ * @param params
622
+ * @returns Promise<SignInWithOtpRes>
692
623
  */
693
- @catchErrorsDecorator({
694
- title: '小程序unionId静默登录失败',
695
- messages: [
696
- '请确认以下各项:',
697
- ' 1 - 当前环境是否开启了小程序unionId静默登录',
698
- ' 2 - 调用 auth().signInWithUnionId() 的语法或参数是否正确',
699
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
700
- ],
701
- })
702
- public async signInWithUnionId(): Promise<LoginState> {
703
- if (!adapterForWxMp.isMatch()) {
704
- throw Error('wx api undefined')
624
+ async signInWithOtp(params: SignInWithOtpReq): Promise<SignInWithOtpRes> {
625
+ if (params.options?.shouldCreateUser === undefined || !!params.options?.shouldCreateUser) {
626
+ return this.signUp({
627
+ email: params.email,
628
+ phone: params.phone,
629
+ })
705
630
  }
706
- try {
707
- await new Promise((resolve, reject) => {
708
- const wxInfo = wx.getAccountInfoSync().miniProgram
709
- wx.login({
710
- success: async (res: { code: string }) => {
711
- const providerId = wxInfo?.appId
712
- try {
713
- const result = await this.oauthInstance.authApi.grantProviderToken({
714
- provider_code: res.code,
715
- provider_id: providerId,
716
- provider_params: {
717
- provider_code_type: 'union_id',
718
- appid: wxInfo?.appId,
719
- },
720
- })
721
631
 
722
- const { provider_token: providerToken } = result
723
-
724
- if (!providerToken) {
725
- reject(result)
726
- return
727
- }
632
+ try {
633
+ // 参数校验:email或phone必填其一
634
+ this.validateAtLeastOne(params, [['email'], ['phone']], 'You must provide either an email or phone number')
728
635
 
729
- const signInRes = await this.oauthInstance.authApi.signInWithProvider({
730
- provider_id: providerId,
731
- provider_token: providerToken,
732
- })
636
+ // 第一步:发送验证码并存储 verificationInfo
637
+ const verificationInfo = await this.getVerification(params.email ? { email: params.email } : { phone_number: this.formatPhone(params.phone) },)
733
638
 
734
- if ((signInRes as any)?.error_code) {
735
- reject(signInRes)
736
- return
737
- }
738
- resolve(true)
739
- } catch (error) {
740
- reject(error)
741
- }
742
- },
743
- fail: (res: any) => {
744
- const error = new Error(res?.errMsg)
745
- ;(error as any).code = res?.errno
746
- reject(error)
747
- },
748
- })
749
- })
639
+ return {
640
+ data: {
641
+ user: null,
642
+ session: null,
643
+ // 第二步:等待用户输入验证码(通过 Promise 包装用户输入事件)
644
+ verifyOtp: async ({ token, messageId = verificationInfo.verification_id }): Promise<SignInRes> => this.verifyOtp({
645
+ email: params.email,
646
+ phone: params.phone,
647
+ token,
648
+ messageId,
649
+ }),
650
+ },
651
+ error: null,
652
+ }
750
653
  } catch (error) {
751
- throw error
654
+ return { data: {}, error: new AuthError(error) }
752
655
  }
753
-
754
- return this.createLoginState()
755
656
  }
756
657
 
757
658
  /**
758
- * 小程序手机号授权登录,目前只支持全托管手机号授权登录
759
- * @returns {Promise<LoginState>}
760
- * @memberof Auth
761
- */
762
- // @catchErrorsDecorator({
763
- // title: '小程序手机号授权登录失败',
764
- // messages: [
765
- // '请确认以下各项:',
766
- // ' 1 - 当前环境是否开启了小程序手机号授权登录',
767
- // ' 2 - 调用 auth().signInWithPhoneAuth() 的语法或参数是否正确',
768
- // `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
769
- // ],
770
- // })
771
- // public async signInWithPhoneAuth({ phoneCode = '' }): Promise<LoginState> {
772
- // if (!adapterForWxMp.isMatch()) {
773
- // throw Error('wx api undefined')
774
- // }
775
- // const wxInfo = wx.getAccountInfoSync().miniProgram
776
- // const providerInfo = {
777
- // provider_params: { provider_code_type: 'phone' },
778
- // provider_id: wxInfo.appId,
779
- // }
780
-
781
- // const { code } = await wx.login()
782
- // ;(providerInfo as any).provider_code = code
783
-
784
- // try {
785
- // let providerToken = await this.oauthInstance.authApi.grantProviderToken(providerInfo)
786
- // if (providerToken.error_code) {
787
- // throw providerToken
788
- // }
789
-
790
- // providerToken = await this.oauthInstance.authApi.patchProviderToken({
791
- // provider_token: providerToken.provider_token,
792
- // provider_id: wxInfo.appId,
793
- // provider_params: {
794
- // code: phoneCode,
795
- // provider_code_type: 'phone',
796
- // },
797
- // })
798
- // if (providerToken.error_code) {
799
- // throw providerToken
800
- // }
801
-
802
- // const signInRes = await this.oauthInstance.authApi.signInWithProvider({
803
- // provider_token: providerToken.provider_token,
804
- // })
805
-
806
- // if ((signInRes as any)?.error_code) {
807
- // throw signInRes
808
- // }
809
- // } catch (error) {
810
- // throw error
811
- // }
812
-
813
- // return this.createLoginState()
814
- // }
815
-
816
- /**
817
- * 小程序短信验证码登陆
818
- * @returns {Promise<LoginState>}
819
- * @memberof Auth
659
+ * 校验第三方平台授权登录回调
660
+ * @param params
661
+ * @returns Promise<SignInRes>
820
662
  */
821
- @catchErrorsDecorator({
822
- title: '短信验证码登陆',
823
- messages: [
824
- '请确认以下各项:',
825
- ' 1 - 当前环境是否开启了小程序短信验证码登陆',
826
- ' 2 - 调用 auth().signInWithSms() 的语法或参数是否正确',
827
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
828
- ],
829
- })
830
- public async signInWithSms({
831
- verificationInfo = { verification_id: '', is_user: false },
832
- verificationCode = '',
833
- phoneNum = '',
834
- bindInfo = undefined,
835
- }): Promise<LoginState> {
663
+ async verifyOAuth(params?: VerifyOAuthReq): Promise<VerifyOAuthRes> {
664
+ const data: any = {}
836
665
  try {
837
- return this.signInWithUsername({
838
- verificationInfo,
839
- verificationCode,
840
- bindInfo,
841
- username: phoneNum,
842
- loginType: 'sms',
843
- })
844
- } catch (error) {
845
- throw error
846
- }
847
- }
666
+ // 回调至 provider_redirect_uri 地址(url query中携带 授权code,state等参数),此时检查 state 是否符合预期(如 自己设置的 wx_open)
667
+ const code = params?.code || utils.getQuery('code')
668
+ const state = params?.state || utils.getQuery('state')
848
669
 
849
- /**
850
- * 邮箱验证码登陆
851
- * @returns {Promise<LoginState>}
852
- * @memberof Auth
853
- */
854
- @catchErrorsDecorator({
855
- title: '邮箱验证码登陆',
856
- messages: [
857
- '请确认以下各项:',
858
- ' 1 - 当前环境是否开启了邮箱登陆',
859
- ' 2 - 调用 auth().signInWithEmail() 的语法或参数是否正确',
860
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
861
- ],
862
- })
863
- public async signInWithEmail({
864
- verificationInfo = { verification_id: '', is_user: false },
865
- verificationCode = '',
866
- bindInfo = undefined,
867
- email = '',
868
- }): Promise<LoginState> {
869
- try {
870
- return this.signInWithUsername({
871
- verificationInfo,
872
- verificationCode,
873
- bindInfo,
874
- username: email,
875
- loginType: 'email',
876
- })
877
- } catch (error) {
878
- throw error
879
- }
880
- }
670
+ // 参数校验:code和state必填
671
+ if (!code) {
672
+ return { data: {}, error: new AuthError({ message: 'Code is required' }) }
673
+ }
881
674
 
882
- /**
883
- * 设置获取自定义登录 ticket 函数
884
- * @param {authModels.GetCustomSignTicketFn} getTickFn
885
- * @memberof Auth
886
- */
887
- public setCustomSignFunc(getTickFn: authModels.GetCustomSignTicketFn): void {
888
- this.oauthInstance.authApi.setCustomSignFunc(getTickFn)
889
- }
675
+ if (!state) {
676
+ return { data: {}, error: new AuthError({ message: 'State is required' }) }
677
+ }
890
678
 
891
- /**
892
- *
893
- * @returns {Promise<LoginState>}
894
- * @memberof Auth
895
- */
896
- // @catchErrorsDecorator({
897
- // title: '自定义登录失败',
898
- // messages: [
899
- // '请确认以下各项:',
900
- // ' 1 - 当前环境是否开启了自定义登录',
901
- // ' 2 - 调用 auth().signInWithCustomTicket() 的语法或参数是否正确',
902
- // ' 3 - ticket 是否归属于当前环境',
903
- // ' 4 - 创建 ticket 的自定义登录私钥是否过期',
904
- // `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
905
- // ],
906
- // })
907
- // public async signInWithCustomTicket(): Promise<LoginState> {
908
- // await this.oauthInstance.authApi.signInWithCustomTicket()
909
- // return this.createLoginState()
910
- // }
679
+ const cacheData = getBrowserSession(state)
680
+ data.type = cacheData?.type
911
681
 
912
- /**
913
- *
914
- * @param {authModels.SignInRequest} params
915
- * @returns {Promise<LoginState>}
916
- * @memberof Auth
917
- */
918
- public async signIn(params: authModels.SignInRequest): Promise<LoginState> {
919
- await this.oauthInstance.authApi.signIn(params)
920
- return this.createLoginState(params)
921
- }
682
+ const provider = params?.provider || cacheData?.provider || utils.getQuery('provider')
922
683
 
923
- // /**
924
- // *
925
- // * @param {authModels.SignUpRequest} params
926
- // * @returns {Promise<LoginState>}
927
- // * @memberof Auth
928
- // */
929
- // @catchErrorsDecorator({
930
- // title: '注册失败',
931
- // messages: [
932
- // '请确认以下各项:',
933
- // ' 1 - 当前环境是否开启了指定登录方式',
934
- // ' 2 - 调用 auth().signUp() 的语法或参数是否正确',
935
- // `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
936
- // ],
937
- // })
938
- // public async signUp(params: authModels.SignUpRequest): Promise<any> {
939
- // await this.oauthInstance.authApi.signUp(params)
940
- // return this.createLoginState()
941
- // }
684
+ if (!provider) {
685
+ return { data, error: new AuthError({ message: 'Provider is required' }) }
686
+ }
942
687
 
943
- /**
944
- * 设置密码
945
- * @param {authModels.SetPasswordRequest} params
946
- * @returns {Promise<void>}
947
- * @memberof Auth
948
- */
949
- public async setPassword(params: authModels.SetPasswordRequest): Promise<void> {
950
- return this.oauthInstance.authApi.setPassword(params)
951
- }
688
+ // state符合预期,则获取该三方平台token
689
+ const { provider_token: token } = await this.grantProviderToken({
690
+ provider_id: provider,
691
+ provider_redirect_uri: location.origin + location.pathname, // 指定三方平台跳回的 url 地址
692
+ provider_code: code, // 第三方平台跳转回页面时,url param 中携带的 code 参数
693
+ })
952
694
 
953
- /**
954
- * 检测用户名是否已经占用
955
- * @param username
956
- */
957
- @catchErrorsDecorator({
958
- title: '获取用户是否被占用失败',
959
- messages: [
960
- '请确认以下各项:',
961
- ' 1 - 调用 auth().isUsernameRegistered() 的语法或参数是否正确',
962
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
963
- ],
964
- })
965
- public async isUsernameRegistered(username: string): Promise<boolean> {
966
- if (typeof username !== 'string') {
967
- throwError(ERRORS.INVALID_PARAMS, 'username must be a string')
968
- }
695
+ let res: VerifyOAuthRes
969
696
 
970
- const { exist } = await this.oauthInstance.authApi.checkIfUserExist({ username })
971
- return exist
972
- }
697
+ if (cacheData.type === OAUTH_TYPE.BIND_IDENTITY) {
698
+ res = await this.oauthInstance.authApi.toBindIdentity({ provider_token: token, provider, fireEvent: true })
699
+ } else {
700
+ // 通过 provider_token 仅登录或登录并注册(与云开发平台-登录方式-身份源登录模式配置有关)
701
+ res = await this.signInWithIdToken({
702
+ token,
703
+ })
704
+ res.data = { ...data, ...res.data }
705
+ }
973
706
 
974
- /**
975
- * 获取本地登录态-同步
976
- */
977
- public hasLoginState(): LoginState | null {
978
- if (this.cache.mode === 'async') {
979
- // async storage的平台调用此API提示
980
- printWarn(
981
- ERRORS.INVALID_OPERATION,
982
- 'current platform\'s storage is asynchronous, please use getLoginState instead',
707
+ const localSearch = new URLSearchParams(location?.search)
708
+ localSearch.delete('code')
709
+ localSearch.delete('state')
710
+ res.data.redirectUrl = addUrlSearch(
711
+ cacheData?.search === undefined ? `?${localSearch.toString()}` : cacheData?.search,
712
+ cacheData?.hash || location.hash,
983
713
  )
984
- return
985
- }
714
+ removeBrowserSession(state)
986
715
 
987
- const oauthLoginState = this.oauthInstance?.authApi.hasLoginStateSync()
988
- if (oauthLoginState) {
989
- const loginState = new LoginState({
990
- envId: this.config.env,
991
- cache: this.cache,
992
- oauthInstance: this.oauthInstance,
993
- })
994
- return loginState
716
+ return res
717
+ } catch (error) {
718
+ return { data, error: new AuthError(error) }
995
719
  }
996
- return null
997
720
  }
998
721
 
999
722
  /**
1000
- * 获取本地登录态-异步
1001
- * 此API为兼容异步storage的平台
723
+ * https://supabase.com/docs/reference/javascript/auth-signinwithoauth
724
+ * 生成第三方平台授权 Uri (如微信二维码扫码授权网页)
725
+ * @param params
726
+ * @returns Promise<SignInOAuthRes>
1002
727
  */
1003
- @catchErrorsDecorator({
1004
- title: '获取本地登录态失败',
1005
- messages: [
1006
- '请确认以下各项:',
1007
- ' 1 - 调用 auth().getLoginState() 的语法或参数是否正确',
1008
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1009
- ],
1010
- })
1011
- public async getLoginState() {
1012
- let oauthLoginState = null
1013
-
728
+ async signInWithOAuth(params: SignInWithOAuthReq): Promise<SignInOAuthRes> {
1014
729
  try {
1015
- oauthLoginState = await this.oauthInstance.authApi.getLoginState()
1016
- } catch (error) {
1017
- return null
1018
- }
1019
-
1020
- if (oauthLoginState) {
1021
- const loginState = new LoginState({
1022
- envId: this.config.env,
1023
- cache: this.cache,
1024
- oauthInstance: this.oauthInstance,
730
+ // 参数校验:provider必填
731
+ this.validateParams(params, {
732
+ provider: { required: true, message: 'Provider is required' },
1025
733
  })
1026
- return loginState
1027
- }
1028
734
 
1029
- return null
1030
- }
735
+ const href = params.options?.redirectTo || location.href
1031
736
 
1032
- @catchErrorsDecorator({
1033
- title: '获取用户信息失败',
1034
- messages: [
1035
- '请确认以下各项:',
1036
- ' 1 - 是否已登录',
1037
- ' 2 - 调用 auth().getUserInfo() 的语法或参数是否正确',
1038
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1039
- ],
1040
- })
1041
- public async getUserInfo(): Promise<(authModels.UserInfo & Partial<User>) | null> {
1042
- return this.getCurrentUser()
1043
- }
737
+ const urlObject = new URL(href)
1044
738
 
1045
- @catchErrorsDecorator({
1046
- title: '获取微搭插件用户信息失败',
1047
- messages: [
1048
- '请确认以下各项:',
1049
- ' 1 - 是否已登录',
1050
- ' 2 - 调用 auth().getWedaUserInfo() 的语法或参数是否正确',
1051
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1052
- ],
1053
- })
1054
- public async getWedaUserInfo(): Promise<any> {
1055
- return this.oauthInstance.authApi.getWedaUserInfo()
1056
- }
739
+ const provider_redirect_uri = urlObject.origin + urlObject.pathname
1057
740
 
1058
- public async updateUserBasicInfo(params: authModels.ModifyUserBasicInfoRequest) {
1059
- const loginState = await this.getLoginState()
1060
- if (loginState) {
1061
- await (loginState.user as User).updateUserBasicInfo(params)
1062
- }
1063
- return
1064
- }
741
+ const state = params.options?.state || `prd-${params.provider}-${Math.random().toString(36)
742
+ .slice(2)}`
1065
743
 
1066
- /**
1067
- * getAuthHeader 兼容处理
1068
- * 返回空对象
1069
- */
1070
- public getAuthHeader(): {} {
1071
- console.error('Auth.getAuthHeader API 已废弃')
1072
- return {}
1073
- }
744
+ const { uri } = await this.genProviderRedirectUri({
745
+ provider_id: params.provider,
746
+ provider_redirect_uri,
747
+ state,
748
+ })
1074
749
 
1075
- /**
1076
- * 为已有账户绑第三方账户
1077
- * @param {authModels.BindWithProviderRequest} params
1078
- * @returns {Promise<void>}
1079
- * @memberof Auth
1080
- */
1081
- @catchErrorsDecorator({
1082
- title: '绑定第三方登录方式失败',
1083
- messages: [
1084
- '请确认以下各项:',
1085
- ' 1 - 调用 auth().bindWithProvider() 的语法或参数是否正确',
1086
- ' 2 - 此账户是否已经绑定此第三方',
1087
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1088
- ],
1089
- })
1090
- public async bindWithProvider(params: authModels.BindWithProviderRequest): Promise<void> {
1091
- return this.oauthInstance.authApi.bindWithProvider(params)
1092
- }
750
+ // 对 URL 进行解码
751
+ const decodedUri = decodeURIComponent(uri)
1093
752
 
1094
- /**
1095
- * 查询用户
1096
- * @param {authModels.QueryUserProfileRequest} appended_params
1097
- * @returns {Promise<authModels.UserProfile>}
1098
- * @memberof Auth
1099
- */
1100
- public async queryUser(queryObj: authModels.QueryUserProfileRequest): Promise<authModels.QueryUserProfileResponse> {
1101
- return this.oauthInstance.authApi.queryUserProfile(queryObj)
1102
- }
753
+ // 合并额外的查询参数
754
+ let finalUri = decodedUri
1103
755
 
1104
- public async getAccessToken() {
1105
- const oauthAccessTokenRes = await this.oauthInstance.oauth2client.getAccessToken()
1106
- return {
1107
- accessToken: oauthAccessTokenRes,
1108
- env: this.config.env,
1109
- }
1110
- }
756
+ if (params.options?.queryParams) {
757
+ const url = new URL(decodedUri)
758
+ Object.entries(params.options.queryParams).forEach(([key, value]) => {
759
+ url.searchParams.set(key, value)
760
+ })
761
+ finalUri = url.toString()
762
+ }
1111
763
 
1112
- public async grantProviderToken(params: authModels.GrantProviderTokenRequest,): Promise<authModels.GrantProviderTokenResponse> {
1113
- return this.oauthInstance.authApi.grantProviderToken(params)
1114
- }
764
+ saveToBrowserSession(state, {
765
+ provider: params.provider,
766
+ search: urlObject.search,
767
+ hash: urlObject.hash,
768
+ type: params.options?.type || OAUTH_TYPE.SIGN_IN,
769
+ })
1115
770
 
1116
- public async patchProviderToken(params: authModels.PatchProviderTokenRequest,): Promise<authModels.PatchProviderTokenResponse> {
1117
- return this.oauthInstance.authApi.patchProviderToken(params)
771
+ if (isBrowser() && !params.options?.skipBrowserRedirect) {
772
+ window.location.assign(finalUri)
773
+ }
774
+
775
+ return { data: { url: finalUri, provider: params.provider }, error: null }
776
+ } catch (error) {
777
+ return { data: {}, error: new AuthError(error) }
778
+ }
1118
779
  }
1119
780
 
1120
- public async signInWithProvider(params: authModels.SignInWithProviderRequest): Promise<LoginState> {
1121
- await this.oauthInstance.authApi.signInWithProvider(params)
1122
- return this.createLoginState(params)
781
+ // https://supabase.com/docs/reference/javascript/auth-getclaims
782
+ async getClaims(): Promise<GetClaimsRes> {
783
+ try {
784
+ const { accessToken } = await this.getAccessToken()
785
+ const parsedToken = weAppJwtDecodeAll(accessToken)
786
+ return { data: parsedToken, error: null }
787
+ } catch (error) {
788
+ return { data: {}, error: new AuthError(error) }
789
+ }
1123
790
  }
1124
791
 
1125
- public async signInWithWechat(params: any = {}) {
1126
- await this.oauthInstance.authApi.signInWithWechat(params)
1127
- return this.createLoginState(params)
1128
- }
792
+ /**
793
+ * https://supabase.com/docs/reference/javascript/auth-resetpasswordforemail
794
+ * 通过 email 或手机号重置密码
795
+ * @param emailOrPhone 邮箱或手机号
796
+ * @returns Promise<ResetPasswordForEmailRes>
797
+ */
798
+ async resetPasswordForEmail(
799
+ emailOrPhone: string,
800
+ options?: { redirectTo?: string },
801
+ ): Promise<ResetPasswordForEmailRes> {
802
+ try {
803
+ // 参数校验:emailOrPhone必填
804
+ this.validateParams(
805
+ { emailOrPhone },
806
+ {
807
+ emailOrPhone: { required: true, message: 'Email or phone is required' },
808
+ },
809
+ )
1129
810
 
1130
- public async grantToken(params: authModels.GrantTokenRequest): Promise<LoginState> {
1131
- await this.oauthInstance.authApi.grantToken(params)
1132
- return this.createLoginState()
1133
- }
811
+ const { redirectTo } = options || {}
1134
812
 
1135
- public async genProviderRedirectUri(params: authModels.GenProviderRedirectUriRequest,): Promise<authModels.GenProviderRedirectUriResponse> {
1136
- return this.oauthInstance.authApi.genProviderRedirectUri(params)
1137
- }
813
+ // 判断是邮箱还是手机号
814
+ const isEmail = emailOrPhone.includes('@')
815
+ let verificationParams: { email?: string; phone_number?: string }
1138
816
 
1139
- public async resetPassword(params: authModels.ResetPasswordRequest): Promise<void> {
1140
- return this.oauthInstance.authApi.resetPassword(params)
1141
- }
817
+ if (isEmail) {
818
+ verificationParams = { email: emailOrPhone }
819
+ } else {
820
+ // 正规化手机号
821
+ const formattedPhone = this.formatPhone(emailOrPhone)
822
+ verificationParams = { phone_number: formattedPhone }
823
+ }
1142
824
 
1143
- public async deviceAuthorize(params: authModels.DeviceAuthorizeRequest): Promise<authModels.DeviceAuthorizeResponse> {
1144
- return this.oauthInstance.authApi.deviceAuthorize(params)
1145
- }
825
+ // 第一步:发送验证码并存储 verificationInfo
826
+ const verificationInfo = await this.getVerification(verificationParams)
1146
827
 
1147
- public async sudo(params: authModels.SudoRequest): Promise<authModels.SudoResponse> {
1148
- return this.oauthInstance.authApi.sudo(params)
1149
- }
828
+ return {
829
+ data: {
830
+ // 第二步:等待用户输入验证码(通过 Promise 包装用户输入事件)
831
+ updateUser: async (attributes: UpdateUserAttributes): Promise<SignInRes> => {
832
+ this.validateParams(attributes, {
833
+ nonce: { required: true, message: 'Nonce is required' },
834
+ password: { required: true, message: 'Password is required' },
835
+ })
836
+ try {
837
+ // 第三步:待用户输入完验证码之后,验证验证码
838
+ const verificationTokenRes = await this.verify({
839
+ verification_id: verificationInfo.verification_id,
840
+ verification_code: attributes.nonce,
841
+ })
1150
842
 
1151
- public async deleteMe(params: authModels.WithSudoRequest): Promise<authModels.UserProfile> {
1152
- return this.oauthInstance.authApi.deleteMe(params)
1153
- }
843
+ await this.oauthInstance.authApi.resetPassword({
844
+ email: isEmail ? emailOrPhone : undefined,
845
+ phone_number: !isEmail ? emailOrPhone : undefined,
846
+ new_password: attributes.password,
847
+ verification_token: verificationTokenRes.verification_token,
848
+ })
1154
849
 
1155
- public async getProviders(): Promise<authModels.ProvidersResponse> {
1156
- return this.oauthInstance.authApi.getProviders()
1157
- }
850
+ this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, {
851
+ event: AUTH_STATE_CHANGED_TYPE.PASSWORD_RECOVERY,
852
+ })
1158
853
 
1159
- public async loginScope(): Promise<string> {
1160
- return this.oauthInstance.authApi.loginScope()
1161
- }
854
+ const res = await this.signInWithPassword({
855
+ email: isEmail ? emailOrPhone : undefined,
856
+ phone: !isEmail ? emailOrPhone : undefined,
857
+ password: attributes.password,
858
+ })
1162
859
 
1163
- public async loginGroups(): Promise<string[]> {
1164
- return this.oauthInstance.authApi.loginGroups()
1165
- }
860
+ if (redirectTo && isBrowser()) {
861
+ window.location.assign(redirectTo)
862
+ }
1166
863
 
1167
- public async onLoginStateChanged(callback: Function) {
1168
- this.config.eventBus?.on(EVENTS.LOGIN_STATE_CHANGED, async (params) => {
1169
- // getLoginState会重复触发getCredentials,导致死循环,所以getCredentials出错不再出发getLoginState
1170
- const loginState = params?.data?.eventType !== LOGIN_STATE_CHANGED_TYPE.CREDENTIALS_ERROR ? await this.getLoginState() : {}
1171
- callback.call(this, { ...params, ...loginState })
1172
- })
1173
- // 立刻执行一次回调
1174
- const loginState = await this.getLoginState()
1175
- callback.call(this, loginState)
864
+ return res
865
+ } catch (error) {
866
+ return { data: {}, error: new AuthError(error) }
867
+ }
868
+ },
869
+ },
870
+ error: null,
871
+ }
872
+ } catch (error) {
873
+ return { data: {}, error: new AuthError(error) }
874
+ }
1176
875
  }
1177
876
 
1178
877
  /**
1179
- * 强制刷新token
1180
- * @param params
878
+ * 通过旧密码重置密码
879
+ * @param new_password
880
+ * @param old_password
1181
881
  * @returns
1182
882
  */
1183
- public async refreshTokenForce(params: { version?: string }): Promise<Credentials> {
1184
- return this.oauthInstance.authApi.refreshTokenForce(params)
883
+ async resetPasswordForOld(params: ResetPasswordForOldReq) {
884
+ try {
885
+ await this.oauthInstance.authApi.updatePasswordByOld({
886
+ old_password: params.old_password,
887
+ new_password: params.new_password,
888
+ })
889
+
890
+ const { data: { session } = {} } = await this.getSession()
891
+
892
+ return { data: { user: session.user, session }, error: null }
893
+ } catch (error) {
894
+ return { data: {}, error: new AuthError(error) }
895
+ }
1185
896
  }
1186
897
 
1187
898
  /**
1188
- * 获取身份信息
1189
- * @returns
899
+ * https://supabase.com/docs/reference/javascript/auth-verifyotp
900
+ * Log in a user given a User supplied OTP and verificationId received through mobile or email.
901
+ * @param params
902
+ * @returns Promise<SignInRes>
1190
903
  */
1191
- public async getCredentials(): Promise<Credentials> {
1192
- return this.oauthInstance.authApi.getCredentials()
904
+ async verifyOtp(params: VerifyOtpReq): Promise<SignInRes> {
905
+ try {
906
+ const { type } = params
907
+ // 参数校验:token和verificationInfo必填
908
+ this.validateParams(params, {
909
+ token: { required: true, message: 'Token is required' },
910
+ messageId: { required: true, message: 'messageId is required' },
911
+ })
912
+
913
+ if (['phone_change', 'email_change'].includes(type)) {
914
+ await this.verify({
915
+ verification_id: params.messageId,
916
+ verification_code: params.token,
917
+ })
918
+ } else {
919
+ await this.signInWithUsername({
920
+ verificationInfo: { verification_id: params.messageId, is_user: true },
921
+ verificationCode: params.token,
922
+ username: params.email || this.formatPhone(params.phone) || '',
923
+ loginType: params.email ? 'email' : 'phone',
924
+ })
925
+ }
926
+
927
+ const { data: { session } = {} } = await this.getSession()
928
+
929
+ return { data: { user: session.user, session }, error: null }
930
+ } catch (error) {
931
+ return { data: {}, error: new AuthError(error) }
932
+ }
1193
933
  }
934
+
1194
935
  /**
1195
- * 写入身份信息
936
+ * https://supabase.com/docs/reference/javascript/auth-getSession
937
+ * Returns the session, refreshing it if necessary.
938
+ * @returns Promise<SignInRes>
1196
939
  */
1197
- public async setCredentials(credentials: Credentials) {
1198
- await this.oauthInstance.oauth2client.setCredentials(credentials)
1199
- }
1200
-
1201
- @catchErrorsDecorator({
1202
- title: '获取身份源类型',
1203
- messages: [
1204
- '请确认以下各项:',
1205
- ' 1 - 调用 auth().getProviderSubType() 的语法或参数是否正确',
1206
- `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1207
- ],
1208
- })
1209
- public async getProviderSubType(): Promise<authModels.ProviderSubType> {
1210
- return this.oauthInstance.authApi.getProviderSubType()
1211
- }
940
+ async getSession(): Promise<SignInRes> {
941
+ try {
942
+ const credentials: Credentials = await this.oauthInstance.oauth2client.getCredentials()
1212
943
 
1213
- public async createCaptchaData(params: { state: string; redirect_uri?: string }) {
1214
- return this.oauthInstance.authApi.createCaptchaData(params)
1215
- }
944
+ if (!credentials || credentials.scope === 'accessKey') {
945
+ return { data: { session: null }, error: null }
946
+ }
1216
947
 
1217
- public async verifyCaptchaData(params: { token: string; key: string }) {
1218
- return this.oauthInstance.authApi.verifyCaptchaData(params)
1219
- }
948
+ const { data: { user } = {} } = await this.getUser()
1220
949
 
1221
- public async getMiniProgramQrCode(params: authModels.GetMiniProgramQrCodeRequest,): Promise<authModels.GetMiniProgramQrCodeResponse> {
1222
- return this.oauthInstance.authApi.getMiniProgramCode(params)
950
+ return { data: { session: { ...credentials, user }, user }, error: null }
951
+ } catch (error) {
952
+ return { data: {}, error: new AuthError(error) }
953
+ }
1223
954
  }
1224
955
 
1225
- public async getMiniProgramQrCodeStatus(params: authModels.GetMiniProgramQrCodeStatusRequest,): Promise<authModels.GetMiniProgramQrCodeStatusResponse> {
1226
- return this.oauthInstance.authApi.getMiniProgramQrCodeStatus(params)
1227
- }
956
+ /**
957
+ * https://supabase.com/docs/reference/javascript/auth-refreshsession
958
+ * 无论过期状态如何,都返回一个新的会话
959
+ * @param refresh_token
960
+ * @returns Promise<SignInRes>
961
+ */
962
+ async refreshSession(refresh_token?: string): Promise<SignInRes> {
963
+ try {
964
+ const credentials: Credentials = await this.oauthInstance.oauth2client.localCredentials.getCredentials()
965
+ credentials.refresh_token = refresh_token || credentials.refresh_token
966
+ const newTokens = await this.oauthInstance.oauth2client.refreshToken(credentials)
967
+ const { data: { user } = {} } = await this.getUser()
1228
968
 
1229
- public async modifyPassword(params: authModels.ModifyUserBasicInfoRequest): Promise<void> {
1230
- return this.oauthInstance.authApi.modifyPassword(params)
969
+ return { data: { user, session: { ...newTokens, user } }, error: null }
970
+ } catch (error) {
971
+ return { data: {}, error: new AuthError(error) }
972
+ }
1231
973
  }
1232
974
 
1233
- public async modifyPasswordWithoutLogin(params: authModels.ModifyPasswordWithoutLoginRequest): Promise<void> {
1234
- return this.oauthInstance.authApi.modifyPasswordWithoutLogin(params)
975
+ /**
976
+ * https://supabase.com/docs/reference/javascript/auth-getuser
977
+ * 如果存在现有会话,则获取当前用户详细信息
978
+ * @returns Promise<GetUserRes>
979
+ */
980
+ async getUser(): Promise<GetUserRes> {
981
+ try {
982
+ const user = this.convertToUser(await this.getCurrentUser())
983
+ return { data: { user }, error: null }
984
+ } catch (error) {
985
+ return { data: {}, error: new AuthError(error) }
986
+ }
1235
987
  }
1236
988
 
1237
- public async getUserBehaviorLog(params: authModels.GetUserBehaviorLog): Promise<authModels.GetUserBehaviorLogRes> {
1238
- return this.oauthInstance.authApi.getUserBehaviorLog(params)
989
+ /**
990
+ * 刷新用户信息
991
+ * @returns Promise<CommonRes>
992
+ */
993
+ async refreshUser(): Promise<CommonRes> {
994
+ try {
995
+ await this.currentUser.refresh()
996
+
997
+ const { data: { session } = {} } = await this.getSession()
998
+ return { data: { user: session.user, session }, error: null }
999
+ } catch (error) {
1000
+ return { data: {}, error: new AuthError(error) }
1001
+ }
1239
1002
  }
1240
1003
 
1241
1004
  /**
1242
- * sms/email 验证码登录/注册,逻辑一致收敛
1005
+ * https://supabase.com/docs/reference/javascript/auth-updateuser
1006
+ * 更新用户信息
1007
+ * @param params
1008
+ * @returns Promise<GetUserRes | UpdateUserWithVerificationRes>
1243
1009
  */
1244
- public async signInWithUsername({
1245
- verificationInfo = { verification_id: '', is_user: false },
1246
- verificationCode = '',
1247
- username: rawUsername = '',
1248
- bindInfo = undefined,
1249
- loginType = '',
1250
- }: {
1251
- verificationInfo?: authModels.GetVerificationResponse
1252
- verificationCode?: string
1253
- username?: string
1254
- bindInfo?: any
1255
- loginType?: string
1256
- }): Promise<LoginState> {
1010
+ async updateUser(params: UpdateUserReq): Promise<GetUserRes | UpdateUserWithVerificationRes> {
1257
1011
  try {
1258
- // 1. 验证验证码
1259
- const verifyRes = await this.oauthInstance.authApi.verify({
1260
- verification_id: verificationInfo.verification_id,
1261
- verification_code: verificationCode,
1262
- })
1263
-
1264
- if ((verifyRes as any)?.error_code) {
1265
- throw verifyRes
1012
+ // 参数校验:至少有一个更新字段被提供
1013
+ const hasValue = Object.keys(params).some(key => params[key] !== undefined && params[key] !== null && params[key] !== '',)
1014
+ if (!hasValue) {
1015
+ throw new AuthError({ message: 'At least one field must be provided for update' })
1266
1016
  }
1267
1017
 
1268
- // eslint-disable-next-line @typescript-eslint/naming-convention
1269
- const { verification_token } = verifyRes
1018
+ const { email, phone, ...restParams } = params
1270
1019
 
1271
- // 手机登录参数
1272
- let username = /^\+\d{1,3}\s+/.test(rawUsername) ? rawUsername : `+86 ${rawUsername}`
1273
- let signUpParam: any = { phone_number: username }
1020
+ // 检查是否需要更新 email 或 phone
1021
+ const needsEmailVerification = email !== undefined
1022
+ const needsPhoneVerification = phone !== undefined
1274
1023
 
1275
- // 邮箱登录参数
1276
- if (loginType === 'email') {
1277
- username = rawUsername
1278
- signUpParam = { email: username }
1279
- }
1024
+ let extraRes = {}
1280
1025
 
1281
- // 2. 根据是否已经是用户,分别走登录或注册逻辑
1282
- if (verificationInfo.is_user) {
1283
- // 私有化环境或者自定义应用走v1版本的老逻辑
1284
- const signInRes = await this.oauthInstance.authApi.signIn({
1285
- username,
1286
- verification_token,
1287
- })
1026
+ if (needsEmailVerification || needsPhoneVerification) {
1027
+ // 需要发送验证码
1028
+ let verificationParams: { email?: string; phone_number?: string }
1029
+ let verificationType: 'email_change' | 'phone_change'
1288
1030
 
1289
- if ((signInRes as any)?.error_code) {
1290
- throw signInRes
1031
+ if (needsEmailVerification) {
1032
+ verificationParams = { email: params.email }
1033
+ verificationType = 'email_change'
1034
+ } else {
1035
+ // 正规化手机号
1036
+ const formattedPhone = this.formatPhone(params.phone)
1037
+ verificationParams = { phone_number: formattedPhone }
1038
+ verificationType = 'phone_change'
1291
1039
  }
1292
1040
 
1293
- if (bindInfo) {
1294
- const bindRes = await this.oauthInstance.authApi.bindWithProvider({
1295
- provider_token: (bindInfo as any)?.providerToken,
1296
- })
1041
+ // 发送验证码
1042
+ const verificationInfo = await this.getVerification(verificationParams)
1297
1043
 
1298
- if ((bindRes as any)?.error_code) {
1299
- throw bindRes
1300
- }
1301
- }
1302
- } else {
1303
- // 自定义应用走signUp逻辑
1304
- const signUpRes = await this.oauthInstance.authApi.signUp({
1305
- ...signUpParam,
1306
- verification_token,
1307
- provider_token: (bindInfo as any)?.providerId,
1308
- })
1044
+ Object.keys(restParams).length > 0 && (await this.updateUserBasicInfo(restParams))
1309
1045
 
1310
- if ((signUpRes as any)?.error_code) {
1311
- throw signUpRes
1046
+ extraRes = {
1047
+ messageId: verificationInfo.verification_id,
1048
+ verifyOtp: async (verifyParams: { email?: string; phone?: string; token: string }): Promise<GetUserRes> => {
1049
+ try {
1050
+ if (verifyParams.email && params.email === verifyParams.email) {
1051
+ // 验证码验证
1052
+ await this.verifyOtp({
1053
+ type: 'email_change',
1054
+ email: params.email,
1055
+ token: verifyParams.token,
1056
+ messageId: verificationInfo.verification_id,
1057
+ })
1058
+ await this.updateUserBasicInfo({ email: params.email })
1059
+ } else if (verifyParams.phone && params.phone === verifyParams.phone) {
1060
+ // 验证码验证
1061
+ await this.verifyOtp({
1062
+ type: 'phone_change',
1063
+ phone: params.phone,
1064
+ token: verifyParams.token,
1065
+ messageId: verificationInfo.verification_id,
1066
+ })
1067
+ await this.updateUserBasicInfo({ phone: this.formatPhone(params.phone) })
1068
+ } else {
1069
+ await this.verifyOtp({
1070
+ type: verificationType,
1071
+ email: needsEmailVerification ? params.email : undefined,
1072
+ phone: !needsEmailVerification ? params.phone : undefined,
1073
+ token: verifyParams.token,
1074
+ messageId: verificationInfo.verification_id,
1075
+ })
1076
+ // 验证成功后更新用户信息
1077
+ await this.updateUserBasicInfo(params)
1078
+ }
1079
+
1080
+ const {
1081
+ data: { user },
1082
+ } = await this.getUser()
1083
+ this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.USER_UPDATED })
1084
+
1085
+ return { data: { user }, error: null }
1086
+ } catch (error) {
1087
+ return { data: {}, error: new AuthError(error) }
1088
+ }
1089
+ },
1312
1090
  }
1091
+ } else {
1092
+ // 不需要验证,直接更新
1093
+ await this.updateUserBasicInfo(params)
1313
1094
  }
1095
+ const {
1096
+ data: { user },
1097
+ } = await this.getUser()
1098
+ this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.USER_UPDATED })
1314
1099
 
1315
- return this.createLoginState()
1100
+ return { data: { user, ...extraRes }, error: null }
1316
1101
  } catch (error) {
1317
- throw error
1102
+ return { data: {}, error: new AuthError(error) }
1318
1103
  }
1319
1104
  }
1320
1105
 
1321
- async createLoginState(
1322
- params?: { version?: string; query?: any },
1323
- options?: { asyncRefreshUser?: boolean; userInfo?: any },
1324
- ): Promise<LoginState> {
1325
- const loginState = new LoginState({
1326
- envId: this.config.env,
1327
- cache: this.cache,
1328
- oauthInstance: this.oauthInstance,
1329
- })
1330
-
1331
- await loginState.checkLocalStateAsync()
1106
+ /**
1107
+ * https://supabase.com/docs/reference/javascript/auth-getuseridentities
1108
+ * 获取所有身份源
1109
+ * @returns Promise<GetUserIdentitiesRes>
1110
+ */
1111
+ async getUserIdentities(): Promise<GetUserIdentitiesRes> {
1112
+ try {
1113
+ const providers = await this.oauthInstance.authApi.getProviders()
1332
1114
 
1333
- if (options?.userInfo) {
1334
- loginState.user.setLocalUserInfo(options.userInfo)
1335
- } else {
1336
- if (options?.asyncRefreshUser) {
1337
- loginState.user.refresh(params)
1338
- } else {
1339
- await loginState.user.refresh(params)
1115
+ return {
1116
+ data: {
1117
+ identities: providers?.data?.filter(v => !!v.bind) as unknown as GetUserIdentitiesRes['data']['identities'],
1118
+ },
1119
+ error: null,
1340
1120
  }
1121
+ } catch (error) {
1122
+ return { data: {}, error: new AuthError(error) }
1341
1123
  }
1124
+ }
1342
1125
 
1343
- this.config.eventBus?.fire(EVENTS.LOGIN_STATE_CHANGED, { eventType: LOGIN_STATE_CHANGED_TYPE.SIGN_IN })
1126
+ /**
1127
+ * https://supabase.com/docs/reference/javascript/auth-linkidentity
1128
+ * 绑定身份源到当前用户
1129
+ * @param params
1130
+ * @returns Promise<LinkIdentityRes>
1131
+ */
1132
+ async linkIdentity(params: LinkIdentityReq): Promise<LinkIdentityRes> {
1133
+ try {
1134
+ // 参数校验:provider必填
1135
+ this.validateParams(params, {
1136
+ provider: { required: true, message: 'Provider is required' },
1137
+ })
1344
1138
 
1345
- this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.SIGNED_IN })
1346
- return loginState
1347
- }
1139
+ await this.signInWithOAuth({
1140
+ provider: params.provider,
1141
+ options: {
1142
+ type: OAUTH_TYPE.BIND_IDENTITY,
1143
+ },
1144
+ })
1348
1145
 
1349
- async setAccessKey() {
1350
- if (this.config.accessKey) {
1351
- try {
1352
- this.oauthInstance.oauth2client.setAccessKeyCredentials({
1353
- access_token: this.config.accessKey,
1354
- token_type: 'Bearer',
1355
- scope: 'accessKey',
1356
- expires_at: new Date(+new Date() + +new Date()),
1357
- expires_in: +new Date() + +new Date(),
1358
- })
1359
- } catch (error) {
1360
- console.warn('accessKey error: ', error)
1361
- }
1146
+ return { data: { provider: params.provider }, error: null }
1147
+ } catch (error) {
1148
+ return { data: {}, error: new AuthError(error) }
1362
1149
  }
1363
1150
  }
1364
1151
 
1365
- // ========== new auth api methods merged below ==========
1366
-
1367
1152
  /**
1368
- * https://supabase.com/docs/reference/javascript/auth-signinanonymously
1369
- * Sign in a user anonymously.
1370
- * const { data, error } = await auth.signInAnonymously();
1153
+ * https://supabase.com/docs/reference/javascript/auth-unlinkidentity
1154
+ * 解绑身份源
1371
1155
  * @param params
1372
- * @returns Promise<SignInRes>
1156
+ * @returns Promise<CommonRes>
1373
1157
  */
1374
- async signInAnonymously(params: SignInAnonymouslyReq): Promise<SignInRes> {
1158
+ async unlinkIdentity(params: UnlinkIdentityReq): Promise<CommonRes> {
1375
1159
  try {
1376
- await this.oauthInstance.authApi.signInAnonymously(params)
1377
- const loginState = await this.createLoginState()
1160
+ // 参数校验:provider必填
1161
+ this.validateParams(params, {
1162
+ provider: { required: true, message: 'Provider is required' },
1163
+ })
1378
1164
 
1379
- const { data: { session } = {} } = await this.getSession()
1165
+ await this.oauthInstance.authApi.unbindProvider({ provider_id: params.provider })
1380
1166
 
1381
- // loginState返回是为了兼容v2版本
1382
- return { ...(loginState as any), data: { user: session.user, session }, error: null }
1167
+ return { data: {}, error: null }
1383
1168
  } catch (error) {
1384
1169
  return { data: {}, error: new AuthError(error) }
1385
1170
  }
1386
1171
  }
1387
1172
 
1388
1173
  /**
1389
- * https://supabase.com/docs/reference/javascript/auth-signup
1390
- * 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.
1391
- * @param params
1392
- * @returns Promise<SignUpRes>
1393
- */
1394
- async signUp(params: authModels.SignUpRequest & { phone?: string }): Promise<SignUpRes> {
1395
- if (params.phone_number || params.verification_code || params.verification_token || params.provider_token) {
1396
- params.phone_number = this.formatPhone(params.phone_number)
1397
- await this.oauthInstance.authApi.signUp(params)
1398
- return this.createLoginState() as any
1399
- }
1174
+ * https://supabase.com/docs/reference/javascript/auth-reauthentication
1175
+ * 重新认证
1176
+ * @returns Promise<ReauthenticateRes>
1177
+ */
1178
+ async reauthenticate(): Promise<ReauthenticateRes> {
1400
1179
  try {
1401
- // 参数校验:email或phone必填其一
1402
- this.validateAtLeastOne(params, [['email'], ['phone']], 'You must provide either an email or phone number')
1180
+ const {
1181
+ data: { user },
1182
+ } = await this.getUser()
1183
+
1184
+ this.validateAtLeastOne(user, [['email', 'phone']], 'You must provide either an email or phone number')
1185
+ const userInfo = user.email ? { email: user.email } : { phone_number: this.formatPhone(user.phone) }
1403
1186
 
1404
1187
  // 第一步:发送验证码并存储 verificationInfo
1405
- const verificationInfo = await this.getVerification(params.email ? { email: params.email } : { phone_number: this.formatPhone(params.phone) },)
1188
+ const verificationInfo = await this.getVerification(userInfo)
1406
1189
 
1407
1190
  return {
1408
1191
  data: {
1409
1192
  // 第二步:等待用户输入验证码(通过 Promise 包装用户输入事件)
1410
- verifyOtp: async ({ token, messageId = verificationInfo.verification_id }): Promise<SignInRes> => {
1193
+ updateUser: async (attributes: UpdateUserAttributes): Promise<SignInRes> => {
1194
+ this.validateParams(attributes, {
1195
+ nonce: { required: true, message: 'Nonce is required' },
1196
+ })
1411
1197
  try {
1412
- // 第三步:待用户输入完验证码之后,验证短信验证码
1413
- const verificationTokenRes = await this.verify({
1414
- verification_id: messageId || verificationInfo.verification_id,
1415
- verification_code: token,
1416
- })
1198
+ if (attributes.password) {
1199
+ // 第三步:待用户输入完验证码之后,验证验证码
1200
+ const verificationTokenRes = await this.verify({
1201
+ verification_id: verificationInfo.verification_id,
1202
+ verification_code: attributes.nonce,
1203
+ })
1417
1204
 
1418
- // 第四步:注册并登录或直接登录
1419
- // 如果用户已经存在,直接登录
1420
- if (verificationInfo.is_user) {
1421
- await this.signIn({
1422
- username: params.email || this.formatPhone(params.phone),
1205
+ // 第四步:获取 sudo_token
1206
+ const sudoRes = await this.oauthInstance.authApi.sudo({
1423
1207
  verification_token: verificationTokenRes.verification_token,
1424
1208
  })
1425
- } else {
1426
- // 如果用户不存在,注册用户
1427
- const data = JSON.parse(JSON.stringify(params))
1428
- delete data.email
1429
- delete data.phone
1430
1209
 
1431
- await this.oauthInstance.authApi.signUp({
1432
- ...data,
1433
- ...(params.email ? { email: params.email } : { phone_number: this.formatPhone(params.phone) }),
1434
- verification_token: verificationTokenRes.verification_token,
1435
- verification_code: token,
1210
+ await this.oauthInstance.authApi.setPassword({
1211
+ new_password: attributes.password,
1212
+ sudo_token: sudoRes.sudo_token,
1213
+ })
1214
+ } else {
1215
+ await this.signInWithUsername({
1216
+ verificationInfo,
1217
+ verificationCode: attributes.nonce,
1218
+ ...userInfo,
1219
+ loginType: userInfo.email ? 'email' : 'phone',
1436
1220
  })
1437
- await this.createLoginState()
1438
1221
  }
1439
1222
 
1440
1223
  const { data: { session } = {} } = await this.getSession()
@@ -1453,1009 +1236,1228 @@ class Auth {
1453
1236
  }
1454
1237
 
1455
1238
  /**
1456
- * https://supabase.com/docs/reference/javascript/auth-signout
1457
- * const result = await auth.signOut();
1458
- *
1239
+ * https://supabase.com/docs/reference/javascript/auth-resend
1240
+ * 重新发送验证码
1459
1241
  * @param params
1242
+ * @returns Promise<ResendRes>
1460
1243
  */
1461
- async signOut(params?: authModels.SignoutRequest,): Promise<authModels.SignoutResponse & { data: Object; error: AuthError }> {
1244
+ async resend(params: ResendReq): Promise<ResendRes> {
1462
1245
  try {
1463
- const { userInfoKey } = this.cache.keys
1464
- const res = await this.oauthInstance.authApi.signOut(params)
1465
- await this.cache.removeStoreAsync(userInfoKey)
1466
- this.setAccessKey()
1246
+ // 参数校验:email或phone必填其一
1247
+ this.validateAtLeastOne(params, [['email'], ['phone']], 'You must provide either an email or phone number')
1467
1248
 
1468
- this.config.eventBus?.fire(EVENTS.LOGIN_STATE_CHANGED, { eventType: LOGIN_STATE_CHANGED_TYPE.SIGN_OUT })
1249
+ const target = params.type === 'signup' ? 'ANY' : 'USER'
1250
+ const data: { email?: string; phone_number?: string; target: 'USER' | 'ANY' } = { target }
1251
+ if ('email' in params) {
1252
+ data.email = params.email
1253
+ }
1469
1254
 
1470
- this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.SIGNED_OUT })
1255
+ if ('phone' in params) {
1256
+ data.phone_number = this.formatPhone(params.phone)
1257
+ }
1471
1258
 
1472
- // res返回是为了兼容v2版本
1473
- return { ...res, data: {}, error: null }
1474
- } catch (error) {
1475
- return { data: {}, error: new AuthError(error) }
1259
+ // 重新发送验证码
1260
+ const { verification_id: verificationId } = await this.oauthInstance.authApi.getVerification(data)
1261
+
1262
+ return {
1263
+ data: { messageId: verificationId },
1264
+ error: null,
1265
+ }
1266
+ } catch (error: any) {
1267
+ return {
1268
+ data: {},
1269
+ error: new AuthError(error),
1270
+ }
1476
1271
  }
1477
1272
  }
1478
1273
 
1479
1274
  /**
1480
- * https://supabase.com/docs/reference/javascript/auth-onauthstatechange
1481
- * Receive a notification every time an auth event happens.
1482
- * @param callback
1483
- * @returns Promise<{ data: { subscription: Subscription }, error: Error | null }>
1275
+ * https://supabase.com/docs/reference/javascript/auth-setsession
1276
+ * 使用access_token和refresh_token来设置会话
1277
+ * @param params
1278
+ * @returns Promise<SignInRes>
1484
1279
  */
1485
- onAuthStateChange(callback: OnAuthStateChangeCallback) {
1486
- if (!this.hasListenerSetUp) {
1487
- this.setupListeners()
1488
- this.hasListenerSetUp = true
1489
- }
1280
+ async setSession(params: SetSessionReq): Promise<SignInRes> {
1281
+ try {
1282
+ this.validateParams(params, {
1283
+ access_token: { required: true, message: 'Access token is required' },
1284
+ refresh_token: { required: true, message: 'Refresh token is required' },
1285
+ })
1490
1286
 
1491
- const id = Math.random().toString(36)
1287
+ await this.oauthInstance.oauth2client.refreshToken(params, { throwError: true })
1492
1288
 
1493
- if (!this.listeners.has(id)) {
1494
- this.listeners.set(id, new Set())
1495
- }
1289
+ const { data: { session } = {} } = await this.getSession()
1496
1290
 
1497
- this.listeners.get(id)!.add(callback)
1291
+ this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.SIGNED_IN })
1498
1292
 
1499
- // 返回 Subscription 对象
1500
- const subscription = {
1501
- id,
1502
- callback,
1503
- unsubscribe: () => {
1504
- const callbacks = this.listeners.get(id)
1505
- if (callbacks) {
1506
- callbacks.delete(callback)
1507
- if (callbacks.size === 0) {
1508
- this.listeners.delete(id)
1509
- }
1510
- }
1511
- },
1293
+ return { data: { user: session.user, session }, error: null }
1294
+ } catch (error) {
1295
+ return { data: {}, error: new AuthError(error) }
1512
1296
  }
1297
+ }
1513
1298
 
1514
- return {
1515
- data: { subscription },
1516
- }
1299
+ // https://supabase.com/docs/reference/javascript/auth-exchangecodeforsession
1300
+ async exchangeCodeForSession() {
1301
+ //
1517
1302
  }
1518
1303
 
1519
1304
  /**
1520
- * https://supabase.com/docs/reference/javascript/auth-signinwithpassword
1521
- * Log in an existing user with an email and password or phone and password or username and password.
1305
+ * 删除当前用户
1522
1306
  * @param params
1523
- * @returns Promise<SignInRes>
1307
+ * @returns
1524
1308
  */
1525
- async signInWithPassword(params: SignInWithPasswordReq): Promise<SignInRes> {
1309
+ async deleteUser(params: DeleteMeReq): Promise<CommonRes> {
1526
1310
  try {
1527
- // 参数校验:username/email/phone三选一,password必填
1528
- this.validateAtLeastOne(
1529
- params,
1530
- [['username'], ['email'], ['phone']],
1531
- 'You must provide either username, email, or phone',
1532
- )
1533
1311
  this.validateParams(params, {
1534
1312
  password: { required: true, message: 'Password is required' },
1535
1313
  })
1536
1314
 
1537
- await this.signIn({
1538
- username: params.username || params.email || this.formatPhone(params.phone),
1539
- password: params.password,
1540
- ...(params.is_encrypt ? { isEncrypt: true, version: 'v2' } : {}),
1541
- })
1315
+ const { sudo_token } = await this.oauthInstance.authApi.sudo(params)
1316
+
1317
+ await this.oauthInstance.authApi.deleteMe({ sudo_token })
1318
+
1319
+ return { data: {}, error: null }
1320
+ } catch (error) {
1321
+ return { data: {}, error: new AuthError(error) }
1322
+ }
1323
+ }
1324
+
1325
+ /**
1326
+ * 跳转系统默认登录页
1327
+ * @returns {Promise<authModels.ToDefaultLoginPage>}
1328
+ * @memberof Auth
1329
+ */
1330
+ async toDefaultLoginPage(params: authModels.ToDefaultLoginPage = {}): Promise<CommonRes> {
1331
+ try {
1332
+ const configVersion = params.config_version || 'env'
1333
+ const query = Object.keys(params.query || {})
1334
+ .map(key => `${key}=${params.query[key]}`)
1335
+ .join('&')
1336
+
1337
+ if (adapterForWxMp.isMatch()) {
1338
+ wx.navigateTo({ url: `/packages/$wd_system/pages/login/index${query ? `?${query}` : ''}` })
1339
+ } else {
1340
+ const redirectUri = params.redirect_uri || window.location.href
1341
+ const urlObj = new URL(redirectUri)
1342
+ const loginPage = `${urlObj.origin}/__auth/?app_id=${params.app_id || ''}&env_id=${this.config.env}&client_id=${
1343
+ this.config.clientId || this.config.env
1344
+ }&config_version=${configVersion}&redirect_uri=${encodeURIComponent(redirectUri)}${query ? `&${query}` : ''}`
1345
+ window.location.href = loginPage
1346
+ }
1347
+ return { data: {}, error: null }
1348
+ } catch (error) {
1349
+ return { data: {}, error: new AuthError(error) }
1350
+ }
1351
+ }
1352
+
1353
+ /**
1354
+ * 自定义登录
1355
+ * @param getTickFn () => Promise<string>, 获取自定义登录 ticket 的函数
1356
+ * @returns
1357
+ */
1358
+ async signInWithCustomTicket(getTickFn?: authModels.GetCustomSignTicketFn): Promise<SignInRes> {
1359
+ if (getTickFn) {
1360
+ this.setCustomSignFunc(getTickFn)
1361
+ }
1362
+
1363
+ try {
1364
+ await this.oauthInstance.authApi.signInWithCustomTicket()
1365
+ const loginState = await this.createLoginState()
1366
+
1542
1367
  const { data: { session } = {} } = await this.getSession()
1543
1368
 
1544
- return { data: { user: session.user, session }, error: null }
1545
- } catch (error) {
1546
- return { data: {}, error: new AuthError(error) }
1369
+ // loginState返回是为了兼容v2版本
1370
+ return { ...(loginState as any), data: { user: session.user, session }, error: null }
1371
+ } catch (error) {
1372
+ return { data: {}, error: new AuthError(error) }
1373
+ }
1374
+ }
1375
+
1376
+ /**
1377
+ * 小程序openId静默登录
1378
+ * @param params
1379
+ * @returns Promise<SignInRes>
1380
+ */
1381
+ async signInWithOpenId({ useWxCloud = true } = {}): Promise<SignInRes> {
1382
+ if (!adapterForWxMp.isMatch()) {
1383
+ throw Error('wx api undefined')
1384
+ }
1385
+ const wxInfo = wx.getAccountInfoSync().miniProgram
1386
+
1387
+ const mainFunc = async (code) => {
1388
+ let result: authModels.GrantProviderTokenResponse | undefined = undefined
1389
+ let credentials: Credentials | undefined = undefined
1390
+
1391
+ try {
1392
+ result = await this.oauthInstance.authApi.grantProviderToken(
1393
+ {
1394
+ provider_id: wxInfo?.appId,
1395
+ provider_code: code,
1396
+ provider_params: {
1397
+ provider_code_type: 'open_id',
1398
+ appid: wxInfo?.appId,
1399
+ },
1400
+ },
1401
+ useWxCloud,
1402
+ )
1403
+
1404
+ if ((result as any)?.error_code || !result.provider_token) {
1405
+ throw result
1406
+ }
1407
+
1408
+ credentials = await this.oauthInstance.authApi.signInWithProvider(
1409
+ { provider_token: result.provider_token },
1410
+ useWxCloud,
1411
+ )
1412
+
1413
+ if ((credentials as any)?.error_code) {
1414
+ throw credentials
1415
+ }
1416
+ } catch (error) {
1417
+ throw error
1418
+ }
1419
+ await this.oauthInstance.oauth2client.setCredentials(credentials as Credentials)
1547
1420
  }
1548
- }
1549
1421
 
1550
- /**
1551
- * https://supabase.com/docs/reference/javascript/auth-signinwithidtoken
1552
- * 第三方平台登录。如果用户不存在,会根据云开发平台-登录方式中对应身份源的登录模式配置,判断是否自动注册
1553
- * @param params
1554
- * @returns Promise<SignInRes>
1555
- */
1556
- async signInWithIdToken(params: SignInWithIdTokenReq): Promise<SignInRes> {
1557
1422
  try {
1558
- // 参数校验:token必填
1559
- this.validateParams(params, {
1560
- token: { required: true, message: 'Token is required' },
1423
+ await new Promise((resolve, reject) => {
1424
+ wx.login({
1425
+ success: async (res: { code: string }) => {
1426
+ try {
1427
+ await mainFunc(res.code)
1428
+ resolve(true)
1429
+ } catch (error) {
1430
+ reject(error)
1431
+ }
1432
+ },
1433
+ fail: (res: any) => {
1434
+ const error = new Error(res?.errMsg)
1435
+ ;(error as any).code = res?.errno
1436
+ reject(error)
1437
+ },
1438
+ })
1561
1439
  })
1562
1440
 
1563
- await this.signInWithProvider({
1564
- provider_token: params.token,
1565
- })
1441
+ const loginState = await this.createLoginState()
1442
+
1566
1443
  const { data: { session } = {} } = await this.getSession()
1567
1444
 
1568
- return { data: { user: session.user, session }, error: null }
1445
+ // loginState返回是为了兼容v2版本
1446
+ return { ...(loginState as any), data: { user: session.user, session }, error: null }
1569
1447
  } catch (error) {
1570
1448
  return { data: {}, error: new AuthError(error) }
1571
1449
  }
1572
1450
  }
1573
1451
 
1574
1452
  /**
1575
- * https://supabase.com/docs/reference/javascript/auth-signinwithotp
1576
- * Log in a user using a one-time password (OTP).
1453
+ * 小程序手机号授权登录
1577
1454
  * @param params
1578
- * @returns Promise<SignInWithOtpRes>
1455
+ * @returns Promise<SignInRes>
1579
1456
  */
1580
- async signInWithOtp(params: SignInWithOtpReq): Promise<SignInWithOtpRes> {
1581
- if (params.options?.shouldCreateUser === undefined || !!params.options?.shouldCreateUser) {
1582
- return this.signUp({
1583
- email: params.email,
1584
- phone: params.phone,
1585
- })
1457
+ // async signInWithPhoneAuth({ phoneCode = '', useWxCloud = false }): Promise<SignInRes> {
1458
+ async signInWithPhoneAuth({ phoneCode = '' }): Promise<SignInRes> {
1459
+ // if (!adapterForWxMp.isMatch()) {
1460
+ // return { data: {}, error: new AuthError({ message: 'wx api undefined' }) }
1461
+ // }
1462
+ // const wxInfo = wx.getAccountInfoSync().miniProgram
1463
+ // const providerInfo = {
1464
+ // provider_params: { provider_code_type: 'phone', code: phoneCode, appid: wxInfo.appId },
1465
+ // provider_id: wxInfo.appId,
1466
+ // }
1467
+
1468
+ // const { code } = await wx.login()
1469
+ // ;(providerInfo as any).provider_code = code
1470
+
1471
+ // try {
1472
+ // const providerToken = await this.oauthInstance.authApi.grantProviderToken(providerInfo, useWxCloud)
1473
+ // if (providerToken.error_code) {
1474
+ // throw providerToken
1475
+ // }
1476
+
1477
+ // const signInRes = await this.oauthInstance.authApi.signInWithProvider({
1478
+ // provider_token: providerToken.provider_token,
1479
+ // }, useWxCloud)
1480
+
1481
+ // if ((signInRes as any)?.error_code) {
1482
+ // throw signInRes
1483
+ // }
1484
+ // } catch (error) {
1485
+ // return { data: {}, error: new AuthError(error) }
1486
+ // }
1487
+
1488
+ // const loginState = await this.createLoginState()
1489
+
1490
+ // const { data: { session } = {} } = await this.getSession()
1491
+
1492
+ // // loginState返回是为了兼容v2版本
1493
+ // return { ...(loginState as any), data: { user: session.user, session }, error: null }
1494
+ if (!adapterForWxMp.isMatch()) {
1495
+ return { data: {}, error: new AuthError({ message: 'wx api undefined' }) }
1496
+ }
1497
+ const wxInfo = wx.getAccountInfoSync().miniProgram
1498
+ const providerInfo = {
1499
+ provider_params: { provider_code_type: 'phone' },
1500
+ provider_id: wxInfo.appId,
1586
1501
  }
1587
1502
 
1588
- try {
1589
- // 参数校验:email或phone必填其一
1590
- this.validateAtLeastOne(params, [['email'], ['phone']], 'You must provide either an email or phone number')
1503
+ const { code } = await wx.login()
1504
+ ;(providerInfo as any).provider_code = code
1591
1505
 
1592
- // 第一步:发送验证码并存储 verificationInfo
1593
- const verificationInfo = await this.getVerification(params.email ? { email: params.email } : { phone_number: this.formatPhone(params.phone) },)
1506
+ try {
1507
+ let providerToken = await this.oauthInstance.authApi.grantProviderToken(providerInfo)
1508
+ if (providerToken.error_code) {
1509
+ throw providerToken
1510
+ }
1594
1511
 
1595
- return {
1596
- data: {
1597
- user: null,
1598
- session: null,
1599
- // 第二步:等待用户输入验证码(通过 Promise 包装用户输入事件)
1600
- verifyOtp: async ({ token, messageId = verificationInfo.verification_id }): Promise<SignInRes> => this.verifyOtp({
1601
- email: params.email,
1602
- phone: params.phone,
1603
- token,
1604
- messageId,
1605
- }),
1512
+ providerToken = await this.oauthInstance.authApi.patchProviderToken({
1513
+ provider_token: providerToken.provider_token,
1514
+ provider_id: wxInfo.appId,
1515
+ provider_params: {
1516
+ code: phoneCode,
1517
+ provider_code_type: 'phone',
1606
1518
  },
1607
- error: null,
1519
+ })
1520
+ if (providerToken.error_code) {
1521
+ throw providerToken
1608
1522
  }
1609
- } catch (error) {
1610
- return { data: {}, error: new AuthError(error) }
1611
- }
1612
- }
1613
-
1614
- /**
1615
- * 校验第三方平台授权登录回调
1616
- * @param params
1617
- * @returns Promise<SignInRes>
1618
- */
1619
- async verifyOAuth(params?: VerifyOAuthReq): Promise<SignInRes | LinkIdentityRes> {
1620
- const data: any = {}
1621
- try {
1622
- // 回调至 provider_redirect_uri 地址(url query中携带 授权code,state等参数),此时检查 state 是否符合预期(如 自己设置的 wx_open)
1623
- const code = params?.code || utils.getQuery('code')
1624
- const state = params?.state || utils.getQuery('state')
1625
1523
 
1626
- // 参数校验:code和state必填
1627
- if (!code) {
1628
- return { data: {}, error: new AuthError({ message: 'Code is required' }) }
1629
- }
1524
+ const signInRes = await this.oauthInstance.authApi.signInWithProvider({
1525
+ provider_token: providerToken.provider_token,
1526
+ })
1630
1527
 
1631
- if (!state) {
1632
- return { data: {}, error: new AuthError({ message: 'State is required' }) }
1528
+ if ((signInRes as any)?.error_code) {
1529
+ throw signInRes
1633
1530
  }
1531
+ } catch (error) {
1532
+ return { data: {}, error: new AuthError(error) }
1533
+ }
1634
1534
 
1635
- const cacheData = getBrowserSession(state)
1636
- data.type = cacheData?.type
1535
+ const loginState = await this.createLoginState()
1637
1536
 
1638
- const provider = params?.provider || cacheData?.provider || utils.getQuery('provider')
1537
+ const { data: { session } = {} } = await this.getSession()
1639
1538
 
1640
- if (!provider) {
1641
- return { data, error: new AuthError({ message: 'Provider is required' }) }
1642
- }
1539
+ // loginState返回是为了兼容v2版本
1540
+ return { ...(loginState as any), data: { user: session.user, session }, error: null }
1541
+ }
1643
1542
 
1644
- // state符合预期,则获取该三方平台token
1645
- const { provider_token: token } = await this.grantProviderToken({
1646
- provider_id: provider,
1647
- provider_redirect_uri: location.origin + location.pathname, // 指定三方平台跳回的 url 地址
1648
- provider_code: code, // 第三方平台跳转回页面时,url param 中携带的 code 参数
1649
- })
1543
+ /**
1544
+ * 绑定手机号
1545
+ * @param phoneNumber
1546
+ * @param phoneCode
1547
+ */
1548
+ @catchErrorsDecorator({
1549
+ title: '绑定手机号失败',
1550
+ messages: [
1551
+ '请确认以下各项:',
1552
+ ' 1 - 调用 auth().bindPhoneNumber() 的语法或参数是否正确',
1553
+ ' 2 - 当前环境是否开通了短信验证码登录',
1554
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1555
+ ],
1556
+ })
1557
+ public async bindPhoneNumber(params: authModels.BindPhoneRequest) {
1558
+ return this.oauthInstance.authApi.editContact(params)
1559
+ }
1650
1560
 
1651
- let res: SignInRes | LinkIdentityRes
1561
+ /**
1562
+ * 解绑三方绑定
1563
+ * @param loginType
1564
+ */
1565
+ @catchErrorsDecorator({
1566
+ title: '解除三方绑定失败',
1567
+ messages: [
1568
+ '请确认以下各项:',
1569
+ ' 1 - 调用 auth().unbindProvider() 的语法或参数是否正确',
1570
+ ' 2 - 当前账户是否已经与此登录方式解绑',
1571
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1572
+ ],
1573
+ })
1574
+ public async unbindProvider(params: authModels.UnbindProviderRequest): Promise<void> {
1575
+ return this.oauthInstance.authApi.unbindProvider(params)
1576
+ }
1652
1577
 
1653
- if (cacheData.type === OAUTH_TYPE.BIND_IDENTITY) {
1654
- res = await this.oauthInstance.authApi.toBindIdentity({ provider_token: token, provider, fireEvent: true })
1655
- } else {
1656
- // 通过 provider_token 仅登录或登录并注册(与云开发平台-登录方式-身份源登录模式配置有关)
1657
- res = await this.signInWithIdToken({
1658
- token,
1659
- })
1660
- res.data = { ...data, ...res.data }
1661
- }
1578
+ /**
1579
+ * 更新邮箱地址
1580
+ * @param email
1581
+ * @param sudo_token
1582
+ * @param verification_token
1583
+ */
1584
+ @catchErrorsDecorator({
1585
+ title: '绑定邮箱地址失败',
1586
+ messages: [
1587
+ '请确认以下各项:',
1588
+ ' 1 - 调用 auth().bindEmail() 的语法或参数是否正确',
1589
+ ' 2 - 当前环境是否开通了邮箱密码登录',
1590
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1591
+ ],
1592
+ })
1593
+ public bindEmail(params: authModels.BindEmailRequest) {
1594
+ return this.oauthInstance.authApi.editContact(params)
1595
+ }
1662
1596
 
1663
- const localSearch = new URLSearchParams(location?.search)
1664
- localSearch.delete('code')
1665
- localSearch.delete('state')
1666
- addUrlSearch(
1667
- cacheData?.search === undefined ? `?${localSearch.toString()}` : cacheData?.search,
1668
- cacheData?.hash || location.hash,
1669
- )
1670
- removeBrowserSession(state)
1597
+ /**
1598
+ * verify
1599
+ * @param {authModels.VerifyRequest} params
1600
+ * @returns {Promise<authModels.VerifyResponse>}
1601
+ * @memberof User
1602
+ */
1603
+ @catchErrorsDecorator({
1604
+ title: '验证码验证失败',
1605
+ messages: [
1606
+ '请确认以下各项:',
1607
+ ' 1 - 调用 auth().verify() 的语法或参数是否正确',
1608
+ ' 2 - 当前环境是否开通了手机验证码/邮箱登录',
1609
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1610
+ ],
1611
+ })
1612
+ public async verify(params: authModels.VerifyRequest): Promise<authModels.VerifyResponse> {
1613
+ return this.oauthInstance.authApi.verify(params)
1614
+ }
1671
1615
 
1672
- return res
1673
- } catch (error) {
1674
- return { data, error: new AuthError(error) }
1616
+ /**
1617
+ * 获取验证码
1618
+ * @param {authModels.GetVerificationRequest} params
1619
+ * @returns {Promise<authModels.GetVerificationResponse>}
1620
+ * @memberof User
1621
+ */
1622
+ @catchErrorsDecorator({
1623
+ title: '获取验证码失败',
1624
+ messages: [
1625
+ '请确认以下各项:',
1626
+ ' 1 - 调用 auth().getVerification() 的语法或参数是否正确',
1627
+ ' 2 - 当前环境是否开通了手机验证码/邮箱登录',
1628
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1629
+ ],
1630
+ })
1631
+ public async getVerification(
1632
+ params: authModels.GetVerificationRequest,
1633
+ options?: { withCaptcha: boolean },
1634
+ ): Promise<authModels.GetVerificationResponse> {
1635
+ if (params.phone_number) {
1636
+ params.phone_number = this.formatPhone(params.phone_number)
1675
1637
  }
1638
+ return this.oauthInstance.authApi.getVerification(params, options)
1676
1639
  }
1677
1640
 
1678
1641
  /**
1679
- * https://supabase.com/docs/reference/javascript/auth-signinwithoauth
1680
- * 生成第三方平台授权 Uri (如微信二维码扫码授权网页)
1681
- * @param params
1682
- * @returns Promise<SignInOAuthRes>
1642
+ * 获取当前登录的用户信息-同步
1683
1643
  */
1684
- async signInWithOAuth(params: SignInWithOAuthReq): Promise<SignInOAuthRes> {
1685
- try {
1686
- // 参数校验:provider必填
1687
- this.validateParams(params, {
1688
- provider: { required: true, message: 'Provider is required' },
1689
- })
1690
-
1691
- const href = params.options?.redirectTo || location.href
1644
+ get currentUser() {
1645
+ if (this.cache.mode === 'async') {
1646
+ // async storage的平台调用此API提示
1647
+ printWarn(
1648
+ ERRORS.INVALID_OPERATION,
1649
+ 'current platform\'s storage is asynchronous, please use getCurrentUser instead',
1650
+ )
1651
+ return
1652
+ }
1692
1653
 
1693
- const urlObject = new URL(href)
1654
+ const loginState = this.hasLoginState()
1694
1655
 
1695
- const provider_redirect_uri = urlObject.origin + urlObject.pathname
1656
+ if (loginState) {
1657
+ return loginState.user || null
1658
+ }
1659
+ return null
1660
+ }
1696
1661
 
1697
- const state = params.options?.state || `prd-${params.provider}-${Math.random().toString(36)
1698
- .slice(2)}`
1662
+ /**
1663
+ * 获取当前登录的用户信息-异步
1664
+ */
1665
+ @catchErrorsDecorator({
1666
+ title: '获取用户信息失败',
1667
+ messages: [
1668
+ '请确认以下各项:',
1669
+ ' 1 - 调用 auth().getCurrentUser() 的语法或参数是否正确',
1670
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1671
+ ],
1672
+ })
1673
+ public async getCurrentUser(): Promise<(authModels.UserInfo & Partial<User>) | null> {
1674
+ const loginState = await this.getLoginState()
1675
+ if (loginState) {
1676
+ const userInfo = loginState.user.getLocalUserInfo() as authModels.UserInfo
1677
+ await loginState.user.checkLocalInfoAsync()
1678
+ return { ...loginState.user, ...userInfo } as unknown as authModels.UserInfo & Partial<User>
1679
+ }
1680
+ return null
1681
+ }
1699
1682
 
1700
- const { uri } = await this.genProviderRedirectUri({
1701
- provider_id: params.provider,
1702
- provider_redirect_uri,
1703
- state,
1704
- })
1683
+ /**
1684
+ * 匿名登录
1685
+ * @returns {Promise<LoginState>}
1686
+ * @memberof Auth
1687
+ */
1688
+ @catchErrorsDecorator({
1689
+ title: '小程序匿名登录失败',
1690
+ messages: [
1691
+ '请确认以下各项:',
1692
+ ' 1 - 当前环境是否开启了匿名登录',
1693
+ ' 2 - 调用 auth().signInAnonymouslyInWx() 的语法或参数是否正确',
1694
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1695
+ ],
1696
+ })
1697
+ public async signInAnonymouslyInWx({
1698
+ useWxCloud,
1699
+ }: {
1700
+ useWxCloud?: boolean
1701
+ } = {}): Promise<LoginState> {
1702
+ if (!adapterForWxMp.isMatch()) {
1703
+ throw Error('wx api undefined')
1704
+ }
1705
+ const wxInfo = wx.getAccountInfoSync().miniProgram
1705
1706
 
1706
- // URL 进行解码
1707
- const decodedUri = decodeURIComponent(uri)
1707
+ const mainFunc = async (code) => {
1708
+ let result: authModels.GrantProviderTokenResponse | undefined = undefined
1709
+ let credentials: Credentials | undefined = undefined
1708
1710
 
1709
- // 合并额外的查询参数
1710
- let finalUri = decodedUri
1711
+ try {
1712
+ result = await this.oauthInstance.authApi.grantProviderToken(
1713
+ {
1714
+ provider_id: wxInfo?.appId,
1715
+ provider_code: code,
1716
+ provider_params: {
1717
+ provider_code_type: 'open_id',
1718
+ appid: wxInfo?.appId,
1719
+ },
1720
+ },
1721
+ useWxCloud,
1722
+ )
1711
1723
 
1712
- if (params.options?.queryParams) {
1713
- const url = new URL(decodedUri)
1714
- Object.entries(params.options.queryParams).forEach(([key, value]) => {
1715
- url.searchParams.set(key, value)
1716
- })
1717
- finalUri = url.toString()
1718
- }
1724
+ if ((result as any)?.error_code || !result.provider_token) {
1725
+ throw result
1726
+ }
1719
1727
 
1720
- saveToBrowserSession(state, {
1721
- provider: params.provider,
1722
- search: urlObject.search,
1723
- hash: urlObject.hash,
1724
- type: params.options?.type || OAUTH_TYPE.SIGN_IN,
1725
- })
1728
+ credentials = await this.oauthInstance.authApi.signInAnonymously(
1729
+ { provider_token: result.provider_token },
1730
+ useWxCloud,
1731
+ )
1726
1732
 
1727
- if (isBrowser() && !params.options?.skipBrowserRedirect) {
1728
- window.location.assign(finalUri)
1733
+ if ((credentials as any)?.error_code) {
1734
+ throw credentials
1735
+ }
1736
+ } catch (error) {
1737
+ throw error
1729
1738
  }
1730
-
1731
- return { data: { url: finalUri, provider: params.provider }, error: null }
1732
- } catch (error) {
1733
- return { data: {}, error: new AuthError(error) }
1734
1739
  }
1735
- }
1736
1740
 
1737
- // https://supabase.com/docs/reference/javascript/auth-getclaims
1738
- async getClaims(): Promise<GetClaimsRes> {
1739
- try {
1740
- const { accessToken } = await this.getAccessToken()
1741
- const parsedToken = weAppJwtDecodeAll(accessToken)
1742
- return { data: parsedToken, error: null }
1743
- } catch (error) {
1744
- return { data: {}, error: new AuthError(error) }
1745
- }
1741
+ await new Promise((resolve, reject) => {
1742
+ wx.login({
1743
+ success: async (res: { code: string }) => {
1744
+ try {
1745
+ await mainFunc(res.code)
1746
+ resolve(true)
1747
+ } catch (error) {
1748
+ reject(error)
1749
+ }
1750
+ },
1751
+ fail: (res: any) => {
1752
+ const error = new Error(res?.errMsg)
1753
+ ;(error as any).code = res?.errno
1754
+ reject(error)
1755
+ },
1756
+ })
1757
+ })
1758
+
1759
+ return this.createLoginState(undefined, { asyncRefreshUser: true })
1746
1760
  }
1747
1761
 
1748
1762
  /**
1749
- * https://supabase.com/docs/reference/javascript/auth-resetpasswordforemail
1750
- * 通过 email 或手机号重置密码
1751
- * @param emailOrPhone 邮箱或手机号
1752
- * @returns Promise<ResetPasswordForEmailRes>
1763
+ * 小程序绑定OpenID
1764
+ * @returns {Promise<LoginState>}
1765
+ * @memberof Auth
1753
1766
  */
1754
- async resetPasswordForEmail(
1755
- emailOrPhone: string,
1756
- options?: { redirectTo?: string },
1757
- ): Promise<ResetPasswordForEmailRes> {
1758
- try {
1759
- // 参数校验:emailOrPhone必填
1760
- this.validateParams(
1761
- { emailOrPhone },
1762
- {
1763
- emailOrPhone: { required: true, message: 'Email or phone is required' },
1764
- },
1765
- )
1767
+ @catchErrorsDecorator({
1768
+ title: '小程序绑定OpenID失败',
1769
+ messages: [
1770
+ '请确认以下各项:',
1771
+ ' 1 - 当前环境是否开启了小程序openId静默登录',
1772
+ ' 2 - 调用 auth().bindOpenId() 的语法或参数是否正确',
1773
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1774
+ ],
1775
+ })
1776
+ public async bindOpenId(): Promise<void> {
1777
+ if (!adapterForWxMp.isMatch()) {
1778
+ throw Error('wx api undefined')
1779
+ }
1780
+ const wxInfo = wx.getAccountInfoSync().miniProgram
1766
1781
 
1767
- const { redirectTo } = options || {}
1782
+ const mainFunc = async (code) => {
1783
+ let result: authModels.GrantProviderTokenResponse | undefined = undefined
1768
1784
 
1769
- // 判断是邮箱还是手机号
1770
- const isEmail = emailOrPhone.includes('@')
1771
- let verificationParams: { email?: string; phone_number?: string }
1785
+ try {
1786
+ result = await this.oauthInstance.authApi.grantProviderToken({
1787
+ provider_id: wxInfo?.appId,
1788
+ provider_code: code,
1789
+ provider_params: {
1790
+ provider_code_type: 'open_id',
1791
+ appid: wxInfo?.appId,
1792
+ },
1793
+ })
1772
1794
 
1773
- if (isEmail) {
1774
- verificationParams = { email: emailOrPhone }
1775
- } else {
1776
- // 正规化手机号
1777
- const formattedPhone = this.formatPhone(emailOrPhone)
1778
- verificationParams = { phone_number: formattedPhone }
1795
+ if ((result as any)?.error_code || !result.provider_token) {
1796
+ throw result
1797
+ }
1798
+
1799
+ await this.oauthInstance.authApi.bindWithProvider({ provider_token: result.provider_token })
1800
+ } catch (error) {
1801
+ throw error
1779
1802
  }
1803
+ }
1780
1804
 
1781
- // 第一步:发送验证码并存储 verificationInfo
1782
- const verificationInfo = await this.getVerification(verificationParams)
1805
+ await new Promise((resolve, reject) => {
1806
+ wx.login({
1807
+ success: async (res: { code: string }) => {
1808
+ try {
1809
+ await mainFunc(res.code)
1810
+ resolve(true)
1811
+ } catch (error) {
1812
+ reject(error)
1813
+ }
1814
+ },
1815
+ fail: (res: any) => {
1816
+ const error = new Error(res?.errMsg)
1817
+ ;(error as any).code = res?.errno
1818
+ reject(error)
1819
+ },
1820
+ })
1821
+ })
1783
1822
 
1784
- return {
1785
- data: {
1786
- // 第二步:等待用户输入验证码(通过 Promise 包装用户输入事件)
1787
- updateUser: async (attributes: UpdateUserAttributes): Promise<SignInRes> => {
1788
- this.validateParams(attributes, {
1789
- nonce: { required: true, message: 'Nonce is required' },
1790
- password: { required: true, message: 'Password is required' },
1791
- })
1823
+ return
1824
+ }
1825
+
1826
+ /**
1827
+ * 小程序unionId静默登录
1828
+ * @returns {Promise<LoginState>}
1829
+ * @memberof Auth
1830
+ */
1831
+ @catchErrorsDecorator({
1832
+ title: '小程序unionId静默登录失败',
1833
+ messages: [
1834
+ '请确认以下各项:',
1835
+ ' 1 - 当前环境是否开启了小程序unionId静默登录',
1836
+ ' 2 - 调用 auth().signInWithUnionId() 的语法或参数是否正确',
1837
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1838
+ ],
1839
+ })
1840
+ public async signInWithUnionId(): Promise<LoginState> {
1841
+ if (!adapterForWxMp.isMatch()) {
1842
+ throw Error('wx api undefined')
1843
+ }
1844
+ try {
1845
+ await new Promise((resolve, reject) => {
1846
+ const wxInfo = wx.getAccountInfoSync().miniProgram
1847
+ wx.login({
1848
+ success: async (res: { code: string }) => {
1849
+ const providerId = wxInfo?.appId
1792
1850
  try {
1793
- // 第三步:待用户输入完验证码之后,验证验证码
1794
- const verificationTokenRes = await this.verify({
1795
- verification_id: verificationInfo.verification_id,
1796
- verification_code: attributes.nonce,
1851
+ const result = await this.oauthInstance.authApi.grantProviderToken({
1852
+ provider_code: res.code,
1853
+ provider_id: providerId,
1854
+ provider_params: {
1855
+ provider_code_type: 'union_id',
1856
+ appid: wxInfo?.appId,
1857
+ },
1797
1858
  })
1798
1859
 
1799
- await this.oauthInstance.authApi.resetPassword({
1800
- email: isEmail ? emailOrPhone : undefined,
1801
- phone_number: !isEmail ? emailOrPhone : undefined,
1802
- new_password: attributes.password,
1803
- verification_token: verificationTokenRes.verification_token,
1804
- })
1860
+ const { provider_token: providerToken } = result
1805
1861
 
1806
- this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, {
1807
- event: AUTH_STATE_CHANGED_TYPE.PASSWORD_RECOVERY,
1808
- })
1862
+ if (!providerToken) {
1863
+ reject(result)
1864
+ return
1865
+ }
1809
1866
 
1810
- const res = await this.signInWithPassword({
1811
- email: isEmail ? emailOrPhone : undefined,
1812
- phone: !isEmail ? emailOrPhone : undefined,
1813
- password: attributes.password,
1867
+ const signInRes = await this.oauthInstance.authApi.signInWithProvider({
1868
+ provider_id: providerId,
1869
+ provider_token: providerToken,
1814
1870
  })
1815
1871
 
1816
- if (redirectTo && isBrowser()) {
1817
- window.location.assign(redirectTo)
1872
+ if ((signInRes as any)?.error_code) {
1873
+ reject(signInRes)
1874
+ return
1818
1875
  }
1819
-
1820
- return res
1876
+ resolve(true)
1821
1877
  } catch (error) {
1822
- return { data: {}, error: new AuthError(error) }
1878
+ reject(error)
1823
1879
  }
1824
1880
  },
1825
- },
1826
- error: null,
1827
- }
1881
+ fail: (res: any) => {
1882
+ const error = new Error(res?.errMsg)
1883
+ ;(error as any).code = res?.errno
1884
+ reject(error)
1885
+ },
1886
+ })
1887
+ })
1828
1888
  } catch (error) {
1829
- return { data: {}, error: new AuthError(error) }
1889
+ throw error
1830
1890
  }
1891
+
1892
+ return this.createLoginState()
1831
1893
  }
1832
1894
 
1833
1895
  /**
1834
- * 通过旧密码重置密码
1835
- * @param new_password
1836
- * @param old_password
1837
- * @returns
1896
+ * 小程序短信验证码登陆
1897
+ * @returns {Promise<LoginState>}
1898
+ * @memberof Auth
1838
1899
  */
1839
- async resetPasswordForOld(params: ResetPasswordForOldReq) {
1900
+ @catchErrorsDecorator({
1901
+ title: '短信验证码登陆',
1902
+ messages: [
1903
+ '请确认以下各项:',
1904
+ ' 1 - 当前环境是否开启了小程序短信验证码登陆',
1905
+ ' 2 - 调用 auth().signInWithSms() 的语法或参数是否正确',
1906
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1907
+ ],
1908
+ })
1909
+ public async signInWithSms({
1910
+ verificationInfo = { verification_id: '', is_user: false },
1911
+ verificationCode = '',
1912
+ phoneNum = '',
1913
+ bindInfo = undefined,
1914
+ }): Promise<LoginState> {
1840
1915
  try {
1841
- await this.oauthInstance.authApi.updatePasswordByOld({
1842
- old_password: params.old_password,
1843
- new_password: params.new_password,
1916
+ return this.signInWithUsername({
1917
+ verificationInfo,
1918
+ verificationCode,
1919
+ bindInfo,
1920
+ username: phoneNum,
1921
+ loginType: 'sms',
1844
1922
  })
1845
-
1846
- const { data: { session } = {} } = await this.getSession()
1847
-
1848
- return { data: { user: session.user, session }, error: null }
1849
1923
  } catch (error) {
1850
- return { data: {}, error: new AuthError(error) }
1924
+ throw error
1851
1925
  }
1852
1926
  }
1853
1927
 
1854
1928
  /**
1855
- * https://supabase.com/docs/reference/javascript/auth-verifyotp
1856
- * Log in a user given a User supplied OTP and verificationId received through mobile or email.
1857
- * @param params
1858
- * @returns Promise<SignInRes>
1929
+ * 邮箱验证码登陆
1930
+ * @returns {Promise<LoginState>}
1931
+ * @memberof Auth
1859
1932
  */
1860
- async verifyOtp(params: VerifyOtpReq): Promise<SignInRes> {
1933
+ @catchErrorsDecorator({
1934
+ title: '邮箱验证码登陆',
1935
+ messages: [
1936
+ '请确认以下各项:',
1937
+ ' 1 - 当前环境是否开启了邮箱登陆',
1938
+ ' 2 - 调用 auth().signInWithEmail() 的语法或参数是否正确',
1939
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1940
+ ],
1941
+ })
1942
+ public async signInWithEmail({
1943
+ verificationInfo = { verification_id: '', is_user: false },
1944
+ verificationCode = '',
1945
+ bindInfo = undefined,
1946
+ email = '',
1947
+ }): Promise<LoginState> {
1861
1948
  try {
1862
- const { type } = params
1863
- // 参数校验:token和verificationInfo必填
1864
- this.validateParams(params, {
1865
- token: { required: true, message: 'Token is required' },
1866
- messageId: { required: true, message: 'messageId is required' },
1949
+ return this.signInWithUsername({
1950
+ verificationInfo,
1951
+ verificationCode,
1952
+ bindInfo,
1953
+ username: email,
1954
+ loginType: 'email',
1867
1955
  })
1868
-
1869
- if (['phone_change', 'email_change'].includes(type)) {
1870
- await this.verify({
1871
- verification_id: params.messageId,
1872
- verification_code: params.token,
1873
- })
1874
- } else {
1875
- await this.signInWithUsername({
1876
- verificationInfo: { verification_id: params.messageId, is_user: true },
1877
- verificationCode: params.token,
1878
- username: params.email || this.formatPhone(params.phone) || '',
1879
- loginType: params.email ? 'email' : 'phone',
1880
- })
1881
- }
1882
-
1883
- const { data: { session } = {} } = await this.getSession()
1884
-
1885
- return { data: { user: session.user, session }, error: null }
1886
1956
  } catch (error) {
1887
- return { data: {}, error: new AuthError(error) }
1957
+ throw error
1888
1958
  }
1889
1959
  }
1890
1960
 
1891
1961
  /**
1892
- * https://supabase.com/docs/reference/javascript/auth-getSession
1893
- * Returns the session, refreshing it if necessary.
1894
- * @returns Promise<SignInRes>
1962
+ * 设置获取自定义登录 ticket 函数
1963
+ * @param {authModels.GetCustomSignTicketFn} getTickFn
1964
+ * @memberof Auth
1895
1965
  */
1896
- async getSession(): Promise<SignInRes> {
1897
- try {
1898
- const credentials: Credentials = await this.oauthInstance.oauth2client.getCredentials()
1966
+ public setCustomSignFunc(getTickFn: authModels.GetCustomSignTicketFn): void {
1967
+ this.oauthInstance.authApi.setCustomSignFunc(getTickFn)
1968
+ }
1899
1969
 
1900
- if (!credentials || credentials.scope === 'accessKey') {
1901
- return { data: { session: null }, error: null }
1902
- }
1970
+ /**
1971
+ *
1972
+ * @returns {Promise<LoginState>}
1973
+ * @memberof Auth
1974
+ */
1975
+ // @catchErrorsDecorator({
1976
+ // title: '自定义登录失败',
1977
+ // messages: [
1978
+ // '请确认以下各项:',
1979
+ // ' 1 - 当前环境是否开启了自定义登录',
1980
+ // ' 2 - 调用 auth().signInWithCustomTicket() 的语法或参数是否正确',
1981
+ // ' 3 - ticket 是否归属于当前环境',
1982
+ // ' 4 - 创建 ticket 的自定义登录私钥是否过期',
1983
+ // `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
1984
+ // ],
1985
+ // })
1986
+ // public async signInWithCustomTicket(): Promise<LoginState> {
1987
+ // await this.oauthInstance.authApi.signInWithCustomTicket()
1988
+ // return this.createLoginState()
1989
+ // }
1903
1990
 
1904
- const { data: { user } = {} } = await this.getUser()
1991
+ /**
1992
+ *
1993
+ * @param {authModels.SignInRequest} params
1994
+ * @returns {Promise<LoginState>}
1995
+ * @memberof Auth
1996
+ */
1997
+ public async signIn(params: authModels.SignInRequest): Promise<LoginState> {
1998
+ await this.oauthInstance.authApi.signIn(params)
1999
+ return this.createLoginState(params)
2000
+ }
1905
2001
 
1906
- return { data: { session: { ...credentials, user }, user }, error: null }
1907
- } catch (error) {
1908
- return { data: {}, error: new AuthError(error) }
2002
+ // /**
2003
+ // *
2004
+ // * @param {authModels.SignUpRequest} params
2005
+ // * @returns {Promise<LoginState>}
2006
+ // * @memberof Auth
2007
+ // */
2008
+ // @catchErrorsDecorator({
2009
+ // title: '注册失败',
2010
+ // messages: [
2011
+ // '请确认以下各项:',
2012
+ // ' 1 - 当前环境是否开启了指定登录方式',
2013
+ // ' 2 - 调用 auth().signUp() 的语法或参数是否正确',
2014
+ // `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
2015
+ // ],
2016
+ // })
2017
+ // public async signUp(params: authModels.SignUpRequest): Promise<any> {
2018
+ // await this.oauthInstance.authApi.signUp(params)
2019
+ // return this.createLoginState()
2020
+ // }
2021
+
2022
+ /**
2023
+ * 设置密码
2024
+ * @param {authModels.SetPasswordRequest} params
2025
+ * @returns {Promise<void>}
2026
+ * @memberof Auth
2027
+ */
2028
+ public async setPassword(params: authModels.SetPasswordRequest): Promise<void> {
2029
+ return this.oauthInstance.authApi.setPassword(params)
2030
+ }
2031
+
2032
+ /**
2033
+ * 检测用户名是否已经占用
2034
+ * @param username
2035
+ */
2036
+ @catchErrorsDecorator({
2037
+ title: '获取用户是否被占用失败',
2038
+ messages: [
2039
+ '请确认以下各项:',
2040
+ ' 1 - 调用 auth().isUsernameRegistered() 的语法或参数是否正确',
2041
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
2042
+ ],
2043
+ })
2044
+ public async isUsernameRegistered(username: string): Promise<boolean> {
2045
+ if (typeof username !== 'string') {
2046
+ throwError(ERRORS.INVALID_PARAMS, 'username must be a string')
1909
2047
  }
2048
+
2049
+ const { exist } = await this.oauthInstance.authApi.checkIfUserExist({ username })
2050
+ return exist
1910
2051
  }
1911
2052
 
1912
2053
  /**
1913
- * https://supabase.com/docs/reference/javascript/auth-refreshsession
1914
- * 无论过期状态如何,都返回一个新的会话
1915
- * @param refresh_token
1916
- * @returns Promise<SignInRes>
2054
+ * 获取本地登录态-同步
1917
2055
  */
1918
- async refreshSession(refresh_token?: string): Promise<SignInRes> {
1919
- try {
1920
- const credentials: Credentials = await this.oauthInstance.oauth2client.localCredentials.getCredentials()
1921
- credentials.refresh_token = refresh_token || credentials.refresh_token
1922
- const newTokens = await this.oauthInstance.oauth2client.refreshToken(credentials)
1923
- const { data: { user } = {} } = await this.getUser()
2056
+ public hasLoginState(): LoginState | null {
2057
+ if (this.cache.mode === 'async') {
2058
+ // async storage的平台调用此API提示
2059
+ printWarn(
2060
+ ERRORS.INVALID_OPERATION,
2061
+ 'current platform\'s storage is asynchronous, please use getLoginState instead',
2062
+ )
2063
+ return
2064
+ }
1924
2065
 
1925
- return { data: { user, session: { ...newTokens, user } }, error: null }
1926
- } catch (error) {
1927
- return { data: {}, error: new AuthError(error) }
2066
+ const oauthLoginState = this.oauthInstance?.authApi.hasLoginStateSync()
2067
+ if (oauthLoginState) {
2068
+ const loginState = new LoginState({
2069
+ envId: this.config.env,
2070
+ cache: this.cache,
2071
+ oauthInstance: this.oauthInstance,
2072
+ })
2073
+ return loginState
1928
2074
  }
2075
+ return null
1929
2076
  }
1930
2077
 
1931
2078
  /**
1932
- * https://supabase.com/docs/reference/javascript/auth-getuser
1933
- * 如果存在现有会话,则获取当前用户详细信息
1934
- * @returns Promise<GetUserRes>
2079
+ * 获取本地登录态-异步
2080
+ * 此API为兼容异步storage的平台
1935
2081
  */
1936
- async getUser(): Promise<GetUserRes> {
2082
+ @catchErrorsDecorator({
2083
+ title: '获取本地登录态失败',
2084
+ messages: [
2085
+ '请确认以下各项:',
2086
+ ' 1 - 调用 auth().getLoginState() 的语法或参数是否正确',
2087
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
2088
+ ],
2089
+ })
2090
+ public async getLoginState() {
2091
+ let oauthLoginState = null
2092
+
1937
2093
  try {
1938
- const user = this.convertToUser(await this.getUserInfo())
1939
- return { data: { user }, error: null }
2094
+ oauthLoginState = await this.oauthInstance.authApi.getLoginState()
1940
2095
  } catch (error) {
1941
- return { data: {}, error: new AuthError(error) }
2096
+ return null
1942
2097
  }
1943
- }
1944
-
1945
- /**
1946
- * 刷新用户信息
1947
- * @returns Promise<CommonRes>
1948
- */
1949
- async refreshUser(): Promise<CommonRes> {
1950
- try {
1951
- await this.currentUser.refresh()
1952
2098
 
1953
- const { data: { session } = {} } = await this.getSession()
1954
- return { data: { user: session.user, session }, error: null }
1955
- } catch (error) {
1956
- return { data: {}, error: new AuthError(error) }
2099
+ if (oauthLoginState) {
2100
+ const loginState = new LoginState({
2101
+ envId: this.config.env,
2102
+ cache: this.cache,
2103
+ oauthInstance: this.oauthInstance,
2104
+ })
2105
+ return loginState
1957
2106
  }
2107
+
2108
+ return null
1958
2109
  }
1959
2110
 
1960
2111
  /**
1961
- * https://supabase.com/docs/reference/javascript/auth-updateuser
1962
- * 更新用户信息
1963
- * @param params
1964
- * @returns Promise<GetUserRes | UpdateUserWithVerificationRes>
2112
+ * @deprecated 使用 getCurrentUser 代替,因为与node-sdk方法名冲突
2113
+ * @returns
1965
2114
  */
1966
- async updateUser(params: UpdateUserReq): Promise<GetUserRes | UpdateUserWithVerificationRes> {
1967
- try {
1968
- // 参数校验:至少有一个更新字段被提供
1969
- const hasValue = Object.keys(params).some(key => params[key] !== undefined && params[key] !== null && params[key] !== '',)
1970
- if (!hasValue) {
1971
- throw new AuthError({ message: 'At least one field must be provided for update' })
1972
- }
1973
-
1974
- const { email, phone, ...restParams } = params
1975
-
1976
- // 检查是否需要更新 email 或 phone
1977
- const needsEmailVerification = email !== undefined
1978
- const needsPhoneVerification = phone !== undefined
1979
-
1980
- let extraRes = {}
1981
-
1982
- if (needsEmailVerification || needsPhoneVerification) {
1983
- // 需要发送验证码
1984
- let verificationParams: { email?: string; phone_number?: string }
1985
- let verificationType: 'email_change' | 'phone_change'
1986
-
1987
- if (needsEmailVerification) {
1988
- verificationParams = { email: params.email }
1989
- verificationType = 'email_change'
1990
- } else {
1991
- // 正规化手机号
1992
- const formattedPhone = this.formatPhone(params.phone)
1993
- verificationParams = { phone_number: formattedPhone }
1994
- verificationType = 'phone_change'
1995
- }
1996
-
1997
- // 发送验证码
1998
- const verificationInfo = await this.getVerification(verificationParams)
1999
-
2000
- Object.keys(restParams).length > 0 && (await this.updateUserBasicInfo(restParams))
2001
-
2002
- extraRes = {
2003
- messageId: verificationInfo.verification_id,
2004
- verifyOtp: async (verifyParams: { email?: string; phone?: string; token: string }): Promise<GetUserRes> => {
2005
- try {
2006
- if (verifyParams.email && params.email === verifyParams.email) {
2007
- // 验证码验证
2008
- await this.verifyOtp({
2009
- type: 'email_change',
2010
- email: params.email,
2011
- token: verifyParams.token,
2012
- messageId: verificationInfo.verification_id,
2013
- })
2014
- await this.updateUserBasicInfo({ email: params.email })
2015
- } else if (verifyParams.phone && params.phone === verifyParams.phone) {
2016
- // 验证码验证
2017
- await this.verifyOtp({
2018
- type: 'phone_change',
2019
- phone: params.phone,
2020
- token: verifyParams.token,
2021
- messageId: verificationInfo.verification_id,
2022
- })
2023
- await this.updateUserBasicInfo({ phone: this.formatPhone(params.phone) })
2024
- } else {
2025
- await this.verifyOtp({
2026
- type: verificationType,
2027
- email: needsEmailVerification ? params.email : undefined,
2028
- phone: !needsEmailVerification ? params.phone : undefined,
2029
- token: verifyParams.token,
2030
- messageId: verificationInfo.verification_id,
2031
- })
2032
- // 验证成功后更新用户信息
2033
- await this.updateUserBasicInfo(params)
2034
- }
2035
-
2036
- const {
2037
- data: { user },
2038
- } = await this.getUser()
2039
- this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.USER_UPDATED })
2115
+ @catchErrorsDecorator({
2116
+ title: '获取用户信息失败',
2117
+ messages: [
2118
+ '请确认以下各项:',
2119
+ ' 1 - 是否已登录',
2120
+ ' 2 - 调用 auth().getUserInfo() 的语法或参数是否正确',
2121
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
2122
+ ],
2123
+ })
2124
+ public async getUserInfo(): Promise<(authModels.UserInfo & Partial<User>) | null> {
2125
+ return this.getCurrentUser()
2126
+ }
2040
2127
 
2041
- return { data: { user }, error: null }
2042
- } catch (error) {
2043
- return { data: {}, error: new AuthError(error) }
2044
- }
2045
- },
2046
- }
2047
- } else {
2048
- // 不需要验证,直接更新
2049
- await this.updateUserBasicInfo(params)
2050
- }
2051
- const {
2052
- data: { user },
2053
- } = await this.getUser()
2054
- this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.USER_UPDATED })
2128
+ @catchErrorsDecorator({
2129
+ title: '获取微搭插件用户信息失败',
2130
+ messages: [
2131
+ '请确认以下各项:',
2132
+ ' 1 - 是否已登录',
2133
+ ' 2 - 调用 auth().getWedaUserInfo() 的语法或参数是否正确',
2134
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
2135
+ ],
2136
+ })
2137
+ public async getWedaUserInfo(): Promise<any> {
2138
+ return this.oauthInstance.authApi.getWedaUserInfo()
2139
+ }
2055
2140
 
2056
- return { data: { user, ...extraRes }, error: null }
2057
- } catch (error) {
2058
- return { data: {}, error: new AuthError(error) }
2141
+ public async updateUserBasicInfo(params: authModels.ModifyUserBasicInfoRequest) {
2142
+ const loginState = await this.getLoginState()
2143
+ if (loginState) {
2144
+ await (loginState.user as User).updateUserBasicInfo(params)
2059
2145
  }
2146
+ return
2060
2147
  }
2061
2148
 
2062
2149
  /**
2063
- * https://supabase.com/docs/reference/javascript/auth-getuseridentities
2064
- * 获取所有身份源
2065
- * @returns Promise<GetUserIdentitiesRes>
2150
+ * getAuthHeader 兼容处理
2151
+ * 返回空对象
2066
2152
  */
2067
- async getUserIdentities(): Promise<GetUserIdentitiesRes> {
2068
- try {
2069
- const providers = await this.oauthInstance.authApi.getProviders()
2070
-
2071
- return { data: { identities: providers?.data?.filter(v => !!v.bind) as unknown as GetUserIdentitiesRes['data']['identities'] }, error: null }
2072
- } catch (error) {
2073
- return { data: {}, error: new AuthError(error) }
2074
- }
2153
+ public getAuthHeader(): {} {
2154
+ console.error('Auth.getAuthHeader API 已废弃')
2155
+ return {}
2075
2156
  }
2076
2157
 
2077
2158
  /**
2078
- * https://supabase.com/docs/reference/javascript/auth-linkidentity
2079
- * 绑定身份源到当前用户
2080
- * @param params
2081
- * @returns Promise<LinkIdentityRes>
2159
+ * 为已有账户绑第三方账户
2160
+ * @param {authModels.BindWithProviderRequest} params
2161
+ * @returns {Promise<void>}
2162
+ * @memberof Auth
2082
2163
  */
2083
- async linkIdentity(params: LinkIdentityReq): Promise<LinkIdentityRes> {
2084
- try {
2085
- // 参数校验:provider必填
2086
- this.validateParams(params, {
2087
- provider: { required: true, message: 'Provider is required' },
2088
- })
2089
-
2090
- await this.signInWithOAuth({
2091
- provider: params.provider,
2092
- options: {
2093
- type: OAUTH_TYPE.BIND_IDENTITY,
2094
- },
2095
- })
2096
-
2097
- return { data: { provider: params.provider }, error: null }
2098
- } catch (error) {
2099
- return { data: {}, error: new AuthError(error) }
2100
- }
2164
+ @catchErrorsDecorator({
2165
+ title: '绑定第三方登录方式失败',
2166
+ messages: [
2167
+ '请确认以下各项:',
2168
+ ' 1 - 调用 auth().bindWithProvider() 的语法或参数是否正确',
2169
+ ' 2 - 此账户是否已经绑定此第三方',
2170
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
2171
+ ],
2172
+ })
2173
+ public async bindWithProvider(params: authModels.BindWithProviderRequest): Promise<void> {
2174
+ return this.oauthInstance.authApi.bindWithProvider(params)
2101
2175
  }
2102
2176
 
2103
2177
  /**
2104
- * https://supabase.com/docs/reference/javascript/auth-unlinkidentity
2105
- * 解绑身份源
2106
- * @param params
2107
- * @returns Promise<CommonRes>
2178
+ * 查询用户
2179
+ * @param {authModels.QueryUserProfileRequest} appended_params
2180
+ * @returns {Promise<authModels.UserProfile>}
2181
+ * @memberof Auth
2108
2182
  */
2109
- async unlinkIdentity(params: UnlinkIdentityReq): Promise<CommonRes> {
2110
- try {
2111
- // 参数校验:provider必填
2112
- this.validateParams(params, {
2113
- provider: { required: true, message: 'Provider is required' },
2114
- })
2115
-
2116
- await this.oauthInstance.authApi.unbindProvider({ provider_id: params.provider })
2183
+ public async queryUser(queryObj: authModels.QueryUserProfileRequest): Promise<authModels.QueryUserProfileResponse> {
2184
+ return this.oauthInstance.authApi.queryUserProfile(queryObj)
2185
+ }
2117
2186
 
2118
- return { data: {}, error: null }
2119
- } catch (error) {
2120
- return { data: {}, error: new AuthError(error) }
2187
+ public async getAccessToken() {
2188
+ const oauthAccessTokenRes = await this.oauthInstance.oauth2client.getAccessToken()
2189
+ return {
2190
+ accessToken: oauthAccessTokenRes,
2191
+ env: this.config.env,
2121
2192
  }
2122
2193
  }
2123
2194
 
2124
- /**
2125
- * https://supabase.com/docs/reference/javascript/auth-reauthentication
2126
- * 重新认证
2127
- * @returns Promise<ReauthenticateRes>
2128
- */
2129
- async reauthenticate(): Promise<ReauthenticateRes> {
2130
- try {
2131
- const {
2132
- data: { user },
2133
- } = await this.getUser()
2134
-
2135
- this.validateAtLeastOne(user, [['email', 'phone']], 'You must provide either an email or phone number')
2136
- const userInfo = user.email ? { email: user.email } : { phone_number: this.formatPhone(user.phone) }
2137
-
2138
- // 第一步:发送验证码并存储 verificationInfo
2139
- const verificationInfo = await this.getVerification(userInfo)
2140
-
2141
- return {
2142
- data: {
2143
- // 第二步:等待用户输入验证码(通过 Promise 包装用户输入事件)
2144
- updateUser: async (attributes: UpdateUserAttributes): Promise<SignInRes> => {
2145
- this.validateParams(attributes, {
2146
- nonce: { required: true, message: 'Nonce is required' },
2147
- })
2148
- try {
2149
- if (attributes.password) {
2150
- // 第三步:待用户输入完验证码之后,验证验证码
2151
- const verificationTokenRes = await this.verify({
2152
- verification_id: verificationInfo.verification_id,
2153
- verification_code: attributes.nonce,
2154
- })
2155
-
2156
- // 第四步:获取 sudo_token
2157
- const sudoRes = await this.oauthInstance.authApi.sudo({
2158
- verification_token: verificationTokenRes.verification_token,
2159
- })
2160
-
2161
- await this.oauthInstance.authApi.setPassword({
2162
- new_password: attributes.password,
2163
- sudo_token: sudoRes.sudo_token,
2164
- })
2165
- } else {
2166
- await this.signInWithUsername({
2167
- verificationInfo,
2168
- verificationCode: attributes.nonce,
2169
- ...userInfo,
2170
- loginType: userInfo.email ? 'email' : 'phone',
2171
- })
2172
- }
2195
+ public async grantProviderToken(params: authModels.GrantProviderTokenRequest,): Promise<authModels.GrantProviderTokenResponse> {
2196
+ return this.oauthInstance.authApi.grantProviderToken(params)
2197
+ }
2173
2198
 
2174
- const { data: { session } = {} } = await this.getSession()
2199
+ public async patchProviderToken(params: authModels.PatchProviderTokenRequest,): Promise<authModels.PatchProviderTokenResponse> {
2200
+ return this.oauthInstance.authApi.patchProviderToken(params)
2201
+ }
2175
2202
 
2176
- return { data: { user: session.user, session }, error: null }
2177
- } catch (error) {
2178
- return { data: {}, error: new AuthError(error) }
2179
- }
2180
- },
2181
- },
2182
- error: null,
2183
- }
2184
- } catch (error) {
2185
- return { data: {}, error: new AuthError(error) }
2186
- }
2203
+ public async signInWithProvider(params: authModels.SignInWithProviderRequest): Promise<LoginState> {
2204
+ await this.oauthInstance.authApi.signInWithProvider(params)
2205
+ return this.createLoginState(params)
2187
2206
  }
2188
2207
 
2189
- /**
2190
- * https://supabase.com/docs/reference/javascript/auth-resend
2191
- * 重新发送验证码
2192
- * @param params
2193
- * @returns Promise<ResendRes>
2194
- */
2195
- async resend(params: ResendReq): Promise<ResendRes> {
2196
- try {
2197
- // 参数校验:email或phone必填其一
2198
- this.validateAtLeastOne(params, [['email'], ['phone']], 'You must provide either an email or phone number')
2208
+ public async signInWithWechat(params: any = {}) {
2209
+ await this.oauthInstance.authApi.signInWithWechat(params)
2210
+ return this.createLoginState(params)
2211
+ }
2199
2212
 
2200
- const target = params.type === 'signup' ? 'ANY' : 'USER'
2201
- const data: { email?: string; phone_number?: string; target: 'USER' | 'ANY' } = { target }
2202
- if ('email' in params) {
2203
- data.email = params.email
2204
- }
2213
+ public async grantToken(params: authModels.GrantTokenRequest): Promise<LoginState> {
2214
+ await this.oauthInstance.authApi.grantToken(params)
2215
+ return this.createLoginState()
2216
+ }
2205
2217
 
2206
- if ('phone' in params) {
2207
- data.phone_number = this.formatPhone(params.phone)
2208
- }
2218
+ public async genProviderRedirectUri(params: authModels.GenProviderRedirectUriRequest,): Promise<authModels.GenProviderRedirectUriResponse> {
2219
+ return this.oauthInstance.authApi.genProviderRedirectUri(params)
2220
+ }
2209
2221
 
2210
- // 重新发送验证码
2211
- const { verification_id: verificationId } = await this.oauthInstance.authApi.getVerification(data)
2222
+ public async resetPassword(params: authModels.ResetPasswordRequest): Promise<void> {
2223
+ return this.oauthInstance.authApi.resetPassword(params)
2224
+ }
2212
2225
 
2213
- return {
2214
- data: { messageId: verificationId },
2215
- error: null,
2216
- }
2217
- } catch (error: any) {
2218
- return {
2219
- data: {},
2220
- error: new AuthError(error),
2221
- }
2222
- }
2226
+ public async deviceAuthorize(params: authModels.DeviceAuthorizeRequest): Promise<authModels.DeviceAuthorizeResponse> {
2227
+ return this.oauthInstance.authApi.deviceAuthorize(params)
2223
2228
  }
2224
2229
 
2225
- /**
2226
- * https://supabase.com/docs/reference/javascript/auth-setsession
2227
- * 使用access_token和refresh_token来设置会话
2228
- * @param params
2229
- * @returns Promise<SignInRes>
2230
- */
2231
- async setSession(params: SetSessionReq): Promise<SignInRes> {
2232
- try {
2233
- this.validateParams(params, {
2234
- access_token: { required: true, message: 'Access token is required' },
2235
- refresh_token: { required: true, message: 'Refresh token is required' },
2236
- })
2230
+ public async sudo(params: authModels.SudoRequest): Promise<authModels.SudoResponse> {
2231
+ return this.oauthInstance.authApi.sudo(params)
2232
+ }
2237
2233
 
2238
- await this.oauthInstance.oauth2client.refreshToken(params, { throwError: true })
2234
+ public async deleteMe(params: authModels.WithSudoRequest): Promise<authModels.UserProfile> {
2235
+ return this.oauthInstance.authApi.deleteMe(params)
2236
+ }
2239
2237
 
2240
- const { data: { session } = {} } = await this.getSession()
2238
+ public async getProviders(): Promise<authModels.ProvidersResponse> {
2239
+ return this.oauthInstance.authApi.getProviders()
2240
+ }
2241
2241
 
2242
- this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.SIGNED_IN })
2242
+ public async loginScope(): Promise<string> {
2243
+ return this.oauthInstance.authApi.loginScope()
2244
+ }
2243
2245
 
2244
- return { data: { user: session.user, session }, error: null }
2245
- } catch (error) {
2246
- return { data: {}, error: new AuthError(error) }
2247
- }
2246
+ public async loginGroups(): Promise<string[]> {
2247
+ return this.oauthInstance.authApi.loginGroups()
2248
2248
  }
2249
2249
 
2250
- // https://supabase.com/docs/reference/javascript/auth-exchangecodeforsession
2251
- async exchangeCodeForSession() {
2252
- //
2250
+ public async onLoginStateChanged(callback: Function) {
2251
+ this.config.eventBus?.on(EVENTS.LOGIN_STATE_CHANGED, async (params) => {
2252
+ // getLoginState会重复触发getCredentials,导致死循环,所以getCredentials出错不再出发getLoginState
2253
+ const loginState = params?.data?.eventType !== LOGIN_STATE_CHANGED_TYPE.CREDENTIALS_ERROR ? await this.getLoginState() : {}
2254
+ callback.call(this, { ...params, ...loginState })
2255
+ })
2256
+ // 立刻执行一次回调
2257
+ const loginState = await this.getLoginState()
2258
+ callback.call(this, loginState)
2253
2259
  }
2254
2260
 
2255
2261
  /**
2256
- * 删除当前用户
2262
+ * 强制刷新token
2257
2263
  * @param params
2258
2264
  * @returns
2259
2265
  */
2260
- async deleteUser(params: DeleteMeReq): Promise<CommonRes> {
2261
- try {
2262
- this.validateParams(params, {
2263
- password: { required: true, message: 'Password is required' },
2264
- })
2265
-
2266
- const { sudo_token } = await this.oauthInstance.authApi.sudo(params)
2267
-
2268
- await this.oauthInstance.authApi.deleteMe({ sudo_token })
2269
-
2270
- return { data: {}, error: null }
2271
- } catch (error) {
2272
- return { data: {}, error: new AuthError(error) }
2273
- }
2266
+ public async refreshTokenForce(params: { version?: string }): Promise<Credentials> {
2267
+ return this.oauthInstance.authApi.refreshTokenForce(params)
2274
2268
  }
2275
2269
 
2276
2270
  /**
2277
- * 跳转系统默认登录页
2278
- * @returns {Promise<authModels.ToDefaultLoginPage>}
2279
- * @memberof Auth
2271
+ * 获取身份信息
2272
+ * @returns
2280
2273
  */
2281
- async toDefaultLoginPage(params: authModels.ToDefaultLoginPage = {}): Promise<CommonRes> {
2282
- try {
2283
- const configVersion = params.config_version || 'env'
2284
- const query = Object.keys(params.query || {})
2285
- .map(key => `${key}=${params.query[key]}`)
2286
- .join('&')
2274
+ public async getCredentials(): Promise<Credentials> {
2275
+ return this.oauthInstance.authApi.getCredentials()
2276
+ }
2277
+ /**
2278
+ * 写入身份信息
2279
+ */
2280
+ public async setCredentials(credentials: Credentials) {
2281
+ await this.oauthInstance.oauth2client.setCredentials(credentials)
2282
+ }
2287
2283
 
2288
- if (adapterForWxMp.isMatch()) {
2289
- wx.navigateTo({ url: `/packages/$wd_system/pages/login/index${query ? `?${query}` : ''}` })
2290
- } else {
2291
- const redirectUri = params.redirect_uri || window.location.href
2292
- const urlObj = new URL(redirectUri)
2293
- const loginPage = `${urlObj.origin}/__auth/?app_id=${params.app_id || ''}&env_id=${this.config.env}&client_id=${
2294
- this.config.clientId || this.config.env
2295
- }&config_version=${configVersion}&redirect_uri=${encodeURIComponent(redirectUri)}${query ? `&${query}` : ''}`
2296
- window.location.href = loginPage
2297
- }
2298
- return { data: {}, error: null }
2299
- } catch (error) {
2300
- return { data: {}, error: new AuthError(error) }
2301
- }
2284
+ @catchErrorsDecorator({
2285
+ title: '获取身份源类型',
2286
+ messages: [
2287
+ '请确认以下各项:',
2288
+ ' 1 - 调用 auth().getProviderSubType() 的语法或参数是否正确',
2289
+ `如果问题依然存在,建议到官方问答社区提问或寻找帮助:${COMMUNITY_SITE_URL}`,
2290
+ ],
2291
+ })
2292
+ public async getProviderSubType(): Promise<authModels.ProviderSubType> {
2293
+ return this.oauthInstance.authApi.getProviderSubType()
2294
+ }
2295
+
2296
+ public async createCaptchaData(params: { state: string; redirect_uri?: string }) {
2297
+ return this.oauthInstance.authApi.createCaptchaData(params)
2302
2298
  }
2303
2299
 
2304
- /**
2305
- * 自定义登录
2306
- * @param getTickFn () => Promise<string>, 获取自定义登录 ticket 的函数
2307
- * @returns
2308
- */
2309
- async signInWithCustomTicket(getTickFn?: authModels.GetCustomSignTicketFn): Promise<SignInRes> {
2310
- if (getTickFn) {
2311
- this.setCustomSignFunc(getTickFn)
2312
- }
2300
+ public async verifyCaptchaData(params: { token: string; key: string }) {
2301
+ return this.oauthInstance.authApi.verifyCaptchaData(params)
2302
+ }
2313
2303
 
2314
- try {
2315
- await this.oauthInstance.authApi.signInWithCustomTicket()
2316
- const loginState = await this.createLoginState()
2304
+ public async getMiniProgramQrCode(params: authModels.GetMiniProgramQrCodeRequest,): Promise<authModels.GetMiniProgramQrCodeResponse> {
2305
+ return this.oauthInstance.authApi.getMiniProgramCode(params)
2306
+ }
2317
2307
 
2318
- const { data: { session } = {} } = await this.getSession()
2308
+ public async getMiniProgramQrCodeStatus(params: authModels.GetMiniProgramQrCodeStatusRequest,): Promise<authModels.GetMiniProgramQrCodeStatusResponse> {
2309
+ return this.oauthInstance.authApi.getMiniProgramQrCodeStatus(params)
2310
+ }
2319
2311
 
2320
- // loginState返回是为了兼容v2版本
2321
- return { ...(loginState as any), data: { user: session.user, session }, error: null }
2322
- } catch (error) {
2323
- return { data: {}, error: new AuthError(error) }
2324
- }
2312
+ public async modifyPassword(params: authModels.ModifyUserBasicInfoRequest): Promise<void> {
2313
+ return this.oauthInstance.authApi.modifyPassword(params)
2314
+ }
2315
+
2316
+ public async modifyPasswordWithoutLogin(params: authModels.ModifyPasswordWithoutLoginRequest): Promise<void> {
2317
+ return this.oauthInstance.authApi.modifyPasswordWithoutLogin(params)
2318
+ }
2319
+
2320
+ public async getUserBehaviorLog(params: authModels.GetUserBehaviorLog): Promise<authModels.GetUserBehaviorLogRes> {
2321
+ return this.oauthInstance.authApi.getUserBehaviorLog(params)
2325
2322
  }
2326
2323
 
2327
2324
  /**
2328
- * 小程序openId静默登录
2329
- * @param params
2330
- * @returns Promise<SignInRes>
2325
+ * sms/email 验证码登录/注册,逻辑一致收敛
2331
2326
  */
2332
- async signInWithOpenId({ useWxCloud = true } = {}): Promise<SignInRes> {
2333
- if (!adapterForWxMp.isMatch()) {
2334
- throw Error('wx api undefined')
2335
- }
2336
- const wxInfo = wx.getAccountInfoSync().miniProgram
2337
-
2338
- const mainFunc = async (code) => {
2339
- let result: authModels.GrantProviderTokenResponse | undefined = undefined
2340
- let credentials: Credentials | undefined = undefined
2327
+ public async signInWithUsername({
2328
+ verificationInfo = { verification_id: '', is_user: false },
2329
+ verificationCode = '',
2330
+ username: rawUsername = '',
2331
+ bindInfo = undefined,
2332
+ loginType = '',
2333
+ }: {
2334
+ verificationInfo?: authModels.GetVerificationResponse
2335
+ verificationCode?: string
2336
+ username?: string
2337
+ bindInfo?: any
2338
+ loginType?: string
2339
+ }): Promise<LoginState> {
2340
+ try {
2341
+ // 1. 验证验证码
2342
+ const verifyRes = await this.oauthInstance.authApi.verify({
2343
+ verification_id: verificationInfo.verification_id,
2344
+ verification_code: verificationCode,
2345
+ })
2341
2346
 
2342
- try {
2343
- result = await this.oauthInstance.authApi.grantProviderToken(
2344
- {
2345
- provider_id: wxInfo?.appId,
2346
- provider_code: code,
2347
- provider_params: {
2348
- provider_code_type: 'open_id',
2349
- appid: wxInfo?.appId,
2350
- },
2351
- },
2352
- useWxCloud,
2353
- )
2347
+ if ((verifyRes as any)?.error_code) {
2348
+ throw verifyRes
2349
+ }
2354
2350
 
2355
- if ((result as any)?.error_code || !result.provider_token) {
2356
- throw result
2357
- }
2351
+ // eslint-disable-next-line @typescript-eslint/naming-convention
2352
+ const { verification_token } = verifyRes
2358
2353
 
2359
- credentials = await this.oauthInstance.authApi.signInWithProvider(
2360
- { provider_token: result.provider_token },
2361
- useWxCloud,
2362
- )
2354
+ // 手机登录参数
2355
+ let username = this.formatPhone(rawUsername)
2356
+ let signUpParam: any = { phone_number: username }
2363
2357
 
2364
- if ((credentials as any)?.error_code) {
2365
- throw credentials
2366
- }
2367
- } catch (error) {
2368
- throw error
2358
+ // 邮箱登录参数
2359
+ if (loginType === 'email') {
2360
+ username = rawUsername
2361
+ signUpParam = { email: username }
2369
2362
  }
2370
- await this.oauthInstance.oauth2client.setCredentials(credentials as Credentials)
2371
- }
2372
2363
 
2373
- try {
2374
- await new Promise((resolve, reject) => {
2375
- wx.login({
2376
- success: async (res: { code: string }) => {
2377
- try {
2378
- await mainFunc(res.code)
2379
- resolve(true)
2380
- } catch (error) {
2381
- reject(error)
2382
- }
2383
- },
2384
- fail: (res: any) => {
2385
- const error = new Error(res?.errMsg)
2386
- ;(error as any).code = res?.errno
2387
- reject(error)
2388
- },
2364
+ // 2. 根据是否已经是用户,分别走登录或注册逻辑
2365
+ if (verificationInfo.is_user) {
2366
+ // 私有化环境或者自定义应用走v1版本的老逻辑
2367
+ const signInRes = await this.oauthInstance.authApi.signIn({
2368
+ username,
2369
+ verification_token,
2389
2370
  })
2390
- })
2391
2371
 
2392
- const loginState = await this.createLoginState()
2372
+ if ((signInRes as any)?.error_code) {
2373
+ throw signInRes
2374
+ }
2393
2375
 
2394
- const { data: { session } = {} } = await this.getSession()
2376
+ if (bindInfo) {
2377
+ const bindRes = await this.oauthInstance.authApi.bindWithProvider({
2378
+ provider_token: (bindInfo as any)?.providerToken,
2379
+ })
2395
2380
 
2396
- // loginState返回是为了兼容v2版本
2397
- return { ...(loginState as any), data: { user: session.user, session }, error: null }
2381
+ if ((bindRes as any)?.error_code) {
2382
+ throw bindRes
2383
+ }
2384
+ }
2385
+ } else {
2386
+ // 自定义应用走signUp逻辑
2387
+ const signUpRes = await this.oauthInstance.authApi.signUp({
2388
+ ...signUpParam,
2389
+ verification_token,
2390
+ provider_token: (bindInfo as any)?.providerId,
2391
+ })
2392
+
2393
+ if ((signUpRes as any)?.error_code) {
2394
+ throw signUpRes
2395
+ }
2396
+ }
2397
+
2398
+ return this.createLoginState()
2398
2399
  } catch (error) {
2399
- return { data: {}, error: new AuthError(error) }
2400
+ throw error
2400
2401
  }
2401
2402
  }
2402
2403
 
2403
- /**
2404
- * 小程序手机号授权登录,目前只支持全托管手机号授权登录
2405
- * @param params
2406
- * @returns Promise<SignInRes>
2407
- */
2408
- async signInWithPhoneAuth({ phoneCode = '' }): Promise<SignInRes> {
2409
- if (!adapterForWxMp.isMatch()) {
2410
- return { data: {}, error: new AuthError({ message: 'wx api undefined' }) }
2411
- }
2412
- const wxInfo = wx.getAccountInfoSync().miniProgram
2413
- const providerInfo = {
2414
- provider_params: { provider_code_type: 'phone' },
2415
- provider_id: wxInfo.appId,
2416
- }
2404
+ async createLoginState(
2405
+ params?: { version?: string; query?: any },
2406
+ options?: { asyncRefreshUser?: boolean; userInfo?: any },
2407
+ ): Promise<LoginState> {
2408
+ const loginState = new LoginState({
2409
+ envId: this.config.env,
2410
+ cache: this.cache,
2411
+ oauthInstance: this.oauthInstance,
2412
+ })
2417
2413
 
2418
- const { code } = await wx.login()
2419
- ;(providerInfo as any).provider_code = code
2414
+ await loginState.checkLocalStateAsync()
2420
2415
 
2421
- try {
2422
- let providerToken = await this.oauthInstance.authApi.grantProviderToken(providerInfo)
2423
- if (providerToken.error_code) {
2424
- throw providerToken
2416
+ if (options?.userInfo) {
2417
+ loginState.user.setLocalUserInfo(options.userInfo)
2418
+ } else {
2419
+ if (options?.asyncRefreshUser) {
2420
+ loginState.user.refresh(params)
2421
+ } else {
2422
+ await loginState.user.refresh(params)
2425
2423
  }
2424
+ }
2426
2425
 
2427
- providerToken = await this.oauthInstance.authApi.patchProviderToken({
2428
- provider_token: providerToken.provider_token,
2429
- provider_id: wxInfo.appId,
2430
- provider_params: {
2431
- code: phoneCode,
2432
- provider_code_type: 'phone',
2433
- },
2434
- })
2435
- if (providerToken.error_code) {
2436
- throw providerToken
2437
- }
2426
+ this.config.eventBus?.fire(EVENTS.LOGIN_STATE_CHANGED, { eventType: LOGIN_STATE_CHANGED_TYPE.SIGN_IN })
2438
2427
 
2439
- const signInRes = await this.oauthInstance.authApi.signInWithProvider({
2440
- provider_token: providerToken.provider_token,
2441
- })
2428
+ this.config.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.SIGNED_IN })
2429
+ return loginState
2430
+ }
2442
2431
 
2443
- if ((signInRes as any)?.error_code) {
2444
- throw signInRes
2445
- }
2446
- } catch (error) {
2447
- return { data: {}, error: new AuthError(error) }
2432
+ async setAccessKey() {
2433
+ let accessToken = ''
2434
+ let scope = ''
2435
+ if (this.config.accessKey) {
2436
+ accessToken = this.config.accessKey
2437
+ scope = 'accessKey'
2438
+ } else if (this.config.auth?.secretId && this.config.auth?.secretKey) {
2439
+ accessToken = ''
2440
+ scope = DEFAULT_NODE_ACCESS_SCOPE
2448
2441
  }
2449
2442
 
2450
- const loginState = await this.createLoginState()
2451
-
2452
- const { data: { session } = {} } = await this.getSession()
2443
+ if (!scope) {
2444
+ return
2445
+ }
2453
2446
 
2454
- // loginState返回是为了兼容v2版本
2455
- return { ...(loginState as any), data: { user: session.user, session }, error: null }
2447
+ try {
2448
+ this.oauthInstance.oauth2client.setAccessKeyCredentials({
2449
+ access_token: accessToken,
2450
+ token_type: 'Bearer',
2451
+ scope,
2452
+ expires_at: new Date(+new Date() + +new Date()),
2453
+ expires_in: +new Date() + +new Date(),
2454
+ })
2455
+ } catch (error) {
2456
+ console.warn('accessKey error: ', error)
2457
+ }
2456
2458
  }
2457
2459
 
2458
- private formatPhone(phone: string) {
2460
+ protected formatPhone(phone: string) {
2459
2461
  if (!/\s+/.test(phone) && /^\+\d{1,3}\d+/.test(phone)) {
2460
2462
  return phone.replace(/^(\+\d{1,2})(\d+)$/, '$1 $2')
2461
2463
  }
@@ -2580,7 +2582,10 @@ class Auth {
2580
2582
  }
2581
2583
  }
2582
2584
 
2583
- type TInitAuthOptions = Pick<ICloudbaseAuthConfig, 'region' | 'persistence' | 'i18n' | 'accessKey' | 'useWxCloud'> &
2585
+ type TInitAuthOptions = Pick<
2586
+ ICloudbaseAuthConfig,
2587
+ 'region' | 'persistence' | 'i18n' | 'accessKey' | 'useWxCloud' | 'auth'
2588
+ > &
2584
2589
  Partial<AuthOptions> & {
2585
2590
  detectSessionInUrl?: boolean
2586
2591
  }
@@ -2633,6 +2638,7 @@ export function generateAuthInstance(
2633
2638
  headers: { 'X-SDK-Version': `@cloudbase/js-sdk/${config.sdkVersion}`, ...(config.headers || {}) },
2634
2639
  detectSessionInUrl: config.detectSessionInUrl,
2635
2640
  debug,
2641
+ auth: options.app?.config?.auth,
2636
2642
  }),)
2637
2643
 
2638
2644
  const authInstance = new Auth({
@@ -2649,6 +2655,7 @@ export function generateAuthInstance(
2649
2655
  runtime: runtime || 'web',
2650
2656
  _fromApp: cloudbase,
2651
2657
  oauthInstance,
2658
+ auth: options.app?.config?.auth,
2652
2659
  })
2653
2660
 
2654
2661
  // Initialize session with user info callback
@@ -2680,6 +2687,51 @@ export function generateAuthInstance(
2680
2687
 
2681
2688
  const NAMESPACE = 'auth'
2682
2689
 
2690
+ /**
2691
+ * 计算两个字符串之间的编辑距离(Levenshtein distance)
2692
+ * 用于在用户调用不存在的 Auth 方法时,推荐最相似的方法名
2693
+ */
2694
+ function levenshteinDistance(a: string, b: string): number {
2695
+ const matrix: number[][] = []
2696
+ for (let i = 0; i <= a.length; i++) {
2697
+ matrix[i] = [i]
2698
+ }
2699
+ for (let j = 0; j <= b.length; j++) {
2700
+ matrix[0][j] = j
2701
+ }
2702
+ for (let i = 1; i <= a.length; i++) {
2703
+ for (let j = 1; j <= b.length; j++) {
2704
+ const cost = a[i - 1].toLowerCase() === b[j - 1].toLowerCase() ? 0 : 1
2705
+ matrix[i][j] = Math.min(
2706
+ matrix[i - 1][j] + 1,
2707
+ matrix[i][j - 1] + 1,
2708
+ matrix[i - 1][j - 1] + cost,
2709
+ )
2710
+ }
2711
+ }
2712
+ return matrix[a.length][b.length]
2713
+ }
2714
+
2715
+ /**
2716
+ * 在方法列表中查找与目标名称最相似的方法
2717
+ * @returns 最相似的方法名,如果没有足够相似的则返回 null
2718
+ */
2719
+ function findSimilarMethod(target: string, methods: string[]): string | null {
2720
+ let bestMatch: string | null = null
2721
+ let bestDistance = Infinity
2722
+
2723
+ for (const method of methods) {
2724
+ // 跳过私有方法和构造函数
2725
+ if (method.startsWith('_') || method === 'constructor') continue
2726
+ const distance = levenshteinDistance(target, method)
2727
+ if (distance < bestDistance) {
2728
+ bestDistance = distance
2729
+ bestMatch = method
2730
+ }
2731
+ }
2732
+ return bestMatch
2733
+ }
2734
+
2683
2735
  const component: ICloudbaseComponent = {
2684
2736
  name: COMPONENT_NAME,
2685
2737
  namespace: NAMESPACE,
@@ -2735,8 +2787,30 @@ const component: ICloudbaseComponent = {
2735
2787
  const authProto = auth.call(this, config)
2736
2788
  Object.assign(auth, authProto)
2737
2789
  Object.setPrototypeOf(auth, Object.getPrototypeOf(authProto))
2738
- this[NAMESPACE] = auth
2739
- return auth
2790
+
2791
+ // 包装 Proxy,当用户调用不存在的方法时提供 "Did you mean?" 提示
2792
+ const authWithHint = typeof Proxy !== 'undefined'
2793
+ ? new Proxy(auth, {
2794
+ get(target, prop, receiver) {
2795
+ const value = Reflect.get(target, prop, receiver)
2796
+ if (value !== undefined || typeof prop !== 'string') {
2797
+ return value
2798
+ }
2799
+ // 查找最相似的方法名
2800
+ const allKeys = Object.getOwnPropertyNames(Object.getPrototypeOf(target))
2801
+ .concat(Object.keys(target))
2802
+ .filter(k => typeof target[k] === 'function')
2803
+ const suggestion = findSimilarMethod(prop, allKeys)
2804
+ if (suggestion) {
2805
+ console.warn(`[CloudBase Auth] auth.${prop} is not a function. Did you mean: auth.${suggestion}() ?`)
2806
+ }
2807
+ return value
2808
+ },
2809
+ })
2810
+ : auth
2811
+
2812
+ this[NAMESPACE] = authWithHint
2813
+ return authWithHint
2740
2814
  },
2741
2815
  }
2742
2816
 
@@ -2746,7 +2820,7 @@ try {
2746
2820
  cloudbase.registerComponent(component)
2747
2821
  } catch (e) {}
2748
2822
 
2749
- export { UserInfo, Auth }
2823
+ export { UserInfo, Auth, WeixinAuthProvider, CustomAuthProvider, AnonymousAuthProvider }
2750
2824
  /**
2751
2825
  * @api 手动注册至cloudbase app
2752
2826
  */