@cloudbase/oauth 3.0.0 → 3.0.1

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.
Files changed (70) hide show
  1. package/dist/cjs/auth/apis.d.ts +22 -4
  2. package/dist/cjs/auth/apis.js +255 -66
  3. package/dist/cjs/auth/auth-error.d.ts +6 -0
  4. package/dist/cjs/auth/auth-error.js +32 -0
  5. package/dist/cjs/auth/consts.d.ts +22 -0
  6. package/dist/cjs/auth/consts.js +24 -2
  7. package/dist/cjs/auth/models.d.ts +13 -4
  8. package/dist/cjs/auth/models.js +1 -1
  9. package/dist/cjs/captcha/captcha-dom.d.ts +3 -0
  10. package/dist/cjs/captcha/captcha-dom.js +223 -0
  11. package/dist/cjs/captcha/captcha.d.ts +3 -1
  12. package/dist/cjs/captcha/captcha.js +11 -102
  13. package/dist/cjs/index.d.ts +12 -2
  14. package/dist/cjs/index.js +27 -4
  15. package/dist/cjs/oauth2client/interface.d.ts +1 -1
  16. package/dist/cjs/oauth2client/interface.js +1 -1
  17. package/dist/cjs/oauth2client/models.d.ts +15 -1
  18. package/dist/cjs/oauth2client/models.js +1 -1
  19. package/dist/cjs/oauth2client/oauth2client.d.ts +62 -3
  20. package/dist/cjs/oauth2client/oauth2client.js +426 -131
  21. package/dist/cjs/utils/base64.d.ts +5 -0
  22. package/dist/cjs/utils/base64.js +15 -2
  23. package/dist/cjs/utils/encryptlong/index.js +22 -16
  24. package/dist/cjs/utils/index.js +1 -1
  25. package/dist/cjs/utils/mp.js +4 -4
  26. package/dist/cjs/utils/urlSearchParams.js +1 -1
  27. package/dist/esm/auth/apis.d.ts +22 -4
  28. package/dist/esm/auth/apis.js +130 -10
  29. package/dist/esm/auth/auth-error.d.ts +6 -0
  30. package/dist/esm/auth/auth-error.js +9 -0
  31. package/dist/esm/auth/consts.d.ts +22 -0
  32. package/dist/esm/auth/consts.js +22 -0
  33. package/dist/esm/auth/models.d.ts +13 -4
  34. package/dist/esm/captcha/captcha-dom.d.ts +3 -0
  35. package/dist/esm/captcha/captcha-dom.js +129 -0
  36. package/dist/esm/captcha/captcha.d.ts +3 -1
  37. package/dist/esm/captcha/captcha.js +14 -97
  38. package/dist/esm/index.d.ts +12 -2
  39. package/dist/esm/index.js +20 -3
  40. package/dist/esm/oauth2client/interface.d.ts +1 -1
  41. package/dist/esm/oauth2client/models.d.ts +15 -1
  42. package/dist/esm/oauth2client/oauth2client.d.ts +62 -3
  43. package/dist/esm/oauth2client/oauth2client.js +200 -55
  44. package/dist/esm/utils/base64.d.ts +5 -0
  45. package/dist/esm/utils/base64.js +12 -0
  46. package/dist/esm/utils/encryptlong/index.js +21 -15
  47. package/dist/esm/utils/mp.js +3 -3
  48. package/dist/miniprogram/index.js +1 -1
  49. package/package.json +10 -4
  50. package/src/auth/apis.ts +222 -17
  51. package/src/auth/auth-error.ts +21 -0
  52. package/src/auth/consts.ts +28 -0
  53. package/src/auth/models.ts +13 -4
  54. package/src/captcha/captcha-dom.ts +178 -0
  55. package/src/captcha/captcha.ts +25 -114
  56. package/src/index.ts +54 -4
  57. package/src/oauth2client/interface.ts +1 -1
  58. package/src/oauth2client/models.ts +29 -1
  59. package/src/oauth2client/oauth2client.ts +308 -55
  60. package/src/utils/base64.ts +12 -0
  61. package/src/utils/encryptlong/index.js +20 -14
  62. package/src/utils/index.ts +1 -0
  63. package/src/utils/mp.ts +3 -3
  64. package/src/utils/urlSearchParams.ts +2 -0
  65. package/tsconfig.json +1 -0
  66. package/dist/cjs/utils/cloudbase-adapter-wx_mp.d.ts +0 -1
  67. package/dist/cjs/utils/cloudbase-adapter-wx_mp.js +0 -40
  68. package/dist/esm/utils/cloudbase-adapter-wx_mp.d.ts +0 -1
  69. package/dist/esm/utils/cloudbase-adapter-wx_mp.js +0 -35
  70. package/src/utils/cloudbase-adapter-wx_mp.ts +0 -42
