Skip to content

Commit c786ada

Browse files
chore(access): helper cleanup (#4842)
1 parent 3ccb3a3 commit c786ada

1 file changed

Lines changed: 70 additions & 187 deletions

File tree

apps/sim/app/api/knowledge/utils.ts

Lines changed: 70 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,16 @@ interface ChunkAccessDenied {
153153
export type ChunkAccessCheck = ChunkAccessResult | ChunkAccessDenied
154154

155155
/**
156-
* Check if a user has access to a knowledge base
156+
* Resolve knowledge-base access for a user, gated by read or write permission.
157+
*
158+
* Read (`requireWrite: false`) grants on any workspace permission; write
159+
* (`requireWrite: true`) requires `write`/`admin`. Legacy non-workspace KBs grant
160+
* to the owning user in both modes.
157161
*/
158-
export async function checkKnowledgeBaseAccess(
162+
async function resolveKnowledgeBaseAccess(
159163
knowledgeBaseId: string,
160-
userId: string
164+
userId: string,
165+
requireWrite: boolean
161166
): Promise<KnowledgeBaseAccessCheck> {
162167
const kb = await db
163168
.select({
@@ -180,10 +185,10 @@ export async function checkKnowledgeBaseAccess(
180185
if (kbData.workspaceId) {
181186
// Workspace KB: use workspace permissions only
182187
const userPermission = await getUserEntityPermissions(userId, 'workspace', kbData.workspaceId)
183-
if (userPermission !== null) {
184-
return { hasAccess: true, knowledgeBase: kbData }
185-
}
186-
return { hasAccess: false }
188+
const permitted = requireWrite
189+
? userPermission === 'write' || userPermission === 'admin'
190+
: userPermission !== null
191+
return permitted ? { hasAccess: true, knowledgeBase: kbData } : { hasAccess: false }
187192
}
188193

189194
// Legacy non-workspace KB: allow owner access
@@ -195,7 +200,18 @@ export async function checkKnowledgeBaseAccess(
195200
}
196201

197202
/**
198-
* Check if a user has write access to a knowledge base
203+
* Check if a user has read access to a knowledge base.
204+
*/
205+
export async function checkKnowledgeBaseAccess(
206+
knowledgeBaseId: string,
207+
userId: string
208+
): Promise<KnowledgeBaseAccessCheck> {
209+
return resolveKnowledgeBaseAccess(knowledgeBaseId, userId, false)
210+
}
211+
212+
/**
213+
* Check if a user has write access to a knowledge base.
214+
*
199215
* Write access is granted if:
200216
* 1. KB has a workspace: user has write or admin permissions on that workspace
201217
* 2. KB has no workspace (legacy): user owns the KB directly
@@ -204,52 +220,20 @@ export async function checkKnowledgeBaseWriteAccess(
204220
knowledgeBaseId: string,
205221
userId: string
206222
): Promise<KnowledgeBaseAccessCheck> {
207-
const kb = await db
208-
.select({
209-
id: knowledgeBase.id,
210-
userId: knowledgeBase.userId,
211-
workspaceId: knowledgeBase.workspaceId,
212-
name: knowledgeBase.name,
213-
embeddingModel: knowledgeBase.embeddingModel,
214-
})
215-
.from(knowledgeBase)
216-
.where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt)))
217-
.limit(1)
218-
219-
if (kb.length === 0) {
220-
return { hasAccess: false, notFound: true }
221-
}
222-
223-
const kbData = kb[0]
224-
225-
if (kbData.workspaceId) {
226-
// Workspace KB: use workspace permissions only
227-
const userPermission = await getUserEntityPermissions(userId, 'workspace', kbData.workspaceId)
228-
if (userPermission === 'write' || userPermission === 'admin') {
229-
return { hasAccess: true, knowledgeBase: kbData }
230-
}
231-
return { hasAccess: false }
232-
}
233-
234-
// Legacy non-workspace KB: allow owner access
235-
if (kbData.userId === userId) {
236-
return { hasAccess: true, knowledgeBase: kbData }
237-
}
238-
239-
return { hasAccess: false }
223+
return resolveKnowledgeBaseAccess(knowledgeBaseId, userId, true)
240224
}
241225

242226
/**
243-
* Check if a user has write access to a specific document
244-
* Write access is granted if user has write access to the knowledge base
227+
* Resolve document access within a knowledge base, gated by read or write
228+
* permission on the KB (see {@link resolveKnowledgeBaseAccess}).
245229
*/
246-
export async function checkDocumentWriteAccess(
230+
async function resolveDocumentAccess(
247231
knowledgeBaseId: string,
248232
documentId: string,
249-
userId: string
233+
userId: string,
234+
requireWrite: boolean
250235
): Promise<DocumentAccessCheck> {
251-
// First check if user has write access to the knowledge base
252-
const kbAccess = await checkKnowledgeBaseWriteAccess(knowledgeBaseId, userId)
236+
const kbAccess = await resolveKnowledgeBaseAccess(knowledgeBaseId, userId, requireWrite)
253237

254238
if (!kbAccess.hasAccess) {
255239
return {
@@ -259,50 +243,8 @@ export async function checkDocumentWriteAccess(
259243
}
260244
}
261245

262-
// Check if document exists
263246
const doc = await db
264-
.select({
265-
id: document.id,
266-
filename: document.filename,
267-
fileUrl: document.fileUrl,
268-
fileSize: document.fileSize,
269-
mimeType: document.mimeType,
270-
chunkCount: document.chunkCount,
271-
tokenCount: document.tokenCount,
272-
characterCount: document.characterCount,
273-
enabled: document.enabled,
274-
processingStatus: document.processingStatus,
275-
processingError: document.processingError,
276-
uploadedAt: document.uploadedAt,
277-
processingStartedAt: document.processingStartedAt,
278-
processingCompletedAt: document.processingCompletedAt,
279-
knowledgeBaseId: document.knowledgeBaseId,
280-
// Text tags
281-
tag1: document.tag1,
282-
tag2: document.tag2,
283-
tag3: document.tag3,
284-
tag4: document.tag4,
285-
tag5: document.tag5,
286-
tag6: document.tag6,
287-
tag7: document.tag7,
288-
// Number tags (5 slots)
289-
number1: document.number1,
290-
number2: document.number2,
291-
number3: document.number3,
292-
number4: document.number4,
293-
number5: document.number5,
294-
// Date tags (2 slots)
295-
date1: document.date1,
296-
date2: document.date2,
297-
// Boolean tags (3 slots)
298-
boolean1: document.boolean1,
299-
boolean2: document.boolean2,
300-
boolean3: document.boolean3,
301-
// Connector fields
302-
connectorId: document.connectorId,
303-
sourceUrl: document.sourceUrl,
304-
externalId: document.externalId,
305-
})
247+
.select()
306248
.from(document)
307249
.where(
308250
and(
@@ -327,60 +269,41 @@ export async function checkDocumentWriteAccess(
327269
}
328270

329271
/**
330-
* Check if a user has access to a document within a knowledge base
272+
* Check if a user has read access to a document within a knowledge base.
331273
*/
332274
export async function checkDocumentAccess(
333275
knowledgeBaseId: string,
334276
documentId: string,
335277
userId: string
336278
): Promise<DocumentAccessCheck> {
337-
// First check if user has access to the knowledge base
338-
const kbAccess = await checkKnowledgeBaseAccess(knowledgeBaseId, userId)
339-
340-
if (!kbAccess.hasAccess) {
341-
return {
342-
hasAccess: false,
343-
notFound: kbAccess.notFound,
344-
reason: kbAccess.notFound ? 'Knowledge base not found' : 'Unauthorized knowledge base access',
345-
}
346-
}
347-
348-
const doc = await db
349-
.select()
350-
.from(document)
351-
.where(
352-
and(
353-
eq(document.id, documentId),
354-
eq(document.knowledgeBaseId, knowledgeBaseId),
355-
eq(document.userExcluded, false),
356-
isNull(document.archivedAt),
357-
isNull(document.deletedAt)
358-
)
359-
)
360-
.limit(1)
361-
362-
if (doc.length === 0) {
363-
return { hasAccess: false, notFound: true, reason: 'Document not found' }
364-
}
279+
return resolveDocumentAccess(knowledgeBaseId, documentId, userId, false)
280+
}
365281

366-
return {
367-
hasAccess: true,
368-
document: doc[0] as DocumentData,
369-
knowledgeBase: kbAccess.knowledgeBase!,
370-
}
282+
/**
283+
* Check if a user has write access to a specific document.
284+
* Write access is granted if user has write access to the knowledge base.
285+
*/
286+
export async function checkDocumentWriteAccess(
287+
knowledgeBaseId: string,
288+
documentId: string,
289+
userId: string
290+
): Promise<DocumentAccessCheck> {
291+
return resolveDocumentAccess(knowledgeBaseId, documentId, userId, true)
371292
}
372293

373294
/**
374-
* Check if a user has access to a chunk within a document and knowledge base
295+
* Resolve chunk access within a document/knowledge base, gated by read or write
296+
* permission on the KB. The document must exist and be fully processed
297+
* (`processingStatus === 'completed'`) before its chunks are accessible.
375298
*/
376-
export async function checkChunkAccess(
299+
async function resolveChunkAccess(
377300
knowledgeBaseId: string,
378301
documentId: string,
379302
chunkId: string,
380-
userId: string
303+
userId: string,
304+
requireWrite: boolean
381305
): Promise<ChunkAccessCheck> {
382-
// First check if user has access to the knowledge base
383-
const kbAccess = await checkKnowledgeBaseAccess(knowledgeBaseId, userId)
306+
const kbAccess = await resolveKnowledgeBaseAccess(knowledgeBaseId, userId, requireWrite)
384307

385308
if (!kbAccess.hasAccess) {
386309
return {
@@ -410,7 +333,7 @@ export async function checkChunkAccess(
410333

411334
const docData = doc[0] as DocumentData
412335

413-
// Check if document processing is completed
336+
// Chunks are only accessible once the document has finished processing.
414337
if (docData.processingStatus !== 'completed') {
415338
return {
416339
hasAccess: false,
@@ -436,71 +359,31 @@ export async function checkChunkAccess(
436359
}
437360
}
438361

362+
/**
363+
* Check if a user has read access to a chunk within a document and knowledge base.
364+
*/
365+
export async function checkChunkAccess(
366+
knowledgeBaseId: string,
367+
documentId: string,
368+
chunkId: string,
369+
userId: string
370+
): Promise<ChunkAccessCheck> {
371+
return resolveChunkAccess(knowledgeBaseId, documentId, chunkId, userId, false)
372+
}
373+
439374
/**
440375
* Check if a user has write access to a chunk.
441376
*
442-
* Mirrors `checkChunkAccess` but requires write/admin on the knowledge base's
443-
* workspace (or KB ownership for legacy KBs), matching the permission needed to
444-
* create chunks. Used for chunk mutation (update and delete) so those operations
445-
* require the same permission as creation rather than read.
377+
* Mirrors {@link checkChunkAccess} but requires write/admin on the knowledge
378+
* base's workspace (or KB ownership for legacy KBs), matching the permission
379+
* needed to create chunks. Used for chunk mutation (update and delete) so those
380+
* operations require the same permission as creation rather than read.
446381
*/
447382
export async function checkChunkWriteAccess(
448383
knowledgeBaseId: string,
449384
documentId: string,
450385
chunkId: string,
451386
userId: string
452387
): Promise<ChunkAccessCheck> {
453-
const kbAccess = await checkKnowledgeBaseWriteAccess(knowledgeBaseId, userId)
454-
455-
if (!kbAccess.hasAccess) {
456-
return {
457-
hasAccess: false,
458-
notFound: kbAccess.notFound,
459-
reason: kbAccess.notFound ? 'Knowledge base not found' : 'Unauthorized knowledge base access',
460-
}
461-
}
462-
463-
const doc = await db
464-
.select()
465-
.from(document)
466-
.where(
467-
and(
468-
eq(document.id, documentId),
469-
eq(document.knowledgeBaseId, knowledgeBaseId),
470-
eq(document.userExcluded, false),
471-
isNull(document.archivedAt),
472-
isNull(document.deletedAt)
473-
)
474-
)
475-
.limit(1)
476-
477-
if (doc.length === 0) {
478-
return { hasAccess: false, notFound: true, reason: 'Document not found' }
479-
}
480-
481-
const docData = doc[0] as DocumentData
482-
483-
if (docData.processingStatus !== 'completed') {
484-
return {
485-
hasAccess: false,
486-
reason: `Document is not ready for access (status: ${docData.processingStatus})`,
487-
}
488-
}
489-
490-
const chunk = await db
491-
.select()
492-
.from(embedding)
493-
.where(and(eq(embedding.id, chunkId), eq(embedding.documentId, documentId)))
494-
.limit(1)
495-
496-
if (chunk.length === 0) {
497-
return { hasAccess: false, notFound: true, reason: 'Chunk not found' }
498-
}
499-
500-
return {
501-
hasAccess: true,
502-
chunk: chunk[0] as EmbeddingData,
503-
document: docData,
504-
knowledgeBase: kbAccess.knowledgeBase!,
505-
}
388+
return resolveChunkAccess(knowledgeBaseId, documentId, chunkId, userId, true)
506389
}

0 commit comments

Comments
 (0)