diff --git a/server/src/env.ts b/server/src/env.ts index 6449730..35f7830 100644 --- a/server/src/env.ts +++ b/server/src/env.ts @@ -2,6 +2,7 @@ export const env = { DATABASE_URL: process.env.DATABASE_URL!, PORT: parseInt(process.env.PORT || "8080"), CORS_ORIGIN: process.env.CORS_ORIGIN || "http://localhost:5173", + INTERNAL_SECRET: process.env.INTERNAL_SECRET || "", }; if (!env.DATABASE_URL) { diff --git a/server/src/middleware/auth.ts b/server/src/middleware/auth.ts index 111a051..1c1a910 100644 --- a/server/src/middleware/auth.ts +++ b/server/src/middleware/auth.ts @@ -3,6 +3,7 @@ import { db } from "../db.js"; import { session as sessionTable, user as userTable } from "../schema.js"; import { eq } from "drizzle-orm"; import { getCookie } from "hono/cookie"; +import { env } from "../env.js"; /** Hono Env type for routes protected by sessionMiddleware */ export type AuthEnv = { @@ -13,16 +14,35 @@ export type AuthEnv = { }; export async function sessionMiddleware(c: Context, next: Next) { - // Extract session token from cookie (same approach as dashboard WS auth) + // ── Internal server-to-server auth (web app → server) ── + const internalSecret = c.req.header("x-internal-secret"); + const internalUserId = c.req.header("x-internal-user-id"); + + if (internalSecret && internalUserId && env.INTERNAL_SECRET && internalSecret === env.INTERNAL_SECRET) { + const users = await db + .select({ id: userTable.id, name: userTable.name, email: userTable.email }) + .from(userTable) + .where(eq(userTable.id, internalUserId)) + .limit(1); + + if (users.length === 0) { + return c.json({ error: "unauthorized" }, 401); + } + + c.set("user", users[0]); + c.set("session", { id: "internal", userId: internalUserId }); + await next(); + return; + } + + // ── Cookie-based auth (browser → server) ── const rawCookie = getCookie(c, "better-auth.session_token"); if (!rawCookie) { - console.log(`[SessionMiddleware] No session cookie. Headers: ${JSON.stringify(Object.fromEntries(c.req.raw.headers.entries())).slice(0, 200)}`); return c.json({ error: "unauthorized" }, 401); } // Token may have a signature appended after a dot — use only the token part const token = rawCookie.split(".")[0]; - console.log(`[SessionMiddleware] cookie prefix: ${rawCookie.slice(0, 20)}... token prefix: ${token.slice(0, 20)}...`); // Direct DB lookup const rows = await db diff --git a/server/src/ws/device.ts b/server/src/ws/device.ts index f26fbf2..6016d90 100644 --- a/server/src/ws/device.ts +++ b/server/src/ws/device.ts @@ -92,12 +92,6 @@ export async function handleDeviceMessage( try { // Hash the incoming key and look it up directly in the DB const hashedKey = await hashApiKey(msg.apiKey); - console.log(`[Device Auth] key prefix: ${msg.apiKey.slice(0, 10)}... hash: ${hashedKey.slice(0, 16)}...`); - - // Debug: list all keys in DB - const allKeys = await db.select({ id: apikey.id, keyPrefix: apikey.start, hash: apikey.key, enabled: apikey.enabled }).from(apikey); - console.log(`[Device Auth] DB has ${allKeys.length} keys:`, allKeys.map(k => `${k.keyPrefix ?? "?"} hash=${k.hash.slice(0, 16)}... enabled=${k.enabled}`)); - const rows = await db .select({ id: apikey.id, userId: apikey.userId, enabled: apikey.enabled, expiresAt: apikey.expiresAt }) .from(apikey) @@ -105,7 +99,6 @@ export async function handleDeviceMessage( .limit(1); if (rows.length === 0 || !rows[0].enabled) { - console.log(`[Device Auth] REJECTED: no matching key found (or disabled)`); ws.send( JSON.stringify({ type: "auth_error", diff --git a/web/src/lib/api/devices.remote.ts b/web/src/lib/api/devices.remote.ts index 06a7025..5092e1a 100644 --- a/web/src/lib/api/devices.remote.ts +++ b/web/src/lib/api/devices.remote.ts @@ -161,15 +161,19 @@ export const listSessionSteps = query( // ─── Commands (write operations) ───────────────────────────── const SERVER_URL = () => env.SERVER_URL || 'http://localhost:8080'; +const INTERNAL_SECRET = () => env.INTERNAL_SECRET || ''; -/** Forward a request to the DroidClaw server with auth cookies */ +/** Forward a request to the DroidClaw server with internal auth */ async function serverFetch(path: string, body: Record) { - const { request } = getRequestEvent(); + const { locals } = getRequestEvent(); + if (!locals.user) throw new Error('unauthorized'); + const res = await fetch(`${SERVER_URL()}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', - cookie: request.headers.get('cookie') ?? '' + 'x-internal-secret': INTERNAL_SECRET(), + 'x-internal-user-id': locals.user.id }, body: JSON.stringify(body) });