Code cleanup
This commit is contained in:
@@ -23,27 +23,16 @@ interface JWTPayload {
|
||||
exp?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* AuthManager - Simplified authentication manager
|
||||
*
|
||||
* Responsibilities:
|
||||
* - JWT generation and validation
|
||||
* - Authentication middleware
|
||||
* - User login/logout
|
||||
*
|
||||
* No more two-layer sessions - use UserKeyManager directly
|
||||
*/
|
||||
class AuthManager {
|
||||
private static instance: AuthManager;
|
||||
private systemCrypto: SystemCrypto;
|
||||
private userCrypto: UserCrypto;
|
||||
private invalidatedTokens: Set<string> = new Set(); // Track invalidated JWT tokens
|
||||
private invalidatedTokens: Set<string> = new Set();
|
||||
|
||||
private constructor() {
|
||||
this.systemCrypto = SystemCrypto.getInstance();
|
||||
this.userCrypto = UserCrypto.getInstance();
|
||||
|
||||
// Set up callback to invalidate JWT tokens when data sessions expire
|
||||
|
||||
this.userCrypto.setSessionExpiredCallback((userId: string) => {
|
||||
this.invalidateUserTokens(userId);
|
||||
});
|
||||
@@ -56,80 +45,58 @@ class AuthManager {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize authentication system
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
await this.systemCrypto.initializeJWTSecret();
|
||||
databaseLogger.info("AuthManager initialized", {
|
||||
operation: "auth_init"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* User registration
|
||||
*/
|
||||
async registerUser(userId: string, password: string): Promise<void> {
|
||||
await this.userCrypto.setupUserEncryption(userId, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* User login with lazy encryption migration
|
||||
*/
|
||||
async authenticateUser(userId: string, password: string): Promise<boolean> {
|
||||
const authenticated = await this.userCrypto.authenticateUser(userId, password);
|
||||
const authenticated = await this.userCrypto.authenticateUser(
|
||||
userId,
|
||||
password,
|
||||
);
|
||||
|
||||
if (authenticated) {
|
||||
// Trigger lazy encryption migration for user's sensitive fields
|
||||
await this.performLazyEncryptionMigration(userId);
|
||||
}
|
||||
|
||||
return authenticated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform lazy encryption migration for user's sensitive data
|
||||
* This runs asynchronously after successful login
|
||||
*/
|
||||
private async performLazyEncryptionMigration(userId: string): Promise<void> {
|
||||
try {
|
||||
const userDataKey = this.getUserDataKey(userId);
|
||||
if (!userDataKey) {
|
||||
databaseLogger.warn("Cannot perform lazy encryption migration - user data key not available", {
|
||||
operation: "lazy_encryption_migration_no_key",
|
||||
userId,
|
||||
});
|
||||
databaseLogger.warn(
|
||||
"Cannot perform lazy encryption migration - user data key not available",
|
||||
{
|
||||
operation: "lazy_encryption_migration_no_key",
|
||||
userId,
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Import database connection - need to access raw SQLite for migration
|
||||
const { getSqlite, saveMemoryDatabaseToFile } = await import("../database/db/index.js");
|
||||
const { getSqlite, saveMemoryDatabaseToFile } = await import(
|
||||
"../database/db/index.js"
|
||||
);
|
||||
|
||||
// Database should already be initialized by starter.ts, but ensure we can access it
|
||||
const sqlite = getSqlite();
|
||||
|
||||
// Perform the migration
|
||||
const migrationResult = await DataCrypto.migrateUserSensitiveFields(
|
||||
userId,
|
||||
userDataKey,
|
||||
sqlite
|
||||
sqlite,
|
||||
);
|
||||
|
||||
if (migrationResult.migrated) {
|
||||
// Save the in-memory database to disk to persist the migration
|
||||
await saveMemoryDatabaseToFile();
|
||||
|
||||
databaseLogger.success("Lazy encryption migration completed for user", {
|
||||
operation: "lazy_encryption_migration_success",
|
||||
userId,
|
||||
migratedTables: migrationResult.migratedTables,
|
||||
migratedFieldsCount: migrationResult.migratedFieldsCount,
|
||||
});
|
||||
} else {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
// Log error but don't fail the login process
|
||||
databaseLogger.error("Lazy encryption migration failed", error, {
|
||||
operation: "lazy_encryption_migration_error",
|
||||
userId,
|
||||
@@ -138,12 +105,9 @@ class AuthManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate JWT Token
|
||||
*/
|
||||
async generateJWTToken(
|
||||
userId: string,
|
||||
options: { expiresIn?: string; pendingTOTP?: boolean } = {}
|
||||
options: { expiresIn?: string; pendingTOTP?: boolean } = {},
|
||||
): Promise<string> {
|
||||
const jwtSecret = await this.systemCrypto.getJWTSecret();
|
||||
|
||||
@@ -153,21 +117,13 @@ class AuthManager {
|
||||
}
|
||||
|
||||
return jwt.sign(payload, jwtSecret, {
|
||||
expiresIn: options.expiresIn || "24h"
|
||||
expiresIn: options.expiresIn || "24h",
|
||||
} as jwt.SignOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify JWT Token
|
||||
*/
|
||||
async verifyJWTToken(token: string): Promise<JWTPayload | null> {
|
||||
try {
|
||||
// Check if token is in invalidated list
|
||||
if (this.invalidatedTokens.has(token)) {
|
||||
databaseLogger.debug("JWT token is invalidated", {
|
||||
operation: "jwt_verify_invalidated",
|
||||
tokenPrefix: token.substring(0, 20) + "..."
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -177,58 +133,37 @@ class AuthManager {
|
||||
} catch (error) {
|
||||
databaseLogger.warn("JWT verification failed", {
|
||||
operation: "jwt_verify_failed",
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate JWT token (add to blacklist)
|
||||
*/
|
||||
invalidateJWTToken(token: string): void {
|
||||
this.invalidatedTokens.add(token);
|
||||
databaseLogger.info("JWT token invalidated", {
|
||||
operation: "jwt_invalidate",
|
||||
tokenPrefix: token.substring(0, 20) + "..."
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate all JWT tokens for a user (when data locks)
|
||||
*/
|
||||
invalidateUserTokens(userId: string): void {
|
||||
// Note: This is a simplified approach. In a production system, you might want
|
||||
// to track tokens by userId and invalidate them more precisely.
|
||||
// For now, we'll rely on the data lock mechanism to handle this.
|
||||
databaseLogger.info("User tokens invalidated due to data lock", {
|
||||
operation: "user_tokens_invalidate",
|
||||
userId
|
||||
userId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get secure cookie options based on request
|
||||
*/
|
||||
getSecureCookieOptions(req: any, maxAge: number = 24 * 60 * 60 * 1000) {
|
||||
return {
|
||||
httpOnly: true, // Prevent XSS attacks
|
||||
secure: req.secure || req.headers['x-forwarded-proto'] === 'https', // Detect HTTPS properly
|
||||
sameSite: "strict" as const, // Prevent CSRF attacks
|
||||
maxAge: maxAge, // Session duration in milliseconds
|
||||
path: "/", // Available site-wide
|
||||
httpOnly: true,
|
||||
secure: req.secure || req.headers["x-forwarded-proto"] === "https",
|
||||
sameSite: "strict" as const,
|
||||
maxAge: maxAge,
|
||||
path: "/",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication middleware
|
||||
*/
|
||||
createAuthMiddleware() {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
// Try to get JWT from secure HttpOnly cookie first
|
||||
let token = req.cookies?.jwt;
|
||||
|
||||
// Fallback to Authorization header for backward compatibility
|
||||
|
||||
if (!token) {
|
||||
const authHeader = req.headers["authorization"];
|
||||
if (authHeader?.startsWith("Bearer ")) {
|
||||
@@ -252,9 +187,6 @@ class AuthManager {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Data access middleware - requires user to have unlocked data
|
||||
*/
|
||||
createDataAccessMiddleware() {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
const userId = (req as any).userId;
|
||||
@@ -266,7 +198,7 @@ class AuthManager {
|
||||
if (!dataKey) {
|
||||
return res.status(401).json({
|
||||
error: "Session expired - please log in again",
|
||||
code: "SESSION_EXPIRED"
|
||||
code: "SESSION_EXPIRED",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -275,9 +207,6 @@ class AuthManager {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin middleware - requires user to be authenticated and have admin privileges
|
||||
*/
|
||||
createAdminMiddleware() {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
const authHeader = req.headers["authorization"];
|
||||
@@ -292,20 +221,25 @@ class AuthManager {
|
||||
return res.status(401).json({ error: "Invalid token" });
|
||||
}
|
||||
|
||||
// Check if user is admin
|
||||
try {
|
||||
const { db } = await import("../database/db/index.js");
|
||||
const { users } = await import("../database/db/schema.js");
|
||||
const { eq } = await import("drizzle-orm");
|
||||
|
||||
const user = await db.select().from(users).where(eq(users.id, payload.userId));
|
||||
const user = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.id, payload.userId));
|
||||
|
||||
if (!user || user.length === 0 || !user[0].is_admin) {
|
||||
databaseLogger.warn("Non-admin user attempted to access admin endpoint", {
|
||||
operation: "admin_access_denied",
|
||||
userId: payload.userId,
|
||||
endpoint: req.path,
|
||||
});
|
||||
databaseLogger.warn(
|
||||
"Non-admin user attempted to access admin endpoint",
|
||||
{
|
||||
operation: "admin_access_denied",
|
||||
userId: payload.userId,
|
||||
endpoint: req.path,
|
||||
},
|
||||
);
|
||||
return res.status(403).json({ error: "Admin access required" });
|
||||
}
|
||||
|
||||
@@ -317,38 +251,36 @@ class AuthManager {
|
||||
operation: "admin_check_failed",
|
||||
userId: payload.userId,
|
||||
});
|
||||
return res.status(500).json({ error: "Failed to verify admin privileges" });
|
||||
return res
|
||||
.status(500)
|
||||
.json({ error: "Failed to verify admin privileges" });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* User logout
|
||||
*/
|
||||
logoutUser(userId: string): void {
|
||||
this.userCrypto.logoutUser(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user data key
|
||||
*/
|
||||
getUserDataKey(userId: string): Buffer | null {
|
||||
return this.userCrypto.getUserDataKey(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is unlocked
|
||||
*/
|
||||
isUserUnlocked(userId: string): boolean {
|
||||
return this.userCrypto.isUserUnlocked(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change user password
|
||||
*/
|
||||
async changeUserPassword(userId: string, oldPassword: string, newPassword: string): Promise<boolean> {
|
||||
return await this.userCrypto.changeUserPassword(userId, oldPassword, newPassword);
|
||||
async changeUserPassword(
|
||||
userId: string,
|
||||
oldPassword: string,
|
||||
newPassword: string,
|
||||
): Promise<boolean> {
|
||||
return await this.userCrypto.changeUserPassword(
|
||||
userId,
|
||||
oldPassword,
|
||||
newPassword,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { AuthManager, type AuthenticationResult, type JWTPayload };
|
||||
export { AuthManager, type AuthenticationResult, type JWTPayload };
|
||||
|
||||
Reference in New Issue
Block a user