UNPKG

supertokens-react-native

Version:
254 lines (253 loc) 10.9 kB
var __awaiter = (this && this.__awaiter) || function(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function(resolve) { resolve(value); }); } return new (P || (P = Promise))(function(resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import AsyncStorage from "@react-native-async-storage/async-storage"; import AuthHttpRequest from "./fetch"; import FrontToken from "./frontToken"; import NormalisedURLDomain from "./normalisedURLDomain"; import NormalisedURLPath from "./normalisedURLPath"; 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"; export function isAnIpAddress(ipaddress) { return /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test( ipaddress ); } export function normaliseURLDomainOrThrowError(input) { let str = new NormalisedURLDomain(input).getAsStringDangerous(); return str; } export function normaliseURLPathOrThrowError(input) { return new NormalisedURLPath(input).getAsStringDangerous(); } export function normaliseSessionScopeOrThrowError(cookieDomain) { function helper(cookieDomain) { cookieDomain = cookieDomain.trim().toLowerCase(); // first we convert it to a URL so that we can use the URL class if (cookieDomain.startsWith(".")) { cookieDomain = cookieDomain.substr(1); } if (!cookieDomain.startsWith("http://") && !cookieDomain.startsWith("https://")) { cookieDomain = "http://" + cookieDomain; } try { let urlObj = new URL(cookieDomain); cookieDomain = urlObj.hostname; return cookieDomain; } catch (err) { throw new Error("Please provide a valid cookieDomain"); } } function isAnIpAddress(ipaddress) { return /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test( ipaddress ); } let noDotNormalised = helper(cookieDomain); if (noDotNormalised === "localhost" || isAnIpAddress(noDotNormalised)) { return noDotNormalised; } if (cookieDomain.startsWith(".")) { return "." + noDotNormalised; } return noDotNormalised; } export function validateAndNormaliseInputOrThrowError(options) { let apiDomain = normaliseURLDomainOrThrowError(options.apiDomain); let apiBasePath = normaliseURLPathOrThrowError("/auth"); if (options.apiBasePath !== undefined) { apiBasePath = normaliseURLPathOrThrowError(options.apiBasePath); } let sessionExpiredStatusCode = 401; if (options.sessionExpiredStatusCode !== undefined) { sessionExpiredStatusCode = options.sessionExpiredStatusCode; } let autoAddCredentials = true; if (options.autoAddCredentials !== undefined) { autoAddCredentials = options.autoAddCredentials; } let sessionTokenBackendDomain = undefined; if (options.sessionTokenBackendDomain !== undefined) { sessionTokenBackendDomain = normaliseSessionScopeOrThrowError(options.sessionTokenBackendDomain); } let maxRetryAttemptsForSessionRefresh = 10; if (options.maxRetryAttemptsForSessionRefresh !== undefined) { if (options.maxRetryAttemptsForSessionRefresh < 0) { throw new Error("maxRetryAttemptsForSessionRefresh must be greater than or equal to 0."); } maxRetryAttemptsForSessionRefresh = options.maxRetryAttemptsForSessionRefresh; } let preAPIHook = context => __awaiter(this, void 0, void 0, function*() { return { url: context.url, requestInit: context.requestInit }; }); if (options.preAPIHook !== undefined) { preAPIHook = options.preAPIHook; } let onHandleEvent = () => {}; if (options.onHandleEvent !== undefined) { onHandleEvent = options.onHandleEvent; } let override = Object.assign({ functions: oI => oI }, options.override); let tokenTransferMethod = options.tokenTransferMethod !== undefined ? options.tokenTransferMethod : "header"; return { apiDomain, apiBasePath, sessionExpiredStatusCode, autoAddCredentials, maxRetryAttemptsForSessionRefresh, sessionTokenBackendDomain, tokenTransferMethod, preAPIHook, onHandleEvent, override }; } 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); } export function storeInStorage(name, value, expiry) { return __awaiter(this, void 0, void 0, function*() { const storageKey = `st-storage-item-${name}`; if (value === "") { return yield AsyncStorage.removeItem(storageKey); } return yield AsyncStorage.setItem(storageKey, value); }); } /** * Last access token update is used to record the last time the access token had changed. * This is used to synchronise parallel calls to the refresh API to prevent multiple calls * to the refresh endpoint */ 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 yield storeInStorage("sIRTFrontend", "", 0); }); } export function getStorageNameForToken(tokenType) { switch (tokenType) { case "access": return ACCESS_TOKEN_NAME; case "refresh": return REFRESH_TOKEN_NAME; } } function getFromStorage(name) { return __awaiter(this, void 0, void 0, function*() { const itemInStorage = yield AsyncStorage.getItem(`st-storage-item-${name}`); if (itemInStorage === null) { return undefined; } return itemInStorage; }); } export function getTokenForHeaderAuth(tokenType) { return __awaiter(this, void 0, void 0, function*() { const name = getStorageNameForToken(tokenType); return getFromStorage(name); }); } /** * The web SDK has additional checks for this function. This difference is because * for the mobile SDKs there will never be a case where the fronttoken is undefined * but a session may still exist */ 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" }; } }); } export function fireSessionUpdateEventsIfNecessary(wasLoggedIn, status, frontTokenHeaderFromResponse) { // In case we've received a 401 that didn't clear the session (e.g.: we've sent no session token, or we should try refreshing) // then onUnauthorised will handle firing the UNAUTHORISED event if necessary // In some rare cases (where we receive a 401 that also clears the session) this will fire the event twice. // 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" }); } }