Actually use the auth middleware for assets routes

This commit is contained in:
Brian Picciano 2022-05-19 22:44:33 -06:00
parent 8da42184eb
commit 3664286506
6 changed files with 70 additions and 17 deletions

View File

@ -11,4 +11,9 @@
# If empty then a derived static directory is used # If empty then a derived static directory is used
staticProxyURL = "http://127.0.0.1:4002"; staticProxyURL = "http://127.0.0.1:4002";
# password is "bar". This should definitely be changed for prod.
apiAuthUsers = {
"foo" = "$2a$13$0JdWlUfHc.3XimEMpEu1cuu6RodhUvzD9l7iiAqa4YkM3mcFV5Pxi";
};
} }

View File

@ -32,6 +32,9 @@
# listening # listening
export MEDIOCRE_BLOG_LISTEN_PROTO="${config.listenProto}" export MEDIOCRE_BLOG_LISTEN_PROTO="${config.listenProto}"
export MEDIOCRE_BLOG_LISTEN_ADDR="${config.listenAddr}" export MEDIOCRE_BLOG_LISTEN_ADDR="${config.listenAddr}"
# api
export MEDIOCRE_BLOG_API_AUTH_USERS='${builtins.toJSON config.apiAuthUsers}'
''; '';
build = buildGoModule { build = buildGoModule {

View File

@ -54,6 +54,11 @@ type Params struct {
// reverse-proxied there. // reverse-proxied there.
StaticDir string StaticDir string
StaticProxy *url.URL StaticProxy *url.URL
// AuthUsers keys are usernames which are allowed to edit server-side data,
// and the values are the password hash which accompanies those users. The
// password hash must have been produced by NewPasswordHash.
AuthUsers map[string]string
} }
// SetupCfg implement the cfg.Cfger interface. // SetupCfg implement the cfg.Cfger interface.
@ -176,6 +181,8 @@ func (a *api) handler() http.Handler {
return h return h
} }
auther := NewAuther(a.params.AuthUsers)
mux := http.NewServeMux() mux := http.NewServeMux()
mux.Handle("/", staticHandler) mux.Handle("/", staticHandler)
@ -209,8 +216,12 @@ func (a *api) handler() http.Handler {
v2Mux.Handle("/assets/", http.StripPrefix("/assets", v2Mux.Handle("/assets/", http.StripPrefix("/assets",
apiutil.MethodMux(map[string]http.Handler{ apiutil.MethodMux(map[string]http.Handler{
"GET": a.getPostAssetHandler(), "GET": a.getPostAssetHandler(),
"POST": formMiddleware(a.postPostAssetHandler()), "POST": authMiddleware(auther,
"DELETE": formMiddleware(a.deletePostAssetHandler()), formMiddleware(a.postPostAssetHandler()),
),
"DELETE": authMiddleware(auther,
formMiddleware(a.deletePostAssetHandler()),
),
}), }),
)) ))
v2Mux.Handle("/", a.renderIndexHandler()) v2Mux.Handle("/", a.renderIndexHandler())

View File

@ -3,13 +3,14 @@ package api
import ( import (
"net/http" "net/http"
"github.com/mediocregopher/blog.mediocregopher.com/srv/api/apiutil"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
// NewPasswordHash returns the hash of the given plaintext password, for use // NewPasswordHash returns the hash of the given plaintext password, for use
// with Auther. // with Auther.
func NewPasswordHash(plaintext string) string { func NewPasswordHash(plaintext string) string {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(plaintext), 12) hashedPassword, err := bcrypt.GenerateFromPassword([]byte(plaintext), 13)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -48,9 +49,10 @@ func (a *auther) Allowed(username, password string) bool {
func authMiddleware(auther Auther, h http.Handler) http.Handler { func authMiddleware(auther Auther, h http.Handler) http.Handler {
respondUnauthorized := func(rw http.ResponseWriter) { respondUnauthorized := func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("WWW-Authenticate", `Basic realm="NOPE"`) rw.Header().Set("WWW-Authenticate", `Basic realm="NOPE"`)
rw.WriteHeader(http.StatusUnauthorized) rw.WriteHeader(http.StatusUnauthorized)
apiutil.GetRequestLogger(r).WarnString(r.Context(), "unauthorized")
} }
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
@ -58,12 +60,12 @@ func authMiddleware(auther Auther, h http.Handler) http.Handler {
username, password, ok := r.BasicAuth() username, password, ok := r.BasicAuth()
if !ok { if !ok {
respondUnauthorized(rw) respondUnauthorized(rw, r)
return return
} }
if !auther.Allowed(username, password) { if !auther.Allowed(username, password) {
respondUnauthorized(rw) respondUnauthorized(rw, r)
return return
} }

View File

@ -0,0 +1,23 @@
package main
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/mediocregopher/blog.mediocregopher.com/srv/api"
)
func main() {
fmt.Fprint(os.Stderr, "Password: ")
line, err := bufio.NewReader(os.Stdin).ReadString('\n')
if err != nil {
panic(err)
}
fmt.Println(api.NewPasswordHash(strings.TrimSpace(line)))
}

View File

@ -2,6 +2,7 @@ package main
import ( import (
"context" "context"
"encoding/json"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
@ -55,6 +56,8 @@ func main() {
pathPrefix := cfg.String("path-prefix", "", "Prefix which is optionally applied to all URL paths rendered by the blog") pathPrefix := cfg.String("path-prefix", "", "Prefix which is optionally applied to all URL paths rendered by the blog")
apiAuthUsersStr := cfg.String("api-auth-users", "{}", "JSON object with usernames as values and password hashes (produced by the hash-password binary) as values. Denotes users which are able to edit server-side data")
// initialization // initialization
err := cfg.Init(ctx) err := cfg.Init(ctx)
@ -128,6 +131,11 @@ func main() {
postStore := post.NewStore(postSQLDB) postStore := post.NewStore(postSQLDB)
postAssetStore := post.NewAssetStore(postSQLDB) postAssetStore := post.NewAssetStore(postSQLDB)
var apiAuthUsers map[string]string
if err := json.Unmarshal([]byte(*apiAuthUsersStr), &apiAuthUsers); err != nil {
logger.Fatal(ctx, "unmarshaling -api-auth-users", err)
}
apiParams.Logger = logger.WithNamespace("api") apiParams.Logger = logger.WithNamespace("api")
apiParams.PowManager = powMgr apiParams.PowManager = powMgr
apiParams.PathPrefix = *pathPrefix apiParams.PathPrefix = *pathPrefix
@ -136,6 +144,7 @@ func main() {
apiParams.MailingList = ml apiParams.MailingList = ml
apiParams.GlobalRoom = chatGlobalRoom apiParams.GlobalRoom = chatGlobalRoom
apiParams.UserIDCalculator = chatUserIDCalc apiParams.UserIDCalculator = chatUserIDCalc
apiParams.AuthUsers = apiAuthUsers
logger.Info(ctx, "listening") logger.Info(ctx, "listening")
a, err := api.New(apiParams) a, err := api.New(apiParams)