UNPKG

@clickup/ent-framework

Version:

A PostgreSQL graph-database-alike library with microsharding and row-level security

86 lines (78 loc) 3.02 kB
import { localUniqueInt, nullthrows } from "../../internal/misc"; import type { Row, RowWithID, Table, Where } from "../../types"; import { ID } from "../../types"; import type { EntClass } from "../types"; import type { VC } from "../VC"; import type { Predicate } from "./Predicate"; import { IDsCacheCanReadIncomingEdge } from "./Predicate"; /** * An ent may represent not necessarily a node in the graph, but also an edge * between two nodes. Consider EntMember in the below example: * * vc.principal <--- EntMember[user_id, company_id] ---> EntCompany * * This predicate verifies that for a e.g. given EntCompany row and a given VC, * an EntMember row exists (and optionally matches some criterion) in the * database. * * - entEdgeVCField = user_id in the above example * - entEdgeFKField = company_id in the above example * - if an EntMember object exists, it must also match entEdgeFilter() */ export class IncomingEdgeFromVCExists<TEdgeTable extends Table> implements Predicate<RowWithID> { private readonly instanceID = localUniqueInt(); readonly name; constructor( public readonly EntEdge: EntClass<TEdgeTable>, public readonly entEdgeVCField: keyof Row<TEdgeTable>, public readonly entEdgeFKField: keyof Row<TEdgeTable>, public readonly entEdgeFilter?: (ent: Row<TEdgeTable>) => boolean, ) { this.name = this.constructor.name + "(" + this.EntEdge.name + "[" + `${this.entEdgeVCField}=vc, ` + `${this.entEdgeFKField}=row.${ID}` + "]" + ")"; } async check(vc: VC, row: RowWithID): Promise<boolean> { const cache = vc.cache(IDsCacheCanReadIncomingEdge); const cacheKey = nullthrows(row[ID]) + ":" + this.instanceID; if (cache.has(cacheKey)) { return true; } const where = { [this.entEdgeFKField]: row[ID], [this.entEdgeVCField]: vc.principal, } as Where<TEdgeTable>; let allow: boolean; if (this.entEdgeFilter) { // We use an omni VC here to avoid cyclic references where the edge ent // delegates permission checks to the row ent, and row ent loads the edge // ent to run the edgeEntFilter function. It's safe, because: // 1. Omni VC is always demoted to the current user's VC (which is vc // since we filter by vc.principal above) or to a guest VC if it cannot // find a user_id field in the edge ent (which also never happens). // 2. The edgeEntFilter function is synchronous, so it can't physically // access the database anyway. const ents = await this.EntEdge.select(vc.toOmniDangerous(), where, 1); const filtered = ents.filter((ent) => this.entEdgeFilter!(ent as Row<TEdgeTable>), ); allow = filtered.length > 0; } else { // Exists is not privacy-checked (it doesn't fetch any row to be checked). allow = await this.EntEdge.exists(vc, where); } if (allow) { cache.add(cacheKey); return true; } return false; } }