Major Enhancement: NestJS Migration + CASL Authorization + Error Handling
Some checks failed
CI/CD Pipeline / Backend Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Tests (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled

Complete rewrite from Express to NestJS with enterprise-grade features:

## Backend Improvements
- Migrated from Express to NestJS 11.0.1 with TypeScript
- Implemented Prisma ORM 7.3.0 for type-safe database access
- Added CASL authorization system replacing role-based guards
- Created global exception filters with structured logging
- Implemented Auth0 JWT authentication with Passport.js
- Added vehicle management with conflict detection
- Enhanced event scheduling with driver/vehicle assignment
- Comprehensive error handling and logging

## Frontend Improvements
- Upgraded to React 19.2.0 with Vite 7.2.4
- Implemented CASL-based permission system
- Added AbilityContext for declarative permissions
- Created ErrorHandler utility for consistent error messages
- Enhanced API client with request/response logging
- Added War Room (Command Center) dashboard
- Created VIP Schedule view with complete itineraries
- Implemented Vehicle Management UI
- Added mock data generators for testing (288 events across 20 VIPs)

## New Features
- Vehicle fleet management (types, capacity, status tracking)
- Complete 3-day Jamboree schedule generation
- Individual VIP schedule pages with PDF export (planned)
- Real-time War Room dashboard with auto-refresh
- Permission-based navigation filtering
- First user auto-approval as administrator

## Documentation
- Created CASL_AUTHORIZATION.md (comprehensive guide)
- Created ERROR_HANDLING.md (error handling patterns)
- Updated CLAUDE.md with new architecture
- Added migration guides and best practices

## Technical Debt Resolved
- Removed custom authentication in favor of Auth0
- Replaced role checks with CASL abilities
- Standardized error responses across API
- Implemented proper TypeScript typing
- Added comprehensive logging

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 08:50:25 +01:00
parent 8ace1ab2c1
commit 868f7efc23
351 changed files with 44997 additions and 6276 deletions

View File

@@ -0,0 +1,217 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.User = void 0;
exports.generateToken = generateToken;
exports.verifyToken = verifyToken;
exports.verifyGoogleToken = verifyGoogleToken;
exports.getGoogleAuthUrl = getGoogleAuthUrl;
exports.exchangeCodeForTokens = exchangeCodeForTokens;
exports.getGoogleUserInfo = getGoogleUserInfo;
const jwtKeyManager_1 = __importDefault(require("../services/jwtKeyManager"));
// JWT Key Manager now handles all token operations with automatic rotation
// No more static JWT_SECRET needed!
var jwtKeyManager_2 = require("../services/jwtKeyManager");
Object.defineProperty(exports, "User", { enumerable: true, get: function () { return jwtKeyManager_2.User; } });
function generateToken(user) {
return jwtKeyManager_1.default.generateToken(user);
}
function verifyToken(token) {
return jwtKeyManager_1.default.verifyToken(token);
}
// Simple Google OAuth2 client using fetch
async function verifyGoogleToken(googleToken) {
try {
const response = await fetch(`https://www.googleapis.com/oauth2/v1/userinfo?access_token=${googleToken}`);
if (!response.ok) {
throw new Error('Invalid Google token');
}
return await response.json();
}
catch (error) {
console.error('Error verifying Google token:', error);
return null;
}
}
// Get Google OAuth2 URL
function getGoogleAuthUrl() {
const clientId = process.env.GOOGLE_CLIENT_ID;
const redirectUri = process.env.GOOGLE_REDIRECT_URI || 'http://localhost:3000/auth/google/callback';
console.log('🔗 Generating Google OAuth URL:', {
client_id_present: !!clientId,
redirect_uri: redirectUri,
environment: process.env.NODE_ENV || 'development'
});
if (!clientId) {
console.error('❌ GOOGLE_CLIENT_ID not configured');
throw new Error('GOOGLE_CLIENT_ID not configured');
}
if (!redirectUri.startsWith('http')) {
console.error('❌ Invalid redirect URI:', redirectUri);
throw new Error('GOOGLE_REDIRECT_URI must be a valid HTTP/HTTPS URL');
}
const params = new URLSearchParams({
client_id: clientId,
redirect_uri: redirectUri,
response_type: 'code',
scope: 'openid email profile',
access_type: 'offline',
prompt: 'consent'
});
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
console.log('✅ Google OAuth URL generated successfully');
return authUrl;
}
// Exchange authorization code for tokens
async function exchangeCodeForTokens(code) {
const clientId = process.env.GOOGLE_CLIENT_ID;
const clientSecret = process.env.GOOGLE_CLIENT_SECRET;
const redirectUri = process.env.GOOGLE_REDIRECT_URI || 'http://localhost:3000/auth/google/callback';
console.log('🔄 Exchanging OAuth code for tokens:', {
client_id_present: !!clientId,
client_secret_present: !!clientSecret,
redirect_uri: redirectUri,
code_length: code?.length || 0
});
if (!clientId || !clientSecret) {
console.error('❌ Google OAuth credentials not configured:', {
client_id: !!clientId,
client_secret: !!clientSecret
});
throw new Error('Google OAuth credentials not configured');
}
if (!code || code.length < 10) {
console.error('❌ Invalid authorization code:', { code_length: code?.length || 0 });
throw new Error('Invalid authorization code provided');
}
try {
const tokenUrl = 'https://oauth2.googleapis.com/token';
const requestBody = new URLSearchParams({
client_id: clientId,
client_secret: clientSecret,
code,
grant_type: 'authorization_code',
redirect_uri: redirectUri,
});
console.log('📡 Making token exchange request to Google:', {
url: tokenUrl,
redirect_uri: redirectUri,
grant_type: 'authorization_code'
});
const response = await fetch(tokenUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
},
body: requestBody,
});
const responseText = await response.text();
console.log('📨 Token exchange response:', {
status: response.status,
ok: response.ok,
content_type: response.headers.get('content-type'),
response_length: responseText.length
});
if (!response.ok) {
console.error('❌ Token exchange failed:', {
status: response.status,
statusText: response.statusText,
response: responseText
});
throw new Error(`Failed to exchange code for tokens: ${response.status} ${response.statusText}`);
}
let tokenData;
try {
tokenData = JSON.parse(responseText);
}
catch (parseError) {
console.error('❌ Failed to parse token response:', { response: responseText });
throw new Error('Invalid JSON response from Google token endpoint');
}
if (!tokenData.access_token) {
console.error('❌ No access token in response:', tokenData);
throw new Error('No access token received from Google');
}
console.log('✅ Token exchange successful:', {
has_access_token: !!tokenData.access_token,
has_refresh_token: !!tokenData.refresh_token,
token_type: tokenData.token_type,
expires_in: tokenData.expires_in
});
return tokenData;
}
catch (error) {
console.error('❌ Error exchanging code for tokens:', {
error: error instanceof Error ? error.message : 'Unknown error',
stack: error instanceof Error ? error.stack : undefined
});
throw error;
}
}
// Get user info from Google
async function getGoogleUserInfo(accessToken) {
console.log('👤 Getting user info from Google:', {
token_length: accessToken?.length || 0,
token_prefix: accessToken ? accessToken.substring(0, 10) + '...' : 'none'
});
if (!accessToken || accessToken.length < 10) {
console.error('❌ Invalid access token for user info request');
throw new Error('Invalid access token provided');
}
try {
const userInfoUrl = `https://www.googleapis.com/oauth2/v2/userinfo?access_token=${accessToken}`;
console.log('📡 Making user info request to Google');
const response = await fetch(userInfoUrl, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${accessToken}`
}
});
const responseText = await response.text();
console.log('📨 User info response:', {
status: response.status,
ok: response.ok,
content_type: response.headers.get('content-type'),
response_length: responseText.length
});
if (!response.ok) {
console.error('❌ Failed to get user info:', {
status: response.status,
statusText: response.statusText,
response: responseText
});
throw new Error(`Failed to get user info: ${response.status} ${response.statusText}`);
}
let userData;
try {
userData = JSON.parse(responseText);
}
catch (parseError) {
console.error('❌ Failed to parse user info response:', { response: responseText });
throw new Error('Invalid JSON response from Google user info endpoint');
}
if (!userData.email) {
console.error('❌ No email in user info response:', userData);
throw new Error('No email address received from Google');
}
console.log('✅ User info retrieved successfully:', {
email: userData.email,
name: userData.name,
verified_email: userData.verified_email,
has_picture: !!userData.picture
});
return userData;
}
catch (error) {
console.error('❌ Error getting Google user info:', {
error: error instanceof Error ? error.message : 'Unknown error',
stack: error instanceof Error ? error.stack : undefined
});
throw error;
}
}
//# sourceMappingURL=simpleAuth.js.map