Documentation
¶
Index ¶
- Constants
- func CheckPassword(plaintext, hash string) error
- func GenerateAPIKey(prefix string) (string, error)
- func GenerateToken() (string, error)
- func GetSessionUserEmail(sm *scs.SessionManager, r *http.Request) string
- func GetSessionUserID(sm *scs.SessionManager, r *http.Request) string
- func HandleLogin(sm *scs.SessionManager) http.HandlerFunc
- func HandleLoginSubmit(sm *scs.SessionManager, authenticateUser PasswordAuthenticator) http.HandlerFunc
- func HandleLogout(sm *scs.SessionManager) http.HandlerFunc
- func HandleOAuthCallback(sm *scs.SessionManager, findOrCreateUser UserFinder) http.HandlerFunc
- func HashPassword(plaintext string) (string, error)
- func IsAPIKey(key string) bool
- func LoginUser(sm *scs.SessionManager, r *http.Request, userID, email string) error
- func LogoutUser(sm *scs.SessionManager, r *http.Request) error
- func NewSessionManager(pool *pgxpool.Pool, isDev bool) *scs.SessionManager
- func RegisterOAuthRoutes(router chi.Router, sm *scs.SessionManager, findOrCreateUser UserFinder, ...)
- func RequireSession(sm *scs.SessionManager) func(http.Handler) http.Handler
- func RoleFromContext(ctx context.Context) string
- func SessionMiddleware(sm *scs.SessionManager) func(http.Handler) http.Handler
- func SetupOAuth(cfg OAuthConfig)
- func TenantFromContext(ctx context.Context) (uuid.UUID, bool)
- func TenantMiddleware(resolver TenantResolver) func(http.Handler) http.Handler
- func UserFromContext(ctx context.Context) uuid.UUID
- func ValidateKeyPrefix(key string) (prefix string, ok bool)
- func WithTenant(ctx context.Context, id uuid.UUID) context.Context
- func WithUserRole(ctx context.Context, userID uuid.UUID, role string) context.Context
- type APIKey
- type APIKeyStore
- type HeaderTenantResolver
- type OAuthConfig
- type PasswordAuthenticator
- type PathTenantResolver
- type SubdomainTenantResolver
- type TenantResolver
- type Token
- type TokenStore
- type UserFinder
Constants ¶
const ( // PrefixLive is the prefix for live-mode API keys. PrefixLive = "forg_live_" // PrefixTest is the prefix for test-mode API keys. PrefixTest = "forg_test_" )
const ( // SessionKeyUserID is the key used to store the authenticated user's ID in // the session. The value is a string UUID. SessionKeyUserID = "user_id" // SessionKeyUserEmail is the key used to store the authenticated user's // email address in the session. SessionKeyUserEmail = "user_email" )
const BcryptCost = 12
BcryptCost is the bcrypt work factor used when hashing passwords. Cost 12 is recommended for 2026 hardware — significantly higher than the default of 10 to slow down offline brute-force attacks.
Variables ¶
This section is empty.
Functions ¶
func CheckPassword ¶
CheckPassword verifies that plaintext matches the stored bcrypt hash. It returns nil on success and bcrypt.ErrMismatchedHashAndPassword when the password does not match. Other bcrypt errors (e.g. malformed hash) are returned as-is.
func GenerateAPIKey ¶
GenerateAPIKey generates a new API key with the given prefix followed by 24 random hex characters, e.g. "forg_live_a1b2c3d4e5f6a1b2c3d4e5f6".
func GenerateToken ¶
GenerateToken generates a cryptographically random 64-character hex string (32 random bytes hex-encoded) suitable for use as a bearer token.
func GetSessionUserEmail ¶
func GetSessionUserEmail(sm *scs.SessionManager, r *http.Request) string
GetSessionUserEmail returns the authenticated user's email address from the session. Returns an empty string when no user is logged in.
func GetSessionUserID ¶
func GetSessionUserID(sm *scs.SessionManager, r *http.Request) string
GetSessionUserID returns the authenticated user's ID string from the session. Returns an empty string when no user is logged in.
func HandleLogin ¶
func HandleLogin(sm *scs.SessionManager) http.HandlerFunc
HandleLogin returns an http.HandlerFunc that renders the login page. The page includes an email/password form and OAuth provider buttons for Google and GitHub.
func HandleLoginSubmit ¶
func HandleLoginSubmit(sm *scs.SessionManager, authenticateUser PasswordAuthenticator) http.HandlerFunc
HandleLoginSubmit returns an http.HandlerFunc that processes an email/password form POST. On success it stores the session and redirects to "/". On failure it re-renders the login page with an error message.
func HandleLogout ¶
func HandleLogout(sm *scs.SessionManager) http.HandlerFunc
HandleLogout returns an http.HandlerFunc that destroys the session and redirects the user to the login page.
func HandleOAuthCallback ¶
func HandleOAuthCallback(sm *scs.SessionManager, findOrCreateUser UserFinder) http.HandlerFunc
HandleOAuthCallback returns an http.HandlerFunc that completes the OAuth2 flow. It:
- Exchanges the OAuth code for a user via gothic.CompleteUserAuth
- Calls findOrCreateUser to obtain the internal user ID
- Stores the user in the SCS session via LoginUser
- Redirects the browser to "/"
func HashPassword ¶
HashPassword hashes plaintext using bcrypt with BcryptCost. It returns an error if plaintext exceeds 72 bytes (the bcrypt truncation limit) or if the hashing operation fails.
func IsAPIKey ¶
IsAPIKey reports whether key starts with a recognised forge API key prefix. It is a convenience wrapper around ValidateKeyPrefix for boolean checks.
func LoginUser ¶
LoginUser writes the user's ID and email into the session. It calls sm.RenewToken first to rotate the session ID and prevent session fixation attacks. Must be called after the session middleware has loaded the session (i.e. inside an HTTP handler, not before SessionMiddleware in the chain).
func LogoutUser ¶
func LogoutUser(sm *scs.SessionManager, r *http.Request) error
LogoutUser destroys the entire session, removing all stored data and invalidating the session cookie on the client side.
func NewSessionManager ¶
func NewSessionManager(pool *pgxpool.Pool, isDev bool) *scs.SessionManager
NewSessionManager creates and configures an SCS session manager backed by PostgreSQL via pgxstore. Sessions are scoped to 24 hours. The cookie is named "forge_session" and is marked httpOnly with SameSite=Lax to prevent CSRF via cross-site requests.
isDev controls Cookie.Secure: set false during local development (HTTP), true in production (HTTPS only).
func RegisterOAuthRoutes ¶
func RegisterOAuthRoutes( router chi.Router, sm *scs.SessionManager, findOrCreateUser UserFinder, authenticateUser PasswordAuthenticator, )
RegisterOAuthRoutes mounts the OAuth2 and email/password auth routes onto the given router. The routes are intentionally registered outside any RequireSession middleware group — placing auth routes inside RequireSession would cause an infinite redirect loop for unauthenticated users.
Routes:
- GET /auth/login -> HandleLogin (renders login page)
- POST /auth/login -> HandleLoginSubmit (processes email/password)
- GET /auth/logout -> HandleLogout
- GET /auth/{provider} -> gothic.BeginAuthHandler (starts OAuth flow)
- GET /auth/{provider}/callback -> HandleOAuthCallback
func RequireSession ¶
RequireSession returns a Chi-compatible middleware that enforces session-based authentication for HTML routes. Unlike the API AuthMiddleware — which returns a 401 JSON response — this middleware redirects unauthenticated users to the login page, matching the UX expectation for browser-rendered pages.
func RoleFromContext ¶
RoleFromContext retrieves the authenticated user's role from the context. Returns an empty string if no role has been stored.
func SessionMiddleware ¶
SessionMiddleware returns an http.Handler middleware that loads the session from the store on each incoming request and saves it back before the response is written. This is the Chi-compatible form of SCS's LoadAndSave middleware.
func SetupOAuth ¶
func SetupOAuth(cfg OAuthConfig)
SetupOAuth registers Google and GitHub OAuth2 providers with Goth and configures gothic.Store with a signed cookie store for OAuth temp state.
OAuth flow:
- gothic.Store holds the short-lived CSRF/state token (30-60 s) in a signed, HttpOnly cookie. This is only required during the OAuth handshake; it is discarded after the callback completes.
- Completed auth sessions are stored in PostgreSQL via SCS + pgxstore (AUTH-03 compliant). The SCS session is loaded by SessionMiddleware.
func TenantFromContext ¶
TenantFromContext retrieves the tenant ID from the context. Returns the tenant ID and true if found, or uuid.Nil and false if not present.
func TenantMiddleware ¶
func TenantMiddleware(resolver TenantResolver) func(http.Handler) http.Handler
TenantMiddleware returns an HTTP middleware that resolves the tenant from each request and stores it in the context via WithTenant. If the resolver returns an error, the middleware responds with 401 Unauthorized and does not call next.
func UserFromContext ¶
UserFromContext retrieves the authenticated user's UUID from the context. Returns uuid.Nil if no user ID has been stored.
func ValidateKeyPrefix ¶
ValidateKeyPrefix checks whether key starts with a recognised forge API key prefix. It returns the matched prefix and true on success, or "" and false if the key does not match any known prefix.
func WithTenant ¶
WithTenant stores a tenant ID in the context. Use TenantFromContext to retrieve it.
Types ¶
type APIKey ¶
type APIKey struct {
ID uuid.UUID
Name string
Key string
Prefix string // "forg_live_" or "forg_test_"
UserID uuid.UUID
Scopes []string
ExpiresAt *time.Time // nullable
RevokedAt *time.Time // nullable
CreatedAt time.Time
}
APIKey represents an API key stored in the database.
type APIKeyStore ¶
type APIKeyStore interface {
// GetByKey looks up an API key by its full value.
GetByKey(ctx context.Context, key string) (*APIKey, error)
// Create creates a new API key for the given user.
Create(ctx context.Context, userID uuid.UUID, name string, prefix string, scopes []string, expiresAt *time.Time) (*APIKey, error)
// Revoke marks a key as revoked by ID.
Revoke(ctx context.Context, keyID uuid.UUID) error
}
APIKeyStore defines the interface for API key persistence. Implementations are provided by the database layer.
type HeaderTenantResolver ¶
type HeaderTenantResolver struct {
// Header is the HTTP header name to read the tenant UUID from.
// Defaults to "X-Tenant-ID" if empty.
Header string
}
HeaderTenantResolver reads the tenant ID from a request header. The Header field defaults to X-Tenant-ID if not set.
type OAuthConfig ¶
type OAuthConfig struct {
Google struct {
ClientID string
ClientSecret string
}
GitHub struct {
ClientID string
ClientSecret string
}
// CallbackBaseURL is the base URL for OAuth callback routes,
// e.g. "http://localhost:8080".
CallbackBaseURL string
// SessionSecret is used to sign the gothic cookie store. Must be a
// sufficiently random secret (32+ bytes recommended).
SessionSecret string
}
OAuthConfig holds OAuth2 provider credentials and settings.
OAuth temp state (the 30-60 second CSRF token used during the OAuth handshake) is stored in a signed cookie via gothic.Store. Completed authentication sessions are stored in PostgreSQL via SCS + pgxstore (AUTH-03 compliant).
type PasswordAuthenticator ¶
type PasswordAuthenticator func(ctx context.Context, email, password string) (userID string, err error)
PasswordAuthenticator verifies an email/password credential pair and returns the internal user ID on success. The implementation is provided by the generated application.
type PathTenantResolver ¶
type PathTenantResolver struct{}
PathTenantResolver extracts the tenant ID from a URL path prefix. It expects paths of the form /tenants/{uuid}/... and strips the prefix before forwarding the request.
type SubdomainTenantResolver ¶
type SubdomainTenantResolver struct{}
SubdomainTenantResolver extracts the tenant ID from the first subdomain segment of the Host header. For example, host "abc123.example.com" extracts "abc123". The subdomain must be a valid UUID.
type TenantResolver ¶
TenantResolver extracts a tenant ID from an HTTP request. Implement this interface to define your tenant identification strategy.
type Token ¶
type Token struct {
ID uuid.UUID
Token string
UserID uuid.UUID
ExpiresAt time.Time
CreatedAt time.Time
}
Token represents a bearer token stored in the database.
type TokenStore ¶
type TokenStore interface {
// GetByToken looks up a token by its value.
GetByToken(ctx context.Context, token string) (*Token, error)
// Create creates a new bearer token for the given user expiring at expiresAt.
Create(ctx context.Context, userID uuid.UUID, expiresAt time.Time) (*Token, error)
// Delete revokes a token by ID.
Delete(ctx context.Context, tokenID uuid.UUID) error
}
TokenStore defines the interface for bearer token persistence. Implementations are provided by the database layer.
type UserFinder ¶
UserFinder maps an OAuth user returned by Goth to an internal user ID. The implementation is provided by the generated application and is responsible for creating the user record if one does not already exist.