@supertokens-plugins/rownd-nodejs 0.3.0-beta.6 → 0.3.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -2
- package/dist/bulkMigrate.js +1 -0
- package/dist/generateAppConfig.js +4 -1
- package/dist/index.d.mts +38 -3
- package/dist/index.d.ts +38 -3
- package/dist/index.js +488 -17
- package/dist/index.mjs +488 -17
- package/dist/setupCoreInstance.js +51 -21
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -273,6 +273,7 @@ __export(index_exports, {
|
|
|
273
273
|
HUB_LOGIN_PAGE_PATH: () => HUB_LOGIN_PAGE_PATH,
|
|
274
274
|
HUB_VERIFY_EMAIL_PAGE_PATH: () => HUB_VERIFY_EMAIL_PAGE_PATH,
|
|
275
275
|
INSTANT_AUTH_METHOD_ID: () => INSTANT_AUTH_METHOD_ID,
|
|
276
|
+
PASSWORDLESS_BYPASS_DEVICE_CONFIRMATION_PARAM: () => PASSWORDLESS_BYPASS_DEVICE_CONFIRMATION_PARAM,
|
|
276
277
|
PLUGIN_ID: () => PLUGIN_ID,
|
|
277
278
|
PLUGIN_SDK_VERSION: () => PLUGIN_SDK_VERSION,
|
|
278
279
|
PLUGIN_VERSION: () => PLUGIN_VERSION,
|
|
@@ -280,6 +281,7 @@ __export(index_exports, {
|
|
|
280
281
|
ROWND_JWT_CLAIMS: () => ROWND_JWT_CLAIMS,
|
|
281
282
|
ROWND_PLUGIN_ERROR_MESSAGES: () => ROWND_PLUGIN_ERROR_MESSAGES,
|
|
282
283
|
RowndPluginError: () => RowndPluginError,
|
|
284
|
+
createMagicLinkWithConfirmationBypass: () => createMagicLinkWithConfirmationBypass,
|
|
283
285
|
default: () => index_default,
|
|
284
286
|
getRowndClient: () => getRowndClient,
|
|
285
287
|
init: () => init,
|
|
@@ -287,6 +289,10 @@ __export(index_exports, {
|
|
|
287
289
|
});
|
|
288
290
|
module.exports = __toCommonJS(index_exports);
|
|
289
291
|
|
|
292
|
+
// src/plugin.ts
|
|
293
|
+
var import_emailverification2 = require("supertokens-node/recipe/emailverification");
|
|
294
|
+
var import_session3 = __toESM(require("supertokens-node/recipe/session"));
|
|
295
|
+
|
|
290
296
|
// ../../shared/js/src/createPluginInit.ts
|
|
291
297
|
var import_supertokens_js_override = __toESM(require_build());
|
|
292
298
|
var createPluginInitFunction = (init2, getImplementation, getNormalisedConfig = (config2) => config2) => {
|
|
@@ -724,6 +730,7 @@ var DEFAULT_ROWND_SCHEMA = {
|
|
|
724
730
|
};
|
|
725
731
|
var HUB_LOGIN_PAGE_PATH = "/account/login";
|
|
726
732
|
var HUB_VERIFY_EMAIL_PAGE_PATH = "/account/verify-email";
|
|
733
|
+
var PASSWORDLESS_BYPASS_DEVICE_CONFIRMATION_PARAM = "bypassDeviceConfirmation";
|
|
727
734
|
|
|
728
735
|
// src/logger.ts
|
|
729
736
|
var { logDebugMessage, enableDebugLogs } = buildLogger(
|
|
@@ -966,6 +973,109 @@ function getStringList(value) {
|
|
|
966
973
|
function getErrorMessage(error) {
|
|
967
974
|
return error instanceof Error ? error.message : "Unknown error";
|
|
968
975
|
}
|
|
976
|
+
function getAppInfoString(value) {
|
|
977
|
+
return value.getAsStringDangerous();
|
|
978
|
+
}
|
|
979
|
+
function getWebsiteDomain(input) {
|
|
980
|
+
return getAppInfoString(input.stConfig.appInfo.getOrigin({
|
|
981
|
+
request: input.request,
|
|
982
|
+
userContext: input.userContext ?? {}
|
|
983
|
+
}));
|
|
984
|
+
}
|
|
985
|
+
function normalizeClientDomain(value) {
|
|
986
|
+
if (value.endsWith("://")) {
|
|
987
|
+
return value;
|
|
988
|
+
}
|
|
989
|
+
const parsed = new URL(value);
|
|
990
|
+
if (parsed.protocol === "http:" || parsed.protocol === "https:") {
|
|
991
|
+
return parsed.origin;
|
|
992
|
+
}
|
|
993
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
994
|
+
}
|
|
995
|
+
function resolveClientDomain(input) {
|
|
996
|
+
if (!input.clientDomain) {
|
|
997
|
+
return input.websiteDomain;
|
|
998
|
+
}
|
|
999
|
+
const resolved = input.pluginConfig.clientDomains?.[input.clientDomain];
|
|
1000
|
+
if (!resolved) {
|
|
1001
|
+
throw new Error(`Unknown clientDomain key: ${input.clientDomain}`);
|
|
1002
|
+
}
|
|
1003
|
+
return resolved;
|
|
1004
|
+
}
|
|
1005
|
+
function resolveAllowedClientDomain(input) {
|
|
1006
|
+
const websiteDomain = getWebsiteDomain(input);
|
|
1007
|
+
const resolvedClientDomain = resolveClientDomain({
|
|
1008
|
+
clientDomain: input.clientDomain,
|
|
1009
|
+
pluginConfig: input.pluginConfig,
|
|
1010
|
+
websiteDomain
|
|
1011
|
+
});
|
|
1012
|
+
const normalizedClientDomain = normalizeClientDomain(resolvedClientDomain);
|
|
1013
|
+
const allowed = [
|
|
1014
|
+
websiteDomain,
|
|
1015
|
+
...Object.values(input.pluginConfig.clientDomains ?? {})
|
|
1016
|
+
].map(normalizeClientDomain);
|
|
1017
|
+
if (!allowed.includes(normalizedClientDomain)) {
|
|
1018
|
+
throw new Error(`clientDomain is not allowed: ${resolvedClientDomain}`);
|
|
1019
|
+
}
|
|
1020
|
+
return normalizedClientDomain;
|
|
1021
|
+
}
|
|
1022
|
+
function normalizeRedirectToPathForClientDomain(redirectToPath, clientDomain) {
|
|
1023
|
+
if (!redirectToPath) {
|
|
1024
|
+
return void 0;
|
|
1025
|
+
}
|
|
1026
|
+
if (redirectToPath === "NATIVE_APP") {
|
|
1027
|
+
return redirectToPath;
|
|
1028
|
+
}
|
|
1029
|
+
if (redirectToPath.startsWith("//")) {
|
|
1030
|
+
throw new Error("redirectToPath cannot be schemaless");
|
|
1031
|
+
}
|
|
1032
|
+
const normalizedClientDomain = normalizeClientDomain(clientDomain);
|
|
1033
|
+
const redirectUrl = new URL(
|
|
1034
|
+
redirectToPath,
|
|
1035
|
+
normalizedClientDomain.endsWith("://") ? "http://localhost" : normalizedClientDomain
|
|
1036
|
+
);
|
|
1037
|
+
const hasExplicitScheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(redirectToPath);
|
|
1038
|
+
if (hasExplicitScheme) {
|
|
1039
|
+
if (redirectUrl.protocol !== "http:" && redirectUrl.protocol !== "https:") {
|
|
1040
|
+
throw new Error("redirectToPath must be http(s) or relative");
|
|
1041
|
+
}
|
|
1042
|
+
if (redirectUrl.origin !== normalizedClientDomain) {
|
|
1043
|
+
throw new Error("redirectToPath must match clientDomain");
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
return `${redirectUrl.pathname}${redirectUrl.search}${redirectUrl.hash}`;
|
|
1047
|
+
}
|
|
1048
|
+
function assertAllowedBypassRedirectPath(pluginConfig2, redirectToPath) {
|
|
1049
|
+
if (!redirectToPath) {
|
|
1050
|
+
throw new Error("redirectToPath is required for confirmation bypass magic links");
|
|
1051
|
+
}
|
|
1052
|
+
const allowedRedirectPaths = pluginConfig2.crossDeviceConfirmationBypass?.allowedRedirectPaths ?? [];
|
|
1053
|
+
if (allowedRedirectPaths.length === 0) {
|
|
1054
|
+
throw new Error("crossDeviceConfirmationBypass.allowedRedirectPaths must be configured");
|
|
1055
|
+
}
|
|
1056
|
+
if (!allowedRedirectPaths.includes(redirectToPath)) {
|
|
1057
|
+
throw new Error(`redirectToPath is not allowed for confirmation bypass: ${redirectToPath}`);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
function getMagicLinkBootstrapParams(input) {
|
|
1061
|
+
return {
|
|
1062
|
+
appKey: input.appKey,
|
|
1063
|
+
apiDomain: input.apiDomain,
|
|
1064
|
+
apiBasePath: input.apiBasePath,
|
|
1065
|
+
...input.appVariantId ? { appVariantId: input.appVariantId } : {},
|
|
1066
|
+
...input.displayContext ? { displayContext: input.displayContext } : {},
|
|
1067
|
+
...input.redirectToPath ? { redirectToPath: input.redirectToPath } : {},
|
|
1068
|
+
...input.clientDomainKey ? { clientDomain: input.clientDomainKey } : {}
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
function rewriteMagicLink(input) {
|
|
1072
|
+
return rewriteLinkToBaseUrl(
|
|
1073
|
+
input.magicLink,
|
|
1074
|
+
HUB_LOGIN_PAGE_PATH,
|
|
1075
|
+
input.clientDomain,
|
|
1076
|
+
input.bootstrapParams
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
969
1079
|
function getRequestedAppVariantIdFromRequest(req) {
|
|
970
1080
|
return req.getKeyValueFromQuery("app_variant_id");
|
|
971
1081
|
}
|
|
@@ -984,12 +1094,19 @@ function getRequestedRedirectToPathFromRequest(req) {
|
|
|
984
1094
|
|
|
985
1095
|
// src/config.ts
|
|
986
1096
|
var pluginConfig;
|
|
1097
|
+
var superTokensConfig;
|
|
987
1098
|
function setPluginConfig(config2) {
|
|
988
1099
|
pluginConfig = config2;
|
|
989
1100
|
}
|
|
990
1101
|
function getPluginConfig() {
|
|
991
1102
|
return pluginConfig;
|
|
992
1103
|
}
|
|
1104
|
+
function setSuperTokensConfig(config2) {
|
|
1105
|
+
superTokensConfig = config2;
|
|
1106
|
+
}
|
|
1107
|
+
function getSuperTokensConfig() {
|
|
1108
|
+
return superTokensConfig;
|
|
1109
|
+
}
|
|
993
1110
|
function assertRowndAppVariantIsConfigured(appVariantId) {
|
|
994
1111
|
if (!appVariantId) {
|
|
995
1112
|
return;
|
|
@@ -1070,7 +1187,10 @@ function buildSignInMethodsConfig(methodsArray) {
|
|
|
1070
1187
|
},
|
|
1071
1188
|
apple: {
|
|
1072
1189
|
enabled: !!appleMethod,
|
|
1073
|
-
client_id: getStringMethodProperty(appleMethod, "clientId") ?? ""
|
|
1190
|
+
client_id: getStringMethodProperty(appleMethod, "clientId") ?? "",
|
|
1191
|
+
...getStringMethodProperty(appleMethod, "webClientType") !== void 0 ? { web_client_type: getStringMethodProperty(appleMethod, "webClientType") } : {},
|
|
1192
|
+
...getStringMethodProperty(appleMethod, "iosClientType") !== void 0 ? { ios_client_type: getStringMethodProperty(appleMethod, "iosClientType") } : {},
|
|
1193
|
+
...getStringMethodProperty(appleMethod, "androidClientType") !== void 0 ? { android_client_type: getStringMethodProperty(appleMethod, "androidClientType") } : {}
|
|
1074
1194
|
},
|
|
1075
1195
|
anonymous: {
|
|
1076
1196
|
enabled: !!anonymousMethod && anonymousType !== "instant",
|
|
@@ -1344,6 +1464,7 @@ function buildAuthMobileConfig(authMobile) {
|
|
|
1344
1464
|
|
|
1345
1465
|
// src/rownd-compatibility.ts
|
|
1346
1466
|
var import_supertokens_node = __toESM(require("supertokens-node"));
|
|
1467
|
+
var import_usermetadata2 = __toESM(require("supertokens-node/recipe/usermetadata"));
|
|
1347
1468
|
var IDENTITY_USER_DATA_FIELDS = /* @__PURE__ */ new Set([
|
|
1348
1469
|
"user_id",
|
|
1349
1470
|
"email",
|
|
@@ -1518,6 +1639,140 @@ function buildRowndSessionClaimPayload(input) {
|
|
|
1518
1639
|
...anonymousId ? { anonymous_id: anonymousId } : {}
|
|
1519
1640
|
};
|
|
1520
1641
|
}
|
|
1642
|
+
function normalizeRowndOAuthScopes(scopes) {
|
|
1643
|
+
return [...new Set(scopes.filter((scope) => scope.length > 0))];
|
|
1644
|
+
}
|
|
1645
|
+
function getRowndOAuthAudience(input) {
|
|
1646
|
+
const requested = input.requestedResource ?? input.requestedAudience;
|
|
1647
|
+
if (requested?.startsWith("app:")) {
|
|
1648
|
+
return requested;
|
|
1649
|
+
}
|
|
1650
|
+
return void 0;
|
|
1651
|
+
}
|
|
1652
|
+
function applyRowndOAuthResourceParams(input) {
|
|
1653
|
+
const resource = firstString(input.params.resource);
|
|
1654
|
+
const audience = firstString(input.params.audience);
|
|
1655
|
+
const rowndAudience = getRowndOAuthAudience({
|
|
1656
|
+
requestedResource: resource,
|
|
1657
|
+
requestedAudience: audience
|
|
1658
|
+
});
|
|
1659
|
+
if (!rowndAudience) {
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
input.userContext.rowndOAuthAudience = rowndAudience;
|
|
1663
|
+
input.params.audience = audience ?? rowndAudience;
|
|
1664
|
+
delete input.params.resource;
|
|
1665
|
+
}
|
|
1666
|
+
async function buildRowndOAuthPayload(input) {
|
|
1667
|
+
const currentPayload = input.currentPayload ?? {};
|
|
1668
|
+
const standardClaims = input.user ? await buildStandardOAuthClaims(input.user, input.scopes) : {};
|
|
1669
|
+
const rowndClaims = input.user ? await buildRowndOAuthSessionClaims(input.user, currentPayload) : {};
|
|
1670
|
+
const audience = getRowndOAuthAudience({
|
|
1671
|
+
requestedAudience: typeof input.userContext?.rowndOAuthAudience === "string" ? input.userContext.rowndOAuthAudience : void 0
|
|
1672
|
+
});
|
|
1673
|
+
return {
|
|
1674
|
+
...currentPayload,
|
|
1675
|
+
...standardClaims,
|
|
1676
|
+
...rowndClaims,
|
|
1677
|
+
...audience ? { aud: audience } : {}
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
async function buildRowndOAuthUserInfo(input) {
|
|
1681
|
+
const standardClaims = await buildStandardOAuthClaims(
|
|
1682
|
+
input.user,
|
|
1683
|
+
input.scopes
|
|
1684
|
+
);
|
|
1685
|
+
const rowndClaims = pickOAuthUserInfoRowndClaims(input.accessTokenPayload);
|
|
1686
|
+
return {
|
|
1687
|
+
...input.currentPayload,
|
|
1688
|
+
...standardClaims,
|
|
1689
|
+
...rowndClaims
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
async function buildStandardOAuthClaims(user, scopes) {
|
|
1693
|
+
const claims = {};
|
|
1694
|
+
const metadata = await getRowndMetadata(user.id);
|
|
1695
|
+
const rowndData = isJsonRecord(metadata.original_rownd_user?.data) ? metadata.original_rownd_user.data : {};
|
|
1696
|
+
const verifiedData = isJsonRecord(metadata.original_rownd_user?.verified_data) ? metadata.original_rownd_user.verified_data : {};
|
|
1697
|
+
if (scopes.includes("email")) {
|
|
1698
|
+
const email = firstString(rowndData.email) ?? user.emails[0];
|
|
1699
|
+
if (email) {
|
|
1700
|
+
claims.email = email;
|
|
1701
|
+
claims.email_verified = isOAuthClaimVerified(
|
|
1702
|
+
verifiedData.email,
|
|
1703
|
+
email,
|
|
1704
|
+
user.loginMethods.some(
|
|
1705
|
+
(method) => method.hasSameEmailAs(email) && method.verified
|
|
1706
|
+
)
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
if (scopes.includes("phone")) {
|
|
1711
|
+
const phoneNumber = firstString(rowndData.phone_number) ?? user.phoneNumbers[0];
|
|
1712
|
+
if (phoneNumber) {
|
|
1713
|
+
claims.phone_number = phoneNumber;
|
|
1714
|
+
claims.phone_number_verified = isOAuthClaimVerified(
|
|
1715
|
+
verifiedData.phone_number,
|
|
1716
|
+
phoneNumber,
|
|
1717
|
+
user.loginMethods.some(
|
|
1718
|
+
(method) => method.hasSamePhoneNumberAs(phoneNumber) && method.verified
|
|
1719
|
+
)
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
if (scopes.includes("profile")) {
|
|
1724
|
+
const givenName = firstString(rowndData.first_name);
|
|
1725
|
+
const familyName = firstString(rowndData.last_name);
|
|
1726
|
+
const name = [givenName, familyName].filter(Boolean).join(" ");
|
|
1727
|
+
if (name) claims.name = name;
|
|
1728
|
+
if (givenName) claims.given_name = givenName;
|
|
1729
|
+
if (familyName) claims.family_name = familyName;
|
|
1730
|
+
if (typeof metadata.original_rownd_user?.data?.updated_at === "string") {
|
|
1731
|
+
claims.updated_at = metadata.original_rownd_user.data.updated_at;
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
return claims;
|
|
1735
|
+
}
|
|
1736
|
+
async function getRowndMetadata(userId) {
|
|
1737
|
+
const metadata = await import_usermetadata2.default.getUserMetadata(userId);
|
|
1738
|
+
return metadata.metadata || {};
|
|
1739
|
+
}
|
|
1740
|
+
function pickOAuthUserInfoRowndClaims(payload) {
|
|
1741
|
+
const claims = {};
|
|
1742
|
+
for (const key of [
|
|
1743
|
+
"app_user_id",
|
|
1744
|
+
"auth_level",
|
|
1745
|
+
"is_verified_user",
|
|
1746
|
+
"is_anonymous",
|
|
1747
|
+
"anonymous_id",
|
|
1748
|
+
"https://auth.rownd.io/app_user_id",
|
|
1749
|
+
"https://auth.rownd.io/auth_level",
|
|
1750
|
+
"https://auth.rownd.io/is_verified_user",
|
|
1751
|
+
"https://auth.rownd.io/is_anonymous"
|
|
1752
|
+
]) {
|
|
1753
|
+
if (payload[key] !== void 0) {
|
|
1754
|
+
claims[key] = payload[key];
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
return claims;
|
|
1758
|
+
}
|
|
1759
|
+
function firstString(value) {
|
|
1760
|
+
if (Array.isArray(value)) {
|
|
1761
|
+
return value.find((entry) => typeof entry === "string");
|
|
1762
|
+
}
|
|
1763
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1764
|
+
}
|
|
1765
|
+
function isOAuthClaimVerified(value, expectedValue, fallback) {
|
|
1766
|
+
return value === true || value === expectedValue || fallback;
|
|
1767
|
+
}
|
|
1768
|
+
async function buildRowndOAuthSessionClaims(user, currentPayload) {
|
|
1769
|
+
return buildRowndSessionClaimPayload({
|
|
1770
|
+
userId: user.id,
|
|
1771
|
+
user,
|
|
1772
|
+
metadata: await getRowndMetadata(user.id),
|
|
1773
|
+
currentPayload
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1521
1776
|
async function shouldLinkRowndAccounts(input) {
|
|
1522
1777
|
const [newAccountInfo, , session] = input;
|
|
1523
1778
|
if (!session) {
|
|
@@ -1619,12 +1874,12 @@ function hasVerifiedRealLoginMethod(user) {
|
|
|
1619
1874
|
});
|
|
1620
1875
|
}
|
|
1621
1876
|
function getEffectiveAuthLevel(user, originalAuthLevel, verifiedData) {
|
|
1622
|
-
if (originalAuthLevel === INSTANT_AUTH_METHOD_ID) {
|
|
1623
|
-
return INSTANT_AUTH_METHOD_ID;
|
|
1624
|
-
}
|
|
1625
1877
|
if (hasVerifiedRealLoginMethod(user)) {
|
|
1626
1878
|
return "verified";
|
|
1627
1879
|
}
|
|
1880
|
+
if (originalAuthLevel === INSTANT_AUTH_METHOD_ID) {
|
|
1881
|
+
return INSTANT_AUTH_METHOD_ID;
|
|
1882
|
+
}
|
|
1628
1883
|
return getGuestAuthLevel(user) || originalAuthLevel || (verifiedData && Object.keys(verifiedData).length > 0 ? "verified" : "unverified");
|
|
1629
1884
|
}
|
|
1630
1885
|
function canUpdateUserDataField(field) {
|
|
@@ -1665,7 +1920,7 @@ var import_emailverification = __toESM(require("supertokens-node/recipe/emailver
|
|
|
1665
1920
|
var import_passwordless = __toESM(require("supertokens-node/recipe/passwordless"));
|
|
1666
1921
|
var import_session = __toESM(require("supertokens-node/recipe/session"));
|
|
1667
1922
|
var import_claims = require("supertokens-node/recipe/session/claims");
|
|
1668
|
-
var
|
|
1923
|
+
var import_usermetadata3 = __toESM(require("supertokens-node/recipe/usermetadata"));
|
|
1669
1924
|
async function importUser(stUser, config2) {
|
|
1670
1925
|
const headers = {
|
|
1671
1926
|
"Content-Type": "application/json"
|
|
@@ -1704,7 +1959,7 @@ async function recordRowndAppVariantForUser(userId, appVariantId) {
|
|
|
1704
1959
|
if (appVariants.includes(appVariantId)) {
|
|
1705
1960
|
return;
|
|
1706
1961
|
}
|
|
1707
|
-
await
|
|
1962
|
+
await import_usermetadata3.default.updateUserMetadata(userId, {
|
|
1708
1963
|
...metadata,
|
|
1709
1964
|
original_rownd_user: {
|
|
1710
1965
|
...originalRowndUser,
|
|
@@ -1736,8 +1991,78 @@ async function buildRowndSessionClaims(userId, currentPayload = {}, appVariantId
|
|
|
1736
1991
|
appVariantId
|
|
1737
1992
|
});
|
|
1738
1993
|
}
|
|
1994
|
+
async function createMagicLinkWithConfirmationBypass(input) {
|
|
1995
|
+
const hasEmail = typeof input.email === "string" && input.email.length > 0;
|
|
1996
|
+
const hasPhoneNumber = typeof input.phoneNumber === "string" && input.phoneNumber.length > 0;
|
|
1997
|
+
if (hasEmail === hasPhoneNumber) {
|
|
1998
|
+
throw new Error("Exactly one of email or phoneNumber is required");
|
|
1999
|
+
}
|
|
2000
|
+
const stConfig = getSuperTokensConfig();
|
|
2001
|
+
if (!stConfig) {
|
|
2002
|
+
throw new Error("SuperTokens config is not initialized");
|
|
2003
|
+
}
|
|
2004
|
+
const pluginConfig2 = getPluginConfig();
|
|
2005
|
+
if (!pluginConfig2) {
|
|
2006
|
+
throw new Error("Rownd plugin config is not initialized");
|
|
2007
|
+
}
|
|
2008
|
+
const tenantId = input.tenantId ?? PUBLIC_TENANT_ID;
|
|
2009
|
+
const appVariantId = input.appVariantId;
|
|
2010
|
+
assertRowndAppVariantIsConfigured(appVariantId);
|
|
2011
|
+
const clientDomain = resolveAllowedClientDomain({
|
|
2012
|
+
clientDomain: input.clientDomain,
|
|
2013
|
+
pluginConfig: pluginConfig2,
|
|
2014
|
+
stConfig,
|
|
2015
|
+
request: input.request,
|
|
2016
|
+
userContext: input.userContext
|
|
2017
|
+
});
|
|
2018
|
+
const redirectToPath = normalizeRedirectToPathForClientDomain(input.redirectToPath, clientDomain);
|
|
2019
|
+
assertAllowedBypassRedirectPath(pluginConfig2, redirectToPath);
|
|
2020
|
+
const userContext = {
|
|
2021
|
+
...input.userContext ?? {},
|
|
2022
|
+
...input.displayContext ? { rowndDisplayContext: input.displayContext } : {},
|
|
2023
|
+
rowndRedirectToPath: redirectToPath,
|
|
2024
|
+
...input.clientDomain ? { rowndClientDomain: input.clientDomain } : {},
|
|
2025
|
+
...appVariantId ? { rowndAppVariantId: appVariantId } : {}
|
|
2026
|
+
};
|
|
2027
|
+
const codeInfo = hasEmail ? await import_passwordless.default.createCode({
|
|
2028
|
+
email: input.email,
|
|
2029
|
+
tenantId,
|
|
2030
|
+
session: input.session,
|
|
2031
|
+
userContext
|
|
2032
|
+
}) : await import_passwordless.default.createCode({
|
|
2033
|
+
phoneNumber: input.phoneNumber,
|
|
2034
|
+
tenantId,
|
|
2035
|
+
session: input.session,
|
|
2036
|
+
userContext
|
|
2037
|
+
});
|
|
2038
|
+
if (codeInfo.status !== "OK") {
|
|
2039
|
+
throw new Error("Failed to create magic link");
|
|
2040
|
+
}
|
|
2041
|
+
const magicLink = `${getWebsiteDomain({
|
|
2042
|
+
stConfig,
|
|
2043
|
+
request: input.request,
|
|
2044
|
+
userContext
|
|
2045
|
+
})}${getAppInfoString(stConfig.appInfo.websiteBasePath)}/verify?preAuthSessionId=${encodeURIComponent(
|
|
2046
|
+
codeInfo.preAuthSessionId
|
|
2047
|
+
)}&tenantId=${encodeURIComponent(tenantId)}#${encodeURIComponent(codeInfo.linkCode)}`;
|
|
2048
|
+
const rewrittenUrl = new URL(rewriteMagicLink({
|
|
2049
|
+
magicLink,
|
|
2050
|
+
clientDomain,
|
|
2051
|
+
bootstrapParams: getMagicLinkBootstrapParams({
|
|
2052
|
+
appKey: pluginConfig2.rowndAppKey,
|
|
2053
|
+
apiDomain: getAppInfoString(stConfig.appInfo.apiDomain),
|
|
2054
|
+
apiBasePath: getAppInfoString(stConfig.appInfo.apiBasePath),
|
|
2055
|
+
appVariantId,
|
|
2056
|
+
displayContext: input.displayContext,
|
|
2057
|
+
redirectToPath,
|
|
2058
|
+
clientDomainKey: input.clientDomain
|
|
2059
|
+
})
|
|
2060
|
+
}));
|
|
2061
|
+
rewrittenUrl.searchParams.set(PASSWORDLESS_BYPASS_DEVICE_CONFIRMATION_PARAM, "true");
|
|
2062
|
+
return rewrittenUrl.toString();
|
|
2063
|
+
}
|
|
1739
2064
|
async function getUserMetadata(userId) {
|
|
1740
|
-
const metadata = await
|
|
2065
|
+
const metadata = await import_usermetadata3.default.getUserMetadata(userId);
|
|
1741
2066
|
return metadata.metadata || {};
|
|
1742
2067
|
}
|
|
1743
2068
|
function getPendingVerifications(metadata) {
|
|
@@ -1894,7 +2219,7 @@ async function updateUserData(userId, inputData) {
|
|
|
1894
2219
|
...metadata,
|
|
1895
2220
|
...inputData
|
|
1896
2221
|
};
|
|
1897
|
-
await
|
|
2222
|
+
await import_usermetadata3.default.updateUserMetadata(userId, updatedMetadata);
|
|
1898
2223
|
return getUserById(userId);
|
|
1899
2224
|
}
|
|
1900
2225
|
async function startPendingEmailVerification(input) {
|
|
@@ -1914,7 +2239,7 @@ async function startPendingEmailVerification(input) {
|
|
|
1914
2239
|
);
|
|
1915
2240
|
}
|
|
1916
2241
|
if (pendingEmailVerifications.length > 0) {
|
|
1917
|
-
await
|
|
2242
|
+
await import_usermetadata3.default.updateUserMetadata(input.userId, {
|
|
1918
2243
|
...metadata,
|
|
1919
2244
|
rownd_pending_verification: pendingVerifications.filter(
|
|
1920
2245
|
(pendingVerification2) => pendingVerification2.field !== "email"
|
|
@@ -1937,7 +2262,7 @@ async function startPendingEmailVerification(input) {
|
|
|
1937
2262
|
value: input.email,
|
|
1938
2263
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1939
2264
|
};
|
|
1940
|
-
await
|
|
2265
|
+
await import_usermetadata3.default.updateUserMetadata(input.userId, {
|
|
1941
2266
|
...metadata,
|
|
1942
2267
|
rownd_pending_verification: [
|
|
1943
2268
|
...pendingVerifications.filter(
|
|
@@ -1983,6 +2308,7 @@ async function completePendingEmailVerification(input) {
|
|
|
1983
2308
|
return;
|
|
1984
2309
|
}
|
|
1985
2310
|
let metadataUserId = userId;
|
|
2311
|
+
let verifiedRecipeUserId = input.recipeUserId;
|
|
1986
2312
|
const passwordlessEmailMethod = getPasswordlessEmailLoginMethod(user);
|
|
1987
2313
|
if (passwordlessEmailMethod) {
|
|
1988
2314
|
const updateResult = await import_passwordless.default.updateUser({
|
|
@@ -1995,6 +2321,7 @@ async function completePendingEmailVerification(input) {
|
|
|
1995
2321
|
`Failed to update verified email method: ${updateResult.status}`
|
|
1996
2322
|
);
|
|
1997
2323
|
}
|
|
2324
|
+
verifiedRecipeUserId = passwordlessEmailMethod.recipeUserId;
|
|
1998
2325
|
} else if (hasOnlyGuestLoginMethods(user)) {
|
|
1999
2326
|
const isPasswordlessSignUpAllowed = await import_accountlinking.default.isSignUpAllowed(
|
|
2000
2327
|
PUBLIC_TENANT_ID,
|
|
@@ -2014,6 +2341,7 @@ async function completePendingEmailVerification(input) {
|
|
|
2014
2341
|
tenantId: PUBLIC_TENANT_ID,
|
|
2015
2342
|
userContext: input.userContext
|
|
2016
2343
|
});
|
|
2344
|
+
verifiedRecipeUserId = passwordlessUser.recipeUserId;
|
|
2017
2345
|
const primaryUserResult = await import_accountlinking.default.createPrimaryUser(
|
|
2018
2346
|
passwordlessUser.recipeUserId,
|
|
2019
2347
|
input.userContext
|
|
@@ -2058,7 +2386,11 @@ async function completePendingEmailVerification(input) {
|
|
|
2058
2386
|
)
|
|
2059
2387
|
)
|
|
2060
2388
|
};
|
|
2061
|
-
await
|
|
2389
|
+
await import_usermetadata3.default.updateUserMetadata(metadataUserId, updatedMetadata);
|
|
2390
|
+
return {
|
|
2391
|
+
userId: metadataUserId,
|
|
2392
|
+
recipeUserId: verifiedRecipeUserId
|
|
2393
|
+
};
|
|
2062
2394
|
}
|
|
2063
2395
|
function isMatchingPendingEmailVerification(verification, email) {
|
|
2064
2396
|
return verification.field === "email" && verification.value === email;
|
|
@@ -2069,7 +2401,7 @@ async function updateUserMetadata(userId, inputMeta) {
|
|
|
2069
2401
|
...metadata,
|
|
2070
2402
|
...inputMeta
|
|
2071
2403
|
};
|
|
2072
|
-
await
|
|
2404
|
+
await import_usermetadata3.default.updateUserMetadata(userId, updatedMetadata);
|
|
2073
2405
|
return {
|
|
2074
2406
|
id: userId,
|
|
2075
2407
|
meta: Object.fromEntries(
|
|
@@ -2090,6 +2422,9 @@ var import_crypto = require("crypto");
|
|
|
2090
2422
|
var import_supertokens_node3 = __toESM(require("supertokens-node"));
|
|
2091
2423
|
var import_session2 = __toESM(require("supertokens-node/recipe/session"));
|
|
2092
2424
|
var import_thirdparty = __toESM(require("supertokens-node/recipe/thirdparty"));
|
|
2425
|
+
function isBodyString(body, key) {
|
|
2426
|
+
return isRecord(body) && typeof body[key] === "string" && body[key].length > 0;
|
|
2427
|
+
}
|
|
2093
2428
|
function handleGetAppConfig(deps) {
|
|
2094
2429
|
return async (req) => {
|
|
2095
2430
|
const appVariantId = getRequestedAppVariantIdFromRequest(req);
|
|
@@ -2110,6 +2445,43 @@ function handleGetAppConfig(deps) {
|
|
|
2110
2445
|
};
|
|
2111
2446
|
};
|
|
2112
2447
|
}
|
|
2448
|
+
function handleValidatePasswordlessConfirmationBypass(deps) {
|
|
2449
|
+
return async (req) => {
|
|
2450
|
+
try {
|
|
2451
|
+
const body = await getJsonBody(req);
|
|
2452
|
+
const clientDomain = isBodyString(body, "clientDomain") ? body.clientDomain : void 0;
|
|
2453
|
+
const redirectToPath = isBodyString(body, "redirectToPath") ? body.redirectToPath : void 0;
|
|
2454
|
+
const appVariantId = isBodyString(body, "appVariantId") ? body.appVariantId : void 0;
|
|
2455
|
+
assertRowndAppVariantIsConfigured(appVariantId);
|
|
2456
|
+
const resolvedClientDomain = resolveAllowedClientDomain({
|
|
2457
|
+
clientDomain,
|
|
2458
|
+
pluginConfig: deps.pluginConfig,
|
|
2459
|
+
stConfig: deps.stConfig,
|
|
2460
|
+
request: req
|
|
2461
|
+
});
|
|
2462
|
+
const normalizedRedirectToPath = normalizeRedirectToPathForClientDomain(
|
|
2463
|
+
redirectToPath,
|
|
2464
|
+
resolvedClientDomain
|
|
2465
|
+
);
|
|
2466
|
+
assertAllowedBypassRedirectPath(
|
|
2467
|
+
deps.pluginConfig,
|
|
2468
|
+
normalizedRedirectToPath
|
|
2469
|
+
);
|
|
2470
|
+
return {
|
|
2471
|
+
status: "OK",
|
|
2472
|
+
bypass: true
|
|
2473
|
+
};
|
|
2474
|
+
} catch (error) {
|
|
2475
|
+
logDebugMessage(
|
|
2476
|
+
`Passwordless confirmation bypass validation failed. Error: ${getErrorMessage(error)}`
|
|
2477
|
+
);
|
|
2478
|
+
return {
|
|
2479
|
+
status: "ERROR",
|
|
2480
|
+
bypass: false
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
};
|
|
2484
|
+
}
|
|
2113
2485
|
function handleGuestLogin(deps) {
|
|
2114
2486
|
return async (req, res, _session, userContext) => {
|
|
2115
2487
|
const startedAt = Date.now();
|
|
@@ -2447,6 +2819,12 @@ function validateWritableFields(fields) {
|
|
|
2447
2819
|
}
|
|
2448
2820
|
|
|
2449
2821
|
// src/plugin.ts
|
|
2822
|
+
var verifyRowndUserSessionOptions = {
|
|
2823
|
+
sessionRequired: true,
|
|
2824
|
+
overrideGlobalClaimValidators: (validators) => validators.filter((validator) => {
|
|
2825
|
+
return !("claim" in validator) || validator.claim.key !== import_emailverification2.EmailVerificationClaim.key;
|
|
2826
|
+
})
|
|
2827
|
+
};
|
|
2450
2828
|
var init = createPluginInitFunction(
|
|
2451
2829
|
(pluginConfig2) => {
|
|
2452
2830
|
const rowndClient2 = (0, import_node.createInstance)({
|
|
@@ -2507,6 +2885,7 @@ var init = createPluginInitFunction(
|
|
|
2507
2885
|
}
|
|
2508
2886
|
},
|
|
2509
2887
|
routeHandlers(stConfig) {
|
|
2888
|
+
setSuperTokensConfig(stConfig);
|
|
2510
2889
|
const apiBasePath = stConfig.appInfo.apiBasePath.getAsStringDangerous();
|
|
2511
2890
|
hubBootstrapParams = {
|
|
2512
2891
|
apiDomain: stConfig.appInfo.apiDomain.getAsStringDangerous(),
|
|
@@ -2537,6 +2916,13 @@ var init = createPluginInitFunction(
|
|
|
2537
2916
|
method: "post",
|
|
2538
2917
|
handler: withRequestHandler(handleMigrate(routeHandlerDeps))
|
|
2539
2918
|
},
|
|
2919
|
+
{
|
|
2920
|
+
path: `${apiBasePath}/plugin/passwordless-cross-device-confirmation/validate`,
|
|
2921
|
+
method: "post",
|
|
2922
|
+
handler: withRequestHandler(
|
|
2923
|
+
handleValidatePasswordlessConfirmationBypass(routeHandlerDeps)
|
|
2924
|
+
)
|
|
2925
|
+
},
|
|
2540
2926
|
{
|
|
2541
2927
|
path: `${apiBasePath}/plugin/migrate-session`,
|
|
2542
2928
|
method: "post",
|
|
@@ -2554,13 +2940,13 @@ var init = createPluginInitFunction(
|
|
|
2554
2940
|
{
|
|
2555
2941
|
path: `${apiBasePath}${HANDLE_BASE_PATH}/user`,
|
|
2556
2942
|
method: "get",
|
|
2557
|
-
verifySessionOptions:
|
|
2943
|
+
verifySessionOptions: verifyRowndUserSessionOptions,
|
|
2558
2944
|
handler: withRequestHandler(handleGetUser())
|
|
2559
2945
|
},
|
|
2560
2946
|
{
|
|
2561
2947
|
path: `${apiBasePath}${HANDLE_BASE_PATH}/user`,
|
|
2562
2948
|
method: "put",
|
|
2563
|
-
verifySessionOptions:
|
|
2949
|
+
verifySessionOptions: verifyRowndUserSessionOptions,
|
|
2564
2950
|
handler: withRequestHandler(handleUpdateUser())
|
|
2565
2951
|
},
|
|
2566
2952
|
{
|
|
@@ -2584,19 +2970,87 @@ var init = createPluginInitFunction(
|
|
|
2584
2970
|
{
|
|
2585
2971
|
path: `${apiBasePath}${HANDLE_BASE_PATH}/user/field`,
|
|
2586
2972
|
method: "get",
|
|
2587
|
-
verifySessionOptions:
|
|
2973
|
+
verifySessionOptions: verifyRowndUserSessionOptions,
|
|
2588
2974
|
handler: withRequestHandler(handleGetUserField())
|
|
2589
2975
|
},
|
|
2590
2976
|
{
|
|
2591
2977
|
path: `${apiBasePath}${HANDLE_BASE_PATH}/user/field`,
|
|
2592
2978
|
method: "put",
|
|
2593
|
-
verifySessionOptions:
|
|
2979
|
+
verifySessionOptions: verifyRowndUserSessionOptions,
|
|
2594
2980
|
handler: withRequestHandler(handleUpdateUserField())
|
|
2595
2981
|
}
|
|
2596
2982
|
]
|
|
2597
2983
|
};
|
|
2598
2984
|
},
|
|
2599
2985
|
overrideMap: {
|
|
2986
|
+
oauth2provider: {
|
|
2987
|
+
recipeInitRequired: false,
|
|
2988
|
+
functions: (originalImplementation) => ({
|
|
2989
|
+
...originalImplementation,
|
|
2990
|
+
getRequestedScopes: async (input) => {
|
|
2991
|
+
const scopes = await originalImplementation.getRequestedScopes(
|
|
2992
|
+
input
|
|
2993
|
+
);
|
|
2994
|
+
return normalizeRowndOAuthScopes(scopes);
|
|
2995
|
+
},
|
|
2996
|
+
buildAccessTokenPayload: async (input) => {
|
|
2997
|
+
const payload = await originalImplementation.buildAccessTokenPayload(
|
|
2998
|
+
input
|
|
2999
|
+
);
|
|
3000
|
+
return buildRowndOAuthPayload({
|
|
3001
|
+
user: input.user,
|
|
3002
|
+
client: input.client,
|
|
3003
|
+
scopes: input.scopes,
|
|
3004
|
+
currentPayload: payload,
|
|
3005
|
+
userContext: input.userContext
|
|
3006
|
+
});
|
|
3007
|
+
},
|
|
3008
|
+
buildIdTokenPayload: async (input) => {
|
|
3009
|
+
const payload = await originalImplementation.buildIdTokenPayload(
|
|
3010
|
+
input
|
|
3011
|
+
);
|
|
3012
|
+
return buildRowndOAuthPayload({
|
|
3013
|
+
user: input.user,
|
|
3014
|
+
client: input.client,
|
|
3015
|
+
scopes: input.scopes,
|
|
3016
|
+
currentPayload: payload,
|
|
3017
|
+
userContext: input.userContext
|
|
3018
|
+
});
|
|
3019
|
+
},
|
|
3020
|
+
buildUserInfo: async (input) => {
|
|
3021
|
+
const payload = await originalImplementation.buildUserInfo(input);
|
|
3022
|
+
return buildRowndOAuthUserInfo({
|
|
3023
|
+
user: input.user,
|
|
3024
|
+
accessTokenPayload: input.accessTokenPayload,
|
|
3025
|
+
scopes: input.scopes,
|
|
3026
|
+
currentPayload: payload
|
|
3027
|
+
});
|
|
3028
|
+
}
|
|
3029
|
+
}),
|
|
3030
|
+
apis: (originalImplementation) => ({
|
|
3031
|
+
...originalImplementation,
|
|
3032
|
+
authGET: async (input) => {
|
|
3033
|
+
if (originalImplementation.authGET === void 0) {
|
|
3034
|
+
throw new Error("OAuth2Provider authGET is unavailable");
|
|
3035
|
+
}
|
|
3036
|
+
applyRowndOAuthResourceParams({
|
|
3037
|
+
params: input.params,
|
|
3038
|
+
userContext: input.userContext
|
|
3039
|
+
});
|
|
3040
|
+
return originalImplementation.authGET(input);
|
|
3041
|
+
},
|
|
3042
|
+
tokenPOST: async (input) => {
|
|
3043
|
+
if (originalImplementation.tokenPOST === void 0) {
|
|
3044
|
+
throw new Error("OAuth2Provider tokenPOST is unavailable");
|
|
3045
|
+
}
|
|
3046
|
+
applyRowndOAuthResourceParams({
|
|
3047
|
+
params: input.body,
|
|
3048
|
+
userContext: input.userContext
|
|
3049
|
+
});
|
|
3050
|
+
return originalImplementation.tokenPOST(input);
|
|
3051
|
+
}
|
|
3052
|
+
})
|
|
3053
|
+
},
|
|
2600
3054
|
passwordless: {
|
|
2601
3055
|
config: (config2) => {
|
|
2602
3056
|
const originalEmailDeliveryOverride = config2.emailDelivery?.override;
|
|
@@ -2817,11 +3271,25 @@ var init = createPluginInitFunction(
|
|
|
2817
3271
|
}
|
|
2818
3272
|
const response = await originalImplementation.verifyEmailPOST(input);
|
|
2819
3273
|
if (response.status === "OK") {
|
|
2820
|
-
await completePendingEmailVerification({
|
|
3274
|
+
const verificationResult = await completePendingEmailVerification({
|
|
2821
3275
|
recipeUserId: response.user.recipeUserId,
|
|
2822
3276
|
email: response.user.email,
|
|
2823
3277
|
userContext: input.userContext
|
|
2824
3278
|
});
|
|
3279
|
+
const session = input.session;
|
|
3280
|
+
const shouldReplaceSession = session && verificationResult && (session.getUserId(input.userContext) !== verificationResult.userId || session.getRecipeUserId(input.userContext).getAsString() !== verificationResult.recipeUserId.getAsString());
|
|
3281
|
+
if (shouldReplaceSession) {
|
|
3282
|
+
await session.revokeSession(input.userContext);
|
|
3283
|
+
response.newSession = await import_session3.default.createNewSession(
|
|
3284
|
+
input.options.req,
|
|
3285
|
+
input.options.res,
|
|
3286
|
+
session.getTenantId(input.userContext),
|
|
3287
|
+
verificationResult.recipeUserId,
|
|
3288
|
+
{},
|
|
3289
|
+
{},
|
|
3290
|
+
input.userContext
|
|
3291
|
+
);
|
|
3292
|
+
}
|
|
2825
3293
|
}
|
|
2826
3294
|
return response;
|
|
2827
3295
|
}
|
|
@@ -2859,6 +3327,7 @@ var init = createPluginInitFunction(
|
|
|
2859
3327
|
rowndAppSecret: config2.rowndAppSecret,
|
|
2860
3328
|
enableDebugLogs: config2.enableDebugLogs,
|
|
2861
3329
|
clientDomains: config2.clientDomains,
|
|
3330
|
+
crossDeviceConfirmationBypass: config2.crossDeviceConfirmationBypass,
|
|
2862
3331
|
telemetry: config2.telemetry,
|
|
2863
3332
|
schema: config2.schema,
|
|
2864
3333
|
appConfig: config2.appConfig,
|
|
@@ -2887,6 +3356,7 @@ var index_default = { init };
|
|
|
2887
3356
|
HUB_LOGIN_PAGE_PATH,
|
|
2888
3357
|
HUB_VERIFY_EMAIL_PAGE_PATH,
|
|
2889
3358
|
INSTANT_AUTH_METHOD_ID,
|
|
3359
|
+
PASSWORDLESS_BYPASS_DEVICE_CONFIRMATION_PARAM,
|
|
2890
3360
|
PLUGIN_ID,
|
|
2891
3361
|
PLUGIN_SDK_VERSION,
|
|
2892
3362
|
PLUGIN_VERSION,
|
|
@@ -2894,6 +3364,7 @@ var index_default = { init };
|
|
|
2894
3364
|
ROWND_JWT_CLAIMS,
|
|
2895
3365
|
ROWND_PLUGIN_ERROR_MESSAGES,
|
|
2896
3366
|
RowndPluginError,
|
|
3367
|
+
createMagicLinkWithConfirmationBypass,
|
|
2897
3368
|
getRowndClient,
|
|
2898
3369
|
init,
|
|
2899
3370
|
setRowndClient
|