internal/httpapi/notes.go exposes:
- GET /api/notes list summaries {id, title, updated_at}
- GET /api/notes/{id} full {id, title, content, updated_at}
- PUT /api/notes/{id} create/update; ?base=<unix> for
optimistic-locking conflict detection
- DELETE /api/notes/{id} remove; ?base=<unix> guards against
deleting a row modified after the
client last saw it
Backed by tenant.FS so all reads/writes go through the per-user
sandbox — path traversal is rejected at parse time (regex slug)
and again by os.Root inside the FS layer.
On-disk format is plain Markdown: first line `# Title`, rest is
content. grep / cat / vim still produce a usable view of raw
files. Title round-trips through composeNote/splitTitle.
Conflict semantics: when the client supplies ?base=<unix>, the
server compares against the file's mtime. If the file is newer,
respond 409 with the current note body so the client can present
a merge UI. Same logic on DELETE returns 409 alone.
cmd/librenotes/serve.go grows a tenantPool that memoises FS
handles per user id; defer-closes them on shutdown.
Tests cover: full CRUD round-trip, cross-tenant isolation,
unauthenticated 401s, invalid IDs (regex rejection), and the
conflict path with a real mtime advance.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
internal/httpapi/ provides:
- Tenant{UserID, Email} carried on context.Context, with
WithTenant / TenantFrom helpers and ErrNoTenant for the
programming-error case (route reached without middleware).
- AuthMiddleware verifies an Authorization: Bearer <jwt> on every
request via auth.Signer.Verify (which already enforces HS256
and rejects alg=none). On failure: 401, with the underlying
reason logged server-side but not exposed to the client.
- RequireTenantOwnership(ownerID) compares the request's tenant
against the resource owner; returns 403 on mismatch. Handlers
that touch tenant-owned resources call this guard.
- Server.Routes() mounts /auth/* unauthenticated and wraps
/api/* with the middleware. /api/whoami is included as the
canonical example of a tenant-scoped endpoint.
Tests cover: valid JWT pass-through, missing/empty Authorization,
wrong scheme, malformed JWT, tampered signature, JWT signed with
a different secret (cross-tenant key confusion), and the 200/403
matrix for RequireTenantOwnership.
Closes#11.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>