minor bugfixes and cleanup

This commit is contained in:
ston1th 2018-09-19 00:10:16 +02:00
commit 97d914bce4
26 changed files with 198 additions and 221 deletions

2
pkg/cache/cache.go vendored
View file

@ -1,3 +1,5 @@
// Copyright (C) 2018 Marius Schellenberger
package cache package cache
import ( import (

View file

@ -3,7 +3,6 @@
package cmd package cmd
import ( import (
"errors"
"git.giftfish.de/ston1th/godrop" "git.giftfish.de/ston1th/godrop"
"github.com/urfave/cli" "github.com/urfave/cli"
stdlog "log" stdlog "log"
@ -13,13 +12,12 @@ import (
"syscall" "syscall"
"time" "time"
"git.giftfish.de/ston1th/gowiki/pkg/core"
"git.giftfish.de/ston1th/gowiki/pkg/log" "git.giftfish.de/ston1th/gowiki/pkg/log"
"git.giftfish.de/ston1th/gowiki/pkg/server" "git.giftfish.de/ston1th/gowiki/pkg/server"
) )
const ( const (
blevePath = "bleve"
defDataDir = "/var/lib/gowiki" defDataDir = "/var/lib/gowiki"
defListen = "127.0.0.1:8090" defListen = "127.0.0.1:8090"
defLogFile = "gowiki.log" defLogFile = "gowiki.log"
@ -28,7 +26,7 @@ const (
defRunGroup = "wiki" defRunGroup = "wiki"
) )
func initServer(conf serverConfig) (err error) { func initServer(conf core.Config) (err error) {
dropCfg := godrop.Config{ dropCfg := godrop.Config{
User: conf.User, User: conf.User,
Group: conf.Group, Group: conf.Group,
@ -51,7 +49,7 @@ func initServer(conf serverConfig) (err error) {
return return
} }
srv := server.NewHTTPServer(l, conf.Version, conf.DataDir+"/"+blevePath, conf.SecureCookie) srv := server.NewHTTPServer(l, conf)
err = srv.Start() err = srv.Start()
if err != nil { if err != nil {
return return
@ -63,8 +61,7 @@ func initServer(conf serverConfig) (err error) {
log.Println("caught " + sig.String()) log.Println("caught " + sig.String())
go func() { go func() {
time.Sleep(time.Second * 20) time.Sleep(time.Second * 20)
//TODO error needed? log.Fatal("stop timed out: killing")
log.Fatal(errors.New("stop timed out: killing"))
}() }()
err = srv.Stop() err = srv.Stop()
if err != nil { if err != nil {
@ -76,7 +73,7 @@ func initServer(conf serverConfig) (err error) {
var ( var (
verbose bool verbose bool
conf serverConfig conf core.Config
) )
func Run(version string) { func Run(version string) {
@ -131,17 +128,6 @@ func Run(version string) {
app.Run(os.Args) app.Run(os.Args)
} }
type serverConfig struct {
DataDir string
User string
Group string
ListenAddr string
LogFile string
Version string
Foreground bool
SecureCookie bool
}
func serverFlags() []cli.Flag { func serverFlags() []cli.Flag {
return []cli.Flag{ return []cli.Flag{
cli.StringFlag{ cli.StringFlag{
@ -168,6 +154,11 @@ func serverFlags() []cli.Flag {
Usage: "log file (leave empty for stdout, this only works in combination with -f)", Usage: "log file (leave empty for stdout, this only works in combination with -f)",
Destination: &conf.LogFile, Destination: &conf.LogFile,
}, },
cli.StringFlag{
Name: "secret",
Usage: "static hmac secret (at least 64 chars, used for JWT signing)",
Destination: &conf.Secret,
},
cli.BoolFlag{ cli.BoolFlag{
Name: "foreground, f", Name: "foreground, f",
Usage: "do not fork into the background", Usage: "do not fork into the background",

View file

@ -1,3 +1,5 @@
// Copyright (C) 2018 Marius Schellenberger
package core package core
const ( const (

View file

@ -1,3 +1,5 @@
// Copyright (C) 2018 Marius Schellenberger
package core package core
type Permission int type Permission int

View file

@ -1,3 +1,5 @@
// Copyright (C) 2018 Marius Schellenberger
package core package core
import "html/template" import "html/template"
@ -36,5 +38,17 @@ func (u Users) Less(i, j int) bool { return u[i].Username < u[j].Username }
type Result struct { type Result struct {
Title string Title string
StoreTitle string StoreTitle string
Text string HTML template.HTML
}
type Config struct {
DataDir string
User string
Group string
ListenAddr string
LogFile string
Version string
Secret string
Foreground bool
SecureCookie bool
} }

View file

@ -21,10 +21,7 @@ This is the [Index](/wiki/Index) page.
You can customize it how you like.` You can customize it how you like.`
) )
/*const ( const blevePath = "bleve"
maxResult = 1e6
maxSearchResult = 101
)*/
func dbErr(i interface{}) error { func dbErr(i interface{}) error {
return fmt.Errorf("db: %s", i) return fmt.Errorf("db: %s", i)
@ -37,13 +34,12 @@ type DB struct {
Index *index.Index Index *index.Index
} }
func New(indexPath string) (db *DB, err error) { func New(datadir string) (db *DB, err error) {
db = &DB{cache: cache.NewCache()} db = &DB{cache: cache.NewCache()}
//TODO db.Index, err = index.NewIndex(datadir + "/" + blevePath)
//db.Index, err = index.NewIndex(indexPath) if err != nil {
//if err != nil { return
// return }
//}
db.users, err = store.NewBoltStore(usersFile, nil) db.users, err = store.NewBoltStore(usersFile, nil)
if err != nil { if err != nil {
@ -80,7 +76,5 @@ func (db *DB) Close() error {
if err != nil { if err != nil {
return err return err
} }
return nil return db.Index.Close()
//TODO
//return db.Index.Close()
} }

View file

@ -1,7 +1,9 @@
// Copyright (C) 2018 Marius Schellenberger
package db package db
import ( import (
"golang.org/x/crypto/sha3" "crypto/sha256"
"time" "time"
) )
@ -31,7 +33,7 @@ func validatePassword(pw string) (b []byte) {
if len(pw) <= 56 { if len(pw) <= 56 {
return return
} }
hash := sha3.New384() hash := sha256.New()
hash.Write(b) hash.Write(b)
b = hash.Sum(nil) b = hash.Sum(nil)
return return

View file

@ -1,28 +0,0 @@
package db
import (
"bytes"
"encoding/gob"
)
type Marshaler interface {
Marshal(v interface{}) ([]byte, error)
Unmarshal(data []byte, v interface{}) error
}
type gobMarshaler struct{}
func NewGOB() Marshaler {
return gobMarshaler{}
}
func (gobMarshaler) Marshal(v interface{}) (b []byte, err error) {
buf := new(bytes.Buffer)
err = gob.NewEncoder(buf).Encode(v)
b = buf.Bytes()
return
}
func (gobMarshaler) Unmarshal(data []byte, v interface{}) error {
return gob.NewDecoder(bytes.NewBuffer(data)).Decode(v)
}

View file

@ -1,3 +1,5 @@
// Copyright (C) 2018 Marius Schellenberger
package db package db
import ( import (
@ -81,19 +83,17 @@ func (db *DB) CreatePage(title, section, markdown, username string, p core.Permi
Owner: owner, Owner: owner,
Perm: p, Perm: p,
} }
//search := render.Render(page) search := render.Render(page)
render.Render(page)
err = db.pages.Set(st, page) err = db.pages.Set(st, page)
if err != nil { if err != nil {
return return
} }
//TODO err = db.Index.Add(title, st, search)
//err = db.Index.Add(title, st, search) if err != nil {
//if err != nil { return
// return }
//}
db.cache.Add(st, page) db.cache.Add(st, page)
return return
} }
@ -119,20 +119,17 @@ func (db *DB) UpdatePage(title, section, markdown, username string, p core.Permi
page.Markdown = markdown page.Markdown = markdown
page.Updated = created(username) page.Updated = created(username)
page.Perm = p page.Perm = p
//TODO search := render.Render(page)
//search := render.Render(page)
render.Render(page)
err = db.pages.Set(st, page) err = db.pages.Set(st, page)
if err != nil { if err != nil {
return return
} }
//TODO err = db.Index.Add(title, st, search)
//err = db.Index.Add(title, st, search) if err != nil {
//if err != nil { return
// return }
//}
db.cache.Add(st, page) db.cache.Add(st, page)
return return
} }
@ -155,7 +152,5 @@ func (db *DB) DeletePage(title, section, username string) (err error) {
return return
} }
db.cache.Delete(st) db.cache.Delete(st)
return nil return db.Index.Delete(st)
//TODO
//return db.Index.Delete(st)
} }

View file

@ -1,3 +1,5 @@
// Copyright (C) 2018 Marius Schellenberger
package db package db
import ( import (
@ -166,6 +168,7 @@ func (db *DB) AdminUpdateUser(username, password string, admin bool) error {
return db.users.Set(username, u) return db.users.Set(username, u)
} }
//TODO delete all user owned pages
func (db *DB) DeleteUser(username string) error { func (db *DB) DeleteUser(username string) error {
if username == defUser { if username == defUser {
return errors.New("user '" + defUser + "' cannot be deleted") return errors.New("user '" + defUser + "' cannot be deleted")

View file

@ -4,15 +4,14 @@ package index
import ( import (
"fmt" "fmt"
"html/template"
"os" "os"
"git.giftfish.de/ston1th/gowiki/pkg/core" "git.giftfish.de/ston1th/gowiki/pkg/core"
"github.com/blevesearch/bleve" "github.com/blevesearch/bleve"
) )
const ( const maxSearchResult = 101
maxSearchResult = 100
)
type indexPage struct { type indexPage struct {
Title string `json:"title"` Title string `json:"title"`
@ -89,7 +88,7 @@ func (i *Index) Search(search string) (results []core.Result, err error) {
} }
if fragField == "title" || fragField == "search" { if fragField == "title" || fragField == "search" {
for _, f := range frags { for _, f := range frags {
r.Text += f r.HTML += template.HTML(f)
} }
break break
} }

View file

@ -22,7 +22,6 @@ func makeFilter() analysis.CharFilter {
} }
var ( var (
//reHeader = regexp.MustCompile("(<h[1-6])>(.+)(</h[1-6]>)")
reHeader = regexp.MustCompile("(<h[1-6]>)(.+)(</h[1-6]>)") reHeader = regexp.MustCompile("(<h[1-6]>)(.+)(</h[1-6]>)")
reLink = regexp.MustCompile(`id="(.+?)"`) reLink = regexp.MustCompile(`id="(.+?)"`)
whiteSpace = regexp.MustCompile(`\s+`) whiteSpace = regexp.MustCompile(`\s+`)
@ -64,7 +63,6 @@ func buildIndex(html string) (string, string) {
var ret string var ret string
m := make(map[string]int) m := make(map[string]int)
hn := reIndex.FindAllStringSubmatch(html, -1) hn := reIndex.FindAllStringSubmatch(html, -1)
//html = reHeader.ReplaceAllString(html, `${1} id="${2}">${2}${3}`)
html = reHeader.ReplaceAllString(html, `<a id="${2}" class="anchor"></a>${1}${2}${3}`) html = reHeader.ReplaceAllString(html, `<a id="${2}" class="anchor"></a>${1}${2}${3}`)
html = reLink.ReplaceAllStringFunc(html, spaceReplace) html = reLink.ReplaceAllStringFunc(html, spaceReplace)
for k, v := range hn { for k, v := range hn {
@ -126,11 +124,3 @@ func closeTag(j int) (ret string) {
} }
return return
} }
func renderResult(res []core.Result) (html string) {
for _, r := range res {
html += `<a href="/` + r.StoreTitle + `"><b>` + r.Title + "</b></a>" +
`<pre style="white-space: pre-wrap">` + r.Text + "</pre><br>"
}
return
}

View file

@ -25,7 +25,7 @@ func newContext(w http.ResponseWriter, r *http.Request, s *HTTPServer) (ctx *Con
h.Set("X-Frame-Options", "DENY") h.Set("X-Frame-Options", "DENY")
h.Set("X-Content-Type-Options", "nosniff") h.Set("X-Content-Type-Options", "nosniff")
h.Set("X-XSS-Protection", "1; mode=block") h.Set("X-XSS-Protection", "1; mode=block")
h.Set("Content-Security-Policy", "default-src 'none';style-src 'self' 'unsafe-inline';frame-ancestors 'none'") h.Set("Content-Security-Policy", "default-src 'none';style-src 'self';frame-ancestors 'none'")
ctx = &Context{ ctx = &Context{
Request: r, Request: r,
Response: w, Response: w,
@ -101,6 +101,10 @@ func (c *Context) Exec() {
c.Data.Login = c.LoggedOn() c.Data.Login = c.LoggedOn()
c.Data.Time = time.Since(c.Time).Nanoseconds() / 1e5 c.Data.Time = time.Since(c.Time).Nanoseconds() / 1e5
if c.Data.Msg != "" {
c.Status = http.StatusBadRequest
}
c.Err = c.T.Execute(c.Response, c.Data) c.Err = c.T.Execute(c.Response, c.Data)
if c.Err != nil { if c.Err != nil {
c.Status = http.StatusInternalServerError c.Status = http.StatusInternalServerError
@ -118,6 +122,16 @@ func (c *Context) log() {
log.Printf("%s %s %s %d\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, c.Status) log.Printf("%s %s %s %d\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, c.Status)
} }
func (c *Context) Error(i interface{}) {
switch v := i.(type) {
case string:
c.Data.Msg = v
case error:
c.Data.Msg = v.Error()
}
c.Exec()
}
func (c *Context) NotFound() { func (c *Context) NotFound() {
c.Template("notFoundHandler") c.Template("notFoundHandler")
c.Data = webData{Title: "404"} c.Data = webData{Title: "404"}
@ -170,17 +184,11 @@ func (c *Context) Var(name string) (ret string) {
return return
} }
// TODO remove f // CheckXsrf validates the xsrf token
// CheckToken validates the xsrf token func (c *Context) CheckXsrf() (ok bool) {
func (c *Context) CheckXsrf(f func()) (ok bool) {
ok = checkXsrf(c.Form("token"), c.Token.RawSig()[:keySize]) ok = checkXsrf(c.Form("token"), c.Token.RawSig()[:keySize])
if !ok { if !ok {
if f != nil { c.Error("wrong csrf token")
f()
return
}
c.Data.Msg = "wrong csrf token"
c.Exec()
} }
return return
} }
@ -190,26 +198,6 @@ func (c *Context) LoggedOn() (ok bool) {
return return
} }
func newCookie(value string, maxAge int) (cookie *http.Cookie) {
cookie = &http.Cookie{
Name: cookieName,
Value: value,
Path: "/",
MaxAge: maxAge,
//TODO make secure configurable
Secure: false,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
}
if maxAge > 0 {
d := time.Duration(maxAge) * time.Second
cookie.Expires = time.Now().Add(d)
} else if maxAge < 0 {
cookie.Expires = time.Unix(1, 0)
}
return
}
func (c *Context) SetCookie(claims map[string]interface{}) { func (c *Context) SetCookie(claims map[string]interface{}) {
t := jwt.NewToken(claims, nil) t := jwt.NewToken(claims, nil)
if err := c.Srv.JWT.Sign(t); err != nil { if err := c.Srv.JWT.Sign(t); err != nil {
@ -217,7 +205,22 @@ func (c *Context) SetCookie(claims map[string]interface{}) {
return return
} }
c.Token = *t c.Token = *t
http.SetCookie(c.Response, newCookie(c.Token.String(), int(jwt.DefaultExpiry.Seconds()))) cookie := &http.Cookie{
Name: cookieName,
Value: c.Token.String(),
Path: "/",
MaxAge: int(jwt.DefaultExpiry.Seconds()),
Secure: c.Srv.Secure,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
}
if cookie.MaxAge > 0 {
d := time.Duration(cookie.MaxAge) * time.Second
cookie.Expires = time.Now().Add(d)
} else if cookie.MaxAge < 0 {
cookie.Expires = time.Unix(1, 0)
}
http.SetCookie(c.Response, cookie)
} }
func (c *Context) User() string { func (c *Context) User() string {

View file

@ -88,21 +88,19 @@ func loginHandler(ctx *Context) {
case "POST": case "POST":
user := ctx.Form("user") user := ctx.Form("user")
password := ctx.Form("password") password := ctx.Form("password")
if !ctx.CheckXsrf(nil) { if !ctx.CheckXsrf() {
return return
} }
if user == "" || password == "" { if user == "" || password == "" {
ctx.Data.Msg = "wrong inputs" ctx.Error("wrong inputs")
ctx.Exec()
return return
} }
u, err := ctx.Srv.DB.Login(user, password) u, err := ctx.Srv.DB.Login(user, password)
if err != nil { if err != nil {
ctx.Data.Msg = err.Error() ctx.Error(err)
ctx.Exec()
return return
} }
ctx.SetCookie(map[string]interface{}{userClaim: u.Username, adminClaim: u.Admin}) ctx.SetCookie(jwt.Claims{userClaim: u.Username, adminClaim: u.Admin})
ctx.Redirect(core.IndexURI, 302) ctx.Redirect(core.IndexURI, 302)
} }
} }
@ -118,18 +116,16 @@ func searchHandler(ctx *Context) {
ctx.Exec() ctx.Exec()
case "POST": case "POST":
search := ctx.Form("search") search := ctx.Form("search")
if !ctx.CheckXsrf(nil) { if !ctx.CheckXsrf() {
return return
} }
//TODO res, err := ctx.Srv.DB.Index.Search(search)
//res, err := ctx.Srv.DB.Index.Search(search) if err != nil {
//if err != nil { ctx.Error(err)
// ctx.Data.Msg = err.Error() return
// ctx.Exec() }
// return ctx.Data.Data = res
//}
//ctx.Data.Data = res
ctx.Data.Search = search ctx.Data.Search = search
ctx.Exec() ctx.Exec()
} }
@ -164,7 +160,7 @@ func pageNewHandler(ctx *Context) {
Perm: perm, Perm: perm,
} }
ctx.Data.Data = p ctx.Data.Data = p
if !ctx.CheckXsrf(nil) { if !ctx.CheckXsrf() {
return return
} }
if section != core.WikiSection { if section != core.WikiSection {
@ -172,8 +168,7 @@ func pageNewHandler(ctx *Context) {
} }
page, err := ctx.Srv.DB.CreatePage(title, section, markdown, ctx.User(), perm) page, err := ctx.Srv.DB.CreatePage(title, section, markdown, ctx.User(), perm)
if err != nil { if err != nil {
ctx.Data.Msg = err.Error() ctx.Error(err)
ctx.Exec()
return return
} }
ctx.Redirect(page.StoreTitle, 302) ctx.Redirect(page.StoreTitle, 302)
@ -242,6 +237,10 @@ func pageShareHandler(ctx *Context) {
ctx.NotFound() ctx.NotFound()
return return
} }
if page.Perm != core.Private {
ctx.Error("only private pages can be shared")
return
}
ctx.Data.Data = st ctx.Data.Data = st
ctx.Data.Title = page.Title ctx.Data.Title = page.Title
ctx.Data.BodyTitle = page.Title ctx.Data.BodyTitle = page.Title
@ -250,13 +249,12 @@ func pageShareHandler(ctx *Context) {
ctx.Exec() ctx.Exec()
case "POST": case "POST":
duration := ctx.Form("duration") duration := ctx.Form("duration")
if !ctx.CheckXsrf(nil) { if !ctx.CheckXsrf() {
return return
} }
d, err := time.ParseDuration(duration) d, err := time.ParseDuration(duration)
if err != nil { if err != nil {
ctx.Data.Msg = err.Error() ctx.Error(err)
ctx.Exec()
return return
} }
t := jwt.NewToken(map[string]interface{}{ t := jwt.NewToken(map[string]interface{}{
@ -286,36 +284,31 @@ func pageBlacklistHandler(ctx *Context) {
case "GET": case "GET":
ctx.Exec() ctx.Exec()
case "POST": case "POST":
if !ctx.CheckXsrf(nil) { if !ctx.CheckXsrf() {
return return
} }
t, err := jwt.DecodeToken(ctx.Form("share")) t, err := jwt.DecodeToken(ctx.Form("share"))
if err != nil { if err != nil {
ctx.Data.Msg = err.Error() ctx.Error(err)
ctx.Exec()
return return
} }
err = ctx.Srv.JWT.Verify(t) err = ctx.Srv.JWT.Verify(t)
if err != nil { if err != nil {
ctx.Data.Msg = err.Error() ctx.Error(err)
ctx.Exec()
return return
} }
user := t.Claims.GetString(sharedClaim) user := t.Claims.GetString(sharedClaim)
if user == "" { if user == "" {
ctx.Data.Msg = "blacklisting denied: no share token" ctx.Error("blacklisting denied: no share token")
ctx.Exec()
return return
} }
if ctx.User() != user { if ctx.User() != user {
ctx.Data.Msg = "blacklisting denied: token was issued by " + user ctx.Error("blacklisting denied: token was issued by " + user)
ctx.Exec()
return return
} }
err = ctx.Srv.JWT.Invalidate(t) err = ctx.Srv.JWT.Invalidate(t)
if err != nil { if err != nil {
ctx.Data.Msg = err.Error() ctx.Error(err)
ctx.Exec()
return return
} }
ctx.Exec() ctx.Exec()
@ -342,13 +335,12 @@ func pageEditHandler(ctx *Context) {
page.Markdown = ctx.Form("markdown") page.Markdown = ctx.Form("markdown")
page.Perm = core.ParsePermString(ctx.Form("perm")) page.Perm = core.ParsePermString(ctx.Form("perm"))
ctx.Data.Data = page ctx.Data.Data = page
if !ctx.CheckXsrf(nil) { if !ctx.CheckXsrf() {
return return
} }
err = ctx.Srv.DB.UpdatePage(title, section, page.Markdown, ctx.User(), page.Perm) err = ctx.Srv.DB.UpdatePage(title, section, page.Markdown, ctx.User(), page.Perm)
if err != nil { if err != nil {
ctx.Data.Msg = err.Error() ctx.Error(err)
ctx.Exec()
return return
} }
ctx.Redirect(page.StoreTitle, 302) ctx.Redirect(page.StoreTitle, 302)
@ -367,12 +359,11 @@ func pageDelHandler(ctx *Context) {
case "GET": case "GET":
ctx.Exec() ctx.Exec()
case "POST": case "POST":
if !ctx.CheckXsrf(nil) { if !ctx.CheckXsrf() {
return return
} }
if err := ctx.Srv.DB.DeletePage(title, section, ctx.User()); err != nil { if err := ctx.Srv.DB.DeletePage(title, section, ctx.User()); err != nil {
ctx.Data.Msg = err.Error() ctx.Error(err)
ctx.Exec()
return return
} }
ctx.Redirect(core.IndexURI, 302) ctx.Redirect(core.IndexURI, 302)
@ -416,17 +407,15 @@ func userNewHandler(ctx *Context) {
if admin == "0" { if admin == "0" {
adm = true adm = true
} }
if !ctx.CheckXsrf(nil) { if !ctx.CheckXsrf() {
return return
} }
if user == "" || password == "" || password != repeat { if user == "" || password == "" || password != repeat {
ctx.Data.Msg = "wrong inputs" ctx.Error("wrong inputs")
ctx.Exec()
return return
} }
if err := ctx.Srv.DB.CreateUser(user, password, adm); err != nil { if err := ctx.Srv.DB.CreateUser(user, password, adm); err != nil {
ctx.Data.Msg = err.Error() ctx.Error(err)
ctx.Exec()
return return
} }
ctx.Redirect("/user", 302) ctx.Redirect("/user", 302)
@ -443,8 +432,7 @@ func userEditHandler(ctx *Context) {
case "GET": case "GET":
u, err := ctx.Srv.DB.GetUserWithoutPassword(ctx.Var("user")) u, err := ctx.Srv.DB.GetUserWithoutPassword(ctx.Var("user"))
if err != nil { if err != nil {
ctx.Data.Msg = err.Error() ctx.Error(err)
ctx.Exec()
return return
} }
ctx.Data.Data = u ctx.Data.Data = u
@ -458,25 +446,22 @@ func userEditHandler(ctx *Context) {
if admin == "0" { if admin == "0" {
adm = true adm = true
} }
if !ctx.CheckXsrf(nil) { if !ctx.CheckXsrf() {
return return
} }
if user == "" || password != repeat { if user == "" || password != repeat {
ctx.Data.Msg = "wrong inputs" ctx.Error("wrong inputs")
ctx.Exec()
return return
} }
if ctx.Admin() { if ctx.Admin() {
if err := ctx.Srv.DB.AdminUpdateUser(user, password, adm); err != nil { if err := ctx.Srv.DB.AdminUpdateUser(user, password, adm); err != nil {
ctx.Data.Msg = err.Error() ctx.Error(err)
ctx.Exec()
return return
} }
} else { } else {
if user == ctx.User() { if user == ctx.User() {
if err := ctx.Srv.DB.UpdateUserPassword(user, password); err != nil { if err := ctx.Srv.DB.UpdateUserPassword(user, password); err != nil {
ctx.Data.Msg = err.Error() ctx.Error(err)
ctx.Exec()
return return
} }
ctx.Redirect(core.IndexURI, 302) ctx.Redirect(core.IndexURI, 302)
@ -505,8 +490,7 @@ func userDelHandler(ctx *Context) {
case "GET": case "GET":
user, err := ctx.Srv.DB.GetUserWithoutPassword(ctx.Var("user")) user, err := ctx.Srv.DB.GetUserWithoutPassword(ctx.Var("user"))
if err != nil { if err != nil {
ctx.Data.Msg = err.Error() ctx.Error(err)
ctx.Exec()
return return
} }
ctx.Data.Data = user ctx.Data.Data = user

View file

@ -1,3 +1,5 @@
// Copyright (C) 2018 Marius Schellenberger
package server package server
import ( import (
@ -9,6 +11,7 @@ import (
"net/http" "net/http"
"time" "time"
"git.giftfish.de/ston1th/gowiki/pkg/core"
"git.giftfish.de/ston1th/gowiki/pkg/db" "git.giftfish.de/ston1th/gowiki/pkg/db"
"git.giftfish.de/ston1th/gowiki/pkg/log" "git.giftfish.de/ston1th/gowiki/pkg/log"
"git.giftfish.de/ston1th/jwt" "git.giftfish.de/ston1th/jwt"
@ -19,10 +22,13 @@ const (
) )
type HTTPServer struct { type HTTPServer struct {
Version string Version string
indexPath string Secure bool
listener net.Listener Secret string
srv *http.Server DataDir string
listener net.Listener
srv *http.Server
DB *db.DB DB *db.DB
JWT *jwt.JWT JWT *jwt.JWT
@ -31,9 +37,12 @@ type HTTPServer struct {
res map[string][]byte res map[string][]byte
} }
func NewHTTPServer(l net.Listener, version, indexPath string, secCookie bool) (srv *HTTPServer) { func NewHTTPServer(l net.Listener, conf core.Config) (srv *HTTPServer) {
srv = &HTTPServer{ srv = &HTTPServer{
Version: version, Version: conf.Version,
Secure: conf.SecureCookie,
Secret: conf.Secret,
DataDir: conf.DataDir,
listener: l, listener: l,
} }
srv.loadTemplates() srv.loadTemplates()
@ -44,13 +53,15 @@ func NewHTTPServer(l net.Listener, version, indexPath string, secCookie bool) (s
} }
func (s *HTTPServer) Start() (err error) { func (s *HTTPServer) Start() (err error) {
//TODO make secret configurable var buf *bytes.Buffer
buf := bytes.NewBufferString("be688838ca8686e5c90689bf2ab585cef1137c999b48c70b92f67a5c34dc15697b5d11c982ed6d71be1e1e7f7b4e0733884aa97c3f7a339a8ed03577cf74be09") if s.Secret != "" {
buf = bytes.NewBufferString(s.Secret)
}
s.JWT, err = jwt.New(jwt.DefaultExpiry, jwt.NewMemBlacklist(), buf) s.JWT, err = jwt.New(jwt.DefaultExpiry, jwt.NewMemBlacklist(), buf)
if err != nil { if err != nil {
return return
} }
s.DB, err = db.New(s.indexPath) s.DB, err = db.New(s.DataDir)
if err != nil { if err != nil {
return return
} }

View file

@ -28,7 +28,7 @@ const (
<ul type="circle"> <ul type="circle">
{{range $item := .Data}} {{range $item := .Data}}
{{if ne $owner $item.Owner}} {{if ne $owner $item.Owner}}
{{$owner := $item.Owner}} {{$owner = $item.Owner}}
</ul> </ul>
</div> </div>
<div class="row"> <div class="row">
@ -48,9 +48,9 @@ const (
<head> <head>
<title>GoWiki | {{.Title}}</title> <title>GoWiki | {{.Title}}</title>
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link> <link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link>
<link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-4M0Ran7skk1gZoHDDh/mtuzwXN51uagML7tkWnhE0JI="></link> <link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-1LNAeuiM/rtsY1jIgLovUZ7HbYnOCkj3ZyailNA3rFQ="></link>
</head> </head>
<body style="padding-top:60px"> <body>
<div class="navbar navbar-expand fixed-top navbar-dark bg-primary"> <div class="navbar navbar-expand fixed-top navbar-dark bg-primary">
<div class="container"> <div class="container">
<a href="/" class="navbar-brand">GoWiki</a> <a href="/" class="navbar-brand">GoWiki</a>
@ -62,7 +62,7 @@ const (
<div class="container"> <div class="container">
<div id="back-to-top" class="proper-content"> <div id="back-to-top" class="proper-content">
{{if .Msg}} {{if .Msg}}
<div class="alert alert-danger" style="margin-top:30px"> <div class="alert alert-danger msg">
<h4>Error!</h4> <h4>Error!</h4>
<p>{{.Msg}}</p> <p>{{.Msg}}</p>
</div> </div>
@ -256,7 +256,9 @@ const (
<a href="/{{.Data.StoreTitle}}/md" class="btn btn-sm btn-primary">Markdown</a> <a href="/{{.Data.StoreTitle}}/md" class="btn btn-sm btn-primary">Markdown</a>
{{if .Login}} {{if .Login}}
<a href="/{{.Data.StoreTitle}}/edit" class="btn btn-sm btn-primary">Edit</a> <a href="/{{.Data.StoreTitle}}/edit" class="btn btn-sm btn-primary">Edit</a>
{{if eq .Data.Perm 3}}
<a href="/{{.Data.StoreTitle}}/share" class="btn btn-sm btn-primary">Share</a> <a href="/{{.Data.StoreTitle}}/share" class="btn btn-sm btn-primary">Share</a>
{{end}}
{{if ne .Data.StoreTitle "wiki/Index"}} {{if ne .Data.StoreTitle "wiki/Index"}}
<a href="/{{.Data.StoreTitle}}/del" class="btn btn-sm btn-primary">Delete</a> <a href="/{{.Data.StoreTitle}}/del" class="btn btn-sm btn-primary">Delete</a>
{{end}} {{end}}
@ -317,6 +319,7 @@ const (
</form> </form>
{{end}}` {{end}}`
pageShare = `{{define "body"}} pageShare = `{{define "body"}}
{{if not .Msg}}
<div class="page-header"> <div class="page-header">
<div class="row"> <div class="row">
<div class="col-xs-4 offset-4"> <div class="col-xs-4 offset-4">
@ -346,6 +349,7 @@ const (
</div> </div>
</div> </div>
</div> </div>
{{end}}
{{end}}` {{end}}`
pageView = `{{define "body"}} pageView = `{{define "body"}}
<div class="page-header"> <div class="page-header">
@ -399,7 +403,7 @@ const (
<div class="col"> <div class="col">
{{range $item := .Data}} {{range $item := .Data}}
<a href="/{{$item.StoreTitle}}"><b>{{$item.Title}}</b></a> <a href="/{{$item.StoreTitle}}"><b>{{$item.Title}}</b></a>
<pre style="white-space:pre-wrap">{{$item.Text}}</pre><br> <pre class="wrap">{{$item.HTML}}</pre><br>
{{end}} {{end}}
</div> </div>
</div> </div>
@ -603,7 +607,12 @@ const (
float: left !important; float: left !important;
} }
} }
body {
padding-top: 60px;
}
.msg {
margin-top: 30px;
}
a.dark { a.dark {
color: #222222; color: #222222;
} }
@ -664,8 +673,11 @@ pre {
border: 1px solid #444444; border: 1px solid #444444;
border-radius: 2px; border-radius: 2px;
} }
pre.wrap {
white-space: pre-wrap;
}
code { code {
padding: 3px 3px 1px 3px; padding: 3px 3px 1px 0px;
font-size: 90%; font-size: 90%;
color: #0cdba6; color: #0cdba6;
background-color: #444444; background-color: #444444;

View file

@ -1,3 +1,5 @@
// Copyright (C) 2018 Marius Schellenberger
package server package server
import ( import (

View file

@ -1,8 +1,8 @@
// Copyright (C) 2018 Marius Schellenberger
package store package store
import ( import "github.com/boltdb/bolt"
"github.com/boltdb/bolt"
)
const defaultBoltBucket = "default" const defaultBoltBucket = "default"

View file

@ -1,3 +1,5 @@
// Copyright (C) 2018 Marius Schellenberger
package store package store
import ( import (

View file

@ -6,21 +6,6 @@ import (
"fmt" "fmt"
) )
//const (
// dbFile = "gowiki.db"
// defUser = "admin"
// defPassword = "gowiki"
// userTable = "user"
// pageTable = "page"
// snippetTable = "snippet"
// bcryptCost = 13
// indexName = "Index"
// welcome = `# Welcome to GoWiki
//This is the [Index](/wiki/Index) page.
//You can customize it how you like.`
// wikiSection = "wiki"
//)
func storeErr(i interface{}) error { func storeErr(i interface{}) error {
return fmt.Errorf("store: %s", i) return fmt.Errorf("store: %s", i)
} }

View file

@ -14,7 +14,7 @@
<ul type="circle"> <ul type="circle">
{{range $item := .Data}} {{range $item := .Data}}
{{if ne $owner $item.Owner}} {{if ne $owner $item.Owner}}
{{$owner := $item.Owner}} {{$owner = $item.Owner}}
</ul> </ul>
</div> </div>
<div class="row"> <div class="row">

View file

@ -22,7 +22,12 @@
float: left !important; float: left !important;
} }
} }
body {
padding-top: 60px;
}
.msg {
margin-top: 30px;
}
a.dark { a.dark {
color: #222222; color: #222222;
} }
@ -83,8 +88,11 @@ pre {
border: 1px solid #444444; border: 1px solid #444444;
border-radius: 2px; border-radius: 2px;
} }
pre.wrap {
white-space: pre-wrap;
}
code { code {
padding: 3px 3px 1px 3px; padding: 3px 3px 1px 0px;
font-size: 90%; font-size: 90%;
color: #0cdba6; color: #0cdba6;
background-color: #444444; background-color: #444444;

View file

@ -3,9 +3,9 @@
<head> <head>
<title>GoWiki | {{.Title}}</title> <title>GoWiki | {{.Title}}</title>
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link> <link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link>
<link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-4M0Ran7skk1gZoHDDh/mtuzwXN51uagML7tkWnhE0JI="></link> <link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-1LNAeuiM/rtsY1jIgLovUZ7HbYnOCkj3ZyailNA3rFQ="></link>
</head> </head>
<body style="padding-top:60px"> <body>
<div class="navbar navbar-expand fixed-top navbar-dark bg-primary"> <div class="navbar navbar-expand fixed-top navbar-dark bg-primary">
<div class="container"> <div class="container">
<a href="/" class="navbar-brand">GoWiki</a> <a href="/" class="navbar-brand">GoWiki</a>
@ -17,7 +17,7 @@
<div class="container"> <div class="container">
<div id="back-to-top" class="proper-content"> <div id="back-to-top" class="proper-content">
{{if .Msg}} {{if .Msg}}
<div class="alert alert-danger" style="margin-top:30px"> <div class="alert alert-danger msg">
<h4>Error!</h4> <h4>Error!</h4>
<p>{{.Msg}}</p> <p>{{.Msg}}</p>
</div> </div>

View file

@ -22,7 +22,9 @@
<a href="/{{.Data.StoreTitle}}/md" class="btn btn-sm btn-primary">Markdown</a> <a href="/{{.Data.StoreTitle}}/md" class="btn btn-sm btn-primary">Markdown</a>
{{if .Login}} {{if .Login}}
<a href="/{{.Data.StoreTitle}}/edit" class="btn btn-sm btn-primary">Edit</a> <a href="/{{.Data.StoreTitle}}/edit" class="btn btn-sm btn-primary">Edit</a>
{{if eq .Data.Perm 3}}
<a href="/{{.Data.StoreTitle}}/share" class="btn btn-sm btn-primary">Share</a> <a href="/{{.Data.StoreTitle}}/share" class="btn btn-sm btn-primary">Share</a>
{{end}}
{{if ne .Data.StoreTitle "wiki/Index"}} {{if ne .Data.StoreTitle "wiki/Index"}}
<a href="/{{.Data.StoreTitle}}/del" class="btn btn-sm btn-primary">Delete</a> <a href="/{{.Data.StoreTitle}}/del" class="btn btn-sm btn-primary">Delete</a>
{{end}} {{end}}

View file

@ -1,4 +1,5 @@
{{define "body"}} {{define "body"}}
{{if not .Msg}}
<div class="page-header"> <div class="page-header">
<div class="row"> <div class="row">
<div class="col-xs-4 offset-4"> <div class="col-xs-4 offset-4">
@ -29,3 +30,4 @@
</div> </div>
</div> </div>
{{end}} {{end}}
{{end}}

View file

@ -23,7 +23,7 @@
<div class="col"> <div class="col">
{{range $item := .Data}} {{range $item := .Data}}
<a href="/{{$item.StoreTitle}}"><b>{{$item.Title}}</b></a> <a href="/{{$item.StoreTitle}}"><b>{{$item.Title}}</b></a>
<pre style="white-space:pre-wrap">{{$item.Text}}</pre><br> <pre class="wrap">{{$item.HTML}}</pre><br>
{{end}} {{end}}
</div> </div>
</div> </div>