mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-17 05:43:36 +00:00
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:
@@ -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 });
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user