UNPKG

@ordojs/core

Version:

Core compiler and runtime for OrdoJS framework

649 lines 20.7 kB
/** * @fileoverview OrdoJS Authentication and Authorization Manager */ /** * Default authentication configuration */ const DEFAULT_AUTH_CONFIG = { jwtSecret: process.env.JWT_SECRET || 'your-secret-key-change-in-production', jwtExpiration: 3600, // 1 hour sessionExpiration: 86400, // 24 hours enableRefreshTokens: true, refreshTokenExpiration: 604800, // 7 days passwordHashRounds: 12, maxLoginAttempts: 5, lockoutDuration: 900, // 15 minutes enableOAuth: false, oauthProviders: [], enableMFA: false, mfaProvider: 'totp' }; /** * Comprehensive authentication and authorization manager */ export class AuthManager { config; sessions = new Map(); users = new Map(); roles = new Map(); permissions = new Map(); loginAttempts = new Map(); refreshTokens = new Map(); constructor(config = {}) { this.config = { ...DEFAULT_AUTH_CONFIG, ...config }; this.initializeDefaultRoles(); } /** * Register a new user */ async registerUser(userData) { try { // Validate input if (!userData.username || !userData.email || !userData.password) { return { success: false, error: 'Username, email, and password are required' }; } // Check if user already exists const existingUser = Array.from(this.users.values()).find(u => u.username === userData.username || u.email === userData.email); if (existingUser) { return { success: false, error: 'User already exists' }; } // Hash password const hashedPassword = await this.hashPassword(userData.password); // Create user const user = { id: this.generateUserId(), username: userData.username, email: userData.email, displayName: userData.displayName, roles: userData.roles || ['user'], permissions: [], status: 'active', createdAt: new Date(), mfaEnabled: false }; // Add user permissions based on roles user.permissions = this.getPermissionsForRoles(user.roles); // Store user this.users.set(user.id, user); return { success: true, user }; } catch (error) { return { success: false, error: `Registration failed: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Authenticate user */ async authenticateUser(credentials) { try { // Check for account lockout const lockoutKey = `lockout:${credentials.username}`; const lockoutInfo = this.loginAttempts.get(lockoutKey); if (lockoutInfo && lockoutInfo.count >= this.config.maxLoginAttempts) { const timeSinceLastAttempt = Date.now() - lockoutInfo.lastAttempt.getTime(); if (timeSinceLastAttempt < this.config.lockoutDuration * 1000) { return { success: false, error: 'Account temporarily locked due to too many failed attempts' }; } else { // Reset lockout this.loginAttempts.delete(lockoutKey); } } // Find user const user = Array.from(this.users.values()).find(u => u.username === credentials.username || u.email === credentials.username); if (!user) { this.recordFailedLogin(credentials.username); return { success: false, error: 'Invalid credentials' }; } // Check if account is active if (user.status !== 'active') { return { success: false, error: `Account is ${user.status}` }; } // Verify password (in a real implementation, you'd compare with hashed password) const passwordValid = await this.verifyPassword(credentials.password, 'hashed-password'); if (!passwordValid) { this.recordFailedLogin(credentials.username); return { success: false, error: 'Invalid credentials' }; } // Check if MFA is required if (user.mfaEnabled) { const mfaToken = this.generateMFAToken(user.id); return { success: false, requiresMFA: true, mfaToken }; } // Create session const session = await this.createSession(user, credentials.ipAddress, credentials.userAgent); // Update last login user.lastLoginAt = new Date(); this.users.set(user.id, user); return { success: true, user, session }; } catch (error) { return { success: false, error: `Authentication failed: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Verify MFA token */ async verifyMFAToken(userId, mfaToken, mfaCode) { try { const user = this.users.get(userId); if (!user) { return { success: false, error: 'User not found' }; } // Verify MFA code (in a real implementation, you'd verify against the actual MFA system) const mfaValid = this.verifyMFACode(mfaToken, mfaCode); if (!mfaValid) { return { success: false, error: 'Invalid MFA code' }; } // Create session const session = await this.createSession(user, 'unknown', 'unknown'); return { success: true, user, session }; } catch (error) { return { success: false, error: `MFA verification failed: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Validate session */ validateSession(sessionId) { const session = this.sessions.get(sessionId); if (!session) { return { success: false, error: 'Session not found' }; } if (new Date() > session.expiresAt) { this.sessions.delete(sessionId); return { success: false, error: 'Session expired' }; } const user = this.users.get(session.userId); if (!user) { this.sessions.delete(sessionId); return { success: false, error: 'User not found' }; } // Update last activity session.lastActivity = new Date(); this.sessions.set(sessionId, session); return { success: true, user, session }; } /** * Refresh session */ async refreshSession(refreshToken) { const tokenInfo = this.refreshTokens.get(refreshToken); if (!tokenInfo) { return { success: false, error: 'Invalid refresh token' }; } if (new Date() > tokenInfo.expiresAt) { this.refreshTokens.delete(refreshToken); return { success: false, error: 'Refresh token expired' }; } const user = this.users.get(tokenInfo.userId); if (!user) { this.refreshTokens.delete(refreshToken); return { success: false, error: 'User not found' }; } // Create new session const session = await this.createSession(user, 'unknown', 'unknown'); // Remove old refresh token this.refreshTokens.delete(refreshToken); return { success: true, user, session }; } /** * Logout user */ logoutUser(sessionId) { const session = this.sessions.get(sessionId); if (!session) { return false; } // Remove session this.sessions.delete(sessionId); // Remove refresh token if exists if (session.refreshToken) { this.refreshTokens.delete(session.refreshToken); } return true; } /** * Authorize user for action */ authorizeUser(userId, resource, action, context) { const user = this.users.get(userId); if (!user) { return { allowed: false, requiredPermissions: [`${resource}:${action}`], userPermissions: [], missingPermissions: [`${resource}:${action}`], error: 'User not found' }; } const requiredPermission = `${resource}:${action}`; const userPermissions = this.getUserPermissions(userId); const hasPermission = userPermissions.includes(requiredPermission); return { allowed: hasPermission, requiredPermissions: [requiredPermission], userPermissions, missingPermissions: hasPermission ? [] : [requiredPermission], error: hasPermission ? undefined : 'Insufficient permissions' }; } /** * Check if user has role */ hasRole(userId, roleName) { const user = this.users.get(userId); if (!user) { return false; } return user.roles.includes(roleName); } /** * Check if user has permission */ hasPermission(userId, permission) { const userPermissions = this.getUserPermissions(userId); return userPermissions.includes(permission); } /** * Add role to user */ addRoleToUser(userId, roleName) { const user = this.users.get(userId); if (!user) { return false; } if (!user.roles.includes(roleName)) { user.roles.push(roleName); user.permissions = this.getPermissionsForRoles(user.roles); this.users.set(userId, user); } return true; } /** * Remove role from user */ removeRoleFromUser(userId, roleName) { const user = this.users.get(userId); if (!user) { return false; } const index = user.roles.indexOf(roleName); if (index > -1) { user.roles.splice(index, 1); user.permissions = this.getPermissionsForRoles(user.roles); this.users.set(userId, user); } return true; } /** * Create role */ createRole(roleData) { if (this.roles.has(roleData.name)) { return false; } const role = { name: roleData.name, description: roleData.description, permissions: roleData.permissions, parentRoles: roleData.parentRoles || [], isSystemRole: false }; this.roles.set(roleData.name, role); return true; } /** * Create permission */ createPermission(permissionData) { if (this.permissions.has(permissionData.name)) { return false; } const permission = { name: permissionData.name, description: permissionData.description, resource: permissionData.resource, action: permissionData.action, conditions: permissionData.conditions }; this.permissions.set(permissionData.name, permission); return true; } /** * Get all users */ getUsers() { return Array.from(this.users.values()); } /** * Get all roles */ getRoles() { return Array.from(this.roles.values()); } /** * Get all permissions */ getPermissions() { return Array.from(this.permissions.values()); } /** * Get active sessions */ getActiveSessions() { const now = new Date(); return Array.from(this.sessions.values()).filter(session => session.expiresAt > now); } /** * Initialize default roles */ initializeDefaultRoles() { // Create default roles this.createRole({ name: 'admin', description: 'Administrator with full access', permissions: ['*:*'] }); this.createRole({ name: 'user', description: 'Standard user', permissions: ['profile:read', 'profile:update'] }); this.createRole({ name: 'moderator', description: 'Content moderator', permissions: ['content:read', 'content:update', 'content:delete', 'user:read'] }); // Create default permissions this.createPermission({ name: 'profile:read', description: 'Read own profile', resource: 'profile', action: 'read' }); this.createPermission({ name: 'profile:update', description: 'Update own profile', resource: 'profile', action: 'update' }); this.createPermission({ name: 'content:read', description: 'Read content', resource: 'content', action: 'read' }); this.createPermission({ name: 'content:create', description: 'Create content', resource: 'content', action: 'create' }); this.createPermission({ name: 'content:update', description: 'Update content', resource: 'content', action: 'update' }); this.createPermission({ name: 'content:delete', description: 'Delete content', resource: 'content', action: 'delete' }); this.createPermission({ name: 'user:read', description: 'Read user information', resource: 'user', action: 'read' }); this.createPermission({ name: 'user:create', description: 'Create users', resource: 'user', action: 'create' }); this.createPermission({ name: 'user:update', description: 'Update users', resource: 'user', action: 'update' }); this.createPermission({ name: 'user:delete', description: 'Delete users', resource: 'user', action: 'delete' }); } /** * Hash password */ async hashPassword(password) { // In a real implementation, you'd use bcrypt or similar // This is a simplified version for demonstration const encoder = new TextEncoder(); const data = encoder.encode(password + this.config.jwtSecret); const hashBuffer = await crypto.subtle.digest('SHA-256', data); const hashArray = Array.from(new Uint8Array(hashBuffer)); return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); } /** * Verify password */ async verifyPassword(password, hashedPassword) { const hashed = await this.hashPassword(password); return hashed === hashedPassword; } /** * Create session */ async createSession(user, ipAddress, userAgent) { const sessionId = this.generateSessionId(); const token = this.generateJWT(user); const refreshToken = this.config.enableRefreshTokens ? this.generateRefreshToken() : undefined; const session = { id: sessionId, userId: user.id, token, refreshToken, expiresAt: new Date(Date.now() + this.config.sessionExpiration * 1000), ipAddress, userAgent, createdAt: new Date(), lastActivity: new Date() }; this.sessions.set(sessionId, session); if (refreshToken) { this.refreshTokens.set(refreshToken, { userId: user.id, expiresAt: new Date(Date.now() + this.config.refreshTokenExpiration * 1000) }); } return session; } /** * Generate user ID */ generateUserId() { return `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Generate session ID */ generateSessionId() { return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Generate JWT token */ generateJWT(user) { const header = { alg: 'HS256', typ: 'JWT' }; const payload = { sub: user.id, username: user.username, email: user.email, roles: user.roles, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + this.config.jwtExpiration }; // In a real implementation, you'd use a proper JWT library const headerB64 = btoa(JSON.stringify(header)); const payloadB64 = btoa(JSON.stringify(payload)); const signature = this.generateSignature(`${headerB64}.${payloadB64}`); return `${headerB64}.${payloadB64}.${signature}`; } /** * Generate signature */ generateSignature(data) { // In a real implementation, you'd use HMAC-SHA256 const encoder = new TextEncoder(); const keyData = encoder.encode(this.config.jwtSecret); const messageData = encoder.encode(data); // Simplified signature generation return btoa(data + this.config.jwtSecret).replace(/[^a-zA-Z0-9]/g, '').substr(0, 32); } /** * Generate refresh token */ generateRefreshToken() { const array = new Uint8Array(32); crypto.getRandomValues(array); return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''); } /** * Generate MFA token */ generateMFAToken(userId) { return `mfa_${userId}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Verify MFA code */ verifyMFACode(mfaToken, mfaCode) { // In a real implementation, you'd verify against TOTP, SMS, or email // This is a simplified version return mfaCode === '123456'; // Demo code } /** * Record failed login attempt */ recordFailedLogin(username) { const lockoutKey = `lockout:${username}`; const current = this.loginAttempts.get(lockoutKey) || { count: 0, lastAttempt: new Date() }; current.count++; current.lastAttempt = new Date(); this.loginAttempts.set(lockoutKey, current); } /** * Get user permissions */ getUserPermissions(userId) { const user = this.users.get(userId); if (!user) { return []; } return this.getPermissionsForRoles(user.roles); } /** * Get permissions for roles */ getPermissionsForRoles(roles) { const permissions = new Set(); for (const roleName of roles) { const role = this.roles.get(roleName); if (role) { for (const permission of role.permissions) { if (permission === '*:*') { // Wildcard permission - add all permissions for (const perm of this.permissions.values()) { permissions.add(`${perm.resource}:${perm.action}`); } } else { permissions.add(permission); } } } } return Array.from(permissions); } } //# sourceMappingURL=auth-manager.js.map