Reduce image size, update feature requset yamls and fix OIDC

This commit is contained in:
LukeGus
2025-09-29 21:21:51 -05:00
parent a09fe0a271
commit 59094ec25d
10 changed files with 250 additions and 73 deletions

View File

@@ -75,12 +75,8 @@ async function verifyOIDCToken(
);
}
} else {
authLogger.error(
`JWKS fetch failed from ${url}: ${response.status} ${response.statusText}`,
);
}
} catch (error) {
authLogger.error(`JWKS fetch error from ${url}:`, error);
continue;
}
}
@@ -117,7 +113,6 @@ async function verifyOIDCToken(
return payload;
} catch (error) {
authLogger.error("OIDC token verification failed:", error);
throw error;
}
}
@@ -655,10 +650,6 @@ router.get("/oidc/callback", async (req, res) => {
config.client_id,
);
} catch (error) {
authLogger.error(
"OIDC token verification failed, trying userinfo endpoints",
error,
);
try {
const parts = tokenData.id_token.split(".");
if (parts.length === 3) {
@@ -767,6 +758,23 @@ router.get("/oidc/callback", async (req, res) => {
scopes: config.scopes,
});
try {
await authManager.registerOIDCUser(id);
} catch (encryptionError) {
await db.delete(users).where(eq(users.id, id));
authLogger.error(
"Failed to setup OIDC user encryption, user creation rolled back",
encryptionError,
{
operation: "oidc_user_create_encryption_failed",
userId: id,
},
);
return res.status(500).json({
error: "Failed to setup user security - user creation cancelled",
});
}
user = await db.select().from(users).where(eq(users.id, id));
} else {
await db
@@ -779,6 +787,15 @@ router.get("/oidc/callback", async (req, res) => {
const userRecord = user[0];
try {
await authManager.authenticateOIDCUser(userRecord.id);
} catch (setupError) {
authLogger.error("Failed to setup OIDC user encryption", setupError, {
operation: "oidc_user_encryption_setup_failed",
userId: userRecord.id,
});
}
const token = await authManager.generateJWTToken(userRecord.id, {
expiresIn: "50d",
});

View File

@@ -53,6 +53,20 @@ class AuthManager {
await this.userCrypto.setupUserEncryption(userId, password);
}
async registerOIDCUser(userId: string): Promise<void> {
await this.userCrypto.setupOIDCUserEncryption(userId);
}
async authenticateOIDCUser(userId: string): Promise<boolean> {
const authenticated = await this.userCrypto.authenticateOIDCUser(userId);
if (authenticated) {
await this.performLazyEncryptionMigration(userId);
}
return authenticated;
}
async authenticateUser(userId: string, password: string): Promise<boolean> {
const authenticated = await this.userCrypto.authenticateUser(
userId,

View File

@@ -69,6 +69,19 @@ class UserCrypto {
DEK.fill(0);
}
async setupOIDCUserEncryption(userId: string): Promise<void> {
const DEK = crypto.randomBytes(UserCrypto.DEK_LENGTH);
const now = Date.now();
this.userSessions.set(userId, {
dataKey: Buffer.from(DEK),
lastActivity: now,
expiresAt: now + UserCrypto.SESSION_DURATION,
});
DEK.fill(0);
}
async authenticateUser(userId: string, password: string): Promise<boolean> {
try {
const kekSalt = await this.getKEKSalt(userId);
@@ -119,6 +132,52 @@ class UserCrypto {
}
}
async authenticateOIDCUser(userId: string): Promise<boolean> {
try {
const kekSalt = await this.getKEKSalt(userId);
if (!kekSalt) {
await this.setupOIDCUserEncryption(userId);
return true;
}
const systemKey = this.deriveOIDCSystemKey(userId);
const encryptedDEK = await this.getEncryptedDEK(userId);
if (!encryptedDEK) {
systemKey.fill(0);
await this.setupOIDCUserEncryption(userId);
return true;
}
const DEK = this.decryptDEK(encryptedDEK, systemKey);
systemKey.fill(0);
if (!DEK || DEK.length === 0) {
await this.setupOIDCUserEncryption(userId);
return true;
}
const now = Date.now();
const oldSession = this.userSessions.get(userId);
if (oldSession) {
oldSession.dataKey.fill(0);
}
this.userSessions.set(userId, {
dataKey: Buffer.from(DEK),
lastActivity: now,
expiresAt: now + UserCrypto.SESSION_DURATION,
});
DEK.fill(0);
return true;
} catch (error) {
await this.setupOIDCUserEncryption(userId);
return true;
}
}
getUserDataKey(userId: string): Buffer | null {
const session = this.userSessions.get(userId);
if (!session) {
@@ -259,6 +318,18 @@ class UserCrypto {
);
}
private deriveOIDCSystemKey(userId: string): Buffer {
const systemSecret = process.env.OIDC_SYSTEM_SECRET || "termix-oidc-system-secret-default";
const salt = Buffer.from(userId, "utf8");
return crypto.pbkdf2Sync(
systemSecret,
salt,
100000,
UserCrypto.KEK_LENGTH,
"sha256",
);
}
private encryptDEK(dek: Buffer, kek: Buffer): EncryptedDEK {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv("aes-256-gcm", kek, iv);

View File

@@ -428,12 +428,10 @@ export function HomepageAuth({
return;
}
if (success && token) {
if (success) {
setOidcLoading(true);
setError(null);
// JWT token is now automatically set as HttpOnly cookie by backend
console.log("OIDC login successful - JWT set as secure HttpOnly cookie");
getUserInfo()
.then((meRes) => {
setInternalLoggedIn(true);
@@ -455,13 +453,11 @@ export function HomepageAuth({
);
})
.catch((err) => {
toast.error(t("errors.failedUserInfo"));
setInternalLoggedIn(false);
setLoggedIn(false);
setIsAdmin(false);
setUsername(null);
setUserId(null);
// HttpOnly cookies cannot be cleared from JavaScript - backend handles this
window.history.replaceState(
{},
document.title,