@cloudbase/oauth 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/auth/apis.d.ts +22 -4
- package/dist/cjs/auth/apis.js +255 -66
- package/dist/cjs/auth/auth-error.d.ts +6 -0
- package/dist/cjs/auth/auth-error.js +32 -0
- package/dist/cjs/auth/consts.d.ts +22 -0
- package/dist/cjs/auth/consts.js +24 -2
- package/dist/cjs/auth/models.d.ts +13 -4
- package/dist/cjs/auth/models.js +1 -1
- package/dist/cjs/captcha/captcha-dom.d.ts +3 -0
- package/dist/cjs/captcha/captcha-dom.js +223 -0
- package/dist/cjs/captcha/captcha.d.ts +3 -1
- package/dist/cjs/captcha/captcha.js +11 -102
- package/dist/cjs/index.d.ts +12 -2
- package/dist/cjs/index.js +27 -4
- package/dist/cjs/oauth2client/interface.d.ts +1 -1
- package/dist/cjs/oauth2client/interface.js +1 -1
- package/dist/cjs/oauth2client/models.d.ts +15 -1
- package/dist/cjs/oauth2client/models.js +1 -1
- package/dist/cjs/oauth2client/oauth2client.d.ts +62 -3
- package/dist/cjs/oauth2client/oauth2client.js +426 -131
- package/dist/cjs/utils/base64.d.ts +5 -0
- package/dist/cjs/utils/base64.js +15 -2
- package/dist/cjs/utils/encryptlong/index.js +22 -16
- package/dist/cjs/utils/index.js +1 -1
- package/dist/cjs/utils/mp.js +4 -4
- package/dist/cjs/utils/urlSearchParams.js +1 -1
- package/dist/esm/auth/apis.d.ts +22 -4
- package/dist/esm/auth/apis.js +130 -10
- package/dist/esm/auth/auth-error.d.ts +6 -0
- package/dist/esm/auth/auth-error.js +9 -0
- package/dist/esm/auth/consts.d.ts +22 -0
- package/dist/esm/auth/consts.js +22 -0
- package/dist/esm/auth/models.d.ts +13 -4
- package/dist/esm/captcha/captcha-dom.d.ts +3 -0
- package/dist/esm/captcha/captcha-dom.js +129 -0
- package/dist/esm/captcha/captcha.d.ts +3 -1
- package/dist/esm/captcha/captcha.js +14 -97
- package/dist/esm/index.d.ts +12 -2
- package/dist/esm/index.js +20 -3
- package/dist/esm/oauth2client/interface.d.ts +1 -1
- package/dist/esm/oauth2client/models.d.ts +15 -1
- package/dist/esm/oauth2client/oauth2client.d.ts +62 -3
- package/dist/esm/oauth2client/oauth2client.js +200 -55
- package/dist/esm/utils/base64.d.ts +5 -0
- package/dist/esm/utils/base64.js +12 -0
- package/dist/esm/utils/encryptlong/index.js +21 -15
- package/dist/esm/utils/mp.js +3 -3
- package/dist/miniprogram/index.js +1 -1
- package/package.json +10 -4
- package/src/auth/apis.ts +222 -17
- package/src/auth/auth-error.ts +21 -0
- package/src/auth/consts.ts +28 -0
- package/src/auth/models.ts +13 -4
- package/src/captcha/captcha-dom.ts +178 -0
- package/src/captcha/captcha.ts +25 -114
- package/src/index.ts +54 -4
- package/src/oauth2client/interface.ts +1 -1
- package/src/oauth2client/models.ts +29 -1
- package/src/oauth2client/oauth2client.ts +308 -55
- package/src/utils/base64.ts +12 -0
- package/src/utils/encryptlong/index.js +20 -14
- package/src/utils/index.ts +1 -0
- package/src/utils/mp.ts +3 -3
- package/src/utils/urlSearchParams.ts +2 -0
- package/tsconfig.json +1 -0
- package/dist/cjs/utils/cloudbase-adapter-wx_mp.d.ts +0 -1
- package/dist/cjs/utils/cloudbase-adapter-wx_mp.js +0 -40
- package/dist/esm/utils/cloudbase-adapter-wx_mp.d.ts +0 -1
- package/dist/esm/utils/cloudbase-adapter-wx_mp.js +0 -35
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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<
|
|
267
|
-
let resp:
|
|
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<
|
|
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
|
|
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: {
|
|
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
|
+
}
|
package/src/auth/consts.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/auth/models.ts
CHANGED
|
@@ -286,7 +286,7 @@ export interface PatchProviderTokenResponse {
|
|
|
286
286
|
|
|
287
287
|
export interface GenProviderRedirectUriRequest {
|
|
288
288
|
provider_id: string
|
|
289
|
-
redirect_uri
|
|
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
|
|
469
|
-
phone_number
|
|
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
|
|
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
|
+
}
|