@supertokens-plugins/rownd-nodejs 0.5.0 → 0.5.1

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.
@@ -40,6 +40,7 @@ var fs2 = __toESM(require("fs/promises"));
40
40
  var import_zod2 = require("zod");
41
41
 
42
42
  // src/rownd-compatibility.ts
43
+ var import_node_crypto = require("crypto");
43
44
  var import_supertokens_node = __toESM(require("supertokens-node"));
44
45
  var import_usermetadata = __toESM(require("supertokens-node/recipe/usermetadata"));
45
46
 
@@ -55,6 +56,11 @@ var IDENTITY_USER_DATA_FIELDS = /* @__PURE__ */ new Set([
55
56
  "google_id",
56
57
  "apple_id"
57
58
  ]);
59
+ var SUPERTOKENS_FAKE_EMAIL_DOMAIN = "stfakeemail.supertokens.com";
60
+ function buildSuperTokensFakeEmail(thirdPartyUserId, thirdPartyId) {
61
+ const hash = (0, import_node_crypto.createHash)("sha256").update(`${thirdPartyId}:${thirdPartyUserId}`).digest("hex").slice(0, 32);
62
+ return `st-${thirdPartyId}-${hash}@${SUPERTOKENS_FAKE_EMAIL_DOMAIN}`;
63
+ }
58
64
  function isIdentityField(field) {
59
65
  return IDENTITY_USER_DATA_FIELDS.has(field);
60
66
  }
@@ -66,27 +72,23 @@ function mapRowndUserToSuperTokens(rowndUser) {
66
72
  throw new Error("Rownd user has no user_id");
67
73
  }
68
74
  if (rowndUserData.google_id) {
69
- if (!rowndUserData.email) {
70
- throw new Error("Rownd Google user is missing email");
71
- }
75
+ const googleEmail = rowndUserData.email ?? buildSuperTokensFakeEmail(rowndUserData.google_id, "google");
72
76
  loginMethods.push({
73
77
  recipeId: "thirdparty",
74
78
  thirdPartyId: "google",
75
79
  thirdPartyUserId: rowndUserData.google_id,
76
- email: rowndUserData.email,
77
- isVerified: !!rowndUserVerifiedData.google_id
80
+ email: googleEmail,
81
+ isVerified: !!rowndUserData.email && !!rowndUserVerifiedData.google_id
78
82
  });
79
83
  }
80
84
  if (rowndUserData.apple_id) {
81
- if (!rowndUserData.email) {
82
- throw new Error("Rownd Apple user is missing email");
83
- }
85
+ const appleEmail = rowndUserData.email ?? buildSuperTokensFakeEmail(rowndUserData.apple_id, "apple");
84
86
  loginMethods.push({
85
87
  recipeId: "thirdparty",
86
88
  thirdPartyId: "apple",
87
89
  thirdPartyUserId: rowndUserData.apple_id,
88
- email: rowndUserData.email,
89
- isVerified: !!rowndUserVerifiedData.apple_id
90
+ email: appleEmail,
91
+ isVerified: !!rowndUserData.email && !!rowndUserVerifiedData.apple_id
90
92
  });
91
93
  }
