123df671f5
Reviewed-on: #5 Co-authored-by: David Gwilliam <dhgwilliam@gmail.com> Co-committed-by: David Gwilliam <dhgwilliam@gmail.com>
218 lines
5.7 KiB
TypeScript
218 lines
5.7 KiB
TypeScript
import { getSession } from './auth-simple';
|
|
import { prisma } from './prisma';
|
|
|
|
export type UserRole = 'player' | 'tournament_admin' | 'club_admin';
|
|
|
|
export interface PermissionCheck {
|
|
allowed: boolean;
|
|
reason?: string;
|
|
}
|
|
|
|
/**
|
|
* Check if the current user has the required role
|
|
* Reads role from database to avoid session cache issues
|
|
*/
|
|
export async function hasRole(requiredRole: UserRole): Promise<PermissionCheck> {
|
|
const session = await getSession();
|
|
|
|
if (!session) {
|
|
return { allowed: false, reason: 'Not authenticated' };
|
|
}
|
|
|
|
const userId = session.user?.id;
|
|
if (!userId) {
|
|
return { allowed: false, reason: 'User ID not found in session' };
|
|
}
|
|
|
|
// Fetch user from database to get current role (bypasses session cache)
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: userId },
|
|
select: { role: true }
|
|
});
|
|
|
|
if (!user) {
|
|
return { allowed: false, reason: 'User not found in database' };
|
|
}
|
|
|
|
const userRole = user.role as UserRole;
|
|
|
|
// Role hierarchy: player < tournament_admin < club_admin
|
|
const roleHierarchy: Record<UserRole, number> = {
|
|
'player': 1,
|
|
'tournament_admin': 2,
|
|
'club_admin': 3
|
|
};
|
|
|
|
const hasRequiredRole = roleHierarchy[userRole] >= roleHierarchy[requiredRole];
|
|
|
|
return {
|
|
allowed: hasRequiredRole,
|
|
reason: hasRequiredRole ? undefined : `Requires ${requiredRole} role, but user has ${userRole}`
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if the user owns the tournament (for tournament_admin)
|
|
*/
|
|
export async function ownsTournament(tournamentId: number): Promise<PermissionCheck> {
|
|
const session = await getSession();
|
|
|
|
if (!session) {
|
|
return { allowed: false, reason: 'Not authenticated' };
|
|
}
|
|
|
|
const userId = session.user?.id;
|
|
if (!userId) {
|
|
return { allowed: false, reason: 'User ID not found in session' };
|
|
}
|
|
|
|
const tournament = await prisma.event.findUnique({
|
|
where: { id: tournamentId },
|
|
select: { ownerId: true }
|
|
});
|
|
|
|
if (!tournament) {
|
|
return { allowed: false, reason: 'Tournament not found' };
|
|
}
|
|
|
|
const isOwner = tournament.ownerId === userId;
|
|
|
|
return {
|
|
allowed: isOwner,
|
|
reason: isOwner ? undefined : 'User does not own this tournament'
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if the user can manage a specific tournament
|
|
* Tournament admins can only manage their own tournaments
|
|
* Club admins can manage all tournaments
|
|
* Reads role from database to avoid session cache issues
|
|
*/
|
|
export async function canManageTournament(tournamentId: number): Promise<PermissionCheck> {
|
|
const session = await getSession();
|
|
|
|
if (!session) {
|
|
return { allowed: false, reason: 'Not authenticated' };
|
|
}
|
|
|
|
const userId = session.user?.id;
|
|
if (!userId) {
|
|
return { allowed: false, reason: 'User ID not found in session' };
|
|
}
|
|
|
|
// Fetch user from database to get current role (bypasses session cache)
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: userId },
|
|
select: { role: true }
|
|
});
|
|
|
|
if (!user) {
|
|
return { allowed: false, reason: 'User not found in database' };
|
|
}
|
|
|
|
const userRole = user.role as UserRole;
|
|
|
|
// Club admins can manage all tournaments
|
|
if (userRole === 'club_admin') {
|
|
return { allowed: true };
|
|
}
|
|
|
|
// Tournament admins can only manage their own tournaments
|
|
if (userRole === 'tournament_admin') {
|
|
return ownsTournament(tournamentId);
|
|
}
|
|
|
|
return { allowed: false, reason: 'Insufficient permissions to manage tournament' };
|
|
}
|
|
|
|
/**
|
|
* Check if the user can create tournaments
|
|
*/
|
|
export async function canCreateTournaments(): Promise<PermissionCheck> {
|
|
return hasRole('tournament_admin');
|
|
}
|
|
|
|
/**
|
|
* Check if the user can manage a specific match
|
|
* Tournament admins can manage matches in their own tournaments
|
|
* Club admins can manage all matches
|
|
*/
|
|
export async function canManageMatch(matchId: number): Promise<PermissionCheck> {
|
|
const session = await getSession();
|
|
|
|
if (!session) {
|
|
return { allowed: false, reason: 'Not authenticated' };
|
|
}
|
|
|
|
const userRole = session.user?.role as UserRole;
|
|
|
|
// Club admins can manage all matches
|
|
if (userRole === 'club_admin') {
|
|
return { allowed: true };
|
|
}
|
|
|
|
// Tournament admins can only manage matches in their own tournaments
|
|
if (userRole === 'tournament_admin') {
|
|
const match = await prisma.match.findUnique({
|
|
where: { id: matchId },
|
|
include: { event: true }
|
|
});
|
|
|
|
if (!match || !match.event) {
|
|
return { allowed: false, reason: 'Match or tournament not found' };
|
|
}
|
|
|
|
return ownsTournament(match.event.id);
|
|
}
|
|
|
|
return { allowed: false, reason: 'Insufficient permissions to manage match' };
|
|
}
|
|
|
|
/**
|
|
* Check if the user can assign tournament_admin privileges
|
|
* Only club admins can assign tournament_admin role
|
|
*/
|
|
export async function canAssignTournamentAdmin(): Promise<PermissionCheck> {
|
|
return hasRole('club_admin');
|
|
}
|
|
|
|
/**
|
|
* Get tournaments that the current user can manage
|
|
*/
|
|
export async function getManageableTournaments() {
|
|
const session = await getSession();
|
|
|
|
if (!session) {
|
|
return [];
|
|
}
|
|
|
|
const userRole = session.user?.role as UserRole;
|
|
const userId = session.user?.id;
|
|
|
|
if (userRole === 'club_admin') {
|
|
// Club admins can see all tournaments
|
|
return prisma.event.findMany({
|
|
where: { eventType: 'tournament' },
|
|
orderBy: { createdAt: 'desc' }
|
|
});
|
|
}
|
|
|
|
if (userRole === 'tournament_admin') {
|
|
// Tournament admins can only see their own tournaments
|
|
return prisma.event.findMany({
|
|
where: {
|
|
eventType: 'tournament',
|
|
ownerId: userId
|
|
},
|
|
orderBy: { createdAt: 'desc' }
|
|
});
|
|
}
|
|
|
|
// Regular players can only view tournaments (not manage them)
|
|
return prisma.event.findMany({
|
|
where: { eventType: 'tournament', status: { not: 'draft' } },
|
|
orderBy: { createdAt: 'desc' }
|
|
});
|
|
}
|