Problem
The server-side checkPermGate helper in packages/server/src/api/routers/games.ts returns true for the Member perm-gate value without actually verifying the user has an active membership record.
The frontend equivalent (getFlagBoolean in packages/amber/utils/settings.tsx) checks isAdmin || isMember, where isMember verifies the user has an active membership for the current year.
This semantic mismatch means that when a perm-gate flag is set to Member, any authenticated user passes the server-side check, even if they don't have a membership.
Current behavior
case 'Member':
return true // Any authenticated user passes
Expected behavior
case 'Member': {
if (isAdmin || isGameAdmin) return true
if (!userId) return false
const membership = await tx.membership.findFirst({
where: { userId, attending: true },
})
return membership !== null
}
Risk
Currently low risk because the flags gated by checkPermGate (allow_game_submission, allow_game_editing) are unlikely to be set to Member in practice. However, if the helper is reused for other perm-gate flags, the mismatch could become a real authorization gap.
Context
Found during code review of PR #289.
References: #182
Problem
The server-side
checkPermGatehelper inpackages/server/src/api/routers/games.tsreturnstruefor theMemberperm-gate value without actually verifying the user has an active membership record.The frontend equivalent (
getFlagBooleaninpackages/amber/utils/settings.tsx) checksisAdmin || isMember, whereisMemberverifies the user has an active membership for the current year.This semantic mismatch means that when a perm-gate flag is set to
Member, any authenticated user passes the server-side check, even if they don't have a membership.Current behavior
Expected behavior
Risk
Currently low risk because the flags gated by
checkPermGate(allow_game_submission,allow_game_editing) are unlikely to be set toMemberin practice. However, if the helper is reused for other perm-gate flags, the mismatch could become a real authorization gap.Context
Found during code review of PR #289.
References: #182