@supertokens-plugins/rownd-nodejs 0.5.0 → 0.6.0
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 +15 -3
- package/dist/bulkMigrate.js +37 -17
- package/dist/generateAppConfig.js +5 -2
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +151 -73
- package/dist/index.mjs +151 -73
- package/dist/initConfig.js +12 -0
- package/dist/setupCoreInstance.js +113 -47
- package/package.json +1 -1
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
|
-
|
|
620
|
-
|
|
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,10 @@ 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
|
+
function resolveTenantId(req) {
|
|
847
|
+
return req.getKeyValueFromQuery("tenantId") || PUBLIC_TENANT_ID;
|
|
848
|
+
}
|
|
846
849
|
function rewriteLinkPath(inputUrl, targetPath, searchParams) {
|
|
847
850
|
try {
|
|
848
851
|
const url = new URL(inputUrl);
|
|
@@ -1043,7 +1046,8 @@ function getMagicLinkBootstrapParams(input) {
|
|
|
1043
1046
|
...input.appVariantId ? { appVariantId: input.appVariantId } : {},
|
|
1044
1047
|
...input.displayContext ? { displayContext: input.displayContext } : {},
|
|
1045
1048
|
...input.redirectToPath ? { redirectToPath: input.redirectToPath } : {},
|
|
1046
|
-
...input.clientDomainKey ? { clientDomain: input.clientDomainKey } : {}
|
|
1049
|
+
...input.clientDomainKey ? { clientDomain: input.clientDomainKey } : {},
|
|
1050
|
+
...input.oauthLoginChallenge ? { oauthLoginChallenge: input.oauthLoginChallenge } : {}
|
|
1047
1051
|
};
|
|
1048
1052
|
}
|
|
1049
1053
|
function rewriteMagicLink(input) {
|
|
@@ -1069,6 +1073,10 @@ function getRequestedRedirectToPathFromRequest(req) {
|
|
|
1069
1073
|
const redirectToPath = req.getKeyValueFromQuery("rownd_redirect_to_path");
|
|
1070
1074
|
return typeof redirectToPath === "string" && redirectToPath.length > 0 ? redirectToPath : void 0;
|
|
1071
1075
|
}
|
|
1076
|
+
function getRequestedOAuthLoginChallengeFromRequest(req) {
|
|
1077
|
+
const loginChallenge = req.getKeyValueFromQuery(ROWND_OAUTH_LOGIN_CHALLENGE_PARAM);
|
|
1078
|
+
return typeof loginChallenge === "string" && loginChallenge.length > 0 ? loginChallenge : void 0;
|
|
1079
|
+
}
|
|
1072
1080
|
|
|
1073
1081
|
// src/config.ts
|
|
1074
1082
|
var pluginConfig;
|
|
@@ -1441,6 +1449,7 @@ function buildAuthMobileConfig(authMobile) {
|
|
|
1441
1449
|
}
|
|
1442
1450
|
|
|
1443
1451
|
// src/rownd-compatibility.ts
|
|
1452
|
+
import { createHash } from "crypto";
|
|
1444
1453
|
import SuperTokens from "supertokens-node";
|
|
1445
1454
|
import UserMetadata2 from "supertokens-node/recipe/usermetadata";
|
|
1446
1455
|
var IDENTITY_USER_DATA_FIELDS = /* @__PURE__ */ new Set([
|
|
@@ -1454,13 +1463,21 @@ var INTERNAL_METADATA_FIELDS = /* @__PURE__ */ new Set([
|
|
|
1454
1463
|
"original_rownd_user",
|
|
1455
1464
|
"rownd_pending_verification"
|
|
1456
1465
|
]);
|
|
1466
|
+
var SUPERTOKENS_FAKE_EMAIL_DOMAIN = "stfakeemail.supertokens.com";
|
|
1467
|
+
function isSuperTokensFakeEmail(email) {
|
|
1468
|
+
return typeof email === "string" && email.toLowerCase().endsWith(`@${SUPERTOKENS_FAKE_EMAIL_DOMAIN}`);
|
|
1469
|
+
}
|
|
1470
|
+
function buildSuperTokensFakeEmail(thirdPartyUserId, thirdPartyId) {
|
|
1471
|
+
const hash = createHash("sha256").update(`${thirdPartyId}:${thirdPartyUserId}`).digest("hex").slice(0, 32);
|
|
1472
|
+
return `st-${thirdPartyId}-${hash}@${SUPERTOKENS_FAKE_EMAIL_DOMAIN}`;
|
|
1473
|
+
}
|
|
1457
1474
|
function isIdentityField(field) {
|
|
1458
1475
|
return IDENTITY_USER_DATA_FIELDS.has(field);
|
|
1459
1476
|
}
|
|
1460
1477
|
function isInternalMetadataField(field) {
|
|
1461
1478
|
return INTERNAL_METADATA_FIELDS.has(field);
|
|
1462
1479
|
}
|
|
1463
|
-
function mapRowndUserToSuperTokens(rowndUser) {
|
|
1480
|
+
function mapRowndUserToSuperTokens(rowndUser, tenantId) {
|
|
1464
1481
|
const loginMethods = [];
|
|
1465
1482
|
const rowndUserData = rowndUser.data || {};
|
|
1466
1483
|
const rowndUserVerifiedData = rowndUser.verified_data || {};
|
|
@@ -1468,41 +1485,41 @@ function mapRowndUserToSuperTokens(rowndUser) {
|
|
|
1468
1485
|
throw new Error("Rownd user has no user_id");
|
|
1469
1486
|
}
|
|
1470
1487
|
if (rowndUserData.google_id) {
|
|
1471
|
-
|
|
1472
|
-
throw new Error("Rownd Google user is missing email");
|
|
1473
|
-
}
|
|
1488
|
+
const googleEmail = rowndUserData.email ?? buildSuperTokensFakeEmail(rowndUserData.google_id, "google");
|
|
1474
1489
|
loginMethods.push({
|
|
1475
1490
|
recipeId: "thirdparty",
|
|
1476
1491
|
thirdPartyId: "google",
|
|
1477
1492
|
thirdPartyUserId: rowndUserData.google_id,
|
|
1478
|
-
email:
|
|
1479
|
-
isVerified: !!rowndUserVerifiedData.google_id
|
|
1493
|
+
email: googleEmail,
|
|
1494
|
+
isVerified: !!rowndUserData.email && !!rowndUserVerifiedData.google_id,
|
|
1495
|
+
...tenantId ? { tenantIds: [tenantId] } : {}
|
|
1480
1496
|
});
|
|
1481
1497
|
}
|
|
1482
1498
|
if (rowndUserData.apple_id) {
|
|
1483
|
-
|
|
1484
|
-
throw new Error("Rownd Apple user is missing email");
|
|
1485
|
-
}
|
|
1499
|
+
const appleEmail = rowndUserData.email ?? buildSuperTokensFakeEmail(rowndUserData.apple_id, "apple");
|
|
1486
1500
|
loginMethods.push({
|
|
1487
1501
|
recipeId: "thirdparty",
|
|
1488
1502
|
thirdPartyId: "apple",
|
|
1489
1503
|
thirdPartyUserId: rowndUserData.apple_id,
|
|
1490
|
-
email:
|
|
1491
|
-
isVerified: !!rowndUserVerifiedData.apple_id
|
|
1504
|
+
email: appleEmail,
|
|
1505
|
+
isVerified: !!rowndUserData.email && !!rowndUserVerifiedData.apple_id,
|
|
1506
|
+
...tenantId ? { tenantIds: [tenantId] } : {}
|
|
1492
1507
|
});
|
|
1493
1508
|
}
|
|
1494
1509
|
if (rowndUserData.phone_number) {
|
|
1495
1510
|
loginMethods.push({
|
|
1496
1511
|
recipeId: "passwordless",
|
|
1497
1512
|
phoneNumber: rowndUserData.phone_number,
|
|
1498
|
-
isVerified: !!rowndUserVerifiedData.phone_number
|
|
1513
|
+
isVerified: !!rowndUserVerifiedData.phone_number,
|
|
1514
|
+
...tenantId ? { tenantIds: [tenantId] } : {}
|
|
1499
1515
|
});
|
|
1500
1516
|
}
|
|
1501
1517
|
if (rowndUserData.email && !rowndUserData.google_id && !rowndUserData.apple_id) {
|
|
1502
1518
|
loginMethods.push({
|
|
1503
1519
|
recipeId: "passwordless",
|
|
1504
1520
|
email: rowndUserData.email,
|
|
1505
|
-
isVerified: !!rowndUserVerifiedData.email
|
|
1521
|
+
isVerified: !!rowndUserVerifiedData.email,
|
|
1522
|
+
...tenantId ? { tenantIds: [tenantId] } : {}
|
|
1506
1523
|
});
|
|
1507
1524
|
}
|
|
1508
1525
|
let authLevel = rowndUser.auth_level;
|
|
@@ -1514,7 +1531,8 @@ function mapRowndUserToSuperTokens(rowndUser) {
|
|
|
1514
1531
|
thirdPartyId,
|
|
1515
1532
|
thirdPartyUserId: rowndUserData.user_id,
|
|
1516
1533
|
email: `${rowndUserData.user_id}@anonymous.local`,
|
|
1517
|
-
isVerified: false
|
|
1534
|
+
isVerified: false,
|
|
1535
|
+
...tenantId ? { tenantIds: [tenantId] } : {}
|
|
1518
1536
|
});
|
|
1519
1537
|
}
|
|
1520
1538
|
const userMetadata = buildRowndUserMetadata(rowndUser);
|
|
@@ -1673,7 +1691,7 @@ async function buildStandardOAuthClaims(user, scopes) {
|
|
|
1673
1691
|
const rowndData = isJsonRecord(metadata.original_rownd_user?.data) ? metadata.original_rownd_user.data : {};
|
|
1674
1692
|
const verifiedData = isJsonRecord(metadata.original_rownd_user?.verified_data) ? metadata.original_rownd_user.verified_data : {};
|
|
1675
1693
|
if (scopes.includes("email")) {
|
|
1676
|
-
const email = firstString(rowndData.email)
|
|
1694
|
+
const email = firstRealEmail(firstString(rowndData.email), ...user.emails);
|
|
1677
1695
|
if (email) {
|
|
1678
1696
|
claims.email = email;
|
|
1679
1697
|
claims.email_verified = isOAuthClaimVerified(
|
|
@@ -1740,6 +1758,11 @@ function firstString(value) {
|
|
|
1740
1758
|
}
|
|
1741
1759
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1742
1760
|
}
|
|
1761
|
+
function firstRealEmail(...values) {
|
|
1762
|
+
return values.find((entry) => {
|
|
1763
|
+
return typeof entry === "string" && entry.length > 0 && !isSuperTokensFakeEmail(entry);
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1743
1766
|
function isOAuthClaimVerified(value, expectedValue, fallback) {
|
|
1744
1767
|
return value === true || value === expectedValue || fallback;
|
|
1745
1768
|
}
|
|
@@ -2023,6 +2046,7 @@ async function createMagicLinkWithConfirmationBypass(input) {
|
|
|
2023
2046
|
})}${getAppInfoString(stConfig.appInfo.websiteBasePath)}/verify?preAuthSessionId=${encodeURIComponent(
|
|
2024
2047
|
codeInfo.preAuthSessionId
|
|
2025
2048
|
)}&tenantId=${encodeURIComponent(tenantId)}#${encodeURIComponent(codeInfo.linkCode)}`;
|
|
2049
|
+
const oauthLoginChallenge = userContext.rowndOAuthLoginChallenge;
|
|
2026
2050
|
const rewrittenUrl = new URL(rewriteMagicLink({
|
|
2027
2051
|
magicLink,
|
|
2028
2052
|
clientDomain,
|
|
@@ -2033,7 +2057,8 @@ async function createMagicLinkWithConfirmationBypass(input) {
|
|
|
2033
2057
|
appVariantId,
|
|
2034
2058
|
displayContext: input.displayContext,
|
|
2035
2059
|
redirectToPath,
|
|
2036
|
-
clientDomainKey: input.clientDomain
|
|
2060
|
+
clientDomainKey: input.clientDomain,
|
|
2061
|
+
oauthLoginChallenge: typeof oauthLoginChallenge === "string" ? oauthLoginChallenge : void 0
|
|
2037
2062
|
})
|
|
2038
2063
|
}));
|
|
2039
2064
|
rewrittenUrl.searchParams.set(PASSWORDLESS_BYPASS_DEVICE_CONFIRMATION_PARAM, "true");
|
|
@@ -2053,7 +2078,7 @@ function getPendingVerifications(metadata) {
|
|
|
2053
2078
|
function isPendingVerification(value) {
|
|
2054
2079
|
return isRecord(value) && typeof value.id === "string" && typeof value.field === "string" && typeof value.value === "string" && typeof value.created_at === "string";
|
|
2055
2080
|
}
|
|
2056
|
-
async function getUserById(userId) {
|
|
2081
|
+
async function getUserById(userId, tenantId = PUBLIC_TENANT_ID) {
|
|
2057
2082
|
const metadata = await getUserMetadata(userId);
|
|
2058
2083
|
const stUser = await SuperTokens2.getUser(userId);
|
|
2059
2084
|
if (!stUser) {
|
|
@@ -2082,9 +2107,12 @@ async function getUserById(userId) {
|
|
|
2082
2107
|
const verifiedData = {
|
|
2083
2108
|
...originalRowndUser?.verified_data || {}
|
|
2084
2109
|
};
|
|
2085
|
-
|
|
2110
|
+
const tenantLoginMethods = stUser.loginMethods.filter(
|
|
2111
|
+
(method) => method.tenantIds.includes(tenantId)
|
|
2112
|
+
);
|
|
2113
|
+
for (const method of tenantLoginMethods) {
|
|
2086
2114
|
if (method.recipeId === "passwordless") {
|
|
2087
|
-
if (method.email) {
|
|
2115
|
+
if (method.email && !isSuperTokensFakeEmail(method.email)) {
|
|
2088
2116
|
verifiedData.email = method.email;
|
|
2089
2117
|
if (data.email === void 0) data.email = method.email;
|
|
2090
2118
|
}
|
|
@@ -2096,10 +2124,10 @@ async function getUserById(userId) {
|
|
|
2096
2124
|
} else if (method.recipeId === "thirdparty") {
|
|
2097
2125
|
const thirdPartyId = getThirdPartyId(method);
|
|
2098
2126
|
const thirdPartyUserId = getThirdPartyUserId(method);
|
|
2099
|
-
if (method.verified && method.email) {
|
|
2127
|
+
if (method.verified && method.email && !isSuperTokensFakeEmail(method.email)) {
|
|
2100
2128
|
verifiedData.email = method.email;
|
|
2101
2129
|
}
|
|
2102
|
-
if (method.email && data.email === void 0) {
|
|
2130
|
+
if (method.email && !isSuperTokensFakeEmail(method.email) && data.email === void 0) {
|
|
2103
2131
|
data.email = method.email;
|
|
2104
2132
|
}
|
|
2105
2133
|
if (thirdPartyId === "google" && thirdPartyUserId) {
|
|
@@ -2111,7 +2139,7 @@ async function getUserById(userId) {
|
|
|
2111
2139
|
verifiedData.apple_id = thirdPartyUserId;
|
|
2112
2140
|
}
|
|
2113
2141
|
} else if (method.recipeId === "emailpassword") {
|
|
2114
|
-
if (method.email && data.email === void 0) {
|
|
2142
|
+
if (method.email && !isSuperTokensFakeEmail(method.email) && data.email === void 0) {
|
|
2115
2143
|
data.email = method.email;
|
|
2116
2144
|
}
|
|
2117
2145
|
}
|
|
@@ -2122,12 +2150,16 @@ async function getUserById(userId) {
|
|
|
2122
2150
|
if (verifiedData.phone_number === true && typeof data.phone_number === "string") {
|
|
2123
2151
|
verifiedData.phone_number = data.phone_number;
|
|
2124
2152
|
}
|
|
2125
|
-
const
|
|
2153
|
+
const tenantUser = {
|
|
2154
|
+
...stUser,
|
|
2155
|
+
loginMethods: tenantLoginMethods
|
|
2156
|
+
};
|
|
2157
|
+
const anonymousId = getAnonymousId(stUser.id, tenantUser, metadata);
|
|
2126
2158
|
if (anonymousId && data.anonymous_id === void 0) {
|
|
2127
2159
|
data.anonymous_id = anonymousId;
|
|
2128
2160
|
}
|
|
2129
2161
|
const authLevel = getEffectiveAuthLevel(
|
|
2130
|
-
|
|
2162
|
+
tenantUser,
|
|
2131
2163
|
originalRowndUser?.auth_level,
|
|
2132
2164
|
verifiedData
|
|
2133
2165
|
);
|
|
@@ -2136,15 +2168,15 @@ async function getUserById(userId) {
|
|
|
2136
2168
|
data[key] = "";
|
|
2137
2169
|
}
|
|
2138
2170
|
}
|
|
2139
|
-
const sortedByJoined = [...
|
|
2171
|
+
const sortedByJoined = [...tenantLoginMethods].sort(
|
|
2140
2172
|
(a, b) => a.timeJoined - b.timeJoined
|
|
2141
2173
|
);
|
|
2142
|
-
const latestSessionInfo = await getLatestSessionInfo(stUser.id);
|
|
2174
|
+
const latestSessionInfo = await getLatestSessionInfo(stUser.id, tenantId);
|
|
2143
2175
|
const firstMethod = sortedByJoined[0];
|
|
2144
2176
|
const latestSessionRecipeUserId = latestSessionInfo?.recipeUserId.getAsString();
|
|
2145
2177
|
const lastMethod = latestSessionRecipeUserId ? stUser.loginMethods.find(
|
|
2146
2178
|
(method) => method.recipeUserId.getAsString() === latestSessionRecipeUserId
|
|
2147
|
-
) : [...
|
|
2179
|
+
) : [...tenantLoginMethods].sort((a, b) => b.timeJoined - a.timeJoined)[0];
|
|
2148
2180
|
const lastSignInAt = latestSessionInfo?.timeCreated ?? stUser.timeJoined;
|
|
2149
2181
|
const metadataMeta = Object.fromEntries(
|
|
2150
2182
|
Object.entries(metadata).filter(
|
|
@@ -2172,11 +2204,11 @@ async function getUserById(userId) {
|
|
|
2172
2204
|
attributes: originalRowndUser?.attributes || {}
|
|
2173
2205
|
};
|
|
2174
2206
|
}
|
|
2175
|
-
async function getLatestSessionInfo(userId) {
|
|
2207
|
+
async function getLatestSessionInfo(userId, tenantId) {
|
|
2176
2208
|
const sessionHandles = await Session.getAllSessionHandlesForUser(
|
|
2177
2209
|
userId,
|
|
2178
2210
|
true,
|
|
2179
|
-
|
|
2211
|
+
tenantId
|
|
2180
2212
|
);
|
|
2181
2213
|
const sessionInfos = await Promise.all(
|
|
2182
2214
|
sessionHandles.map(
|
|
@@ -2191,21 +2223,21 @@ async function getLatestSessionInfo(userId) {
|
|
|
2191
2223
|
}
|
|
2192
2224
|
return latestSessionInfo;
|
|
2193
2225
|
}
|
|
2194
|
-
async function updateUserData(userId, inputData) {
|
|
2226
|
+
async function updateUserData(userId, inputData, tenantId = PUBLIC_TENANT_ID) {
|
|
2195
2227
|
const metadata = await getUserMetadata(userId);
|
|
2196
2228
|
const updatedMetadata = {
|
|
2197
2229
|
...metadata,
|
|
2198
2230
|
...inputData
|
|
2199
2231
|
};
|
|
2200
2232
|
await UserMetadata3.updateUserMetadata(userId, updatedMetadata);
|
|
2201
|
-
return getUserById(userId);
|
|
2233
|
+
return getUserById(userId, tenantId);
|
|
2202
2234
|
}
|
|
2203
2235
|
async function startPendingEmailVerification(input) {
|
|
2204
2236
|
const metadata = await getUserMetadata(input.userId);
|
|
2205
|
-
const currentEmail = (await getUserById(input.userId)).data.email;
|
|
2237
|
+
const currentEmail = (await getUserById(input.userId, input.tenantId)).data.email;
|
|
2206
2238
|
const pendingVerifications = getPendingVerifications(metadata);
|
|
2207
2239
|
const pendingEmailVerifications = pendingVerifications.filter(
|
|
2208
|
-
(pendingVerification2) => pendingVerification2.field === "email"
|
|
2240
|
+
(pendingVerification2) => pendingVerification2.field === "email" && (pendingVerification2.tenantId ?? PUBLIC_TENANT_ID) === input.tenantId
|
|
2209
2241
|
);
|
|
2210
2242
|
if (currentEmail === input.email) {
|
|
2211
2243
|
for (const pendingVerification2 of pendingEmailVerifications) {
|
|
@@ -2220,11 +2252,11 @@ async function startPendingEmailVerification(input) {
|
|
|
2220
2252
|
await UserMetadata3.updateUserMetadata(input.userId, {
|
|
2221
2253
|
...metadata,
|
|
2222
2254
|
rownd_pending_verification: pendingVerifications.filter(
|
|
2223
|
-
(pendingVerification2) => pendingVerification2.field !== "email"
|
|
2255
|
+
(pendingVerification2) => pendingVerification2.field !== "email" || (pendingVerification2.tenantId ?? PUBLIC_TENANT_ID) !== input.tenantId
|
|
2224
2256
|
)
|
|
2225
2257
|
});
|
|
2226
2258
|
}
|
|
2227
|
-
return getUserById(input.userId);
|
|
2259
|
+
return getUserById(input.userId, input.tenantId);
|
|
2228
2260
|
}
|
|
2229
2261
|
for (const pendingVerification2 of pendingEmailVerifications) {
|
|
2230
2262
|
await EmailVerification.revokeEmailVerificationTokens(
|
|
@@ -2238,13 +2270,14 @@ async function startPendingEmailVerification(input) {
|
|
|
2238
2270
|
id: input.pendingVerificationId,
|
|
2239
2271
|
field: "email",
|
|
2240
2272
|
value: input.email,
|
|
2241
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2273
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2274
|
+
tenantId: input.tenantId
|
|
2242
2275
|
};
|
|
2243
2276
|
await UserMetadata3.updateUserMetadata(input.userId, {
|
|
2244
2277
|
...metadata,
|
|
2245
2278
|
rownd_pending_verification: [
|
|
2246
2279
|
...pendingVerifications.filter(
|
|
2247
|
-
(pendingVerification2) => pendingVerification2.field !== "email"
|
|
2280
|
+
(pendingVerification2) => pendingVerification2.field !== "email" || (pendingVerification2.tenantId ?? PUBLIC_TENANT_ID) !== input.tenantId
|
|
2248
2281
|
),
|
|
2249
2282
|
pendingVerification
|
|
2250
2283
|
]
|
|
@@ -2263,12 +2296,14 @@ async function startPendingEmailVerification(input) {
|
|
|
2263
2296
|
await completePendingEmailVerification({
|
|
2264
2297
|
recipeUserId: input.recipeUserId,
|
|
2265
2298
|
email: input.email,
|
|
2299
|
+
tenantId: input.tenantId,
|
|
2266
2300
|
userContext: input.userContext
|
|
2267
2301
|
});
|
|
2268
2302
|
}
|
|
2269
|
-
return getUserById(input.userId);
|
|
2303
|
+
return getUserById(input.userId, input.tenantId);
|
|
2270
2304
|
}
|
|
2271
2305
|
async function completePendingEmailVerification(input) {
|
|
2306
|
+
const tenantId = input.tenantId ?? PUBLIC_TENANT_ID;
|
|
2272
2307
|
const user = await SuperTokens2.getUser(
|
|
2273
2308
|
input.recipeUserId.getAsString(),
|
|
2274
2309
|
input.userContext
|
|
@@ -2279,7 +2314,8 @@ async function completePendingEmailVerification(input) {
|
|
|
2279
2314
|
const pendingVerification = pendingVerifications.find(
|
|
2280
2315
|
(pendingVerification2) => isMatchingPendingEmailVerification(
|
|
2281
2316
|
pendingVerification2,
|
|
2282
|
-
input.email
|
|
2317
|
+
input.email,
|
|
2318
|
+
tenantId
|
|
2283
2319
|
)
|
|
2284
2320
|
);
|
|
2285
2321
|
if (!pendingVerification) {
|
|
@@ -2287,7 +2323,12 @@ async function completePendingEmailVerification(input) {
|
|
|
2287
2323
|
}
|
|
2288
2324
|
let metadataUserId = userId;
|
|
2289
2325
|
let verifiedRecipeUserId = input.recipeUserId;
|
|
2290
|
-
const
|
|
2326
|
+
const tenantLoginMethods = user?.loginMethods.filter(
|
|
2327
|
+
(method) => method.tenantIds.includes(tenantId)
|
|
2328
|
+
) ?? [];
|
|
2329
|
+
const passwordlessEmailMethod = getPasswordlessEmailLoginMethod(
|
|
2330
|
+
tenantLoginMethods
|
|
2331
|
+
);
|
|
2291
2332
|
if (passwordlessEmailMethod) {
|
|
2292
2333
|
const updateResult = await Passwordless.updateUser({
|
|
2293
2334
|
recipeUserId: passwordlessEmailMethod.recipeUserId,
|
|
@@ -2300,9 +2341,9 @@ async function completePendingEmailVerification(input) {
|
|
|
2300
2341
|
);
|
|
2301
2342
|
}
|
|
2302
2343
|
verifiedRecipeUserId = passwordlessEmailMethod.recipeUserId;
|
|
2303
|
-
} else if (hasOnlyGuestLoginMethods(user)) {
|
|
2344
|
+
} else if (hasOnlyGuestLoginMethods(user ? { ...user, loginMethods: tenantLoginMethods } : user)) {
|
|
2304
2345
|
const isPasswordlessSignUpAllowed = await AccountLinking.isSignUpAllowed(
|
|
2305
|
-
|
|
2346
|
+
tenantId,
|
|
2306
2347
|
{
|
|
2307
2348
|
recipeId: "passwordless",
|
|
2308
2349
|
email: input.email
|
|
@@ -2316,7 +2357,7 @@ async function completePendingEmailVerification(input) {
|
|
|
2316
2357
|
}
|
|
2317
2358
|
const passwordlessUser = await Passwordless.signInUp({
|
|
2318
2359
|
email: input.email,
|
|
2319
|
-
tenantId
|
|
2360
|
+
tenantId,
|
|
2320
2361
|
userContext: input.userContext
|
|
2321
2362
|
});
|
|
2322
2363
|
verifiedRecipeUserId = passwordlessUser.recipeUserId;
|
|
@@ -2360,7 +2401,8 @@ async function completePendingEmailVerification(input) {
|
|
|
2360
2401
|
rownd_pending_verification: targetPendingVerifications.filter(
|
|
2361
2402
|
(verification) => !isMatchingPendingEmailVerification(
|
|
2362
2403
|
verification,
|
|
2363
|
-
input.email
|
|
2404
|
+
input.email,
|
|
2405
|
+
tenantId
|
|
2364
2406
|
)
|
|
2365
2407
|
)
|
|
2366
2408
|
};
|
|
@@ -2370,8 +2412,8 @@ async function completePendingEmailVerification(input) {
|
|
|
2370
2412
|
recipeUserId: verifiedRecipeUserId
|
|
2371
2413
|
};
|
|
2372
2414
|
}
|
|
2373
|
-
function isMatchingPendingEmailVerification(verification, email) {
|
|
2374
|
-
return verification.field === "email" && verification.value === email;
|
|
2415
|
+
function isMatchingPendingEmailVerification(verification, email, tenantId) {
|
|
2416
|
+
return verification.field === "email" && verification.value === email && (verification.tenantId ?? PUBLIC_TENANT_ID) === tenantId;
|
|
2375
2417
|
}
|
|
2376
2418
|
async function updateUserMetadata(userId, inputMeta) {
|
|
2377
2419
|
const metadata = await getUserMetadata(userId);
|
|
@@ -2389,8 +2431,8 @@ async function updateUserMetadata(userId, inputMeta) {
|
|
|
2389
2431
|
)
|
|
2390
2432
|
};
|
|
2391
2433
|
}
|
|
2392
|
-
function getPasswordlessEmailLoginMethod(
|
|
2393
|
-
return
|
|
2434
|
+
function getPasswordlessEmailLoginMethod(loginMethods) {
|
|
2435
|
+
return loginMethods.find((method) => {
|
|
2394
2436
|
return method.recipeId === "passwordless" && !!method.email;
|
|
2395
2437
|
});
|
|
2396
2438
|
}
|
|
@@ -2400,6 +2442,7 @@ import { randomUUID } from "crypto";
|
|
|
2400
2442
|
import SuperTokens3 from "supertokens-node";
|
|
2401
2443
|
import Session2 from "supertokens-node/recipe/session";
|
|
2402
2444
|
import ThirdParty from "supertokens-node/recipe/thirdparty";
|
|
2445
|
+
import MultiTenancy from "supertokens-node/recipe/multitenancy";
|
|
2403
2446
|
function isBodyString(body, key) {
|
|
2404
2447
|
return isRecord(body) && typeof body[key] === "string" && body[key].length > 0;
|
|
2405
2448
|
}
|
|
@@ -2464,7 +2507,9 @@ function handleGuestLogin(deps) {
|
|
|
2464
2507
|
return async (req, res, _session, userContext) => {
|
|
2465
2508
|
const startedAt = Date.now();
|
|
2466
2509
|
const guestId = `guest_${randomUUID()}`;
|
|
2510
|
+
let tenantId;
|
|
2467
2511
|
try {
|
|
2512
|
+
tenantId = resolveTenantId(req);
|
|
2468
2513
|
const body = parseGuestBody(await getJsonBody(req));
|
|
2469
2514
|
const appVariantId = getRequestedAppVariantIdFromRequest(req);
|
|
2470
2515
|
assertRowndAppVariantIsConfigured(appVariantId);
|
|
@@ -2472,7 +2517,7 @@ function handleGuestLogin(deps) {
|
|
|
2472
2517
|
const thirdPartyUserId = thirdPartyId === INSTANT_AUTH_METHOD_ID ? `anon_${randomUUID()}` : guestId;
|
|
2473
2518
|
const authLevel = thirdPartyId === INSTANT_AUTH_METHOD_ID ? INSTANT_AUTH_METHOD_ID : GUEST_AUTH_METHOD_ID;
|
|
2474
2519
|
const response = await ThirdParty.manuallyCreateOrUpdateUser(
|
|
2475
|
-
|
|
2520
|
+
tenantId,
|
|
2476
2521
|
thirdPartyId,
|
|
2477
2522
|
thirdPartyUserId,
|
|
2478
2523
|
`${thirdPartyUserId}@anonymous.local`,
|
|
@@ -2488,7 +2533,7 @@ function handleGuestLogin(deps) {
|
|
|
2488
2533
|
await Session2.createNewSession(
|
|
2489
2534
|
req,
|
|
2490
2535
|
res,
|
|
2491
|
-
|
|
2536
|
+
tenantId,
|
|
2492
2537
|
response.recipeUserId,
|
|
2493
2538
|
{
|
|
2494
2539
|
...buildRowndAudience({}, appVariantId),
|
|
@@ -2503,7 +2548,7 @@ function handleGuestLogin(deps) {
|
|
|
2503
2548
|
deps.telemetryClient.recordSuccess({
|
|
2504
2549
|
outcome: "success",
|
|
2505
2550
|
durationMs: Date.now() - startedAt,
|
|
2506
|
-
tenantId
|
|
2551
|
+
tenantId,
|
|
2507
2552
|
superTokensUserId: response.user.id
|
|
2508
2553
|
});
|
|
2509
2554
|
return {
|
|
@@ -2515,7 +2560,7 @@ function handleGuestLogin(deps) {
|
|
|
2515
2560
|
deps.telemetryClient.recordError({
|
|
2516
2561
|
error,
|
|
2517
2562
|
startedAt,
|
|
2518
|
-
tenantId
|
|
2563
|
+
tenantId
|
|
2519
2564
|
});
|
|
2520
2565
|
return {
|
|
2521
2566
|
status: "ERROR",
|
|
@@ -2527,7 +2572,7 @@ function handleGuestLogin(deps) {
|
|
|
2527
2572
|
function handleMigrate(deps) {
|
|
2528
2573
|
return async (req, res, _session, userContext) => {
|
|
2529
2574
|
const startedAt = Date.now();
|
|
2530
|
-
let tenantId
|
|
2575
|
+
let tenantId;
|
|
2531
2576
|
let rowndUserId;
|
|
2532
2577
|
let superTokensUserId;
|
|
2533
2578
|
let user;
|
|
@@ -2536,6 +2581,7 @@ function handleMigrate(deps) {
|
|
|
2536
2581
|
if (!deps.stConfig.supertokens) {
|
|
2537
2582
|
throw new Error("Supertokens config not found");
|
|
2538
2583
|
}
|
|
2584
|
+
tenantId = resolveTenantId(req);
|
|
2539
2585
|
const parsed = await parseRequest(req);
|
|
2540
2586
|
const appVariantId = getRequestedAppVariantIdFromRequest(req);
|
|
2541
2587
|
assertRowndAppVariantIsConfigured(appVariantId);
|
|
@@ -2543,13 +2589,16 @@ function handleMigrate(deps) {
|
|
|
2543
2589
|
const rowndUser = await fetchOptionalRowndUserInfo(rowndUserId);
|
|
2544
2590
|
if (!rowndUser) {
|
|
2545
2591
|
logDebugMessage(
|
|
2546
|
-
`Skipping migration because user does not exist in Rownd. tenantId: ${
|
|
2592
|
+
`Skipping migration because user does not exist in Rownd. tenantId: ${tenantId}, rowndUserId: ${rowndUserId}`
|
|
2547
2593
|
);
|
|
2548
2594
|
return { status: "OK" };
|
|
2549
2595
|
}
|
|
2550
2596
|
user = await SuperTokens3.getUser(rowndUserId, userContext);
|
|
2551
2597
|
if (!user) {
|
|
2552
|
-
const stUserImport = mapRowndUserToSuperTokens(
|
|
2598
|
+
const stUserImport = mapRowndUserToSuperTokens(
|
|
2599
|
+
rowndUser,
|
|
2600
|
+
tenantId === PUBLIC_TENANT_ID ? void 0 : tenantId
|
|
2601
|
+
);
|
|
2553
2602
|
try {
|
|
2554
2603
|
await importUser(stUserImport, deps.stConfig.supertokens);
|
|
2555
2604
|
clearSuperTokensCoreCallCache(userContext);
|
|
@@ -2567,29 +2616,43 @@ function handleMigrate(deps) {
|
|
|
2567
2616
|
superTokensUserId = user.id;
|
|
2568
2617
|
recipeUserId = user.loginMethods[0]?.recipeUserId;
|
|
2569
2618
|
logDebugMessage(
|
|
2570
|
-
`User already migrated (race condition). tenantId: ${
|
|
2619
|
+
`User already migrated (race condition). tenantId: ${tenantId}, rowndUserId: ${rowndUserId}`
|
|
2571
2620
|
);
|
|
2572
2621
|
}
|
|
2573
2622
|
logDebugMessage(
|
|
2574
|
-
`User migrated successfully. tenantId: ${
|
|
2623
|
+
`User migrated successfully. tenantId: ${tenantId}, rowndUserId: ${rowndUserId}`
|
|
2575
2624
|
);
|
|
2576
2625
|
} else {
|
|
2577
2626
|
superTokensUserId = user.id;
|
|
2578
2627
|
recipeUserId = user.loginMethods[0]?.recipeUserId;
|
|
2579
2628
|
logDebugMessage(
|
|
2580
|
-
`User already migrated. tenantId: ${
|
|
2629
|
+
`User already migrated. tenantId: ${tenantId}, rowndUserId: ${rowndUserId}`
|
|
2581
2630
|
);
|
|
2582
2631
|
}
|
|
2583
2632
|
if (superTokensUserId) {
|
|
2584
2633
|
await recordRowndAppVariantForUser(superTokensUserId, appVariantId);
|
|
2585
2634
|
}
|
|
2635
|
+
const tenantLoginMethod = user?.loginMethods.find(
|
|
2636
|
+
(method) => method.tenantIds.includes(tenantId)
|
|
2637
|
+
);
|
|
2638
|
+
recipeUserId = tenantLoginMethod?.recipeUserId ?? recipeUserId;
|
|
2586
2639
|
if (!recipeUserId) {
|
|
2587
2640
|
throw new Error("User not found or has no login methods");
|
|
2588
2641
|
}
|
|
2642
|
+
if (!tenantLoginMethod) {
|
|
2643
|
+
const associationResult = await MultiTenancy.associateUserToTenant(
|
|
2644
|
+
tenantId,
|
|
2645
|
+
recipeUserId,
|
|
2646
|
+
userContext
|
|
2647
|
+
);
|
|
2648
|
+
if (associationResult.status !== "OK") {
|
|
2649
|
+
throw new Error(`Failed to associate migrated user with tenant: ${associationResult.status}`);
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2589
2652
|
await Session2.createNewSession(
|
|
2590
2653
|
req,
|
|
2591
2654
|
res,
|
|
2592
|
-
|
|
2655
|
+
tenantId,
|
|
2593
2656
|
recipeUserId,
|
|
2594
2657
|
{
|
|
2595
2658
|
...buildRowndAudience({}, appVariantId)
|
|
@@ -2598,7 +2661,7 @@ function handleMigrate(deps) {
|
|
|
2598
2661
|
userContext
|
|
2599
2662
|
);
|
|
2600
2663
|
logDebugMessage(
|
|
2601
|
-
`Session migrated successfully. tenantId: ${
|
|
2664
|
+
`Session migrated successfully. tenantId: ${tenantId}, userId: ${superTokensUserId}`
|
|
2602
2665
|
);
|
|
2603
2666
|
deps.telemetryClient.recordSuccess({
|
|
2604
2667
|
outcome: "success",
|
|
@@ -2632,7 +2695,7 @@ function clearSuperTokensCoreCallCache(userContext) {
|
|
|
2632
2695
|
}
|
|
2633
2696
|
function handleGetUser() {
|
|
2634
2697
|
return async (_req, _res, session) => {
|
|
2635
|
-
const user = await getUserById(session.getUserId());
|
|
2698
|
+
const user = await getUserById(session.getUserId(), session.getTenantId());
|
|
2636
2699
|
return {
|
|
2637
2700
|
status: "OK",
|
|
2638
2701
|
...user
|
|
@@ -2658,7 +2721,11 @@ function handleUpdateUser() {
|
|
|
2658
2721
|
return permissionError;
|
|
2659
2722
|
}
|
|
2660
2723
|
if (Object.keys(dataWithoutEmail).length > 0) {
|
|
2661
|
-
await updateUserData(
|
|
2724
|
+
await updateUserData(
|
|
2725
|
+
session.getUserId(),
|
|
2726
|
+
dataWithoutEmail,
|
|
2727
|
+
session.getTenantId()
|
|
2728
|
+
);
|
|
2662
2729
|
}
|
|
2663
2730
|
if (hasEmailUpdate) {
|
|
2664
2731
|
const pendingVerificationResult = await startPendingEmailVerification({
|
|
@@ -2674,7 +2741,7 @@ function handleUpdateUser() {
|
|
|
2674
2741
|
...pendingVerificationResult
|
|
2675
2742
|
};
|
|
2676
2743
|
}
|
|
2677
|
-
const user = await getUserById(session.getUserId());
|
|
2744
|
+
const user = await getUserById(session.getUserId(), session.getTenantId());
|
|
2678
2745
|
return {
|
|
2679
2746
|
status: "OK",
|
|
2680
2747
|
...user
|
|
@@ -2741,7 +2808,7 @@ function handleGetUserField() {
|
|
|
2741
2808
|
if (!field) {
|
|
2742
2809
|
return missingFieldResponse();
|
|
2743
2810
|
}
|
|
2744
|
-
const user = await getUserById(session.getUserId());
|
|
2811
|
+
const user = await getUserById(session.getUserId(), session.getTenantId());
|
|
2745
2812
|
return {
|
|
2746
2813
|
status: "OK",
|
|
2747
2814
|
value: user.data[field]
|
|
@@ -2775,9 +2842,11 @@ function handleUpdateUserField() {
|
|
|
2775
2842
|
if (permissionError) {
|
|
2776
2843
|
return permissionError;
|
|
2777
2844
|
}
|
|
2778
|
-
const updateUserDataResult = await updateUserData(
|
|
2779
|
-
|
|
2780
|
-
|
|
2845
|
+
const updateUserDataResult = await updateUserData(
|
|
2846
|
+
session.getUserId(),
|
|
2847
|
+
{ [field]: payload.value },
|
|
2848
|
+
session.getTenantId()
|
|
2849
|
+
);
|
|
2781
2850
|
return {
|
|
2782
2851
|
status: "OK",
|
|
2783
2852
|
...updateUserDataResult
|
|
@@ -2816,6 +2885,7 @@ var init = createPluginInitFunction(
|
|
|
2816
2885
|
const displayContext = input?.userContext?.rowndDisplayContext;
|
|
2817
2886
|
const redirectToPath = input?.userContext?.rowndRedirectToPath;
|
|
2818
2887
|
const clientDomain = input?.userContext?.rowndClientDomain;
|
|
2888
|
+
const oauthLoginChallenge = input?.userContext?.rowndOAuthLoginChallenge;
|
|
2819
2889
|
const clientDomainKey = clientDomain ?? (displayContext === "mobile_app" ? "mobile" : "browser");
|
|
2820
2890
|
const clientBaseUrl = pluginConfig2.clientDomains?.[clientDomainKey];
|
|
2821
2891
|
const bootstrapParams = {
|
|
@@ -2823,7 +2893,8 @@ var init = createPluginInitFunction(
|
|
|
2823
2893
|
...hubBootstrapParams ?? {},
|
|
2824
2894
|
...typeof appVariantId === "string" ? { appVariantId } : {},
|
|
2825
2895
|
...typeof displayContext === "string" ? { displayContext } : {},
|
|
2826
|
-
...typeof redirectToPath === "string" ? { redirectToPath } : {}
|
|
2896
|
+
...typeof redirectToPath === "string" ? { redirectToPath } : {},
|
|
2897
|
+
...typeof oauthLoginChallenge === "string" ? { oauthLoginChallenge } : {}
|
|
2827
2898
|
};
|
|
2828
2899
|
const rewrittenLink = input[linkKey] ? clientBaseUrl ? rewriteLinkToBaseUrl(
|
|
2829
2900
|
input[linkKey],
|
|
@@ -3109,6 +3180,12 @@ var init = createPluginInitFunction(
|
|
|
3109
3180
|
if (appVariantId) {
|
|
3110
3181
|
input.userContext.rowndAppVariantId = appVariantId;
|
|
3111
3182
|
}
|
|
3183
|
+
const oauthLoginChallenge = getRequestedOAuthLoginChallengeFromRequest(
|
|
3184
|
+
input.options.req
|
|
3185
|
+
);
|
|
3186
|
+
if (oauthLoginChallenge) {
|
|
3187
|
+
input.userContext.rowndOAuthLoginChallenge = oauthLoginChallenge;
|
|
3188
|
+
}
|
|
3112
3189
|
assertRowndAppVariantIsConfigured(appVariantId);
|
|
3113
3190
|
return originalImplementation.createCodePOST(input);
|
|
3114
3191
|
},
|
|
@@ -3252,6 +3329,7 @@ var init = createPluginInitFunction(
|
|
|
3252
3329
|
const verificationResult = await completePendingEmailVerification({
|
|
3253
3330
|
recipeUserId: response.user.recipeUserId,
|
|
3254
3331
|
email: response.user.email,
|
|
3332
|
+
tenantId: input.tenantId,
|
|
3255
3333
|
userContext: input.userContext
|
|
3256
3334
|
});
|
|
3257
3335
|
const session = input.session;
|
package/dist/initConfig.js
CHANGED
|
@@ -49,10 +49,22 @@ 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>
|
|
55
65
|
apiKey: <API_KEY>
|
|
66
|
+
# Tenant that receives every imported login method.
|
|
67
|
+
tenantId: public
|
|
56
68
|
# Number of users to send to SuperTokens per bulk import request.
|
|
57
69
|
batchSize: 500
|
|
58
70
|
`;
|