Fix RBAC role system bugs and improve UX (#446)
* Fix RBAC role system bugs and improve UX - Fix user list dropdown selection in host sharing - Fix role sharing permissions to include role-based access - Fix translation template interpolation for success messages - Standardize system roles to admin and user only - Auto-assign user role to new registrations - Remove blocking confirmation dialogs in modal contexts - Add missing i18n keys for common actions - Fix button type to prevent unintended form submissions * Enhance RBAC system with UI improvements and security fixes - Move role assignment to Users tab with per-user role management - Protect system roles (admin/user) from editing and manual assignment - Simplify permission system: remove Use level, keep View and Manage - Hide Update button and Sharing tab for view-only/shared hosts - Prevent users from sharing hosts with themselves - Unify table and modal styling across admin panels - Auto-assign system roles on user registration - Add permission metadata to host interface * Add empty state message for role assignment - Display helpful message when no custom roles available - Clarify that system roles are auto-assigned - Add noCustomRolesToAssign translation in English and Chinese * fix: Prevent credential sharing errors for shared hosts - Skip credential resolution for shared hosts with credential authentication to prevent decryption errors (credentials are encrypted per-user) - Add warning alert in sharing tab when host uses credential authentication - Inform users that shared users cannot connect to credential-based hosts - Add translations for credential sharing warning (EN/ZH) This prevents authentication failures when sharing hosts configured with credential authentication while maintaining security by keeping credentials isolated per user. * feat: Improve rbac UI and fixes some bugs --------- Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com> Co-authored-by: LukeGus <bugattiguy527@gmail.com>
This commit was merged in pull request #446.
This commit is contained in:
@@ -12,6 +12,48 @@ import type {
|
||||
DockerLogOptions,
|
||||
DockerValidation,
|
||||
} from "../types/index.js";
|
||||
|
||||
// ============================================================================
|
||||
// RBAC TYPE DEFINITIONS
|
||||
// ============================================================================
|
||||
|
||||
export interface Role {
|
||||
id: number;
|
||||
name: string;
|
||||
displayName: string;
|
||||
description: string | null;
|
||||
isSystem: boolean;
|
||||
permissions: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface UserRole {
|
||||
userId: string;
|
||||
roleId: number;
|
||||
roleName: string;
|
||||
roleDisplayName: string;
|
||||
grantedBy: string;
|
||||
grantedByUsername: string;
|
||||
grantedAt: string;
|
||||
}
|
||||
|
||||
export interface AccessRecord {
|
||||
id: number;
|
||||
targetType: "user" | "role";
|
||||
userId: string | null;
|
||||
roleId: number | null;
|
||||
username: string | null;
|
||||
roleName: string | null;
|
||||
roleDisplayName: string | null;
|
||||
grantedBy: string;
|
||||
grantedByUsername: string;
|
||||
permissionLevel: string;
|
||||
expiresAt: string | null;
|
||||
createdAt: string;
|
||||
lastAccessedAt: string | null;
|
||||
accessCount: number;
|
||||
}
|
||||
import {
|
||||
apiLogger,
|
||||
authLogger,
|
||||
@@ -599,6 +641,9 @@ function initializeApiInstances() {
|
||||
// Homepage API (port 30006)
|
||||
homepageApi = createApiInstance(getApiUrl("", 30006), "HOMEPAGE");
|
||||
|
||||
// RBAC API (port 30001)
|
||||
rbacApi = createApiInstance(getApiUrl("", 30001), "RBAC");
|
||||
|
||||
// Docker Management API (port 30007)
|
||||
dockerApi = createApiInstance(getApiUrl("/docker", 30007), "DOCKER");
|
||||
}
|
||||
@@ -621,6 +666,9 @@ export let authApi: AxiosInstance;
|
||||
// Homepage API (port 30006)
|
||||
export let homepageApi: AxiosInstance;
|
||||
|
||||
// RBAC API (port 30001)
|
||||
export let rbacApi: AxiosInstance;
|
||||
|
||||
// Docker Management API (port 30007)
|
||||
export let dockerApi: AxiosInstance;
|
||||
|
||||
@@ -3132,6 +3180,129 @@ export async function unlinkOIDCFromPasswordAccount(
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// RBAC MANAGEMENT
|
||||
// ============================================================================
|
||||
|
||||
// Role Management
|
||||
export async function getRoles(): Promise<{ roles: Role[] }> {
|
||||
try {
|
||||
const response = await rbacApi.get("/rbac/roles");
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "fetch roles");
|
||||
}
|
||||
}
|
||||
|
||||
export async function createRole(roleData: {
|
||||
name: string;
|
||||
displayName: string;
|
||||
description?: string | null;
|
||||
}): Promise<{ role: Role }> {
|
||||
try {
|
||||
const response = await rbacApi.post("/rbac/roles", roleData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "create role");
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateRole(
|
||||
roleId: number,
|
||||
roleData: {
|
||||
displayName?: string;
|
||||
description?: string | null;
|
||||
},
|
||||
): Promise<{ role: Role }> {
|
||||
try {
|
||||
const response = await rbacApi.put(`/rbac/roles/${roleId}`, roleData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "update role");
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteRole(roleId: number): Promise<{ success: boolean }> {
|
||||
try {
|
||||
const response = await rbacApi.delete(`/rbac/roles/${roleId}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "delete role");
|
||||
}
|
||||
}
|
||||
|
||||
// User-Role Management
|
||||
export async function getUserRoles(userId: string): Promise<{ roles: UserRole[] }> {
|
||||
try {
|
||||
const response = await rbacApi.get(`/rbac/users/${userId}/roles`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "fetch user roles");
|
||||
}
|
||||
}
|
||||
|
||||
export async function assignRoleToUser(
|
||||
userId: string,
|
||||
roleId: number,
|
||||
): Promise<{ success: boolean }> {
|
||||
try {
|
||||
const response = await rbacApi.post(`/rbac/users/${userId}/roles`, { roleId });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "assign role to user");
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeRoleFromUser(
|
||||
userId: string,
|
||||
roleId: number,
|
||||
): Promise<{ success: boolean }> {
|
||||
try {
|
||||
const response = await rbacApi.delete(`/rbac/users/${userId}/roles/${roleId}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "remove role from user");
|
||||
}
|
||||
}
|
||||
|
||||
// Host Sharing Management
|
||||
export async function shareHost(
|
||||
hostId: number,
|
||||
shareData: {
|
||||
targetType: "user" | "role";
|
||||
targetUserId?: string;
|
||||
targetRoleId?: number;
|
||||
permissionLevel: string;
|
||||
durationHours?: number;
|
||||
},
|
||||
): Promise<{ success: boolean }> {
|
||||
try {
|
||||
const response = await rbacApi.post(`/rbac/host/${hostId}/share`, shareData);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "share host");
|
||||
}
|
||||
}
|
||||
|
||||
export async function getHostAccess(hostId: number): Promise<{ accessList: AccessRecord[] }> {
|
||||
try {
|
||||
const response = await rbacApi.get(`/rbac/host/${hostId}/access`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "fetch host access");
|
||||
}
|
||||
}
|
||||
|
||||
export async function revokeHostAccess(
|
||||
hostId: number,
|
||||
accessId: number,
|
||||
): Promise<{ success: boolean }> {
|
||||
try {
|
||||
const response = await rbacApi.delete(`/rbac/host/${hostId}/access/${accessId}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "revoke host access");
|
||||
|
||||
// ============================================================================
|
||||
// DOCKER MANAGEMENT API
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user