recoder-code
Version:
🚀 AI-powered development platform - Chat with 32+ models, build projects, automate workflows. Free models included!
385 lines • 15.7 kB
JavaScript
"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