Skip to content

Support JSON API v2 and scoped API keys (anytype-heart v0.51.3) - #62

Merged
requilence merged 15 commits into
mainfrom
feat/apiv2-granular-api-keys
Sep 30, 2026
Merged

requilence merged 15 commits into
mainfrom
feat/apiv2-granular-api-keys

Conversation

@requilence

Copy link
Copy Markdown
Contributor

Upgrades the embedded anytype-heart to v0.51.3 (JSON API v2 and per-key access control) and makes the CLI create API keys that work with v2. This is PR 1 of the plan in docs/plans/apiv2-granular-api-keys.md; apikey list columns, apikey update, and --expires / --json come in follow-up PRs.

Breaking changes

  • anytype auth apikey create <name> now requires an explicit choice of spaces (--space <id|name>, repeatable, or --all-spaces) and permission (--read-only or --read-write). There is no default; without both it creates nothing and lists your spaces. Today's behaviour is --all-spaces --read-write.
  • New keys are JsonAPI scope. They work with the JSON API (v2, and v1 when --all-spaces --read-write), but not for direct gRPC calls.
  • Keys created by earlier versions keep working on /v1; /v2 rejects them. The README has a migration section.
  • Building needs Go 1.26.5+ (heart's requirement). CI, release and CodeQL move to Go 1.26; golangci-lint is pinned to v2.12.2.
  • The HTTP API and gRPC-Web now only accept localhost/IP hosts and local origins unless allowed with ANYTYPE_API_ALLOWED_{HOSTS,ORIGINS} / ANYTYPE_GRPCWEB_ALLOWED_{HOSTS,ORIGINS}. gRPC-Web WebSockets are off unless ANYTYPE_GRPCWEB_ENABLE_WEBSOCKETS=1.

Changes

  • Dependencies: heart v0.51.3; replace directives synced with heart's (zeroconf bump, plus previously missing gogo/protobuf, badger, ristretto, go-multiaddr, genproto rpc, dateparse, go-jpeg-image-structure).
  • gRPC-Web: ported heart's proxy policy: reject untrusted origins/hosts before dispatch, WebSockets off by default, Origin carried into the request context.
  • apikey create: resolves spaces to full Ids (Id wins over name, ambiguous names fail, the tech space only by explicit Id, with a warning). Before creating, it checks that the running server supports grants (older servers would silently drop them). After creating, it reads the key back and revokes it if the stored scope or grant differs, or if it can't be verified.
  • Shell mode: flags are reset between commands (previously --space values accumulated across apikey create calls and widened the next key's grant), and lines are split with quote support.
  • Docs: README (new flags, v2 migration, host/origin allowlists), CLAUDE.md.

Testing

  • make build, make test (12 packages), and make lint (0 issues) pass.
  • Unit tests cover grant building and space resolution, the create command's flag rules, create/verify/revoke against a fake server (old server, dropped grant, verification failure, expired deadline, failed cleanup), the gRPC-Web origin policy, and shell flag reset and quoting.
  • Not run live: anytype serve auto-logs in with the stored account, so it wasn't run against a real account. Needs a smoke test on a throwaway account: create a key and call /v2/auth/whoami and /v1/spaces; send gRPC-Web a request with a hostile Origin (expect 403); check the desktop app and web clipper still connect.

Known gaps (deferred)

  • No warning that an --all-spaces --read-write key can reach the tech space through /v1.
  • The gRPC-Web tests don't assert CORS response headers for trusted origins.

Sync replace directives with heart (zeroconf bump, plus previously
missing gogo/protobuf, badger, ristretto, go-multiaddr, genproto rpc,
dateparse, go-jpeg-image-structure). Move CI/release/CodeQL to Go 1.26
and golangci-lint to v2.12.2.
Port anytype-heart's cmd/grpcserver proxy policy: reject untrusted
origins/hosts before dispatch, disable grpc-websockets unless
ANYTYPE_GRPCWEB_ENABLE_WEBSOCKETS=1, honor ANYTYPE_GRPCWEB_ALLOWED_{ORIGINS,HOSTS},
and carry the Origin metadata into the request context.
Require an explicit space choice (--space/--all-spaces) and permission
choice (--read-only/--read-write); resolve spaces to full Ids (Id wins
over name, ambiguous names fail, tech space only by explicit Id); never
build a nil or empty grant; validate key names like the server does.
apikey create now requires --space <id|name>... or --all-spaces, and
--read-only or --read-write, and creates JsonAPI-scope keys that work
with the JSON API v2. Before creating, probe the server for grant
support (old servers would silently drop the grant); after creating,
read the key back and revoke it if its scope or grant differ.
Shell mode reused the command tree, so flag values leaked into later
commands: --space values accumulated across 'apikey create' calls and
widened the next key's grant. Reset every flag before each line, and
split lines with quote support so space names with spaces work.
If reading the new key back fails, re-list and revoke it with a fresh
deadline instead of leaving a live, undisclosed key. Report leftover
keys by name or Id, without wrapping gRPC statuses that GRPCCall would
rewrite into 'anytype is not running'.
# Conflicts:
#	.github/workflows/ci.yml
#	.github/workflows/codeql.yml
#	.github/workflows/release.yml
#	go.mod
#	go.sum
The JSON API starts when an account logs in, on the address that login
carries, so 'serve --listen-address X' followed by a plain 'auth login'
used to put the API on the default address. Now an explicit
--listen-address is remembered in the CLI config (kept across logout)
and used by later commands; login, account creation and serve's
auto-login print 'JSON API listening on ...', and 'auth status' shows it.

serve saves the address only once its servers are listening, and service
install only after a successful install, so a failed start changes
nothing.
serve (and the user service) now prints where the JSON API will listen
as soon as the gRPC servers are up. Previously it only appeared after a
successful auto-login, so a server started without a stored account
key never showed it.
heart applies ANYTYPE_LOG_LEVEL only when an account logs in, so
everything logged before that (including our own startup lines) used
the logger's built-in DEBUG default. Apply the level (default ERROR) as
soon as the server starts.
The address was easy to miss among server logs. serve, service install,
auth login and auth create now print it in a framed banner.
The startup banner now shows just the JSON API address when a stored
account key will be used for auto-login. Without one, it adds the
commands to log in or create a bot account; if auto-login fails, a
second banner says the JSON API isn't running and how to log in.
Brings GO-7552: heart loggers now follow the configured log level, so
'anytype serve' no longer prints DEBUG lines at the default ERROR level.
@requilence
requilence merged commit bb73c27 into main Sep 30, 2026
5 checks passed
@requilence
requilence deleted the feat/apiv2-granular-api-keys branch September 30, 2026 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant