Adapted from the auth repository docs. Private repo — documents internal security behavior.
The Black Candle account authentication microservice (Go). Owns signup, email verification, login, sessions, and transactional email for the whole company. Public endpoint: https://auth.blackcandletech.com. The website is the first client; future products (Price Tracker, Lumen, …) authenticate against the same service — one Black Candle account everywhere.
It is also an OAuth 2.0 authorization server (authorization-code + PKCE S256), so third-party and agent clients can get narrowly scoped tokens with user consent.
All endpoints except GET /healthz require the shared service key (X-Api-Key).
| Method | Path | Notes |
|---|---|---|
GET |
/healthz |
200 {"ok":true}, no key needed |
POST |
/v1/register |
{email, password, ip} → 201; 409 already registered; 429 rate-limited |
POST |
/v1/login |
→ {user, session_token, expires_at}; 403 unverified; 429 locked |
POST |
/v1/verify |
Single-use 24h email verification token |
POST |
/v1/resend-verification |
Always 200 — never reveals registration state |
POST |
/v1/logout |
|
GET |
/v1/me |
Bearer session → {user} |
POST |
/v1/tokens |
Mint a personal access token (name + scope) |
GET |
/v1/tokens |
List token metadata (never hashes/plaintext) |
DELETE |
/v1/tokens/{id} |
Revoke; idempotent |
Security behavior: bcrypt cost 12 (min 12 / max 256 chars); password checked before verification state is disclosed; sessions are 32 random bytes (SHA-256 stored, 30-day expiry); 5 bad passwords → 15-minute per-account lockout; per-IP rate buckets; every successful login fires a "New sign-in" email asynchronously.
Authorization-code flow + PKCE S256 (RFC 6749/7636). Discovery at /.well-known/oauth-authorization-server. Three client kinds: static confidential client (e.g. Courier dashboard), DCR public clients (self-register, bct_-prefixed ids, PKCE mandatory), CIMD public clients (metadata-URL client ids, SSRF-protected fetch).
| Scope | Grants |
|---|---|
identity |
Verify account identity incl. email (default) |
courier:messages:read / :write |
Read / send Courier messages as the account |
openmgmt:tasks:read / :write |
Read / manage OpenMGMT work |
Write implies read. Scope vocabulary is a registry (internal/api/oauth_scopes.go) — anything unlisted is rejected. Tokens: 1h access, 30d refresh with rotation and reuse-detection (reuse = theft → whole family revoked).
Long-lived bearer credentials users mint for their agents from the dashboard. Plaintext returned once; only SHA-256 stored. Validate at GET /oauth/userinfo exactly like OAuth tokens. Default 1-year lifetime, max 20 live tokens per user.
Authorization: Bearer <token>.GET https://auth.blackcandletech.com/oauth/userinfo.scope (missing/empty = identity).cmd/authd/ service entrypoint (config, graceful shutdown, hourly purge)
internal/config/ environment-based configuration
internal/store/ SQLite schema + queries
internal/auth/ bcrypt, token generation, email normalization
internal/mail/ SMTP (STARTTLS) verification + new-sign-in emails
internal/ratelimit/ in-memory per-IP fixed-window rate limiting
internal/api/ HTTP JSON API
deploy/ systemd unit + Caddy snippet
Runs as systemd unit bct-authd under user bct-auth, secrets in /etc/bct-authd.env (root-only 0600). Caddy (Docker) terminates TLS for auth.blackcandletech.com and reverse-proxies to the host port; ufw allows that port from Docker networks only. Key env: AUTH_API_KEY (required), SMTP_*, OAUTH_CLIENT_ID/SECRET (empty disables the OAuth provider).
go build ./... && go test ./...
AUTH_API_KEY=dev-key MAIL_DEBUG=1 go run ./cmd/authd
black-candle-technologies/auth (private)https://auth.blackcandletech.com