diff --git a/docs/oidc-client-ts.api.md b/docs/oidc-client-ts.api.md index 1c27a7ae3..7f742e209 100644 --- a/docs/oidc-client-ts.api.md +++ b/docs/oidc-client-ts.api.md @@ -17,6 +17,10 @@ export class AccessTokenEvents { addAccessTokenExpiring(cb: AccessTokenCallback): void; // (undocumented) load(container: User): void; + // Warning: (ae-forgotten-export) The symbol "Logger" needs to be exported by the entry point index.d.ts + // + // (undocumented) + protected _logger: Logger; // (undocumented) removeAccessTokenExpired(cb: AccessTokenCallback): void; // (undocumented) @@ -102,36 +106,28 @@ export class InMemoryWebStorage implements Storage { setItem(key: string, value: string): void; } -// @public (undocumented) +// @public export class Log { // (undocumented) static get DEBUG(): number; // (undocumented) - static debug(...args: any[]): void; - // (undocumented) static get ERROR(): number; // (undocumented) - static error(...args: any[]): void; - // (undocumented) static get INFO(): number; // (undocumented) - static info(...args: any[]): void; - // (undocumented) static get level(): number; static set level(value: number); - // Warning: (ae-forgotten-export) The symbol "Logger" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "ILogger" needs to be exported by the entry point index.d.ts // // (undocumented) - static get logger(): Logger; - static set logger(value: Logger); + static get logger(): ILogger; + static set logger(value: ILogger); // (undocumented) static get NONE(): number; // (undocumented) static reset(): void; // (undocumented) static get WARN(): number; - // (undocumented) - static warn(...args: any[]): void; } // @public (undocumented) @@ -180,6 +176,8 @@ export class OidcClient { // (undocumented) createSignoutRequest({ state, post_logout_redirect_uri, extraQueryParams, request_type }?: CreateSignoutRequestArgs): Promise; // (undocumented) + protected readonly _logger: Logger; + // (undocumented) readonly metadataService: MetadataService; // (undocumented) processSigninResponse(url?: string): Promise; @@ -375,6 +373,8 @@ export class UserManager { // (undocumented) protected _loadUser(): Promise; // (undocumented) + protected readonly _logger: Logger; + // (undocumented) get metadataService(): MetadataService; // Warning: (ae-forgotten-export) The symbol "PopupNavigator" needs to be exported by the entry point index.d.ts // @@ -565,10 +565,10 @@ export class WebStorageStateStore implements StateStore { // Warnings were encountered during analysis: // -// src/OidcClient.ts:113:88 - (ae-forgotten-export) The symbol "SigninState" needs to be exported by the entry point index.d.ts -// src/OidcClient.ts:113:108 - (ae-forgotten-export) The symbol "SigninResponse" needs to be exported by the entry point index.d.ts -// src/OidcClient.ts:181:89 - (ae-forgotten-export) The symbol "State" needs to be exported by the entry point index.d.ts -// src/OidcClient.ts:181:115 - (ae-forgotten-export) The symbol "SignoutResponse" needs to be exported by the entry point index.d.ts +// src/OidcClient.ts:116:88 - (ae-forgotten-export) The symbol "SigninState" needs to be exported by the entry point index.d.ts +// src/OidcClient.ts:116:108 - (ae-forgotten-export) The symbol "SigninResponse" needs to be exported by the entry point index.d.ts +// src/OidcClient.ts:184:89 - (ae-forgotten-export) The symbol "State" needs to be exported by the entry point index.d.ts +// src/OidcClient.ts:184:115 - (ae-forgotten-export) The symbol "SignoutResponse" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/AccessTokenEvents.ts b/src/AccessTokenEvents.ts index 5207cc12a..5db491576 100644 --- a/src/AccessTokenEvents.ts +++ b/src/AccessTokenEvents.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log, Timer } from "./utils"; +import { Logger, Timer } from "./utils"; import type { User } from "./User"; export type AccessTokenCallback = (...ev: any[]) => void; @@ -10,11 +10,15 @@ export type AccessTokenCallback = (...ev: any[]) => void; * @public */ export class AccessTokenEvents { + protected _logger: Logger; + private _expiringNotificationTimeInSeconds: number private _expiringTimer: Timer private _expiredTimer: Timer public constructor({ expiringNotificationTimeInSeconds }: { expiringNotificationTimeInSeconds: number }) { + this._logger = new Logger("AccessTokenEvents"); + this._expiringNotificationTimeInSeconds = expiringNotificationTimeInSeconds; this._expiringTimer = new Timer("Access token expiring"); this._expiredTimer = new Timer("Access token expired"); @@ -24,7 +28,7 @@ export class AccessTokenEvents { // only register events if there's an access token and it has an expiration if (container.access_token && container.expires_in !== undefined) { const duration = container.expires_in; - Log.debug("AccessTokenEvents.load: access token present, remaining duration:", duration); + this._logger.debug("load: access token present, remaining duration:", duration); if (duration > 0) { // only register expiring if we still have time @@ -33,17 +37,17 @@ export class AccessTokenEvents { expiring = 1; } - Log.debug("AccessTokenEvents.load: registering expiring timer in:", expiring); + this._logger.debug("load: registering expiring timer in:", expiring); this._expiringTimer.init(expiring); } else { - Log.debug("AccessTokenEvents.load: canceling existing expiring timer becase we're past expiration."); + this._logger.debug("load: canceling existing expiring timer because we're past expiration."); this._expiringTimer.cancel(); } // if it's negative, it will still fire const expired = duration + 1; - Log.debug("AccessTokenEvents.load: registering expired timer in:", expired); + this._logger.debug("load: registering expired timer in:", expired); this._expiredTimer.init(expired); } else { @@ -53,7 +57,7 @@ export class AccessTokenEvents { } public unload(): void { - Log.debug("AccessTokenEvents.unload: canceling existing access token timers"); + this._logger.debug("unload: canceling existing access token timers"); this._expiringTimer.cancel(); this._expiredTimer.cancel(); } diff --git a/src/CheckSessionIFrame.ts b/src/CheckSessionIFrame.ts index 7ad49d097..3ca075437 100644 --- a/src/CheckSessionIFrame.ts +++ b/src/CheckSessionIFrame.ts @@ -1,12 +1,13 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log } from "./utils"; +import { Logger } from "./utils"; /** * @public */ export class CheckSessionIFrame { + private readonly _logger: Logger; private _frame_origin: string; private _frame: HTMLIFrameElement; private _timer: number | null = null @@ -19,6 +20,8 @@ export class CheckSessionIFrame { private _intervalInSeconds: number, private _stopOnError: boolean ) { + this._logger = new Logger("CheckSessionIFrame"); + const idx = url.indexOf("/", url.indexOf("//") + 2); this._frame_origin = url.substr(0, idx); @@ -50,18 +53,18 @@ export class CheckSessionIFrame { e.source === this._frame.contentWindow ) { if (e.data === "error") { - Log.error("CheckSessionIFrame: error message from check session op iframe"); + this._logger.error("error message from check session op iframe"); if (this._stopOnError) { this.stop(); } } else if (e.data === "changed") { - Log.debug("CheckSessionIFrame: changed message from check session op iframe"); + this._logger.debug("changed message from check session op iframe"); this.stop(); void this._callback(); } else { - Log.debug("CheckSessionIFrame: " + e.data + " message from check session op iframe"); + this._logger.debug(e.data + " message from check session op iframe"); } } } @@ -71,7 +74,7 @@ export class CheckSessionIFrame { return; } - Log.debug("CheckSessionIFrame.start"); + this._logger.debug("start"); this.stop(); @@ -96,7 +99,7 @@ export class CheckSessionIFrame { this._session_state = null; if (this._timer) { - Log.debug("CheckSessionIFrame.stop"); + this._logger.debug("stop"); window.clearInterval(this._timer); this._timer = null; diff --git a/src/ErrorResponse.ts b/src/ErrorResponse.ts index b849a5e0b..bd7c7d397 100644 --- a/src/ErrorResponse.ts +++ b/src/ErrorResponse.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log } from "./utils"; +import { Logger } from "./utils"; export class ErrorResponse extends Error { public readonly name: string; @@ -16,13 +16,13 @@ export class ErrorResponse extends Error { public constructor(args: { error?: string; error_description?: string; error_uri?: string; state?: any; session_state?: string; }) { + super(args.error_description || args.error); + if (!args.error) { - Log.error("No error passed to ErrorResponse"); - throw new Error("error"); + Logger.error("ErrorResponse", "No error passed"); + throw new Error("No error passed"); } - super(args.error_description || args.error); - this.name = "ErrorResponse"; this.error = args.error; diff --git a/src/InMemoryWebStorage.ts b/src/InMemoryWebStorage.ts index 023b580ee..8ad7cb2d8 100644 --- a/src/InMemoryWebStorage.ts +++ b/src/InMemoryWebStorage.ts @@ -1,35 +1,37 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log } from "./utils"; +import { Logger } from "./utils"; /** * @public */ export class InMemoryWebStorage implements Storage { + private readonly _logger: Logger; private _data: Record; public constructor() { + this._logger = new Logger("InMemoryWebStorage"); this._data = {}; } public clear(): void { - Log.debug("InMemoryWebStorage.clear"); + this._logger.debug("clear"); this._data = {}; } public getItem(key: string): string { - Log.debug("InMemoryWebStorage.getItem", key); + this._logger.debug("getItem", key); return this._data[key]; } public setItem(key: string, value: string): void { - Log.debug("InMemoryWebStorage.setItem", key); + this._logger.debug("setItem", key); this._data[key] = value; } public removeItem(key: string): void { - Log.debug("InMemoryWebStorage.removeItem", key); + this._logger.debug("removeItem", key); delete this._data[key]; } diff --git a/src/JsonService.ts b/src/JsonService.ts index f3c9b2e47..f407092ec 100644 --- a/src/JsonService.ts +++ b/src/JsonService.ts @@ -1,11 +1,13 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log } from "./utils"; +import { Logger } from "./utils"; export type JwtHandler = (text: string) => Promise; export class JsonService { + private readonly _logger: Logger; + private _contentTypes: string[]; private _jwtHandler: JwtHandler | null; @@ -13,6 +15,8 @@ export class JsonService { additionalContentTypes: string[] = [], jwtHandler: JwtHandler | null = null ) { + this._logger = new Logger("JsonService"); + this._contentTypes = additionalContentTypes.slice(); this._contentTypes.push("application/json"); if (jwtHandler) { @@ -24,27 +28,27 @@ export class JsonService { public async getJson(url: string, token?: string): Promise { if (!url) { - Log.error("JsonService.getJson: No url passed"); + this._logger.error("getJson: No url passed"); throw new Error("url"); } const headers: HeadersInit = {}; if (token) { - Log.debug("JsonService.getJson: token passed, setting Authorization header"); + this._logger.debug("getJson: token passed, setting Authorization header"); headers["Authorization"] = "Bearer " + token; } let response: Response; try { - Log.debug("JsonService.getJson, url: ", url); + this._logger.debug("getJson, url:", url); response = await fetch(url, { method: "GET", headers }); } catch (err) { - Log.error("JsonService.getJson: network error"); + this._logger.error("getJson: network error"); throw new Error("Network Error"); } - Log.debug("JsonService.getJson: HTTP response received, status", response.status); + this._logger.debug("getJson: HTTP response received, status", response.status); if (response.status === 200) { const contentType = response.headers.get("Content-Type"); if (contentType) { @@ -60,7 +64,7 @@ export class JsonService { return json; } catch (err) { - Log.error("JsonService.getJson: Error parsing JSON response", err instanceof Error ? err.message : err); + this._logger.error("getJson: Error parsing JSON response", err instanceof Error ? err.message : err); throw err; } } @@ -74,7 +78,7 @@ export class JsonService { public async postForm(url: string, payload: any, basicAuth?: string): Promise { if (!url) { - Log.error("JsonService.postForm: No url passed"); + this._logger.error("postForm: No url passed"); throw new Error("url"); } @@ -96,17 +100,17 @@ export class JsonService { let response: Response; try { - Log.debug("JsonService.postForm, url: ", url); + this._logger.debug("postForm, url:", url); response = await fetch(url, { method: "POST", headers, body }); } catch (err) { - Log.error("JsonService.postForm: network error"); + this._logger.error("postForm: network error"); throw new Error("Network Error"); } const allowedContentTypes = this._contentTypes; - Log.debug("JsonService.postForm: HTTP response received, status", response.status); + this._logger.debug("postForm: HTTP response received, status", response.status); if (response.status === 200) { const contentType = response.headers.get("Content-Type"); if (contentType) { @@ -117,7 +121,7 @@ export class JsonService { return json; } catch (err) { - Log.error("JsonService.postForm: Error parsing JSON response", err instanceof Error ? err.message : err); + this._logger.error("postForm: Error parsing JSON response", err instanceof Error ? err.message : err); throw err; } } @@ -133,14 +137,14 @@ export class JsonService { try { const json = await response.json(); if (json && json.error) { - Log.error("JsonService.postForm: Error from server: ", json.error); + this._logger.error("postForm: Error from server:", json.error); throw new Error(payload.error); } return json; } catch (err) { - Log.error("JsonService.postForm: Error parsing JSON response", err instanceof Error ? err.message : err); + this._logger.error("postForm: Error parsing JSON response", err instanceof Error ? err.message : err); throw err; } } diff --git a/src/MetadataService.ts b/src/MetadataService.ts index f516788e5..c8376790b 100644 --- a/src/MetadataService.ts +++ b/src/MetadataService.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log } from "./utils"; +import { Logger } from "./utils"; import { JsonService } from "./JsonService"; import type { OidcClientSettingsStore, SigningKey } from "./OidcClientSettings"; import type { OidcMetadata } from "./OidcMetadata"; @@ -12,8 +12,9 @@ const OidcMetadataUrlPath = ".well-known/openid-configuration"; * @public */ export class MetadataService { - private readonly _settings: OidcClientSettingsStore - private readonly _jsonService: JsonService + private readonly _settings: OidcClientSettingsStore; + private readonly _logger: Logger; + private readonly _jsonService: JsonService; // cache private _metadataUrl: string | null; @@ -22,6 +23,7 @@ export class MetadataService { public constructor(settings: OidcClientSettingsStore) { this._settings = settings; + this._logger = new Logger("MetadataService"); this._jsonService = new JsonService(["application/jwk-set+json"]); this._metadataUrl = null; @@ -37,13 +39,13 @@ export class MetadataService { this._signingKeys = null; if (this._settings.signingKeys) { - Log.debug("MetadataService.ctor: Using signingKeys from settings"); + this._logger.debug("ctor: Using signingKeys from settings"); this._signingKeys = this._settings.signingKeys; } this._metadata = null; if (this._settings.metadata) { - Log.debug("MetadataService.ctor: Using metadata from settings"); + this._logger.debug("ctor: Using metadata from settings"); this._metadata = this._settings.metadata; } } @@ -54,19 +56,19 @@ export class MetadataService { public async getMetadata(): Promise> { if (this._metadata) { - Log.debug("MetadataService.getMetadata: Returning metadata from cache"); + this._logger.debug("getMetadata: Returning metadata from cache"); return this._metadata; } if (!this._metadataUrl) { - Log.error("MetadataService.getMetadata: No authority or metadataUrl configured on settings"); + this._logger.error("getMetadata: No authority or metadataUrl configured on settings"); throw new Error("No authority or metadataUrl configured on settings"); } - Log.debug("MetadataService.getMetadata: getting metadata from", this._metadataUrl); + this._logger.debug("getMetadata: getting metadata from", this._metadataUrl); const metadata = await this._jsonService.getJson(this._metadataUrl); - Log.debug("MetadataService.getMetadata: json received"); + this._logger.debug("getMetadata: json received"); const seed = this._settings.metadataSeed || {}; this._metadata = Object.assign({}, seed, metadata) as Partial; return this._metadata; @@ -105,18 +107,18 @@ export class MetadataService { } protected async _getMetadataProperty(name: keyof OidcMetadata, optional=false): Promise { - Log.debug("MetadataService.getMetadataProperty for: " + name); + this._logger.debug("getMetadataProperty for: " + name); const metadata = await this.getMetadata(); - Log.debug("MetadataService.getMetadataProperty: metadata recieved"); + this._logger.debug("getMetadataProperty: metadata received"); if (metadata[name] === undefined) { if (optional === true) { - Log.warn("MetadataService.getMetadataProperty: Metadata does not contain optional property " + name); + this._logger.warn("getMetadataProperty: Metadata does not contain optional property " + name); return undefined; } - Log.error("MetadataService.getMetadataProperty: Metadata does not contain property " + name); + this._logger.error("getMetadataProperty: Metadata does not contain property " + name); throw new Error("Metadata does not contain property " + name); } @@ -125,18 +127,18 @@ export class MetadataService { public async getSigningKeys(): Promise { if (this._signingKeys) { - Log.debug("MetadataService.getSigningKeys: Returning signingKeys from cache"); + this._logger.debug("getSigningKeys: Returning signingKeys from cache"); return this._signingKeys; } const jwks_uri = await this.getKeysEndpoint(false) as string; - Log.debug("MetadataService.getSigningKeys: jwks_uri received", jwks_uri); + this._logger.debug("getSigningKeys: jwks_uri received", jwks_uri); const keySet = await this._jsonService.getJson(jwks_uri); - Log.debug("MetadataService.getSigningKeys: key set received", keySet); + this._logger.debug("getSigningKeys: key set received", keySet); if (!keySet.keys) { - Log.error("MetadataService.getSigningKeys: Missing keys on keyset"); + this._logger.error("getSigningKeys: Missing keys on keyset"); throw new Error("Missing keys on keyset"); } diff --git a/src/OidcClient.ts b/src/OidcClient.ts index 5d2fde474..697f29588 100644 --- a/src/OidcClient.ts +++ b/src/OidcClient.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log } from "./utils"; +import { Logger } from "./utils"; import { OidcClientSettings, OidcClientSettingsStore } from "./OidcClientSettings"; import { ResponseValidator } from "./ResponseValidator"; import { MetadataService } from "./MetadataService"; @@ -51,11 +51,14 @@ export type CreateSignoutRequestArgs = Omit { - Log.debug("OidcClient.createSigninRequest"); + this._logger.debug("createSigninRequest"); response_type = response_type || this.settings.response_type; scope = scope || this.settings.scope; @@ -89,7 +92,7 @@ export class OidcClient { } const url = await this.metadataService.getAuthorizationEndpoint(); - Log.debug("OidcClient.createSigninRequest: Received authorization endpoint", url); + this._logger.debug("createSigninRequest: Received authorization endpoint", url); const signinRequest = new SigninRequest({ url, @@ -111,7 +114,7 @@ export class OidcClient { } public async readSigninResponseState(url?: string, removeState = false): Promise<{ state: SigninState; response: SigninResponse }> { - Log.debug("OidcClient.readSigninResponseState"); + this._logger.debug("readSigninResponseState"); const useQuery = this.settings.response_mode === "query" || (!this.settings.response_mode && this.settings.response_type === "code"); @@ -119,7 +122,7 @@ export class OidcClient { const response = new SigninResponse(url, delimiter); if (!response.state) { - Log.error("OidcClient.readSigninResponseState: No state in response"); + this._logger.error("readSigninResponseState: No state in response"); throw new Error("No state in response"); } @@ -128,7 +131,7 @@ export class OidcClient { const storedStateString = await stateApi(response.state); if (!storedStateString) { - Log.error("OidcClient.readSigninResponseState: No matching state found in storage"); + this._logger.error("readSigninResponseState: No matching state found in storage"); throw new Error("No matching state found in storage"); } @@ -137,10 +140,10 @@ export class OidcClient { } public async processSigninResponse(url?: string): Promise { - Log.debug("OidcClient.processSigninResponse"); + this._logger.debug("processSigninResponse"); const { state, response } = await this.readSigninResponseState(url, true); - Log.debug("OidcClient.processSigninResponse: Received state from storage; validating response"); + this._logger.debug("processSigninResponse: Received state from storage; validating response"); return this._validator.validateSigninResponse(state, response); } @@ -148,18 +151,18 @@ export class OidcClient { state, post_logout_redirect_uri, extraQueryParams, request_type }: CreateSignoutRequestArgs = {}): Promise { - Log.debug("OidcClient.createSignoutRequest"); + this._logger.debug("createSignoutRequest"); post_logout_redirect_uri = post_logout_redirect_uri || this.settings.post_logout_redirect_uri; extraQueryParams = extraQueryParams || this.settings.extraQueryParams; const url = await this.metadataService.getEndSessionEndpoint(); if (!url) { - Log.error("OidcClient.createSignoutRequest: No end session endpoint url returned"); + this._logger.error("createSignoutRequest: No end session endpoint url returned"); throw new Error("no end session endpoint"); } - Log.debug("OidcClient.createSignoutRequest: Received end session endpoint", url); + this._logger.debug("createSignoutRequest: Received end session endpoint", url); const request = new SignoutRequest({ url, @@ -171,7 +174,7 @@ export class OidcClient { const signoutState = request.state; if (signoutState) { - Log.debug("OidcClient.createSignoutRequest: Signout request has state to persist"); + this._logger.debug("createSignoutRequest: Signout request has state to persist"); await this.settings.stateStore.set(signoutState.id, signoutState.toStorageString()); } @@ -179,14 +182,14 @@ export class OidcClient { } public async readSignoutResponseState(url?: string, removeState = false): Promise<{ state: undefined | State; response: SignoutResponse }> { - Log.debug("OidcClient.readSignoutResponseState"); + this._logger.debug("readSignoutResponseState"); const response = new SignoutResponse(url); if (!response.state) { - Log.debug("OidcClient.readSignoutResponseState: No state in response"); + this._logger.debug("readSignoutResponseState: No state in response"); if (response.error) { - Log.warn("OidcClient.readSignoutResponseState: Response was error: ", response.error); + this._logger.warn("readSignoutResponseState: Response was error:", response.error); throw new ErrorResponse(response); } @@ -199,7 +202,7 @@ export class OidcClient { const stateApi = removeState ? stateStore.remove.bind(stateStore) : stateStore.get.bind(stateStore); const storedStateString = await stateApi(stateKey); if (!storedStateString) { - Log.error("OidcClient.readSignoutResponseState: No matching state found in storage"); + this._logger.error("readSignoutResponseState: No matching state found in storage"); throw new Error("No matching state found in storage"); } @@ -208,20 +211,20 @@ export class OidcClient { } public async processSignoutResponse(url: string): Promise { - Log.debug("OidcClient.processSignoutResponse"); + this._logger.debug("processSignoutResponse"); const { state, response } = await this.readSignoutResponseState(url, true); if (state) { - Log.debug("OidcClient.processSignoutResponse: Received state from storage; validating response"); + this._logger.debug("processSignoutResponse: Received state from storage; validating response"); return this._validator.validateSignoutResponse(state, response); } - Log.debug("OidcClient.processSignoutResponse: No state from storage; skipping validating response"); + this._logger.debug("processSignoutResponse: No state from storage; skipping validating response"); return response; } public clearStaleState(): Promise { - Log.debug("OidcClient.clearStaleState"); + this._logger.debug("clearStaleState"); return State.clearStaleState(this.settings.stateStore, this.settings.staleStateAgeInSeconds); } } diff --git a/src/ResponseValidator.ts b/src/ResponseValidator.ts index 34749736a..a505f72ff 100644 --- a/src/ResponseValidator.ts +++ b/src/ResponseValidator.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log } from "./utils"; +import { Logger } from "./utils"; import type { MetadataService } from "./MetadataService"; import { TokenClient } from "./TokenClient"; import { ErrorResponse } from "./ErrorResponse"; @@ -16,44 +16,46 @@ const ProtocolClaims = ["at_hash", "iat", "nbf", "exp", "aud", "iss", "c_hash"]; export class ResponseValidator { protected readonly _settings: OidcClientSettingsStore; + protected readonly _logger: Logger; protected readonly _metadataService: MetadataService; protected readonly _tokenClient: TokenClient; public constructor(settings: OidcClientSettingsStore, metadataService: MetadataService) { this._settings = settings; + this._logger = new Logger("ResponseValidator"); this._metadataService = metadataService; this._tokenClient = new TokenClient(this._settings, metadataService); } public async validateSigninResponse(state: SigninState, response: SigninResponse): Promise { - Log.debug("ResponseValidator.validateSigninResponse"); + this._logger.debug("validateSigninResponse"); response = this._processSigninParams(state, response); - Log.debug("ResponseValidator.validateSigninResponse: state processed"); + this._logger.debug("validateSigninResponse: state processed"); response = await this._validateTokens(state, response); - Log.debug("ResponseValidator.validateSigninResponse: tokens validated"); + this._logger.debug("validateSigninResponse: tokens validated"); response = await this._processClaims(state, response); - Log.debug("ResponseValidator.validateSigninResponse: claims processed"); + this._logger.debug("validateSigninResponse: claims processed"); return response; } public validateSignoutResponse(state: State, response: SignoutResponse): SignoutResponse { if (state.id !== response.state) { - Log.error("ResponseValidator.validateSignoutResponse: State does not match"); + this._logger.error("validateSignoutResponse: State does not match"); throw new Error("State does not match"); } // now that we know the state matches, take the stored data // and set it into the response so callers can get their state // this is important for both success & error outcomes - Log.debug("ResponseValidator.validateSignoutResponse: state validated"); + this._logger.debug("validateSignoutResponse: state validated"); response.state = state.data; if (response.error) { - Log.warn("ResponseValidator.validateSignoutResponse: Response was error", response.error); + this._logger.warn("validateSignoutResponse: Response was error", response.error); throw new ErrorResponse(response); } @@ -62,48 +64,48 @@ export class ResponseValidator { protected _processSigninParams(state: SigninState, response: SigninResponse): SigninResponse { if (state.id !== response.state) { - Log.error("ResponseValidator._processSigninParams: State does not match"); + this._logger.error("_processSigninParams: State does not match"); throw new Error("State does not match"); } if (!state.client_id) { - Log.error("ResponseValidator._processSigninParams: No client_id on state"); + this._logger.error("_processSigninParams: No client_id on state"); throw new Error("No client_id on state"); } if (!state.authority) { - Log.error("ResponseValidator._processSigninParams: No authority on state"); + this._logger.error("_processSigninParams: No authority on state"); throw new Error("No authority on state"); } // ensure we're using the correct authority if (this._settings.authority !== state.authority) { - Log.error("ResponseValidator._processSigninParams: authority mismatch on settings vs. signin state"); + this._logger.error("_processSigninParams: authority mismatch on settings vs. signin state"); throw new Error("authority mismatch on settings vs. signin state"); } if (this._settings.client_id && this._settings.client_id !== state.client_id) { - Log.error("ResponseValidator._processSigninParams: client_id mismatch on settings vs. signin state"); + this._logger.error("_processSigninParams: client_id mismatch on settings vs. signin state"); throw new Error("client_id mismatch on settings vs. signin state"); } // now that we know the state matches, take the stored data // and set it into the response so callers can get their state // this is important for both success & error outcomes - Log.debug("ResponseValidator._processSigninParams: state validated"); + this._logger.debug("_processSigninParams: state validated"); response.state = state.data; if (response.error) { - Log.warn("ResponseValidator._processSigninParams: Response was error", response.error); + this._logger.warn("_processSigninParams: Response was error", response.error); throw new ErrorResponse(response); } if (state.code_verifier && !response.code) { - Log.error("ResponseValidator._processSigninParams: Expecting code in response"); + this._logger.error("_processSigninParams: Expecting code in response"); throw new Error("No code in response"); } if (!state.code_verifier && response.code) { - Log.error("ResponseValidator._processSigninParams: Not expecting code in response"); + this._logger.error("_processSigninParams: Not expecting code in response"); throw new Error("Unexpected code in response"); } @@ -117,18 +119,18 @@ export class ResponseValidator { protected async _processClaims(state: SigninState, response: SigninResponse): Promise { if (response.isOpenIdConnect) { - Log.debug("ResponseValidator._processClaims: response is OIDC, processing claims"); + this._logger.debug("_processClaims: response is OIDC, processing claims"); response.profile = this._filterProtocolClaims(response.profile); } else { - Log.debug("ResponseValidator._processClaims: response is not OIDC, not processing claims"); + this._logger.debug("_processClaims: response is not OIDC, not processing claims"); } return response; } protected _filterProtocolClaims(claims: UserProfile): UserProfile { - Log.debug("ResponseValidator._filterProtocolClaims, incoming claims:", claims); + this._logger.debug("_filterProtocolClaims, incoming claims:", claims); const result = Object.assign({}, claims as Record); @@ -137,10 +139,10 @@ export class ResponseValidator { delete result[type]; }); - Log.debug("ResponseValidator._filterProtocolClaims: protocol claims filtered", result); + this._logger.debug("_filterProtocolClaims: protocol claims filtered:", result); } else { - Log.debug("ResponseValidator._filterProtocolClaims: protocol claims not filtered"); + this._logger.debug("_filterProtocolClaims: protocol claims not filtered"); } return result; @@ -148,11 +150,11 @@ export class ResponseValidator { protected async _validateTokens(state: SigninState, response: SigninResponse): Promise { if (response.code) { - Log.debug("ResponseValidator._validateTokens: Validating code"); + this._logger.debug("_validateTokens: Validating code"); return this._processCode(state, response); } - Log.debug("ResponseValidator._validateTokens: No code to process"); + this._logger.debug("_validateTokens: No code to process"); return response; } @@ -181,7 +183,7 @@ export class ResponseValidator { response.scope = tokenResponse.scope || response.scope; response.expires_in = parseInt(tokenResponse.expires_in) || response.expires_in; - Log.debug("ResponseValidator._processCode: token response successful, returning response"); + this._logger.debug("_processCode: token response successful, returning response"); return response; } } diff --git a/src/SessionMonitor.ts b/src/SessionMonitor.ts index d69b3bc75..0d2e82e14 100644 --- a/src/SessionMonitor.ts +++ b/src/SessionMonitor.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log, IntervalTimer, g_timer } from "./utils"; +import { Logger, IntervalTimer, g_timer } from "./utils"; import { CheckSessionIFrame } from "./CheckSessionIFrame"; import type { UserManager } from "./UserManager"; import type { User } from "./User"; @@ -10,6 +10,8 @@ import type { User } from "./User"; * @public */ export class SessionMonitor { + private readonly _logger: Logger; + private readonly _userManager: UserManager; private readonly _timer: IntervalTimer; private _sub: string | undefined; @@ -17,8 +19,10 @@ export class SessionMonitor { private _checkSessionIFrame?: CheckSessionIFrame; public constructor(userManager: UserManager) { + this._logger = new Logger("SessionMonitor"); + if (!userManager) { - Log.error("SessionMonitor.ctor: No user manager passed to SessionMonitor"); + this._logger.error("ctor: No user manager passed to SessionMonitor"); throw new Error("userManager"); } @@ -31,7 +35,7 @@ export class SessionMonitor { Promise.resolve(this._init()) .catch((err: Error) => { // catch to suppress errors since we're in a ctor - Log.error("SessionMonitor ctor: error:", err.message); + this._logger.error("ctor: error:", err.message); }); } @@ -71,12 +75,12 @@ export class SessionMonitor { if (user.profile) { this._sub = user.profile.sub; this._sid = user.profile.sid; - Log.debug("SessionMonitor._start: session_state:", session_state, ", sub:", this._sub); + this._logger.debug("_start: session_state:", session_state, ", sub:", this._sub); } else { this._sub = undefined; this._sid = undefined; - Log.debug("SessionMonitor._start: session_state:", session_state, ", anonymous user"); + this._logger.debug("_start: session_state:", session_state, ", anonymous user"); } if (this._checkSessionIFrame) { @@ -87,7 +91,7 @@ export class SessionMonitor { try { const url = await this._userManager.metadataService.getCheckSessionIframe(); if (url) { - Log.debug("SessionMonitor._start: Initializing check session iframe"); + this._logger.debug("_start: Initializing check session iframe"); const client_id = this._userManager.settings.client_id; const intervalInSeconds = this._userManager.settings.checkSessionIntervalInSeconds; @@ -99,12 +103,12 @@ export class SessionMonitor { checkSessionIFrame.start(session_state); } else { - Log.warn("SessionMonitor._start: No check session iframe found in the metadata"); + this._logger.warn("_start: No check session iframe found in the metadata"); } } catch (err) { // catch to suppress errors since we're in non-promise callback - Log.error("SessionMonitor._start: Error from getCheckSessionIframe:", err instanceof Error ? err.message : err); + this._logger.error("_start: Error from getCheckSessionIframe:", err instanceof Error ? err.message : err); } } @@ -113,7 +117,7 @@ export class SessionMonitor { this._sid = undefined; if (this._checkSessionIFrame) { - Log.debug("SessionMonitor._stop"); + this._logger.debug("_stop"); this._checkSessionIFrame.stop(); } @@ -139,7 +143,7 @@ export class SessionMonitor { } catch (err) { // catch to suppress errors since we're in a callback - Log.error("SessionMonitor: error from querySessionStatus:", err instanceof Error ? err.message : err); + this._logger.error("error from querySessionStatus:", err instanceof Error ? err.message : err); } }, 1000); } @@ -156,35 +160,35 @@ export class SessionMonitor { this._checkSessionIFrame.start(session.session_state); if (session.sid === this._sid) { - Log.debug("SessionMonitor._callback: Same sub still logged in at OP, restarting check session iframe; session_state:", session.session_state); + this._logger.debug("_callback: Same sub still logged in at OP, restarting check session iframe; session_state:", session.session_state); } else { - Log.debug("SessionMonitor._callback: Same sub still logged in at OP, session state has changed, restarting check session iframe; session_state:", session.session_state); + this._logger.debug("_callback: Same sub still logged in at OP, session state has changed, restarting check session iframe; session_state:", session.session_state); this._userManager.events._raiseUserSessionChanged(); } } else { - Log.debug("SessionMonitor._callback: Different subject signed into OP:", session.sub); + this._logger.debug("_callback: Different subject signed into OP:", session.sub); } } else { - Log.debug("SessionMonitor._callback: Subject no longer signed into OP"); + this._logger.debug("_callback: Subject no longer signed into OP"); } if (raiseEvent) { if (this._sub) { - Log.debug("SessionMonitor._callback: SessionMonitor._callback; raising signed out event"); + this._logger.debug("_callback: SessionMonitor._callback; raising signed out event"); this._userManager.events._raiseUserSignedOut(); } else { - Log.debug("SessionMonitor._callback: SessionMonitor._callback; raising signed in event"); + this._logger.debug("_callback: SessionMonitor._callback; raising signed in event"); this._userManager.events._raiseUserSignedIn(); } } } catch (err) { if (this._sub) { - Log.debug("SessionMonitor._callback: Error calling queryCurrentSigninSession; raising signed out event", + this._logger.debug("_callback: Error calling queryCurrentSigninSession; raising signed out event", err instanceof Error ? err.message : err); this._userManager.events._raiseUserSignedOut(); } diff --git a/src/SigninRequest.ts b/src/SigninRequest.ts index 38fb0aa12..61d6e005f 100644 --- a/src/SigninRequest.ts +++ b/src/SigninRequest.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log, UrlUtils } from "./utils"; +import { Logger, UrlUtils } from "./utils"; import { SigninState } from "./SigninState"; export interface SigninRequestArgs { @@ -33,6 +33,8 @@ export interface SigninRequestArgs { } export class SigninRequest { + private readonly _logger: Logger; + public readonly url: string; public readonly state: SigninState; @@ -43,28 +45,30 @@ export class SigninRequest { state_data, prompt, display, max_age, ui_locales, login_hint, acr_values, resource, response_mode, request, request_uri, extraQueryParams, request_type, client_secret, extraTokenParams, skipUserInfo }: SigninRequestArgs) { + this._logger = new Logger("SigninRequest"); + if (!url) { - Log.error("SigninRequest.ctor: No url passed"); + this._logger.error("ctor: No url passed"); throw new Error("url"); } if (!client_id) { - Log.error("SigninRequest.ctor: No client_id passed"); + this._logger.error("ctor: No client_id passed"); throw new Error("client_id"); } if (!redirect_uri) { - Log.error("SigninRequest.ctor: No redirect_uri passed"); + this._logger.error("ctor: No redirect_uri passed"); throw new Error("redirect_uri"); } if (!response_type) { - Log.error("SigninRequest.ctor: No response_type passed"); + this._logger.error("ctor: No response_type passed"); throw new Error("response_type"); } if (!scope) { - Log.error("SigninRequest.ctor: No scope passed"); + this._logger.error("ctor: No scope passed"); throw new Error("scope"); } if (!authority) { - Log.error("SigninRequest.ctor: No authority passed"); + this._logger.error("SigninRequest.ctor: No authority passed"); throw new Error("authority"); } diff --git a/src/SigninState.ts b/src/SigninState.ts index 1b64f8c78..97add0f95 100644 --- a/src/SigninState.ts +++ b/src/SigninState.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log, CryptoUtils } from "./utils"; +import { Logger, CryptoUtils } from "./utils"; import { State } from "./State"; export class SigninState extends State { @@ -61,7 +61,7 @@ export class SigninState extends State { } public toStorageString(): string { - Log.debug("SigninState.toStorageString"); + Logger.debug("SigninState", "toStorageString"); return JSON.stringify({ id: this.id, data: this.data, @@ -81,7 +81,7 @@ export class SigninState extends State { } public static fromStorageString(storageString: string): SigninState { - Log.debug("SigninState.fromStorageString"); + Logger.debug("SigninState", "fromStorageString"); const data = JSON.parse(storageString); return new SigninState(data); } diff --git a/src/SignoutRequest.ts b/src/SignoutRequest.ts index a6e35ec68..88c24f4d1 100644 --- a/src/SignoutRequest.ts +++ b/src/SignoutRequest.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log, UrlUtils } from "./utils"; +import { Logger, UrlUtils } from "./utils"; import { State } from "./State"; export interface SignoutRequestArgs { @@ -16,6 +16,8 @@ export interface SignoutRequestArgs { } export class SignoutRequest { + private readonly _logger: Logger; + public readonly url: string public readonly state?: State @@ -23,8 +25,10 @@ export class SignoutRequest { url, state_data, post_logout_redirect_uri, extraQueryParams, request_type }: SignoutRequestArgs) { + this._logger = new Logger("SignoutRequest"); + if (!url) { - Log.error("SignoutRequest.ctor: No url passed"); + this._logger.error("ctor: No url passed"); throw new Error("url"); } diff --git a/src/SilentRenewService.ts b/src/SilentRenewService.ts index 5903f4e93..0b3ab1488 100644 --- a/src/SilentRenewService.ts +++ b/src/SilentRenewService.ts @@ -1,14 +1,17 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log } from "./utils"; +import { Logger } from "./utils"; import type { UserManager } from "./UserManager"; import type { AccessTokenCallback } from "./AccessTokenEvents"; export class SilentRenewService { + protected _logger: Logger; private _isStarted = false; - public constructor(private _userManager: UserManager) {} + public constructor(private _userManager: UserManager) { + this._logger = new Logger("SilentRenewService"); + } public async start(): Promise { if (!this._isStarted) { @@ -22,7 +25,7 @@ export class SilentRenewService { } catch (err) { // catch to suppress errors since we're in a ctor - Log.error("SilentRenewService.start: Error from getUser:", err instanceof Error ? err.message : err); + this._logger.error("start: Error from getUser:", err instanceof Error ? err.message : err); } } } @@ -37,11 +40,12 @@ export class SilentRenewService { protected _tokenExpiring: AccessTokenCallback = () => { this._userManager.signinSilent() .then(() => { - Log.debug("SilentRenewService._tokenExpiring: Silent token renewal successful"); + this._logger.debug("_tokenExpiring: Silent token renewal successful"); }) .catch((err) => { - Log.error("SilentRenewService._tokenExpiring: Error from signinSilent:", err instanceof Error ? err.message : err); - this._userManager.events._raiseSilentRenewError(err instanceof Error ? err : new Error("Silent renew failed")); + const error = err instanceof Error ? err : new Error("Silent renew failed"); + this._logger.error("_tokenExpiring: Error from signinSilent:", error.message); + this._userManager.events._raiseSilentRenewError(error); }); } } diff --git a/src/State.ts b/src/State.ts index 27c0d40e4..4a3d728f7 100644 --- a/src/State.ts +++ b/src/State.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log, CryptoUtils, Timer } from "./utils"; +import { Logger, CryptoUtils, Timer } from "./utils"; import type { StateStore } from "./StateStore"; export class State { @@ -29,7 +29,7 @@ export class State { } public toStorageString(): string { - Log.debug("State.toStorageString"); + Logger.debug("State", "toStorageString"); return JSON.stringify({ id: this.id, data: this.data, @@ -39,7 +39,7 @@ export class State { } public static fromStorageString(storageString: string): State { - Log.debug("State.fromStorageString"); + Logger.debug("State", "fromStorageString"); return new State(JSON.parse(storageString)); } @@ -47,7 +47,7 @@ export class State { const cutoff = Timer.getEpochTime() - age; const keys = await storage.getAllKeys(); - Log.debug("State.clearStaleState: got keys", keys); + Logger.debug("State", "clearStaleState: got keys", keys); for (let i = 0; i < keys.length; i++) { const key = keys[i]; @@ -58,23 +58,23 @@ export class State { try { const state = State.fromStorageString(item); - Log.debug("State.clearStaleState: got item from key: ", key, state.created); + Logger.debug("State", "clearStaleState: got item from key: ", key, state.created); if (state.created <= cutoff) { remove = true; } } catch (err) { - Log.error("State.clearStaleState: Error parsing state for key", key, err instanceof Error ? err.message : err); + Logger.error("State", "clearStaleState: Error parsing state for key", key, err instanceof Error ? err.message : err); remove = true; } } else { - Log.debug("State.clearStaleState: no item in storage for key: ", key); + Logger.debug("State", "clearStaleState: no item in storage for key: ", key); remove = true; } if (remove) { - Log.debug("State.clearStaleState: removed item for key: ", key); + Logger.debug("State", "clearStaleState: removed item for key: ", key); void storage.remove(key); } } diff --git a/src/TokenClient.ts b/src/TokenClient.ts index 5b3cbe131..c991cf0ce 100644 --- a/src/TokenClient.ts +++ b/src/TokenClient.ts @@ -1,9 +1,9 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. +import { Logger } from "./utils"; import { JsonService } from "./JsonService"; import type { MetadataService } from "./MetadataService"; -import { Log } from "./utils"; import type { OidcClientSettingsStore } from "./OidcClientSettings"; interface ExchangeCodeArgs { @@ -26,11 +26,13 @@ interface ExchangeRefreshTokenArgs { export class TokenClient { private readonly _settings: OidcClientSettingsStore; + private readonly _logger: Logger; private readonly _jsonService: JsonService; private readonly _metadataService: MetadataService; public constructor(settings: OidcClientSettingsStore, metadataService: MetadataService) { this._settings = settings; + this._logger = new Logger("TokenClient"); this._jsonService = new JsonService(); this._metadataService = metadataService; } @@ -46,19 +48,19 @@ export class TokenClient { const client_authentication = this._settings.client_authentication; if (!args.client_id) { - Log.error("TokenClient.exchangeCode: No client_id passed"); + this._logger.error("exchangeCode: No client_id passed"); throw new Error("A client_id is required"); } if (!args.redirect_uri) { - Log.error("TokenClient.exchangeCode: No redirect_uri passed"); + this._logger.error("exchangeCode: No redirect_uri passed"); throw new Error("A redirect_uri is required"); } if (!args.code) { - Log.error("TokenClient.exchangeCode: No code passed"); + this._logger.error("exchangeCode: No code passed"); throw new Error("A code is required"); } if (!args.code_verifier) { - Log.error("TokenClient.exchangeCode: No code_verifier passed"); + this._logger.error("exchangeCode: No code_verifier passed"); throw new Error("A code_verifier is required"); } @@ -66,7 +68,7 @@ export class TokenClient { let basicAuth: string | undefined = undefined; if (client_authentication == "client_secret_basic") { if (!args.client_secret) { - Log.error("TokenClient.exchangeCode: No client_secret passed"); + this._logger.error("exchangeCode: No client_secret passed"); throw new Error("A client_secret is required"); } @@ -76,10 +78,10 @@ export class TokenClient { } const url = await this._metadataService.getTokenEndpoint(false) as string; - Log.debug("TokenClient.exchangeCode: Received token endpoint"); + this._logger.debug("exchangeCode: Received token endpoint"); const response = await this._jsonService.postForm(url, args, basicAuth); - Log.debug("TokenClient.exchangeCode: response received"); + this._logger.debug("exchangeCode: response received"); return response; } @@ -94,11 +96,11 @@ export class TokenClient { const client_authentication = this._settings.client_authentication; if (!args.refresh_token) { - Log.error("TokenClient.exchangeRefreshToken: No refresh_token passed"); + this._logger.error("exchangeRefreshToken: No refresh_token passed"); throw new Error("A refresh_token is required"); } if (!args.client_id) { - Log.error("TokenClient.exchangeRefreshToken: No client_id passed"); + this._logger.error("exchangeRefreshToken: No client_id passed"); throw new Error("A client_id is required"); } @@ -106,7 +108,7 @@ export class TokenClient { let basicAuth: string | undefined = undefined; if (client_authentication == "client_secret_basic") { if (!args.client_secret) { - Log.error("TokenClient.exchangeCode: No client_secret passed"); + this._logger.error("exchangeCode: No client_secret passed"); throw new Error("A client_secret is required"); } @@ -116,10 +118,10 @@ export class TokenClient { } const url = await this._metadataService.getTokenEndpoint(false) as string; - Log.debug("TokenClient.exchangeRefreshToken: Received token endpoint"); + this._logger.debug("exchangeRefreshToken: Received token endpoint"); const response = await this._jsonService.postForm(url, args, basicAuth); - Log.debug("TokenClient.exchangeRefreshToken: response received"); + this._logger.debug("exchangeRefreshToken: response received"); return response; } diff --git a/src/TokenRevocationClient.ts b/src/TokenRevocationClient.ts index 4276892c7..770abe0eb 100644 --- a/src/TokenRevocationClient.ts +++ b/src/TokenRevocationClient.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log } from "./utils"; +import { Logger } from "./utils"; import type { MetadataService } from "./MetadataService"; import type { OidcClientSettingsStore } from "./OidcClientSettings"; @@ -12,29 +12,31 @@ const RefreshTokenTypeHint = "refresh_token"; * @public */ export class TokenRevocationClient { - private _settings: OidcClientSettingsStore + private _settings: OidcClientSettingsStore; + private readonly _logger: Logger; private _metadataService: MetadataService; public constructor(settings: OidcClientSettingsStore, metadataService: MetadataService) { this._settings = settings; + this._logger = new Logger("TokenRevocationClient"); this._metadataService = metadataService; } public async revoke(token: string, required: boolean, type = "access_token"): Promise { if (!token) { - Log.error("TokenRevocationClient.revoke: No token provided"); + this._logger.error("revoke: No token provided"); throw new Error("No token provided."); } if (type !== AccessTokenTypeHint && type != RefreshTokenTypeHint) { - Log.error("TokenRevocationClient.revoke: Invalid token type"); + this._logger.error("revoke: Invalid token type"); throw new Error("Invalid token type."); } const url = await this._metadataService.getRevocationEndpoint(); if (!url) { if (required) { - Log.error("TokenRevocationClient.revoke: Revocation not supported"); + this._logger.error("revoke: Revocation not supported"); throw new Error("Revocation not supported"); } @@ -42,7 +44,7 @@ export class TokenRevocationClient { return; } - Log.debug("TokenRevocationClient.revoke: Revoking " + type); + this._logger.debug("revoke: Revoking " + type); const client_id = this._settings.client_id; const client_secret = this._settings.client_secret; await this._revoke(url, client_id, client_secret, token, type); @@ -63,15 +65,15 @@ export class TokenRevocationClient { let response: Response; try { - Log.debug("TokenRevocationClient.revoke, url: ", url); + this._logger.debug("revoke, url: ", url); response = await fetch(url, { method: "POST", headers, body }); } catch (err) { - Log.error("TokenRevocationClient.revoke: network error"); + this._logger.error("revoke: network error"); throw new Error("Network Error"); } - Log.debug("TokenRevocationClient.revoke: HTTP response received, status", response.status); + this._logger.debug("revoke: HTTP response received, status", response.status); if (response.status !== 200) { throw new Error(response.statusText + " (" + response.status.toString() + ")"); } diff --git a/src/User.ts b/src/User.ts index 47a0bf2f7..7c8872a54 100644 --- a/src/User.ts +++ b/src/User.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log, Timer } from "./utils"; +import { Logger, Timer } from "./utils"; export interface UserProfile { sub?: string; @@ -65,7 +65,7 @@ export class User { } public toStorageString(): string { - Log.debug("User.toStorageString"); + Logger.debug("User", "toStorageString"); return JSON.stringify({ session_state: this.session_state, access_token: this.access_token, @@ -78,7 +78,7 @@ export class User { } public static fromStorageString(storageString: string): User { - Log.debug("User.fromStorageString"); + Logger.debug("User", "fromStorageString"); return new User(JSON.parse(storageString)); } } diff --git a/src/UserManager.ts b/src/UserManager.ts index 26c773f68..e024cff91 100644 --- a/src/UserManager.ts +++ b/src/UserManager.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log } from "./utils"; +import { Logger } from "./utils"; import { IFrameNavigator, NavigateResponse, PopupNavigator, RedirectNavigator, PopupWindowParams, IWindow, IFrameWindowParams, RedirectParams } from "./navigators"; import { OidcClient, CreateSigninRequestArgs, CreateSignoutRequestArgs } from "./OidcClient"; import { UserManagerSettings, UserManagerSettingsStore } from "./UserManagerSettings"; @@ -55,6 +55,7 @@ export type SignoutPopupArgs = PopupWindowParams & ExtraSignoutRequestArgs; */ export class UserManager { public readonly settings: UserManagerSettingsStore; + protected readonly _logger: Logger; protected readonly _client: OidcClient; protected readonly _redirectNavigator: RedirectNavigator; @@ -68,6 +69,7 @@ export class UserManager { public constructor(settings: UserManagerSettings) { this.settings = new UserManagerSettingsStore(settings); + this._logger = new Logger("UserManager"); this._client = new OidcClient(settings); @@ -80,13 +82,13 @@ export class UserManager { // order is important for the following properties; these services depend upon the events. if (this.settings.automaticSilentRenew) { - Log.debug("UserManager.ctor: automaticSilentRenew is configured, setting up silent renew"); + this._logger.debug("ctor: automaticSilentRenew is configured, setting up silent renew"); this.startSilentRenew(); } this._sessionMonitor = null; if (this.settings.monitorSession) { - Log.debug("UserManager.ctor: monitorSession is configured, setting up session monitor"); + this._logger.debug("ctor: monitorSession is configured, setting up session monitor"); this._sessionMonitor = new SessionMonitor(this); } @@ -105,18 +107,18 @@ export class UserManager { public async getUser(): Promise { const user = await this._loadUser(); if (user) { - Log.info("UserManager.getUser: user loaded"); + this._logger.info("getUser: user loaded"); this._events.load(user, false); return user; } - Log.info("UserManager.getUser: user not found in storage"); + this._logger.info("getUser: user not found in storage"); return null; } public async removeUser(): Promise { await this.storeUser(null); - Log.info("UserManager.removeUser: user removed from storage"); + this._logger.info("removeUser: user removed from storage"); this._events.unload(); } @@ -130,16 +132,16 @@ export class UserManager { request_type: "si:r", ...requestArgs, }, handle); - Log.info("UserManager.signinRedirect: successful"); + this._logger.info("signinRedirect: successful"); } public async signinRedirectCallback(url = window.location.href): Promise { const user = await this._signinEnd(url); if (user.profile && user.profile.sub) { - Log.info("UserManager.signinRedirectCallback: successful, signed in sub: ", user.profile.sub); + this._logger.info("signinRedirectCallback: successful, signed in sub: ", user.profile.sub); } else { - Log.info("UserManager.signinRedirectCallback: no sub"); + this._logger.info("signinRedirectCallback: no sub"); } return user; @@ -153,7 +155,7 @@ export class UserManager { } = args; const url = this.settings.popup_redirect_uri || this.settings.redirect_uri; if (!url) { - Log.error("UserManager.signinPopup: No popup_redirect_uri or redirect_uri configured"); + this._logger.error("signinPopup: No popup_redirect_uri or redirect_uri configured"); throw new Error("No popup_redirect_uri or redirect_uri configured"); } @@ -166,10 +168,10 @@ export class UserManager { }, handle); if (user) { if (user.profile && user.profile.sub) { - Log.info("UserManager.signinPopup: signinPopup successful, signed in sub: ", user.profile.sub); + this._logger.info("signinPopup: signinPopup successful, signed in sub: ", user.profile.sub); } else { - Log.info("UserManager.signinPopup: no sub"); + this._logger.info("signinPopup: no sub"); } } @@ -178,10 +180,10 @@ export class UserManager { public async signinPopupCallback(url?: string): Promise { try { await this._signinCallback(url, this._popupNavigator); - Log.info("UserManager.signinPopupCallback: successful"); + this._logger.info("signinPopupCallback: successful"); } catch (err) { - Log.error("UserManager.signinPopupCallback error", err instanceof Error ? err.message : err); + this._logger.error("signinPopupCallback error", err instanceof Error ? err.message : err); } } @@ -198,13 +200,13 @@ export class UserManager { const url = this.settings.silent_redirect_uri || this.settings.redirect_uri; if (!url) { - Log.error("UserManager.signinSilent: No silent_redirect_uri configured"); + this._logger.error("signinSilent: No silent_redirect_uri configured"); throw new Error("No silent_redirect_uri configured"); } let verifySub: string | undefined; if (user && this.settings.validateSubOnSilentRenew) { - Log.debug("UserManager.signinSilent, subject prior to silent renew: ", user.profile.sub); + this._logger.debug("signinSilent, subject prior to silent renew: ", user.profile.sub); verifySub = user.profile.sub; } @@ -217,10 +219,10 @@ export class UserManager { }, handle, verifySub); if (user) { if (user.profile && user.profile.sub) { - Log.info("UserManager.signinSilent: successful, signed in sub: ", user.profile.sub); + this._logger.info("signinSilent: successful, signed in sub: ", user.profile.sub); } else { - Log.info("UserManager.signinSilent: no sub"); + this._logger.info("signinSilent: no sub"); } } @@ -232,16 +234,16 @@ export class UserManager { refresh_token: user.refresh_token || "" }); if (!result) { - Log.error("UserManager._useRefreshToken: No response returned from token endpoint"); + this._logger.error("_useRefreshToken: No response returned from token endpoint"); throw new Error("No response returned from token endpoint"); } if (!result.access_token) { - Log.error("UserManager._useRefreshToken: No access token returned from token endpoint"); + this._logger.error("_useRefreshToken: No access token returned from token endpoint"); throw new Error("No access token returned from token endpoint"); } - Log.debug("UserManager._useRefreshToken: refresh token response success"); + this._logger.debug("_useRefreshToken: refresh token response success"); user.access_token = result.access_token || user.access_token; user.refresh_token = result.refresh_token || user.refresh_token; user.expires_in = result.expires_in; @@ -253,7 +255,7 @@ export class UserManager { public async signinSilentCallback(url?: string): Promise { await this._signinCallback(url, this._iframeNavigator); - Log.info("UserManager.signinSilentCallback: successful"); + this._logger.info("signinSilentCallback: successful"); } public async signinCallback(url?: string): Promise { @@ -292,7 +294,7 @@ export class UserManager { } = args; const url = this.settings.silent_redirect_uri || this.settings.redirect_uri; if (!url) { - Log.error("UserManager.querySessionStatus: No silent_redirect_uri configured"); + this._logger.error("querySessionStatus: No silent_redirect_uri configured"); throw new Error("No silent_redirect_uri configured"); } @@ -308,10 +310,10 @@ export class UserManager { }, handle); try { const signinResponse = await this._client.processSigninResponse(navResponse.url); - Log.debug("UserManager.querySessionStatus: got signin response"); + this._logger.debug("querySessionStatus: got signin response"); if (signinResponse.session_state && signinResponse.profile.sub) { - Log.info("UserManager.querySessionStatus: querySessionStatus success for sub: ", signinResponse.profile.sub); + this._logger.info("querySessionStatus: querySessionStatus success for sub: ", signinResponse.profile.sub); return { session_state: signinResponse.session_state, sub: signinResponse.profile.sub, @@ -319,7 +321,7 @@ export class UserManager { }; } - Log.info("querySessionStatus successful, user not authenticated"); + this._logger.info("querySessionStatus: successful, user not authenticated"); return null; } catch (err) { @@ -330,7 +332,7 @@ export class UserManager { err.message == "interaction_required" || err.message == "account_selection_required" ) { - Log.info("UserManager.querySessionStatus: querySessionStatus success for anonymous user"); + this._logger.info("querySessionStatus: querySessionStatus success for anonymous user"); return { session_state: err.session_state }; @@ -346,11 +348,11 @@ export class UserManager { return this._signinEnd(navResponse.url, verifySub); } protected async _signinStart(args: CreateSigninRequestArgs, handle: IWindow): Promise { - Log.debug("UserManager._signinStart: got navigator window handle"); + this._logger.debug("_signinStart: got navigator window handle"); try { const signinRequest = await this._client.createSigninRequest(args); - Log.debug("UserManager._signinStart: got signin request"); + this._logger.debug("_signinStart: got signin request"); return handle.navigate({ url: signinRequest.url, @@ -358,34 +360,34 @@ export class UserManager { }); } catch (err) { - Log.debug("UserManager._signinStart: Error after preparing navigator, closing navigator window"); + this._logger.debug("_signinStart: Error after preparing navigator, closing navigator window"); handle.close(); throw err; } } protected async _signinEnd(url: string, verifySub?: string): Promise { const signinResponse = await this._client.processSigninResponse(url); - Log.debug("UserManager._signinEnd: got signin response"); + this._logger.debug("_signinEnd: got signin response"); const user = new User(signinResponse); if (verifySub) { if (verifySub !== user.profile.sub) { - Log.debug("UserManager._signinEnd: current user does not match user returned from signin. sub from signin: ", user.profile.sub); + this._logger.debug("_signinEnd: current user does not match user returned from signin. sub from signin: ", user.profile.sub); throw new Error("login_required"); } else { - Log.debug("UserManager._signinEnd: current user matches user returned from signin"); + this._logger.debug("_signinEnd: current user matches user returned from signin"); } } await this.storeUser(user); - Log.debug("UserManager._signinEnd: user stored"); + this._logger.debug("_signinEnd: user stored"); this._events.load(user); return user; } protected async _signinCallback(url: string | undefined, navigator: IFrameNavigator | PopupNavigator): Promise { - Log.debug("UserManager._signinCallback"); + this._logger.debug("_signinCallback"); const useQuery = this.settings.response_mode === "query" || (!this.settings.response_mode && this.settings.response_type === "code"); const delimiter = useQuery ? "?" : "#"; @@ -403,11 +405,11 @@ export class UserManager { post_logout_redirect_uri: this.settings.post_logout_redirect_uri, ...requestArgs, }, handle); - Log.info("UserManager.signoutRedirect: successful"); + this._logger.info("signoutRedirect: successful"); } public async signoutRedirectCallback(url = window.location.href): Promise { const response = await this._signoutEnd(url); - Log.info("UserManager.signoutRedirectCallback: successful"); + this._logger.info("signoutRedirectCallback: successful"); return response; } @@ -431,12 +433,12 @@ export class UserManager { state: url == null ? undefined : {}, ...requestArgs, }, handle); - Log.info("UserManager.signoutPopup: successful"); + this._logger.info("signoutPopup: successful"); } public async signoutPopupCallback(url?: string, keepOpen = false): Promise { const delimiter = "?"; await this._popupNavigator.callback(url, keepOpen, delimiter); - Log.info("UserManager.signoutPopupCallback: successful"); + this._logger.info("signoutPopupCallback: successful"); } protected async _signout(args: CreateSignoutRequestArgs, handle: IWindow): Promise { @@ -444,21 +446,21 @@ export class UserManager { return this._signoutEnd(navResponse.url); } protected async _signoutStart(args: CreateSignoutRequestArgs = {}, handle: IWindow): Promise { - Log.debug("UserManager._signoutStart: got navigator window handle"); + this._logger.debug("_signoutStart: got navigator window handle"); try { const user = await this._loadUser(); - Log.debug("UserManager._signoutStart: loaded current user from storage"); + this._logger.debug("_signoutStart: loaded current user from storage"); if (this.settings.revokeAccessTokenOnSignout) { await this._revokeInternal(user); } await this.removeUser(); - Log.debug("UserManager._signoutStart: user removed, creating signout request"); + this._logger.debug("_signoutStart: user removed, creating signout request"); const signoutRequest = await this._client.createSignoutRequest(args); - Log.debug("UserManager._signoutStart: got signout request"); + this._logger.debug("_signoutStart: got signout request"); return handle.navigate({ url: signoutRequest.url, @@ -466,14 +468,14 @@ export class UserManager { }); } catch (err) { - Log.debug("UserManager._signoutStart: Error after preparing navigator, closing navigator window"); + this._logger.debug("_signoutStart: Error after preparing navigator, closing navigator window"); handle.close(); throw err; } } protected async _signoutEnd(url: string): Promise { const signoutResponse = await this._client.processSignoutResponse(url); - Log.debug("UserManager._signoutEnd: got signout response"); + this._logger.debug("_signoutEnd: got signout response"); return signoutResponse; } @@ -482,7 +484,7 @@ export class UserManager { const user = await this._loadUser(); const success = await this._revokeInternal(user, true); if (success && user) { - Log.debug("UserManager.revokeAccessToken: removing token properties from user and re-storing"); + this._logger.debug("revokeAccessToken: removing token properties from user and re-storing"); user.access_token = ""; user.refresh_token = ""; @@ -490,11 +492,11 @@ export class UserManager { user.token_type = ""; await this.storeUser(user); - Log.debug("UserManager.revokeAccessToken: user stored"); + this._logger.debug("revokeAccessToken: user stored"); this._events.load(user); } - Log.info("UserManager.revokeAccessToken: access token revoked successfully"); + this._logger.info("revokeAccessToken: access token revoked successfully"); } protected async _revokeInternal(user: User | null, required = false): Promise { @@ -505,7 +507,7 @@ export class UserManager { const atSuccess = await this._revokeAccessTokenInternal(access_token, required); const rtSuccess = await this._revokeRefreshTokenInternal(refresh_token, required); if (!atSuccess && !rtSuccess) { - Log.debug("UserManager.revokeAccessToken: no need to revoke due to no token(s), or JWT format"); + this._logger.debug("revokeAccessToken: no need to revoke due to no token(s), or JWT format"); } return atSuccess || rtSuccess; @@ -548,22 +550,22 @@ export class UserManager { protected async _loadUser(): Promise { const storageString = await this.settings.userStore.get(this._userStoreKey); if (storageString) { - Log.debug("UserManager._loadUser: user storageString loaded"); + this._logger.debug("_loadUser: user storageString loaded"); return User.fromStorageString(storageString); } - Log.debug("UserManager._loadUser: no user storageString"); + this._logger.debug("_loadUser: no user storageString"); return null; } public async storeUser(user: User | null): Promise { if (user) { - Log.debug("UserManager.storeUser: storing user"); + this._logger.debug("storeUser: storing user"); const storageString = user.toStorageString(); await this.settings.userStore.set(this._userStoreKey, storageString); } else { - Log.debug("storeUser.storeUser: removing user"); + this._logger.debug("storeUser: removing user"); await this.settings.userStore.remove(this._userStoreKey); } } diff --git a/src/UserManagerEvents.ts b/src/UserManagerEvents.ts index b917d41ec..d1c162e03 100644 --- a/src/UserManagerEvents.ts +++ b/src/UserManagerEvents.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log, Event } from "./utils"; +import { Logger, Event } from "./utils"; import { AccessTokenEvents } from "./AccessTokenEvents"; import type { UserManagerSettingsStore } from "./UserManagerSettings"; import type { User } from "./User"; @@ -26,6 +26,8 @@ export class UserManagerEvents extends AccessTokenEvents { public constructor(settings: UserManagerSettingsStore) { super({ expiringNotificationTimeInSeconds: settings.accessTokenExpiringNotificationTimeInSeconds }); + this._logger = new Logger("UserManagerEvents"); + this._userLoaded = new Event("User loaded"); this._userUnloaded = new Event("User unloaded"); this._silentRenewError = new Event("Silent renew error"); @@ -35,14 +37,14 @@ export class UserManagerEvents extends AccessTokenEvents { } public load(user: User, raiseEvent=true): void { - Log.debug("UserManagerEvents.load"); + this._logger.debug("load"); super.load(user); if (raiseEvent) { this._userLoaded.raise(user); } } public unload(): void { - Log.debug("UserManagerEvents.unload"); + this._logger.debug("unload"); super.unload(); this._userUnloaded.raise(); } @@ -68,7 +70,7 @@ export class UserManagerEvents extends AccessTokenEvents { this._silentRenewError.removeHandler(cb); } public _raiseSilentRenewError(e: Error): void { - Log.debug("UserManagerEvents._raiseSilentRenewError", e.message); + this._logger.debug("UserManagerEvents._raiseSilentRenewError", e.message); this._silentRenewError.raise(e); } @@ -79,7 +81,7 @@ export class UserManagerEvents extends AccessTokenEvents { this._userSignedIn.removeHandler(cb); } public _raiseUserSignedIn(): void { - Log.debug("UserManagerEvents._raiseUserSignedIn"); + this._logger.debug("UserManagerEvents._raiseUserSignedIn"); this._userSignedIn.raise(); } @@ -90,7 +92,7 @@ export class UserManagerEvents extends AccessTokenEvents { this._userSignedOut.removeHandler(cb); } public _raiseUserSignedOut(): void { - Log.debug("UserManagerEvents._raiseUserSignedOut"); + this._logger.debug("UserManagerEvents._raiseUserSignedOut"); this._userSignedOut.raise(); } @@ -101,7 +103,7 @@ export class UserManagerEvents extends AccessTokenEvents { this._userSessionChanged.removeHandler(cb); } public _raiseUserSessionChanged(): void { - Log.debug("UserManagerEvents._raiseUserSessionChanged"); + this._logger.debug("UserManagerEvents._raiseUserSessionChanged"); this._userSessionChanged.raise(); } } diff --git a/src/WebStorageStateStore.ts b/src/WebStorageStateStore.ts index a804ad91f..187c1bcad 100644 --- a/src/WebStorageStateStore.ts +++ b/src/WebStorageStateStore.ts @@ -1,23 +1,27 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log } from "./utils"; +import { Logger } from "./utils"; import type { StateStore } from "./StateStore"; /** * @public */ export class WebStorageStateStore implements StateStore { + private _logger: Logger; + private _store: Storage private _prefix: string public constructor({ prefix = "oidc.", store = localStorage } = {}) { + this._logger = new Logger("WebStorageStateStore"); + this._store = store; this._prefix = prefix; } public set(key: string, value: string): Promise { - Log.debug("WebStorageStateStore.set", key); + this._logger.debug("set", key); key = this._prefix + key; this._store.setItem(key, value); @@ -25,7 +29,7 @@ export class WebStorageStateStore implements StateStore { } public get(key: string): Promise { - Log.debug("WebStorageStateStore.get", key); + this._logger.debug("get", key); key = this._prefix + key; const item = this._store.getItem(key); @@ -33,7 +37,7 @@ export class WebStorageStateStore implements StateStore { } public remove(key: string): Promise { - Log.debug("WebStorageStateStore.remove", key); + this._logger.debug("remove", key); key = this._prefix + key; const item = this._store.getItem(key); @@ -42,7 +46,7 @@ export class WebStorageStateStore implements StateStore { } public getAllKeys(): Promise { - Log.debug("WebStorageStateStore.getAllKeys"); + this._logger.debug("getAllKeys"); const keys = []; for (let index = 0; index < this._store.length; index++) { diff --git a/src/navigators/IFrameNavigator.ts b/src/navigators/IFrameNavigator.ts index 6ae299d1c..162511f20 100644 --- a/src/navigators/IFrameNavigator.ts +++ b/src/navigators/IFrameNavigator.ts @@ -1,13 +1,17 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. +import { Logger } from "../utils"; import type { UserManagerSettingsStore } from "../UserManagerSettings"; -import { Log } from "../utils"; import { IFrameWindow, IFrameWindowParams } from "./IFrameWindow"; import type { INavigator } from "./INavigator"; export class IFrameNavigator implements INavigator { - constructor(private _settings: UserManagerSettingsStore) {} + private readonly _logger: Logger; + + constructor(private _settings: UserManagerSettingsStore) { + this._logger = new Logger("IFrameNavigator"); + } public async prepare({ silentRequestTimeoutInSeconds = this._settings.silentRequestTimeoutInSeconds @@ -16,7 +20,7 @@ export class IFrameNavigator implements INavigator { } public async callback(url: string | undefined): Promise { - Log.debug("IFrameNavigator.callback"); + this._logger.debug("callback"); IFrameWindow.notifyParent(url); } } diff --git a/src/navigators/IFrameWindow.ts b/src/navigators/IFrameWindow.ts index fe33dd904..e6e84915c 100644 --- a/src/navigators/IFrameWindow.ts +++ b/src/navigators/IFrameWindow.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log } from "../utils"; +import { Logger } from "../utils"; import type { IWindow, NavigateParams, NavigateResponse } from "./IWindow"; const defaultTimeoutInSeconds = 10; @@ -11,6 +11,8 @@ export interface IFrameWindowParams { } export class IFrameWindow implements IWindow { + private readonly _logger: Logger; + private _resolve!: (value: NavigateResponse) => void; private _reject!: (reason?: any) => void; private _promise = new Promise((resolve, reject) => { @@ -24,6 +26,8 @@ export class IFrameWindow implements IWindow { public constructor({ silentRequestTimeoutInSeconds = defaultTimeoutInSeconds }: IFrameWindowParams) { + this._logger = new Logger("IFrameWindow"); + this._timeoutInSeconds = silentRequestTimeoutInSeconds; window.addEventListener("message", this._message, false); @@ -48,7 +52,7 @@ export class IFrameWindow implements IWindow { this._error("No _frame, already closed"); } else { - Log.debug("IFrameWindow.navigate: Using timeout of:", this._timeoutInSeconds); + this._logger.debug("navigate: Using timeout of:", this._timeoutInSeconds); this._timer = window.setTimeout(this._timeout, this._timeoutInSeconds * 1000); this._frame.src = params.url; } @@ -59,13 +63,13 @@ export class IFrameWindow implements IWindow { protected _success(data: NavigateResponse): void { this._cleanup(); - Log.debug("IFrameWindow: Successful response from frame window"); + this._logger.debug("Successful response from frame window"); this._resolve(data); } protected _error(message: string): void { this._cleanup(); - Log.error(message); + this._logger.error(message); this._reject(new Error(message)); } @@ -74,7 +78,7 @@ export class IFrameWindow implements IWindow { } protected _cleanup(): void { - Log.debug("IFrameWindow: cleanup"); + this._logger.debug("_cleanup"); if (this._timer != null) { window.clearTimeout(this._timer); } @@ -88,12 +92,12 @@ export class IFrameWindow implements IWindow { } protected _timeout = (): void => { - Log.debug("IFrameWindow.timeout"); + this._logger.debug("_timeout"); this._error("Frame window timed out"); } protected _message = (e: MessageEvent): void => { - Log.debug("IFrameWindow.message"); + this._logger.debug("_message"); const origin = location.protocol + "//" + location.host; if (this._timer && this._frame && @@ -112,10 +116,10 @@ export class IFrameWindow implements IWindow { } public static notifyParent(url: string | undefined): void { - Log.debug("IFrameWindow.notifyParent"); + Logger.debug("IFrameWindow", "notifyParent"); url = url || window.location.href; if (url) { - Log.debug("IFrameWindow.notifyParent: posting url message to parent"); + Logger.debug("IFrameWindow", "notifyParent: posting url message to parent"); window.parent.postMessage(url, location.protocol + "//" + location.host); } } diff --git a/src/navigators/PopupNavigator.ts b/src/navigators/PopupNavigator.ts index 34dfc4a1a..8a64fcfd3 100644 --- a/src/navigators/PopupNavigator.ts +++ b/src/navigators/PopupNavigator.ts @@ -1,13 +1,17 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log } from "../utils"; +import { Logger } from "../utils"; import { PopupWindow, PopupWindowParams } from "./PopupWindow"; import type { INavigator } from "./INavigator"; import type { UserManagerSettingsStore } from "../UserManagerSettings"; export class PopupNavigator implements INavigator { - constructor(private _settings: UserManagerSettingsStore) {} + private readonly _logger: Logger; + + constructor(private _settings: UserManagerSettingsStore) { + this._logger = new Logger("PopupNavigator"); + } public async prepare({ popupWindowFeatures = this._settings.popupWindowFeatures, @@ -17,7 +21,7 @@ export class PopupNavigator implements INavigator { } public async callback(url: string | undefined, keepOpen: boolean, delimiter: string): Promise { - Log.debug("PopupNavigator.callback"); + this._logger.debug("PopupNavigator.callback"); PopupWindow.notifyOpener(url, keepOpen, delimiter); } diff --git a/src/navigators/PopupWindow.ts b/src/navigators/PopupWindow.ts index 32b0d97fc..65615e279 100644 --- a/src/navigators/PopupWindow.ts +++ b/src/navigators/PopupWindow.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log, UrlUtils } from "../utils"; +import { Logger, UrlUtils } from "../utils"; import type { IWindow, NavigateParams, NavigateResponse } from "./IWindow"; const checkForPopupClosedInterval = 500; @@ -15,6 +15,8 @@ export interface PopupWindowParams { } export class PopupWindow implements IWindow { + private readonly _logger: Logger; + private _resolve!: (value: NavigateResponse) => void; private _reject!: (reason?: any) => void; private _promise = new Promise((resolve, reject) => { @@ -29,9 +31,11 @@ export class PopupWindow implements IWindow { popupWindowTarget = defaultPopupTarget, popupWindowFeatures = defaultPopupFeatures }: PopupWindowParams) { + this._logger = new Logger("PopupWindow"); + this._popup = window.open("", popupWindowTarget, popupWindowFeatures); if (this._popup) { - Log.debug("PopupWindow.ctor: popup successfully created"); + this._logger.debug("ctor: popup successfully created"); this._checkForPopupClosedTimer = window.setInterval(this._checkForPopupClosed, checkForPopupClosedInterval); } } @@ -45,7 +49,7 @@ export class PopupWindow implements IWindow { this._error("No url provided"); } else { - Log.debug("PopupWindow.navigate: Setting URL in popup"); + this._logger.debug("navigate: Setting URL in popup"); this._id = params.id; if (this._id) { @@ -62,7 +66,7 @@ export class PopupWindow implements IWindow { protected _messageReceived = (event: MessageEvent): void => { if (event.origin !== window.location.origin) { - Log.warn("PopupWindow:_messageReceived: Message not coming from same origin: " + event.origin); + this._logger.warn("_messageReceived: Message not coming from same origin: " + event.origin); return; } @@ -73,27 +77,27 @@ export class PopupWindow implements IWindow { // @ts-ignore const callback = window["popupCallback_" + data.state]; if (callback) { - Log.debug("PopupWindow._messageReceived: passing url message to opener"); + this._logger.debug("_messageReceived: passing url message to opener"); callback(url, keepOpen); } else { - Log.warn("PopupWindow._messageReceived: no matching callback found on opener"); + this._logger.warn("_messageReceived: no matching callback found on opener"); } } else { - Log.warn("PopupWindow._messageReceived: no state found in response url"); + this._logger.warn("_messageReceived: no state found in response url"); } } protected _success(data: NavigateResponse): void { - Log.debug("PopupWindow.callback: Successful response from popup window"); + this._logger.debug("callback: Successful response from popup window"); this._cleanup(); this._resolve(data); } protected _error(message: string): void { - Log.error("PopupWindow.error: ", message); + this._logger.error("_error", message); this._cleanup(); this._reject(new Error(message)); @@ -104,7 +108,7 @@ export class PopupWindow implements IWindow { } protected _cleanup(keepOpen?: boolean): void { - Log.debug("PopupWindow.cleanup"); + this._logger.debug("cleanup"); if (this._checkForPopupClosedTimer) { window.clearInterval(this._checkForPopupClosedTimer); @@ -134,11 +138,11 @@ export class PopupWindow implements IWindow { this._cleanup(keepOpen); if (url) { - Log.debug("PopupWindow.callback success"); + this._logger.debug("callback success"); this._success({ url: url }); } else { - Log.debug("PopupWindow.callback: Invalid response from popup"); + this._logger.debug("callback: Invalid response from popup"); this._error("Invalid response from popup"); } } @@ -157,7 +161,7 @@ export class PopupWindow implements IWindow { } } else { - Log.warn("PopupWindow.notifyOpener: no window.opener. Can't complete notification."); + Logger.warn("PopupWindow", "notifyOpener: no window.opener. Can't complete notification."); } } } diff --git a/src/navigators/RedirectNavigator.ts b/src/navigators/RedirectNavigator.ts index a4a859a51..d41d42793 100644 --- a/src/navigators/RedirectNavigator.ts +++ b/src/navigators/RedirectNavigator.ts @@ -1,8 +1,8 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. +import { Logger } from "../utils"; import type { UserManagerSettingsStore } from "../UserManagerSettings"; -import { Log } from "../utils"; import type { INavigator } from "./INavigator"; import type { IWindow, NavigateParams, NavigateResponse } from "./IWindow"; @@ -11,9 +11,12 @@ export interface RedirectParams { } export class RedirectNavigator implements INavigator, IWindow { + private readonly _logger: Logger; private _redirectMethod: "replace" | "assign" | undefined; - constructor(private _settings: UserManagerSettingsStore) {} + constructor(private _settings: UserManagerSettingsStore) { + this._logger = new Logger("RedirectNavigator"); + } public async prepare({ redirectMethod }: RedirectParams): Promise { this._redirectMethod = redirectMethod ?? this._settings.redirectMethod; @@ -22,7 +25,7 @@ export class RedirectNavigator implements INavigator, IWindow { public async navigate(params: NavigateParams): Promise { if (!params || !params.url) { - Log.error("RedirectNavigator.navigate: No url provided"); + this._logger.error("navigate: No url provided"); throw new Error("No url provided"); } @@ -31,6 +34,6 @@ export class RedirectNavigator implements INavigator, IWindow { } public close(): void { - Log.warn("RedirectNavigator cannot close the current window"); + this._logger.warn("cannot close the current window"); } } diff --git a/src/utils/CryptoUtils.ts b/src/utils/CryptoUtils.ts index a40fef76a..cc407b99e 100644 --- a/src/utils/CryptoUtils.ts +++ b/src/utils/CryptoUtils.ts @@ -1,7 +1,7 @@ import sha256 from "crypto-js/sha256"; import Base64 from "crypto-js/enc-base64"; -import { Log } from "./Log"; +import { Logger } from "./Log"; const UUID_V4_TEMPLATE = "10000000-1000-4000-8000-100000000000"; @@ -46,7 +46,7 @@ export class CryptoUtils { return Base64.stringify(hashed).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); } catch (err) { - Log.error(err); + Logger.error("CryptoUtils", err instanceof Error ? err.message : err); throw err; } } diff --git a/src/utils/Event.ts b/src/utils/Event.ts index 5e3288892..81fcf40ce 100644 --- a/src/utils/Event.ts +++ b/src/utils/Event.ts @@ -1,14 +1,18 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log } from "./Log"; +import { Logger } from "./Log"; type Callback = (...ev: EventType) => (Promise | void); export class Event { + protected readonly _logger: Logger; + private _callbacks: Array> = []; - public constructor(protected _name: string) {} + public constructor(protected _name: string) { + this._logger = new Logger("Event"); + } public addHandler(cb: Callback): void { this._callbacks.push(cb); @@ -22,7 +26,7 @@ export class Event { } public raise(...ev: EventType): void { - Log.debug("Event: Raising event: " + this._name); + this._logger.debug("Raising event: " + this._name); for (let i = 0; i < this._callbacks.length; i++) { void this._callbacks[i](...ev); } diff --git a/src/utils/Log.ts b/src/utils/Log.ts index ee456580c..641b5ae11 100644 --- a/src/utils/Log.ts +++ b/src/utils/Log.ts @@ -1,14 +1,19 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -export interface Logger { +/** + * Native interface + * + * @public + */ +export interface ILogger { debug(...args: any[]): void; info(...args: any[]): void; warn(...args: any[]): void; error(...args: any[]): void; } -const nopLogger: Logger = { +const nopLogger: ILogger = { debug: () => undefined, info: () => undefined, warn: () => undefined, @@ -21,10 +26,9 @@ const WARN = 2; const INFO = 3; const DEBUG = 4; -let logger: Logger; -let level: number; - /** + * Log manager + * * @public */ export class Log { @@ -34,47 +38,75 @@ export class Log { public static get INFO(): number {return INFO;} public static get DEBUG(): number {return DEBUG;} + private static _level: number; + private static _logger: ILogger; + public static reset(): void { - level = INFO; - logger = nopLogger; + this._level = INFO; + this._logger = nopLogger; } public static get level(): number { - return level; + return this._level; } public static set level(value: number) { if (NONE > value || value > DEBUG) { throw new Error("Invalid log level"); } - level = value; + this._level = value; } - public static get logger(): Logger { - return logger; + // native logger + public static get logger(): ILogger { + return this._logger; + } + public static set logger(value: ILogger) { + this._logger = value; + } +} + +/** + * Internal logger instance + */ +export class Logger { + private readonly _name: string; + public constructor(name: string) { + this._name = name; + } + + public debug(...args: any[]): void { + Logger.debug(this._name, ...args); + } + public info(...args: any[]): void { + Logger.info(this._name, ...args); + } + public warn(...args: any[]): void { + Logger.warn(this._name, ...args); } - public static set logger(value: Logger) { - logger = value; + public error(...args: any[]): void { + Logger.error(this._name, ...args); } - public static debug(...args: any[]): void { - if (level >= DEBUG) { - logger.debug(...args); + // helpers for static class methods + public static debug(name: string, ...args: any[]): void { + if (Log.level >= DEBUG) { + Log.logger.debug(`[${name}]`, ...args); } } - public static info(...args: any[]): void { - if (level >= INFO) { - logger.info(...args); + public static info(name: string,...args: any[]): void { + if (Log.level >= INFO) { + Log.logger.info(`[${name}]`, ...args); } } - public static warn(...args: any[]): void { - if (level >= WARN) { - logger.warn(...args); + public static warn(name: string,...args: any[]): void { + if (Log.level >= WARN) { + Log.logger.warn(`[${name}]`, ...args); } } - public static error(...args: any[]): void { - if (level >= ERROR) { - logger.error(...args); + public static error(name: string,...args: any[]): void { + if (Log.level >= ERROR) { + Log.logger.error(`[${name}]`, ...args); } } } diff --git a/src/utils/Timer.ts b/src/utils/Timer.ts index 94dff1b6c..04a73c7f1 100644 --- a/src/utils/Timer.ts +++ b/src/utils/Timer.ts @@ -1,7 +1,6 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log } from "./Log"; import { Event } from "./Event"; const DefaultTimerDurationInSeconds = 5; // seconds @@ -38,13 +37,13 @@ export class Timer extends Event<[void]> { const expiration = Timer.getEpochTime() + durationInSeconds; if (this.expiration === expiration && this._timerHandle) { // no need to reinitialize to same expiration, so bail out - Log.debug("Timer.init timer " + this._name + " skipping initialization since already initialized for expiration:", this.expiration); + this._logger.debug("init timer " + this._name + " skipping initialization since already initialized for expiration:", this.expiration); return; } this.cancel(); - Log.debug("Timer.init timer " + this._name + " for duration:", durationInSeconds); + this._logger.debug("init timer " + this._name + " for duration:", durationInSeconds); this._expiration = expiration; // we're using a fairly short timer and then checking the expiration in the @@ -63,7 +62,7 @@ export class Timer extends Event<[void]> { public cancel(): void { if (this._timerHandle) { - Log.debug("Timer.cancel: ", this._name); + this._logger.debug("cancel: ", this._name); this._timer.clearInterval(this._timerHandle); this._timerHandle = null; } @@ -71,7 +70,7 @@ export class Timer extends Event<[void]> { protected _callback = (): void => { const diff = this._expiration - Timer.getEpochTime(); - Log.debug("Timer.callback; " + this._name + " timer expires in:", diff); + this._logger.debug("callback; " + this._name + " timer expires in:", diff); if (this._expiration <= Timer.getEpochTime()) { this.cancel(); diff --git a/src/utils/UrlUtils.ts b/src/utils/UrlUtils.ts index 81bbb07f3..9fb33237e 100644 --- a/src/utils/UrlUtils.ts +++ b/src/utils/UrlUtils.ts @@ -1,7 +1,7 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log } from "./Log"; +import { Logger } from "./Log"; export class UrlUtils { public static addQueryParam(url: string, name: string, value: string | number | boolean): string { @@ -46,7 +46,7 @@ export class UrlUtils { while ((m = regex.exec(value)) !== null) { params[decodeURIComponent(m[1])] = decodeURIComponent(m[2].replace(/\+/g, " ")); if (counter++ > 50) { - Log.error("UrlUtils.parseUrlFragment: response exceeded expected number of parameters", value); + Logger.error("UrlUtils", "parseUrlFragment: response exceeded expected number of parameters", value); return { error: "Response exceeded expected number of parameters" }; diff --git a/test/unit/UserManager.test.ts b/test/unit/UserManager.test.ts index a27ba8a10..a4f92692e 100644 --- a/test/unit/UserManager.test.ts +++ b/test/unit/UserManager.test.ts @@ -61,7 +61,6 @@ describe("UserManager", () => { // lamda function is never called but complier thinks it returns Promise // eslint-disable-next-line @typescript-eslint/no-misused-promises subject.events.addUserLoaded(async (user) => { - Log.debug("event.load", user); await subject.getUser(); }); @@ -91,7 +90,6 @@ describe("UserManager", () => { let navInstance: any = null; subject["_signin"] = async function (args: any, handle: IWindow) { - Log.debug("_signin", args, handle); navInstance = handle; return user; }; @@ -120,7 +118,6 @@ describe("UserManager", () => { let navInstance: any = null; subject["_signin"] = async function (args: any, handle: IWindow) { - Log.debug("_signin", args, handle); navInstance = handle; return user; }; @@ -146,7 +143,6 @@ describe("UserManager", () => { subject = new UserManager(settings); subject["_signin"] = async function (args: any, handle: IWindow) { - Log.debug("_signin", args, handle); return user; }; diff --git a/test/unit/utils/Log.test.ts b/test/unit/utils/Log.test.ts index 17145fce6..d73c84e0a 100644 --- a/test/unit/utils/Log.test.ts +++ b/test/unit/utils/Log.test.ts @@ -1,12 +1,15 @@ // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -import { Log, Logger } from "../../../src/utils"; +import { Log, ILogger, Logger } from "../../../src/utils"; describe("Log", () => { + let subject: Logger; + beforeEach(() => { Log.reset(); Log.level = Log.INFO; + subject = new Logger("name"); }); describe("level", () => { @@ -18,9 +21,9 @@ describe("Log", () => { Log.level = Log.NONE; // act - Log.info("test info"); - Log.warn("test warn"); - Log.error("test error"); + subject.info("test info"); + subject.warn("test warn"); + subject.error("test error"); // assert expect(stub.infoWasCalled).toEqual(false); @@ -35,9 +38,9 @@ describe("Log", () => { Log.level = Log.ERROR; // act - Log.info("test info"); - Log.warn("test warn"); - Log.error("test error"); + subject.info("test info"); + subject.warn("test warn"); + subject.error("test error"); // assert expect(stub.infoWasCalled).toEqual(false); @@ -52,9 +55,9 @@ describe("Log", () => { Log.level = Log.WARN; // act - Log.info("test info"); - Log.warn("test warn"); - Log.error("test error"); + subject.info("test info"); + subject.warn("test warn"); + subject.error("test error"); // assert expect(stub.infoWasCalled).toEqual(false); @@ -69,9 +72,9 @@ describe("Log", () => { Log.level = Log.INFO; // act - Log.info("test info"); - Log.warn("test warn"); - Log.error("test error"); + subject.info("test info"); + subject.warn("test warn"); + subject.error("test error"); // assert expect(stub.infoWasCalled).toEqual(true); @@ -88,28 +91,28 @@ describe("Log", () => { Log.logger = stub; // act - Log.info("test info"); - Log.warn("test warn"); - Log.error("test error"); + subject.info("test info"); + subject.warn("test warn"); + subject.error("test error"); // assert - expect(stub.infoParam).toEqual("test info"); - expect(stub.warnParam).toEqual("test warn"); - expect(stub.errorParam).toEqual("test error"); + expect(stub.infoParam).toEqual("[name] test info"); + expect(stub.warnParam).toEqual("[name] test warn"); + expect(stub.errorParam).toEqual("[name] test error"); }); }); describe("info", () => { it("should work with no config", () => { - Log.info("test"); + subject.info("test"); }); }); describe("warn", () => { it("should work with no config", () => { - Log.warn("test"); + subject.warn("test"); }); }); @@ -117,12 +120,12 @@ describe("Log", () => { describe("error", () => { it("should work with no config", () => { - Log.error("test"); + subject.error("test"); }); }); }); -class StubLog implements Logger { +class StubLog implements ILogger { debugWasCalled: boolean; infoWasCalled: boolean; warnWasCalled: boolean; @@ -138,20 +141,20 @@ class StubLog implements Logger { this.warnWasCalled = false; this.errorWasCalled = false; } - debug(arg: any) { - this.debugParam = arg; + debug(...args: any[]) { + this.debugParam = args.join(" "); this.debugWasCalled = true; } - info(arg: any) { - this.infoParam = arg; + info(...args: any[]) { + this.infoParam = args.join(" "); this.infoWasCalled = true; } - warn(arg: any) { - this.warnParam = arg; + warn(...args: any[]) { + this.warnParam = args.join(" "); this.warnWasCalled = true; } - error(arg: any) { - this.errorParam = arg; + error(...args: any[]) { + this.errorParam = args.join(" "); this.errorWasCalled = true; } }