// Copyright (C) 2017 Marius Schellenberger package server import ( "errors" "git.giftfish.de/ston1th/gowiki/pkg/log" "git.giftfish.de/ston1th/jwt" "github.com/gorilla/mux" "html/template" "net/http" "time" ) func newContext(w http.ResponseWriter, r *http.Request, s *HTTPServer) (ctx *Context) { 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'") ctx = &Context{ Request: r, Response: w, Srv: s, Time: time.Now(), } if c, err := r.Cookie(cookieName); err == nil { t, err := jwt.DecodeToken(c.Value) if err != nil { log.Println("DecodeToken:", err) } else { if err = s.JWT.Verify(t); err != nil { log.Println("VerifyToken:", err) } else { ctx.Token = *t return } } } ctx.SetCookie(nil) return } type Context struct { Request *http.Request Response http.ResponseWriter Srv *HTTPServer T *template.Template Status int Err error Time time.Time Token jwt.Token Data webData } type webData struct { Version string Time int64 Title string BodyTitle string Admin bool Login bool User string Token string Msg string Search string Data interface{} } func (c *Context) Exec() { defer c.log() if c.T == nil { c.Status = http.StatusInternalServerError c.Err = errors.New("template is nil") return } c.Data.Token = newXsrf(c.Token.RawSig()[:keySize]) c.Data.Version = c.Srv.Version if c.Data.BodyTitle == "" { c.Data.BodyTitle = c.Data.Title } c.Data.Admin = c.GetAdmin() c.Data.User = c.GetUser() c.Data.Login = c.LoggedOn() c.Data.Time = time.Since(c.Time).Nanoseconds() / 1e6 c.Err = c.T.Execute(c.Response, c.Data) if c.Err != nil { c.Status = http.StatusInternalServerError } if c.Status == 0 { c.Status = http.StatusOK } } func (c *Context) log() { if c.Err != nil { log.Printf("%s %s %s %d error: %s\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, c.Status, c.Err) return } log.Printf("%s %s %s %d\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, c.Status) } func (c *Context) NotFound() { c.Template("notFoundHandler") c.Data = webData{Title: "404"} c.Status = http.StatusNotFound c.Response.WriteHeader(http.StatusNotFound) c.Exec() } func (c *Context) Template(name string) { c.T = c.Srv.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) { log.Printf("%s %s %s %d %s\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, code, uri) 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 } // CheckToken validates the xsrf token func (c *Context) CheckXsrf(f func()) (ok bool) { ok = checkXsrf(c.Form("token"), c.Token.RawSig()[:keySize]) if !ok { if f != nil { f() return } c.Data.Msg = "wrong csrf token" c.Exec() } return } func (c *Context) LoggedOn() (ok bool) { _, ok = c.Token.Claims["user"] return } func (c *Context) SetCookie(claims map[string]interface{}) { t := jwt.NewToken(claims, nil) if err := c.Srv.JWT.Sign(t); err != nil { log.Println(err) return } c.Token = *t http.SetCookie(c.Response, &http.Cookie{ Name: cookieName, Value: t.Sig(), MaxAge: 3600 * 12, Secure: true, HttpOnly: true, SameSite: http.SameSiteStrictMode, }) } func (c *Context) GetUser() string { if ret, ok := c.Token.Claims["user"].(string); ok { return ret } return "" } func (c *Context) GetAdmin() bool { if ret, ok := c.Token.Claims["admin"].(bool); ok { return ret } return false } type ctxHandler func(*Context)