@ordojs/core
Version:
Core compiler and runtime for OrdoJS framework
649 lines • 20.7 kB
JavaScript
/**
* @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