feat: add email verification, password reset, and WSDC integration (Phase 1.5)

Backend features:
- AWS SES email service with HTML templates
- Email verification with dual method (link + 6-digit PIN code)
- Password reset workflow with secure tokens
- WSDC API proxy for dancer lookup and auto-fill registration
- Extended User model with verification and WSDC fields
- Email verification middleware for protected routes

Frontend features:
- Two-step registration with WSDC ID lookup
- Password strength indicator component
- Email verification page with code input
- Password reset flow (request + reset pages)
- Verification banner for unverified users
- Updated authentication context and API service

Testing:
- 65 unit tests with 100% coverage of new features
- Tests for auth utils, email service, WSDC controller, and middleware
- Integration tests for full authentication flows
- Comprehensive mocking of AWS SES and external APIs

Database:
- Migration: add WSDC fields (firstName, lastName, wsdcId)
- Migration: add email verification fields (token, code, expiry)
- Migration: add password reset fields (token, expiry)

Documentation:
- Complete Phase 1.5 documentation
- Test suite documentation and best practices
- Updated session context with new features
This commit is contained in:
Radosław Gierwiało
2025-11-13 15:47:54 +01:00
parent 4d7f814538
commit 7a2f6d07ec
31 changed files with 5586 additions and 87 deletions

View File

@@ -0,0 +1,76 @@
/**
* WSDC API Controller
* Provides proxy endpoint for World Swing Dance Council (WSDC) dancer lookup
*/
const WSDC_API_BASE = 'https://points.worldsdc.com/lookup2020/find';
/**
* Lookup dancer by WSDC ID
* GET /api/wsdc/lookup?id=26997
*/
exports.lookupDancer = async (req, res) => {
try {
const { id } = req.query;
// Validate WSDC ID
if (!id) {
return res.status(400).json({
error: 'Bad Request',
message: 'WSDC ID is required'
});
}
// Validate WSDC ID format (numeric, max 10 digits)
if (!/^\d{1,10}$/.test(id)) {
return res.status(400).json({
error: 'Bad Request',
message: 'Invalid WSDC ID format. Must be numeric.'
});
}
// Fetch from WSDC API
const url = `${WSDC_API_BASE}?q=${id}`;
const response = await fetch(url);
if (!response.ok) {
console.error(`WSDC API error: ${response.status} ${response.statusText}`);
return res.status(502).json({
error: 'Bad Gateway',
message: 'Failed to fetch data from WSDC API'
});
}
const data = await response.json();
// Check if dancer was found
if (!data || !data.dancer_wsdcid) {
return res.status(404).json({
error: 'Not Found',
message: 'Dancer with this WSDC ID not found'
});
}
// Extract relevant fields
const dancerData = {
wsdcId: data.dancer_wsdcid,
firstName: data.dancer_first || '',
lastName: data.dancer_last || '',
// Optional: include competitive level info if needed
recentYear: data.recent_year,
dominateRole: data.dominate_data?.short_dominate_role || null
};
return res.status(200).json({
success: true,
dancer: dancerData
});
} catch (error) {
console.error('Error in lookupDancer:', error);
return res.status(500).json({
error: 'Internal Server Error',
message: 'An error occurred while looking up dancer'
});
}
};