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
|
FROM alpine:3.9 AS builder
|
||||||
RUN apk --no-cache add go libc-dev
|
RUN apk --no-cache add go libc-dev git
|
||||||
COPY src /src
|
COPY src /root/go/src/github.com/dirtbags/moth/src
|
||||||
RUN go build -o /mothd /src/*.go
|
WORKDIR /root/go/src/github.com/dirtbags/moth/src
|
||||||
|
RUN go get .
|
||||||
|
RUN go build -o /mothd *.go
|
||||||
|
|
||||||
FROM alpine
|
FROM alpine
|
||||||
COPY --from=builder /mothd /mothd
|
COPY --from=builder /mothd /mothd
|
||||||
|
|
121
src/handlers.go
121
src/handlers.go
|
@ -21,14 +21,20 @@ type JSendData struct {
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShowJSend renders a JSend response to w
|
type Status int
|
||||||
func ShowJSend(w http.ResponseWriter, status Status, short string, description string) {
|
|
||||||
|
|
||||||
|
const (
|
||||||
|
Success = iota
|
||||||
|
Fail
|
||||||
|
Error
|
||||||
|
)
|
||||||
|
|
||||||
|
func respond(w http.ResponseWriter, req *http.Request, status Status, short string, format string, a ...interface{}) {
|
||||||
resp := JSend{
|
resp := JSend{
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Data: JSendData{
|
Data: JSendData{
|
||||||
Short: short,
|
Short: short,
|
||||||
Description: description,
|
Description: fmt.Sprintf(format, a...),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
switch status {
|
switch status {
|
||||||
|
@ -51,59 +57,6 @@ func ShowJSend(w http.ResponseWriter, status Status, short string, description s
|
||||||
w.Write(respBytes)
|
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.
|
// hasLine returns true if line appears in r.
|
||||||
// The entire line must match.
|
// The entire line must match.
|
||||||
func hasLine(r io.Reader, line string) bool {
|
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)
|
http.ServeContent(w, req, path, d.ModTime(), f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *Instance) BindHandlers(mux *http.ServeMux) {
|
type FurtiveResponseWriter struct {
|
||||||
mux.HandleFunc(ctx.Base+"/", ctx.staticHandler)
|
w http.ResponseWriter
|
||||||
mux.HandleFunc(ctx.Base+"/register", ctx.registerHandler)
|
statusCode *int
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -17,20 +18,24 @@ type Instance struct {
|
||||||
MothballDir string
|
MothballDir string
|
||||||
StateDir string
|
StateDir string
|
||||||
ResourcesDir string
|
ResourcesDir string
|
||||||
|
Password string
|
||||||
Categories map[string]*Mothball
|
Categories map[string]*Mothball
|
||||||
update chan bool
|
update chan bool
|
||||||
jPuzzleList []byte
|
jPuzzleList []byte
|
||||||
jPointsLog []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{
|
ctx := &Instance{
|
||||||
Base: strings.TrimRight(base, "/"),
|
Base: strings.TrimRight(base, "/"),
|
||||||
MothballDir: mothballDir,
|
MothballDir: mothballDir,
|
||||||
StateDir: stateDir,
|
StateDir: stateDir,
|
||||||
ResourcesDir: resourcesDir,
|
ResourcesDir: resourcesDir,
|
||||||
|
Password: password,
|
||||||
Categories: map[string]*Mothball{},
|
Categories: map[string]*Mothball{},
|
||||||
update: make(chan bool, 10),
|
update: make(chan bool, 10),
|
||||||
|
mux: http.NewServeMux(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Roll over and die if directories aren't even set up
|
// Roll over and die if directories aren't even set up
|
||||||
|
@ -41,6 +46,7 @@ func NewInstance(base, mothballDir, stateDir, resourcesDir string) (*Instance, e
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.BindHandlers()
|
||||||
ctx.MaybeInitialize()
|
ctx.MaybeInitialize()
|
||||||
|
|
||||||
return ctx, nil
|
return ctx, nil
|
||||||
|
|
19
src/mothd.go
19
src/mothd.go
|
@ -1,24 +1,19 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"github.com/namsral/flag"
|
||||||
"log"
|
"log"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"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 {
|
func setup() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
base := flag.String(
|
base := flag.String(
|
||||||
"base",
|
"base",
|
||||||
|
@ -40,6 +35,11 @@ func main() {
|
||||||
"/theme",
|
"/theme",
|
||||||
"Path to static theme resources (HTML, images, css, ...)",
|
"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(
|
maintenanceInterval := flag.Duration(
|
||||||
"maint",
|
"maint",
|
||||||
20*time.Second,
|
20*time.Second,
|
||||||
|
@ -56,11 +56,10 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, err := NewInstance(*base, *mothballDir, *stateDir, *themeDir)
|
ctx, err := NewInstance(*base, *mothballDir, *stateDir, *themeDir, *password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
ctx.BindHandlers(http.DefaultServeMux)
|
|
||||||
|
|
||||||
// Add some MIME extensions
|
// Add some MIME extensions
|
||||||
// Doing this avoids decompressing a mothball entry twice per request
|
// Doing this avoids decompressing a mothball entry twice per request
|
||||||
|
@ -70,5 +69,5 @@ func main() {
|
||||||
go ctx.Maintenance(*maintenanceInterval)
|
go ctx.Maintenance(*maintenanceInterval)
|
||||||
|
|
||||||
log.Printf("Listening on %s", *listen)
|
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