From 0790f37f5e1ac138202893d8c4794029e6cf1dc3 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Mon, 3 Nov 2025 17:03:46 -0800 Subject: [PATCH] hash device codes --- server/routers/auth/pollDeviceWebAuth.ts | 14 +++++++++++++- server/routers/auth/startDeviceWebAuth.ts | 16 ++++++++++++++-- server/routers/auth/verifyDeviceWebAuth.ts | 14 +++++++++++++- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/server/routers/auth/pollDeviceWebAuth.ts b/server/routers/auth/pollDeviceWebAuth.ts index 21740344..ef749c30 100644 --- a/server/routers/auth/pollDeviceWebAuth.ts +++ b/server/routers/auth/pollDeviceWebAuth.ts @@ -11,6 +11,8 @@ import { createSession, generateSessionToken } from "@server/auth/sessions/app"; +import { encodeHexLowerCase } from "@oslojs/encoding"; +import { sha256 } from "@oslojs/crypto/sha2"; const paramsSchema = z.object({ code: z.string().min(1, "Code is required") @@ -18,6 +20,13 @@ const paramsSchema = z.object({ export type PollDeviceWebAuthParams = z.infer; +// Helper function to hash device code before querying database +function hashDeviceCode(code: string): string { + return encodeHexLowerCase( + sha256(new TextEncoder().encode(code)) + ); +} + export type PollDeviceWebAuthResponse = { verified: boolean; token?: string; @@ -68,11 +77,14 @@ export async function pollDeviceWebAuth( const now = Date.now(); const requestIp = extractIpFromRequest(req); + // Hash the code before querying + const hashedCode = hashDeviceCode(code); + // Find the code in the database const [deviceCode] = await db .select() .from(deviceWebAuthCodes) - .where(eq(deviceWebAuthCodes.code, code)) + .where(eq(deviceWebAuthCodes.code, hashedCode)) .limit(1); if (!deviceCode) { diff --git a/server/routers/auth/startDeviceWebAuth.ts b/server/routers/auth/startDeviceWebAuth.ts index 45106b82..8897e73f 100644 --- a/server/routers/auth/startDeviceWebAuth.ts +++ b/server/routers/auth/startDeviceWebAuth.ts @@ -10,6 +10,8 @@ import { alphabet, generateRandomString } from "oslo/crypto"; import { createDate } from "oslo"; import { TimeSpan } from "oslo"; import { maxmindLookup } from "@server/db/maxmind"; +import { encodeHexLowerCase } from "@oslojs/encoding"; +import { sha256 } from "@oslojs/crypto/sha2"; const bodySchema = z.object({ deviceName: z.string().optional(), @@ -30,6 +32,13 @@ function generateDeviceCode(): string { return `${part1}-${part2}`; } +// Helper function to hash device code before storing in database +function hashDeviceCode(code: string): string { + return encodeHexLowerCase( + sha256(new TextEncoder().encode(code)) + ); +} + // Helper function to extract IP from request function extractIpFromRequest(req: Request): string | undefined { const ip = req.ip || req.socket.remoteAddress; @@ -99,6 +108,9 @@ export async function startDeviceWebAuth( // Generate device code const code = generateDeviceCode(); + // Hash the code before storing in database + const hashedCode = hashDeviceCode(code); + // Extract IP from request const ip = extractIpFromRequest(req); @@ -108,9 +120,9 @@ export async function startDeviceWebAuth( // Set expiration to 5 minutes from now const expiresAt = createDate(new TimeSpan(5, "m")).getTime(); - // Insert into database + // Insert into database (store hashed code) await db.insert(deviceWebAuthCodes).values({ - code, + code: hashedCode, ip: ip || null, city: city || null, deviceName: deviceName || null, diff --git a/server/routers/auth/verifyDeviceWebAuth.ts b/server/routers/auth/verifyDeviceWebAuth.ts index d8837591..c9284925 100644 --- a/server/routers/auth/verifyDeviceWebAuth.ts +++ b/server/routers/auth/verifyDeviceWebAuth.ts @@ -7,12 +7,21 @@ import logger from "@server/logger"; import { response } from "@server/lib/response"; import { db, deviceWebAuthCodes } from "@server/db"; import { eq, and, gt } from "drizzle-orm"; +import { encodeHexLowerCase } from "@oslojs/encoding"; +import { sha256 } from "@oslojs/crypto/sha2"; const bodySchema = z.object({ code: z.string().min(1, "Code is required"), verify: z.boolean().optional().default(false) // If false, just check and return metadata }).strict(); +// Helper function to hash device code before querying database +function hashDeviceCode(code: string): string { + return encodeHexLowerCase( + sha256(new TextEncoder().encode(code)) + ); +} + export type VerifyDeviceWebAuthBody = z.infer; export type VerifyDeviceWebAuthResponse = { @@ -49,13 +58,16 @@ export async function verifyDeviceWebAuth( logger.debug("Verifying device web auth code:", { code }); + // Hash the code before querying + const hashedCode = hashDeviceCode(code); + // Find the code in the database that is not expired and not already verified const [deviceCode] = await db .select() .from(deviceWebAuthCodes) .where( and( - eq(deviceWebAuthCodes.code, code), + eq(deviceWebAuthCodes.code, hashedCode), gt(deviceWebAuthCodes.expiresAt, now), eq(deviceWebAuthCodes.verified, false) )