// Copyright (C) 2017 Marius Schellenberger package main import ( "crypto/rand" "crypto/subtle" "encoding/base64" "errors" "github.com/gorilla/mux" "github.com/gorilla/sessions" "html/template" "io" "net/http" "time" ) const ( keyLen = 32 authLen = keyLen * 2 cookieName = "gosession" ) var store *sessions.CookieStore func cookieStore() { options := &sessions.Options{ Path: "/", MaxAge: 3600 * 12, Secure: secureCookie, HttpOnly: true, } authKey := genKey(authLen) encKey := genKey(keyLen) store = sessions.NewCookieStore(authKey, encKey) store.Options = options go func() { for { time.Sleep(time.Hour * 12) newAuthKey := genKey(authLen) newEncKey := genKey(keyLen) newStore := sessions.NewCookieStore(newAuthKey, newEncKey, authKey, encKey) newStore.Options = options store = newStore authKey = newAuthKey encKey = newEncKey } }() } func genKey(length int) (bytes []byte) { bytes = make([]byte, length) if _, err := io.ReadFull(rand.Reader, bytes); err != nil { log.Println("genKey:", err) } return } func newContext(w http.ResponseWriter, r *http.Request) *Context { log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL.Path) h := w.Header() h.Set("X-Frame-Options", "DENY") h.Set("X-Content-Type-Options", "nosniff") h.Set("X-XSS-Protection", "1; mode=block") h.Set("Content-Security-Policy", "default-src 'none';style-src 'self' 'unsafe-inline';frame-ancestors 'none'") return &Context{ Request: r, Response: w, } } type Context struct { Request *http.Request Response http.ResponseWriter T *template.Template Data webData } type webData struct { Title string BodyTitle string Admin bool Login bool User string Token string Msg string Search string Data interface{} } func (c *Context) Exec() { if c.T == nil { log.Println("Exec:", errors.New("template is nil")) return } c.Data.Admin = c.GetAdmin() c.Data.User = c.GetUser() c.Data.Login = c.LoggedOn() err := c.T.Execute(c.Response, c.Data) if err != nil { log.Println("Exec:", err) } } func (c *Context) Template(name string) { c.T = templ[name] } func (c *Context) Write(buf []byte) (err error) { _, err = c.Response.Write(buf) return } func (c *Context) SetHeader(name, value string) { c.Response.Header().Set(name, value) } func (c *Context) Redirect(uri string, code int) { http.Redirect(c.Response, c.Request, uri, code) } func (c *Context) Method() string { return c.Request.Method } func (c *Context) Path() string { return c.Request.URL.Path } func (c *Context) FormSlice(name string) []string { // does nothing if called twice c.Request.ParseForm() return c.Request.PostForm[name] } func (c *Context) Form(name string) (ret string) { ret = c.Request.PostFormValue(name) return } func (c *Context) Var(name string) (ret string) { ret, _ = mux.Vars(c.Request)[name] return } func (c *Context) InitToken() { token := c.getToken() if token == nil || len(token) != keyLen { s, _ := store.Get(c.Request, cookieName) s.Values["token"] = genKey(keyLen) s.Options = store.Options err := s.Save(c.Request, c.Response) if err != nil { log.Println("InitToken: ", err) } } } // NewToken sets and returns the xsrf token func (c *Context) NewToken() { rnd := genKey(keyLen) c.Data.Token = base64.StdEncoding.EncodeToString(append(rnd, xor(rnd, c.getToken())...)) } // CheckToken validates the xsrf token func (c *Context) CheckToken() bool { token, err := base64.StdEncoding.DecodeString(c.Form("token")) if err != nil { log.Println("CheckToken: ", err) return false } if len(token) != authLen { return false } return subtle.ConstantTimeCompare(c.getToken(), xor(token[keyLen:], token[:keyLen])) == 1 } func (c *Context) LoggedOn() (ok bool) { s, _ := store.Get(c.Request, cookieName) _, ok = s.Values["user"] return } func (c *Context) SetUdata(user string, admin bool) { s, _ := store.Get(c.Request, cookieName) s.Values["user"] = user s.Values["admin"] = admin s.Options = store.Options if err := s.Save(c.Request, c.Response); err != nil { log.Println(err) } } func (c *Context) UnsetUdata() { s, _ := store.Get(c.Request, cookieName) s.Values = make(map[interface{}]interface{}) s.Options = store.Options if err := s.Save(c.Request, c.Response); err != nil { log.Println(err) } } func (c *Context) GetUser() string { s, _ := store.Get(c.Request, cookieName) if ret, ok := s.Values["user"].(string); ok { return ret } return "" } func (c *Context) GetAdmin() bool { s, _ := store.Get(c.Request, cookieName) if ret, ok := s.Values["admin"].(bool); ok { return ret } return false } func (c *Context) getToken() []byte { s, _ := store.Get(c.Request, cookieName) t, ok := s.Values["token"] if !ok { return nil } b, ok := t.([]byte) if !ok { return nil } return b } func xor(a, b []byte) (c []byte) { n := len(a) if len(b) != n { return } c = make([]byte, n) for i := 0; i < n; i++ { c[i] = a[i] ^ b[i] } return } type ctxHandler func(*Context)