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