supertokens-auth-react
Version:
ReactJS SDK that provides login functionality with SuperTokens.
590 lines (582 loc) • 29.5 kB
JavaScript
"use strict";
var genericComponentOverrideContext = require("./genericComponentOverrideContext.js");
var WebJSSessionRecipe = require("supertokens-web-js/recipe/session");
var recipe = require("./oauth2provider-shared.js");
var index = require("./recipeModule-shared.js");
var utils = require("supertokens-web-js/utils");
var windowHandler = require("supertokens-web-js/utils/windowHandler");
function _interopDefault(e) {
return e && e.__esModule ? e : { default: e };
}
var WebJSSessionRecipe__default = /*#__PURE__*/ _interopDefault(WebJSSessionRecipe);
/* 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.
*/
function normaliseSessionConfig(config) {
var _a, _b, _c;
if (config === undefined) {
config = {};
}
var accessDeniedScreenStyle =
(_b = (_a = config.accessDeniedScreen) === null || _a === void 0 ? void 0 : _a.style) !== null && _b !== void 0
? _b
: "";
var accessDeniedScreen = {
style: accessDeniedScreenStyle,
};
var override = genericComponentOverrideContext.__assign(
{
functions: function (originalImplementation) {
return originalImplementation;
},
},
config.override
);
return genericComponentOverrideContext.__assign(
genericComponentOverrideContext.__assign(
{},
genericComponentOverrideContext.normaliseRecipeModuleConfig(config)
),
{
// TODO: ideally we'd get the default (or normalized) value from supertokens-website
invalidClaimStatusCode: (_c = config.invalidClaimStatusCode) !== null && _c !== void 0 ? _c : 403,
accessDeniedScreen: accessDeniedScreen,
override: override,
}
);
}
var getFailureRedirectionInfo = function (_a) {
var invalidClaims = _a.invalidClaims,
overrideGlobalClaimValidators = _a.overrideGlobalClaimValidators,
userContext = _a.userContext;
return genericComponentOverrideContext.__awaiter(void 0, void 0, void 0, function () {
var globalValidators, failedClaim, _loop_1, _i, globalValidators_1, validator, state_1;
return genericComponentOverrideContext.__generator(this, function (_b) {
switch (_b.label) {
case 0:
globalValidators = utils.getGlobalClaimValidators({
overrideGlobalClaimValidators: overrideGlobalClaimValidators,
userContext: userContext,
});
failedClaim = undefined;
_loop_1 = function (validator) {
var claim, failureCallback, redirectPath;
return genericComponentOverrideContext.__generator(this, function (_c) {
switch (_c.label) {
case 0:
claim = invalidClaims.find(function (c) {
return c.id === validator.id;
});
if (!(claim !== undefined)) return [3 /*break*/, 2];
failureCallback = validator.onFailureRedirection;
if (!failureCallback) return [3 /*break*/, 2];
return [
4 /*yield*/,
failureCallback({ reason: claim.reason, userContext: userContext }),
];
case 1:
redirectPath = _c.sent();
if (redirectPath !== undefined) {
return [
2 /*return*/,
{
value: {
redirectPath: redirectPath,
failedClaim: claim,
},
},
];
}
_c.label = 2;
case 2:
if (validator.showAccessDeniedOnFailure !== false && failedClaim === undefined) {
failedClaim = claim;
}
return [2 /*return*/];
}
});
};
(_i = 0), (globalValidators_1 = globalValidators);
_b.label = 1;
case 1:
if (!(_i < globalValidators_1.length)) return [3 /*break*/, 4];
validator = globalValidators_1[_i];
return [5 /*yield**/, _loop_1(validator)];
case 2:
state_1 = _b.sent();
if (typeof state_1 === "object") return [2 /*return*/, state_1.value];
_b.label = 3;
case 3:
_i++;
return [3 /*break*/, 1];
case 4:
return [
2 /*return*/,
{
redirectPath: undefined,
failedClaim: failedClaim,
},
];
}
});
});
};
function validateAndCompareOnFailureRedirectionURLToCurrent(redirectURL) {
var currentUrl = windowHandler.WindowHandlerReference.getReferenceOrThrow().windowHandler.location.getHref();
var fullRedirectURL;
try {
new URL(redirectURL);
// if the url is a full, valid url, we can use that
fullRedirectURL = redirectURL;
} catch (_a) {
// If we get here, we know it's not full url
// We check if it's an absolute path
if (!redirectURL.startsWith("/")) {
throw new Error("onFailureRedirectionURL returned a relative url: ".concat(redirectURL));
}
var appInfo = genericComponentOverrideContext.SuperTokens.getInstanceOrThrow().appInfo;
// otherwise we prepend the websiteDomain
fullRedirectURL = "".concat(appInfo.websiteDomain.getAsStringDangerous()).concat(redirectURL);
}
return currentUrl === fullRedirectURL;
}
/* 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.
*/
var Session = /** @class */ (function (_super) {
genericComponentOverrideContext.__extends(Session, _super);
function Session(config, webJSRecipe) {
if (webJSRecipe === void 0) {
webJSRecipe = WebJSSessionRecipe__default.default;
}
var _this = _super.call(this, config) || this;
_this.webJSRecipe = webJSRecipe;
_this.recipeID = Session.RECIPE_ID;
_this.eventListeners = new Set();
_this.getUserId = function (input) {
return _this.webJSRecipe.getUserId(input);
};
_this.getAccessToken = function (input) {
return _this.webJSRecipe.getAccessToken(input);
};
_this.getClaimValue = function (input) {
return _this.webJSRecipe.getClaimValue(input);
};
_this.getAccessTokenPayloadSecurely = function (input) {
return genericComponentOverrideContext.__awaiter(_this, void 0, void 0, function () {
return genericComponentOverrideContext.__generator(this, function (_a) {
return [2 /*return*/, this.webJSRecipe.getAccessTokenPayloadSecurely(input)];
});
});
};
_this.doesSessionExist = function (input) {
return _this.webJSRecipe.doesSessionExist(input);
};
_this.signOut = function (input) {
return _this.webJSRecipe.signOut(input);
};
_this.attemptRefreshingSession = function () {
return genericComponentOverrideContext.__awaiter(_this, void 0, void 0, function () {
return genericComponentOverrideContext.__generator(this, function (_a) {
return [2 /*return*/, this.webJSRecipe.attemptRefreshingSession()];
});
});
};
_this.validateClaims = function (input) {
return _this.webJSRecipe.validateClaims(input);
};
_this.getInvalidClaimsFromResponse = function (input) {
return _this.webJSRecipe.getInvalidClaimsFromResponse(input);
};
/**
* @returns Function to remove event listener
*/
_this.addEventListener = function (listener) {
_this.eventListeners.add(listener);
return function () {
return _this.eventListeners.delete(listener);
};
};
_this.validateGlobalClaimsAndHandleSuccessRedirection = function (
// We redefine recipeId to be a string here, because everywhere in the SDK we treat
// it as a string (e.g.: when defining it in recipes), but we want to type it more
// strictly in the callbacks the app provides to help integrating our SDK.
// This is the "meeting point" between the two types, so we need to cast between them here.
successRedirectContext,
fallbackRecipeId,
redirectToPath,
userContext,
navigate
) {
return genericComponentOverrideContext.__awaiter(_this, void 0, void 0, function () {
var invalidClaims, jsonContext, failureRedirectInfo, successContextStr, storedContext;
return genericComponentOverrideContext.__generator(this, function (_a) {
switch (_a.label) {
case 0:
userContext = genericComponentOverrideContext.getNormalisedUserContext(userContext);
return [4 /*yield*/, this.doesSessionExist({ userContext: userContext })];
case 1:
// First we check if there is an active session
if (!_a.sent()) {
// If there is none, we have no way of checking claims, so we redirect to the auth page
// This can happen e.g.: if the user clicked on the email verification link in a browser without an active session
return [
2 /*return*/,
genericComponentOverrideContext.SuperTokens.getInstanceOrThrow().redirectToAuth({
navigate: navigate,
redirectBack: false,
userContext: userContext,
}),
];
}
return [4 /*yield*/, this.validateClaims({ userContext: userContext })];
case 2:
invalidClaims = _a.sent();
if (!(invalidClaims.length > 0)) return [3 /*break*/, 6];
if (!(successRedirectContext !== undefined)) return [3 /*break*/, 4];
jsonContext = JSON.stringify({
successRedirectContext: successRedirectContext,
redirectToPath: redirectToPath,
});
return [
4 /*yield*/,
genericComponentOverrideContext.setLocalStorage(
"supertokens-success-redirection-context",
jsonContext
),
];
case 3:
_a.sent();
_a.label = 4;
case 4:
return [
4 /*yield*/,
getFailureRedirectionInfo({
invalidClaims: invalidClaims,
userContext: userContext,
}),
];
case 5:
failureRedirectInfo = _a.sent();
// if redirectPath is string that means failed claim had callback that returns path, we redirect there otherwise continue
if (failureRedirectInfo.redirectPath !== undefined) {
// the validation part can throw, but this is handled in all places where this is called,
// since getFailureRedirectionInfo can also throw
if (
validateAndCompareOnFailureRedirectionURLToCurrent(failureRedirectInfo.redirectPath)
) {
throw new Error(
"onFailureRedirectionURL returned the current URL (".concat(
failureRedirectInfo.redirectPath,
") during success redirection. This indicates that the user is in a stuck state."
)
);
}
return [
2 /*return*/,
genericComponentOverrideContext.SuperTokens.getInstanceOrThrow().redirectToUrl(
failureRedirectInfo.redirectPath,
navigate
),
];
}
_a.label = 6;
case 6:
if (!(successRedirectContext === undefined)) return [3 /*break*/, 13];
return [
4 /*yield*/,
genericComponentOverrideContext.getLocalStorage(
"supertokens-success-redirection-context"
),
];
case 7:
successContextStr = _a.sent();
if (!(successContextStr !== null)) return [3 /*break*/, 12];
_a.label = 8;
case 8:
_a.trys.push([8, , 9, 11]);
storedContext = JSON.parse(successContextStr);
successRedirectContext = storedContext.successRedirectContext;
// if we have a redirectToPath set in the queryparams that takes priority over the stored value
if (redirectToPath === undefined) {
redirectToPath = storedContext.redirectToPath;
}
return [3 /*break*/, 11];
case 9:
return [
4 /*yield*/,
genericComponentOverrideContext.removeFromLocalStorage(
"supertokens-success-redirection-context"
),
];
case 10:
_a.sent();
return [7 /*endfinally*/];
case 11:
return [3 /*break*/, 13];
case 12:
// If there was nothing in localstorage we set a default
// this can happen if the user visited email verification screen without an auth recipe redirecting them there
// but already had the email verified and an active session
successRedirectContext = {
recipeId: fallbackRecipeId,
action: "SUCCESS",
createdNewUser: false,
isNewRecipeUser: false,
newSessionCreated: false,
tenantIdFromQueryParams: genericComponentOverrideContext.getTenantIdFromQueryParams(),
};
_a.label = 13;
case 13:
if (successRedirectContext === undefined) {
throw new Error("This should never happen: successRedirectContext undefined ");
}
if (successRedirectContext.action === "SUCCESS_OAUTH2") {
return [
2 /*return*/,
recipe.OAuth2Provider.getInstanceOrThrow().redirect(
successRedirectContext,
navigate,
{},
userContext
),
];
}
if (successRedirectContext.action === "SUCCESS" && redirectToPath !== undefined) {
successRedirectContext.redirectToPath = redirectToPath;
}
return [
2 /*return*/,
genericComponentOverrideContext.SuperTokens.getInstanceOrThrow().redirect(
successRedirectContext,
navigate,
{},
userContext
),
];
}
});
});
};
/**
* This should only get called if validateGlobalClaimsAndHandleSuccessRedirection couldn't get a redirectInfo
* @returns "/"
*/
_this.getDefaultRedirectionURL = function () {
return genericComponentOverrideContext.__awaiter(_this, void 0, void 0, function () {
return genericComponentOverrideContext.__generator(this, function (_a) {
// We do not use the util here, since we are redirecting outside of the SDK routes
return [2 /*return*/, "/"];
});
});
};
_this.notifyListeners = function (event) {
return genericComponentOverrideContext.__awaiter(_this, void 0, void 0, function () {
var sessionContext;
return genericComponentOverrideContext.__generator(this, function (_a) {
switch (_a.label) {
case 0:
return [4 /*yield*/, this.getSessionContext(event)];
case 1:
sessionContext = _a.sent();
// We copy this.eventListeners into a new array to "freeze" it for the loop
// We do this to avoid an infinite loop in case one of the listeners causes a new listener to be added (e.g.: through re-rendering)
Array.from(this.eventListeners).forEach(function (listener) {
return listener(
genericComponentOverrideContext.__assign({ sessionContext: sessionContext }, event)
);
});
return [2 /*return*/];
}
});
});
};
return _this;
}
Session.prototype.getSessionContext = function (_a) {
var action = _a.action,
userContext = _a.userContext;
return genericComponentOverrideContext.__awaiter(this, void 0, void 0, function () {
var _b, userId, accessTokenPayload;
return genericComponentOverrideContext.__generator(this, function (_c) {
switch (_c.label) {
case 0:
if (
!(
action === "SESSION_CREATED" ||
action === "REFRESH_SESSION" ||
action === "API_INVALID_CLAIM" ||
action === "ACCESS_TOKEN_PAYLOAD_UPDATED"
)
)
return [3 /*break*/, 2];
return [
4 /*yield*/,
Promise.all([
this.getUserId({
userContext: userContext,
}),
this.getAccessTokenPayloadSecurely({
userContext: userContext,
}),
]),
];
case 1:
(_b = _c.sent()), (userId = _b[0]), (accessTokenPayload = _b[1]);
return [
2 /*return*/,
{
doesSessionExist: true,
accessTokenPayload: accessTokenPayload,
userId: userId,
},
];
case 2:
if (action === "SIGN_OUT" || action === "UNAUTHORISED") {
return [
2 /*return*/,
{
doesSessionExist: false,
accessTokenPayload: {},
userId: "",
},
];
}
throw new Error("Unhandled recipe event: ".concat(action));
}
});
});
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
Session.addAxiosInterceptors = function (axiosInstance, userContext) {
return WebJSSessionRecipe__default.default.addAxiosInterceptors(axiosInstance, userContext);
};
Session.init = function (config) {
var _this = this;
var normalisedConfig = normaliseSessionConfig(config);
return {
recipeID: Session.RECIPE_ID,
authReact: function (appInfo) {
Session.instance = new Session(
genericComponentOverrideContext.__assign(
genericComponentOverrideContext.__assign({}, normalisedConfig),
{ appInfo: appInfo, recipeId: Session.RECIPE_ID }
)
);
return Session.instance;
},
webJS: WebJSSessionRecipe__default.default.init(
genericComponentOverrideContext.__assign(
genericComponentOverrideContext.__assign({}, normalisedConfig),
{
onHandleEvent: function (event) {
if (normalisedConfig.onHandleEvent !== undefined) {
normalisedConfig.onHandleEvent(event);
}
void Session.getInstanceOrThrow().notifyListeners(event);
},
preAPIHook: function (context) {
return genericComponentOverrideContext.__awaiter(_this, void 0, void 0, function () {
var response;
return genericComponentOverrideContext.__generator(this, function (_a) {
response = genericComponentOverrideContext.__assign(
genericComponentOverrideContext.__assign({}, context),
{
requestInit: genericComponentOverrideContext.__assign(
genericComponentOverrideContext.__assign({}, context.requestInit),
{
headers: genericComponentOverrideContext.__assign(
genericComponentOverrideContext.__assign(
{},
context.requestInit.headers
),
{ rid: Session.RECIPE_ID }
),
}
),
}
);
if (normalisedConfig.preAPIHook === undefined) {
return [2 /*return*/, response];
} else {
return [2 /*return*/, normalisedConfig.preAPIHook(context)];
}
});
});
},
}
)
),
};
};
Session.getInstanceOrThrow = function () {
if (Session.instance === undefined) {
throw Error(
"No instance of Session found. Make sure to call the Session.init method. See https://supertokens.io/docs/emailpassword/quick-setup/frontend"
);
}
return Session.instance;
};
Session.getInstance = function () {
return Session.instance;
};
Session.reset = function () {
if (!genericComponentOverrideContext.isTest()) {
return;
}
Session.instance = undefined;
return;
};
Session.RECIPE_ID = "session";
return Session;
})(index.RecipeModule);
/* 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.
*/
var FactorIds = {
EMAILPASSWORD: "emailpassword",
OTP_EMAIL: "otp-email",
OTP_PHONE: "otp-phone",
LINK_EMAIL: "link-email",
LINK_PHONE: "link-phone",
THIRDPARTY: "thirdparty",
TOTP: "totp",
WEBAUTHN: "webauthn",
};
exports.FactorIds = FactorIds;
exports.Session = Session;
exports.getFailureRedirectionInfo = getFailureRedirectionInfo;
exports.validateAndCompareOnFailureRedirectionURLToCurrent = validateAndCompareOnFailureRedirectionURLToCurrent;