Sessions are stored as hashes, so a database dump contains nothing that
can be pasted into a browser. A login that has passed the password but
not the second factor is modelled as a pending session: it expires, is
revocable, and reaches exactly one endpoint.
Signup is invite-only and takes the email address from the invite rather
than the form, so a link cannot be redirected to somebody else. The
first admin is created by an endpoint that refuses once any account
exists, checked and inserted in one transaction because it is
necessarily unauthenticated.
Login failures and forgotten-password requests answer identically
whether or not the address has an account — otherwise the form is an
account-existence oracle. Password rules are a length floor and nothing
else: composition rules push people towards worse passwords.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Initial data model for verdande: users, sessions, invites, projects,
sections, tasks, labels, reminders, comments, attachments, activity
and filters, plus personal API tokens.
Search indexes a generated `fold` column alongside the original text.
FTS5's remove_diacritics does not touch ø and æ — Unicode treats them
as letters rather than accented forms — so without it a Danish user
cannot find "grøn" by typing "gron". MatchExpr quotes every term so
free text containing FTS5 operators cannot reach the query planner as
syntax.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>