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