@rpcbase/server 0.635.0 → 0.637.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/{convertHeifToWebp-DwSrD30G.js → convertHeifToWebp-CIP6qLY7.js} +2 -2
- package/dist/{convertHeifToWebp-DwSrD30G.js.map → convertHeifToWebp-CIP6qLY7.js.map} +1 -1
- package/dist/{handler-Z6wToZhb.js → handler-BkGfOJ1l.js} +2 -2
- package/dist/{handler-Z6wToZhb.js.map → handler-BkGfOJ1l.js.map} +1 -1
- package/dist/{handler-C34TFH28.js → handler-Dy_L6_-U.js} +36 -8
- package/dist/handler-Dy_L6_-U.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/proxyAuth.d.ts +4 -1
- package/dist/proxyAuth.d.ts.map +1 -1
- package/dist/{queryWindow-NCe5vJHt.js → queryWindow-RHVy3D0L.js} +51 -35
- package/dist/queryWindow-RHVy3D0L.js.map +1 -0
- package/dist/rts/index.d.ts +3 -1
- package/dist/rts/index.d.ts.map +1 -1
- package/dist/rts/index.js +87 -10
- package/dist/rts/index.js.map +1 -1
- package/dist/rts/origin.d.ts +3 -0
- package/dist/rts/origin.d.ts.map +1 -0
- package/dist/sharp.d.ts +3 -0
- package/dist/sharp.d.ts.map +1 -0
- package/dist/sharp.js +2 -0
- package/dist/syncAuthenticatedSession.d.ts +2 -0
- package/dist/syncAuthenticatedSession.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/uploads/api/files/handlers/getFile.d.ts.map +1 -1
- package/dist/uploads/fileContentHeaders.d.ts +2 -0
- package/dist/uploads/fileContentHeaders.d.ts.map +1 -0
- package/dist/uploads/worker.js +1 -1
- package/dist/uploads.js +2 -2
- package/package.json +6 -1
- package/dist/handler-C34TFH28.js.map +0 -1
- package/dist/queryWindow-NCe5vJHt.js.map +0 -1
|
@@ -12,6 +12,7 @@ var getDerivedKey = (masterKey, info, length = 32, salt = "") => {
|
|
|
12
12
|
//#region src/proxyAuth.ts
|
|
13
13
|
var AUTHENTICATED_USER_ID_HEADER = "rb-authenticated-user-id";
|
|
14
14
|
var AUTHENTICATED_TENANT_ID_HEADER = "rb-authenticated-tenant-id";
|
|
15
|
+
var AUTH_VERSION_HEADER = "rb-auth-version";
|
|
15
16
|
var PROXY_AUTH_TIMESTAMP_HEADER = "rb-proxy-auth-timestamp";
|
|
16
17
|
var PROXY_AUTH_SIGNATURE_HEADER = "rb-proxy-auth-signature";
|
|
17
18
|
var MAX_PROXY_AUTH_AGE_MS = 3e5;
|
|
@@ -33,14 +34,17 @@ var timingSafeEqualText = (left, right) => {
|
|
|
33
34
|
if (leftBuffer.length !== rightBuffer.length) return false;
|
|
34
35
|
return timingSafeEqual(leftBuffer, rightBuffer);
|
|
35
36
|
};
|
|
36
|
-
var buildProxyAuthSignature = ({ userId, tenantId, timestamp, secret }) => {
|
|
37
|
-
return createHmac("sha256", secret).update(`${timestamp}:${userId}:${tenantId}`).digest("hex");
|
|
37
|
+
var buildProxyAuthSignature = ({ userId, tenantId, timestamp, secret, authVersion }) => {
|
|
38
|
+
return createHmac("sha256", secret).update(`${timestamp}:${userId}:${tenantId}${authVersion === void 0 ? "" : `:${authVersion}`}`).digest("hex");
|
|
38
39
|
};
|
|
39
40
|
var getTrustedProxyAuth = (headers) => {
|
|
40
41
|
const userId = getHeaderValue(headers, AUTHENTICATED_USER_ID_HEADER);
|
|
41
42
|
const tenantId = getHeaderValue(headers, AUTHENTICATED_TENANT_ID_HEADER);
|
|
42
43
|
const timestamp = getHeaderValue(headers, PROXY_AUTH_TIMESTAMP_HEADER);
|
|
43
44
|
const signature = getHeaderValue(headers, PROXY_AUTH_SIGNATURE_HEADER);
|
|
45
|
+
const versionHeader = getHeaderValue(headers, AUTH_VERSION_HEADER);
|
|
46
|
+
const authVersion = versionHeader === null ? void 0 : Number(versionHeader);
|
|
47
|
+
if (authVersion !== void 0 && (!Number.isSafeInteger(authVersion) || authVersion < 0)) return null;
|
|
44
48
|
if (!userId || !tenantId || !timestamp || !signature) return null;
|
|
45
49
|
const parsedTimestamp = Number(timestamp);
|
|
46
50
|
if (!Number.isInteger(parsedTimestamp)) return null;
|
|
@@ -51,11 +55,13 @@ var getTrustedProxyAuth = (headers) => {
|
|
|
51
55
|
userId,
|
|
52
56
|
tenantId,
|
|
53
57
|
timestamp,
|
|
54
|
-
secret
|
|
58
|
+
secret,
|
|
59
|
+
authVersion
|
|
55
60
|
}))) return null;
|
|
56
61
|
return {
|
|
57
62
|
userId,
|
|
58
|
-
tenantId
|
|
63
|
+
tenantId,
|
|
64
|
+
authVersion: authVersion ?? 0
|
|
59
65
|
};
|
|
60
66
|
};
|
|
61
67
|
//#endregion
|
|
@@ -89,18 +95,16 @@ var normalizeTenantRoles = (value) => {
|
|
|
89
95
|
}).filter((entry) => Boolean(entry));
|
|
90
96
|
return nextRoles.length ? Object.fromEntries(nextRoles) : void 0;
|
|
91
97
|
};
|
|
92
|
-
var
|
|
93
|
-
if (!sessionUser) return false;
|
|
94
|
-
const signedInTenants = normalizeStringArray(sessionUser.signedInTenants);
|
|
95
|
-
if (signedInTenants.length > 0) return signedInTenants.includes(tenantId);
|
|
96
|
-
return normalizeString$1(sessionUser.currentTenantId) === tenantId;
|
|
97
|
-
};
|
|
98
|
-
var loadSessionUser = async (userId, tenantId) => {
|
|
98
|
+
var loadSessionUser = async (userId, tenantId, authVersion) => {
|
|
99
99
|
const user = await (await models.getGlobal("RBUser", { req: { session: null } })).findById(userId, {
|
|
100
100
|
tenants: 1,
|
|
101
|
-
tenantRoles: 1
|
|
101
|
+
tenantRoles: 1,
|
|
102
|
+
authVersion: 1,
|
|
103
|
+
registrationStatus: 1,
|
|
104
|
+
emailVerificationCode: 1
|
|
102
105
|
}).lean();
|
|
103
|
-
if (!user) return null;
|
|
106
|
+
if (!user || user.registrationStatus === "pending_verification" || typeof user.emailVerificationCode === "string") return null;
|
|
107
|
+
if ((user.authVersion ?? 0) !== (authVersion ?? 0)) return null;
|
|
104
108
|
const signedInTenants = normalizeStringArray(user.tenants);
|
|
105
109
|
if (!signedInTenants.includes(tenantId)) return null;
|
|
106
110
|
const tenantRoles = normalizeTenantRoles(user.tenantRoles);
|
|
@@ -109,32 +113,44 @@ var loadSessionUser = async (userId, tenantId) => {
|
|
|
109
113
|
currentTenantId: tenantId,
|
|
110
114
|
signedInTenants,
|
|
111
115
|
isEntryGateAuthorized: true,
|
|
116
|
+
authVersion: user.authVersion ?? 0,
|
|
112
117
|
...tenantRoles ? { tenantRoles } : {}
|
|
113
118
|
};
|
|
114
119
|
};
|
|
120
|
+
var revalidateAuthenticatedSession = async (req) => {
|
|
121
|
+
const session = req.session;
|
|
122
|
+
const userId = normalizeString$1(session?.user?.id);
|
|
123
|
+
const tenantId = normalizeString$1(session?.user?.currentTenantId);
|
|
124
|
+
if (!session || !userId || !tenantId) return;
|
|
125
|
+
const nextUser = await loadSessionUser(userId, tenantId, session.user?.authVersion);
|
|
126
|
+
if (!nextUser) {
|
|
127
|
+
delete session.user;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
session.user = {
|
|
131
|
+
...session.user,
|
|
132
|
+
tenantRoles: void 0,
|
|
133
|
+
...nextUser
|
|
134
|
+
};
|
|
135
|
+
};
|
|
115
136
|
var syncAuthenticatedSessionFromRequest = async (req) => {
|
|
116
137
|
const proxyAuth = getTrustedProxyAuth(req.headers);
|
|
117
|
-
if (!proxyAuth)
|
|
118
|
-
|
|
119
|
-
const session = req.session;
|
|
120
|
-
if (!session) return;
|
|
121
|
-
const sessionUser = session.user;
|
|
122
|
-
if (normalizeString$1(sessionUser?.id) === userId && isSessionAuthorizedForTenant(sessionUser, tenantId)) {
|
|
123
|
-
if (normalizeString$1(sessionUser?.currentTenantId) === tenantId) return;
|
|
124
|
-
session.user = {
|
|
125
|
-
...sessionUser && typeof sessionUser === "object" ? sessionUser : {},
|
|
126
|
-
id: userId,
|
|
127
|
-
currentTenantId: tenantId,
|
|
128
|
-
isEntryGateAuthorized: true
|
|
129
|
-
};
|
|
138
|
+
if (!proxyAuth) {
|
|
139
|
+
await revalidateAuthenticatedSession(req);
|
|
130
140
|
return;
|
|
131
141
|
}
|
|
132
|
-
const
|
|
133
|
-
if (!
|
|
134
|
-
|
|
142
|
+
const session = req.session;
|
|
143
|
+
if (!session) return;
|
|
144
|
+
const nextUser = await loadSessionUser(proxyAuth.userId, proxyAuth.tenantId, proxyAuth.authVersion);
|
|
145
|
+
if (!nextUser) {
|
|
146
|
+
delete session.user;
|
|
135
147
|
return;
|
|
136
148
|
}
|
|
137
|
-
session.user =
|
|
149
|
+
session.user = {
|
|
150
|
+
...session.user?.id === proxyAuth.userId ? session.user : void 0,
|
|
151
|
+
tenantRoles: void 0,
|
|
152
|
+
...nextUser
|
|
153
|
+
};
|
|
138
154
|
};
|
|
139
155
|
var syncAuthenticatedSessionMiddleware = (req, _res, next) => {
|
|
140
156
|
syncAuthenticatedSessionFromRequest(req).then(() => {
|
|
@@ -212,8 +228,8 @@ var getTenantModel = async (tenantId, modelName, ability) => {
|
|
|
212
228
|
var getUnsafeTenantModel = async (tenantId, modelName) => models.getUnsafe(modelName, { tenantId });
|
|
213
229
|
var normalizeLimit = (limit) => {
|
|
214
230
|
if (typeof limit !== "number") return QUERY_MAX_LIMIT;
|
|
215
|
-
if (!Number.
|
|
216
|
-
return Math.min(QUERY_MAX_LIMIT,
|
|
231
|
+
if (!Number.isSafeInteger(limit) || limit <= 0) return QUERY_MAX_LIMIT;
|
|
232
|
+
return Math.min(QUERY_MAX_LIMIT, limit);
|
|
217
233
|
};
|
|
218
234
|
var normalizeNonNegativeInteger = (value) => {
|
|
219
235
|
if (typeof value !== "number") return 0;
|
|
@@ -326,7 +342,7 @@ var normalizePopulateOptions = (value) => {
|
|
|
326
342
|
const raw = value;
|
|
327
343
|
const normalized = {};
|
|
328
344
|
if (raw.sort && typeof raw.sort === "object" && !Array.isArray(raw.sort)) normalized.sort = raw.sort;
|
|
329
|
-
if (typeof raw.limit === "number"
|
|
345
|
+
if (raw.limit !== void 0) normalized.limit = normalizeLimit(typeof raw.limit === "number" ? raw.limit : void 0);
|
|
330
346
|
if (!normalized.sort && normalized.limit === void 0) return void 0;
|
|
331
347
|
return normalized;
|
|
332
348
|
};
|
|
@@ -551,6 +567,6 @@ var runRtsCount = async ({ tenantId, ability, modelName, query, allowInternalMod
|
|
|
551
567
|
//#region src/rts/queryWindow.ts
|
|
552
568
|
var RTS_QUERY_WINDOW_MAX_COUNT = 4096;
|
|
553
569
|
//#endregion
|
|
554
|
-
export { resolveRtsQueryDependencyModelNames as a, runRtsQuery as c,
|
|
570
|
+
export { resolveRtsQueryDependencyModelNames as a, runRtsQuery as c, syncAuthenticatedSessionMiddleware as d, getTrustedProxyAuth as f, normalizeRtsQueryOptions as i, revalidateAuthenticatedSession as l, buildRtsAbilityFromRequest as n, resolveRtsRequestTenantId as o, getDerivedKey as p, isRtsRequestAuthorized as r, runRtsCount as s, RTS_QUERY_WINDOW_MAX_COUNT as t, syncAuthenticatedSessionFromRequest as u };
|
|
555
571
|
|
|
556
|
-
//# sourceMappingURL=queryWindow-
|
|
572
|
+
//# sourceMappingURL=queryWindow-RHVy3D0L.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queryWindow-RHVy3D0L.js","names":[],"sources":["../src/getDerivedKey.ts","../src/proxyAuth.ts","../src/syncAuthenticatedSession.ts","../src/rts/queryExecutor.ts","../src/rts/queryWindow.ts"],"sourcesContent":["import assert from \"assert\"\nimport { hkdfSync } from \"crypto\"\n\n\nexport const getDerivedKey = (\n masterKey: string,\n info: string,\n length: number = 32, // Default to 256-bit keys\n salt: string = \"\",\n): string => {\n assert(masterKey?.length >= 32, \"MASTER_KEY must be 32 chars or longer.\")\n\n return Buffer.from(hkdfSync(\n \"sha256\",\n masterKey,\n Buffer.from(salt),\n Buffer.from(info),\n length,\n )).toString(\"hex\")\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\"\n\n\nexport const AUTHENTICATED_USER_ID_HEADER = \"rb-authenticated-user-id\"\nexport const AUTHENTICATED_TENANT_ID_HEADER = \"rb-authenticated-tenant-id\"\nexport const AUTH_VERSION_HEADER = \"rb-auth-version\"\nexport const PROXY_AUTH_TIMESTAMP_HEADER = \"rb-proxy-auth-timestamp\"\nexport const PROXY_AUTH_SIGNATURE_HEADER = \"rb-proxy-auth-signature\"\n\nconst MAX_PROXY_AUTH_AGE_MS = 5 * 60 * 1000\n\ntype HeadersLike = Record<string, string | string[] | undefined>\n\nconst normalizeString = (value: unknown): string | null => {\n if (typeof value !== \"string\") return null\n const normalized = value.trim()\n return normalized || null\n}\n\nconst getHeaderValue = (headers: HeadersLike | undefined, name: string): string | null => {\n const value = headers?.[name]\n if (Array.isArray(value)) return normalizeString(value[0])\n return normalizeString(value)\n}\n\nconst getProxySharedSecret = (): string | null => {\n const secret = process.env.RB_PROXY_SHARED_SECRET?.trim()\n return secret || null\n}\n\nconst timingSafeEqualText = (left: string, right: string): boolean => {\n const leftBuffer = Buffer.from(left)\n const rightBuffer = Buffer.from(right)\n if (leftBuffer.length !== rightBuffer.length) return false\n return timingSafeEqual(leftBuffer, rightBuffer)\n}\n\nexport const buildProxyAuthSignature = ({\n userId,\n tenantId,\n timestamp,\n secret,\n authVersion,\n}: {\n userId: string\n tenantId: string\n timestamp: string\n secret: string\n authVersion?: number\n}): string => {\n return createHmac(\"sha256\", secret)\n .update(`${timestamp}:${userId}:${tenantId}${authVersion === undefined ? \"\" : `:${authVersion}`}`)\n .digest(\"hex\")\n}\n\nexport const getTrustedProxyAuth = (\n headers: HeadersLike | undefined,\n): { userId: string; tenantId: string; authVersion: number } | null => {\n const userId = getHeaderValue(headers, AUTHENTICATED_USER_ID_HEADER)\n const tenantId = getHeaderValue(headers, AUTHENTICATED_TENANT_ID_HEADER)\n const timestamp = getHeaderValue(headers, PROXY_AUTH_TIMESTAMP_HEADER)\n const signature = getHeaderValue(headers, PROXY_AUTH_SIGNATURE_HEADER)\n const versionHeader = getHeaderValue(headers, AUTH_VERSION_HEADER)\n const authVersion = versionHeader === null ? undefined : Number(versionHeader)\n if (authVersion !== undefined && (!Number.isSafeInteger(authVersion) || authVersion < 0)) return null\n if (!userId || !tenantId || !timestamp || !signature) return null\n\n const parsedTimestamp = Number(timestamp)\n if (!Number.isInteger(parsedTimestamp)) return null\n\n const now = Date.now()\n if (Math.abs(now - parsedTimestamp) > MAX_PROXY_AUTH_AGE_MS) return null\n\n const secret = getProxySharedSecret()\n if (!secret) return null\n\n const expectedSignature = buildProxyAuthSignature({\n userId,\n tenantId,\n timestamp,\n secret,\n authVersion,\n })\n\n if (!timingSafeEqualText(signature, expectedSignature)) return null\n\n return { userId, tenantId, authVersion: authVersion ?? 0 }\n}\n","import type { RequestHandler } from \"express\"\nimport { models, type LoadModelCtx } from \"@rpcbase/db\"\n\nimport {\n getTrustedProxyAuth,\n} from \"./proxyAuth\"\n\n\ntype SessionUserLike = {\n id?: unknown\n currentTenantId?: unknown\n signedInTenants?: unknown\n tenantRoles?: unknown\n isEntryGateAuthorized?: unknown\n authVersion?: unknown\n}\n\ntype RequestLike = {\n headers?: Record<string, string | string[] | undefined>\n session?: {\n user?: SessionUserLike\n }\n}\n\ntype LoadedUserDoc = {\n tenants?: unknown\n tenantRoles?: unknown\n authVersion?: unknown\n registrationStatus?: unknown\n emailVerificationCode?: unknown\n}\n\nconst normalizeString = (value: unknown): string | null => {\n if (typeof value !== \"string\") return null\n const normalized = value.trim()\n return normalized || null\n}\n\nconst normalizeStringArray = (value: unknown): string[] => {\n if (!Array.isArray(value)) return []\n return value\n .map((entry) => normalizeString(String(entry)))\n .filter((entry): entry is string => Boolean(entry))\n}\n\nconst normalizeRoles = (value: unknown): string[] => {\n if (!Array.isArray(value)) return []\n return value\n .map((entry) => normalizeString(entry))\n .filter((entry): entry is string => Boolean(entry))\n}\n\nconst normalizeTenantRoles = (value: unknown): Record<string, string[]> | undefined => {\n if (!value || typeof value !== \"object\") return undefined\n\n if (value instanceof Map) {\n const entries = Array.from(value.entries())\n .map(([tenantId, roles]) => {\n const normalizedTenantId = normalizeString(String(tenantId))\n if (!normalizedTenantId) return null\n return [normalizedTenantId, normalizeRoles(roles)] as const\n })\n .filter((entry): entry is readonly [string, string[]] => Boolean(entry))\n return entries.length ? Object.fromEntries(entries) : undefined\n }\n\n const nextRoles = Object.entries(value as Record<string, unknown>)\n .map(([tenantId, roles]) => {\n const normalizedTenantId = normalizeString(tenantId)\n if (!normalizedTenantId) return null\n return [normalizedTenantId, normalizeRoles(roles)] as const\n })\n .filter((entry): entry is readonly [string, string[]] => Boolean(entry))\n\n return nextRoles.length ? Object.fromEntries(nextRoles) : undefined\n}\n\nconst loadSessionUser = async (userId: string, tenantId: string, authVersion: unknown): Promise<Record<string, unknown> | null> => {\n const ctx: LoadModelCtx = { req: { session: null } }\n const User = await models.getGlobal(\"RBUser\", ctx)\n const user = await User.findById(userId, { tenants: 1, tenantRoles: 1, authVersion: 1, registrationStatus: 1, emailVerificationCode: 1 }).lean() as LoadedUserDoc | null\n if (!user || user.registrationStatus === \"pending_verification\" || typeof user.emailVerificationCode === \"string\") return null\n if ((user.authVersion ?? 0) !== (authVersion ?? 0)) return null\n\n const signedInTenants = normalizeStringArray(user.tenants)\n if (!signedInTenants.includes(tenantId)) return null\n\n const tenantRoles = normalizeTenantRoles(user.tenantRoles)\n\n return {\n id: userId,\n currentTenantId: tenantId,\n signedInTenants,\n isEntryGateAuthorized: true,\n authVersion: user.authVersion ?? 0,\n ...(tenantRoles ? { tenantRoles } : {}),\n }\n}\n\nexport const revalidateAuthenticatedSession = async (req: RequestLike): Promise<void> => {\n const session = req.session\n const userId = normalizeString(session?.user?.id)\n const tenantId = normalizeString(session?.user?.currentTenantId)\n if (!session || !userId || !tenantId) return\n\n const nextUser = await loadSessionUser(userId, tenantId, session.user?.authVersion)\n if (!nextUser) {\n delete session.user\n return\n }\n session.user = { ...session.user, tenantRoles: undefined, ...nextUser }\n}\n\nexport const syncAuthenticatedSessionFromRequest = async (req: RequestLike): Promise<void> => {\n const proxyAuth = getTrustedProxyAuth(req.headers)\n if (!proxyAuth) {\n await revalidateAuthenticatedSession(req)\n return\n }\n const session = req.session\n if (!session) return\n const nextUser = await loadSessionUser(proxyAuth.userId, proxyAuth.tenantId, proxyAuth.authVersion)\n if (!nextUser) {\n delete session.user\n return\n }\n const previousUser = session.user?.id === proxyAuth.userId ? session.user : undefined\n session.user = { ...previousUser, tenantRoles: undefined, ...nextUser }\n}\n\nexport const syncAuthenticatedSessionMiddleware: RequestHandler = (req, _res, next) => {\n void syncAuthenticatedSessionFromRequest(req).then(() => {\n next()\n }, next)\n}\n","import type { Request } from \"express\"\nimport type { PaginationPageInfo, PaginationSpec } from \"@rpcbase/api\"\nimport { models, type LoadModelCtx } from \"@rpcbase/db\"\nimport { buildAbilityFromSession, getAccessibleByQuery, type AclSubjectType, type AppAbility } from \"@rpcbase/db/acl\"\nimport type { Model } from \"mongoose\"\n\nimport { getDerivedKey } from \"../getDerivedKey\"\n\n\ntype JsonObject = Record<string, unknown>\n\ntype SessionUser = {\n id?: unknown\n currentTenantId?: unknown\n}\n\nexport type RtsPopulateObject = {\n path: string\n model?: string\n select?: string | JsonObject\n match?: JsonObject\n options?: {\n sort?: Record<string, 1 | -1>\n limit?: number\n }\n populate?: RtsPopulateOption\n}\n\nexport type RtsPopulateOption =\n | string\n | RtsPopulateObject\n | Array<string | RtsPopulateObject>\n\nexport type RtsQueryOptions = {\n projection?: JsonObject\n sort?: Record<string, 1 | -1>\n limit?: number\n populate?: RtsPopulateOption\n pagination?: PaginationSpec\n}\n\nexport type RtsQueryResult = {\n data: unknown[]\n pageInfo?: PaginationPageInfo\n totalCount?: number\n}\n\ntype PreparedRtsExecution = {\n model: Model<any>\n finalQuery: JsonObject\n}\n\nconst QUERY_MAX_LIMIT = 4096\nconst INTERNAL_MODEL_NAMES = new Set([\"RBRtsChange\", \"RBRtsCounter\"])\nconst DEFAULT_APPROX_COUNT_SAMPLE_SIZE = 1000\nconst MAX_APPROX_COUNT_SAMPLE_SIZE = 10_000\nconst UNSUPPORTED_APPROX_COUNT_OPERATORS = new Set([\"$text\", \"$near\", \"$nearSphere\", \"$where\"])\nlet paginationCursorSigningSecret: string | null = null\n\nconst getPaginationCursorSigningSecret = (): string => {\n if (paginationCursorSigningSecret) return paginationCursorSigningSecret\n const masterKey = process.env.MASTER_KEY?.trim()\n if (!masterKey) {\n throw new Error(\"MASTER_KEY must be defined to derive pagination cursor signing secret\")\n }\n paginationCursorSigningSecret = getDerivedKey(masterKey, \"pagination_cursor_signing\")\n return paginationCursorSigningSecret\n}\n\nconst normalizeTenantId = (value: unknown): string | null => {\n if (typeof value !== \"string\") return null\n const normalized = value.trim()\n return normalized ? normalized : null\n}\n\nconst getTenantIdFromRequest = (req: Request): string | null => {\n return normalizeTenantId((req.session?.user as SessionUser | undefined)?.currentTenantId)\n}\n\nexport const resolveRtsRequestTenantId = (req: Request): string | null => {\n return getTenantIdFromRequest(req)\n}\n\nexport const resolveRtsRequestUserId = (req: Request): string | null => {\n return normalizeTenantId((req.session?.user as SessionUser | undefined)?.id)\n}\n\nexport const isRtsRequestAuthorized = (req: Request, tenantId: string): boolean => {\n const sessionUser = req.session?.user as SessionUser | undefined\n if (!sessionUser) return false\n\n const currentTenantId = normalizeTenantId(sessionUser.currentTenantId)\n if (!currentTenantId) return false\n return currentTenantId === tenantId\n}\n\nexport const buildRtsAbilityFromRequest = async (\n req: Request,\n tenantId: string,\n): Promise<{ ability: AppAbility; userId: string | null }> => {\n const sessionUserId = normalizeTenantId((req.session?.user as SessionUser | undefined)?.id)\n if (!sessionUserId) {\n const currentTenantId = normalizeTenantId((req.session?.user as SessionUser | undefined)?.currentTenantId)\n if (!currentTenantId || currentTenantId !== tenantId) {\n throw new Error(\"Tenant not authorized for this session\")\n }\n\n return {\n ability: buildAbilityFromSession({ tenantId, session: req.session }),\n userId: null,\n }\n }\n\n const currentTenantId = normalizeTenantId((req.session?.user as SessionUser | undefined)?.currentTenantId)\n if (!currentTenantId || currentTenantId !== tenantId) {\n throw new Error(\"Tenant not authorized for this session\")\n }\n\n return {\n ability: buildAbilityFromSession({ tenantId, session: req.session }),\n userId: sessionUserId,\n }\n}\n\nconst getTenantModel = async (tenantId: string, modelName: string, ability: AppAbility): Promise<Model<any>> => {\n const ctx: LoadModelCtx = {\n req: {\n session: {\n user: {\n currentTenantId: tenantId,\n },\n },\n },\n ability,\n }\n\n return models.get(modelName, ctx)\n}\n\nconst getUnsafeTenantModel = async (tenantId: string, modelName: string): Promise<Model<any>> =>\n models.getUnsafe(modelName, { tenantId })\n\nconst normalizeLimit = (limit?: number): number => {\n if (typeof limit !== \"number\") return QUERY_MAX_LIMIT\n if (!Number.isSafeInteger(limit) || limit <= 0) return QUERY_MAX_LIMIT\n return Math.min(QUERY_MAX_LIMIT, limit)\n}\n\nconst normalizeNonNegativeInteger = (value: unknown): number => {\n if (typeof value !== \"number\") return 0\n if (!Number.isFinite(value) || value < 0) return 0\n return Math.floor(value)\n}\n\nconst getApproxCountSampleSize = (): number => {\n const raw = process.env.RB_RTS_APPROX_COUNT_SAMPLE_SIZE?.trim() ?? \"\"\n if (!raw) return DEFAULT_APPROX_COUNT_SAMPLE_SIZE\n\n const parsed = Number(raw)\n if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_APPROX_COUNT_SAMPLE_SIZE\n return Math.min(MAX_APPROX_COUNT_SAMPLE_SIZE, Math.floor(parsed))\n}\n\nconst findUnsupportedApproxCountOperator = (value: unknown): string | null => {\n if (!value || typeof value !== \"object\") return null\n\n if (Array.isArray(value)) {\n for (const entry of value) {\n const unsupportedOperator = findUnsupportedApproxCountOperator(entry)\n if (unsupportedOperator) return unsupportedOperator\n }\n return null\n }\n\n for (const [key, nestedValue] of Object.entries(value as Record<string, unknown>)) {\n if (UNSUPPORTED_APPROX_COUNT_OPERATORS.has(key)) {\n return key\n }\n\n const unsupportedOperator = findUnsupportedApproxCountOperator(nestedValue)\n if (unsupportedOperator) return unsupportedOperator\n }\n\n return null\n}\n\nconst castApproxCountQuery = (model: Model<any>, query: JsonObject): JsonObject => {\n const castedQuery = model.find(query).cast(model)\n if (!castedQuery || typeof castedQuery !== \"object\" || Array.isArray(castedQuery)) {\n return query\n }\n\n return castedQuery as JsonObject\n}\n\nconst normalizeString = (value: unknown): string => {\n return typeof value === \"string\" ? value.trim() : \"\"\n}\n\nconst normalizeObject = (value: unknown): JsonObject | undefined => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined\n return value as JsonObject\n}\n\nconst normalizePagination = (value: unknown): PaginationSpec | undefined => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined\n return value as PaginationSpec\n}\n\nconst prepareCursorProjection = (\n projection: JsonObject | undefined,\n pagination: PaginationSpec,\n includeCursors: boolean,\n): { projection: JsonObject | undefined; addedPaths: string[] } => {\n if (!projection || !includeCursors) return { projection, addedPaths: [] }\n\n const inclusionProjection = Object.entries(projection).some(([, value]) => value === 1)\n const nextProjection = { ...projection }\n const addedPaths: string[] = []\n const sortFields = Array.isArray(pagination.sort)\n ? pagination.sort\n .map((entry) => entry?.field)\n .filter((field): field is string => typeof field === \"string\")\n : []\n const cursorPaths = Array.from(new Set([...sortFields, \"_id\"]))\n\n if (!inclusionProjection) {\n for (const path of cursorPaths) {\n const excludedPath = Object.entries(projection).find(([projectedPath, value]) =>\n value === 0 && (projectedPath === path || path.startsWith(`${projectedPath}.`)),\n )?.[0]\n if (!excludedPath) continue\n if (excludedPath !== path) {\n throw new Error(`Pagination projection cannot exclude cursor path: ${path}`)\n }\n delete nextProjection[path]\n addedPaths.push(path)\n }\n return { projection: nextProjection, addedPaths }\n }\n\n for (const path of cursorPaths) {\n const isIncluded = path === \"_id\"\n ? projection._id !== 0\n : Object.entries(projection).some(([projectedPath, value]) =>\n value === 1 && (projectedPath === path || path.startsWith(`${projectedPath}.`)),\n )\n if (isIncluded) continue\n nextProjection[path] = 1\n addedPaths.push(path)\n }\n\n return { projection: nextProjection, addedPaths }\n}\n\nconst stripPath = (target: JsonObject, path: string): void => {\n const parts = path.split(\".\")\n let current = target\n\n for (let index = 0; index < parts.length - 1; index += 1) {\n const part = parts[index]!\n const child = current[part]\n if (!child || typeof child !== \"object\" || Array.isArray(child)) return\n const cloned = { ...(child as JsonObject) }\n current[part] = cloned\n current = cloned\n }\n\n delete current[parts[parts.length - 1]!]\n}\n\nconst stripAddedProjectionPaths = (nodes: unknown[], addedPaths: string[]): unknown[] => {\n if (!addedPaths.length) return nodes\n\n return nodes.map((node) => {\n if (!node || typeof node !== \"object\" || Array.isArray(node)) return node\n const toObject = (node as { toObject?: unknown }).toObject\n const raw = typeof toObject === \"function\"\n ? toObject.call(node)\n : node\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return raw\n const projected = { ...(raw as JsonObject) }\n for (const path of addedPaths) stripPath(projected, path)\n return projected\n })\n}\n\nconst normalizePopulateSelect = (value: unknown): string | JsonObject | undefined => {\n if (typeof value === \"string\") {\n const normalized = value.trim()\n return normalized || undefined\n }\n return normalizeObject(value)\n}\n\nconst normalizePopulateOptions = (value: unknown): RtsPopulateObject[\"options\"] | undefined => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined\n const raw = value as { sort?: unknown; limit?: unknown }\n const normalized: RtsPopulateObject[\"options\"] = {}\n\n if (raw.sort && typeof raw.sort === \"object\" && !Array.isArray(raw.sort)) {\n normalized.sort = raw.sort as Record<string, 1 | -1>\n }\n\n if (raw.limit !== undefined) {\n normalized.limit = normalizeLimit(typeof raw.limit === \"number\" ? raw.limit : undefined)\n }\n\n if (!normalized.sort && normalized.limit === undefined) return undefined\n return normalized\n}\n\nconst normalizePopulateObject = (value: unknown): RtsPopulateObject | undefined => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined\n const raw = value as Record<string, unknown>\n const path = normalizeString(raw.path)\n if (!path) return undefined\n\n const normalized: RtsPopulateObject = { path }\n\n const model = normalizeString(raw.model)\n if (model) normalized.model = model\n\n const select = normalizePopulateSelect(raw.select)\n if (select !== undefined) normalized.select = select\n\n const match = normalizeObject(raw.match)\n if (match) normalized.match = match\n\n const nestedPopulate = normalizeRtsPopulateOption(raw.populate)\n if (nestedPopulate !== undefined) normalized.populate = nestedPopulate\n\n const options = normalizePopulateOptions(raw.options)\n if (options) normalized.options = options\n\n return normalized\n}\n\nconst normalizeRtsPopulateOption = (value: unknown): RtsPopulateOption | undefined => {\n if (typeof value === \"string\") {\n const normalized = value.trim()\n return normalized || undefined\n }\n\n if (Array.isArray(value)) {\n const normalized = value\n .map((entry) => {\n if (typeof entry === \"string\") {\n const path = entry.trim()\n return path || null\n }\n return normalizePopulateObject(entry) ?? null\n })\n .filter((entry): entry is string | RtsPopulateObject => entry !== null)\n\n return normalized.length > 0 ? normalized : undefined\n }\n\n return normalizePopulateObject(value)\n}\n\nconst normalizeModelName = (value: unknown): string | null => {\n if (typeof value !== \"string\") return null\n const normalized = value.trim()\n return normalized || null\n}\n\nconst resolvePopulateRefModelName = (\n model: Model<any>,\n path: string,\n explicitModelName: string | null,\n): string | null => {\n if (explicitModelName) return explicitModelName\n\n const schema = model.schema as any\n const schemaPath = typeof schema.path === \"function\" ? schema.path(path) : null\n const directRef = normalizeModelName(schemaPath?.options?.ref)\n if (directRef) return directRef\n\n const arrayRef = normalizeModelName(schemaPath?.caster?.options?.ref)\n if (arrayRef) return arrayRef\n\n const virtualPath = typeof schema.virtualpath === \"function\" ? schema.virtualpath(path) : null\n const virtualRef = normalizeModelName(virtualPath?.options?.ref)\n if (virtualRef) return virtualRef\n\n return null\n}\n\nconst mergePopulateMatchWithAcl = (\n populateMatch: JsonObject | undefined,\n aclMatch: JsonObject,\n): JsonObject => {\n if (!populateMatch || Object.keys(populateMatch).length === 0) return aclMatch\n return { $and: [populateMatch, aclMatch] }\n}\n\ntype PreparedPopulateObject = {\n path: string\n model?: string\n select?: string | JsonObject\n match?: JsonObject\n options?: {\n sort?: Record<string, 1 | -1>\n limit?: number\n }\n populate?: PreparedPopulateOption\n}\n\ntype PreparedPopulateOption =\n | string\n | PreparedPopulateObject\n | Array<string | PreparedPopulateObject>\n\nconst resolvePopulateSpecForModel = async ({\n tenantId,\n model,\n ability,\n populate,\n allowInternalModels,\n modelCache,\n dependencyModelNames,\n}: {\n tenantId: string\n model: Model<any>\n ability: AppAbility\n populate: RtsPopulateOption | undefined\n allowInternalModels: boolean\n modelCache: Map<string, Model<any>>\n dependencyModelNames: Set<string>\n}): Promise<PreparedPopulateOption | undefined> => {\n if (!populate) return undefined\n\n const getModelCached = async (targetModelName: string): Promise<Model<any>> => {\n const cached = modelCache.get(targetModelName)\n if (cached) return cached\n const loaded = await getTenantModel(tenantId, targetModelName, ability)\n modelCache.set(targetModelName, loaded)\n return loaded\n }\n\n const resolveOne = async (\n entry: string | RtsPopulateObject,\n parentModel: Model<any>,\n ): Promise<string | PreparedPopulateObject | null> => {\n if (typeof entry === \"string\") {\n const path = entry.trim()\n if (!path) return null\n\n const refModelName = resolvePopulateRefModelName(parentModel, path, null)\n if (!refModelName) return path\n if (!allowInternalModels && INTERNAL_MODEL_NAMES.has(refModelName)) {\n throw new Error(\"Model not allowed\")\n }\n if (!ability.can(\"read\", refModelName as AclSubjectType)) {\n throw new Error(\"forbidden\")\n }\n\n dependencyModelNames.add(refModelName)\n\n const aclMatch = getAccessibleByQuery(\n ability,\n \"read\",\n refModelName as Exclude<AclSubjectType, \"all\">,\n )\n return {\n path,\n match: aclMatch as JsonObject,\n }\n }\n\n const path = entry.path.trim()\n if (!path) return null\n\n const explicitModelName = normalizeModelName(entry.model)\n const refModelName = resolvePopulateRefModelName(parentModel, path, explicitModelName)\n let nestedModel = parentModel\n\n const normalizedEntry: PreparedPopulateObject = {\n path,\n }\n\n if (entry.select !== undefined) normalizedEntry.select = entry.select\n if (entry.options !== undefined) normalizedEntry.options = entry.options\n if (explicitModelName) normalizedEntry.model = explicitModelName\n if (entry.match !== undefined) normalizedEntry.match = entry.match\n\n if (refModelName) {\n if (!allowInternalModels && INTERNAL_MODEL_NAMES.has(refModelName)) {\n throw new Error(\"Model not allowed\")\n }\n if (!ability.can(\"read\", refModelName as AclSubjectType)) {\n throw new Error(\"forbidden\")\n }\n\n dependencyModelNames.add(refModelName)\n nestedModel = await getModelCached(refModelName)\n\n const aclMatch = getAccessibleByQuery(\n ability,\n \"read\",\n refModelName as Exclude<AclSubjectType, \"all\">,\n ) as JsonObject\n normalizedEntry.match = mergePopulateMatchWithAcl(\n normalizedEntry.match,\n aclMatch,\n )\n } else if (entry.populate !== undefined) {\n throw new Error(\"Populate path must reference a model when nested populate is used\")\n }\n\n const nestedPopulate = await resolvePopulateSpecForModel({\n tenantId,\n model: nestedModel,\n ability,\n populate: entry.populate,\n allowInternalModels,\n modelCache,\n dependencyModelNames,\n })\n if (nestedPopulate !== undefined) normalizedEntry.populate = nestedPopulate\n\n return normalizedEntry\n }\n\n if (Array.isArray(populate)) {\n const resolved = await Promise.all(populate.map((entry) => resolveOne(entry, model)))\n const filtered = resolved.filter((entry): entry is string | PreparedPopulateObject => entry !== null)\n return filtered.length > 0 ? filtered : undefined\n }\n\n const resolved = await resolveOne(populate, model)\n return resolved ?? undefined\n}\n\nexport const normalizeRtsQueryOptions = (options: RtsQueryOptions | undefined): RtsQueryOptions => {\n if (!options || typeof options !== \"object\") return {}\n const normalized: RtsQueryOptions = {}\n\n if (options.projection && typeof options.projection === \"object\" && !Array.isArray(options.projection)) {\n normalized.projection = options.projection\n }\n\n if (options.sort && typeof options.sort === \"object\" && !Array.isArray(options.sort)) {\n normalized.sort = options.sort\n }\n\n normalized.limit = normalizeLimit(options.limit)\n normalized.populate = normalizeRtsPopulateOption(options.populate)\n normalized.pagination = normalizePagination(options.pagination)\n\n return normalized\n}\n\nexport const resolveRtsQueryDependencyModelNames = async ({\n tenantId,\n ability,\n modelName,\n options,\n allowInternalModels = false,\n}: {\n tenantId: string\n ability: AppAbility\n modelName: string\n options: RtsQueryOptions\n allowInternalModels?: boolean\n}): Promise<string[]> => {\n const model = await getTenantModel(tenantId, modelName, ability)\n const modelCache = new Map<string, Model<any>>()\n modelCache.set(modelName, model)\n\n const dependencyModelNames = new Set<string>()\n await resolvePopulateSpecForModel({\n tenantId,\n model,\n ability,\n populate: options.populate,\n allowInternalModels,\n modelCache,\n dependencyModelNames,\n })\n\n return Array.from(dependencyModelNames)\n}\n\nexport const runRtsQuery = async ({\n tenantId,\n ability,\n modelName,\n query,\n options,\n allowInternalModels = false,\n paginationMaxLimit,\n includePaginationCursors = true,\n}: {\n tenantId: string\n ability: AppAbility\n modelName: string\n query: JsonObject\n options: RtsQueryOptions\n allowInternalModels?: boolean\n paginationMaxLimit?: number\n includePaginationCursors?: boolean\n}): Promise<RtsQueryResult> => {\n const { model, finalQuery } = await prepareRtsExecution({\n tenantId,\n ability,\n modelName,\n query,\n allowInternalModels,\n })\n const projection = options.projection ?? undefined\n const sort = options.sort\n const limit = normalizeLimit(options.limit)\n const modelCache = new Map<string, Model<any>>()\n modelCache.set(modelName, model)\n\n const populate = await resolvePopulateSpecForModel({\n tenantId,\n model,\n ability,\n populate: options.populate,\n allowInternalModels,\n modelCache,\n dependencyModelNames: new Set<string>(),\n })\n\n if (options.pagination) {\n const cursorProjection = prepareCursorProjection(\n projection,\n options.pagination,\n includePaginationCursors,\n )\n const paginatedQuery = model.find(finalQuery, cursorProjection.projection)\n if (populate !== undefined) {\n paginatedQuery.populate(populate as any)\n }\n\n const paginatedResult = await paginatedQuery.paginate(options.pagination, {\n cursor: {\n signingSecret: getPaginationCursorSigningSecret(),\n ...(paginationMaxLimit !== undefined ? { maxLimit: paginationMaxLimit } : {}),\n },\n includeCursors: includePaginationCursors,\n })\n const totalCount = typeof paginatedResult.totalCount === \"number\"\n && Number.isFinite(paginatedResult.totalCount)\n && paginatedResult.totalCount >= 0\n ? Math.floor(paginatedResult.totalCount)\n : undefined\n const approximateTotalCount = totalCount === undefined\n ? await getUnsafeTenantModel(tenantId, modelName)\n .then((estimatedCountModel) => runApproximateRtsCount(\n model,\n estimatedCountModel,\n finalQuery,\n ))\n .catch(() => undefined)\n : undefined\n const resolvedTotalCount = totalCount ?? approximateTotalCount\n\n return {\n data: Array.isArray(paginatedResult.nodes)\n ? stripAddedProjectionPaths(paginatedResult.nodes, cursorProjection.addedPaths)\n : [],\n pageInfo: paginatedResult.pageInfo,\n ...(resolvedTotalCount !== undefined ? { totalCount: resolvedTotalCount } : {}),\n }\n }\n\n const queryPromise = model.find(finalQuery, projection)\n if (populate !== undefined) {\n queryPromise.populate(populate as any)\n }\n if (sort && Object.keys(sort).length) {\n queryPromise.sort(sort)\n }\n queryPromise.limit(limit)\n\n const data = await queryPromise\n return { data: Array.isArray(data) ? data : [] }\n}\n\nconst prepareRtsExecution = async ({\n tenantId,\n ability,\n modelName,\n query,\n allowInternalModels = false,\n}: {\n tenantId: string\n ability: AppAbility\n modelName: string\n query: JsonObject\n allowInternalModels?: boolean\n}): Promise<PreparedRtsExecution> => {\n if (!allowInternalModels && INTERNAL_MODEL_NAMES.has(modelName)) {\n throw new Error(\"Model not allowed\")\n }\n\n if (!ability.can(\"read\", modelName as AclSubjectType)) {\n throw new Error(\"forbidden\")\n }\n\n const model = await getTenantModel(tenantId, modelName, ability)\n const accessQuery = getAccessibleByQuery(ability, \"read\", modelName as Exclude<AclSubjectType, \"all\">)\n const finalQuery: JsonObject = { $and: [query, accessQuery] }\n\n return { model, finalQuery }\n}\n\nconst runApproximateRtsCount = async (\n model: Model<any>,\n estimatedCountModel: Model<any>,\n query: JsonObject,\n): Promise<number> => {\n const unsupportedOperator = findUnsupportedApproxCountOperator(query)\n if (unsupportedOperator) {\n throw new Error(`Approximate RTS count does not support ${unsupportedOperator} queries`)\n }\n\n const castedQuery = castApproxCountQuery(model, query)\n const estimatedTotal = normalizeNonNegativeInteger(\n await estimatedCountModel.estimatedDocumentCount(),\n )\n if (estimatedTotal === 0) return 0\n\n const sampleSize = Math.min(getApproxCountSampleSize(), estimatedTotal)\n const sampleResult = await model.aggregate([\n { $sample: { size: sampleSize } },\n { $match: castedQuery },\n { $count: \"count\" },\n ]) as Array<{ count?: unknown }>\n const sampleMatches = normalizeNonNegativeInteger(sampleResult[0]?.count)\n\n if (sampleSize >= estimatedTotal) {\n return Math.min(sampleMatches, estimatedTotal)\n }\n\n const estimatedMatches = Math.round((estimatedTotal * sampleMatches) / sampleSize)\n return Math.max(0, Math.min(estimatedTotal, estimatedMatches))\n}\n\nexport const runRtsCount = async ({\n tenantId,\n ability,\n modelName,\n query,\n allowInternalModels = false,\n}: {\n tenantId: string\n ability: AppAbility\n modelName: string\n query: JsonObject\n allowInternalModels?: boolean\n}): Promise<number> => {\n const { model, finalQuery } = await prepareRtsExecution({\n tenantId,\n ability,\n modelName,\n query,\n allowInternalModels,\n })\n\n const estimatedCountModel = await getUnsafeTenantModel(tenantId, modelName)\n return await runApproximateRtsCount(model, estimatedCountModel, finalQuery)\n}\n","export const RTS_QUERY_WINDOW_MAX_COUNT = 4096\n"],"mappings":"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"}
|
package/dist/rts/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ type SocketMeta = {
|
|
|
12
12
|
tenantId: string;
|
|
13
13
|
userId: string;
|
|
14
14
|
ability: AppAbility;
|
|
15
|
+
validate: () => Promise<boolean>;
|
|
15
16
|
};
|
|
16
17
|
type HandlerFn = (socket: RtsSocket) => void | (() => void);
|
|
17
18
|
declare class RtsSocket {
|
|
@@ -36,7 +37,7 @@ declare class RtsSocket {
|
|
|
36
37
|
dispatch(event: string, payload: unknown): void;
|
|
37
38
|
dispatchRtsMessage(channel: string, payload: unknown): void;
|
|
38
39
|
}
|
|
39
|
-
export declare const initRts: ({ server, path, sessionMiddleware, postSyncSessionMiddleware, maxPayloadBytes: maxPayloadBytesArg, maxSubscriptionsPerSocket: maxSubscriptionsPerSocketArg, dispatchDebounceMs: dispatchDebounceMsArg, allowInternalModels: allowInternalModelsArg, }: {
|
|
40
|
+
export declare const initRts: ({ server, path, sessionMiddleware, postSyncSessionMiddleware, maxPayloadBytes: maxPayloadBytesArg, maxSubscriptionsPerSocket: maxSubscriptionsPerSocketArg, dispatchDebounceMs: dispatchDebounceMsArg, allowInternalModels: allowInternalModelsArg, allowedOrigins, }: {
|
|
40
41
|
server: HttpServer;
|
|
41
42
|
path?: string;
|
|
42
43
|
sessionMiddleware: RequestHandler;
|
|
@@ -45,6 +46,7 @@ export declare const initRts: ({ server, path, sessionMiddleware, postSyncSessio
|
|
|
45
46
|
maxSubscriptionsPerSocket?: number;
|
|
46
47
|
dispatchDebounceMs?: number;
|
|
47
48
|
allowInternalModels?: boolean;
|
|
49
|
+
allowedOrigins?: string[];
|
|
48
50
|
}) => void;
|
|
49
51
|
export declare const registerRtsHandler: (handler: HandlerFn) => void;
|
|
50
52
|
export declare const publishRtsMessage: ({ tenantId, userId, channel, payload }: PublishRtsMessageInput) => void;
|
package/dist/rts/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rts/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAA;AAGtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAG7C,OAAO,EAAgD,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAG/F,OAAO,EAAiC,KAAK,SAAS,EAAE,MAAM,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rts/index.ts"],"names":[],"mappings":"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"}
|
package/dist/rts/index.js
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
|
-
import { a as resolveRtsQueryDependencyModelNames, c as runRtsQuery, i as normalizeRtsQueryOptions, l as
|
|
1
|
+
import { a as resolveRtsQueryDependencyModelNames, c as runRtsQuery, f as getTrustedProxyAuth, i as normalizeRtsQueryOptions, l as revalidateAuthenticatedSession, s as runRtsCount, t as RTS_QUERY_WINDOW_MAX_COUNT, u as syncAuthenticatedSessionFromRequest } from "../queryWindow-RHVy3D0L.js";
|
|
2
2
|
import { models } from "@rpcbase/db";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { buildAbilityFromSession } from "@rpcbase/db/acl";
|
|
5
5
|
import { WebSocketServer } from "ws";
|
|
6
|
+
//#region src/rts/origin.ts
|
|
7
|
+
var isRtsOriginAllowed = (req, allowedOrigins = []) => {
|
|
8
|
+
const origin = req.headers.origin;
|
|
9
|
+
if (origin === void 0) return true;
|
|
10
|
+
try {
|
|
11
|
+
const parsed = new URL(origin);
|
|
12
|
+
if (!["http:", "https:"].includes(parsed.protocol) || parsed.origin !== origin) return false;
|
|
13
|
+
if (allowedOrigins.includes(origin)) return true;
|
|
14
|
+
const trustedProxy = getTrustedProxyAuth(req.headers);
|
|
15
|
+
const forwardedHost = trustedProxy && req.headers["x-forwarded-host"];
|
|
16
|
+
const forwardedProto = trustedProxy && req.headers["x-forwarded-proto"];
|
|
17
|
+
const host = typeof forwardedHost === "string" ? forwardedHost : req.headers.host;
|
|
18
|
+
const protocol = typeof forwardedProto === "string" ? forwardedProto === "wss" ? "https:" : forwardedProto === "ws" ? "http:" : `${forwardedProto}:` : "encrypted" in req.socket && req.socket.encrypted ? "https:" : "http:";
|
|
19
|
+
return origin === new URL(`${protocol}//${host}`).origin;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
6
25
|
//#region src/rts/routes.ts
|
|
7
26
|
var routes = Object.entries({ .../* #__PURE__ */ Object.assign({ "./api/changes/handler.ts": () => import("../handler-BL-Je36s.js") }) }).reduce((acc, [path, mod]) => {
|
|
8
27
|
acc[path.replace("./api/", "@rpcbase/server/rts/api/")] = mod;
|
|
@@ -19,6 +38,7 @@ var initializedServers = /* @__PURE__ */ new WeakSet();
|
|
|
19
38
|
var customHandlers = [];
|
|
20
39
|
var sockets = /* @__PURE__ */ new Map();
|
|
21
40
|
var socketMeta = /* @__PURE__ */ new Map();
|
|
41
|
+
var socketValidators = /* @__PURE__ */ new WeakMap();
|
|
22
42
|
var socketWrappers = /* @__PURE__ */ new Map();
|
|
23
43
|
var socketCleanup = /* @__PURE__ */ new Map();
|
|
24
44
|
var socketSubscriptions = /* @__PURE__ */ new Map();
|
|
@@ -109,7 +129,11 @@ var rawToText = (raw) => {
|
|
|
109
129
|
var safeJsonParse = (raw) => JSON.parse(rawToText(raw));
|
|
110
130
|
var sendWs = (ws, message) => {
|
|
111
131
|
if (ws.readyState !== 1) return;
|
|
112
|
-
|
|
132
|
+
const validate = socketValidators.get(ws);
|
|
133
|
+
if (!validate) return;
|
|
134
|
+
validate().then((valid) => {
|
|
135
|
+
if (valid && ws.readyState === 1) ws.send(JSON.stringify(message));
|
|
136
|
+
}).catch(() => ws.close(1011, "Session validation failed"));
|
|
113
137
|
};
|
|
114
138
|
var ensureSocketErrorHandler = (socket, context) => {
|
|
115
139
|
if (socket.listenerCount("error") > 0) return;
|
|
@@ -185,13 +209,42 @@ var parseUpgradeMeta = async ({ req, url, sessionMiddleware, postSyncSessionMidd
|
|
|
185
209
|
if (!sessionUserId) throw new Error("Not signed in (missing session.user.id)");
|
|
186
210
|
const tenantId = sessionUser?.currentTenantId?.trim();
|
|
187
211
|
if (!tenantId) throw new Error("Missing current tenant in session");
|
|
212
|
+
const ability = buildAbilityFromSession({
|
|
213
|
+
tenantId,
|
|
214
|
+
session: upgradeReq.session
|
|
215
|
+
});
|
|
216
|
+
const authVersion = sessionUser?.authVersion ?? 0;
|
|
217
|
+
const rules = JSON.stringify(ability.rules);
|
|
218
|
+
if (upgradeReq.session?.save) await new Promise((resolve, reject) => {
|
|
219
|
+
upgradeReq.session.save((err) => err ? reject(err) : resolve());
|
|
220
|
+
});
|
|
221
|
+
let validation;
|
|
222
|
+
const validate = () => {
|
|
223
|
+
if (validation) return validation;
|
|
224
|
+
validation = (async () => {
|
|
225
|
+
if (upgradeReq.session?.reload) await new Promise((resolve, reject) => {
|
|
226
|
+
upgradeReq.session.reload((err) => err ? reject(err) : resolve());
|
|
227
|
+
});
|
|
228
|
+
const expires = upgradeReq.session?.cookie?.expires;
|
|
229
|
+
if (expires && new Date(expires).getTime() <= Date.now()) return false;
|
|
230
|
+
await revalidateAuthenticatedSession(upgradeReq);
|
|
231
|
+
if (!upgradeReq.session?.user?.id) return false;
|
|
232
|
+
if (postSyncSessionMiddleware) await runSessionMiddleware(postSyncSessionMiddleware, upgradeReq);
|
|
233
|
+
const currentUser = upgradeReq.session?.user;
|
|
234
|
+
return currentUser?.id === sessionUserId && currentUser.currentTenantId === tenantId && (currentUser.authVersion ?? 0) === authVersion && JSON.stringify(buildAbilityFromSession({
|
|
235
|
+
tenantId,
|
|
236
|
+
session: upgradeReq.session
|
|
237
|
+
}).rules) === rules;
|
|
238
|
+
})().catch(() => false).finally(() => {
|
|
239
|
+
validation = void 0;
|
|
240
|
+
});
|
|
241
|
+
return validation;
|
|
242
|
+
};
|
|
188
243
|
return {
|
|
189
244
|
tenantId,
|
|
190
245
|
userId: sessionUserId,
|
|
191
|
-
ability
|
|
192
|
-
|
|
193
|
-
session: upgradeReq.session
|
|
194
|
-
})
|
|
246
|
+
ability,
|
|
247
|
+
validate
|
|
195
248
|
};
|
|
196
249
|
};
|
|
197
250
|
var getTenantModel = async (tenantId, modelName) => {
|
|
@@ -359,12 +412,20 @@ var hasAnyCountSubscriptionsForModel = (tenantId, modelName) => {
|
|
|
359
412
|
for (const userSubs of tenantSubs.values()) if (userSubs.get(modelName)?.size) return true;
|
|
360
413
|
return false;
|
|
361
414
|
};
|
|
415
|
+
var getValidatedSocketIds = async (socketIds) => {
|
|
416
|
+
const ids = Array.from(socketIds);
|
|
417
|
+
const valid = await Promise.all(ids.map((id) => {
|
|
418
|
+
const ws = sockets.get(id);
|
|
419
|
+
return ws ? socketValidators.get(ws)?.() : false;
|
|
420
|
+
}));
|
|
421
|
+
return ids.filter((_, index) => valid[index]);
|
|
422
|
+
};
|
|
362
423
|
var dispatchSubscriptionsForModel = async (tenantId, modelName) => {
|
|
363
424
|
const tenantSubs = subscriptions.get(tenantId);
|
|
364
425
|
if (!tenantSubs || !tenantSubs.size) return;
|
|
365
426
|
for (const userSubs of tenantSubs.values()) for (const [ownerModelName, modelSubs] of userSubs.entries()) for (const [queryKey, sub] of modelSubs.entries()) {
|
|
366
427
|
if (!subscriptionDependsOnModel(modelName, ownerModelName, sub)) continue;
|
|
367
|
-
const targetSocketIds =
|
|
428
|
+
const targetSocketIds = await getValidatedSocketIds(sub.socketIds);
|
|
368
429
|
if (!targetSocketIds.length) continue;
|
|
369
430
|
const socketId = targetSocketIds[0];
|
|
370
431
|
const ability = socketMeta.get(socketId)?.ability;
|
|
@@ -400,7 +461,7 @@ var dispatchCountSubscriptionsForModel = async (tenantId, modelName) => {
|
|
|
400
461
|
const modelSubs = userSubs.get(modelName);
|
|
401
462
|
if (!modelSubs) continue;
|
|
402
463
|
for (const [queryKey, sub] of modelSubs.entries()) {
|
|
403
|
-
const targetSocketIds =
|
|
464
|
+
const targetSocketIds = await getValidatedSocketIds(sub.socketIds);
|
|
404
465
|
if (!targetSocketIds.length) continue;
|
|
405
466
|
const socketId = targetSocketIds[0];
|
|
406
467
|
const ability = socketMeta.get(socketId)?.ability;
|
|
@@ -624,6 +685,7 @@ var cleanupSocket = (socketId) => {
|
|
|
624
685
|
var handleClientMessage = async ({ socketId, meta, message }) => {
|
|
625
686
|
const ws = sockets.get(socketId);
|
|
626
687
|
if (!ws) return;
|
|
688
|
+
if (!await socketValidators.get(ws)?.()) return;
|
|
627
689
|
if (message.type === "event") {
|
|
628
690
|
socketWrappers.get(socketId)?.dispatch(message.event, message.payload);
|
|
629
691
|
return;
|
|
@@ -884,7 +946,7 @@ var handleClientMessage = async ({ socketId, meta, message }) => {
|
|
|
884
946
|
}
|
|
885
947
|
}
|
|
886
948
|
};
|
|
887
|
-
var initRts = ({ server, path = "/rts", sessionMiddleware, postSyncSessionMiddleware, maxPayloadBytes: maxPayloadBytesArg, maxSubscriptionsPerSocket: maxSubscriptionsPerSocketArg, dispatchDebounceMs: dispatchDebounceMsArg, allowInternalModels: allowInternalModelsArg }) => {
|
|
949
|
+
var initRts = ({ server, path = "/rts", sessionMiddleware, postSyncSessionMiddleware, maxPayloadBytes: maxPayloadBytesArg, maxSubscriptionsPerSocket: maxSubscriptionsPerSocketArg, dispatchDebounceMs: dispatchDebounceMsArg, allowInternalModels: allowInternalModelsArg, allowedOrigins = (process.env.RB_RTS_ALLOWED_ORIGINS ?? "").split(",").map((value) => value.trim()).filter(Boolean) }) => {
|
|
888
950
|
if (initializedServers.has(server)) return;
|
|
889
951
|
initializedServers.add(server);
|
|
890
952
|
if (typeof maxPayloadBytesArg === "number" && Number.isFinite(maxPayloadBytesArg) && maxPayloadBytesArg > 0) maxPayloadBytes = Math.floor(maxPayloadBytesArg);
|
|
@@ -910,6 +972,10 @@ var initRts = ({ server, path = "/rts", sessionMiddleware, postSyncSessionMiddle
|
|
|
910
972
|
return;
|
|
911
973
|
}
|
|
912
974
|
if (url.pathname !== path) return;
|
|
975
|
+
if (!isRtsOriginAllowed(req, allowedOrigins)) {
|
|
976
|
+
socket.end("HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n");
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
913
979
|
(async () => {
|
|
914
980
|
try {
|
|
915
981
|
const meta = await parseUpgradeMeta({
|
|
@@ -962,13 +1028,24 @@ var initRts = ({ server, path = "/rts", sessionMiddleware, postSyncSessionMiddle
|
|
|
962
1028
|
const socketId = randomUUID();
|
|
963
1029
|
sockets.set(socketId, ws);
|
|
964
1030
|
socketMeta.set(socketId, meta);
|
|
1031
|
+
const validate = async () => {
|
|
1032
|
+
if (ws.readyState !== 1) return false;
|
|
1033
|
+
const valid = await meta.validate();
|
|
1034
|
+
if (!valid) ws.close(1008, "Session expired or permissions changed");
|
|
1035
|
+
return valid;
|
|
1036
|
+
};
|
|
1037
|
+
socketValidators.set(ws, validate);
|
|
965
1038
|
const wrapper = new RtsSocket({
|
|
966
1039
|
id: socketId,
|
|
967
1040
|
ws,
|
|
968
1041
|
meta
|
|
969
1042
|
});
|
|
970
1043
|
socketWrappers.set(socketId, wrapper);
|
|
971
|
-
const
|
|
1044
|
+
const validationTimer = setInterval(() => {
|
|
1045
|
+
validate();
|
|
1046
|
+
}, 3e4);
|
|
1047
|
+
validationTimer.unref();
|
|
1048
|
+
const cleanupFns = [() => clearInterval(validationTimer)];
|
|
972
1049
|
for (const handler of customHandlers) try {
|
|
973
1050
|
const cleanup = handler(wrapper);
|
|
974
1051
|
if (typeof cleanup === "function") cleanupFns.push(cleanup);
|