331 lines
11 KiB
TypeScript
Executable File
331 lines
11 KiB
TypeScript
Executable File
'use client';
|
||
|
||
import { createContext, useContext, useState, useEffect, useCallback } from 'react';
|
||
import { oidcClient } from '@/lib/auth/oidc-client';
|
||
import { TokenManager } from '@/lib/auth/token-manager';
|
||
import { useTRPC } from '@fenghuo/client';
|
||
import { useQuery } from '@tanstack/react-query';
|
||
import {
|
||
SystemPermission,
|
||
PermissionUtils,
|
||
SystemRole,
|
||
SystemRoleConfig,
|
||
type PermissionMeta,
|
||
UserWithRelations,
|
||
userWithRelationsSelect
|
||
} from '@fenghuo/common';
|
||
import { useToken } from '@/components/providers/token-provider';
|
||
import { Prisma, Role } from '@fenghuo/db/index';
|
||
|
||
|
||
// 导出权限相关类型和工具
|
||
export {
|
||
SystemPermission,
|
||
PermissionUtils,
|
||
SystemRole,
|
||
SystemRoleConfig,
|
||
type PermissionMeta
|
||
} from '@fenghuo/common';
|
||
|
||
// 扩展用户接口
|
||
export interface User extends Omit<UserWithRelations, 'roles'> {
|
||
roles: string[]; // 角色名称数组
|
||
permissions?: SystemPermission[]; // 使用新的权限系统
|
||
}
|
||
|
||
interface AuthProviderProps {
|
||
children: React.ReactNode;
|
||
}
|
||
|
||
interface AuthContextType {
|
||
// 用户状态
|
||
user: User | null;
|
||
isLoading: boolean;
|
||
isAuthenticated: boolean;
|
||
|
||
// 权限检查方法
|
||
hasRole: (role: string) => boolean;
|
||
hasPermission: (permission: SystemPermission) => boolean;
|
||
hasAnyRole: (roles: string[]) => boolean;
|
||
hasAnyPermission: (permissions: SystemPermission[]) => boolean;
|
||
hasAllPermissions: (permissions: SystemPermission[]) => boolean;
|
||
isSuperAdmin: () => boolean;
|
||
isAdmin: () => boolean;
|
||
|
||
// 认证操作
|
||
login: (username: string, password: string) => Promise<void>;
|
||
register: (userData: Prisma.UserCreateArgs) => Promise<void>;
|
||
logout: () => Promise<void>;
|
||
getAccessToken: () => Promise<string | null>;
|
||
loadUser: () => Promise<void>;
|
||
}
|
||
|
||
const AuthContext = createContext<AuthContextType | null>(null);
|
||
|
||
export default function AuthProvider({ children }: AuthProviderProps) {
|
||
const { refreshToken, clearTokens } = useToken();
|
||
const [userId, setUserId] = useState<string | null>(null);
|
||
const [user, setUser] = useState<User | null>(null);
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||
|
||
const trpc = useTRPC();
|
||
|
||
// 修改 tRPC 查询,包含角色权限
|
||
const { data: backendUser, error: userError, isLoading: isUserLoading } = useQuery({
|
||
...trpc.user.findFirst.queryOptions({
|
||
where: { id: userId! },
|
||
select: userWithRelationsSelect
|
||
}),
|
||
enabled: !!userId,
|
||
retry: false,
|
||
}) as { data: UserWithRelations | null, error: Error | null, isLoading: boolean };
|
||
|
||
// 更新用户状态当后端用户数据加载完成时
|
||
useEffect(() => {
|
||
if (backendUser) {
|
||
const userRoles = backendUser.roles?.map(r => r.slug) || [];
|
||
// 传递完整的角色数据给权限计算函数
|
||
const userPermissions = getUserPermissions(backendUser.roles || []);
|
||
const appUser: User = {
|
||
...backendUser,
|
||
roles: userRoles,
|
||
permissions: userPermissions,
|
||
};
|
||
setUser(appUser);
|
||
} else if (userError) {
|
||
console.error('获取用户数据失败:', userError);
|
||
setUser(null);
|
||
setIsAuthenticated(false);
|
||
TokenManager.clearTokens();
|
||
}
|
||
}, [backendUser, userError]);
|
||
|
||
// 加载用户信息
|
||
const loadUser = useCallback(async () => {
|
||
setIsLoading(true);
|
||
try {
|
||
const accessToken = await TokenManager.getValidAccessToken();
|
||
if (accessToken) {
|
||
// 只获取 OIDC 用户信息来获得用户 ID
|
||
const oidcUser = await oidcClient.getUserInfo(accessToken);
|
||
setUserId(oidcUser.sub);
|
||
setIsAuthenticated(true);
|
||
|
||
// 触发 QueryProvider 刷新 token
|
||
refreshToken();
|
||
} else {
|
||
setUserId(null);
|
||
setUser(null);
|
||
setIsAuthenticated(false);
|
||
setIsLoading(false);
|
||
}
|
||
} catch (error) {
|
||
console.error('加载用户信息失败:', error);
|
||
setUserId(null);
|
||
setUser(null);
|
||
setIsAuthenticated(false);
|
||
TokenManager.clearTokens();
|
||
setIsLoading(false);
|
||
}
|
||
}, [refreshToken]);
|
||
|
||
// 当 tRPC 查询完成时更新 loading 状态
|
||
useEffect(() => {
|
||
if (userId && !isUserLoading) {
|
||
setIsLoading(false);
|
||
}
|
||
}, [userId, isUserLoading]);
|
||
|
||
// 初始化时加载用户信息
|
||
useEffect(() => {
|
||
loadUser();
|
||
}, [loadUser]);
|
||
|
||
// 登录功能
|
||
const login = useCallback(async (username: string, password: string): Promise<void> => {
|
||
setIsLoading(true);
|
||
try {
|
||
// 调用 OIDC 服务登录
|
||
const tokenResponse = await oidcClient.loginWithPassword(username, password);
|
||
|
||
// 保存令牌
|
||
TokenManager.saveTokens(tokenResponse);
|
||
|
||
// 获取用户信息并设置 userId,让 tRPC 查询处理剩余逻辑
|
||
const oidcUser = await oidcClient.getUserInfo(tokenResponse.access_token);
|
||
setUserId(oidcUser.sub);
|
||
setIsAuthenticated(true);
|
||
|
||
// 登录成功后直接刷新token context
|
||
await refreshToken();
|
||
} catch (error) {
|
||
console.error('登录失败:', error);
|
||
throw error;
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
}, [refreshToken]);
|
||
|
||
// 注册功能
|
||
const register = useCallback(async (userData: Prisma.UserCreateArgs): Promise<void> => {
|
||
setIsLoading(true);
|
||
try {
|
||
await oidcClient.register(userData);
|
||
} catch (error) {
|
||
console.error('注册失败:', error);
|
||
throw error;
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
// 退出登录功能
|
||
const logout = useCallback(async () => {
|
||
setIsLoading(true);
|
||
try {
|
||
const accessToken = TokenManager.getAccessToken();
|
||
if (accessToken) {
|
||
await oidcClient.logout(accessToken);
|
||
}
|
||
} catch (error) {
|
||
console.error('退出登录失败:', error);
|
||
} finally {
|
||
TokenManager.clearTokens();
|
||
setUserId(null);
|
||
setUser(null);
|
||
setIsAuthenticated(false);
|
||
setIsLoading(false);
|
||
|
||
// 退出时清除token
|
||
clearTokens();
|
||
}
|
||
}, [clearTokens]);
|
||
|
||
// 获取访问令牌
|
||
const getAccessToken = useCallback(async () => {
|
||
return await TokenManager.getValidAccessToken();
|
||
}, []);
|
||
|
||
// 权限检查方法 - 使用新的权限系统
|
||
const hasRole = useCallback((role: string): boolean => {
|
||
return user?.roles.includes(role) || false;
|
||
}, [user]);
|
||
|
||
const hasPermission = useCallback((permission: SystemPermission): boolean => {
|
||
if (!user?.permissions) return false;
|
||
return PermissionUtils.hasPermission(user.permissions, permission);
|
||
}, [user]);
|
||
|
||
const hasAnyRole = useCallback((roles: string[]): boolean => {
|
||
return roles.some(role => hasRole(role));
|
||
}, [hasRole]);
|
||
|
||
const hasAnyPermission = useCallback((permissions: SystemPermission[]): boolean => {
|
||
if (!user?.permissions) return false;
|
||
return PermissionUtils.hasAnyPermission(user.permissions, permissions);
|
||
}, [user]);
|
||
|
||
const hasAllPermissions = useCallback((permissions: SystemPermission[]): boolean => {
|
||
if (!user?.permissions) return false;
|
||
return PermissionUtils.hasAllPermissions(user.permissions, permissions);
|
||
}, [user]);
|
||
|
||
// 检查是否为超级管理员
|
||
const isSuperAdmin = useCallback((): boolean => {
|
||
return hasPermission(SystemPermission.SUPER_ADMIN);
|
||
}, [hasPermission]);
|
||
|
||
// 检查是否为管理员级别
|
||
const isAdmin = useCallback((): boolean => {
|
||
return hasRole(SystemRole.SUPER_ADMIN) || hasRole(SystemRole.ADMIN);
|
||
}, [hasRole]);
|
||
|
||
const contextValue: AuthContextType = {
|
||
user,
|
||
isLoading,
|
||
isAuthenticated,
|
||
hasRole,
|
||
hasPermission,
|
||
hasAnyRole,
|
||
hasAnyPermission,
|
||
hasAllPermissions,
|
||
isSuperAdmin,
|
||
isAdmin,
|
||
login,
|
||
register,
|
||
logout,
|
||
getAccessToken,
|
||
loadUser,
|
||
};
|
||
|
||
return (
|
||
<AuthContext.Provider value={contextValue}>
|
||
{children}
|
||
</AuthContext.Provider>
|
||
);
|
||
}
|
||
|
||
export function useAuth() {
|
||
const context = useContext(AuthContext);
|
||
if (!context) {
|
||
throw new Error('useAuth must be used within an AuthProvider');
|
||
}
|
||
return context;
|
||
}
|
||
|
||
// 计算用户权限 - 基于数据库角色权限
|
||
const getUserPermissions = (roles: Role[]): SystemPermission[] => {
|
||
const permissions = new Set<SystemPermission>();
|
||
|
||
roles.forEach((role) => {
|
||
if (!role.permissions || !Array.isArray(role.permissions)) {
|
||
console.warn('角色权限数据格式错误:', role);
|
||
return;
|
||
}
|
||
|
||
role.permissions.forEach(permission => {
|
||
if (PermissionUtils.isValidPermission(permission)) {
|
||
permissions.add(permission as SystemPermission);
|
||
} else {
|
||
console.warn('无效的权限代码:', permission);
|
||
}
|
||
});
|
||
});
|
||
return Array.from(permissions);
|
||
};
|
||
|
||
// 权限检查工具函数 - 更新为使用新的权限系统
|
||
export const checkPermission = (userPermissions: SystemPermission[], requiredPermissions?: SystemPermission[]): boolean => {
|
||
if (!requiredPermissions || requiredPermissions.length === 0) return true;
|
||
return PermissionUtils.hasAnyPermission(userPermissions, requiredPermissions);
|
||
};
|
||
|
||
// 检查角色权限的工具函数
|
||
export const checkRole = (userRoles: string[], requiredRoles?: string[]): boolean => {
|
||
if (!requiredRoles || requiredRoles.length === 0) return true;
|
||
return requiredRoles.some(role => userRoles.includes(role));
|
||
};
|
||
|
||
// 过滤菜单项的工具函数 - 支持角色和权限检查
|
||
export const filterMenuItems = <T extends { roles?: string[]; permissions?: SystemPermission[] }>(
|
||
items: T[],
|
||
user: User | null
|
||
): T[] => {
|
||
return items
|
||
if (!user) return [];
|
||
|
||
return items.filter(item => {
|
||
// 检查角色要求
|
||
if (item.roles && !checkRole(user.roles, item.roles)) {
|
||
return false;
|
||
}
|
||
|
||
// 检查权限要求
|
||
if (item.permissions && user.permissions && !checkPermission(user.permissions, item.permissions)) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
});
|
||
}; |