feat: add namespace-based multi-user isolation

Implement namespace support across sessions, machines, and users for multi-user server deployments. Add access control with specific error reasons (namespace-missing, access-denied, not-found) and database schema updates with namespace columns and indexes.
This commit is contained in:
weishu
2025-12-31 21:56:01 +08:00
parent 460c393006
commit e821458af8
30 changed files with 737 additions and 124 deletions
+19 -4
View File
@@ -167,10 +167,17 @@ export class HappyBot {
return
}
const namespace = this.getNamespaceForChatId(ctx.from?.id ?? null)
if (!namespace) {
await ctx.answerCallbackQuery('Telegram account is not bound')
return
}
const data = ctx.callbackQuery.data
const callbackContext: CallbackContext = {
syncEngine: this.syncEngine,
namespace,
answerCallback: async (text?: string) => {
await ctx.answerCallbackQuery(text)
},
@@ -220,8 +227,8 @@ export class HappyBot {
/**
* Get bound Telegram chat IDs from storage.
*/
private getBoundChatIds(): number[] {
const users = this.store.getUsersByPlatform('telegram')
private getBoundChatIds(namespace: string): number[] {
const users = this.store.getUsersByPlatformAndNamespace('telegram', namespace)
const ids = new Set<number>()
for (const user of users) {
const chatId = Number(user.platformUserId)
@@ -232,6 +239,14 @@ export class HappyBot {
return Array.from(ids)
}
private getNamespaceForChatId(chatId: number | null | undefined): string | null {
if (!chatId) {
return null
}
const stored = this.store.getUser('telegram', String(chatId))
return stored?.namespace ?? null
}
/**
* Send a push notification when agent is ready for input.
*/
@@ -259,7 +274,7 @@ export class HappyBot {
const keyboard = new InlineKeyboard()
.webApp('Open Session', url)
const chatIds = this.getBoundChatIds()
const chatIds = this.getBoundChatIds(session.namespace)
if (chatIds.length === 0) {
return
}
@@ -338,7 +353,7 @@ export class HappyBot {
const text = formatSessionNotification(session)
const keyboard = createNotificationKeyboard(session, this.miniAppUrl)
const chatIds = this.getBoundChatIds()
const chatIds = this.getBoundChatIds(session.namespace)
if (chatIds.length === 0) {
return
}