Documentation
¶
Index ¶
- Constants
- func DeriveRootToken(bootstrapToken token.SecureToken, signingKey *rsa.PrivateKey) (string, error)
- func GenerateSigningKeys() (*rsa.PrivateKey, error)
- func LoadPrivateKey(path string) (*rsa.PrivateKey, error)
- func SavePrivateKey(key *rsa.PrivateKey, path string) error
- func SignToken(claims jwt.MapClaims, privateKey *rsa.PrivateKey) (string, error)
- func VerifyToken(tokenString string, publicKey *rsa.PublicKey) (jwt.MapClaims, error)
- type AuditStore
- type AuthConfig
- type ConfigManager
- type ErrorResponse
- type JWTConfig
- type Middleware
- func (m *Middleware) JWTAuthenticator(next http.Handler) http.Handler
- func (m *Middleware) JWTVerifier(next http.Handler) http.Handler
- func (m *Middleware) RequireRole(allowedRoles ...string) func(http.Handler) http.Handler
- func (m *Middleware) SetAuditStore(store AuditStore)
- func (m *Middleware) SetServiceAccountStore(store ServiceAccountStoreInterface)
- func (m *Middleware) VerifyToken(tokenString string) (jwt.MapClaims, error)
- type Provider
- type RoleMapping
- type ServiceAccountStoreInterface
- type SessionMiddleware
- type SessionStore
- type UserSessionCreator
Constants ¶
const ( // ClaimsContextKey stores JWT claims in the request context ClaimsContextKey = contextKey("claims") // ErrorContextKey stores verification errors in the request context ErrorContextKey = contextKey("error") )
const ( // ClaimSubject is the subject claim (user ID or "root") ClaimSubject = "sub" // ClaimRole is the role claim ("admin" or "readonly") ClaimRole = "role" // ClaimType is the token type claim ("session", "service-account", or "root") ClaimType = "type" // ClaimIssuedAt is the issued at timestamp claim ClaimIssuedAt = "iat" // ClaimExpiresAt is the expiration timestamp claim (omitted for root tokens) ClaimExpiresAt = "exp" // ClaimJTI is the JWT ID claim for tracking/revocation ClaimJTI = "jti" )
Standard JWT claim names used in Neuwerk tokens
const ( // RoleAdmin grants full API access (all HTTP methods) RoleAdmin = "admin" // RoleReadonly grants read-only access (GET only) RoleReadonly = "readonly" )
Role constants define access levels
const ( // TokenTypeSession represents browser session tokens TokenTypeSession = "session" // TokenTypeServiceAccount represents long-lived API tokens TokenTypeServiceAccount = "service-account" // TokenTypeRoot represents bootstrap-derived admin tokens TokenTypeRoot = "root" )
TokenType constants define token purposes
Variables ¶
This section is empty.
Functions ¶
func DeriveRootToken ¶
func DeriveRootToken(bootstrapToken token.SecureToken, signingKey *rsa.PrivateKey) (string, error)
DeriveRootToken derives a permanent admin JWT from the bootstrap token using HKDF.
The root token provides unrestricted API access and never expires. It's intended for break-glass admin access and initial service account creation.
Derivation properties:
- Deterministic: same bootstrap token always produces same root token
- Domain-separated: uses salt "neuwerk-root-token-v1" to prevent cross-domain attacks
- RFC 5869 compliant: uses HKDF with SHA-256
JWT claims:
- sub: "root" (root user)
- role: "admin" (full access)
- type: "root" (token type)
- iat: issued at timestamp
- jti: derived from HKDF output (for tracking)
- NO exp: root tokens never expire
Security notes:
- New bootstrap token generates new root token (one-to-one binding)
- Root token should only be used for initial setup or emergency access
- Tokens should be stored securely and never logged
Parameters:
- bootstrapToken: 256-bit bootstrap token (base64-encoded)
- signingKey: RSA 4096-bit private key for JWT signing
Returns:
- Signed JWT token string (ready for Authorization header)
- Error if derivation or signing fails
func GenerateSigningKeys ¶
func GenerateSigningKeys() (*rsa.PrivateKey, error)
GenerateSigningKeys generates an RSA 4096-bit key pair for JWT signing Returns the private key (public key accessible via privateKey.Public()) Matches Phase 17 CA key parameters for long-term security
func LoadPrivateKey ¶
func LoadPrivateKey(path string) (*rsa.PrivateKey, error)
LoadPrivateKey loads an RSA private key from a PEM-encoded file Verifies the PEM block type matches "RSA PRIVATE KEY" and parses PKCS1 format
func SavePrivateKey ¶
func SavePrivateKey(key *rsa.PrivateKey, path string) error
SavePrivateKey saves an RSA private key to disk in PEM format with 0600 permissions The key is marshaled using PKCS1 format ("RSA PRIVATE KEY" block type) Parent directory must exist before calling this function
func SignToken ¶
SignToken creates a JWT token signed with RS256 algorithm Claims are passed as jwt.MapClaims for flexibility Returns the signed JWT string ready for transmission
func VerifyToken ¶
VerifyToken verifies a JWT token signature and extracts claims Enforces RS256 algorithm to prevent algorithm confusion attacks Validates expiration if present (root tokens have no expiration) Returns extracted claims on successful verification
Types ¶
type AuditStore ¶
type AuditStore interface {
RecordAuthEvent(entry interface{}) error
}
AuditStore interface for authentication event logging (Phase 29)
type AuthConfig ¶
type AuthConfig struct {
// JWT configuration (signing keys)
JWT JWTConfig `yaml:"jwt"`
// Providers is the list of OIDC/OAuth providers
Providers []Provider `yaml:"providers"`
// RoleMappings defines how provider claims map to Neuwerk roles
RoleMappings []RoleMapping `yaml:"role_mappings"`
}
AuthConfig is the top-level configuration for authentication
func LoadConfig ¶
func LoadConfig(path string) (*AuthConfig, error)
LoadConfig loads and validates authentication configuration from YAML file Environment variables override secrets with pattern: NEUWERK_AUTH_{TYPE}_CLIENT_SECRET
func (*AuthConfig) String ¶
func (c *AuthConfig) String() string
String implements fmt.Stringer with secret redaction for safe logging
func (*AuthConfig) Validate ¶
func (c *AuthConfig) Validate() error
Validate checks AuthConfig for required fields and valid values
type ConfigManager ¶
type ConfigManager struct {
// contains filtered or unexported fields
}
ConfigManager manages auth configuration with hot-reload support
func NewConfigManager ¶
func NewConfigManager(path string) (*ConfigManager, error)
NewConfigManager creates a new config manager with hot-reload support Loads initial config and sets up fsnotify watcher for file changes
func (*ConfigManager) Get ¶
func (m *ConfigManager) Get() *AuthConfig
Get returns the current configuration (thread-safe)
func (*ConfigManager) Start ¶
func (m *ConfigManager) Start(ctx context.Context)
Start begins the file watch loop for hot-reload Runs in background goroutine until context is canceled
type ErrorResponse ¶
ErrorResponse represents an error response in JSON format
type JWTConfig ¶
type JWTConfig struct {
// SigningKeyPath is the path to RSA private key (PEM format)
SigningKeyPath string `yaml:"signing_key_path"`
// PublicKeyPath is the path to RSA public key (PEM format, optional - derived from private)
PublicKeyPath string `yaml:"public_key_path,omitempty"`
}
JWTConfig contains JWT signing key configuration
type Middleware ¶
type Middleware struct {
// contains filtered or unexported fields
}
Middleware provides JWT verification and authorization middleware for Chi
func NewMiddleware ¶
func NewMiddleware(publicKey *rsa.PublicKey) *Middleware
NewMiddleware creates a new JWT middleware with the provided RSA public key
func (*Middleware) JWTAuthenticator ¶
func (m *Middleware) JWTAuthenticator(next http.Handler) http.Handler
JWTAuthenticator enforces that a valid JWT is present in the request context. This middleware should be used after JWTVerifier in the middleware chain. It rejects requests with missing or invalid tokens with a 401 error.
For service account tokens (type:"service-account"), this middleware also:
- Validates the service account has not been revoked
- Tracks usage metadata (last used, IP, endpoints) with 5-minute throttle
func (*Middleware) JWTVerifier ¶
func (m *Middleware) JWTVerifier(next http.Handler) http.Handler
JWTVerifier extracts the JWT token from the Authorization Bearer header and verifies it using the public key. It stores the claims (or error) in the request context for downstream middleware to handle.
This middleware DOES NOT reject requests - it only extracts and verifies. Use JWTAuthenticator to enforce authentication.
func (*Middleware) RequireRole ¶
RequireRole returns a middleware that enforces role-based access control. It checks if the JWT claims contain a role that matches one of the allowed roles. Rejects requests with insufficient permissions with a 403 error.
func (*Middleware) SetAuditStore ¶
func (m *Middleware) SetAuditStore(store AuditStore)
SetAuditStore sets the audit store for authentication event logging (Phase 29). This method should be called after middleware construction when the store becomes available.
func (*Middleware) SetServiceAccountStore ¶
func (m *Middleware) SetServiceAccountStore(store ServiceAccountStoreInterface)
SetServiceAccountStore sets the service account store for revocation checking and usage tracking. This method should be called after middleware construction when the store becomes available. The middleware will check token revocation and track usage for service account tokens.
func (*Middleware) VerifyToken ¶
func (m *Middleware) VerifyToken(tokenString string) (jwt.MapClaims, error)
VerifyToken verifies a JWT token and returns its claims. This is a convenience method that wraps the standalone VerifyToken function.
type Provider ¶
type Provider struct {
// Type identifies the provider: "google", "github", "azure", "okta"
Type string `yaml:"type"`
// Name is the display name for UI
Name string `yaml:"name"`
// ClientID is the OAuth client ID
ClientID string `yaml:"client_id"`
// ClientSecret is the OAuth client secret (prefer environment variable override)
ClientSecret string `yaml:"client_secret,omitempty"`
// IssuerURL is the OIDC issuer URL (for discovery)
IssuerURL string `yaml:"issuer_url"`
// RedirectURL is the OAuth redirect URL
RedirectURL string `yaml:"redirect_url"`
// Scopes are the OAuth scopes to request
Scopes []string `yaml:"scopes"`
}
Provider defines an OIDC or OAuth provider configuration
type RoleMapping ¶
type RoleMapping struct {
// Provider is the provider type this mapping applies to
Provider string `yaml:"provider"`
// AdminEmails defines email addresses that grant admin role
AdminEmails []string `yaml:"admin_emails"`
// AdminGroups defines group names that grant admin role
AdminGroups []string `yaml:"admin_groups"`
// ReadonlyGroups defines group names that grant readonly role
ReadonlyGroups []string `yaml:"readonly_groups"`
}
RoleMapping defines how provider claims map to Neuwerk roles
type ServiceAccountStoreInterface ¶
type ServiceAccountStoreInterface interface {
// Get retrieves a service account by ID
// Returns jetstream.ErrKeyNotFound if the service account is revoked
Get(ctx context.Context, id string) (interface{}, error)
// UpdateUsage updates the service account's usage tracking fields
// Updates LastUsed, LastIP, and LastEndpoints (last 10 unique, FIFO)
UpdateUsage(ctx context.Context, id, ip, endpoint string) error
}
ServiceAccountStoreInterface defines the contract for service account storage operations. This interface enables clean dependency injection without import cycles. Implementations must provide:
- Get: Retrieve service account by ID (returns error if revoked/not found)
- UpdateUsage: Track last used timestamp, IP, and endpoint
The middleware uses this interface to:
- Check if a service account token has been revoked
- Track usage metadata for audit purposes
type SessionMiddleware ¶
type SessionMiddleware struct {
// contains filtered or unexported fields
}
SessionMiddleware provides cookie-based session authentication with sliding window TTL.
func NewSessionMiddleware ¶
func NewSessionMiddleware(store SessionStore) *SessionMiddleware
NewSessionMiddleware creates a new SessionMiddleware with the provided session store.
func (*SessionMiddleware) CreateUserSession ¶
func (m *SessionMiddleware) CreateUserSession(ctx context.Context, data session.UserSession, ttl time.Duration) (string, error)
CreateUserSession creates a new authenticated user session. This delegates to the underlying store if it supports session creation. Returns an error if the store doesn't support CreateUserSession.
func (*SessionMiddleware) ExtendSession ¶
func (m *SessionMiddleware) ExtendSession(next http.Handler) http.Handler
ExtendSession is a middleware that extracts the session cookie, validates the session, and refreshes the session TTL if needed (sliding window expiration).
Flow:
- Extract "neuwerk_session" cookie
- If no cookie or empty, pass to next handler (may be public route or Bearer token auth)
- Get session from store
- If session not found/expired, clear cookie and redirect to login
- Check if session exceeds 7-day limit for non-RememberMe sessions
- Refresh session TTL if LastActive > 5 minutes ago (throttled updates)
- Store session in context as both UserSession and JWT claims (RequireRole compatibility)
- Call next handler
type SessionStore ¶
type SessionStore interface {
GetUserSession(ctx context.Context, sessionID string) (*session.UserSession, error)
UpdateUserSession(ctx context.Context, sessionID string, userSession session.UserSession, ttl time.Duration) error
}
SessionStore interface defines the operations needed for user session management. This allows the middleware to work with any session storage implementation.
type UserSessionCreator ¶
type UserSessionCreator interface {
CreateUserSession(ctx context.Context, data session.UserSession, ttl time.Duration) (string, error)
}
UserSessionCreator is an extended interface for session stores that support user session creation. This is used by token-login to create browser sessions from API tokens.