Add basic authentication and remove legacy html responses

This commit is contained in:
Neale Pickett 2019-02-21 23:00:06 +00:00
parent 54fffc5b77
commit e68a041f33
4 changed files with 81 additions and 79 deletions

View File

@ -1,7 +1,9 @@
FROM alpine:3.8 AS builder
RUN apk --no-cache add go libc-dev
COPY src /src
RUN go build -o /mothd /src/*.go
FROM alpine:3.9 AS builder
RUN apk --no-cache add go libc-dev git
COPY src /root/go/src/github.com/dirtbags/moth/src
WORKDIR /root/go/src/github.com/dirtbags/moth/src
RUN go get .
RUN go build -o /mothd *.go
FROM alpine
COPY --from=builder /mothd /mothd

View File

@ -21,14 +21,20 @@ type JSendData struct {
Description string `json:"description"`
}
// ShowJSend renders a JSend response to w
func ShowJSend(w http.ResponseWriter, status Status, short string, description string) {
type Status int
const (
Success = iota
Fail
Error
)
func respond(w http.ResponseWriter, req *http.Request, status Status, short string, format string, a ...interface{}) {
resp := JSend{
Status: "success",
Data: JSendData{
Short: short,
Description: description,
Description: fmt.Sprintf(format, a...),
},
}
switch status {
@ -51,59 +57,6 @@ func ShowJSend(w http.ResponseWriter, status Status, short string, description s
w.Write(respBytes)
}
type Status int
const (
Success = iota
Fail
Error
)
// ShowHtml delevers an HTML response to w
func ShowHtml(w http.ResponseWriter, status Status, title string, body string) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
statusStr := ""
switch status {
case Success:
statusStr = "Success"
case Fail:
statusStr = "Fail"
default:
statusStr = "Error"
}
fmt.Fprintf(w, "<!DOCTYPE html>")
fmt.Fprintf(w, "<!-- If you put `application/json` in the `Accept` header of this request, you would have gotten a JSON object instead of HTML. -->\n")
fmt.Fprintf(w, "<html><head>")
fmt.Fprintf(w, "<title>%s</title>", title)
fmt.Fprintf(w, "<link rel=\"stylesheet\" href=\"basic.css\">")
fmt.Fprintf(w, "<meta name=\"viewport\" content=\"width=device-width\">")
fmt.Fprintf(w, "<link rel=\"icon\" href=\"res/icon.svg\" type=\"image/svg+xml\">")
fmt.Fprintf(w, "<link rel=\"icon\" href=\"res/icon.png\" type=\"image/png\">")
fmt.Fprintf(w, "</head><body><h1 class=\"%s\">%s</h1>", statusStr, title)
fmt.Fprintf(w, "<section>%s</section>", body)
fmt.Fprintf(w, "<nav>")
fmt.Fprintf(w, "<ul>")
fmt.Fprintf(w, "<li><a href=\"puzzle-list.html\">Puzzles</a></li>")
fmt.Fprintf(w, "<li><a href=\"scoreboard.html\">Scoreboard</a></li>")
fmt.Fprintf(w, "</ul>")
fmt.Fprintf(w, "</nav>")
fmt.Fprintf(w, "</body></html>")
}
func respond(w http.ResponseWriter, req *http.Request, status Status, short string, format string, a ...interface{}) {
long := fmt.Sprintf(format, a...)
// This is a kludge. Do proper parsing when this causes problems.
accept := req.Header.Get("Accept")
if strings.Contains(accept, "application/json") {
ShowJSend(w, status, short, long)
} else {
ShowHtml(w, status, short, long)
}
}
// hasLine returns true if line appears in r.
// The entire line must match.
func hasLine(r io.Reader, line string) bool {
@ -301,11 +254,53 @@ func (ctx *Instance) staticHandler(w http.ResponseWriter, req *http.Request) {
http.ServeContent(w, req, path, d.ModTime(), f)
}
func (ctx *Instance) BindHandlers(mux *http.ServeMux) {
mux.HandleFunc(ctx.Base+"/", ctx.staticHandler)
mux.HandleFunc(ctx.Base+"/register", ctx.registerHandler)
mux.HandleFunc(ctx.Base+"/answer", ctx.answerHandler)
mux.HandleFunc(ctx.Base+"/content/", ctx.contentHandler)
mux.HandleFunc(ctx.Base+"/puzzles.json", ctx.puzzlesHandler)
mux.HandleFunc(ctx.Base+"/points.json", ctx.pointsHandler)
type FurtiveResponseWriter struct {
w http.ResponseWriter
statusCode *int
}
func (w FurtiveResponseWriter) WriteHeader(statusCode int) {
*w.statusCode = statusCode
w.w.WriteHeader(statusCode)
}
func (w FurtiveResponseWriter) Write(buf []byte) (n int, err error) {
n, err = w.w.Write(buf)
return
}
func (w FurtiveResponseWriter) Header() http.Header {
return w.w.Header()
}
// This gives Instances the signature of http.Handler
func (ctx *Instance) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
w := FurtiveResponseWriter{
w: wOrig,
statusCode: new(int),
}
w.Header().Set("WWW-Authenticate", "Basic")
_, password, _ := r.BasicAuth()
if password != ctx.Password {
http.Error(w, "Authentication Required", 401)
} else {
ctx.mux.ServeHTTP(w, r)
}
log.Printf(
"%s %s %s %d\n",
r.RemoteAddr,
r.Method,
r.URL,
*w.statusCode,
)
}
func (ctx *Instance) BindHandlers() {
ctx.mux.HandleFunc(ctx.Base+"/", ctx.staticHandler)
ctx.mux.HandleFunc(ctx.Base+"/register", ctx.registerHandler)
ctx.mux.HandleFunc(ctx.Base+"/answer", ctx.answerHandler)
ctx.mux.HandleFunc(ctx.Base+"/content/", ctx.contentHandler)
ctx.mux.HandleFunc(ctx.Base+"/puzzles.json", ctx.puzzlesHandler)
ctx.mux.HandleFunc(ctx.Base+"/points.json", ctx.pointsHandler)
}

View File

@ -6,6 +6,7 @@ import (
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"strings"
@ -17,20 +18,24 @@ type Instance struct {
MothballDir string
StateDir string
ResourcesDir string
Password string
Categories map[string]*Mothball
update chan bool
jPuzzleList []byte
jPointsLog []byte
mux *http.ServeMux
}
func NewInstance(base, mothballDir, stateDir, resourcesDir string) (*Instance, error) {
func NewInstance(base, mothballDir, stateDir, resourcesDir, password string) (*Instance, error) {
ctx := &Instance{
Base: strings.TrimRight(base, "/"),
MothballDir: mothballDir,
StateDir: stateDir,
ResourcesDir: resourcesDir,
Password: password,
Categories: map[string]*Mothball{},
update: make(chan bool, 10),
mux: http.NewServeMux(),
}
// Roll over and die if directories aren't even set up
@ -40,7 +45,8 @@ func NewInstance(base, mothballDir, stateDir, resourcesDir string) (*Instance, e
if _, err := os.Stat(stateDir); err != nil {
return nil, err
}
ctx.BindHandlers()
ctx.MaybeInitialize()
return ctx, nil

View File

@ -1,24 +1,19 @@
package main
import (
"flag"
"github.com/namsral/flag"
"log"
"mime"
"net/http"
"time"
)
func logRequest(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("HTTP %s %s %s\n", r.RemoteAddr, r.Method, r.URL)
handler.ServeHTTP(w, r)
})
}
func setup() error {
return nil
}
func main() {
base := flag.String(
"base",
@ -40,6 +35,11 @@ func main() {
"/theme",
"Path to static theme resources (HTML, images, css, ...)",
)
password := flag.String(
"password",
"sesame",
"Pass Word (in the 1920s sense) to view the site. Not a secure passphrase.",
)
maintenanceInterval := flag.Duration(
"maint",
20*time.Second,
@ -56,11 +56,10 @@ func main() {
log.Fatal(err)
}
ctx, err := NewInstance(*base, *mothballDir, *stateDir, *themeDir)
ctx, err := NewInstance(*base, *mothballDir, *stateDir, *themeDir, *password)
if err != nil {
log.Fatal(err)
}
ctx.BindHandlers(http.DefaultServeMux)
// Add some MIME extensions
// Doing this avoids decompressing a mothball entry twice per request
@ -70,5 +69,5 @@ func main() {
go ctx.Maintenance(*maintenanceInterval)
log.Printf("Listening on %s", *listen)
log.Fatal(http.ListenAndServe(*listen, logRequest(http.DefaultServeMux)))
log.Fatal(http.ListenAndServe(*listen, ctx))
}