gowiki/context.go

250 lines
5 KiB
Go

// 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)