diff --git a/lib/core/src/lib/app-config/app-config.service.ts b/lib/core/src/lib/app-config/app-config.service.ts index 314522a5ad..7a94032484 100644 --- a/lib/core/src/lib/app-config/app-config.service.ts +++ b/lib/core/src/lib/app-config/app-config.service.ts @@ -81,6 +81,13 @@ export class AppConfigService { return this.status === Status.LOADED; } + /** + * Returns current authentication type as configured via `authType` property. + */ + get authType(): 'OAUTH' | 'BASIC' { + return this.get('authType'); + } + constructor(protected http: HttpClient, protected extensionService: ExtensionService) { this.onLoadSubject = new ReplaySubject(); this.onLoad = this.onLoadSubject.asObservable(); diff --git a/lib/core/src/lib/auth/oidc/auth-config.service.ts b/lib/core/src/lib/auth/oidc/auth-config.service.ts index e60fa66340..1beafd9f1d 100644 --- a/lib/core/src/lib/auth/oidc/auth-config.service.ts +++ b/lib/core/src/lib/auth/oidc/auth-config.service.ts @@ -40,11 +40,6 @@ export class AuthConfigService { @Inject(AUTH_MODULE_CONFIG) private readonly authModuleConfig: AuthModuleConfig ) {} - private _authConfig!: AuthConfig; - get authConfig(): AuthConfig { - return this._authConfig; - } - loadConfig(): Promise { return this.appConfigService.onLoad.pipe(take(1)).toPromise().then(this.loadAppConfig.bind(this)); } diff --git a/lib/core/src/lib/auth/oidc/auth.module.ts b/lib/core/src/lib/auth/oidc/auth.module.ts index 354edad04a..da7efb8b78 100644 --- a/lib/core/src/lib/auth/oidc/auth.module.ts +++ b/lib/core/src/lib/auth/oidc/auth.module.ts @@ -27,6 +27,7 @@ import { AuthRoutingModule } from './auth-routing.module'; import { AuthService } from './auth.service'; import { RedirectAuthService } from './redirect-auth.service'; import { AuthenticationConfirmationComponent } from './view/authentication-confirmation/authentication-confirmation.component'; +import { AppConfigService } from '../../app-config/app-config.service'; /** * Create a Login Factory function @@ -36,8 +37,8 @@ import { AuthenticationConfirmationComponent } from './view/authentication-confi * @param config auth configuration * @returns a factory function */ -export function loginFactory(oAuthService: OAuthService, storage: OAuthStorage, config: AuthConfig) { - const service = new RedirectAuthService(oAuthService, storage, config); +export function loginFactory(oAuthService: OAuthService, storage: OAuthStorage, appConfig: AppConfigService, config: AuthConfig) { + const service = new RedirectAuthService(oAuthService, storage, appConfig, config); return () => service.init(); } @@ -49,19 +50,18 @@ export function loginFactory(oAuthService: OAuthService, storage: OAuthStorage, // { provide: AuthGuard, useClass: OidcAuthGuard }, // { provide: AuthGuardEcm, useClass: OidcAuthGuard }, // { provide: AuthGuardBpm, useClass: OidcAuthGuard }, - { provide: AuthenticationService}, + AuthenticationService, { provide: AlfrescoApiService, useClass: AlfrescoApiNoAuthService }, { provide: AUTH_CONFIG, useFactory: authConfigFactory, deps: [AuthConfigService] }, - RedirectAuthService, { provide: AuthService, useExisting: RedirectAuthService }, { provide: APP_INITIALIZER, useFactory: loginFactory, - deps: [OAuthService, OAuthStorage, AUTH_CONFIG], + deps: [OAuthService, OAuthStorage, AUTH_CONFIG, AppConfigService], multi: true } ] diff --git a/lib/core/src/lib/auth/oidc/redirect-auth.service.ts b/lib/core/src/lib/auth/oidc/redirect-auth.service.ts index 96cdc5a5e1..f52256ab10 100644 --- a/lib/core/src/lib/auth/oidc/redirect-auth.service.ts +++ b/lib/core/src/lib/auth/oidc/redirect-auth.service.ts @@ -21,152 +21,163 @@ import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks'; import { from, Observable } from 'rxjs'; import { distinctUntilChanged, filter, map, shareReplay } from 'rxjs/operators'; import { AuthService } from './auth.service'; +import { AppConfigService } from '../../app-config/app-config.service'; const isPromise = (value: T | Promise): value is Promise => value && typeof (value as Promise).then === 'function'; -@Injectable() +@Injectable({ providedIn: 'root' }) export class RedirectAuthService extends AuthService { + onLogin: Observable; - onLogin: Observable; + private _loadDiscoveryDocumentPromise = Promise.resolve(false); - private _loadDiscoveryDocumentPromise = Promise.resolve(false); + /** Subscribe to whether the user has valid Id/Access tokens. */ + authenticated$!: Observable; - /** Subscribe to whether the user has valid Id/Access tokens. */ - authenticated$!: Observable; + /** Subscribe to errors reaching the IdP. */ + idpUnreachable$!: Observable; - /** Subscribe to errors reaching the IdP. */ - idpUnreachable$!: Observable; - - /** - * Get whether the user has valid Id/Access tokens. - * - * @returns `true` if the user is authenticated, otherwise `false` - */ - get authenticated(): boolean { - return this.oauthService.hasValidIdToken() && this.oauthService.hasValidAccessToken(); - } - - private authConfig!: AuthConfig | Promise; - - constructor( - private oauthService: OAuthService, - private _oauthStorage: OAuthStorage, - @Inject(AUTH_CONFIG) authConfig: AuthConfig - ) { - super(); - this.authConfig = authConfig; - - this.oauthService.clearHashAfterLogin = true; - - this.authenticated$ = this.oauthService.events.pipe( - map(() => this.authenticated), - distinctUntilChanged(), - shareReplay(1) - ); - - this.onLogin = this.authenticated$.pipe( - filter((authenticated) => authenticated), - map(() => undefined) - ); - - this.idpUnreachable$ = this.oauthService.events.pipe( - filter((event): event is OAuthErrorEvent => event.type === 'discovery_document_load_error'), - map((event) => event.reason as Error) - ); - } - - init() { - if (isPromise(this.authConfig)) { - return this.authConfig.then((config) => this.configureAuth(config)); + /** + * Get whether the user has valid Id/Access tokens. + * + * @returns `true` if the user is authenticated, otherwise `false` + */ + get authenticated(): boolean { + return this.oauthService.hasValidIdToken() && this.oauthService.hasValidAccessToken(); } - return this.configureAuth(this.authConfig); - } + private authConfig!: AuthConfig | Promise; - logout() { - this.oauthService.logOut(); - } + constructor( + private oauthService: OAuthService, + private oauthStorage: OAuthStorage, + private appConfig: AppConfigService, + @Inject(AUTH_CONFIG) authConfig: AuthConfig + ) { + super(); + this.authConfig = authConfig; - ensureDiscoveryDocument(): Promise { - this._loadDiscoveryDocumentPromise = this._loadDiscoveryDocumentPromise - .catch(() => false) - .then((loaded) => { - if (!loaded) { - return this.oauthService.loadDiscoveryDocument().then(() => true); + this.oauthService.clearHashAfterLogin = true; + + this.authenticated$ = this.oauthService.events.pipe( + map(() => this.authenticated), + distinctUntilChanged(), + shareReplay(1) + ); + + this.onLogin = this.authenticated$.pipe( + filter((authenticated) => authenticated), + map(() => undefined) + ); + + this.idpUnreachable$ = this.oauthService.events.pipe( + filter((event): event is OAuthErrorEvent => event.type === 'discovery_document_load_error'), + map((event) => event.reason as Error) + ); + } + + async init() { + if (isPromise(this.authConfig)) { + return this.authConfig.then((config) => this.configureAuth(config)); } - return true; - }); - return this._loadDiscoveryDocumentPromise; - } - - login(currentUrl?: string): void { - let stateKey: string | undefined; - - if (currentUrl) { - const randomValue = window.crypto.getRandomValues(new Uint32Array(1))[0]; - stateKey = `auth_state_${randomValue}${Date.now()}`; - this._oauthStorage.setItem(stateKey, JSON.stringify(currentUrl || {})); + return this.configureAuth(this.authConfig); } - // initLoginFlow will initialize the login flow in either code or implicit depending on the configuration - this.ensureDiscoveryDocument().then(() => void this.oauthService.initLoginFlow(stateKey)); - } - - baseAuthLogin(username: string, password: string): Observable { - this.oauthService.useHttpBasicAuth = true; - - return from(this.oauthService.fetchTokenUsingPasswordFlow(username, password)).pipe( - map((response) => { - const props = new Map(); - props.set('id_token', response.id_token); - // for backward compatibility we need to set the response in our storage - this.oauthService['storeAccessTokenResponse'](response.access_token, response.refresh_token, response.expires_in, response.scope, props); - return response; - }) - ); - } - - async loginCallback(): Promise { - return this.ensureDiscoveryDocument() - .then(() => this.oauthService.tryLogin({ preventClearHashAfterLogin: false })) - .then(() => this._getRedirectUrl()); - } - - private _getRedirectUrl() { - const DEFAULT_REDIRECT = '/'; - const stateKey = this.oauthService.state; - - if (stateKey) { - const stateStringified = this._oauthStorage.getItem(stateKey); - if (stateStringified) { - // cleanup state from storage - this._oauthStorage.removeItem(stateKey); - return JSON.parse(stateStringified); - } - } - - return DEFAULT_REDIRECT; - } - - private configureAuth(config: AuthConfig) { - this.oauthService.configure(config); - this.oauthService.tokenValidationHandler = new JwksValidationHandler(); - - if (config.sessionChecksEnabled) { - this.oauthService.events.pipe(filter((event) => event.type === 'session_terminated')).subscribe(() => { + logout() { this.oauthService.logOut(); - }); } - return this.ensureDiscoveryDocument().then(() => - void this.oauthService.setupAutomaticSilentRefresh() - ).catch(() => { - // catch error to prevent the app from crashing when trying to access unprotected routes - }); - } + ensureDiscoveryDocument(): Promise { + if (this.appConfig.authType === 'BASIC') { + return Promise.resolve(true); + } - updateIDPConfiguration(config: AuthConfig) { - this.oauthService.configure(config); - } + this._loadDiscoveryDocumentPromise = this._loadDiscoveryDocumentPromise + .catch(() => false) + .then((loaded) => { + if (!loaded) { + return this.oauthService.loadDiscoveryDocument().then(() => true); + } + return true; + }); + return this._loadDiscoveryDocumentPromise; + } + + login(currentUrl?: string): void { + let stateKey: string | undefined; + + if (currentUrl) { + const randomValue = window.crypto.getRandomValues(new Uint32Array(1))[0]; + stateKey = `auth_state_${randomValue}${Date.now()}`; + this.oauthStorage.setItem(stateKey, JSON.stringify(currentUrl || {})); + } + + // initLoginFlow will initialize the login flow in either code or implicit depending on the configuration + this.ensureDiscoveryDocument().then(() => void this.oauthService.initLoginFlow(stateKey)); + } + + baseAuthLogin(username: string, password: string): Observable { + this.oauthService.useHttpBasicAuth = true; + + return from(this.oauthService.fetchTokenUsingPasswordFlow(username, password)).pipe( + map((response) => { + const props = new Map(); + props.set('id_token', response.id_token); + // for backward compatibility we need to set the response in our storage + this.oauthService['storeAccessTokenResponse']( + response.access_token, + response.refresh_token, + response.expires_in, + response.scope, + props + ); + return response; + }) + ); + } + + async loginCallback(): Promise { + return this.ensureDiscoveryDocument() + .then(() => this.oauthService.tryLogin({ preventClearHashAfterLogin: false })) + .then(() => this._getRedirectUrl()); + } + + private _getRedirectUrl() { + const DEFAULT_REDIRECT = '/'; + const stateKey = this.oauthService.state; + + if (stateKey) { + const stateStringified = this.oauthStorage.getItem(stateKey); + if (stateStringified) { + // cleanup state from storage + this.oauthStorage.removeItem(stateKey); + return JSON.parse(stateStringified); + } + } + + return DEFAULT_REDIRECT; + } + + private async configureAuth(config: AuthConfig) { + this.oauthService.configure(config); + this.oauthService.tokenValidationHandler = new JwksValidationHandler(); + + if (config.sessionChecksEnabled) { + this.oauthService.events.pipe(filter((event) => event.type === 'session_terminated')).subscribe(() => { + this.oauthService.logOut(); + }); + } + + try { + await this.ensureDiscoveryDocument(); + return void this.oauthService.setupAutomaticSilentRefresh(); + } catch { + // do nothing + } + } + + updateIDPConfiguration(config: AuthConfig) { + this.oauthService.configure(config); + } }