92
94
  if (rowndUserData.phone_number) {
@@ -159,7 +161,9 @@ var ConfigSchema = import_zod.z.object({
159
161
  appKey: import_zod.z.string(),
160
162
  appSecret: import_zod.z.string(),
161
163
  bearerToken: import_zod.z.string().optional(),
162
- pageSize: import_zod.z.number().int().positive()
164
+ pageSize: import_zod.z.number().int().positive(),
165
+ oidcClientSecrets: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
166
+ provisionOidcClientIdAliases: import_zod.z.boolean().default(true)
163
167
  }),
164
168
  supertokens: import_zod.z.object({
165
169
  connectionURI: import_zod.z.string(),
@@ -61,7 +61,9 @@ var ConfigSchema = import_zod.z.object({
61
61
  appKey: import_zod.z.string(),
62
62
  appSecret: import_zod.z.string(),
63
63
  bearerToken: import_zod.z.string().optional(),
64
- pageSize: import_zod.z.number().int().positive()
64
+ pageSize: import_zod.z.number().int().positive(),
65
+ oidcClientSecrets: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
66
+ provisionOidcClientIdAliases: import_zod.z.boolean().default(true)
65
67
  }),
66
68
  supertokens: import_zod.z.object({
67
69
  connectionURI: import_zod.z.string(),
package/dist/index.js CHANGED
@@ -639,12 +639,11 @@ function createLoggerConfig() {
639
639
  }
640
640
  var config = createLoggerConfig();
641
641
  var logger = common_default(config);
642
- var formatters = logger.formatters;
643
- if (formatters) {
644
- formatters.o = function(value) {
642
+ if (logger.formatters) {
643
+ logger.formatters.o = function(value) {
645
644
  return import_util.default.inspect(value, { colors: this.useColors, compact: true }).split("\n").map((line) => line.trim()).join(" ");
646
645
  };
647
- formatters.O = function(value) {
646
+ logger.formatters.O = function(value) {
648
647
  return import_util.default.inspect(value, { colors: this.useColors, compact: false });
649
648
  };
650
649
  }
@@ -866,6 +865,7 @@ var RowndPluginError = class extends Error {
866
865
  };
867
866
 
868
867
  // src/utils.ts
868
+ var ROWND_OAUTH_LOGIN_CHALLENGE_PARAM = "rownd_oauth_login_challenge";
869
869
  function rewriteLinkPath(inputUrl, targetPath, searchParams) {
870
870
  try {
871
871
  const url = new URL(inputUrl);
@@ -1066,7 +1066,8 @@ function getMagicLinkBootstrapParams(input) {
1066
1066
  ...input.appVariantId ? { appVariantId: input.appVariantId } : {},
1067
1067
  ...input.displayContext ? { displayContext: input.displayContext } : {},
1068
1068
  ...input.redirectToPath ? { redirectToPath: input.redirectToPath } : {},
1069
- ...input.clientDomainKey ? { clientDomain: input.clientDomainKey } : {}
1069
+ ...input.clientDomainKey ? { clientDomain: input.clientDomainKey } : {},
1070
+ ...input.oauthLoginChallenge ? { oauthLoginChallenge: input.oauthLoginChallenge } : {}
1070
1071
  };
1071
1072
  }
1072
1073
  function rewriteMagicLink(input) {
@@ -1092,6 +1093,10 @@ function getRequestedRedirectToPathFromRequest(req) {
1092
1093
  const redirectToPath = req.getKeyValueFromQuery("rownd_redirect_to_path");
1093
1094
  return typeof redirectToPath === "string" && redirectToPath.length > 0 ? redirectToPath : void 0;
1094
1095
  }
1096
+ function getRequestedOAuthLoginChallengeFromRequest(req) {
1097
+ const loginChallenge = req.getKeyValueFromQuery(ROWND_OAUTH_LOGIN_CHALLENGE_PARAM);
1098
+ return typeof loginChallenge === "string" && loginChallenge.length > 0 ? loginChallenge : void 0;
1099
+ }
1095
1100
 
1096
1101
  // src/config.ts
1097
1102
  var pluginConfig;
@@ -1464,6 +1469,7 @@ function buildAuthMobileConfig(authMobile) {
1464
1469
  }
1465
1470
 
1466
1471
  // src/rownd-compatibility.ts
1472
+ var import_node_crypto = require("crypto");
1467
1473
  var import_supertokens_node = __toESM(require("supertokens-node"));
1468
1474
  var import_usermetadata2 = __toESM(require("supertokens-node/recipe/usermetadata"));
1469
1475
  var IDENTITY_USER_DATA_FIELDS = /* @__PURE__ */ new Set([
@@ -1477,6 +1483,14 @@ var INTERNAL_METADATA_FIELDS = /* @__PURE__ */ new Set([
1477
1483
  "original_rownd_user",
1478
1484
  "rownd_pending_verification"
1479
1485
  ]);
1486
+ var SUPERTOKENS_FAKE_EMAIL_DOMAIN = "stfakeemail.supertokens.com";
1487
+ function isSuperTokensFakeEmail(email) {
1488
+ return typeof email === "string" && email.toLowerCase().endsWith(`@${SUPERTOKENS_FAKE_EMAIL_DOMAIN}`);
1489
+ }
1490
+ function buildSuperTokensFakeEmail(thirdPartyUserId, thirdPartyId) {
1491
+ const hash = (0, import_node_crypto.createHash)("sha256").update(`${thirdPartyId}:${thirdPartyUserId}`).digest("hex").slice(0, 32);
1492
+ return `st-${thirdPartyId}-${hash}@${SUPERTOKENS_FAKE_EMAIL_DOMAIN}`;
1493
+ }
1480
1494
  function isIdentityField(field) {
1481
1495
  return IDENTITY_USER_DATA_FIELDS.has(field);
1482
1496
  }
@@ -1491,27 +1505,23 @@ function mapRowndUserToSuperTokens(rowndUser) {
1491
1505
  throw new Error("Rownd user has no user_id");
1492
1506
  }
1493
1507
  if (rowndUserData.google_id) {
1494
- if (!rowndUserData.email) {
1495
- throw new Error("Rownd Google user is missing email");
1496
- }
1508
+ const googleEmail = rowndUserData.email ?? buildSuperTokensFakeEmail(rowndUserData.google_id, "google");
1497
1509
  loginMethods.push({
1498
1510
  recipeId: "thirdparty",
1499
1511
  thirdPartyId: "google",
1500
1512
  thirdPartyUserId: rowndUserData.google_id,
1501
- email: rowndUserData.email,
1502
- isVerified: !!rowndUserVerifiedData.google_id
1513
+ email: googleEmail,
1514
+ isVerified: !!rowndUserData.email && !!rowndUserVerifiedData.google_id
1503
1515
  });
1504
1516
  }
1505
1517
  if (rowndUserData.apple_id) {
1506
- if (!rowndUserData.email) {
1507
- throw new Error("Rownd Apple user is missing email");
1508
- }
1518
+ const appleEmail = rowndUserData.email ?? buildSuperTokensFakeEmail(rowndUserData.apple_id, "apple");
1509
1519
  loginMethods.push({
1510
1520
  recipeId: "thirdparty",
1511
1521
  thirdPartyId: "apple",
1512
1522
  thirdPartyUserId: rowndUserData.apple_id,
1513
- email: rowndUserData.email,
1514
- isVerified: !!rowndUserVerifiedData.apple_id
1523
+ email: appleEmail,
1524
+ isVerified: !!rowndUserData.email && !!rowndUserVerifiedData.apple_id
1515
1525
  });
1516
1526
  }
1517
1527
  if (rowndUserData.phone_number) {
@@ -1696,7 +1706,7 @@ async function buildStandardOAuthClaims(user, scopes) {
1696
1706
  const rowndData = isJsonRecord(metadata.original_rownd_user?.data) ? metadata.original_rownd_user.data : {};
1697
1707
  const verifiedData = isJsonRecord(metadata.original_rownd_user?.verified_data) ? metadata.original_rownd_user.verified_data : {};
1698
1708
  if (scopes.includes("email")) {
1699
- const email = firstString(rowndData.email) ?? user.emails[0];
1709
+ const email = firstRealEmail(firstString(rowndData.email), ...user.emails);
1700
1710
  if (email) {
1701
1711
  claims.email = email;
1702
1712
  claims.email_verified = isOAuthClaimVerified(
@@ -1763,6 +1773,11 @@ function firstString(value) {
1763
1773
  }
1764
1774
  return typeof value === "string" && value.length > 0 ? value : void 0;
1765
1775
  }
1776
+ function firstRealEmail(...values) {
1777
+ return values.find((entry) => {
1778
+ return typeof entry === "string" && entry.length > 0 && !isSuperTokensFakeEmail(entry);
1779
+ });
1780
+ }
1766
1781
  function isOAuthClaimVerified(value, expectedValue, fallback) {
1767
1782
  return value === true || value === expectedValue || fallback;
1768
1783
  }
@@ -2046,6 +2061,7 @@ async function createMagicLinkWithConfirmationBypass(input) {
2046
2061
  })}${getAppInfoString(stConfig.appInfo.websiteBasePath)}/verify?preAuthSessionId=${encodeURIComponent(
2047
2062
  codeInfo.preAuthSessionId
2048
2063
  )}&tenantId=${encodeURIComponent(tenantId)}#${encodeURIComponent(codeInfo.linkCode)}`;
2064
+ const oauthLoginChallenge = userContext.rowndOAuthLoginChallenge;
2049
2065
  const rewrittenUrl = new URL(rewriteMagicLink({
2050
2066
  magicLink,
2051
2067
  clientDomain,
@@ -2056,7 +2072,8 @@ async function createMagicLinkWithConfirmationBypass(input) {
2056
2072
  appVariantId,
2057
2073
  displayContext: input.displayContext,
2058
2074
  redirectToPath,
2059
- clientDomainKey: input.clientDomain
2075
+ clientDomainKey: input.clientDomain,
2076
+ oauthLoginChallenge: typeof oauthLoginChallenge === "string" ? oauthLoginChallenge : void 0
2060
2077
  })
2061
2078
  }));
2062
2079
  rewrittenUrl.searchParams.set(PASSWORDLESS_BYPASS_DEVICE_CONFIRMATION_PARAM, "true");
@@ -2107,7 +2124,7 @@ async function getUserById(userId) {
2107
2124
  };
2108
2125
  for (const method of stUser.loginMethods) {
2109
2126
  if (method.recipeId === "passwordless") {
2110
- if (method.email) {
2127
+ if (method.email && !isSuperTokensFakeEmail(method.email)) {
2111
2128
  verifiedData.email = method.email;
2112
2129
  if (data.email === void 0) data.email = method.email;
2113
2130
  }
@@ -2119,10 +2136,10 @@ async function getUserById(userId) {
2119
2136
  } else if (method.recipeId === "thirdparty") {
2120
2137
  const thirdPartyId = getThirdPartyId(method);
2121
2138
  const thirdPartyUserId = getThirdPartyUserId(method);
2122
- if (method.verified && method.email) {
2139
+ if (method.verified && method.email && !isSuperTokensFakeEmail(method.email)) {
2123
2140
  verifiedData.email = method.email;
2124
2141
  }
2125
- if (method.email && data.email === void 0) {
2142
+ if (method.email && !isSuperTokensFakeEmail(method.email) && data.email === void 0) {
2126
2143
  data.email = method.email;
2127
2144
  }
2128
2145
  if (thirdPartyId === "google" && thirdPartyUserId) {
@@ -2134,7 +2151,7 @@ async function getUserById(userId) {
2134
2151
  verifiedData.apple_id = thirdPartyUserId;
2135
2152
  }
2136
2153
  } else if (method.recipeId === "emailpassword") {
2137
- if (method.email && data.email === void 0) {
2154
+ if (method.email && !isSuperTokensFakeEmail(method.email) && data.email === void 0) {
2138
2155
  data.email = method.email;
2139
2156
  }
2140
2157
  }
@@ -2839,6 +2856,7 @@ var init = createPluginInitFunction(
2839
2856
  const displayContext = input?.userContext?.rowndDisplayContext;
2840
2857
  const redirectToPath = input?.userContext?.rowndRedirectToPath;
2841
2858
  const clientDomain = input?.userContext?.rowndClientDomain;
2859
+ const oauthLoginChallenge = input?.userContext?.rowndOAuthLoginChallenge;
2842
2860
  const clientDomainKey = clientDomain ?? (displayContext === "mobile_app" ? "mobile" : "browser");
2843
2861
  const clientBaseUrl = pluginConfig2.clientDomains?.[clientDomainKey];
2844
2862
  const bootstrapParams = {
@@ -2846,7 +2864,8 @@ var init = createPluginInitFunction(
2846
2864
  ...hubBootstrapParams ?? {},
2847
2865
  ...typeof appVariantId === "string" ? { appVariantId } : {},
2848
2866
  ...typeof displayContext === "string" ? { displayContext } : {},
2849
- ...typeof redirectToPath === "string" ? { redirectToPath } : {}
2867
+ ...typeof redirectToPath === "string" ? { redirectToPath } : {},
2868
+ ...typeof oauthLoginChallenge === "string" ? { oauthLoginChallenge } : {}
2850
2869
  };
2851
2870
  const rewrittenLink = input[linkKey] ? clientBaseUrl ? rewriteLinkToBaseUrl(
2852
2871
  input[linkKey],
@@ -3132,6 +3151,12 @@ var init = createPluginInitFunction(
3132
3151
  if (appVariantId) {
3133
3152
  input.userContext.rowndAppVariantId = appVariantId;
3134
3153
  }
3154
+ const oauthLoginChallenge = getRequestedOAuthLoginChallengeFromRequest(
3155
+ input.options.req
3156
+ );
3157
+ if (oauthLoginChallenge) {
3158
+ input.userContext.rowndOAuthLoginChallenge = oauthLoginChallenge;
3159
+ }
3135
3160
  assertRowndAppVariantIsConfigured(appVariantId);
3136
3161
  return originalImplementation.createCodePOST(input);
3137
3162
  },
package/dist/index.mjs CHANGED
@@ -616,12 +616,11 @@ function createLoggerConfig() {
616
616
  }
617
617
  var config = createLoggerConfig();
618
618
  var logger = common_default(config);
619
- var formatters = logger.formatters;
620
- if (formatters) {
621
- formatters.o = function(value) {
619
+ if (logger.formatters) {
620
+ logger.formatters.o = function(value) {
622
621
  return util.inspect(value, { colors: this.useColors, compact: true }).split("\n").map((line) => line.trim()).join(" ");
623
622
  };
624
- formatters.O = function(value) {
623
+ logger.formatters.O = function(value) {
625
624
  return util.inspect(value, { colors: this.useColors, compact: false });
626
625
  };
627
626
  }
@@ -843,6 +842,7 @@ var RowndPluginError = class extends Error {
843
842
  };
844
843
 
845
844
  // src/utils.ts
845
+ var ROWND_OAUTH_LOGIN_CHALLENGE_PARAM = "rownd_oauth_login_challenge";
846
846
  function rewriteLinkPath(inputUrl, targetPath, searchParams) {
847
847
  try {
848
848
  const url = new URL(inputUrl);
@@ -1043,7 +1043,8 @@ function getMagicLinkBootstrapParams(input) {
1043
1043
  ...input.appVariantId ? { appVariantId: input.appVariantId } : {},
1044
1044
  ...input.displayContext ? { displayContext: input.displayContext } : {},
1045
1045
  ...input.redirectToPath ? { redirectToPath: input.redirectToPath } : {},
1046
- ...input.clientDomainKey ? { clientDomain: input.clientDomainKey } : {}
1046
+ ...input.clientDomainKey ? { clientDomain: input.clientDomainKey } : {},
1047
+ ...input.oauthLoginChallenge ? { oauthLoginChallenge: input.oauthLoginChallenge } : {}
1047
1048
  };
1048
1049
  }
1049
1050
  function rewriteMagicLink(input) {
@@ -1069,6 +1070,10 @@ function getRequestedRedirectToPathFromRequest(req) {
1069
1070
  const redirectToPath = req.getKeyValueFromQuery("rownd_redirect_to_path");
1070
1071
  return typeof redirectToPath === "string" && redirectToPath.length > 0 ? redirectToPath : void 0;
1071
1072
  }
1073
+ function getRequestedOAuthLoginChallengeFromRequest(req) {
1074
+ const loginChallenge = req.getKeyValueFromQuery(ROWND_OAUTH_LOGIN_CHALLENGE_PARAM);
1075
+ return typeof loginChallenge === "string" && loginChallenge.length > 0 ? loginChallenge : void 0;
1076
+ }
1072
1077
 
1073
1078
  // src/config.ts
1074
1079
  var pluginConfig;
@@ -1441,6 +1446,7 @@ function buildAuthMobileConfig(authMobile) {
1441
1446
  }
1442
1447
 
1443
1448
  // src/rownd-compatibility.ts
1449
+ import { createHash } from "crypto";
1444
1450
  import SuperTokens from "supertokens-node";
1445
1451
  import UserMetadata2 from "supertokens-node/recipe/usermetadata";
1446
1452
  var IDENTITY_USER_DATA_FIELDS = /* @__PURE__ */ new Set([
@@ -1454,6 +1460,14 @@ var INTERNAL_METADATA_FIELDS = /* @__PURE__ */ new Set([
1454
1460
  "original_rownd_user",
1455
1461
  "rownd_pending_verification"
1456
1462
  ]);
1463
+ var SUPERTOKENS_FAKE_EMAIL_DOMAIN = "stfakeemail.supertokens.com";
1464
+ function isSuperTokensFakeEmail(email) {
1465
+ return typeof email === "string" && email.toLowerCase().endsWith(`@${SUPERTOKENS_FAKE_EMAIL_DOMAIN}`);
1466
+ }
1467
+ function buildSuperTokensFakeEmail(thirdPartyUserId, thirdPartyId) {
1468
+ const hash = createHash("sha256").update(`${thirdPartyId}:${thirdPartyUserId}`).digest("hex").slice(0, 32);
1469
+ return `st-${thirdPartyId}-${hash}@${SUPERTOKENS_FAKE_EMAIL_DOMAIN}`;
1470
+ }
1457
1471
  function isIdentityField(field) {
1458
1472
  return IDENTITY_USER_DATA_FIELDS.has(field);
1459
1473
  }
@@ -1468,27 +1482,23 @@ function mapRowndUserToSuperTokens(rowndUser) {
1468
1482
  throw new Error("Rownd user has no user_id");
1469
1483
  }
1470
1484
  if (rowndUserData.google_id) {
1471
- if (!rowndUserData.email) {
1472
- throw new Error("Rownd Google user is missing email");
1473
- }
1485
+ const googleEmail = rowndUserData.email ?? buildSuperTokensFakeEmail(rowndUserData.google_id, "google");
1474
1486
  loginMethods.push({
1475
1487
  recipeId: "thirdparty",
1476
1488
  thirdPartyId: "google",
1477
1489
  thirdPartyUserId: rowndUserData.google_id,
1478
- email: rowndUserData.email,
1479
- isVerified: !!rowndUserVerifiedData.google_id
1490
+ email: googleEmail,
1491
+ isVerified: !!rowndUserData.email && !!rowndUserVerifiedData.google_id
1480
1492
  });
1481
1493
  }
1482
1494
  if (rowndUserData.apple_id) {
1483
- if (!rowndUserData.email) {
1484
- throw new Error("Rownd Apple user is missing email");
1485
- }
1495
+ const appleEmail = rowndUserData.email ?? buildSuperTokensFakeEmail(rowndUserData.apple_id, "apple");
1486
1496
  loginMethods.push({
1487
1497
  recipeId: "thirdparty",
1488
1498
  thirdPartyId: "apple",
1489
1499
  thirdPartyUserId: rowndUserData.apple_id,
1490
- email: rowndUserData.email,
1491
- isVerified: !!rowndUserVerifiedData.apple_id
1500
+ email: appleEmail,
1501
+ isVerified: !!rowndUserData.email && !!rowndUserVerifiedData.apple_id
1492
1502
  });
1493
1503
  }
1494
1504
  if (rowndUserData.phone_number) {
@@ -1673,7 +1683,7 @@ async function buildStandardOAuthClaims(user, scopes) {
1673
1683
  const rowndData = isJsonRecord(metadata.original_rownd_user?.data) ? metadata.original_rownd_user.data : {};
1674
1684
  const verifiedData = isJsonRecord(metadata.original_rownd_user?.verified_data) ? metadata.original_rownd_user.verified_data : {};
1675
1685
  if (scopes.includes("email")) {
1676
- const email = firstString(rowndData.email) ?? user.emails[0];
1686
+ const email = firstRealEmail(firstString(rowndData.email), ...user.emails);
1677
1687
  if (email) {
1678
1688
  claims.email = email;
1679
1689
  claims.email_verified = isOAuthClaimVerified(
@@ -1740,6 +1750,11 @@ function firstString(value) {
1740
1750
  }
1741
1751
  return typeof value === "string" && value.length > 0 ? value : void 0;
1742
1752
  }
1753
+ function firstRealEmail(...values) {
1754
+ return values.find((entry) => {
1755
+ return typeof entry === "string" && entry.length > 0 && !isSuperTokensFakeEmail(entry);
1756
+ });
1757
+ }
1743
1758
  function isOAuthClaimVerified(value, expectedValue, fallback) {
1744
1759
  return value === true || value === expectedValue || fallback;
1745
1760
  }
@@ -2023,6 +2038,7 @@ async function createMagicLinkWithConfirmationBypass(input) {
2023
2038
  })}${getAppInfoString(stConfig.appInfo.websiteBasePath)}/verify?preAuthSessionId=${encodeURIComponent(
2024
2039
  codeInfo.preAuthSessionId
2025
2040
  )}&tenantId=${encodeURIComponent(tenantId)}#${encodeURIComponent(codeInfo.linkCode)}`;
2041
+ const oauthLoginChallenge = userContext.rowndOAuthLoginChallenge;
2026
2042
  const rewrittenUrl = new URL(rewriteMagicLink({
2027
2043
  magicLink,
2028
2044
  clientDomain,
@@ -2033,7 +2049,8 @@ async function createMagicLinkWithConfirmationBypass(input) {
2033
2049
  appVariantId,
2034
2050
  displayContext: input.displayContext,
2035
2051
  redirectToPath,
2036
- clientDomainKey: input.clientDomain
2052
+ clientDomainKey: input.clientDomain,
2053
+ oauthLoginChallenge: typeof oauthLoginChallenge === "string" ? oauthLoginChallenge : void 0
2037
2054
  })
2038
2055
  }));
2039
2056
  rewrittenUrl.searchParams.set(PASSWORDLESS_BYPASS_DEVICE_CONFIRMATION_PARAM, "true");
@@ -2084,7 +2101,7 @@ async function getUserById(userId) {
2084
2101
  };
2085
2102
  for (const method of stUser.loginMethods) {
2086
2103
  if (method.recipeId === "passwordless") {
2087
- if (method.email) {
2104
+ if (method.email && !isSuperTokensFakeEmail(method.email)) {
2088
2105
  verifiedData.email = method.email;
2089
2106
  if (data.email === void 0) data.email = method.email;
2090
2107
  }
@@ -2096,10 +2113,10 @@ async function getUserById(userId) {
2096
2113
  } else if (method.recipeId === "thirdparty") {
2097
2114
  const thirdPartyId = getThirdPartyId(method);
2098
2115
  const thirdPartyUserId = getThirdPartyUserId(method);
2099
- if (method.verified && method.email) {
2116
+ if (method.verified && method.email && !isSuperTokensFakeEmail(method.email)) {
2100
2117
  verifiedData.email = method.email;
2101
2118
  }
2102
- if (method.email && data.email === void 0) {
2119
+ if (method.email && !isSuperTokensFakeEmail(method.email) && data.email === void 0) {
2103
2120
  data.email = method.email;
2104
2121
  }
2105
2122
  if (thirdPartyId === "google" && thirdPartyUserId) {
@@ -2111,7 +2128,7 @@ async function getUserById(userId) {
2111
2128
  verifiedData.apple_id = thirdPartyUserId;
2112
2129
  }
2113
2130
  } else if (method.recipeId === "emailpassword") {
2114
- if (method.email && data.email === void 0) {
2131
+ if (method.email && !isSuperTokensFakeEmail(method.email) && data.email === void 0) {
2115
2132
  data.email = method.email;
2116
2133
  }
2117
2134
  }
@@ -2816,6 +2833,7 @@ var init = createPluginInitFunction(
2816
2833
  const displayContext = input?.userContext?.rowndDisplayContext;
2817
2834
  const redirectToPath = input?.userContext?.rowndRedirectToPath;
2818
2835
  const clientDomain = input?.userContext?.rowndClientDomain;
2836
+ const oauthLoginChallenge = input?.userContext?.rowndOAuthLoginChallenge;
2819
2837
  const clientDomainKey = clientDomain ?? (displayContext === "mobile_app" ? "mobile" : "browser");
2820
2838
  const clientBaseUrl = pluginConfig2.clientDomains?.[clientDomainKey];
2821
2839
  const bootstrapParams = {
@@ -2823,7 +2841,8 @@ var init = createPluginInitFunction(
2823
2841
  ...hubBootstrapParams ?? {},
2824
2842
  ...typeof appVariantId === "string" ? { appVariantId } : {},
2825
2843
  ...typeof displayContext === "string" ? { displayContext } : {},
2826
- ...typeof redirectToPath === "string" ? { redirectToPath } : {}
2844
+ ...typeof redirectToPath === "string" ? { redirectToPath } : {},
2845
+ ...typeof oauthLoginChallenge === "string" ? { oauthLoginChallenge } : {}
2827
2846
  };
2828
2847
  const rewrittenLink = input[linkKey] ? clientBaseUrl ? rewriteLinkToBaseUrl(
2829
2848
  input[linkKey],
@@ -3109,6 +3128,12 @@ var init = createPluginInitFunction(
3109
3128
  if (appVariantId) {
3110
3129
  input.userContext.rowndAppVariantId = appVariantId;
3111
3130
  }
3131
+ const oauthLoginChallenge = getRequestedOAuthLoginChallengeFromRequest(
3132
+ input.options.req
3133
+ );
3134
+ if (oauthLoginChallenge) {
3135
+ input.userContext.rowndOAuthLoginChallenge = oauthLoginChallenge;
3136
+ }
3112
3137
  assertRowndAppVariantIsConfigured(appVariantId);
3113
3138
  return originalImplementation.createCodePOST(input);
3114
3139
  },
@@ -49,6 +49,16 @@ rownd:
49
49
  # bearerToken: <ROWND_API_BEARER_TOKEN>
50
50
  # Number of Rownd users to request per page.
51
51
  pageSize: 100
52
+ # Rownd stores existing OIDC credential secrets hashed. Provide plaintext
53
+ # values here when migrating OIDC clients. Keys can be credential client IDs
54
+ # (key_...) and OIDC client configuration IDs (oc_...).
55
+ oidcClientSecrets: {}
56
+ # oidcClientSecrets:
57
+ # key_123: ras_plaintext_secret
58
+ # oc_123: ras_plaintext_secret_for_oidc_client_id_alias
59
+ # Rownd accepts the OIDC client configuration ID (oc_...) as a client_id in
60
+ # addition to each linked credential client_id (key_...).
61
+ provisionOidcClientIdAliases: true
52
62
 
53
63
  supertokens:
54
64
  connectionURI: <CONNECTION_URI>
@@ -62,7 +62,9 @@ var ConfigSchema = import_zod.z.object({
62
62
  appKey: import_zod.z.string(),
63
63
  appSecret: import_zod.z.string(),
64
64
  bearerToken: import_zod.z.string().optional(),
65
- pageSize: import_zod.z.number().int().positive()
65
+ pageSize: import_zod.z.number().int().positive(),
66
+ oidcClientSecrets: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
67
+ provisionOidcClientIdAliases: import_zod.z.boolean().default(true)
66
68
  }),
67
69
  supertokens: import_zod.z.object({
68
70
  connectionURI: import_zod.z.string(),
@@ -187,55 +189,118 @@ async function provisionSuperTokensInfrastructure(config, oidcClients) {
187
189
  if (config.supertokens.apiKey) {
188
190
  headers["api-key"] = config.supertokens.apiKey;
189
191
  }
192
+ const provisionedClientIds = /* @__PURE__ */ new Set();
190
193
  for (const oidcClient of oidcClients) {
191
- for (const credential of oidcClient.credentials ?? []) {
192
- console.log(
193
- `Provisioning SuperTokens OAuth2 Client: ${credential.client_id}`
194
+ const credentials = oidcClient.credentials ?? [];
195
+ for (const credential of credentials) {
196
+ await createSuperTokensOAuthClient({
197
+ config,
198
+ headers,
199
+ oidcClient,
200
+ clientId: credential.client_id,
201
+ clientSecret: resolveOidcClientSecret(
202
+ config,
203
+ credential.client_id,
204
+ credential.secret
205
+ ),
206
+ appVariantId: credential.app_variant_id,
207
+ provisionedClientIds
208
+ });
209
+ }
210
+ if (config.rownd.provisionOidcClientIdAliases && credentials.length > 0) {
211
+ const firstCredential = credentials[0];
212
+ if (!firstCredential) {
213
+ continue;
214
+ }
215
+ const firstCredentialSecret = resolveOidcClientSecret(
216
+ config,
217
+ firstCredential.client_id,
218
+ firstCredential.secret
194
219
  );
195
- const oauthRes = await fetch(
196
- new URL(
197
- "/recipe/oauth/clients",
198
- config.supertokens.connectionURI
199
- ).toString(),
200
- {
201
- method: "POST",
202
- headers,
203
- body: JSON.stringify({
204
- clientName: oidcClient.name || credential.client_id || "Rownd Client",
205
- clientId: credential.client_id,
206
- clientSecret: credential.secret,
207
- redirectUris: oidcClient.config.redirect_uris || [],
208
- postLogoutRedirectUris: oidcClient.config.post_logout_uris || [],
209
- scope: oidcClient.config.allowed_scopes?.join(" "),
210
- responseTypes: ["code", "token", "id_token"],
211
- grantTypes: [
212
- "authorization_code",
213
- "refresh_token",
214
- "client_credentials",
215
- "implicit"
216
- ],
217
- tokenEndpointAuthMethod: oidcClient.config.token_endpoint_auth_method ?? "client_secret_basic",
218
- audience: [`app:${config.rownd.appId}`],
219
- metadata: {
220
- rowndOidcClientId: oidcClient.id,
221
- rowndAppVariantId: credential.app_variant_id || void 0,
222
- rowndAllowedScopes: oidcClient.config.allowed_scopes || [],
223
- rowndApplicationType: oidcClient.config.application_type,
224
- rowndIsPkceRequired: oidcClient.config.is_pkce_required,
225
- rowndIsPkceSupported: oidcClient.config.is_pkce_supported,
226
- rowndDeviceFlowEnabled: oidcClient.config.device_flow_enabled
227
- }
228
- })
229
- }
220
+ const aliasSecret = resolveOidcClientSecret(
221
+ config,
222
+ oidcClient.id,
223
+ firstCredentialSecret
230
224
  );
231
- if (!oauthRes.ok) {
232
- const errorText = await oauthRes.text();
233
- if (!errorText.includes("already exists") && !errorText.includes("Duplicate")) {
234
- throw new Error(
235
- `Failed to create OAuth2 Client ${credential.client_id}: ${oauthRes.status} ${errorText}`
236
- );
225
+ await createSuperTokensOAuthClient({
226
+ config,
227
+ headers,
228
+ oidcClient,
229
+ clientId: oidcClient.id,
230
+ clientSecret: aliasSecret,
231
+ isOidcClientIdAlias: true,
232
+ provisionedClientIds
233
+ });
234
+ }
235
+ }
236
+ }
237
+ function resolveOidcClientSecret(config, clientId, apiSecret) {
238
+ const secret = config.rownd.oidcClientSecrets?.[clientId] ?? apiSecret;
239
+ if (!secret) {
240
+ throw new Error(
241
+ `Missing plaintext secret for Rownd OIDC client ${clientId}. Add rownd.oidcClientSecrets.${clientId} to the config.`
242
+ );
243
+ }
244
+ if (secret.startsWith("$argon2")) {
245
+ throw new Error(
246
+ `Rownd returned a hashed secret for OIDC client ${clientId}. Add the plaintext secret under rownd.oidcClientSecrets.${clientId}.`
247
+ );
248
+ }
249
+ return secret;
250
+ }
251
+ async function createSuperTokensOAuthClient(input) {
252
+ if (input.provisionedClientIds.has(input.clientId)) {
253
+ return;
254
+ }
255
+ input.provisionedClientIds.add(input.clientId);
256
+ console.log(`Provisioning SuperTokens OAuth2 Client: ${input.clientId}`);
257
+ const grantTypes = [
258
+ "authorization_code",
259
+ "refresh_token",
260
+ "client_credentials",
261
+ "implicit"
262
+ ];
263
+ if (input.oidcClient.config.device_flow_enabled) {
264
+ grantTypes.push("urn:ietf:params:oauth:grant-type:device_code");
265
+ }
266
+ const oauthRes = await fetch(
267
+ new URL(
268
+ "/recipe/oauth/clients",
269
+ input.config.supertokens.connectionURI
270
+ ).toString(),
271
+ {
272
+ method: "POST",
273
+ headers: input.headers,
274
+ body: JSON.stringify({
275
+ clientName: input.oidcClient.name || input.clientId || "Rownd Client",
276
+ clientId: input.clientId,
277
+ clientSecret: input.clientSecret,
278
+ redirectUris: input.oidcClient.config.redirect_uris || [],
279
+ postLogoutRedirectUris: input.oidcClient.config.post_logout_uris || [],
280
+ scope: input.oidcClient.config.allowed_scopes?.join(" "),
281
+ responseTypes: ["code", "token", "id_token"],
282
+ grantTypes,
283
+ tokenEndpointAuthMethod: input.oidcClient.config.token_endpoint_auth_method ?? "client_secret_basic",
284
+ audience: [`app:${input.config.rownd.appId}`],
285
+ metadata: {
286
+ rowndOidcClientId: input.oidcClient.id,
287
+ rowndAppVariantId: input.appVariantId || void 0,
288
+ rowndAllowedScopes: input.oidcClient.config.allowed_scopes || [],
289
+ rowndApplicationType: input.oidcClient.config.application_type,
290
+ rowndIsPkceRequired: input.oidcClient.config.is_pkce_required,
291
+ rowndIsPkceSupported: input.oidcClient.config.is_pkce_supported,
292
+ rowndDeviceFlowEnabled: input.oidcClient.config.device_flow_enabled,
293
+ rowndIsOidcClientIdAlias: input.isOidcClientIdAlias || void 0
237
294
  }
238
- }
295
+ })
296
+ }
297
+ );
298
+ if (!oauthRes.ok) {
299
+ const errorText = await oauthRes.text();
300
+ if (!errorText.includes("already exists") && !errorText.includes("Duplicate")) {
301
+ throw new Error(
302
+ `Failed to create OAuth2 Client ${input.clientId}: ${oauthRes.status} ${errorText}`
303
+ );
239
304
  }
240
305
  }
241
306
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supertokens-plugins/rownd-nodejs",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Rownd User Migration Plugin for SuperTokens",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",