// Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package vcweb import ( "encoding/json" "fmt" "io" "log" "net/http" "os" "path" "strings" ) // authHandler serves requests only if the Basic Auth data sent with the request // matches the contents of a ".access" file in the requested directory. // // For each request, the handler looks for a file named ".access" and parses it // as a JSON-serialized accessToken. If the credentials from the request match // the accessToken, the file is served normally; otherwise, it is rejected with // the StatusCode and Message provided by the token. type authHandler struct{} type accessToken struct { Username, Password string StatusCode int // defaults to 401. Message string } func (h *authHandler) Available() bool { return true } func (h *authHandler) Handler(dir string, env []string, logger *log.Logger) (http.Handler, error) { fs := http.Dir(dir) handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { urlPath := req.URL.Path if urlPath != "" && strings.HasPrefix(path.Base(urlPath), ".") { http.Error(w, "filename contains leading dot", http.StatusBadRequest) return } f, err := fs.Open(urlPath) if err != nil { if os.IsNotExist(err) { http.NotFound(w, req) } else { http.Error(w, err.Error(), http.StatusInternalServerError) } return } accessDir := urlPath if fi, err := f.Stat(); err == nil && !fi.IsDir() { accessDir = path.Dir(urlPath) } f.Close() var accessFile http.File for { var err error accessFile, err = fs.Open(path.Join(accessDir, ".access")) if err == nil { break } if !os.IsNotExist(err) { http.Error(w, err.Error(), http.StatusInternalServerError) return } if accessDir == "." { http.Error(w, "failed to locate access file", http.StatusInternalServerError) return } accessDir = path.Dir(accessDir) } data, err := io.ReadAll(accessFile) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } var token accessToken if err := json.Unmarshal(data, &token); err != nil { logger.Print(err) http.Error(w, "malformed access file", http.StatusInternalServerError) return } if username, password, ok := req.BasicAuth(); !ok || username != token.Username || password != token.Password { code := token.StatusCode if code == 0 { code = http.StatusUnauthorized } if code == http.StatusUnauthorized { w.Header().Add("WWW-Authenticate", fmt.Sprintf("basic realm=%s", accessDir)) } http.Error(w, token.Message, code) return } http.FileServer(fs).ServeHTTP(w, req) }) return handler, nil }