oidc-client-rx/src/flows/callback-handling/history-jwt-keys-callback-handler.service.ts
lonelyhentxi 983254164b
Some checks are pending
Build, Lint & Test Lib / Built, Lint and Test Library (push) Waiting to run
Build, Lint & Test Lib / Angular latest (push) Blocked by required conditions
Build, Lint & Test Lib / Angular latest & Schematics Job (push) Blocked by required conditions
Build, Lint & Test Lib / Angular latest Standalone & Schematics Job (push) Blocked by required conditions
Build, Lint & Test Lib / Angular 16 & RxJs 6 (push) Blocked by required conditions
Build, Lint & Test Lib / Angular V16 (push) Blocked by required conditions
Docs / Build and Deploy Docs Job (push) Waiting to run
Docs / Close Pull Request Job (push) Waiting to run
feat: init and fork some code from angular-auth-oidc-client
2025-01-17 04:24:12 +08:00

187 lines
5.6 KiB
TypeScript

import { DOCUMENT } from '../../dom';
import { inject, Injectable } from 'injection-js';
import { Observable, of, throwError } from 'rxjs';
import { catchError, switchMap, tap } from 'rxjs/operators';
import { AuthStateService } from '../../auth-state/auth-state.service';
import { OpenIdConfiguration } from '../../config/openid-configuration';
import { LoggerService } from '../../logging/logger.service';
import { StoragePersistenceService } from '../../storage/storage-persistence.service';
import { JwtKeys } from '../../validation/jwtkeys';
import { ValidationResult } from '../../validation/validation-result';
import { CallbackContext } from '../callback-context';
import { FlowsDataService } from '../flows-data.service';
import { ResetAuthDataService } from '../reset-auth-data.service';
import { SigninKeyDataService } from '../signin-key-data.service';
const JWT_KEYS = 'jwtKeys';
@Injectable()
export class HistoryJwtKeysCallbackHandlerService {
private readonly loggerService = inject(LoggerService);
private readonly authStateService = inject(AuthStateService);
private readonly flowsDataService = inject(FlowsDataService);
private readonly signInKeyDataService = inject(SigninKeyDataService);
private readonly storagePersistenceService = inject(
StoragePersistenceService
);
private readonly resetAuthDataService = inject(ResetAuthDataService);
private readonly document = inject(DOCUMENT);
// STEP 3 Code Flow, STEP 2 Implicit Flow, STEP 3 Refresh Token
callbackHistoryAndResetJwtKeys(
callbackContext: CallbackContext,
config: OpenIdConfiguration,
allConfigs: OpenIdConfiguration[]
): Observable<CallbackContext> {
let toWrite = { ...callbackContext.authResult };
if (!this.responseHasIdToken(callbackContext)) {
const existingIdToken = this.storagePersistenceService.getIdToken(config);
toWrite = {
...toWrite,
id_token: existingIdToken,
};
}
this.storagePersistenceService.write('authnResult', toWrite, config);
if (
config.allowUnsafeReuseRefreshToken &&
callbackContext.authResult?.refresh_token
) {
this.storagePersistenceService.write(
'reusable_refresh_token',
callbackContext.authResult.refresh_token,
config
);
}
if (
this.historyCleanUpTurnedOn(config) &&
!callbackContext.isRenewProcess
) {
this.resetBrowserHistory();
} else {
this.loggerService.logDebug(config, 'history clean up inactive');
}
if (callbackContext.authResult?.error) {
const errorMessage = `AuthCallback AuthResult came with error: ${callbackContext.authResult.error}`;
this.loggerService.logDebug(config, errorMessage);
this.resetAuthDataService.resetAuthorizationData(config, allConfigs);
this.flowsDataService.setNonce('', config);
this.handleResultErrorFromCallback(
callbackContext.authResult,
callbackContext.isRenewProcess
);
return throwError(() => new Error(errorMessage));
}
this.loggerService.logDebug(
config,
`AuthResult '${JSON.stringify(callbackContext.authResult, null, 2)}'.
AuthCallback created, begin token validation`
);
return this.signInKeyDataService.getSigningKeys(config).pipe(
tap((jwtKeys: JwtKeys) => this.storeSigningKeys(jwtKeys, config)),
catchError((err) => {
// fallback: try to load jwtKeys from storage
const storedJwtKeys = this.readSigningKeys(config);
if (!!storedJwtKeys) {
this.loggerService.logWarning(
config,
`Failed to retrieve signing keys, fallback to stored keys`
);
return of(storedJwtKeys);
}
return throwError(() => new Error(err));
}),
switchMap((jwtKeys) => {
if (jwtKeys) {
callbackContext.jwtKeys = jwtKeys;
return of(callbackContext);
}
const errorMessage = `Failed to retrieve signing key`;
this.loggerService.logWarning(config, errorMessage);
return throwError(() => new Error(errorMessage));
}),
catchError((err) => {
const errorMessage = `Failed to retrieve signing key with error: ${err}`;
this.loggerService.logWarning(config, errorMessage);
return throwError(() => new Error(errorMessage));
})
);
}
private responseHasIdToken(callbackContext: CallbackContext): boolean {
return !!callbackContext?.authResult?.id_token;
}
private handleResultErrorFromCallback(
result: unknown,
isRenewProcess: boolean
): void {
let validationResult = ValidationResult.SecureTokenServerError;
if (
result &&
typeof result === 'object' &&
'error' in result &&
(result.error as string) === 'login_required'
) {
validationResult = ValidationResult.LoginRequired;
}
this.authStateService.updateAndPublishAuthState({
isAuthenticated: false,
validationResult,
isRenewProcess,
});
}
private historyCleanUpTurnedOn(config: OpenIdConfiguration): boolean {
const { historyCleanupOff } = config;
return !historyCleanupOff;
}
private resetBrowserHistory(): void {
this.document.defaultView?.history.replaceState(
{},
this.document.title,
this.document.defaultView.location.origin +
this.document.defaultView.location.pathname
);
}
private storeSigningKeys(
jwtKeys: JwtKeys,
config: OpenIdConfiguration
): void {
this.storagePersistenceService.write(JWT_KEYS, jwtKeys, config);
}
private readSigningKeys(config: OpenIdConfiguration): any {
return this.storagePersistenceService.read(JWT_KEYS, config);
}
}