Repository Public
1import { Notice, PageHead, Panel } from "@/components/studio/ui";2import { requireRole } from "@/lib/auth";3import { changePassword, confirmTotp, disableTotp, startTotp, totpQr } from "../../actions";45export const metadata = { title: "Account" };6export default async function Account({ searchParams }: { searchParams: Promise<{ ok?: string; err?: string; first?: string; enroll?: string }> }) {7 const sp = await searchParams;8 const user = await requireRole("viewer", { allowPasswordChange: true });9 const qr = sp.enroll && !user.must_change_password ? await totpQr() : null;10 return (11 <>12 <PageHead title="Account" sub={user.email} />13 <Notice ok={sp.ok} err={sp.err ?? (user.must_change_password ? "Choose your own password before using the studio. The one-time password stops working once you do." : undefined)} />14 <div className="grid gap-5 lg:grid-cols-2">15 <Panel title="Password">16 <form action={changePassword} className="space-y-4">17 <div><label htmlFor="current" className="label">Current password</label><input id="current" name="current" type="password" autoComplete="current-password" required className="field mt-1" /></div>18 <div><label htmlFor="next" className="label">New password (12 characters or more)</label><input id="next" name="next" type="password" autoComplete="new-password" minLength={12} required className="field mt-1" /></div>19 <div><label htmlFor="confirm" className="label">Repeat the new password</label><input id="confirm" name="confirm" type="password" autoComplete="new-password" minLength={12} required className="field mt-1" /></div>20 <button className="btn btn-primary">Change password</button>21 </form>22 </Panel>23 <Panel title="Two-factor authentication (TOTP)">24 {user.must_change_password ? <p className="text-sm text-[var(--color-muted)]">Change your password first.</p> : user.totp_enabled ? (25 <form action={disableTotp} className="space-y-4">26 <p className="text-sm text-[var(--color-ok)]">On. A code from your authenticator app is required at every sign-in.</p>27 <div><label htmlFor="pw" className="label">Password (to turn it off)</label><input id="pw" name="password" type="password" autoComplete="current-password" required className="field mt-1" /></div>28 <button className="btn btn-danger">Turn off</button>29 </form>30 ) : qr ? (31 <form action={confirmTotp} className="space-y-4">32 <p className="text-sm text-[var(--color-muted)]">Scan this code with an authenticator app (1Password, Authy, Google Authenticator…), then enter the 6-digit code it shows.</p>33 <div className="inline-block rounded-xl bg-white p-3" dangerouslySetInnerHTML={{ __html: qr.svg }} aria-label="QR code for the authenticator app" role="img" />34 <p className="text-xs text-[var(--color-faint)]">Manual key: <span className="mono break-all">{qr.secret}</span></p>35 <div><label htmlFor="code" className="label">Code</label><input id="code" name="code" inputMode="numeric" autoComplete="one-time-code" required className="field mt-1 max-w-[12rem] text-center text-xl tracking-[0.3em]" /></div>36 <button className="btn btn-primary">Turn on</button>37 </form>38 ) : (39 <form action={startTotp} className="space-y-4">40 <p className="text-sm text-[var(--color-warn)]">Off. Anyone with your password can enter the studio and spend the generation budget.</p>41 <button className="btn btn-primary">Set up two-factor authentication</button>42 </form>43 )}44 </Panel>45 </div>46 </>47 );48}49