Remove public sign-up; accounts are created by an administrator

This is a private instance, so anyone able to reach the login page could
previously give themselves an account. The registration route is gone
rather than hidden, and the login screen no longer hints that accounts
can be made.

Accounts now come from an admin under Account → Administration: create,
delete, and reset a password. Appointing further admins is deliberately
not offered — with one admin it is not needed, and it is the one power
that turns a compromised account into a lost instance.

The first admin comes from INKNOTE_ADMIN_USER / INKNOTE_ADMIN_PASSWORD,
used only when the database has no users, or to choose whom to promote
when an older database has users but no admin. Once an admin exists both
are ignored, so leaving them in .env cannot reset anything.

Two things that would otherwise have been quietly wrong:

- a deleted user's token stayed valid until it expired, because tokens
  are signed rather than stored and there was no session to revoke.
  authMiddleware now checks the account still exists, so an open tab
  stops working the moment the account is deleted.
- deleting an account takes its notes, notebooks, drawings and version
  history with it via ON DELETE CASCADE. Verified: no orphaned rows in
  notes, notebooks, sync_log, attachments or note_versions.

Deletion asks for the username to be typed back, since it destroys data
that version history cannot bring back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-08 12:53:08 +02:00
1 parent 55a6b3caab
commit 68e184163e
12 files changed
+859 -123

No files matched your search

+24 -1
View File
@@ -3,7 +3,7 @@ import * as sync from "../syncEngine";
import { I } from "./icons.jsx";
/* ═══════ ACCOUNT MENU ═══════ */
export function AccountMenu({ user, onClose, onLogout, onUserUpdate, dark }) {
export function AccountMenu({ user, onClose, onLogout, onUserUpdate, onOpenAdmin, dark }) {
const [tab, setTab] = useState("profile"); // profile | username | password
const [displayName, setDisplayName] = useState(user?.displayName || "");
const [newUsername, setNewUsername] = useState(user?.username || "");
@@ -367,6 +367,29 @@ export function AccountMenu({ user, onClose, onLogout, onUserUpdate, dark }) {
)}
</div>
<div style={{ padding: "0 22px 18px" }}>
{user?.isAdmin && (
<button
onClick={onOpenAdmin}
style={{
display: "flex",
alignItems: "center",
gap: 8,
width: "100%",
padding: "10px",
borderRadius: 10,
border: `1px solid ${T.accent}33`,
background: T.accent + "0c",
cursor: "pointer",
color: T.accent,
fontSize: 13,
fontWeight: 600,
justifyContent: "center",
marginBottom: 8,
}}
>
{I.users} Administration
</button>
)}
<button
onClick={onLogout}
style={{