import crypto from 'crypto'; import fs from 'fs'; import { jwtVerify } from 'jose'; import path from 'path'; import { MemberCredential, comparePassword, hashPassword } from './auth'; // Keys for JWT signing and verification let privateKey: Uint8Array | null = null; let publicKey: Uint8Array | null = null; // Path to store the keypair const KEYS_FILE = path.join(process.cwd(), 'data', 'jwt_keys.json'); // Path to the blacklist file const BLACKLIST_FILE = path.join(process.cwd(), 'data', 'used_tokens.json'); // Path to the member credentials file const MEMBER_CREDENTIALS_FILE = path.join(process.cwd(), 'data', 'member_credentials.json'); // Path to the settings file const SETTINGS_FILE = path.join(process.cwd(), 'data', 'settings.json'); // Ensure the data directory exists function ensureDataDirectory() { const dataDir = path.join(process.cwd(), 'data'); if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir, { recursive: true }); } } // Get all used tokens from the blacklist file export function getUsedTokens(): string[] { ensureDataDirectory(); if (!fs.existsSync(BLACKLIST_FILE)) { return []; } const data = fs.readFileSync(BLACKLIST_FILE, 'utf-8'); return JSON.parse(data); } // Add a token to the blacklist export function addToBlacklist(tokenId: string): void { const usedTokens = getUsedTokens(); if (!usedTokens.includes(tokenId)) { usedTokens.push(tokenId); ensureDataDirectory(); fs.writeFileSync(BLACKLIST_FILE, JSON.stringify(usedTokens, null, 2)); } } // Get settings export function getSettings(): { memberAuthEnabled: boolean } { ensureDataDirectory(); if (!fs.existsSync(SETTINGS_FILE)) { const defaultSettings = { memberAuthEnabled: false }; fs.writeFileSync(SETTINGS_FILE, JSON.stringify(defaultSettings, null, 2)); return defaultSettings; } const data = fs.readFileSync(SETTINGS_FILE, 'utf-8'); return JSON.parse(data); } // Update settings export function updateSettings(settings: { memberAuthEnabled: boolean }): void { ensureDataDirectory(); fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2)); } // Get all member credentials export function getMemberCredentials(): MemberCredential[] { ensureDataDirectory(); if (!fs.existsSync(MEMBER_CREDENTIALS_FILE)) { fs.writeFileSync(MEMBER_CREDENTIALS_FILE, JSON.stringify([], null, 2)); return []; } const data = fs.readFileSync(MEMBER_CREDENTIALS_FILE, 'utf-8'); return JSON.parse(data); } // Save member credentials export function saveMemberCredentials(credentials: MemberCredential[]): void { ensureDataDirectory(); fs.writeFileSync(MEMBER_CREDENTIALS_FILE, JSON.stringify(credentials, null, 2)); } // Add a new member export function addMember(memberNumber: string, password: string): boolean { const members = getMemberCredentials(); // Check if member already exists if (members.some(m => m.memberNumber === memberNumber)) { return false; } // Hash the password const hashedPassword = hashPassword(password); // Add the new member members.push({ memberNumber, password: hashedPassword, hasVoted: false }); saveMemberCredentials(members); return true; } // Update a member export function updateMember(memberNumber: string, data: { password?: string, hasVoted?: boolean }): boolean { const members = getMemberCredentials(); const memberIndex = members.findIndex(m => m.memberNumber === memberNumber); if (memberIndex === -1) { return false; } if (data.password) { members[memberIndex].password = hashPassword(data.password); } if (data.hasVoted !== undefined) { members[memberIndex].hasVoted = data.hasVoted; } saveMemberCredentials(members); return true; } // Delete a member export function deleteMember(memberNumber: string): boolean { const members = getMemberCredentials(); const initialLength = members.length; const filteredMembers = members.filter(m => m.memberNumber !== memberNumber); if (filteredMembers.length === initialLength) { return false; } saveMemberCredentials(filteredMembers); return true; } // Import members from CSV content export function importMembersFromCSV(csvContent: string): { added: number, skipped: number } { const members = getMemberCredentials(); const existingMemberNumbers = new Set(members.map(m => m.memberNumber)); let added = 0; let skipped = 0; // Parse CSV content const lines = csvContent.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (!line) continue; // Skip header row if present if (i === 0 && (line.toLowerCase().includes('member') || line.toLowerCase().includes('password'))) { continue; } // Split by comma, semicolon, or tab const parts = line.split(/[,;\t]/); if (parts.length >= 2) { const memberNumber = parts[0].trim(); const password = parts[1].trim(); // Skip if member number already exists if (existingMemberNumbers.has(memberNumber)) { skipped++; continue; } // Hash the password const hashedPassword = hashPassword(password); // Add the new member members.push({ memberNumber, password: hashedPassword, hasVoted: false }); existingMemberNumbers.add(memberNumber); added++; } } saveMemberCredentials(members); return { added, skipped }; } // Verify member credentials export function verifyMemberCredentials(memberNumber: string, password: string): { valid: boolean, hasVoted: boolean } { const members = getMemberCredentials(); const member = members.find(m => m.memberNumber === memberNumber); if (!member) { return { valid: false, hasVoted: false }; } const passwordValid = comparePassword(password, member.password); if (passwordValid) { // Update last login time member.lastLogin = new Date().toISOString(); saveMemberCredentials(members); } return { valid: passwordValid, hasVoted: member.hasVoted }; } // Mark member as voted export function markMemberAsVoted(memberNumber: string): boolean { return updateMember(memberNumber, { hasVoted: true }); } // Reset all member voting status export function resetMemberVotingStatus(): void { const members = getMemberCredentials(); for (const member of members) { member.hasVoted = false; } saveMemberCredentials(members); } // Function to generate or load keys export async function ensureKeys() { if (!privateKey || !publicKey) { try { // Check if keys exist in the data directory if (fs.existsSync(KEYS_FILE)) { // Load keys from file const keysData = JSON.parse(fs.readFileSync(KEYS_FILE, 'utf-8')); const encoder = new TextEncoder(); privateKey = encoder.encode(keysData.privateKey); publicKey = encoder.encode(keysData.publicKey); } else { // Check if secret key is provided in environment variable if (process.env.JWT_SECRET_KEY) { // Use the provided secret key const encoder = new TextEncoder(); const secretKey = process.env.JWT_SECRET_KEY; privateKey = encoder.encode(secretKey); publicKey = encoder.encode(secretKey); } else { // Generate a random secret key const secretKey = crypto.randomBytes(32).toString('hex'); const encoder = new TextEncoder(); privateKey = encoder.encode(secretKey); publicKey = encoder.encode(secretKey); } // Save keys to file ensureDataDirectory(); fs.writeFileSync(KEYS_FILE, JSON.stringify({ privateKey: new TextDecoder().decode(privateKey), publicKey: new TextDecoder().decode(publicKey) }, null, 2)); } } catch (error) { console.error('Error handling JWT keys:', error); // Fallback to a default key in case of error const encoder = new TextEncoder(); const secretKey = 'schafwaschener-segelverein-secret-key-for-jwt-signing'; privateKey = encoder.encode(secretKey); publicKey = encoder.encode(secretKey); } } } // Verify a token and mark it as used export async function verifyToken(token: string): Promise<{ valid: boolean, memberNumber?: string }> { try { // Use a secret key from environment variable or a default one const secretKey = process.env.JWT_SECRET_KEY || 'schafwaschener-segelverein-secret-key-for-jwt-signing'; const encoder = new TextEncoder(); const key = encoder.encode(secretKey); const { payload } = await jwtVerify(token, key); const tokenId = payload.tokenId as string; const memberNumber = payload.memberNumber as string | undefined; // Check if token has been used before const usedTokens = getUsedTokens(); if (usedTokens.includes(tokenId)) { return { valid: false }; } // Mark token as used by adding to blacklist addToBlacklist(tokenId); // If token contains a member number, mark that member as voted if (memberNumber) { markMemberAsVoted(memberNumber); } return { valid: true, memberNumber }; } catch (error) { console.error('Token verification failed:', error); return { valid: false }; } } // These functions are removed because they're causing issues // We'll handle cookies directly in the API routes