← Назад к вопросам
Делится ли в приложении функционал по ролям на нынешней работе
1.2 Junior🔥 121 комментариев
#JavaScript Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Разделение функционала по ролям пользователя
В современных веб-приложениях разделение функционала по ролям пользователя (Role-Based Access Control, RBAC) - это стандартная практика. Рассмотрю, как это реализуется на фронтенде и бэкенде.
Типичная архитектура разделения ролей
В большинстве приложений есть несколько уровней пользователей:
// Типы ролей
enum UserRole {
ADMIN = "admin",
MODERATOR = "moderator",
USER = "user",
GUEST = "guest"
}
interface User {
id: string;
name: string;
email: string;
role: UserRole;
permissions: Permission[];
}
enum Permission {
CREATE_POST = "create_post",
EDIT_POST = "edit_post",
DELETE_POST = "delete_post",
MANAGE_USERS = "manage_users",
VIEW_ANALYTICS = "view_analytics"
}
Способ 1: Условный рендеринг на фронтенде
Простейший подход - показывать/скрывать элементы в зависимости от роли:
// React пример
function Dashboard({ user }: { user: User }) {
return (
<div>
{/* Видно всем */}
<h1>Доброе пожаловать, {user.name}!</h1>
{/* Только для модераторов и админов */}
{(user.role === UserRole.MODERATOR ||
user.role === UserRole.ADMIN) && (
<section>
<h2>Модерация</h2>
<ModerationPanel />
</section>
)}
{/* Только для админов */}
{user.role === UserRole.ADMIN && (
<section>
<h2>Администрирование</h2>
<AdminPanel />
</section>
)}
{/* Только для обычных пользователей */}
{user.role === UserRole.USER && (
<section>
<h2>Мои постройки</h2>
<UserPosts />
</section>
)}
</div>
);
}
Проблемы:
- Небезопасно - пользователь может открыть скрытый раздел через DevTools
- Код становится нечитаемым с множеством проверок
- Сложно масштабировать
Способ 2: Утилита для проверки прав (useAuth хук)
Лучше создать переиспользуемый хук:
const AuthContext = React.createContext<{
user: User | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
} | null>(null);
export function useAuth() {
const context = React.useContext(AuthContext);
if (!context) {
throw new Error("useAuth должен быть внутри AuthProvider");
}
return context;
}
export function usePermission() {
const { user } = useAuth();
const hasPermission = (permission: Permission): boolean => {
if (!user) return false;
// Админы имеют все права
if (user.role === UserRole.ADMIN) return true;
// Проверяем явные права
return user.permissions.includes(permission);
};
const hasRole = (role: UserRole): boolean => {
return user?.role === role;
};
const hasAnyRole = (roles: UserRole[]): boolean => {
return user ? roles.includes(user.role) : false;
};
return { hasPermission, hasRole, hasAnyRole };
}
Способ 3: Компонент для условного рендеринга
interface ProtectedProps {
children: React.ReactNode;
permission?: Permission;
role?: UserRole;
fallback?: React.ReactNode;
}
function ProtectedSection({
children,
permission,
role,
fallback = null
}: ProtectedProps) {
const { hasPermission, hasRole } = usePermission();
let hasAccess = true;
if (permission && !hasPermission(permission)) {
hasAccess = false;
}
if (role && !hasRole(role)) {
hasAccess = false;
}
return hasAccess ? children : fallback;
}
// Использование
function Dashboard() {
return (
<div>
<ProtectedSection>
<UserDashboard />
</ProtectedSection>
<ProtectedSection permission={Permission.MANAGE_USERS}>
<AdminPanel />
</ProtectedSection>
<ProtectedSection role={UserRole.ADMIN}>
<AdminOnlyFeature />
</ProtectedSection>
<ProtectedSection
role={UserRole.ADMIN}
fallback={<p>У вас нет доступа</p>}
>
<SecretFeature />
</ProtectedSection>
</div>
);
}
Способ 4: Защита маршрутов
// React Router
const ProtectedRoute = ({
component: Component,
requiredRole,
...rest
}: any) => {
const { user } = useAuth();
return (
<Route
{...rest}
element={
user && user.role === requiredRole ? (
<Component />
) : (
<Navigate to="/login" />
)
}
/>
);
};
// Использование
function Router() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/profile" element={<Profile />} />
<ProtectedRoute
path="/admin"
component={AdminPanel}
requiredRole={UserRole.ADMIN}
/>
<ProtectedRoute
path="/moderator"
component={ModerationPanel}
requiredRole={UserRole.MODERATOR}
/>
</Routes>
);
}
Способ 5: Проверка прав в API запросах
Большинство проверок должны быть на бэкенде:
class ApiService {
constructor(private http: HttpClient) {}
// Обычные запросы
getPublicData() {
return this.http.get("/api/public/data");
}
// Защищённые запросы
getUserData() {
return this.http.get("/api/protected/user-data");
}
// Только для админов
getAllUsers() {
return this.http.get("/api/admin/users");
}
// С обработкой 403
deleteUser(userId: string) {
return this.http.delete(`/api/admin/users/${userId}`).pipe(
catchError(error => {
if (error.status === 403) {
console.error("У вас нет прав для этого действия");
throw new Error("Доступ запрещен");
}
throw error;
})
);
}
}
Реальный пример: Система управления постами
// API сервис
@Injectable()
export class PostService {
constructor(private http: HttpClient) {}
// Получить посты (доступно всем)
getPosts() {
return this.http.get("/api/posts");
}
// Создать пост (нужна авторизация)
createPost(data: CreatePostDto) {
return this.http.post("/api/posts", data);
}
// Отредактировать свой пост
updatePost(id: string, data: UpdatePostDto) {
return this.http.put(`/api/posts/${id}`, data);
}
// Удалить свой пост
deletePost(id: string) {
return this.http.delete(`/api/posts/${id}`);
}
// Модерирование поста
approvePost(id: string) {
return this.http.post(`/api/admin/posts/${id}/approve`, {});
}
// Удаление поста админом
forceDeletePost(id: string) {
return this.http.delete(`/api/admin/posts/${id}`);
}
}
// Компонент с разделением функционала
@Component({
selector: "app-post",
template: `
<div class="post">
<h2>{{ post.title }}</h2>
<p>{{ post.content }}</p>
<!-- Автор может редактировать/удалять -->>
<div *ngIf="isAuthor">
<button (click)="onEdit()">Редактировать</button>
<button (click)="onDelete()">Удалить</button>
</div>
<!-- Статус (видно при наличии права) -->
<p *ngIf="canModerate">
Статус: {{ post.status }}
</p>
<!-- Кнопки модерации только для модераторов -->
<div *ngIf="canModerate && post.status === pending">
<button (click)="onApprove()">Одобрить</button>
<button (click)="onReject()">Отклонить</button>
</div>
<!-- Удаление админом -->
<div *ngIf="isAdmin">
<button (click)="onForceDelete()" class="dangerous">
Удалить (админ)
</button>
</div>
</div>
`
})
export class PostComponent implements OnInit {
@Input() post: Post;
@Input() currentUserId: string;
@Input() currentUserRole: UserRole;
isAuthor: boolean;
canModerate: boolean;
isAdmin: boolean;
ngOnInit() {
this.isAuthor = this.post.authorId === this.currentUserId;
this.canModerate = this.currentUserRole === UserRole.MODERATOR ||
this.currentUserRole === UserRole.ADMIN;
this.isAdmin = this.currentUserRole === UserRole.ADMIN;
}
onEdit() { /* ... */ }
onDelete() { /* ... */ }
onApprove() { /* ... */ }
onReject() { /* ... */ }
onForceDelete() { /* ... */ }
}
Сервер должен проверять права
// На сервере (Node.js/Express)
app.post("/api/admin/users/:id/ban", authMiddleware, (req, res) => {
// Проверка 1: Пользователь авторизирован
if (!req.user) {
return res.status(401).json({ error: "Not authenticated" });
}
// Проверка 2: Только админ может выполнить это
if (req.user.role !== "admin") {
return res.status(403).json({ error: "Forbidden" });
}
// Проверка 3: ID параметр валидный
const { id } = req.params;
if (!isValidUUID(id)) {
return res.status(400).json({ error: "Invalid user ID" });
}
// Выполняем действие
User.findByIdAndUpdate(id, { banned: true }, (err, user) => {
if (err) {
return res.status(500).json({ error: "Database error" });
}
res.json({ message: "User banned", user });
});
});
Лучшие практики
- Всегда проверяйте на бэкенде - не доверяйте фронтенду
- Используйте токены с информацией о ролях (JWT)
- Кэшируйте информацию о пользователе в контексте/хранилище
- Обрабатывайте 403 ошибки (доступ запрещен)
- Логируйте попытки несанкционированного доступа
- Предоставляйте понятные сообщения об ошибках
- Разделяйте ответственность между фронтом и беком
Итог
Разделение функционала по ролям - это критичная часть безопасности приложения. На фронтенде это улучшает UX, но основная защита должна быть на сервере. Хороший подход:
- Фронтенд: Скрывает UI элементы, улучшает UX
- Сервер: Проверяет права при каждом запросе
- Оба: Работают вместе для безопасного приложения
Это стандартный паттерн, используемый во всех крупных приложениях.