hash device codes

This commit is contained in:
miloschwartz
2025-11-03 17:03:46 -08:00
parent 5746d69f98
commit 0790f37f5e
3 changed files with 40 additions and 4 deletions

View File

@@ -11,6 +11,8 @@ import {
createSession, createSession,
generateSessionToken generateSessionToken
} from "@server/auth/sessions/app"; } from "@server/auth/sessions/app";
import { encodeHexLowerCase } from "@oslojs/encoding";
import { sha256 } from "@oslojs/crypto/sha2";
const paramsSchema = z.object({ const paramsSchema = z.object({
code: z.string().min(1, "Code is required") code: z.string().min(1, "Code is required")
@@ -18,6 +20,13 @@ const paramsSchema = z.object({
export type PollDeviceWebAuthParams = z.infer<typeof paramsSchema>; export type PollDeviceWebAuthParams = z.infer<typeof paramsSchema>;
// Helper function to hash device code before querying database
function hashDeviceCode(code: string): string {
return encodeHexLowerCase(
sha256(new TextEncoder().encode(code))
);
}
export type PollDeviceWebAuthResponse = { export type PollDeviceWebAuthResponse = {
verified: boolean; verified: boolean;
token?: string; token?: string;
@@ -68,11 +77,14 @@ export async function pollDeviceWebAuth(
const now = Date.now(); const now = Date.now();
const requestIp = extractIpFromRequest(req); const requestIp = extractIpFromRequest(req);
// Hash the code before querying
const hashedCode = hashDeviceCode(code);
// Find the code in the database // Find the code in the database
const [deviceCode] = await db const [deviceCode] = await db
.select() .select()
.from(deviceWebAuthCodes) .from(deviceWebAuthCodes)
.where(eq(deviceWebAuthCodes.code, code)) .where(eq(deviceWebAuthCodes.code, hashedCode))
.limit(1); .limit(1);
if (!deviceCode) { if (!deviceCode) {

View File

@@ -10,6 +10,8 @@ import { alphabet, generateRandomString } from "oslo/crypto";
import { createDate } from "oslo"; import { createDate } from "oslo";
import { TimeSpan } from "oslo"; import { TimeSpan } from "oslo";
import { maxmindLookup } from "@server/db/maxmind"; import { maxmindLookup } from "@server/db/maxmind";
import { encodeHexLowerCase } from "@oslojs/encoding";
import { sha256 } from "@oslojs/crypto/sha2";
const bodySchema = z.object({ const bodySchema = z.object({
deviceName: z.string().optional(), deviceName: z.string().optional(),
@@ -30,6 +32,13 @@ function generateDeviceCode(): string {
return `${part1}-${part2}`; 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 // Helper function to extract IP from request
function extractIpFromRequest(req: Request): string | undefined { function extractIpFromRequest(req: Request): string | undefined {
const ip = req.ip || req.socket.remoteAddress; const ip = req.ip || req.socket.remoteAddress;
@@ -99,6 +108,9 @@ export async function startDeviceWebAuth(
// Generate device code // Generate device code
const code = generateDeviceCode(); const code = generateDeviceCode();
// Hash the code before storing in database
const hashedCode = hashDeviceCode(code);
// Extract IP from request // Extract IP from request
const ip = extractIpFromRequest(req); const ip = extractIpFromRequest(req);
@@ -108,9 +120,9 @@ export async function startDeviceWebAuth(
// Set expiration to 5 minutes from now // Set expiration to 5 minutes from now
const expiresAt = createDate(new TimeSpan(5, "m")).getTime(); const expiresAt = createDate(new TimeSpan(5, "m")).getTime();
// Insert into database // Insert into database (store hashed code)
await db.insert(deviceWebAuthCodes).values({ await db.insert(deviceWebAuthCodes).values({
code, code: hashedCode,
ip: ip || null, ip: ip || null,
city: city || null, city: city || null,
deviceName: deviceName || null, deviceName: deviceName || null,

View File

@@ -7,12 +7,21 @@ import logger from "@server/logger";
import { response } from "@server/lib/response"; import { response } from "@server/lib/response";
import { db, deviceWebAuthCodes } from "@server/db"; import { db, deviceWebAuthCodes } from "@server/db";
import { eq, and, gt } from "drizzle-orm"; import { eq, and, gt } from "drizzle-orm";
import { encodeHexLowerCase } from "@oslojs/encoding";
import { sha256 } from "@oslojs/crypto/sha2";
const bodySchema = z.object({ const bodySchema = z.object({
code: z.string().min(1, "Code is required"), code: z.string().min(1, "Code is required"),
verify: z.boolean().optional().default(false) // If false, just check and return metadata verify: z.boolean().optional().default(false) // If false, just check and return metadata
}).strict(); }).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<typeof bodySchema>; export type VerifyDeviceWebAuthBody = z.infer<typeof bodySchema>;
export type VerifyDeviceWebAuthResponse = { export type VerifyDeviceWebAuthResponse = {
@@ -49,13 +58,16 @@ export async function verifyDeviceWebAuth(
logger.debug("Verifying device web auth code:", { code }); 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 // Find the code in the database that is not expired and not already verified
const [deviceCode] = await db const [deviceCode] = await db
.select() .select()
.from(deviceWebAuthCodes) .from(deviceWebAuthCodes)
.where( .where(
and( and(
eq(deviceWebAuthCodes.code, code), eq(deviceWebAuthCodes.code, hashedCode),
gt(deviceWebAuthCodes.expiresAt, now), gt(deviceWebAuthCodes.expiresAt, now),
eq(deviceWebAuthCodes.verified, false) eq(deviceWebAuthCodes.verified, false)
) )