Eliminate JWT security vulnerability with unified encryption architecture
SECURITY FIX: Replace dangerous JWT_SECRET environment variable with encrypted database storage using hardware-bound KEK protection. Changes: - EncryptionKeyManager: Add JWT secret management with AES-256-GCM encryption - All route files: Eliminate process.env.JWT_SECRET dependencies - Database server: Initialize JWT secret during startup with proper error handling - Testing: Add comprehensive JWT secret management test coverage - API: Add /encryption/regenerate-jwt endpoint for key rotation Technical implementation: - JWT secrets now use same protection as SSH keys (hardware fingerprint binding) - 512-bit JWT secrets generated via crypto.randomBytes(64) - KEK-protected storage prevents cross-device secret migration - No backward compatibility for insecure environment variable approach This eliminates the critical security flaw where JWT tokens could be forged using the default "secret" value, achieving uniform security architecture with no special cases. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -130,7 +130,7 @@ interface JWTPayload {
|
||||
}
|
||||
|
||||
// JWT authentication middleware
|
||||
function authenticateJWT(req: Request, res: Response, next: NextFunction) {
|
||||
async function authenticateJWT(req: Request, res: Response, next: NextFunction) {
|
||||
const authHeader = req.headers["authorization"];
|
||||
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
||||
authLogger.warn("Missing or invalid Authorization header", {
|
||||
@@ -143,8 +143,12 @@ function authenticateJWT(req: Request, res: Response, next: NextFunction) {
|
||||
.json({ error: "Missing or invalid Authorization header" });
|
||||
}
|
||||
const token = authHeader.split(" ")[1];
|
||||
const jwtSecret = process.env.JWT_SECRET || "secret";
|
||||
|
||||
try {
|
||||
const { EncryptionKeyManager } = await import("../../utils/encryption-key-manager.js");
|
||||
const keyManager = EncryptionKeyManager.getInstance();
|
||||
const jwtSecret = await keyManager.getJWTSecret();
|
||||
|
||||
const payload = jwt.verify(token, jwtSecret) as JWTPayload;
|
||||
(req as any).userId = payload.userId;
|
||||
next();
|
||||
@@ -693,7 +697,9 @@ router.get("/oidc/callback", async (req, res) => {
|
||||
|
||||
const userRecord = user[0];
|
||||
|
||||
const jwtSecret = process.env.JWT_SECRET || "secret";
|
||||
const { EncryptionKeyManager } = await import("../../utils/encryption-key-manager.js");
|
||||
const keyManager = EncryptionKeyManager.getInstance();
|
||||
const jwtSecret = await keyManager.getJWTSecret();
|
||||
const token = jwt.sign({ userId: userRecord.id }, jwtSecret, {
|
||||
expiresIn: "50d",
|
||||
});
|
||||
@@ -775,7 +781,9 @@ router.post("/login", async (req, res) => {
|
||||
});
|
||||
return res.status(401).json({ error: "Incorrect password" });
|
||||
}
|
||||
const jwtSecret = process.env.JWT_SECRET || "secret";
|
||||
const { EncryptionKeyManager } = await import("../../utils/encryption-key-manager.js");
|
||||
const keyManager = EncryptionKeyManager.getInstance();
|
||||
const jwtSecret = await keyManager.getJWTSecret();
|
||||
const token = jwt.sign({ userId: userRecord.id }, jwtSecret, {
|
||||
expiresIn: "50d",
|
||||
});
|
||||
@@ -1245,9 +1253,11 @@ router.post("/totp/verify-login", async (req, res) => {
|
||||
return res.status(400).json({ error: "Token and TOTP code are required" });
|
||||
}
|
||||
|
||||
const jwtSecret = process.env.JWT_SECRET || "secret";
|
||||
|
||||
try {
|
||||
const { EncryptionKeyManager } = await import("../../utils/encryption-key-manager.js");
|
||||
const keyManager = EncryptionKeyManager.getInstance();
|
||||
const jwtSecret = await keyManager.getJWTSecret();
|
||||
|
||||
const decoded = jwt.verify(temp_token, jwtSecret) as any;
|
||||
if (!decoded.pending_totp) {
|
||||
return res.status(401).json({ error: "Invalid temporary token" });
|
||||
|
||||
Reference in New Issue
Block a user