src/api/aaai/impl/oAuthProvider.ts
OAuth provider implementation
Properties |
|
Methods |
|
constructor(injector: Injector, oAuthService: OAuthService)
|
|||||||||
Defined in src/api/aaai/impl/oAuthProvider.ts:40
|
|||||||||
Parameters :
|
Private Static Readonly CYFRONET_ISSUER |
Default value : OAuthAuthenticationProvider.CYFRONET_ROOT + '/oauth2'
|
Defined in src/api/aaai/impl/oAuthProvider.ts:30
|
Private Static Readonly CYFRONET_ROOT |
Type : string
|
Default value : 'https://aaai.epos-eu.org'
|
Defined in src/api/aaai/impl/oAuthProvider.ts:29
|
Private Static Readonly EPOS_CLIENT |
Type : string
|
Default value : 'eposICS'
|
Defined in src/api/aaai/impl/oAuthProvider.ts:28
|
Private Readonly http |
Type : HttpClient
|
Defined in src/api/aaai/impl/oAuthProvider.ts:35
|
Private Static Readonly REDIRECTION_PAGE |
Type : string
|
Default value : '/last-page-redirect'
|
Defined in src/api/aaai/impl/oAuthProvider.ts:32
|
Private Static Readonly REVOKE_ENDPOINT |
Default value : OAuthAuthenticationProvider.CYFRONET_ISSUER + '/revoke'
|
Defined in src/api/aaai/impl/oAuthProvider.ts:31
|
Private Readonly router |
Type : Router
|
Defined in src/api/aaai/impl/oAuthProvider.ts:34
|
Private updateUserProfileTimeout |
Type : NodeJS.Timeout
|
Defined in src/api/aaai/impl/oAuthProvider.ts:37
|
Private Readonly userProfileSource |
Default value : new BehaviorSubject<null | AAAIUser>(null)
|
Defined in src/api/aaai/impl/oAuthProvider.ts:40
|
Current user |
Private configure |
configure()
|
Defined in src/api/aaai/impl/oAuthProvider.ts:171
|
Returns :
void
|
Public getManageUrl |
getManageUrl()
|
Defined in src/api/aaai/impl/oAuthProvider.ts:74
|
Returns :
string
|
Public getUser |
getUser()
|
Defined in src/api/aaai/impl/oAuthProvider.ts:55
|
Returns :
null | AAAIUser
|
Private getUserId |
getUserId()
|
Defined in src/api/aaai/impl/oAuthProvider.ts:200
|
Returns :
null | string
|
Private getUserToken |
getUserToken()
|
Defined in src/api/aaai/impl/oAuthProvider.ts:208
|
Returns :
string
|
Private init |
init()
|
Defined in src/api/aaai/impl/oAuthProvider.ts:119
|
Returns :
void
|
Public login |
login()
|
Defined in src/api/aaai/impl/oAuthProvider.ts:61
|
Returns :
void
|
Public logout |
logout()
|
Defined in src/api/aaai/impl/oAuthProvider.ts:65
|
Returns :
void
|
Private makeAuthConfig | ||||||
makeAuthConfig(router: Router)
|
||||||
Defined in src/api/aaai/impl/oAuthProvider.ts:79
|
||||||
Parameters :
Returns :
AuthConfig
|
Private revokeTokenManually |
revokeTokenManually()
|
Defined in src/api/aaai/impl/oAuthProvider.ts:176
|
Returns :
Promise<void>
|
Private updateUserProfile |
updateUserProfile()
|
Defined in src/api/aaai/impl/oAuthProvider.ts:146
|
Returns :
void
|
Public watchForUserChange |
watchForUserChange()
|
Defined in src/api/aaai/impl/oAuthProvider.ts:51
|
Returns :
Observable<null | AAAIUser>
|
import { AuthConfig, OAuthService, UserInfo } from 'angular-oauth2-oidc';
import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
import { Router } from '@angular/router';
import { AuthenticationProvider } from '../authProvider.interface';
import { BehaviorSubject, Observable } from 'rxjs';
import { AAAIUser } from '../aaaiUser.interface';
import { BasicUser } from './basicUser';
import { Injector } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
/** OAuth provider implementation */
export class OAuthAuthenticationProvider implements AuthenticationProvider {
private static readonly EPOS_CLIENT = 'eposICS';
private static readonly CYFRONET_ROOT = 'https://aaai.epos-eu.org';
private static readonly CYFRONET_ISSUER = OAuthAuthenticationProvider.CYFRONET_ROOT + '/oauth2';
private static readonly REVOKE_ENDPOINT = OAuthAuthenticationProvider.CYFRONET_ISSUER + '/revoke';
private static readonly REDIRECTION_PAGE = '/last-page-redirect';
private readonly router: Router;
private readonly http: HttpClient;
private updateUserProfileTimeout: NodeJS.Timeout;
/** Current user */
private readonly userProfileSource = new BehaviorSubject<null | AAAIUser>(null);
constructor(
injector: Injector,
private readonly oAuthService: OAuthService,
) {
this.router = injector.get(Router);
this.http = injector.get(HttpClient);
this.init();
}
public watchForUserChange(): Observable<null | AAAIUser> {
return this.userProfileSource.asObservable();
}
public getUser(): null | AAAIUser {
return this.userProfileSource.getValue();
}
// angular-oauth2-oidc suggests that "Code Flow" rather than "Implicit Flow" should be favoured.
// SHould we adopt that? https://www.npmjs.com/package/angular-oauth2-oidc
public login(): void {
this.oAuthService.initCodeFlow();
}
public logout(): void {
// This only logs out the client.
// in order to log out at the server, we need a logout url.
void this.revokeTokenManually()
.then(() => {
this.oAuthService.logOut();
});
}
public getManageUrl(): string {
return OAuthAuthenticationProvider.CYFRONET_ROOT;
}
private makeAuthConfig(router: Router): AuthConfig {
const authConfig: AuthConfig = {
// Url of the Identity Provider
issuer: OAuthAuthenticationProvider.CYFRONET_ISSUER,
// URL of the SPA to redirect the user to after login
// redirectUri: this.redirectionUri(),
get redirectUri(): string {
// eslint-disable-next-line max-len
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, no-underscore-dangle, @typescript-eslint/dot-notation
const base = String(router['location']._baseHref); // e.g. /testpath
const origin = window.location.origin; // e.g. http://localhost:4200
// e.g. http://localhost:4200/testpath/last-page-redirect
const redirect = origin + base + OAuthAuthenticationProvider.REDIRECTION_PAGE;
return redirect;
},
// The SPA's id. The SPA is registerd with this id at the auth-server
clientId: OAuthAuthenticationProvider.EPOS_CLIENT,
// URL of the SPA to redirect the user after silent refresh
silentRefreshRedirectUri: window.location.origin + '/silent-token-refresh.html',
timeoutFactor: 0.75,
// set the scope for the permissions the client should request
// The first three are defined by OIDC. The 4th is a usecase-specific one
scope: [
'openid',
'profile',
'single-logout',
].join(' '),
disableAtHashCheck: true,
};
return authConfig;
}
private init() {
this.configure();
this.oAuthService.setupAutomaticSilentRefresh();
this.oAuthService.tokenValidationHandler = new JwksValidationHandler();
void this.oAuthService.loadDiscoveryDocumentAndTryLogin()
// maybe we should do this like this
// https://www.linkedin.com/pulse/implicit-flow-authentication-using-angular-ghanshyam-shukla
.catch((e) => {
console.warn('Caught Error - Failed to contact Authentication Server.', e);
})
.then(() => {
console.log('Successfully Contacted Authentication Server.');
});
this.oAuthService.events.subscribe(e => {
// angular-oauth2-oidc EventType string values
switch (e.type) {
case ('discovery_document_loaded'): // page refresh when logged in
case ('token_received'): // first logged in
case ('logout'): // logout to clear user info
this.updateUserProfile();
break;
}
});
}
private updateUserProfile(): void {
// ensure not called too often
clearTimeout(this.updateUserProfileTimeout);
this.updateUserProfileTimeout = setTimeout(() => {
const token = this.getUserToken();
const currentProfile = this.userProfileSource.getValue();
// only if the token has changed
if ((currentProfile == null) || (currentProfile.getToken() !== token)) {
// Try protects against a promise not being returned from "loadUserProfile" function.
try {
this.oAuthService.loadUserProfile()
.then((object: UserInfo) => {
this.userProfileSource.next(BasicUser.makeFromProfileResponse(token, object));
})
.catch((error: unknown) => {
const userId = this.getUserId();
const user = BasicUser.makeOrDefault(userId, userId, token, userId);
this.userProfileSource.next(user);
});
} catch (error) {
this.userProfileSource.next(null);
}
}
}, 100);
}
private configure() {
this.oAuthService.configure(this.makeAuthConfig(this.router));
}
private revokeTokenManually(): Promise<void> {
const httpOptions = {
headers: new HttpHeaders({
Authorization: 'Bearer ' + this.oAuthService.getAccessToken(),
// eslint-disable-next-line @typescript-eslint/naming-convention
'Content-Type': 'application/x-www-form-urlencoded',
}),
};
return this.http.post(
OAuthAuthenticationProvider.REVOKE_ENDPOINT,
`token=${this.oAuthService.getAccessToken()}` +
`&client_id=${this.oAuthService.clientId}` +
'&token_type_hint=access_token' +
'&logout=true',
httpOptions,
)
.toPromise()
.then(() => { })
.catch((e) => {
console.warn('Unable to revoke Access Token', e);
});
}
private getUserId(): null | string {
const claims = this.oAuthService.getIdentityClaims() as Record<string, unknown>;
if (claims) {
return String(claims.sub);
}
return null;
}
private getUserToken(): string {
return this.oAuthService.getAccessToken();
}
}