Files
SciMesh/users

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.

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:

  • roleuser 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

# 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

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).