/** * QR System Demonstration Script * Shows QR validation, backup code generation, and manual entry flow */ // Import the QR validator and generator (simplified for demo) function createMockQRValidator() { return { validateQR: (qrString) => { if (!qrString || typeof qrString !== 'string') { return { valid: false, format: 'unknown', errorReason: 'invalid_format' }; } const trimmed = qrString.trim(); if (trimmed.startsWith('BCT.v')) { const parts = trimmed.split('.'); if (parts.length === 4) { return { valid: true, format: 'signed', ticketId: '550e8400-e29b-41d4-a716-446655440000', eventId: 'evt_789012345', metadata: { version: 2, issuedAt: Math.floor(Date.now() / 1000), expiresAt: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60, zone: 'GA', seat: 'A12' } }; } } if (trimmed.startsWith('TICKET_')) { const ticketId = trimmed.substring(7); if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(ticketId)) { return { valid: true, format: 'simple', ticketId }; } } if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(trimmed)) { return { valid: true, format: 'simple', ticketId: trimmed }; } return { valid: false, format: 'unknown', errorReason: 'invalid_format' }; }, validateBackupCode: (code) => { if (!code || typeof code !== 'string') { return { valid: false }; } const normalized = code.replace(/[^a-zA-Z0-9]/g, '').toUpperCase(); if (normalized.length !== 8) { return { valid: false }; } if (!/^[0-9A-F]{8}$/.test(normalized)) { return { valid: false }; } return { valid: true, normalizedCode: normalized }; }, extractTicketId: (qrString) => { const result = this.validateQR(qrString); return result.valid ? result.ticketId : null; }, generateBackupCode: (ticketId) => { if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(ticketId)) { throw new Error('Invalid ticket ID format'); } const cleanId = ticketId.replace(/-/g, ''); return cleanId.slice(-8).toUpperCase(); } }; } function getQRErrorMessage(result) { if (result.valid) { return 'Valid ticket'; } switch (result.errorReason) { case 'invalid_format': return 'Invalid QR code format'; case 'expired': return 'Ticket has expired'; case 'signature_invalid': return 'Invalid or tampered ticket'; case 'malformed': return 'Corrupted QR code data'; case 'missing_data': return 'Incomplete ticket information'; default: return 'Unknown error occurred'; } } function formatBackupCode(code) { if (code.length !== 8) { return code; } return `${code.substring(0, 4)}-${code.substring(4)}`; } console.log('🎫 Black Canyon Tickets - QR System Demonstration\\n'); // Sample ticket data const sampleTicketData = { ticketId: '550e8400-e29b-41d4-a716-446655440000', eventId: 'evt_789012345', zone: 'GA', seat: 'A12', expiresInDays: 30 }; console.log('1. QR Code Validation'); console.log('===================='); const validator = createMockQRValidator(); // Test different QR formats const testQRs = [ // Simple format `TICKET_${sampleTicketData.ticketId}`, // Signed token format (simulated) 'BCT.v2.eyJraWQiOiIxMjMiLCJlaWQiOiI0NTYifQ.abc123signature', // Legacy UUID only sampleTicketData.ticketId, // Invalid formats 'INVALID_QR_CODE', 'BCT.v2.invalid.signature', 'TICKET_not-a-uuid' ]; testQRs.forEach((qr, index) => { console.log(`Test ${index + 1}: ${qr.substring(0, 40)}${qr.length > 40 ? '...' : ''}`); const result = validator.validateQR(qr); console.log(` ✅ Valid: ${result.valid}`); console.log(` 📱 Format: ${result.format}`); if (result.ticketId) { console.log(` 🎫 Ticket ID: ${result.ticketId}`); } if (result.eventId) { console.log(` 🎪 Event ID: ${result.eventId}`); } if (!result.valid) { console.log(` ❌ Error: ${getQRErrorMessage(result)}`); } if (result.metadata) { if (result.metadata.expiresAt) { const expires = new Date(result.metadata.expiresAt * 1000); console.log(` ⏰ Expires: ${expires.toLocaleString()}`); } if (result.metadata.zone) { console.log(` 🏟️ Zone: ${result.metadata.zone}`); } if (result.metadata.seat) { console.log(` 💺 Seat: ${result.metadata.seat}`); } } console.log(); }); console.log('2. Backup Code Validation'); console.log('========================='); const testBackupCodes = [ '55440000', // Valid (last 8 chars of ticket ID) '1234ABCD', // Valid hex format '12345', // Too short '123456789', // Too long '1234GHIJ', // Invalid characters '' // Empty ]; testBackupCodes.forEach((code, index) => { console.log(`Test ${index + 1}: "${code}"`); const result = validator.validateBackupCode(code); console.log(` ✅ Valid: ${result.valid}`); if (result.valid && result.normalizedCode) { console.log(` 🔢 Normalized: ${formatBackupCode(result.normalizedCode)}`); } console.log(); }); console.log('3. Backup Code Generation'); console.log('========================'); const testTicketIds = [ sampleTicketData.ticketId, '123e4567-e89b-12d3-a456-426614174000', 'invalid-uuid' ]; testTicketIds.forEach((ticketId, index) => { console.log(`Test ${index + 1}: ${ticketId}`); try { const backupCode = validator.generateBackupCode(ticketId); console.log(` 🔢 Backup Code: ${formatBackupCode(backupCode)}`); } catch (error) { console.log(` ❌ Error: ${error.message}`); } console.log(); }); console.log('4. QR Format Detection'); console.log('======================'); const mixedQRs = [ 'TICKET_550e8400-e29b-41d4-a716-446655440000', 'BCT.v2.eyJ0aWQiOiIxMjMiLCJlaWQiOiI0NTYifQ.signature', '550e8400-e29b-41d4-a716-446655440000', // Legacy UUID only 'MANUAL_55440000' ]; mixedQRs.forEach((qr, index) => { console.log(`QR ${index + 1}: ${qr}`); const ticketId = validator.extractTicketId(qr); if (ticketId) { console.log(` 🎫 Extracted Ticket ID: ${ticketId}`); try { const backupCode = validator.generateBackupCode(ticketId); console.log(` 🔢 Backup Code: ${formatBackupCode(backupCode)}`); } catch (error) { console.log(` ❌ Backup Code Error: ${error.message}`); } } else { console.log(` ❌ Could not extract ticket ID`); } console.log(); }); console.log('5. Manual Entry Flow Simulation'); console.log('==============================='); console.log('Simulating gate staff manual entry scenarios:'); console.log(); // Scenario 1: Perfect entry console.log('Scenario 1: Gate staff enters backup code correctly'); const perfectCode = '55440000'; console.log(`Staff enters: "${perfectCode}"`); const result1 = validator.validateBackupCode(perfectCode); if (result1.valid) { console.log(`✅ Code accepted: ${formatBackupCode(result1.normalizedCode)}`); console.log(`🚪 Entry granted`); } else { console.log(`❌ Code rejected`); } console.log(); // Scenario 2: Entry with hyphens console.log('Scenario 2: Gate staff enters code with formatting'); const formattedCode = '5544-0000'; console.log(`Staff enters: "${formattedCode}"`); const result2 = validator.validateBackupCode(formattedCode); if (result2.valid) { console.log(`✅ Code accepted: ${formatBackupCode(result2.normalizedCode)}`); console.log(`🚪 Entry granted`); } else { console.log(`❌ Code rejected`); } console.log(); // Scenario 3: Invalid entry console.log('Scenario 3: Gate staff makes typing error'); const errorCode = '5544000'; // Missing digit console.log(`Staff enters: "${errorCode}"`); const result3 = validator.validateBackupCode(errorCode); if (result3.valid) { console.log(`✅ Code accepted`); } else { console.log(`❌ Code rejected - ask customer to show physical ticket`); console.log(`💡 Suggestion: Check last 8 characters on ticket bottom`); } console.log(); console.log('6. Security Features'); console.log('==================='); console.log('✅ Signed tokens prevent counterfeiting'); console.log('✅ UUID ticket IDs prevent enumeration attacks'); console.log('✅ Backup codes are last 8 characters (not sequential)'); console.log('✅ HMAC signatures detect tampering'); console.log('✅ Time-based expiration prevents replay attacks'); console.log('✅ Offline validation available for signed tokens'); console.log(); console.log('7. Production Recommendations'); console.log('============================='); console.log('✅ Use signed tokens (BCT.v2.{payload}.{signature}) for security'); console.log('✅ Implement proper HMAC-SHA256 signatures in production'); console.log('✅ Rotate signing keys quarterly'); console.log('✅ Use Error Correction Level M (15%) for most use cases'); console.log('✅ Use Error Correction Level H (30%) for thermal printers'); console.log('✅ Include backup codes on all printed tickets'); console.log('✅ Train gate staff on manual entry procedures'); console.log('✅ Test QR codes across different devices and lighting conditions'); console.log('✅ Monitor QR scan success rates and manual entry frequency'); console.log(); console.log('🚀 QR System Demo Complete!'); console.log('Visit /scanner?eventId=test-event-123 to test the scanner interface'); console.log(`Development server: http://localhost:5174`); console.log(); console.log('📱 Manual Entry Instructions for Gate Staff:'); console.log('============================================'); console.log('1. If QR code won\'t scan, click the # button'); console.log('2. Enter the last 8 characters from bottom of ticket'); console.log('3. Characters can be numbers 0-9 or letters A-F'); console.log('4. System will show XXXX-XXXX format as you type'); console.log('5. Press Submit when all 8 characters entered'); console.log('6. Green checkmark = valid ticket, allow entry'); console.log('7. Red X = invalid code, ask to see physical ticket'); console.log();