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
@@ -1,6 +1,7 @@
import { DatabaseSaveTrigger } from "../../utils/database-save-trigger.js"; import { DatabaseSaveTrigger } from "../../utils/database-save-trigger.js";
import { getDb, getSqlite } from "../db/index.js"; import { getDb, getSqlite } from "../db/index.js";
import type { DatabaseContext } from "./database-context.js"; import type { DatabaseContext } from "./database-context.js";
import { WebauthnCredentialRepository } from "./webauthn-credential-repository.js";
import { AlertRepository } from "./alert-repository.js"; import { AlertRepository } from "./alert-repository.js";
import { ApiKeyRepository } from "./api-key-repository.js"; import { ApiKeyRepository } from "./api-key-repository.js";
import { AuditLogRepository } from "./audit-log-repository.js"; import { AuditLogRepository } from "./audit-log-repository.js";
@@ -68,6 +69,13 @@ export function getCurrentSettingValue(key: string): string | null {
return row?.value ?? null; return row?.value ?? null;
} }
export function createCurrentWebauthnCredentialRepository(): WebauthnCredentialRepository {
return new WebauthnCredentialRepository(
createCurrentRepositoryContext(),
createCurrentRepositoryWriteHook("webauthn_credential_repository_write"),
);
}
export function createCurrentAlertRepository(): AlertRepository { export function createCurrentAlertRepository(): AlertRepository {
return new AlertRepository( return new AlertRepository(
createCurrentRepositoryContext(), createCurrentRepositoryContext(),
@@ -78,6 +78,32 @@ export class HostRepository {
return rows[0] ?? null; return rows[0] ?? null;
} }
async findDecryptedByIdAs(
userId: string,
hostId: number,
): Promise<HostRecord | null> {
const row = await this.findById(hostId);
if (!row) return null;
const userDataKey = DataCrypto.getUserDataKey(userId);
if (!userDataKey) return null;
return DataCrypto.decryptRecord("ssh_data", row, userId, userDataKey);
}
async listProxmoxEnabled(): Promise<
Pick<HostRecord, "id" | "userId" | "proxmoxConfig">[]
> {
return this.context.drizzle
.select({
id: hosts.id,
userId: hosts.userId,
proxmoxConfig: hosts.proxmoxConfig,
})
.from(hosts)
.where(eq(hosts.enableProxmox, true));
}
async listByUserId(userId: string): Promise<HostRecord[]> { async listByUserId(userId: string): Promise<HostRecord[]> {
return this.context.drizzle return this.context.drizzle
.select() .select()
@@ -35,6 +35,14 @@ export class SsoProviderRepository {
.orderBy(asc(ssoProviders.displayOrder), asc(ssoProviders.id)); .orderBy(asc(ssoProviders.displayOrder), asc(ssoProviders.id));
} }
async listEnabled(): Promise<SsoProviderRecord[]> {
return this.context.drizzle
.select()
.from(ssoProviders)
.where(eq(ssoProviders.enabled, true))
.orderBy(asc(ssoProviders.displayOrder), asc(ssoProviders.id));
}
async listAll(): Promise<SsoProviderRecord[]> { async listAll(): Promise<SsoProviderRecord[]> {
return this.context.drizzle return this.context.drizzle
.select() .select()
@@ -0,0 +1,86 @@
import { and, eq } from "drizzle-orm";
import { webauthnCredentials } from "../db/schema.js";
import type { DatabaseContext } from "./database-context.js";
export type WebauthnCredentialRecord = typeof webauthnCredentials.$inferSelect;
export type NewWebauthnCredentialRecord =
typeof webauthnCredentials.$inferInsert;
export interface WebauthnCredentialAuthState {
counter: number;
backedUp: boolean;
deviceType: string | null;
lastUsedAt: string;
}
export class WebauthnCredentialRepository {
constructor(
private readonly context: DatabaseContext,
private readonly onWrite?: () => void | Promise<void>,
) {}
async listByUserId(userId: string): Promise<WebauthnCredentialRecord[]> {
return this.context.drizzle
.select()
.from(webauthnCredentials)
.where(eq(webauthnCredentials.userId, userId));
}
async findByCredentialId(
credentialId: string,
): Promise<WebauthnCredentialRecord | null> {
const rows = await this.context.drizzle
.select()
.from(webauthnCredentials)
.where(eq(webauthnCredentials.credentialId, credentialId))
.limit(1);
return rows[0] ?? null;
}
async create(
record: NewWebauthnCredentialRecord,
): Promise<WebauthnCredentialRecord> {
const rows = await this.context.drizzle
.insert(webauthnCredentials)
.values(record)
.returning();
await this.afterWrite();
return rows[0];
}
async updateAuthState(
id: string,
state: WebauthnCredentialAuthState,
): Promise<void> {
await this.context.drizzle
.update(webauthnCredentials)
.set(state)
.where(eq(webauthnCredentials.id, id));
await this.afterWrite();
}
async deleteForUser(userId: string, id: string): Promise<boolean> {
const rows = await this.context.drizzle
.delete(webauthnCredentials)
.where(
and(
eq(webauthnCredentials.id, id),
eq(webauthnCredentials.userId, userId),
),
)
.returning({ id: webauthnCredentials.id });
if (rows.length > 0) {
await this.afterWrite();
}
return rows.length > 0;
}
private async afterWrite(): Promise<void> {
await this.onWrite?.();
}
}
+36 -68
View File
@@ -1,10 +1,11 @@
import express from "express"; import express from "express";
import { Client as SSHClient } from "ssh2"; import { Client as SSHClient } from "ssh2";
import { db, getDb, DatabaseSaveTrigger } from "../db/index.js";
import { hosts, sshCredentials } from "../db/schema.js";
import { eq, and } from "drizzle-orm";
import { logger } from "../../utils/logger.js"; import { logger } from "../../utils/logger.js";
import { SimpleDBOps } from "../../utils/simple-db-ops.js"; import { DataCrypto } from "../../utils/data-crypto.js";
import {
createCurrentCredentialRepository,
createCurrentHostRepository,
} from "../repositories/factory.js";
import { AuthManager } from "../../utils/auth-manager.js"; import { AuthManager } from "../../utils/auth-manager.js";
import type { AuthenticatedRequest } from "../../../types/index.js"; import type { AuthenticatedRequest } from "../../../types/index.js";
import type { SSHHost } from "../../../types/index.js"; import type { SSHHost } from "../../../types/index.js";
@@ -288,25 +289,24 @@ async function discoverProxmoxGuestsForHost(
defaultCredentialId: number | null; defaultCredentialId: number | null;
config: ReturnType<typeof parseProxmoxConfig>; config: ReturnType<typeof parseProxmoxConfig>;
}> { }> {
if (!SimpleDBOps.isUserDataUnlocked(userId)) { if (!DataCrypto.canUserAccessData(userId)) {
const error = new Error("Session expired — please log in again"); const error = new Error("Session expired — please log in again");
(error as Error & { code?: string }).code = "SESSION_EXPIRED"; (error as Error & { code?: string }).code = "SESSION_EXPIRED";
throw error; throw error;
} }
const hostResults = await SimpleDBOps.select( const hostRecord = await createCurrentHostRepository().findDecryptedByIdAs(
getDb().select().from(hosts).where(eq(hosts.id, parsedHostId)),
"ssh_data",
userId, userId,
parsedHostId,
); );
if (!hostResults.length) { if (!hostRecord) {
const error = new Error("Host not found"); const error = new Error("Host not found");
(error as Error & { status?: number }).status = 404; (error as Error & { status?: number }).status = 404;
throw error; throw error;
} }
const host = hostResults[0] as unknown as SSHHost; const host = hostRecord as unknown as SSHHost;
const proxmoxCfgRaw = parseJsonObject(host.proxmoxConfig); const proxmoxCfgRaw = parseJsonObject(host.proxmoxConfig);
const config = parseProxmoxConfig(proxmoxCfgRaw); const config = parseProxmoxConfig(proxmoxCfgRaw);
@@ -362,21 +362,13 @@ async function discoverProxmoxGuestsForHost(
}); });
} }
} else { } else {
const creds = await SimpleDBOps.select( const cred =
getDb() await createCurrentCredentialRepository().findDecryptedByIdForUser(
.select()
.from(sshCredentials)
.where(
and(
eq(sshCredentials.id, host.credentialId as number),
eq(sshCredentials.userId, userId),
),
),
"ssh_credentials",
userId, userId,
host.credentialId as number,
); );
if (creds.length > 0) { if (cred) {
const c = creds[0]; const c = cred;
resolvedCredentials = { resolvedCredentials = {
password: c.password as string | undefined, password: c.password as string | undefined,
sshKey: (c.key || c.privateKey) as string | undefined, sshKey: (c.key || c.privateKey) as string | undefined,
@@ -634,11 +626,10 @@ async function syncProxmoxHost(
); );
const now = new Date().toISOString(); const now = new Date().toISOString();
const existingHosts = await SimpleDBOps.select<Record<string, unknown>>( const existingHosts =
db.select().from(hosts).where(eq(hosts.userId, userId)), (await createCurrentHostRepository().listDecryptedByUserId(
"ssh_data",
userId, userId,
); )) as unknown as Record<string, unknown>[];
const existingBySource = new Map<string, Record<string, unknown>>(); const existingBySource = new Map<string, Record<string, unknown>>();
for (const host of existingHosts) { for (const host of existingHosts) {
const source = getProxmoxSource(host); const source = getProxmoxSource(host);
@@ -712,12 +703,10 @@ async function syncProxmoxHost(
}; };
if (existing) { if (existing) {
await SimpleDBOps.update( await createCurrentHostRepository().updateEncryptedForUser(
hosts,
"ssh_data",
eq(hosts.id, existing.id as number),
update,
userId, userId,
existing.id as number,
update,
); );
result.updated++; result.updated++;
continue; continue;
@@ -732,17 +721,13 @@ async function syncProxmoxHost(
enableRdp: connectionType === "rdp", enableRdp: connectionType === "rdp",
}); });
await SimpleDBOps.insert( await createCurrentHostRepository().createEncryptedForUser(userId, {
hosts,
"ssh_data",
{
...update, ...update,
userId, userId,
createdAt: now, createdAt: now,
pin: false, pin: false,
authType: connectionType === "rdp" ? "password" : importAuth.authType, authType: connectionType === "rdp" ? "password" : importAuth.authType,
credentialId: credentialId: connectionType === "ssh" ? importAuth.credentialId : null,
connectionType === "ssh" ? importAuth.credentialId : null,
overrideCredentialUsername: importAuth.overrideCredentialUsername, overrideCredentialUsername: importAuth.overrideCredentialUsername,
password: null, password: null,
key: null, key: null,
@@ -780,9 +765,7 @@ async function syncProxmoxHost(
showTunnelInSidebar: 0, showTunnelInSidebar: 0,
showDockerInSidebar: 0, showDockerInSidebar: 0,
showServerStatsInSidebar: 0, showServerStatsInSidebar: 0,
}, });
userId,
);
result.created++; result.created++;
} }
@@ -793,10 +776,9 @@ async function syncProxmoxHost(
const source = getProxmoxSource(existing); const source = getProxmoxSource(existing);
if (!source) continue; if (!source) continue;
const missingSince = source.missingSince || now; const missingSince = source.missingSince || now;
await SimpleDBOps.update( await createCurrentHostRepository().updateEncryptedForUser(
hosts, userId,
"ssh_data", existing.id as number,
eq(hosts.id, existing.id as number),
{ {
tags: mergeTags(existing.tags, ["proxmox-missing"]), tags: mergeTags(existing.tags, ["proxmox-missing"]),
proxmoxConfig: JSON.stringify({ proxmoxConfig: JSON.stringify({
@@ -808,7 +790,6 @@ async function syncProxmoxHost(
}), }),
updatedAt: now, updatedAt: now,
}, },
userId,
); );
result.markedMissing++; result.markedMissing++;
} }
@@ -842,18 +823,13 @@ async function writeSyncStatus(
hostId: number, hostId: number,
patch: Record<string, unknown>, patch: Record<string, unknown>,
): Promise<void> { ): Promise<void> {
const rows = await db const hostRepository = createCurrentHostRepository();
.select({ proxmoxConfig: hosts.proxmoxConfig }) const row = await hostRepository.findByIdForUser(userId, hostId);
.from(hosts) if (!row) return;
.where(and(eq(hosts.id, hostId), eq(hosts.userId, userId))) const config = parseJsonObject(row.proxmoxConfig);
.limit(1); await hostRepository.updateForUser(userId, hostId, {
if (!rows.length) return; proxmoxConfig: JSON.stringify({ ...config, ...patch }),
const config = parseJsonObject(rows[0].proxmoxConfig); });
await db
.update(hosts)
.set({ proxmoxConfig: JSON.stringify({ ...config, ...patch }) })
.where(and(eq(hosts.id, hostId), eq(hosts.userId, userId)));
DatabaseSaveTrigger.triggerSave("proxmox_sync_status");
} }
router.post("/sync", authenticateJWT, requireDataAccess, async (req, res) => { router.post("/sync", authenticateJWT, requireDataAccess, async (req, res) => {
@@ -886,22 +862,14 @@ router.post("/sync", authenticateJWT, requireDataAccess, async (req, res) => {
async function runDueProxmoxAutoSyncs(): Promise<void> { async function runDueProxmoxAutoSyncs(): Promise<void> {
try { try {
const rows = await db const rows = await createCurrentHostRepository().listProxmoxEnabled();
.select({
id: hosts.id,
userId: hosts.userId,
enableProxmox: hosts.enableProxmox,
proxmoxConfig: hosts.proxmoxConfig,
})
.from(hosts)
.where(eq(hosts.enableProxmox, true));
const now = Date.now(); const now = Date.now();
for (const row of rows) { for (const row of rows) {
const configRaw = parseJsonObject(row.proxmoxConfig); const configRaw = parseJsonObject(row.proxmoxConfig);
const config = parseProxmoxConfig(configRaw); const config = parseProxmoxConfig(configRaw);
if (!config.autoSyncEnabled) continue; if (!config.autoSyncEnabled) continue;
if (!SimpleDBOps.isUserDataUnlocked(row.userId)) continue; if (!DataCrypto.canUserAccessData(row.userId)) continue;
const lastSyncAt = const lastSyncAt =
typeof configRaw.lastSyncAt === "string" typeof configRaw.lastSyncAt === "string"
@@ -6,8 +6,11 @@ import { apiLogger } from "../../utils/logger.js";
import { AuthManager } from "../../utils/auth-manager.js"; import { AuthManager } from "../../utils/auth-manager.js";
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import { PermissionManager } from "../../utils/permission-manager.js"; import { PermissionManager } from "../../utils/permission-manager.js";
import { createCurrentSessionRecordingRepository } from "../repositories/factory.js"; import {
import { getDb } from "../db/index.js"; createCurrentSessionRecordingRepository,
createCurrentSettingsRepository,
getCurrentSettingValue,
} from "../repositories/factory.js";
const router = express.Router(); const router = express.Router();
@@ -29,12 +32,10 @@ function getRetentionDays(): number {
10, 10,
); );
try { try {
const row = getDb() const configured = parseInt(
.$client.prepare( getCurrentSettingValue("session_recording_retention_days") || "",
"SELECT value FROM settings WHERE key = 'session_recording_retention_days'", 10,
) );
.get() as { value?: string } | undefined;
const configured = parseInt(row?.value || "", 10);
if (configured >= 1 && configured <= 3650) return configured; if (configured >= 1 && configured <= 3650) return configured;
} catch { } catch {
// use environment/default below // use environment/default below
@@ -164,11 +165,10 @@ router.put(
.status(400) .status(400)
.json({ error: "Retention must be between 1 and 3650 days" }); .json({ error: "Retention must be between 1 and 3650 days" });
} }
getDb() await createCurrentSettingsRepository().upsert(
.$client.prepare( "session_recording_retention_days",
"INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value", String(retentionDays),
) );
.run("session_recording_retention_days", String(retentionDays));
void pruneOldLogs(); void pruneOldLogs();
res.json({ retentionDays }); res.json({ retentionDays });
}, },
@@ -2,9 +2,6 @@ import { authLogger } from "../../utils/logger.js";
import type { SSOProviderType } from "../../../types/index.js"; import type { SSOProviderType } from "../../../types/index.js";
import { DataCrypto } from "../../utils/data-crypto.js"; import { DataCrypto } from "../../utils/data-crypto.js";
import { Agent } from "undici"; import { Agent } from "undici";
import { eq } from "drizzle-orm";
import { getDb } from "../db/index.js";
import { ssoProviders } from "../db/schema.js";
import { import {
createCurrentSettingsRepository, createCurrentSettingsRepository,
createCurrentSsoProviderRepository, createCurrentSsoProviderRepository,
@@ -426,10 +423,7 @@ export async function resolveProviderByIssuer(issuer: string): Promise<{
const target = normalizeIssuer(issuer); const target = normalizeIssuer(issuer);
try { try {
const rows = await getDb() const rows = await createCurrentSsoProviderRepository().listEnabled();
.select()
.from(ssoProviders)
.where(eq(ssoProviders.enabled, true));
for (const row of rows) { for (const row of rows) {
if (!["oidc", "github", "google"].includes(row.type)) continue; if (!["oidc", "github", "google"].includes(row.type)) continue;
let parsed: Record<string, unknown>; let parsed: Record<string, unknown>;
@@ -12,7 +12,6 @@ import {
verifyAuthenticationResponse, verifyAuthenticationResponse,
verifyRegistrationResponse, verifyRegistrationResponse,
} from "@simplewebauthn/server"; } from "@simplewebauthn/server";
import { and, eq } from "drizzle-orm";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import type { AuthenticatedRequest } from "../../../types/index.js"; import type { AuthenticatedRequest } from "../../../types/index.js";
import { AuthManager } from "../../utils/auth-manager.js"; import { AuthManager } from "../../utils/auth-manager.js";
@@ -21,8 +20,12 @@ import {
generateDeviceFingerprint, generateDeviceFingerprint,
parseUserAgent, parseUserAgent,
} from "../../utils/user-agent-parser.js"; } from "../../utils/user-agent-parser.js";
import { db, saveMemoryDatabaseToFile } from "../db/index.js"; import {
import { users, webauthnCredentials } from "../db/schema.js"; createCurrentUserRepository,
createCurrentWebauthnCredentialRepository,
getCurrentSettingValue,
} from "../repositories/factory.js";
import type { WebauthnCredentialRecord } from "../repositories/webauthn-credential-repository.js";
type UserVerification = "discouraged" | "preferred" | "required"; type UserVerification = "discouraged" | "preferred" | "required";
type NativeAppRequestChecker = (req: Request) => boolean; type NativeAppRequestChecker = (req: Request) => boolean;
@@ -113,7 +116,7 @@ function parseTransports(value: string | null): AuthenticatorTransportFuture[] {
} }
function getCredentialForVerification( function getCredentialForVerification(
credential: typeof webauthnCredentials.$inferSelect, credential: WebauthnCredentialRecord,
): WebAuthnCredential { ): WebAuthnCredential {
return { return {
id: credential.credentialId as Base64URLString, id: credential.credentialId as Base64URLString,
@@ -135,10 +138,8 @@ export function registerUserWebAuthnRoutes(
return res.status(401).json({ error: "Authentication required" }); return res.status(401).json({ error: "Authentication required" });
} }
const credentials = await db const credentials =
.select() await createCurrentWebauthnCredentialRepository().listByUserId(userId);
.from(webauthnCredentials)
.where(eq(webauthnCredentials.userId, userId));
res.json({ res.json({
credentials: credentials.map((credential) => ({ credentials: credentials.map((credential) => ({
@@ -169,15 +170,13 @@ export function registerUserWebAuthnRoutes(
}); });
} }
const user = await db.select().from(users).where(eq(users.id, userId)); const user = await createCurrentUserRepository().findById(userId);
if (!user.length) { if (!user) {
return res.status(404).json({ error: "User not found" }); return res.status(404).json({ error: "User not found" });
} }
const existing = await db const existing =
.select() await createCurrentWebauthnCredentialRepository().listByUserId(userId);
.from(webauthnCredentials)
.where(eq(webauthnCredentials.userId, userId));
const origin = getRequestOrigin(req); const origin = getRequestOrigin(req);
const rpID = getRpID(origin); const rpID = getRpID(origin);
@@ -189,8 +188,8 @@ export function registerUserWebAuthnRoutes(
rpName: "Termix", rpName: "Termix",
rpID, rpID,
userID: Buffer.from(userId, "utf8"), userID: Buffer.from(userId, "utf8"),
userName: user[0].username, userName: user.username,
userDisplayName: user[0].username, userDisplayName: user.username,
attestationType: "none", attestationType: "none",
excludeCredentials: existing.map((credential) => ({ excludeCredentials: existing.map((credential) => ({
id: credential.credentialId as Base64URLString, id: credential.credentialId as Base64URLString,
@@ -259,7 +258,7 @@ export function registerUserWebAuthnRoutes(
? req.body.name.trim().slice(0, 80) ? req.body.name.trim().slice(0, 80)
: "Passkey"; : "Passkey";
await db.insert(webauthnCredentials).values({ await createCurrentWebauthnCredentialRepository().create({
id: nanoid(), id: nanoid(),
userId, userId,
name, name,
@@ -273,7 +272,6 @@ export function registerUserWebAuthnRoutes(
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
}); });
await saveMemoryDatabaseToFile();
res.json({ success: true }); res.json({ success: true });
} catch (error) { } catch (error) {
authLogger.warn("WebAuthn registration failed", { authLogger.warn("WebAuthn registration failed", {
@@ -301,19 +299,14 @@ export function registerUserWebAuthnRoutes(
| undefined; | undefined;
if (username) { if (username) {
const user = await db const user = await createCurrentUserRepository().findByUsername(username);
.select() if (!user) {
.from(users)
.where(eq(users.username, username));
if (!user.length) {
return res.status(404).json({ error: "No passkeys found" }); return res.status(404).json({ error: "No passkeys found" });
} }
userId = user[0].id; userId = user.id;
const credentials = await db const credentials =
.select() await createCurrentWebauthnCredentialRepository().listByUserId(userId);
.from(webauthnCredentials)
.where(eq(webauthnCredentials.userId, userId));
if (!credentials.length) { if (!credentials.length) {
return res.status(404).json({ error: "No passkeys found" }); 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" }); return res.status(400).json({ error: "Invalid passkey response" });
} }
const credentials = await db const credential =
.select() await createCurrentWebauthnCredentialRepository().findByCredentialId(
.from(webauthnCredentials) response.id,
.where(eq(webauthnCredentials.credentialId, response.id)); );
if (!credentials.length) { if (!credential) {
return res.status(401).json({ error: "Passkey not recognized" }); return res.status(401).json({ error: "Passkey not recognized" });
} }
const credential = credentials[0];
if (challenge.userId && challenge.userId !== credential.userId) { if (challenge.userId && challenge.userId !== credential.userId) {
return res.status(401).json({ error: "Passkey not recognized" }); 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" }); return res.status(401).json({ error: "Passkey authentication failed" });
} }
const user = await db const userRecord = await createCurrentUserRepository().findById(
.select() credential.userId,
.from(users) );
.where(eq(users.id, credential.userId)); if (!userRecord) {
if (!user.length) {
return res.status(404).json({ error: "User not found" }); return res.status(404).json({ error: "User not found" });
} }
const userRecord = user[0];
const deviceInfo = parseUserAgent(req); const deviceInfo = parseUserAgent(req);
const dataUnlocked = await authManager.authenticateWebAuthnUser( const dataUnlocked = await authManager.authenticateWebAuthnUser(
userRecord.id, userRecord.id,
@@ -413,15 +403,15 @@ export function registerUserWebAuthnRoutes(
}); });
} }
await db await createCurrentWebauthnCredentialRepository().updateAuthState(
.update(webauthnCredentials) credential.id,
.set({ {
counter: verification.authenticationInfo.newCounter, counter: verification.authenticationInfo.newCounter,
backedUp: verification.authenticationInfo.credentialBackedUp, backedUp: verification.authenticationInfo.credentialBackedUp,
deviceType: verification.authenticationInfo.credentialDeviceType, deviceType: verification.authenticationInfo.credentialDeviceType,
lastUsedAt: new Date().toISOString(), lastUsedAt: new Date().toISOString(),
}) },
.where(eq(webauthnCredentials.id, credential.id)); );
if (userRecord.totpEnabled) { if (userRecord.totpEnabled) {
const deviceFingerprint = generateDeviceFingerprint(deviceInfo); const deviceFingerprint = generateDeviceFingerprint(deviceInfo);
@@ -431,7 +421,6 @@ export function registerUserWebAuthnRoutes(
); );
if (!isTrusted) { if (!isTrusted) {
await saveMemoryDatabaseToFile();
const tempToken = await authManager.generateJWTToken(userRecord.id, { const tempToken = await authManager.generateJWTToken(userRecord.id, {
pendingTOTP: true, pendingTOTP: true,
expiresIn: "10m", expiresIn: "10m",
@@ -451,15 +440,9 @@ export function registerUserWebAuthnRoutes(
deviceInfo: deviceInfo.deviceInfo, deviceInfo: deviceInfo.deviceInfo,
}); });
await saveMemoryDatabaseToFile(); const timeoutSetting = getCurrentSettingValue("session_timeout_hours");
const timeoutHours = timeoutSetting
const timeoutRow = db.$client ? parseInt(timeoutSetting, 10) || 24
.prepare(
"SELECT value FROM settings WHERE key = 'session_timeout_hours'",
)
.get() as { value: string } | undefined;
const timeoutHours = timeoutRow
? parseInt(timeoutRow.value, 10) || 24
: 24; : 24;
const maxAge = req.body?.rememberMe const maxAge = req.body?.rememberMe
? 30 * 24 * 60 * 60 * 1000 ? 30 * 24 * 60 * 60 * 1000
@@ -498,15 +481,10 @@ export function registerUserWebAuthnRoutes(
const credentialId = String(req.params.credentialId); const credentialId = String(req.params.credentialId);
await db await createCurrentWebauthnCredentialRepository().deleteForUser(
.delete(webauthnCredentials) userId,
.where( credentialId,
and(
eq(webauthnCredentials.id, credentialId),
eq(webauthnCredentials.userId, userId),
),
); );
await saveMemoryDatabaseToFile();
res.json({ success: true }); res.json({ success: true });
}, },
+2 -5
View File
@@ -5,8 +5,7 @@ import { getCurrentSettingValue } from "../database/repositories/factory.js";
import { resolveGuacdOptions } from "../utils/guacd-config.js"; import { resolveGuacdOptions } from "../utils/guacd-config.js";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import { getDb } from "../database/db/index.js"; import { createCurrentSessionRecordingRepository } from "../database/repositories/factory.js";
import { sessionRecordings } from "../database/db/schema.js";
import type { GuacamoleRecordingMetadata } from "./token-service.js"; import type { GuacamoleRecordingMetadata } from "./token-service.js";
const tokenService = GuacamoleTokenService.getInstance(); const tokenService = GuacamoleTokenService.getInstance();
@@ -63,9 +62,7 @@ async function persistGuacamoleRecording(
const endedAt = new Date(); const endedAt = new Date();
const startedAt = new Date(recording.startedAt); const startedAt = new Date(recording.startedAt);
await getDb() await createCurrentSessionRecordingRepository().create({
.insert(sessionRecordings)
.values({
hostId: recording.hostId, hostId: recording.hostId,
userId: recording.userId, userId: recording.userId,
startedAt: startedAt.toISOString(), startedAt: startedAt.toISOString(),