cm_bot_v2/web/lib/auth.ts
yiekheng 312cc4dc21 fix(web-auth): gate Secure cookie on CM_DEBUG, pass CM_AGENT creds to web-next
Previously the session cookie used Secure=NODE_ENV==='production', and the
dev override still runs the standalone build with NODE_ENV=production, so
the cookie was unreachable from phone-on-LAN testing over HTTP. Switching
to CM_DEBUG lets dev (CM_DEBUG=true) drop the Secure flag while keeping
prod (CM_DEBUG=false) safe.

Also wires CM_AGENT_ID/CM_AGENT_PASSWORD/CM_DEBUG into the web-next
service env block so the login Server Action can compare against them.
2026-05-03 09:01:35 +08:00

62 lines
1.4 KiB
TypeScript

import "server-only";
import { cookies } from "next/headers";
import { sealData, unsealData } from "iron-session";
const COOKIE_NAME = "cm_auth";
const COOKIE_TTL_SECONDS = 30 * 24 * 60 * 60;
export type Session = {
username: string;
authenticatedAt: number;
pendingChallenge?: {
kind: "register" | "authenticate";
challenge: string;
expiresAt: number;
};
};
function secret(): string {
const s = process.env.CM_AUTH_SECRET;
if (!s || s.length < 32) {
throw new Error("CM_AUTH_SECRET missing or shorter than 32 chars");
}
return s;
}
export async function getSession(): Promise<Session | null> {
const jar = await cookies();
const raw = jar.get(COOKIE_NAME)?.value;
if (!raw) return null;
try {
return await unsealData<Session>(raw, { password: secret() });
} catch {
return null;
}
}
export async function setSession(session: Session): Promise<void> {
const sealed = await sealData(session, {
password: secret(),
ttl: COOKIE_TTL_SECONDS,
});
const jar = await cookies();
jar.set(COOKIE_NAME, sealed, {
httpOnly: true,
secure: process.env.CM_DEBUG !== "true",
sameSite: "lax",
path: "/",
maxAge: COOKIE_TTL_SECONDS,
});
}
export async function clearSession(): Promise<void> {
const jar = await cookies();
jar.delete(COOKIE_NAME);
}
export async function requireSession(): Promise<Session> {
const s = await getSession();
if (!s) throw new Error("Unauthenticated");
return s;
}