@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/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 import_usermetadata2 = __toESM(require("supertokens-node/recipe/usermetadata"));
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 import_usermetadata2.default.updateUserMetadata(userId, {
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 import_usermetadata2.default.getUserMetadata(userId);
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 import_usermetadata2.default.updateUserMetadata(userId, updatedMetadata);
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 import_usermetadata2.default.updateUserMetadata(input.userId, {
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 import_usermetadata2.default.updateUserMetadata(input.userId, {
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 import_usermetadata2.default.updateUserMetadata(metadataUserId, updatedMetadata);
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 import_usermetadata2.default.updateUserMetadata(userId, updatedMetadata);
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: { sessionRequired: true },
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: { sessionRequired: true },
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: { sessionRequired: true },
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: { sessionRequired: true },
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