# SciMesh userservice Authentication service for SciMesh, in Go on PostgreSQL. It owns user accounts and issues the JWTs the coordinator trusts. It is a **separate bounded context** from the coordinator: its own database, its own binary. The only thing shared between the two services is the JWT signing secret. The versioned external contract is [`docs/user-service-api-contract.md`](../docs/user-service-api-contract.md). Built as a modular monolith following Clean Architecture — one binary, four layers, dependencies pointing strictly inward: ``` infra config, DB pool, clock, HTTP server ← drivers transport HTTP handlers + JWT middleware ← incoming storage SQL repository ← outgoing usecase Register / Login + PORTS (interfaces) ← application rules domain User, Role, invariants ← business rules auth bcrypt hasher, HS256 JWT issuer ← crypto adapters ``` ## Endpoints | Method | Path | Auth | Purpose | |--------|---------------------------|--------------|---------------------------------------------| | GET | `/health` | none | Liveness probe (checks the database) | | POST | `/register` | none | Create an account (always role `user`) | | POST | `/login` | none | Verify credentials, return a signed JWT | | GET | `/me` | Bearer JWT | Return the caller's own account | | POST | `/users/{id}/verify` | Bearer admin | Grant the trusted-contributor badge | | POST | `/users/{id}/unverify` | Bearer admin | Revoke the badge | | POST | `/users/{id}/promote` | Bearer admin | Set the user's role to admin | | POST | `/users/{id}/demote` | Bearer admin | Set the user's role back to user | Two independent attributes live on an account: - **`role`** — `user` or `admin`. Governs what you may do with your own jobs. Registration always creates a `user`; promotion to `admin` is a manual database operation, never a request. - **`verified`** — a boolean trust badge, granted **only by an admin** (the `/verify` endpoints above, 403 for anyone else). It tells the coordinator whether this user's volunteer workers are trusted: a verified contributor's results are accepted directly, an unverified one's must pass quorum cross-checking. Defaults to false. Both attributes ride in the JWT (`role`, `verified` claims), so the coordinator reads them from the signed token without ever calling this service. ## How it connects to the coordinator The coordinator never calls this service at runtime. A client logs in here, gets a JWT, and presents it to the coordinator, which verifies the signature locally with the same `JWT_SECRET` and reads `sub` (the user id) into `jobs.owner_id`. That link is **off by default**: until the coordinator is given a matching `JWT_SECRET`, it accepts only the shared worker token and stores `owner_id` as NULL. Set the same secret (≥ 32 bytes, byte-for-byte identical) on both services to turn it on. ## Run ```sh # whole stack: Postgres + migrations + the service on :8081 make up # or locally against your own Postgres cp .env.example .env # then edit JWT_SECRET and DATABASE_URL make run ``` ## Verify ```sh make test # unit tests make check # vet, lint, race, integration, smoke — needs Docker make smoke # end-to-end against a running service ``` Password hashing uses bcrypt (`golang.org/x/crypto/bcrypt`); the salt and cost are embedded in the stored hash, so there is no separate salt column. Tokens are HS256 (`github.com/golang-jwt/jwt/v5`).