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