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" });