import { projectRow } from '../db/schema.ts'; import type { Connection } from '../protocol/connection.ts'; import type { ResourceAuthDef, RequestingUser, Row } from '../types.ts'; // The identity a resource-level auth hook (rowFilter/hideFields) sees for the // requesting connection. userName is undefined for unauthenticated // connections — possible when requireAuth is off, or pre-login. export function requestingUser(connection: Connection): RequestingUser { return { userName: connection.userName, profile: connection.profile, permission: connection.permission, }; } export function hiddenFields(auth: ResourceAuthDef | undefined, user: RequestingUser): Set { const fields = typeof auth?.hideFields === 'function' ? auth.hideFields(user) : auth?.hideFields; return new Set(fields ?? []); } // Applies a resource's permissioning to a reply/snapshot row set: drops rows // the user's rowFilter rejects, then strips hidden fields from what's left. // Mirrors dataserver auth maps + hideFields on the real server. export function applyResourceAuth( rows: Row[], auth: ResourceAuthDef | undefined, user: RequestingUser, ): Row[] { if (!auth) return rows; let result = auth.rowFilter ? rows.filter((row) => auth.rowFilter!(row, user)) : rows; const hidden = hiddenFields(auth, user); if (hidden.size > 0) { result = result.map((row) => Object.fromEntries(Object.entries(row).filter(([field]) => !hidden.has(field))), ); } return result; } // One dataserver row as this user sees it: undefined when rowFilter rejects // it, else only the query's columns (projectRow: fieldNames from // queryFieldNames) minus the hidden ones. rowFilter sees the whole row, so a // filter may read a column the query doesn't expose. export function permittedQueryRow( row: Row, auth: ResourceAuthDef | undefined, user: RequestingUser, fieldNames: string[] | undefined, ): Row | undefined { if (auth?.rowFilter && !auth.rowFilter(row, user)) return undefined; const projected = projectRow(row, fieldNames); const hidden = hiddenFields(auth, user); if (hidden.size === 0) return projected; return Object.fromEntries(Object.entries(projected).filter(([field]) => !hidden.has(field))); }