chore(access): helper cleanup (#4842)

This commit is contained in:
Vikhyath Mondreti
2026-06-01 18:12:21 -07:00
committed by GitHub
parent 3ccb3a392a
commit c786adac2c
+129 -246
View File
@@ -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<KnowledgeBaseAccessCheck> {
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<KnowledgeBaseAccessCheck> {
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,138 +220,20 @@ export async function checkKnowledgeBaseWriteAccess(
knowledgeBaseId: string,
userId: string
): Promise<KnowledgeBaseAccessCheck> {
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<DocumentAccessCheck> {
// First check if user has write access to the knowledge base
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',
}
}
// 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,
})
.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 {
hasAccess: true,
document: doc[0] as DocumentData,
knowledgeBase: kbAccess.knowledgeBase!,
}
}
/**
* Check if a user has access to a document within a knowledge base
*/
export async function checkDocumentAccess(
knowledgeBaseId: string,
documentId: string,
userId: string
): Promise<DocumentAccessCheck> {
// 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 {
@@ -371,7 +269,98 @@ export async function checkDocumentAccess(
}
/**
* Check if a user has access to a chunk within a document and 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<DocumentAccessCheck> {
return resolveDocumentAccess(knowledgeBaseId, documentId, userId, false)
}
/**
* 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<DocumentAccessCheck> {
return resolveDocumentAccess(knowledgeBaseId, documentId, userId, true)
}
/**
* 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.
*/
async function resolveChunkAccess(
knowledgeBaseId: string,
documentId: string,
chunkId: string,
userId: string,
requireWrite: boolean
): Promise<ChunkAccessCheck> {
const kbAccess = await resolveKnowledgeBaseAccess(knowledgeBaseId, userId, requireWrite)
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
// Chunks are only accessible once the document has finished processing.
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!,
}
}
/**
* Check if a user has read access to a chunk within a document and knowledge base.
*/
export async function checkChunkAccess(
knowledgeBaseId: string,
@@ -379,70 +368,16 @@ export async function checkChunkAccess(
chunkId: string,
userId: string
): Promise<ChunkAccessCheck> {
// 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' }
}
const docData = doc[0] as DocumentData
// Check if document processing is completed
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, 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<ChunkAccessCheck> {
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)
}