@cloudbase/oauth 2.23.3 → 2.24.0
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.js +170 -14
- package/dist/cjs/auth/auth-error.js +32 -0
- package/dist/cjs/auth/consts.js +24 -2
- package/dist/cjs/auth/models.js +1 -1
- package/dist/cjs/captcha/captcha-dom.js +223 -0
- package/dist/cjs/captcha/captcha.js +11 -102
- package/dist/cjs/index.js +25 -3
- package/dist/cjs/oauth2client/interface.js +1 -1
- package/dist/cjs/oauth2client/models.js +1 -1
- package/dist/cjs/oauth2client/oauth2client.js +384 -110
- package/dist/cjs/utils/base64.js +15 -2
- package/dist/esm/auth/apis.js +113 -2
- package/dist/esm/auth/auth-error.js +9 -0
- package/dist/esm/auth/consts.js +22 -0
- package/dist/esm/captcha/captcha-dom.js +129 -0
- package/dist/esm/captcha/captcha.js +14 -97
- package/dist/esm/index.js +18 -2
- package/dist/esm/oauth2client/oauth2client.js +162 -36
- package/dist/esm/utils/base64.js +12 -0
- package/dist/miniprogram/index.js +1 -1
- package/dist/types/auth/apis.d.ts +19 -1
- package/dist/types/auth/auth-error.d.ts +6 -0
- package/dist/types/auth/consts.d.ts +22 -0
- package/dist/types/auth/models.d.ts +2 -0
- package/dist/types/captcha/captcha-dom.d.ts +3 -0
- package/dist/types/captcha/captcha.d.ts +3 -1
- package/dist/types/index.d.ts +11 -2
- package/dist/types/oauth2client/interface.d.ts +1 -1
- package/dist/types/oauth2client/models.d.ts +14 -0
- package/dist/types/oauth2client/oauth2client.d.ts +58 -2
- package/dist/types/utils/base64.d.ts +5 -0
- package/package.json +4 -4
- package/src/auth/apis.ts +189 -4
- package/src/auth/auth-error.ts +21 -0
- package/src/auth/consts.ts +28 -0
- package/src/auth/models.ts +2 -0
- package/src/captcha/captcha-dom.ts +178 -0
- package/src/captcha/captcha.ts +25 -115
- package/src/index.ts +51 -3
- package/src/oauth2client/interface.ts +1 -1
- package/src/oauth2client/models.ts +28 -0
- package/src/oauth2client/oauth2client.ts +254 -34
- package/src/utils/base64.ts +12 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ErrorType } from './consts'
|
|
2
|
-
import { ApiUrls, ApiUrlsV2, AUTH_API_PREFIX } from '../auth/consts'
|
|
2
|
+
import { ApiUrls, ApiUrlsV2, AUTH_API_PREFIX, AUTH_STATE_CHANGED_TYPE, EVENTS } from '../auth/consts'
|
|
3
3
|
|
|
4
4
|
import { AuthClient, SimpleStorage } from './interface'
|
|
5
5
|
|
|
@@ -315,6 +315,20 @@ export class OAuth2Client implements AuthClient {
|
|
|
315
315
|
private static maxRetry = 5
|
|
316
316
|
private static retryInterval = 1000
|
|
317
317
|
|
|
318
|
+
public localCredentials: LocalCredentials
|
|
319
|
+
/**
|
|
320
|
+
* Keeps track of the async client initialization.
|
|
321
|
+
* When null or not yet resolved the auth state is `unknown`
|
|
322
|
+
* Once resolved the auth state is known and it's safe to call any further client methods.
|
|
323
|
+
*/
|
|
324
|
+
public initializePromise: Promise<{ error: Error | null }> | null = null
|
|
325
|
+
protected lockAcquired = false
|
|
326
|
+
protected pendingInLock: Promise<any>[] = []
|
|
327
|
+
protected logDebugMessages: boolean
|
|
328
|
+
protected getInitialSession?: () => Promise<{
|
|
329
|
+
data: { session: Credentials; user?: any } | null
|
|
330
|
+
error: Error | null
|
|
331
|
+
}>
|
|
318
332
|
private apiOrigin: string
|
|
319
333
|
private apiPath: string
|
|
320
334
|
private clientId: string
|
|
@@ -322,7 +336,6 @@ export class OAuth2Client implements AuthClient {
|
|
|
322
336
|
private retry: number
|
|
323
337
|
private clientSecret?: string
|
|
324
338
|
private baseRequest: <T>(url: string, options?: RequestOptions) => Promise<T>
|
|
325
|
-
private localCredentials: LocalCredentials
|
|
326
339
|
private storage: SimpleStorage
|
|
327
340
|
private deviceID?: string
|
|
328
341
|
private tokenInURL?: boolean
|
|
@@ -332,8 +345,10 @@ export class OAuth2Client implements AuthClient {
|
|
|
332
345
|
private anonymousSignInFunc: (Credentials) => Promise<Credentials | void>
|
|
333
346
|
private wxCloud: any
|
|
334
347
|
private useWxCloud: boolean
|
|
348
|
+
private eventBus: any
|
|
335
349
|
private basicAuth: string
|
|
336
350
|
private onCredentialsError: AuthOptions['onCredentialsError'] | undefined
|
|
351
|
+
private onInitialSessionObtained?: (data: { session: Credentials; user?: any }, error?: any) => void | Promise<void>
|
|
337
352
|
|
|
338
353
|
/**
|
|
339
354
|
* constructor
|
|
@@ -352,6 +367,7 @@ export class OAuth2Client implements AuthClient {
|
|
|
352
367
|
this.apiPath = options.apiPath || AUTH_API_PREFIX
|
|
353
368
|
this.clientId = options.clientId
|
|
354
369
|
this.i18n = options.i18n
|
|
370
|
+
this.eventBus = options.eventBus
|
|
355
371
|
this.singlePromise = new SinglePromise({ clientId: this.clientId })
|
|
356
372
|
this.retry = this.formatRetry(options.retry, OAuth2Client.defaultRetry)
|
|
357
373
|
if (options.baseRequest) {
|
|
@@ -389,9 +405,56 @@ export class OAuth2Client implements AuthClient {
|
|
|
389
405
|
this.anonymousSignInFunc = options.anonymousSignInFunc
|
|
390
406
|
this.onCredentialsError = options.onCredentialsError
|
|
391
407
|
|
|
392
|
-
|
|
408
|
+
// New options for session detection
|
|
409
|
+
this.getInitialSession = options.getInitialSession
|
|
410
|
+
this.onInitialSessionObtained = options.onInitialSessionObtained
|
|
411
|
+
this.logDebugMessages = options.debug ?? false
|
|
412
|
+
|
|
413
|
+
langEvent.bus.on(langEvent.LANG_CHANGE_EVENT, (params: any) => {
|
|
393
414
|
this.i18n = params.data?.i18n || this.i18n
|
|
394
415
|
})
|
|
416
|
+
|
|
417
|
+
// Note: Auto-initialize is NOT called here to allow CloudbaseOAuth to set getInitialSession first
|
|
418
|
+
// CloudbaseOAuth.constructor will call initialize() after setting up the callback
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Sets the getInitialSession callback.
|
|
423
|
+
* This callback is called during initialize() to get the initial session (e.g., from OAuth callback in URL).
|
|
424
|
+
* @param callback The callback function to get initial session
|
|
425
|
+
*/
|
|
426
|
+
public setGetInitialSession(callback: () => Promise<{
|
|
427
|
+
data: { session: Credentials; user?: any } | null
|
|
428
|
+
error: Error | null
|
|
429
|
+
}>,): void {
|
|
430
|
+
this.getInitialSession = callback
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Sets the onInitialSessionObtained callback.
|
|
435
|
+
* This callback is invoked after initial session is obtained and stored,
|
|
436
|
+
* allowing upper layers to handle user info storage.
|
|
437
|
+
* @param callback The callback function to handle session and user data
|
|
438
|
+
*/
|
|
439
|
+
public setOnInitialSessionObtained(callback: (data: { session: Credentials; user?: any }) => void | Promise<void>,): void {
|
|
440
|
+
this.onInitialSessionObtained = callback
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Initializes the client session either from the url or from storage.
|
|
445
|
+
* This method is automatically called when instantiating the client with detectSessionInUrl=true,
|
|
446
|
+
* but should also be called manually when checking for an error from an auth redirect.
|
|
447
|
+
* @param onInitialSessionObtained Optional callback to set before initialization starts
|
|
448
|
+
*/
|
|
449
|
+
async initialize(): Promise<{ error: Error | null }> {
|
|
450
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
451
|
+
if (this.initializePromise) {
|
|
452
|
+
return await this.initializePromise
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
this.initializePromise = (async () => await this._acquireLock(-1, async () => await this._initialize()))()
|
|
456
|
+
|
|
457
|
+
return await this.initializePromise
|
|
395
458
|
}
|
|
396
459
|
|
|
397
460
|
/**
|
|
@@ -399,8 +462,10 @@ export class OAuth2Client implements AuthClient {
|
|
|
399
462
|
* @param {Credentials} credentials
|
|
400
463
|
* @return {Promise<void>}
|
|
401
464
|
*/
|
|
402
|
-
public setCredentials(credentials?: Credentials): Promise<void> {
|
|
403
|
-
|
|
465
|
+
public async setCredentials(credentials?: Credentials): Promise<void> {
|
|
466
|
+
// If initialization is in progress, wait for it first
|
|
467
|
+
await this.initializePromise
|
|
468
|
+
return this._acquireLock(-1, async () => this.localCredentials.setCredentials(credentials))
|
|
404
469
|
}
|
|
405
470
|
|
|
406
471
|
/**
|
|
@@ -415,6 +480,8 @@ export class OAuth2Client implements AuthClient {
|
|
|
415
480
|
* getAccessToken return a validate access token
|
|
416
481
|
*/
|
|
417
482
|
public async getAccessToken(): Promise<string> {
|
|
483
|
+
// If initialization is in progress, wait for it first
|
|
484
|
+
await this.initializePromise
|
|
418
485
|
const credentials: Credentials = await this.getCredentials()
|
|
419
486
|
if (credentials?.access_token) {
|
|
420
487
|
return Promise.resolve(credentials.access_token)
|
|
@@ -451,7 +518,9 @@ export class OAuth2Client implements AuthClient {
|
|
|
451
518
|
options.headers.Authorization = this.basicAuth
|
|
452
519
|
}
|
|
453
520
|
if (options?.withCredentials) {
|
|
454
|
-
|
|
521
|
+
// Use custom getCredentials function if provided, otherwise use default
|
|
522
|
+
// Custom getCredentials avoids default getCredentials() call which may cause deadlock during initialization
|
|
523
|
+
const credentials = options.getCredentials ? await options.getCredentials() : await this.getCredentials()
|
|
455
524
|
if (credentials) {
|
|
456
525
|
if (this.tokenInURL) {
|
|
457
526
|
if (url.indexOf('?') < 0) {
|
|
@@ -543,33 +612,10 @@ export class OAuth2Client implements AuthClient {
|
|
|
543
612
|
* Get credentials.
|
|
544
613
|
*/
|
|
545
614
|
public async getCredentials(): Promise<Credentials | null> {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
return this.unAuthenticatedError(msg)
|
|
551
|
-
}
|
|
552
|
-
if (isCredentialsExpired(credentials)) {
|
|
553
|
-
if (credentials.refresh_token) {
|
|
554
|
-
try {
|
|
555
|
-
credentials = await this.refreshToken(credentials)
|
|
556
|
-
} catch (error) {
|
|
557
|
-
if (credentials.scope === 'anonymous') {
|
|
558
|
-
credentials = await this.anonymousLogin(credentials)
|
|
559
|
-
} else {
|
|
560
|
-
this.onCredentialsError?.({ msg: error.error_description })
|
|
561
|
-
return Promise.reject(error)
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
} else if (credentials.scope === 'anonymous') {
|
|
565
|
-
credentials = await this.anonymousLogin(credentials)
|
|
566
|
-
} else {
|
|
567
|
-
const msg = 'no refresh token found in credentials'
|
|
568
|
-
this.onCredentialsError?.({ msg })
|
|
569
|
-
return this.unAuthenticatedError(msg)
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
return credentials
|
|
615
|
+
// If initialization is in progress, wait for it first
|
|
616
|
+
await this.initializePromise
|
|
617
|
+
|
|
618
|
+
return this._acquireLock(-1, async () => this._getCredentials())
|
|
573
619
|
}
|
|
574
620
|
|
|
575
621
|
/**
|
|
@@ -609,7 +655,17 @@ export class OAuth2Client implements AuthClient {
|
|
|
609
655
|
* @param {Credentials} credentials
|
|
610
656
|
* @return {Promise<Credentials>}
|
|
611
657
|
*/
|
|
612
|
-
public async refreshToken(credentials: Credentials): Promise<Credentials> {
|
|
658
|
+
public async refreshToken(credentials: Credentials, options?: { throwError?: boolean }): Promise<Credentials> {
|
|
659
|
+
// If initialization is in progress, wait for it first
|
|
660
|
+
await this.initializePromise
|
|
661
|
+
|
|
662
|
+
return this._acquireLock(-1, async () => this._refreshToken(credentials, options))
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Internal refresh token method (called within lock)
|
|
667
|
+
*/
|
|
668
|
+
private async _refreshToken(credentials: Credentials, options?: { throwError?: boolean }): Promise<Credentials> {
|
|
613
669
|
return this.singlePromise.run('_refreshToken', async () => {
|
|
614
670
|
if (!credentials || !credentials.refresh_token) {
|
|
615
671
|
const msg = 'no refresh token found in credentials'
|
|
@@ -619,8 +675,14 @@ export class OAuth2Client implements AuthClient {
|
|
|
619
675
|
try {
|
|
620
676
|
const newCredentials: Credentials = await this.refreshTokenFunc(credentials.refresh_token, credentials)
|
|
621
677
|
await this.localCredentials.setCredentials(newCredentials)
|
|
678
|
+
|
|
679
|
+
this.eventBus?.fire(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.TOKEN_REFRESHED })
|
|
680
|
+
|
|
622
681
|
return newCredentials
|
|
623
682
|
} catch (error) {
|
|
683
|
+
if (options?.throwError) {
|
|
684
|
+
throw error
|
|
685
|
+
}
|
|
624
686
|
if (error.error === ErrorType.INVALID_GRANT) {
|
|
625
687
|
await this.localCredentials.setCredentials(null)
|
|
626
688
|
const msg = error.error_description
|
|
@@ -777,4 +839,162 @@ export class OAuth2Client implements AuthClient {
|
|
|
777
839
|
}
|
|
778
840
|
return Promise.reject(respErr)
|
|
779
841
|
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Debug logging helper
|
|
845
|
+
*/
|
|
846
|
+
private _debug(...args: any[]): void {
|
|
847
|
+
if (this.logDebugMessages) {
|
|
848
|
+
console.log('[OAuth2Client]', ...args)
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* IMPORTANT:
|
|
854
|
+
* 1. Never throw in this method, as it is called from the constructor
|
|
855
|
+
* 2. Never return a session from this method as it would be cached over
|
|
856
|
+
* the whole lifetime of the client
|
|
857
|
+
*/
|
|
858
|
+
private async _initialize(): Promise<{ error: Error | null }> {
|
|
859
|
+
try {
|
|
860
|
+
// If no getInitialSession callback is set, nothing to do
|
|
861
|
+
if (!this.getInitialSession) {
|
|
862
|
+
this._debug('#_initialize()', 'no getInitialSession callback set, skipping')
|
|
863
|
+
return { error: null }
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
this._debug('#_initialize()', 'calling getInitialSession callback')
|
|
867
|
+
|
|
868
|
+
try {
|
|
869
|
+
const { data, error } = await this.getInitialSession()
|
|
870
|
+
|
|
871
|
+
if (data?.session) {
|
|
872
|
+
this._debug('#_initialize()', 'session obtained from getInitialSession', data.session)
|
|
873
|
+
await this.localCredentials.setCredentials(data?.session)
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Invoke callback for upper layer to handle user storage
|
|
877
|
+
// This is called BEFORE lock is released so user storage is atomic with session
|
|
878
|
+
if (this.onInitialSessionObtained) {
|
|
879
|
+
this._debug('#_initialize()', 'calling onInitialSessionObtained callback')
|
|
880
|
+
try {
|
|
881
|
+
await this.onInitialSessionObtained(data, error)
|
|
882
|
+
} catch (callbackError) {
|
|
883
|
+
this._debug('#_initialize()', 'error in onInitialSessionObtained', callbackError)
|
|
884
|
+
// Don't fail initialization if callback fails
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
if (error) {
|
|
888
|
+
this._debug('#_initialize()', 'error from getInitialSession', error)
|
|
889
|
+
return { error }
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
return { error: null }
|
|
893
|
+
} catch (err) {
|
|
894
|
+
this._debug('#_initialize()', 'exception during getInitialSession', err)
|
|
895
|
+
return { error: err instanceof Error ? err : new Error(String(err)) }
|
|
896
|
+
}
|
|
897
|
+
} catch (error) {
|
|
898
|
+
this._debug('#_initialize()', 'unexpected error', error)
|
|
899
|
+
return { error: error instanceof Error ? error : new Error(String(error)) }
|
|
900
|
+
} finally {
|
|
901
|
+
this._debug('#_initialize()', 'end')
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Acquires a global lock based on the client ID.
|
|
907
|
+
* This ensures that only one operation can modify credentials at a time.
|
|
908
|
+
*/
|
|
909
|
+
private async _acquireLock<R>(acquireTimeout: number, fn: () => Promise<R>): Promise<R> {
|
|
910
|
+
this._debug('#_acquireLock', 'begin', acquireTimeout)
|
|
911
|
+
|
|
912
|
+
try {
|
|
913
|
+
if (this.lockAcquired) {
|
|
914
|
+
// Lock is already acquired, queue the operation
|
|
915
|
+
const last = this.pendingInLock.length ? this.pendingInLock[this.pendingInLock.length - 1] : Promise.resolve()
|
|
916
|
+
|
|
917
|
+
const result = (async () => {
|
|
918
|
+
await last
|
|
919
|
+
return await fn()
|
|
920
|
+
})()
|
|
921
|
+
|
|
922
|
+
this.pendingInLock.push((async () => {
|
|
923
|
+
try {
|
|
924
|
+
await result
|
|
925
|
+
} catch (_e: any) {
|
|
926
|
+
// we just care if it finished
|
|
927
|
+
}
|
|
928
|
+
})(),)
|
|
929
|
+
|
|
930
|
+
return result
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// Acquire the lock
|
|
934
|
+
this._debug('#_acquireLock', 'acquiring lock for client', this.clientId)
|
|
935
|
+
|
|
936
|
+
try {
|
|
937
|
+
this.lockAcquired = true
|
|
938
|
+
|
|
939
|
+
const result = fn()
|
|
940
|
+
|
|
941
|
+
this.pendingInLock.push((async () => {
|
|
942
|
+
try {
|
|
943
|
+
await result
|
|
944
|
+
} catch (_e: any) {
|
|
945
|
+
// we just care if it finished
|
|
946
|
+
}
|
|
947
|
+
})(),)
|
|
948
|
+
|
|
949
|
+
await result
|
|
950
|
+
|
|
951
|
+
// keep draining the queue until there's nothing to wait on
|
|
952
|
+
while (this.pendingInLock.length) {
|
|
953
|
+
const waitOn = [...this.pendingInLock]
|
|
954
|
+
await Promise.all(waitOn)
|
|
955
|
+
this.pendingInLock.splice(0, waitOn.length)
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
return await result
|
|
959
|
+
} finally {
|
|
960
|
+
this._debug('#_acquireLock', 'releasing lock for client', this.clientId)
|
|
961
|
+
this.lockAcquired = false
|
|
962
|
+
}
|
|
963
|
+
} finally {
|
|
964
|
+
this._debug('#_acquireLock', 'end')
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Internal method to get credentials (called within lock)
|
|
970
|
+
*/
|
|
971
|
+
private async _getCredentials(): Promise<Credentials | null> {
|
|
972
|
+
let credentials: Credentials = await this.localCredentials.getCredentials()
|
|
973
|
+
if (!credentials) {
|
|
974
|
+
const msg = 'credentials not found'
|
|
975
|
+
this.onCredentialsError?.({ msg })
|
|
976
|
+
return this.unAuthenticatedError(msg)
|
|
977
|
+
}
|
|
978
|
+
if (isCredentialsExpired(credentials)) {
|
|
979
|
+
if (credentials.refresh_token) {
|
|
980
|
+
try {
|
|
981
|
+
credentials = await this._refreshToken(credentials)
|
|
982
|
+
} catch (error) {
|
|
983
|
+
if (credentials.scope === 'anonymous') {
|
|
984
|
+
credentials = await this.anonymousLogin(credentials)
|
|
985
|
+
} else {
|
|
986
|
+
this.onCredentialsError?.({ msg: error.error_description })
|
|
987
|
+
return Promise.reject(error)
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
} else if (credentials.scope === 'anonymous') {
|
|
991
|
+
credentials = await this.anonymousLogin(credentials)
|
|
992
|
+
} else {
|
|
993
|
+
const msg = 'no refresh token found in credentials'
|
|
994
|
+
this.onCredentialsError?.({ msg })
|
|
995
|
+
return this.unAuthenticatedError(msg)
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return credentials
|
|
999
|
+
}
|
|
780
1000
|
}
|
package/src/utils/base64.ts
CHANGED
|
@@ -98,3 +98,15 @@ export function weappJwtDecode(token: string, options?: any) {
|
|
|
98
98
|
throw new Error(`Invalid token specified: ${e}` ? (e as any).message : '')
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
|
|
102
|
+
export function weappJwtDecodeAll(token: string) {
|
|
103
|
+
if (typeof token !== 'string') {
|
|
104
|
+
throw new Error('Invalid token specified')
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const parts = token.split('.').map((segment, i) => i === 2 ? segment : JSON.parse(base64_url_decode(segment)))
|
|
108
|
+
return { claims: parts[1], header: parts[0], signature: parts[2]}
|
|
109
|
+
} catch (e) {
|
|
110
|
+
throw new Error(`Invalid token specified: ${e}` ? (e as any).message : '')
|
|
111
|
+
}
|
|
112
|
+
}
|