SPB Git

Repository Public MIT

page.tsx 2.7 KB 42 lines tsx

1import { ActionForm, Field } from "@/components/action-form";2import { dateTime } from "@/lib/format";3import { requireSession } from "@/lib/session";4import { changePasswordAction } from "../actions";56export const metadata = { title: "Account" };78export default async function Page({ searchParams }: { searchParams: Promise<{ first?: string }> }) {9  const { ctx, principal } = await requireSession({ allowPasswordChange: true });10  const first = principal.user.must_change_password || (await searchParams).first === "1";11  const sessions = await ctx.sql<{ id: string; created_at: Date; last_seen_at: Date; ip: string | null; user_agent: string | null }[]>`12    SELECT id, created_at, last_seen_at, ip, user_agent FROM sessions WHERE user_id = ${principal.user.id} AND revoked_at IS NULL AND expires_at > now() ORDER BY created_at DESC LIMIT 20`;13  return (14    <div className="max-w-xl">15      <h1 className="text-2xl font-semibold tracking-tight">Account</h1>16      {first && principal.user.must_change_password && (17        <p role="alert" className="mt-4 rounded-lg border border-warn/50 bg-warn/5 p-4 text-sm">You are using an initial password issued by the operator. Choose your own before doing anything else.</p>18      )}19      <section className="mt-6">20        <h2 className="mb-3 text-base font-semibold">Change password</h2>21        <ActionForm action={changePasswordAction} submit="Change password" className="card space-y-4 p-6">22          <Field label="Current password" name="current" type="password" required autoComplete="current-password" />23          <Field label="New password" name="next" type="password" required minLength={12} maxLength={200} autoComplete="new-password" hint="At least 12 characters. A password manager is strongly recommended." />24          <Field label="Repeat new password" name="confirm" type="password" required minLength={12} autoComplete="new-password" />25        </ActionForm>26        <p className="mt-3 text-xs text-faint">Changing the password signs out every other session. Passkeys and two-factor authentication are planned; until then, use a unique generated password.</p>27      </section>28      <section className="mt-8">29        <h2 className="mb-3 text-base font-semibold">Active sessions</h2>30        <ul className="divide-y divide-line rounded-lg border border-line text-sm">31          {sessions.map((s) => (32            <li key={s.id} className="px-3 py-2">33              <span className="font-mono text-[12px]">{dateTime(s.created_at)}</span> <span className="text-muted">· {s.ip ?? "unknown address"}{s.id === principal.sessionId ? " · this session" : ""}</span>34              <span className="block truncate text-xs text-faint">{s.user_agent}</span>35            </li>36          ))}37        </ul>38      </section>39    </div>40  );41}42
Files main
Menu

Theme