casualroom/apps/fenghuo/web/components/providers/auth-provider.tsx

331 lines
11 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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;
});
};