security(users): SafeUser — passwordHash/токены не уходят клиенту

Шаг 0.8 плана production-готовности:
- shared/schema.ts: тип SafeUser + safeUserColumns
- getUsersByOrganization, listUsers, searchUsers, getUsersByRole,
  getUsersByOrgRoleId, documents getUser — без чувствительных колонок
- sync (initial/delta), автоматизации ctx.users.list, MCP list_users — sanitized
This commit is contained in:
2026-09-07 21:03:00 +03:00
parent 26419f395f
commit cf6f937108
12 changed files with 79 additions and 51 deletions

View File

@@ -1,4 +1,4 @@
import { users, userNotifications, bookmarkFolders, bookmarks, type User, type UserNotification, type BookmarkFolder, type Bookmark, type InsertUserNotification, type InsertBookmarkFolder, type InsertBookmark } from "@shared/schema";
import { users, userNotifications, bookmarkFolders, bookmarks, safeUserColumns, type User, type SafeUser, type UserNotification, type BookmarkFolder, type Bookmark, type InsertUserNotification, type InsertBookmarkFolder, type InsertBookmark } from "@shared/schema";
import { db } from "../db";
import { eq, and, desc, isNull, sql } from "drizzle-orm";
import crypto from "crypto";
@@ -347,7 +347,8 @@ export class SocialStorage extends TasksStorage {
}
// User Search
async searchUsers(query: string, organizationId: number, limit = 10): Promise<User[]> {
// Поиск уходит клиенту — выбираем только безопасные колонки (без хэша пароля и токенов)
async searchUsers(query: string, organizationId: number, limit = 10): Promise<SafeUser[]> {
// Поддержка похожих символов: і/и для корректного поиска
const normalizedQuery = query
.replace(/і/g, 'и')
@@ -355,7 +356,7 @@ export class SocialStorage extends TasksStorage {
.trim();
const result = await db
.select()
.select(safeUserColumns)
.from(users)
.where(and(
eq(users.organizationId, organizationId),