divan/web/src/lib/auth.ts
Anas Rashid acf7767495 Admin panel: users, password reset on request, disable, roles, delete, audit log (#27)
- API (api/src/admin.ts, admins only): user list and search; password reset generates a temporary
  password shown once and ends the user's sessions; disable/enable (ends sessions, blocks sign-in);
  roles reader/admin; delete; an admin cannot disable, demote or delete themself. Every action is
  written to audit_log (kept when users are deleted).
- First admin from the server: npm run make-admin -- <email> (after signing up).
- Site: /admin (users) and /admin/audit; 'ایڈمن' link in the header for admins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:08:19 +02:00

34 lines
1.6 KiB
TypeScript

// Site side of accounts: the session token lives in an HTTP-only cookie and is sent to the private API.
import type { AstroCookies } from 'astro';
const API = process.env.API_URL ?? 'http://127.0.0.1:4100';
export const COOKIE = 'divan_session';
export type User = { id: number; email: string; role: string; created_at: string };
// call an /api/auth endpoint as the reader (their token, their IP for rate limits)
export async function auth(path: string, opts: { token?: string; body?: object; ip?: string } = {}) {
const res = await fetch(`${API}/api/auth/${path}`, {
method: opts.body || path !== 'me' ? 'POST' : 'GET',
headers: {
...(opts.body && { 'content-type': 'application/json' }),
...(opts.token && { authorization: `Bearer ${opts.token}` }),
...(opts.ip && { 'x-client-ip': opts.ip }),
},
body: opts.body ? JSON.stringify(opts.body) : undefined,
});
return { ok: res.ok, status: res.status, data: await res.json().catch(() => ({})) };
}
export const setSession = (cookies: AstroCookies, token: string, secure: boolean) =>
cookies.set(COOKIE, token, { path: '/', httpOnly: true, sameSite: 'lax', secure, maxAge: 30 * 86400 });
// call any API endpoint as the signed-in reader (admin pages)
export async function asUser(token: string, path: string, body?: object) {
const res = await fetch(`${API}${path}`, {
method: body ? 'POST' : 'GET',
headers: { authorization: `Bearer ${token}`, ...(body && { 'content-type': 'application/json' }) },
body: body ? JSON.stringify(body) : undefined,
});
return { ok: res.ok, status: res.status, data: await res.json().catch(() => ({})) };
}