Bumps the go-modules-updates group in /backend with 5 updates: | Package | From | To | | --- | --- | --- | | [github.com/PuerkitoBio/goquery](https://github.com/PuerkitoBio/goquery) | `1.10.0` | `1.10.1` | | [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) | `5.1.0` | `5.2.0` | | [github.com/go-pkgz/rest](https://github.com/go-pkgz/rest) | `1.19.0` | `1.20.2` | | [golang.org/x/image](https://github.com/golang/image) | `0.22.0` | `0.23.0` | | [golang.org/x/net](https://github.com/golang/net) | `0.31.0` | `0.33.0` | Updates `github.com/PuerkitoBio/goquery` from 1.10.0 to 1.10.1 - [Release notes](https://github.com/PuerkitoBio/goquery/releases) - [Commits](https://github.com/PuerkitoBio/goquery/compare/v1.10.0...v1.10.1) Updates `github.com/go-chi/chi/v5` from 5.1.0 to 5.2.0 - [Release notes](https://github.com/go-chi/chi/releases) - [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md) - [Commits](https://github.com/go-chi/chi/compare/v5.1.0...v5.2.0) Updates `github.com/go-pkgz/rest` from 1.19.0 to 1.20.2 - [Release notes](https://github.com/go-pkgz/rest/releases) - [Commits](https://github.com/go-pkgz/rest/compare/v1.19.0...v1.20.2) Updates `golang.org/x/image` from 0.22.0 to 0.23.0 - [Commits](https://github.com/golang/image/compare/v0.22.0...v0.23.0) Updates `golang.org/x/net` from 0.31.0 to 0.33.0 - [Commits](https://github.com/golang/net/compare/v0.31.0...v0.33.0) --- updated-dependencies: - dependency-name: github.com/PuerkitoBio/goquery dependency-type: direct:production update-type: version-update:semver-patch dependency-group: go-modules-updates - dependency-name: github.com/go-chi/chi/v5 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates - dependency-name: github.com/go-pkgz/rest dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates - dependency-name: golang.org/x/image dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-modules-updates ... Signed-off-by: dependabot[bot] <support@github.com>
164 lines
5.4 KiB
Go
164 lines
5.4 KiB
Go
package rest
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/subtle"
|
|
"encoding/base64"
|
|
"net/http"
|
|
|
|
"golang.org/x/crypto/argon2"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
const baContextKey = "authorizedWithBasicAuth"
|
|
|
|
// BasicAuth middleware requires basic auth and matches user & passwd with client-provided checker
|
|
func BasicAuth(checker func(user, passwd string) bool) func(http.Handler) http.Handler {
|
|
|
|
return func(h http.Handler) http.Handler {
|
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
u, p, ok := r.BasicAuth()
|
|
if !ok {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
return
|
|
}
|
|
if !checker(u, p) {
|
|
w.WriteHeader(http.StatusForbidden)
|
|
return
|
|
}
|
|
h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), contextKey(baContextKey), true)))
|
|
}
|
|
return http.HandlerFunc(fn)
|
|
}
|
|
}
|
|
|
|
// BasicAuthWithUserPasswd middleware requires basic auth and matches user & passwd with client-provided values
|
|
func BasicAuthWithUserPasswd(user, passwd string) func(http.Handler) http.Handler {
|
|
checkFn := func(reqUser, reqPasswd string) bool {
|
|
matchUser := subtle.ConstantTimeCompare([]byte(user), []byte(reqUser))
|
|
matchPass := subtle.ConstantTimeCompare([]byte(passwd), []byte(reqPasswd))
|
|
return matchUser == 1 && matchPass == 1
|
|
}
|
|
return BasicAuth(checkFn)
|
|
}
|
|
|
|
// BasicAuthWithBcryptHash middleware requires basic auth and matches user & bcrypt hashed password
|
|
func BasicAuthWithBcryptHash(user, hashedPassword string) func(http.Handler) http.Handler {
|
|
checkFn := func(reqUser, reqPasswd string) bool {
|
|
if reqUser != user {
|
|
return false
|
|
}
|
|
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(reqPasswd))
|
|
return err == nil
|
|
}
|
|
return BasicAuth(checkFn)
|
|
}
|
|
|
|
// BasicAuthWithArgon2Hash middleware requires basic auth and matches user & argon2 hashed password
|
|
// both hashedPassword and salt must be base64 encoded strings
|
|
// Uses Argon2id with parameters: t=1, m=64*1024 KB, p=4 threads
|
|
func BasicAuthWithArgon2Hash(user, hashedPassword, salt string) func(http.Handler) http.Handler {
|
|
checkFn := func(reqUser, reqPasswd string) bool {
|
|
if reqUser != user {
|
|
return false
|
|
}
|
|
|
|
saltBytes, err := base64.StdEncoding.DecodeString(salt)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
storedHashBytes, err := base64.StdEncoding.DecodeString(hashedPassword)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
hash := argon2.IDKey([]byte(reqPasswd), saltBytes, 1, 64*1024, 4, 32)
|
|
return subtle.ConstantTimeCompare(hash, storedHashBytes) == 1
|
|
}
|
|
return BasicAuth(checkFn)
|
|
}
|
|
|
|
// IsAuthorized returns true is user authorized.
|
|
// it can be used in handlers to check if BasicAuth middleware was applied
|
|
func IsAuthorized(ctx context.Context) bool {
|
|
v := ctx.Value(contextKey(baContextKey))
|
|
return v != nil && v.(bool)
|
|
}
|
|
|
|
// BasicAuthWithPrompt middleware requires basic auth and matches user & passwd with client-provided values
|
|
// If the user is not authorized, it will prompt for basic auth
|
|
func BasicAuthWithPrompt(user, passwd string) func(http.Handler) http.Handler {
|
|
checkFn := func(reqUser, reqPasswd string) bool {
|
|
matchUser := subtle.ConstantTimeCompare([]byte(user), []byte(reqUser))
|
|
matchPass := subtle.ConstantTimeCompare([]byte(passwd), []byte(reqPasswd))
|
|
return matchUser == 1 && matchPass == 1
|
|
}
|
|
|
|
return func(h http.Handler) http.Handler {
|
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
// extract basic auth from request
|
|
u, p, ok := r.BasicAuth()
|
|
if ok && checkFn(u, p) {
|
|
h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), contextKey(baContextKey), true)))
|
|
return
|
|
}
|
|
// not authorized, prompt for basic auth
|
|
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
}
|
|
return http.HandlerFunc(fn)
|
|
}
|
|
}
|
|
|
|
// BasicAuthWithBcryptHashAndPrompt middleware requires basic auth and matches user & bcrypt hashed password
|
|
// If the user is not authorized, it will prompt for basic auth
|
|
func BasicAuthWithBcryptHashAndPrompt(user, hashedPassword string) func(http.Handler) http.Handler {
|
|
checkFn := func(reqUser, reqPasswd string) bool {
|
|
if reqUser != user {
|
|
return false
|
|
}
|
|
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(reqPasswd))
|
|
return err == nil
|
|
}
|
|
|
|
return func(h http.Handler) http.Handler {
|
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
|
// extract basic auth from request
|
|
u, p, ok := r.BasicAuth()
|
|
if ok && checkFn(u, p) {
|
|
h.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), contextKey(baContextKey), true)))
|
|
return
|
|
}
|
|
// not authorized, prompt for basic auth
|
|
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
}
|
|
return http.HandlerFunc(fn)
|
|
}
|
|
}
|
|
|
|
// GenerateBcryptHash generates a bcrypt hash from a password
|
|
func GenerateBcryptHash(password string) (string, error) {
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(hash), nil
|
|
}
|
|
|
|
// GenerateArgon2Hash generates an argon2 hash and salt from a password
|
|
func GenerateArgon2Hash(password string) (hash, salt string, err error) {
|
|
saltBytes := make([]byte, 16)
|
|
if _, err := rand.Read(saltBytes); err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
// using recommended parameters: time=1, memory=64*1024, threads=4, keyLen=32
|
|
hashBytes := argon2.IDKey([]byte(password), saltBytes, 1, 64*1024, 4, 32)
|
|
|
|
return base64.StdEncoding.EncodeToString(hashBytes), base64.StdEncoding.EncodeToString(saltBytes), nil
|
|
}
|