1 package battlemap 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "fmt" 7 "io" 8 "net/http" 9 "time" 10 11 "golang.org/x/net/websocket" 12 "vimagination.zapto.org/memio" 13 "vimagination.zapto.org/sessions" 14 ) 15 16 // Auth allows for specifying a custom authorisation module 17 type Auth interface { 18 http.Handler 19 Auth(*http.Request) *http.Request 20 IsAdmin(*http.Request) bool 21 IsUser(*http.Request) bool 22 } 23 24 type userState uint8 25 26 const ( 27 userStateNone userState = iota 28 userStateUser 29 userStateAdmin 30 ) 31 32 func (u userState) IsAdmin() bool { 33 return u == userStateAdmin 34 } 35 36 func (u userState) IsUser() bool { 37 return u == userStateUser 38 } 39 40 type auth struct { 41 *Battlemap 42 store *sessions.CookieStore 43 sessionData memio.Buffer 44 } 45 46 func (a *auth) Init(b *Battlemap) error { 47 var save bool 48 sessionKey := make(memio.Buffer, 0, 16) 49 b.config.Get("sessionKey", &sessionKey) 50 if len(sessionKey) != 16 { 51 sessionKey = sessionKey[:16] 52 rand.Read(sessionKey) 53 save = true 54 } 55 sessionData := make(memio.Buffer, 0, 32) 56 b.config.Get("sessionData", &sessionData) 57 if len(sessionData) < 16 { 58 sessionData = sessionData[:32] 59 rand.Read(sessionData) 60 save = true 61 } 62 var err error 63 a.store, err = sessions.NewCookieStore(sessionKey, sessions.HTTPOnly(), sessions.Path("/"), sessions.Name("admin"), sessions.Expiry(time.Hour*24*30)) 64 if err != nil { 65 return fmt.Errorf("error creating Cookie Store: %w", err) 66 } 67 a.sessionData = sessionData 68 if save { 69 if err = b.config.SetAll(map[string]io.WriterTo{ 70 "sessionKey": &sessionKey, 71 "sessionData": &sessionData, 72 }); err != nil { 73 return fmt.Errorf("error setting auth config: %w", err) 74 } 75 } 76 a.Battlemap = b 77 return nil 78 } 79 80 func (a *auth) IsAdmin(r *http.Request) bool { 81 rData := a.store.Get(r) 82 isAdmin := bytes.Equal(rData, a.sessionData) 83 return isAdmin 84 } 85 86 func (a *auth) IsUser(r *http.Request) bool { 87 return !a.IsAdmin(r) 88 } 89 90 func (a *auth) Auth(r *http.Request) *http.Request { return r } 91 92 func (a *auth) ServeHTTP(w http.ResponseWriter, r *http.Request) { 93 switch r.URL.Path { 94 case "logout": 95 a.store.Set(w, nil) 96 case "login": 97 a.store.Set(w, a.sessionData) 98 default: 99 http.NotFound(w, r) 100 return 101 } 102 http.Redirect(w, r, "../", http.StatusFound) 103 } 104 105 var ( 106 loggedOut = []byte("{\"id\": -1, \"result\": 0}") 107 loggedInUser = []byte("{\"id\": -1, \"result\": 1}") 108 loggedInAdmin = []byte("{\"id\": -1, \"result\": 2}") 109 ) 110 111 func (b *Battlemap) authConn(w *websocket.Conn) userState { 112 r := w.Request() 113 if b.auth.IsAdmin(r) { 114 w.Write(loggedInAdmin) 115 return userStateAdmin 116 } else if b.auth.IsUser(r) { 117 w.Write(loggedInUser) 118 return userStateUser 119 } 120 w.Write(loggedOut) 121 return userStateNone 122 } 123