@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.
Files changed (32) hide show
  1. package/dist/{convertHeifToWebp-DwSrD30G.js → convertHeifToWebp-CIP6qLY7.js} +2 -2
  2. package/dist/{convertHeifToWebp-DwSrD30G.js.map → convertHeifToWebp-CIP6qLY7.js.map} +1 -1
  3. package/dist/{handler-Z6wToZhb.js → handler-BkGfOJ1l.js} +2 -2
  4. package/dist/{handler-Z6wToZhb.js.map → handler-BkGfOJ1l.js.map} +1 -1
  5. package/dist/{handler-C34TFH28.js → handler-Dy_L6_-U.js} +36 -8
  6. package/dist/handler-Dy_L6_-U.js.map +1 -0
  7. package/dist/index.js +1 -1
  8. package/dist/proxyAuth.d.ts +4 -1
  9. package/dist/proxyAuth.d.ts.map +1 -1
  10. package/dist/{queryWindow-NCe5vJHt.js → queryWindow-RHVy3D0L.js} +51 -35
  11. package/dist/queryWindow-RHVy3D0L.js.map +1 -0
  12. package/dist/rts/index.d.ts +3 -1
  13. package/dist/rts/index.d.ts.map +1 -1
  14. package/dist/rts/index.js +87 -10
  15. package/dist/rts/index.js.map +1 -1
  16. package/dist/rts/origin.d.ts +3 -0
  17. package/dist/rts/origin.d.ts.map +1 -0
  18. package/dist/sharp.d.ts +3 -0
  19. package/dist/sharp.d.ts.map +1 -0
  20. package/dist/sharp.js +2 -0
  21. package/dist/syncAuthenticatedSession.d.ts +2 -0
  22. package/dist/syncAuthenticatedSession.d.ts.map +1 -1
  23. package/dist/types/index.d.ts +1 -0
  24. package/dist/types/index.d.ts.map +1 -1
  25. package/dist/uploads/api/files/handlers/getFile.d.ts.map +1 -1
  26. package/dist/uploads/fileContentHeaders.d.ts +2 -0
  27. package/dist/uploads/fileContentHeaders.d.ts.map +1 -0
  28. package/dist/uploads/worker.js +1 -1
  29. package/dist/uploads.js +2 -2
  30. package/package.json +6 -1
  31. package/dist/handler-C34TFH28.js.map +0 -1
  32. 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 isSessionAuthorizedForTenant = (sessionUser, tenantId) => {
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) return;
118
- const { userId, tenantId } = proxyAuth;
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 nextSessionUser = await loadSessionUser(userId, tenantId);
133
- if (!nextSessionUser) {
134
- if (session.user) delete session.user;
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 = nextSessionUser;
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.isFinite(limit)) return QUERY_MAX_LIMIT;
216
- return Math.min(QUERY_MAX_LIMIT, Math.abs(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" && Number.isFinite(raw.limit)) normalized.limit = Math.max(0, Math.floor(Math.abs(raw.limit)));
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, getDerivedKey as d, normalizeRtsQueryOptions as i, syncAuthenticatedSessionFromRequest as l, buildRtsAbilityFromRequest as n, resolveRtsRequestTenantId as o, isRtsRequestAuthorized as r, runRtsCount as s, RTS_QUERY_WINDOW_MAX_COUNT as t, syncAuthenticatedSessionMiddleware as u };
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-NCe5vJHt.js.map
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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"}
@@ -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;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rts/index.ts"],"names":[],"mappings":"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"}
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 syncAuthenticatedSessionFromRequest, s as runRtsCount, t as RTS_QUERY_WINDOW_MAX_COUNT } from "../queryWindow-NCe5vJHt.js";
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
- ws.send(JSON.stringify(message));
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: buildAbilityFromSession({
192
- tenantId,
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 = Array.from(sub.socketIds);
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 = Array.from(sub.socketIds);
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 cleanupFns = [];
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);