74 lines
1.8 KiB
TypeScript
74 lines
1.8 KiB
TypeScript
|
|
import { cookies } from "next/headers";
|
||
|
|
import { PB_URL } from "./pocketbase";
|
||
|
|
import type { AppUser } from "./types";
|
||
|
|
|
||
|
|
const COOKIE_NAME = "pb_auth";
|
||
|
|
const COOKIE_MAX_AGE = 60 * 60 * 24 * 7; // 7 days
|
||
|
|
|
||
|
|
export interface AuthState {
|
||
|
|
user: AppUser | null;
|
||
|
|
token: string | null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Server-side auth: read the PB token from the cookie and verify it against
|
||
|
|
* the PB API. We use plain fetch (not the PocketBase SDK) to avoid Next.js
|
||
|
|
* RSC's auto-cancellation of the SDK's internal fetch.
|
||
|
|
*/
|
||
|
|
export async function getAuth(): Promise<AuthState> {
|
||
|
|
const cookieStore = await cookies();
|
||
|
|
const cookie = cookieStore.get(COOKIE_NAME);
|
||
|
|
if (!cookie?.value) return { user: null, token: null };
|
||
|
|
|
||
|
|
try {
|
||
|
|
const res = await fetch(`${PB_URL}/api/collections/users/auth-refresh`, {
|
||
|
|
method: "POST",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
Authorization: cookie.value,
|
||
|
|
},
|
||
|
|
cache: "no-store",
|
||
|
|
});
|
||
|
|
if (!res.ok) {
|
||
|
|
return { user: null, token: null };
|
||
|
|
}
|
||
|
|
const data = await res.json();
|
||
|
|
const record = data.record;
|
||
|
|
return {
|
||
|
|
user: {
|
||
|
|
id: record.id,
|
||
|
|
email: record.email,
|
||
|
|
name: record.name,
|
||
|
|
verified: record.verified,
|
||
|
|
},
|
||
|
|
token: cookie.value,
|
||
|
|
};
|
||
|
|
} catch {
|
||
|
|
return { user: null, token: null };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function requireAuth(): Promise<AuthState> {
|
||
|
|
const auth = await getAuth();
|
||
|
|
if (!auth.user) {
|
||
|
|
throw new Error("Unauthorized");
|
||
|
|
}
|
||
|
|
return auth;
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function setAuthCookie(token: string) {
|
||
|
|
const cookieStore = await cookies();
|
||
|
|
cookieStore.set(COOKIE_NAME, token, {
|
||
|
|
httpOnly: true,
|
||
|
|
secure: process.env.NODE_ENV === "production",
|
||
|
|
sameSite: "lax",
|
||
|
|
path: "/",
|
||
|
|
maxAge: COOKIE_MAX_AGE,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function clearAuthCookie() {
|
||
|
|
const cookieStore = await cookies();
|
||
|
|
cookieStore.delete(COOKIE_NAME);
|
||
|
|
}
|