ax(mining): replace prose comments in auth.go with usage examples
All comments on AuthConfig, DefaultAuthConfig, AuthConfigFromEnv, DigestAuth, NewDigestAuth, Stop, Middleware, and all private methods were restating the type signature in prose. Replaced with concrete call-site examples per AX Principle 2 (comments as usage examples). Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
parent
f18991470d
commit
cc8eeab231
1 changed files with 19 additions and 27 deletions
|
|
@ -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[:])
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue