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:
@@ -1,4 +1,4 @@
|
||||
import { users, organizations, userSessions, invitations, userCustomFields, userCustomValues, roleMembers, forms, formFields, formStatuses, tasks, taskFieldValues, userOfflineSubscriptions, type User, type Organization, type UserSession, type Invitation, type InsertUser, type InsertOrganization, type InsertUserSession, type InsertInvitation, type UserWithOrganization, type UserCustomField, type UserCustomValue, type CreateUserCustomField, type UpdateUserCustomField, type Task, type FormField, type FormStatus, type UserOfflineSubscription, type InsertUserOfflineSubscription } from "@shared/schema";
|
||||
import { users, organizations, userSessions, invitations, userCustomFields, userCustomValues, roleMembers, forms, formFields, formStatuses, tasks, taskFieldValues, userOfflineSubscriptions, safeUserColumns, type User, type SafeUser, type Organization, type UserSession, type Invitation, type InsertUser, type InsertOrganization, type InsertUserSession, type InsertInvitation, type UserWithOrganization, type UserCustomField, type UserCustomValue, type CreateUserCustomField, type UpdateUserCustomField, type Task, type FormField, type FormStatus, type UserOfflineSubscription, type InsertUserOfflineSubscription } from "@shared/schema";
|
||||
import { db } from "../db";
|
||||
import { eq, and, desc, asc, sql, ilike, or, gte, lte, inArray } from "drizzle-orm";
|
||||
import crypto from "crypto";
|
||||
@@ -16,7 +16,7 @@ export interface ListUsersOptions {
|
||||
}
|
||||
|
||||
export interface ListUsersResult {
|
||||
users: User[];
|
||||
users: SafeUser[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
@@ -90,9 +90,10 @@ export class UsersStorage {
|
||||
};
|
||||
}
|
||||
|
||||
async getUsersByOrganization(organizationId: number): Promise<User[]> {
|
||||
// Массовая выдача пользователей клиенту — только безопасные колонки (без хэша пароля и токенов)
|
||||
async getUsersByOrganization(organizationId: number): Promise<SafeUser[]> {
|
||||
return await db
|
||||
.select()
|
||||
.select(safeUserColumns)
|
||||
.from(users)
|
||||
.where(eq(users.organizationId, organizationId))
|
||||
.orderBy(desc(users.createdAt));
|
||||
@@ -142,7 +143,7 @@ export class UsersStorage {
|
||||
.where(whereClause);
|
||||
|
||||
let query = db
|
||||
.select()
|
||||
.select(safeUserColumns)
|
||||
.from(users)
|
||||
.where(whereClause)
|
||||
.orderBy(orderDir)
|
||||
@@ -157,9 +158,10 @@ export class UsersStorage {
|
||||
) as any;
|
||||
}
|
||||
|
||||
const rows = await query;
|
||||
// При явном списке колонок drizzle возвращает плоские строки даже с join
|
||||
const rows: SafeUser[] = await query;
|
||||
return {
|
||||
users: rows.map((r: any) => ('users' in r ? r.users : r)),
|
||||
users: rows,
|
||||
total: countResult?.total || 0,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user