feat: init and fork some code from angular-auth-oidc-client
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
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
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user