angular-oauth2-oidc
Version:
Support for OAuth 2(.1) and OpenId Connect (OIDC) in Angular
1 lines • 225 kB
Source Map (JSON)
{"version":3,"file":"angular-oauth2-oidc.mjs","sources":["../../../projects/lib/src/token-validation/null-validation-handler.ts","../../../projects/lib/src/oauth-module.config.ts","../../../projects/lib/src/date-time-provider.ts","../../../projects/lib/src/types.ts","../../../projects/lib/src/events.ts","../../../projects/lib/src/base64-helper.ts","../../../projects/lib/src/auth.config.ts","../../../projects/lib/src/encoder.ts","../../../projects/lib/src/token-validation/validation-handler.ts","../../../projects/lib/src/url-helper.service.ts","../../../projects/lib/src/token-validation/fast-sha256js.ts","../../../projects/lib/src/token-validation/hash-handler.ts","../../../projects/lib/src/oauth-service.ts","../../../projects/lib/src/interceptors/resource-server-error-handler.ts","../../../projects/lib/src/interceptors/default-oauth.interceptor.ts","../../../projects/lib/src/factories.ts","../../../projects/lib/src/provider.ts","../../../projects/lib/src/angular-oauth-oidc.module.ts","../../../projects/lib/src/token-validation/jwks-validation-handler.ts","../../../projects/lib/src/tokens.ts","../../../projects/lib/src/angular-oauth2-oidc.ts"],"sourcesContent":["import { ValidationHandler, ValidationParams } from './validation-handler';\n\n/**\n * A validation handler that isn't validating nothing.\n * Can be used to skip validation (at your own risk).\n */\nexport class NullValidationHandler implements ValidationHandler {\n validateSignature(validationParams: ValidationParams): Promise<any> {\n return Promise.resolve(null);\n }\n validateAtHash(validationParams: ValidationParams): Promise<boolean> {\n return Promise.resolve(true);\n }\n}\n","export abstract class OAuthModuleConfig {\n resourceServer: OAuthResourceServerConfig;\n}\n\nexport abstract class OAuthResourceServerConfig {\n /**\n * Urls for which calls should be intercepted.\n * If there is an ResourceServerErrorHandler registered, it is used for them.\n * If sendAccessToken is set to true, the access_token is send to them too.\n */\n allowedUrls?: Array<string>;\n sendAccessToken: boolean;\n customUrlValidation?: (url: string) => boolean;\n}\n","import { Injectable } from '@angular/core';\n\nexport abstract class DateTimeProvider {\n abstract now(): number;\n abstract new(): Date;\n}\n\n@Injectable()\nexport class SystemDateTimeProvider extends DateTimeProvider {\n now(): number {\n return Date.now();\n }\n\n new(): Date {\n return new Date();\n }\n}\n","import { Injectable } from '@angular/core';\n\n/**\n * Additional options that can be passed to tryLogin.\n */\nexport class LoginOptions {\n /**\n * Is called, after a token has been received and\n * successfully validated.\n *\n * Deprecated: Use property ``events`` on OAuthService instead.\n */\n onTokenReceived?: (receivedTokens: ReceivedTokens) => void;\n\n /**\n * Hook, to validate the received tokens.\n *\n * Deprecated: Use property ``tokenValidationHandler`` on OAuthService instead.\n */\n validationHandler?: (receivedTokens: ReceivedTokens) => Promise<any>;\n\n /**\n * Called when tryLogin detects that the auth server\n * included an error message into the hash fragment.\n *\n * Deprecated: Use property ``events`` on OAuthService instead.\n */\n onLoginError?: (params: object) => void;\n\n /**\n * A custom hash fragment to be used instead of the\n * actual one. This is used for silent refreshes, to\n * pass the iframes hash fragment to this method, and\n * is also used by popup flows in the same manner.\n * This can be used with code flow, where is must be set\n * to a hash symbol followed by the querystring. The\n * question mark is optional, but may be present following\n * the hash symbol.\n */\n customHashFragment?: string;\n\n /**\n * Set this to true to disable the oauth2 state\n * check which is a best practice to avoid\n * security attacks.\n * As OIDC defines a nonce check that includes\n * this, this can be set to true when only doing\n * OIDC.\n */\n disableOAuth2StateCheck?: boolean;\n\n /**\n * Set this to true to disable the nonce\n * check which is used to avoid\n * replay attacks.\n * This flag should never be true in\n * production environments.\n */\n disableNonceCheck? = false;\n\n /**\n * Normally, you want to clear your hash fragment after\n * the lib read the token(s) so that they are not displayed\n * anymore in the url. If not, set this to true. For code flow\n * this controls removing query string values.\n */\n preventClearHashAfterLogin? = false;\n\n /**\n * Set this for code flow if you used a custom redirect Uri\n * when retrieving the code. This is used internally for silent\n * refresh and popup flows.\n */\n customRedirectUri?: string;\n}\n\n/**\n * Defines the logging interface the OAuthService uses\n * internally. Is compatible with the `console` object,\n * but you can provide your own implementation as well\n * through dependency injection.\n */\nexport abstract class OAuthLogger {\n abstract debug(message?: any, ...optionalParams: any[]): void;\n abstract info(message?: any, ...optionalParams: any[]): void;\n abstract log(message?: any, ...optionalParams: any[]): void;\n abstract warn(message?: any, ...optionalParams: any[]): void;\n abstract error(message?: any, ...optionalParams: any[]): void;\n}\n\n/**\n * Defines a simple storage that can be used for\n * storing the tokens at client side.\n * Is compatible to localStorage and sessionStorage,\n * but you can also create your own implementations.\n */\nexport abstract class OAuthStorage {\n abstract getItem(key: string): string | null;\n abstract removeItem(key: string): void;\n abstract setItem(key: string, data: string): void;\n}\n\n@Injectable()\nexport class MemoryStorage implements OAuthStorage {\n private data = new Map<string, string>();\n\n getItem(key: string): string {\n return this.data.get(key);\n }\n\n removeItem(key: string): void {\n this.data.delete(key);\n }\n\n setItem(key: string, data: string): void {\n this.data.set(key, data);\n }\n}\n\n/**\n * Represents the received tokens, the received state\n * and the parsed claims from the id-token.\n */\nexport class ReceivedTokens {\n idToken: string;\n accessToken: string;\n idClaims?: object;\n state?: string;\n}\n\n/**\n * Represents the parsed and validated id_token.\n */\nexport interface ParsedIdToken {\n idToken: string;\n idTokenClaims: object;\n idTokenHeader: object;\n idTokenClaimsJson: string;\n idTokenHeaderJson: string;\n idTokenExpiresAt: number;\n}\n\n/**\n * Represents the response from the token endpoint\n * http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint\n */\nexport interface TokenResponse {\n access_token: string;\n id_token: string;\n token_type: string;\n expires_in: number;\n refresh_token: string;\n scope: string;\n state?: string;\n}\n\n/**\n * Represents the response from the user info endpoint\n * http://openid.net/specs/openid-connect-core-1_0.html#UserInfo\n */\nexport interface UserInfo {\n sub: string;\n [key: string]: any;\n}\n\n/**\n * Represents an OpenID Connect discovery document\n */\nexport interface OidcDiscoveryDoc {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n token_endpoint_auth_methods_supported: string[];\n token_endpoint_auth_signing_alg_values_supported: string[];\n userinfo_endpoint: string;\n check_session_iframe: string;\n end_session_endpoint: string;\n jwks_uri: string;\n registration_endpoint: string;\n scopes_supported: string[];\n response_types_supported: string[];\n acr_values_supported: string[];\n response_modes_supported: string[];\n grant_types_supported: string[];\n subject_types_supported: string[];\n userinfo_signing_alg_values_supported: string[];\n userinfo_encryption_alg_values_supported: string[];\n userinfo_encryption_enc_values_supported: string[];\n id_token_signing_alg_values_supported: string[];\n id_token_encryption_alg_values_supported: string[];\n id_token_encryption_enc_values_supported: string[];\n request_object_signing_alg_values_supported: string[];\n display_values_supported: string[];\n claim_types_supported: string[];\n claims_supported: string[];\n claims_parameter_supported: boolean;\n service_documentation: string;\n ui_locales_supported: string[];\n revocation_endpoint: string;\n}\n","export type EventType =\n | 'discovery_document_loaded'\n | 'jwks_load_error'\n | 'invalid_nonce_in_state'\n | 'discovery_document_load_error'\n | 'discovery_document_validation_error'\n | 'user_profile_loaded'\n | 'user_profile_load_error'\n | 'token_received'\n | 'token_error'\n | 'code_error'\n | 'token_refreshed'\n | 'token_refresh_error'\n | 'silent_refresh_error'\n | 'silently_refreshed'\n | 'silent_refresh_timeout'\n | 'token_validation_error'\n | 'token_expires'\n | 'session_changed'\n | 'session_error'\n | 'session_terminated'\n | 'session_unchanged'\n | 'logout'\n | 'popup_closed'\n | 'popup_blocked'\n | 'token_revoke_error';\n\nexport abstract class OAuthEvent {\n constructor(readonly type: EventType) {}\n}\n\nexport class OAuthSuccessEvent extends OAuthEvent {\n constructor(type: EventType, readonly info: any = null) {\n super(type);\n }\n}\n\nexport class OAuthInfoEvent extends OAuthEvent {\n constructor(type: EventType, readonly info: any = null) {\n super(type);\n }\n}\n\nexport class OAuthErrorEvent extends OAuthEvent {\n constructor(\n type: EventType,\n readonly reason: object,\n readonly params: object = null\n ) {\n super(type);\n }\n}\n","// see: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_.22Unicode_Problem.22\nexport function b64DecodeUnicode(str) {\n const base64 = str.replace(/-/g, '+').replace(/_/g, '/');\n\n return decodeURIComponent(\n atob(base64)\n .split('')\n .map(function (c) {\n return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);\n })\n .join('')\n );\n}\n\nexport function base64UrlEncode(str): string {\n const base64 = btoa(str);\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '');\n}\n","export class AuthConfig {\n /**\n * The client's id as registered with the auth server\n */\n public clientId? = '';\n\n /**\n * The client's redirectUri as registered with the auth server\n */\n public redirectUri? = '';\n\n /**\n * An optional second redirectUri where the auth server\n * redirects the user to after logging out.\n */\n public postLogoutRedirectUri? = '';\n\n /**\n * Defines whether to use 'redirectUri' as a replacement\n * of 'postLogoutRedirectUri' if the latter is not set.\n */\n public redirectUriAsPostLogoutRedirectUriFallback? = true;\n\n /**\n * The auth server's endpoint that allows to log\n * the user in when using implicit flow.\n */\n public loginUrl? = '';\n\n /**\n * The requested scopes\n */\n public scope? = 'openid profile';\n\n public resource? = '';\n\n public rngUrl? = '';\n\n /**\n * Defines whether to use OpenId Connect during\n * implicit flow.\n */\n public oidc? = true;\n\n /**\n * Defines whether to request an access token during\n * implicit flow.\n */\n public requestAccessToken? = true;\n\n public options?: any = null;\n\n /**\n * The issuer's uri.\n */\n public issuer? = '';\n\n /**\n * The logout url.\n */\n public logoutUrl? = '';\n\n /**\n * Defines whether to clear the hash fragment after logging in.\n */\n public clearHashAfterLogin? = true;\n\n /**\n * Url of the token endpoint as defined by OpenId Connect and OAuth 2.\n */\n public tokenEndpoint?: string = null;\n\n /**\n * Url of the revocation endpoint as defined by OpenId Connect and OAuth 2.\n */\n public revocationEndpoint?: string = null;\n\n /**\n * Names of known parameters sent out in the TokenResponse. https://tools.ietf.org/html/rfc6749#section-5.1\n */\n public customTokenParameters?: string[] = [];\n\n /**\n * Url of the userinfo endpoint as defined by OpenId Connect.\n */\n public userinfoEndpoint?: string = null;\n\n public responseType? = '';\n\n /**\n * Defines whether additional debug information should\n * be shown at the console. Note that in certain browsers\n * the verbosity of the console needs to be explicitly set\n * to include Debug level messages.\n */\n public showDebugInformation? = false;\n\n /**\n * The redirect uri used when doing silent refresh.\n */\n public silentRefreshRedirectUri? = '';\n\n public silentRefreshMessagePrefix? = '';\n\n /**\n * Set this to true to display the iframe used for\n * silent refresh for debugging.\n */\n public silentRefreshShowIFrame? = false;\n\n /**\n * Timeout for silent refresh.\n * @internal\n * @deprecated use silentRefreshTimeout\n */\n public siletRefreshTimeout?: number = 1000 * 20;\n\n /**\n * Timeout for silent refresh.\n */\n public silentRefreshTimeout?: number = 1000 * 20;\n\n /**\n * Some auth servers don't allow using password flow\n * w/o a client secret while the standards do not\n * demand for it. In this case, you can set a password\n * here. As this password is exposed to the public\n * it does not bring additional security and is therefore\n * as good as using no password.\n */\n public dummyClientSecret?: string = '';\n\n /**\n * Defines whether https is required.\n * The default value is remoteOnly which only allows\n * http for localhost, while every other domains need\n * to be used with https.\n */\n public requireHttps?: boolean | 'remoteOnly' = 'remoteOnly';\n\n /**\n * Defines whether every url provided by the discovery\n * document has to start with the issuer's url.\n */\n public strictDiscoveryDocumentValidation? = true;\n\n /**\n * JSON Web Key Set (https://tools.ietf.org/html/rfc7517)\n * with keys used to validate received id_tokens.\n * This is taken out of the disovery document. Can be set manually too.\n */\n public jwks?: object = null;\n\n /**\n * Map with additional query parameter that are appended to\n * the request when initializing implicit flow.\n */\n public customQueryParams?: object = null;\n\n public silentRefreshIFrameName? = 'angular-oauth-oidc-silent-refresh-iframe';\n\n /**\n * Defines when the token_timeout event should be raised.\n * If you set this to the default value 0.75, the event\n * is triggered after 75% of the token's life time.\n */\n public timeoutFactor? = 0.75;\n\n /**\n * If true, the lib will try to check whether the user\n * is still logged in on a regular basis as described\n * in http://openid.net/specs/openid-connect-session-1_0.html#ChangeNotification\n */\n public sessionChecksEnabled? = false;\n\n /**\n * Interval in msec for checking the session\n * according to http://openid.net/specs/openid-connect-session-1_0.html#ChangeNotification\n */\n public sessionCheckIntervall? = 3 * 1000;\n\n /**\n * Url for the iframe used for session checks\n */\n public sessionCheckIFrameUrl?: string = null;\n\n /**\n * Name of the iframe to use for session checks\n */\n public sessionCheckIFrameName? = 'angular-oauth-oidc-check-session-iframe';\n\n /**\n * This property has been introduced to disable at_hash checks\n * and is indented for Identity Provider that does not deliver\n * an at_hash EVEN THOUGH its recommended by the OIDC specs.\n * Of course, when disabling these checks then we are bypassing\n * a security check which means we are more vulnerable.\n */\n public disableAtHashCheck? = false;\n\n /**\n * Defines wether to check the subject of a refreshed token after silent refresh.\n * Normally, it should be the same as before.\n */\n public skipSubjectCheck? = false;\n\n public useIdTokenHintForSilentRefresh? = false;\n\n /**\n * Defined whether to skip the validation of the issuer in the discovery document.\n * Normally, the discovey document's url starts with the url of the issuer.\n */\n public skipIssuerCheck? = false;\n\n /**\n * According to rfc6749 it is recommended (but not required) that the auth\n * server exposes the access_token's life time in seconds.\n * This is a fallback value for the case this value is not exposed.\n */\n public fallbackAccessTokenExpirationTimeInSec?: number;\n\n /**\n * final state sent to issuer is built as follows:\n * state = nonce + nonceStateSeparator + additional state\n * Default separator is ';' (encoded %3B).\n * In rare cases, this character might be forbidden or inconvenient to use by the issuer so it can be customized.\n */\n public nonceStateSeparator? = ';';\n\n /**\n * Set this to true to use HTTP BASIC auth for AJAX calls\n */\n public useHttpBasicAuth? = false;\n\n /**\n * The window of time (in seconds) to allow the current time to deviate when validating id_token's iat and exp values.\n */\n public clockSkewInSec?: number;\n\n /**\n * Decreases the Expiration time of tokens by this number of seconds\n */\n public decreaseExpirationBySec? = 0;\n\n /**\n * The interceptors waits this time span if there is no token\n */\n public waitForTokenInMsec? = 0;\n\n /**\n * Set this to true if you want to use silent refresh together with\n * code flow. As silent refresh is the only option for refreshing\n * with implicit flow, you don't need to explicitly turn it on in\n * this case.\n */\n public useSilentRefresh?;\n\n /**\n * Code Flow is by defauld used together with PKCI which is also higly recommented.\n * You can disbale it here by setting this flag to true.\n * https://tools.ietf.org/html/rfc7636#section-1.1\n */\n public disablePKCE? = false;\n\n /**\n * Set this to true to preserve the requested route including query parameters after code flow login.\n * This setting enables deep linking for the code flow.\n */\n public preserveRequestedRoute? = false;\n\n /**\n * Allows to disable the timer for the id_token used\n * for token refresh\n */\n public disableIdTokenTimer? = false;\n\n /**\n * Blocks other origins requesting a silent refresh\n */\n public checkOrigin? = false;\n\n constructor(json?: Partial<AuthConfig>) {\n if (json) {\n Object.assign(this, json);\n }\n }\n\n /**\n * This property allows you to override the method that is used to open the login url,\n * allowing a way for implementations to specify their own method of routing to new\n * urls.\n */\n public openUri?: (uri: string) => void = (uri) => {\n location.href = uri;\n };\n}\n","import { HttpParameterCodec } from '@angular/common/http';\n/**\n * This custom encoder allows charactes like +, % and / to be used in passwords\n */\nexport class WebHttpUrlEncodingCodec implements HttpParameterCodec {\n encodeKey(k: string): string {\n return encodeURIComponent(k);\n }\n\n encodeValue(v: string): string {\n return encodeURIComponent(v);\n }\n\n decodeKey(k: string): string {\n return decodeURIComponent(k);\n }\n\n decodeValue(v: string) {\n return decodeURIComponent(v);\n }\n}\n","import { base64UrlEncode } from '../base64-helper';\n\nexport interface ValidationParams {\n idToken: string;\n accessToken: string;\n idTokenHeader: object;\n idTokenClaims: object;\n jwks: object;\n loadKeys: () => Promise<object>;\n}\n\n/**\n * Interface for Handlers that are hooked in to\n * validate tokens.\n */\nexport abstract class ValidationHandler {\n /**\n * Validates the signature of an id_token.\n */\n public abstract validateSignature(\n validationParams: ValidationParams\n ): Promise<any>;\n\n /**\n * Validates the at_hash in an id_token against the received access_token.\n */\n public abstract validateAtHash(\n validationParams: ValidationParams\n ): Promise<boolean>;\n}\n\n/**\n * This abstract implementation of ValidationHandler already implements\n * the method validateAtHash. However, to make use of it,\n * you have to override the method calcHash.\n */\nexport abstract class AbstractValidationHandler implements ValidationHandler {\n /**\n * Validates the signature of an id_token.\n */\n abstract validateSignature(validationParams: ValidationParams): Promise<any>;\n\n /**\n * Validates the at_hash in an id_token against the received access_token.\n */\n async validateAtHash(params: ValidationParams): Promise<boolean> {\n const hashAlg = this.inferHashAlgorithm(params.idTokenHeader);\n\n const tokenHash = await this.calcHash(params.accessToken, hashAlg); // sha256(accessToken, { asString: true });\n\n const leftMostHalf = tokenHash.substr(0, tokenHash.length / 2);\n\n const atHash = base64UrlEncode(leftMostHalf);\n\n const claimsAtHash = params.idTokenClaims['at_hash'].replace(/=/g, '');\n\n if (atHash !== claimsAtHash) {\n console.error('exptected at_hash: ' + atHash);\n console.error('actual at_hash: ' + claimsAtHash);\n }\n\n return atHash === claimsAtHash;\n }\n\n /**\n * Infers the name of the hash algorithm to use\n * from the alg field of an id_token.\n *\n * @param jwtHeader the id_token's parsed header\n */\n protected inferHashAlgorithm(jwtHeader: object): string {\n const alg: string = jwtHeader['alg'];\n\n if (!alg.match(/^.S[0-9]{3}$/)) {\n throw new Error('Algorithm not supported: ' + alg);\n }\n\n return 'sha-' + alg.substr(2);\n }\n\n /**\n * Calculates the hash for the passed value by using\n * the passed hash algorithm.\n *\n * @param valueToHash\n * @param algorithm\n */\n protected abstract calcHash(\n valueToHash: string,\n algorithm: string\n ): Promise<string>;\n}\n","import { Injectable } from '@angular/core';\n\n@Injectable()\nexport class UrlHelperService {\n public getHashFragmentParams(customHashFragment?: string): object {\n let hash = customHashFragment || window.location.hash;\n\n hash = decodeURIComponent(hash);\n\n if (hash.indexOf('#') !== 0) {\n return {};\n }\n\n const questionMarkPosition = hash.indexOf('?');\n\n if (questionMarkPosition > -1) {\n hash = hash.substr(questionMarkPosition + 1);\n } else {\n hash = hash.substr(1);\n }\n\n return this.parseQueryString(hash);\n }\n\n public parseQueryString(queryString: string): object {\n const data = {};\n let pair, separatorIndex, escapedKey, escapedValue, key, value;\n\n if (queryString === null) {\n return data;\n }\n\n const pairs = queryString.split('&');\n\n for (let i = 0; i < pairs.length; i++) {\n pair = pairs[i];\n separatorIndex = pair.indexOf('=');\n\n if (separatorIndex === -1) {\n escapedKey = pair;\n escapedValue = null;\n } else {\n escapedKey = pair.substr(0, separatorIndex);\n escapedValue = pair.substr(separatorIndex + 1);\n }\n\n key = decodeURIComponent(escapedKey);\n value = decodeURIComponent(escapedValue);\n\n if (key.substr(0, 1) === '/') {\n key = key.substr(1);\n }\n\n data[key] = value;\n }\n\n return data;\n }\n}\n","// Credits: https://github.com/dchest/fast-sha256-js/tree/master/src\n// We add this lib directly b/c the published version of fast-sha256-js\n// is commonjs and hence leads to a warning about tree-shakability emitted\n// by the Angular CLI\n\n// SHA-256 (+ HMAC and PBKDF2) for JavaScript.\n//\n// Written in 2014-2016 by Dmitry Chestnykh.\n// Public domain, no warranty.\n//\n// Functions (accept and return Uint8Arrays):\n//\n// sha256(message) -> hash\n// sha256.hmac(key, message) -> mac\n// sha256.pbkdf2(password, salt, rounds, dkLen) -> dk\n//\n// Classes:\n//\n// new sha256.Hash()\n// new sha256.HMAC(key)\n//\nexport const digestLength = 32;\nexport const blockSize = 64;\n\n// SHA-256 constants\nconst K = new Uint32Array([\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,\n 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,\n 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,\n 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,\n 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,\n 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,\n 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,\n 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,\n 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,\n]);\n\nfunction hashBlocks(\n w: Int32Array,\n v: Int32Array,\n p: Uint8Array,\n pos: number,\n len: number\n): number {\n let a: number,\n b: number,\n c: number,\n d: number,\n e: number,\n f: number,\n g: number,\n h: number,\n u: number,\n i: number,\n j: number,\n t1: number,\n t2: number;\n while (len >= 64) {\n a = v[0];\n b = v[1];\n c = v[2];\n d = v[3];\n e = v[4];\n f = v[5];\n g = v[6];\n h = v[7];\n\n for (i = 0; i < 16; i++) {\n j = pos + i * 4;\n w[i] =\n ((p[j] & 0xff) << 24) |\n ((p[j + 1] & 0xff) << 16) |\n ((p[j + 2] & 0xff) << 8) |\n (p[j + 3] & 0xff);\n }\n\n for (i = 16; i < 64; i++) {\n u = w[i - 2];\n t1 =\n ((u >>> 17) | (u << (32 - 17))) ^\n ((u >>> 19) | (u << (32 - 19))) ^\n (u >>> 10);\n\n u = w[i - 15];\n t2 =\n ((u >>> 7) | (u << (32 - 7))) ^\n ((u >>> 18) | (u << (32 - 18))) ^\n (u >>> 3);\n\n w[i] = ((t1 + w[i - 7]) | 0) + ((t2 + w[i - 16]) | 0);\n }\n\n for (i = 0; i < 64; i++) {\n t1 =\n ((((((e >>> 6) | (e << (32 - 6))) ^\n ((e >>> 11) | (e << (32 - 11))) ^\n ((e >>> 25) | (e << (32 - 25)))) +\n ((e & f) ^ (~e & g))) |\n 0) +\n ((h + ((K[i] + w[i]) | 0)) | 0)) |\n 0;\n\n t2 =\n ((((a >>> 2) | (a << (32 - 2))) ^\n ((a >>> 13) | (a << (32 - 13))) ^\n ((a >>> 22) | (a << (32 - 22)))) +\n ((a & b) ^ (a & c) ^ (b & c))) |\n 0;\n\n h = g;\n g = f;\n f = e;\n e = (d + t1) | 0;\n d = c;\n c = b;\n b = a;\n a = (t1 + t2) | 0;\n }\n\n v[0] += a;\n v[1] += b;\n v[2] += c;\n v[3] += d;\n v[4] += e;\n v[5] += f;\n v[6] += g;\n v[7] += h;\n\n pos += 64;\n len -= 64;\n }\n return pos;\n}\n\n// Hash implements SHA256 hash algorithm.\nexport class Hash {\n digestLength: number = digestLength;\n blockSize: number = blockSize;\n\n // Note: Int32Array is used instead of Uint32Array for performance reasons.\n private state: Int32Array = new Int32Array(8); // hash state\n private temp: Int32Array = new Int32Array(64); // temporary state\n private buffer: Uint8Array = new Uint8Array(128); // buffer for data to hash\n private bufferLength = 0; // number of bytes in buffer\n private bytesHashed = 0; // number of total bytes hashed\n\n finished = false; // indicates whether the hash was finalized\n\n constructor() {\n this.reset();\n }\n\n // Resets hash state making it possible\n // to re-use this instance to hash other data.\n reset(): this {\n this.state[0] = 0x6a09e667;\n this.state[1] = 0xbb67ae85;\n this.state[2] = 0x3c6ef372;\n this.state[3] = 0xa54ff53a;\n this.state[4] = 0x510e527f;\n this.state[5] = 0x9b05688c;\n this.state[6] = 0x1f83d9ab;\n this.state[7] = 0x5be0cd19;\n this.bufferLength = 0;\n this.bytesHashed = 0;\n this.finished = false;\n return this;\n }\n\n // Cleans internal buffers and re-initializes hash state.\n clean() {\n for (let i = 0; i < this.buffer.length; i++) {\n this.buffer[i] = 0;\n }\n for (let i = 0; i < this.temp.length; i++) {\n this.temp[i] = 0;\n }\n this.reset();\n }\n\n // Updates hash state with the given data.\n //\n // Optionally, length of the data can be specified to hash\n // fewer bytes than data.length.\n //\n // Throws error when trying to update already finalized hash:\n // instance must be reset to use it again.\n update(data: Uint8Array, dataLength: number = data.length): this {\n if (this.finished) {\n throw new Error(\"SHA256: can't update because hash was finished.\");\n }\n let dataPos = 0;\n this.bytesHashed += dataLength;\n if (this.bufferLength > 0) {\n while (this.bufferLength < 64 && dataLength > 0) {\n this.buffer[this.bufferLength++] = data[dataPos++];\n dataLength--;\n }\n if (this.bufferLength === 64) {\n hashBlocks(this.temp, this.state, this.buffer, 0, 64);\n this.bufferLength = 0;\n }\n }\n if (dataLength >= 64) {\n dataPos = hashBlocks(this.temp, this.state, data, dataPos, dataLength);\n dataLength %= 64;\n }\n while (dataLength > 0) {\n this.buffer[this.bufferLength++] = data[dataPos++];\n dataLength--;\n }\n return this;\n }\n\n // Finalizes hash state and puts hash into out.\n //\n // If hash was already finalized, puts the same value.\n finish(out: Uint8Array): this {\n if (!this.finished) {\n const bytesHashed = this.bytesHashed;\n const left = this.bufferLength;\n const bitLenHi = (bytesHashed / 0x20000000) | 0;\n const bitLenLo = bytesHashed << 3;\n const padLength = bytesHashed % 64 < 56 ? 64 : 128;\n\n this.buffer[left] = 0x80;\n for (let i = left + 1; i < padLength - 8; i++) {\n this.buffer[i] = 0;\n }\n this.buffer[padLength - 8] = (bitLenHi >>> 24) & 0xff;\n this.buffer[padLength - 7] = (bitLenHi >>> 16) & 0xff;\n this.buffer[padLength - 6] = (bitLenHi >>> 8) & 0xff;\n this.buffer[padLength - 5] = (bitLenHi >>> 0) & 0xff;\n this.buffer[padLength - 4] = (bitLenLo >>> 24) & 0xff;\n this.buffer[padLength - 3] = (bitLenLo >>> 16) & 0xff;\n this.buffer[padLength - 2] = (bitLenLo >>> 8) & 0xff;\n this.buffer[padLength - 1] = (bitLenLo >>> 0) & 0xff;\n\n hashBlocks(this.temp, this.state, this.buffer, 0, padLength);\n\n this.finished = true;\n }\n\n for (let i = 0; i < 8; i++) {\n out[i * 4 + 0] = (this.state[i] >>> 24) & 0xff;\n out[i * 4 + 1] = (this.state[i] >>> 16) & 0xff;\n out[i * 4 + 2] = (this.state[i] >>> 8) & 0xff;\n out[i * 4 + 3] = (this.state[i] >>> 0) & 0xff;\n }\n\n return this;\n }\n\n // Returns the final hash digest.\n digest(): Uint8Array {\n const out = new Uint8Array(this.digestLength);\n this.finish(out);\n return out;\n }\n\n // Internal function for use in HMAC for optimization.\n _saveState(out: Uint32Array) {\n for (let i = 0; i < this.state.length; i++) {\n out[i] = this.state[i];\n }\n }\n\n // Internal function for use in HMAC for optimization.\n _restoreState(from: Uint32Array, bytesHashed: number) {\n for (let i = 0; i < this.state.length; i++) {\n this.state[i] = from[i];\n }\n this.bytesHashed = bytesHashed;\n this.finished = false;\n this.bufferLength = 0;\n }\n}\n\n// HMAC implements HMAC-SHA256 message authentication algorithm.\nexport class HMAC {\n private inner: Hash = new Hash();\n private outer: Hash = new Hash();\n\n blockSize: number = this.inner.blockSize;\n digestLength: number = this.inner.digestLength;\n\n // Copies of hash states after keying.\n // Need for quick reset without hashing they key again.\n private istate: Uint32Array;\n private ostate: Uint32Array;\n\n constructor(key: Uint8Array) {\n const pad = new Uint8Array(this.blockSize);\n if (key.length > this.blockSize) {\n new Hash().update(key).finish(pad).clean();\n } else {\n for (let i = 0; i < key.length; i++) {\n pad[i] = key[i];\n }\n }\n for (let i = 0; i < pad.length; i++) {\n pad[i] ^= 0x36;\n }\n this.inner.update(pad);\n\n for (let i = 0; i < pad.length; i++) {\n pad[i] ^= 0x36 ^ 0x5c;\n }\n this.outer.update(pad);\n\n this.istate = new Uint32Array(8);\n this.ostate = new Uint32Array(8);\n\n this.inner._saveState(this.istate);\n this.outer._saveState(this.ostate);\n\n for (let i = 0; i < pad.length; i++) {\n pad[i] = 0;\n }\n }\n\n // Returns HMAC state to the state initialized with key\n // to make it possible to run HMAC over the other data with the same\n // key without creating a new instance.\n reset(): this {\n this.inner._restoreState(this.istate, this.inner.blockSize);\n this.outer._restoreState(this.ostate, this.outer.blockSize);\n return this;\n }\n\n // Cleans HMAC state.\n clean() {\n for (let i = 0; i < this.istate.length; i++) {\n this.ostate[i] = this.istate[i] = 0;\n }\n this.inner.clean();\n this.outer.clean();\n }\n\n // Updates state with provided data.\n update(data: Uint8Array): this {\n this.inner.update(data);\n return this;\n }\n\n // Finalizes HMAC and puts the result in out.\n finish(out: Uint8Array): this {\n if (this.outer.finished) {\n this.outer.finish(out);\n } else {\n this.inner.finish(out);\n this.outer.update(out, this.digestLength).finish(out);\n }\n return this;\n }\n\n // Returns message authentication code.\n digest(): Uint8Array {\n const out = new Uint8Array(this.digestLength);\n this.finish(out);\n return out;\n }\n}\n\n// Returns SHA256 hash of data.\nexport function hash(data: Uint8Array): Uint8Array {\n const h = new Hash().update(data);\n const digest = h.digest();\n h.clean();\n return digest;\n}\n\n// Function hash is both available as module.hash and as default export.\nexport default hash;\n\n// Returns HMAC-SHA256 of data under the key.\nexport function hmac(key: Uint8Array, data: Uint8Array) {\n const h = new HMAC(key).update(data);\n const digest = h.digest();\n h.clean();\n return digest;\n}\n\n// Fills hkdf buffer like this:\n// T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)\nfunction fillBuffer(\n buffer: Uint8Array,\n hmac: HMAC,\n info: Uint8Array | undefined,\n counter: Uint8Array\n) {\n // Counter is a byte value: check if it overflowed.\n const num = counter[0];\n\n if (num === 0) {\n throw new Error('hkdf: cannot expand more');\n }\n\n // Prepare HMAC instance for new data with old key.\n hmac.reset();\n\n // Hash in previous output if it was generated\n // (i.e. counter is greater than 1).\n if (num > 1) {\n hmac.update(buffer);\n }\n\n // Hash in info if it exists.\n if (info) {\n hmac.update(info);\n }\n\n // Hash in the counter.\n hmac.update(counter);\n\n // Output result to buffer and clean HMAC instance.\n hmac.finish(buffer);\n\n // Increment counter inside typed array, this works properly.\n counter[0]++;\n}\n\nconst hkdfSalt = new Uint8Array(digestLength); // Filled with zeroes.\nexport function hkdf(\n key: Uint8Array,\n salt: Uint8Array = hkdfSalt,\n info?: Uint8Array,\n length = 32\n) {\n const counter = new Uint8Array([1]);\n\n // HKDF-Extract uses salt as HMAC key, and key as data.\n const okm = hmac(salt, key);\n\n // Initialize HMAC for expanding with extracted key.\n // Ensure no collisions with `hmac` function.\n const hmac_ = new HMAC(okm);\n\n // Allocate buffer.\n const buffer = new Uint8Array(hmac_.digestLength);\n let bufpos = buffer.length;\n\n const out = new Uint8Array(length);\n for (let i = 0; i < length; i++) {\n if (bufpos === buffer.length) {\n fillBuffer(buffer, hmac_, info, counter);\n bufpos = 0;\n }\n out[i] = buffer[bufpos++];\n }\n\n hmac_.clean();\n buffer.fill(0);\n counter.fill(0);\n return out;\n}\n\n// Derives a key from password and salt using PBKDF2-HMAC-SHA256\n// with the given number of iterations.\n//\n// The number of bytes returned is equal to dkLen.\n//\n// (For better security, avoid dkLen greater than hash length - 32 bytes).\nexport function pbkdf2(\n password: Uint8Array,\n salt: Uint8Array,\n iterations: number,\n dkLen: number\n) {\n const prf = new HMAC(password);\n const len = prf.digestLength;\n const ctr = new Uint8Array(4);\n const t = new Uint8Array(len);\n const u = new Uint8Array(len);\n const dk = new Uint8Array(dkLen);\n\n for (let i = 0; i * len < dkLen; i++) {\n const c = i + 1;\n ctr[0] = (c >>> 24) & 0xff;\n ctr[1] = (c >>> 16) & 0xff;\n ctr[2] = (c >>> 8) & 0xff;\n ctr[3] = (c >>> 0) & 0xff;\n prf.reset();\n prf.update(salt);\n prf.update(ctr);\n prf.finish(u);\n for (let j = 0; j < len; j++) {\n t[j] = u[j];\n }\n for (let j = 2; j <= iterations; j++) {\n prf.reset();\n prf.update(u).finish(u);\n for (let k = 0; k < len; k++) {\n t[k] ^= u[k];\n }\n }\n for (let j = 0; j < len && i * len + j < dkLen; j++) {\n dk[i * len + j] = t[j];\n }\n }\n for (let i = 0; i < len; i++) {\n t[i] = u[i] = 0;\n }\n for (let i = 0; i < 4; i++) {\n ctr[i] = 0;\n }\n prf.clean();\n return dk;\n}\n","import { Injectable } from '@angular/core';\n\nimport { factory } from './js-sha256';\n// const sha256 = factory();\n\nimport fsha256 from './fast-sha256js';\n\n/**\n * Abstraction for crypto algorithms\n */\nexport abstract class HashHandler {\n abstract calcHash(valueToHash: string, algorithm: string): Promise<string>;\n}\n\nfunction decodeUTF8(s) {\n if (typeof s !== 'string') throw new TypeError('expected string');\n const d = s,\n b = new Uint8Array(d.length);\n for (let i = 0; i < d.length; i++) b[i] = d.charCodeAt(i);\n return b;\n}\n\nfunction encodeUTF8(arr) {\n const s = [];\n for (let i = 0; i < arr.length; i++) s.push(String.fromCharCode(arr[i]));\n return s.join('');\n}\n\n@Injectable()\nexport class DefaultHashHandler implements HashHandler {\n async calcHash(valueToHash: string, algorithm: string): Promise<string> {\n // const encoder = new TextEncoder();\n // const hashArray = await window.crypto.subtle.digest(algorithm, data);\n // const data = encoder.encode(valueToHash);\n\n // const fhash = fsha256(valueToHash);\n\n const candHash = encodeUTF8(fsha256(decodeUTF8(valueToHash)));\n\n // const hashArray = (sha256 as any).array(valueToHash);\n // // const hashString = this.toHashString(hashArray);\n // const hashString = this.toHashString2(hashArray);\n\n // console.debug('hash orig - cand', candHash, hashString);\n // alert(1);\n\n return candHash;\n }\n\n toHashString2(byteArray: number[]) {\n let result = '';\n for (const e of byteArray) {\n result += String.fromCharCode(e);\n }\n return result;\n }\n\n toHashString(buffer: ArrayBuffer) {\n const byteArray = new Uint8Array(buffer);\n let result = '';\n for (const e of byteArray) {\n result += String.fromCharCode(e);\n }\n return result;\n }\n\n // hexString(buffer) {\n // const byteArray = new Uint8Array(buffer);\n // const hexCodes = [...byteArray].map(value => {\n // const hexCode = value.toString(16);\n // const paddedHexCode = hexCode.padStart(2, '0');\n // return paddedHexCode;\n // });\n\n // return hexCodes.join('');\n // }\n\n // toHashString(hexString: string) {\n // let result = '';\n // for (let i = 0; i < hexString.length; i += 2) {\n // let hexDigit = hexString.charAt(i) + hexString.charAt(i + 1);\n // let num = parseInt(hexDigit, 16);\n // result += String.fromCharCode(num);\n // }\n // return result;\n // }\n}\n","import { Injectable, NgZone, Optional, OnDestroy, Inject } from '@angular/core';\nimport {\n HttpClient,\n HttpHeaders,\n HttpParams,\n HttpErrorResponse,\n} from '@angular/common/http';\nimport {\n Observable,\n Subject,\n Subscription,\n of,\n race,\n from,\n combineLatest,\n throwError,\n} from 'rxjs';\nimport {\n filter,\n delay,\n first,\n tap,\n map,\n switchMap,\n debounceTime,\n catchError,\n} from 'rxjs/operators';\nimport { DOCUMENT } from '@angular/common';\nimport { DateTimeProvider } from './date-time-provider';\n\nimport {\n ValidationHandler,\n ValidationParams,\n} from './token-validation/validation-handler';\nimport { UrlHelperService } from './url-helper.service';\nimport {\n OAuthEvent,\n OAuthInfoEvent,\n OAuthErrorEvent,\n OAuthSuccessEvent,\n} from './events';\nimport {\n OAuthLogger,\n OAuthStorage,\n LoginOptions,\n ParsedIdToken,\n OidcDiscoveryDoc,\n TokenResponse,\n} from './types';\nimport { b64DecodeUnicode, base64UrlEncode } from './base64-helper';\nimport { AuthConfig } from './auth.config';\nimport { WebHttpUrlEncodingCodec } from './encoder';\nimport { HashHandler } from './token-validation/hash-handler';\n\n/**\n * Service for logging in and logging out with\n * OIDC and OAuth2. Supports implicit flow and\n * password flow.\n */\n@Injectable()\nexport class OAuthService extends AuthConfig implements OnDestroy {\n // Extending AuthConfig ist just for LEGACY reasons\n // to not break existing code.\n\n /**\n * The ValidationHandler used to validate received\n * id_tokens.\n */\n public tokenValidationHandler: ValidationHandler;\n\n /**\n * @internal\n * Deprecated: use property events instead\n */\n public discoveryDocumentLoaded = false;\n\n /**\n * @internal\n * Deprecated: use property events instead\n */\n public discoveryDocumentLoaded$: Observable<OidcDiscoveryDoc>;\n\n /**\n * Informs about events, like token_received or token_expires.\n * See the string enum EventType for a full list of event types.\n */\n public events: Observable<OAuthEvent>;\n\n /**\n * The received (passed around) state, when logging\n * in with implicit flow.\n */\n public state? = '';\n\n protected eventsSubject: Subject<OAuthEvent> = new Subject<OAuthEvent>();\n protected discoveryDocumentLoadedSubject: Subject<OidcDiscoveryDoc> =\n new Subject<OidcDiscoveryDoc>();\n protected silentRefreshPostMessageEventListener: EventListener;\n protected grantTypesSupported: Array<string> = [];\n protected _storage: OAuthStorage;\n protected accessTokenTimeoutSubscription: Subscription;\n protected idTokenTimeoutSubscription: Subscription;\n protected tokenReceivedSubscription: Subscription;\n protected automaticRefreshSubscription: Subscription;\n protected sessionCheckEventListener: EventListener;\n protected jwksUri: string;\n protected sessionCheckTimer: any;\n protected silentRefreshSubject: string;\n protected inImplicitFlow = false;\n\n protected saveNoncesInLocalStorage = false;\n private document: Document;\n\n constructor(\n protected ngZone: NgZone,\n protected http: HttpClient,\n @Optional() storage: OAuthStorage,\n @Optional() tokenValidationHandler: ValidationHandler,\n @Optional() protected config: AuthConfig,\n protected urlHelper: UrlHelperService,\n protected logger: OAuthLogger,\n @Optional() protected crypto: HashHandler,\n @Inject(DOCUMENT) document: Document,\n protected dateTimeService: DateTimeProvider\n ) {\n super();\n\n this.debug('angular-oauth2-oidc v10');\n\n // See https://github.com/manfredsteyer/angular-oauth2-oidc/issues/773 for why this is needed\n this.document = document;\n\n if (!config) {\n config = {};\n }\n\n this.discoveryDocumentLoaded$ =\n this.discoveryDocumentLoadedSubject.asObservable();\n this.events = this.eventsSubject.asObservable();\n\n if (tokenValidationHandler) {\n this.tokenValidationHandler = tokenValidationHandler;\n }\n\n if (config) {\n this.configure(config);\n }\n\n try {\n if (storage) {\n this.setStorage(storage);\n } else if (typeof sessionStorage !== 'undefined') {\n this.setStorage(sessionStorage);\n }\n } catch (e) {\n console.error(\n 'No OAuthStorage provided and cannot access default (sessionStorage).' +\n 'Consider providing a custom OAuthStorage implementation in your module.',\n e\n );\n }\n\n // in IE, sessionStorage does not always survive a redirect\n if (this.checkLocalStorageAccessable()) {\n const ua = window?.navigator?.userAgent;\n const msie = ua?.includes('MSIE ') || ua?.includes('Trident');\n\n if (msie) {\n this.saveNoncesInLocalStorage = true;\n }\n }\n\n this.setupRefreshTimer();\n }\n\n private checkLocalStorageAccessable() {\n if (typeof window === 'undefined') return false;\n\n const test = 'test';\n try {\n if (typeof window['localStorage'] === 'undefined') return false;\n\n localStorage.setItem(test, test);\n localStorage.removeItem(test);\n return true;\n } catch (e) {\n return false;\n }\n }\n\n /**\n * Use this method to configure the service\n * @param config the configuration\n */\n public configure(config: AuthConfig): void {\n // For the sake of downward compatibility with\n // original configuration API\n Object.assign(this, new AuthConfig(), config);\n\n this.config = Object.assign({} as AuthConfig, new AuthConfig(), config);\n\n if (this.sessionChecksEnabled) {\n this.setupSessionCheck();\n }\n\n this.configChanged();\n }\n\n protected configChanged(): void {\n this.setupRefreshTimer();\n }\n\n public restartSessionChecksIfStillLoggedIn(): void {\n if (this.hasValidIdToken()) {\n this.initSessionCheck();\n }\n }\n\n protected restartRefreshTimerIfStillLoggedIn(): void {\n this.setupExpirationTimers();\n }\n\n protected setupSessionCheck(): void {\n this.events\n .pipe(filter((e) => e.type === 'token_received'))\n .subscribe(() => {\n this.initSessionCheck();\n });\n }\n\n /**\n * Will setup up silent refreshing for when the token is\n * about to expire. When the user is logged out via this.logOut method, the\n * silent refreshing will pause and not refresh the tokens until the user is\n * logged back in via receiving a new token.\n * @param params Additional parameter to pass\n * @param listenTo Setup automatic refresh of a specific token type\n */\n public setupAutomaticSilentRefresh(\n params: object = {},\n listenTo?: 'access_token' | 'id_token' | 'any',\n noPrompt = true\n ): void {\n let shouldRunSilentRefresh = true;\n this.clearAutomaticRefreshTimer();\n this.automaticRefreshSubscription = this.events\n .pipe(\n tap((e) => {\n if (e.type === 'token_received') {\n shouldRunSilentRefresh = true;\n } else if (e.type === 'logout') {\n shouldRunSilentRefresh = false;\n }\n }),\n filter(\n (e: OAuthInfoEvent) =>\n e.type === 'token_expires' &&\n (listenTo == null || listenTo === 'any' || e.info === listenTo)\n ),\n debounceTime(1000)\n )\n .subscribe(() => {\n if (shouldRunSilentRefresh) {\n // this.silentRefresh(params, noPrompt).catch(_ => {\n this.refreshInternal(params, noPrompt).catch(() => {\n this.debug('Automatic silent refresh did not work');\n });\n }\n });\n\n this.restartRefreshTimerIfStillLoggedIn();\n }\n\n protected refreshInternal(\n params,\n noPrompt\n ): Promise<TokenResponse | OAuthEvent> {\n if (!this.useSilentRefresh && this.responseType === 'code') {\n return this.refreshToken();\n } else {\n return this.silentRefresh(params, noPrompt);\n }\n }\n\n /**\n * Convenience method that first calls `loadDiscoveryDocument(...)` and\n * directly chains using the `then(...)` part of the promise to call\n * the `tryLogin(...)` method.\n *\n * @param options LoginOptions to pass through to `tryLogin(...)`\n */\n public loadDiscoveryDocumentAndTryLogin(\n options: LoginOptions = null\n ): Promise<boolean> {\n return this.loadDiscoveryDocument().then(() => {\n return this.tryLogin(options);\n });\n }\n\n /**\n * Convenience method that first calls `loadDiscoveryDocumentAndTryLogin(...)`\n * and if then chains to `initLoginFlow()`, but only if there is no valid\n * IdToken or no valid AccessToken.\n *\n * @param options LoginOptions to pass through to `tryLogin(...)`\n */\n public loadDiscoveryDocumentAndLogin(\n options: LoginOptions & { state?: string } = null\n ): Promise<boolean> {\n options = options || {};\n return this.loadDiscoveryDocumentAndTryLogin(options).then(() => {\n if (!this.hasValidIdToken() || !this.hasValidAccessToken()) {\n const state = typeof options.state === 'string' ? options.state : '';\n this.initLoginFlow(state);\n return false;\n } else {\n return true;\n }\n });\n }\n\n protected debug(...args): void {\n if (this.showDebugInformation) {\n this.logger.debug(...args);\n }\n }\n\n protected validateUrlFromDiscoveryDocument(url: string): string[] {\n const errors: string[] = [];\n const httpsCheck = this.validateUrlForHttps(url);\n const issuerCheck = this.validateUrlAgainstIssuer(url);\n\n if (!httpsCheck) {\n errors.push(\n 'https for all urls required. Also for urls received by discovery.'\n );\n }\n\n if (!issuerCheck) {\n errors.push(\n 'Every url in discovery document has to start with the issuer url.' +\n 'Also see property strictDiscoveryDocumentValidation.'\n );\n }\n\n return errors;\n }\n\n protected validateUrlForHttps(url: string): boolean {\n if (!url) {\n return true;\n }\n\n const lcUrl = url.toLowerCase();\n\n if (this.requireHttps === false) {\n return true;\n }\n\n if (\n (lcUrl.match(/^ht