github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/middlewares/basic_auth.go (about)

     1  package middlewares
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"net/http"
     7  	"os"
     8  	"time"
     9  
    10  	"github.com/cozy/cozy-stack/pkg/config/config"
    11  	"github.com/cozy/cozy-stack/pkg/crypto"
    12  	"github.com/cozy/cozy-stack/pkg/logger"
    13  	"github.com/labstack/echo/v4"
    14  )
    15  
    16  // BasicAuth use HTTP basic authentication to authenticate a user. The secret
    17  // of the user should be stored in a file with the specified name, stored in
    18  // one of the the config.Paths directories.
    19  //
    20  // The format of the secret is the same as our hashed passwords in database: a
    21  // scrypt hash with a salt contained in the value.
    22  func BasicAuth(secretFileName string) echo.MiddlewareFunc {
    23  	check := func(next echo.HandlerFunc, c echo.Context) error {
    24  		if c.QueryParam("Trace") == "true" {
    25  			t := time.Now()
    26  			defer func() {
    27  				elapsed := time.Since(t)
    28  				logger.
    29  					WithDomain("admin").
    30  					WithNamespace("trace").
    31  					Infof("Check basic auth: %v", elapsed)
    32  			}()
    33  		}
    34  
    35  		_, passphrase, ok := c.Request().BasicAuth()
    36  		if !ok {
    37  			return echo.NewHTTPError(http.StatusUnauthorized, "missing basic auth")
    38  		}
    39  
    40  		shadowFile, err := config.FindConfigFile(secretFileName)
    41  		if err != nil {
    42  			return echo.NewHTTPError(http.StatusInternalServerError, err)
    43  		}
    44  
    45  		f, err := os.Open(shadowFile)
    46  		if err != nil {
    47  			return echo.NewHTTPError(http.StatusInternalServerError, err)
    48  		}
    49  		defer f.Close()
    50  
    51  		b, err := io.ReadAll(f)
    52  		if err != nil {
    53  			return echo.NewHTTPError(http.StatusInternalServerError, err)
    54  		}
    55  		b = bytes.TrimSpace(b)
    56  
    57  		needUpdate, err := crypto.CompareHashAndPassphrase(b, []byte(passphrase))
    58  		if err != nil {
    59  			return echo.NewHTTPError(http.StatusForbidden, "bad passphrase")
    60  		}
    61  		if needUpdate {
    62  			logger.
    63  				WithDomain("admin").
    64  				Warnf("Passphrase hash from %q needs update and should be regenerated", secretFileName)
    65  		}
    66  
    67  		return nil
    68  	}
    69  
    70  	return func(next echo.HandlerFunc) echo.HandlerFunc {
    71  		return func(c echo.Context) error {
    72  			if err := check(next, c); err != nil {
    73  				return err
    74  			}
    75  
    76  			return next(c)
    77  		}
    78  	}
    79  }