mirror of https://github.com/dirtbags/moth.git
Add basic authentication and remove legacy html responses
This commit is contained in:
parent
54fffc5b77
commit
e68a041f33
|
@ -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
|
||||
|
|
121
src/handlers.go
121
src/handlers.go
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
19
src/mothd.go
19
src/mothd.go
|
@ -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))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue