UNPKG

realtimecursor

Version:

Real-time collaboration system with cursor tracking and approval workflow

309 lines (266 loc) 6.91 kB
/** * Authentication Service for RealtimeCursor * Handles user authentication, registration, and authorization */ const crypto = require('crypto'); const jwt = require('jsonwebtoken'); // Secret key for JWT signing const JWT_SECRET = process.env.JWT_SECRET || 'realtimecursor-secret-key-for-development'; class AuthService { constructor() { this.users = new Map(); this.projects = new Map(); this.invitations = new Map(); this.comments = new Map(); this.stagedChanges = new Map(); // Create default admin user this.createDefaultAdmin(); } /** * Create a default admin user */ createDefaultAdmin() { const adminId = 'admin-' + crypto.randomBytes(8).toString('hex'); this.users.set(adminId, { id: adminId, email: 'admin@example.com', password: this.hashPassword('Admin123!'), firstName: 'Admin', lastName: 'User', role: 'admin', email_confirmed: true, created_at: new Date().toISOString(), last_sign_in_at: null, user_metadata: { firstName: 'Admin', lastName: 'User', fullName: 'Admin User' } }); } /** * Hash a password */ hashPassword(password) { return crypto.createHash('sha256').update(password).digest('hex'); } /** * Generate a JWT token */ generateToken(user) { return jwt.sign( { sub: user.id, email: user.email, role: user.role || 'user' }, JWT_SECRET, { expiresIn: '7d' } ); } /** * Register a new user */ async register(userData) { const { email, password, firstName, lastName } = userData; // Check if email already exists for (const [id, user] of this.users.entries()) { if (user.email === email) { throw new Error('Email already registered'); } } const userId = 'user-' + crypto.randomBytes(8).toString('hex'); const now = new Date().toISOString(); const newUser = { id: userId, email, password: this.hashPassword(password), firstName, lastName, role: 'user', email_confirmed: true, created_at: now, last_sign_in_at: now, user_metadata: { firstName, lastName, fullName: `${firstName} ${lastName}` } }; this.users.set(userId, newUser); const token = this.generateToken(newUser); return { user: { id: userId, email, firstName, lastName, role: 'user' }, token }; } /** * Login a user */ async login(email, password) { let foundUser = null; // Find user by email for (const [id, user] of this.users.entries()) { if (user.email === email) { foundUser = user; break; } } if (!foundUser) { throw new Error('Invalid email or password'); } // Check password if (foundUser.password !== this.hashPassword(password)) { throw new Error('Invalid email or password'); } // Update last login foundUser.last_sign_in_at = new Date().toISOString(); const token = this.generateToken(foundUser); return { user: { id: foundUser.id, email: foundUser.email, firstName: foundUser.user_metadata?.firstName, lastName: foundUser.user_metadata?.lastName, role: foundUser.role || 'user' }, token }; } /** * Get user by ID */ async getUserById(userId) { return this.users.get(userId); } /** * Update user */ async updateUser(userId, userData) { const user = this.users.get(userId); if (!user) { throw new Error('User not found'); } const { firstName, lastName, email } = userData; if (email && email !== user.email) { // Check if email already exists for (const [id, u] of this.users.entries()) { if (u.email === email && id !== userId) { throw new Error('Email already in use'); } } user.email = email; } if (firstName) { user.user_metadata.firstName = firstName; } if (lastName) { user.user_metadata.lastName = lastName; } if (firstName || lastName) { user.user_metadata.fullName = `${user.user_metadata.firstName} ${user.user_metadata.lastName}`; } return { user: { id: user.id, email: user.email, firstName: user.user_metadata.firstName, lastName: user.user_metadata.lastName, fullName: user.user_metadata.fullName, role: user.role } }; } /** * Authenticate token middleware */ authenticateToken(req, res, next) { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; if (!token) { return res.status(401).json({ success: false, message: 'Authentication token required' }); } try { const decoded = jwt.verify(token, JWT_SECRET); req.user = decoded; next(); } catch (error) { return res.status(403).json({ success: false, message: 'Invalid or expired token' }); } } /** * Require admin middleware */ requireAdmin(req, res, next) { this.authenticateToken(req, res, () => { if (req.user.role !== 'admin' && req.user.role !== 'superadmin') { return res.status(403).json({ success: false, message: 'Admin access required' }); } next(); }); } /** * Get all users */ async getAllUsers(filters = {}) { let users = Array.from(this.users.values()).map(user => ({ id: user.id, email: user.email, firstName: user.user_metadata?.firstName, lastName: user.user_metadata?.lastName, fullName: user.user_metadata?.fullName, role: user.role || 'user', emailConfirmed: user.email_confirmed, createdAt: user.created_at, lastLoginAt: user.last_sign_in_at })); // Apply filters if (filters.role) { users = users.filter(user => user.role === filters.role); } if (filters.search) { const search = filters.search.toLowerCase(); users = users.filter(user => user.email.toLowerCase().includes(search) || user.fullName.toLowerCase().includes(search) ); } return { users, total: users.length }; } /** * Check if a token is valid */ isAuthenticated(token) { try { const decoded = jwt.verify(token, JWT_SECRET); return { authenticated: true, user: decoded }; } catch (error) { return { authenticated: false, error: error.message }; } } } module.exports = AuthService;