Skip to content

Commit

Permalink
Merge pull request #1073 from JerrettDavis/feature/start-documenting-…
Browse files Browse the repository at this point in the history
…code

Document public facing API
  • Loading branch information
damienbod committed Apr 30, 2021
2 parents 7f8701c + e90e957 commit debcb33
Show file tree
Hide file tree
Showing 11 changed files with 264 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,26 @@ const STORAGE_KEY = 'redirect';

@Injectable()
export class AutoLoginService {
getStoredRedirectRoute() {
/**
* Gets the stored redirect route from storage.
*/
getStoredRedirectRoute(): string {
return localStorage.getItem(STORAGE_KEY);
}

saveStoredRedirectRoute(url: string) {
/**
* Saves the redirect url to storage.
*
* @param url The redirect url to save.
*/
saveStoredRedirectRoute(url: string): void {
localStorage.setItem(STORAGE_KEY, url);
}

deleteStoredRedirectRoute() {
/**
* Removes the redirect url from storage.
*/
deleteStoredRedirectRoute(): void {
localStorage.removeItem(STORAGE_KEY);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Injectable } from '@angular/core';
import { throwError } from 'rxjs';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { FlowsService } from '../flows/flows.service';
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
import { LoggerService } from '../logging/logger.service';
import { IntervallService } from './intervall.service';
import { CallbackContext } from '../flows/callback-context';

@Injectable({ providedIn: 'root' })
export class RefreshSessionRefreshTokenService {
Expand All @@ -15,7 +16,7 @@ export class RefreshSessionRefreshTokenService {
private intervalService: IntervallService
) {}

refreshSessionWithRefreshTokens(customParams?: { [key: string]: string | number | boolean }) {
refreshSessionWithRefreshTokens(customParams?: { [key: string]: string | number | boolean }): Observable<CallbackContext> {
this.loggerService.logDebug('BEGIN refresh session Authorize');

return this.flowsService.processRefreshToken(customParams).pipe(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { SilentRenewService } from '../iframe/silent-renew.service';
import { LoggerService } from '../logging/logger.service';
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
import { RefreshSessionRefreshTokenService } from './refresh-session-refresh-token.service';
import { TokenResponse } from '../tokens/token-response';
import { CallbackContext } from '../flows/callback-context';

export const MAX_RETRY_ATTEMPTS = 3;
@Injectable({ providedIn: 'root' })
Expand All @@ -26,7 +28,7 @@ export class RefreshSessionService {
private refreshSessionRefreshTokenService: RefreshSessionRefreshTokenService
) {}

forceRefreshSession(customParams?: { [key: string]: string | number | boolean }) {
forceRefreshSession(customParams?: { [key: string]: string | number | boolean }): Observable<TokenResponse | null> {
if (this.flowHelper.isCurrentFlowCodeFlowWithRefreshTokens()) {
return this.startRefreshSession(customParams).pipe(
map(() => {
Expand Down Expand Up @@ -66,7 +68,7 @@ export class RefreshSessionService {
);
}

private startRefreshSession(customParams?: { [key: string]: string | number | boolean }) {
private startRefreshSession(customParams?: { [key: string]: string | number | boolean }): Observable<boolean | CallbackContext | null> {
const isSilentRenewRunning = this.flowsDataService.isSilentRenewRunning();
this.loggerService.logDebug(`Checking: silentRenewRunning: ${isSilentRenewRunning}`);
const shouldBeExecuted = !isSilentRenewRunning;
Expand Down Expand Up @@ -96,7 +98,7 @@ export class RefreshSessionService {
);
}

private timeoutRetryStrategy(errorAttempts: Observable<any>) {
private timeoutRetryStrategy(errorAttempts: Observable<any>): Observable<number> {
return errorAttempts.pipe(
mergeMap((error, index) => {
const scalingDuration = 1000;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,151 @@
import { LogLevel } from '../logging/log-level';

export interface OpenIdConfiguration {
/**
* The url to the Security Token Service (STS) server.
* This field is required.
*/
stsServer?: string;
/** Override the default STS wellknown endpoint postfix. */
authWellknownEndpoint?: string;
/** The redirect URL defined on the STS. */
redirectUrl?: string;
/**
* The Client MUST validate that the aud (audience) Claim contains its client_id value
* registered at the Issuer identified by the iss (issuer) Claim as an audience.
* The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience,
* or if it contains additional audiences not trusted by the Client.
*/
clientId?: string;
/**
* 'code', 'id_token token' or 'id_token' Name of the flow which can be configured.
* You must use the 'id_token token' flow, if you want to access an API or get user data from the server.
* The access_token is required for this, and only returned with this flow.
*/
responseType?: string;
/**
* This is this scopes which are requested from the server from this client.
* This must match the STS server configuration.
* The 'openid' scope is required. The 'offline_access' scope can be requested when using refresh tokens
* but this is optional and some STS do not support this or recommend not requesting this even when using
* refresh tokens in the browser.
*/
scope?: string;
/**
* Optional hd parameter for Google Auth with particular G Suite domain,
* see https://developers.google.com/identity/protocols/OpenIDConnect#hd-param
*/
hdParam?: string;
/** URL after a server logout if using the end session API. */
postLogoutRedirectUri?: string;
/** Starts the OpenID session management for this client. */
startCheckSession?: boolean;
/** Renews the client tokens, once the token_id expires. Can use the iframes, or the refresh tokens */
silentRenew?: boolean;
/** An optional URL to handle silent renew callbacks */
silentRenewUrl?: string;
/**
* Sets the maximum waiting time for silent renew process. If this time is exceeded, the silent renew state will
* be reset. Default = 20.
* */
silentRenewTimeoutInSeconds?: number;
/**
* Makes it possible to add an offset to the silent renew check in seconds.
* By entering a value, you can renew the tokens, before the tokens expire.
*/
renewTimeBeforeTokenExpiresInSeconds?: number;
/**
* When set to true, refresh tokens are used to renew the user session.
* When set to false, standard silent renew is used.
* Default value is false.
*/
useRefreshToken?: boolean;
/**
* Activates Pushed Authorisation Requests for login and popup login.
* Not compatible with Iframe renew.
*/
usePushedAuthorisationRequests?: boolean;
/**
* A token obtained by using a refresh token normally doesn't contain a nonce value.
* However, some OIDC endpoint implementations do send one. The library checks to see if the nonce is present.
* Note that if the nonce value is present, it will not be verified.
* This is not recommended, if the STS returns a nonce in the refresh.
* Default value is false
*/
ignoreNonceAfterRefresh?: boolean;
/**
* The default Angular route which is used after a successful login, if not using the
* trigger_authorization_result_event
*/
postLoginRoute?: string;
/** Route, if the server returns a 403. This is an Angular route. HTTP 403 */
forbiddenRoute?: string;
/** Route, if the server returns a 401. This is an Angular route. HTTP 401 */
unauthorizedRoute?: string;
/** When set to true, library automatically gets user info after authentication */
autoUserinfo?: boolean;
/** When set to true, library automatically gets user info after token renew */
renewUserInfoAfterTokenRenew?: boolean;
/** Used for custom state logic handling. The state is not automatically reset when set to false */
autoCleanStateAfterAuthentication?: boolean;
/**
* This can be set to true which emits an event instead of an angular route change.
* Instead of forcing the application consuming this library to automatically redirect to one of the 3
* hard-configured routes (start, unauthorized, forbidden), this modification will add an extra
* configuration option to override such behavior and trigger an event that will allow to subscribe to
* it and let the application perform other actions. This would be useful to allow the application to
* save an initial return url so that the user is redirected to it after a successful login on the STS
* (ie: saving the return url previously on sessionStorage and then retrieving it during the triggering of the event).
*/
triggerAuthorizationResultEvent?: boolean;
/** 0, 1, 2 can be used to set the log level displayed in the console. */
logLevel?: LogLevel;
/** Make it possible to turn the iss validation off per configuration. You should not turn this off! */
issValidationOff?: boolean;
/**
* If this is active, the history is not cleaned up on an authorize callback.
* This can be used, when the application needs to preserve the history.
*/
historyCleanupOff?: boolean;
/**
* Amount of offset allowed between the server creating the token, and the client app receiving the id_token.
* The diff in time between the server time and client time is also important in validating this value.
* All times are in UTC.
*/
maxIdTokenIatOffsetAllowedInSeconds?: number;
/**
* This allows the application to disable the iat offset validation check.
* The iat Claim can be used to reject tokens that were issued too far away from the current time,
* limiting the amount of time that nonces need to be stored to prevent attacks.
* The acceptable range is client specific.
*/
disableIatOffsetValidation?: boolean;
/** The storage mechanism to use */
storage?: any;
/** Extra parameters to add to the authorization URL request */
customParams?: { [key: string]: string | number | boolean };
/** Extra parameters to add to the token URL request */
customTokenParams?: { [key: string]: string | number | boolean };
/** Denotes if the AuthWellKnownEndpoints should be loaded at startup or when the user calls the authorize method. */
eagerLoadAuthWellKnownEndpoints?: boolean;

// Azure B2C have implemented this incorrectly. Add support for to disable this until fixed.
/** disables the auth_time validation for id_tokens in a refresh due to Azure's incorrect implementation */
disableRefreshIdTokenAuthTimeValidation?: boolean;
/** Controls the periodic check time interval in sections.
* Default value is 3
*/
tokenRefreshInSeconds?: number;
/**
* Array of secure urls on which the token should be send if the interceptor is added to the HTTP_INTERCEPTORS
*/
secureRoutes?: string[];
/**
* Controls the periodic retry time interval for retrieving new tokens in seconds..
* silentRenewTimeoutInSeconds and tokenRefreshInSeconds are upper bounds for this value.
* Default value is 3
*/
refreshTokenRetryInSeconds?: number;
/** Adds the ngsw-bypass param to all requests */
ngswBypass?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { RefreshSessionCallbackHandlerService } from './callback-handling/refres
import { RefreshTokenCallbackHandlerService } from './callback-handling/refresh-token-callback-handler.service';
import { StateValidationCallbackHandlerService } from './callback-handling/state-validation-callback-handler.service';
import { UserCallbackHandlerService } from './callback-handling/user-callback-handler.service';
import { Observable } from 'rxjs';

@Injectable()
export class FlowsService {
Expand All @@ -21,7 +22,7 @@ export class FlowsService {
private readonly refreshTokenCallbackHandlerService: RefreshTokenCallbackHandlerService
) {}

processCodeFlowCallback(urlToCheck: string) {
processCodeFlowCallback(urlToCheck: string): Observable<CallbackContext> {
return this.codeFlowCallbackHandlerService.codeFlowCallback(urlToCheck).pipe(
switchMap((callbackContext) => this.codeFlowCallbackHandlerService.codeFlowCodeRequest(callbackContext)),
switchMap((callbackContext) => this.historyJwtKeysCallbackHandlerService.callbackHistoryAndResetJwtKeys(callbackContext)),
Expand All @@ -30,23 +31,23 @@ export class FlowsService {
);
}

processSilentRenewCodeFlowCallback(firstContext: CallbackContext) {
processSilentRenewCodeFlowCallback(firstContext: CallbackContext): Observable<CallbackContext> {
return this.codeFlowCallbackHandlerService.codeFlowCodeRequest(firstContext).pipe(
switchMap((callbackContext) => this.historyJwtKeysCallbackHandlerService.callbackHistoryAndResetJwtKeys(callbackContext)),
switchMap((callbackContext) => this.stateValidationCallbackHandlerService.callbackStateValidation(callbackContext)),
switchMap((callbackContext) => this.userHandlerService.callbackUser(callbackContext))
);
}

processImplicitFlowCallback(hash?: string) {
processImplicitFlowCallback(hash?: string): Observable<CallbackContext> {
return this.implicitFlowCallbackHandlerService.implicitFlowCallback(hash).pipe(
switchMap((callbackContext) => this.historyJwtKeysCallbackHandlerService.callbackHistoryAndResetJwtKeys(callbackContext)),
switchMap((callbackContext) => this.stateValidationCallbackHandlerService.callbackStateValidation(callbackContext)),
switchMap((callbackContext) => this.userHandlerService.callbackUser(callbackContext))
);
}

processRefreshToken(customParams?: { [key: string]: string | number | boolean }) {
processRefreshToken(customParams?: { [key: string]: string | number | boolean }): Observable<CallbackContext> {
return this.refreshSessionCallbackHandlerService.refreshSessionWithRefreshTokens().pipe(
switchMap((callbackContext) => this.refreshTokenCallbackHandlerService.refreshTokensRequestTokens(callbackContext, customParams)),
switchMap((callbackContext) => this.historyJwtKeysCallbackHandlerService.callbackHistoryAndResetJwtKeys(callbackContext)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class RefreshSessionIframeService {
});
}

private initSilentRenewRequest() {
private initSilentRenewRequest(): void {
const instanceId = Math.random();

const initDestroyHandler = this.renderer.listen('window', 'oidc-silent-renew-init', (e: CustomEvent) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { of, throwError } from 'rxjs';
import { Observable, of, throwError } from 'rxjs';
import { catchError, retry, switchMap, tap } from 'rxjs/operators';
import { DataService } from '../api/data.service';
import { ResetAuthDataService } from '../flows/reset-auth-data.service';
Expand All @@ -24,7 +24,7 @@ export class LogoffRevocationService {

// Logs out on the server and the local client.
// If the server state has changed, checksession, then only a local logout.
logoff(urlHandler?: (url: string) => any) {
logoff(urlHandler?: (url: string) => any): void {
this.loggerService.logDebug('logoff, remove auth ');
const endSessionUrl = this.getEndSessionUrl();
this.resetAuthDataService.resetAuthorizationData();
Expand All @@ -43,14 +43,14 @@ export class LogoffRevocationService {
}
}

logoffLocal() {
logoffLocal(): void {
this.resetAuthDataService.resetAuthorizationData();
this.checkSessionService.stop();
}

// The refresh token and and the access token are revoked on the server. If the refresh token does not exist
// only the access token is revoked. Then the logout run.
logoffAndRevokeTokens(urlHandler?: (url: string) => any) {
logoffAndRevokeTokens(urlHandler?: (url: string) => any): Observable<any> {
if (!this.storagePersistenceService.read('authWellKnownEndPoints')?.revocationEndpoint) {
this.loggerService.logDebug('revocation endpoint not supported');
this.logoff(urlHandler);
Expand Down Expand Up @@ -82,7 +82,7 @@ export class LogoffRevocationService {
// revokes an access token on the STS. If no token is provided, then the token from
// the storage is revoked. You can pass any token to revoke. This makes it possible to
// manage your own tokens. The is a public API.
revokeAccessToken(accessToken?: any) {
revokeAccessToken(accessToken?: any): Observable<any> {
const accessTok = accessToken || this.storagePersistenceService.getAccessToken();
const body = this.urlService.createRevocationEndpointBodyAccessToken(accessTok);
const url = this.urlService.getRevocationEndpointUrl();
Expand All @@ -108,7 +108,7 @@ export class LogoffRevocationService {
// revokes an refresh token on the STS. This is only required in the code flow with refresh tokens.
// If no token is provided, then the token from the storage is revoked. You can pass any token to revoke.
// This makes it possible to manage your own tokens.
revokeRefreshToken(refreshToken?: any) {
revokeRefreshToken(refreshToken?: any): Observable<any> {
const refreshTok = refreshToken || this.storagePersistenceService.getRefreshToken();
const body = this.urlService.createRevocationEndpointBodyRefreshToken(refreshTok);
const url = this.urlService.getRevocationEndpointUrl();
Expand Down

0 comments on commit debcb33

Please sign in to comment.