Skip to content

Release/v0.1.0 - #1

Merged
abdullahwaqar merged 8 commits into
masterfrom
release/v0.1.0
Oct 27, 2026
Merged

abdullahwaqar merged 8 commits into
masterfrom
release/v0.1.0

Conversation

@abdullahwaqar

Copy link
Copy Markdown
Member

No description provided.

New keys use the format <prefix>_<key_id>_<secret>: a 16 hex char key_id
(64 bits) and a 48 hex char secret (192 bits) from crypto/rand. Keys are
parsed from the right, so prefixes may contain single underscores and an
empty prefix is allowed.

Keys are stored as SHA-256 digests and compared in constant time. With
192 bits of entropy a slow hash adds nothing, and bcrypt both costs ~80ms
per verify and silently truncates input past 72 bytes. Legacy bcrypt
hashes are still accepted so existing keys keep working.
Load every setting from the environment in one place and fail fast with
a combined error listing what is wrong. AUTHCELL_ADMIN_TOKEN is required,
must be at least 32 characters, and may not be the .env.example
placeholder.

The database URL is built with net/url so passwords containing special
characters are escaped, and DATABASE_URL can override the DB_* values.
PORT now defaults to 8080 instead of binding a random port when unset.
…/list

Security
- Require "Authorization: Bearer <AUTHCELL_ADMIN_TOKEN>" on every
  management route. Previously anyone who could reach the port could
  create, list and revoke keys.
- Optionally protect /v1/keys/verify with AUTHCELL_VERIFY_TOKEN.
- Never serialize key_hash (GET /v1/keys/:id returned it).
- Return generic messages to clients and log the real error server side.
- Only reveal revoked/expired/rate_limited after the caller proves they
  hold the key; everything else is "invalid".
- CORS is opt-in via CORS_ORIGINS.

Fixes
- /v1/health no longer calls log.Fatalf when the database is down; it
  returns 503.
- Key listing queried a nonexistent "api_keys" table and was never routed.
- Revoking an unknown id returned 200; it now returns 404 (400 for a
  malformed id).
- Usage rows for unknown keys inserted "" into a uuid column and failed.
- Rows with NULL columns were silently dropped from listings.
- Empty listings returned null instead of [].
- Key id collisions surfaced as 500s; ids are now 64 bits with a retry.

Features
- GET /v1/keys with limit/offset/active/prefix filters.
- PATCH /v1/keys/:id for name, metadata, expires_at (null clears) and
  rate_limit.
- POST /v1/keys/:id/rotate issues a replacement and revokes the old key.
- Per-key rate limits over RATE_LIMIT_WINDOW, done in one atomic UPDATE
  so they hold across instances, with X-RateLimit-* and Retry-After.
- Verify accepts X-API-Key and returns key id, prefix, name, metadata,
  expiry and remaining quota.
- Legacy bcrypt hashes are upgraded to SHA-256 on first successful use.
- Cursor pagination for usage and audit logs; event_type filter.
- Hourly pruning of usage rows past USAGE_RETENTION_DAYS (default 90).

Internals
- Switch from database/sql to pgxpool; all queries take a request
  context with REQUEST_TIMEOUT.
- Versioned migrations under a Postgres advisory lock. Migration 1 is the
  original schema (IF NOT EXISTS) so v0.0.x databases upgrade in place.
- slog JSON logs with request ids, recover middleware, 64KB body limit,
  security headers and JSON 404s.
- Graceful shutdown closes the pool; -version and -healthcheck flags.
- Target Go 1.24 and drop testcontainers; integration tests run against
  AUTHCELL_TEST_DATABASE_URL and skip when it is unset.

BREAKING CHANGE: management endpoints require the admin bearer token,
rate_limit is now enforced, revoke returns the key record, and
audit_log.actor_id changes from uuid to text.
- Send the admin token (AUTHCELL_ADMIN_TOKEN) and optional verify token
  (AUTHCELL_VERIFY_TOKEN), read from the environment or passed in.
- verifyKey / verify_key return { valid, reason } for bad keys instead of
  throwing, so callers can branch on the result.
- Fix revoke always throwing because the response had no data field.
- Add listKeys, updateKey, rotateKey and health, with typed responses.
- Raise AuthCellError carrying the HTTP status and response body.
- Add request timeouts and trim trailing slashes from the base URL.
- Node: add package.json and tsconfig.json so the client builds and
  ships type declarations.

BREAKING CHANGE: verify no longer throws on 401/429, and revoke returns
the revoked key.
- Build a static binary with the version baked in and run it on
  distroless as non-root, with a HEALTHCHECK using -healthcheck.
- Add .dockerignore; .env was being copied into the build context.
- Compose always points the app at the postgres service. With
  DB_HOST=localhost from .env the app was trying to reach itself.
- Fail early when AUTHCELL_ADMIN_TOKEN or DB_PASSWORD is unset.
- Pin postgres:17-alpine and publish it on 127.0.0.1 only.
- Document every setting in .env.example.
- CI reads the Go version from go.mod (it used 1.22 while go.mod required
  1.25.3), runs gofmt/vet, runs unit and integration tests against a
  Postgres service, and builds the Docker image.
- Release workflow installed a GoReleaser version named after the git
  tag; pin it to "~> v2" and fetch full history for changelogs.
- GoReleaser injected the version into a placeholder package path; set
  main.version instead, and ship LICENSE and .env.example in archives.
- Makefile: add lint and itest, bake the version into builds, simplify
  the docker and watch targets.
Document the key format, authentication, every endpoint and response,
all configuration, both clients, development workflow and the upgrade
path from v0.0.x. Fix the broken Python example and code fences.
@abdullahwaqar
abdullahwaqar merged commit 12ebd2a into master Oct 27, 2026
2 checks passed
abdullahwaqar added a commit that referenced this pull request Oct 27, 2026
@abdullahwaqar
abdullahwaqar deleted the release/v0.1.0 branch October 27, 2026 20:41
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