diff --git a/pkg/mining/auth.go b/pkg/mining/auth.go index f680c31..fe726c1 100644 --- a/pkg/mining/auth.go +++ b/pkg/mining/auth.go @@ -16,22 +16,16 @@ import ( "github.com/gin-gonic/gin" ) -// AuthConfig holds authentication configuration +// AuthConfig{Enabled: true, Username: "admin", Password: "secret", Realm: "Mining API"} type AuthConfig struct { - // Enabled determines if authentication is required - Enabled bool - // Username for basic/digest auth - Username string - // Password for basic/digest auth - Password string - // Realm for digest auth - Realm string - // NonceExpiry is how long a nonce is valid + Enabled bool + Username string + Password string + Realm string NonceExpiry time.Duration } -// DefaultAuthConfig returns the default auth configuration. -// Auth is disabled by default for local development. +// cfg := DefaultAuthConfig() // Enabled: false, Realm: "Mining API", NonceExpiry: 5m func DefaultAuthConfig() AuthConfig { return AuthConfig{ Enabled: false, @@ -42,8 +36,7 @@ func DefaultAuthConfig() AuthConfig { } } -// AuthConfigFromEnv creates auth config from environment variables. -// Set MINING_API_AUTH=true to enable, MINING_API_USER and MINING_API_PASS for credentials. +// cfg := AuthConfigFromEnv() // reads MINING_API_AUTH, MINING_API_USER, MINING_API_PASS, MINING_API_REALM func AuthConfigFromEnv() AuthConfig { config := DefaultAuthConfig() @@ -67,7 +60,7 @@ func AuthConfigFromEnv() AuthConfig { return config } -// DigestAuth implements HTTP Digest Authentication middleware +// da := NewDigestAuth(cfg); router.Use(da.Middleware()); defer da.Stop() type DigestAuth struct { config AuthConfig nonces sync.Map // map[string]time.Time for nonce expiry tracking @@ -75,7 +68,7 @@ type DigestAuth struct { stopOnce sync.Once } -// NewDigestAuth creates a new digest auth middleware +// da := NewDigestAuth(AuthConfigFromEnv()); router.Use(da.Middleware()) func NewDigestAuth(config AuthConfig) *DigestAuth { da := &DigestAuth{ config: config, @@ -86,15 +79,14 @@ func NewDigestAuth(config AuthConfig) *DigestAuth { return da } -// Stop gracefully shuts down the DigestAuth, stopping the cleanup goroutine. -// Safe to call multiple times. +// defer da.Stop() // safe to call multiple times; stops the nonce cleanup goroutine func (da *DigestAuth) Stop() { da.stopOnce.Do(func() { close(da.stopChan) }) } -// Middleware returns a Gin middleware that enforces digest authentication +// router.Use(da.Middleware()) // enforces Digest or Basic auth on all routes func (da *DigestAuth) Middleware() gin.HandlerFunc { return func(c *gin.Context) { if !da.config.Enabled { @@ -130,7 +122,7 @@ func (da *DigestAuth) Middleware() gin.HandlerFunc { } } -// sendChallenge sends a 401 response with digest auth challenge +// da.sendChallenge(c) // writes WWW-Authenticate header and 401 JSON response func (da *DigestAuth) sendChallenge(c *gin.Context) { nonce := da.generateNonce() da.nonces.Store(nonce, time.Now()) @@ -150,7 +142,7 @@ func (da *DigestAuth) sendChallenge(c *gin.Context) { }) } -// validateDigest validates a digest auth header +// ok := da.validateDigest(c, c.GetHeader("Authorization")) func (da *DigestAuth) validateDigest(c *gin.Context, authHeader string) bool { params := parseDigestParams(authHeader[7:]) // Skip "Digest " @@ -190,7 +182,7 @@ func (da *DigestAuth) validateDigest(c *gin.Context, authHeader string) bool { return subtle.ConstantTimeCompare([]byte(expectedResponse), []byte(params["response"])) == 1 } -// validateBasic validates a basic auth header +// ok := da.validateBasic(c, c.GetHeader("Authorization")) func (da *DigestAuth) validateBasic(c *gin.Context, authHeader string) bool { // Gin has built-in basic auth, but we do manual validation for consistency user, pass, ok := c.Request.BasicAuth() @@ -205,7 +197,7 @@ func (da *DigestAuth) validateBasic(c *gin.Context, authHeader string) bool { return userMatch && passMatch } -// generateNonce creates a cryptographically random nonce +// nonce := da.generateNonce() // 32-char hex string, cryptographically random func (da *DigestAuth) generateNonce() string { b := make([]byte, 16) if _, err := rand.Read(b); err != nil { @@ -216,12 +208,12 @@ func (da *DigestAuth) generateNonce() string { return hex.EncodeToString(b) } -// generateOpaque creates an opaque value +// opaque := da.generateOpaque() // MD5 of realm, stable per auth instance func (da *DigestAuth) generateOpaque() string { return md5Hash(da.config.Realm) } -// cleanupNonces removes expired nonces periodically +// go da.cleanupNonces() // runs until stopChan is closed; interval = NonceExpiry func (da *DigestAuth) cleanupNonces() { interval := da.config.NonceExpiry if interval <= 0 { @@ -246,7 +238,7 @@ func (da *DigestAuth) cleanupNonces() { } } -// parseDigestParams parses the parameters from a digest auth header +// params := parseDigestParams(authHeader[7:]) // {"nonce": "abc", "uri": "/api", "qop": "auth"} func parseDigestParams(header string) map[string]string { params := make(map[string]string) parts := strings.Split(header, ",") @@ -267,7 +259,7 @@ func parseDigestParams(header string) map[string]string { return params } -// md5Hash returns the MD5 hash of a string as a hex string +// h := md5Hash("user:realm:pass") // "5f4dcc3b5aa765d61d8327deb882cf99" func md5Hash(s string) string { h := md5.Sum([]byte(s)) return hex.EncodeToString(h[:])