13 Commits
Author SHA1 Message Date
marcandClaude Opus 5 7c1bbc8176 refactor(honeybar): HONEYBAR_ORIGIN statt INKNOTE_HONEYBAR_ORIGIN
Dieselben Namen wie in Aether. Die Leiste gehoert keiner App, sie ist ueberall
dieselbe Sache — dann soll sie auch ueberall gleich heissen. Ein zweiter Name
fuer dieselbe Einstellung ist eine Fehlerquelle beim Umziehen und beim Suchen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 12:00:15 +02:00
marcandClaude Opus 5 00b1d4469f feat(honeybar): the HoneyOS bar, loaded at runtime
A strip along the top that leads to the other apps of the same system. It is
served by HoneyOS; InkNote only says where to find it, through
INKNOTE_HONEYBAR_ORIGIN. Empty means no bar and no change at all.

The origin is served by the API rather than built into the page, because an
image is pulled ready-made and a service worker keeps whole responses: anything
frozen into index.html would outlive the setting.

Layout follows the bar through --honeybar-h. The body has overflow:hidden, so
the surplus height would not have scrolled but been cut off at the bottom; and
#root padded itself around the notch that the bar now covers, which would have
counted that gap twice.

Unit 263/263, all fourteen browser suites green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 11:07:43 +02:00
marcandClaude Opus 5 43a98b0972 A second way in: signing in through an identity provider
OpenID Connect, off unless four settings are present. It is a second way to
the same token the password login mints — every route and the WebSocket
still know only a signed JWT — which is why it fits in one file and needs no
new dependency: Node imports a JWK itself, so checking an ID token is a
signature and five claims.

Authorization Code with PKCE, exchanged on the server so the client secret
stays there. The browser comes home with a one-time ticket rather than the
token, because a token in a redirect URL sits in the history, in the proxy's
log and in the referrer of the next request.

Somebody the app has not seen gets an account; an account that already
exists under that name is linked to the identity rather than duplicated, by
subject from then on. Never an administrator. An account made this way has
no password here, and the interface stops offering one — but an admin can
still set one, which is the way back in if the provider is ever gone.

The suite signs in at a provider of its own that signs real RS256 tokens,
and holds down the four ways the journey must fail as well as the way it
must work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:38:24 +02:00
marcandClaude Opus 5 53965281b4 Store imported PDFs as encrypted, deduplicated attachments
Stage two of PDF support: the file can now get in and out. No viewer
yet — that is next.

A PDF is an attachment like any other, so it inherits everything already
built: encryption at rest, naming by content hash, deduplication, the
orphan sweep, and the authenticated retrieval route the client already
caches offline. The note points at it through notes.pdf_attachment_id.

Raw request body rather than the base64 JSON images travel in. A scanned
book is tens of megabytes and base64 adds a third to that, on a path that
carries nothing but the file. The bytes are checked for %PDF- before
anything is written, so the declared content type is never trusted.

Images and PDFs are limited separately — INKNOTE_MAX_IMAGE_MB (25) and
INKNOTE_MAX_PDF_MB (200) — because a 25 MB picture is nearly always a
mistake and a 25 MB document is not. Both configurable; the limits exist
so a runaway upload cannot fill the disk, not to ration anything. The
JSON body limit now follows the image limit instead of being a second
number that could drift away from it.

Verified against a real PDF: uploaded, stored with the INKENC01 header,
returned byte-identical, deduplicated on a second upload, a GIF renamed
to .pdf refused with 415, unauthenticated refused with 401.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 20:06:34 +02:00
marcandClaude Opus 5 982de53c77 Explain the ${VAR:-fallback} syntax where it is actually read
The line reads as if /srv were appended to whatever you set. Says so
right above it now, and drops the comment block that repeated the mkdir
instruction already in the header.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 13:25:09 +02:00
marcandClaude Opus 5 a61e6901b1 Move .env.example where Compose actually looks, and make it true
It sat in backend/ while Compose reads .env from the repository root, so
the "cp .env.example .env" in the compose header pointed at nothing.

