250 lines
5 KiB
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)
|