@cloudbase/auth 3.1.8 → 3.1.10

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