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
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:
168
backend-old-20260125/dist/services/authService.js
vendored
Normal file
168
backend-old-20260125/dist/services/authService.js
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const jwt = require('jsonwebtoken');
|
||||
const google_auth_library_1 = require("google-auth-library");
|
||||
const unifiedDataService_1 = __importDefault(require("./unifiedDataService"));
|
||||
// Simplified authentication service - removes excessive logging and complexity
|
||||
class AuthService {
|
||||
constructor() {
|
||||
this.jwtExpiry = '24h';
|
||||
// Middleware to check authentication
|
||||
this.requireAuth = async (req, res, next) => {
|
||||
const token = req.headers.authorization?.replace('Bearer ', '');
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'Authentication required' });
|
||||
}
|
||||
const decoded = this.verifyToken(token);
|
||||
if (!decoded) {
|
||||
return res.status(401).json({ error: 'Invalid or expired token' });
|
||||
}
|
||||
// Get fresh user data
|
||||
const user = await unifiedDataService_1.default.getUserById(decoded.id);
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: 'User not found' });
|
||||
}
|
||||
req.user = user;
|
||||
next();
|
||||
};
|
||||
// Middleware to check role
|
||||
this.requireRole = (roles) => {
|
||||
return (req, res, next) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Authentication required' });
|
||||
}
|
||||
if (!roles.includes(req.user.role)) {
|
||||
return res.status(403).json({ error: 'Insufficient permissions' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
// Auto-generate a secure JWT secret if not provided
|
||||
if (process.env.JWT_SECRET) {
|
||||
this.jwtSecret = process.env.JWT_SECRET;
|
||||
console.log('Using JWT_SECRET from environment');
|
||||
}
|
||||
else {
|
||||
// Generate a cryptographically secure random secret
|
||||
const crypto = require('crypto');
|
||||
this.jwtSecret = crypto.randomBytes(64).toString('hex');
|
||||
console.log('Generated new JWT_SECRET (this will change on restart)');
|
||||
console.log('To persist sessions across restarts, set JWT_SECRET in .env');
|
||||
}
|
||||
// Initialize Google OAuth client
|
||||
this.googleClient = new google_auth_library_1.OAuth2Client(process.env.GOOGLE_CLIENT_ID);
|
||||
}
|
||||
// Generate JWT token
|
||||
generateToken(user) {
|
||||
const payload = { id: user.id, email: user.email, role: user.role };
|
||||
return jwt.sign(payload, this.jwtSecret, { expiresIn: this.jwtExpiry });
|
||||
}
|
||||
// Verify Google ID token from frontend
|
||||
async verifyGoogleToken(credential) {
|
||||
try {
|
||||
// Verify the token with Google
|
||||
const ticket = await this.googleClient.verifyIdToken({
|
||||
idToken: credential,
|
||||
audience: process.env.GOOGLE_CLIENT_ID,
|
||||
});
|
||||
const payload = ticket.getPayload();
|
||||
if (!payload || !payload.email) {
|
||||
throw new Error('Invalid token payload');
|
||||
}
|
||||
// Find or create user
|
||||
let user = await unifiedDataService_1.default.getUserByEmail(payload.email);
|
||||
if (!user) {
|
||||
// Auto-create user with coordinator role
|
||||
user = await unifiedDataService_1.default.createUser({
|
||||
email: payload.email,
|
||||
name: payload.name || payload.email,
|
||||
role: 'coordinator',
|
||||
googleId: payload.sub
|
||||
});
|
||||
}
|
||||
// Generate our JWT
|
||||
const token = this.generateToken(user);
|
||||
return { user, token };
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Token verification error:', error);
|
||||
throw new Error('Failed to verify Google token');
|
||||
}
|
||||
}
|
||||
// Verify JWT token
|
||||
verifyToken(token) {
|
||||
try {
|
||||
return jwt.verify(token, this.jwtSecret);
|
||||
}
|
||||
catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Google OAuth helpers
|
||||
getGoogleAuthUrl() {
|
||||
if (!process.env.GOOGLE_CLIENT_ID || !process.env.GOOGLE_REDIRECT_URI) {
|
||||
throw new Error('Google OAuth not configured. Please set GOOGLE_CLIENT_ID and GOOGLE_REDIRECT_URI in .env file');
|
||||
}
|
||||
const params = new URLSearchParams({
|
||||
client_id: process.env.GOOGLE_CLIENT_ID,
|
||||
redirect_uri: process.env.GOOGLE_REDIRECT_URI,
|
||||
response_type: 'code',
|
||||
scope: 'email profile',
|
||||
access_type: 'offline',
|
||||
prompt: 'consent'
|
||||
});
|
||||
return `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
|
||||
}
|
||||
async exchangeGoogleCode(code) {
|
||||
const response = await fetch('https://oauth2.googleapis.com/token', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
code,
|
||||
client_id: process.env.GOOGLE_CLIENT_ID,
|
||||
client_secret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
redirect_uri: process.env.GOOGLE_REDIRECT_URI,
|
||||
grant_type: 'authorization_code'
|
||||
})
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to exchange authorization code');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
async getGoogleUserInfo(accessToken) {
|
||||
const response = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
|
||||
headers: { Authorization: `Bearer ${accessToken}` }
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to get user info');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
// Simplified login/signup
|
||||
async handleGoogleAuth(code) {
|
||||
// Exchange code for tokens
|
||||
const tokens = await this.exchangeGoogleCode(code);
|
||||
// Get user info
|
||||
const googleUser = await this.getGoogleUserInfo(tokens.access_token);
|
||||
// Find or create user
|
||||
let user = await unifiedDataService_1.default.getUserByEmail(googleUser.email);
|
||||
if (!user) {
|
||||
// Auto-create user with coordinator role
|
||||
user = await unifiedDataService_1.default.createUser({
|
||||
email: googleUser.email,
|
||||
name: googleUser.name,
|
||||
role: 'coordinator',
|
||||
googleId: googleUser.id
|
||||
});
|
||||
}
|
||||
// Generate JWT
|
||||
const token = this.generateToken(user);
|
||||
return { user, token };
|
||||
}
|
||||
}
|
||||
exports.default = new AuthService();
|
||||
//# sourceMappingURL=authService.js.map
|
||||
Reference in New Issue
Block a user