1 package main 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/rand" 7 "encoding/base64" 8 "flag" 9 "fmt" 10 "net" 11 "net/http" 12 "os" 13 "os/signal" 14 "path/filepath" 15 "time" 16 17 "vimagination.zapto.org/battlemap" 18 "vimagination.zapto.org/memio" 19 "vimagination.zapto.org/sessions" 20 ) 21 22 var unauthorised = []byte(`<html> 23 <head> 24 <title>Unauthorised</title> 25 </head> 26 <body> 27 <h1>Not Authorised</h1> 28 </body> 29 `) 30 31 type Auth struct { 32 username string 33 password string 34 store *sessions.CookieStore 35 sessionData memio.Buffer 36 } 37 38 func NewAuth(username, password string, sessionKey []byte) (*Auth, error) { 39 store, err := sessions.NewCookieStore(sessionKey, sessions.HTTPOnly(), sessions.Path("/"), sessions.Name("battlemap"), sessions.Expiry(time.Hour*24*30)) 40 if err != nil { 41 return nil, fmt.Errorf("error starting Cookie Store: %w", err) 42 } 43 sessionData := make(memio.Buffer, 32) 44 rand.Read(sessionData) 45 return &Auth{ 46 username: username, 47 password: password, 48 store: store, 49 sessionData: sessionData, 50 }, nil 51 } 52 53 func (a *Auth) Auth(r *http.Request) *http.Request { return r } 54 55 func (a *Auth) IsAdmin(r *http.Request) bool { 56 rData := a.store.Get(r) 57 isAdmin := bytes.Equal(rData, a.sessionData) 58 return isAdmin 59 } 60 61 func (a *Auth) IsUser(r *http.Request) bool { 62 return !a.IsAdmin(r) 63 } 64 65 func (a *Auth) ServeHTTP(w http.ResponseWriter, r *http.Request) { 66 switch r.URL.Path { 67 case "logout": 68 a.store.Set(w, nil) 69 case "login": 70 username, password, _ := r.BasicAuth() 71 ok := username == a.username && password == a.password 72 if !ok { 73 w.Header().Set("WWW-Authenticate", "Basic realm=\"Enter Credentials\"") 74 w.WriteHeader(http.StatusUnauthorized) 75 w.Write(unauthorised) 76 return 77 } 78 a.store.Set(w, a.sessionData) 79 default: 80 http.NotFound(w, r) 81 return 82 } 83 http.Redirect(w, r, "../", http.StatusFound) 84 } 85 86 func main() { 87 if err := run(); err != nil { 88 fmt.Fprintln(os.Stderr, err) 89 os.Exit(1) 90 } 91 } 92 93 func run() error { 94 defaultDir, err := os.UserConfigDir() 95 if err != nil { 96 return fmt.Errorf("error getting user config dir: %w", err) 97 } 98 username := flag.String("user", "", "Username") 99 password := flag.String("pass", "", "Password") 100 p := flag.String("path", filepath.Join(defaultDir, "battlemap"), "Data Path") 101 port := flag.Int("port", 8080, "Web Port") 102 key := flag.String("key", "MDEyMzQ1Njc4OUFCQ0RFRg==", "Encryption Key (Base64: 16, 24, or 32 bytes)") 103 flag.Parse() 104 encKey, err := base64.StdEncoding.DecodeString(*key) 105 if err != nil { 106 return fmt.Errorf("error decoding encryption key: %w", err) 107 } 108 auth, err := NewAuth(*username, *password, encKey) 109 if err != nil { 110 return err 111 } 112 b, err := battlemap.New(*p, auth) 113 if err != nil { 114 return fmt.Errorf("error creating Battlemap: %w", err) 115 } 116 l, err := net.ListenTCP("tcp", &net.TCPAddr{Port: *port}) 117 if err != nil { 118 return fmt.Errorf("error opening port: %w", err) 119 } 120 server := http.Server{Handler: b} 121 go server.Serve(l) 122 sc := make(chan os.Signal, 1) 123 signal.Notify(sc, os.Interrupt) 124 <-sc 125 signal.Stop(sc) 126 close(sc) 127 return server.Shutdown(context.Background()) 128 } 129