mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
fix(auth): reset tokenRefreshErrorCount on successful token event
The tokenRefreshErrorCount in the oauthErrorEventOccurDueToClockOutOfSync$ stream now resets to 0 when a 'token_received' or 'token_refreshed' event occurs. This prevents intermittent errors that self-correct from accumulating toward the clock-drift detection threshold.
This commit is contained in:
@@ -494,6 +494,70 @@ describe('RedirectAuthService', () => {
|
|||||||
expect(oauthLoggerSpy.error).toHaveBeenCalledWith(expectedErrorMessage);
|
expect(oauthLoggerSpy.error).toHaveBeenCalledWith(expectedErrorMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should reset token_refresh_error counter when a successful token event occurs', () => {
|
||||||
|
timeSyncServiceSpy.checkTimeSync.and.returnValue(of({ outOfSync: false } as TimeSync));
|
||||||
|
|
||||||
|
// First token_refresh_error (skipped by oauthErrorEventOccurDueToClockOutOfSync$)
|
||||||
|
oauthEvents$.next(new OAuthErrorEvent('token_refresh_error', { reason: 'first error' }, {}));
|
||||||
|
|
||||||
|
// Successful token event resets the counter
|
||||||
|
oauthEvents$.next(new OAuthSuccessEvent('token_received'));
|
||||||
|
|
||||||
|
// Next token_refresh_error should be treated as the first again (skipped)
|
||||||
|
// Since secondTokenRefreshErrorEventOccur$ already consumed the first error,
|
||||||
|
// verify via the oauthErrorEventOccurDueToClockOutOfSync$ observable directly
|
||||||
|
let emitted = false;
|
||||||
|
service.oauthErrorEventOccurDueToClockOutOfSync$.subscribe(() => {
|
||||||
|
emitted = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// After reset, this is treated as "first" by the clock-out-of-sync stream
|
||||||
|
oauthEvents$.next(new OAuthErrorEvent('token_refresh_error', { reason: 'second error after reset' }, {}));
|
||||||
|
expect(emitted).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should logout on second consecutive token_refresh_error after counter reset and another error', fakeAsync(() => {
|
||||||
|
const mockDateNowInMilliseconds = 1728597618621;
|
||||||
|
const tokenExpiresAtInSeconds = 1728597000; // already expired
|
||||||
|
const tokenIssuedAtInSeconds = 1728596000;
|
||||||
|
|
||||||
|
timeSyncServiceSpy.checkTimeSync.and.returnValue(
|
||||||
|
of({ outOfSync: true, localDateTimeISO: '2024-10-10T22:00:18.621Z', serverDateTimeISO: '2024-10-10T22:10:53.000Z' } as TimeSync)
|
||||||
|
);
|
||||||
|
timeSyncServiceSpy.getCorrectedNow.and.returnValue(mockDateNowInMilliseconds);
|
||||||
|
oauthServiceSpy.getIdentityClaims.and.returnValue({ exp: tokenExpiresAtInSeconds, iat: tokenIssuedAtInSeconds });
|
||||||
|
oauthServiceSpy.refreshToken.and.rejectWith('refresh failed');
|
||||||
|
oauthServiceSpy.clockSkewInSec = 0;
|
||||||
|
(oauthServiceSpy as any).decreaseExpirationBySec = 0;
|
||||||
|
|
||||||
|
const expectedErrorMessage = new Error(
|
||||||
|
'OAuth error occurred due to local machine clock 2024-10-10T22:00:18.621Z being out of sync with server time 2024-10-10T22:10:53.000Z'
|
||||||
|
);
|
||||||
|
|
||||||
|
let clockOutOfSyncError: Error | null = null;
|
||||||
|
service.oauthErrorEventOccurDueToClockOutOfSync$.subscribe((error) => {
|
||||||
|
clockOutOfSyncError = error;
|
||||||
|
});
|
||||||
|
|
||||||
|
// First token_refresh_error (skipped by clock-out-of-sync stream)
|
||||||
|
oauthEvents$.next(new OAuthErrorEvent('token_refresh_error', { reason: 'first error' }, {}));
|
||||||
|
tick();
|
||||||
|
expect(clockOutOfSyncError).toBeNull();
|
||||||
|
|
||||||
|
// Successful token event resets the counter
|
||||||
|
oauthEvents$.next(new OAuthSuccessEvent('token_received'));
|
||||||
|
|
||||||
|
// After reset, first error is skipped again
|
||||||
|
oauthEvents$.next(new OAuthErrorEvent('token_refresh_error', { reason: 'error after reset' }, {}));
|
||||||
|
tick();
|
||||||
|
expect(clockOutOfSyncError).toBeNull();
|
||||||
|
|
||||||
|
// Second consecutive error after reset triggers clock-out-of-sync detection
|
||||||
|
oauthEvents$.next(new OAuthErrorEvent('token_refresh_error', { reason: 'second consecutive error' }, {}));
|
||||||
|
tick();
|
||||||
|
expect(clockOutOfSyncError).toEqual(expectedErrorMessage);
|
||||||
|
}));
|
||||||
|
|
||||||
it('should logout user if discovery_document_load_error is emitted because of clock out of sync', () => {
|
it('should logout user if discovery_document_load_error is emitted because of clock out of sync', () => {
|
||||||
const expectedErrorMessage = new Error(
|
const expectedErrorMessage = new Error(
|
||||||
'OAuth error occurred due to local machine clock 2024-10-10T22:00:18.621Z being out of sync with server time 2024-10-10T22:10:53.000Z'
|
'OAuth error occurred due to local machine clock 2024-10-10T22:00:18.621Z being out of sync with server time 2024-10-10T22:10:53.000Z'
|
||||||
|
|||||||
@@ -171,17 +171,27 @@ export class RedirectAuthService extends AuthService {
|
|||||||
filter((_, index) => index === 1)
|
filter((_, index) => index === 1)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.oauthErrorEventOccurDueToClockOutOfSync$ = this.oauthErrorEvent$.pipe(
|
this.oauthErrorEventOccurDueToClockOutOfSync$ = this.oauthService.events.pipe(
|
||||||
// For token_refresh_error, skip the first occurrence so the library's
|
// For token_refresh_error, skip the first occurrence so the library's
|
||||||
// built-in retry (secondTokenRefreshErrorEventOccur$) has a chance to run
|
// built-in retry (secondTokenRefreshErrorEventOccur$) has a chance to run
|
||||||
// before we conclude the issue is clock drift. All other error types are
|
// before we conclude the issue is clock drift. All other error types are
|
||||||
// checked immediately.
|
// checked immediately. The counter resets when a successful token event
|
||||||
|
// occurs, so intermittent errors that self-correct don't accumulate.
|
||||||
scan(
|
scan(
|
||||||
(acc, event) => ({
|
(acc, event) => {
|
||||||
event,
|
if (event instanceof OAuthErrorEvent) {
|
||||||
shouldProcess: event.type !== 'token_refresh_error' || acc.tokenRefreshErrorCount >= 1,
|
return {
|
||||||
tokenRefreshErrorCount: event.type === 'token_refresh_error' ? acc.tokenRefreshErrorCount + 1 : acc.tokenRefreshErrorCount
|
event,
|
||||||
}),
|
shouldProcess: event.type !== 'token_refresh_error' || acc.tokenRefreshErrorCount >= 1,
|
||||||
|
tokenRefreshErrorCount: event.type === 'token_refresh_error' ? acc.tokenRefreshErrorCount + 1 : acc.tokenRefreshErrorCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
event: null,
|
||||||
|
shouldProcess: false,
|
||||||
|
tokenRefreshErrorCount: event.type === 'token_received' || event.type === 'token_refreshed' ? 0 : acc.tokenRefreshErrorCount
|
||||||
|
};
|
||||||
|
},
|
||||||
{ event: null as OAuthErrorEvent | null, shouldProcess: false, tokenRefreshErrorCount: 0 }
|
{ event: null as OAuthErrorEvent | null, shouldProcess: false, tokenRefreshErrorCount: 0 }
|
||||||
),
|
),
|
||||||
filter(({ shouldProcess }) => shouldProcess),
|
filter(({ shouldProcess }) => shouldProcess),
|
||||||
|
|||||||
Reference in New Issue
Block a user