It also documented four settings that Compose never forwarded to the
container: ALLOWED_ORIGINS and the three version-retention knobs would
have been set in .env and silently done nothing. They are passed through
now, unset meaning the server's own defaults. Checked both directions:
every variable Compose reads is documented, and nothing documented is
ignored.

Dropped PORT from the example — the bundled nginx proxies to 4000 by
name, so changing it could only break that. Added the data-directory and
uid/gid settings, which were read but undocumented.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 13:09:29 +02:00
marcandClaude Opus 5 d01aa85ff9 Clean up the production compose file
Three leftovers: a named-volume block that nothing has used since the
move to bind mounts, the obsolete version key, and a comment claiming the
admin variables only apply to an empty database — they also decide whom
to promote when upgrading one that has users but no admin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 13:05:21 +02:00
marcandClaude Opus 5 68e184163e 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>
2026-08-08 12:53:08 +02:00
marcandClaude Opus 5 55a6b3caab Correct the registry namespace to the Docker org
The previous commit moved the images to honey/, which was wrong: the
images have lived under the Docker organisation since February, alongside
learning-tracker and whopays, and are linked to this repository there.
Only the placeholder 'marc' was never real.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:05:48 +02:00
marcandClaude Opus 5 dc33d614ad Point the registry at the namespace that actually exists
The images were tagged gitea.../marc/..., a placeholder that was never
changed and is not a Gitea account, so every push failed with a 404 and
the images had in fact never been published. The registry owner is honey.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:02:19 +02:00
marcandClaude Opus 5 92d525b3ab TipTap editor, version history, search, attachments — and a cleanup
The app is meant to run in daily private use now, so this pass was mostly
about the ways it could quietly lose data.

Reliability and security:
- the server no longer invents a JWT_SECRET or ENCRYPTION_KEY when one is
  missing; it refuses to start. The old fallback silently made every stored
  note unreadable after a restart.
- notes are encrypted with AES-256-GCM (v2:); existing CBC bodies still read.
- login is rate limited and compares against a dummy hash when the user does
  not exist, so timing does not reveal who is registered.
- a server response issued before a local save is never adopted. This had
  already eaten drawing strokes and note links; sync.hasPendingWrites() now
  guards every path that takes a body from the server.

Editor and features:
- TipTap replaces the contenteditable editor: tables, task lists, and links
  to other notes and notebooks, with a backlinks bar on the target.
- content format v2 ({v:2,doc}); v1 {html,tasks} bodies migrate on open.
- version history: the state before each write is snapshotted, coalesced
  into 5-minute windows, 50 versions or 90 days per note.
- full-text search runs in the client, because the server cannot read
  encrypted bodies and a server index would defeat the encryption.
- image attachments are stored by SHA-256 and deduplicated; the client
  fetches them authenticated and caches the blob, so they work offline.
- drawings are vector strokes in a fixed 1200x1600 page space and now look
  the same on every screen (0.0-0.2% deviation across four widths).
- login is by username; existing email accounts migrate automatically.

Operations:
- data lives on host bind mounts, not Docker volumes, so Proxmox Backup
  covers it. Back up .env with it — without ENCRYPTION_KEY the dump is
  unreadable.

Structure:
- NoteApp.jsx is gone. App.jsx now holds only where the user is; note data,
  search, layout, history and drag each live behind a hook, and the four
  panes are stateless.
- the whole data layer is TypeScript, with 81 unit tests concentrated on
  the places that have actually broken.
- fixed along the way: the browser back button had a dead press, because
  restoring a history entry pushed a duplicate one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:00:14 +02:00
Honey 443184a6fa added docker files and compose 2026-02-17 21:09:58 +01:00
Honey 4709096ab3 First Commit 2026-02-17 19:25:52 +01:00