@supertokens-plugins/rownd-nodejs 0.3.0-beta.0 → 0.3.0-beta.2

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
@@ -271,8 +271,11 @@ __export(index_exports, {
271
271
  DEFAULT_ROWND_SCHEMA: () => DEFAULT_ROWND_SCHEMA,
272
272
  GUEST_AUTH_METHOD_ID: () => GUEST_AUTH_METHOD_ID,
273
273
  HANDLE_BASE_PATH: () => HANDLE_BASE_PATH,
274
+ HUB_LOGIN_PAGE_PATH: () => HUB_LOGIN_PAGE_PATH,
275
+ HUB_VERIFY_EMAIL_PAGE_PATH: () => HUB_VERIFY_EMAIL_PAGE_PATH,
274
276
  PLUGIN_ID: () => PLUGIN_ID,
275
277
  PLUGIN_SDK_VERSION: () => PLUGIN_SDK_VERSION,
278
+ PLUGIN_VERSION: () => PLUGIN_VERSION,
276
279
  PUBLIC_TENANT_ID: () => PUBLIC_TENANT_ID,
277
280
  ROWND_JWT_CLAIMS: () => ROWND_JWT_CLAIMS,
278
281
  ROWND_PLUGIN_ERROR_MESSAGES: () => ROWND_PLUGIN_ERROR_MESSAGES,
@@ -678,10 +681,11 @@ var buildLogger = (pluginId, version, { fileLocation = true } = {}) => {
678
681
 
679
682
  // src/plugin.ts
680
683
  var import_node = require("@rownd/node");
681
- var import_supertokens_node2 = __toESM(require("supertokens-node"));
684
+ var import_supertokens_node4 = __toESM(require("supertokens-node"));
682
685
 
683
686
  // src/constants.ts
684
687
  var PLUGIN_ID = "supertokens-plugin-rownd";
688
+ var PLUGIN_VERSION = "0.3.0";
685
689
  var PLUGIN_SDK_VERSION = ["23.0.0", "23.0.1", ">=23.0.1"];
686
690
  var HANDLE_BASE_PATH = "/plugin/rownd";
687
691
  var PUBLIC_TENANT_ID = "public";
@@ -718,11 +722,13 @@ var DEFAULT_ROWND_SCHEMA = {
718
722
  user_visible: true
719
723
  }
720
724
  };
725
+ var HUB_LOGIN_PAGE_PATH = "/account/login";
726
+ var HUB_VERIFY_EMAIL_PAGE_PATH = "/account/verify-email";
721
727
 
722
728
  // src/logger.ts
723
729
  var { logDebugMessage, enableDebugLogs } = buildLogger(
724
730
  PLUGIN_ID,
725
- PLUGIN_SDK_VERSION
731
+ PLUGIN_VERSION
726
732
  );
727
733
 
728
734
  // src/telemetry/axiomTelemetryClient.ts
@@ -839,17 +845,6 @@ function safeRecordEvent(client, event, outcome) {
839
845
  }
840
846
  }
841
847
 
842
- // src/pluginImplementation.ts
843
- var import_crypto = require("crypto");
844
- var import_supertokens_node = __toESM(require("supertokens-node"));
845
- var import_accountlinking = __toESM(require("supertokens-node/recipe/accountlinking"));
846
- var import_emailverification = __toESM(require("supertokens-node/recipe/emailverification"));
847
- var import_passwordless = __toESM(require("supertokens-node/recipe/passwordless"));
848
- var import_session = __toESM(require("supertokens-node/recipe/session"));
849
- var import_claims = require("supertokens-node/recipe/session/claims");
850
- var import_thirdparty = __toESM(require("supertokens-node/recipe/thirdparty"));
851
- var import_usermetadata2 = __toESM(require("supertokens-node/recipe/usermetadata"));
852
-
853
848
  // src/errors.ts
854
849
  var ROWND_PLUGIN_ERROR_MESSAGES = {
855
850
  MISSING_AUTHORIZATION_HEADER: "Missing authorization header",
@@ -862,7 +857,7 @@ var RowndPluginError = class extends Error {
862
857
  }
863
858
  };
864
859
 
865
- // src/pluginImplementation.ts
860
+ // src/utils.ts
866
861
  function rewriteLinkPath(inputUrl, targetPath, searchParams) {
867
862
  try {
868
863
  const url = new URL(inputUrl);
@@ -881,19 +876,25 @@ function rewriteLinkPath(inputUrl, targetPath, searchParams) {
881
876
  return queryString ? `${path}?${queryString}` : path;
882
877
  }
883
878
  }
884
- var rowndClient;
885
- var pluginConfig;
886
- function setRowndClient(client) {
887
- rowndClient = client;
888
- }
889
- function getRowndClient() {
890
- if (!rowndClient) {
891
- throw new Error("Rownd client not initialized");
879
+ function rewriteLinkToBaseUrl(inputUrl, targetPath, baseUrl, searchParams) {
880
+ const normalizedBaseUrl = baseUrl.endsWith("://") ? baseUrl : baseUrl.replace(/\/+$/, "") + "/";
881
+ const targetUrl = new URL(
882
+ `${normalizedBaseUrl}${targetPath.replace(/^\//, "")}`
883
+ );
884
+ try {
885
+ const sourceUrl = new URL(inputUrl);
886
+ targetUrl.search = sourceUrl.search;
887
+ targetUrl.hash = sourceUrl.hash;
888
+ } catch {
889
+ const [, query = ""] = inputUrl.split("?");
890
+ targetUrl.search = query ? `?${query.split("#")[0]}` : "";
891
+ const hash = inputUrl.split("#")[1];
892
+ targetUrl.hash = hash ? `#${hash}` : "";
892
893
  }
893
- return rowndClient;
894
- }
895
- function setPluginConfig(config2) {
896
- pluginConfig = config2;
894
+ for (const [key, value] of Object.entries(searchParams ?? {})) {
895
+ targetUrl.searchParams.set(key, value);
896
+ }
897
+ return targetUrl.toString();
897
898
  }
898
899
  async function parseRequest(req) {
899
900
  const authHeader = req.getHeaderValue("authorization");
@@ -908,386 +909,37 @@ async function parseRequest(req) {
908
909
  token
909
910
  };
910
911
  }
911
- function handleGetAppConfig(deps) {
912
- return async (req) => {
913
- const appVariantId = getRequestedAppVariantIdFromRequest(req);
914
- const appConfig = buildAppConfig(
915
- deps.pluginConfig,
916
- deps.stConfig,
917
- appVariantId
918
- );
919
- if (!appConfig) {
920
- return {
921
- status: "ERROR",
922
- message: `Unknown Rownd app variant: ${appVariantId}`
923
- };
924
- }
925
- return {
926
- status: "OK",
927
- ...appConfig
928
- };
929
- };
912
+ async function getJsonBody(req) {
913
+ return req.getJSONBody();
930
914
  }
931
- function handleGuestLogin(deps) {
932
- return async (req, res, _session, userContext) => {
933
- const startedAt = Date.now();
934
- const guestId = `guest_${(0, import_crypto.randomUUID)()}`;
935
- try {
936
- const body = parseGuestBody(await getJsonBody(req));
937
- const appVariantId = getRequestedAppVariantIdFromRequest(req);
938
- assertRowndAppVariantIsConfigured(appVariantId);
939
- const thirdPartyId = body.authLevel === ANONYMOUS_AUTH_METHOD_ID ? ANONYMOUS_AUTH_METHOD_ID : GUEST_AUTH_METHOD_ID;
940
- const thirdPartyUserId = thirdPartyId === ANONYMOUS_AUTH_METHOD_ID ? `anon_${(0, import_crypto.randomUUID)()}` : guestId;
941
- const response = await import_thirdparty.default.manuallyCreateOrUpdateUser(
942
- PUBLIC_TENANT_ID,
943
- thirdPartyId,
944
- thirdPartyUserId,
945
- `${thirdPartyUserId}@anonymous.local`,
946
- false,
947
- void 0,
948
- userContext
949
- );
950
- if (response.status !== "OK") {
951
- throw new Error(
952
- `Guest user creation failed with status: ${response.status}`
953
- );
954
- }
955
- await import_session.default.createNewSession(
956
- req,
957
- res,
958
- PUBLIC_TENANT_ID,
959
- response.recipeUserId,
960
- {
961
- ...buildRowndAudience({}, appVariantId),
962
- auth_level: GUEST_AUTH_METHOD_ID,
963
- is_anonymous: true,
964
- app_user_id: response.user.id
965
- },
966
- {},
967
- userContext
968
- );
969
- logDebugMessage(`Guest session created for user: ${response.user.id}`);
970
- deps.telemetryClient.recordSuccess({
971
- outcome: "success",
972
- durationMs: Date.now() - startedAt,
973
- tenantId: PUBLIC_TENANT_ID,
974
- superTokensUserId: response.user.id
975
- });
976
- return {
977
- status: "OK",
978
- createdNewRecipeUser: response.createdNewRecipeUser
979
- };
980
- } catch (error) {
981
- logDebugMessage(`Guest login failed. Error: ${getErrorMessage(error)}`);
982
- deps.telemetryClient.recordError({
983
- error,
984
- startedAt,
985
- tenantId: PUBLIC_TENANT_ID
986
- });
987
- return {
988
- status: "ERROR",
989
- message: "Guest login failed"
990
- };
991
- }
915
+ function parseGuestBody(value) {
916
+ if (!isJsonRecord(value)) {
917
+ return {};
918
+ }
919
+ return {
920
+ authLevel: typeof value.auth_level === "string" ? value.auth_level : void 0
992
921
  };
993
922
  }
994
- function handleMigrate(deps) {
995
- return async (req, res, _session, userContext) => {
996
- const startedAt = Date.now();
997
- let tenantId = PUBLIC_TENANT_ID;
998
- let rowndUserId;
999
- let superTokensUserId;
1000
- let user;
1001
- let recipeUserId;
1002
- try {
1003
- if (!deps.stConfig.supertokens) {
1004
- throw new Error("Supertokens config not found");
1005
- }
1006
- const parsed = await parseRequest(req);
1007
- const appVariantId = getRequestedAppVariantIdFromRequest(req);
1008
- assertRowndAppVariantIsConfigured(appVariantId);
1009
- rowndUserId = await validateRowndToken(parsed.token);
1010
- user = await import_supertokens_node.default.getUser(rowndUserId, userContext);
1011
- if (!user) {
1012
- const rowndUser = await fetchRowndUserInfo(rowndUserId);
1013
- const stUserImport = mapRowndUserToSuperTokens(rowndUser);
1014
- try {
1015
- const importedUser = await importUser(
1016
- stUserImport,
1017
- deps.stConfig.supertokens
1018
- );
1019
- superTokensUserId = importedUser.id;
1020
- if (importedUser.loginMethods[0]?.recipeUserId) {
1021
- recipeUserId = import_supertokens_node.default.convertToRecipeUserId(
1022
- importedUser.loginMethods[0].recipeUserId
1023
- );
1024
- }
1025
- } catch (err) {
1026
- user = await import_supertokens_node.default.getUser(rowndUserId, userContext);
1027
- if (!user) {
1028
- throw err;
1029
- }
1030
- superTokensUserId = user.id;
1031
- recipeUserId = user.loginMethods[0]?.recipeUserId;
1032
- logDebugMessage(
1033
- `User already migrated (race condition). tenantId: ${PUBLIC_TENANT_ID}, rowndUserId: ${rowndUserId}`
1034
- );
1035
- }
1036
- logDebugMessage(
1037
- `User migrated successfully. tenantId: ${PUBLIC_TENANT_ID}, rowndUserId: ${rowndUserId}`
1038
- );
1039
- } else {
1040
- superTokensUserId = user.id;
1041
- recipeUserId = user.loginMethods[0]?.recipeUserId;
1042
- logDebugMessage(
1043
- `User already migrated. tenantId: ${PUBLIC_TENANT_ID}, rowndUserId: ${rowndUserId}`
1044
- );
1045
- }
1046
- if (superTokensUserId) {
1047
- await recordRowndAppVariantForUser(superTokensUserId, appVariantId);
1048
- }
1049
- if (!recipeUserId) {
1050
- throw new Error("User not found or has no login methods");
1051
- }
1052
- await import_session.default.createNewSession(
1053
- req,
1054
- res,
1055
- PUBLIC_TENANT_ID,
1056
- recipeUserId,
1057
- {
1058
- ...buildRowndAudience({}, appVariantId)
1059
- },
1060
- {},
1061
- userContext
1062
- );
1063
- logDebugMessage(
1064
- `Session migrated successfully. tenantId: ${PUBLIC_TENANT_ID}, userId: ${superTokensUserId}`
1065
- );
1066
- deps.telemetryClient.recordSuccess({
1067
- outcome: "success",
1068
- durationMs: Date.now() - startedAt,
1069
- tenantId,
1070
- rowndUserId,
1071
- superTokensUserId
1072
- });
1073
- return { status: "OK" };
1074
- } catch (error) {
1075
- logDebugMessage(`Migration failed. Error: ${getErrorMessage(error)}`);
1076
- deps.telemetryClient.recordError({
1077
- error,
1078
- startedAt,
1079
- tenantId,
1080
- rowndUserId,
1081
- superTokensUserId
1082
- });
1083
- return {
1084
- status: "ERROR",
1085
- message: error instanceof RowndPluginError ? error.message : "Migration failed"
1086
- };
1087
- }
923
+ function parseUpdateUserBody(value) {
924
+ if (!isJsonRecord(value) || !isJsonRecord(value.data)) {
925
+ return {};
926
+ }
927
+ return {
928
+ data: value.data,
929
+ ...isJsonRecord(value.context) ? { context: value.context } : {}
1088
930
  };
1089
931
  }
1090
- function handleGetUser() {
1091
- return async (_req, _res, maybeSession) => {
1092
- const session = requireSession(maybeSession);
1093
- return {
1094
- status: "OK",
1095
- ...await getUserById(session.getUserId())
1096
- };
1097
- };
932
+ function parseUpdateMetaBody(value) {
933
+ if (!isJsonRecord(value) || !isJsonRecord(value.meta)) {
934
+ return {};
935
+ }
936
+ return { meta: value.meta };
1098
937
  }
1099
- function handleUpdateUser() {
1100
- return async (req, _res, maybeSession, userContext) => {
1101
- const session = requireSession(maybeSession);
1102
- const appVariantId = getRequestedAppVariantIdFromRequest(req);
1103
- assertRowndAppVariantIsConfigured(appVariantId);
1104
- const payload = parseUpdateUserBody(await getJsonBody(req));
1105
- const inputData = payload.data ?? {};
1106
- const requestUserContext = {
1107
- ...userContext,
1108
- ...payload.context
1109
- };
1110
- const { email, ...dataWithoutEmail } = inputData;
1111
- const hasEmailUpdate = hasOwn(inputData, "email") && typeof email === "string";
1112
- const permissionError = validateWritableFields(
1113
- Object.keys(dataWithoutEmail)
1114
- );
1115
- if (permissionError) {
1116
- return permissionError;
1117
- }
1118
- if (Object.keys(dataWithoutEmail).length > 0) {
1119
- await updateUserData(session.getUserId(), dataWithoutEmail);
1120
- }
1121
- if (hasEmailUpdate) {
1122
- return {
1123
- status: "OK",
1124
- ...await startPendingEmailVerification({
1125
- userId: session.getUserId(),
1126
- recipeUserId: session.getRecipeUserId(),
1127
- tenantId: session.getTenantId(),
1128
- email,
1129
- pendingVerificationId: (0, import_crypto.randomUUID)(),
1130
- userContext: appVariantId ? { ...requestUserContext, rowndAppVariantId: appVariantId } : requestUserContext
1131
- })
1132
- };
1133
- }
1134
- return {
1135
- status: "OK",
1136
- ...await getUserById(session.getUserId())
1137
- };
1138
- };
1139
- }
1140
- function handleDeleteUser() {
1141
- return async (_req, _res, maybeSession) => {
1142
- const session = requireSession(maybeSession);
1143
- await import_supertokens_node.default.deleteUser(session.getUserId(), true);
1144
- return { status: "OK" };
1145
- };
1146
- }
1147
- function handleSignOut() {
1148
- return async (_req, _res, maybeSession, userContext) => {
1149
- const session = requireSession(maybeSession);
1150
- await import_session.default.revokeAllSessionsForUser(
1151
- session.getUserId(),
1152
- true,
1153
- session.getTenantId(),
1154
- userContext
1155
- );
1156
- return { status: "OK" };
1157
- };
1158
- }
1159
- function handleGetUserMeta() {
1160
- return async (_req, _res, maybeSession) => {
1161
- const session = requireSession(maybeSession);
1162
- const metadata = await getUserMetadata(session.getUserId());
1163
- return {
1164
- status: "OK",
1165
- id: session.getUserId(),
1166
- meta: Object.fromEntries(
1167
- Object.entries(metadata).filter(
1168
- ([key]) => !isInternalMetadataField(key)
1169
- )
1170
- )
1171
- };
1172
- };
1173
- }
1174
- function handleUpdateUserMeta() {
1175
- return async (req, _res, maybeSession) => {
1176
- const session = requireSession(maybeSession);
1177
- const payload = parseUpdateMetaBody(await getJsonBody(req));
1178
- const internalField = Object.keys(payload.meta ?? {}).find(
1179
- isInternalMetadataField
1180
- );
1181
- if (internalField) {
1182
- return {
1183
- status: "ERROR",
1184
- code: 403,
1185
- message: `field is not writable: ${internalField}`
1186
- };
1187
- }
1188
- return {
1189
- status: "OK",
1190
- ...await updateUserMetadata(session.getUserId(), payload.meta ?? {})
1191
- };
1192
- };
1193
- }
1194
- function handleGetUserField() {
1195
- return async (req, _res, maybeSession) => {
1196
- const session = requireSession(maybeSession);
1197
- const field = req.getKeyValueFromQuery("field");
1198
- if (!field) {
1199
- return missingFieldResponse();
1200
- }
1201
- const user = await getUserById(session.getUserId());
1202
- return {
1203
- status: "OK",
1204
- value: user.data[field]
1205
- };
1206
- };
1207
- }
1208
- function handleUpdateUserField() {
1209
- return async (req, _res, maybeSession, userContext) => {
1210
- const session = requireSession(maybeSession);
1211
- const appVariantId = getRequestedAppVariantIdFromRequest(req);
1212
- assertRowndAppVariantIsConfigured(appVariantId);
1213
- const field = req.getKeyValueFromQuery("field");
1214
- if (!field) {
1215
- return missingFieldResponse();
1216
- }
1217
- const payload = parseUpdateFieldBody(await getJsonBody(req));
1218
- if (field === "email" && typeof payload.value === "string") {
1219
- return {
1220
- status: "OK",
1221
- ...await startPendingEmailVerification({
1222
- userId: session.getUserId(),
1223
- recipeUserId: session.getRecipeUserId(),
1224
- tenantId: session.getTenantId(),
1225
- email: payload.value,
1226
- pendingVerificationId: (0, import_crypto.randomUUID)(),
1227
- userContext: appVariantId ? { ...userContext, rowndAppVariantId: appVariantId } : userContext
1228
- })
1229
- };
1230
- }
1231
- const permissionError = validateWritableFields([field]);
1232
- if (permissionError) {
1233
- return permissionError;
1234
- }
1235
- return {
1236
- status: "OK",
1237
- ...await updateUserData(session.getUserId(), {
1238
- [field]: payload.value
1239
- })
1240
- };
1241
- };
1242
- }
1243
- function requireSession(session) {
1244
- if (!session) {
1245
- throw new Error("Session not found");
1246
- }
1247
- return session;
1248
- }
1249
- async function getJsonBody(req) {
1250
- return req.getJSONBody();
1251
- }
1252
- function parseGuestBody(value) {
1253
- if (!isJsonRecord(value)) {
1254
- return {};
1255
- }
1256
- return {
1257
- authLevel: typeof value.auth_level === "string" ? value.auth_level : void 0
1258
- };
1259
- }
1260
- function parseUpdateUserBody(value) {
1261
- if (!isJsonRecord(value) || !isJsonRecord(value.data)) {
1262
- return {};
1263
- }
1264
- return {
1265
- data: value.data,
1266
- ...isJsonRecord(value.context) ? { context: value.context } : {}
1267
- };
1268
- }
1269
- function parseUpdateMetaBody(value) {
1270
- if (!isJsonRecord(value) || !isJsonRecord(value.meta)) {
1271
- return {};
1272
- }
1273
- return { meta: value.meta };
1274
- }
1275
- function parseUpdateFieldBody(value) {
1276
- if (!isJsonRecord(value) || !hasOwn(value, "value")) {
1277
- return {};
1278
- }
1279
- return { value: value.value };
1280
- }
1281
- function validateWritableFields(fields) {
1282
- const readOnlyField = fields.find((field) => !canUpdateUserDataField(field));
1283
- if (!readOnlyField) {
1284
- return void 0;
1285
- }
1286
- return {
1287
- status: "ERROR",
1288
- code: 403,
1289
- message: `field is not writable: ${readOnlyField}`
1290
- };
938
+ function parseUpdateFieldBody(value) {
939
+ if (!isJsonRecord(value) || !hasOwn(value, "value")) {
940
+ return {};
941
+ }
942
+ return { value: value.value };
1291
943
  }
1292
944
  function missingFieldResponse() {
1293
945
  return {
@@ -1299,138 +951,21 @@ function missingFieldResponse() {
1299
951
  function isJsonRecord(value) {
1300
952
  return typeof value === "object" && value !== null && !Array.isArray(value);
1301
953
  }
954
+ function isRecord(value) {
955
+ return typeof value === "object" && value !== null && !Array.isArray(value);
956
+ }
1302
957
  function hasOwn(value, key) {
1303
958
  return Object.prototype.hasOwnProperty.call(value, key);
1304
959
  }
1305
- function getErrorMessage(error) {
1306
- return error instanceof Error ? error.message : "Unknown error";
1307
- }
1308
- function mapRowndUserToSuperTokens(rowndUser) {
1309
- const loginMethods = [];
1310
- const rowndUserData = rowndUser.data || {};
1311
- const rowndUserVerifiedData = rowndUser.verified_data || {};
1312
- if (!rowndUserData.user_id) {
1313
- throw new Error("Rownd user has no user_id");
1314
- }
1315
- if (rowndUserData.google_id) {
1316
- if (!rowndUserData.email) {
1317
- throw new Error("Rownd Google user is missing email");
1318
- }
1319
- loginMethods.push({
1320
- recipeId: "thirdparty",
1321
- thirdPartyId: "google",
1322
- thirdPartyUserId: rowndUserData.google_id,
1323
- email: rowndUserData.email,
1324
- isVerified: !!rowndUserVerifiedData.google_id
1325
- });
1326
- }
1327
- if (rowndUserData.apple_id) {
1328
- if (!rowndUserData.email) {
1329
- throw new Error("Rownd Apple user is missing email");
1330
- }
1331
- loginMethods.push({
1332
- recipeId: "thirdparty",
1333
- thirdPartyId: "apple",
1334
- thirdPartyUserId: rowndUserData.apple_id,
1335
- email: rowndUserData.email,
1336
- isVerified: !!rowndUserVerifiedData.apple_id
1337
- });
1338
- }
1339
- if (rowndUserData.phone_number) {
1340
- loginMethods.push({
1341
- recipeId: "passwordless",
1342
- phoneNumber: rowndUserData.phone_number,
1343
- isVerified: !!rowndUserVerifiedData.phone_number
1344
- });
1345
- }
1346
- if (rowndUserData.email && !rowndUserData.google_id && !rowndUserData.apple_id) {
1347
- loginMethods.push({
1348
- recipeId: "passwordless",
1349
- email: rowndUserData.email,
1350
- isVerified: !!rowndUserVerifiedData.email
1351
- });
1352
- }
1353
- let authLevel = rowndUser.auth_level;
1354
- if (loginMethods.length === 0) {
1355
- const thirdPartyId = authLevel === GUEST_AUTH_METHOD_ID ? GUEST_AUTH_METHOD_ID : ANONYMOUS_AUTH_METHOD_ID;
1356
- if (!authLevel) authLevel = thirdPartyId;
1357
- loginMethods.push({
1358
- recipeId: "thirdparty",
1359
- thirdPartyId,
1360
- thirdPartyUserId: rowndUserData.user_id,
1361
- email: `${rowndUserData.user_id}@anonymous.local`,
1362
- isVerified: false
1363
- });
1364
- }
1365
- const userMetadata = buildRowndUserMetadata(rowndUser);
1366
- return {
1367
- externalUserId: rowndUserData.user_id,
1368
- loginMethods,
1369
- userMetadata
1370
- };
1371
- }
1372
- async function importUser(stUser, config2) {
1373
- const headers = {
1374
- "Content-Type": "application/json"
1375
- };
1376
- if (config2.apiKey) {
1377
- headers["api-key"] = config2.apiKey;
1378
- }
1379
- const response = await fetch(`${config2.connectionURI}/bulk-import/import`, {
1380
- method: "POST",
1381
- headers,
1382
- body: JSON.stringify(stUser)
1383
- });
1384
- if (!response.ok) {
1385
- const errorText = await response.text();
1386
- throw new Error(
1387
- `Bulk import failed with status ${response.status}: ${errorText}`
1388
- );
1389
- }
1390
- const importResponse = await response.json();
1391
- if (importResponse.status !== "OK" || !importResponse.user) {
1392
- throw new Error(
1393
- `Bulk import failed: ${importResponse.message || "Missing user in response"}`
1394
- );
1395
- }
1396
- return importResponse.user;
1397
- }
1398
- async function validateRowndToken(token) {
1399
- const client = getRowndClient();
1400
- const tokenInfo = await client.validateToken(token);
1401
- return tokenInfo.user_id;
1402
- }
1403
- async function fetchRowndUserInfo(userId) {
1404
- const client = getRowndClient();
1405
- const rowndUser = await client.fetchUserInfo({ user_id: userId });
1406
- if (!rowndUser) {
1407
- throw new RowndPluginError("ROWND_USER_NOT_FOUND");
1408
- }
1409
- return rowndUser;
1410
- }
1411
- var IDENTITY_USER_DATA_FIELDS = /* @__PURE__ */ new Set([
1412
- "user_id",
1413
- "email",
1414
- "phone_number",
1415
- "google_id",
1416
- "apple_id"
1417
- ]);
1418
- var INTERNAL_METADATA_FIELDS = /* @__PURE__ */ new Set([
1419
- "original_rownd_user",
1420
- "rownd_pending_verification"
1421
- ]);
1422
- function isIdentityField(field) {
1423
- return IDENTITY_USER_DATA_FIELDS.has(field);
1424
- }
1425
- function isInternalMetadataField(field) {
1426
- return INTERNAL_METADATA_FIELDS.has(field);
1427
- }
1428
960
  function getStringList(value) {
1429
961
  if (Array.isArray(value)) {
1430
962
  return value.filter((entry) => typeof entry === "string");
1431
963
  }
1432
964
  return typeof value === "string" ? [value] : [];
1433
965
  }
966
+ function getErrorMessage(error) {
967
+ return error instanceof Error ? error.message : "Unknown error";
968
+ }
1434
969
  function getRequestedAppVariantIdFromRequest(req) {
1435
970
  return req.getKeyValueFromQuery("app_variant_id");
1436
971
  }
@@ -1438,518 +973,626 @@ function getRequestedDisplayContextFromRequest(req) {
1438
973
  const displayContext = req.getKeyValueFromQuery("rownd_display_context");
1439
974
  return displayContext === "browser" || displayContext === "mobile_app" || displayContext === "customer_web_view" ? displayContext : void 0;
1440
975
  }
976
+ function getRequestedClientDomainFromRequest(req) {
977
+ const clientDomain = req.getKeyValueFromQuery("rownd_client_domain");
978
+ return typeof clientDomain === "string" && clientDomain.length > 0 ? clientDomain : void 0;
979
+ }
1441
980
  function getRequestedRedirectToPathFromRequest(req) {
1442
981
  const redirectToPath = req.getKeyValueFromQuery("rownd_redirect_to_path");
1443
982
  return typeof redirectToPath === "string" && redirectToPath.length > 0 ? redirectToPath : void 0;
1444
983
  }
1445
- function assertRowndAppVariantIsConfigured(appVariantId) {
1446
- if (!appVariantId) {
1447
- return;
1448
- }
1449
- if (pluginConfig?.subBrands && !pluginConfig.subBrands[appVariantId]) {
1450
- throw new Error(`Unknown Rownd app variant: ${appVariantId}`);
1451
- }
984
+
985
+ // src/config.ts
986
+ var pluginConfig;
987
+ function setPluginConfig(config2) {
988
+ pluginConfig = config2;
1452
989
  }
1453
- async function recordRowndAppVariantForUser(userId, appVariantId) {
990
+ function getPluginConfig() {
991
+ return pluginConfig;
992
+ }
993
+ function assertRowndAppVariantIsConfigured(appVariantId) {
1454
994
  if (!appVariantId) {
1455
995
  return;
1456
996
  }
1457
- assertRowndAppVariantIsConfigured(appVariantId);
1458
- const metadata = await getUserMetadata(userId);
1459
- const originalRowndUser = isJsonRecord(metadata.original_rownd_user) ? metadata.original_rownd_user : {};
1460
- const attributes = isJsonRecord(originalRowndUser.attributes) ? originalRowndUser.attributes : {};
1461
- const appVariants = getStringList(attributes["rownd:app_variants"]);
1462
- if (appVariants.includes(appVariantId)) {
1463
- return;
997
+ if (pluginConfig?.subBrands && !pluginConfig.subBrands[appVariantId]) {
998
+ throw new Error(`Unknown Rownd app variant: ${appVariantId}`);
1464
999
  }
1465
- await import_usermetadata2.default.updateUserMetadata(userId, {
1466
- ...metadata,
1467
- original_rownd_user: {
1468
- ...originalRowndUser,
1469
- data: isJsonRecord(originalRowndUser.data) ? originalRowndUser.data : { user_id: userId },
1470
- verified_data: isJsonRecord(originalRowndUser.verified_data) ? originalRowndUser.verified_data : {},
1471
- attributes: {
1472
- ...attributes,
1473
- "rownd:app_variants": [...appVariants, appVariantId]
1474
- }
1475
- }
1476
- });
1477
1000
  }
1478
- function buildRowndUserMetadata(rowndUser) {
1479
- const metadata = {
1480
- ...rowndUser.meta || {},
1481
- original_rownd_user: rowndUser
1482
- };
1483
- for (const [key, value] of Object.entries(rowndUser.data || {})) {
1484
- if (!isIdentityField(key) && value !== void 0) {
1485
- metadata[key] = value;
1486
- }
1001
+ var BUILTIN_SIGN_IN_METHOD_KEYS = [
1002
+ "email",
1003
+ "phone",
1004
+ "google",
1005
+ "apple",
1006
+ "anonymous"
1007
+ ];
1008
+ function normalizeSchemaField(key, field) {
1009
+ let ownedBy = field.owned_by;
1010
+ if (key === "google_id" || key === "apple_id") {
1011
+ ownedBy = "app";
1012
+ } else if (!ownedBy) {
1013
+ ownedBy = "user";
1487
1014
  }
1488
- return metadata;
1015
+ return {
1016
+ display_name: field.display_name,
1017
+ type: field.type,
1018
+ owned_by: ownedBy,
1019
+ user_visible: field.user_visible,
1020
+ read_only: field.read_only ?? ownedBy === "app",
1021
+ show_empty: field.show_empty ?? false
1022
+ };
1489
1023
  }
1490
- var RowndIsAnonymousClaim = new import_claims.BooleanClaim({
1491
- key: "is_anonymous",
1492
- fetchValue: async (userId) => {
1493
- const user = await import_supertokens_node.default.getUser(userId);
1494
- const effectiveAuthLevel = getEffectiveAuthLevel(user);
1495
- return effectiveAuthLevel === GUEST_AUTH_METHOD_ID;
1496
- }
1497
- });
1498
- async function buildRowndSessionClaims(userId, currentPayload = {}, appVariantId) {
1499
- const user = await import_supertokens_node.default.getUser(userId);
1500
- const metadata = user ? await getUserMetadata(user.id) : void 0;
1501
- const originalRowndUser = metadata?.original_rownd_user;
1502
- const verifiedData = originalRowndUser?.verified_data;
1503
- const authLevel = getEffectiveAuthLevel(
1504
- user,
1505
- originalRowndUser?.auth_level,
1506
- verifiedData
1024
+ var DEFAULT_PRIMARY_COLOR = "#5b5bd6";
1025
+ function buildSignInMethodsConfig(methodsArray) {
1026
+ const methods = (methodsArray ?? []).reduce(
1027
+ (acc, curr) => {
1028
+ acc[curr.method] = curr;
1029
+ return acc;
1030
+ },
1031
+ {}
1507
1032
  );
1508
- const appUserId = getRowndAppUserId(userId, user, currentPayload, metadata);
1509
- const isAnonymous = authLevel === GUEST_AUTH_METHOD_ID;
1510
- const anonymousId = getAnonymousId(userId, user, metadata);
1511
- const isVerifiedUser = authLevel !== "unverified";
1512
- const audience = buildRowndAudience(currentPayload, appVariantId);
1513
- const configuredClaims = buildConfiguredSessionClaims(metadata);
1033
+ const customProviders = Object.fromEntries(
1034
+ Object.entries(methods).filter(([key]) => !BUILTIN_SIGN_IN_METHOD_KEYS.includes(key)).map(([key, val]) => {
1035
+ return val ? [
1036
+ key,
1037
+ {
1038
+ enabled: true,
1039
+ display_name: getStringMethodProperty(val, "displayName") ?? key,
1040
+ icon_light_url: getStringMethodProperty(val, "iconLightUrl"),
1041
+ icon_dark_url: getStringMethodProperty(val, "iconDarkUrl")
1042
+ }
1043
+ ] : [key, void 0];
1044
+ }).filter(([, v]) => v !== void 0)
1045
+ );
1046
+ const googleMethod = methods.google;
1047
+ const appleMethod = methods.apple;
1048
+ const anonymousMethod = methods.anonymous;
1049
+ const anonymousType = getAnonymousType(anonymousMethod);
1050
+ const googleOneTap = getOneTapConfig(googleMethod);
1514
1051
  return {
1515
- ...audience,
1516
- ...configuredClaims,
1517
- app_user_id: appUserId,
1518
- auth_level: authLevel,
1519
- is_verified_user: isVerifiedUser,
1520
- [ROWND_JWT_CLAIMS.AppUserId]: appUserId,
1521
- [ROWND_JWT_CLAIMS.AuthLevel]: authLevel,
1522
- [ROWND_JWT_CLAIMS.IsVerifiedUser]: isVerifiedUser,
1523
- [ROWND_JWT_CLAIMS.IsAnonymous]: isAnonymous,
1524
- ...anonymousId ? { anonymous_id: anonymousId } : {}
1052
+ email: { enabled: !!methods.email },
1053
+ phone: { enabled: !!methods.phone },
1054
+ google: {
1055
+ enabled: !!googleMethod,
1056
+ client_id: getStringMethodProperty(googleMethod, "clientId") ?? "",
1057
+ ios_client_id: getStringMethodProperty(googleMethod, "iosClientId") ?? "",
1058
+ scopes: getStringArrayMethodProperty(googleMethod, "scopes") ?? [],
1059
+ ...getSignInFasterWithGoogle(googleMethod) ? { sign_in_faster_with_google: getSignInFasterWithGoogle(googleMethod) } : {},
1060
+ one_tap: {
1061
+ browser: {
1062
+ auto_prompt: googleOneTap?.browser?.autoPrompt ?? false,
1063
+ delay: googleOneTap?.browser?.delay ?? 7e3
1064
+ },
1065
+ mobile_app: {
1066
+ auto_prompt: googleOneTap?.mobileApp?.autoPrompt ?? false,
1067
+ delay: googleOneTap?.mobileApp?.delay ?? 7e3
1068
+ }
1069
+ }
1070
+ },
1071
+ apple: {
1072
+ enabled: !!appleMethod,
1073
+ client_id: getStringMethodProperty(appleMethod, "clientId") ?? ""
1074
+ },
1075
+ anonymous: {
1076
+ enabled: !!anonymousMethod && anonymousType !== "instant",
1077
+ ...anonymousMethod && anonymousType !== "instant" ? { type: anonymousType } : {},
1078
+ ...anonymousType !== "instant" && getStringMethodProperty(anonymousMethod, "displayName") !== void 0 ? {
1079
+ display_name: getStringMethodProperty(
1080
+ anonymousMethod,
1081
+ "displayName"
1082
+ )
1083
+ } : {},
1084
+ ...anonymousType !== "instant" && getStringMethodProperty(anonymousMethod, "iconLightUrl") !== void 0 ? {
1085
+ icon_light_url: getStringMethodProperty(
1086
+ anonymousMethod,
1087
+ "iconLightUrl"
1088
+ )
1089
+ } : {},
1090
+ ...anonymousType !== "instant" && getStringMethodProperty(anonymousMethod, "iconDarkUrl") !== void 0 ? {
1091
+ icon_dark_url: getStringMethodProperty(
1092
+ anonymousMethod,
1093
+ "iconDarkUrl"
1094
+ )
1095
+ } : {}
1096
+ },
1097
+ ...customProviders
1525
1098
  };
1526
1099
  }
1527
- function buildConfiguredSessionClaims(metadata) {
1528
- if (!metadata) {
1529
- return {};
1530
- }
1531
- const schema = pluginConfig?.schema || DEFAULT_ROWND_SCHEMA;
1532
- const claims = {};
1533
- for (const [key, field] of Object.entries(schema)) {
1534
- if (field.include_in_session_claims !== true) {
1535
- continue;
1536
- }
1537
- const value = metadata.original_rownd_user?.data?.[key] ?? metadata[key];
1538
- if (value !== void 0) {
1539
- claims[field.session_claim_name || key] = value;
1540
- }
1541
- }
1542
- return claims;
1100
+ function isInstantAnonymousMethod(methods) {
1101
+ return (methods ?? []).some(
1102
+ (method) => method.method === "anonymous" && getAnonymousType(method) === "instant"
1103
+ );
1543
1104
  }
1544
- function getRowndAppUserId(userId, user, currentPayload, metadata) {
1545
- const originalUserId = metadata?.original_rownd_user?.data?.user_id;
1546
- if (typeof originalUserId === "string") {
1547
- return originalUserId;
1548
- }
1549
- if (typeof currentPayload.app_user_id === "string") {
1550
- return currentPayload.app_user_id;
1551
- }
1552
- return user?.id || userId;
1105
+ function getAnonymousType(method) {
1106
+ const type = getStringMethodProperty(method, "type");
1107
+ return type === "instant" ? "instant" : "guest";
1553
1108
  }
1554
- function buildRowndAudience(currentPayload, appVariantId) {
1555
- const audience = getStringList(currentPayload.aud);
1556
- const appId = pluginConfig?.appConfig?.id;
1557
- if (appId) {
1558
- audience.push(`app:${appId}`);
1559
- }
1560
- if (appVariantId) {
1561
- audience.push(`app_variant:${appVariantId}`);
1562
- }
1563
- return audience.length > 0 ? { aud: [...new Set(audience)] } : {};
1109
+ function getSignInFasterWithGoogle(method) {
1110
+ const value = getStringMethodProperty(method, "signInFasterWithGoogle");
1111
+ return value === "enabled" || value === "disabled" ? value : void 0;
1564
1112
  }
1565
- function getAnonymousId(userId, user, metadata) {
1566
- const originalAnonymousId = metadata?.original_rownd_user?.data?.anonymous_id;
1567
- if (typeof originalAnonymousId === "string") {
1568
- return originalAnonymousId;
1113
+ function getMethodProperty(method, property) {
1114
+ if (!method) {
1115
+ return void 0;
1569
1116
  }
1570
- const anonymousMethod = user?.loginMethods.find((loginMethod) => {
1571
- return loginMethod.recipeId === "thirdparty" && getThirdPartyId(loginMethod) === ANONYMOUS_AUTH_METHOD_ID;
1572
- });
1573
- const thirdPartyUserId = anonymousMethod ? getThirdPartyUserId(anonymousMethod) : void 0;
1574
- return thirdPartyUserId || (hasAnonymousLoginMethod(user) ? `anon_${userId}` : void 0);
1117
+ return method[property];
1575
1118
  }
1576
- async function shouldLinkRowndAccounts(input) {
1577
- const [newAccountInfo, , session] = input;
1578
- if (!session) {
1119
+ function getStringMethodProperty(method, property) {
1120
+ const value = getMethodProperty(method, property);
1121
+ return typeof value === "string" ? value : void 0;
1122
+ }
1123
+ function getStringArrayMethodProperty(method, property) {
1124
+ const value = getMethodProperty(method, property);
1125
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : void 0;
1126
+ }
1127
+ function getOneTapConfig(method) {
1128
+ const oneTap = getMethodProperty(method, "oneTap");
1129
+ if (!isRecord(oneTap)) {
1579
1130
  return void 0;
1580
1131
  }
1581
- const currentUser = await import_supertokens_node.default.getUser(session.getUserId());
1582
- if (hasOnlyGuestLoginMethods(currentUser)) {
1583
- return {
1584
- shouldAutomaticallyLink: true,
1585
- shouldRequireVerification: false
1586
- };
1587
- }
1588
- if (!currentUser || isGuestAccountInfo(newAccountInfo)) {
1132
+ return {
1133
+ browser: parseOneTapPlatform(oneTap.browser),
1134
+ mobileApp: parseOneTapPlatform(oneTap.mobileApp)
1135
+ };
1136
+ }
1137
+ function parseOneTapPlatform(value) {
1138
+ if (!isRecord(value)) {
1589
1139
  return void 0;
1590
1140
  }
1591
- if (doesAccountInfoMatchAuthMethod(currentUser, newAccountInfo)) {
1592
- return {
1593
- shouldAutomaticallyLink: true,
1594
- shouldRequireVerification: true
1595
- };
1596
- }
1597
- return void 0;
1598
- }
1599
- async function getUserMetadata(userId) {
1600
- const metadata = await import_usermetadata2.default.getUserMetadata(userId);
1601
- return metadata.metadata || {};
1141
+ return {
1142
+ autoPrompt: typeof value.autoPrompt === "boolean" ? value.autoPrompt : void 0,
1143
+ delay: typeof value.delay === "number" ? value.delay : void 0
1144
+ };
1602
1145
  }
1603
- function getPendingVerifications(metadata) {
1604
- const pendingVerification = metadata.rownd_pending_verification;
1605
- if (Array.isArray(pendingVerification)) {
1606
- return pendingVerification.filter(isPendingVerification);
1146
+ function getSubBrandVariant(app) {
1147
+ if (isRecord(app) && isRecord(app.variant) && typeof app.variant.id === "string") {
1148
+ return app.variant;
1607
1149
  }
1608
- return [];
1609
- }
1610
- function isPendingVerification(value) {
1611
- return isRecord(value) && typeof value.id === "string" && typeof value.field === "string" && typeof value.value === "string" && typeof value.created_at === "string";
1150
+ return void 0;
1612
1151
  }
1613
- async function getUserById(userId) {
1614
- const metadata = await getUserMetadata(userId);
1615
- const stUser = await import_supertokens_node.default.getUser(userId);
1616
- if (!stUser) {
1617
- throw new RowndPluginError("ROWND_USER_NOT_FOUND");
1618
- }
1619
- const originalRowndUser = metadata.original_rownd_user;
1620
- const rowndUser = originalRowndUser?.data?.user_id || userId;
1621
- const state = originalRowndUser?.state || "enabled";
1622
- const dataFieldKeys = /* @__PURE__ */ new Set();
1623
- const data = {
1624
- user_id: userId
1625
- };
1626
- for (const [key, value] of Object.entries(originalRowndUser?.data || {})) {
1627
- if (!isIdentityField(key)) {
1628
- data[key] = value;
1629
- dataFieldKeys.add(key);
1630
- }
1631
- }
1632
- const schema = pluginConfig?.schema || DEFAULT_ROWND_SCHEMA;
1633
- for (const key of Object.keys(schema)) {
1634
- dataFieldKeys.add(key);
1635
- if (!isInternalMetadataField(key) && !isIdentityField(key) && metadata[key] !== void 0) {
1636
- data[key] = metadata[key];
1152
+ function mergeConfigInput(base, override) {
1153
+ const result = { ...base };
1154
+ for (const [key, value] of Object.entries(override)) {
1155
+ if (value === void 0) {
1156
+ continue;
1637
1157
  }
1158
+ const existing = result[key];
1159
+ result[key] = isRecord(existing) && isRecord(value) ? mergeConfigInput(existing, value) : value;
1638
1160
  }
1639
- const verifiedData = {
1640
- ...originalRowndUser?.verified_data || {}
1641
- };
1642
- let lastUsedAt = stUser.timeJoined;
1643
- for (const method of stUser.loginMethods) {
1644
- if (typeof method.lastUsed === "number" && method.lastUsed > lastUsedAt) {
1645
- lastUsedAt = method.lastUsed;
1646
- }
1647
- if (method.recipeId === "passwordless") {
1648
- if (method.email) {
1649
- verifiedData.email = method.email;
1650
- if (data.email === void 0) data.email = method.email;
1651
- }
1652
- if (method.phoneNumber) {
1653
- verifiedData.phone_number = method.phoneNumber;
1654
- if (data.phone_number === void 0)
1655
- data.phone_number = method.phoneNumber;
1656
- }
1657
- } else if (method.recipeId === "thirdparty") {
1658
- const thirdPartyId = getThirdPartyId(method);
1659
- const thirdPartyUserId = getThirdPartyUserId(method);
1660
- if (method.verified && method.email) {
1661
- verifiedData.email = method.email;
1662
- }
1663
- if (method.email && data.email === void 0) {
1664
- data.email = method.email;
1665
- }
1666
- if (thirdPartyId === "google" && thirdPartyUserId) {
1667
- data.google_id = thirdPartyUserId;
1668
- verifiedData.google_id = thirdPartyUserId;
1669
- }
1670
- if (thirdPartyId === "apple" && thirdPartyUserId) {
1671
- data.apple_id = thirdPartyUserId;
1672
- verifiedData.apple_id = thirdPartyUserId;
1673
- }
1674
- } else if (method.recipeId === "emailpassword") {
1675
- if (method.email && data.email === void 0) {
1676
- data.email = method.email;
1677
- }
1678
- }
1161
+ return result;
1162
+ }
1163
+ function buildRowndAppConfig(config2, stConfig, appVariantId) {
1164
+ const userSchema = config2.schema ?? DEFAULT_ROWND_SCHEMA;
1165
+ const baseApp = config2.appConfig ?? {};
1166
+ const subBrand = appVariantId ? config2.subBrands?.[appVariantId] : void 0;
1167
+ const app = appVariantId ? subBrand && mergeConfigInput(baseApp, subBrand) : baseApp;
1168
+ if (!app) {
1169
+ return void 0;
1679
1170
  }
1680
- if (verifiedData.email === true && typeof data.email === "string") {
1681
- verifiedData.email = data.email;
1171
+ const branding = app.branding ?? {};
1172
+ const auth = app.auth ?? {};
1173
+ const signInMethods = buildSignInMethodsConfig(app.signInMethods);
1174
+ const variant = getSubBrandVariant(app);
1175
+ const finalSchema = { ...userSchema };
1176
+ if (signInMethods.email.enabled && !finalSchema.email) {
1177
+ finalSchema.email = {
1178
+ display_name: "Email",
1179
+ type: "string",
1180
+ user_visible: true
1181
+ };
1682
1182
  }
1683
- if (verifiedData.phone_number === true && typeof data.phone_number === "string") {
1684
- verifiedData.phone_number = data.phone_number;
1183
+ if (signInMethods.phone.enabled && !finalSchema.phone_number) {
1184
+ finalSchema.phone_number = {
1185
+ display_name: "Phone number",
1186
+ type: "string",
1187
+ user_visible: true
1188
+ };
1685
1189
  }
1686
- const anonymousId = getAnonymousId(stUser.id, stUser, metadata);
1687
- if (anonymousId && data.anonymous_id === void 0) {
1688
- data.anonymous_id = anonymousId;
1190
+ if (signInMethods.google.enabled && !finalSchema.google_id) {
1191
+ finalSchema.google_id = {
1192
+ display_name: "Google ID",
1193
+ type: "string",
1194
+ user_visible: false
1195
+ };
1689
1196
  }
1690
- const authLevel = getEffectiveAuthLevel(
1691
- stUser,
1692
- originalRowndUser?.auth_level,
1693
- verifiedData
1694
- );
1695
- for (const [key, field] of Object.entries(schema)) {
1696
- if (data[key] === void 0 && field.type === "string") {
1697
- data[key] = "";
1698
- }
1197
+ if (signInMethods.apple.enabled && !finalSchema.apple_id) {
1198
+ finalSchema.apple_id = {
1199
+ display_name: "Apple ID",
1200
+ type: "string",
1201
+ user_visible: false
1202
+ };
1699
1203
  }
1700
- const mapMethod = (method) => {
1701
- if (method.recipeId === "thirdparty") {
1702
- if (getThirdPartyId(method) === "google") return "google";
1703
- if (getThirdPartyId(method) === "apple") return "apple";
1704
- } else if (method.recipeId === "passwordless") {
1705
- if (method.email) return "email";
1706
- if (method.phoneNumber) return "phone";
1707
- } else if (method.recipeId === "emailpassword") {
1708
- return "email";
1709
- }
1710
- return "email";
1711
- };
1712
- const sortedByJoined = [...stUser.loginMethods].sort(
1713
- (a, b) => a.timeJoined - b.timeJoined
1714
- );
1715
- const sortedByLastUsed = [
1716
- ...stUser.loginMethods
1717
- ].sort((a, b) => (b.lastUsed || b.timeJoined) - (a.lastUsed || a.timeJoined));
1718
- const firstMethod = sortedByJoined[0];
1719
- const lastMethod = sortedByLastUsed[0];
1720
- const metadataMeta = Object.fromEntries(
1721
- Object.entries(metadata).filter(
1722
- ([key]) => !isInternalMetadataField(key) && !dataFieldKeys.has(key)
1723
- )
1724
- );
1725
- const meta = {
1726
- ...metadataMeta,
1727
- created: new Date(stUser.timeJoined).toISOString(),
1728
- first_sign_in: new Date(stUser.timeJoined).toISOString(),
1729
- last_sign_in: new Date(lastUsedAt).toISOString(),
1730
- last_active: new Date(lastUsedAt).toISOString(),
1731
- first_sign_in_method: firstMethod ? mapMethod(firstMethod) : "email",
1732
- last_sign_in_method: lastMethod ? mapMethod(lastMethod) : "email"
1733
- };
1734
1204
  return {
1735
- rownd_user: rowndUser,
1736
- data,
1737
- meta,
1738
- verified_data: verifiedData,
1739
- state,
1740
- auth_level: authLevel,
1741
- redacted: [],
1742
- groups: originalRowndUser?.groups || [],
1743
- attributes: originalRowndUser?.attributes || {}
1744
- };
1745
- }
1746
- async function updateUserData(userId, inputData) {
1747
- const metadata = await getUserMetadata(userId);
1748
- const updatedMetadata = {
1749
- ...metadata,
1750
- ...inputData
1751
- };
1752
- await import_usermetadata2.default.updateUserMetadata(userId, updatedMetadata);
1753
- return getUserById(userId);
1754
- }
1755
- async function startPendingEmailVerification(input) {
1756
- const metadata = await getUserMetadata(input.userId);
1757
- const currentEmail = (await getUserById(input.userId)).data.email;
1758
- if (currentEmail === input.email) {
1759
- return getUserById(input.userId);
1760
- }
1761
- const pendingVerifications = getPendingVerifications(metadata);
1762
- const pendingEmailVerifications = pendingVerifications.filter(
1763
- (pendingVerification2) => pendingVerification2.field === "email"
1764
- );
1765
- for (const pendingVerification2 of pendingEmailVerifications) {
1766
- await import_emailverification.default.revokeEmailVerificationTokens(
1767
- input.tenantId,
1768
- input.recipeUserId,
1769
- pendingVerification2.value,
1770
- input.userContext
1771
- );
1772
- }
1773
- const pendingVerification = {
1774
- id: input.pendingVerificationId,
1775
- field: "email",
1776
- value: input.email,
1777
- created_at: (/* @__PURE__ */ new Date()).toISOString()
1778
- };
1779
- await import_usermetadata2.default.updateUserMetadata(input.userId, {
1780
- ...metadata,
1781
- rownd_pending_verification: [
1782
- ...pendingVerifications.filter(
1783
- (pendingVerification2) => pendingVerification2.field !== "email"
1205
+ config_type: appVariantId ? "variant" : "app",
1206
+ ...variant ? { variant } : {},
1207
+ app: {
1208
+ id: app.id ?? "",
1209
+ name: app.name ?? stConfig.appInfo.appName,
1210
+ icon: app.icon ?? "",
1211
+ ...app.userVerificationFields ? { user_verification_fields: app.userVerificationFields } : {},
1212
+ schema: Object.fromEntries(
1213
+ Object.entries(finalSchema).map(([key, field]) => [
1214
+ key,
1215
+ normalizeSchemaField(key, field)
1216
+ ])
1784
1217
  ),
1785
- pendingVerification
1786
- ]
1787
- });
1788
- const response = await import_emailverification.default.sendEmailVerificationEmail(
1789
- input.tenantId,
1790
- input.userId,
1791
- input.recipeUserId,
1792
- input.email,
1793
- {
1794
- ...input.userContext,
1795
- rowndPendingVerificationId: pendingVerification.id
1796
- }
1797
- );
1798
- if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") {
1799
- await completePendingEmailVerification({
1800
- recipeUserId: input.recipeUserId,
1801
- email: input.email,
1802
- userContext: input.userContext
1803
- });
1804
- }
1805
- return getUserById(input.userId);
1806
- }
1807
- async function completePendingEmailVerification(input) {
1808
- const user = await import_supertokens_node.default.getUser(
1809
- input.recipeUserId.getAsString(),
1810
- input.userContext
1811
- );
1812
- const userId = user?.id ?? input.recipeUserId.getAsString();
1813
- const metadata = await getUserMetadata(userId);
1814
- const pendingVerifications = getPendingVerifications(metadata);
1815
- const pendingVerification = pendingVerifications.find(
1816
- (pendingVerification2) => pendingVerification2.field === "email" && pendingVerification2.value === input.email
1817
- );
1818
- if (!pendingVerification) {
1819
- return;
1820
- }
1821
- let metadataUserId = userId;
1822
- const passwordlessEmailMethod = getPasswordlessEmailLoginMethod(user);
1823
- if (passwordlessEmailMethod) {
1824
- const updateResult = await import_passwordless.default.updateUser({
1825
- recipeUserId: passwordlessEmailMethod.recipeUserId,
1826
- email: input.email,
1827
- userContext: input.userContext
1828
- });
1829
- if (updateResult.status !== "OK") {
1830
- throw new Error(
1831
- `Failed to update verified email method: ${updateResult.status}`
1832
- );
1833
- }
1834
- } else if (hasOnlyGuestLoginMethods(user)) {
1835
- const isPasswordlessSignUpAllowed = await import_accountlinking.default.isSignUpAllowed(
1836
- PUBLIC_TENANT_ID,
1837
- {
1838
- recipeId: "passwordless",
1839
- email: input.email
1840
- },
1841
- true,
1842
- void 0,
1843
- input.userContext
1844
- );
1845
- if (!isPasswordlessSignUpAllowed) {
1846
- throw new Error("Passwordless sign up is not allowed for this email");
1847
- }
1848
- const passwordlessUser = await import_passwordless.default.signInUp({
1849
- email: input.email,
1850
- tenantId: PUBLIC_TENANT_ID,
1851
- userContext: input.userContext
1852
- });
1853
- const primaryUserResult = await import_accountlinking.default.createPrimaryUser(
1854
- passwordlessUser.recipeUserId,
1855
- input.userContext
1856
- );
1857
- const primaryUserId = primaryUserResult.status === "OK" ? primaryUserResult.user.id : primaryUserResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" ? primaryUserResult.primaryUserId : passwordlessUser.user.id;
1858
- if (userId !== primaryUserId) {
1859
- const linkResult = await import_accountlinking.default.linkAccounts(
1860
- input.recipeUserId,
1861
- primaryUserId,
1862
- input.userContext
1863
- );
1864
- if (linkResult.status !== "OK") {
1865
- throw new Error(
1866
- `Failed to link verified email method: ${linkResult.status}`
1867
- );
1868
- }
1869
- }
1870
- metadataUserId = primaryUserId;
1871
- }
1872
- const updatedMetadata = {
1873
- ...metadata,
1874
- ...metadata.original_rownd_user ? {
1875
- original_rownd_user: {
1876
- ...metadata.original_rownd_user,
1877
- data: {
1878
- ...metadata.original_rownd_user.data,
1879
- email: input.email
1218
+ config: {
1219
+ ...app.capabilities ? { capabilities: app.capabilities } : {},
1220
+ ...app.web ? { web: app.web } : {},
1221
+ ...app.bottomSheet ? { bottom_sheet: app.bottomSheet } : {},
1222
+ ...app.profileStorageVersion ? { profile_storage_version: app.profileStorageVersion } : {},
1223
+ customizations: {
1224
+ primary_color: branding.primaryColor ?? DEFAULT_PRIMARY_COLOR,
1225
+ ...branding.logo ? { logo: branding.logo } : {},
1226
+ ...branding.logoDarkMode ? { logo_dark_mode: branding.logoDarkMode } : {},
1227
+ ...branding.animations ? { animations: branding.animations } : {}
1880
1228
  },
1881
- verified_data: {
1882
- ...metadata.original_rownd_user.verified_data,
1883
- email: input.email
1229
+ hub: {
1230
+ ...app.allowedWebOrigins ? { allowed_web_origins: app.allowedWebOrigins } : {},
1231
+ customizations: {
1232
+ rounded_corners: branding.roundedCorners ?? true,
1233
+ ...branding.containerBorderRadius !== void 0 ? { container_border_radius: branding.containerBorderRadius } : {},
1234
+ ...branding.placement !== void 0 ? { placement: branding.placement } : {},
1235
+ ...branding.hubPrimaryColor !== void 0 ? { primary_color: branding.hubPrimaryColor } : {},
1236
+ ...branding.primaryColorDarkMode !== void 0 ? { primary_color_dark_mode: branding.primaryColorDarkMode } : {},
1237
+ ...branding.backgroundColor !== void 0 ? { background_color: branding.backgroundColor } : {},
1238
+ ...branding.fontFamily !== void 0 ? { font_family: branding.fontFamily } : {},
1239
+ ...branding.hideVerificationIcons !== void 0 ? { hide_verification_icons: branding.hideVerificationIcons } : {},
1240
+ visual_swoops: branding.visualSwoops ?? true,
1241
+ blur_background: branding.blurBackground ?? true,
1242
+ ...branding.blurBackgroundOpacity !== void 0 ? { blur_background_opacity: branding.blurBackgroundOpacity } : {},
1243
+ ...branding.offsetX !== void 0 ? { offset_x: branding.offsetX } : {},
1244
+ ...branding.offsetY !== void 0 ? { offset_y: branding.offsetY } : {},
1245
+ ...branding.propertyOverrides ? { property_overrides: branding.propertyOverrides } : {},
1246
+ dark_mode: branding.darkMode ?? "auto"
1247
+ },
1248
+ ...branding.customScripts ? { custom_scripts: branding.customScripts } : {},
1249
+ ...branding.customStyles ? { custom_styles: branding.customStyles } : {},
1250
+ auth: {
1251
+ email: buildAuthEmailConfig(auth.email),
1252
+ ...auth.mobile ? { mobile: buildAuthMobileConfig(auth.mobile) } : {},
1253
+ sign_in_methods: signInMethods,
1254
+ additional_fields: auth.additionalFields ?? [],
1255
+ ...auth.rememberSignInMethod !== void 0 ? { remember_sign_in_method: auth.rememberSignInMethod } : {},
1256
+ ...auth.useExplicitSignUpFlow !== void 0 ? { use_explicit_sign_up_flow: auth.useExplicitSignUpFlow } : {},
1257
+ ...auth.allowUnverifiedUsers !== void 0 ? { allow_unverified_users: auth.allowUnverifiedUsers } : {},
1258
+ ...auth.primarySignUpMethod ? { primary_sign_up_method: auth.primarySignUpMethod } : {},
1259
+ ...auth.preferredMethod ? { preferred_method: auth.preferredMethod } : {},
1260
+ ...auth.order ? { order: auth.order } : {},
1261
+ ...isInstantAnonymousMethod(app.signInMethods) ? { instant_user: { enabled: true } } : {},
1262
+ show_app_icon: branding.showAppIcon ?? false
1263
+ },
1264
+ legal: {
1265
+ ...app.legal?.companyName ? { company_name: app.legal.companyName } : {},
1266
+ ...app.legal?.privacyPolicyUrl ? { privacy_policy_url: app.legal.privacyPolicyUrl } : {},
1267
+ ...app.legal?.termsConditionsUrl ? { terms_conditions_url: app.legal.termsConditionsUrl } : {},
1268
+ ...app.legal?.supportEmail ? { support_email: app.legal.supportEmail } : {}
1269
+ },
1270
+ custom_content: {
1271
+ ...app.customContent?.signInModal ? {
1272
+ sign_in_modal: {
1273
+ ...app.customContent.signInModal.title ? { title: app.customContent.signInModal.title } : {},
1274
+ ...app.customContent.signInModal.subtitle ? { subtitle: app.customContent.signInModal.subtitle } : {},
1275
+ ...app.customContent.signInModal.signInTitle ? {
1276
+ sign_in_title: app.customContent.signInModal.signInTitle
1277
+ } : {},
1278
+ ...app.customContent.signInModal.signUpTitle ? {
1279
+ sign_up_title: app.customContent.signInModal.signUpTitle
1280
+ } : {},
1281
+ ...app.customContent.signInModal.signInSubtitle ? {
1282
+ sign_in_subtitle: app.customContent.signInModal.signInSubtitle
1283
+ } : {},
1284
+ ...app.customContent.signInModal.signUpSubtitle ? {
1285
+ sign_up_subtitle: app.customContent.signInModal.signUpSubtitle
1286
+ } : {},
1287
+ ...app.customContent.signInModal.signInButton ? {
1288
+ sign_in_button: app.customContent.signInModal.signInButton
1289
+ } : {},
1290
+ ...app.customContent.signInModal.signUpButton ? {
1291
+ sign_up_button: app.customContent.signInModal.signUpButton
1292
+ } : {}
1293
+ }
1294
+ } : {},
1295
+ ...app.customContent?.profileModal ? { profile_modal: app.customContent.profileModal } : {},
1296
+ ...app.customContent?.verificationModal ? {
1297
+ verification_modal: {
1298
+ ...app.customContent.verificationModal.title ? { title: app.customContent.verificationModal.title } : {},
1299
+ ...app.customContent.verificationModal.subtitle ? { subtitle: app.customContent.verificationModal.subtitle } : {}
1300
+ }
1301
+ } : {},
1302
+ ...app.customContent?.signInFailureModal ? {
1303
+ sign_in_failure_modal: {
1304
+ failure_message: app.customContent.signInFailureModal.failureMessage
1305
+ }
1306
+ } : {},
1307
+ ...app.customContent?.noAccountMessage ? { no_account_message: app.customContent.noAccountMessage } : {},
1308
+ ...app.customContent?.mobile ? { mobile: app.customContent.mobile } : {}
1309
+ },
1310
+ profile: {
1311
+ ...app.profile?.accountInformation ? { account_information: app.profile.accountInformation } : {},
1312
+ ...app.profile?.personalInformation ? { personal_information: app.profile.personalInformation } : {},
1313
+ ...app.profile?.preferences ? { preferences: app.profile.preferences } : {},
1314
+ ...app.profile?.signOutButton ? { sign_out_button: app.profile.signOutButton } : {},
1315
+ ...app.profile?.deleteAccountButton ? { delete_account_button: app.profile.deleteAccountButton } : {},
1316
+ ...app.profile?.addSignInMethodsButton ? { add_sign_in_methods_button: app.profile.addSignInMethodsButton } : {}
1317
+ }
1884
1318
  }
1885
1319
  }
1886
- } : {},
1887
- rownd_pending_verification: pendingVerifications.filter(
1888
- (verification) => verification !== pendingVerification
1889
- )
1320
+ }
1890
1321
  };
1891
- await import_usermetadata2.default.updateUserMetadata(metadataUserId, updatedMetadata);
1892
1322
  }
1893
- async function updateUserMetadata(userId, inputMeta) {
1894
- const metadata = await getUserMetadata(userId);
1895
- const updatedMetadata = {
1896
- ...metadata,
1897
- ...inputMeta
1898
- };
1899
- await import_usermetadata2.default.updateUserMetadata(userId, updatedMetadata);
1323
+ function buildAuthEmailConfig(authEmail) {
1900
1324
  return {
1901
- id: userId,
1902
- meta: Object.fromEntries(
1903
- Object.entries(updatedMetadata).filter(
1904
- ([key]) => !isInternalMetadataField(key)
1905
- )
1906
- )
1325
+ from_address: authEmail?.fromAddress ?? "no-reply@rownd.io",
1326
+ image: authEmail?.image ?? "",
1327
+ ...authEmail?.subject ? { subject: authEmail.subject } : {},
1328
+ ...authEmail?.callToActionText ? { call_to_action_text: authEmail.callToActionText } : {},
1329
+ ...authEmail?.verifyTemplate ? { verify_template: authEmail.verifyTemplate } : {},
1330
+ ...authEmail?.customContent ? { custom_content: authEmail.customContent } : {},
1331
+ ...authEmail?.customClosingContent ? { custom_closing_content: authEmail.customClosingContent } : {}
1907
1332
  };
1908
1333
  }
1909
- function getThirdPartyId(method) {
1910
- return method.thirdPartyId || method.thirdParty?.id;
1911
- }
1912
- function getThirdPartyUserId(method) {
1913
- return method.thirdPartyUserId || method.thirdParty?.userId;
1914
- }
1915
- function getGuestAuthLevel(user) {
1916
- const guestMethod = user?.loginMethods.find(isGuestLoginMethod);
1917
- return guestMethod ? GUEST_AUTH_METHOD_ID : void 0;
1918
- }
1919
- function hasAnonymousLoginMethod(user) {
1920
- return !!user?.loginMethods.some((loginMethod) => {
1921
- return loginMethod.recipeId === "thirdparty" && getThirdPartyId(loginMethod) === ANONYMOUS_AUTH_METHOD_ID;
1922
- });
1923
- }
1924
- function getPasswordlessEmailLoginMethod(user) {
1925
- return user?.loginMethods.find((method) => {
1926
- return method.recipeId === "passwordless" && !!method.email;
1927
- });
1928
- }
1929
- function hasOnlyGuestLoginMethods(user) {
1930
- return !!user?.loginMethods.length && user.loginMethods.every(isGuestLoginMethod);
1334
+ function buildAuthMobileConfig(authMobile) {
1335
+ return {
1336
+ ...authMobile?.title ? { title: authMobile.title } : {},
1337
+ ...authMobile?.image ? { image: authMobile.image } : {},
1338
+ ...authMobile?.callToActionText ? { call_to_action_text: authMobile.callToActionText } : {},
1339
+ ...authMobile?.hyperlinkText ? { hyperlink_text: authMobile.hyperlinkText } : {},
1340
+ ...authMobile?.hyperlinkRedirectUrl ? { hyperlink_redirect_url: authMobile.hyperlinkRedirectUrl } : {},
1341
+ ...authMobile?.customContent ? { custom_content: authMobile.customContent } : {}
1342
+ };
1931
1343
  }
1932
- function isGuestLoginMethod(method) {
1933
- const thirdPartyId = getThirdPartyId(method);
1934
- return method.recipeId === "thirdparty" && (thirdPartyId === GUEST_AUTH_METHOD_ID || thirdPartyId === ANONYMOUS_AUTH_METHOD_ID);
1344
+
1345
+ // src/rownd-compatibility.ts
1346
+ var import_supertokens_node = __toESM(require("supertokens-node"));
1347
+ var IDENTITY_USER_DATA_FIELDS = /* @__PURE__ */ new Set([
1348
+ "user_id",
1349
+ "email",
1350
+ "phone_number",
1351
+ "google_id",
1352
+ "apple_id"
1353
+ ]);
1354
+ var INTERNAL_METADATA_FIELDS = /* @__PURE__ */ new Set([
1355
+ "original_rownd_user",
1356
+ "rownd_pending_verification"
1357
+ ]);
1358
+ function isIdentityField(field) {
1359
+ return IDENTITY_USER_DATA_FIELDS.has(field);
1935
1360
  }
1936
- function isGuestAccountInfo(input) {
1937
- return input?.recipeId === "thirdparty" && (input.thirdParty?.id === GUEST_AUTH_METHOD_ID || input.thirdParty?.id === ANONYMOUS_AUTH_METHOD_ID);
1361
+ function isInternalMetadataField(field) {
1362
+ return INTERNAL_METADATA_FIELDS.has(field);
1938
1363
  }
1939
- function doesAccountInfoMatchAuthMethod(user, accountInfo) {
1940
- if (!user) {
1941
- return false;
1364
+ function mapRowndUserToSuperTokens(rowndUser) {
1365
+ const loginMethods = [];
1366
+ const rowndUserData = rowndUser.data || {};
1367
+ const rowndUserVerifiedData = rowndUser.verified_data || {};
1368
+ if (!rowndUserData.user_id) {
1369
+ throw new Error("Rownd user has no user_id");
1942
1370
  }
1943
- const normalizedEmail = accountInfo.email?.toLowerCase();
1944
- if (normalizedEmail) {
1945
- return user.loginMethods.some((method) => {
1946
- if (isGuestLoginMethod(method) || !method.email) {
1947
- return false;
1948
- }
1949
- return method.email.toLowerCase() === normalizedEmail;
1371
+ if (rowndUserData.google_id) {
1372
+ if (!rowndUserData.email) {
1373
+ throw new Error("Rownd Google user is missing email");
1374
+ }
1375
+ loginMethods.push({
1376
+ recipeId: "thirdparty",
1377
+ thirdPartyId: "google",
1378
+ thirdPartyUserId: rowndUserData.google_id,
1379
+ email: rowndUserData.email,
1380
+ isVerified: !!rowndUserVerifiedData.google_id
1950
1381
  });
1951
1382
  }
1952
- if (accountInfo.phoneNumber) {
1383
+ if (rowndUserData.apple_id) {
1384
+ if (!rowndUserData.email) {
1385
+ throw new Error("Rownd Apple user is missing email");
1386
+ }
1387
+ loginMethods.push({
1388
+ recipeId: "thirdparty",
1389
+ thirdPartyId: "apple",
1390
+ thirdPartyUserId: rowndUserData.apple_id,
1391
+ email: rowndUserData.email,
1392
+ isVerified: !!rowndUserVerifiedData.apple_id
1393
+ });
1394
+ }
1395
+ if (rowndUserData.phone_number) {
1396
+ loginMethods.push({
1397
+ recipeId: "passwordless",
1398
+ phoneNumber: rowndUserData.phone_number,
1399
+ isVerified: !!rowndUserVerifiedData.phone_number
1400
+ });
1401
+ }
1402
+ if (rowndUserData.email && !rowndUserData.google_id && !rowndUserData.apple_id) {
1403
+ loginMethods.push({
1404
+ recipeId: "passwordless",
1405
+ email: rowndUserData.email,
1406
+ isVerified: !!rowndUserVerifiedData.email
1407
+ });
1408
+ }
1409
+ let authLevel = rowndUser.auth_level;
1410
+ if (loginMethods.length === 0) {
1411
+ const thirdPartyId = authLevel === GUEST_AUTH_METHOD_ID ? GUEST_AUTH_METHOD_ID : ANONYMOUS_AUTH_METHOD_ID;
1412
+ if (!authLevel) authLevel = thirdPartyId;
1413
+ loginMethods.push({
1414
+ recipeId: "thirdparty",
1415
+ thirdPartyId,
1416
+ thirdPartyUserId: rowndUserData.user_id,
1417
+ email: `${rowndUserData.user_id}@anonymous.local`,
1418
+ isVerified: false
1419
+ });
1420
+ }
1421
+ const userMetadata = buildRowndUserMetadata(rowndUser);
1422
+ return {
1423
+ externalUserId: rowndUserData.user_id,
1424
+ loginMethods,
1425
+ userMetadata
1426
+ };
1427
+ }
1428
+ function buildRowndUserMetadata(rowndUser) {
1429
+ const metadata = {
1430
+ ...rowndUser.meta || {},
1431
+ original_rownd_user: rowndUser
1432
+ };
1433
+ for (const [key, value] of Object.entries(rowndUser.data || {})) {
1434
+ if (!isIdentityField(key) && value !== void 0) {
1435
+ metadata[key] = value;
1436
+ }
1437
+ }
1438
+ return metadata;
1439
+ }
1440
+ function buildRowndAudience(currentPayload, appVariantId) {
1441
+ const audience = getStringList(currentPayload.aud);
1442
+ const appId = getPluginConfig()?.appConfig?.id;
1443
+ if (appId) {
1444
+ audience.push(`app:${appId}`);
1445
+ }
1446
+ if (appVariantId) {
1447
+ audience.push(`app_variant:${appVariantId}`);
1448
+ }
1449
+ return audience.length > 0 ? { aud: [...new Set(audience)] } : {};
1450
+ }
1451
+ function buildConfiguredSessionClaims(metadata) {
1452
+ if (!metadata) {
1453
+ return {};
1454
+ }
1455
+ const schema = getPluginConfig()?.schema || DEFAULT_ROWND_SCHEMA;
1456
+ const claims = {};
1457
+ for (const [key, field] of Object.entries(schema)) {
1458
+ if (field.include_in_session_claims !== true) {
1459
+ continue;
1460
+ }
1461
+ const value = metadata.original_rownd_user?.data?.[key] ?? metadata[key];
1462
+ if (value !== void 0) {
1463
+ claims[field.session_claim_name || key] = value;
1464
+ }
1465
+ }
1466
+ return claims;
1467
+ }
1468
+ function getRowndAppUserId(userId, user, currentPayload, metadata) {
1469
+ const originalUserId = metadata?.original_rownd_user?.data?.user_id;
1470
+ if (typeof originalUserId === "string") {
1471
+ return originalUserId;
1472
+ }
1473
+ if (typeof currentPayload.app_user_id === "string") {
1474
+ return currentPayload.app_user_id;
1475
+ }
1476
+ return user?.id || userId;
1477
+ }
1478
+ function getAnonymousId(userId, user, metadata) {
1479
+ const originalAnonymousId = metadata?.original_rownd_user?.data?.anonymous_id;
1480
+ if (typeof originalAnonymousId === "string") {
1481
+ return originalAnonymousId;
1482
+ }
1483
+ const anonymousMethod = user?.loginMethods.find((loginMethod) => {
1484
+ return loginMethod.recipeId === "thirdparty" && getThirdPartyId(loginMethod) === ANONYMOUS_AUTH_METHOD_ID;
1485
+ });
1486
+ const thirdPartyUserId = anonymousMethod ? getThirdPartyUserId(anonymousMethod) : void 0;
1487
+ return thirdPartyUserId || (hasAnonymousLoginMethod(user) ? `anon_${userId}` : void 0);
1488
+ }
1489
+ function buildRowndSessionClaimPayload(input) {
1490
+ const currentPayload = input.currentPayload ?? {};
1491
+ const originalRowndUser = input.metadata?.original_rownd_user;
1492
+ const verifiedData = originalRowndUser?.verified_data;
1493
+ const authLevel = getEffectiveAuthLevel(
1494
+ input.user,
1495
+ originalRowndUser?.auth_level,
1496
+ verifiedData
1497
+ );
1498
+ const appUserId = getRowndAppUserId(
1499
+ input.userId,
1500
+ input.user,
1501
+ currentPayload,
1502
+ input.metadata
1503
+ );
1504
+ const isAnonymous = authLevel === GUEST_AUTH_METHOD_ID;
1505
+ const anonymousId = getAnonymousId(input.userId, input.user, input.metadata);
1506
+ const isVerifiedUser = authLevel !== "unverified";
1507
+ const audience = buildRowndAudience(currentPayload, input.appVariantId);
1508
+ const configuredClaims = buildConfiguredSessionClaims(input.metadata);
1509
+ return {
1510
+ ...audience,
1511
+ ...configuredClaims,
1512
+ app_user_id: appUserId,
1513
+ auth_level: authLevel,
1514
+ is_verified_user: isVerifiedUser,
1515
+ [ROWND_JWT_CLAIMS.AppUserId]: appUserId,
1516
+ [ROWND_JWT_CLAIMS.AuthLevel]: authLevel,
1517
+ [ROWND_JWT_CLAIMS.IsVerifiedUser]: isVerifiedUser,
1518
+ [ROWND_JWT_CLAIMS.IsAnonymous]: isAnonymous,
1519
+ ...anonymousId ? { anonymous_id: anonymousId } : {}
1520
+ };
1521
+ }
1522
+ async function shouldLinkRowndAccounts(input) {
1523
+ const [newAccountInfo, , session] = input;
1524
+ if (!session) {
1525
+ return void 0;
1526
+ }
1527
+ const currentUser = await import_supertokens_node.default.getUser(session.getUserId());
1528
+ if (hasOnlyGuestLoginMethods(currentUser)) {
1529
+ return {
1530
+ shouldAutomaticallyLink: true,
1531
+ shouldRequireVerification: false
1532
+ };
1533
+ }
1534
+ if (!currentUser || isGuestAccountInfo(newAccountInfo)) {
1535
+ return void 0;
1536
+ }
1537
+ if (doesAccountInfoMatchAuthMethod(currentUser, newAccountInfo)) {
1538
+ return {
1539
+ shouldAutomaticallyLink: true,
1540
+ shouldRequireVerification: true
1541
+ };
1542
+ }
1543
+ return void 0;
1544
+ }
1545
+ function mapMethod(method) {
1546
+ if (method.recipeId === "thirdparty") {
1547
+ if (getThirdPartyId(method) === "google") return "google";
1548
+ if (getThirdPartyId(method) === "apple") return "apple";
1549
+ } else if (method.recipeId === "passwordless") {
1550
+ if (method.email) return "email";
1551
+ if (method.phoneNumber) return "phone";
1552
+ } else if (method.recipeId === "emailpassword") {
1553
+ return "email";
1554
+ }
1555
+ return "email";
1556
+ }
1557
+ function getThirdPartyId(method) {
1558
+ return method.thirdParty?.id;
1559
+ }
1560
+ function getThirdPartyUserId(method) {
1561
+ return method.thirdParty?.userId;
1562
+ }
1563
+ function getGuestAuthLevel(user) {
1564
+ const guestMethod = user?.loginMethods.find(isGuestLoginMethod);
1565
+ return guestMethod ? GUEST_AUTH_METHOD_ID : void 0;
1566
+ }
1567
+ function hasAnonymousLoginMethod(user) {
1568
+ return !!user?.loginMethods.some((loginMethod) => {
1569
+ return loginMethod.recipeId === "thirdparty" && getThirdPartyId(loginMethod) === ANONYMOUS_AUTH_METHOD_ID;
1570
+ });
1571
+ }
1572
+ function hasOnlyGuestLoginMethods(user) {
1573
+ return !!user?.loginMethods.length && user.loginMethods.every(isGuestLoginMethod);
1574
+ }
1575
+ function isGuestLoginMethod(method) {
1576
+ const thirdPartyId = getThirdPartyId(method);
1577
+ return method.recipeId === "thirdparty" && (thirdPartyId === GUEST_AUTH_METHOD_ID || thirdPartyId === ANONYMOUS_AUTH_METHOD_ID);
1578
+ }
1579
+ function isGuestAccountInfo(input) {
1580
+ return input?.recipeId === "thirdparty" && (input.thirdParty?.id === GUEST_AUTH_METHOD_ID || input.thirdParty?.id === ANONYMOUS_AUTH_METHOD_ID);
1581
+ }
1582
+ function doesAccountInfoMatchAuthMethod(user, accountInfo) {
1583
+ if (!user) {
1584
+ return false;
1585
+ }
1586
+ const normalizedEmail = accountInfo.email?.toLowerCase();
1587
+ if (normalizedEmail) {
1588
+ return user.loginMethods.some((method) => {
1589
+ if (isGuestLoginMethod(method) || !method.email) {
1590
+ return false;
1591
+ }
1592
+ return method.email.toLowerCase() === normalizedEmail;
1593
+ });
1594
+ }
1595
+ if (accountInfo.phoneNumber) {
1953
1596
  return user.loginMethods.some((method) => {
1954
1597
  return !isGuestLoginMethod(method) && method.phoneNumber === accountInfo.phoneNumber;
1955
1598
  });
@@ -1980,7 +1623,7 @@ function getEffectiveAuthLevel(user, originalAuthLevel, verifiedData) {
1980
1623
  return getGuestAuthLevel(user) || originalAuthLevel || (verifiedData && Object.keys(verifiedData).length > 0 ? "verified" : "unverified");
1981
1624
  }
1982
1625
  function canUpdateUserDataField(field) {
1983
- const schema = pluginConfig?.schema || DEFAULT_ROWND_SCHEMA;
1626
+ const schema = getPluginConfig()?.schema || DEFAULT_ROWND_SCHEMA;
1984
1627
  const schemaField = schema[field];
1985
1628
  if (!schemaField) {
1986
1629
  return false;
@@ -1988,350 +1631,806 @@ function canUpdateUserDataField(field) {
1988
1631
  const ownedBy = field === "google_id" || field === "apple_id" ? "app" : schemaField.owned_by || "user";
1989
1632
  return ownedBy !== "app" && schemaField.read_only !== true;
1990
1633
  }
1991
- var BUILTIN_SIGN_IN_METHOD_KEYS = [
1992
- "email",
1993
- "phone",
1994
- "google",
1995
- "apple",
1996
- "anonymous"
1997
- ];
1998
- function normalizeSchemaField(key, field) {
1999
- let ownedBy = field.owned_by;
2000
- if (key === "google_id" || key === "apple_id") {
2001
- ownedBy = "app";
2002
- } else if (!ownedBy) {
2003
- ownedBy = "user";
1634
+
1635
+ // src/rownd-repository.ts
1636
+ var rowndClient;
1637
+ function setRowndClient(client) {
1638
+ rowndClient = client;
1639
+ }
1640
+ function getRowndClient() {
1641
+ if (!rowndClient) {
1642
+ throw new Error("Rownd client not initialized");
2004
1643
  }
2005
- return {
2006
- display_name: field.display_name,
2007
- type: field.type,
2008
- owned_by: ownedBy,
2009
- user_visible: field.user_visible,
2010
- read_only: field.read_only ?? ownedBy === "app",
2011
- show_empty: field.show_empty ?? false
2012
- };
1644
+ return rowndClient;
2013
1645
  }
2014
- var DEFAULT_PRIMARY_COLOR = "#5b5bd6";
2015
- function buildSignInMethodsConfig(methodsArray) {
2016
- const methods = (methodsArray ?? []).reduce(
2017
- (acc, curr) => {
2018
- acc[curr.method] = curr;
2019
- return acc;
2020
- },
2021
- {}
2022
- );
2023
- const customProviders = Object.fromEntries(
2024
- Object.entries(methods).filter(([key]) => !BUILTIN_SIGN_IN_METHOD_KEYS.includes(key)).map(([key, val]) => {
2025
- return val ? [
2026
- key,
2027
- {
2028
- enabled: true,
2029
- display_name: getStringMethodProperty(val, "displayName") ?? key,
2030
- icon_light_url: getStringMethodProperty(val, "iconLightUrl"),
2031
- icon_dark_url: getStringMethodProperty(val, "iconDarkUrl")
2032
- }
2033
- ] : [key, void 0];
2034
- }).filter(([, v]) => v !== void 0)
2035
- );
2036
- const googleMethod = methods.google;
2037
- const appleMethod = methods.apple;
2038
- const anonymousMethod = methods.anonymous;
2039
- const anonymousType = getAnonymousType(anonymousMethod);
2040
- const googleOneTap = getOneTapConfig(googleMethod);
2041
- return {
2042
- email: { enabled: !!methods.email },
2043
- phone: { enabled: !!methods.phone },
2044
- google: {
2045
- enabled: !!googleMethod,
2046
- client_id: getStringMethodProperty(googleMethod, "clientId") ?? "",
2047
- ios_client_id: getStringMethodProperty(googleMethod, "iosClientId") ?? "",
2048
- scopes: getStringArrayMethodProperty(googleMethod, "scopes") ?? [],
2049
- ...getSignInFasterWithGoogle(googleMethod) ? { sign_in_faster_with_google: getSignInFasterWithGoogle(googleMethod) } : {},
2050
- one_tap: {
2051
- browser: {
2052
- auto_prompt: googleOneTap?.browser?.autoPrompt ?? false,
2053
- delay: googleOneTap?.browser?.delay ?? 7e3
2054
- },
2055
- mobile_app: {
2056
- auto_prompt: googleOneTap?.mobileApp?.autoPrompt ?? false,
2057
- delay: googleOneTap?.mobileApp?.delay ?? 7e3
2058
- }
2059
- }
2060
- },
2061
- apple: {
2062
- enabled: !!appleMethod,
2063
- client_id: getStringMethodProperty(appleMethod, "clientId") ?? ""
2064
- },
2065
- anonymous: {
2066
- enabled: !!anonymousMethod && anonymousType !== "instant",
2067
- ...anonymousMethod && anonymousType !== "instant" ? { type: anonymousType } : {},
2068
- ...anonymousType !== "instant" && getStringMethodProperty(anonymousMethod, "displayName") !== void 0 ? {
2069
- display_name: getStringMethodProperty(
2070
- anonymousMethod,
2071
- "displayName"
2072
- )
2073
- } : {},
2074
- ...anonymousType !== "instant" && getStringMethodProperty(anonymousMethod, "iconLightUrl") !== void 0 ? {
2075
- icon_light_url: getStringMethodProperty(
2076
- anonymousMethod,
2077
- "iconLightUrl"
2078
- )
2079
- } : {},
2080
- ...anonymousType !== "instant" && getStringMethodProperty(anonymousMethod, "iconDarkUrl") !== void 0 ? {
2081
- icon_dark_url: getStringMethodProperty(
2082
- anonymousMethod,
2083
- "iconDarkUrl"
2084
- )
2085
- } : {}
2086
- },
2087
- ...customProviders
2088
- };
2089
- }
2090
- function isInstantAnonymousMethod(methods) {
2091
- return (methods ?? []).some(
2092
- (method) => method.method === "anonymous" && getAnonymousType(method) === "instant"
2093
- );
1646
+ async function validateRowndToken(token) {
1647
+ const client = getRowndClient();
1648
+ const tokenInfo = await client.validateToken(token);
1649
+ return tokenInfo.user_id;
2094
1650
  }
2095
- function getAnonymousType(method) {
2096
- const type = getStringMethodProperty(method, "type");
2097
- return type === "instant" ? "instant" : "guest";
1651
+ async function fetchRowndUserInfo(userId) {
1652
+ const client = getRowndClient();
1653
+ const rowndUser = await client.fetchUserInfo({ user_id: userId });
1654
+ if (!rowndUser) {
1655
+ throw new RowndPluginError("ROWND_USER_NOT_FOUND");
1656
+ }
1657
+ return rowndUser;
2098
1658
  }
2099
- function getSignInFasterWithGoogle(method) {
2100
- const value = getStringMethodProperty(method, "signInFasterWithGoogle");
2101
- return value === "enabled" || value === "disabled" ? value : void 0;
1659
+
1660
+ // src/supertokens-repository.ts
1661
+ var import_supertokens_node2 = __toESM(require("supertokens-node"));
1662
+ var import_accountlinking = __toESM(require("supertokens-node/recipe/accountlinking"));
1663
+ var import_emailverification = __toESM(require("supertokens-node/recipe/emailverification"));
1664
+ var import_passwordless = __toESM(require("supertokens-node/recipe/passwordless"));
1665
+ var import_session = __toESM(require("supertokens-node/recipe/session"));
1666
+ var import_claims = require("supertokens-node/recipe/session/claims");
1667
+ var import_usermetadata2 = __toESM(require("supertokens-node/recipe/usermetadata"));
1668
+ async function importUser(stUser, config2) {
1669
+ const headers = {
1670
+ "Content-Type": "application/json"
1671
+ };
1672
+ if (config2.apiKey) {
1673
+ headers["api-key"] = config2.apiKey;
1674
+ }
1675
+ const response = await fetch(`${config2.connectionURI}/bulk-import/import`, {
1676
+ method: "POST",
1677
+ headers,
1678
+ body: JSON.stringify(stUser)
1679
+ });
1680
+ if (!response.ok) {
1681
+ const errorText = await response.text();
1682
+ throw new Error(
1683
+ `Bulk import failed with status ${response.status}: ${errorText}`
1684
+ );
1685
+ }
1686
+ const importResponse = await response.json();
1687
+ if (importResponse.status !== "OK" || !importResponse.user) {
1688
+ throw new Error(
1689
+ `Bulk import failed: ${importResponse.message || "Missing user in response"}`
1690
+ );
1691
+ }
1692
+ return importResponse.user;
2102
1693
  }
2103
- function getMethodProperty(method, property) {
2104
- if (!method) {
2105
- return void 0;
1694
+ async function recordRowndAppVariantForUser(userId, appVariantId) {
1695
+ if (!appVariantId) {
1696
+ return;
2106
1697
  }
2107
- return method[property];
1698
+ assertRowndAppVariantIsConfigured(appVariantId);
1699
+ const metadata = await getUserMetadata(userId);
1700
+ const originalRowndUser = isJsonRecord(metadata.original_rownd_user) ? metadata.original_rownd_user : {};
1701
+ const attributes = isJsonRecord(originalRowndUser.attributes) ? originalRowndUser.attributes : {};
1702
+ const appVariants = getStringList(attributes["rownd:app_variants"]);
1703
+ if (appVariants.includes(appVariantId)) {
1704
+ return;
1705
+ }
1706
+ await import_usermetadata2.default.updateUserMetadata(userId, {
1707
+ ...metadata,
1708
+ original_rownd_user: {
1709
+ ...originalRowndUser,
1710
+ data: isJsonRecord(originalRowndUser.data) ? originalRowndUser.data : { user_id: userId },
1711
+ verified_data: isJsonRecord(originalRowndUser.verified_data) ? originalRowndUser.verified_data : {},
1712
+ attributes: {
1713
+ ...attributes,
1714
+ "rownd:app_variants": [...appVariants, appVariantId]
1715
+ }
1716
+ }
1717
+ });
2108
1718
  }
2109
- function getStringMethodProperty(method, property) {
2110
- const value = getMethodProperty(method, property);
2111
- return typeof value === "string" ? value : void 0;
1719
+ var RowndIsAnonymousClaim = new import_claims.BooleanClaim({
1720
+ key: "is_anonymous",
1721
+ fetchValue: async (userId) => {
1722
+ const user = await import_supertokens_node2.default.getUser(userId);
1723
+ const effectiveAuthLevel = getEffectiveAuthLevel(user);
1724
+ return effectiveAuthLevel === GUEST_AUTH_METHOD_ID;
1725
+ }
1726
+ });
1727
+ async function buildRowndSessionClaims(userId, currentPayload = {}, appVariantId) {
1728
+ const user = await import_supertokens_node2.default.getUser(userId);
1729
+ const metadata = user ? await getUserMetadata(user.id) : void 0;
1730
+ return buildRowndSessionClaimPayload({
1731
+ userId,
1732
+ user,
1733
+ metadata,
1734
+ currentPayload,
1735
+ appVariantId
1736
+ });
2112
1737
  }
2113
- function getStringArrayMethodProperty(method, property) {
2114
- const value = getMethodProperty(method, property);
2115
- return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : void 0;
1738
+ async function getUserMetadata(userId) {
1739
+ const metadata = await import_usermetadata2.default.getUserMetadata(userId);
1740
+ return metadata.metadata || {};
2116
1741
  }
2117
- function getOneTapConfig(method) {
2118
- const oneTap = getMethodProperty(method, "oneTap");
2119
- if (!isRecord(oneTap)) {
2120
- return void 0;
1742
+ function getPendingVerifications(metadata) {
1743
+ const pendingVerification = metadata.rownd_pending_verification;
1744
+ if (Array.isArray(pendingVerification)) {
1745
+ return pendingVerification.filter(isPendingVerification);
2121
1746
  }
2122
- return {
2123
- browser: parseOneTapPlatform(oneTap.browser),
2124
- mobileApp: parseOneTapPlatform(oneTap.mobileApp)
2125
- };
1747
+ return [];
2126
1748
  }
2127
- function parseOneTapPlatform(value) {
2128
- if (!isRecord(value)) {
2129
- return void 0;
1749
+ function isPendingVerification(value) {
1750
+ return isRecord(value) && typeof value.id === "string" && typeof value.field === "string" && typeof value.value === "string" && typeof value.created_at === "string";
1751
+ }
1752
+ async function getUserById(userId) {
1753
+ const metadata = await getUserMetadata(userId);
1754
+ const stUser = await import_supertokens_node2.default.getUser(userId);
1755
+ if (!stUser) {
1756
+ throw new RowndPluginError("ROWND_USER_NOT_FOUND");
2130
1757
  }
2131
- return {
2132
- autoPrompt: typeof value.autoPrompt === "boolean" ? value.autoPrompt : void 0,
2133
- delay: typeof value.delay === "number" ? value.delay : void 0
1758
+ const originalRowndUser = metadata.original_rownd_user;
1759
+ const rowndUser = originalRowndUser?.data?.user_id || userId;
1760
+ const state = originalRowndUser?.state || "enabled";
1761
+ const dataFieldKeys = /* @__PURE__ */ new Set();
1762
+ const data = {
1763
+ user_id: userId
2134
1764
  };
2135
- }
2136
- function isRecord(value) {
2137
- return typeof value === "object" && value !== null && !Array.isArray(value);
2138
- }
2139
- function getSubBrandVariant(app) {
2140
- if (isRecord(app) && isRecord(app.variant) && typeof app.variant.id === "string") {
2141
- return app.variant;
1765
+ for (const [key, value] of Object.entries(originalRowndUser?.data || {})) {
1766
+ if (!isIdentityField(key)) {
1767
+ data[key] = value;
1768
+ dataFieldKeys.add(key);
1769
+ }
2142
1770
  }
2143
- return void 0;
2144
- }
2145
- function mergeConfigInput(base, override) {
2146
- const result = { ...base };
2147
- for (const [key, value] of Object.entries(override)) {
2148
- if (value === void 0) {
2149
- continue;
1771
+ const schema = getPluginConfig()?.schema || DEFAULT_ROWND_SCHEMA;
1772
+ for (const key of Object.keys(schema)) {
1773
+ dataFieldKeys.add(key);
1774
+ if (!isInternalMetadataField(key) && !isIdentityField(key) && metadata[key] !== void 0) {
1775
+ data[key] = metadata[key];
2150
1776
  }
2151
- const existing = result[key];
2152
- result[key] = isRecord(existing) && isRecord(value) ? mergeConfigInput(existing, value) : value;
2153
1777
  }
2154
- return result;
2155
- }
2156
- function buildAppConfig(config2, stConfig, appVariantId) {
2157
- const userSchema = config2.schema ?? DEFAULT_ROWND_SCHEMA;
2158
- const baseApp = config2.appConfig ?? {};
2159
- const subBrand = appVariantId ? config2.subBrands?.[appVariantId] : void 0;
2160
- const app = appVariantId ? subBrand && mergeConfigInput(baseApp, subBrand) : baseApp;
2161
- if (!app) {
2162
- return void 0;
1778
+ const verifiedData = {
1779
+ ...originalRowndUser?.verified_data || {}
1780
+ };
1781
+ for (const method of stUser.loginMethods) {
1782
+ if (method.recipeId === "passwordless") {
1783
+ if (method.email) {
1784
+ verifiedData.email = method.email;
1785
+ if (data.email === void 0) data.email = method.email;
1786
+ }
1787
+ if (method.phoneNumber) {
1788
+ verifiedData.phone_number = method.phoneNumber;
1789
+ if (data.phone_number === void 0)
1790
+ data.phone_number = method.phoneNumber;
1791
+ }
1792
+ } else if (method.recipeId === "thirdparty") {
1793
+ const thirdPartyId = getThirdPartyId(method);
1794
+ const thirdPartyUserId = getThirdPartyUserId(method);
1795
+ if (method.verified && method.email) {
1796
+ verifiedData.email = method.email;
1797
+ }
1798
+ if (method.email && data.email === void 0) {
1799
+ data.email = method.email;
1800
+ }
1801
+ if (thirdPartyId === "google" && thirdPartyUserId) {
1802
+ data.google_id = thirdPartyUserId;
1803
+ verifiedData.google_id = thirdPartyUserId;
1804
+ }
1805
+ if (thirdPartyId === "apple" && thirdPartyUserId) {
1806
+ data.apple_id = thirdPartyUserId;
1807
+ verifiedData.apple_id = thirdPartyUserId;
1808
+ }
1809
+ } else if (method.recipeId === "emailpassword") {
1810
+ if (method.email && data.email === void 0) {
1811
+ data.email = method.email;
1812
+ }
1813
+ }
2163
1814
  }
2164
- const branding = app.branding ?? {};
2165
- const auth = app.auth ?? {};
2166
- const signInMethods = buildSignInMethodsConfig(app.signInMethods);
2167
- const variant = getSubBrandVariant(app);
2168
- const finalSchema = { ...userSchema };
2169
- if (signInMethods.email.enabled && !finalSchema.email) {
2170
- finalSchema.email = {
2171
- display_name: "Email",
2172
- type: "string",
2173
- user_visible: true
2174
- };
1815
+ if (verifiedData.email === true && typeof data.email === "string") {
1816
+ verifiedData.email = data.email;
2175
1817
  }
2176
- if (signInMethods.phone.enabled && !finalSchema.phone_number) {
2177
- finalSchema.phone_number = {
2178
- display_name: "Phone number",
2179
- type: "string",
2180
- user_visible: true
2181
- };
1818
+ if (verifiedData.phone_number === true && typeof data.phone_number === "string") {
1819
+ verifiedData.phone_number = data.phone_number;
2182
1820
  }
2183
- if (signInMethods.google.enabled && !finalSchema.google_id) {
2184
- finalSchema.google_id = {
2185
- display_name: "Google ID",
2186
- type: "string",
2187
- user_visible: false
2188
- };
1821
+ const anonymousId = getAnonymousId(stUser.id, stUser, metadata);
1822
+ if (anonymousId && data.anonymous_id === void 0) {
1823
+ data.anonymous_id = anonymousId;
2189
1824
  }
2190
- if (signInMethods.apple.enabled && !finalSchema.apple_id) {
2191
- finalSchema.apple_id = {
2192
- display_name: "Apple ID",
2193
- type: "string",
2194
- user_visible: false
2195
- };
1825
+ const authLevel = getEffectiveAuthLevel(
1826
+ stUser,
1827
+ originalRowndUser?.auth_level,
1828
+ verifiedData
1829
+ );
1830
+ for (const [key, field] of Object.entries(schema)) {
1831
+ if (data[key] === void 0 && field.type === "string") {
1832
+ data[key] = "";
1833
+ }
2196
1834
  }
1835
+ const sortedByJoined = [...stUser.loginMethods].sort(
1836
+ (a, b) => a.timeJoined - b.timeJoined
1837
+ );
1838
+ const latestSessionInfo = await getLatestSessionInfo(stUser.id);
1839
+ const firstMethod = sortedByJoined[0];
1840
+ const latestSessionRecipeUserId = latestSessionInfo?.recipeUserId.getAsString();
1841
+ const lastMethod = latestSessionRecipeUserId ? stUser.loginMethods.find(
1842
+ (method) => method.recipeUserId.getAsString() === latestSessionRecipeUserId
1843
+ ) : [...stUser.loginMethods].sort((a, b) => b.timeJoined - a.timeJoined)[0];
1844
+ const lastSignInAt = latestSessionInfo?.timeCreated ?? stUser.timeJoined;
1845
+ const metadataMeta = Object.fromEntries(
1846
+ Object.entries(metadata).filter(
1847
+ ([key]) => !isInternalMetadataField(key) && !dataFieldKeys.has(key)
1848
+ )
1849
+ );
1850
+ const meta = {
1851
+ ...metadataMeta,
1852
+ created: new Date(stUser.timeJoined).toISOString(),
1853
+ first_sign_in: new Date(stUser.timeJoined).toISOString(),
1854
+ last_sign_in: new Date(lastSignInAt).toISOString(),
1855
+ last_active: new Date(lastSignInAt).toISOString(),
1856
+ first_sign_in_method: firstMethod ? mapMethod(firstMethod) : "email",
1857
+ last_sign_in_method: lastMethod ? mapMethod(lastMethod) : "email"
1858
+ };
2197
1859
  return {
2198
- config_type: appVariantId ? "variant" : "app",
2199
- ...variant ? { variant } : {},
2200
- app: {
2201
- id: app.id ?? "",
2202
- name: app.name ?? stConfig.appInfo.appName,
2203
- icon: app.icon ?? "",
2204
- ...app.userVerificationFields ? { user_verification_fields: app.userVerificationFields } : {},
2205
- schema: Object.fromEntries(
2206
- Object.entries(finalSchema).map(([key, field]) => [
2207
- key,
2208
- normalizeSchemaField(key, field)
2209
- ])
1860
+ rownd_user: rowndUser,
1861
+ data,
1862
+ meta,
1863
+ verified_data: verifiedData,
1864
+ state,
1865
+ auth_level: authLevel,
1866
+ redacted: [],
1867
+ groups: originalRowndUser?.groups || [],
1868
+ attributes: originalRowndUser?.attributes || {}
1869
+ };
1870
+ }
1871
+ async function getLatestSessionInfo(userId) {
1872
+ const sessionHandles = await import_session.default.getAllSessionHandlesForUser(
1873
+ userId,
1874
+ true,
1875
+ PUBLIC_TENANT_ID
1876
+ );
1877
+ const sessionInfos = await Promise.all(
1878
+ sessionHandles.map(
1879
+ (sessionHandle) => import_session.default.getSessionInformation(sessionHandle)
1880
+ )
1881
+ );
1882
+ let latestSessionInfo;
1883
+ for (const sessionInfo of sessionInfos) {
1884
+ if (sessionInfo && (!latestSessionInfo || sessionInfo.timeCreated > latestSessionInfo.timeCreated)) {
1885
+ latestSessionInfo = sessionInfo;
1886
+ }
1887
+ }
1888
+ return latestSessionInfo;
1889
+ }
1890
+ async function updateUserData(userId, inputData) {
1891
+ const metadata = await getUserMetadata(userId);
1892
+ const updatedMetadata = {
1893
+ ...metadata,
1894
+ ...inputData
1895
+ };
1896
+ await import_usermetadata2.default.updateUserMetadata(userId, updatedMetadata);
1897
+ return getUserById(userId);
1898
+ }
1899
+ async function startPendingEmailVerification(input) {
1900
+ const metadata = await getUserMetadata(input.userId);
1901
+ const currentEmail = (await getUserById(input.userId)).data.email;
1902
+ const pendingVerifications = getPendingVerifications(metadata);
1903
+ const pendingEmailVerifications = pendingVerifications.filter(
1904
+ (pendingVerification2) => pendingVerification2.field === "email"
1905
+ );
1906
+ if (currentEmail === input.email) {
1907
+ for (const pendingVerification2 of pendingEmailVerifications) {
1908
+ await import_emailverification.default.revokeEmailVerificationTokens(
1909
+ input.tenantId,
1910
+ input.recipeUserId,
1911
+ pendingVerification2.value,
1912
+ input.userContext
1913
+ );
1914
+ }
1915
+ if (pendingEmailVerifications.length > 0) {
1916
+ await import_usermetadata2.default.updateUserMetadata(input.userId, {
1917
+ ...metadata,
1918
+ rownd_pending_verification: pendingVerifications.filter(
1919
+ (pendingVerification2) => pendingVerification2.field !== "email"
1920
+ )
1921
+ });
1922
+ }
1923
+ return getUserById(input.userId);
1924
+ }
1925
+ for (const pendingVerification2 of pendingEmailVerifications) {
1926
+ await import_emailverification.default.revokeEmailVerificationTokens(
1927
+ input.tenantId,
1928
+ input.recipeUserId,
1929
+ pendingVerification2.value,
1930
+ input.userContext
1931
+ );
1932
+ }
1933
+ const pendingVerification = {
1934
+ id: input.pendingVerificationId,
1935
+ field: "email",
1936
+ value: input.email,
1937
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
1938
+ };
1939
+ await import_usermetadata2.default.updateUserMetadata(input.userId, {
1940
+ ...metadata,
1941
+ rownd_pending_verification: [
1942
+ ...pendingVerifications.filter(
1943
+ (pendingVerification2) => pendingVerification2.field !== "email"
2210
1944
  ),
2211
- config: {
2212
- ...app.capabilities ? { capabilities: app.capabilities } : {},
2213
- ...app.web ? { web: app.web } : {},
2214
- ...app.bottomSheet ? { bottom_sheet: app.bottomSheet } : {},
2215
- ...app.profileStorageVersion ? { profile_storage_version: app.profileStorageVersion } : {},
2216
- customizations: {
2217
- primary_color: branding.primaryColor ?? DEFAULT_PRIMARY_COLOR,
2218
- ...branding.logo ? { logo: branding.logo } : {},
2219
- ...branding.logoDarkMode ? { logo_dark_mode: branding.logoDarkMode } : {},
2220
- ...branding.animations ? { animations: branding.animations } : {}
1945
+ pendingVerification
1946
+ ]
1947
+ });
1948
+ const response = await import_emailverification.default.sendEmailVerificationEmail(
1949
+ input.tenantId,
1950
+ input.userId,
1951
+ input.recipeUserId,
1952
+ input.email,
1953
+ {
1954
+ ...input.userContext,
1955
+ rowndPendingVerificationId: pendingVerification.id
1956
+ }
1957
+ );
1958
+ if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") {
1959
+ await completePendingEmailVerification({
1960
+ recipeUserId: input.recipeUserId,
1961
+ email: input.email,
1962
+ userContext: input.userContext
1963
+ });
1964
+ }
1965
+ return getUserById(input.userId);
1966
+ }
1967
+ async function completePendingEmailVerification(input) {
1968
+ const user = await import_supertokens_node2.default.getUser(
1969
+ input.recipeUserId.getAsString(),
1970
+ input.userContext
1971
+ );
1972
+ const userId = user?.id ?? input.recipeUserId.getAsString();
1973
+ const metadata = await getUserMetadata(userId);
1974
+ const pendingVerifications = getPendingVerifications(metadata);
1975
+ const pendingVerification = pendingVerifications.find(
1976
+ (pendingVerification2) => isMatchingPendingEmailVerification(
1977
+ pendingVerification2,
1978
+ input.email
1979
+ )
1980
+ );
1981
+ if (!pendingVerification) {
1982
+ return;
1983
+ }
1984
+ let metadataUserId = userId;
1985
+ const passwordlessEmailMethod = getPasswordlessEmailLoginMethod(user);
1986
+ if (passwordlessEmailMethod) {
1987
+ const updateResult = await import_passwordless.default.updateUser({
1988
+ recipeUserId: passwordlessEmailMethod.recipeUserId,
1989
+ email: input.email,
1990
+ userContext: input.userContext
1991
+ });
1992
+ if (updateResult.status !== "OK") {
1993
+ throw new Error(
1994
+ `Failed to update verified email method: ${updateResult.status}`
1995
+ );
1996
+ }
1997
+ } else if (hasOnlyGuestLoginMethods(user)) {
1998
+ const isPasswordlessSignUpAllowed = await import_accountlinking.default.isSignUpAllowed(
1999
+ PUBLIC_TENANT_ID,
2000
+ {
2001
+ recipeId: "passwordless",
2002
+ email: input.email
2003
+ },
2004
+ true,
2005
+ void 0,
2006
+ input.userContext
2007
+ );
2008
+ if (!isPasswordlessSignUpAllowed) {
2009
+ throw new Error("Passwordless sign up is not allowed for this email");
2010
+ }
2011
+ const passwordlessUser = await import_passwordless.default.signInUp({
2012
+ email: input.email,
2013
+ tenantId: PUBLIC_TENANT_ID,
2014
+ userContext: input.userContext
2015
+ });
2016
+ const primaryUserResult = await import_accountlinking.default.createPrimaryUser(
2017
+ passwordlessUser.recipeUserId,
2018
+ input.userContext
2019
+ );
2020
+ const primaryUserId = primaryUserResult.status === "OK" ? primaryUserResult.user.id : primaryUserResult.status === "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR" ? primaryUserResult.primaryUserId : passwordlessUser.user.id;
2021
+ if (userId !== primaryUserId) {
2022
+ const linkResult = await import_accountlinking.default.linkAccounts(
2023
+ input.recipeUserId,
2024
+ primaryUserId,
2025
+ input.userContext
2026
+ );
2027
+ if (linkResult.status !== "OK") {
2028
+ throw new Error(
2029
+ `Failed to link verified email method: ${linkResult.status}`
2030
+ );
2031
+ }
2032
+ }
2033
+ metadataUserId = primaryUserId;
2034
+ }
2035
+ const targetMetadata = metadataUserId === userId ? metadata : await getUserMetadata(metadataUserId);
2036
+ const originalRowndUser = targetMetadata.original_rownd_user ?? metadata.original_rownd_user;
2037
+ const targetPendingVerifications = getPendingVerifications(targetMetadata);
2038
+ const updatedMetadata = {
2039
+ ...targetMetadata,
2040
+ ...originalRowndUser ? {
2041
+ original_rownd_user: {
2042
+ ...originalRowndUser,
2043
+ data: {
2044
+ ...originalRowndUser.data,
2045
+ email: input.email
2221
2046
  },
2222
- hub: {
2223
- ...app.allowedWebOrigins ? { allowed_web_origins: app.allowedWebOrigins } : {},
2224
- customizations: {
2225
- rounded_corners: branding.roundedCorners ?? true,
2226
- ...branding.containerBorderRadius !== void 0 ? { container_border_radius: branding.containerBorderRadius } : {},
2227
- ...branding.placement !== void 0 ? { placement: branding.placement } : {},
2228
- ...branding.hubPrimaryColor !== void 0 ? { primary_color: branding.hubPrimaryColor } : {},
2229
- ...branding.primaryColorDarkMode !== void 0 ? { primary_color_dark_mode: branding.primaryColorDarkMode } : {},
2230
- ...branding.backgroundColor !== void 0 ? { background_color: branding.backgroundColor } : {},
2231
- ...branding.fontFamily !== void 0 ? { font_family: branding.fontFamily } : {},
2232
- ...branding.hideVerificationIcons !== void 0 ? { hide_verification_icons: branding.hideVerificationIcons } : {},
2233
- visual_swoops: branding.visualSwoops ?? true,
2234
- blur_background: branding.blurBackground ?? true,
2235
- ...branding.blurBackgroundOpacity !== void 0 ? { blur_background_opacity: branding.blurBackgroundOpacity } : {},
2236
- ...branding.offsetX !== void 0 ? { offset_x: branding.offsetX } : {},
2237
- ...branding.offsetY !== void 0 ? { offset_y: branding.offsetY } : {},
2238
- ...branding.propertyOverrides ? { property_overrides: branding.propertyOverrides } : {},
2239
- dark_mode: branding.darkMode ?? "auto"
2240
- },
2241
- ...branding.customScripts ? { custom_scripts: branding.customScripts } : {},
2242
- ...branding.customStyles ? { custom_styles: branding.customStyles } : {},
2243
- auth: {
2244
- email: buildAuthEmailConfig(auth.email),
2245
- ...auth.mobile ? { mobile: buildAuthMobileConfig(auth.mobile) } : {},
2246
- sign_in_methods: signInMethods,
2247
- additional_fields: auth.additionalFields ?? [],
2248
- ...auth.rememberSignInMethod !== void 0 ? { remember_sign_in_method: auth.rememberSignInMethod } : {},
2249
- ...auth.useExplicitSignUpFlow !== void 0 ? { use_explicit_sign_up_flow: auth.useExplicitSignUpFlow } : {},
2250
- ...auth.allowUnverifiedUsers !== void 0 ? { allow_unverified_users: auth.allowUnverifiedUsers } : {},
2251
- ...auth.primarySignUpMethod ? { primary_sign_up_method: auth.primarySignUpMethod } : {},
2252
- ...auth.preferredMethod ? { preferred_method: auth.preferredMethod } : {},
2253
- ...auth.order ? { order: auth.order } : {},
2254
- ...isInstantAnonymousMethod(app.signInMethods) ? { instant_user: { enabled: true } } : {},
2255
- show_app_icon: branding.showAppIcon ?? false
2256
- },
2257
- legal: {
2258
- ...app.legal?.companyName ? { company_name: app.legal.companyName } : {},
2259
- ...app.legal?.privacyPolicyUrl ? { privacy_policy_url: app.legal.privacyPolicyUrl } : {},
2260
- ...app.legal?.termsConditionsUrl ? { terms_conditions_url: app.legal.termsConditionsUrl } : {},
2261
- ...app.legal?.supportEmail ? { support_email: app.legal.supportEmail } : {}
2262
- },
2263
- custom_content: {
2264
- ...app.customContent?.signInModal ? {
2265
- sign_in_modal: {
2266
- ...app.customContent.signInModal.title ? { title: app.customContent.signInModal.title } : {},
2267
- ...app.customContent.signInModal.subtitle ? { subtitle: app.customContent.signInModal.subtitle } : {},
2268
- ...app.customContent.signInModal.signInTitle ? {
2269
- sign_in_title: app.customContent.signInModal.signInTitle
2270
- } : {},
2271
- ...app.customContent.signInModal.signUpTitle ? {
2272
- sign_up_title: app.customContent.signInModal.signUpTitle
2273
- } : {},
2274
- ...app.customContent.signInModal.signInSubtitle ? {
2275
- sign_in_subtitle: app.customContent.signInModal.signInSubtitle
2276
- } : {},
2277
- ...app.customContent.signInModal.signUpSubtitle ? {
2278
- sign_up_subtitle: app.customContent.signInModal.signUpSubtitle
2279
- } : {},
2280
- ...app.customContent.signInModal.signInButton ? {
2281
- sign_in_button: app.customContent.signInModal.signInButton
2282
- } : {},
2283
- ...app.customContent.signInModal.signUpButton ? {
2284
- sign_up_button: app.customContent.signInModal.signUpButton
2285
- } : {}
2286
- }
2287
- } : {},
2288
- ...app.customContent?.profileModal ? { profile_modal: app.customContent.profileModal } : {},
2289
- ...app.customContent?.verificationModal ? {
2290
- verification_modal: {
2291
- ...app.customContent.verificationModal.title ? { title: app.customContent.verificationModal.title } : {},
2292
- ...app.customContent.verificationModal.subtitle ? { subtitle: app.customContent.verificationModal.subtitle } : {}
2293
- }
2294
- } : {},
2295
- ...app.customContent?.signInFailureModal ? {
2296
- sign_in_failure_modal: {
2297
- failure_message: app.customContent.signInFailureModal.failureMessage
2298
- }
2299
- } : {},
2300
- ...app.customContent?.noAccountMessage ? { no_account_message: app.customContent.noAccountMessage } : {},
2301
- ...app.customContent?.mobile ? { mobile: app.customContent.mobile } : {}
2302
- },
2303
- profile: {
2304
- ...app.profile?.accountInformation ? { account_information: app.profile.accountInformation } : {},
2305
- ...app.profile?.personalInformation ? { personal_information: app.profile.personalInformation } : {},
2306
- ...app.profile?.preferences ? { preferences: app.profile.preferences } : {},
2307
- ...app.profile?.signOutButton ? { sign_out_button: app.profile.signOutButton } : {},
2308
- ...app.profile?.deleteAccountButton ? { delete_account_button: app.profile.deleteAccountButton } : {},
2309
- ...app.profile?.addSignInMethodsButton ? { add_sign_in_methods_button: app.profile.addSignInMethodsButton } : {}
2047
+ verified_data: {
2048
+ ...originalRowndUser.verified_data,
2049
+ email: input.email
2050
+ }
2051
+ }
2052
+ } : {},
2053
+ rownd_pending_verification: targetPendingVerifications.filter(
2054
+ (verification) => !isMatchingPendingEmailVerification(
2055
+ verification,
2056
+ input.email
2057
+ )
2058
+ )
2059
+ };
2060
+ await import_usermetadata2.default.updateUserMetadata(metadataUserId, updatedMetadata);
2061
+ }
2062
+ function isMatchingPendingEmailVerification(verification, email) {
2063
+ return verification.field === "email" && verification.value === email;
2064
+ }
2065
+ async function updateUserMetadata(userId, inputMeta) {
2066
+ const metadata = await getUserMetadata(userId);
2067
+ const updatedMetadata = {
2068
+ ...metadata,
2069
+ ...inputMeta
2070
+ };
2071
+ await import_usermetadata2.default.updateUserMetadata(userId, updatedMetadata);
2072
+ return {
2073
+ id: userId,
2074
+ meta: Object.fromEntries(
2075
+ Object.entries(updatedMetadata).filter(
2076
+ ([key]) => !isInternalMetadataField(key)
2077
+ )
2078
+ )
2079
+ };
2080
+ }
2081
+ function getPasswordlessEmailLoginMethod(user) {
2082
+ return user?.loginMethods.find((method) => {
2083
+ return method.recipeId === "passwordless" && !!method.email;
2084
+ });
2085
+ }
2086
+
2087
+ // src/pluginImplementation.ts
2088
+ var import_crypto = require("crypto");
2089
+ var import_supertokens_node3 = __toESM(require("supertokens-node"));
2090
+ var import_session2 = __toESM(require("supertokens-node/recipe/session"));
2091
+ var import_thirdparty = __toESM(require("supertokens-node/recipe/thirdparty"));
2092
+ function handleGetAppConfig(deps) {
2093
+ return async (req) => {
2094
+ const appVariantId = getRequestedAppVariantIdFromRequest(req);
2095
+ const appConfig = buildRowndAppConfig(
2096
+ deps.pluginConfig,
2097
+ deps.stConfig,
2098
+ appVariantId
2099
+ );
2100
+ if (!appConfig) {
2101
+ return {
2102
+ status: "ERROR",
2103
+ message: `Unknown Rownd app variant: ${appVariantId}`
2104
+ };
2105
+ }
2106
+ return {
2107
+ status: "OK",
2108
+ ...appConfig
2109
+ };
2110
+ };
2111
+ }
2112
+ function handleGuestLogin(deps) {
2113
+ return async (req, res, _session, userContext) => {
2114
+ const startedAt = Date.now();
2115
+ const guestId = `guest_${(0, import_crypto.randomUUID)()}`;
2116
+ try {
2117
+ const body = parseGuestBody(await getJsonBody(req));
2118
+ const appVariantId = getRequestedAppVariantIdFromRequest(req);
2119
+ assertRowndAppVariantIsConfigured(appVariantId);
2120
+ const thirdPartyId = body.authLevel === ANONYMOUS_AUTH_METHOD_ID ? ANONYMOUS_AUTH_METHOD_ID : GUEST_AUTH_METHOD_ID;
2121
+ const thirdPartyUserId = thirdPartyId === ANONYMOUS_AUTH_METHOD_ID ? `anon_${(0, import_crypto.randomUUID)()}` : guestId;
2122
+ const response = await import_thirdparty.default.manuallyCreateOrUpdateUser(
2123
+ PUBLIC_TENANT_ID,
2124
+ thirdPartyId,
2125
+ thirdPartyUserId,
2126
+ `${thirdPartyUserId}@anonymous.local`,
2127
+ false,
2128
+ void 0,
2129
+ userContext
2130
+ );
2131
+ if (response.status !== "OK") {
2132
+ throw new Error(
2133
+ `Guest user creation failed with status: ${response.status}`
2134
+ );
2135
+ }
2136
+ await import_session2.default.createNewSession(
2137
+ req,
2138
+ res,
2139
+ PUBLIC_TENANT_ID,
2140
+ response.recipeUserId,
2141
+ {
2142
+ ...buildRowndAudience({}, appVariantId),
2143
+ auth_level: GUEST_AUTH_METHOD_ID,
2144
+ is_anonymous: true,
2145
+ app_user_id: response.user.id
2146
+ },
2147
+ {},
2148
+ userContext
2149
+ );
2150
+ logDebugMessage(`Guest session created for user: ${response.user.id}`);
2151
+ deps.telemetryClient.recordSuccess({
2152
+ outcome: "success",
2153
+ durationMs: Date.now() - startedAt,
2154
+ tenantId: PUBLIC_TENANT_ID,
2155
+ superTokensUserId: response.user.id
2156
+ });
2157
+ return {
2158
+ status: "OK",
2159
+ createdNewRecipeUser: response.createdNewRecipeUser
2160
+ };
2161
+ } catch (error) {
2162
+ logDebugMessage(`Guest login failed. Error: ${getErrorMessage(error)}`);
2163
+ deps.telemetryClient.recordError({
2164
+ error,
2165
+ startedAt,
2166
+ tenantId: PUBLIC_TENANT_ID
2167
+ });
2168
+ return {
2169
+ status: "ERROR",
2170
+ message: "Guest login failed"
2171
+ };
2172
+ }
2173
+ };
2174
+ }
2175
+ function handleMigrate(deps) {
2176
+ return async (req, res, _session, userContext) => {
2177
+ const startedAt = Date.now();
2178
+ let tenantId = PUBLIC_TENANT_ID;
2179
+ let rowndUserId;
2180
+ let superTokensUserId;
2181
+ let user;
2182
+ let recipeUserId;
2183
+ try {
2184
+ if (!deps.stConfig.supertokens) {
2185
+ throw new Error("Supertokens config not found");
2186
+ }
2187
+ const parsed = await parseRequest(req);
2188
+ const appVariantId = getRequestedAppVariantIdFromRequest(req);
2189
+ assertRowndAppVariantIsConfigured(appVariantId);
2190
+ rowndUserId = await validateRowndToken(parsed.token);
2191
+ user = await import_supertokens_node3.default.getUser(rowndUserId, userContext);
2192
+ if (!user) {
2193
+ const rowndUser = await fetchRowndUserInfo(rowndUserId);
2194
+ const stUserImport = mapRowndUserToSuperTokens(rowndUser);
2195
+ try {
2196
+ const importedUser = await importUser(
2197
+ stUserImport,
2198
+ deps.stConfig.supertokens
2199
+ );
2200
+ superTokensUserId = importedUser.id;
2201
+ if (importedUser.loginMethods[0]?.recipeUserId) {
2202
+ recipeUserId = import_supertokens_node3.default.convertToRecipeUserId(
2203
+ importedUser.loginMethods[0].recipeUserId
2204
+ );
2205
+ }
2206
+ } catch (err) {
2207
+ user = await import_supertokens_node3.default.getUser(rowndUserId, userContext);
2208
+ if (!user) {
2209
+ throw err;
2310
2210
  }
2211
+ superTokensUserId = user.id;
2212
+ recipeUserId = user.loginMethods[0]?.recipeUserId;
2213
+ logDebugMessage(
2214
+ `User already migrated (race condition). tenantId: ${PUBLIC_TENANT_ID}, rowndUserId: ${rowndUserId}`
2215
+ );
2311
2216
  }
2217
+ logDebugMessage(
2218
+ `User migrated successfully. tenantId: ${PUBLIC_TENANT_ID}, rowndUserId: ${rowndUserId}`
2219
+ );
2220
+ } else {
2221
+ superTokensUserId = user.id;
2222
+ recipeUserId = user.loginMethods[0]?.recipeUserId;
2223
+ logDebugMessage(
2224
+ `User already migrated. tenantId: ${PUBLIC_TENANT_ID}, rowndUserId: ${rowndUserId}`
2225
+ );
2226
+ }
2227
+ if (superTokensUserId) {
2228
+ await recordRowndAppVariantForUser(superTokensUserId, appVariantId);
2229
+ }
2230
+ if (!recipeUserId) {
2231
+ throw new Error("User not found or has no login methods");
2312
2232
  }
2233
+ await import_session2.default.createNewSession(
2234
+ req,
2235
+ res,
2236
+ PUBLIC_TENANT_ID,
2237
+ recipeUserId,
2238
+ {
2239
+ ...buildRowndAudience({}, appVariantId)
2240
+ },
2241
+ {},
2242
+ userContext
2243
+ );
2244
+ logDebugMessage(
2245
+ `Session migrated successfully. tenantId: ${PUBLIC_TENANT_ID}, userId: ${superTokensUserId}`
2246
+ );
2247
+ deps.telemetryClient.recordSuccess({
2248
+ outcome: "success",
2249
+ durationMs: Date.now() - startedAt,
2250
+ tenantId,
2251
+ rowndUserId,
2252
+ superTokensUserId
2253
+ });
2254
+ return { status: "OK" };
2255
+ } catch (error) {
2256
+ logDebugMessage(`Migration failed. Error: ${getErrorMessage(error)}`);
2257
+ deps.telemetryClient.recordError({
2258
+ error,
2259
+ startedAt,
2260
+ tenantId,
2261
+ rowndUserId,
2262
+ superTokensUserId
2263
+ });
2264
+ return {
2265
+ status: "ERROR",
2266
+ message: error instanceof RowndPluginError ? error.message : "Migration failed"
2267
+ };
2313
2268
  }
2314
2269
  };
2315
2270
  }
2316
- function buildAuthEmailConfig(authEmail) {
2317
- return {
2318
- from_address: authEmail?.fromAddress ?? "no-reply@rownd.io",
2319
- image: authEmail?.image ?? "",
2320
- ...authEmail?.subject ? { subject: authEmail.subject } : {},
2321
- ...authEmail?.callToActionText ? { call_to_action_text: authEmail.callToActionText } : {},
2322
- ...authEmail?.verifyTemplate ? { verify_template: authEmail.verifyTemplate } : {},
2323
- ...authEmail?.customContent ? { custom_content: authEmail.customContent } : {},
2324
- ...authEmail?.customClosingContent ? { custom_closing_content: authEmail.customClosingContent } : {}
2271
+ function handleGetUser() {
2272
+ return async (_req, _res, session) => {
2273
+ const user = await getUserById(session.getUserId());
2274
+ return {
2275
+ status: "OK",
2276
+ ...user
2277
+ };
2325
2278
  };
2326
2279
  }
2327
- function buildAuthMobileConfig(authMobile) {
2280
+ function handleUpdateUser() {
2281
+ return async (req, _res, session, userContext) => {
2282
+ const appVariantId = getRequestedAppVariantIdFromRequest(req);
2283
+ assertRowndAppVariantIsConfigured(appVariantId);
2284
+ const payload = parseUpdateUserBody(await getJsonBody(req));
2285
+ const inputData = payload.data ?? {};
2286
+ const requestUserContext = {
2287
+ ...userContext,
2288
+ ...payload.context
2289
+ };
2290
+ const { email, ...dataWithoutEmail } = inputData;
2291
+ const hasEmailUpdate = hasOwn(inputData, "email") && typeof email === "string";
2292
+ const permissionError = validateWritableFields(
2293
+ Object.keys(dataWithoutEmail)
2294
+ );
2295
+ if (permissionError) {
2296
+ return permissionError;
2297
+ }
2298
+ if (Object.keys(dataWithoutEmail).length > 0) {
2299
+ await updateUserData(session.getUserId(), dataWithoutEmail);
2300
+ }
2301
+ if (hasEmailUpdate) {
2302
+ const pendingVerificationResult = await startPendingEmailVerification({
2303
+ userId: session.getUserId(),
2304
+ recipeUserId: session.getRecipeUserId(),
2305
+ tenantId: session.getTenantId(),
2306
+ email,
2307
+ pendingVerificationId: (0, import_crypto.randomUUID)(),
2308
+ userContext: appVariantId ? { ...requestUserContext, rowndAppVariantId: appVariantId } : requestUserContext
2309
+ });
2310
+ return {
2311
+ status: "OK",
2312
+ ...pendingVerificationResult
2313
+ };
2314
+ }
2315
+ const user = await getUserById(session.getUserId());
2316
+ return {
2317
+ status: "OK",
2318
+ ...user
2319
+ };
2320
+ };
2321
+ }
2322
+ function handleDeleteUser() {
2323
+ return async (_req, _res, session) => {
2324
+ await import_supertokens_node3.default.deleteUser(session.getUserId(), true);
2325
+ return { status: "OK" };
2326
+ };
2327
+ }
2328
+ function handleSignOut() {
2329
+ return async (_req, _res, session, userContext) => {
2330
+ await import_session2.default.revokeAllSessionsForUser(
2331
+ session.getUserId(),
2332
+ true,
2333
+ session.getTenantId(),
2334
+ userContext
2335
+ );
2336
+ return { status: "OK" };
2337
+ };
2338
+ }
2339
+ function handleGetUserMeta() {
2340
+ return async (_req, _res, session) => {
2341
+ const metadata = await getUserMetadata(session.getUserId());
2342
+ return {
2343
+ status: "OK",
2344
+ id: session.getUserId(),
2345
+ meta: Object.fromEntries(
2346
+ Object.entries(metadata).filter(
2347
+ ([key]) => !isInternalMetadataField(key)
2348
+ )
2349
+ )
2350
+ };
2351
+ };
2352
+ }
2353
+ function handleUpdateUserMeta() {
2354
+ return async (req, _res, session) => {
2355
+ const payload = parseUpdateMetaBody(await getJsonBody(req));
2356
+ const internalField = Object.keys(payload.meta ?? {}).find(
2357
+ isInternalMetadataField
2358
+ );
2359
+ if (internalField) {
2360
+ return {
2361
+ status: "ERROR",
2362
+ code: 403,
2363
+ message: `field is not writable: ${internalField}`
2364
+ };
2365
+ }
2366
+ const updateMetadataResult = await updateUserMetadata(
2367
+ session.getUserId(),
2368
+ payload.meta ?? {}
2369
+ );
2370
+ return {
2371
+ status: "OK",
2372
+ ...updateMetadataResult
2373
+ };
2374
+ };
2375
+ }
2376
+ function handleGetUserField() {
2377
+ return async (req, _res, session) => {
2378
+ const field = req.getKeyValueFromQuery("field");
2379
+ if (!field) {
2380
+ return missingFieldResponse();
2381
+ }
2382
+ const user = await getUserById(session.getUserId());
2383
+ return {
2384
+ status: "OK",
2385
+ value: user.data[field]
2386
+ };
2387
+ };
2388
+ }
2389
+ function handleUpdateUserField() {
2390
+ return async (req, _res, session, userContext) => {
2391
+ const appVariantId = getRequestedAppVariantIdFromRequest(req);
2392
+ assertRowndAppVariantIsConfigured(appVariantId);
2393
+ const field = req.getKeyValueFromQuery("field");
2394
+ if (!field) {
2395
+ return missingFieldResponse();
2396
+ }
2397
+ const payload = parseUpdateFieldBody(await getJsonBody(req));
2398
+ if (field === "email" && typeof payload.value === "string") {
2399
+ const pendingVerificationResult = await startPendingEmailVerification({
2400
+ userId: session.getUserId(),
2401
+ recipeUserId: session.getRecipeUserId(),
2402
+ tenantId: session.getTenantId(),
2403
+ email: payload.value,
2404
+ pendingVerificationId: (0, import_crypto.randomUUID)(),
2405
+ userContext: appVariantId ? { ...userContext, rowndAppVariantId: appVariantId } : userContext
2406
+ });
2407
+ return {
2408
+ status: "OK",
2409
+ ...pendingVerificationResult
2410
+ };
2411
+ }
2412
+ const permissionError = validateWritableFields([field]);
2413
+ if (permissionError) {
2414
+ return permissionError;
2415
+ }
2416
+ const updateUserDataResult = await updateUserData(session.getUserId(), {
2417
+ [field]: payload.value
2418
+ });
2419
+ return {
2420
+ status: "OK",
2421
+ ...updateUserDataResult
2422
+ };
2423
+ };
2424
+ }
2425
+ function validateWritableFields(fields) {
2426
+ const readOnlyField = fields.find((field) => !canUpdateUserDataField(field));
2427
+ if (!readOnlyField) {
2428
+ return void 0;
2429
+ }
2328
2430
  return {
2329
- ...authMobile?.title ? { title: authMobile.title } : {},
2330
- ...authMobile?.image ? { image: authMobile.image } : {},
2331
- ...authMobile?.callToActionText ? { call_to_action_text: authMobile.callToActionText } : {},
2332
- ...authMobile?.hyperlinkText ? { hyperlink_text: authMobile.hyperlinkText } : {},
2333
- ...authMobile?.hyperlinkRedirectUrl ? { hyperlink_redirect_url: authMobile.hyperlinkRedirectUrl } : {},
2334
- ...authMobile?.customContent ? { custom_content: authMobile.customContent } : {}
2431
+ status: "ERROR",
2432
+ code: 403,
2433
+ message: `field is not writable: ${readOnlyField}`
2335
2434
  };
2336
2435
  }
2337
2436
 
@@ -2348,6 +2447,9 @@ var init = createPluginInitFunction(
2348
2447
  const appVariantId = input?.userContext?.rowndAppVariantId;
2349
2448
  const displayContext = input?.userContext?.rowndDisplayContext;
2350
2449
  const redirectToPath = input?.userContext?.rowndRedirectToPath;
2450
+ const clientDomain = input?.userContext?.rowndClientDomain;
2451
+ const clientDomainKey = clientDomain ?? (displayContext === "mobile_app" ? "mobile" : "browser");
2452
+ const clientBaseUrl = pluginConfig2.clientDomains?.[clientDomainKey];
2351
2453
  const bootstrapParams = {
2352
2454
  appKey: pluginConfig2.rowndAppKey,
2353
2455
  ...hubBootstrapParams ?? {},
@@ -2355,9 +2457,15 @@ var init = createPluginInitFunction(
2355
2457
  ...typeof displayContext === "string" ? { displayContext } : {},
2356
2458
  ...typeof redirectToPath === "string" ? { redirectToPath } : {}
2357
2459
  };
2460
+ const rewrittenLink = input[linkKey] ? clientBaseUrl ? rewriteLinkToBaseUrl(
2461
+ input[linkKey],
2462
+ targetPath,
2463
+ clientBaseUrl,
2464
+ bootstrapParams
2465
+ ) : rewriteLinkPath(input[linkKey], targetPath, bootstrapParams) : input[linkKey];
2358
2466
  return {
2359
2467
  ...input,
2360
- [linkKey]: input[linkKey] ? rewriteLinkPath(input[linkKey], targetPath, bootstrapParams) : input[linkKey]
2468
+ [linkKey]: rewrittenLink
2361
2469
  };
2362
2470
  };
2363
2471
  setRowndClient(rowndClient2);
@@ -2370,17 +2478,17 @@ var init = createPluginInitFunction(
2370
2478
  id: PLUGIN_ID,
2371
2479
  compatibleSDKVersions: PLUGIN_SDK_VERSION,
2372
2480
  init: async () => {
2373
- if (!import_supertokens_node2.default.isRecipeInitialized("session")) {
2481
+ if (!import_supertokens_node4.default.isRecipeInitialized("session")) {
2374
2482
  console.warn(
2375
2483
  "RowndMigrationPlugin: Session recipe is not initialized. Session migration will fail."
2376
2484
  );
2377
2485
  }
2378
- if (!import_supertokens_node2.default.isRecipeInitialized("thirdparty")) {
2486
+ if (!import_supertokens_node4.default.isRecipeInitialized("thirdparty")) {
2379
2487
  console.warn(
2380
2488
  "RowndMigrationPlugin: ThirdParty recipe is not initialized. Guest login will fail."
2381
2489
  );
2382
2490
  }
2383
- if (!import_supertokens_node2.default.isRecipeInitialized("emailverification")) {
2491
+ if (!import_supertokens_node4.default.isRecipeInitialized("emailverification")) {
2384
2492
  console.warn(
2385
2493
  "RowndMigrationPlugin: EmailVerification recipe is not initialized. Verified email profile updates will fail."
2386
2494
  );
@@ -2493,13 +2601,13 @@ var init = createPluginInitFunction(
2493
2601
  return {
2494
2602
  ...implementation,
2495
2603
  sendEmail: async function(input) {
2496
- return implementation.sendEmail({
2497
- ...addHubBootstrapParams(
2604
+ return implementation.sendEmail(
2605
+ addHubBootstrapParams(
2498
2606
  input,
2499
2607
  "urlWithLinkCode",
2500
- "account/login"
2608
+ HUB_LOGIN_PAGE_PATH
2501
2609
  )
2502
- });
2610
+ );
2503
2611
  }
2504
2612
  };
2505
2613
  }
@@ -2514,13 +2622,13 @@ var init = createPluginInitFunction(
2514
2622
  return {
2515
2623
  ...implementation,
2516
2624
  sendSms: async function(input) {
2517
- return implementation.sendSms({
2518
- ...addHubBootstrapParams(
2625
+ return implementation.sendSms(
2626
+ addHubBootstrapParams(
2519
2627
  input,
2520
2628
  "urlWithLinkCode",
2521
- "account/login"
2629
+ HUB_LOGIN_PAGE_PATH
2522
2630
  )
2523
- });
2631
+ );
2524
2632
  }
2525
2633
  };
2526
2634
  }
@@ -2536,22 +2644,29 @@ var init = createPluginInitFunction(
2536
2644
  const displayContext = getRequestedDisplayContextFromRequest(
2537
2645
  input.options.req
2538
2646
  );
2647
+ if (displayContext) {
2648
+ input.userContext.rowndDisplayContext = displayContext;
2649
+ }
2539
2650
  const redirectToPath = getRequestedRedirectToPathFromRequest(
2540
2651
  input.options.req
2541
2652
  );
2653
+ if (redirectToPath) {
2654
+ input.userContext.rowndRedirectToPath = redirectToPath;
2655
+ }
2656
+ const clientDomain = getRequestedClientDomainFromRequest(
2657
+ input.options.req
2658
+ );
2659
+ if (clientDomain) {
2660
+ input.userContext.rowndClientDomain = clientDomain;
2661
+ }
2542
2662
  const appVariantId = getRequestedAppVariantIdFromRequest(
2543
2663
  input.options.req
2544
2664
  );
2665
+ if (appVariantId) {
2666
+ input.userContext.rowndAppVariantId = appVariantId;
2667
+ }
2545
2668
  assertRowndAppVariantIsConfigured(appVariantId);
2546
- return originalImplementation.createCodePOST({
2547
- ...input,
2548
- userContext: displayContext || redirectToPath || appVariantId ? {
2549
- ...input.userContext,
2550
- ...appVariantId ? { rowndAppVariantId: appVariantId } : {},
2551
- ...displayContext ? { rowndDisplayContext: displayContext } : {},
2552
- ...redirectToPath ? { rowndRedirectToPath: redirectToPath } : {}
2553
- } : input.userContext
2554
- });
2669
+ return originalImplementation.createCodePOST(input);
2555
2670
  },
2556
2671
  consumeCodePOST: async (input) => {
2557
2672
  if (originalImplementation.consumeCodePOST === void 0) {
@@ -2562,11 +2677,11 @@ var init = createPluginInitFunction(
2562
2677
  const appVariantId = getRequestedAppVariantIdFromRequest(
2563
2678
  input.options.req
2564
2679
  );
2680
+ if (appVariantId) {
2681
+ input.userContext.rowndAppVariantId = appVariantId;
2682
+ }
2565
2683
  assertRowndAppVariantIsConfigured(appVariantId);
2566
- const response = await originalImplementation.consumeCodePOST({
2567
- ...input,
2568
- userContext: appVariantId ? { ...input.userContext, rowndAppVariantId: appVariantId } : input.userContext
2569
- });
2684
+ const response = await originalImplementation.consumeCodePOST(input);
2570
2685
  if (response.status === "OK") {
2571
2686
  await recordRowndAppVariantForUser(
2572
2687
  response.user.id,
@@ -2587,11 +2702,11 @@ var init = createPluginInitFunction(
2587
2702
  const appVariantId = getRequestedAppVariantIdFromRequest(
2588
2703
  input.options.req
2589
2704
  );
2705
+ if (appVariantId) {
2706
+ input.userContext.rowndAppVariantId = appVariantId;
2707
+ }
2590
2708
  assertRowndAppVariantIsConfigured(appVariantId);
2591
- const response = await originalImplementation.signInUpPOST({
2592
- ...input,
2593
- userContext: appVariantId ? { ...input.userContext, rowndAppVariantId: appVariantId } : input.userContext
2594
- });
2709
+ const response = await originalImplementation.signInUpPOST(input);
2595
2710
  if (response.status === "OK") {
2596
2711
  await recordRowndAppVariantForUser(
2597
2712
  response.user.id,
@@ -2629,19 +2744,23 @@ var init = createPluginInitFunction(
2629
2744
  functions: (originalImplementation) => ({
2630
2745
  ...originalImplementation,
2631
2746
  createNewSession: async (input) => {
2632
- input.accessTokenPayload = {
2633
- ...input.accessTokenPayload,
2634
- ...await buildRowndSessionClaims(
2747
+ const [rowndSessionClaims, rowndIsAnonymousClaim] = await Promise.all([
2748
+ buildRowndSessionClaims(
2635
2749
  input.userId,
2636
2750
  input.accessTokenPayload
2637
2751
  ),
2638
- ...await RowndIsAnonymousClaim.build(
2752
+ RowndIsAnonymousClaim.build(
2639
2753
  input.userId,
2640
2754
  input.recipeUserId,
2641
2755
  input.tenantId,
2642
2756
  input.accessTokenPayload,
2643
2757
  input.userContext
2644
2758
  )
2759
+ ]);
2760
+ input.accessTokenPayload = {
2761
+ ...input.accessTokenPayload,
2762
+ ...rowndSessionClaims,
2763
+ ...rowndIsAnonymousClaim
2645
2764
  };
2646
2765
  return originalImplementation.createNewSession(input);
2647
2766
  }
@@ -2667,7 +2786,7 @@ var init = createPluginInitFunction(
2667
2786
  ...addHubBootstrapParams(
2668
2787
  input,
2669
2788
  "emailVerifyLink",
2670
- "account/verify-email"
2789
+ HUB_VERIFY_EMAIL_PAGE_PATH
2671
2790
  )
2672
2791
  });
2673
2792
  }
@@ -2720,10 +2839,14 @@ var init = createPluginInitFunction(
2720
2839
  );
2721
2840
  }
2722
2841
  }
2842
+ for (const [key, value] of Object.entries(config2.clientDomains ?? {})) {
2843
+ validateClientDomainUrl(key, value);
2844
+ }
2723
2845
  return {
2724
2846
  rowndAppKey: config2.rowndAppKey,
2725
2847
  rowndAppSecret: config2.rowndAppSecret,
2726
2848
  enableDebugLogs: config2.enableDebugLogs,
2849
+ clientDomains: config2.clientDomains,
2727
2850
  telemetry: config2.telemetry,
2728
2851
  schema: config2.schema,
2729
2852
  appConfig: config2.appConfig,
@@ -2731,6 +2854,16 @@ var init = createPluginInitFunction(
2731
2854
  };
2732
2855
  }
2733
2856
  );
2857
+ function validateClientDomainUrl(key, value) {
2858
+ try {
2859
+ if (typeof value !== "string" || !/^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(value)) {
2860
+ throw new Error();
2861
+ }
2862
+ new URL(value);
2863
+ } catch {
2864
+ throw new Error(`Invalid clientDomains.${key} in plugin config`);
2865
+ }
2866
+ }
2734
2867
 
2735
2868
  // src/index.ts
2736
2869
  var index_default = { init };
@@ -2740,8 +2873,11 @@ var index_default = { init };
2740
2873
  DEFAULT_ROWND_SCHEMA,
2741
2874
  GUEST_AUTH_METHOD_ID,
2742
2875
  HANDLE_BASE_PATH,
2876
+ HUB_LOGIN_PAGE_PATH,
2877
+ HUB_VERIFY_EMAIL_PAGE_PATH,
2743
2878
  PLUGIN_ID,
2744
2879
  PLUGIN_SDK_VERSION,
2880
+ PLUGIN_VERSION,
2745
2881
  PUBLIC_TENANT_ID,
2746
2882
  ROWND_JWT_CLAIMS,
2747
2883
  ROWND_PLUGIN_ERROR_MESSAGES,