Identity Service
Port :3001. Owns everything about who — users, sessions,
applications for elevated roles, and the platform's moderation
infrastructure.
Responsibilities
- Auth — register, login, refresh with family rotation, logout, email verification, forgot / reset password.
- Profile —
GET /me,PATCH /me,DELETE /me(soft delete with PII anonymization). - Community membership — join, set active community, change primary community (30-day cooldown).
- Applications — the queue where citizens apply to be representatives or organizations. Admins review.
- Content flags — citizens report content; moderators resolve.
- Audit log — the immutable trail of admin actions across every service. The schema is owned here; three services INSERT to it.
- Admin metrics — platform-wide snapshot + per-community drill-down.
- User administration — list, change role, ban, unban.
Package layout
services/identity-service/
├── cmd/server/main.go # DI + Gin bootstrap
├── internal/
│ ├── adminmetrics/ # /admin/metrics + /admin/communities/:id/stats
│ ├── applications/ # Rep + org applications and admin review
│ ├── audit/ # Shared Auditor writer (imported by other services too)
│ ├── auditlogs/ # Read surface for admins
│ ├── auth/ # Register, login, refresh, JWT signing
│ ├── domain/models.go # GORM models + enums
│ ├── flags/ # Content flag queue and moderator resolution
│ ├── middleware/ # JWTAuth, RequireVerified, RequireRole
│ └── users/ # Admin user administration
├── migrations/ # Optional — AutoMigrate handles most of it
└── pkg/
├── config/
├── database/
├── mailer/ # SMTP + console mailers
└── response/
Key domain concepts
Refresh token family rotation (OWASP)
- Every
POST /auth/refreshconsumes the presented token (marksconsumed_at) and issues a fresh one in the same family (family_idis stable). - Presenting an already-consumed token = replay = theft. The
service revokes every row where
family_idmatches, forcing the legitimate user and the attacker to sign in again. - The raw token is 32 bytes of
crypto/randhex — never stored. OnlySHA256(raw)intoken_hash. Leaking the DB can't hijack live sessions.
Application approval flow
- Citizen signs up with
requestedAccountType: REPRESENTATIVEorORGANIZATION. - Their
Userrow starts atapprovalStatus: PENDING; the citizen can act as a citizen while pending. - An admin lists the queue:
GET /admin/applications?status=PENDING. - Admin approves / rejects / requests changes:
PATCH /admin/applications/{kind}/{id}. - On approval, the citizen's
roleandapprovalStatusare updated and a notification fires. - Every review is written to the
application_review_eventstable and to the audit log.
Primary vs. active community
- Primary community is the citizen's home constituency — where they can create issues, petitions, and rep profiles.
- Active community is what they're currently viewing/acting in for signatures, comments, and upvotes.
- Both are stored on the
Userrow. First join sets both. PATCH /auth/me/primary-communityenforces a 30-day cooldown. Returns429withnextEligibleAtif you try earlier.
Ban and self-delete enforcement
bannedAtanddeletedAtonUser. The JWT middleware in every service blocks writes from either.- Refresh path returns
403withACCOUNT_BANNEDorACCOUNT_DELETEDso the client can clear state and stop retrying. - Self-delete anonymizes name and email, revokes every refresh token,
and marks
deleted_at. Content the user authored stays in place with a placeholder author name.
Environment
Required:
DATABASE_URL— the shared Postgres instance.JWT_SECRET— 32+ chars.- Ports —
PORT/IDENTITY_SERVICE_PORT(default3001).
Optional but strongly recommended:
SMTP_HOST/SMTP_PORT/SMTP_USER/SMTP_PASSWORD/SMTP_FROM— enables real email. Without these, the mailer prints to stdout (development mode).APP_URL— used in verification and reset email links.