mirror of
https://codeberg.org/Codeberg/pages-server.git
synced 2025-04-24 13:56:57 +00:00
Basic HTTP Auth (#163).
This commit is contained in:
parent
513e79832a
commit
faeb8ae499
4 changed files with 125 additions and 4 deletions
|
@ -78,6 +78,8 @@ func Serve(ctx *cli.Context) error {
|
|||
challengeCache := cache.NewKeyValueCache()
|
||||
// canonicalDomainCache stores canonical domains
|
||||
canonicalDomainCache := cache.NewKeyValueCache()
|
||||
// authCache stores basic HTTP Auth credentials
|
||||
authCache := cache.NewKeyValueCache()
|
||||
// dnsLookupCache stores DNS lookups for custom domains
|
||||
dnsLookupCache := cache.NewKeyValueCache()
|
||||
// clientResponseCache stores responses from the Gitea server
|
||||
|
@ -93,7 +95,7 @@ func Serve(ctx *cli.Context) error {
|
|||
giteaClient,
|
||||
rawInfoPage,
|
||||
BlacklistedPaths, allowedCorsDomains,
|
||||
dnsLookupCache, canonicalDomainCache)
|
||||
dnsLookupCache, canonicalDomainCache, authCache)
|
||||
|
||||
httpHandler := server.SetupHTTPACMEChallengeServer(challengeCache)
|
||||
|
||||
|
|
|
@ -25,12 +25,26 @@ func Handler(mainDomainSuffix, rawDomain string,
|
|||
giteaClient *gitea.Client,
|
||||
rawInfoPage string,
|
||||
blacklistedPaths, allowedCorsDomains []string,
|
||||
dnsLookupCache, canonicalDomainCache cache.SetGetKey,
|
||||
dnsLookupCache, canonicalDomainCache, authCache cache.SetGetKey,
|
||||
) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
log := log.With().Strs("Handler", []string{req.Host, req.RequestURI}).Logger()
|
||||
ctx := context.New(w, req)
|
||||
|
||||
trimmedHost := ctx.TrimHostPort()
|
||||
|
||||
credentials := handleAuth(log, ctx, giteaClient,
|
||||
mainDomainSuffix,
|
||||
trimmedHost,
|
||||
dnsLookupCache, authCache)
|
||||
|
||||
if len(credentials) > 0 {
|
||||
authenticated := enforceBasicHTTPAuth(credentials, w, req)
|
||||
if !authenticated {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.RespWriter.Header().Set("Server", "CodebergPages/"+version.Version)
|
||||
|
||||
// Force new default from specification (since November 2020) - see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#strict-origin-when-cross-origin
|
||||
|
@ -39,8 +53,6 @@ func Handler(mainDomainSuffix, rawDomain string,
|
|||
// Enable browser caching for up to 10 minutes
|
||||
ctx.RespWriter.Header().Set("Cache-Control", "public, max-age=600")
|
||||
|
||||
trimmedHost := ctx.TrimHostPort()
|
||||
|
||||
// Add HSTS for RawDomain and MainDomainSuffix
|
||||
if hsts := getHSTSHeader(trimmedHost, mainDomainSuffix, rawDomain); hsts != "" {
|
||||
ctx.RespWriter.Header().Set("Strict-Transport-Security", hsts)
|
||||
|
@ -109,5 +121,44 @@ func Handler(mainDomainSuffix, rawDomain string,
|
|||
pathElements,
|
||||
dnsLookupCache, canonicalDomainCache)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func enforceBasicHTTPAuth(credentials []string, w http.ResponseWriter, req *http.Request) bool {
|
||||
authorizedUsers := getAuthorizedUsers(credentials)
|
||||
username, password, ok := req.BasicAuth()
|
||||
if !ok {
|
||||
w.Header().Add("WWW-Authenticate", `Basic realm="Give username and password"`)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(`{"message": "No basic auth present"}`))
|
||||
return false
|
||||
}
|
||||
if !isAuthorized(username, password, authorizedUsers) {
|
||||
w.Header().Add("WWW-Authenticate", `Basic realm="Give username and password"`)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(`{"message": "Invalid username or password"}`))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getAuthorizedUsers(credentials []string) map[string]string {
|
||||
authorizedUsers := make(map[string]string)
|
||||
for _, authLine := range credentials {
|
||||
authLineParts := strings.Split(authLine, ",")
|
||||
user := strings.TrimSpace(authLineParts[0])
|
||||
password := strings.TrimSpace(authLineParts[1])
|
||||
authorizedUsers[user] = password
|
||||
}
|
||||
return authorizedUsers
|
||||
|
||||
}
|
||||
|
||||
func isAuthorized(username, password string, credentials map[string]string) bool {
|
||||
pass, ok := credentials[username]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return password == pass
|
||||
}
|
||||
|
|
33
server/handler/handler_auth.go
Normal file
33
server/handler/handler_auth.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"codeberg.org/codeberg/pages/server/cache"
|
||||
"codeberg.org/codeberg/pages/server/context"
|
||||
"codeberg.org/codeberg/pages/server/dns"
|
||||
"codeberg.org/codeberg/pages/server/gitea"
|
||||
"codeberg.org/codeberg/pages/server/upstream"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
func handleAuth(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Client,
|
||||
mainDomainSuffix string,
|
||||
trimmedHost string,
|
||||
dnsLookupCache, authCache cache.SetGetKey,
|
||||
) []string {
|
||||
// Get credentials for a given branch/repo/owner
|
||||
targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, mainDomainSuffix, dnsLookupCache)
|
||||
var credentials []string
|
||||
canonicalLink := false
|
||||
|
||||
// Try to use the given repo on the given branch or the default branch
|
||||
log.Debug().Msg("auth preparations, trying to get credentials")
|
||||
if targetOpt, works := tryBranch(log, ctx, giteaClient, &upstream.Options{
|
||||
TargetOwner: targetOwner,
|
||||
TargetRepo: targetRepo,
|
||||
TargetBranch: targetBranch,
|
||||
}, canonicalLink); works {
|
||||
credentials = targetOpt.CheckAuth(giteaClient, authCache)
|
||||
}
|
||||
|
||||
return credentials
|
||||
}
|
35
server/upstream/auth.go
Normal file
35
server/upstream/auth.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package upstream
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"codeberg.org/codeberg/pages/server/cache"
|
||||
"codeberg.org/codeberg/pages/server/gitea"
|
||||
)
|
||||
|
||||
// authCacheTimeout specifies the timeout for the auth cache.
|
||||
var authCacheTimeout = 5 * time.Minute
|
||||
|
||||
const authConfig = ".auth"
|
||||
|
||||
// CheckAuth returns the username and password for basic HTTP Auth specified in the repo (using the `.auth` file).
|
||||
func (o *Options) CheckAuth(giteaClient *gitea.Client, authCache cache.SetGetKey) []string {
|
||||
var credentials []string
|
||||
if cachedValue, ok := authCache.Get(o.TargetOwner + "/" + o.TargetRepo + "/" + o.TargetBranch); ok {
|
||||
credentials = cachedValue.([]string)
|
||||
} else {
|
||||
body, err := giteaClient.GiteaRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, authConfig)
|
||||
if err == nil {
|
||||
for _, authLine := range strings.Split(string(body), "\n") {
|
||||
credentials = append(credentials, authLine)
|
||||
}
|
||||
} else {
|
||||
log.Error().Err(err).Msgf("could not read %s of %s/%s", authConfig, o.TargetOwner, o.TargetRepo)
|
||||
}
|
||||
authCache.Set(o.TargetOwner+"/"+o.TargetRepo+"/"+o.TargetBranch, credentials, authCacheTimeout)
|
||||
}
|
||||
return credentials
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue