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>
217 lines
8.6 KiB
JavaScript
217 lines
8.6 KiB
JavaScript
"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
|