Signed-off-by: Max <ai-agent@topdoglabs.com>
This commit is contained in:
parent
2f0f11e111
commit
718683748a
@ -98,6 +98,10 @@ Task and sprint board built with Next.js + Zustand and Supabase-backed API persi
|
||||
- API routes now enforce auth on task read/write/delete (`/api/tasks` returns `401` when unauthenticated).
|
||||
- Added `PATCH /api/auth/account` to update profile and password for the current user.
|
||||
- Session cookie: `gantt_session` (HTTP-only, same-site lax, secure in production).
|
||||
- Optional machine-to-machine auth for API routes:
|
||||
- Set `GANTT_MACHINE_TOKEN` in server env.
|
||||
- Send either `Authorization: Bearer <token>` or `x-gantt-machine-token: <token>`.
|
||||
- Optional identity fields: `GANTT_MACHINE_USER_ID`, `GANTT_MACHINE_USER_NAME`, `GANTT_MACHINE_USER_EMAIL`.
|
||||
- Added `Remember me` in auth forms:
|
||||
- Checked: persistent 30-day cookie/session.
|
||||
- Unchecked: browser-session cookie (clears on browser close), with server-side short-session expiry.
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { randomBytes, createHash } from "crypto";
|
||||
import { cookies } from "next/headers";
|
||||
import { randomBytes, createHash, timingSafeEqual } from "crypto";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
import { getServiceSupabase } from "@/lib/supabase/client";
|
||||
|
||||
const SESSION_COOKIE_NAME = "gantt_session";
|
||||
const SESSION_HOURS_SHORT = 12;
|
||||
const SESSION_DAYS_REMEMBER = 30;
|
||||
const MACHINE_AUTH_HEADER = "x-gantt-machine-token";
|
||||
|
||||
export interface AuthUser {
|
||||
id: string;
|
||||
@ -66,6 +67,19 @@ function hashSessionToken(token: string): string {
|
||||
return createHash("sha256").update(token).digest("hex");
|
||||
}
|
||||
|
||||
function safeTokenEquals(left: string, right: string): boolean {
|
||||
const leftBuffer = Buffer.from(left, "utf8");
|
||||
const rightBuffer = Buffer.from(right, "utf8");
|
||||
if (leftBuffer.length !== rightBuffer.length) return false;
|
||||
return timingSafeEqual(leftBuffer, rightBuffer);
|
||||
}
|
||||
|
||||
function extractBearerToken(authorization: string | null): string | null {
|
||||
if (!authorization) return null;
|
||||
const match = authorization.match(/^Bearer\s+(.+)$/i);
|
||||
return match?.[1]?.trim() || null;
|
||||
}
|
||||
|
||||
async function deleteExpiredSessions() {
|
||||
const supabase = getServiceSupabase();
|
||||
const { error } = await supabase
|
||||
@ -446,7 +460,36 @@ export async function getSessionTokenFromCookies(): Promise<string | null> {
|
||||
return cookieStore.get(SESSION_COOKIE_NAME)?.value ?? null;
|
||||
}
|
||||
|
||||
async function getMachineAuthenticatedUser(): Promise<AuthUser | null> {
|
||||
const configuredToken = process.env.GANTT_MACHINE_TOKEN;
|
||||
if (!configuredToken) return null;
|
||||
|
||||
const requestHeaders = await headers();
|
||||
const headerToken = requestHeaders.get(MACHINE_AUTH_HEADER);
|
||||
const bearerToken = extractBearerToken(requestHeaders.get("authorization"));
|
||||
const incomingToken = bearerToken || headerToken;
|
||||
if (!incomingToken) return null;
|
||||
|
||||
if (!safeTokenEquals(incomingToken, configuredToken)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const machineUserId = process.env.GANTT_MACHINE_USER_ID || "machine-service-user";
|
||||
const machineUserName = process.env.GANTT_MACHINE_USER_NAME || "Mission Control Service";
|
||||
const machineUserEmail = process.env.GANTT_MACHINE_USER_EMAIL || "mission-control@internal.local";
|
||||
|
||||
return {
|
||||
id: machineUserId,
|
||||
name: machineUserName,
|
||||
email: machineUserEmail,
|
||||
createdAt: "1970-01-01T00:00:00.000Z",
|
||||
};
|
||||
}
|
||||
|
||||
export async function getAuthenticatedUser(): Promise<AuthUser | null> {
|
||||
const machineUser = await getMachineAuthenticatedUser();
|
||||
if (machineUser) return machineUser;
|
||||
|
||||
const token = await getSessionTokenFromCookies();
|
||||
if (!token) return null;
|
||||
return getUserBySessionToken(token);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user