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