UNPKG

recoder-code

Version:

🚀 AI-powered development platform - Chat with 32+ models, build projects, automate workflows. Free models included!

385 lines • 15.7 kB
"use strict"; /** * AuthService * Handles user authentication, session management, and API key operations */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthService = void 0; const bcrypt = __importStar(require("bcrypt")); const jwt = __importStar(require("jsonwebtoken")); const crypto = __importStar(require("crypto")); const User_1 = require("../entities/User"); const ApiKey_1 = require("../entities/ApiKey"); const database_1 = require("../database"); class AuthService { constructor(config) { this.tokenExpiry = '24h'; this.saltRounds = 12; this.logger = { log: (message) => console.log(`[AuthService] ${message}`), warn: (message, error) => console.warn(`[AuthService] ${message}`, error), error: (message, error) => console.error(`[AuthService] ${message}`, error), debug: (message) => console.debug(`[AuthService] ${message}`) }; this.userRepository = database_1.AppDataSource.getRepository(User_1.User); this.apiKeyRepository = database_1.AppDataSource.getRepository(ApiKey_1.ApiKey); this.jwtSecret = config?.jwt?.secret || 'fallback-secret-key'; } async authenticateUser(usernameOrEmail, password) { try { console.log(`Authenticating user: ${usernameOrEmail}`); // Find user by username or email const user = await this.userRepository .createQueryBuilder('user') .where('user.username = :identifier OR user.email = :identifier', { identifier: usernameOrEmail }) .getOne(); if (!user) { console.warn(`Authentication failed: user not found - ${usernameOrEmail}`); return { success: false, error: 'Invalid credentials' }; } // Check if user is active if (user.status !== User_1.UserStatus.ACTIVE) { console.warn(`Authentication failed: user not active - ${usernameOrEmail}`); return { success: false, error: 'Account is not active' }; } // Verify password const isPasswordValid = await bcrypt.compare(password, user.password_hash); if (!isPasswordValid) { console.warn(`Authentication failed: invalid password - ${usernameOrEmail}`); return { success: false, error: 'Invalid credentials' }; } console.log(`User authenticated successfully: ${user.username}`); return { success: true, user }; } catch (error) { console.error(`Authentication error: ${error.message}`); return { success: false, error: 'Authentication failed' }; } } generateToken(payload) { return jwt.sign(payload, this.jwtSecret, { expiresIn: this.tokenExpiry }); } async validateToken(token) { try { // Check if it's a JWT session token if (token.startsWith('ey')) { return await this.validateJwtToken(token); } // Otherwise, treat as API key return await this.validateApiKey(token); } catch (error) { this.logger.error(`Token validation error: ${error instanceof Error ? error.message : String(error)}`); return { valid: false, error: 'Invalid token' }; } } async validateJwtToken(token) { try { const decoded = jwt.verify(token, this.jwtSecret); const user = await this.userRepository.findOne({ where: { id: decoded.userId } }); if (!user || user.status !== User_1.UserStatus.ACTIVE) { return { valid: false, error: 'User not found or inactive' }; } return { valid: true, user }; } catch (error) { return { valid: false, error: 'Invalid JWT token' }; } } async validateApiKey(key) { try { const keyHash = ApiKey_1.ApiKey.hashKey(key); const apiKey = await this.apiKeyRepository.findOne({ where: { key_hash: keyHash }, relations: ['user'] }); if (!apiKey) { return { valid: false, error: 'API key not found' }; } if (!apiKey.isActive()) { return { valid: false, error: 'API key is not active' }; } if (!apiKey.user || apiKey.user.status !== User_1.UserStatus.ACTIVE) { return { valid: false, error: 'Associated user is not active' }; } // Record usage apiKey.recordUsage(); await this.apiKeyRepository.save(apiKey); return { valid: true, user: apiKey.user, apiKey }; } catch (error) { return { valid: false, error: 'API key validation failed' }; } } async generateSessionToken(userId) { const payload = { userId, type: 'session', iat: Math.floor(Date.now() / 1000) }; const token = jwt.sign(payload, this.jwtSecret, { expiresIn: this.tokenExpiry }); // Calculate expiry date const expiresAt = new Date(); expiresAt.setHours(expiresAt.getHours() + 24); return { token, expiresAt }; } async createApiKey(userId, options) { try { const user = await this.userRepository.findOne({ where: { id: userId } }); if (!user) { return { success: false, error: 'User not found' }; } // Check if user can create API keys with these scopes if (options.scopes.includes(ApiKey_1.ApiKeyScope.ADMIN) && !user.is_admin) { return { success: false, error: 'Insufficient permissions for admin scope' }; } // Generate API key const { key, hash, prefix } = ApiKey_1.ApiKey.generateKey(); // Create API key entity const apiKey = this.apiKeyRepository.create({ name: options.name, key_hash: hash, key_prefix: prefix, scopes: options.scopes, description: options.description, expires_at: options.expires_at, restrictions: options.restrictions, user_id: userId }); await this.apiKeyRepository.save(apiKey); this.logger.log(`API key created: ${options.name} for user ${user.username}`); return { success: true, key, apiKey }; } catch (error) { if (error instanceof Error) { this.logger.error(`Failed to create API key: ${error.message}`, error.stack); } else { this.logger.error(`Failed to create API key: ${String(error)}`); } return { success: false, error: 'Failed to create API key' }; } } async getUserApiKeys(userId) { return this.apiKeyRepository.find({ where: { user_id: userId }, order: { created_at: 'DESC' } }); } async revokeApiKey(userId, keyId) { try { const apiKey = await this.apiKeyRepository.findOne({ where: { id: keyId, user_id: userId } }); if (!apiKey) { return { success: false, error: 'API key not found' }; } apiKey.revoke('Revoked by user'); await this.apiKeyRepository.save(apiKey); this.logger.log(`API key revoked: ${keyId}`); return { success: true }; } catch (error) { this.logger.error(`Failed to revoke API key: ${error instanceof Error ? error.message : String(error)}`); return { success: false, error: 'Failed to revoke API key' }; } } async revokeToken(token) { try { // For JWT tokens, we would typically add to a blacklist // For API keys, we can revoke them directly if (!token.startsWith('ey')) { const keyHash = ApiKey_1.ApiKey.hashKey(token); const apiKey = await this.apiKeyRepository.findOne({ where: { key_hash: keyHash } }); if (apiKey) { apiKey.revoke('Manually revoked'); await this.apiKeyRepository.save(apiKey); } } } catch (error) { this.logger.warn(`Failed to revoke token: ${error instanceof Error ? error.message : String(error)}`); } } async revokeAllTokens(userId, exceptKeyId) { try { const query = this.apiKeyRepository .createQueryBuilder() .update(ApiKey_1.ApiKey) .set({ status: ApiKey_1.ApiKeyStatus.REVOKED, revoked_at: new Date(), revocation_reason: 'Security revocation' }) .where('user_id = :userId', { userId }) .andWhere('status = :status', { status: ApiKey_1.ApiKeyStatus.ACTIVE }); if (exceptKeyId) { query.andWhere('id != :exceptKeyId', { exceptKeyId }); } await query.execute(); this.logger.log(`All tokens revoked for user: ${userId}`); } catch (error) { this.logger.error(`Failed to revoke all tokens: ${error instanceof Error ? error.message : String(error)}`); } } async verifyPassword(userId, password) { try { const user = await this.userRepository.findOne({ where: { id: userId } }); if (!user) { return false; } return bcrypt.compare(password, user.password_hash); } catch (error) { this.logger.error(`Password verification failed: ${error instanceof Error ? error.message : String(error)}`); return false; } } async changePassword(userId, newPassword) { try { const user = await this.userRepository.findOne({ where: { id: userId } }); if (!user) { return { success: false, error: 'User not found' }; } const hashedPassword = await this.hashPassword(newPassword); user.password_hash = hashedPassword; user.updated_at = new Date(); await this.userRepository.save(user); this.logger.log(`Password changed for user: ${user.username}`); return { success: true }; } catch (error) { if (error instanceof Error) { this.logger.error(`Failed to change password: ${error.message}`); } else { this.logger.error(`Failed to change password: ${String(error)}`); } return { success: false, error: 'Failed to change password' }; } } async hashPassword(password) { return bcrypt.hash(password, this.saltRounds); } async generatePasswordResetToken() { return crypto.randomBytes(32).toString('hex'); } async generateVerificationToken() { return crypto.randomBytes(32).toString('hex'); } // Rate limiting helpers async checkAuthRateLimit(identifier) { // This would integrate with a rate limiting service // For now, return true (allow) return true; } async recordFailedAttempt(identifier) { // Record failed authentication attempt for monitoring this.logger.warn(`Failed authentication attempt from: ${identifier}`); } // Session management async createSession(userId, userAgent, ip) { const sessionId = crypto.randomUUID(); // In a real implementation, store session in Redis or database const sessionData = { id: sessionId, userId, userAgent, ip, createdAt: new Date(), lastAccessedAt: new Date() }; // Store session (would use Redis in production) this.logger.debug(`Session created: ${sessionId} for user ${userId}`); return sessionId; } async validateSession(sessionId) { // Validate session from storage // For now, return invalid since we're not implementing session storage return { valid: false }; } async destroySession(sessionId) { // Remove session from storage this.logger.debug(`Session destroyed: ${sessionId}`); } // Two-factor authentication helpers async generateTotpSecret(userId) { // Generate TOTP secret for 2FA const secret = crypto.randomBytes(20).toString('hex'); // Store secret associated with user // Implementation would depend on requirements return secret; } async verifyTotpToken(userId, token) { // Verify TOTP token // This would use a library like 'otplib' return false; // Not implemented } // Admin functions async suspendUser(userId, reason) { try { const user = await this.userRepository.findOne({ where: { id: userId } }); if (!user) { return { success: false, error: 'User not found' }; } user.suspend(reason); await this.userRepository.save(user); // Revoke all tokens await this.revokeAllTokens(userId); this.logger.log(`User suspended: ${user.username} - ${reason}`); return { success: true }; } catch (error) { this.logger.error(`Failed to suspend user: ${error instanceof Error ? error.message : String(error)}`); return { success: false, error: 'Failed to suspend user' }; } } async unsuspendUser(userId) { try { const user = await this.userRepository.findOne({ where: { id: userId } }); if (!user) { return { success: false, error: 'User not found' }; } user.unsuspend(); await this.userRepository.save(user); this.logger.log(`User unsuspended: ${user.username}`); return { success: true }; } catch (error) { this.logger.error(`Failed to unsuspend user: ${error instanceof Error ? error.message : String(error)}`); return { success: false, error: 'Failed to unsuspend user' }; } } } exports.AuthService = AuthService; //# sourceMappingURL=AuthService.js.map