Files
vip-coordinator/backend/src/services/authService.ts
kyle dc4655cef4 Backup: 2025-06-07 19:48 - Script test
[Restore from backup: vip-coordinator-backup-2025-06-07-19-48-script-test]
2026-01-24 09:33:58 +01:00

197 lines
5.8 KiB
TypeScript

const jwt = require('jsonwebtoken');
import { Request, Response, NextFunction } from 'express';
import { OAuth2Client } from 'google-auth-library';
import dataService from './unifiedDataService';
// Simplified authentication service - removes excessive logging and complexity
class AuthService {
private jwtSecret: string;
private jwtExpiry: string = '24h';
private googleClient: OAuth2Client;
constructor() {
// 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 OAuth2Client(process.env.GOOGLE_CLIENT_ID);
}
// Generate JWT token
generateToken(user: any): string {
const payload = { id: user.id, email: user.email, role: user.role };
return jwt.sign(payload, this.jwtSecret, { expiresIn: this.jwtExpiry }) as string;
}
// Verify Google ID token from frontend
async verifyGoogleToken(credential: string): Promise<{ user: any; token: string }> {
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 dataService.getUserByEmail(payload.email);
if (!user) {
// Auto-create user with coordinator role
user = await dataService.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: string): any {
try {
return jwt.verify(token, this.jwtSecret);
} catch (error) {
return null;
}
}
// Middleware to check authentication
requireAuth = async (req: Request & { user?: any }, res: Response, next: NextFunction) => {
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 dataService.getUserById(decoded.id);
if (!user) {
return res.status(401).json({ error: 'User not found' });
}
req.user = user;
next();
};
// Middleware to check role
requireRole = (roles: string[]) => {
return (req: Request & { user?: any }, res: Response, next: NextFunction) => {
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();
};
};
// Google OAuth helpers
getGoogleAuthUrl(): string {
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: string): Promise<any> {
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: string): Promise<any> {
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: string): Promise<{ user: any; token: string }> {
// 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 dataService.getUserByEmail(googleUser.email);
if (!user) {
// Auto-create user with coordinator role
user = await dataService.createUser({
email: googleUser.email,
name: googleUser.name,
role: 'coordinator',
googleId: googleUser.id
});
}
// Generate JWT
const token = this.generateToken(user);
return { user, token };
}
}
export default new AuthService();