fix: oidc, global default, and remember me issues

This commit is contained in:
LukeGus
2026-03-10 23:32:43 -05:00
parent ea2e59abd8
commit a255a08903
10 changed files with 186 additions and 122 deletions
+11 -3
View File
@@ -815,6 +815,13 @@ router.get("/oidc/authorize", async (req, res) => {
backendCallbackUri: backendCallbackUri,
});
authLogger.info(
`\n${"=".repeat(68)}\n` +
` OIDC CALLBACK URL - Register this in your OAuth provider:\n` +
` ${backendCallbackUri}\n` +
`${"=".repeat(68)}`,
);
const envConfig = getOIDCConfigFromEnv();
let config;
@@ -1526,9 +1533,10 @@ router.post("/login", async (req, res) => {
if (userRecord.totpEnabled) {
const deviceFingerprint = generateDeviceFingerprint(deviceInfo);
const isTrusted = rememberMe
? await authManager.isTrustedDevice(userRecord.id, deviceFingerprint)
: false;
const isTrusted = await authManager.isTrustedDevice(
userRecord.id,
deviceFingerprint,
);
if (isTrusted) {
authLogger.info("TOTP bypassed for trusted device", {
+21 -10
View File
@@ -1001,17 +1001,21 @@ class PollingManager {
}
}
refreshAllPolling(): void {
async refreshAllPolling(): Promise<void> {
const hostsToRefresh: Array<{
host: SSHHostWithCredentials;
viewerUserId?: string;
}> = [];
for (const [hostId, config] of this.pollingConfigs.entries()) {
hostsToRefresh.push({
host: config.host,
viewerUserId: config.viewerUserId,
});
const status = this.statusStore.get(hostId);
if (!status || status.status === "online") {
hostsToRefresh.push({
host: config.host,
viewerUserId: config.viewerUserId,
});
}
}
for (const hostId of this.pollingConfigs.keys()) {
@@ -1019,8 +1023,10 @@ class PollingManager {
}
for (const { host, viewerUserId } of hostsToRefresh) {
this.startPollingForHost(host, { statusOnly: true, viewerUserId });
await this.startPollingForHost(host, { statusOnly: true, viewerUserId });
}
const skipped = this.pollingConfigs.size - hostsToRefresh.length;
}
registerViewer(hostId: number, sessionId: string, userId: string): void {
@@ -3206,16 +3212,21 @@ app.post("/global-settings", requireAdmin, async (req, res) => {
.run(String(metricsInterval));
}
// Refresh all active polling to apply new intervals immediately
pollingManager.refreshAllPolling();
await pollingManager.refreshAllPolling();
res.json({ success: true });
res.json({
success: true,
message: "Settings updated and polling refreshed",
});
} catch (error) {
statsLogger.error("Failed to save global settings", {
operation: "global_settings_save_error",
error: error instanceof Error ? error.message : String(error),
});
res.status(500).json({ error: "Failed to save global settings" });
res.status(500).json({
error: "Failed to save global settings",
details: error instanceof Error ? error.message : String(error),
});
}
});
+23 -40
View File
@@ -99,7 +99,7 @@ class AuthManager {
const sessionDurationMs =
deviceType === "desktop" || deviceType === "mobile"
? 30 * 24 * 60 * 60 * 1000
: 2 * 60 * 60 * 1000;
: 24 * 60 * 60 * 1000;
const authenticated = await this.userCrypto.authenticateOIDCUser(
userId,
@@ -121,7 +121,7 @@ class AuthManager {
const sessionDurationMs =
deviceType === "desktop" || deviceType === "mobile"
? 30 * 24 * 60 * 60 * 1000
: 2 * 60 * 60 * 1000;
: 24 * 60 * 60 * 1000;
const authenticated = await this.userCrypto.authenticateUser(
userId,
@@ -154,9 +154,8 @@ class AuthManager {
return;
}
const { getSqlite, saveMemoryDatabaseToFile } = await import(
"../database/db/index.js"
);
const { getSqlite, saveMemoryDatabaseToFile } =
await import("../database/db/index.js");
const sqlite = getSqlite();
@@ -171,9 +170,8 @@ class AuthManager {
}
try {
const { CredentialSystemEncryptionMigration } = await import(
"./credential-system-encryption-migration.js"
);
const { CredentialSystemEncryptionMigration } =
await import("./credential-system-encryption-migration.js");
const credMigration = new CredentialSystemEncryptionMigration();
const credResult = await credMigration.migrateUserCredentials(userId);
@@ -213,10 +211,10 @@ class AuthManager {
if (options.rememberMe) {
expiresIn = "30d";
} else {
expiresIn = "2h";
expiresIn = "24h";
}
} else if (!expiresIn) {
expiresIn = "2h";
expiresIn = "24h";
}
const payload: JWTPayload = { userId };
@@ -250,9 +248,8 @@ class AuthManager {
});
try {
const { saveMemoryDatabaseToFile } = await import(
"../database/db/index.js"
);
const { saveMemoryDatabaseToFile } =
await import("../database/db/index.js");
await saveMemoryDatabaseToFile();
} catch (saveError) {
databaseLogger.error(
@@ -280,7 +277,7 @@ class AuthManager {
private parseExpiresIn(expiresIn: string): number {
const match = expiresIn.match(/^(\d+)([smhd])$/);
if (!match) return 2 * 60 * 60 * 1000;
if (!match) return 24 * 60 * 60 * 1000;
const value = parseInt(match[1]);
const unit = match[2];
@@ -295,7 +292,7 @@ class AuthManager {
case "d":
return value * 24 * 60 * 60 * 1000;
default:
return 2 * 60 * 60 * 1000;
return 24 * 60 * 60 * 1000;
}
}
@@ -364,9 +361,8 @@ class AuthManager {
await db.delete(sessions).where(eq(sessions.id, sessionId));
try {
const { saveMemoryDatabaseToFile } = await import(
"../database/db/index.js"
);
const { saveMemoryDatabaseToFile } =
await import("../database/db/index.js");
await saveMemoryDatabaseToFile();
} catch (saveError) {
databaseLogger.error(
@@ -423,9 +419,8 @@ class AuthManager {
}
try {
const { saveMemoryDatabaseToFile } = await import(
"../database/db/index.js"
);
const { saveMemoryDatabaseToFile } =
await import("../database/db/index.js");
await saveMemoryDatabaseToFile();
} catch (saveError) {
databaseLogger.error(
@@ -466,9 +461,8 @@ class AuthManager {
.where(sql`${sessions.expiresAt} < datetime('now')`);
try {
const { saveMemoryDatabaseToFile } = await import(
"../database/db/index.js"
);
const { saveMemoryDatabaseToFile } =
await import("../database/db/index.js");
await saveMemoryDatabaseToFile();
} catch (saveError) {
databaseLogger.error(
@@ -531,7 +525,7 @@ class AuthManager {
getSecureCookieOptions(
req: RequestWithHeaders,
maxAge: number = 2 * 60 * 60 * 1000,
maxAge: number = 24 * 60 * 60 * 1000,
) {
return {
httpOnly: false,
@@ -613,9 +607,8 @@ class AuthManager {
.where(eq(sessions.id, payload.sessionId))
.then(async () => {
try {
const { saveMemoryDatabaseToFile } = await import(
"../database/db/index.js"
);
const { saveMemoryDatabaseToFile } =
await import("../database/db/index.js");
await saveMemoryDatabaseToFile();
const remainingSessions = await db
@@ -759,9 +752,8 @@ class AuthManager {
await db.delete(sessions).where(eq(sessions.id, sessionId));
try {
const { saveMemoryDatabaseToFile } = await import(
"../database/db/index.js"
);
const { saveMemoryDatabaseToFile } =
await import("../database/db/index.js");
await saveMemoryDatabaseToFile();
} catch (saveError) {
databaseLogger.error(
@@ -827,9 +819,6 @@ class AuthManager {
);
}
/**
* Check if device is trusted for TOTP bypass
*/
async isTrustedDevice(
userId: string,
deviceFingerprint: string,
@@ -875,9 +864,6 @@ class AuthManager {
}
}
/**
* Add device to trusted list for TOTP bypass
*/
async addTrustedDevice(
userId: string,
deviceFingerprint: string,
@@ -925,9 +911,6 @@ class AuthManager {
}
}
/**
* Remove trusted device
*/
async removeTrustedDevice(
userId: string,
deviceFingerprint: string,
+10 -3
View File
@@ -19,7 +19,7 @@ export function getRequestOrigin(req: Request | IncomingMessage): string {
}
const portHeader = req.headers["x-forwarded-port"];
const port =
let port: string | undefined =
typeof portHeader === "string"
? portHeader.split(",")[0].trim()
: undefined;
@@ -31,8 +31,15 @@ export function getRequestOrigin(req: Request | IncomingMessage): string {
? hostHeaderRaw.split(",")[0].trim()
: String(hostHeaderRaw);
if (!port && hostHeader.includes(":")) {
const parts = hostHeader.split(":");
if (parts.length === 2 && !parts[0].includes("[")) {
port = parts[1];
}
}
const hostWithoutPort = hostHeader.split(":")[0];
if (port) {
const hostWithoutPort = hostHeader.split(":")[0];
const isDefaultPort =
(protocol === "http" && port === "80") ||
(protocol === "https" && port === "443");
@@ -42,7 +49,7 @@ export function getRequestOrigin(req: Request | IncomingMessage): string {
: `${protocol}://${hostWithoutPort}:${port}`;
}
return `${protocol}://${hostHeader}`;
return `${protocol}://${hostWithoutPort}`;
}
export function getRequestOriginWithForceHTTPS(