fix(auth): use internal secret for web→server calls instead of cookie forwarding

Cookie forwarding between dash.droidclaw.ai and tunnel.droidclaw.ai was
unreliable. Now the web app passes userId + shared internal secret via
headers. Also removes debug logging from device auth and session middleware.
This commit is contained in:
Sanju Sivalingam
2026-02-18 12:40:49 +05:30
parent 562d4095f0
commit 3bab84f611
4 changed files with 31 additions and 13 deletions

View File

@@ -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) {

View File

@@ -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

View File

@@ -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",