auth

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 23, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Index

Constants

View Source
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_"
)
View Source
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"
)
View Source
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

func CheckPassword(plaintext, hash string) error

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

func GenerateAPIKey(prefix string) (string, error)

GenerateAPIKey generates a new API key with the given prefix followed by 24 random hex characters, e.g. "forg_live_a1b2c3d4e5f6a1b2c3d4e5f6".

func GenerateToken

func GenerateToken() (string, error)

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:

  1. Exchanges the OAuth code for a user via gothic.CompleteUserAuth
  2. Calls findOrCreateUser to obtain the internal user ID
  3. Stores the user in the SCS session via LoginUser
  4. Redirects the browser to "/"

func HashPassword

func HashPassword(plaintext string) (string, error)

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

func IsAPIKey(key string) bool

IsAPIKey reports whether key starts with a recognised forge API key prefix. It is a convenience wrapper around ValidateKeyPrefix for boolean checks.

func LoginUser

func LoginUser(sm *scs.SessionManager, r *http.Request, userID, email string) error

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

func RequireSession(sm *scs.SessionManager) func(http.Handler) http.Handler

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

func RoleFromContext(ctx context.Context) string

RoleFromContext retrieves the authenticated user's role from the context. Returns an empty string if no role has been stored.

func SessionMiddleware

func SessionMiddleware(sm *scs.SessionManager) func(http.Handler) http.Handler

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

func TenantFromContext(ctx context.Context) (uuid.UUID, bool)

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

func UserFromContext(ctx context.Context) uuid.UUID

UserFromContext retrieves the authenticated user's UUID from the context. Returns uuid.Nil if no user ID has been stored.

func ValidateKeyPrefix

func ValidateKeyPrefix(key string) (prefix string, ok bool)

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

func WithTenant(ctx context.Context, id uuid.UUID) context.Context

WithTenant stores a tenant ID in the context. Use TenantFromContext to retrieve it.

func WithUserRole

func WithUserRole(ctx context.Context, userID uuid.UUID, role string) context.Context

WithUserRole stores both a user ID and role in the context. These values can be retrieved with UserFromContext and RoleFromContext.

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.

func (HeaderTenantResolver) Resolve

func (h HeaderTenantResolver) Resolve(r *http.Request) (uuid.UUID, error)

Resolve extracts the tenant ID from the configured header.

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.

func (PathTenantResolver) Resolve

func (p PathTenantResolver) Resolve(r *http.Request) (uuid.UUID, error)

Resolve extracts the tenant UUID from the second path segment (e.g. /tenants/{uuid}/...).

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.

func (SubdomainTenantResolver) Resolve

Resolve extracts the tenant ID from the first subdomain segment of the Host header.

type TenantResolver

type TenantResolver interface {
	Resolve(r *http.Request) (uuid.UUID, error)
}

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

type UserFinder func(ctx context.Context, gothUser goth.User) (userID string, err error)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL