6.5 KiB
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 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 as the HTTP framework but relies on gorilla/sessions for session management and 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 for environment variable details and Deployment for running the application.