forgejo-tickets/docs/architecture.md

128 lines
6.5 KiB
Markdown

# Architecture
## Two-Server Design
The application runs two separate HTTP servers from a single binary:
- **Public server** (default `:8080`) — Customer-facing UI: registration, login, ticket management, OAuth callbacks, Forgejo webhooks.
- **Admin server** (default `:8081`) — Internal admin panel: dashboard, user/ticket/repo management. Protected by Tailscale authentication.
Both servers are started as goroutines in `cmd/server/main.go` and share the same database connection, template renderer, and service instances. Graceful shutdown handles SIGINT/SIGTERM with a 30-second timeout.
## Directory Structure
```
cmd/
server/
main.go # Entry point — wires dependencies, starts both servers
internal/
config/
config.go # Loads env vars into Config struct
database/
database.go # PostgreSQL connection (GORM) and migration runner
models/
models.go # GORM models: User, OAuthAccount, Session, Repo, Ticket, TicketComment, EmailToken
auth/
auth.go # Service: register, login, password hashing, session create/destroy
session.go # Session middleware, RequireAuth, CurrentUser helpers
store.go # PGStore — PostgreSQL-backed gorilla/sessions store
tokens.go # Email verification and password reset token generation/redemption
oauth.go # Google and Microsoft OAuth provider setup, FindOrCreateOAuthUser
apple.go # Apple Sign In provider (JWT client secret, ID token parsing)
middleware/
middleware.go # RequestID, Logging, Recovery middleware
csrf.go # CSRF protection via gorilla/csrf adapted for Gin
handlers/
public/
routes.go # Public router setup and route registration
home.go # Landing page
auth.go # Login, register, logout, email verification, password reset
oauth.go # OAuth login/callback for Google, Microsoft, Apple
tickets.go # Ticket list, create, detail, add comment
webhook.go # Forgejo webhook receiver (issue close -> ticket close)
sso.go # JWT SSO handler: Ed25519 token verification, user auto-provisioning
admin/
routes.go # Admin router setup and route registration
auth.go # Tailscale whois-based authentication middleware
dashboard.go # Dashboard with aggregate counts
users.go # User list, detail, create
tickets.go # Ticket list (with status filter), detail, status update
repos.go # Repo list, create, edit
templates/
render.go # Template renderer: parses layouts + partials + pages, injects PageData
funcs.go # Template helper functions
email/
email.go # Postmark email client: verification, password reset, ticket closed, welcome
templates.go # HTML email templates
forgejo/
client.go # Forgejo API client: create issue, create comment
webhook.go # Webhook signature verification (HMAC-SHA256) and payload parsing
web/
templates/
layouts/
base.html # Public page layout
admin.html # Admin page layout
pages/ # Page templates (mapped by path: "login", "tickets/list", "admin/dashboard", etc.)
partials/ # Shared partials (nav, flash messages)
static/
css/
input.css # Tailwind CSS source
output.css # Compiled CSS (generated by build)
```
## Package Responsibilities
| Package | Role |
|---------|------|
| `config` | Reads all configuration from environment variables; validates required fields |
| `database` | Opens the GORM PostgreSQL connection; runs auto-migrations including enum types and custom indexes |
| `models` | Defines all database models and the `AutoMigrate` function |
| `auth` | User registration/login with bcrypt, session management via PostgreSQL-backed gorilla/sessions, email token generation/redemption, OAuth user linking |
| `middleware` | Request ID generation, structured logging, panic recovery, CSRF protection |
| `handlers/public` | All customer-facing HTTP handlers and route wiring |
| `handlers/admin` | All admin HTTP handlers, Tailscale auth middleware, and route wiring |
| `templates` | Parses Go HTML templates with layout/partial/page inheritance; injects current user, CSRF token, and flash messages into every render |
| `email` | Sends transactional emails via Postmark: verification, password reset, ticket closed notification, welcome/account creation |
| `forgejo` | Forgejo API client for creating issues and comments; webhook signature verification and payload parsing |
## Request Lifecycle
### Public Server
```
Request
-> RequestID middleware (generates X-Request-ID header)
-> Logging middleware (logs method, path, status, duration)
-> Recovery middleware (catches panics)
-> Session middleware (loads user from session cookie into context)
-> CSRF middleware (gorilla/csrf — on routes that need it)
-> Handler (processes request)
-> Template render (injects User, CSRFToken, Flash into PageData)
```
The webhook endpoint (`POST /webhooks/forgejo/:repoSlug`) and the SSO endpoint (`GET /sso/:slug`) sit outside the CSRF group since they authenticate via HMAC signature and JWT signature respectively. See [SSO Integration](./SSO_INTEGRATION.md) for the JWT flow.
### Admin Server
```
Request
-> RequestID middleware
-> Logging middleware
-> Recovery middleware
-> Tailscale auth middleware (whois lookup to verify allowed user)
-> Handler
-> Template render
```
The admin server has no session/CSRF middleware — authentication is handled entirely by Tailscale identity.
## Gin + Gorilla Interop
The application uses [Gin](https://github.com/gin-gonic/gin) as the HTTP framework but relies on [gorilla/sessions](https://github.com/gorilla/sessions) for session management and [gorilla/csrf](https://github.com/gorilla/csrf) for CSRF protection.
Gorilla middleware expects `http.ResponseWriter` and `*http.Request`, while Gin provides `*gin.Context`. The CSRF middleware bridges this by wrapping Gin's handler chain as an `http.Handler` and passing `c.Writer` / `c.Request` through. After gorilla/csrf processes the request (potentially adding context values), the modified `*http.Request` is written back to `c.Request` so downstream handlers can access the CSRF token via `csrf.Token(req)`.
See [Configuration](./configuration.md) for environment variable details and [Deployment](./deployment.md) for running the application.