@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.mjs
CHANGED
|
@@ -264,6 +264,12 @@ var require_supports_color = __commonJS({
|
|
|
264
264
|
}
|
|
265
265
|
});
|
|
266
266
|
|
|
267
|
+
// src/plugin.ts
|
|
268
|
+
import {
|
|
269
|
+
EmailVerificationClaim
|
|
270
|
+
} from "supertokens-node/recipe/emailverification";
|
|
271
|
+
import Session3 from "supertokens-node/recipe/session";
|
|
272
|
+
|
|
267
273
|
// ../../shared/js/src/createPluginInit.ts
|
|
268
274
|
var import_supertokens_js_override = __toESM(require_build());
|
|
269
275
|
var createPluginInitFunction = (init2, getImplementation, getNormalisedConfig = (config2) => config2) => {
|
|
@@ -701,6 +707,7 @@ var DEFAULT_ROWND_SCHEMA = {
|
|
|
701
707
|
};
|
|
702
708
|
var HUB_LOGIN_PAGE_PATH = "/account/login";
|
|
703
709
|
var HUB_VERIFY_EMAIL_PAGE_PATH = "/account/verify-email";
|
|
710
|
+
var PASSWORDLESS_BYPASS_DEVICE_CONFIRMATION_PARAM = "bypassDeviceConfirmation";
|
|
704
711
|
|
|
705
712
|
// src/logger.ts
|
|
706
713
|
var { logDebugMessage, enableDebugLogs } = buildLogger(
|
|
@@ -943,6 +950,109 @@ function getStringList(value) {
|
|
|
943
950
|
function getErrorMessage(error) {
|
|
944
951
|
return error instanceof Error ? error.message : "Unknown error";
|
|
945
952
|
}
|
|
953
|
+
function getAppInfoString(value) {
|
|
954
|
+
return value.getAsStringDangerous();
|
|
955
|
+
}
|
|
956
|
+
function getWebsiteDomain(input) {
|
|
957
|
+
return getAppInfoString(input.stConfig.appInfo.getOrigin({
|
|
958
|
+
request: input.request,
|
|
959
|
+
userContext: input.userContext ?? {}
|
|
960
|
+
}));
|
|
961
|
+
}
|
|
962
|
+
function normalizeClientDomain(value) {
|
|
963
|
+
if (value.endsWith("://")) {
|
|
964
|
+
return value;
|
|
965
|
+
}
|
|
966
|
+
const parsed = new URL(value);
|
|
967
|
+
if (parsed.protocol === "http:" || parsed.protocol === "https:") {
|
|
968
|
+
return parsed.origin;
|
|
969
|
+
}
|
|
970
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
971
|
+
}
|
|
972
|
+
function resolveClientDomain(input) {
|
|
973
|
+
if (!input.clientDomain) {
|
|
974
|
+
return input.websiteDomain;
|
|
975
|
+
}
|
|
976
|
+
const resolved = input.pluginConfig.clientDomains?.[input.clientDomain];
|
|
977
|
+
if (!resolved) {
|
|
978
|
+
throw new Error(`Unknown clientDomain key: ${input.clientDomain}`);
|
|
979
|
+
}
|
|
980
|
+
return resolved;
|
|
981
|
+
}
|
|
982
|
+
function resolveAllowedClientDomain(input) {
|
|
983
|
+
const websiteDomain = getWebsiteDomain(input);
|
|
984
|
+
const resolvedClientDomain = resolveClientDomain({
|
|
985
|
+
clientDomain: input.clientDomain,
|
|
986
|
+
pluginConfig: input.pluginConfig,
|
|
987
|
+
websiteDomain
|
|
988
|
+
});
|
|
989
|
+
const normalizedClientDomain = normalizeClientDomain(resolvedClientDomain);
|
|
990
|
+
const allowed = [
|
|
991
|
+
websiteDomain,
|
|
992
|
+
...Object.values(input.pluginConfig.clientDomains ?? {})
|
|
993
|
+
].map(normalizeClientDomain);
|
|
994
|
+
if (!allowed.includes(normalizedClientDomain)) {
|
|
995
|
+
throw new Error(`clientDomain is not allowed: ${resolvedClientDomain}`);
|
|
996
|
+
}
|
|
997
|
+
return normalizedClientDomain;
|
|
998
|
+
}
|
|
999
|
+
function normalizeRedirectToPathForClientDomain(redirectToPath, clientDomain) {
|
|
1000
|
+
if (!redirectToPath) {
|
|
1001
|
+
return void 0;
|
|
1002
|
+
}
|
|
1003
|
+
if (redirectToPath === "NATIVE_APP") {
|
|
1004
|
+
return redirectToPath;
|
|
1005
|
+
}
|
|
1006
|
+
if (redirectToPath.startsWith("//")) {
|
|
1007
|
+
throw new Error("redirectToPath cannot be schemaless");
|
|
1008
|
+
}
|
|
1009
|
+
const normalizedClientDomain = normalizeClientDomain(clientDomain);
|
|
1010
|
+
const redirectUrl = new URL(
|
|
1011
|
+
redirectToPath,
|
|
1012
|
+
normalizedClientDomain.endsWith("://") ? "http://localhost" : normalizedClientDomain
|
|
1013
|
+
);
|
|
1014
|
+
const hasExplicitScheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(redirectToPath);
|
|
1015
|
+
if (hasExplicitScheme) {
|
|
1016
|
+
if (redirectUrl.protocol !== "http:" && redirectUrl.protocol !== "https:") {
|
|
1017
|
+
throw new Error("redirectToPath must be http(s) or relative");
|
|
1018
|
+
}
|
|
1019
|
+
if (redirectUrl.origin !== normalizedClientDomain) {
|
|
1020
|
+
throw new Error("redirectToPath must match clientDomain");
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
return `${redirectUrl.pathname}${redirectUrl.search}${redirectUrl.hash}`;
|
|
1024
|
+
}
|
|
1025
|
+
function assertAllowedBypassRedirectPath(pluginConfig2, redirectToPath) {
|
|
1026
|
+
if (!redirectToPath) {
|
|
1027
|
+
throw new Error("redirectToPath is required for confirmation bypass magic links");
|
|
1028
|
+
}
|
|
1029
|
+
const allowedRedirectPaths = pluginConfig2.crossDeviceConfirmationBypass?.allowedRedirectPaths ?? [];
|
|
1030
|
+
if (allowedRedirectPaths.length === 0) {
|
|
1031
|
+
throw new Error("crossDeviceConfirmationBypass.allowedRedirectPaths must be configured");
|
|
1032
|
+
}
|
|
1033
|
+
if (!allowedRedirectPaths.includes(redirectToPath)) {
|
|
1034
|
+
throw new Error(`redirectToPath is not allowed for confirmation bypass: ${redirectToPath}`);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
function getMagicLinkBootstrapParams(input) {
|
|
1038
|
+
return {
|
|
1039
|
+
appKey: input.appKey,
|
|
1040
|
+
apiDomain: input.apiDomain,
|
|
1041
|
+
apiBasePath: input.apiBasePath,
|
|
1042
|
+
...input.appVariantId ? { appVariantId: input.appVariantId } : {},
|
|
1043
|
+
...input.displayContext ? { displayContext: input.displayContext } : {},
|
|
1044
|
+
...input.redirectToPath ? { redirectToPath: input.redirectToPath } : {},
|
|
1045
|
+
...input.clientDomainKey ? { clientDomain: input.clientDomainKey } : {}
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
function rewriteMagicLink(input) {
|
|
1049
|
+
return rewriteLinkToBaseUrl(
|
|
1050
|
+
input.magicLink,
|
|
1051
|
+
HUB_LOGIN_PAGE_PATH,
|
|
1052
|
+
input.clientDomain,
|
|
1053
|
+
input.bootstrapParams
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
946
1056
|
function getRequestedAppVariantIdFromRequest(req) {
|
|
947
1057
|
return req.getKeyValueFromQuery("app_variant_id");
|
|
948
1058
|
}
|
|
@@ -961,12 +1071,19 @@ function getRequestedRedirectToPathFromRequest(req) {
|
|
|
961
1071
|
|
|
962
1072
|
// src/config.ts
|
|
963
1073
|
var pluginConfig;
|
|
1074
|
+
var superTokensConfig;
|
|
964
1075
|
function setPluginConfig(config2) {
|
|
965
1076
|
pluginConfig = config2;
|
|
966
1077
|
}
|
|
967
1078
|
function getPluginConfig() {
|
|
968
1079
|
return pluginConfig;
|
|
969
1080
|
}
|
|
1081
|
+
function setSuperTokensConfig(config2) {
|
|
1082
|
+
superTokensConfig = config2;
|
|
1083
|
+
}
|
|
1084
|
+
function getSuperTokensConfig() {
|
|
1085
|
+
return superTokensConfig;
|
|
1086
|
+
}
|
|
970
1087
|
function assertRowndAppVariantIsConfigured(appVariantId) {
|
|
971
1088
|
if (!appVariantId) {
|
|
972
1089
|
return;
|
|
@@ -1047,7 +1164,10 @@ function buildSignInMethodsConfig(methodsArray) {
|
|
|
1047
1164
|
},
|
|
1048
1165
|
apple: {
|
|
1049
1166
|
enabled: !!appleMethod,
|
|
1050
|
-
client_id: getStringMethodProperty(appleMethod, "clientId") ?? ""
|
|
1167
|
+
client_id: getStringMethodProperty(appleMethod, "clientId") ?? "",
|
|
1168
|
+
...getStringMethodProperty(appleMethod, "webClientType") !== void 0 ? { web_client_type: getStringMethodProperty(appleMethod, "webClientType") } : {},
|
|
1169
|
+
...getStringMethodProperty(appleMethod, "iosClientType") !== void 0 ? { ios_client_type: getStringMethodProperty(appleMethod, "iosClientType") } : {},
|
|
1170
|
+
...getStringMethodProperty(appleMethod, "androidClientType") !== void 0 ? { android_client_type: getStringMethodProperty(appleMethod, "androidClientType") } : {}
|
|
1051
1171
|
},
|
|
1052
1172
|
anonymous: {
|
|
1053
1173
|
enabled: !!anonymousMethod && anonymousType !== "instant",
|
|
@@ -1321,6 +1441,7 @@ function buildAuthMobileConfig(authMobile) {
|
|
|
1321
1441
|
|
|
1322
1442
|
// src/rownd-compatibility.ts
|
|
1323
1443
|
import SuperTokens from "supertokens-node";
|
|
1444
|
+
import UserMetadata2 from "supertokens-node/recipe/usermetadata";
|
|
1324
1445
|
var IDENTITY_USER_DATA_FIELDS = /* @__PURE__ */ new Set([
|
|
1325
1446
|
"user_id",
|
|
1326
1447
|
"email",
|
|
@@ -1495,6 +1616,140 @@ function buildRowndSessionClaimPayload(input) {
|
|
|
1495
1616
|
...anonymousId ? { anonymous_id: anonymousId } : {}
|
|
1496
1617
|
};
|
|
1497
1618
|
}
|
|
1619
|
+
function normalizeRowndOAuthScopes(scopes) {
|
|
1620
|
+
return [...new Set(scopes.filter((scope) => scope.length > 0))];
|
|
1621
|
+
}
|
|
1622
|
+
function getRowndOAuthAudience(input) {
|
|
1623
|
+
const requested = input.requestedResource ?? input.requestedAudience;
|
|
1624
|
+
if (requested?.startsWith("app:")) {
|
|
1625
|
+
return requested;
|
|
1626
|
+
}
|
|
1627
|
+
return void 0;
|
|
1628
|
+
}
|
|
1629
|
+
function applyRowndOAuthResourceParams(input) {
|
|
1630
|
+
const resource = firstString(input.params.resource);
|
|
1631
|
+
const audience = firstString(input.params.audience);
|
|
1632
|
+
const rowndAudience = getRowndOAuthAudience({
|
|
1633
|
+
requestedResource: resource,
|
|
1634
|
+
requestedAudience: audience
|
|
1635
|
+
});
|
|
1636
|
+
if (!rowndAudience) {
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
input.userContext.rowndOAuthAudience = rowndAudience;
|
|
1640
|
+
input.params.audience = audience ?? rowndAudience;
|
|
1641
|
+
delete input.params.resource;
|
|
1642
|
+
}
|
|
1643
|
+
async function buildRowndOAuthPayload(input) {
|
|
1644
|
+
const currentPayload = input.currentPayload ?? {};
|
|
1645
|
+
const standardClaims = input.user ? await buildStandardOAuthClaims(input.user, input.scopes) : {};
|
|
1646
|
+
const rowndClaims = input.user ? await buildRowndOAuthSessionClaims(input.user, currentPayload) : {};
|
|
1647
|
+
const audience = getRowndOAuthAudience({
|
|
1648
|
+
requestedAudience: typeof input.userContext?.rowndOAuthAudience === "string" ? input.userContext.rowndOAuthAudience : void 0
|
|
1649
|
+
});
|
|
1650
|
+
return {
|
|
1651
|
+
...currentPayload,
|
|
1652
|
+
...standardClaims,
|
|
1653
|
+
...rowndClaims,
|
|
1654
|
+
...audience ? { aud: audience } : {}
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
async function buildRowndOAuthUserInfo(input) {
|
|
1658
|
+
const standardClaims = await buildStandardOAuthClaims(
|
|
1659
|
+
input.user,
|
|
1660
|
+
input.scopes
|
|
1661
|
+
);
|
|
1662
|
+
const rowndClaims = pickOAuthUserInfoRowndClaims(input.accessTokenPayload);
|
|
1663
|
+
return {
|
|
1664
|
+
...input.currentPayload,
|
|
1665
|
+
...standardClaims,
|
|
1666
|
+
...rowndClaims
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
async function buildStandardOAuthClaims(user, scopes) {
|
|
1670
|
+
const claims = {};
|
|
1671
|
+
const metadata = await getRowndMetadata(user.id);
|
|
1672
|
+
const rowndData = isJsonRecord(metadata.original_rownd_user?.data) ? metadata.original_rownd_user.data : {};
|
|
1673
|
+
const verifiedData = isJsonRecord(metadata.original_rownd_user?.verified_data) ? metadata.original_rownd_user.verified_data : {};
|
|
1674
|
+
if (scopes.includes("email")) {
|
|
1675
|
+
const email = firstString(rowndData.email) ?? user.emails[0];
|
|
1676
|
+
if (email) {
|
|
1677
|
+
claims.email = email;
|
|
1678
|
+
claims.email_verified = isOAuthClaimVerified(
|
|
1679
|
+
verifiedData.email,
|
|
1680
|
+
email,
|
|
1681
|
+
user.loginMethods.some(
|
|
1682
|
+
(method) => method.hasSameEmailAs(email) && method.verified
|
|
1683
|
+
)
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
if (scopes.includes("phone")) {
|
|
1688
|
+
const phoneNumber = firstString(rowndData.phone_number) ?? user.phoneNumbers[0];
|
|
1689
|
+
if (phoneNumber) {
|
|
1690
|
+
claims.phone_number = phoneNumber;
|
|
1691
|
+
claims.phone_number_verified = isOAuthClaimVerified(
|
|
1692
|
+
verifiedData.phone_number,
|
|
1693
|
+
phoneNumber,
|
|
1694
|
+
user.loginMethods.some(
|
|
1695
|
+
(method) => method.hasSamePhoneNumberAs(phoneNumber) && method.verified
|
|
1696
|
+
)
|
|
1697
|
+
);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
if (scopes.includes("profile")) {
|
|
1701
|
+
const givenName = firstString(rowndData.first_name);
|
|
1702
|
+
const familyName = firstString(rowndData.last_name);
|
|
1703
|
+
const name = [givenName, familyName].filter(Boolean).join(" ");
|
|
1704
|
+
if (name) claims.name = name;
|
|
1705
|
+
if (givenName) claims.given_name = givenName;
|
|
1706
|
+
if (familyName) claims.family_name = familyName;
|
|
1707
|
+
if (typeof metadata.original_rownd_user?.data?.updated_at === "string") {
|
|
1708
|
+
claims.updated_at = metadata.original_rownd_user.data.updated_at;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
return claims;
|
|
1712
|
+
}
|
|
1713
|
+
async function getRowndMetadata(userId) {
|
|
1714
|
+
const metadata = await UserMetadata2.getUserMetadata(userId);
|
|
1715
|
+
return metadata.metadata || {};
|
|
1716
|
+
}
|
|
1717
|
+
function pickOAuthUserInfoRowndClaims(payload) {
|
|
1718
|
+
const claims = {};
|
|
1719
|
+
for (const key of [
|
|
1720
|
+
"app_user_id",
|
|
1721
|
+
"auth_level",
|
|
1722
|
+
"is_verified_user",
|
|
1723
|
+
"is_anonymous",
|
|
1724
|
+
"anonymous_id",
|
|
1725
|
+
"https://auth.rownd.io/app_user_id",
|
|
1726
|
+
"https://auth.rownd.io/auth_level",
|
|
1727
|
+
"https://auth.rownd.io/is_verified_user",
|
|
1728
|
+
"https://auth.rownd.io/is_anonymous"
|
|
1729
|
+
]) {
|
|
1730
|
+
if (payload[key] !== void 0) {
|
|
1731
|
+
claims[key] = payload[key];
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
return claims;
|
|
1735
|
+
}
|
|
1736
|
+
function firstString(value) {
|
|
1737
|
+
if (Array.isArray(value)) {
|
|
1738
|
+
return value.find((entry) => typeof entry === "string");
|
|
1739
|
+
}
|
|
1740
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1741
|
+
}
|
|
1742
|
+
function isOAuthClaimVerified(value, expectedValue, fallback) {
|
|
1743
|
+
return value === true || value === expectedValue || fallback;
|
|
1744
|
+
}
|
|
1745
|
+
async function buildRowndOAuthSessionClaims(user, currentPayload) {
|
|
1746
|
+
return buildRowndSessionClaimPayload({
|
|
1747
|
+
userId: user.id,
|
|
1748
|
+
user,
|
|
1749
|
+
metadata: await getRowndMetadata(user.id),
|
|
1750
|
+
currentPayload
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1498
1753
|
async function shouldLinkRowndAccounts(input) {
|
|
1499
1754
|
const [newAccountInfo, , session] = input;
|
|
1500
1755
|
if (!session) {
|
|
@@ -1596,12 +1851,12 @@ function hasVerifiedRealLoginMethod(user) {
|
|
|
1596
1851
|
});
|
|
1597
1852
|
}
|
|
1598
1853
|
function getEffectiveAuthLevel(user, originalAuthLevel, verifiedData) {
|
|
1599
|
-
if (originalAuthLevel === INSTANT_AUTH_METHOD_ID) {
|
|
1600
|
-
return INSTANT_AUTH_METHOD_ID;
|
|
1601
|
-
}
|
|
1602
1854
|
if (hasVerifiedRealLoginMethod(user)) {
|
|
1603
1855
|
return "verified";
|
|
1604
1856
|
}
|
|
1857
|
+
if (originalAuthLevel === INSTANT_AUTH_METHOD_ID) {
|
|
1858
|
+
return INSTANT_AUTH_METHOD_ID;
|
|
1859
|
+
}
|
|
1605
1860
|
return getGuestAuthLevel(user) || originalAuthLevel || (verifiedData && Object.keys(verifiedData).length > 0 ? "verified" : "unverified");
|
|
1606
1861
|
}
|
|
1607
1862
|
function canUpdateUserDataField(field) {
|
|
@@ -1642,7 +1897,7 @@ import EmailVerification from "supertokens-node/recipe/emailverification";
|
|
|
1642
1897
|
import Passwordless from "supertokens-node/recipe/passwordless";
|
|
1643
1898
|
import Session from "supertokens-node/recipe/session";
|
|
1644
1899
|
import { BooleanClaim } from "supertokens-node/recipe/session/claims";
|
|
1645
|
-
import
|
|
1900
|
+
import UserMetadata3 from "supertokens-node/recipe/usermetadata";
|
|
1646
1901
|
async function importUser(stUser, config2) {
|
|
1647
1902
|
const headers = {
|
|
1648
1903
|
"Content-Type": "application/json"
|
|
@@ -1681,7 +1936,7 @@ async function recordRowndAppVariantForUser(userId, appVariantId) {
|
|
|
1681
1936
|
if (appVariants.includes(appVariantId)) {
|
|
1682
1937
|
return;
|
|
1683
1938
|
}
|
|
1684
|
-
await
|
|
1939
|
+
await UserMetadata3.updateUserMetadata(userId, {
|
|
1685
1940
|
...metadata,
|
|
1686
1941
|
original_rownd_user: {
|
|
1687
1942
|
...originalRowndUser,
|
|
@@ -1713,8 +1968,78 @@ async function buildRowndSessionClaims(userId, currentPayload = {}, appVariantId
|
|
|
1713
1968
|
appVariantId
|
|
1714
1969
|
});
|
|
1715
1970
|
}
|
|
1971
|
+
async function createMagicLinkWithConfirmationBypass(input) {
|
|
1972
|
+
const hasEmail = typeof input.email === "string" && input.email.length > 0;
|
|
1973
|
+
const hasPhoneNumber = typeof input.phoneNumber === "string" && input.phoneNumber.length > 0;
|
|
1974
|
+
if (hasEmail === hasPhoneNumber) {
|
|
1975
|
+
throw new Error("Exactly one of email or phoneNumber is required");
|
|
1976
|
+
}
|
|
1977
|
+
const stConfig = getSuperTokensConfig();
|
|
1978
|
+
if (!stConfig) {
|
|
1979
|
+
throw new Error("SuperTokens config is not initialized");
|
|
1980
|
+
}
|
|
1981
|
+
const pluginConfig2 = getPluginConfig();
|
|
1982
|
+
if (!pluginConfig2) {
|
|
1983
|
+
throw new Error("Rownd plugin config is not initialized");
|
|
1984
|
+
}
|
|
1985
|
+
const tenantId = input.tenantId ?? PUBLIC_TENANT_ID;
|
|
1986
|
+
const appVariantId = input.appVariantId;
|
|
1987
|
+
assertRowndAppVariantIsConfigured(appVariantId);
|
|
1988
|
+
const clientDomain = resolveAllowedClientDomain({
|
|
1989
|
+
clientDomain: input.clientDomain,
|
|
1990
|
+
pluginConfig: pluginConfig2,
|
|
1991
|
+
stConfig,
|
|
1992
|
+
request: input.request,
|
|
1993
|
+
userContext: input.userContext
|
|
1994
|
+
});
|
|
1995
|
+
const redirectToPath = normalizeRedirectToPathForClientDomain(input.redirectToPath, clientDomain);
|
|
1996
|
+
assertAllowedBypassRedirectPath(pluginConfig2, redirectToPath);
|
|
1997
|
+
const userContext = {
|
|
1998
|
+
...input.userContext ?? {},
|
|
1999
|
+
...input.displayContext ? { rowndDisplayContext: input.displayContext } : {},
|
|
2000
|
+
rowndRedirectToPath: redirectToPath,
|
|
2001
|
+
...input.clientDomain ? { rowndClientDomain: input.clientDomain } : {},
|
|
2002
|
+
...appVariantId ? { rowndAppVariantId: appVariantId } : {}
|
|
2003
|
+
};
|
|
2004
|
+
const codeInfo = hasEmail ? await Passwordless.createCode({
|
|
2005
|
+
email: input.email,
|
|
2006
|
+
tenantId,
|
|
2007
|
+
session: input.session,
|
|
2008
|
+
userContext
|
|
2009
|
+
}) : await Passwordless.createCode({
|
|
2010
|
+
phoneNumber: input.phoneNumber,
|
|
2011
|
+
tenantId,
|
|
2012
|
+
session: input.session,
|
|
2013
|
+
userContext
|
|
2014
|
+
});
|
|
2015
|
+
if (codeInfo.status !== "OK") {
|
|
2016
|
+
throw new Error("Failed to create magic link");
|
|
2017
|
+
}
|
|
2018
|
+
const magicLink = `${getWebsiteDomain({
|
|
2019
|
+
stConfig,
|
|
2020
|
+
request: input.request,
|
|
2021
|
+
userContext
|
|
2022
|
+
})}${getAppInfoString(stConfig.appInfo.websiteBasePath)}/verify?preAuthSessionId=${encodeURIComponent(
|
|
2023
|
+
codeInfo.preAuthSessionId
|
|
2024
|
+
)}&tenantId=${encodeURIComponent(tenantId)}#${encodeURIComponent(codeInfo.linkCode)}`;
|
|
2025
|
+
const rewrittenUrl = new URL(rewriteMagicLink({
|
|
2026
|
+
magicLink,
|
|
2027
|
+
clientDomain,
|
|
2028
|
+
bootstrapParams: getMagicLinkBootstrapParams({
|
|
2029
|
+
appKey: pluginConfig2.rowndAppKey,
|
|
2030
|
+
apiDomain: getAppInfoString(stConfig.appInfo.apiDomain),
|
|
2031
|
+
apiBasePath: getAppInfoString(stConfig.appInfo.apiBasePath),
|
|
2032
|
+
appVariantId,
|
|
2033
|
+
displayContext: input.displayContext,
|
|
2034
|
+
redirectToPath,
|
|
2035
|
+
clientDomainKey: input.clientDomain
|
|
2036
|
+
})
|
|
2037
|
+
}));
|
|
2038
|
+
rewrittenUrl.searchParams.set(PASSWORDLESS_BYPASS_DEVICE_CONFIRMATION_PARAM, "true");
|
|
2039
|
+
return rewrittenUrl.toString();
|
|
2040
|
+
}
|
|
1716
2041
|
async function getUserMetadata(userId) {
|
|
1717
|
-
const metadata = await
|
|
2042
|
+
const metadata = await UserMetadata3.getUserMetadata(userId);
|
|
1718
2043
|
return metadata.metadata || {};
|
|
1719
2044
|
}
|
|
1720
2045
|
function getPendingVerifications(metadata) {
|
|
@@ -1871,7 +2196,7 @@ async function updateUserData(userId, inputData) {
|
|
|
1871
2196
|
...metadata,
|
|
1872
2197
|
...inputData
|
|
1873
2198
|
};
|
|
1874
|
-
await
|
|
2199
|
+
await UserMetadata3.updateUserMetadata(userId, updatedMetadata);
|
|
1875
2200
|
return getUserById(userId);
|
|
1876
2201
|
}
|
|
1877
2202
|
async function startPendingEmailVerification(input) {
|
|
@@ -1891,7 +2216,7 @@ async function startPendingEmailVerification(input) {
|
|
|
1891
2216
|
);
|
|
1892
2217
|
}
|
|
1893
2218
|
if (pendingEmailVerifications.length > 0) {
|
|
1894
|
-
await
|
|
2219
|
+
await UserMetadata3.updateUserMetadata(input.userId, {
|
|
1895
2220
|
...metadata,
|
|
1896
2221
|
rownd_pending_verification: pendingVerifications.filter(
|
|
1897
2222
|
(pendingVerification2) => pendingVerification2.field !== "email"
|
|
@@ -1914,7 +2239,7 @@ async function startPendingEmailVerification(input) {
|
|
|
1914
2239
|
value: input.email,
|
|
1915
2240
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1916
2241
|
};
|
|
1917
|
-
await
|
|
2242
|
+
await UserMetadata3.updateUserMetadata(input.userId, {
|
|
1918
2243
|
...metadata,
|
|
1919
2244
|
rownd_pending_verification: [
|
|
1920
2245
|
...pendingVerifications.filter(
|
|
@@ -1960,6 +2285,7 @@ async function completePendingEmailVerification(input) {
|
|
|
1960
2285
|
return;
|
|
1961
2286
|
}
|
|
1962
2287
|
let metadataUserId = userId;
|
|
2288
|
+
let verifiedRecipeUserId = input.recipeUserId;
|
|
1963
2289
|
const passwordlessEmailMethod = getPasswordlessEmailLoginMethod(user);
|
|
1964
2290
|
if (passwordlessEmailMethod) {
|
|
1965
2291
|
const updateResult = await Passwordless.updateUser({
|
|
@@ -1972,6 +2298,7 @@ async function completePendingEmailVerification(input) {
|
|
|
1972
2298
|
`Failed to update verified email method: ${updateResult.status}`
|
|
1973
2299
|
);
|
|
1974
2300
|
}
|
|
2301
|
+
verifiedRecipeUserId = passwordlessEmailMethod.recipeUserId;
|
|
1975
2302
|
} else if (hasOnlyGuestLoginMethods(user)) {
|
|
1976
2303
|
const isPasswordlessSignUpAllowed = await AccountLinking.isSignUpAllowed(
|
|
1977
2304
|
PUBLIC_TENANT_ID,
|
|
@@ -1991,6 +2318,7 @@ async function completePendingEmailVerification(input) {
|
|
|
1991
2318
|
tenantId: PUBLIC_TENANT_ID,
|
|
1992
2319
|
userContext: input.userContext
|
|
1993
2320
|
});
|
|
2321
|
+
verifiedRecipeUserId = passwordlessUser.recipeUserId;
|
|
1994
2322
|
const primaryUserResult = await AccountLinking.createPrimaryUser(
|
|
1995
2323
|
passwordlessUser.recipeUserId,
|
|
1996
2324
|
input.userContext
|
|
@@ -2035,7 +2363,11 @@ async function completePendingEmailVerification(input) {
|
|
|
2035
2363
|
)
|
|
2036
2364
|
)
|
|
2037
2365
|
};
|
|
2038
|
-
await
|
|
2366
|
+
await UserMetadata3.updateUserMetadata(metadataUserId, updatedMetadata);
|
|
2367
|
+
return {
|
|
2368
|
+
userId: metadataUserId,
|
|
2369
|
+
recipeUserId: verifiedRecipeUserId
|
|
2370
|
+
};
|
|
2039
2371
|
}
|
|
2040
2372
|
function isMatchingPendingEmailVerification(verification, email) {
|
|
2041
2373
|
return verification.field === "email" && verification.value === email;
|
|
@@ -2046,7 +2378,7 @@ async function updateUserMetadata(userId, inputMeta) {
|
|
|
2046
2378
|
...metadata,
|
|
2047
2379
|
...inputMeta
|
|
2048
2380
|
};
|
|
2049
|
-
await
|
|
2381
|
+
await UserMetadata3.updateUserMetadata(userId, updatedMetadata);
|
|
2050
2382
|
return {
|
|
2051
2383
|
id: userId,
|
|
2052
2384
|
meta: Object.fromEntries(
|
|
@@ -2067,6 +2399,9 @@ import { randomUUID } from "crypto";
|
|
|
2067
2399
|
import SuperTokens3 from "supertokens-node";
|
|
2068
2400
|
import Session2 from "supertokens-node/recipe/session";
|
|
2069
2401
|
import ThirdParty from "supertokens-node/recipe/thirdparty";
|
|
2402
|
+
function isBodyString(body, key) {
|
|
2403
|
+
return isRecord(body) && typeof body[key] === "string" && body[key].length > 0;
|
|
2404
|
+
}
|
|
2070
2405
|
function handleGetAppConfig(deps) {
|
|
2071
2406
|
return async (req) => {
|
|
2072
2407
|
const appVariantId = getRequestedAppVariantIdFromRequest(req);
|
|
@@ -2087,6 +2422,43 @@ function handleGetAppConfig(deps) {
|
|
|
2087
2422
|
};
|
|
2088
2423
|
};
|
|
2089
2424
|
}
|
|
2425
|
+
function handleValidatePasswordlessConfirmationBypass(deps) {
|
|
2426
|
+
return async (req) => {
|
|
2427
|
+
try {
|
|
2428
|
+
const body = await getJsonBody(req);
|
|
2429
|
+
const clientDomain = isBodyString(body, "clientDomain") ? body.clientDomain : void 0;
|
|
2430
|
+
const redirectToPath = isBodyString(body, "redirectToPath") ? body.redirectToPath : void 0;
|
|
2431
|
+
const appVariantId = isBodyString(body, "appVariantId") ? body.appVariantId : void 0;
|
|
2432
|
+
assertRowndAppVariantIsConfigured(appVariantId);
|
|
2433
|
+
const resolvedClientDomain = resolveAllowedClientDomain({
|
|
2434
|
+
clientDomain,
|
|
2435
|
+
pluginConfig: deps.pluginConfig,
|
|
2436
|
+
stConfig: deps.stConfig,
|
|
2437
|
+
request: req
|
|
2438
|
+
});
|
|
2439
|
+
const normalizedRedirectToPath = normalizeRedirectToPathForClientDomain(
|
|
2440
|
+
redirectToPath,
|
|
2441
|
+
resolvedClientDomain
|
|
2442
|
+
);
|
|
2443
|
+
assertAllowedBypassRedirectPath(
|
|
2444
|
+
deps.pluginConfig,
|
|
2445
|
+
normalizedRedirectToPath
|
|
2446
|
+
);
|
|
2447
|
+
return {
|
|
2448
|
+
status: "OK",
|
|
2449
|
+
bypass: true
|
|
2450
|
+
};
|
|
2451
|
+
} catch (error) {
|
|
2452
|
+
logDebugMessage(
|
|
2453
|
+
`Passwordless confirmation bypass validation failed. Error: ${getErrorMessage(error)}`
|
|
2454
|
+
);
|
|
2455
|
+
return {
|
|
2456
|
+
status: "ERROR",
|
|
2457
|
+
bypass: false
|
|
2458
|
+
};
|
|
2459
|
+
}
|
|
2460
|
+
};
|
|
2461
|
+
}
|
|
2090
2462
|
function handleGuestLogin(deps) {
|
|
2091
2463
|
return async (req, res, _session, userContext) => {
|
|
2092
2464
|
const startedAt = Date.now();
|
|
@@ -2424,6 +2796,12 @@ function validateWritableFields(fields) {
|
|
|
2424
2796
|
}
|
|
2425
2797
|
|
|
2426
2798
|
// src/plugin.ts
|
|
2799
|
+
var verifyRowndUserSessionOptions = {
|
|
2800
|
+
sessionRequired: true,
|
|
2801
|
+
overrideGlobalClaimValidators: (validators) => validators.filter((validator) => {
|
|
2802
|
+
return !("claim" in validator) || validator.claim.key !== EmailVerificationClaim.key;
|
|
2803
|
+
})
|
|
2804
|
+
};
|
|
2427
2805
|
var init = createPluginInitFunction(
|
|
2428
2806
|
(pluginConfig2) => {
|
|
2429
2807
|
const rowndClient2 = createInstance({
|
|
@@ -2484,6 +2862,7 @@ var init = createPluginInitFunction(
|
|
|
2484
2862
|
}
|
|
2485
2863
|
},
|
|
2486
2864
|
routeHandlers(stConfig) {
|
|
2865
|
+
setSuperTokensConfig(stConfig);
|
|
2487
2866
|
const apiBasePath = stConfig.appInfo.apiBasePath.getAsStringDangerous();
|
|
2488
2867
|
hubBootstrapParams = {
|
|
2489
2868
|
apiDomain: stConfig.appInfo.apiDomain.getAsStringDangerous(),
|
|
@@ -2514,6 +2893,13 @@ var init = createPluginInitFunction(
|
|
|
2514
2893
|
method: "post",
|
|
2515
2894
|
handler: withRequestHandler(handleMigrate(routeHandlerDeps))
|
|
2516
2895
|
},
|
|
2896
|
+
{
|
|
2897
|
+
path: `${apiBasePath}/plugin/passwordless-cross-device-confirmation/validate`,
|
|
2898
|
+
method: "post",
|
|
2899
|
+
handler: withRequestHandler(
|
|
2900
|
+
handleValidatePasswordlessConfirmationBypass(routeHandlerDeps)
|
|
2901
|
+
)
|
|
2902
|
+
},
|
|
2517
2903
|
{
|
|
2518
2904
|
path: `${apiBasePath}/plugin/migrate-session`,
|
|
2519
2905
|
method: "post",
|
|
@@ -2531,13 +2917,13 @@ var init = createPluginInitFunction(
|
|
|
2531
2917
|
{
|
|
2532
2918
|
path: `${apiBasePath}${HANDLE_BASE_PATH}/user`,
|
|
2533
2919
|
method: "get",
|
|
2534
|
-
verifySessionOptions:
|
|
2920
|
+
verifySessionOptions: verifyRowndUserSessionOptions,
|
|
2535
2921
|
handler: withRequestHandler(handleGetUser())
|
|
2536
2922
|
},
|
|
2537
2923
|
{
|
|
2538
2924
|
path: `${apiBasePath}${HANDLE_BASE_PATH}/user`,
|
|
2539
2925
|
method: "put",
|
|
2540
|
-
verifySessionOptions:
|
|
2926
|
+
verifySessionOptions: verifyRowndUserSessionOptions,
|
|
2541
2927
|
handler: withRequestHandler(handleUpdateUser())
|
|
2542
2928
|
},
|
|
2543
2929
|
{
|
|
@@ -2561,19 +2947,87 @@ var init = createPluginInitFunction(
|
|
|
2561
2947
|
{
|
|
2562
2948
|
path: `${apiBasePath}${HANDLE_BASE_PATH}/user/field`,
|
|
2563
2949
|
method: "get",
|
|
2564
|
-
verifySessionOptions:
|
|
2950
|
+
verifySessionOptions: verifyRowndUserSessionOptions,
|
|
2565
2951
|
handler: withRequestHandler(handleGetUserField())
|
|
2566
2952
|
},
|
|
2567
2953
|
{
|
|
2568
2954
|
path: `${apiBasePath}${HANDLE_BASE_PATH}/user/field`,
|
|
2569
2955
|
method: "put",
|
|
2570
|
-
verifySessionOptions:
|
|
2956
|
+
verifySessionOptions: verifyRowndUserSessionOptions,
|
|
2571
2957
|
handler: withRequestHandler(handleUpdateUserField())
|
|
2572
2958
|
}
|
|
2573
2959
|
]
|
|
2574
2960
|
};
|
|
2575
2961
|
},
|
|
2576
2962
|
overrideMap: {
|
|
2963
|
+
oauth2provider: {
|
|
2964
|
+
recipeInitRequired: false,
|
|
2965
|
+
functions: (originalImplementation) => ({
|
|
2966
|
+
...originalImplementation,
|
|
2967
|
+
getRequestedScopes: async (input) => {
|
|
2968
|
+
const scopes = await originalImplementation.getRequestedScopes(
|
|
2969
|
+
input
|
|
2970
|
+
);
|
|
2971
|
+
return normalizeRowndOAuthScopes(scopes);
|
|
2972
|
+
},
|
|
2973
|
+
buildAccessTokenPayload: async (input) => {
|
|
2974
|
+
const payload = await originalImplementation.buildAccessTokenPayload(
|
|
2975
|
+
input
|
|
2976
|
+
);
|
|
2977
|
+
return buildRowndOAuthPayload({
|
|
2978
|
+
user: input.user,
|
|
2979
|
+
client: input.client,
|
|
2980
|
+
scopes: input.scopes,
|
|
2981
|
+
currentPayload: payload,
|
|
2982
|
+
userContext: input.userContext
|
|
2983
|
+
});
|
|
2984
|
+
},
|
|
2985
|
+
buildIdTokenPayload: async (input) => {
|
|
2986
|
+
const payload = await originalImplementation.buildIdTokenPayload(
|
|
2987
|
+
input
|
|
2988
|
+
);
|
|
2989
|
+
return buildRowndOAuthPayload({
|
|
2990
|
+
user: input.user,
|
|
2991
|
+
client: input.client,
|
|
2992
|
+
scopes: input.scopes,
|
|
2993
|
+
currentPayload: payload,
|
|
2994
|
+
userContext: input.userContext
|
|
2995
|
+
});
|
|
2996
|
+
},
|
|
2997
|
+
buildUserInfo: async (input) => {
|
|
2998
|
+
const payload = await originalImplementation.buildUserInfo(input);
|
|
2999
|
+
return buildRowndOAuthUserInfo({
|
|
3000
|
+
user: input.user,
|
|
3001
|
+
accessTokenPayload: input.accessTokenPayload,
|
|
3002
|
+
scopes: input.scopes,
|
|
3003
|
+
currentPayload: payload
|
|
3004
|
+
});
|
|
3005
|
+
}
|
|
3006
|
+
}),
|
|
3007
|
+
apis: (originalImplementation) => ({
|
|
3008
|
+
...originalImplementation,
|
|
3009
|
+
authGET: async (input) => {
|
|
3010
|
+
if (originalImplementation.authGET === void 0) {
|
|
3011
|
+
throw new Error("OAuth2Provider authGET is unavailable");
|
|
3012
|
+
}
|
|
3013
|
+
applyRowndOAuthResourceParams({
|
|
3014
|
+
params: input.params,
|
|
3015
|
+
userContext: input.userContext
|
|
3016
|
+
});
|
|
3017
|
+
return originalImplementation.authGET(input);
|
|
3018
|
+
},
|
|
3019
|
+
tokenPOST: async (input) => {
|
|
3020
|
+
if (originalImplementation.tokenPOST === void 0) {
|
|
3021
|
+
throw new Error("OAuth2Provider tokenPOST is unavailable");
|
|
3022
|
+
}
|
|
3023
|
+
applyRowndOAuthResourceParams({
|
|
3024
|
+
params: input.body,
|
|
3025
|
+
userContext: input.userContext
|
|
3026
|
+
});
|
|
3027
|
+
return originalImplementation.tokenPOST(input);
|
|
3028
|
+
}
|
|
3029
|
+
})
|
|
3030
|
+
},
|
|
2577
3031
|
passwordless: {
|
|
2578
3032
|
config: (config2) => {
|
|
2579
3033
|
const originalEmailDeliveryOverride = config2.emailDelivery?.override;
|
|
@@ -2794,11 +3248,25 @@ var init = createPluginInitFunction(
|
|
|
2794
3248
|
}
|
|
2795
3249
|
const response = await originalImplementation.verifyEmailPOST(input);
|
|
2796
3250
|
if (response.status === "OK") {
|
|
2797
|
-
await completePendingEmailVerification({
|
|
3251
|
+
const verificationResult = await completePendingEmailVerification({
|
|
2798
3252
|
recipeUserId: response.user.recipeUserId,
|
|
2799
3253
|
email: response.user.email,
|
|
2800
3254
|
userContext: input.userContext
|
|
2801
3255
|
});
|
|
3256
|
+
const session = input.session;
|
|
3257
|
+
const shouldReplaceSession = session && verificationResult && (session.getUserId(input.userContext) !== verificationResult.userId || session.getRecipeUserId(input.userContext).getAsString() !== verificationResult.recipeUserId.getAsString());
|
|
3258
|
+
if (shouldReplaceSession) {
|
|
3259
|
+
await session.revokeSession(input.userContext);
|
|
3260
|
+
response.newSession = await Session3.createNewSession(
|
|
3261
|
+
input.options.req,
|
|
3262
|
+
input.options.res,
|
|
3263
|
+
session.getTenantId(input.userContext),
|
|
3264
|
+
verificationResult.recipeUserId,
|
|
3265
|
+
{},
|
|
3266
|
+
{},
|
|
3267
|
+
input.userContext
|
|
3268
|
+
);
|
|
3269
|
+
}
|
|
2802
3270
|
}
|
|
2803
3271
|
return response;
|
|
2804
3272
|
}
|
|
@@ -2836,6 +3304,7 @@ var init = createPluginInitFunction(
|
|
|
2836
3304
|
rowndAppSecret: config2.rowndAppSecret,
|
|
2837
3305
|
enableDebugLogs: config2.enableDebugLogs,
|
|
2838
3306
|
clientDomains: config2.clientDomains,
|
|
3307
|
+
crossDeviceConfirmationBypass: config2.crossDeviceConfirmationBypass,
|
|
2839
3308
|
telemetry: config2.telemetry,
|
|
2840
3309
|
schema: config2.schema,
|
|
2841
3310
|
appConfig: config2.appConfig,
|
|
@@ -2863,6 +3332,7 @@ export {
|
|
|
2863
3332
|
HUB_LOGIN_PAGE_PATH,
|
|
2864
3333
|
HUB_VERIFY_EMAIL_PAGE_PATH,
|
|
2865
3334
|
INSTANT_AUTH_METHOD_ID,
|
|
3335
|
+
PASSWORDLESS_BYPASS_DEVICE_CONFIRMATION_PARAM,
|
|
2866
3336
|
PLUGIN_ID,
|
|
2867
3337
|
PLUGIN_SDK_VERSION,
|
|
2868
3338
|
PLUGIN_VERSION,
|
|
@@ -2870,6 +3340,7 @@ export {
|
|
|
2870
3340
|
ROWND_JWT_CLAIMS,
|
|
2871
3341
|
ROWND_PLUGIN_ERROR_MESSAGES,
|
|
2872
3342
|
RowndPluginError,
|
|
3343
|
+
createMagicLinkWithConfirmationBypass,
|
|
2873
3344
|
index_default as default,
|
|
2874
3345
|
getRowndClient,
|
|
2875
3346
|
init,
|