@supertokens-plugins/rownd-nodejs 0.2.1 → 0.3.0-beta.1

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