From cb3e095db607564a4ccedb9d59ccfaa625687775 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Mon, 1 Jun 2026 17:59:07 -0700 Subject: [PATCH] chore(access): helper cleanup --- apps/sim/app/api/knowledge/utils.ts | 257 ++++++++-------------------- 1 file changed, 70 insertions(+), 187 deletions(-) diff --git a/apps/sim/app/api/knowledge/utils.ts b/apps/sim/app/api/knowledge/utils.ts index 316f8af301..13c9079202 100644 --- a/apps/sim/app/api/knowledge/utils.ts +++ b/apps/sim/app/api/knowledge/utils.ts @@ -153,11 +153,16 @@ interface ChunkAccessDenied { export type ChunkAccessCheck = ChunkAccessResult | ChunkAccessDenied /** - * Check if a user has access to a knowledge base + * Resolve knowledge-base access for a user, gated by read or write permission. + * + * Read (`requireWrite: false`) grants on any workspace permission; write + * (`requireWrite: true`) requires `write`/`admin`. Legacy non-workspace KBs grant + * to the owning user in both modes. */ -export async function checkKnowledgeBaseAccess( +async function resolveKnowledgeBaseAccess( knowledgeBaseId: string, - userId: string + userId: string, + requireWrite: boolean ): Promise { const kb = await db .select({ @@ -180,10 +185,10 @@ export async function checkKnowledgeBaseAccess( if (kbData.workspaceId) { // Workspace KB: use workspace permissions only const userPermission = await getUserEntityPermissions(userId, 'workspace', kbData.workspaceId) - if (userPermission !== null) { - return { hasAccess: true, knowledgeBase: kbData } - } - return { hasAccess: false } + const permitted = requireWrite + ? userPermission === 'write' || userPermission === 'admin' + : userPermission !== null + return permitted ? { hasAccess: true, knowledgeBase: kbData } : { hasAccess: false } } // Legacy non-workspace KB: allow owner access @@ -195,7 +200,18 @@ export async function checkKnowledgeBaseAccess( } /** - * Check if a user has write access to a knowledge base + * Check if a user has read access to a knowledge base. + */ +export async function checkKnowledgeBaseAccess( + knowledgeBaseId: string, + userId: string +): Promise { + return resolveKnowledgeBaseAccess(knowledgeBaseId, userId, false) +} + +/** + * Check if a user has write access to a knowledge base. + * * Write access is granted if: * 1. KB has a workspace: user has write or admin permissions on that workspace * 2. KB has no workspace (legacy): user owns the KB directly @@ -204,52 +220,20 @@ export async function checkKnowledgeBaseWriteAccess( knowledgeBaseId: string, userId: string ): Promise { - const kb = await db - .select({ - id: knowledgeBase.id, - userId: knowledgeBase.userId, - workspaceId: knowledgeBase.workspaceId, - name: knowledgeBase.name, - embeddingModel: knowledgeBase.embeddingModel, - }) - .from(knowledgeBase) - .where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt))) - .limit(1) - - if (kb.length === 0) { - return { hasAccess: false, notFound: true } - } - - const kbData = kb[0] - - if (kbData.workspaceId) { - // Workspace KB: use workspace permissions only - const userPermission = await getUserEntityPermissions(userId, 'workspace', kbData.workspaceId) - if (userPermission === 'write' || userPermission === 'admin') { - return { hasAccess: true, knowledgeBase: kbData } - } - return { hasAccess: false } - } - - // Legacy non-workspace KB: allow owner access - if (kbData.userId === userId) { - return { hasAccess: true, knowledgeBase: kbData } - } - - return { hasAccess: false } + return resolveKnowledgeBaseAccess(knowledgeBaseId, userId, true) } /** - * Check if a user has write access to a specific document - * Write access is granted if user has write access to the knowledge base + * Resolve document access within a knowledge base, gated by read or write + * permission on the KB (see {@link resolveKnowledgeBaseAccess}). */ -export async function checkDocumentWriteAccess( +async function resolveDocumentAccess( knowledgeBaseId: string, documentId: string, - userId: string + userId: string, + requireWrite: boolean ): Promise { - // First check if user has write access to the knowledge base - const kbAccess = await checkKnowledgeBaseWriteAccess(knowledgeBaseId, userId) + const kbAccess = await resolveKnowledgeBaseAccess(knowledgeBaseId, userId, requireWrite) if (!kbAccess.hasAccess) { return { @@ -259,50 +243,8 @@ export async function checkDocumentWriteAccess( } } - // Check if document exists const doc = await db - .select({ - id: document.id, - filename: document.filename, - fileUrl: document.fileUrl, - fileSize: document.fileSize, - mimeType: document.mimeType, - chunkCount: document.chunkCount, - tokenCount: document.tokenCount, - characterCount: document.characterCount, - enabled: document.enabled, - processingStatus: document.processingStatus, - processingError: document.processingError, - uploadedAt: document.uploadedAt, - processingStartedAt: document.processingStartedAt, - processingCompletedAt: document.processingCompletedAt, - knowledgeBaseId: document.knowledgeBaseId, - // Text tags - tag1: document.tag1, - tag2: document.tag2, - tag3: document.tag3, - tag4: document.tag4, - tag5: document.tag5, - tag6: document.tag6, - tag7: document.tag7, - // Number tags (5 slots) - number1: document.number1, - number2: document.number2, - number3: document.number3, - number4: document.number4, - number5: document.number5, - // Date tags (2 slots) - date1: document.date1, - date2: document.date2, - // Boolean tags (3 slots) - boolean1: document.boolean1, - boolean2: document.boolean2, - boolean3: document.boolean3, - // Connector fields - connectorId: document.connectorId, - sourceUrl: document.sourceUrl, - externalId: document.externalId, - }) + .select() .from(document) .where( and( @@ -327,60 +269,41 @@ export async function checkDocumentWriteAccess( } /** - * Check if a user has access to a document within a knowledge base + * Check if a user has read access to a document within a knowledge base. */ export async function checkDocumentAccess( knowledgeBaseId: string, documentId: string, userId: string ): Promise { - // First check if user has access to the knowledge base - const kbAccess = await checkKnowledgeBaseAccess(knowledgeBaseId, userId) - - if (!kbAccess.hasAccess) { - return { - hasAccess: false, - notFound: kbAccess.notFound, - reason: kbAccess.notFound ? 'Knowledge base not found' : 'Unauthorized knowledge base access', - } - } - - const doc = await db - .select() - .from(document) - .where( - and( - eq(document.id, documentId), - eq(document.knowledgeBaseId, knowledgeBaseId), - eq(document.userExcluded, false), - isNull(document.archivedAt), - isNull(document.deletedAt) - ) - ) - .limit(1) - - if (doc.length === 0) { - return { hasAccess: false, notFound: true, reason: 'Document not found' } - } + return resolveDocumentAccess(knowledgeBaseId, documentId, userId, false) +} - return { - hasAccess: true, - document: doc[0] as DocumentData, - knowledgeBase: kbAccess.knowledgeBase!, - } +/** + * Check if a user has write access to a specific document. + * Write access is granted if user has write access to the knowledge base. + */ +export async function checkDocumentWriteAccess( + knowledgeBaseId: string, + documentId: string, + userId: string +): Promise { + return resolveDocumentAccess(knowledgeBaseId, documentId, userId, true) } /** - * Check if a user has access to a chunk within a document and knowledge base + * Resolve chunk access within a document/knowledge base, gated by read or write + * permission on the KB. The document must exist and be fully processed + * (`processingStatus === 'completed'`) before its chunks are accessible. */ -export async function checkChunkAccess( +async function resolveChunkAccess( knowledgeBaseId: string, documentId: string, chunkId: string, - userId: string + userId: string, + requireWrite: boolean ): Promise { - // First check if user has access to the knowledge base - const kbAccess = await checkKnowledgeBaseAccess(knowledgeBaseId, userId) + const kbAccess = await resolveKnowledgeBaseAccess(knowledgeBaseId, userId, requireWrite) if (!kbAccess.hasAccess) { return { @@ -410,7 +333,7 @@ export async function checkChunkAccess( const docData = doc[0] as DocumentData - // Check if document processing is completed + // Chunks are only accessible once the document has finished processing. if (docData.processingStatus !== 'completed') { return { hasAccess: false, @@ -436,13 +359,25 @@ export async function checkChunkAccess( } } +/** + * Check if a user has read access to a chunk within a document and knowledge base. + */ +export async function checkChunkAccess( + knowledgeBaseId: string, + documentId: string, + chunkId: string, + userId: string +): Promise { + return resolveChunkAccess(knowledgeBaseId, documentId, chunkId, userId, false) +} + /** * Check if a user has write access to a chunk. * - * Mirrors `checkChunkAccess` but requires write/admin on the knowledge base's - * workspace (or KB ownership for legacy KBs), matching the permission needed to - * create chunks. Used for chunk mutation (update and delete) so those operations - * require the same permission as creation rather than read. + * Mirrors {@link checkChunkAccess} but requires write/admin on the knowledge + * base's workspace (or KB ownership for legacy KBs), matching the permission + * needed to create chunks. Used for chunk mutation (update and delete) so those + * operations require the same permission as creation rather than read. */ export async function checkChunkWriteAccess( knowledgeBaseId: string, @@ -450,57 +385,5 @@ export async function checkChunkWriteAccess( chunkId: string, userId: string ): Promise { - const kbAccess = await checkKnowledgeBaseWriteAccess(knowledgeBaseId, userId) - - if (!kbAccess.hasAccess) { - return { - hasAccess: false, - notFound: kbAccess.notFound, - reason: kbAccess.notFound ? 'Knowledge base not found' : 'Unauthorized knowledge base access', - } - } - - const doc = await db - .select() - .from(document) - .where( - and( - eq(document.id, documentId), - eq(document.knowledgeBaseId, knowledgeBaseId), - eq(document.userExcluded, false), - isNull(document.archivedAt), - isNull(document.deletedAt) - ) - ) - .limit(1) - - if (doc.length === 0) { - return { hasAccess: false, notFound: true, reason: 'Document not found' } - } - - const docData = doc[0] as DocumentData - - if (docData.processingStatus !== 'completed') { - return { - hasAccess: false, - reason: `Document is not ready for access (status: ${docData.processingStatus})`, - } - } - - const chunk = await db - .select() - .from(embedding) - .where(and(eq(embedding.id, chunkId), eq(embedding.documentId, documentId))) - .limit(1) - - if (chunk.length === 0) { - return { hasAccess: false, notFound: true, reason: 'Chunk not found' } - } - - return { - hasAccess: true, - chunk: chunk[0] as EmbeddingData, - document: docData, - knowledgeBase: kbAccess.knowledgeBase!, - } + return resolveChunkAccess(knowledgeBaseId, documentId, chunkId, userId, true) }