@rpcbase/db 0.135.0 → 0.136.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/README.md +19 -3
- package/dist/acl/index.d.ts +1 -0
- package/dist/acl/index.d.ts.map +1 -1
- package/dist/acl/mongooseAclAggregate.d.ts +8 -0
- package/dist/acl/mongooseAclAggregate.d.ts.map +1 -0
- package/dist/acl/mongooseAclDriver.d.ts +22 -0
- package/dist/acl/mongooseAclDriver.d.ts.map +1 -0
- package/dist/acl/mongooseAclDriverContext.d.ts +17 -0
- package/dist/acl/mongooseAclDriverContext.d.ts.map +1 -0
- package/dist/acl/mongooseAclModel.d.ts +31 -0
- package/dist/acl/mongooseAclModel.d.ts.map +1 -0
- package/dist/acl/mongooseAclUtils.d.ts +6 -0
- package/dist/acl/mongooseAclUtils.d.ts.map +1 -0
- package/dist/ensureMongooseConnection.d.ts.map +1 -1
- package/dist/index.js +1025 -166
- package/dist/index.js.map +1 -1
- package/dist/models/RBUploadSession.d.ts +2 -2
- package/dist/modelsApi.d.ts +1 -1
- package/dist/modelsApi.d.ts.map +1 -1
- package/dist/registerModels.d.ts.map +1 -1
- package/dist/rtsChangeLogPlugin.d.ts +54 -0
- package/dist/rtsChangeLogPlugin.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/acl/mongooseAclPlugin.d.ts +0 -37
- package/dist/acl/mongooseAclPlugin.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -2,12 +2,14 @@ import { buildAbility, buildAbilityFromSession, can, getAccessibleByQuery, getRe
|
|
|
2
2
|
import { a as makeZE164Phone, c as buildLocaleFallbackChain, d as zI18nString, f as zLocalizedString, i as E164_PHONE_REGEX, l as resolveLocalizedString, n as extendZod, o as zE164Phone, r as E164_PHONE_OR_EMPTY_REGEX, s as LANGUAGE_CODE_REGEX, t as z, u as withLocalizedStringFallback } from "./zod-DSnhyNso.js";
|
|
3
3
|
import "./model.js";
|
|
4
4
|
import { getMongoDirectConnection, getMongoUrl } from "./mongo.js";
|
|
5
|
+
import { ForbiddenError, subject } from "@casl/ability";
|
|
5
6
|
import { accessibleBy, accessibleRecordsPlugin } from "@casl/mongoose";
|
|
6
7
|
import mongoose, { Schema as Schema$1, Types, default as mongoose$1 } from "mongoose";
|
|
7
8
|
import { z as z$1 } from "zod";
|
|
8
9
|
import { SUBSCRIPTION_CHANGE_DIRECTIONS, SUBSCRIPTION_EVENT_TYPES, SUBSCRIPTION_INTERVAL_UNITS, SUBSCRIPTION_SCOPES, SUBSCRIPTION_STATUSES, SUBSCRIPTION_TYPES } from "@rpcbase/billing";
|
|
9
10
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
10
11
|
import assert from "assert";
|
|
12
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
11
13
|
//#region \0rolldown/runtime.js
|
|
12
14
|
var __defProp = Object.defineProperty;
|
|
13
15
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -1470,16 +1472,16 @@ var paginateMongoQuery = async (query, pagination, options) => {
|
|
|
1470
1472
|
};
|
|
1471
1473
|
//#endregion
|
|
1472
1474
|
//#region src/pagination/mongoPaginationPlugin.ts
|
|
1473
|
-
var getQueryOptions
|
|
1475
|
+
var getQueryOptions = (query) => {
|
|
1474
1476
|
if (!query || typeof query !== "object") return void 0;
|
|
1475
1477
|
if (!("getOptions" in query) || typeof query.getOptions !== "function") return void 0;
|
|
1476
1478
|
return query.getOptions();
|
|
1477
1479
|
};
|
|
1478
1480
|
var getPaginationFromOptions = (query) => {
|
|
1479
|
-
return getQueryOptions
|
|
1481
|
+
return getQueryOptions(query)?.pagination;
|
|
1480
1482
|
};
|
|
1481
1483
|
var getCursorFromOptions = (query) => {
|
|
1482
|
-
return getQueryOptions
|
|
1484
|
+
return getQueryOptions(query)?.paginationCursor;
|
|
1483
1485
|
};
|
|
1484
1486
|
var mongoPaginationPlugin = (schema, pluginOptions) => {
|
|
1485
1487
|
schema.query.paginate = async function(pagination, options) {
|
|
@@ -1574,7 +1576,643 @@ var getTenantDbName = (tenantId, env = process.env) => {
|
|
|
1574
1576
|
return `${getAppName$1(env)}-${tenantId.trim()}-db`;
|
|
1575
1577
|
};
|
|
1576
1578
|
//#endregion
|
|
1579
|
+
//#region src/acl/mongooseAclUtils.ts
|
|
1580
|
+
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1581
|
+
var mergeMongoQuery = (left, right) => {
|
|
1582
|
+
if (left != null && !isRecord(left)) throw new TypeError("ACL-protected MongoDB filters must be objects.");
|
|
1583
|
+
const leftQuery = left ?? {};
|
|
1584
|
+
if (Object.keys(leftQuery).length === 0) return right;
|
|
1585
|
+
if (Object.keys(right).length === 0) return leftQuery;
|
|
1586
|
+
return { $and: [leftQuery, right] };
|
|
1587
|
+
};
|
|
1588
|
+
var cloneMongoValue = (value) => {
|
|
1589
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
1590
|
+
if (Buffer.isBuffer(value)) return Buffer.from(value);
|
|
1591
|
+
if (Array.isArray(value)) return value.map((entry) => cloneMongoValue(entry));
|
|
1592
|
+
if (!isRecord(value)) return value;
|
|
1593
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1594
|
+
if (prototype !== Object.prototype && prototype !== null) return value;
|
|
1595
|
+
const copy = Object.create(prototype);
|
|
1596
|
+
for (const [key, entry] of Object.entries(value)) copy[key] = cloneMongoValue(entry);
|
|
1597
|
+
return copy;
|
|
1598
|
+
};
|
|
1599
|
+
var castMongoQuery = (model, query) => {
|
|
1600
|
+
const casted = model.find(cloneMongoValue(query)).cast(model);
|
|
1601
|
+
if (!isRecord(casted)) throw new TypeError(`Mongoose returned an invalid casted query for model "${model.modelName}".`);
|
|
1602
|
+
return casted;
|
|
1603
|
+
};
|
|
1604
|
+
//#endregion
|
|
1605
|
+
//#region src/acl/mongooseAclAggregate.ts
|
|
1606
|
+
var allowedAggregateStages = /* @__PURE__ */ new Set([
|
|
1607
|
+
"$addFields",
|
|
1608
|
+
"$bucket",
|
|
1609
|
+
"$bucketAuto",
|
|
1610
|
+
"$count",
|
|
1611
|
+
"$densify",
|
|
1612
|
+
"$facet",
|
|
1613
|
+
"$fill",
|
|
1614
|
+
"$geoNear",
|
|
1615
|
+
"$graphLookup",
|
|
1616
|
+
"$group",
|
|
1617
|
+
"$limit",
|
|
1618
|
+
"$lookup",
|
|
1619
|
+
"$match",
|
|
1620
|
+
"$project",
|
|
1621
|
+
"$redact",
|
|
1622
|
+
"$replaceRoot",
|
|
1623
|
+
"$replaceWith",
|
|
1624
|
+
"$sample",
|
|
1625
|
+
"$search",
|
|
1626
|
+
"$set",
|
|
1627
|
+
"$setWindowFields",
|
|
1628
|
+
"$skip",
|
|
1629
|
+
"$sort",
|
|
1630
|
+
"$sortByCount",
|
|
1631
|
+
"$unionWith",
|
|
1632
|
+
"$unset",
|
|
1633
|
+
"$unwind",
|
|
1634
|
+
"$vectorSearch"
|
|
1635
|
+
]);
|
|
1636
|
+
var injectAggregateMatch = (pipeline, match) => {
|
|
1637
|
+
if (Object.keys(match).length === 0) return;
|
|
1638
|
+
if (pipeline.length === 0) {
|
|
1639
|
+
pipeline.unshift({ $match: match });
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
const first = pipeline[0];
|
|
1643
|
+
if (!isRecord(first)) {
|
|
1644
|
+
pipeline.unshift({ $match: match });
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
if ("$geoNear" in first) {
|
|
1648
|
+
const geoNear = first.$geoNear;
|
|
1649
|
+
if (isRecord(geoNear)) {
|
|
1650
|
+
geoNear.query = mergeMongoQuery(geoNear.query, match);
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
if ("$search" in first || "$vectorSearch" in first) {
|
|
1655
|
+
pipeline.splice(1, 0, { $match: match });
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
pipeline.unshift({ $match: match });
|
|
1659
|
+
};
|
|
1660
|
+
var containsSearchMetadataReference = (value) => {
|
|
1661
|
+
if (typeof value === "string") return value === "$$SEARCH_META" || value.startsWith("$$SEARCH_META.");
|
|
1662
|
+
if (Array.isArray(value)) return value.some(containsSearchMetadataReference);
|
|
1663
|
+
if (!isRecord(value)) return false;
|
|
1664
|
+
return Object.values(value).some(containsSearchMetadataReference);
|
|
1665
|
+
};
|
|
1666
|
+
var getModelCollectionName$2 = (model) => {
|
|
1667
|
+
const collection = model.collection;
|
|
1668
|
+
if (typeof collection.collectionName === "string") return collection.collectionName;
|
|
1669
|
+
if (typeof collection.name === "string") return collection.name;
|
|
1670
|
+
return null;
|
|
1671
|
+
};
|
|
1672
|
+
var resolveAggregateModel = (sourceModel, collectionName) => {
|
|
1673
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
1674
|
+
const connectionModels = sourceModel.db?.models ?? {};
|
|
1675
|
+
for (const model of [...Object.values(connectionModels), ...Object.values(mongoose$1.models)]) {
|
|
1676
|
+
const typedModel = model;
|
|
1677
|
+
if (getModelCollectionName$2(typedModel) !== collectionName) continue;
|
|
1678
|
+
candidates.set(typedModel.modelName, typedModel);
|
|
1679
|
+
}
|
|
1680
|
+
if (candidates.size === 0) throw new Error(`ACL driver cannot access collection "${collectionName}" because no registered Mongoose model maps to it.`);
|
|
1681
|
+
if (candidates.size > 1) throw new Error(`ACL driver cannot resolve collection "${collectionName}" unambiguously.`);
|
|
1682
|
+
return [...candidates.values()][0];
|
|
1683
|
+
};
|
|
1684
|
+
var assertAllowedAggregateStage = (stage) => {
|
|
1685
|
+
const stageNames = Object.keys(stage);
|
|
1686
|
+
if (stageNames.length !== 1) throw new Error("ACL-protected aggregates require exactly one operator per pipeline stage.");
|
|
1687
|
+
const stageName = stageNames[0];
|
|
1688
|
+
if (stageName === "$merge" || stageName === "$out") throw new Error(`ACL-protected aggregates cannot use ${stageName}. Use models.getUnsafe(...) after explicit authorization.`);
|
|
1689
|
+
if (!allowedAggregateStages.has(stageName)) throw new Error(`ACL-protected aggregates cannot use unreviewed stage ${stageName}. Use models.getUnsafe(...) explicitly.`);
|
|
1690
|
+
};
|
|
1691
|
+
var secureTargetPipeline = (pipeline, targetModel, ability) => {
|
|
1692
|
+
secureAggregateStages(pipeline, targetModel, ability);
|
|
1693
|
+
if (!hasRegisteredPolicy(targetModel.modelName)) return;
|
|
1694
|
+
if (!ability) throw new Error(`ACL-protected aggregate target "${targetModel.modelName}" requires .acl(ability).`);
|
|
1695
|
+
injectAggregateMatch(pipeline, castMongoQuery(targetModel, accessibleBy(ability, "read").ofType(targetModel.modelName)));
|
|
1696
|
+
};
|
|
1697
|
+
var secureAggregateStages = (pipeline, sourceModel, ability) => {
|
|
1698
|
+
for (const stage of pipeline) {
|
|
1699
|
+
if (!isRecord(stage)) throw new Error("ACL-protected aggregates require object pipeline stages.");
|
|
1700
|
+
if (containsSearchMetadataReference(stage)) throw new Error("ACL-protected aggregates cannot expose $$SEARCH_META because it includes unauthorized search matches.");
|
|
1701
|
+
assertAllowedAggregateStage(stage);
|
|
1702
|
+
if (isRecord(stage.$lookup)) {
|
|
1703
|
+
const lookup = stage.$lookup;
|
|
1704
|
+
const nestedPipeline = Array.isArray(lookup.pipeline) ? lookup.pipeline : [];
|
|
1705
|
+
if (typeof lookup.from === "string") {
|
|
1706
|
+
const targetModel = resolveAggregateModel(sourceModel, lookup.from);
|
|
1707
|
+
lookup.pipeline = nestedPipeline;
|
|
1708
|
+
secureTargetPipeline(nestedPipeline, targetModel, ability);
|
|
1709
|
+
} else if (lookup.from === void 0 && Array.isArray(lookup.pipeline)) secureAggregateStages(nestedPipeline, sourceModel, ability);
|
|
1710
|
+
else throw new Error("ACL-protected $lookup requires a registered collection name in `from`.");
|
|
1711
|
+
}
|
|
1712
|
+
if (typeof stage.$unionWith === "string") {
|
|
1713
|
+
const targetModel = resolveAggregateModel(sourceModel, stage.$unionWith);
|
|
1714
|
+
const nestedPipeline = [];
|
|
1715
|
+
stage.$unionWith = {
|
|
1716
|
+
coll: stage.$unionWith,
|
|
1717
|
+
pipeline: nestedPipeline
|
|
1718
|
+
};
|
|
1719
|
+
secureTargetPipeline(nestedPipeline, targetModel, ability);
|
|
1720
|
+
} else if (isRecord(stage.$unionWith)) {
|
|
1721
|
+
const unionWith = stage.$unionWith;
|
|
1722
|
+
const nestedPipeline = Array.isArray(unionWith.pipeline) ? unionWith.pipeline : [];
|
|
1723
|
+
if (typeof unionWith.coll === "string") {
|
|
1724
|
+
const targetModel = resolveAggregateModel(sourceModel, unionWith.coll);
|
|
1725
|
+
unionWith.pipeline = nestedPipeline;
|
|
1726
|
+
secureTargetPipeline(nestedPipeline, targetModel, ability);
|
|
1727
|
+
} else if (unionWith.coll === void 0 && Array.isArray(unionWith.pipeline)) secureAggregateStages(nestedPipeline, sourceModel, ability);
|
|
1728
|
+
else throw new Error("ACL-protected $unionWith requires a registered collection name in `coll`.");
|
|
1729
|
+
}
|
|
1730
|
+
if (isRecord(stage.$graphLookup)) {
|
|
1731
|
+
const graphLookup = stage.$graphLookup;
|
|
1732
|
+
const collectionName = graphLookup.from;
|
|
1733
|
+
if (typeof collectionName !== "string") throw new Error("ACL-protected $graphLookup requires a registered collection name in `from`.");
|
|
1734
|
+
const targetModel = resolveAggregateModel(sourceModel, collectionName);
|
|
1735
|
+
if (hasRegisteredPolicy(targetModel.modelName)) {
|
|
1736
|
+
if (!ability) throw new Error(`ACL-protected aggregate target "${targetModel.modelName}" requires .acl(ability).`);
|
|
1737
|
+
const targetAccessQuery = castMongoQuery(targetModel, accessibleBy(ability, "read").ofType(targetModel.modelName));
|
|
1738
|
+
graphLookup.restrictSearchWithMatch = mergeMongoQuery(graphLookup.restrictSearchWithMatch, targetAccessQuery);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
if (isRecord(stage.$facet)) for (const nestedPipeline of Object.values(stage.$facet)) {
|
|
1742
|
+
if (!Array.isArray(nestedPipeline)) throw new Error("ACL-protected $facet entries must be pipelines.");
|
|
1743
|
+
secureAggregateStages(nestedPipeline, sourceModel, ability);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
var secureAggregatePipeline = (pipeline, model, ability, action) => {
|
|
1748
|
+
secureAggregateStages(pipeline, model, ability);
|
|
1749
|
+
if (!hasRegisteredPolicy(model.modelName)) return;
|
|
1750
|
+
if (!ability) throw new Error(`ACL-protected model "${model.modelName}" requires .acl(ability).`);
|
|
1751
|
+
injectAggregateMatch(pipeline, castMongoQuery(model, accessibleBy(ability, action).ofType(model.modelName)));
|
|
1752
|
+
};
|
|
1753
|
+
//#endregion
|
|
1754
|
+
//#region src/acl/mongooseAclDriverContext.ts
|
|
1755
|
+
var contextStorage = new AsyncLocalStorage();
|
|
1756
|
+
var collectionContexts = /* @__PURE__ */ new WeakMap();
|
|
1757
|
+
var rawConnections = /* @__PURE__ */ new WeakMap();
|
|
1758
|
+
var unsafeMongooseAclDriverContext = Object.freeze({ mode: "unsafe" });
|
|
1759
|
+
var unboundMongooseAclDriverContext = Object.freeze({ mode: "unbound" });
|
|
1760
|
+
var bindMongooseAclDriverContext = (value, context) => {
|
|
1761
|
+
collectionContexts.set(value, context);
|
|
1762
|
+
};
|
|
1763
|
+
var getMongooseAclDriverContext = (value) => {
|
|
1764
|
+
return (value ? collectionContexts.get(value) : void 0) ?? contextStorage.getStore();
|
|
1765
|
+
};
|
|
1766
|
+
var bindMongooseAclRawConnection = (scopedConnection, rawConnection) => {
|
|
1767
|
+
rawConnections.set(scopedConnection, rawConnection);
|
|
1768
|
+
};
|
|
1769
|
+
var getMongooseAclRawConnection = (connection) => rawConnections.get(connection) ?? connection;
|
|
1770
|
+
var runWithMongooseAclDriverContext = (context, callback) => contextStorage.run(context, callback);
|
|
1771
|
+
//#endregion
|
|
1772
|
+
//#region src/acl/mongooseAclDriver.ts
|
|
1773
|
+
var nativeDriver = mongoose$1.driver.get();
|
|
1774
|
+
var NativeCollection = nativeDriver.Collection;
|
|
1775
|
+
var rawCollections = /* @__PURE__ */ new WeakMap();
|
|
1776
|
+
var rawCollectionProxies = /* @__PURE__ */ new WeakMap();
|
|
1777
|
+
var blockedNativeCollectionProperties = /* @__PURE__ */ new Set([
|
|
1778
|
+
"client",
|
|
1779
|
+
"db",
|
|
1780
|
+
"s"
|
|
1781
|
+
]);
|
|
1782
|
+
var blockedCursorProperties = /* @__PURE__ */ new Set([
|
|
1783
|
+
"aggregateOptions",
|
|
1784
|
+
"client",
|
|
1785
|
+
"cursorClient",
|
|
1786
|
+
"cursorFilter",
|
|
1787
|
+
"cursorOptions",
|
|
1788
|
+
"cursorSession",
|
|
1789
|
+
"selectedServer",
|
|
1790
|
+
"server",
|
|
1791
|
+
"session"
|
|
1792
|
+
]);
|
|
1793
|
+
var aggregateCursorMutationMethods = /* @__PURE__ */ new Set([
|
|
1794
|
+
"addStage",
|
|
1795
|
+
"geoNear",
|
|
1796
|
+
"group",
|
|
1797
|
+
"limit",
|
|
1798
|
+
"lookup",
|
|
1799
|
+
"match",
|
|
1800
|
+
"out",
|
|
1801
|
+
"project",
|
|
1802
|
+
"redact",
|
|
1803
|
+
"skip",
|
|
1804
|
+
"sort",
|
|
1805
|
+
"unwind"
|
|
1806
|
+
]);
|
|
1807
|
+
var dataMethods = /* @__PURE__ */ new Set([
|
|
1808
|
+
"aggregate",
|
|
1809
|
+
"bulkWrite",
|
|
1810
|
+
"count",
|
|
1811
|
+
"countDocuments",
|
|
1812
|
+
"deleteMany",
|
|
1813
|
+
"deleteOne",
|
|
1814
|
+
"distinct",
|
|
1815
|
+
"estimatedDocumentCount",
|
|
1816
|
+
"find",
|
|
1817
|
+
"findOne",
|
|
1818
|
+
"findOneAndDelete",
|
|
1819
|
+
"findOneAndReplace",
|
|
1820
|
+
"findOneAndUpdate",
|
|
1821
|
+
"insertMany",
|
|
1822
|
+
"insertOne",
|
|
1823
|
+
"replaceOne",
|
|
1824
|
+
"updateMany",
|
|
1825
|
+
"updateOne"
|
|
1826
|
+
]);
|
|
1827
|
+
var readFilterArgumentByMethod = {
|
|
1828
|
+
count: 0,
|
|
1829
|
+
countDocuments: 0,
|
|
1830
|
+
distinct: 1,
|
|
1831
|
+
find: 0,
|
|
1832
|
+
findOne: 0
|
|
1833
|
+
};
|
|
1834
|
+
var writeFilterActionByMethod = {
|
|
1835
|
+
deleteMany: "delete",
|
|
1836
|
+
deleteOne: "delete",
|
|
1837
|
+
findOneAndDelete: "delete",
|
|
1838
|
+
findOneAndReplace: "update",
|
|
1839
|
+
findOneAndUpdate: "update",
|
|
1840
|
+
replaceOne: "update",
|
|
1841
|
+
updateMany: "update",
|
|
1842
|
+
updateOne: "update"
|
|
1843
|
+
};
|
|
1844
|
+
var getCollectionName = (collection) => {
|
|
1845
|
+
const value = collection.collectionName ?? collection.name;
|
|
1846
|
+
return typeof value === "string" ? value : "";
|
|
1847
|
+
};
|
|
1848
|
+
var getModelCollectionName$1 = (model) => {
|
|
1849
|
+
const collection = model.collection;
|
|
1850
|
+
if (typeof collection.collectionName === "string") return collection.collectionName;
|
|
1851
|
+
if (typeof collection.name === "string") return collection.name;
|
|
1852
|
+
return null;
|
|
1853
|
+
};
|
|
1854
|
+
var getCollectionModels = (collection) => {
|
|
1855
|
+
const collectionName = getCollectionName(collection);
|
|
1856
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
1857
|
+
for (const model of [...Object.values(mongoose$1.models), ...Object.values(collection.conn.models ?? {})]) {
|
|
1858
|
+
const typedModel = model;
|
|
1859
|
+
if (getModelCollectionName$1(typedModel) === collectionName) candidates.set(typedModel.modelName, typedModel);
|
|
1860
|
+
}
|
|
1861
|
+
return candidates;
|
|
1862
|
+
};
|
|
1863
|
+
var getCollectionModelName = (collection) => {
|
|
1864
|
+
if (typeof collection.modelName === "string" && collection.modelName) return collection.modelName;
|
|
1865
|
+
const candidates = getCollectionModels(collection);
|
|
1866
|
+
return candidates.size === 1 ? [...candidates.keys()][0] : null;
|
|
1867
|
+
};
|
|
1868
|
+
var getProtectedCollectionModelNames = (collection) => [...getCollectionModels(collection).keys()].filter(hasRegisteredPolicy);
|
|
1869
|
+
var hasSecuredCollectionBoundary = (collection) => {
|
|
1870
|
+
const context = getMongooseAclDriverContext(collection);
|
|
1871
|
+
if (context) return context.mode !== "unsafe";
|
|
1872
|
+
if (typeof collection.modelName === "string" && collection.modelName) {
|
|
1873
|
+
if (hasRegisteredPolicy(collection.modelName)) return true;
|
|
1874
|
+
}
|
|
1875
|
+
return [...getCollectionModels(collection).keys()].some(hasRegisteredPolicy);
|
|
1876
|
+
};
|
|
1877
|
+
var getCollectionModel = (collection, modelName) => {
|
|
1878
|
+
const model = collection.conn.models?.[modelName] ?? mongoose$1.models[modelName];
|
|
1879
|
+
if (!model) throw new Error(`ACL driver could not resolve Mongoose model "${modelName}".`);
|
|
1880
|
+
return model;
|
|
1881
|
+
};
|
|
1882
|
+
var getAccessQuery = (ability, action, model) => castMongoQuery(model, accessibleBy(ability, action).ofType(model.modelName));
|
|
1883
|
+
var assertCanCreate = (ability, modelName, value) => {
|
|
1884
|
+
const rawValue = value instanceof mongoose$1.Document ? value.toObject() : value;
|
|
1885
|
+
const document = isRecord(rawValue) ? rawValue : {};
|
|
1886
|
+
ForbiddenError.from(ability).throwUnlessCan("create", subject(modelName, document));
|
|
1887
|
+
};
|
|
1888
|
+
var materializeBsonValue = (value, seen) => {
|
|
1889
|
+
if (!value || typeof value !== "object") return value;
|
|
1890
|
+
if (seen.has(value)) return seen.get(value);
|
|
1891
|
+
const toBSON = value.toBSON;
|
|
1892
|
+
if (typeof toBSON === "function") {
|
|
1893
|
+
const placeholder = Object.create(null);
|
|
1894
|
+
seen.set(value, placeholder);
|
|
1895
|
+
const materialized = Reflect.apply(toBSON, value, []);
|
|
1896
|
+
if (materialized !== value) {
|
|
1897
|
+
const result = materializeBsonValue(materialized, seen);
|
|
1898
|
+
seen.set(value, result);
|
|
1899
|
+
return result;
|
|
1900
|
+
}
|
|
1901
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1902
|
+
if (key === "toBSON" && typeof entry === "function") continue;
|
|
1903
|
+
placeholder[key] = materializeBsonValue(entry, seen);
|
|
1904
|
+
}
|
|
1905
|
+
return placeholder;
|
|
1906
|
+
}
|
|
1907
|
+
if (value instanceof Date || Buffer.isBuffer(value)) return cloneMongoValue(value);
|
|
1908
|
+
if (value instanceof RegExp || ArrayBuffer.isView(value) || isRecord(value) && typeof value._bsontype === "string") return value;
|
|
1909
|
+
if (Array.isArray(value)) {
|
|
1910
|
+
const copy = [];
|
|
1911
|
+
seen.set(value, copy);
|
|
1912
|
+
for (const entry of value) copy.push(materializeBsonValue(entry, seen));
|
|
1913
|
+
return copy;
|
|
1914
|
+
}
|
|
1915
|
+
if (value instanceof Map) {
|
|
1916
|
+
const copy = /* @__PURE__ */ new Map();
|
|
1917
|
+
seen.set(value, copy);
|
|
1918
|
+
for (const [key, entry] of value) copy.set(key, materializeBsonValue(entry, seen));
|
|
1919
|
+
return copy;
|
|
1920
|
+
}
|
|
1921
|
+
const copy = Object.create(null);
|
|
1922
|
+
seen.set(value, copy);
|
|
1923
|
+
for (const [key, entry] of Object.entries(value)) copy[key] = materializeBsonValue(entry, seen);
|
|
1924
|
+
return copy;
|
|
1925
|
+
};
|
|
1926
|
+
var materializeInsertDocument = (value) => materializeBsonValue(value, /* @__PURE__ */ new WeakMap());
|
|
1927
|
+
var safeExplainVerbosities = /* @__PURE__ */ new Set(["queryPlanner", "queryPlannerExtended"]);
|
|
1928
|
+
var isSafeExplainVerbosity = (value) => {
|
|
1929
|
+
if (value === false) return true;
|
|
1930
|
+
if (typeof value === "string") return safeExplainVerbosities.has(value);
|
|
1931
|
+
return isRecord(value) && isSafeExplainVerbosity(value.verbosity);
|
|
1932
|
+
};
|
|
1933
|
+
var assertSafeExplainVerbosity = (value) => {
|
|
1934
|
+
if (!isSafeExplainVerbosity(value)) throw new Error("ACL-protected cursors cannot expose execution statistics. Use queryPlanner verbosity or models.getUnsafe(...) explicitly.");
|
|
1935
|
+
};
|
|
1936
|
+
var assertSafeExplainOptions = (options) => {
|
|
1937
|
+
if (isRecord(options) && Object.hasOwn(options, "explain") && options.explain != null) assertSafeExplainVerbosity(options.explain);
|
|
1938
|
+
};
|
|
1939
|
+
var assertNoUpsert = (methodName, options) => {
|
|
1940
|
+
if (isRecord(options) && options.upsert === true) throw new Error(`ACL-protected ${methodName}() cannot use upsert. Use models.getUnsafe(...) after explicit create and update authorization.`);
|
|
1941
|
+
};
|
|
1942
|
+
var secureBulkWriteOperations = (ability, model, value) => {
|
|
1943
|
+
if (!Array.isArray(value)) throw new TypeError("ACL-protected bulkWrite() expects an array of operations.");
|
|
1944
|
+
return value.map((operation) => {
|
|
1945
|
+
if (!isRecord(operation)) throw new TypeError("ACL-protected bulkWrite() received an invalid operation.");
|
|
1946
|
+
const operationActions = [
|
|
1947
|
+
{
|
|
1948
|
+
key: "updateOne",
|
|
1949
|
+
action: "update"
|
|
1950
|
+
},
|
|
1951
|
+
{
|
|
1952
|
+
key: "updateMany",
|
|
1953
|
+
action: "update"
|
|
1954
|
+
},
|
|
1955
|
+
{
|
|
1956
|
+
key: "replaceOne",
|
|
1957
|
+
action: "update"
|
|
1958
|
+
},
|
|
1959
|
+
{
|
|
1960
|
+
key: "deleteOne",
|
|
1961
|
+
action: "delete"
|
|
1962
|
+
},
|
|
1963
|
+
{
|
|
1964
|
+
key: "deleteMany",
|
|
1965
|
+
action: "delete"
|
|
1966
|
+
}
|
|
1967
|
+
];
|
|
1968
|
+
const supportedKeys = ["insertOne", ...operationActions.map(({ key }) => key)].filter((key) => Object.hasOwn(operation, key));
|
|
1969
|
+
if (supportedKeys.length !== 1 || Object.keys(operation).length !== 1) throw new Error("ACL-protected bulkWrite() requires exactly one supported operator per operation.");
|
|
1970
|
+
if (supportedKeys[0] === "insertOne" && isRecord(operation.insertOne)) {
|
|
1971
|
+
const document = materializeInsertDocument(operation.insertOne.document);
|
|
1972
|
+
assertCanCreate(ability, model.modelName, document);
|
|
1973
|
+
return {
|
|
1974
|
+
...cloneMongoValue(operation),
|
|
1975
|
+
insertOne: {
|
|
1976
|
+
...cloneMongoValue(operation.insertOne),
|
|
1977
|
+
document
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
}
|
|
1981
|
+
for (const { key, action } of operationActions) {
|
|
1982
|
+
const details = operation[key];
|
|
1983
|
+
if (!isRecord(details)) continue;
|
|
1984
|
+
assertNoUpsert(`bulkWrite ${key}`, details);
|
|
1985
|
+
return {
|
|
1986
|
+
...cloneMongoValue(operation),
|
|
1987
|
+
[key]: {
|
|
1988
|
+
...cloneMongoValue(details),
|
|
1989
|
+
filter: mergeMongoQuery(details.filter, getAccessQuery(ability, action, model))
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
throw new Error("ACL-protected bulkWrite() received an unsupported operation.");
|
|
1994
|
+
});
|
|
1995
|
+
};
|
|
1996
|
+
var secureCollectionArguments = (collection, ability, modelName, methodName, input) => {
|
|
1997
|
+
const args = input.map((value) => cloneMongoValue(value));
|
|
1998
|
+
const model = getCollectionModel(collection, modelName);
|
|
1999
|
+
if (methodName === "find" || methodName === "findOne" || methodName === "aggregate") assertSafeExplainOptions(args[1]);
|
|
2000
|
+
const readFilterIndex = readFilterArgumentByMethod[methodName];
|
|
2001
|
+
if (readFilterIndex !== void 0) {
|
|
2002
|
+
args[readFilterIndex] = mergeMongoQuery(args[readFilterIndex], getAccessQuery(ability, "read", model));
|
|
2003
|
+
return args;
|
|
2004
|
+
}
|
|
2005
|
+
const writeAction = writeFilterActionByMethod[methodName];
|
|
2006
|
+
if (writeAction) {
|
|
2007
|
+
if (writeAction === "update") assertNoUpsert(methodName, args[2]);
|
|
2008
|
+
args[0] = mergeMongoQuery(args[0], getAccessQuery(ability, writeAction, model));
|
|
2009
|
+
return args;
|
|
2010
|
+
}
|
|
2011
|
+
if (methodName === "insertOne") {
|
|
2012
|
+
args[0] = materializeInsertDocument(input[0]);
|
|
2013
|
+
assertCanCreate(ability, modelName, args[0]);
|
|
2014
|
+
return args;
|
|
2015
|
+
}
|
|
2016
|
+
if (methodName === "insertMany") {
|
|
2017
|
+
if (!Array.isArray(input[0])) throw new TypeError("ACL-protected insertMany() expects an array of documents.");
|
|
2018
|
+
const documents = input[0].map((document) => materializeInsertDocument(document));
|
|
2019
|
+
for (const document of documents) assertCanCreate(ability, modelName, document);
|
|
2020
|
+
args[0] = documents;
|
|
2021
|
+
return args;
|
|
2022
|
+
}
|
|
2023
|
+
if (methodName === "bulkWrite") {
|
|
2024
|
+
args[0] = secureBulkWriteOperations(ability, model, input[0]);
|
|
2025
|
+
return args;
|
|
2026
|
+
}
|
|
2027
|
+
if (methodName === "aggregate") {
|
|
2028
|
+
if (!Array.isArray(args[0])) throw new TypeError("ACL-protected aggregate() expects a pipeline array.");
|
|
2029
|
+
const pipeline = args[0];
|
|
2030
|
+
secureAggregatePipeline(pipeline, model, ability, "read");
|
|
2031
|
+
args[0] = pipeline;
|
|
2032
|
+
return args;
|
|
2033
|
+
}
|
|
2034
|
+
if (methodName === "estimatedDocumentCount") throw new Error("ACL-protected estimatedDocumentCount() cannot be filtered. Use countDocuments() or models.getUnsafe(...).");
|
|
2035
|
+
throw new Error(`ACL-protected collection method ${methodName}() is not supported. Use models.getUnsafe(...) explicitly.`);
|
|
2036
|
+
};
|
|
2037
|
+
var wrapFindCursor = (cursor) => {
|
|
2038
|
+
const proxy = new Proxy(cursor, {
|
|
2039
|
+
get(target, prop) {
|
|
2040
|
+
if (blockedCursorProperties.has(prop)) throw new Error(`ACL-protected cursor property .${String(prop)} is blocked.`);
|
|
2041
|
+
const value = Reflect.get(target, prop, target);
|
|
2042
|
+
if (prop === "filter") return () => {
|
|
2043
|
+
throw new Error("ACL-protected cursors cannot replace their filter.");
|
|
2044
|
+
};
|
|
2045
|
+
if (prop === "addQueryModifier" && typeof value === "function") return (...args) => {
|
|
2046
|
+
if (args[0] === "$query") throw new Error("ACL-protected cursors cannot replace their filter.");
|
|
2047
|
+
if (args[0] === "$explain") assertSafeExplainVerbosity(args[1]);
|
|
2048
|
+
const result = Reflect.apply(value, target, args);
|
|
2049
|
+
return result === target ? proxy : result;
|
|
2050
|
+
};
|
|
2051
|
+
if (prop === "explain" && typeof value === "function") return (...args) => {
|
|
2052
|
+
assertSafeExplainVerbosity(args[0]);
|
|
2053
|
+
return Reflect.apply(value, target, args);
|
|
2054
|
+
};
|
|
2055
|
+
if (typeof value !== "function") return value;
|
|
2056
|
+
return (...args) => {
|
|
2057
|
+
const result = Reflect.apply(value, target, args);
|
|
2058
|
+
if (prop === "stream" && result && typeof result === "object") return wrapCursorStream(result);
|
|
2059
|
+
if (prop === "clone" && result && typeof result === "object") return wrapFindCursor(result);
|
|
2060
|
+
return result === target ? proxy : result;
|
|
2061
|
+
};
|
|
2062
|
+
},
|
|
2063
|
+
set() {
|
|
2064
|
+
throw new Error("ACL-protected cursors cannot be mutated directly.");
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
return proxy;
|
|
2068
|
+
};
|
|
2069
|
+
var wrapCursorStream = (stream) => {
|
|
2070
|
+
const proxy = new Proxy(stream, {
|
|
2071
|
+
get(target, prop) {
|
|
2072
|
+
if (prop === "_cursor") throw new Error("ACL-protected cursor streams do not expose their native cursor.");
|
|
2073
|
+
const value = Reflect.get(target, prop, target);
|
|
2074
|
+
if (typeof value !== "function") return value;
|
|
2075
|
+
return (...args) => {
|
|
2076
|
+
const result = Reflect.apply(value, target, args);
|
|
2077
|
+
return result === target ? proxy : result;
|
|
2078
|
+
};
|
|
2079
|
+
},
|
|
2080
|
+
set(target, prop, value) {
|
|
2081
|
+
if (prop === "_cursor") throw new Error("ACL-protected cursor streams cannot replace their native cursor.");
|
|
2082
|
+
return Reflect.set(target, prop, value, target);
|
|
2083
|
+
}
|
|
2084
|
+
});
|
|
2085
|
+
return proxy;
|
|
2086
|
+
};
|
|
2087
|
+
var wrapAggregateCursor = (cursor) => {
|
|
2088
|
+
const proxy = new Proxy(cursor, {
|
|
2089
|
+
get(target, prop) {
|
|
2090
|
+
if (blockedCursorProperties.has(prop)) throw new Error(`ACL-protected aggregation cursor property .${String(prop)} is blocked.`);
|
|
2091
|
+
if (prop === "pipeline") return cloneMongoValue(Reflect.get(target, prop, target));
|
|
2092
|
+
const value = Reflect.get(target, prop, target);
|
|
2093
|
+
if (aggregateCursorMutationMethods.has(prop)) return () => {
|
|
2094
|
+
throw new Error("ACL-protected aggregation cursors cannot mutate their pipeline after validation.");
|
|
2095
|
+
};
|
|
2096
|
+
if (prop === "explain" && typeof value === "function") return (...args) => {
|
|
2097
|
+
assertSafeExplainVerbosity(args[0]);
|
|
2098
|
+
return Reflect.apply(value, target, args);
|
|
2099
|
+
};
|
|
2100
|
+
if (typeof value !== "function") return value;
|
|
2101
|
+
return (...args) => {
|
|
2102
|
+
const result = Reflect.apply(value, target, args);
|
|
2103
|
+
if (prop === "stream" && result && typeof result === "object") return wrapCursorStream(result);
|
|
2104
|
+
if (prop === "clone" && result && typeof result === "object") return wrapAggregateCursor(result);
|
|
2105
|
+
return result === target ? proxy : result;
|
|
2106
|
+
};
|
|
2107
|
+
},
|
|
2108
|
+
set() {
|
|
2109
|
+
throw new Error("ACL-protected aggregation cursors cannot be mutated directly.");
|
|
2110
|
+
}
|
|
2111
|
+
});
|
|
2112
|
+
return proxy;
|
|
2113
|
+
};
|
|
2114
|
+
var wrapCursorResult = (methodName, result) => {
|
|
2115
|
+
if (result && typeof result === "object" && "then" in result && typeof result.then === "function") return Promise.resolve(result).then((resolved) => wrapCursorResult(methodName, resolved));
|
|
2116
|
+
if (!result || typeof result !== "object") return result;
|
|
2117
|
+
if (methodName === "find") return wrapFindCursor(result);
|
|
2118
|
+
if (methodName === "aggregate") return wrapAggregateCursor(result);
|
|
2119
|
+
return result;
|
|
2120
|
+
};
|
|
2121
|
+
var invokeSafeUnprotectedCollectionMethod = (collection, rawCollection, modelName, context, methodName, method, input) => {
|
|
2122
|
+
if (!dataMethods.has(methodName)) throw new Error(`Safe collection method ${methodName}() is not supported. Use models.getUnsafe(...) explicitly.`);
|
|
2123
|
+
const args = input.map((value) => cloneMongoValue(value));
|
|
2124
|
+
if (methodName === "aggregate") {
|
|
2125
|
+
if (!Array.isArray(args[0])) throw new TypeError("Safe aggregate() expects a pipeline array.");
|
|
2126
|
+
secureAggregatePipeline(args[0], getCollectionModel(collection, modelName), context.mode === "ability" ? context.ability : void 0, "read");
|
|
2127
|
+
}
|
|
2128
|
+
return wrapCursorResult(methodName, Reflect.apply(method, rawCollection, args));
|
|
2129
|
+
};
|
|
2130
|
+
var invokeNativeCollectionMethod = (collection, rawCollection, methodName, method, input) => {
|
|
2131
|
+
const context = getMongooseAclDriverContext(collection);
|
|
2132
|
+
const modelName = getCollectionModelName(collection);
|
|
2133
|
+
if (context?.mode === "unsafe") return Reflect.apply(method, rawCollection, input);
|
|
2134
|
+
if (modelName && !hasRegisteredPolicy(modelName)) {
|
|
2135
|
+
if (getProtectedCollectionModelNames(collection).length > 0) throw new Error(`Model "${modelName}" maps to ACL-protected collection "${getCollectionName(collection)}" without its own policy.`);
|
|
2136
|
+
if (!context) return Reflect.apply(method, rawCollection, input);
|
|
2137
|
+
return invokeSafeUnprotectedCollectionMethod(collection, rawCollection, modelName, context, methodName, method, input);
|
|
2138
|
+
}
|
|
2139
|
+
if (!modelName) {
|
|
2140
|
+
const collectionModels = getCollectionModels(collection);
|
|
2141
|
+
const protectedModelNames = [...collectionModels.keys()].filter(hasRegisteredPolicy);
|
|
2142
|
+
if (collectionModels.size > 0 && protectedModelNames.length === 0) {
|
|
2143
|
+
if (!context) return Reflect.apply(method, rawCollection, input);
|
|
2144
|
+
return invokeSafeUnprotectedCollectionMethod(collection, rawCollection, [...collectionModels.keys()][0], context, methodName, method, input);
|
|
2145
|
+
}
|
|
2146
|
+
if (protectedModelNames.length > 0) throw new Error(`ACL driver cannot choose a policy for collection "${getCollectionName(collection)}" (${protectedModelNames.join(", ")}). Use models.get(...).acl(ability).`);
|
|
2147
|
+
throw new Error(`ACL driver cannot authorize unregistered collection "${getCollectionName(collection)}". Use models.getUnsafe(...) explicitly.`);
|
|
2148
|
+
}
|
|
2149
|
+
if (context?.mode !== "ability") {
|
|
2150
|
+
if (context?.mode === "unbound") throw new Error(`Model "${modelName}" is ACL-protected. Call .acl(ability) before executing the operation.`);
|
|
2151
|
+
throw new Error(`Model "${modelName}" is ACL-protected. Use models.get(...).acl(ability) or models.getUnsafe(...) explicitly.`);
|
|
2152
|
+
}
|
|
2153
|
+
if (!dataMethods.has(methodName)) throw new Error(`ACL-protected collection method ${methodName}() is not filterable. Use models.getUnsafe(...) explicitly.`);
|
|
2154
|
+
const args = secureCollectionArguments(collection, context.ability, modelName, methodName, input);
|
|
2155
|
+
return wrapCursorResult(methodName, Reflect.apply(method, rawCollection, args));
|
|
2156
|
+
};
|
|
2157
|
+
var getRawCollectionProxy = (collection, rawCollection) => {
|
|
2158
|
+
const cached = rawCollectionProxies.get(collection);
|
|
2159
|
+
if (cached?.raw === rawCollection) return cached.proxy;
|
|
2160
|
+
const proxy = new Proxy(rawCollection, {
|
|
2161
|
+
get(target, prop) {
|
|
2162
|
+
if (blockedNativeCollectionProperties.has(prop) && hasSecuredCollectionBoundary(collection)) throw new Error(`ACL-protected native collection property .${String(prop)} is blocked. Use models.getUnsafe(...) explicitly.`);
|
|
2163
|
+
const value = Reflect.get(target, prop, target);
|
|
2164
|
+
if (typeof value !== "function") return value;
|
|
2165
|
+
return (...args) => invokeNativeCollectionMethod(collection, target, String(prop), value, args);
|
|
2166
|
+
},
|
|
2167
|
+
set(target, prop, value) {
|
|
2168
|
+
if (hasSecuredCollectionBoundary(collection)) throw new Error(`ACL-protected native collection property .${String(prop)} cannot be changed. Use models.getUnsafe(...) explicitly.`);
|
|
2169
|
+
return Reflect.set(target, prop, value, target);
|
|
2170
|
+
}
|
|
2171
|
+
});
|
|
2172
|
+
rawCollectionProxies.set(collection, {
|
|
2173
|
+
raw: rawCollection,
|
|
2174
|
+
proxy
|
|
2175
|
+
});
|
|
2176
|
+
return proxy;
|
|
2177
|
+
};
|
|
2178
|
+
var installRawCollectionAccessor = (collection) => {
|
|
2179
|
+
const initialRawCollection = collection.collection;
|
|
2180
|
+
Object.defineProperty(collection, "collection", {
|
|
2181
|
+
configurable: true,
|
|
2182
|
+
enumerable: true,
|
|
2183
|
+
get() {
|
|
2184
|
+
const rawCollection = rawCollections.get(collection);
|
|
2185
|
+
return rawCollection ? getRawCollectionProxy(collection, rawCollection) : rawCollection;
|
|
2186
|
+
},
|
|
2187
|
+
set(value) {
|
|
2188
|
+
rawCollections.set(collection, value && typeof value === "object" ? value : null);
|
|
2189
|
+
rawCollectionProxies.delete(collection);
|
|
2190
|
+
}
|
|
2191
|
+
});
|
|
2192
|
+
collection.collection = initialRawCollection;
|
|
2193
|
+
};
|
|
2194
|
+
var AclMongooseCollection = class extends NativeCollection {
|
|
2195
|
+
constructor(name, connection, options = {}) {
|
|
2196
|
+
super(name, connection, options);
|
|
2197
|
+
installRawCollectionAccessor(this);
|
|
2198
|
+
}
|
|
2199
|
+
};
|
|
2200
|
+
var ACL_DRIVER_GLOBAL_KEY = Symbol.for("@rpcbase/db/acl/mongooseDriverInstalled");
|
|
2201
|
+
var installMongooseAclDriver = () => {
|
|
2202
|
+
const globalState = globalThis;
|
|
2203
|
+
const installedInstances = globalState[ACL_DRIVER_GLOBAL_KEY] instanceof WeakSet ? globalState[ACL_DRIVER_GLOBAL_KEY] : /* @__PURE__ */ new WeakSet();
|
|
2204
|
+
if (installedInstances.has(mongoose$1)) return;
|
|
2205
|
+
mongoose$1.setDriver({
|
|
2206
|
+
...nativeDriver,
|
|
2207
|
+
Collection: AclMongooseCollection
|
|
2208
|
+
});
|
|
2209
|
+
installedInstances.add(mongoose$1);
|
|
2210
|
+
globalState[ACL_DRIVER_GLOBAL_KEY] = installedInstances;
|
|
2211
|
+
};
|
|
2212
|
+
var runWithUnsafeMongooseAclDriver = (callback) => runWithMongooseAclDriverContext(unsafeMongooseAclDriverContext, callback);
|
|
2213
|
+
//#endregion
|
|
1577
2214
|
//#region src/ensureMongooseConnection.ts
|
|
2215
|
+
installMongooseAclDriver();
|
|
1578
2216
|
var connections = /* @__PURE__ */ new Map();
|
|
1579
2217
|
var rootConnection = null;
|
|
1580
2218
|
var CONNECTION_MAX_LISTENERS = 50;
|
|
@@ -1640,9 +2278,10 @@ var getQuerySession = (query) => {
|
|
|
1640
2278
|
return session;
|
|
1641
2279
|
};
|
|
1642
2280
|
var getRtsModels = (db) => {
|
|
2281
|
+
const rawDb = getMongooseAclRawConnection(db);
|
|
1643
2282
|
return {
|
|
1644
|
-
RtsCounter:
|
|
1645
|
-
RtsChange:
|
|
2283
|
+
RtsCounter: rawDb.models.RBRtsCounter ?? rawDb.model("RBRtsCounter", RBRtsCounterSchema),
|
|
2284
|
+
RtsChange: rawDb.models.RBRtsChange ?? rawDb.model("RBRtsChange", RBRtsChangeSchema)
|
|
1646
2285
|
};
|
|
1647
2286
|
};
|
|
1648
2287
|
var allocateSeqRange = async (db, count, session) => {
|
|
@@ -1776,157 +2415,6 @@ var rtsChangeLogPlugin = (schema) => {
|
|
|
1776
2415
|
});
|
|
1777
2416
|
};
|
|
1778
2417
|
//#endregion
|
|
1779
|
-
//#region src/acl/mongooseAclPlugin.ts
|
|
1780
|
-
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1781
|
-
var mergeMongoQuery = (left, right) => {
|
|
1782
|
-
const leftQuery = isRecord(left) ? left : {};
|
|
1783
|
-
if (Object.keys(leftQuery).length === 0) return right;
|
|
1784
|
-
if (Object.keys(right).length === 0) return leftQuery;
|
|
1785
|
-
return { $and: [leftQuery, right] };
|
|
1786
|
-
};
|
|
1787
|
-
var getQueryOptions = (query) => {
|
|
1788
|
-
if (!query || typeof query !== "object") return void 0;
|
|
1789
|
-
if (!("getOptions" in query) || typeof query.getOptions !== "function") return void 0;
|
|
1790
|
-
return query.getOptions();
|
|
1791
|
-
};
|
|
1792
|
-
var getStoredAclFromQuery = (query) => {
|
|
1793
|
-
const raw = getQueryOptions(query)?.rbAcl;
|
|
1794
|
-
if (!isRecord(raw)) return null;
|
|
1795
|
-
if (!("ability" in raw)) return null;
|
|
1796
|
-
return raw;
|
|
1797
|
-
};
|
|
1798
|
-
var getStoredAclFromAggregate = (aggregate) => {
|
|
1799
|
-
if (!aggregate || typeof aggregate !== "object") return null;
|
|
1800
|
-
const raw = aggregate.options;
|
|
1801
|
-
if (!isRecord(raw)) return null;
|
|
1802
|
-
const acl = raw.rbAcl;
|
|
1803
|
-
if (!isRecord(acl)) return null;
|
|
1804
|
-
if (!("ability" in acl)) return null;
|
|
1805
|
-
return acl;
|
|
1806
|
-
};
|
|
1807
|
-
var addQueryAclFilter = (query, action) => {
|
|
1808
|
-
const storedAcl = getStoredAclFromQuery(query);
|
|
1809
|
-
if (!storedAcl) return;
|
|
1810
|
-
const ability = storedAcl.ability;
|
|
1811
|
-
const resolvedAction = storedAcl.action ?? action;
|
|
1812
|
-
const modelName = query.model.modelName;
|
|
1813
|
-
const accessQuery = accessibleBy(ability, resolvedAction).ofType(modelName);
|
|
1814
|
-
query.and([accessQuery]);
|
|
1815
|
-
};
|
|
1816
|
-
var injectAggregateMatch = (pipeline, match) => {
|
|
1817
|
-
if (pipeline.length === 0) {
|
|
1818
|
-
pipeline.unshift({ $match: match });
|
|
1819
|
-
return;
|
|
1820
|
-
}
|
|
1821
|
-
const first = pipeline[0];
|
|
1822
|
-
if (!isRecord(first)) {
|
|
1823
|
-
pipeline.unshift({ $match: match });
|
|
1824
|
-
return;
|
|
1825
|
-
}
|
|
1826
|
-
if ("$geoNear" in first) {
|
|
1827
|
-
const geoNear = first.$geoNear;
|
|
1828
|
-
if (isRecord(geoNear)) {
|
|
1829
|
-
geoNear.query = mergeMongoQuery(geoNear.query, match);
|
|
1830
|
-
return;
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
if ("$search" in first || "$vectorSearch" in first || "$searchMeta" in first) {
|
|
1834
|
-
pipeline.splice(1, 0, { $match: match });
|
|
1835
|
-
return;
|
|
1836
|
-
}
|
|
1837
|
-
pipeline.unshift({ $match: match });
|
|
1838
|
-
};
|
|
1839
|
-
var addAggregateAclFilter = (aggregate, action) => {
|
|
1840
|
-
const storedAcl = getStoredAclFromAggregate(aggregate);
|
|
1841
|
-
if (!storedAcl) return;
|
|
1842
|
-
const ability = storedAcl.ability;
|
|
1843
|
-
const resolvedAction = storedAcl.action ?? action;
|
|
1844
|
-
const modelName = aggregate.model().modelName;
|
|
1845
|
-
const accessQuery = accessibleBy(ability, resolvedAction).ofType(modelName);
|
|
1846
|
-
injectAggregateMatch(aggregate.pipeline(), accessQuery);
|
|
1847
|
-
};
|
|
1848
|
-
var patchAggregateAcl = () => {
|
|
1849
|
-
const globalKey = Symbol.for("@rpcbase/db/acl/mongooseAggregateAclPatched");
|
|
1850
|
-
const globalState = globalThis;
|
|
1851
|
-
if (globalState[globalKey]) return;
|
|
1852
|
-
globalState[globalKey] = true;
|
|
1853
|
-
const AggregatePrototype = mongoose$1.Aggregate.prototype;
|
|
1854
|
-
if (typeof AggregatePrototype.acl === "function") return;
|
|
1855
|
-
AggregatePrototype.acl = function(ability, action = "read") {
|
|
1856
|
-
this.option({ rbAcl: {
|
|
1857
|
-
ability,
|
|
1858
|
-
action
|
|
1859
|
-
} });
|
|
1860
|
-
return this;
|
|
1861
|
-
};
|
|
1862
|
-
};
|
|
1863
|
-
var createModelAclProxy = (model, ability) => {
|
|
1864
|
-
return new Proxy(model, { get(target, prop, receiver) {
|
|
1865
|
-
if (prop === "acl") return () => {
|
|
1866
|
-
throw new Error(`Model "${target.modelName}" is already ACL-scoped. Do not call .acl(...) again.`);
|
|
1867
|
-
};
|
|
1868
|
-
const value = Reflect.get(target, prop, receiver);
|
|
1869
|
-
if (typeof value !== "function") return value;
|
|
1870
|
-
return (...args) => {
|
|
1871
|
-
const result = Reflect.apply(value, target, args);
|
|
1872
|
-
if (result && typeof result === "object" && "acl" in result && typeof result.acl === "function") return result.acl(ability);
|
|
1873
|
-
return result;
|
|
1874
|
-
};
|
|
1875
|
-
} });
|
|
1876
|
-
};
|
|
1877
|
-
var mongooseAclPlugin = (schema) => {
|
|
1878
|
-
patchAggregateAcl();
|
|
1879
|
-
schema.query.acl = function(ability, action) {
|
|
1880
|
-
this.setOptions({ rbAcl: {
|
|
1881
|
-
ability,
|
|
1882
|
-
action
|
|
1883
|
-
} });
|
|
1884
|
-
return this;
|
|
1885
|
-
};
|
|
1886
|
-
schema.statics.acl = function(ability) {
|
|
1887
|
-
return createModelAclProxy(this, ability);
|
|
1888
|
-
};
|
|
1889
|
-
schema.pre("aggregate", function() {
|
|
1890
|
-
addAggregateAclFilter(this, "read");
|
|
1891
|
-
});
|
|
1892
|
-
schema.pre("countDocuments", function() {
|
|
1893
|
-
addQueryAclFilter(this, "read");
|
|
1894
|
-
});
|
|
1895
|
-
schema.pre("deleteMany", function() {
|
|
1896
|
-
addQueryAclFilter(this, "delete");
|
|
1897
|
-
});
|
|
1898
|
-
schema.pre("deleteOne", function() {
|
|
1899
|
-
addQueryAclFilter(this, "delete");
|
|
1900
|
-
});
|
|
1901
|
-
schema.pre("distinct", function() {
|
|
1902
|
-
addQueryAclFilter(this, "read");
|
|
1903
|
-
});
|
|
1904
|
-
schema.pre("find", function() {
|
|
1905
|
-
addQueryAclFilter(this, "read");
|
|
1906
|
-
});
|
|
1907
|
-
schema.pre("findOne", function() {
|
|
1908
|
-
addQueryAclFilter(this, "read");
|
|
1909
|
-
});
|
|
1910
|
-
schema.pre("findOneAndDelete", function() {
|
|
1911
|
-
addQueryAclFilter(this, "delete");
|
|
1912
|
-
});
|
|
1913
|
-
schema.pre("findOneAndReplace", function() {
|
|
1914
|
-
addQueryAclFilter(this, "update");
|
|
1915
|
-
});
|
|
1916
|
-
schema.pre("findOneAndUpdate", function() {
|
|
1917
|
-
addQueryAclFilter(this, "update");
|
|
1918
|
-
});
|
|
1919
|
-
schema.pre("replaceOne", function() {
|
|
1920
|
-
addQueryAclFilter(this, "update");
|
|
1921
|
-
});
|
|
1922
|
-
schema.pre("updateMany", function() {
|
|
1923
|
-
addQueryAclFilter(this, "update");
|
|
1924
|
-
});
|
|
1925
|
-
schema.pre("updateOne", function() {
|
|
1926
|
-
addQueryAclFilter(this, "update");
|
|
1927
|
-
});
|
|
1928
|
-
};
|
|
1929
|
-
//#endregion
|
|
1930
2418
|
//#region src/registerModels.ts
|
|
1931
2419
|
var cachedModels = null;
|
|
1932
2420
|
var DEFAULT_GLOBAL_RB_MODEL_NAMES_SET = /* @__PURE__ */ new Set([
|
|
@@ -1949,7 +2437,6 @@ var getFrameworkSchemaForModelName = (modelName) => {
|
|
|
1949
2437
|
};
|
|
1950
2438
|
var applyTenantPlugins = (schema) => {
|
|
1951
2439
|
schema.plugin(accessibleRecordsPlugin);
|
|
1952
|
-
schema.plugin(mongooseAclPlugin);
|
|
1953
2440
|
schema.plugin(mongoPaginationPlugin);
|
|
1954
2441
|
schema.plugin(rtsChangeLogPlugin);
|
|
1955
2442
|
};
|
|
@@ -1965,6 +2452,7 @@ var buildSchemasFromModules = (modules) => Object.entries(modules).filter(([key]
|
|
|
1965
2452
|
};
|
|
1966
2453
|
});
|
|
1967
2454
|
var registerModels = ({ tenant, global }, options = {}) => {
|
|
2455
|
+
installMongooseAclDriver();
|
|
1968
2456
|
registerPoliciesFromModules(models_exports);
|
|
1969
2457
|
registerPoliciesFromModules(tenant);
|
|
1970
2458
|
const tenantSchemas = {};
|
|
@@ -1999,7 +2487,7 @@ var registerModels = ({ tenant, global }, options = {}) => {
|
|
|
1999
2487
|
...globalSchemas,
|
|
2000
2488
|
...tenantSchemas
|
|
2001
2489
|
};
|
|
2002
|
-
for (const [modelName, schema] of Object.entries(allSchemas)) if (!mongoose$1.models[modelName]) mongoose$1.model(modelName, schema);
|
|
2490
|
+
for (const [modelName, schema] of Object.entries(allSchemas)) if (!mongoose$1.models[modelName]) runWithUnsafeMongooseAclDriver(() => mongoose$1.model(modelName, schema));
|
|
2003
2491
|
cachedModels = {
|
|
2004
2492
|
tenant: {
|
|
2005
2493
|
...cachedModels?.tenant ?? {},
|
|
@@ -2016,13 +2504,388 @@ var getRegisteredModels = (scope) => {
|
|
|
2016
2504
|
return cachedModels[scope];
|
|
2017
2505
|
};
|
|
2018
2506
|
//#endregion
|
|
2507
|
+
//#region src/acl/mongooseAclModel.ts
|
|
2508
|
+
var scopedModelsByAbility = /* @__PURE__ */ new WeakMap();
|
|
2509
|
+
var unboundModels = /* @__PURE__ */ new WeakMap();
|
|
2510
|
+
var unsafeModels = /* @__PURE__ */ new WeakMap();
|
|
2511
|
+
var collectionTargets = /* @__PURE__ */ new WeakMap();
|
|
2512
|
+
var blockedModelMethods = /* @__PURE__ */ new Set([
|
|
2513
|
+
"$__updateConnection",
|
|
2514
|
+
"__subclass",
|
|
2515
|
+
"clientEncryption",
|
|
2516
|
+
"cleanIndexes",
|
|
2517
|
+
"compile",
|
|
2518
|
+
"createCollection",
|
|
2519
|
+
"createIndexes",
|
|
2520
|
+
"createSearchIndex",
|
|
2521
|
+
"createSearchIndexes",
|
|
2522
|
+
"diffIndexes",
|
|
2523
|
+
"dropSearchIndex",
|
|
2524
|
+
"ensureIndexes",
|
|
2525
|
+
"init",
|
|
2526
|
+
"listIndexes",
|
|
2527
|
+
"listSearchIndexes",
|
|
2528
|
+
"recompileSchema",
|
|
2529
|
+
"startSession",
|
|
2530
|
+
"syncIndexes",
|
|
2531
|
+
"updateSearchIndex",
|
|
2532
|
+
"useConnection",
|
|
2533
|
+
"watch"
|
|
2534
|
+
]);
|
|
2535
|
+
var blockedCollectionProperties = /* @__PURE__ */ new Set([
|
|
2536
|
+
"_getCollection",
|
|
2537
|
+
"addQueue",
|
|
2538
|
+
"doQueue",
|
|
2539
|
+
"emitter",
|
|
2540
|
+
"onClose",
|
|
2541
|
+
"onOpen",
|
|
2542
|
+
"queue",
|
|
2543
|
+
"removeQueue"
|
|
2544
|
+
]);
|
|
2545
|
+
var blockedConnectionMethods = /* @__PURE__ */ new Set([
|
|
2546
|
+
"aggregate",
|
|
2547
|
+
"bulkWrite",
|
|
2548
|
+
"close",
|
|
2549
|
+
"createClient",
|
|
2550
|
+
"createCollection",
|
|
2551
|
+
"createCollections",
|
|
2552
|
+
"deleteModel",
|
|
2553
|
+
"destroy",
|
|
2554
|
+
"doClose",
|
|
2555
|
+
"dropCollection",
|
|
2556
|
+
"dropDatabase",
|
|
2557
|
+
"listCollections",
|
|
2558
|
+
"listDatabases",
|
|
2559
|
+
"onClose",
|
|
2560
|
+
"onOpen",
|
|
2561
|
+
"openUri",
|
|
2562
|
+
"plugin",
|
|
2563
|
+
"set",
|
|
2564
|
+
"setClient",
|
|
2565
|
+
"startSession",
|
|
2566
|
+
"syncIndexes",
|
|
2567
|
+
"transaction",
|
|
2568
|
+
"useDb",
|
|
2569
|
+
"watch",
|
|
2570
|
+
"withSession"
|
|
2571
|
+
]);
|
|
2572
|
+
var blockedConnectionProperties = /* @__PURE__ */ new Set([
|
|
2573
|
+
"$initialConnection",
|
|
2574
|
+
"_parent",
|
|
2575
|
+
"client",
|
|
2576
|
+
"otherDbs",
|
|
2577
|
+
"relatedDbs"
|
|
2578
|
+
]);
|
|
2579
|
+
var blockedMongooseMethods = /* @__PURE__ */ new Set([
|
|
2580
|
+
"connect",
|
|
2581
|
+
"createConnection",
|
|
2582
|
+
"deleteModel",
|
|
2583
|
+
"disconnect",
|
|
2584
|
+
"model",
|
|
2585
|
+
"plugin",
|
|
2586
|
+
"set",
|
|
2587
|
+
"setDriver",
|
|
2588
|
+
"startSession",
|
|
2589
|
+
"syncIndexes"
|
|
2590
|
+
]);
|
|
2591
|
+
var blockedMongooseProperties = /* @__PURE__ */ new Set([
|
|
2592
|
+
"connection",
|
|
2593
|
+
"connections",
|
|
2594
|
+
"models"
|
|
2595
|
+
]);
|
|
2596
|
+
var allowedDbProperties = /* @__PURE__ */ new Set([
|
|
2597
|
+
"databaseName",
|
|
2598
|
+
"namespace",
|
|
2599
|
+
"readConcern",
|
|
2600
|
+
"readPreference",
|
|
2601
|
+
"writeConcern"
|
|
2602
|
+
]);
|
|
2603
|
+
var getModelCollectionName = (model) => {
|
|
2604
|
+
const collection = model.collection;
|
|
2605
|
+
const value = collection.collectionName ?? collection.name;
|
|
2606
|
+
return typeof value === "string" ? value : "";
|
|
2607
|
+
};
|
|
2608
|
+
var resolveModelNameForCollection = (connection, collectionName) => {
|
|
2609
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
2610
|
+
for (const model of [...Object.values(connection.models ?? {}), ...Object.values(mongoose$1.models)]) {
|
|
2611
|
+
const typedModel = model;
|
|
2612
|
+
if (getModelCollectionName(typedModel) === collectionName) candidates.add(typedModel.modelName);
|
|
2613
|
+
}
|
|
2614
|
+
return candidates.size === 1 ? [...candidates][0] : void 0;
|
|
2615
|
+
};
|
|
2616
|
+
var detachMongooseSubclassRetention = (rawModel, scopedModel) => {
|
|
2617
|
+
const symbol = Object.getOwnPropertySymbols(rawModel).find((entry) => entry.description === "mongoose#Model#subclassed");
|
|
2618
|
+
if (!symbol) return;
|
|
2619
|
+
const subclasses = rawModel[symbol];
|
|
2620
|
+
if (!Array.isArray(subclasses)) return;
|
|
2621
|
+
const index = subclasses.lastIndexOf(scopedModel);
|
|
2622
|
+
if (index >= 0) subclasses.splice(index, 1);
|
|
2623
|
+
};
|
|
2624
|
+
var getCachedModel = (model, context) => {
|
|
2625
|
+
if (context.mode === "unsafe") return unsafeModels.get(model);
|
|
2626
|
+
if (context.mode === "unbound") return unboundModels.get(model);
|
|
2627
|
+
return scopedModelsByAbility.get(context.ability)?.get(model);
|
|
2628
|
+
};
|
|
2629
|
+
var cacheModel = (rawModel, scopedModel, context) => {
|
|
2630
|
+
if (context.mode === "unsafe") {
|
|
2631
|
+
unsafeModels.set(rawModel, scopedModel);
|
|
2632
|
+
return;
|
|
2633
|
+
}
|
|
2634
|
+
if (context.mode === "unbound") {
|
|
2635
|
+
unboundModels.set(rawModel, scopedModel);
|
|
2636
|
+
return;
|
|
2637
|
+
}
|
|
2638
|
+
const models = scopedModelsByAbility.get(context.ability) ?? /* @__PURE__ */ new WeakMap();
|
|
2639
|
+
models.set(rawModel, scopedModel);
|
|
2640
|
+
scopedModelsByAbility.set(context.ability, models);
|
|
2641
|
+
};
|
|
2642
|
+
var lockModelCapability = (target, property, value) => {
|
|
2643
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, property);
|
|
2644
|
+
Object.defineProperty(target, property, {
|
|
2645
|
+
configurable: false,
|
|
2646
|
+
enumerable: descriptor?.enumerable ?? true,
|
|
2647
|
+
get: () => value,
|
|
2648
|
+
set: () => {
|
|
2649
|
+
throw new Error(`ACL-protected model property .${String(property)} cannot be changed. Use models.getUnsafe(...) explicitly.`);
|
|
2650
|
+
}
|
|
2651
|
+
});
|
|
2652
|
+
};
|
|
2653
|
+
var createAclModelWithContext = (model, context) => {
|
|
2654
|
+
const cached = getCachedModel(model, context);
|
|
2655
|
+
if (cached) return cached;
|
|
2656
|
+
const rawConnection = model.db;
|
|
2657
|
+
const primaryCollectionName = getModelCollectionName(model);
|
|
2658
|
+
const collections = /* @__PURE__ */ new Map();
|
|
2659
|
+
let mongooseProxy;
|
|
2660
|
+
let collectionsProxy;
|
|
2661
|
+
let modelsProxy;
|
|
2662
|
+
let dbProxy;
|
|
2663
|
+
const getMongooseProxy = () => {
|
|
2664
|
+
const rawMongoose = rawConnection.base;
|
|
2665
|
+
if (context.mode === "unsafe") return rawMongoose;
|
|
2666
|
+
if (mongooseProxy) return mongooseProxy;
|
|
2667
|
+
mongooseProxy = new Proxy(rawMongoose, {
|
|
2668
|
+
get(target, prop) {
|
|
2669
|
+
if (prop === "default" || prop === "mongoose") return mongooseProxy;
|
|
2670
|
+
if (blockedMongooseProperties.has(prop)) throw new Error(`ACL-protected Mongoose property .${String(prop)} is blocked. Use models.getUnsafe(...) explicitly.`);
|
|
2671
|
+
if (blockedMongooseMethods.has(prop)) return () => {
|
|
2672
|
+
throw new Error(`ACL-protected Mongoose method ${String(prop)}() is blocked. Use models.getUnsafe(...) explicitly.`);
|
|
2673
|
+
};
|
|
2674
|
+
const value = Reflect.get(target, prop, target);
|
|
2675
|
+
if (typeof value !== "function") return value;
|
|
2676
|
+
return (...args) => {
|
|
2677
|
+
const result = Reflect.apply(value, target, args);
|
|
2678
|
+
return result === target ? mongooseProxy : result;
|
|
2679
|
+
};
|
|
2680
|
+
},
|
|
2681
|
+
set(_target, prop) {
|
|
2682
|
+
throw new Error(`ACL-protected Mongoose property .${String(prop)} cannot be changed. Use models.getUnsafe(...) explicitly.`);
|
|
2683
|
+
}
|
|
2684
|
+
});
|
|
2685
|
+
return mongooseProxy;
|
|
2686
|
+
};
|
|
2687
|
+
const createCollectionProxy = (collection) => {
|
|
2688
|
+
if (context.mode === "unsafe") return collection;
|
|
2689
|
+
const proxy = new Proxy(collection, {
|
|
2690
|
+
get(target, prop) {
|
|
2691
|
+
if (prop === "conn") return connectionProxy;
|
|
2692
|
+
if (blockedCollectionProperties.has(prop)) throw new Error(`ACL-protected collection property .${String(prop)} is blocked. Use models.getUnsafe(...) explicitly.`);
|
|
2693
|
+
const value = Reflect.get(target, prop, target);
|
|
2694
|
+
if (typeof value !== "function") return value;
|
|
2695
|
+
return (...args) => {
|
|
2696
|
+
const result = Reflect.apply(value, target, args);
|
|
2697
|
+
return result === target ? proxy : result;
|
|
2698
|
+
};
|
|
2699
|
+
},
|
|
2700
|
+
set(_target, prop) {
|
|
2701
|
+
throw new Error(`ACL-protected collection property .${String(prop)} cannot be changed. Use models.getUnsafe(...) explicitly.`);
|
|
2702
|
+
}
|
|
2703
|
+
});
|
|
2704
|
+
collectionTargets.set(proxy, collection);
|
|
2705
|
+
return proxy;
|
|
2706
|
+
};
|
|
2707
|
+
const getCollection = (collectionName, options = {}) => {
|
|
2708
|
+
const existing = collections.get(collectionName);
|
|
2709
|
+
if (existing) return existing;
|
|
2710
|
+
const modelName = collectionName === primaryCollectionName ? model.modelName : resolveModelNameForCollection(rawConnection, collectionName);
|
|
2711
|
+
if (context.mode !== "unsafe" && collectionName !== primaryCollectionName && (!modelName || !hasRegisteredPolicy(modelName))) throw new Error(`ACL-protected connections cannot access collection "${collectionName}" without a protected model. Load that model explicitly or use models.getUnsafe(...).`);
|
|
2712
|
+
const collection = new AclMongooseCollection(collectionName, rawConnection, {
|
|
2713
|
+
...options,
|
|
2714
|
+
modelName
|
|
2715
|
+
});
|
|
2716
|
+
bindMongooseAclDriverContext(collection, context);
|
|
2717
|
+
const exposedCollection = createCollectionProxy(collection);
|
|
2718
|
+
collections.set(collectionName, exposedCollection);
|
|
2719
|
+
return exposedCollection;
|
|
2720
|
+
};
|
|
2721
|
+
const getRelatedModel = (modelName) => {
|
|
2722
|
+
return createAclModelWithContext(rawConnection.model(modelName), context);
|
|
2723
|
+
};
|
|
2724
|
+
const getModelsProxy = () => {
|
|
2725
|
+
if (modelsProxy) return modelsProxy;
|
|
2726
|
+
modelsProxy = new Proxy(rawConnection.models, {
|
|
2727
|
+
get(target, prop, receiver) {
|
|
2728
|
+
const value = Reflect.get(target, prop, receiver);
|
|
2729
|
+
return typeof prop === "string" && Object.hasOwn(target, prop) && value && typeof value === "function" ? getRelatedModel(prop) : value;
|
|
2730
|
+
},
|
|
2731
|
+
set(target, prop, value, receiver) {
|
|
2732
|
+
if (context.mode === "unsafe") return Reflect.set(target, prop, value, receiver);
|
|
2733
|
+
throw new Error(`ACL-protected connection model .${String(prop)} cannot be changed.`);
|
|
2734
|
+
},
|
|
2735
|
+
deleteProperty(target, prop) {
|
|
2736
|
+
if (context.mode === "unsafe") return Reflect.deleteProperty(target, prop);
|
|
2737
|
+
throw new Error(`ACL-protected connection model .${String(prop)} cannot be deleted.`);
|
|
2738
|
+
}
|
|
2739
|
+
});
|
|
2740
|
+
return modelsProxy;
|
|
2741
|
+
};
|
|
2742
|
+
const getCollectionsProxy = () => {
|
|
2743
|
+
if (collectionsProxy) return collectionsProxy;
|
|
2744
|
+
collectionsProxy = new Proxy(rawConnection.collections, {
|
|
2745
|
+
get(target, prop, receiver) {
|
|
2746
|
+
const value = Reflect.get(target, prop, receiver);
|
|
2747
|
+
return typeof prop === "string" && Object.hasOwn(target, prop) && value ? getCollection(prop) : value;
|
|
2748
|
+
},
|
|
2749
|
+
set(target, prop, value, receiver) {
|
|
2750
|
+
if (context.mode === "unsafe") return Reflect.set(target, prop, value, receiver);
|
|
2751
|
+
throw new Error(`ACL-protected connection collection .${String(prop)} cannot be changed.`);
|
|
2752
|
+
},
|
|
2753
|
+
deleteProperty(target, prop) {
|
|
2754
|
+
if (context.mode === "unsafe") return Reflect.deleteProperty(target, prop);
|
|
2755
|
+
throw new Error(`ACL-protected connection collection .${String(prop)} cannot be deleted.`);
|
|
2756
|
+
}
|
|
2757
|
+
});
|
|
2758
|
+
return collectionsProxy;
|
|
2759
|
+
};
|
|
2760
|
+
const getDbProxy = () => {
|
|
2761
|
+
const rawDb = rawConnection.db;
|
|
2762
|
+
if (!rawDb || context.mode === "unsafe") return rawDb;
|
|
2763
|
+
if (dbProxy) return dbProxy;
|
|
2764
|
+
dbProxy = new Proxy(rawDb, {
|
|
2765
|
+
get(target, prop) {
|
|
2766
|
+
if (prop === "collection") return (collectionName) => getCollection(collectionName).collection;
|
|
2767
|
+
if (allowedDbProperties.has(prop)) return Reflect.get(target, prop, target);
|
|
2768
|
+
throw new Error(`ACL-protected database access .${String(prop)} is blocked. Use models.getUnsafe(...) explicitly.`);
|
|
2769
|
+
},
|
|
2770
|
+
set(_target, prop) {
|
|
2771
|
+
throw new Error(`ACL-protected database property .${String(prop)} cannot be changed. Use models.getUnsafe(...) explicitly.`);
|
|
2772
|
+
}
|
|
2773
|
+
});
|
|
2774
|
+
return dbProxy;
|
|
2775
|
+
};
|
|
2776
|
+
const connectionProxy = new Proxy(rawConnection, {
|
|
2777
|
+
get(target, prop) {
|
|
2778
|
+
if (prop === "collection") return getCollection;
|
|
2779
|
+
if (prop === "collections") return getCollectionsProxy();
|
|
2780
|
+
if (prop === "models") return getModelsProxy();
|
|
2781
|
+
if (prop === "db") return getDbProxy();
|
|
2782
|
+
if (prop === "base") return getMongooseProxy();
|
|
2783
|
+
if (context.mode !== "unsafe" && blockedConnectionProperties.has(prop)) throw new Error(`ACL-protected connection property .${String(prop)} is blocked. Use models.getUnsafe(...) explicitly.`);
|
|
2784
|
+
if (prop === "getClient") {
|
|
2785
|
+
if (context.mode === "unsafe") return target.getClient.bind(target);
|
|
2786
|
+
return () => {
|
|
2787
|
+
throw new Error("ACL-protected MongoClient access is blocked. Use models.getUnsafe(...) explicitly.");
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
2790
|
+
if (prop === "model") return (modelName, ...args) => {
|
|
2791
|
+
if (context.mode !== "unsafe" && args.length > 0) throw new Error("ACL-protected connections cannot compile models. Use models.getUnsafe(...) explicitly.");
|
|
2792
|
+
return createAclModelWithContext(Reflect.apply(target.model, target, [modelName, ...args]), context);
|
|
2793
|
+
};
|
|
2794
|
+
if (context.mode !== "unsafe" && blockedConnectionMethods.has(prop)) return () => {
|
|
2795
|
+
throw new Error(`ACL-protected connection method ${String(prop)}() is blocked. Use models.getUnsafe(...) explicitly.`);
|
|
2796
|
+
};
|
|
2797
|
+
const value = Reflect.get(target, prop, target);
|
|
2798
|
+
if (typeof value !== "function") return value;
|
|
2799
|
+
return (...args) => {
|
|
2800
|
+
const result = Reflect.apply(value, target, args);
|
|
2801
|
+
if (result === target) return connectionProxy;
|
|
2802
|
+
if (result && typeof result === "object" && "then" in result) {
|
|
2803
|
+
if (typeof result.then === "function") return Promise.resolve(result).then((resolved) => resolved === target ? connectionProxy : resolved);
|
|
2804
|
+
}
|
|
2805
|
+
return result;
|
|
2806
|
+
};
|
|
2807
|
+
},
|
|
2808
|
+
set(target, prop, value, receiver) {
|
|
2809
|
+
if (context.mode === "unsafe") return Reflect.set(target, prop, value, receiver);
|
|
2810
|
+
throw new Error(`ACL-protected connection property .${String(prop)} cannot be changed. Use models.getUnsafe(...) explicitly.`);
|
|
2811
|
+
}
|
|
2812
|
+
});
|
|
2813
|
+
bindMongooseAclRawConnection(connectionProxy, rawConnection);
|
|
2814
|
+
const scopedModel = model.__subclass(connectionProxy, model.schema, primaryCollectionName);
|
|
2815
|
+
detachMongooseSubclassRetention(model, scopedModel);
|
|
2816
|
+
cacheModel(model, scopedModel, context);
|
|
2817
|
+
Object.defineProperty(scopedModel, "acl", {
|
|
2818
|
+
configurable: false,
|
|
2819
|
+
value: (ability) => createAclModelWithContext(model, {
|
|
2820
|
+
mode: "ability",
|
|
2821
|
+
ability
|
|
2822
|
+
})
|
|
2823
|
+
});
|
|
2824
|
+
if (context.mode !== "unsafe") {
|
|
2825
|
+
const scopedCollection = scopedModel.collection;
|
|
2826
|
+
lockModelCapability(scopedModel, "base", getMongooseProxy());
|
|
2827
|
+
lockModelCapability(scopedModel, "db", connectionProxy);
|
|
2828
|
+
lockModelCapability(scopedModel, "collection", scopedCollection);
|
|
2829
|
+
lockModelCapability(scopedModel, "$__collection", scopedCollection);
|
|
2830
|
+
lockModelCapability(scopedModel.prototype, "db", connectionProxy);
|
|
2831
|
+
lockModelCapability(scopedModel.prototype, "collection", scopedCollection);
|
|
2832
|
+
lockModelCapability(scopedModel.prototype, "$collection", scopedCollection);
|
|
2833
|
+
}
|
|
2834
|
+
const rawDiscriminators = model.discriminators;
|
|
2835
|
+
if (rawDiscriminators) {
|
|
2836
|
+
const generatedDiscriminators = scopedModel.discriminators ?? {};
|
|
2837
|
+
const scopedDiscriminators = {};
|
|
2838
|
+
for (const [name, discriminator] of Object.entries(rawDiscriminators)) {
|
|
2839
|
+
const generated = generatedDiscriminators[name];
|
|
2840
|
+
if (generated) detachMongooseSubclassRetention(discriminator, generated);
|
|
2841
|
+
scopedDiscriminators[name] = createAclModelWithContext(discriminator, context);
|
|
2842
|
+
}
|
|
2843
|
+
scopedModel.discriminators = scopedDiscriminators;
|
|
2844
|
+
}
|
|
2845
|
+
if (context.mode !== "unsafe") {
|
|
2846
|
+
for (const methodName of blockedModelMethods) Object.defineProperty(scopedModel, methodName, {
|
|
2847
|
+
configurable: false,
|
|
2848
|
+
value: () => {
|
|
2849
|
+
throw new Error(`ACL-protected model method ${String(methodName)}() is blocked. Use models.getUnsafe(...) explicitly.`);
|
|
2850
|
+
}
|
|
2851
|
+
});
|
|
2852
|
+
Object.defineProperty(scopedModel, "discriminator", {
|
|
2853
|
+
configurable: false,
|
|
2854
|
+
value: () => {
|
|
2855
|
+
throw new Error("ACL-protected models cannot compile discriminators. Define them before models.get(...).");
|
|
2856
|
+
}
|
|
2857
|
+
});
|
|
2858
|
+
}
|
|
2859
|
+
return scopedModel;
|
|
2860
|
+
};
|
|
2861
|
+
installMongooseAclDriver();
|
|
2862
|
+
var createAclMongooseModel = (model, ability) => createAclModelWithContext(model, {
|
|
2863
|
+
mode: "ability",
|
|
2864
|
+
ability
|
|
2865
|
+
});
|
|
2866
|
+
var createAclMongooseModelGateway = (model) => createAclModelWithContext(model, unboundMongooseAclDriverContext);
|
|
2867
|
+
var createUnsafeMongooseModel = (model) => createAclModelWithContext(model, unsafeMongooseAclDriverContext);
|
|
2868
|
+
var installMongooseAclModelFactory = () => {
|
|
2869
|
+
const modelConstructor = mongoose$1.Model;
|
|
2870
|
+
if (Object.hasOwn(modelConstructor, "acl")) return;
|
|
2871
|
+
Object.defineProperty(modelConstructor, "acl", {
|
|
2872
|
+
configurable: false,
|
|
2873
|
+
value: function(ability) {
|
|
2874
|
+
return createAclMongooseModel(this, ability);
|
|
2875
|
+
}
|
|
2876
|
+
});
|
|
2877
|
+
};
|
|
2878
|
+
installMongooseAclModelFactory();
|
|
2879
|
+
//#endregion
|
|
2019
2880
|
//#region src/modelsApi.ts
|
|
2020
2881
|
var loadModelFromDb = async (modelName, dbName, scope) => {
|
|
2021
2882
|
const schemas = getRegisteredModels(scope);
|
|
2022
2883
|
const schema = schemas[modelName];
|
|
2023
2884
|
assert(schema, `Model ${modelName} not registered. Available models: ${Object.keys(schemas).join(", ")}`);
|
|
2024
2885
|
const modelConnection = await ensureMongooseConnection(dbName);
|
|
2025
|
-
if (!modelConnection.models[modelName])
|
|
2886
|
+
if (!modelConnection.models[modelName]) await runWithUnsafeMongooseAclDriver(async () => {
|
|
2887
|
+
await modelConnection.model(modelName, schema).init();
|
|
2888
|
+
});
|
|
2026
2889
|
return modelConnection.models[modelName];
|
|
2027
2890
|
};
|
|
2028
2891
|
var normalizeTenantId$1 = (value) => {
|
|
@@ -2037,15 +2900,11 @@ var getTenantIdFromLoadModelCtx = (ctx) => {
|
|
|
2037
2900
|
var models = {
|
|
2038
2901
|
register: registerModels,
|
|
2039
2902
|
getUnsafe: async (modelName, ctx) => {
|
|
2040
|
-
return loadModelFromDb(modelName, getTenantDbName(getTenantIdFromLoadModelCtx(ctx)), "tenant");
|
|
2903
|
+
return createUnsafeMongooseModel(await loadModelFromDb(modelName, getTenantDbName(getTenantIdFromLoadModelCtx(ctx)), "tenant"));
|
|
2041
2904
|
},
|
|
2042
2905
|
get: async (modelName, ctx) => {
|
|
2043
|
-
const model = await
|
|
2044
|
-
|
|
2045
|
-
if (!hasRegisteredPolicy(modelName)) return model;
|
|
2046
|
-
if (!resolvedAbility) throw new Error(`Model "${modelName}" is ACL-protected. Set ctx.ability or use models.getUnsafe(...) explicitly.`);
|
|
2047
|
-
if (typeof model.acl !== "function") return model;
|
|
2048
|
-
return model.acl(resolvedAbility);
|
|
2906
|
+
const model = await loadModelFromDb(modelName, getTenantDbName(getTenantIdFromLoadModelCtx(ctx)), "tenant");
|
|
2907
|
+
return ctx.ability ? createAclMongooseModel(model, ctx.ability) : createAclMongooseModelGateway(model);
|
|
2049
2908
|
},
|
|
2050
2909
|
getGlobal: async (modelName, ctx) => {
|
|
2051
2910
|
return loadModelFromDb(modelName, getGlobalDbName(), "global");
|