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

+35 -3
View File
@@ -25,7 +25,8 @@ A cross-platform note-taking application with rich text editing, freehand drawin
- **Conflict-Aware Sync** — Vector clock–based conflict detection; notifies user of merge conflicts
- **WebSocket Real-Time Push** — Changes appear on other devices instantly
- **Encrypted at Rest** — AES-256-CBC encryption for all note content on the server
- **JWT Authentication** — Username + password accounts with bcrypt password hashing
- **JWT Authentication** — Username + password accounts with bcrypt password hashing. No public
sign-up: an administrator creates the accounts
- **Vector Drawings** — Strokes are stored as vectors in one canonical page space, so a drawing looks identical on every screen and stays sharp at any pixel density
### Architecture
@@ -127,16 +128,47 @@ The server runs on port `4000`, the web UI on port `3000`.
| Method | Endpoint | Body | Response |
|--------|-----------------------|-------------------------------|---------------------|
| POST | `/api/auth/register` | `{username, password, displayName}` | `{token, user}` |
| POST | `/api/auth/login` | `{username, password}` | `{token, user}` |
| GET | `/api/auth/me` | — | `{id, username, displayName}` |
| GET | `/api/auth/me` | — | `{id, username, displayName, isAdmin}` |
| PUT | `/api/auth/username` | `{newUsername, password}` | updated user |
| PUT | `/api/auth/password` | `{currentPassword, newPassword}` | `{success}` |
**There is no registration endpoint.** This is a private instance: accounts are created by an
administrator, and nothing on the login screen suggests otherwise.
Usernames are 3–32 characters of letters, digits, `.`, `_` or `-`, and match case-insensitively.
Servers created before this carried e-mail logins; on first start they migrate automatically,
deriving each username from the local part of the address (logged to the console).
### Administration
| Method | Endpoint | Body | Response |
|--------|-----------------------------------|-----------------------------|----------|
| GET | `/api/admin/users` | — | user list with note counts |
| POST | `/api/admin/users` | `{username, password, displayName}` | created user |
| PUT | `/api/admin/users/:id/password` | `{newPassword}` | `{success}` |
| DELETE | `/api/admin/users/:id` | — | `{success}` |
All four require an account flagged `is_admin`; the server checks this itself, so hiding the menu
entry is presentation, not access control. An admin may not delete their own account.
**Where the first admin comes from.** `INKNOTE_ADMIN_USER` and `INKNOTE_ADMIN_PASSWORD` in `.env`:
| Database state | What happens at start |
| --- | --- |
| no users at all | the account is created from both values |
| users, but no admin | `INKNOTE_ADMIN_USER` picks which one is promoted; without it, the oldest |
| an admin exists | both are ignored |
So the variables can never reset an existing password or take rights away — on a running
instance the worst they can do is nothing. Set `INKNOTE_ADMIN_USER` *before* upgrading an older
installation if the oldest account is not the one that should hold the keys.
Deleting an account destroys its notes, notebooks, drawings and version history through
`ON DELETE CASCADE`; the files behind attachments are collected by the hourly orphan sweep. The
deleted person's session stops working immediately: tokens are signed rather than stored, so
`authMiddleware` checks on every request that the account still exists.
### Notebooks
| Method | Endpoint | Description |