@rpcbase/server 0.628.0 → 0.630.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accountDeletion.d.ts +18 -0
- package/dist/accountDeletion.d.ts.map +1 -0
- package/dist/accountDeletionUploads.d.ts +2 -0
- package/dist/accountDeletionUploads.d.ts.map +1 -0
- package/dist/{handler-BX5YTbN0.js → handler-Z6wToZhb.js} +3 -3
- package/dist/{handler-BX5YTbN0.js.map → handler-Z6wToZhb.js.map} +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +269 -2
- package/dist/index.js.map +1 -1
- package/dist/uploads/worker.js +1 -1
- package/dist/uploads.js +1 -1
- package/dist/userSessionDeletion.d.ts +13 -0
- package/dist/userSessionDeletion.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,11 +2,13 @@ import { n as __toESM, t as __commonJSMin } from "./rolldown-runtime-BPOCksWG.js
|
|
|
2
2
|
import { c as runRtsQuery, d as getDerivedKey, i as normalizeRtsQueryOptions, n as buildRtsAbilityFromRequest, o as resolveRtsRequestTenantId, r as isRtsRequestAuthorized, s as runRtsCount, t as RTS_QUERY_WINDOW_MAX_COUNT, u as syncAuthenticatedSessionMiddleware } from "./queryWindow-NCe5vJHt.js";
|
|
3
3
|
import { i as sendEmail, n as configureEmailTransport, r as createResendEmailTransport, t as EmailDeliveryError } from "./email-6gJ8ibKp.js";
|
|
4
4
|
import { DISPOSABLE_EMAIL_ERROR_CODE, INVALID_EMAIL_DOMAIN_ERROR_CODE, clearEmailDomainValidationCache, getEmailAddressDomain, getEmailAddressValidationErrorCode, isEmailAddressValid, validateEmailDomain } from "./email-validation/index.js";
|
|
5
|
+
import { l as getFileReferencesCollectionName, s as getBucketName, y as resolveStoredUploadAccessScope } from "./shared-B1VLaHsk.js";
|
|
6
|
+
import { r as deleteUploadFileReference } from "./fileReferences-Dcv9PTqD.js";
|
|
5
7
|
import session from "express-session";
|
|
6
8
|
import { RedisStore } from "connect-redis";
|
|
7
9
|
import MongoStore from "connect-mongo";
|
|
8
10
|
import { createClient } from "redis";
|
|
9
|
-
import { MongoClient } from "mongodb";
|
|
11
|
+
import { GridFSBucket, MongoClient, ObjectId } from "mongodb";
|
|
10
12
|
import env from "@rpcbase/env";
|
|
11
13
|
import { RtsSsrRuntimeProvider, SSR_ERROR_STATE_GLOBAL_KEY, STATIC_RPCBASE_RTS_HYDRATION_DATA_KEY, SsrErrorFallback, initApiClient, serializeSsrErrorState } from "@rpcbase/client";
|
|
12
14
|
import { getMongoDirectConnection, getMongoUrl } from "@rpcbase/db/mongo";
|
|
@@ -22,6 +24,7 @@ import crypto, { scrypt } from "crypto";
|
|
|
22
24
|
import httpProxy from "http-proxy-3";
|
|
23
25
|
import inspector from "node:inspector";
|
|
24
26
|
import { fileURLToPath } from "node:url";
|
|
27
|
+
import { getTenantFilesystemDb, models } from "@rpcbase/db";
|
|
25
28
|
import { Transform, Writable } from "node:stream";
|
|
26
29
|
import { StrictMode, createElement } from "react";
|
|
27
30
|
import { renderToPipeableStream, renderToStaticMarkup } from "react-dom/server";
|
|
@@ -7744,6 +7747,270 @@ var ssrMiddleware = ({ viteInstance, dataRoutes, errorExtraComponent, getHtmlOut
|
|
|
7744
7747
|
}
|
|
7745
7748
|
};
|
|
7746
7749
|
//#endregion
|
|
7747
|
-
|
|
7750
|
+
//#region src/accountDeletionUploads.ts
|
|
7751
|
+
var toTrimmedString$2 = (value) => typeof value === "string" ? value.trim() : "";
|
|
7752
|
+
var getNativeDb$1 = (connection) => {
|
|
7753
|
+
if (!connection.db) throw new Error(`Mongo database '${connection.name}' is unavailable.`);
|
|
7754
|
+
return connection.db;
|
|
7755
|
+
};
|
|
7756
|
+
var deleteGridFsFile = async (db, bucket, bucketName, fileId) => {
|
|
7757
|
+
const variants = await db.collection(`${bucketName}.files`).find({ "metadata.variantOf": fileId.toHexString() }, { projection: { _id: 1 } }).toArray();
|
|
7758
|
+
await bucket.delete(fileId);
|
|
7759
|
+
for (const variant of variants) if (variant._id instanceof ObjectId) await bucket.delete(variant._id);
|
|
7760
|
+
};
|
|
7761
|
+
var removeUserFromSharedFile = async (files, fileId) => {
|
|
7762
|
+
await files.updateMany({ $or: [{ _id: fileId }, { "metadata.variantOf": fileId.toHexString() }] }, { $unset: { "metadata.userId": "" } });
|
|
7763
|
+
};
|
|
7764
|
+
var deleteRpcbaseUserUploads = async (tenantId, userId) => {
|
|
7765
|
+
const modelCtx = { tenantId };
|
|
7766
|
+
const [UploadSession, UploadChunk] = await Promise.all([models.getUnsafe("RBUploadSession", modelCtx), models.getUnsafe("RBUploadChunk", modelCtx)]);
|
|
7767
|
+
const uploadIds = (await UploadSession.find({ userId }, { _id: 1 }).lean()).map((session) => session._id);
|
|
7768
|
+
const filesystemDb = getNativeDb$1(await getTenantFilesystemDb(tenantId));
|
|
7769
|
+
const bucketName = getBucketName();
|
|
7770
|
+
const references = filesystemDb.collection(getFileReferencesCollectionName(bucketName));
|
|
7771
|
+
const userReferences = await references.find({ userId }).toArray();
|
|
7772
|
+
for (const reference of userReferences) {
|
|
7773
|
+
if (resolveStoredUploadAccessScope(reference) === "owner") {
|
|
7774
|
+
await deleteUploadFileReference({
|
|
7775
|
+
tenantId,
|
|
7776
|
+
bucketName,
|
|
7777
|
+
referenceId: reference._id
|
|
7778
|
+
});
|
|
7779
|
+
continue;
|
|
7780
|
+
}
|
|
7781
|
+
await references.updateOne({ _id: reference._id }, { $unset: { userId: "" } });
|
|
7782
|
+
}
|
|
7783
|
+
const files = filesystemDb.collection(`${bucketName}.files`);
|
|
7784
|
+
const userFiles = await files.find({ "metadata.userId": userId }).toArray();
|
|
7785
|
+
const bucket = userFiles.length > 0 ? new GridFSBucket(filesystemDb, { bucketName }) : null;
|
|
7786
|
+
for (const file of userFiles) {
|
|
7787
|
+
if (!(file._id instanceof ObjectId) || !bucket) continue;
|
|
7788
|
+
const metadata = file.metadata ?? {};
|
|
7789
|
+
const deduplication = metadata.deduplication;
|
|
7790
|
+
const isDeduplicated = Boolean(deduplication && typeof deduplication === "object" && toTrimmedString$2(deduplication.key));
|
|
7791
|
+
if (resolveStoredUploadAccessScope(metadata) === "owner" && !isDeduplicated) {
|
|
7792
|
+
await deleteGridFsFile(filesystemDb, bucket, bucketName, file._id);
|
|
7793
|
+
continue;
|
|
7794
|
+
}
|
|
7795
|
+
await removeUserFromSharedFile(files, file._id);
|
|
7796
|
+
}
|
|
7797
|
+
await Promise.all([uploadIds.length > 0 ? UploadChunk.deleteMany({ uploadId: { $in: uploadIds } }) : Promise.resolve(), UploadSession.deleteMany({ userId })]);
|
|
7798
|
+
};
|
|
7799
|
+
//#endregion
|
|
7800
|
+
//#region src/accountDeletion.ts
|
|
7801
|
+
var RpcbaseUserAccountNotFoundError = class extends Error {
|
|
7802
|
+
constructor(userId) {
|
|
7803
|
+
super(`RPCBase user account '${userId}' was not found.`);
|
|
7804
|
+
this.name = "RpcbaseUserAccountNotFoundError";
|
|
7805
|
+
}
|
|
7806
|
+
};
|
|
7807
|
+
var toTrimmedString$1 = (value) => typeof value === "string" ? value.trim() : "";
|
|
7808
|
+
var normalizeRequiredId = (value, label) => {
|
|
7809
|
+
const normalized = value.trim();
|
|
7810
|
+
if (!normalized) throw new Error(`${label} is required.`);
|
|
7811
|
+
return normalized;
|
|
7812
|
+
};
|
|
7813
|
+
var toStringArray = (value) => {
|
|
7814
|
+
if (!Array.isArray(value)) return [];
|
|
7815
|
+
return value.map(toTrimmedString$1).filter(Boolean);
|
|
7816
|
+
};
|
|
7817
|
+
var getTenantRoleIds = (value) => {
|
|
7818
|
+
if (value instanceof Map) return Array.from(value.keys()).map(toTrimmedString$1).filter(Boolean);
|
|
7819
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return [];
|
|
7820
|
+
return Object.keys(value).map(toTrimmedString$1).filter(Boolean);
|
|
7821
|
+
};
|
|
7822
|
+
var getAccountTenantIds = (user) => Array.from(/* @__PURE__ */ new Set([...toStringArray(user.tenants), ...getTenantRoleIds(user.tenantRoles)]));
|
|
7823
|
+
var getNativeDb = (connection) => {
|
|
7824
|
+
if (!connection.db) throw new Error(`Mongo database '${connection.name}' is unavailable.`);
|
|
7825
|
+
return connection.db;
|
|
7826
|
+
};
|
|
7827
|
+
var listCollectionNames = async (connection) => {
|
|
7828
|
+
return (await getNativeDb(connection).listCollections({}, { nameOnly: true }).toArray()).map((collection) => toTrimmedString$1(collection.name)).filter((collectionName) => collectionName && !collectionName.startsWith("system."));
|
|
7829
|
+
};
|
|
7830
|
+
var buildTenantMembershipFilter = (tenantId) => {
|
|
7831
|
+
const clauses = [{ tenants: tenantId }];
|
|
7832
|
+
if (!tenantId.includes(".") && !tenantId.startsWith("$")) clauses.push({ [`tenantRoles.${tenantId}`]: { $exists: true } });
|
|
7833
|
+
return { $or: clauses };
|
|
7834
|
+
};
|
|
7835
|
+
var deleteRpcbaseTenantUserData = async (tenantId, userId) => {
|
|
7836
|
+
const modelCtx = { tenantId };
|
|
7837
|
+
const [Notification, NotificationDelivery, NotificationSettings] = await Promise.all([
|
|
7838
|
+
models.getUnsafe("RBNotification", modelCtx),
|
|
7839
|
+
models.getUnsafe("RBNotificationDelivery", modelCtx),
|
|
7840
|
+
models.getUnsafe("RBNotificationSettings", modelCtx)
|
|
7841
|
+
]);
|
|
7842
|
+
await Promise.all([
|
|
7843
|
+
Notification.deleteMany({ userId }),
|
|
7844
|
+
NotificationDelivery.deleteMany({ userId }),
|
|
7845
|
+
NotificationSettings.deleteMany({ userId }),
|
|
7846
|
+
deleteRpcbaseUserUploads(tenantId, userId)
|
|
7847
|
+
]);
|
|
7848
|
+
};
|
|
7849
|
+
var deleteTenantGlobalData = async (connection, collectionNames, userCollectionName, tenantId, session) => {
|
|
7850
|
+
for (const collectionName of collectionNames) {
|
|
7851
|
+
if (collectionName === userCollectionName) continue;
|
|
7852
|
+
const collection = connection.collection(collectionName);
|
|
7853
|
+
if (await collection.findOne({ tenantId }, {
|
|
7854
|
+
projection: { _id: 1 },
|
|
7855
|
+
session
|
|
7856
|
+
})) await collection.deleteMany({ tenantId }, { session });
|
|
7857
|
+
}
|
|
7858
|
+
};
|
|
7859
|
+
var deleteGlobalUserAccount = async ({ connection, users, userDocumentId }) => {
|
|
7860
|
+
const collectionNames = await listCollectionNames(connection);
|
|
7861
|
+
const session = await connection.startSession();
|
|
7862
|
+
let result = {
|
|
7863
|
+
tenantIds: [],
|
|
7864
|
+
deletedTenantIds: []
|
|
7865
|
+
};
|
|
7866
|
+
try {
|
|
7867
|
+
await session.withTransaction(async () => {
|
|
7868
|
+
const user = await users.findOne({ _id: userDocumentId }, {
|
|
7869
|
+
projection: {
|
|
7870
|
+
_id: 1,
|
|
7871
|
+
tenants: 1,
|
|
7872
|
+
tenantRoles: 1
|
|
7873
|
+
},
|
|
7874
|
+
session
|
|
7875
|
+
});
|
|
7876
|
+
if (!user) throw new RpcbaseUserAccountNotFoundError(String(userDocumentId));
|
|
7877
|
+
const tenantIds = getAccountTenantIds(user);
|
|
7878
|
+
const deletedTenantIds = [];
|
|
7879
|
+
for (const tenantId of tenantIds) if (!await users.findOne({
|
|
7880
|
+
_id: { $ne: userDocumentId },
|
|
7881
|
+
...buildTenantMembershipFilter(tenantId)
|
|
7882
|
+
}, {
|
|
7883
|
+
projection: { _id: 1 },
|
|
7884
|
+
session
|
|
7885
|
+
})) deletedTenantIds.push(tenantId);
|
|
7886
|
+
for (const tenantId of deletedTenantIds) await deleteTenantGlobalData(connection, collectionNames, users.collectionName, tenantId, session);
|
|
7887
|
+
if ((await users.deleteOne({ _id: userDocumentId }, { session })).deletedCount !== 1) throw new Error("RPCBase user account was not deleted.");
|
|
7888
|
+
result = {
|
|
7889
|
+
tenantIds,
|
|
7890
|
+
deletedTenantIds
|
|
7891
|
+
};
|
|
7892
|
+
});
|
|
7893
|
+
return result;
|
|
7894
|
+
} finally {
|
|
7895
|
+
await session.endSession();
|
|
7896
|
+
}
|
|
7897
|
+
};
|
|
7898
|
+
var dropTenantDatabases = async (tenantId) => {
|
|
7899
|
+
const tenantModel = await models.getUnsafe("RBNotification", { tenantId });
|
|
7900
|
+
const filesystemConnection = await getTenantFilesystemDb(tenantId);
|
|
7901
|
+
return (await Promise.allSettled([tenantModel.db.dropDatabase(), filesystemConnection.dropDatabase()])).every((result) => result.status === "fulfilled");
|
|
7902
|
+
};
|
|
7903
|
+
var deleteRpcbaseUserAccount = async ({ userId: rawUserId, deleteTenantUserData }) => {
|
|
7904
|
+
const userId = normalizeRequiredId(rawUserId, "User ID");
|
|
7905
|
+
const User = await models.getGlobal("RBUser", { req: { session: null } });
|
|
7906
|
+
const account = await User.findById(userId, {
|
|
7907
|
+
_id: 1,
|
|
7908
|
+
tenants: 1,
|
|
7909
|
+
tenantRoles: 1
|
|
7910
|
+
}).lean();
|
|
7911
|
+
if (!account?._id) throw new RpcbaseUserAccountNotFoundError(userId);
|
|
7912
|
+
const initialTenantIds = getAccountTenantIds(account);
|
|
7913
|
+
for (const tenantId of initialTenantIds) {
|
|
7914
|
+
await deleteTenantUserData?.({
|
|
7915
|
+
tenantId,
|
|
7916
|
+
userId
|
|
7917
|
+
});
|
|
7918
|
+
await deleteRpcbaseTenantUserData(tenantId, userId);
|
|
7919
|
+
}
|
|
7920
|
+
const users = User.collection;
|
|
7921
|
+
const globalDeletion = await deleteGlobalUserAccount({
|
|
7922
|
+
connection: User.db,
|
|
7923
|
+
users,
|
|
7924
|
+
userDocumentId: account._id
|
|
7925
|
+
});
|
|
7926
|
+
const failedTenantDatabaseIds = [];
|
|
7927
|
+
for (const tenantId of globalDeletion.deletedTenantIds) if (!await dropTenantDatabases(tenantId)) failedTenantDatabaseIds.push(tenantId);
|
|
7928
|
+
return {
|
|
7929
|
+
...globalDeletion,
|
|
7930
|
+
failedTenantDatabaseIds
|
|
7931
|
+
};
|
|
7932
|
+
};
|
|
7933
|
+
//#endregion
|
|
7934
|
+
//#region src/userSessionDeletion.ts
|
|
7935
|
+
var toTrimmedString = (value) => typeof value === "string" ? value.trim() : "";
|
|
7936
|
+
var getSessionStore = (value) => {
|
|
7937
|
+
if (!value || typeof value !== "object") return null;
|
|
7938
|
+
const candidate = value;
|
|
7939
|
+
return typeof candidate.destroy === "function" ? candidate : null;
|
|
7940
|
+
};
|
|
7941
|
+
var getCurrentSession = (value) => {
|
|
7942
|
+
if (!value || typeof value !== "object") return null;
|
|
7943
|
+
const candidate = value;
|
|
7944
|
+
return typeof candidate.destroy === "function" ? candidate : null;
|
|
7945
|
+
};
|
|
7946
|
+
var destroyStoredSession = async (store, sessionId) => {
|
|
7947
|
+
await new Promise((resolve, reject) => {
|
|
7948
|
+
store.destroy(sessionId, (error) => {
|
|
7949
|
+
if (error) {
|
|
7950
|
+
reject(error);
|
|
7951
|
+
return;
|
|
7952
|
+
}
|
|
7953
|
+
resolve();
|
|
7954
|
+
});
|
|
7955
|
+
});
|
|
7956
|
+
};
|
|
7957
|
+
var loadStoredSessions = async (store) => {
|
|
7958
|
+
if (!store.all) return null;
|
|
7959
|
+
return await new Promise((resolve, reject) => {
|
|
7960
|
+
store.all?.((error, sessions) => {
|
|
7961
|
+
if (error) {
|
|
7962
|
+
reject(error);
|
|
7963
|
+
return;
|
|
7964
|
+
}
|
|
7965
|
+
resolve(sessions ?? null);
|
|
7966
|
+
});
|
|
7967
|
+
});
|
|
7968
|
+
};
|
|
7969
|
+
var destroyOtherUserSessions = async (request, userId) => {
|
|
7970
|
+
const store = getSessionStore(request.sessionStore);
|
|
7971
|
+
if (!store?.all) return 0;
|
|
7972
|
+
const storedSessions = await loadStoredSessions(store);
|
|
7973
|
+
if (!storedSessions) return 0;
|
|
7974
|
+
const currentSessionId = toTrimmedString(request.sessionID);
|
|
7975
|
+
const sessionIds = (Array.isArray(storedSessions) ? storedSessions.map((session) => [toTrimmedString(session.id), session]) : Object.entries(storedSessions)).filter(([, session]) => toTrimmedString(session.user?.id) === userId).map(([sessionId]) => sessionId).filter((sessionId) => sessionId && sessionId !== currentSessionId);
|
|
7976
|
+
await Promise.all(sessionIds.map((sessionId) => destroyStoredSession(store, sessionId)));
|
|
7977
|
+
return sessionIds.length;
|
|
7978
|
+
};
|
|
7979
|
+
var destroyCurrentSession = async (request) => {
|
|
7980
|
+
const session = getCurrentSession(request.session);
|
|
7981
|
+
if (!session) return 0;
|
|
7982
|
+
session.user = void 0;
|
|
7983
|
+
await new Promise((resolve, reject) => {
|
|
7984
|
+
session.destroy((error) => {
|
|
7985
|
+
if (error) {
|
|
7986
|
+
reject(error);
|
|
7987
|
+
return;
|
|
7988
|
+
}
|
|
7989
|
+
resolve();
|
|
7990
|
+
});
|
|
7991
|
+
});
|
|
7992
|
+
return 1;
|
|
7993
|
+
};
|
|
7994
|
+
var destroyRpcbaseUserSessions = async ({ request, userId: rawUserId }) => {
|
|
7995
|
+
const userId = rawUserId.trim();
|
|
7996
|
+
if (!userId) throw new Error("User ID is required.");
|
|
7997
|
+
const errors = [];
|
|
7998
|
+
let destroyedSessionCount = 0;
|
|
7999
|
+
try {
|
|
8000
|
+
destroyedSessionCount += await destroyOtherUserSessions(request, userId);
|
|
8001
|
+
} catch (error) {
|
|
8002
|
+
errors.push(error);
|
|
8003
|
+
}
|
|
8004
|
+
try {
|
|
8005
|
+
destroyedSessionCount += await destroyCurrentSession(request);
|
|
8006
|
+
} catch (error) {
|
|
8007
|
+
errors.push(error);
|
|
8008
|
+
}
|
|
8009
|
+
if (errors.length === 1) throw errors[0];
|
|
8010
|
+
if (errors.length > 1) throw new AggregateError(errors, "Could not destroy every RPCBase user session.");
|
|
8011
|
+
return { destroyedSessionCount };
|
|
8012
|
+
};
|
|
8013
|
+
//#endregion
|
|
8014
|
+
export { DISPOSABLE_EMAIL_ERROR_CODE, EmailDeliveryError, INVALID_EMAIL_DOMAIN_ERROR_CODE, RPCBASE_SERVICE_WORKER_MESSAGE_SOURCE, RPCBASE_SERVICE_WORKER_PATH, RpcbaseUserAccountNotFoundError, bindShutdownSignals, clearEmailDomainValidationCache, configureEmailTransport, createResendEmailTransport, deleteRpcbaseUserAccount, destroyRpcbaseUserSessions, getDerivedKey, getEmailAddressDomain, getEmailAddressValidationErrorCode, hashPassword, hashPasswordForStorage, initServer, isEmailAddressValid, onShutdown, registerRpcbaseServiceWorkerRoute, rpcbaseServiceWorkerRequestHandler, rpcbaseServiceWorkerSource, sendEmail, ssrMiddleware, validateEmailDomain, verifyPasswordFromStorage };
|
|
7748
8015
|
|
|
7749
8016
|
//# sourceMappingURL=index.js.map
|