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

@@ -12,6 +12,7 @@ import { systemConfig } from "@shared/schema";
import { taskAuditLog, type TaskAuditLog, type InsertTaskAuditLog } from "@shared/schema";
import { taskRoles, type TaskRole, taskUserAccess, type TaskUserAccess, roleMembers, roles, taskAssignees, type TaskAssignee } from "@shared/schema";
import { userOfflineSubscriptions, type UserOfflineSubscription, type InsertUserOfflineSubscription } from "@shared/schema";
import type { SafeUser } from "@shared/schema";
import { db } from "./db";
import { eq, and, or, desc, asc, exists, isNull, isNotNull, sql, ilike, inArray, lte } from "drizzle-orm";
import { alias } from "drizzle-orm/pg-core";
@@ -41,7 +42,7 @@ export interface IStorage {
getUserWithOrganization(id: number): Promise<UserWithOrganization | undefined>;
getUserByEmail(email: string, organizationId?: number): Promise<User | undefined>;
getUserByEmailAndSlug(email: string, slug: string): Promise<UserWithOrganization | undefined>;
getUsersByOrganization(organizationId: number): Promise<User[]>;
getUsersByOrganization(organizationId: number): Promise<SafeUser[]>;
listUsers(organizationId: number, options?: import('./storage/users.storage').ListUsersOptions): Promise<import('./storage/users.storage').ListUsersResult>;
getLinkedTasksForUser(userId: number, organizationId: number): Promise<
Array<{
@@ -175,7 +176,7 @@ export interface IStorage {
fieldValues: TaskFieldValue[];
subtasks: Task[];
messages: TaskMessageWithAuthor[];
users: User[];
users: Array<SafeUser & { fullName: string }>;
hasRelations: boolean;
assignees: Array<TaskAssignee & { user: { id: number; firstName: string | null; lastName: string | null; email: string } }>;
} | null>;
@@ -250,7 +251,7 @@ export interface IStorage {
deleteNotification(id: number, userId: number, organizationId: number): Promise<void>;
// User Search
searchUsers(query: string, organizationId: number, limit?: number): Promise<User[]>;
searchUsers(query: string, organizationId: number, limit?: number): Promise<SafeUser[]>;
// Bots
getBot(id: number, organizationId: number): Promise<Bot | undefined>;
@@ -416,8 +417,8 @@ export interface IStorage {
deleteTaskReminder(id: number, organizationId: number): Promise<void>;
getDueReminders(): Promise<TaskReminder[]>;
markReminderSent(id: number): Promise<void>;
getUsersByRole(role: string, organizationId: number): Promise<User[]>;
getUsersByOrgRoleId(roleId: number, organizationId: number): Promise<User[]>;
getUsersByRole(role: string, organizationId: number): Promise<SafeUser[]>;
getUsersByOrgRoleId(roleId: number, organizationId: number): Promise<SafeUser[]>;
// Task Audit Log
addTaskAuditLog(entry: InsertTaskAuditLog): Promise<TaskAuditLog>;