diff --git a/src/server/database/repositories/user/service.ts b/src/server/database/repositories/user/service.ts index f130da20..0113478a 100644 --- a/src/server/database/repositories/user/service.ts +++ b/src/server/database/repositories/user/service.ts @@ -58,6 +58,17 @@ export class UserService { this.#statements = createPreparedStatement(db); } + #createTotp(user: Pick) { + return new TOTP({ + issuer: 'wg-easy', + label: user.username, + algorithm: 'SHA1', + digits: 6, + period: 30, + secret: user.totpKey!, + }); + } + async getAll() { return this.#statements.findAll.execute(); } @@ -160,18 +171,8 @@ export class UserService { return { success: false, error: 'UNEXPECTED_ERROR' }; } - const totp = new TOTP({ - issuer: 'wg-easy', - label: txUser.username, - algorithm: 'SHA1', - digits: 6, - period: 30, - secret: txUser.totpKey, - }); - - const valid = totp.validate({ token: code, window: 1 }); - - if (valid === null) { + const totp = this.#createTotp(txUser); + if (totp.validate({ token: code, window: 1 }) === null) { return { success: false, error: 'INVALID_TOTP_CODE' }; } } @@ -199,18 +200,8 @@ export class UserService { throw new Error('TOTP key is not set'); } - const totp = new TOTP({ - issuer: 'wg-easy', - label: txUser.username, - algorithm: 'SHA1', - digits: 6, - period: 30, - secret: txUser.totpKey, - }); - - const valid = totp.validate({ token: code, window: 1 }); - - if (valid === null) { + const totp = this.#createTotp(txUser); + if (totp.validate({ token: code, window: 1 }) === null) { throw new Error('Invalid TOTP code'); } diff --git a/src/server/routes/metrics/prometheus.get.ts b/src/server/routes/metrics/prometheus.get.ts index 9a1447ca..23a1b638 100644 --- a/src/server/routes/metrics/prometheus.get.ts +++ b/src/server/routes/metrics/prometheus.get.ts @@ -6,14 +6,12 @@ export default defineMetricsHandler('prometheus', async ({ event }) => { async function getPrometheusResponse() { const wgInterface = await Database.interfaces.get(); const clients = await WireGuard.getAllClients(); - let wireguardPeerCount = 0; let wireguardEnabledPeersCount = 0; let wireguardConnectedPeersCount = 0; const wireguardSentBytes = []; const wireguardReceivedBytes = []; const wireguardLatestHandshakeSeconds = []; for (const client of clients) { - wireguardPeerCount++; if (client.enabled === true) { wireguardEnabledPeersCount++; } @@ -41,7 +39,7 @@ async function getPrometheusResponse() { const returnText = [ '# HELP wireguard_configured_peers', '# TYPE wireguard_configured_peers gauge', - `wireguard_configured_peers{${id}} ${wireguardPeerCount}`, + `wireguard_configured_peers{${id}} ${clients.length}`, '', '# HELP wireguard_enabled_peers', '# TYPE wireguard_enabled_peers gauge', diff --git a/src/server/utils/cache.ts b/src/server/utils/cache.ts index 96ea8c2d..000ca6e3 100644 --- a/src/server/utils/cache.ts +++ b/src/server/utils/cache.ts @@ -6,7 +6,7 @@ type Opts = { }; /** - * Cache function for 1 hour + * Cache the result of a function for the given expiry time */ export function cacheFunction(fn: () => T, { expiry }: Opts): () => T { let cache: { value: T; expiry: number } | null = null; diff --git a/src/server/utils/password.ts b/src/server/utils/password.ts index 915795c7..f007471e 100644 --- a/src/server/utils/password.ts +++ b/src/server/utils/password.ts @@ -28,11 +28,7 @@ export function isValidPasswordHash(hash: string): boolean { try { const obj = deserialize(hash); - if (obj.id !== 'argon2i' && obj.id !== 'argon2d' && obj.id !== 'argon2id') { - return false; - } - - return true; + return obj.id === 'argon2i' || obj.id === 'argon2d' || obj.id === 'argon2id'; } catch { return false; } diff --git a/src/server/utils/session.ts b/src/server/utils/session.ts index 1a144cea..15e0ae97 100644 --- a/src/server/utils/session.ts +++ b/src/server/utils/session.ts @@ -70,26 +70,21 @@ export async function getCurrentUser(event: H3Event) { }); } - // TODO: timing can be used to enumerate usernames - const foundUser = await Database.users.getByUsername(username); - if (!foundUser) { - throw createError({ - statusCode: 401, - statusMessage: 'Session failed', - }); - } - - const userHashPassword = foundUser.password; + // Always verify password to prevent timing-based username enumeration + const userHashPassword = + foundUser?.password ?? + '$argon2id$v=19$m=65536,t=3,p=4$aaaaaaaaaaaaaaaa$bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; const passwordValid = await isPasswordValid(password, userHashPassword); - if (!passwordValid) { + if (!foundUser || !passwordValid) { throw createError({ statusCode: 401, statusMessage: 'Session failed', }); } + user = foundUser; } else { throw createError({