linstrom/web/public/middleware/authFetchCheck.go
mstar 7eac1db475
All checks were successful
/ docker (push) Successful in 4m12s
Start work on auth fetch middleware
2025-04-16 16:43:57 +02:00

104 lines
3 KiB
Go

package webmiddleware
import (
"net/http"
"net/url"
"regexp"
webutils "git.mstar.dev/mstar/goutils/http"
"git.mstar.dev/mstar/goutils/other"
)
const signatureRegexString = `keyId=([a-zA-Z0-9_\-\.:@/\?&=#%\+\[\]!$\(\)\*,;]+),headers="([a-z0-9-_\(\) ]+)",(?:algorithm="([a-z0-9-])+",)?signature="(.+)"`
var publicPaths = []*regexp.Regexp{
regexp.MustCompile(`/\.well-known/.+^`),
// regexp.MustCompile(``),
}
var signatureRegex = regexp.MustCompile(signatureRegexString)
// Builder for the authorized fetch check middleware.
// forNonGet determines whether the check should happen for non-GET requests.
// forGet determines whether the check should also happen for GET requests.
// forGet being true implicitly sets forNonGet true too.
// Requests to the server actor and other, required public resources
// will never be checked
func BuildAuthorizedFetchCheck(forNonGet bool, forGet bool) webutils.HandlerBuilder {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
// Check always open path first
for _, re := range publicPaths {
if re.Match([]byte(path)) {
h.ServeHTTP(w, r)
return
}
}
// Not an always open path, check methods
if r.Method == "GET" && !forGet {
h.ServeHTTP(w, r)
return
} else if !forGet && !forNonGet {
h.ServeHTTP(w, r)
return
}
// TODO: Perform check here
signatureHeader := r.Header.Get("Signature")
if signatureHeader == "" {
webutils.ProblemDetails(
w,
http.StatusUnauthorized,
"/errors/invalid-auth-signature",
"invalid authorization signature",
other.IntoPointer("Missing Signature header for authorization"),
nil,
)
return
}
match := signatureRegex.FindStringSubmatch(signatureHeader)
if len(match) <= 1 {
webutils.ProblemDetails(
w,
http.StatusUnauthorized,
"/errors/invalid-auth-signature",
"invalid authorization signature",
other.IntoPointer("Invalid signature header format"),
map[string]any{
"used signature regex": signatureRegexString,
"signature format": `keyId="<url to actor with used key>",headers="<list of headers used, including pseudo header (request-target)>",(optional: algorithm="<one of rsa-sha256, hs2019>", (defaults to rsa-sha256 if not set))signature="signed message"`,
},
)
return
}
// fullMatch = match[0]
rawKeyId := match[1]
rawHeaders := match[2]
rawAlgorithm := match[3]
signature := match[4]
_, err := url.Parse(rawKeyId)
if err != nil {
webutils.ProblemDetails(
w,
http.StatusUnauthorized,
"/errors/invalid-auth-signature",
"invalid authorization signature",
other.IntoPointer("keyId must be a valid url"),
nil,
)
return
}
if rawAlgorithm == "" {
rawAlgorithm = "hs2019"
// w.Header().Add("X-Algorithm-Hint", "")
}
_ = rawHeaders
_ = signature
_ = rawAlgorithm
panic("not implemented")
})
}
}