From 85f93bae69960d095806d0224000a88e5c6ac1a5 Mon Sep 17 00:00:00 2001 From: ston1th Date: Fri, 14 Sep 2018 00:14:49 +0200 Subject: [PATCH] first working version --- .gitignore | 1 + Makefile | 3 +- pkg/cmd/cmd.go | 21 +- pkg/core/const.go | 8 + pkg/core/permission.go | 21 +- pkg/core/types.go | 39 +- pkg/db/db.go | 26 +- pkg/db/helper.go | 20 + pkg/db/page.go | 92 +++-- pkg/db/user.go | 20 +- pkg/index/index.go | 1 - pkg/render/render.go | 8 +- pkg/server/context.go | 70 ++-- pkg/server/handler.go | 292 ++++++++------ pkg/server/routes.go | 91 +++-- pkg/server/server.go | 28 +- pkg/server/templates.go | 450 +++++++++++++--------- pkg/server/xsrf.go | 11 +- templates.sh | 20 +- templates/all.html | 2 +- templates/menu.html | 9 +- templates/page.html | 43 +++ templates/pageDel.html | 20 + templates/{wikiNew.html => pageEdit.html} | 17 +- templates/{wikiMD.html => pageMD.html} | 2 +- templates/{wikiEdit.html => pageNew.html} | 31 +- templates/pageShare.html | 16 + templates/pageView.html | 26 ++ templates/search.html | 5 +- templates/{users.html => user.html} | 0 templates/wiki.html | 41 -- todo.txt | 2 - 32 files changed, 864 insertions(+), 572 deletions(-) create mode 100644 pkg/core/const.go create mode 100644 templates/page.html create mode 100644 templates/pageDel.html rename templates/{wikiNew.html => pageEdit.html} (75%) rename templates/{wikiMD.html => pageMD.html} (72%) rename templates/{wikiEdit.html => pageNew.html} (65%) create mode 100644 templates/pageShare.html create mode 100644 templates/pageView.html rename templates/{users.html => user.html} (100%) delete mode 100644 templates/wiki.html delete mode 100644 todo.txt diff --git a/.gitignore b/.gitignore index bd54b44..a4694b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ gowiki tmp run.sh +TODO.txt diff --git a/Makefile b/Makefile index 3131911..8950a7c 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,6 @@ PROGRAM=gowiki all: $(PROGRAM) setup: - $(CC) get github.com/tools/godep $(CC) get github.com/client9/misspell $(PROGRAM): clean codeqa @@ -26,7 +25,7 @@ golint: $(GOPATH)/bin/golint . misspell: - $(GOPATH)/bin/misspell **/* + $(GOPATH)/bin/misspell -i functionallity,avaliable,HALP,halp,agains,becuase,Christiaan,compatiblity,conjuction,frequence,inital,inot,occurance,overriden,pervious,preceeded,preceeds,precendence,PROPOGATE,reamining,represenation,soley,substract,supression,whcih * templates: $(CC) generate diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 97db392..b4bb16f 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -13,7 +13,6 @@ import ( "syscall" "time" - "git.giftfish.de/ston1th/gowiki/pkg/db" "git.giftfish.de/ston1th/gowiki/pkg/log" "git.giftfish.de/ston1th/gowiki/pkg/server" ) @@ -29,7 +28,7 @@ const ( defRunGroup = "wiki" ) -func server(conf serverConfig) (err error) { +func initServer(conf serverConfig) (err error) { dropCfg := godrop.Config{ User: conf.User, Group: conf.Group, @@ -52,7 +51,7 @@ func server(conf serverConfig) (err error) { return } - srv := server.NewHTTPServer(l, conf.Version, conf.SecureCookie) + srv := server.NewHTTPServer(l, conf.Version, conf.DataDir+"/"+blevePath, conf.SecureCookie) err = srv.Start() if err != nil { return @@ -92,7 +91,7 @@ func Run(version string) { Flags: defaultFlags(serverFlags()), Action: func(c *cli.Context) error { conf.Version = app.Version - if err := server(conf); err != nil { + if err := initServer(conf); err != nil { stdlog.Fatal("server: ", err) } return nil @@ -106,9 +105,10 @@ func Run(version string) { if err := os.Chdir(conf.DataDir); err != nil { stdlog.Fatal("dump: ", err) } - if err := db.Dump(blevePath, verbose); err != nil { - stdlog.Fatal("dump: ", err) - } + //TODO + //if err := db.Dump(blevePath, verbose); err != nil { + // stdlog.Fatal("dump: ", err) + //} return nil }, }, @@ -120,9 +120,10 @@ func Run(version string) { if err := os.Chdir(conf.DataDir); err != nil { stdlog.Fatal("restore: ", err) } - if err := db.Restore(blevePath, verbose); err != nil { - stdlog.Fatal("restore: ", err) - } + //TODO + //if err := db.Restore(blevePath, verbose); err != nil { + // stdlog.Fatal("restore: ", err) + //} return nil }, }, diff --git a/pkg/core/const.go b/pkg/core/const.go new file mode 100644 index 0000000..5b1fc86 --- /dev/null +++ b/pkg/core/const.go @@ -0,0 +1,8 @@ +package core + +const ( + IndexPage = "Index" + WikiSection = "wiki" + + IndexURI = WikiSection + "/" + IndexPage +) diff --git a/pkg/core/permission.go b/pkg/core/permission.go index 8c9953a..93efd48 100644 --- a/pkg/core/permission.go +++ b/pkg/core/permission.go @@ -21,6 +21,18 @@ func ParsePerm(p int) Permission { return Invalid } +func ParsePermString(p string) Permission { + switch p { + case "1": + return Public + case "2": + return Internal + case "3": + return Private + } + return Invalid +} + func ReadPerm(username string, p *Page) bool { switch p.Perm { case Public: @@ -33,7 +45,14 @@ func ReadPerm(username string, p *Page) bool { return false } -func WritePerm(username string, p *Page) bool { +func WritePerm(username, section string, p *Page) bool { + if section != WikiSection && section != username { + return false + } + return writePerm(username, p) +} + +func writePerm(username string, p *Page) bool { switch p.Perm { case Public, Internal: if p.Owner == "" { diff --git a/pkg/core/types.go b/pkg/core/types.go index 4a17c8f..fc50391 100644 --- a/pkg/core/types.go +++ b/pkg/core/types.go @@ -3,21 +3,22 @@ package core import "html/template" type Page struct { - Title string `json:"title"` - Markdown string `json:"markdown"` - Index template.HTML `json:"index"` - Text template.HTML `json:"text"` - Created string `json:"created"` - Updated string `json:"updated"` - Owner string `json:"owner"` - Perm Permission `json:"perm"` + Title string `json:"title"` + StoreTitle string `json:"store_title"` + Markdown string `json:"markdown"` + PageIndex template.HTML `json:"page_index"` + HTML template.HTML `json:"html"` + Created string `json:"created"` + Updated string `json:"updated"` + Owner string `json:"owner"` + Perm Permission `json:"perm"` } -type Result struct { - Title string - StoreTitle string - Text string -} +type Pages []Page + +func (p Pages) Len() int { return len(p) } +func (p Pages) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p Pages) Less(i, j int) bool { return p[i].StoreTitle < p[j].StoreTitle } type User struct { Username string @@ -25,3 +26,15 @@ type User struct { Admin bool Locked int } + +type Users []User + +func (u Users) Len() int { return len(u) } +func (u Users) Swap(i, j int) { u[i], u[j] = u[j], u[i] } +func (u Users) Less(i, j int) bool { return u[i].Username < u[j].Username } + +type Result struct { + Title string + StoreTitle string + Text string +} diff --git a/pkg/db/db.go b/pkg/db/db.go index 21b97de..90cfd83 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -8,7 +8,6 @@ import ( "git.giftfish.de/ston1th/gowiki/pkg/core" "git.giftfish.de/ston1th/gowiki/pkg/index" "git.giftfish.de/ston1th/gowiki/pkg/store" - "git.giftfish.de/ston1th/gowiki/pkg/util" ) const ( @@ -17,11 +16,9 @@ const ( usersFile = "users.db" pagesFile = "pages.db" bcryptCost = 13 - indexName = "Index" welcome = `# Welcome to GoWiki This is the [Index](/wiki/Index) page. You can customize it how you like.` - wikiSection = "wiki" ) /*const ( @@ -40,12 +37,13 @@ type DB struct { Index *index.Index } -func NewDB(indexPath string) (db *DB, err error) { +func New(indexPath string) (db *DB, err error) { db = &DB{cache: cache.NewCache()} - db.Index, err = index.NewIndex(indexPath) - if err != nil { - return - } + //TODO + //db.Index, err = index.NewIndex(indexPath) + //if err != nil { + // return + //} db.users, err = store.NewBoltStore(usersFile, nil) if err != nil { @@ -66,11 +64,9 @@ func NewDB(indexPath string) (db *DB, err error) { if err != nil { return } - if _, err = db.pageExists(util.StoreTitle(wikiSection, indexName), defUser); err != nil { - _, err = db.CreatePage(indexName, wikiSection, welcome, defUser, core.Public) - if err != nil { - return - } + _, err = db.CreatePage(core.IndexPage, core.WikiSection, welcome, defUser, core.Public) + if err != nil && err != store.ErrKeyNotFound { + return } return } @@ -84,5 +80,7 @@ func (db *DB) Close() error { if err != nil { return err } - return db.Index.Close() + return nil + //TODO + //return db.Index.Close() } diff --git a/pkg/db/helper.go b/pkg/db/helper.go index ae0cd11..e2dc880 100644 --- a/pkg/db/helper.go +++ b/pkg/db/helper.go @@ -5,6 +5,26 @@ import ( "time" ) +var reservedUsers = []string{ + "all", + "login", + "logout", + "new", + "search", + "user", + "view", + "wiki", +} + +func reservedUser(username string) bool { + for _, v := range reservedUsers { + if username == v { + return true + } + } + return false +} + func validatePassword(pw string) (b []byte) { b = []byte(pw) if len(pw) <= 56 { diff --git a/pkg/db/page.go b/pkg/db/page.go index 7b0d400..f8ce39b 100644 --- a/pkg/db/page.go +++ b/pkg/db/page.go @@ -5,20 +5,38 @@ import ( "git.giftfish.de/ston1th/gowiki/pkg/render" "git.giftfish.de/ston1th/gowiki/pkg/store" "git.giftfish.de/ston1th/gowiki/pkg/util" + "sort" ) var ( errPageNotFound = store.ErrKeyNotFound + errPermissionDenied = dbErr("permission denied") errInvalidPermission = dbErr("invalid permission") errPrivateWikiPage = dbErr("private wiki pages are not allowed") + errDeleteIndexPage = dbErr("index page can not be deleted") ) +func (db *DB) GetAllPages(username string) (pages core.Pages) { + db.pages.ForEach(func(title string, _ []byte) error { + if title == core.IndexURI { + return nil + } + p, err := db.GetPage(title, username) + if err == nil { + pages = append(pages, *p) + } + return nil + }) + sort.Sort(pages) + return +} + func (db *DB) GetPage(title, username string) (p *core.Page, err error) { p = db.cache.Get(title) if p == nil { - p = new(core.Page) - err = db.pages.Get(title, p) + p, err = db.getPage(title, username) db.cache.Add(title, p) + return } if !core.ReadPerm(username, p) { err = errPageNotFound @@ -26,7 +44,7 @@ func (db *DB) GetPage(title, username string) (p *core.Page, err error) { return } -func (db *DB) pageExists(title, username string) (p *core.Page, err error) { +func (db *DB) getPage(title, username string) (p *core.Page, err error) { p = new(core.Page) err = db.pages.Get(title, p) if err != nil { @@ -43,78 +61,98 @@ func (db *DB) CreatePage(title, section, markdown, username string, p core.Permi err = errInvalidPermission return } - if section == wikiSection && p == core.Private { + if section == core.WikiSection && p == core.Private { err = errPrivateWikiPage return } st := util.StoreTitle(section, title) - if _, err = db.pageExists(st, username); err != nil { + if _, err = db.getPage(st, username); err != nil && err != store.ErrKeyNotFound { return } page = &core.Page{ - Title: title, - Markdown: markdown, - Created: created(username), - Owner: username, - Perm: p, + Title: title, + StoreTitle: st, + Markdown: markdown, + Created: created(username), + Owner: username, + Perm: p, } - search := render.Render(page) + //search := render.Render(page) + render.Render(page) err = db.pages.Set(st, page) if err != nil { return } - err = db.Index.Add(title, st, search) - if err != nil { - return - } + //TODO + //err = db.Index.Add(title, st, search) + //if err != nil { + // return + //} db.cache.Add(st, page) return } -func (db *DB) UpdatePage(title, section, markdown, username string, perm int) (page *core.Page, err error) { - p := core.ParsePerm(perm) +func (db *DB) UpdatePage(title, section, markdown, username string, p core.Permission) (err error) { if p == core.Invalid { err = errInvalidPermission return } - if section == wikiSection && p == core.Private { + if section == core.WikiSection && p == core.Private { err = errPrivateWikiPage return } st := util.StoreTitle(section, title) - page, err = db.pageExists(st, username) + page, err := db.getPage(st, username) if err != nil { return } + if !core.WritePerm(username, section, page) { + return errPermissionDenied + } page.Markdown = markdown page.Updated = created(username) page.Perm = p - search := render.Render(page) + //TODO + //search := render.Render(page) + render.Render(page) err = db.pages.Set(st, page) if err != nil { return } - err = db.Index.Add(title, st, search) - if err != nil { - return - } + //TODO + //err = db.Index.Add(title, st, search) + //if err != nil { + // return + //} db.cache.Add(st, page) return } func (db *DB) DeletePage(title, section, username string) (err error) { - st := util.StoreTitle(section, title) - if _, err = db.pageExists(st, username); err != nil { + var page *core.Page + if section == core.WikiSection && title == core.IndexPage { + err = errDeleteIndexPage return } + st := util.StoreTitle(section, title) + if page, err = db.getPage(st, username); err != nil { + return + } + if !core.WritePerm(username, section, page) { + return errPermissionDenied + } + if err = db.pages.Delete(st); err != nil { return } - return db.Index.Delete(st) + db.cache.Delete(st) + return nil + //TODO + //return db.Index.Delete(st) } diff --git a/pkg/db/user.go b/pkg/db/user.go index b4a66f2..28f3502 100644 --- a/pkg/db/user.go +++ b/pkg/db/user.go @@ -4,19 +4,24 @@ import ( "errors" "git.giftfish.de/ston1th/gowiki/pkg/core" "golang.org/x/crypto/bcrypt" + "sort" ) var ( errUserNotFound = dbErr("user not found") errUserExists = dbErr("user already exist") + errUserReserved = dbErr("username is reserved") ) -func (db *DB) GetUsers() (users []core.User, err error) { - // TODO - /*users, err = db.DumpUsers() - for i := range users { - users[i].Password = "" - }*/ +func (db *DB) GetUsers() (users core.Users, err error) { + db.users.ForEach(func(username string, _ []byte) error { + u, err := db.GetUser(username) + if err == nil { + users = append(users, u) + } + return nil + }) + sort.Sort(users) return } @@ -81,6 +86,9 @@ func (bs *BoltStore) RestoreUsers(users []User) error { }*/ func (db *DB) CreateUser(username, password string, admin bool) error { + if reservedUser(username) { + return errUserReserved + } if err := db.UserExists(username); err == nil { return errUserExists } diff --git a/pkg/index/index.go b/pkg/index/index.go index 5a24a38..fb44231 100644 --- a/pkg/index/index.go +++ b/pkg/index/index.go @@ -8,7 +8,6 @@ import ( "git.giftfish.de/ston1th/gowiki/pkg/core" "github.com/blevesearch/bleve" - // "github.com/blevesearch/bleve/document" ) const ( diff --git a/pkg/render/render.go b/pkg/render/render.go index b4a822b..d52a106 100644 --- a/pkg/render/render.go +++ b/pkg/render/render.go @@ -10,14 +10,14 @@ import ( "git.giftfish.de/ston1th/gowiki/pkg/core" "github.com/blevesearch/bleve/analysis" - htmlFilter "github.com/blevesearch/bleve/analysis/char_filters/html_char_filter" + "github.com/blevesearch/bleve/analysis/char/html" "github.com/russross/blackfriday" ) var filter = makeFilter() func makeFilter() analysis.CharFilter { - f, _ := htmlFilter.CharFilterConstructor(nil, nil) + f, _ := html.CharFilterConstructor(nil, nil) return f } @@ -35,8 +35,8 @@ func Render(p *core.Page) string { html = strings.Replace(html, "", `
`, -1) html = strings.Replace(html, "", `
`, -1) html = strings.Replace(html, " 0 && uri[0] != '/' { + uri = "/" + uri + } 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) } @@ -146,9 +161,8 @@ func (c *Context) FormSlice(name string) []string { return c.Request.PostForm[name] } -func (c *Context) Form(name string) (ret string) { - ret = c.Request.PostFormValue(name) - return +func (c *Context) Form(name string) string { + return c.Request.PostFormValue(name) } func (c *Context) Var(name string) (ret string) { @@ -156,6 +170,7 @@ func (c *Context) Var(name string) (ret string) { return } +// TODO remove f // CheckToken validates the xsrf token func (c *Context) CheckXsrf(f func()) (ok bool) { ok = checkXsrf(c.Form("token"), c.Token.RawSig()[:keySize]) @@ -171,7 +186,27 @@ func (c *Context) CheckXsrf(f func()) (ok bool) { } func (c *Context) LoggedOn() (ok bool) { - _, ok = c.Token.Claims["user"] + _, ok = c.Token.Claims.Get(userClaim) + 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 } @@ -182,28 +217,15 @@ func (c *Context) SetCookie(claims map[string]interface{}) { 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, - }) + http.SetCookie(c.Response, newCookie(c.Token.String(), int(jwt.DefaultExpiry.Seconds()))) } -func (c *Context) GetUser() string { - if ret, ok := c.Token.Claims["user"].(string); ok { - return ret - } - return "" +func (c *Context) User() string { + return c.Token.Claims.GetString(userClaim) } -func (c *Context) GetAdmin() bool { - if ret, ok := c.Token.Claims["admin"].(bool); ok { - return ret - } - return false +func (c *Context) Admin() bool { + return c.Token.Claims.GetBool(adminClaim) } type ctxHandler func(*Context) diff --git a/pkg/server/handler.go b/pkg/server/handler.go index 6d60cca..9ec70a5 100644 --- a/pkg/server/handler.go +++ b/pkg/server/handler.go @@ -5,7 +5,8 @@ package server import ( "git.giftfish.de/ston1th/gowiki/pkg/core" "git.giftfish.de/ston1th/gowiki/pkg/log" - "html/template" + "git.giftfish.de/ston1th/gowiki/pkg/util" + "git.giftfish.de/ston1th/jwt" "net/http" "time" ) @@ -20,7 +21,7 @@ func (nf *notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func adminAuthHandler(h ctxHandler) ctxHandler { return func(ctx *Context) { - if ctx.GetAdmin() { + if ctx.Admin() { h(ctx) return } @@ -30,11 +31,11 @@ func adminAuthHandler(h ctxHandler) ctxHandler { func userAuthHandler(h ctxHandler) ctxHandler { return func(ctx *Context) { - if ctx.GetAdmin() { + if ctx.Admin() { h(ctx) return } - if ctx.GetUser() == ctx.Var("user") { + if ctx.User() == ctx.Var("user") { h(ctx) return } @@ -68,12 +69,12 @@ func staticHandler(ctx *Context) { } func indexHandler(ctx *Context) { - ctx.Redirect("/wiki/Index", 302) + ctx.Redirect(core.IndexURI, 302) } func loginHandler(ctx *Context) { if ctx.LoggedOn() { - ctx.Redirect("/wiki/Index", 302) + ctx.Redirect(core.IndexURI, 302) return } ctx.Template("loginHandler") @@ -87,11 +88,10 @@ func loginHandler(ctx *Context) { case "POST": user := ctx.Form("user") password := ctx.Form("password") - submit := ctx.Form("submit") if !ctx.CheckXsrf(nil) { return } - if user == "" || password == "" || submit != "Login" { + if user == "" || password == "" { ctx.Data.Msg = "wrong inputs" ctx.Exec() return @@ -102,8 +102,8 @@ func loginHandler(ctx *Context) { ctx.Exec() return } - ctx.SetCookie(map[string]interface{}{"username": u.Username, "admin": u.Admin}) - ctx.Redirect("/wiki/Index", 302) + ctx.SetCookie(map[string]interface{}{userClaim: u.Username, adminClaim: u.Admin}) + ctx.Redirect(core.IndexURI, 302) } } @@ -118,15 +118,9 @@ func searchHandler(ctx *Context) { ctx.Exec() case "POST": search := ctx.Form("search") - submit := ctx.Form("submit") if !ctx.CheckXsrf(nil) { return } - if submit != "Search" { - ctx.Data.Msg = "wrong inputs" - ctx.Exec() - return - } res, err := ctx.Srv.DB.Index.Search(search) if err != nil { @@ -135,7 +129,7 @@ func searchHandler(ctx *Context) { return } ctx.Data.Search = search - ctx.Data.Data = template.HTML(res) + ctx.Data.Data = res ctx.Exec() } } @@ -143,167 +137,213 @@ func searchHandler(ctx *Context) { func allHandler(ctx *Context) { ctx.Template("allHandler") ctx.Data = webData{ - Title: "All Articles", - BodyTitle: "All Articles", - Admin: ctx.GetAdmin(), + Title: "All Pages", + BodyTitle: "All Pages", + Admin: ctx.Admin(), } - all, err := ctx.Srv.DB.Index.GetAll(!ctx.LoggedOn()) - if err != nil { - ctx.Data.Msg = err.Error() - } - ctx.Data.Data = all + ctx.Data.Data = ctx.Srv.DB.GetAllPages(ctx.User()) ctx.Exec() } -func wikiNewHandler(ctx *Context) { - ctx.Template("wikiNewHandler") - ctx.Data = webData{Title: "New Article"} +func pageNewHandler(ctx *Context) { + ctx.Template("pageNewHandler") + ctx.Data = webData{Title: "New Page"} switch ctx.Method() { case "GET": ctx.Exec() case "POST": + section := ctx.Form("section") title := ctx.Form("title") - text := ctx.Form("text") - public := ctx.Form("public") - pub := false - if public == "0" { - pub = true - } - submit := ctx.Form("submit") + markdown := ctx.Form("markdown") + perm := ctx.Form("perm") p := core.Page{ - Title: title, - MD: text, + Title: title, + Markdown: markdown, } ctx.Data.Data = p if !ctx.CheckXsrf(nil) { return } - if submit != "Submit" { - ctx.Data.Msg = "wrong inputs" - ctx.Exec() - return + if section != core.WikiSection { + section = ctx.User() } - newTitle, err := ctx.Srv.DB.Index.Index(title, text, ctx.GetUser(), pub) + page, err := ctx.Srv.DB.CreatePage(title, section, markdown, ctx.User(), core.ParsePermString(perm)) if err != nil { ctx.Data.Msg = err.Error() ctx.Exec() return } - ctx.Redirect("/wiki/"+newTitle, 302) + ctx.Redirect(page.StoreTitle, 302) } } -func wikiHandler(ctx *Context) { - if ctx.T == nil { - ctx.Template("wikiHandler") - } +func pageHandler(ctx *Context) { ctx.Data = webData{ - Admin: ctx.GetAdmin(), + Admin: ctx.Admin(), } + section := ctx.Var("section") title := ctx.Var("title") - art, err := ctx.Srv.DB.Index.Get(!ctx.LoggedOn(), title) + user := ctx.User() + if key := ctx.Var("key"); key != "" { + t, err := jwt.DecodeToken(key) + if err != nil { + log.Println("DecodeToken:", err) + ctx.NotFound() + return + } + if err = ctx.Srv.JWT.Verify(t); err != nil { + log.Println("VerifyToken:", err) + ctx.NotFound() + return + } + user = t.Claims.GetString(sharedClaim) + section = t.Claims.GetString(sectionClaim) + title = t.Claims.GetString(titleClaim) + ctx.Data.Key = key + if ctx.T == nil { + ctx.Template("pageViewHandler") + } + } else { + if ctx.T == nil { + ctx.Template("pageHandler") + } + } + page, err := ctx.Srv.DB.GetPage(util.StoreTitle(section, title), user) if err != nil { + log.Println(err) ctx.NotFound() return } - art.LinkTitle = title - ctx.Data.Data = art - ctx.Data.Title = art.Title - ctx.Data.BodyTitle = art.Title + ctx.Data.Data = page + ctx.Data.Title = page.Title + ctx.Data.BodyTitle = page.Title ctx.Exec() } -func wikiMDHandler(f ctxHandler) ctxHandler { +func pageMDHandler(f ctxHandler) ctxHandler { return func(ctx *Context) { - ctx.Template("wikiMDHandler") + ctx.Template("pageMDHandler") f(ctx) } } -func wikiEditHandler(ctx *Context) { - ctx.Template("wikiEditHandler") +// TODO add share token blacklisting +func pageShareHandler(ctx *Context) { + ctx.Template("pageShareHandler") ctx.Data = webData{} + section := ctx.Var("section") title := ctx.Var("title") + user := ctx.User() + st := util.StoreTitle(section, title) + page, err := ctx.Srv.DB.GetPage(st, user) + if err != nil { + ctx.NotFound() + return + } + t := jwt.NewToken(map[string]interface{}{ + sharedClaim: user, + sectionClaim: section, + titleClaim: title, + jwt.ExpClaim: jwt.NewExp(time.Hour * 24), + }, nil) + + if err = ctx.Srv.JWT.Sign(t); err != nil { + log.Println("SignToken:", err) + ctx.NotFound() + return + } + /* TODO + host := ctx.Request.Header.Get("X-Forwarded-For") + if host == "" { + ctx.Request.Host + } + ctx.Data.Data = "https://" + host + "/view/" + t.String() + */ + ctx.Data.Key = t.String() + ctx.Data.Data = st + ctx.Data.Title = page.Title + ctx.Data.BodyTitle = page.Title + ctx.Exec() +} + +func pageEditHandler(ctx *Context) { + ctx.Template("pageEditHandler") + ctx.Data = webData{} + section := ctx.Var("section") + title := ctx.Var("title") + page, err := ctx.Srv.DB.GetPage(util.StoreTitle(section, title), ctx.User()) + if err != nil { + ctx.NotFound() + return + } switch ctx.Method() { case "GET": - art, err := ctx.Srv.DB.Index.Get(!ctx.LoggedOn(), title) - if err != nil { - ctx.NotFound() - return - } - art.LinkTitle = title - ctx.Data.Data = art - ctx.Data.Title = art.Title - ctx.Data.BodyTitle = art.Title + ctx.Data.Data = page + ctx.Data.Title = page.Title + ctx.Data.BodyTitle = page.Title ctx.Exec() case "POST": - ftitle := ctx.Form("title") - text := ctx.Form("text") - public := ctx.Form("public") - pub := false - if public == "0" { - pub = true - } - submit := ctx.Form("submit") + storeTitle := ctx.Form("store_title") + section, title = util.UnstoreTitle(storeTitle) + markdown := ctx.Form("markdown") + perm := core.ParsePermString(ctx.Form("perm")) ctx.Data.Data = core.Page{ - Title: ftitle, - LinkTitle: title, - MD: text, - Public: pub, + Title: page.Title, + StoreTitle: storeTitle, + Markdown: markdown, + Perm: perm, } - ctx.Data.Title = ftitle - ctx.Data.BodyTitle = ftitle + ctx.Data.Title = page.Title + ctx.Data.BodyTitle = page.Title if !ctx.CheckXsrf(nil) { return } - if submit != "Submit" { - ctx.Data.Msg = "wrong inputs" - ctx.Exec() - return - } - err := ctx.Srv.DB.Index.Update(title, text, ctx.GetUser(), pub) + err = ctx.Srv.DB.UpdatePage(title, section, markdown, ctx.User(), perm) if err != nil { ctx.Data.Msg = err.Error() ctx.Exec() return } - ctx.Srv.Editors.Remove(title) - ctx.Redirect("/wiki/"+title, 302) + ctx.Redirect("/"+page.StoreTitle, 302) } } -func wikiDelHandler(ctx *Context) { +func pageDelHandler(ctx *Context) { + section := ctx.Var("section") title := ctx.Var("title") - submit := ctx.Form("submit") - f := func() { - ctx.Redirect("/wiki/"+title, 302) + st := util.StoreTitle(section, title) + ctx.Template("pageDelHandler") + ctx.Data.Data = st + ctx.Data.Title = title + ctx.Data.BodyTitle = title + switch ctx.Method() { + case "GET": + ctx.Exec() + case "POST": + if !ctx.CheckXsrf(nil) { + return + } + if err := ctx.Srv.DB.DeletePage(title, section, ctx.User()); err != nil { + ctx.Data.Msg = err.Error() + ctx.Exec() + return + } + ctx.Redirect(core.IndexURI, 302) } - if !ctx.CheckXsrf(f) { - return - } - if submit != "Delete" { - f() - return - } - if err := ctx.Srv.Index.Delete(title); err != nil { - log.Println("delete:", err) - f() - } - ctx.Redirect("/wiki/Index", 302) } -func usersHandler(ctx *Context) { +func userHandler(ctx *Context) { if !ctx.LoggedOn() { ctx.NotFound() return } - ctx.Template("usersHandler") + ctx.Template("userHandler") ctx.Data = webData{ Title: "Users", BodyTitle: "Users", - User: ctx.GetUser(), + User: ctx.User(), } - us, err := ctx.Srv.BS.GetUsers() + us, err := ctx.Srv.DB.GetUsers() if err != nil { ctx.Data.Msg = err.Error() } @@ -329,21 +369,20 @@ func userNewHandler(ctx *Context) { if admin == "0" { adm = true } - submit := ctx.Form("submit") if !ctx.CheckXsrf(nil) { return } - if user == "" || password == "" || password != repeat || submit != "Submit" { + if user == "" || password == "" || password != repeat { ctx.Data.Msg = "wrong inputs" ctx.Exec() return } - if err := ctx.Srv.BS.CreateUser(user, password, adm); err != nil { + if err := ctx.Srv.DB.CreateUser(user, password, adm); err != nil { ctx.Data.Msg = err.Error() ctx.Exec() return } - ctx.Redirect("/users", 302) + ctx.Redirect("/user", 302) } } @@ -355,7 +394,7 @@ func userEditHandler(ctx *Context) { } switch ctx.Method() { case "GET": - u, err := ctx.Srv.BS.GetUserWithoutPassword(ctx.Var("user")) + u, err := ctx.Srv.DB.GetUserWithoutPassword(ctx.Var("user")) if err != nil { ctx.Data.Msg = err.Error() ctx.Exec() @@ -372,42 +411,41 @@ func userEditHandler(ctx *Context) { if admin == "0" { adm = true } - submit := ctx.Form("submit") if !ctx.CheckXsrf(nil) { return } - if user == "" || password != repeat || submit != "Submit" { + if user == "" || password != repeat { ctx.Data.Msg = "wrong inputs" ctx.Exec() return } - if ctx.GetAdmin() { - if err := ctx.Srv.BS.AdminUpdateUser(user, password, adm); err != nil { + if ctx.Admin() { + if err := ctx.Srv.DB.AdminUpdateUser(user, password, adm); err != nil { ctx.Data.Msg = err.Error() ctx.Exec() return } } else { - if user == ctx.GetUser() { - if err := ctx.Srv.BS.UpdateUserPassword(user, password); err != nil { + if user == ctx.User() { + if err := ctx.Srv.DB.UpdateUserPassword(user, password); err != nil { ctx.Data.Msg = err.Error() ctx.Exec() return } - ctx.Redirect("/wiki/Index", 302) + ctx.Redirect(core.IndexURI, 302) return } } - ctx.Redirect("/users", 302) + ctx.Redirect("/user", 302) } } func userUnlockHandler(ctx *Context) { user := ctx.Var("user") - if err := ctx.Srv.BS.UnlockUser(user); err != nil { + if err := ctx.Srv.DB.UnlockUser(user); err != nil { log.Println("unlock:", err) } - ctx.Redirect("/users", 302) + ctx.Redirect("/user", 302) } func userDelHandler(ctx *Context) { @@ -418,7 +456,7 @@ func userDelHandler(ctx *Context) { } switch ctx.Method() { case "GET": - user, err := ctx.Srv.BS.GetUserWithoutPassword(ctx.Var("user")) + user, err := ctx.Srv.DB.GetUserWithoutPassword(ctx.Var("user")) if err != nil { ctx.Data.Msg = err.Error() ctx.Exec() @@ -427,14 +465,14 @@ func userDelHandler(ctx *Context) { ctx.Data.Data = user ctx.Exec() case "POST": - if err := ctx.Srv.BS.DeleteUser(ctx.Var("user")); err != nil { + if err := ctx.Srv.DB.DeleteUser(ctx.Var("user")); err != nil { log.Println("delete:", err) } - ctx.Redirect("/users", 302) + ctx.Redirect("/user", 302) } } func logoutHandler(ctx *Context) { ctx.SetCookie(nil) - ctx.Redirect("/wiki/Index", 302) + ctx.Redirect(core.IndexURI, 302) } diff --git a/pkg/server/routes.go b/pkg/server/routes.go index 9b3ebac..fdb8c0e 100644 --- a/pkg/server/routes.go +++ b/pkg/server/routes.go @@ -1,4 +1,4 @@ -// Copyright (C) 2017 Marius Schellenberger +// Copyright (C) 2018 Marius Schellenberger package server @@ -37,7 +37,7 @@ var routes = []route{ { "/new", authHandler( - wikiNewHandler), + pageNewHandler), []string{"GET", "POST"}, }, { @@ -46,55 +46,11 @@ var routes = []route{ []string{"GET"}, }, { - "/wiki/{title:[a-zA-Z0-9+-]+$}", - wikiHandler, - []string{"GET"}, - }, - { - "/wiki/{title:[a-zA-Z0-9+-]+}/md", - wikiMDHandler( - wikiHandler), - []string{"GET"}, - }, - { - "/wiki/{title:[a-zA-Z0-9+-]+}/edit", - authHandler( - wikiEditHandler), - []string{"GET", "POST"}, - }, - { - "/wiki/{title:[a-zA-Z0-9+-]+}/del", - authHandler( - wikiDelHandler), - []string{"POST"}, - }, - { - "/users", + "/user", adminAuthHandler( - usersHandler), + userHandler), []string{"GET"}, }, - { - "/users/{user:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+$}", - wikiHandler, - []string{"GET"}, - }, - { - "/users/{user:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}/md", - wikiMDHandler( - wikiHandler), - []string{"GET"}, - }, - { - "/users/{user:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}/edit", - wikiEditHandler, - []string{"GET", "POST"}, - }, - { - "/users/{user:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}/del", - wikiDelHandler, - []string{"POST"}, - }, { "/user/new", adminAuthHandler( @@ -124,4 +80,43 @@ var routes = []route{ logoutHandler, []string{"GET", "POST"}, }, + { + "/view/{key}", + pageHandler, + []string{"GET"}, + }, + { + "/view/{key}/md", + pageMDHandler( + pageHandler), + []string{"GET"}, + }, + { + "/{section}/{title}", + pageHandler, + []string{"GET"}, + }, + { + "/{section}/{title}/md", + pageMDHandler( + pageHandler), + []string{"GET"}, + }, + { + "/{section}/{title}/share", + pageShareHandler, + []string{"GET"}, + }, + { + "/{section}/{title}/edit", + authHandler( + pageEditHandler), + []string{"GET", "POST"}, + }, + { + "/{section}/{title}/del", + authHandler( + pageDelHandler), + []string{"GET", "POST"}, + }, } diff --git a/pkg/server/server.go b/pkg/server/server.go index 74f1450..ecc23d7 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -1,6 +1,7 @@ package server import ( + "bytes" "context" "github.com/gorilla/mux" "html/template" @@ -9,6 +10,7 @@ import ( "time" "git.giftfish.de/ston1th/gowiki/pkg/db" + "git.giftfish.de/ston1th/gowiki/pkg/log" "git.giftfish.de/ston1th/jwt" ) @@ -17,9 +19,10 @@ const ( ) type HTTPServer struct { - Version string - listener net.Listener - srv *http.Server + Version string + indexPath string + listener net.Listener + srv *http.Server DB *db.DB JWT *jwt.JWT @@ -28,14 +31,11 @@ type HTTPServer struct { res map[string][]byte } -func NewHTTPServer(l net.Listener, versin string, secCookie bool) (srv *HTTPServer) { +func NewHTTPServer(l net.Listener, version, indexPath string, secCookie bool) (srv *HTTPServer) { srv = &HTTPServer{ - Version: version, - SecCookie: secCookie, - listener: l, - JWT: jwt.New(time.Hour*12, nil, nil), + Version: version, + listener: l, } - srv.cookieStore() srv.loadTemplates() srv.srv = &http.Server{ Handler: srv.buildRoutes(), @@ -44,11 +44,13 @@ func NewHTTPServer(l net.Listener, versin string, secCookie bool) (srv *HTTPServ } func (s *HTTPServer) Start() (err error) { - s.DB, err = db.New() + //TODO make secret configurable + buf := bytes.NewBufferString("be688838ca8686e5c90689bf2ab585cef1137c999b48c70b92f67a5c34dc15697b5d11c982ed6d71be1e1e7f7b4e0733884aa97c3f7a339a8ed03577cf74be09") + s.JWT, err = jwt.New(jwt.DefaultExpiry, jwt.NewMemBlacklist(), buf) if err != nil { return } - s.Index, err = NewIndex(blevePath) + s.DB, err = db.New(s.indexPath) if err != nil { return } @@ -59,8 +61,8 @@ func (s *HTTPServer) Start() (err error) { } func (s *HTTPServer) Stop() error { - s.BS.Close() - s.Index.Close() + s.DB.Close() + s.JWT.Stop() ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() return s.srv.Shutdown(ctx) diff --git a/pkg/server/templates.go b/pkg/server/templates.go index 9e8e4eb..60bac8b 100644 --- a/pkg/server/templates.go +++ b/pkg/server/templates.go @@ -1,12 +1,13 @@ -// Copyright (C) 2017 Marius Schellenberger +// Copyright (C) 2018 Marius Schellenberger -// This file is auto-generated by build/templates.sh +// This file is auto-generated by templates.sh // using the contents of the templates directory package server import ( "errors" + "git.giftfish.de/ston1th/gowiki/pkg/log" "html/template" ) @@ -21,7 +22,7 @@ const ( @@ -118,16 +119,13 @@ const ( Index
  • - All Articles + All Pages
  • {{if .Login}}
  • - New Article + New Page
  • {{end}} -
  • - Snippets -
  • @@ -143,7 +141,7 @@ const ( @@ -29,7 +26,7 @@