From 1129ee722578544af555d1308c4f0ba4e19eede0 Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Mon, 1 Jan 2024 16:29:36 +0530 Subject: [PATCH 01/18] Add logger --- lib/ts/logger.ts | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 lib/ts/logger.ts diff --git a/lib/ts/logger.ts b/lib/ts/logger.ts new file mode 100644 index 0000000..07be0c9 --- /dev/null +++ b/lib/ts/logger.ts @@ -0,0 +1,35 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { package_version as version } from "./version"; + +const SUPERTOKENS_DEBUG_NAMESPACE = "com.supertokens"; + +let __supertokensWebsiteLogging = false; + +export function enableLogging() { + __supertokensWebsiteLogging = true; +} +export function disableLogging() { + __supertokensWebsiteLogging = false; +} + +export function logDebugMessage(message: string) { + if (__supertokensWebsiteLogging) { + console.log( + `${SUPERTOKENS_DEBUG_NAMESPACE} {t: "${new Date().toISOString()}", message: \"${message}\", supertokens-website-ver: "${version}"}` + ); + } +} From dd2da34212912ef97071adb735c1d3bfe7c8ca9c Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Mon, 1 Jan 2024 16:42:19 +0530 Subject: [PATCH 02/18] Add option to enable logging --- lib/ts/types.ts | 1 + lib/ts/utils.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/lib/ts/types.ts b/lib/ts/types.ts index 8c35449..761f071 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -27,6 +27,7 @@ export type Event = export type EventHandler = (event: Event) => void; export type InputType = { + enableDebugLogs?: boolean; apiDomain: string; apiBasePath?: string; sessionExpiredStatusCode?: number; diff --git a/lib/ts/utils.ts b/lib/ts/utils.ts index 2001953..4e87a53 100644 --- a/lib/ts/utils.ts +++ b/lib/ts/utils.ts @@ -5,6 +5,7 @@ import FrontToken from "./frontToken"; import NormalisedURLDomain from "./normalisedURLDomain"; import NormalisedURLPath from "./normalisedURLPath"; import { InputType, NormalisedInputType, EventHandler, RecipeInterface, TokenType } from "./types"; +import { enableLogging } from "./logger"; const LAST_ACCESS_TOKEN_UPDATE = "st-last-access-token-update"; const REFRESH_TOKEN_NAME = "st-refresh-token"; @@ -112,6 +113,10 @@ export function validateAndNormaliseInputOrThrowError(options: InputType): Norma onHandleEvent = options.onHandleEvent; } + if (options.enableDebugLogs !== undefined && options.enableDebugLogs) { + enableLogging(); + } + let override: { functions: (originalImplementation: RecipeInterface) => RecipeInterface; } = { From 2f61f701030688ea74774ba138c7d512c94c4325 Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Tue, 2 Jan 2024 12:30:36 +0530 Subject: [PATCH 03/18] Add debug logs for fetch --- lib/ts/fetch.ts | 51 +++++++++++++++++++++++++++++++++++++++++++++++- lib/ts/logger.ts | 3 --- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/lib/ts/fetch.ts b/lib/ts/fetch.ts index d9d028a..06b8cca 100644 --- a/lib/ts/fetch.ts +++ b/lib/ts/fetch.ts @@ -29,6 +29,7 @@ import { import FrontToken from "./frontToken"; import RecipeImplementation from "./recipeImplementation"; import OverrideableBuilder from "supertokens-js-override"; +import { logDebugMessage } from "./logger"; declare let global: any; @@ -47,6 +48,13 @@ export default class AuthHttpRequest { static init(options: InputType) { let config = validateAndNormaliseInputOrThrowError(options); + logDebugMessage("init: called"); + logDebugMessage("init: Input apiBasePath: " + config.apiBasePath); + logDebugMessage("init: Input apiDomain: " + config.apiDomain); + logDebugMessage("init: Input autoAddCredentials: " + config.autoAddCredentials); + logDebugMessage("init: Input sessionTokenBackendDomain: " + config.sessionTokenBackendDomain); + logDebugMessage("init: Input sessionExpiredStatusCode: " + config.sessionExpiredStatusCode); + logDebugMessage("init: Input tokenTransferMethod: " + config.tokenTransferMethod); AuthHttpRequest.env = global; AuthHttpRequest.refreshTokenUrl = config.apiDomain + config.apiBasePath + "/session/refresh"; @@ -55,6 +63,7 @@ export default class AuthHttpRequest { AuthHttpRequest.config = config; if (AuthHttpRequest.env.__supertokensOriginalFetch === undefined) { + logDebugMessage("init: __supertokensOriginalFetch is undefined"); // this block contains code that is run just once per page load.. // all items in this block are attached to the global env so that // even if the init function is called more than once (maybe across JS scripts), @@ -91,6 +100,7 @@ export default class AuthHttpRequest { throw Error("init function not called"); } + logDebugMessage("doRequest: start of fetch interception"); let doNotDoInterception = false; try { @@ -114,7 +124,9 @@ export default class AuthHttpRequest { throw err; } + logDebugMessage("doRequest: Value of doNotDoInterception: " + doNotDoInterception); if (doNotDoInterception) { + logDebugMessage("doRequest: Returning without interception"); return await httpCall(config); } @@ -135,10 +147,14 @@ export default class AuthHttpRequest { // If we do not ignore this, then this header would be used even if the request is being retried after a refresh, even though it contains an outdated access token. // This causes an infinite refresh loop. + logDebugMessage( + "doRequest: Removing Authorization from user provided headers because it contains our access token" + ); originalHeaders.delete("Authorization"); } } + logDebugMessage("doRequest: Interception started"); ProcessState.getInstance().addState(PROCESS_STATE.CALLING_INTERCEPTION_REQUEST); try { @@ -157,11 +173,13 @@ export default class AuthHttpRequest { if (preRequestLocalSessionState.status === "EXISTS") { const antiCsrfToken = await AntiCSRF.getToken(preRequestLocalSessionState.lastAccessTokenUpdate); if (antiCsrfToken !== undefined) { + logDebugMessage("doRequest: Adding anti-csrf token to request"); clonedHeaders.set("anti-csrf", antiCsrfToken); } } if (AuthHttpRequest.config.autoAddCredentials) { + logDebugMessage("doRequest: Adding credentials include"); if (configWithAntiCsrf === undefined) { configWithAntiCsrf = { credentials: "include" @@ -176,15 +194,21 @@ export default class AuthHttpRequest { // adding rid for anti-csrf protection: Anti-csrf via custom header if (!clonedHeaders.has("rid")) { + logDebugMessage("doRequest: Adding rid header: anti-csrf"); clonedHeaders.set("rid", "anti-csrf"); + } else { + logDebugMessage("doRequest: rid header was already there in request"); } const transferMethod = AuthHttpRequest.config.tokenTransferMethod; + logDebugMessage("doRequest: Adding st-auth-mode header: " + transferMethod); clonedHeaders.set("st-auth-mode", transferMethod); await setAuthorizationHeaderIfRequired(clonedHeaders); + logDebugMessage("doRequest: Making user's http call"); let response = await httpCall(configWithAntiCsrf); + logDebugMessage("doRequest: User's http call ended"); await saveTokensFromHeaders(response); @@ -195,8 +219,10 @@ export default class AuthHttpRequest { ); if (response.status === AuthHttpRequest.config.sessionExpiredStatusCode) { + logDebugMessage("doRequest: Status code is: " + response.status); let refreshResponse = await onUnauthorisedResponse(preRequestLocalSessionState); if (refreshResponse.result !== "RETRY") { + logDebugMessage("doRequest: Not retrying original request"); returnObj = refreshResponse.error !== undefined ? refreshResponse.error : response; break; } @@ -212,6 +238,7 @@ export default class AuthHttpRequest { // or the backend is down and we don't need to call it. const postRequestLocalSessionState = await getLocalSessionState(); if (postRequestLocalSessionState.status === "NOT_EXISTS") { + logDebugMessage("doRequest: local session doesn't exist, so removing anti-csrf and sFrontToken"); await AntiCSRF.removeToken(); await FrontToken.removeToken(); } @@ -240,11 +267,14 @@ export async function onUnauthorisedResponse( preRequestLocalSessionState: LocalSessionState ): Promise<{ result: "SESSION_EXPIRED"; error?: any } | { result: "API_ERROR"; error: any } | { result: "RETRY" }> { let lock = getLock(); + logDebugMessage("onUnauthorisedResponse: trying to acquire lock"); await lock.lock(LOCK_NAME); + logDebugMessage("onUnauthorisedResponse: lock acquired"); try { let postLockLocalSessionState = await getLocalSessionState(); if (postLockLocalSessionState.status === "NOT_EXISTS") { + logDebugMessage("onUnauthorisedResponse: Not refreshing because local session state is NOT_EXISTS"); // if it comes here, it means a request was made thinking // that the session exists, but it doesn't actually exist. AuthHttpRequest.config.onHandleEvent({ @@ -260,6 +290,9 @@ export async function onUnauthorisedResponse( preRequestLocalSessionState.status === "EXISTS" && postLockLocalSessionState.lastAccessTokenUpdate !== preRequestLocalSessionState.lastAccessTokenUpdate) ) { + logDebugMessage( + "onUnauthorisedResponse: Retrying early because pre and post id refresh tokens don't match" + ); // means that some other process has already called this API and succeeded. so we need to call it again return { result: "RETRY" }; } @@ -269,18 +302,22 @@ export async function onUnauthorisedResponse( if (preRequestLocalSessionState.status === "EXISTS") { const antiCsrfToken = await AntiCSRF.getToken(preRequestLocalSessionState.lastAccessTokenUpdate); if (antiCsrfToken !== undefined) { + logDebugMessage("onUnauthorisedResponse: Adding anti-csrf token to refresh API call"); headers.set("anti-csrf", antiCsrfToken); } } + logDebugMessage("onUnauthorisedResponse: Adding rid and fdi-versions to refresh call header"); headers.set("rid", AuthHttpRequest.rid); headers.set("fdi-version", supported_fdi.join(",")); const transferMethod = AuthHttpRequest.config.tokenTransferMethod; + logDebugMessage("onUnauthorisedResponse: Adding st-auth-mode header: " + transferMethod); headers.set("st-auth-mode", transferMethod); await setAuthorizationHeaderIfRequired(headers, true); + logDebugMessage("onUnauthorisedResponse: Calling refresh pre API hook"); let preAPIResult = await AuthHttpRequest.config.preAPIHook({ action: "REFRESH_SESSION", requestInit: { @@ -290,14 +327,19 @@ export async function onUnauthorisedResponse( }, url: AuthHttpRequest.refreshTokenUrl }); + logDebugMessage("onUnauthorisedResponse: Making refresh call"); const response = await AuthHttpRequest.env.__supertokensOriginalFetch( preAPIResult.url, preAPIResult.requestInit ); + logDebugMessage("onUnauthorisedResponse: Refresh call ended"); + await saveTokensFromHeaders(response); + logDebugMessage("onUnauthorisedResponse: Refresh status code is: " + response.status); + const isUnauthorised = response.status === AuthHttpRequest.config.sessionExpiredStatusCode; // There is a case where the FE thinks the session is valid, but backend doesn't get the tokens. @@ -319,6 +361,7 @@ export async function onUnauthorisedResponse( } if ((await getLocalSessionState()).status === "NOT_EXISTS") { + logDebugMessage("onUnauthorisedResponse: local session doesn't exist, so returning session expired"); // The execution should never come here.. but just in case. // removed by server. So we logout @@ -332,9 +375,11 @@ export async function onUnauthorisedResponse( AuthHttpRequest.config.onHandleEvent({ action: "REFRESH_SESSION" }); + logDebugMessage("onUnauthorisedResponse: Sending RETRY signal"); return { result: "RETRY" }; } catch (error) { if ((await getLocalSessionState()).status === "NOT_EXISTS") { + logDebugMessage("onUnauthorisedResponse: local session doesn't exist, so returning session expired"); // removed by server. // we do not send "UNAUTHORISED" event here because @@ -343,12 +388,16 @@ export async function onUnauthorisedResponse( // in the first place. return { result: "SESSION_EXPIRED", error }; } - + logDebugMessage("onUnauthorisedResponse: sending API_ERROR"); return { result: "API_ERROR", error }; } finally { lock.unlock(LOCK_NAME); + logDebugMessage("onUnauthorisedResponse: Released lock"); if ((await getLocalSessionState()).status === "NOT_EXISTS") { + logDebugMessage( + "onUnauthorisedResponse: local session doesn't exist, so removing anti-csrf and sFrontToken" + ); await AntiCSRF.removeToken(); await FrontToken.removeToken(); } diff --git a/lib/ts/logger.ts b/lib/ts/logger.ts index 07be0c9..bddcf5e 100644 --- a/lib/ts/logger.ts +++ b/lib/ts/logger.ts @@ -22,9 +22,6 @@ let __supertokensWebsiteLogging = false; export function enableLogging() { __supertokensWebsiteLogging = true; } -export function disableLogging() { - __supertokensWebsiteLogging = false; -} export function logDebugMessage(message: string) { if (__supertokensWebsiteLogging) { From cf1d6685387d37cf9de8a0b366412fe14c203481 Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Tue, 2 Jan 2024 12:36:04 +0530 Subject: [PATCH 04/18] Add debug logs for remaining fetch helpers --- lib/ts/fetch.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/ts/fetch.ts b/lib/ts/fetch.ts index 06b8cca..eeee0b6 100644 --- a/lib/ts/fetch.ts +++ b/lib/ts/fetch.ts @@ -405,30 +405,36 @@ export async function onUnauthorisedResponse( } async function saveTokensFromHeaders(response: Response) { + logDebugMessage("saveTokensFromHeaders: Saving updated tokens from the response headers"); const refreshToken = response.headers.get("st-refresh-token"); if (refreshToken !== undefined && refreshToken !== null) { + logDebugMessage("saveTokensFromHeaders: saving new refresh token"); await setToken("refresh", refreshToken); } const accessToken = response.headers.get("st-access-token"); if (accessToken !== undefined && accessToken !== null) { + logDebugMessage("saveTokensFromHeaders: saving new access token"); await setToken("access", accessToken); } const frontToken = response.headers.get("front-token"); if (frontToken !== undefined && frontToken !== null) { + logDebugMessage("saveTokensFromHeaders: Setting sFrontToken: " + frontToken); await FrontToken.setItem(frontToken); } const antiCsrfToken = response.headers.get("anti-csrf"); if (antiCsrfToken !== undefined && antiCsrfToken !== null) { const tok = await getLocalSessionState(); if (tok.status === "EXISTS") { + logDebugMessage("saveTokensFromHeaders: Setting anti-csrf token"); await AntiCSRF.setItem(tok.lastAccessTokenUpdate, antiCsrfToken); } } } async function setAuthorizationHeaderIfRequired(clonedHeaders: Headers, addRefreshToken: boolean = false) { + logDebugMessage("setTokenHeaders: adding existing tokens as header"); // We set the Authorization header even if the tokenTransferMethod preference set in the config is cookies // since the active session may be using cookies. By default, we want to allow users to continue these sessions. // The new session preference should be applied at the start of the next session, if the backend allows it. @@ -442,8 +448,12 @@ async function setAuthorizationHeaderIfRequired(clonedHeaders: Headers, addRefre if (accessToken !== undefined && refreshToken !== undefined) { // the Headers class normalizes header names so we don't have to worry about casing if (clonedHeaders.has("Authorization")) { + logDebugMessage("setAuthorizationHeaderIfRequired: Authorization header defined by the user, not adding"); } else { clonedHeaders.set("Authorization", `Bearer ${addRefreshToken ? refreshToken : accessToken}`); + logDebugMessage("setAuthorizationHeaderIfRequired: added authorization header"); } + } else { + logDebugMessage("setAuthorizationHeaderIfRequired: token for header based auth not found"); } } From aa6fea3de691f80add3944b901a719efd5bb54ee Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Tue, 2 Jan 2024 13:25:58 +0530 Subject: [PATCH 05/18] Add debug logs to frontToken --- lib/ts/fetch.ts | 2 -- lib/ts/frontToken.ts | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/ts/fetch.ts b/lib/ts/fetch.ts index eeee0b6..cec00fc 100644 --- a/lib/ts/fetch.ts +++ b/lib/ts/fetch.ts @@ -335,9 +335,7 @@ export async function onUnauthorisedResponse( ); logDebugMessage("onUnauthorisedResponse: Refresh call ended"); - await saveTokensFromHeaders(response); - logDebugMessage("onUnauthorisedResponse: Refresh status code is: " + response.status); const isUnauthorised = response.status === AuthHttpRequest.config.sessionExpiredStatusCode; diff --git a/lib/ts/frontToken.ts b/lib/ts/frontToken.ts index 487c544..0de1d72 100644 --- a/lib/ts/frontToken.ts +++ b/lib/ts/frontToken.ts @@ -2,6 +2,7 @@ import AsyncStorage from "@react-native-async-storage/async-storage"; import AuthHttpRequest from "./fetch"; import { decode as atob } from "base-64"; import { getLocalSessionState, saveLastAccessTokenUpdate, setToken } from "./utils"; +import { logDebugMessage } from "./logger"; const FRONT_TOKEN_KEY = "supertokens-rn-front-token-key"; const FRONT_TOKEN_NAME = "sFrontToken"; @@ -14,6 +15,7 @@ export default class FrontToken { private constructor() {} private static async getFrontTokenFromStorage(): Promise { + logDebugMessage("getFrontTokenFromStorage: called"); let frontTokenFromStorage = await AsyncStorage.getItem(FRONT_TOKEN_KEY); if (frontTokenFromStorage !== null) { @@ -53,11 +55,14 @@ export default class FrontToken { } static async getFrontToken(): Promise { + logDebugMessage("getFrontToken: called"); if ((await getLocalSessionState()).status !== "EXISTS") { + logDebugMessage("getFrontToken: Returning because sIRTFrontend != EXISTS"); return null; } let token = await this.getFrontTokenFromStorage(); + logDebugMessage("getFrontToken: returning: " + token); return token; } @@ -69,6 +74,7 @@ export default class FrontToken { } | undefined > { + logDebugMessage("FrontToken.getTokenInfo: called"); let frontToken = await this.getFrontToken(); if (frontToken === null) { if ((await getLocalSessionState()).status === "EXISTS") { @@ -82,10 +88,15 @@ export default class FrontToken { return undefined; } } - return JSON.parse(decodeURIComponent(escape(atob(frontToken)))); + const parsedToken = JSON.parse(decodeURIComponent(escape(atob(frontToken)))); + logDebugMessage("FrontToken.getTokenInfo: returning ate: " + parsedToken.ate); + logDebugMessage("FrontToken.getTokenInfo: returning uid: " + parsedToken.uid); + logDebugMessage("FrontToken.getTokenInfo: returning up: " + parsedToken.up); + return parsedToken; } private static async setFrontToken(frontToken: string | undefined) { + logDebugMessage("setFrontToken: called"); async function setFrontTokenToStorage(frontToken: string | undefined) { if (frontToken === undefined) { await AsyncStorage.removeItem(FRONT_TOKEN_KEY); @@ -98,6 +109,7 @@ export default class FrontToken { } static async removeToken() { + logDebugMessage("FrontToken.removeToken: called"); await this.setFrontToken(undefined); await setToken("access", ""); await setToken("refresh", ""); @@ -119,6 +131,7 @@ export default class FrontToken { return FrontToken.removeToken(); } + logDebugMessage("FrontToken.setItem: called"); await this.setFrontToken(frontToken); FrontToken.waiters.forEach(f => f(undefined)); FrontToken.waiters = []; From 064237e83b1026aa8a5e877818ea167927a1e455 Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Tue, 2 Jan 2024 15:27:21 +0530 Subject: [PATCH 06/18] Add debug logs to utils --- lib/ts/utils.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/ts/utils.ts b/lib/ts/utils.ts index 4e87a53..f712162 100644 --- a/lib/ts/utils.ts +++ b/lib/ts/utils.ts @@ -5,7 +5,7 @@ import FrontToken from "./frontToken"; import NormalisedURLDomain from "./normalisedURLDomain"; import NormalisedURLPath from "./normalisedURLPath"; import { InputType, NormalisedInputType, EventHandler, RecipeInterface, TokenType } from "./types"; -import { enableLogging } from "./logger"; +import { enableLogging, logDebugMessage } from "./logger"; const LAST_ACCESS_TOKEN_UPDATE = "st-last-access-token-update"; const REFRESH_TOKEN_NAME = "st-refresh-token"; @@ -141,7 +141,7 @@ export function validateAndNormaliseInputOrThrowError(options: InputType): Norma export function setToken(tokenType: TokenType, value: string) { const name = getStorageNameForToken(tokenType); - + logDebugMessage(`setToken: saved ${tokenType} token in storage`); // We save the tokens with a 100-year expiration time return storeInStorage(name, value, Date.now() + 3153600000); } @@ -161,8 +161,10 @@ export async function storeInStorage(name: string, value: string, expiry: number * to the refresh endpoint */ export async function saveLastAccessTokenUpdate() { + logDebugMessage("saveLastAccessTokenUpdate: called"); const now = Date.now().toString(); + logDebugMessage("saveLastAccessTokenUpdate: setting " + now); await storeInStorage(LAST_ACCESS_TOKEN_UPDATE, now, Number.MAX_SAFE_INTEGER); // We clear the sIRTFrontend cookie @@ -212,11 +214,18 @@ export type LocalSessionState = * but a session may still exist */ export async function getLocalSessionState(): Promise { + logDebugMessage("getLocalSessionState: called"); const lastAccessTokenUpdate = await getFromStorage(LAST_ACCESS_TOKEN_UPDATE); const frontTokenExists = await FrontToken.doesTokenExists(); if (frontTokenExists && lastAccessTokenUpdate !== undefined) { + logDebugMessage( + "getLocalSessionState: returning EXISTS since both frontToken and lastAccessTokenUpdate exists" + ); return { status: "EXISTS", lastAccessTokenUpdate: lastAccessTokenUpdate }; } else { + logDebugMessage( + "getLocalSessionState: returning NOT_EXISTS since frontToken was cleared but lastAccessTokenUpdate exists" + ); return { status: "NOT_EXISTS" }; } } @@ -232,6 +241,7 @@ export function fireSessionUpdateEventsIfNecessary( // This may be considered a bug, but it is the existing behaviour before the rework if (frontTokenHeaderFromResponse === undefined || frontTokenHeaderFromResponse === null) { // The access token (and the session) hasn't been updated. + logDebugMessage("fireSessionUpdateEventsIfNecessary returning early because the front token was not updated"); return; } @@ -239,23 +249,30 @@ export function fireSessionUpdateEventsIfNecessary( // any other update means it's created or updated. const frontTokenExistsAfter = frontTokenHeaderFromResponse !== "remove"; + logDebugMessage( + `fireSessionUpdateEventsIfNecessary wasLoggedIn: ${wasLoggedIn} frontTokenExistsAfter: ${frontTokenExistsAfter} status: ${status}` + ); + if (wasLoggedIn) { // we check for wasLoggedIn cause we don't want to fire an event // unnecessarily on first app load or if the user tried // to query an API that returned 401 while the user was not logged in... if (!frontTokenExistsAfter) { if (status === AuthHttpRequest.config.sessionExpiredStatusCode) { + logDebugMessage("onUnauthorisedResponse: firing UNAUTHORISED event"); AuthHttpRequest.config.onHandleEvent({ action: "UNAUTHORISED", sessionExpiredOrRevoked: true }); } else { + logDebugMessage("onUnauthorisedResponse: firing SIGN_OUT event"); AuthHttpRequest.config.onHandleEvent({ action: "SIGN_OUT" }); } } } else if (frontTokenExistsAfter) { + logDebugMessage("onUnauthorisedResponse: firing SESSION_CREATED event"); AuthHttpRequest.config.onHandleEvent({ action: "SESSION_CREATED" }); From b7c658dc8328f8d613457df0f1b5b66ff4b01311 Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Tue, 2 Jan 2024 16:03:22 +0530 Subject: [PATCH 07/18] Add logging to AntiCSRF --- lib/ts/antiCsrf.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/ts/antiCsrf.ts b/lib/ts/antiCsrf.ts index 0e0305e..6fa5efb 100644 --- a/lib/ts/antiCsrf.ts +++ b/lib/ts/antiCsrf.ts @@ -16,6 +16,7 @@ import AsyncStorage from "@react-native-async-storage/async-storage"; import AuthHttpRequest from "./fetch"; import { getLocalSessionState } from "./utils"; +import { logDebugMessage } from "./logger"; const TOKEN_KEY = "supertokens-rn-anticsrf-key"; const ANTI_CSRF_NAME = "sAntiCsrf"; @@ -31,7 +32,9 @@ export default class AntiCSRF { private constructor() {} private static async getAntiCSRFToken(associatedAccessTokenUpdate: string | undefined): Promise { + logDebugMessage("AntiCSRF.getAntiCSRFToken: called"); if (!((await getLocalSessionState()).status === "EXISTS")) { + logDebugMessage("AntiCSRF.getAntiCSRFToken: returning null because local session state != EXISTS"); return null; } @@ -62,6 +65,7 @@ export default class AntiCSRF { if (expiry < currentTime) { await AntiCSRF.removeToken(); + logDebugMessage("AntiCSRF.getAntiCSRFToken: returning null because of expired token"); return null; } @@ -69,21 +73,25 @@ export default class AntiCSRF { if (temp !== undefined) { // We update storage to set just the value and return it await AntiCSRF.setItem(associatedAccessTokenUpdate, temp); + logDebugMessage("AntiCSRF.getToken: returning " + temp); return temp; } - // This means that the storage had a cookie string but it was malformed somehow + logDebugMessage("AntiCSRF.getAntiCSRFToken: returning null because of malformed cookie string"); return null; } } } + logDebugMessage("AntiCSRF.getAntiCSRFToken: returning null"); return fromStorage; } static async getToken(associatedAccessTokenUpdate: string | undefined): Promise { + logDebugMessage("AntiCSRF.getToken: called"); if (associatedAccessTokenUpdate === undefined) { AntiCSRF.tokenInfo = undefined; + logDebugMessage("AntiCSRF.getToken: returning undefined"); return undefined; } @@ -91,6 +99,7 @@ export default class AntiCSRF { let antiCsrf = await this.getAntiCSRFToken(associatedAccessTokenUpdate); if (antiCsrf === null) { + logDebugMessage("AntiCSRF.getToken: returning undefined"); return undefined; } @@ -103,11 +112,13 @@ export default class AntiCSRF { AntiCSRF.tokenInfo = undefined; return await AntiCSRF.getToken(associatedAccessTokenUpdate); } + logDebugMessage("AntiCSRF.getToken: returning: " + AntiCSRF.tokenInfo.antiCsrf); return AntiCSRF.tokenInfo.antiCsrf; } // give antiCSRFToken as undefined to remove it. private static async setAntiCSRF(antiCSRFToken: string | undefined) { + logDebugMessage("AntiCSRF.setAntiCSRF: called " + antiCSRFToken); async function setAntiCSRFToStorage(antiCSRFToken: string | undefined) { if (antiCSRFToken === undefined) { await AntiCSRF.removeToken(); @@ -124,7 +135,7 @@ export default class AntiCSRF { AntiCSRF.tokenInfo = undefined; return; } - + logDebugMessage("AntiCSRF.setItem: called"); await this.setAntiCSRF(antiCsrf); AntiCSRF.tokenInfo = { antiCsrf, @@ -133,6 +144,7 @@ export default class AntiCSRF { } static async removeToken() { + logDebugMessage("AntiCSRF.removeToken: called"); AntiCSRF.tokenInfo = undefined; await AsyncStorage.removeItem(TOKEN_KEY); } From f2478d5ac3c404eded4ae973a23917daf73a77ff Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Tue, 2 Jan 2024 16:46:27 +0530 Subject: [PATCH 08/18] Add debug logs to recipeImplementation --- lib/ts/recipeImplementation.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/ts/recipeImplementation.ts b/lib/ts/recipeImplementation.ts index 8de0559..7030f2b 100644 --- a/lib/ts/recipeImplementation.ts +++ b/lib/ts/recipeImplementation.ts @@ -6,10 +6,12 @@ import { supported_fdi } from "./version"; import { interceptorFunctionRequestFulfilled, responseErrorInterceptor, responseInterceptor } from "./axios"; import { SuperTokensGeneralError } from "./error"; import { getLocalSessionState, normaliseCookieDomainOrThrowError, normaliseURLDomainOrThrowError } from "./utils"; +import { logDebugMessage } from "./logger"; export default function RecipeImplementation(): RecipeInterface { return { addFetchInterceptorsAndReturnModifiedFetch: function(originalFetch: any, _: NormalisedInputType): typeof fetch { + logDebugMessage("addFetchInterceptorsAndReturnModifiedFetch: called"); return async function(url: RequestInfo, config?: RequestInit): Promise { return await AuthHttpRequest.doRequest( (config?: RequestInit) => { @@ -24,10 +26,12 @@ export default function RecipeImplementation(): RecipeInterface { }, addAxiosInterceptors: function(axiosInstance: any, _: NormalisedInputType): void { + logDebugMessage("addAxiosInterceptors: called"); // we first check if this axiosInstance already has our interceptors. let requestInterceptors = axiosInstance.interceptors.request; for (let i = 0; i < requestInterceptors.handlers.length; i++) { if (requestInterceptors.handlers[i].fulfilled === interceptorFunctionRequestFulfilled) { + logDebugMessage("addAxiosInterceptors: not adding because already added on this instance"); return; } } @@ -44,20 +48,24 @@ export default function RecipeImplementation(): RecipeInterface { }, getUserId: async function(config: NormalisedInputType): Promise { + logDebugMessage("getUserId: called"); let tokenInfo = await FrontToken.getTokenInfo(); if (tokenInfo === undefined) { throw new Error("No session exists"); } + logDebugMessage("getUserId: returning: " + tokenInfo.uid); return tokenInfo.uid; }, getAccessTokenPayloadSecurely: async function(config: NormalisedInputType): Promise { + logDebugMessage("getAccessTokenPayloadSecurely: called"); let tokenInfo = await FrontToken.getTokenInfo(); if (tokenInfo === undefined) { throw new Error("No session exists"); } if (tokenInfo.ate < Date.now()) { + logDebugMessage("getAccessTokenPayloadSecurely: access token expired. Refreshing session"); let retry = await AuthHttpRequest.attemptRefreshingSession(); if (retry) { return await this.getAccessTokenPayloadSecurely(config); @@ -65,17 +73,22 @@ export default function RecipeImplementation(): RecipeInterface { throw new Error("Could not refresh session"); } } + logDebugMessage("getAccessTokenPayloadSecurely: returning: " + JSON.stringify(tokenInfo.up)); return tokenInfo.up; }, doesSessionExist: async function(config: NormalisedInputType): Promise { + logDebugMessage("doesSessionExist: called"); const tokenInfo = await FrontToken.getTokenInfo(); if (tokenInfo === undefined) { + logDebugMessage("doesSessionExist: access token does not exist locally"); return false; } if (tokenInfo.ate < Date.now()) { + logDebugMessage("doesSessionExist: access token expired. Refreshing session"); + const preRequestLocalSessionState = await getLocalSessionState(); const refresh = await onUnauthorisedResponse(preRequestLocalSessionState); return refresh.result === "RETRY"; @@ -85,6 +98,7 @@ export default function RecipeImplementation(): RecipeInterface { }, signOut: async function(config: NormalisedInputType): Promise { + logDebugMessage("signOut: called"); if (!(await this.doesSessionExist(config))) { config.onHandleEvent({ action: "SIGN_OUT" @@ -92,6 +106,7 @@ export default function RecipeImplementation(): RecipeInterface { return; } + logDebugMessage("signOut: Calling refresh pre API hook"); let preAPIResult = await config.preAPIHook({ action: "SIGN_OUT", requestInit: { @@ -104,7 +119,10 @@ export default function RecipeImplementation(): RecipeInterface { url: AuthHttpRequest.signOutUrl }); + logDebugMessage("signOut: Calling API"); let resp = await fetch(preAPIResult.url, preAPIResult.requestInit); + logDebugMessage("signOut: API ended"); + logDebugMessage("signOut: API responded with status code: " + resp.status); if (resp.status === config.sessionExpiredStatusCode) { // refresh must have already sent session expiry event @@ -118,6 +136,7 @@ export default function RecipeImplementation(): RecipeInterface { let responseJson = await resp.clone().json(); if (responseJson.status === "GENERAL_ERROR") { + logDebugMessage("doRequest: Throwing general error"); let message = responseJson.message === undefined ? "No Error Message Provided" : responseJson.message; throw new SuperTokensGeneralError(message); } @@ -126,6 +145,14 @@ export default function RecipeImplementation(): RecipeInterface { }, shouldDoInterceptionBasedOnUrl: (toCheckUrl, apiDomain, sessionTokenBackendDomain) => { + logDebugMessage( + "shouldDoInterceptionBasedOnUrl: toCheckUrl: " + + toCheckUrl + + " apiDomain: " + + apiDomain + + " sessionTokenBackendDomain: " + + sessionTokenBackendDomain + ); function isNumeric(str: any) { if (typeof str != "string") return false; // we only process strings! return ( From 4f932f4c9fa20cf9e1832bc2859dfbedef0447b9 Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Wed, 3 Jan 2024 11:21:30 +0530 Subject: [PATCH 09/18] Add debug logs to axios --- lib/ts/axios.ts | 66 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/lib/ts/axios.ts b/lib/ts/axios.ts index 881a255..6fa9f1f 100644 --- a/lib/ts/axios.ts +++ b/lib/ts/axios.ts @@ -21,6 +21,7 @@ import FrontToken from "./frontToken"; import AntiCSRF from "./antiCsrf"; import { PROCESS_STATE, ProcessState } from "./processState"; import { fireSessionUpdateEventsIfNecessary, getLocalSessionState, getTokenForHeaderAuth, setToken } from "./utils"; +import { logDebugMessage } from "./logger"; function getUrlFromConfig(config: AxiosRequestConfig) { let url: string = config.url === undefined ? "" : config.url; @@ -38,6 +39,7 @@ function getUrlFromConfig(config: AxiosRequestConfig) { } export async function interceptorFunctionRequestFulfilled(config: AxiosRequestConfig) { + logDebugMessage("interceptorFunctionRequestFulfilled: started axios interception"); let url = getUrlFromConfig(config); let doNotDoInterception = false; @@ -56,11 +58,14 @@ export async function interceptorFunctionRequestFulfilled(config: AxiosRequestCo throw err; } + logDebugMessage("interceptorFunctionRequestFulfilled: Value of doNotDoInterception: " + doNotDoInterception); if (doNotDoInterception) { + logDebugMessage("interceptorFunctionRequestFulfilled: Returning config unchanged"); // this check means that if you are using axios via inteceptor, then we only do the refresh steps if you are calling your APIs. return config; } + logDebugMessage("interceptorFunctionRequestFulfilled: Modifying config"); ProcessState.getInstance().addState(PROCESS_STATE.CALLING_INTERCEPTION_REQUEST); const preRequestLocalSessionState = await getLocalSessionState(); let configWithAntiCsrf: AxiosRequestConfig = config; @@ -68,6 +73,7 @@ export async function interceptorFunctionRequestFulfilled(config: AxiosRequestCo if (preRequestLocalSessionState.status === "EXISTS") { const antiCsrfToken = await AntiCSRF.getToken(preRequestLocalSessionState.lastAccessTokenUpdate); if (antiCsrfToken !== undefined) { + logDebugMessage("interceptorFunctionRequestFulfilled: Adding anti-csrf token to request"); configWithAntiCsrf = { ...configWithAntiCsrf, headers: @@ -84,6 +90,7 @@ export async function interceptorFunctionRequestFulfilled(config: AxiosRequestCo } if (AuthHttpRequestFetch.config.autoAddCredentials && configWithAntiCsrf.withCredentials === undefined) { + logDebugMessage("interceptorFunctionRequestFulfilled: Adding credentials include"); configWithAntiCsrf = { ...configWithAntiCsrf, withCredentials: true @@ -91,6 +98,9 @@ export async function interceptorFunctionRequestFulfilled(config: AxiosRequestCo } // adding rid for anti-csrf protection: Anti-csrf via custom header + logDebugMessage( + "interceptorFunctionRequestFulfilled: Adding rid header: anti-csrf (it may be overriden by the user's provided rid)" + ); configWithAntiCsrf = { ...configWithAntiCsrf, headers: @@ -105,12 +115,14 @@ export async function interceptorFunctionRequestFulfilled(config: AxiosRequestCo }; const transferMethod = AuthHttpRequestFetch.config.tokenTransferMethod; + logDebugMessage("interceptorFunctionRequestFulfilled: Adding st-auth-mode header: " + transferMethod); configWithAntiCsrf.headers!["st-auth-mode"] = transferMethod; configWithAntiCsrf = await removeAuthHeaderIfMatchesLocalToken(configWithAntiCsrf); await setAuthorizationHeaderIfRequired(configWithAntiCsrf); + logDebugMessage("interceptorFunctionRequestFulfilled: returning modified config"); return configWithAntiCsrf; } @@ -122,6 +134,10 @@ export function responseInterceptor(axiosInstance: any) { if (!AuthHttpRequestFetch.initCalled) { throw new Error("init function not called"); } + logDebugMessage("responseInterceptor: started"); + logDebugMessage( + "responseInterceptor: already intercepted: " + response.headers["x-supertokens-xhr-intercepted"] + ); let url = getUrlFromConfig(response.config); try { @@ -138,10 +154,13 @@ export function responseInterceptor(axiosInstance: any) { throw err; } + logDebugMessage("responseInterceptor: Value of doNotDoInterception: " + doNotDoInterception); if (doNotDoInterception) { + logDebugMessage("responseInterceptor: Returning without interception"); // this check means that if you are using axios via inteceptor, then we only do the refresh steps if you are calling your APIs. return response; } + logDebugMessage("responseInterceptor: Interception started"); ProcessState.getInstance().addState(PROCESS_STATE.CALLING_INTERCEPTION_RESPONSE); @@ -155,6 +174,7 @@ export function responseInterceptor(axiosInstance: any) { ); if (response.status === AuthHttpRequestFetch.config.sessionExpiredStatusCode) { + logDebugMessage("responseInterceptor: Status code is: " + response.status); let config = response.config; return AuthHttpRequest.doRequest( (config: AxiosRequestConfig) => { @@ -173,6 +193,9 @@ export function responseInterceptor(axiosInstance: any) { } } finally { if (!doNotDoInterception && (await getLocalSessionState()).status !== "EXISTS") { + logDebugMessage( + "responseInterceptor: local session doesn't exist, so removing anti-csrf and sFrontToken" + ); await AntiCSRF.removeToken(); await FrontToken.removeToken(); } @@ -182,10 +205,16 @@ export function responseInterceptor(axiosInstance: any) { export function responseErrorInterceptor(axiosInstance: any) { return (error: any) => { + logDebugMessage("responseErrorInterceptor: called"); + logDebugMessage( + "responseErrorInterceptor: already intercepted: " + + (error.response && error.response.headers["x-supertokens-xhr-intercepted"]) + ); if ( error.response !== undefined && error.response.status === AuthHttpRequestFetch.config.sessionExpiredStatusCode ) { + logDebugMessage("responseErrorInterceptor: Status code is: " + error.response.status); let config = error.config; return AuthHttpRequest.doRequest( (config: AxiosRequestConfig) => { @@ -228,6 +257,7 @@ export default class AuthHttpRequest { if (!AuthHttpRequestFetch.initCalled) { throw Error("init function not called"); } + logDebugMessage("doRequest: called"); let doNotDoInterception = false; try { @@ -245,7 +275,9 @@ export default class AuthHttpRequest { throw err; } + logDebugMessage("doRequest: Value of doNotDoInterception: " + doNotDoInterception); if (doNotDoInterception) { + logDebugMessage("doRequest: Returning without interception"); if (prevError !== undefined) { throw prevError; } else if (prevResponse !== undefined) { @@ -253,6 +285,7 @@ export default class AuthHttpRequest { } return await httpCall(config); } + logDebugMessage("doRequest: Interception started"); config = await removeAuthHeaderIfMatchesLocalToken(config); try { @@ -266,6 +299,7 @@ export default class AuthHttpRequest { if (preRequestLocalSessionState.status === "EXISTS") { const antiCsrfToken = await AntiCSRF.getToken(preRequestLocalSessionState.lastAccessTokenUpdate); if (antiCsrfToken !== undefined) { + logDebugMessage("doRequest: Adding anti-csrf token to request"); configWithAntiCsrf = { ...configWithAntiCsrf, headers: @@ -285,6 +319,7 @@ export default class AuthHttpRequest { AuthHttpRequestFetch.config.autoAddCredentials && configWithAntiCsrf.withCredentials === undefined ) { + logDebugMessage("doRequest: Adding credentials include"); configWithAntiCsrf = { ...configWithAntiCsrf, withCredentials: true @@ -292,6 +327,7 @@ export default class AuthHttpRequest { } // adding rid for anti-csrf protection: Anti-csrf via custom header + logDebugMessage("doRequest: Adding rid header: anti-csrf (May get overriden by user's rid)"); configWithAntiCsrf = { ...configWithAntiCsrf, headers: @@ -306,6 +342,7 @@ export default class AuthHttpRequest { }; const transferMethod = AuthHttpRequestFetch.config.tokenTransferMethod; + logDebugMessage("doRequest: Adding st-auth-mode header: " + transferMethod); configWithAntiCsrf.headers!["st-auth-mode"] = transferMethod; await setAuthorizationHeaderIfRequired(configWithAntiCsrf); @@ -316,10 +353,17 @@ export default class AuthHttpRequest { prevError = undefined; prevResponse = undefined; if (localPrevError !== undefined) { + logDebugMessage("doRequest: Not making call because localPrevError is not undefined"); throw localPrevError; } + if (localPrevResponse !== undefined) { + logDebugMessage("doRequest: Not making call because localPrevResponse is not undefined"); + } else { + logDebugMessage("doRequest: Making user's http call"); + } let response = localPrevResponse === undefined ? await httpCall(configWithAntiCsrf) : localPrevResponse; + logDebugMessage("doRequest: User's http call ended"); await saveTokensFromHeaders(response); @@ -330,14 +374,17 @@ export default class AuthHttpRequest { ); if (response.status === AuthHttpRequestFetch.config.sessionExpiredStatusCode) { + logDebugMessage("doRequest: Status code is: " + response.status); const refreshResult = await onUnauthorisedResponse(preRequestLocalSessionState); if (refreshResult.result !== "RETRY") { + logDebugMessage("doRequest: Not retrying original request"); returnObj = refreshResult.error ? await createAxiosErrorFromFetchResp(refreshResult.error) : await createAxiosErrorFromAxiosResp(response); break; } + logDebugMessage("doRequest: Retrying original request"); } else { return response; } @@ -353,8 +400,10 @@ export default class AuthHttpRequest { ); if (err.response.status === AuthHttpRequestFetch.config.sessionExpiredStatusCode) { + logDebugMessage("doRequest: Status code is: " + response.status); const refreshResult = await onUnauthorisedResponse(preRequestLocalSessionState); if (refreshResult.result !== "RETRY") { + logDebugMessage("doRequest: Not retrying original request"); // Returning refreshResult.error as an Axios Error if we attempted a refresh // Returning the original error if we did not attempt refreshing returnObj = @@ -363,6 +412,7 @@ export default class AuthHttpRequest { : err; break; } + logDebugMessage("doRequest: Retrying original request"); } else { throw err; } @@ -380,6 +430,7 @@ export default class AuthHttpRequest { // The backend should not be down if we get here, but even if it were we shouldn't need to call refresh const postRequestLocalSessionState = await getLocalSessionState(); if (postRequestLocalSessionState.status === "NOT_EXISTS") { + logDebugMessage("doRequest: local session doesn't exist, so removing anti-csrf and sFrontToken"); await AntiCSRF.removeToken(); await FrontToken.removeToken(); } @@ -388,18 +439,22 @@ export default class AuthHttpRequest { } async function saveTokensFromHeaders(response: AxiosResponse) { + logDebugMessage("saveTokensFromHeaders: Saving updated tokens from the response"); const refreshToken = response.headers["st-refresh-token"]; if (refreshToken !== undefined && refreshToken !== null) { + logDebugMessage("saveTokensFromHeaders: saving new refresh token"); await setToken("refresh", refreshToken); } const accessToken = response.headers["st-access-token"]; if (accessToken !== undefined && accessToken !== null) { + logDebugMessage("saveTokensFromHeaders: saving new access token"); await setToken("access", accessToken); } const frontToken = response.headers["front-token"]; if (frontToken !== undefined && frontToken !== null) { + logDebugMessage("doRequest: Setting sFrontToken: " + frontToken); await FrontToken.setItem(frontToken); } @@ -407,6 +462,7 @@ async function saveTokensFromHeaders(response: AxiosResponse) { if (antiCsrfToken !== undefined && antiCsrfToken !== null) { const tok = await getLocalSessionState(); if (tok.status === "EXISTS") { + logDebugMessage("doRequest: Setting anti-csrf token"); await AntiCSRF.setItem(tok.lastAccessTokenUpdate, antiCsrfToken); } } @@ -418,6 +474,8 @@ async function setAuthorizationHeaderIfRequired(requestConfig: AxiosRequestConfi requestConfig.headers = {}; } + logDebugMessage("setAuthorizationHeaderIfRequired: adding existing tokens as header"); + // We set the Authorization header even if the tokenTransferMethod preference set in the config is cookies // since the active session may be using cookies. By default, we want to allow users to continue these sessions. // The new session preference should be applied at the start of the next session, if the backend allows it. @@ -433,13 +491,16 @@ async function setAuthorizationHeaderIfRequired(requestConfig: AxiosRequestConfi requestConfig.headers["Authorization"] !== undefined || requestConfig.headers["authorization"] !== undefined ) { - // No-op, keeping it this way for simplicity to compare with web SDKs + logDebugMessage("setAuthorizationHeaderIfRequired: Authorization header defined by the user, not adding"); } else { + logDebugMessage("setAuthorizationHeaderIfRequired: added authorization header"); requestConfig.headers = { ...requestConfig.headers, Authorization: `Bearer ${accessToken}` }; } + } else { + logDebugMessage("setAuthorizationHeaderIfRequired: token for header based auth not found"); } } @@ -453,6 +514,9 @@ async function removeAuthHeaderIfMatchesLocalToken(config: AxiosRequestConfig) { // We are ignoring the Authorization header set by the user in this case, because it would cause issues // If we do not ignore this, then this header would be used even if the request is being retried after a refresh, even though it contains an outdated access token. // This causes an infinite refresh loop. + logDebugMessage( + "removeAuthHeaderIfMatchesLocalToken: Removing Authorization from user provided headers because it contains our access token" + ); const res = { ...config, headers: { ...config.headers } }; delete res.headers.authorization; delete res.headers.Authorization; From 0b00e9a724c6ee1b668b461295dc658a945566c0 Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Wed, 3 Jan 2024 11:44:12 +0530 Subject: [PATCH 10/18] Update build files --- lib/build/antiCsrf.js | 14 +++++++ lib/build/axios.js | 67 ++++++++++++++++++++++++++++++- lib/build/fetch.js | 60 +++++++++++++++++++++++++++ lib/build/frontToken.js | 15 ++++++- lib/build/logger.d.ts | 2 + lib/build/logger.js | 27 +++++++++++++ lib/build/recipeImplementation.js | 26 ++++++++++++ lib/build/types.d.ts | 1 + lib/build/utils.js | 21 ++++++++++ 9 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 lib/build/logger.d.ts create mode 100644 lib/build/logger.js diff --git a/lib/build/antiCsrf.js b/lib/build/antiCsrf.js index 98456a4..bd84b9e 100644 --- a/lib/build/antiCsrf.js +++ b/lib/build/antiCsrf.js @@ -45,13 +45,16 @@ var __awaiter = }; import AsyncStorage from "@react-native-async-storage/async-storage"; import { getLocalSessionState } from "./utils"; +import { logDebugMessage } from "./logger"; const TOKEN_KEY = "supertokens-rn-anticsrf-key"; const ANTI_CSRF_NAME = "sAntiCsrf"; export default class AntiCSRF { constructor() {} static getAntiCSRFToken(associatedAccessTokenUpdate) { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("AntiCSRF.getAntiCSRFToken: called"); if (!((yield getLocalSessionState()).status === "EXISTS")) { + logDebugMessage("AntiCSRF.getAntiCSRFToken: returning null because local session state != EXISTS"); return null; } function getAntiCSRFFromStorage() { @@ -76,31 +79,38 @@ export default class AntiCSRF { let currentTime = Date.now(); if (expiry < currentTime) { yield AntiCSRF.removeToken(); + logDebugMessage("AntiCSRF.getAntiCSRFToken: returning null because of expired token"); return null; } let temp = last.split(";").shift(); if (temp !== undefined) { // We update storage to set just the value and return it yield AntiCSRF.setItem(associatedAccessTokenUpdate, temp); + logDebugMessage("AntiCSRF.getToken: returning " + temp); return temp; } // This means that the storage had a cookie string but it was malformed somehow + logDebugMessage("AntiCSRF.getAntiCSRFToken: returning null because of malformed cookie string"); return null; } } } + logDebugMessage("AntiCSRF.getAntiCSRFToken: returning null"); return fromStorage; }); } static getToken(associatedAccessTokenUpdate) { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("AntiCSRF.getToken: called"); if (associatedAccessTokenUpdate === undefined) { AntiCSRF.tokenInfo = undefined; + logDebugMessage("AntiCSRF.getToken: returning undefined"); return undefined; } if (AntiCSRF.tokenInfo === undefined) { let antiCsrf = yield this.getAntiCSRFToken(associatedAccessTokenUpdate); if (antiCsrf === null) { + logDebugMessage("AntiCSRF.getToken: returning undefined"); return undefined; } AntiCSRF.tokenInfo = { @@ -112,12 +122,14 @@ export default class AntiCSRF { AntiCSRF.tokenInfo = undefined; return yield AntiCSRF.getToken(associatedAccessTokenUpdate); } + logDebugMessage("AntiCSRF.getToken: returning: " + AntiCSRF.tokenInfo.antiCsrf); return AntiCSRF.tokenInfo.antiCsrf; }); } // give antiCSRFToken as undefined to remove it. static setAntiCSRF(antiCSRFToken) { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("AntiCSRF.setAntiCSRF: called " + antiCSRFToken); function setAntiCSRFToStorage(antiCSRFToken) { return __awaiter(this, void 0, void 0, function*() { if (antiCSRFToken === undefined) { @@ -136,6 +148,7 @@ export default class AntiCSRF { AntiCSRF.tokenInfo = undefined; return; } + logDebugMessage("AntiCSRF.setItem: called"); yield this.setAntiCSRF(antiCsrf); AntiCSRF.tokenInfo = { antiCsrf, @@ -145,6 +158,7 @@ export default class AntiCSRF { } static removeToken() { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("AntiCSRF.removeToken: called"); AntiCSRF.tokenInfo = undefined; yield AsyncStorage.removeItem(TOKEN_KEY); }); diff --git a/lib/build/axios.js b/lib/build/axios.js index e615c20..599cee4 100644 --- a/lib/build/axios.js +++ b/lib/build/axios.js @@ -35,6 +35,7 @@ import FrontToken from "./frontToken"; import AntiCSRF from "./antiCsrf"; import { PROCESS_STATE, ProcessState } from "./processState"; import { fireSessionUpdateEventsIfNecessary, getLocalSessionState, getTokenForHeaderAuth, setToken } from "./utils"; +import { logDebugMessage } from "./logger"; function getUrlFromConfig(config) { let url = config.url === undefined ? "" : config.url; let baseURL = config.baseURL; @@ -51,6 +52,7 @@ function getUrlFromConfig(config) { } export function interceptorFunctionRequestFulfilled(config) { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("interceptorFunctionRequestFulfilled: started axios interception"); let url = getUrlFromConfig(config); let doNotDoInterception = false; try { @@ -66,16 +68,20 @@ export function interceptorFunctionRequestFulfilled(config) { // so we do not need to check for the case where axios is called with just a path (for example axios.post("/login")) throw err; } + logDebugMessage("interceptorFunctionRequestFulfilled: Value of doNotDoInterception: " + doNotDoInterception); if (doNotDoInterception) { + logDebugMessage("interceptorFunctionRequestFulfilled: Returning config unchanged"); // this check means that if you are using axios via inteceptor, then we only do the refresh steps if you are calling your APIs. return config; } + logDebugMessage("interceptorFunctionRequestFulfilled: Modifying config"); ProcessState.getInstance().addState(PROCESS_STATE.CALLING_INTERCEPTION_REQUEST); const preRequestLocalSessionState = yield getLocalSessionState(); let configWithAntiCsrf = config; if (preRequestLocalSessionState.status === "EXISTS") { const antiCsrfToken = yield AntiCSRF.getToken(preRequestLocalSessionState.lastAccessTokenUpdate); if (antiCsrfToken !== undefined) { + logDebugMessage("interceptorFunctionRequestFulfilled: Adding anti-csrf token to request"); configWithAntiCsrf = Object.assign(Object.assign({}, configWithAntiCsrf), { headers: configWithAntiCsrf === undefined @@ -89,9 +95,13 @@ export function interceptorFunctionRequestFulfilled(config) { } } if (AuthHttpRequestFetch.config.autoAddCredentials && configWithAntiCsrf.withCredentials === undefined) { + logDebugMessage("interceptorFunctionRequestFulfilled: Adding credentials include"); configWithAntiCsrf = Object.assign(Object.assign({}, configWithAntiCsrf), { withCredentials: true }); } // adding rid for anti-csrf protection: Anti-csrf via custom header + logDebugMessage( + "interceptorFunctionRequestFulfilled: Adding rid header: anti-csrf (it may be overriden by the user's provided rid)" + ); configWithAntiCsrf = Object.assign(Object.assign({}, configWithAntiCsrf), { headers: configWithAntiCsrf === undefined @@ -101,9 +111,11 @@ export function interceptorFunctionRequestFulfilled(config) { : Object.assign({ rid: "anti-csrf" }, configWithAntiCsrf.headers) }); const transferMethod = AuthHttpRequestFetch.config.tokenTransferMethod; + logDebugMessage("interceptorFunctionRequestFulfilled: Adding st-auth-mode header: " + transferMethod); configWithAntiCsrf.headers["st-auth-mode"] = transferMethod; configWithAntiCsrf = yield removeAuthHeaderIfMatchesLocalToken(configWithAntiCsrf); yield setAuthorizationHeaderIfRequired(configWithAntiCsrf); + logDebugMessage("interceptorFunctionRequestFulfilled: returning modified config"); return configWithAntiCsrf; }); } @@ -115,6 +127,10 @@ export function responseInterceptor(axiosInstance) { if (!AuthHttpRequestFetch.initCalled) { throw new Error("init function not called"); } + logDebugMessage("responseInterceptor: started"); + logDebugMessage( + "responseInterceptor: already intercepted: " + response.headers["x-supertokens-xhr-intercepted"] + ); let url = getUrlFromConfig(response.config); try { doNotDoInterception = @@ -129,10 +145,13 @@ export function responseInterceptor(axiosInstance) { // so we do not need to check for the case where axios is called with just a path (for example axios.post("/login")) throw err; } + logDebugMessage("responseInterceptor: Value of doNotDoInterception: " + doNotDoInterception); if (doNotDoInterception) { + logDebugMessage("responseInterceptor: Returning without interception"); // this check means that if you are using axios via inteceptor, then we only do the refresh steps if you are calling your APIs. return response; } + logDebugMessage("responseInterceptor: Interception started"); ProcessState.getInstance().addState(PROCESS_STATE.CALLING_INTERCEPTION_RESPONSE); const preRequestLocalSessionState = yield getLocalSessionState(); yield saveTokensFromHeaders(response); @@ -142,6 +161,7 @@ export function responseInterceptor(axiosInstance) { response.headers["front-token"] ); if (response.status === AuthHttpRequestFetch.config.sessionExpiredStatusCode) { + logDebugMessage("responseInterceptor: Status code is: " + response.status); let config = response.config; return AuthHttpRequest.doRequest( config => { @@ -160,6 +180,9 @@ export function responseInterceptor(axiosInstance) { } } finally { if (!doNotDoInterception && (yield getLocalSessionState()).status !== "EXISTS") { + logDebugMessage( + "responseInterceptor: local session doesn't exist, so removing anti-csrf and sFrontToken" + ); yield AntiCSRF.removeToken(); yield FrontToken.removeToken(); } @@ -168,10 +191,16 @@ export function responseInterceptor(axiosInstance) { } export function responseErrorInterceptor(axiosInstance) { return error => { + logDebugMessage("responseErrorInterceptor: called"); + logDebugMessage( + "responseErrorInterceptor: already intercepted: " + + (error.response && error.response.headers["x-supertokens-xhr-intercepted"]) + ); if ( error.response !== undefined && error.response.status === AuthHttpRequestFetch.config.sessionExpiredStatusCode ) { + logDebugMessage("responseErrorInterceptor: Status code is: " + error.response.status); let config = error.config; return AuthHttpRequest.doRequest( config => { @@ -207,6 +236,7 @@ AuthHttpRequest.doRequest = (httpCall, config, url, prevResponse, prevError, via if (!AuthHttpRequestFetch.initCalled) { throw Error("init function not called"); } + logDebugMessage("doRequest: called"); let doNotDoInterception = false; try { doNotDoInterception = @@ -222,7 +252,9 @@ AuthHttpRequest.doRequest = (httpCall, config, url, prevResponse, prevError, via // so we do not need to check for the case where axios is called with just a path (for example axios.post("/login")) throw err; } + logDebugMessage("doRequest: Value of doNotDoInterception: " + doNotDoInterception); if (doNotDoInterception) { + logDebugMessage("doRequest: Returning without interception"); if (prevError !== undefined) { throw prevError; } else if (prevResponse !== undefined) { @@ -230,6 +262,7 @@ AuthHttpRequest.doRequest = (httpCall, config, url, prevResponse, prevError, via } return yield httpCall(config); } + logDebugMessage("doRequest: Interception started"); config = yield removeAuthHeaderIfMatchesLocalToken(config); try { let returnObj = undefined; @@ -241,6 +274,7 @@ AuthHttpRequest.doRequest = (httpCall, config, url, prevResponse, prevError, via if (preRequestLocalSessionState.status === "EXISTS") { const antiCsrfToken = yield AntiCSRF.getToken(preRequestLocalSessionState.lastAccessTokenUpdate); if (antiCsrfToken !== undefined) { + logDebugMessage("doRequest: Adding anti-csrf token to request"); configWithAntiCsrf = Object.assign(Object.assign({}, configWithAntiCsrf), { headers: configWithAntiCsrf === undefined @@ -257,11 +291,13 @@ AuthHttpRequest.doRequest = (httpCall, config, url, prevResponse, prevError, via AuthHttpRequestFetch.config.autoAddCredentials && configWithAntiCsrf.withCredentials === undefined ) { + logDebugMessage("doRequest: Adding credentials include"); configWithAntiCsrf = Object.assign(Object.assign({}, configWithAntiCsrf), { withCredentials: true }); } // adding rid for anti-csrf protection: Anti-csrf via custom header + logDebugMessage("doRequest: Adding rid header: anti-csrf (May get overriden by user's rid)"); configWithAntiCsrf = Object.assign(Object.assign({}, configWithAntiCsrf), { headers: configWithAntiCsrf === undefined @@ -271,6 +307,7 @@ AuthHttpRequest.doRequest = (httpCall, config, url, prevResponse, prevError, via : Object.assign({ rid: "anti-csrf" }, configWithAntiCsrf.headers) }); const transferMethod = AuthHttpRequestFetch.config.tokenTransferMethod; + logDebugMessage("doRequest: Adding st-auth-mode header: " + transferMethod); configWithAntiCsrf.headers["st-auth-mode"] = transferMethod; yield setAuthorizationHeaderIfRequired(configWithAntiCsrf); try { @@ -279,10 +316,17 @@ AuthHttpRequest.doRequest = (httpCall, config, url, prevResponse, prevError, via prevError = undefined; prevResponse = undefined; if (localPrevError !== undefined) { + logDebugMessage("doRequest: Not making call because localPrevError is not undefined"); throw localPrevError; } + if (localPrevResponse !== undefined) { + logDebugMessage("doRequest: Not making call because localPrevResponse is not undefined"); + } else { + logDebugMessage("doRequest: Making user's http call"); + } let response = localPrevResponse === undefined ? yield httpCall(configWithAntiCsrf) : localPrevResponse; + logDebugMessage("doRequest: User's http call ended"); yield saveTokensFromHeaders(response); fireSessionUpdateEventsIfNecessary( preRequestLocalSessionState.status === "EXISTS", @@ -290,13 +334,16 @@ AuthHttpRequest.doRequest = (httpCall, config, url, prevResponse, prevError, via response.headers["front-token"] ); if (response.status === AuthHttpRequestFetch.config.sessionExpiredStatusCode) { + logDebugMessage("doRequest: Status code is: " + response.status); const refreshResult = yield onUnauthorisedResponse(preRequestLocalSessionState); if (refreshResult.result !== "RETRY") { + logDebugMessage("doRequest: Not retrying original request"); returnObj = refreshResult.error ? yield createAxiosErrorFromFetchResp(refreshResult.error) : yield createAxiosErrorFromAxiosResp(response); break; } + logDebugMessage("doRequest: Retrying original request"); } else { return response; } @@ -310,8 +357,10 @@ AuthHttpRequest.doRequest = (httpCall, config, url, prevResponse, prevError, via response.headers["front-token"] ); if (err.response.status === AuthHttpRequestFetch.config.sessionExpiredStatusCode) { + logDebugMessage("doRequest: Status code is: " + response.status); const refreshResult = yield onUnauthorisedResponse(preRequestLocalSessionState); if (refreshResult.result !== "RETRY") { + logDebugMessage("doRequest: Not retrying original request"); // Returning refreshResult.error as an Axios Error if we attempted a refresh // Returning the original error if we did not attempt refreshing returnObj = @@ -320,6 +369,7 @@ AuthHttpRequest.doRequest = (httpCall, config, url, prevResponse, prevError, via : err; break; } + logDebugMessage("doRequest: Retrying original request"); } else { throw err; } @@ -336,6 +386,7 @@ AuthHttpRequest.doRequest = (httpCall, config, url, prevResponse, prevError, via // The backend should not be down if we get here, but even if it were we shouldn't need to call refresh const postRequestLocalSessionState = yield getLocalSessionState(); if (postRequestLocalSessionState.status === "NOT_EXISTS") { + logDebugMessage("doRequest: local session doesn't exist, so removing anti-csrf and sFrontToken"); yield AntiCSRF.removeToken(); yield FrontToken.removeToken(); } @@ -343,22 +394,27 @@ AuthHttpRequest.doRequest = (httpCall, config, url, prevResponse, prevError, via }); function saveTokensFromHeaders(response) { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("saveTokensFromHeaders: Saving updated tokens from the response"); const refreshToken = response.headers["st-refresh-token"]; if (refreshToken !== undefined && refreshToken !== null) { + logDebugMessage("saveTokensFromHeaders: saving new refresh token"); yield setToken("refresh", refreshToken); } const accessToken = response.headers["st-access-token"]; if (accessToken !== undefined && accessToken !== null) { + logDebugMessage("saveTokensFromHeaders: saving new access token"); yield setToken("access", accessToken); } const frontToken = response.headers["front-token"]; if (frontToken !== undefined && frontToken !== null) { + logDebugMessage("doRequest: Setting sFrontToken: " + frontToken); yield FrontToken.setItem(frontToken); } const antiCsrfToken = response.headers["anti-csrf"]; if (antiCsrfToken !== undefined && antiCsrfToken !== null) { const tok = yield getLocalSessionState(); if (tok.status === "EXISTS") { + logDebugMessage("doRequest: Setting anti-csrf token"); yield AntiCSRF.setItem(tok.lastAccessTokenUpdate, antiCsrfToken); } } @@ -370,6 +426,7 @@ function setAuthorizationHeaderIfRequired(requestConfig) { // This is makes TS happy requestConfig.headers = {}; } + logDebugMessage("setAuthorizationHeaderIfRequired: adding existing tokens as header"); // We set the Authorization header even if the tokenTransferMethod preference set in the config is cookies // since the active session may be using cookies. By default, we want to allow users to continue these sessions. // The new session preference should be applied at the start of the next session, if the backend allows it. @@ -383,12 +440,17 @@ function setAuthorizationHeaderIfRequired(requestConfig) { requestConfig.headers["Authorization"] !== undefined || requestConfig.headers["authorization"] !== undefined ) { - // No-op, keeping it this way for simplicity to compare with web SDKs + logDebugMessage( + "setAuthorizationHeaderIfRequired: Authorization header defined by the user, not adding" + ); } else { + logDebugMessage("setAuthorizationHeaderIfRequired: added authorization header"); requestConfig.headers = Object.assign(Object.assign({}, requestConfig.headers), { Authorization: `Bearer ${accessToken}` }); } + } else { + logDebugMessage("setAuthorizationHeaderIfRequired: token for header based auth not found"); } }); } @@ -402,6 +464,9 @@ function removeAuthHeaderIfMatchesLocalToken(config) { // We are ignoring the Authorization header set by the user in this case, because it would cause issues // If we do not ignore this, then this header would be used even if the request is being retried after a refresh, even though it contains an outdated access token. // This causes an infinite refresh loop. + logDebugMessage( + "removeAuthHeaderIfMatchesLocalToken: Removing Authorization from user provided headers because it contains our access token" + ); const res = Object.assign(Object.assign({}, config), { headers: Object.assign({}, config.headers) }); delete res.headers.authorization; delete res.headers.Authorization; diff --git a/lib/build/fetch.js b/lib/build/fetch.js index 784ff46..af32783 100644 --- a/lib/build/fetch.js +++ b/lib/build/fetch.js @@ -57,6 +57,7 @@ import { import FrontToken from "./frontToken"; import RecipeImplementation from "./recipeImplementation"; import OverrideableBuilder from "supertokens-js-override"; +import { logDebugMessage } from "./logger"; /** * @class AuthHttpRequest * @description wrapper for common http methods. @@ -64,12 +65,20 @@ import OverrideableBuilder from "supertokens-js-override"; export default class AuthHttpRequest { static init(options) { let config = validateAndNormaliseInputOrThrowError(options); + logDebugMessage("init: called"); + logDebugMessage("init: Input apiBasePath: " + config.apiBasePath); + logDebugMessage("init: Input apiDomain: " + config.apiDomain); + logDebugMessage("init: Input autoAddCredentials: " + config.autoAddCredentials); + logDebugMessage("init: Input sessionTokenBackendDomain: " + config.sessionTokenBackendDomain); + logDebugMessage("init: Input sessionExpiredStatusCode: " + config.sessionExpiredStatusCode); + logDebugMessage("init: Input tokenTransferMethod: " + config.tokenTransferMethod); AuthHttpRequest.env = global; AuthHttpRequest.refreshTokenUrl = config.apiDomain + config.apiBasePath + "/session/refresh"; AuthHttpRequest.signOutUrl = config.apiDomain + config.apiBasePath + "/signout"; AuthHttpRequest.rid = "session"; AuthHttpRequest.config = config; if (AuthHttpRequest.env.__supertokensOriginalFetch === undefined) { + logDebugMessage("init: __supertokensOriginalFetch is undefined"); // this block contains code that is run just once per page load.. // all items in this block are attached to the global env so that // even if the init function is called more than once (maybe across JS scripts), @@ -102,6 +111,7 @@ AuthHttpRequest.doRequest = (httpCall, config, url) => if (!AuthHttpRequest.initCalled) { throw Error("init function not called"); } + logDebugMessage("doRequest: start of fetch interception"); let doNotDoInterception = false; try { doNotDoInterception = @@ -123,7 +133,9 @@ AuthHttpRequest.doRequest = (httpCall, config, url) => // so we dont need to check for that here throw err; } + logDebugMessage("doRequest: Value of doNotDoInterception: " + doNotDoInterception); if (doNotDoInterception) { + logDebugMessage("doRequest: Returning without interception"); return yield httpCall(config); } const originalHeaders = new Headers( @@ -140,9 +152,13 @@ AuthHttpRequest.doRequest = (httpCall, config, url) => // We are ignoring the Authorization header set by the user in this case, because it would cause issues // If we do not ignore this, then this header would be used even if the request is being retried after a refresh, even though it contains an outdated access token. // This causes an infinite refresh loop. + logDebugMessage( + "doRequest: Removing Authorization from user provided headers because it contains our access token" + ); originalHeaders.delete("Authorization"); } } + logDebugMessage("doRequest: Interception started"); ProcessState.getInstance().addState(PROCESS_STATE.CALLING_INTERCEPTION_REQUEST); try { let returnObj = undefined; @@ -155,10 +171,12 @@ AuthHttpRequest.doRequest = (httpCall, config, url) => if (preRequestLocalSessionState.status === "EXISTS") { const antiCsrfToken = yield AntiCSRF.getToken(preRequestLocalSessionState.lastAccessTokenUpdate); if (antiCsrfToken !== undefined) { + logDebugMessage("doRequest: Adding anti-csrf token to request"); clonedHeaders.set("anti-csrf", antiCsrfToken); } } if (AuthHttpRequest.config.autoAddCredentials) { + logDebugMessage("doRequest: Adding credentials include"); if (configWithAntiCsrf === undefined) { configWithAntiCsrf = { credentials: "include" @@ -171,12 +189,18 @@ AuthHttpRequest.doRequest = (httpCall, config, url) => } // adding rid for anti-csrf protection: Anti-csrf via custom header if (!clonedHeaders.has("rid")) { + logDebugMessage("doRequest: Adding rid header: anti-csrf"); clonedHeaders.set("rid", "anti-csrf"); + } else { + logDebugMessage("doRequest: rid header was already there in request"); } const transferMethod = AuthHttpRequest.config.tokenTransferMethod; + logDebugMessage("doRequest: Adding st-auth-mode header: " + transferMethod); clonedHeaders.set("st-auth-mode", transferMethod); yield setAuthorizationHeaderIfRequired(clonedHeaders); + logDebugMessage("doRequest: Making user's http call"); let response = yield httpCall(configWithAntiCsrf); + logDebugMessage("doRequest: User's http call ended"); yield saveTokensFromHeaders(response); fireSessionUpdateEventsIfNecessary( preRequestLocalSessionState.status === "EXISTS", @@ -184,8 +208,10 @@ AuthHttpRequest.doRequest = (httpCall, config, url) => response.headers.get("front-token") ); if (response.status === AuthHttpRequest.config.sessionExpiredStatusCode) { + logDebugMessage("doRequest: Status code is: " + response.status); let refreshResponse = yield onUnauthorisedResponse(preRequestLocalSessionState); if (refreshResponse.result !== "RETRY") { + logDebugMessage("doRequest: Not retrying original request"); returnObj = refreshResponse.error !== undefined ? refreshResponse.error : response; break; } @@ -200,6 +226,7 @@ AuthHttpRequest.doRequest = (httpCall, config, url) => // or the backend is down and we don't need to call it. const postRequestLocalSessionState = yield getLocalSessionState(); if (postRequestLocalSessionState.status === "NOT_EXISTS") { + logDebugMessage("doRequest: local session doesn't exist, so removing anti-csrf and sFrontToken"); yield AntiCSRF.removeToken(); yield FrontToken.removeToken(); } @@ -221,10 +248,13 @@ const LOCK_NAME = "REFRESH_TOKEN_USE"; export function onUnauthorisedResponse(preRequestLocalSessionState) { return __awaiter(this, void 0, void 0, function*() { let lock = getLock(); + logDebugMessage("onUnauthorisedResponse: trying to acquire lock"); yield lock.lock(LOCK_NAME); + logDebugMessage("onUnauthorisedResponse: lock acquired"); try { let postLockLocalSessionState = yield getLocalSessionState(); if (postLockLocalSessionState.status === "NOT_EXISTS") { + logDebugMessage("onUnauthorisedResponse: Not refreshing because local session state is NOT_EXISTS"); // if it comes here, it means a request was made thinking // that the session exists, but it doesn't actually exist. AuthHttpRequest.config.onHandleEvent({ @@ -240,6 +270,9 @@ export function onUnauthorisedResponse(preRequestLocalSessionState) { postLockLocalSessionState.lastAccessTokenUpdate !== preRequestLocalSessionState.lastAccessTokenUpdate) ) { + logDebugMessage( + "onUnauthorisedResponse: Retrying early because pre and post id refresh tokens don't match" + ); // means that some other process has already called this API and succeeded. so we need to call it again return { result: "RETRY" }; } @@ -247,14 +280,18 @@ export function onUnauthorisedResponse(preRequestLocalSessionState) { if (preRequestLocalSessionState.status === "EXISTS") { const antiCsrfToken = yield AntiCSRF.getToken(preRequestLocalSessionState.lastAccessTokenUpdate); if (antiCsrfToken !== undefined) { + logDebugMessage("onUnauthorisedResponse: Adding anti-csrf token to refresh API call"); headers.set("anti-csrf", antiCsrfToken); } } + logDebugMessage("onUnauthorisedResponse: Adding rid and fdi-versions to refresh call header"); headers.set("rid", AuthHttpRequest.rid); headers.set("fdi-version", supported_fdi.join(",")); const transferMethod = AuthHttpRequest.config.tokenTransferMethod; + logDebugMessage("onUnauthorisedResponse: Adding st-auth-mode header: " + transferMethod); headers.set("st-auth-mode", transferMethod); yield setAuthorizationHeaderIfRequired(headers, true); + logDebugMessage("onUnauthorisedResponse: Calling refresh pre API hook"); let preAPIResult = yield AuthHttpRequest.config.preAPIHook({ action: "REFRESH_SESSION", requestInit: { @@ -264,11 +301,14 @@ export function onUnauthorisedResponse(preRequestLocalSessionState) { }, url: AuthHttpRequest.refreshTokenUrl }); + logDebugMessage("onUnauthorisedResponse: Making refresh call"); const response = yield AuthHttpRequest.env.__supertokensOriginalFetch( preAPIResult.url, preAPIResult.requestInit ); + logDebugMessage("onUnauthorisedResponse: Refresh call ended"); yield saveTokensFromHeaders(response); + logDebugMessage("onUnauthorisedResponse: Refresh status code is: " + response.status); const isUnauthorised = response.status === AuthHttpRequest.config.sessionExpiredStatusCode; // There is a case where the FE thinks the session is valid, but backend doesn't get the tokens. // In this event, session expired error will be thrown and the frontend should remove this token @@ -286,6 +326,7 @@ export function onUnauthorisedResponse(preRequestLocalSessionState) { throw response; } if ((yield getLocalSessionState()).status === "NOT_EXISTS") { + logDebugMessage("onUnauthorisedResponse: local session doesn't exist, so returning session expired"); // The execution should never come here.. but just in case. // removed by server. So we logout // we do not send "UNAUTHORISED" event here because @@ -297,9 +338,11 @@ export function onUnauthorisedResponse(preRequestLocalSessionState) { AuthHttpRequest.config.onHandleEvent({ action: "REFRESH_SESSION" }); + logDebugMessage("onUnauthorisedResponse: Sending RETRY signal"); return { result: "RETRY" }; } catch (error) { if ((yield getLocalSessionState()).status === "NOT_EXISTS") { + logDebugMessage("onUnauthorisedResponse: local session doesn't exist, so returning session expired"); // removed by server. // we do not send "UNAUTHORISED" event here because // this is a result of the refresh API returning a session expiry, which @@ -307,10 +350,15 @@ export function onUnauthorisedResponse(preRequestLocalSessionState) { // in the first place. return { result: "SESSION_EXPIRED", error }; } + logDebugMessage("onUnauthorisedResponse: sending API_ERROR"); return { result: "API_ERROR", error }; } finally { lock.unlock(LOCK_NAME); + logDebugMessage("onUnauthorisedResponse: Released lock"); if ((yield getLocalSessionState()).status === "NOT_EXISTS") { + logDebugMessage( + "onUnauthorisedResponse: local session doesn't exist, so removing anti-csrf and sFrontToken" + ); yield AntiCSRF.removeToken(); yield FrontToken.removeToken(); } @@ -319,22 +367,27 @@ export function onUnauthorisedResponse(preRequestLocalSessionState) { } function saveTokensFromHeaders(response) { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("saveTokensFromHeaders: Saving updated tokens from the response headers"); const refreshToken = response.headers.get("st-refresh-token"); if (refreshToken !== undefined && refreshToken !== null) { + logDebugMessage("saveTokensFromHeaders: saving new refresh token"); yield setToken("refresh", refreshToken); } const accessToken = response.headers.get("st-access-token"); if (accessToken !== undefined && accessToken !== null) { + logDebugMessage("saveTokensFromHeaders: saving new access token"); yield setToken("access", accessToken); } const frontToken = response.headers.get("front-token"); if (frontToken !== undefined && frontToken !== null) { + logDebugMessage("saveTokensFromHeaders: Setting sFrontToken: " + frontToken); yield FrontToken.setItem(frontToken); } const antiCsrfToken = response.headers.get("anti-csrf"); if (antiCsrfToken !== undefined && antiCsrfToken !== null) { const tok = yield getLocalSessionState(); if (tok.status === "EXISTS") { + logDebugMessage("saveTokensFromHeaders: Setting anti-csrf token"); yield AntiCSRF.setItem(tok.lastAccessTokenUpdate, antiCsrfToken); } } @@ -342,6 +395,7 @@ function saveTokensFromHeaders(response) { } function setAuthorizationHeaderIfRequired(clonedHeaders, addRefreshToken = false) { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("setTokenHeaders: adding existing tokens as header"); // We set the Authorization header even if the tokenTransferMethod preference set in the config is cookies // since the active session may be using cookies. By default, we want to allow users to continue these sessions. // The new session preference should be applied at the start of the next session, if the backend allows it. @@ -353,9 +407,15 @@ function setAuthorizationHeaderIfRequired(clonedHeaders, addRefreshToken = false if (accessToken !== undefined && refreshToken !== undefined) { // the Headers class normalizes header names so we don't have to worry about casing if (clonedHeaders.has("Authorization")) { + logDebugMessage( + "setAuthorizationHeaderIfRequired: Authorization header defined by the user, not adding" + ); } else { clonedHeaders.set("Authorization", `Bearer ${addRefreshToken ? refreshToken : accessToken}`); + logDebugMessage("setAuthorizationHeaderIfRequired: added authorization header"); } + } else { + logDebugMessage("setAuthorizationHeaderIfRequired: token for header based auth not found"); } }); } diff --git a/lib/build/frontToken.js b/lib/build/frontToken.js index 6bf3210..9c53dde 100644 --- a/lib/build/frontToken.js +++ b/lib/build/frontToken.js @@ -32,12 +32,14 @@ var __awaiter = import AsyncStorage from "@react-native-async-storage/async-storage"; import { decode as atob } from "base-64"; import { getLocalSessionState, saveLastAccessTokenUpdate, setToken } from "./utils"; +import { logDebugMessage } from "./logger"; const FRONT_TOKEN_KEY = "supertokens-rn-front-token-key"; const FRONT_TOKEN_NAME = "sFrontToken"; export default class FrontToken { constructor() {} static getFrontTokenFromStorage() { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("getFrontTokenFromStorage: called"); let frontTokenFromStorage = yield AsyncStorage.getItem(FRONT_TOKEN_KEY); if (frontTokenFromStorage !== null) { let value = "; " + frontTokenFromStorage; @@ -70,15 +72,19 @@ export default class FrontToken { } static getFrontToken() { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("getFrontToken: called"); if ((yield getLocalSessionState()).status !== "EXISTS") { + logDebugMessage("getFrontToken: Returning because sIRTFrontend != EXISTS"); return null; } let token = yield this.getFrontTokenFromStorage(); + logDebugMessage("getFrontToken: returning: " + token); return token; }); } static getTokenInfo() { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("FrontToken.getTokenInfo: called"); let frontToken = yield this.getFrontToken(); if (frontToken === null) { if ((yield getLocalSessionState()).status === "EXISTS") { @@ -92,11 +98,16 @@ export default class FrontToken { return undefined; } } - return JSON.parse(decodeURIComponent(escape(atob(frontToken)))); + const parsedToken = JSON.parse(decodeURIComponent(escape(atob(frontToken)))); + logDebugMessage("FrontToken.getTokenInfo: returning ate: " + parsedToken.ate); + logDebugMessage("FrontToken.getTokenInfo: returning uid: " + parsedToken.uid); + logDebugMessage("FrontToken.getTokenInfo: returning up: " + parsedToken.up); + return parsedToken; }); } static setFrontToken(frontToken) { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("setFrontToken: called"); function setFrontTokenToStorage(frontToken) { return __awaiter(this, void 0, void 0, function*() { if (frontToken === undefined) { @@ -111,6 +122,7 @@ export default class FrontToken { } static removeToken() { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("FrontToken.removeToken: called"); yield this.setFrontToken(undefined); yield setToken("access", ""); yield setToken("refresh", ""); @@ -130,6 +142,7 @@ export default class FrontToken { if (frontToken === "remove") { return FrontToken.removeToken(); } + logDebugMessage("FrontToken.setItem: called"); yield this.setFrontToken(frontToken); FrontToken.waiters.forEach(f => f(undefined)); FrontToken.waiters = []; diff --git a/lib/build/logger.d.ts b/lib/build/logger.d.ts new file mode 100644 index 0000000..a5e767e --- /dev/null +++ b/lib/build/logger.d.ts @@ -0,0 +1,2 @@ +export declare function enableLogging(): void; +export declare function logDebugMessage(message: string): void; diff --git a/lib/build/logger.js b/lib/build/logger.js new file mode 100644 index 0000000..283298c --- /dev/null +++ b/lib/build/logger.js @@ -0,0 +1,27 @@ +/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { package_version as version } from "./version"; +const SUPERTOKENS_DEBUG_NAMESPACE = "com.supertokens"; +let __supertokensWebsiteLogging = false; +export function enableLogging() { + __supertokensWebsiteLogging = true; +} +export function logDebugMessage(message) { + if (__supertokensWebsiteLogging) { + console.log( + `${SUPERTOKENS_DEBUG_NAMESPACE} {t: "${new Date().toISOString()}", message: \"${message}\", supertokens-website-ver: "${version}"}` + ); + } +} diff --git a/lib/build/recipeImplementation.js b/lib/build/recipeImplementation.js index 36d92f5..789a41e 100644 --- a/lib/build/recipeImplementation.js +++ b/lib/build/recipeImplementation.js @@ -36,9 +36,11 @@ import { supported_fdi } from "./version"; import { interceptorFunctionRequestFulfilled, responseErrorInterceptor, responseInterceptor } from "./axios"; import { SuperTokensGeneralError } from "./error"; import { getLocalSessionState, normaliseCookieDomainOrThrowError, normaliseURLDomainOrThrowError } from "./utils"; +import { logDebugMessage } from "./logger"; export default function RecipeImplementation() { return { addFetchInterceptorsAndReturnModifiedFetch: function(originalFetch, _) { + logDebugMessage("addFetchInterceptorsAndReturnModifiedFetch: called"); return function(url, config) { return __awaiter(this, void 0, void 0, function*() { return yield AuthHttpRequest.doRequest( @@ -52,10 +54,12 @@ export default function RecipeImplementation() { }; }, addAxiosInterceptors: function(axiosInstance, _) { + logDebugMessage("addAxiosInterceptors: called"); // we first check if this axiosInstance already has our interceptors. let requestInterceptors = axiosInstance.interceptors.request; for (let i = 0; i < requestInterceptors.handlers.length; i++) { if (requestInterceptors.handlers[i].fulfilled === interceptorFunctionRequestFulfilled) { + logDebugMessage("addAxiosInterceptors: not adding because already added on this instance"); return; } } @@ -73,20 +77,24 @@ export default function RecipeImplementation() { }, getUserId: function(config) { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("getUserId: called"); let tokenInfo = yield FrontToken.getTokenInfo(); if (tokenInfo === undefined) { throw new Error("No session exists"); } + logDebugMessage("getUserId: returning: " + tokenInfo.uid); return tokenInfo.uid; }); }, getAccessTokenPayloadSecurely: function(config) { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("getAccessTokenPayloadSecurely: called"); let tokenInfo = yield FrontToken.getTokenInfo(); if (tokenInfo === undefined) { throw new Error("No session exists"); } if (tokenInfo.ate < Date.now()) { + logDebugMessage("getAccessTokenPayloadSecurely: access token expired. Refreshing session"); let retry = yield AuthHttpRequest.attemptRefreshingSession(); if (retry) { return yield this.getAccessTokenPayloadSecurely(config); @@ -94,16 +102,20 @@ export default function RecipeImplementation() { throw new Error("Could not refresh session"); } } + logDebugMessage("getAccessTokenPayloadSecurely: returning: " + JSON.stringify(tokenInfo.up)); return tokenInfo.up; }); }, doesSessionExist: function(config) { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("doesSessionExist: called"); const tokenInfo = yield FrontToken.getTokenInfo(); if (tokenInfo === undefined) { + logDebugMessage("doesSessionExist: access token does not exist locally"); return false; } if (tokenInfo.ate < Date.now()) { + logDebugMessage("doesSessionExist: access token expired. Refreshing session"); const preRequestLocalSessionState = yield getLocalSessionState(); const refresh = yield onUnauthorisedResponse(preRequestLocalSessionState); return refresh.result === "RETRY"; @@ -113,12 +125,14 @@ export default function RecipeImplementation() { }, signOut: function(config) { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("signOut: called"); if (!(yield this.doesSessionExist(config))) { config.onHandleEvent({ action: "SIGN_OUT" }); return; } + logDebugMessage("signOut: Calling refresh pre API hook"); let preAPIResult = yield config.preAPIHook({ action: "SIGN_OUT", requestInit: { @@ -130,7 +144,10 @@ export default function RecipeImplementation() { }, url: AuthHttpRequest.signOutUrl }); + logDebugMessage("signOut: Calling API"); let resp = yield fetch(preAPIResult.url, preAPIResult.requestInit); + logDebugMessage("signOut: API ended"); + logDebugMessage("signOut: API responded with status code: " + resp.status); if (resp.status === config.sessionExpiredStatusCode) { // refresh must have already sent session expiry event return; @@ -140,6 +157,7 @@ export default function RecipeImplementation() { } let responseJson = yield resp.clone().json(); if (responseJson.status === "GENERAL_ERROR") { + logDebugMessage("doRequest: Throwing general error"); let message = responseJson.message === undefined ? "No Error Message Provided" : responseJson.message; throw new SuperTokensGeneralError(message); @@ -148,6 +166,14 @@ export default function RecipeImplementation() { }); }, shouldDoInterceptionBasedOnUrl: (toCheckUrl, apiDomain, sessionTokenBackendDomain) => { + logDebugMessage( + "shouldDoInterceptionBasedOnUrl: toCheckUrl: " + + toCheckUrl + + " apiDomain: " + + apiDomain + + " sessionTokenBackendDomain: " + + sessionTokenBackendDomain + ); function isNumeric(str) { if (typeof str != "string") return false; // we only process strings! return ( diff --git a/lib/build/types.d.ts b/lib/build/types.d.ts index 5cf302f..40547a2 100644 --- a/lib/build/types.d.ts +++ b/lib/build/types.d.ts @@ -7,6 +7,7 @@ export declare type Event = { }; export declare type EventHandler = (event: Event) => void; export declare type InputType = { + enableDebugLogs?: boolean; apiDomain: string; apiBasePath?: string; sessionExpiredStatusCode?: number; diff --git a/lib/build/utils.js b/lib/build/utils.js index a3a313e..e5594fa 100644 --- a/lib/build/utils.js +++ b/lib/build/utils.js @@ -35,6 +35,7 @@ import AuthHttpRequest from "./fetch"; import FrontToken from "./frontToken"; import NormalisedURLDomain from "./normalisedURLDomain"; import NormalisedURLPath from "./normalisedURLPath"; +import { enableLogging, logDebugMessage } from "./logger"; const LAST_ACCESS_TOKEN_UPDATE = "st-last-access-token-update"; const REFRESH_TOKEN_NAME = "st-refresh-token"; const ACCESS_TOKEN_NAME = "st-access-token"; @@ -116,6 +117,9 @@ export function validateAndNormaliseInputOrThrowError(options) { if (options.onHandleEvent !== undefined) { onHandleEvent = options.onHandleEvent; } + if (options.enableDebugLogs !== undefined && options.enableDebugLogs) { + enableLogging(); + } let override = Object.assign({ functions: oI => oI }, options.override); let tokenTransferMethod = options.tokenTransferMethod !== undefined ? options.tokenTransferMethod : "header"; return { @@ -132,6 +136,7 @@ export function validateAndNormaliseInputOrThrowError(options) { } export function setToken(tokenType, value) { const name = getStorageNameForToken(tokenType); + logDebugMessage(`setToken: saved ${tokenType} token in storage`); // We save the tokens with a 100-year expiration time return storeInStorage(name, value, Date.now() + 3153600000); } @@ -151,7 +156,9 @@ export function storeInStorage(name, value, expiry) { */ export function saveLastAccessTokenUpdate() { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("saveLastAccessTokenUpdate: called"); const now = Date.now().toString(); + logDebugMessage("saveLastAccessTokenUpdate: setting " + now); yield storeInStorage(LAST_ACCESS_TOKEN_UPDATE, now, Number.MAX_SAFE_INTEGER); // We clear the sIRTFrontend cookie // We are handling this as a special case here because we want to limit the scope of legacy code @@ -188,11 +195,18 @@ export function getTokenForHeaderAuth(tokenType) { */ export function getLocalSessionState() { return __awaiter(this, void 0, void 0, function*() { + logDebugMessage("getLocalSessionState: called"); const lastAccessTokenUpdate = yield getFromStorage(LAST_ACCESS_TOKEN_UPDATE); const frontTokenExists = yield FrontToken.doesTokenExists(); if (frontTokenExists && lastAccessTokenUpdate !== undefined) { + logDebugMessage( + "getLocalSessionState: returning EXISTS since both frontToken and lastAccessTokenUpdate exists" + ); return { status: "EXISTS", lastAccessTokenUpdate: lastAccessTokenUpdate }; } else { + logDebugMessage( + "getLocalSessionState: returning NOT_EXISTS since frontToken was cleared but lastAccessTokenUpdate exists" + ); return { status: "NOT_EXISTS" }; } }); @@ -204,28 +218,35 @@ export function fireSessionUpdateEventsIfNecessary(wasLoggedIn, status, frontTok // This may be considered a bug, but it is the existing behaviour before the rework if (frontTokenHeaderFromResponse === undefined || frontTokenHeaderFromResponse === null) { // The access token (and the session) hasn't been updated. + logDebugMessage("fireSessionUpdateEventsIfNecessary returning early because the front token was not updated"); return; } // if the current endpoint clears the session it'll set the front-token to remove // any other update means it's created or updated. const frontTokenExistsAfter = frontTokenHeaderFromResponse !== "remove"; + logDebugMessage( + `fireSessionUpdateEventsIfNecessary wasLoggedIn: ${wasLoggedIn} frontTokenExistsAfter: ${frontTokenExistsAfter} status: ${status}` + ); if (wasLoggedIn) { // we check for wasLoggedIn cause we don't want to fire an event // unnecessarily on first app load or if the user tried // to query an API that returned 401 while the user was not logged in... if (!frontTokenExistsAfter) { if (status === AuthHttpRequest.config.sessionExpiredStatusCode) { + logDebugMessage("onUnauthorisedResponse: firing UNAUTHORISED event"); AuthHttpRequest.config.onHandleEvent({ action: "UNAUTHORISED", sessionExpiredOrRevoked: true }); } else { + logDebugMessage("onUnauthorisedResponse: firing SIGN_OUT event"); AuthHttpRequest.config.onHandleEvent({ action: "SIGN_OUT" }); } } } else if (frontTokenExistsAfter) { + logDebugMessage("onUnauthorisedResponse: firing SESSION_CREATED event"); AuthHttpRequest.config.onHandleEvent({ action: "SESSION_CREATED" }); From 90ff3f9133df891e36629b2ca0a2c7ee9cb783e9 Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Wed, 3 Jan 2024 12:58:11 +0530 Subject: [PATCH 11/18] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2616365..b0a0e24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update contributing Prerequisites - Remove unused ESLint and Prettier config files from TestingApp +## [4.1.0] - 2024-01-03 +- Added debug logs to the SDK + ## [4.0.8] - 2023-09-26 - use `URL` polyfill for `shouldDoInterceptionBasedOnUrl`: https://github.com/supertokens/supertokens-react-native/pull/111 From 9fab4bc532bf268efb73b4029f8701429d7c9530 Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Wed, 3 Jan 2024 13:03:32 +0530 Subject: [PATCH 12/18] Update version --- lib/build/version.d.ts | 2 +- lib/build/version.js | 2 +- lib/ts/version.ts | 2 +- package-lock.json | 2 +- package.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/build/version.d.ts b/lib/build/version.d.ts index 466e169..f3bd561 100644 --- a/lib/build/version.d.ts +++ b/lib/build/version.d.ts @@ -1,2 +1,2 @@ -export declare const package_version = "4.0.8"; +export declare const package_version = "4.1.0"; export declare const supported_fdi: string[]; diff --git a/lib/build/version.js b/lib/build/version.js index e042580..ea710d9 100644 --- a/lib/build/version.js +++ b/lib/build/version.js @@ -12,5 +12,5 @@ * License for the specific language governing permissions and limitations * under the License. */ -export const package_version = "4.0.8"; +export const package_version = "4.1.0"; export const supported_fdi = ["1.16", "1.17", "1.18"]; diff --git a/lib/ts/version.ts b/lib/ts/version.ts index 814cd8e..7b913e7 100644 --- a/lib/ts/version.ts +++ b/lib/ts/version.ts @@ -12,6 +12,6 @@ * License for the specific language governing permissions and limitations * under the License. */ -export const package_version = "4.0.8"; +export const package_version = "4.1.0"; export const supported_fdi = ["1.16", "1.17", "1.18"]; diff --git a/package-lock.json b/package-lock.json index ca2c65e..96e7a89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "supertokens-react-native", - "version": "4.0.8", + "version": "4.1.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 4a81be6..2dba897 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "supertokens-react-native", - "version": "4.0.8", + "version": "4.1.0", "description": "React Native SDK for SuperTokens", "main": "index.js", "scripts": { From 03701ef8cf49caa77e78d492d2880b7bec2a954e Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Wed, 3 Jan 2024 15:54:41 +0530 Subject: [PATCH 13/18] Review fixes --- lib/ts/fetch.ts | 7 ++++++- lib/ts/logger.ts | 2 +- lib/ts/utils.ts | 6 +----- package-lock.json | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/ts/fetch.ts b/lib/ts/fetch.ts index cec00fc..89341b9 100644 --- a/lib/ts/fetch.ts +++ b/lib/ts/fetch.ts @@ -29,7 +29,7 @@ import { import FrontToken from "./frontToken"; import RecipeImplementation from "./recipeImplementation"; import OverrideableBuilder from "supertokens-js-override"; -import { logDebugMessage } from "./logger"; +import { logDebugMessage, enableLogging } from "./logger"; declare let global: any; @@ -48,6 +48,11 @@ export default class AuthHttpRequest { static init(options: InputType) { let config = validateAndNormaliseInputOrThrowError(options); + + if (options.enableDebugLogs !== undefined && options.enableDebugLogs) { + enableLogging(); + } + logDebugMessage("init: called"); logDebugMessage("init: Input apiBasePath: " + config.apiBasePath); logDebugMessage("init: Input apiDomain: " + config.apiDomain); diff --git a/lib/ts/logger.ts b/lib/ts/logger.ts index bddcf5e..f40368f 100644 --- a/lib/ts/logger.ts +++ b/lib/ts/logger.ts @@ -26,7 +26,7 @@ export function enableLogging() { export function logDebugMessage(message: string) { if (__supertokensWebsiteLogging) { console.log( - `${SUPERTOKENS_DEBUG_NAMESPACE} {t: "${new Date().toISOString()}", message: \"${message}\", supertokens-website-ver: "${version}"}` + `${SUPERTOKENS_DEBUG_NAMESPACE} {t: "${new Date().toISOString()}", message: \"${message}\", supertokens-react-native: "${version}"}` ); } } diff --git a/lib/ts/utils.ts b/lib/ts/utils.ts index f712162..dd1438c 100644 --- a/lib/ts/utils.ts +++ b/lib/ts/utils.ts @@ -5,7 +5,7 @@ import FrontToken from "./frontToken"; import NormalisedURLDomain from "./normalisedURLDomain"; import NormalisedURLPath from "./normalisedURLPath"; import { InputType, NormalisedInputType, EventHandler, RecipeInterface, TokenType } from "./types"; -import { enableLogging, logDebugMessage } from "./logger"; +import { logDebugMessage } from "./logger"; const LAST_ACCESS_TOKEN_UPDATE = "st-last-access-token-update"; const REFRESH_TOKEN_NAME = "st-refresh-token"; @@ -113,10 +113,6 @@ export function validateAndNormaliseInputOrThrowError(options: InputType): Norma onHandleEvent = options.onHandleEvent; } - if (options.enableDebugLogs !== undefined && options.enableDebugLogs) { - enableLogging(); - } - let override: { functions: (originalImplementation: RecipeInterface) => RecipeInterface; } = { diff --git a/package-lock.json b/package-lock.json index 96e7a89..1ba3d7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "supertokens-react-native", - "version": "4.0.8", + "version": "4.1.0", "license": "Apache 2.0", "dependencies": { "base-64": "^1.0.0", From 547e4230b098c6528669b87d39d7b16163e8c522 Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Wed, 3 Jan 2024 16:09:47 +0530 Subject: [PATCH 14/18] Fix typo in logs --- lib/ts/fetch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ts/fetch.ts b/lib/ts/fetch.ts index 89341b9..85ccfe3 100644 --- a/lib/ts/fetch.ts +++ b/lib/ts/fetch.ts @@ -296,7 +296,7 @@ export async function onUnauthorisedResponse( postLockLocalSessionState.lastAccessTokenUpdate !== preRequestLocalSessionState.lastAccessTokenUpdate) ) { logDebugMessage( - "onUnauthorisedResponse: Retrying early because pre and post id refresh tokens don't match" + "onUnauthorisedResponse: Retrying early because pre and post lastAccessTokenUpdate don't match" ); // means that some other process has already called this API and succeeded. so we need to call it again return { result: "RETRY" }; From 3974aa012844e53fe8ce3b4f4b6977186a25fd5b Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Wed, 3 Jan 2024 16:48:59 +0530 Subject: [PATCH 15/18] Update build files --- lib/build/fetch.js | 7 +++++-- lib/build/logger.d.ts | 1 + lib/build/logger.js | 5 ++++- lib/build/utils.js | 5 +---- lib/ts/logger.ts | 4 ++++ 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/build/fetch.js b/lib/build/fetch.js index af32783..8c01e2e 100644 --- a/lib/build/fetch.js +++ b/lib/build/fetch.js @@ -57,7 +57,7 @@ import { import FrontToken from "./frontToken"; import RecipeImplementation from "./recipeImplementation"; import OverrideableBuilder from "supertokens-js-override"; -import { logDebugMessage } from "./logger"; +import { logDebugMessage, enableLogging } from "./logger"; /** * @class AuthHttpRequest * @description wrapper for common http methods. @@ -65,6 +65,9 @@ import { logDebugMessage } from "./logger"; export default class AuthHttpRequest { static init(options) { let config = validateAndNormaliseInputOrThrowError(options); + if (options.enableDebugLogs !== undefined && options.enableDebugLogs) { + enableLogging(); + } logDebugMessage("init: called"); logDebugMessage("init: Input apiBasePath: " + config.apiBasePath); logDebugMessage("init: Input apiDomain: " + config.apiDomain); @@ -271,7 +274,7 @@ export function onUnauthorisedResponse(preRequestLocalSessionState) { preRequestLocalSessionState.lastAccessTokenUpdate) ) { logDebugMessage( - "onUnauthorisedResponse: Retrying early because pre and post id refresh tokens don't match" + "onUnauthorisedResponse: Retrying early because pre and post lastAccessTokenUpdate don't match" ); // means that some other process has already called this API and succeeded. so we need to call it again return { result: "RETRY" }; diff --git a/lib/build/logger.d.ts b/lib/build/logger.d.ts index a5e767e..fc6306f 100644 --- a/lib/build/logger.d.ts +++ b/lib/build/logger.d.ts @@ -1,2 +1,3 @@ export declare function enableLogging(): void; +export declare function disableLogging(): void; export declare function logDebugMessage(message: string): void; diff --git a/lib/build/logger.js b/lib/build/logger.js index 283298c..1c55600 100644 --- a/lib/build/logger.js +++ b/lib/build/logger.js @@ -18,10 +18,13 @@ let __supertokensWebsiteLogging = false; export function enableLogging() { __supertokensWebsiteLogging = true; } +export function disableLogging() { + __supertokensWebsiteLogging = false; +} export function logDebugMessage(message) { if (__supertokensWebsiteLogging) { console.log( - `${SUPERTOKENS_DEBUG_NAMESPACE} {t: "${new Date().toISOString()}", message: \"${message}\", supertokens-website-ver: "${version}"}` + `${SUPERTOKENS_DEBUG_NAMESPACE} {t: "${new Date().toISOString()}", message: \"${message}\", supertokens-react-native: "${version}"}` ); } } diff --git a/lib/build/utils.js b/lib/build/utils.js index e5594fa..3b2e909 100644 --- a/lib/build/utils.js +++ b/lib/build/utils.js @@ -35,7 +35,7 @@ import AuthHttpRequest from "./fetch"; import FrontToken from "./frontToken"; import NormalisedURLDomain from "./normalisedURLDomain"; import NormalisedURLPath from "./normalisedURLPath"; -import { enableLogging, logDebugMessage } from "./logger"; +import { logDebugMessage } from "./logger"; const LAST_ACCESS_TOKEN_UPDATE = "st-last-access-token-update"; const REFRESH_TOKEN_NAME = "st-refresh-token"; const ACCESS_TOKEN_NAME = "st-access-token"; @@ -117,9 +117,6 @@ export function validateAndNormaliseInputOrThrowError(options) { if (options.onHandleEvent !== undefined) { onHandleEvent = options.onHandleEvent; } - if (options.enableDebugLogs !== undefined && options.enableDebugLogs) { - enableLogging(); - } let override = Object.assign({ functions: oI => oI }, options.override); let tokenTransferMethod = options.tokenTransferMethod !== undefined ? options.tokenTransferMethod : "header"; return { diff --git a/lib/ts/logger.ts b/lib/ts/logger.ts index f40368f..a40dc37 100644 --- a/lib/ts/logger.ts +++ b/lib/ts/logger.ts @@ -23,6 +23,10 @@ export function enableLogging() { __supertokensWebsiteLogging = true; } +export function disableLogging() { + __supertokensWebsiteLogging = false; +} + export function logDebugMessage(message: string) { if (__supertokensWebsiteLogging) { console.log( From 234df125b4e38b324cba1018f1b9dfa8ec829be7 Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Wed, 3 Jan 2024 17:12:49 +0530 Subject: [PATCH 16/18] Add test for logger --- TestingApp/test/logger.spec.js | 62 ++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 TestingApp/test/logger.spec.js diff --git a/TestingApp/test/logger.spec.js b/TestingApp/test/logger.spec.js new file mode 100644 index 0000000..ccd1b7f --- /dev/null +++ b/TestingApp/test/logger.spec.js @@ -0,0 +1,62 @@ +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +const tough = require("tough-cookie"); +import SuperTokens from "supertokens-react-native"; +import { disableLogging } from "supertokens-react-native/lib/build/logger"; +// jest does not call setupFiles properly with the new react-native init, so doing it this way instead +import "./setup"; + +process.env.TEST_MODE = "testing"; + +describe("Logger test", () => { + const consoleSpy = jest.spyOn(console, "log"); + + beforeEach(() => { + disableLogging(); + let nodeFetch = require("node-fetch").default; + const fetch = require("fetch-cookie")(nodeFetch, new tough.CookieJar()); + global.fetch = fetch; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it("should log to console when debug logging is enabled", async () => { + SuperTokens.init({ + apiDomain: "http://localhost:3000", + enableDebugLogs: true + }); + + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("init: called")); + }); + + it("should not log to console when debug logging is disabled", async () => { + SuperTokens.init({ + apiDomain: "http://localhost:3000", + enableDebugLogs: false + }); + + expect(consoleSpy).not.toHaveBeenCalledWith(expect.stringContaining("init: called")); + }); + + it("should not log to console when no argument is passed for enableDebugLogs", async () => { + SuperTokens.init({ + apiDomain: "http://localhost:3000" + }); + + expect(consoleSpy).not.toHaveBeenCalledWith(expect.stringContaining("init: called")); + }); +}); From 0136e9a10f6a19c42cfbe75e170a997a7ec9107f Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Wed, 3 Jan 2024 17:16:43 +0530 Subject: [PATCH 17/18] Update size limit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2dba897..1b37827 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "size-limit": [ { "path": "lib/build/index.js", - "limit": "25kb" + "limit": "27kb" } ] } \ No newline at end of file From 9fbb4a01e4b803bcb191dff1c725e20d50302286 Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Wed, 3 Jan 2024 17:31:46 +0530 Subject: [PATCH 18/18] Move unreleased changes to latest release in Changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a0e24..f90b753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [unreleased] -- Update contributing Prerequisites -- Remove unused ESLint and Prettier config files from TestingApp ## [4.1.0] - 2024-01-03 + - Added debug logs to the SDK +- Update contributing Prerequisites +- Remove unused ESLint and Prettier config files from TestingApp ## [4.0.8] - 2023-09-26 - use `URL` polyfill for `shouldDoInterceptionBasedOnUrl`: https://github.com/supertokens/supertokens-react-native/pull/111