Files
SciMesh/users
Efremenko Arhip 0c1f5f06d4 chore(users): remove coordinator leftovers, tidy for userservice
- delete api/requests.http and ARCHITECTURE.md (coordinator content)
- rewrite README.md and .env.example for the userservice
- drop dead RunPeriodic (reaper machinery userservice has no use for)
- fix .gitignore/.dockerignore/.golangci.yml module + artifact names
- degeneralize stale copied comments that said "coordinator"
2026-07-26 16:36:17 +03:00
..
2026-07-26 14:38:24 +03:00
2026-07-26 14:38:24 +03:00
2026-07-26 14:38:24 +03:00
2026-07-26 14:38:24 +03:00
2026-07-26 14:38:24 +03:00
2026-07-26 14:38:24 +03:00
2026-07-26 14:38:24 +03:00
2026-07-26 14:38:24 +03:00

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

Roles are user and admin. Registration always creates a user; promotion to admin is a manual database operation, never a request. The role→permission mapping lives in the coordinator's authorization checks, not in a table.

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