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);
|
||||
});
|
||||
|
||||
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', () => {
|
||||
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'
|
||||
|
||||
@@ -171,17 +171,27 @@ export class RedirectAuthService extends AuthService {
|
||||
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
|
||||
// built-in retry (secondTokenRefreshErrorEventOccur$) has a chance to run
|
||||
// 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(
|
||||
(acc, event) => ({
|
||||
event,
|
||||
shouldProcess: event.type !== 'token_refresh_error' || acc.tokenRefreshErrorCount >= 1,
|
||||
tokenRefreshErrorCount: event.type === 'token_refresh_error' ? acc.tokenRefreshErrorCount + 1 : acc.tokenRefreshErrorCount
|
||||
}),
|
||||
(acc, event) => {
|
||||
if (event instanceof OAuthErrorEvent) {
|
||||
return {
|
||||
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 }
|
||||
),
|
||||
filter(({ shouldProcess }) => shouldProcess),
|
||||
|
||||
Reference in New Issue
Block a user