@cloudbase/auth 2.26.0 → 2.26.2

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