Key-authenticated Textlog server
Cross-repository contract
The server lives at ../gotextlog-srv and the client remains in this repository.
The server implements the API consumed in doc/api.md, except that account
creation and login use SSH Ed25519 keys. The existing email-code methods remain
available in the client for other Textlog instances.
Key authentication uses these endpoints under /api/v1:
POST /auth/key/challengeaccepts{"public_key": <OpenSSH authorized-key string>, "handle": <optional string>}. It rejects algorithms other thanssh-ed25519. An unknown key requires a handle and creates the account only after proof succeeds. It returns{"data":{"challenge_id":string,"message":base64,"expires_at":RFC3339,"registered":bool}}.POST /auth/key/verifyaccepts{"challenge_id":string,"signature":base64}, where signature is the SSH wire encoding ofssh.Signatureover the decoded message. It consumes the challenge and returns the existing session envelope.- The challenge message is server-generated and domain-separated with
gotextlog-auth-v1, the configured public origin, challenge ID, nonce, purpose, and expiry. Challenges expire after five minutes and are single-use.
The TUI discovers ssh-ed25519 public keys from ~/.ssh/*.pub. A selected
public key is signed through SSH_AUTH_SOCK when available, otherwise through
the matching unencrypted private-key file. Encrypted private keys must be loaded
into an agent. The login screen allows switching between existing email-code
login and key login, selecting a discovered key, and entering a handle for
first use.
Bearer sessions remain unchanged. The server returns empty email and false
email_verified fields so the existing current-user decoder remains compatible.
Opaque cursors represent stable (created_at,id) pagination positions.
Ownership and boundaries
gotextlog-srv/internal/storeowns schema execution, SQL, transactions, persistence models, cursor ordering inputs, relationship invariants, and activity records.gotextlog-srv/internal/serverowns HTTP routing, validation, auth middleware, API JSON shapes, URL derivation, cursor encoding, and SSE fanout.gotextlog-srv/internal/keyauthowns challenge message construction and SSH public-key/signature verification.gotextlog/internal/textlogowns wire types and key-auth HTTP methods.gotextlog/internal/sshkeyowns local key discovery and signing.gotextlog/internal/tuiowns login-mode state and presentation.
W1: Complete server
Files: all files under ../gotextlog-srv. Do not edit this repository.
Deliverables:
- Initialize a Go repository and command at
cmd/gotextlog-srv. - Start with a coarse failing HTTP integration test covering registration by key, login, two users, posts/replies, feeds/search, profiles, follow/block, tags, activities, reports, session revocation, and SSE. Make it pass.
- Implement the full endpoint inventory in
doc/api.md, including cursor pagination and response fields consumed by the client. - Use SQLite through cgo. Vendor
github.com/mattn/go-sqlite3, including its amalgamation, and make normal builds use the vendor directory. - Add
doc/schema.mdwith tables, columns, constraints, indexes, ownership, and migration policy. Adddoc/api.mdfor the key-auth difference. - Add migrations, foreign keys, WAL/busy timeout, graceful shutdown, public origin/listen/database flags, JSON errors, and tests.
- Add a justfile and companion scripts under
dev/for build, test, and an end-to-end smoke test.
Verification: CGO_ENABLED=1 go test -race ./..., just build, and the
server smoke script.
W2: Client key-auth transport and local SSH keys
Files: internal/textlog/client.go, internal/textlog/client_test.go,
internal/textlog/types.go, internal/textlog/types_test.go, new
internal/sshkey/*, and go.mod/go.sum. Do not edit TUI, docs, command,
or config files.
Deliverables:
- Start with a coarse failing test that performs challenge and verify against
an
httptest.Serverusing a temporary Ed25519 private key. - Add wire types and client methods for the fixed key-auth contract.
- Discover sorted Ed25519 public keys and sign with an injected signer source; production supports ssh-agent and matching unencrypted private files.
- Keep email-code APIs and types intact.
Verification: go test -race ./internal/textlog ./internal/sshkey.
W3: TUI key-login flow
Depends on W2. Files: internal/tui/backend.go,
internal/tui/backend_test.go, internal/tui/model.go,
internal/tui/model_test.go, and internal/tui/view.go. Do not edit client,
SSH-key, config, docs, or command files.
Deliverables:
- Start with a coarse failing model test covering mode switch, key selection, first-use handle entry, successful signing/login, token save, and account transition.
- Add backend operations that request a challenge, sign its exact message, and verify it.
- Preserve the email-code flow and make key-login errors actionable.
Verification: go test -race ./internal/tui.
Integration and acceptance
Handled after W1-W3. Add durable client API/usage documentation, then run two real TUI backends against a temporary server/database using generated keys. The acceptance test registers two accounts, logs both in, creates a post and reply, follows, observes feeds/activity, searches, blocks, and revokes sessions. Run all tests in both repositories and remove this completed plan.