refactor(db): route remaining raw DB access through repositories

proxmox, session-log, oidc-utils, webauthn and guacamole recording now use
repositories (new WebauthnCredentialRepository; SsoProviderRepository
listEnabled; HostRepository findDecryptedByIdAs/listProxmoxEnabled).
Remaining raw access: db boot code, simple-db-ops and docker.ts, which are
removed/restructured in later phases.
This commit is contained in:
LukeGus
2026-07-16 00:04:36 -05:00
parent ef4969dd35
commit d710989070
9 changed files with 278 additions and 213 deletions
@@ -12,7 +12,6 @@ import {
verifyAuthenticationResponse,
verifyRegistrationResponse,
} from "@simplewebauthn/server";
import { and, eq } from "drizzle-orm";
import { nanoid } from "nanoid";
import type { AuthenticatedRequest } from "../../../types/index.js";
import { AuthManager } from "../../utils/auth-manager.js";
@@ -21,8 +20,12 @@ import {
generateDeviceFingerprint,
parseUserAgent,
} from "../../utils/user-agent-parser.js";
import { db, saveMemoryDatabaseToFile } from "../db/index.js";
import { users, webauthnCredentials } from "../db/schema.js";
import {
createCurrentUserRepository,
createCurrentWebauthnCredentialRepository,
getCurrentSettingValue,
} from "../repositories/factory.js";
import type { WebauthnCredentialRecord } from "../repositories/webauthn-credential-repository.js";
type UserVerification = "discouraged" | "preferred" | "required";
type NativeAppRequestChecker = (req: Request) => boolean;
@@ -113,7 +116,7 @@ function parseTransports(value: string | null): AuthenticatorTransportFuture[] {
}
function getCredentialForVerification(
credential: typeof webauthnCredentials.$inferSelect,
credential: WebauthnCredentialRecord,
): WebAuthnCredential {
return {
id: credential.credentialId as Base64URLString,
@@ -135,10 +138,8 @@ export function registerUserWebAuthnRoutes(
return res.status(401).json({ error: "Authentication required" });
}
const credentials = await db
.select()
.from(webauthnCredentials)
.where(eq(webauthnCredentials.userId, userId));
const credentials =
await createCurrentWebauthnCredentialRepository().listByUserId(userId);
res.json({
credentials: credentials.map((credential) => ({
@@ -169,15 +170,13 @@ export function registerUserWebAuthnRoutes(
});
}
const user = await db.select().from(users).where(eq(users.id, userId));
if (!user.length) {
const user = await createCurrentUserRepository().findById(userId);
if (!user) {
return res.status(404).json({ error: "User not found" });
}
const existing = await db
.select()
.from(webauthnCredentials)
.where(eq(webauthnCredentials.userId, userId));
const existing =
await createCurrentWebauthnCredentialRepository().listByUserId(userId);
const origin = getRequestOrigin(req);
const rpID = getRpID(origin);
@@ -189,8 +188,8 @@ export function registerUserWebAuthnRoutes(
rpName: "Termix",
rpID,
userID: Buffer.from(userId, "utf8"),
userName: user[0].username,
userDisplayName: user[0].username,
userName: user.username,
userDisplayName: user.username,
attestationType: "none",
excludeCredentials: existing.map((credential) => ({
id: credential.credentialId as Base64URLString,
@@ -259,7 +258,7 @@ export function registerUserWebAuthnRoutes(
? req.body.name.trim().slice(0, 80)
: "Passkey";
await db.insert(webauthnCredentials).values({
await createCurrentWebauthnCredentialRepository().create({
id: nanoid(),
userId,
name,
@@ -273,7 +272,6 @@ export function registerUserWebAuthnRoutes(
createdAt: new Date().toISOString(),
});
await saveMemoryDatabaseToFile();
res.json({ success: true });
} catch (error) {
authLogger.warn("WebAuthn registration failed", {
@@ -301,19 +299,14 @@ export function registerUserWebAuthnRoutes(
| undefined;
if (username) {
const user = await db
.select()
.from(users)
.where(eq(users.username, username));
if (!user.length) {
const user = await createCurrentUserRepository().findByUsername(username);
if (!user) {
return res.status(404).json({ error: "No passkeys found" });
}
userId = user[0].id;
const credentials = await db
.select()
.from(webauthnCredentials)
.where(eq(webauthnCredentials.userId, userId));
userId = user.id;
const credentials =
await createCurrentWebauthnCredentialRepository().listByUserId(userId);
if (!credentials.length) {
return res.status(404).json({ error: "No passkeys found" });
@@ -360,16 +353,15 @@ export function registerUserWebAuthnRoutes(
return res.status(400).json({ error: "Invalid passkey response" });
}
const credentials = await db
.select()
.from(webauthnCredentials)
.where(eq(webauthnCredentials.credentialId, response.id));
const credential =
await createCurrentWebauthnCredentialRepository().findByCredentialId(
response.id,
);
if (!credentials.length) {
if (!credential) {
return res.status(401).json({ error: "Passkey not recognized" });
}
const credential = credentials[0];
if (challenge.userId && challenge.userId !== credential.userId) {
return res.status(401).json({ error: "Passkey not recognized" });
}
@@ -391,15 +383,13 @@ export function registerUserWebAuthnRoutes(
return res.status(401).json({ error: "Passkey authentication failed" });
}
const user = await db
.select()
.from(users)
.where(eq(users.id, credential.userId));
if (!user.length) {
const userRecord = await createCurrentUserRepository().findById(
credential.userId,
);
if (!userRecord) {
return res.status(404).json({ error: "User not found" });
}
const userRecord = user[0];
const deviceInfo = parseUserAgent(req);
const dataUnlocked = await authManager.authenticateWebAuthnUser(
userRecord.id,
@@ -413,15 +403,15 @@ export function registerUserWebAuthnRoutes(
});
}
await db
.update(webauthnCredentials)
.set({
await createCurrentWebauthnCredentialRepository().updateAuthState(
credential.id,
{
counter: verification.authenticationInfo.newCounter,
backedUp: verification.authenticationInfo.credentialBackedUp,
deviceType: verification.authenticationInfo.credentialDeviceType,
lastUsedAt: new Date().toISOString(),
})
.where(eq(webauthnCredentials.id, credential.id));
},
);
if (userRecord.totpEnabled) {
const deviceFingerprint = generateDeviceFingerprint(deviceInfo);
@@ -431,7 +421,6 @@ export function registerUserWebAuthnRoutes(
);
if (!isTrusted) {
await saveMemoryDatabaseToFile();
const tempToken = await authManager.generateJWTToken(userRecord.id, {
pendingTOTP: true,
expiresIn: "10m",
@@ -451,15 +440,9 @@ export function registerUserWebAuthnRoutes(
deviceInfo: deviceInfo.deviceInfo,
});
await saveMemoryDatabaseToFile();
const timeoutRow = db.$client
.prepare(
"SELECT value FROM settings WHERE key = 'session_timeout_hours'",
)
.get() as { value: string } | undefined;
const timeoutHours = timeoutRow
? parseInt(timeoutRow.value, 10) || 24
const timeoutSetting = getCurrentSettingValue("session_timeout_hours");
const timeoutHours = timeoutSetting
? parseInt(timeoutSetting, 10) || 24
: 24;
const maxAge = req.body?.rememberMe
? 30 * 24 * 60 * 60 * 1000
@@ -498,15 +481,10 @@ export function registerUserWebAuthnRoutes(
const credentialId = String(req.params.credentialId);
await db
.delete(webauthnCredentials)
.where(
and(
eq(webauthnCredentials.id, credentialId),
eq(webauthnCredentials.userId, userId),
),
);
await saveMemoryDatabaseToFile();
await createCurrentWebauthnCredentialRepository().deleteForUser(
userId,
credentialId,
);
res.json({ success: true });
},