package/src/auth/apis.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- import { ApiUrls, ApiUrlsV2, ErrorType } from './consts'
3
+ import { ApiUrls, ApiUrlsV2, AUTH_STATE_CHANGED_TYPE, ErrorType, EVENTS, OAUTH_TYPE } from './consts'
4
4
  import {
5
5
  GetVerificationRequest,
6
6
  GetVerificationResponse,
@@ -56,7 +56,7 @@ import {
56
56
  GetUserBehaviorLog,
57
57
  GetUserBehaviorLogRes,
58
58
  RevokeDeviceRequest,
59
- SignoutReponse,
59
+ SignoutResponse,
60
60
  ProvidersResponse,
61
61
  SignoutRequest,
62
62
  ModifyPasswordWithoutLoginRequest,
@@ -69,9 +69,10 @@ import { deepClone } from '../utils'
69
69
  import MyURLSearchParams from '../utils/urlSearchParams'
70
70
  import { SDKAdapterInterface } from '@cloudbase/adapter-interface'
71
71
  import { ICloudbaseConfig } from '@cloudbase/types'
72
+ import { AuthError } from './auth-error'
72
73
 
73
- function getEncryptUtils(isEncrypt, adapter: SDKAdapterInterface) {
74
- const getUtils = () => {
74
+ async function getEncryptUtils(isEncrypt, adapter: SDKAdapterInterface) {
75
+ const getUtils = async () => {
75
76
  try {
76
77
  /* eslint-disable */
77
78
  // @ts-ignore
@@ -79,7 +80,13 @@ function getEncryptUtils(isEncrypt, adapter: SDKAdapterInterface) {
79
80
  /* eslint-enable */
80
81
  return utils
81
82
  } catch (error) {
82
- return
83
+ try {
84
+ // @ts-ignore
85
+ const utils = await import('../utils/encrypt')
86
+ return utils
87
+ } catch (error) {
88
+ return
89
+ }
83
90
  }
84
91
  }
85
92
 
@@ -118,7 +125,17 @@ export interface AuthOptions {
118
125
  onCredentialsError?: (data: { msg: string; eventType?: 'credentials_error' }) => void
119
126
  headers?: { [key: string]: string }
120
127
  i18n?: ICloudbaseConfig['i18n']
121
- publishable_key?: string
128
+ useWxCloud?: boolean
129
+ eventBus?: any
130
+ /**
131
+ * Set to true if you want to automatically detect OAuth grants in the URL
132
+ * and exchange the code for credentials.
133
+ */
134
+ detectSessionInUrl?: boolean
135
+ /**
136
+ * Enable debug logging
137
+ */
138
+ debug?: boolean
122
139
  }
123
140
 
124
141
  /**
@@ -158,17 +175,19 @@ export class Auth {
158
175
  onCredentialsError: opts.onCredentialsError,
159
176
  headers: opts.headers || {},
160
177
  i18n: opts.i18n,
161
- publishable_key: opts.publishable_key,
178
+ debug: opts.debug,
162
179
  }
163
180
  oAuth2Client = new OAuth2Client(initOptions)
164
181
  }
165
182
  if (!request) {
166
183
  const baseRequest = oAuth2Client.request.bind(oAuth2Client)
167
184
  const captcha = new Captcha({
185
+ env: opts.env,
168
186
  clientId: opts.clientId,
169
187
  request: baseRequest,
170
188
  storage: opts.storage,
171
189
  adapter: opts.adapter,
190
+ oauthInstance: this,
172
191
  ...opts.captchaOptions,
173
192
  })
174
193
  request = captcha.request.bind(captcha)
@@ -263,11 +282,11 @@ export class Auth {
263
282
  * Sign out.
264
283
  * @return {Object} A Promise<SignoutRequest> object.
265
284
  */
266
- public async signOut(params?: SignoutRequest): Promise<SignoutReponse> {
267
- let resp: SignoutReponse = {}
285
+ public async signOut(params?: SignoutRequest): Promise<SignoutResponse> {
286
+ let resp: SignoutResponse = {}
268
287
  if (params) {
269
288
  try {
270
- resp = await this.config.request<SignoutReponse>(ApiUrls.AUTH_SIGNOUT_URL, {
289
+ resp = await this.config.request<SignoutResponse>(ApiUrls.AUTH_SIGNOUT_URL, {
271
290
  method: 'POST',
272
291
  withCredentials: true,
273
292
  body: params,
@@ -332,9 +351,16 @@ export class Auth {
332
351
  withCredentials = true
333
352
  }
334
353
  }
354
+
355
+ const body = deepClone(params)
356
+
357
+ if (body.phone_number && !/^\+\d{1,3}\s+\d+/.test(body.phone_number)) {
358
+ body.phone_number = `+86 ${body.phone_number}`
359
+ }
360
+
335
361
  return this.config.request<GetVerificationResponse>(ApiUrls.VERIFICATION_URL, {
336
362
  method: 'POST',
337
- body: params,
363
+ body,
338
364
  withCaptcha: options?.withCaptcha || false,
339
365
  withCredentials,
340
366
  })
@@ -368,6 +394,7 @@ export class Auth {
368
394
  * @return {Promise<GenProviderRedirectUriResponse>} A Promise<GenProviderRedirectUriResponse> object.
369
395
  */
370
396
  public async genProviderRedirectUri(params: GenProviderRedirectUriRequest): Promise<GenProviderRedirectUriResponse> {
397
+ // eslint-disable-next-line @typescript-eslint/naming-convention
371
398
  const { provider_redirect_uri: redirect_uri, other_params: otherParams = {}, ...restParams } = params
372
399
  if (redirect_uri && !restParams.redirect_uri) {
373
400
  restParams.redirect_uri = redirect_uri
@@ -436,6 +463,154 @@ export class Auth {
436
463
  return Promise.resolve(credentials)
437
464
  }
438
465
 
466
+ public async toBindIdentity(params: {
467
+ provider_token: string
468
+ provider: string
469
+ credentials?: Credentials
470
+ fireEvent?: boolean
471
+ }) {
472
+ const credentials = params.credentials || (await this.config.credentialsClient.localCredentials.getCredentials())
473
+ let res: any
474
+ try {
475
+ await this.bindWithProvider(
476
+ {
477
+ provider_token: params.provider_token,
478
+ },
479
+ credentials,
480
+ )
481
+ res = { data: { type: OAUTH_TYPE.BIND_IDENTITY, provider: params.provider }, error: null }
482
+ } catch (error) {
483
+ res = { data: { type: OAUTH_TYPE.BIND_IDENTITY }, error: new AuthError(error) }
484
+ }
485
+
486
+ if (params.fireEvent) {
487
+ this.config.eventBus?.fire?.(EVENTS.AUTH_STATE_CHANGED, {
488
+ event: AUTH_STATE_CHANGED_TYPE.BIND_IDENTITY,
489
+ info: res,
490
+ })
491
+ }
492
+
493
+ return res
494
+ }
495
+
496
+ /**
497
+ * 获取初始 session(如从 URL 中的 OAuth 回调)。
498
+ * 用于 OAuth2Client.initialize() 回调。
499
+ * 内部处理 URL 检测、OAuth 验证,返回 credentials 和 user。
500
+ * 不调用 setCredentials,由 OAuth2Client 负责保存。
501
+ *
502
+ * @returns { data: { session: Credentials; user?: any } | null, error: Error | null }
503
+ */
504
+ public async getInitialSession(): Promise<{
505
+ data: { session: Credentials; user?: any } | null
506
+ error: Error | null
507
+ }> {
508
+ let data: any = {}
509
+ try {
510
+ // Check if running in browser
511
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
512
+ return { data: null, error: null }
513
+ }
514
+
515
+ // Parse URL parameters
516
+ const localSearch = new URLSearchParams(location?.search)
517
+ const code = localSearch.get('code')
518
+ const state = localSearch.get('state')
519
+
520
+ // No OAuth callback detected
521
+ if (!code || !state) {
522
+ return { data: null, error: null }
523
+ }
524
+
525
+ // Get provider from sessionStorage (saved during signInWithOAuth)
526
+ let cacheData: {
527
+ provider?: string
528
+ search?: string
529
+ hash?: string
530
+ type?: (typeof OAUTH_TYPE)[keyof typeof OAUTH_TYPE]
531
+ } | null = null
532
+ try {
533
+ cacheData = JSON.parse(sessionStorage.getItem(state) || 'null')
534
+ } catch {
535
+ // ignore
536
+ }
537
+ data = { type: cacheData?.type }
538
+ // Check for error in URL
539
+ const errorParam = localSearch.get('error')
540
+ const errorDescription = localSearch.get('error_description')
541
+ if (errorParam || errorDescription) {
542
+ return {
543
+ data,
544
+ error: new AuthError({ message: errorDescription || errorParam || 'Unknown error from OAuth provider' }),
545
+ }
546
+ }
547
+
548
+ const provider = cacheData?.provider || localSearch.get('provider')
549
+
550
+ if (!provider) {
551
+ return { data, error: new AuthError({ message: 'Provider is required for OAuth verification' }) }
552
+ }
553
+
554
+ // 获取当前页面的 redirect_uri
555
+ const redirectUri = location.origin + location.pathname
556
+
557
+ // Step 1: 获取 provider token
558
+ const { provider_token: providerToken } = await this.grantProviderToken({
559
+ provider_id: provider,
560
+ provider_redirect_uri: redirectUri,
561
+ provider_code: code,
562
+ })
563
+
564
+ let credentials: Credentials
565
+ let user: any = null
566
+ let res: any
567
+
568
+ if (cacheData.type === OAUTH_TYPE.BIND_IDENTITY) {
569
+ credentials = await this.config.credentialsClient.localCredentials.getCredentials()
570
+ res = await this.toBindIdentity({ provider, provider_token: providerToken, credentials })
571
+ } else if (cacheData.type === OAUTH_TYPE.SIGN_IN) {
572
+ res = this.getParamsByVersion({ provider }, 'AUTH_SIGN_IN_WITH_PROVIDER_URL')
573
+
574
+ // Step 2: 用 provider token 换取 credentials(直接调用 API,不触发 setCredentials)
575
+ credentials = await this.config.request<Credentials>(res.url, {
576
+ method: 'POST',
577
+ body: { provider_token: providerToken },
578
+ })
579
+
580
+ // Step 3: 获取 user info,传入 getCredentials 函数避免死锁
581
+ // 不调用 setCredentials,由 OAuth2Client._initialize 负责保存
582
+ try {
583
+ user = await this.getUserInfo({ credentials })
584
+ } catch (e) {
585
+ console.error('get user info error', e)
586
+ // 获取 user 失败不影响登录流程
587
+ }
588
+
589
+ res = { data: { ...data, session: credentials, user }, error: null }
590
+ }
591
+
592
+ // Clean up URL parameters and restore original URL state
593
+ localSearch.delete('code')
594
+ localSearch.delete('state')
595
+ localSearch.delete('provider')
596
+ this.restoreUrlState(
597
+ cacheData?.search === undefined ? `?${localSearch.toString()}` : cacheData.search,
598
+ cacheData?.hash || location.hash,
599
+ )
600
+
601
+ // Remove session storage
602
+ try {
603
+ sessionStorage.removeItem(state)
604
+ } catch {
605
+ // ignore
606
+ }
607
+
608
+ return res
609
+ } catch (error) {
610
+ return { data, error: new AuthError(error) }
611
+ }
612
+ }
613
+
439
614
  /**
440
615
  * Signin with custom.
441
616
  * @param {SignInCustomRequest} params A SignInCustomRequest object.
@@ -469,11 +644,12 @@ export class Auth {
469
644
  * @param {BindWithProviderRequest} params A BindWithProviderRequest object.
470
645
  * @return {Promise<void>} A Promise<any> object.
471
646
  */
472
- public async bindWithProvider(params: BindWithProviderRequest): Promise<void> {
647
+ public async bindWithProvider(params: BindWithProviderRequest, credentials?: Credentials): Promise<void> {
473
648
  return this.config.request<any>(ApiUrls.PROVIDER_BIND_URL, {
474
649
  method: 'POST',
475
650
  body: params,
476
651
  withCredentials: true,
652
+ getCredentials: credentials ? () => credentials : undefined,
477
653
  })
478
654
  }
479
655
 
@@ -487,9 +663,14 @@ export class Auth {
487
663
 
488
664
  /**
489
665
  * Get the user info.
666
+ * @param params.getCredentials Optional custom getCredentials function to bypass default getCredentials() and avoid deadlock
490
667
  * @return {Promise<UserInfo>} A Promise<UserProfile> object.
491
668
  */
492
- public async getUserInfo(params: { version?: string; query?: string } = {}): Promise<UserInfo> {
669
+ public async getUserInfo(params: {
670
+ version?: string
671
+ query?: string
672
+ credentials?: Credentials
673
+ } = {},): Promise<UserInfo> {
493
674
  const res = this.getParamsByVersion(params, 'USER_ME_URL')
494
675
 
495
676
  if (res.params?.query) {
@@ -500,6 +681,7 @@ export class Auth {
500
681
  const userInfo = await this.config.request<UserInfo>(res.url, {
501
682
  method: 'GET',
502
683
  withCredentials: true,
684
+ getCredentials: params.credentials ? () => params.credentials : undefined,
503
685
  })
504
686
 
505
687
  if (userInfo.sub) {
@@ -701,7 +883,8 @@ export class Auth {
701
883
  }
702
884
 
703
885
  /**
704
- * Patch the user profile.
886
+ * Patch the user profile. 没有和数据源同步
887
+ * @deprecated use updateUserBasicInfo
705
888
  * @param {UserProfile} params A UserProfile Object.
706
889
  * @return {Promise<UserProfile>} A Promise<UserProfile> object.
707
890
  */
@@ -912,13 +1095,14 @@ export class Auth {
912
1095
 
913
1096
  const payload = deepClone(params)
914
1097
 
915
- const encryptUtils = getEncryptUtils(isEncrypt, this.config.adapter)
1098
+ const encryptUtils = await getEncryptUtils(isEncrypt, this.config.adapter)
916
1099
 
917
1100
  if (!encryptUtils) {
918
1101
  return params
919
1102
  }
920
1103
 
921
1104
  let publicKey = ''
1105
+ // eslint-disable-next-line @typescript-eslint/naming-convention
922
1106
  let public_key_thumbprint = ''
923
1107
 
924
1108
  try {
@@ -1015,9 +1199,10 @@ export class Auth {
1015
1199
  */
1016
1200
  public async modifyPassword(params: ModifyUserBasicInfoRequest): Promise<void> {
1017
1201
  let publicKey = ''
1202
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1018
1203
  let public_key_thumbprint = ''
1019
1204
 
1020
- const encryptUtils = getEncryptUtils(true, this.config.adapter)
1205
+ const encryptUtils = await getEncryptUtils(true, this.config.adapter)
1021
1206
 
1022
1207
  if (!encryptUtils) {
1023
1208
  throw new Error('do not support encrypt, a encrypt util required.')
@@ -1035,7 +1220,9 @@ export class Auth {
1035
1220
  throw error
1036
1221
  }
1037
1222
 
1223
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1038
1224
  const encrypt_password = params.password ? encryptUtils.getEncryptInfo({ publicKey, payload: params.password }) : ''
1225
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1039
1226
  const encrypt_new_password = encryptUtils.getEncryptInfo({ publicKey, payload: params.new_password })
1040
1227
  return this.config.request(ApiUrls.USER_BASIC_EDIT_URL, {
1041
1228
  method: 'POST',
@@ -1056,9 +1243,10 @@ export class Auth {
1056
1243
  */
1057
1244
  public async modifyPasswordWithoutLogin(params: ModifyPasswordWithoutLoginRequest): Promise<void> {
1058
1245
  let publicKey = ''
1246
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1059
1247
  let public_key_thumbprint = ''
1060
1248
 
1061
- const encryptUtils = getEncryptUtils(true, this.config.adapter)
1249
+ const encryptUtils = await getEncryptUtils(true, this.config.adapter)
1062
1250
 
1063
1251
  if (!encryptUtils) {
1064
1252
  throw new Error('do not support encrypt, a encrypt util required.')
@@ -1076,7 +1264,9 @@ export class Auth {
1076
1264
  throw error
1077
1265
  }
1078
1266
 
1267
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1079
1268
  const encrypt_password = params.password ? encryptUtils.getEncryptInfo({ publicKey, payload: params.password }) : ''
1269
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1080
1270
  const encrypt_new_password = encryptUtils.getEncryptInfo({ publicKey, payload: params.new_password })
1081
1271
  return this.config.request(ApiUrlsV2.AUTH_RESET_PASSWORD, {
1082
1272
  method: 'POST',
@@ -1088,4 +1278,19 @@ export class Auth {
1088
1278
  },
1089
1279
  })
1090
1280
  }
1281
+
1282
+ /**
1283
+ * Restore URL state after OAuth callback
1284
+ */
1285
+ private restoreUrlState(search?: string, hash?: string): void {
1286
+ if (search === undefined) return
1287
+ try {
1288
+ const url = new URL(window.location.href)
1289
+ url.search = search
1290
+ url.hash = hash || ''
1291
+ window.history.replaceState(null, '', url.toString())
1292
+ } catch {
1293
+ // ignore
1294
+ }
1295
+ }
1091
1296
  }
@@ -0,0 +1,21 @@
1
+ export class AuthError extends Error {
2
+ /**
3
+ * Error code associated with the error. Most errors coming from
4
+ * HTTP responses will have a code, though some errors that occur
5
+ * before a response is received will not have one present. In that
6
+ * case {@link #status} will also be undefined.
7
+ */
8
+ code: (string & {}) | undefined
9
+
10
+ /** HTTP status code that caused the error. */
11
+ status: number | undefined
12
+
13
+ protected __isAuthError = true
14
+
15
+ constructor(error) {
16
+ super(error.error_description || error.message)
17
+ this.name = 'AuthError'
18
+ this.status = error.error
19
+ this.code = error.error_code
20
+ }
21
+ }
@@ -119,3 +119,31 @@ export enum ErrorType {
119
119
  CAPTCHA_REQUIRED = 'captcha_required',
120
120
  CAPTCHA_INVALID = 'captcha_invalid',
121
121
  }
122
+
123
+ export const LOGIN_STATE_CHANGED_TYPE = {
124
+ SIGN_OUT: 'sign_out',
125
+ SIGN_IN: 'sign_in',
126
+ CREDENTIALS_ERROR: 'credentials_error',
127
+ }
128
+
129
+ export const AUTH_STATE_CHANGED_TYPE = {
130
+ SIGNED_OUT: 'SIGNED_OUT',
131
+ SIGNED_IN: 'SIGNED_IN',
132
+ INITIAL_SESSION: 'INITIAL_SESSION',
133
+ PASSWORD_RECOVERY: 'PASSWORD_RECOVERY',
134
+ TOKEN_REFRESHED: 'TOKEN_REFRESHED',
135
+ USER_UPDATED: 'USER_UPDATED',
136
+ BIND_IDENTITY: 'BIND_IDENTITY',
137
+ }
138
+
139
+ export const EVENTS = {
140
+ // 登录态改变后触发
141
+ LOGIN_STATE_CHANGED: 'loginStateChanged',
142
+ // 授权态改变后触发
143
+ AUTH_STATE_CHANGED: 'authStateChanged',
144
+ }
145
+
146
+ export const OAUTH_TYPE = {
147
+ SIGN_IN: 'sign_in',
148
+ BIND_IDENTITY: 'bind_identity',
149
+ }
@@ -286,7 +286,7 @@ export interface PatchProviderTokenResponse {
286
286
 
287
287
  export interface GenProviderRedirectUriRequest {
288
288
  provider_id: string
289
- redirect_uri: string
289
+ redirect_uri?: string
290
290
  /**
291
291
  * @deprecated
292
292
  */
@@ -338,6 +338,12 @@ export interface UserProfile {
338
338
  zoneinfo?: string
339
339
  locale?: string
340
340
  created_from?: string
341
+ created_at?: string
342
+ updated_at?: string
343
+ loginType?: string
344
+ avatarUrl?: string
345
+ location?: any
346
+ hasPassword?: boolean
341
347
  }
342
348
 
343
349
  interface UserProvider {
@@ -465,8 +471,8 @@ export interface QueryUserProfileResponse {
465
471
  }
466
472
 
467
473
  export interface ResetPasswordRequest extends BaseRequest {
468
- email: string
469
- phone_number: string
474
+ email?: string
475
+ phone_number?: string
470
476
  new_password: string
471
477
  verification_token: string
472
478
  }
@@ -532,7 +538,7 @@ export interface SignoutRequest {
532
538
  state?: string
533
539
  }
534
540
 
535
- export interface SignoutReponse {
541
+ export interface SignoutResponse {
536
542
  redirect_uri?: string
537
543
  }
538
544
 
@@ -622,6 +628,8 @@ export interface ModifyUserBasicInfoRequest {
622
628
  gender?: 'MALE' | 'FEMALE'
623
629
  password?: string // 旧密码
624
630
  new_password?: string // 新密码
631
+ email?: string
632
+ phone?: string
625
633
  }
626
634
 
627
635
  export interface GetUserBehaviorLog {
@@ -651,4 +659,5 @@ export interface ToDefaultLoginPage {
651
659
  config_version?: 'env' | string; // 登录配置,默认env(托管登录页)
652
660
  redirect_uri?: string; // 登录后回调地址,默认当前地址
653
661
  app_id?: string; // 应用id,托管登录页不需要传
662
+ query?: Record<string, any> // 跳转登录链接携带的自定义参数,会拼接在链接后面
654
663
  }
@@ -0,0 +1,178 @@
1
+ import { CaptchaToken } from '@cloudbase/adapter-interface'
2
+ import { CloudbaseEventEmitter } from '@cloudbase/utilities/dist/cjs/libs/events'
3
+ import { parseCaptcha } from '@cloudbase/utilities/dist/cjs/libs/util'
4
+ import { Auth } from '../auth/apis'
5
+
6
+ // 防抖函数实现
7
+ const debounce = (func: Function, delay: number) => {
8
+ let timeoutId: NodeJS.Timeout
9
+ return (...args: any[]) => {
10
+ clearTimeout(timeoutId)
11
+ timeoutId = setTimeout(() => func.apply(null, args), delay)
12
+ }
13
+ }
14
+
15
+ const eventBus = new CloudbaseEventEmitter()
16
+
17
+ const CAPTCHA_EVENT = {
18
+ CAPTCHA_DATA_CHANGE: 'captchaDataChange',
19
+ RESOLVE_CAPTCHA_DATA: 'resolveCaptchaData',
20
+ }
21
+
22
+ // 图形验证码弹窗样式类
23
+ const CAPTCHA_STYLES = {
24
+ overlay: 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:999',
25
+ modal:
26
+ 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:320px;padding:20px;background:#fff;border-radius:8px;box-shadow:0 0 10px rgba(0,0,0,0.2);z-index:1000',
27
+ prompt: 'margin-bottom:15px',
28
+ captchaWrap: 'display:flex;align-items:center;justify-content:space-between;margin-bottom:15px',
29
+ captchaImg: 'width:100%;height:auto;border:1px solid #eee',
30
+ refreshBtn:
31
+ 'padding:10px 8px;background:#007bff;color:#fff;border:none;border-radius:4px;cursor:pointer;flex:1;white-space:nowrap;margin-left:10px',
32
+ input:
33
+ 'width:100%;padding:14px 16px;margin-bottom:15px;box-sizing:border-box;border:2px solid #e8ecf4;border-radius:10px;font-size:15px',
34
+ confirmBtn: 'width:100%;padding:10px;background:#007bff;color:#fff;border:none;border-radius:4px;cursor:pointer',
35
+ loading: 'background:#6c757d;cursor:not-allowed',
36
+ }
37
+
38
+ /**
39
+ * 显示图形图形验证码
40
+ * @param param0
41
+ */
42
+ const genCaptchaDom = ({ oauthInstance, captchaState }) => {
43
+ const CAPTCHA_IMG_ID = 'captcha-image'
44
+
45
+ // 刷新图形验证码函数
46
+ const refreshCaptcha = async () => {
47
+ try {
48
+ const result = await oauthInstance.createCaptchaData({ state: captchaState.state })
49
+ captchaState = { ...captchaState, captchaData: result.data, token: result.token }
50
+ ;(document.getElementById(CAPTCHA_IMG_ID) as HTMLImageElement).src = result.data
51
+ } catch (error) {
52
+ console.error('刷新图形验证码失败', error)
53
+ }
54
+ }
55
+
56
+ // 创建元素并设置样式
57
+ const createElement = (tag: string, styles?: string, text?: string) => {
58
+ const el = document.createElement(tag)
59
+ if (styles) el.style.cssText = styles
60
+ if (text) el.textContent = text
61
+ return el
62
+ }
63
+
64
+ // 创建遮罩层和弹窗
65
+ const overlay = createElement('div', CAPTCHA_STYLES.overlay)
66
+ const modal = createElement('div', CAPTCHA_STYLES.modal)
67
+
68
+ // 添加提示文字
69
+ modal.appendChild(createElement('p', CAPTCHA_STYLES.prompt, '请输入你看到的字符:'))
70
+
71
+ // 图形验证码区域
72
+ const captchaWrap = createElement('div', CAPTCHA_STYLES.captchaWrap)
73
+ const captchaImg = createElement('img', CAPTCHA_STYLES.captchaImg) as HTMLImageElement
74
+ captchaImg.id = CAPTCHA_IMG_ID
75
+ captchaImg.src = captchaState.captchaData
76
+ captchaWrap.appendChild(captchaImg)
77
+
78
+ // 刷新按钮
79
+ const refreshBtn = createElement('button', CAPTCHA_STYLES.refreshBtn, '刷新') as HTMLButtonElement
80
+ refreshBtn.onclick = debounce(async () => {
81
+ refreshBtn.textContent = '加载中...'
82
+ refreshBtn.disabled = true
83
+ refreshBtn.style.cssText = `${CAPTCHA_STYLES.refreshBtn};${CAPTCHA_STYLES.loading}`
84
+
85
+ try {
86
+ await refreshCaptcha()
87
+ } finally {
88
+ refreshBtn.textContent = '刷新'
89
+ refreshBtn.disabled = false
90
+ refreshBtn.style.cssText = CAPTCHA_STYLES.refreshBtn
91
+ }
92
+ }, 500)
93
+ captchaWrap.appendChild(refreshBtn)
94
+ modal.appendChild(captchaWrap)
95
+
96
+ // 输入框
97
+ const input = createElement('input', CAPTCHA_STYLES.input) as HTMLInputElement
98
+ input.type = 'text'
99
+ input.placeholder = '输入字符'
100
+ input.maxLength = 4
101
+ modal.appendChild(input)
102
+
103
+ // 错误提示元素
104
+ const errorMsg = createElement(
105
+ 'div',
106
+ 'color:#dc3545;font-size:12px;margin-bottom:10px;display:none;padding:8px;background:#f8d7da;border:1px solid #f5c6cb;border-radius:4px',
107
+ )
108
+ modal.appendChild(errorMsg)
109
+
110
+ // 显示错误提示
111
+ const showError = (message: string) => {
112
+ errorMsg.textContent = message
113
+ errorMsg.style.display = 'block'
114
+ }
115
+
116
+ // 确定按钮
117
+ const confirmBtn = createElement('button', CAPTCHA_STYLES.confirmBtn, '确定') as HTMLButtonElement
118
+ confirmBtn.onclick = debounce(async () => {
119
+ const inputValue = input.value.trim()
120
+ if (!inputValue) {
121
+ showError('请输入字符')
122
+ return
123
+ }
124
+
125
+ // 隐藏之前的错误提示
126
+ errorMsg.style.display = 'none'
127
+
128
+ confirmBtn.textContent = '验证中...'
129
+ confirmBtn.disabled = true
130
+ confirmBtn.style.cssText = `${CAPTCHA_STYLES.confirmBtn};${CAPTCHA_STYLES.loading}`
131
+
132
+ try {
133
+ const verifyResult = await oauthInstance.verifyCaptchaData({
134
+ token: captchaState.token,
135
+ key: inputValue,
136
+ })
137
+ eventBus.fire(CAPTCHA_EVENT.RESOLVE_CAPTCHA_DATA, verifyResult)
138
+ console.log('图形验证码校验成功')
139
+ document.body.removeChild(overlay)
140
+ document.body.removeChild(modal)
141
+ } catch (error) {
142
+ console.error('图形验证码校验失败', error)
143
+ // 根据错误类型显示不同的错误提示
144
+ const errorMessage = error.error_description || '图形验证码校验失败'
145
+ showError(errorMessage)
146
+
147
+ // 清空输入框并刷新图形验证码
148
+ input.value = ''
149
+ input.focus()
150
+ await refreshCaptcha()
151
+ } finally {
152
+ confirmBtn.textContent = '确定'
153
+ confirmBtn.disabled = false
154
+ confirmBtn.style.cssText = CAPTCHA_STYLES.confirmBtn
155
+ }
156
+ }, 500)
157
+ modal.appendChild(confirmBtn)
158
+
159
+ // 插入页面
160
+ document.body.appendChild(overlay)
161
+ document.body.appendChild(modal)
162
+ }
163
+
164
+ export const openURIWithCallback = async (url: string, oauthInstance?: Auth): Promise<CaptchaToken> => {
165
+ // 解析URL中的图形验证码参数
166
+ const { captchaData, state, token } = parseCaptcha(url)
167
+
168
+ genCaptchaDom({ oauthInstance, captchaState: { captchaData, state, token } })
169
+
170
+ // 监听图形验证码校验结果
171
+ return new Promise((resolve) => {
172
+ console.log('等待图形验证码校验结果...')
173
+ eventBus.on(CAPTCHA_EVENT.RESOLVE_CAPTCHA_DATA, (res) => {
174
+ // auth.verifyCaptchaData的校验结果
175
+ resolve(res?.data)
176
+ })
177
+ })
178
+ }