mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
chore(access): helper cleanup (#4842)
This commit is contained in:
+129
-246
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user