first working version
This commit is contained in:
parent
14e0611f06
commit
85f93bae69
32 changed files with 863 additions and 571 deletions
|
|
@ -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
|
||||
},
|
||||
},
|
||||
|
|
|
|||
8
pkg/core/const.go
Normal file
8
pkg/core/const.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package core
|
||||
|
||||
const (
|
||||
IndexPage = "Index"
|
||||
WikiSection = "wiki"
|
||||
|
||||
IndexURI = WikiSection + "/" + IndexPage
|
||||
)
|
||||
|
|
@ -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 == "" {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
26
pkg/db/db.go
26
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import (
|
|||
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||
"github.com/blevesearch/bleve"
|
||||
// "github.com/blevesearch/bleve/document"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -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, "<table>", `<table class="table">`, -1)
|
||||
html = strings.Replace(html, "</h1>", `</h1><hr>`, -1)
|
||||
html = strings.Replace(html, "<a", `<a target="_blank"`, -1)
|
||||
p.Index = template.HTML(index)
|
||||
p.Text = template.HTML(html)
|
||||
p.PageIndex = template.HTML(index)
|
||||
p.HTML = template.HTML(html)
|
||||
return whiteSpace.ReplaceAllString(string(filter.Filter(rend)), " ")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,14 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
userClaim = "user"
|
||||
adminClaim = "admin"
|
||||
sharedClaim = "shared"
|
||||
sectionClaim = "section"
|
||||
titleClaim = "title"
|
||||
)
|
||||
|
||||
func newContext(w http.ResponseWriter, r *http.Request, s *HTTPServer) (ctx *Context) {
|
||||
h := w.Header()
|
||||
h.Set("X-Frame-Options", "DENY")
|
||||
|
|
@ -65,6 +73,8 @@ type webData struct {
|
|||
Admin bool
|
||||
Login bool
|
||||
|
||||
Key string
|
||||
|
||||
User string
|
||||
Token string
|
||||
Msg string
|
||||
|
|
@ -86,10 +96,11 @@ func (c *Context) Exec() {
|
|||
if c.Data.BodyTitle == "" {
|
||||
c.Data.BodyTitle = c.Data.Title
|
||||
}
|
||||
c.Data.Admin = c.GetAdmin()
|
||||
c.Data.User = c.GetUser()
|
||||
c.Data.Admin = c.Admin()
|
||||
c.Data.User = c.User()
|
||||
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
|
||||
|
|
@ -114,6 +125,7 @@ func (c *Context) NotFound() {
|
|||
c.Response.WriteHeader(http.StatusNotFound)
|
||||
c.Exec()
|
||||
}
|
||||
|
||||
func (c *Context) Template(name string) {
|
||||
c.T = c.Srv.templ[name]
|
||||
}
|
||||
|
|
@ -128,6 +140,9 @@ func (c *Context) SetHeader(name, value string) {
|
|||
}
|
||||
|
||||
func (c *Context) Redirect(uri string, code int) {
|
||||
if len(uri) > 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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 (
|
|||
<ul type="circle">
|
||||
{{if .Data}}
|
||||
{{range $item := .Data}}
|
||||
<li><a href="/wiki/{{$item.LinkTitle}}"><b>{{$item.Title}}</b></a></li>
|
||||
<li><a href="/{{$item.StoreTitle}}"><b>{{$item.Title}}</b></a></li>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</ul>
|
||||
|
|
@ -118,16 +119,13 @@ const (
|
|||
Index
|
||||
</a></li>
|
||||
<li><a href="/all">
|
||||
All Articles
|
||||
All Pages
|
||||
</a></li>
|
||||
{{if .Login}}
|
||||
<li><a href="/new">
|
||||
New Article
|
||||
New Page
|
||||
</a></li>
|
||||
{{end}}
|
||||
<li><a href="/snippets">
|
||||
Snippets
|
||||
</a></li>
|
||||
</ul>
|
||||
<form class="navbar-form navbar-left menu-search-bar" role="search" method="post" action="/search">
|
||||
<input type="hidden" name="token" value="{{.Token}}">
|
||||
|
|
@ -143,7 +141,7 @@ const (
|
|||
<ul class="nav navbar-nav navbar-right">
|
||||
{{if .Login}}
|
||||
{{if .Admin}}
|
||||
<li><a href="/users">
|
||||
<li><a href="/user">
|
||||
Users
|
||||
</a></li>
|
||||
{{else}}
|
||||
|
|
@ -177,6 +175,217 @@ const (
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}`
|
||||
pageDel = `{{define "body"}}
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<div class="col-xs-4 col-xs-offset-4">
|
||||
<div class="panel panel-danger">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Delete {{.Data}}</h3>
|
||||
</div>
|
||||
<form class="form-horizontal" action="/{{.Data}}/del" method="post">
|
||||
<input type="hidden" name="token" value="{{.Token}}">
|
||||
<button class="btn btn-sm btn-danger lg-pull-right" type="submit">Delete</button>
|
||||
</form>
|
||||
<div class="panel-body text-center">
|
||||
<a href="/{{.Data}}" class="btn btn-sm btn-primary">Back</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}`
|
||||
pageEdit = `{{define "body"}}
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<h2>{{.BodyTitle}}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<form class="form-horizontal" action="/{{.Data.StoreTitle}}/edit" method="post">
|
||||
<input type="hidden" name="token" value="{{.Token}}">
|
||||
<input type="hidden" name="store_title" value="{{if .Data}}{{.Data.StoreTitle}}{{end}}">
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label class="col-xs-1 control-label">Title</label>
|
||||
<div class="col-xs-11">
|
||||
<input class="form-control input-sm" type="text" value="{{if .Data}}{{.Data.Title}}{{end}}" spellcheck="false" disabled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-xs-1 control-label">Markdown</label>
|
||||
<div class="col-xs-11">
|
||||
<textarea class="form-control input-sm md-text" rows="30" name="markdown" spellcheck="false" autofocus>{{if .Data}}{{.Data.Markdown}}{{end}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-xs-11">
|
||||
<div class="custom-control custom-radio">
|
||||
<input type="radio" id="perm1" name="perm" value="1" class="custom-control-input" checked="">
|
||||
<label class="custom-control-label" for="perm1">Public</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio">
|
||||
<input type="radio" id="perm2" name="perm" value="2" class="custom-control-input">
|
||||
<label class="custom-control-label" for="perm2">Internal</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio">
|
||||
<input type="radio" id="perm3" name="perm" value="3" class="custom-control-input">
|
||||
<label class="custom-control-label" for="perm3">Private</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-xs-11 col-xs-offset-1">
|
||||
<button class="btn btn-sm btn-primary" type="submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{{end}}`
|
||||
page = `{{define "body"}}
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<h2>{{.BodyTitle}}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<strong>Created by:</strong> {{.Data.Created}}<br>
|
||||
{{if .Data.Updated}}
|
||||
<strong>Updated by:</strong> {{.Data.Updated}}{{end}}
|
||||
<br><br>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
{{.Data.PageIndex}}
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
{{if .Login}}
|
||||
<div class="row" style="padding-bottom: 14px">
|
||||
<div class="col-xs-6 {{if ne .Data.StoreTitle "wiki/Index"}}col-xs-offset-4{{else}}col-xs-offset-6{{end}}">
|
||||
<a href="/{{.Data.StoreTitle}}/edit" class="btn btn-sm btn-primary lg-pull-right">Edit</a>
|
||||
</div>
|
||||
{{if ne .Data.StoreTitle "wiki/Index"}}
|
||||
<div class="col-xs-2">
|
||||
<a href="/{{.Data.StoreTitle}}/del" class="btn btn-sm btn-primary lg-pull-right">Delete</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-xs-offset-6">
|
||||
<a href="/{{.Data.StoreTitle}}/md" class="btn btn-sm btn-primary lg-pull-right">Markdown</a>
|
||||
</div>
|
||||
{{if .Login}}
|
||||
<div class="col-xs-6 col-xs-offset-6">
|
||||
<a href="/{{.Data.StoreTitle}}/share" class="btn btn-sm btn-primary lg-pull-right">Share</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{.Data.HTML}}
|
||||
{{end}}`
|
||||
pageMD = `{{define "body"}}
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<h2>{{.BodyTitle}}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<textarea class="form-control input-sm md-text" rows="30" spellcheck="false" autofocus>{{if .Data}}{{.Data.Markdown}}{{end}}</textarea>
|
||||
{{end}}`
|
||||
pageNew = `{{define "body"}}
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<h2>{{.BodyTitle}}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<form class="form-horizontal" action="/new" method="post">
|
||||
<input type="hidden" name="token" value="{{.Token}}">
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label class="col-xs-1 control-label">Section</label>
|
||||
<div class="col-xs-11">
|
||||
<select class="form-control input-sm" name="section" autofocus required>
|
||||
<option>wiki</option>
|
||||
<option>{{.User}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<label class="col-xs-1 control-label">Title</label>
|
||||
<div class="col-xs-11">
|
||||
<input class="form-control input-sm" type="text" placeholder="Title" name="title" value="{{if .Data}}{{.Data.Title}}{{end}}" spellcheck="false" autofocus required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-xs-1 control-label">Markdown</label>
|
||||
<div class="col-xs-11">
|
||||
<textarea class="form-control input-sm md-text" rows="30" name="markdown" spellcheck="false">{{if .Data}}{{.Data.Markdown}}{{end}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-xs-11">
|
||||
<div class="custom-control custom-radio">
|
||||
<input type="radio" id="perm1" name="perm" value="1" class="custom-control-input" checked="">
|
||||
<label class="custom-control-label" for="perm1">Public</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio">
|
||||
<input type="radio" id="perm2" name="perm" value="2" class="custom-control-input">
|
||||
<label class="custom-control-label" for="perm2">Internal</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio">
|
||||
<input type="radio" id="perm3" name="perm" value="3" class="custom-control-input">
|
||||
<label class="custom-control-label" for="perm3">Private</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-xs-11 col-xs-offset-1">
|
||||
<button class="btn btn-sm btn-primary" type="submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{{end}}`
|
||||
pageShare = `{{define "body"}}
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<div class="col-xs-4 col-xs-offset-4">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Share {{.Data}}</h3>
|
||||
</div>
|
||||
<div class="panel-body text-center">
|
||||
<a href="/view/{{.Key}}">Share Link</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}`
|
||||
pageView = `{{define "body"}}
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<h2>{{.BodyTitle}}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<strong>Created by:</strong> {{.Data.Created}}<br>
|
||||
{{if .Data.Updated}}
|
||||
<strong>Updated by:</strong> {{.Data.Updated}}{{end}}
|
||||
<br><br>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
{{.Data.PageIndex}}
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-xs-offset-6">
|
||||
<a href="/view/{{.Key}}/md" class="btn btn-sm btn-primary lg-pull-right">Markdown</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{.Data.HTML}}
|
||||
{{end}}`
|
||||
search = `{{define "body"}}
|
||||
<div class="page-header">
|
||||
|
|
@ -202,8 +411,9 @@ const (
|
|||
</form>
|
||||
</div>
|
||||
<div class="row col-xs-12">
|
||||
{{if .Data}}
|
||||
{{.Data}}
|
||||
{{range $item := .Data}}
|
||||
<a href="/{{$item.StireTitle}}"><b>{{$item.Title}}</b></a>
|
||||
<pre style="white-space:pre-wrap">{{$item.Text}}</pre><br>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}`
|
||||
|
|
@ -294,48 +504,7 @@ const (
|
|||
{{end}}
|
||||
{{end}}
|
||||
{{end}}`
|
||||
userNew = `{{define "body"}}
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<h2>{{.BodyTitle}}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<form class="form-horizontal" action="/user/new" method="post">
|
||||
<input type="hidden" name="token" value="{{.Token}}">
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label class="col-lg-3 control-label">Username</label>
|
||||
<div class="col-lg-6">
|
||||
<input class="form-control input-sm" type="text" name="user" value required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-lg-3 control-label">Password</label>
|
||||
<div class="col-lg-6">
|
||||
<input class="form-control input-sm" type="password" name="password" value required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-lg-3 control-label">Repeat</label>
|
||||
<div class="col-lg-6">
|
||||
<input class="form-control input-sm" type="password" name="repeat" value required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-lg-3 control-label">Admin Privileges</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="checkbox" name="admin" value="0">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-lg-6 col-lg-offset-3">
|
||||
<button class="btn btn-sm btn-primary" type="submit" name="submit" value="Submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{{end}}`
|
||||
users = `{{define "body"}}
|
||||
user = `{{define "body"}}
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<h2>{{.BodyTitle}}</h2>
|
||||
|
|
@ -386,151 +555,45 @@ const (
|
|||
{{end}}
|
||||
</div>
|
||||
{{end}}`
|
||||
wikiEdit = `{{define "body"}}
|
||||
{{if .Editor}}
|
||||
<div class="alert alert-warning" style="margin-top:30px">
|
||||
<h4>Warning!</h4>
|
||||
<p><strong>{{.Editor.Username}}</strong> is editing this page since {{.Editor.Time}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
userNew = `{{define "body"}}
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<h2>{{.BodyTitle}}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<form class="form-horizontal" action="/wiki/{{.Data.LinkTitle}}/edit" method="post">
|
||||
<input type="hidden" name="token" value="{{.Token}}">
|
||||
<input type="hidden" name="title" value="{{if .Data}}{{.Data.Title}}{{end}}">
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label class="col-xs-1 control-label">Title</label>
|
||||
<div class="col-xs-11">
|
||||
<input class="form-control input-sm" type="text" value="{{if .Data}}{{.Data.Title}}{{end}}" spellcheck="false" disabled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-xs-1 control-label">Text</label>
|
||||
<div class="col-xs-11">
|
||||
<textarea class="form-control input-sm md-text" rows="30" name="text" spellcheck="false" autofocus>{{if .Data}}{{.Data.MD}}{{end}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-xs-11">
|
||||
<div class="custom-control custom-radio">
|
||||
<input type="radio" id="perm1" name="perm" value="0" class="custom-control-input" checked="">
|
||||
<label class="custom-control-label" for="perm1">Public</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio">
|
||||
<input type="radio" id="perm2" name="perm" value="1" class="custom-control-input">
|
||||
<label class="custom-control-label" for="perm2">Internal</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio">
|
||||
<input type="radio" id="perm3" name="perm" value="2" class="custom-control-input">
|
||||
<label class="custom-control-label" for="perm3">Private</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-xs-11 col-xs-offset-1">
|
||||
<button class="btn btn-sm btn-primary" type="submit" name="submit" value="Submit">Submit</button>
|
||||
<button class="btn btn-sm btn-default" type="submit" name="submit" value="Back">Back</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{{end}}`
|
||||
wiki = `{{define "body"}}
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<h2>{{.BodyTitle}}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<strong>Created by:</strong> {{.Data.Created}}<br>
|
||||
{{if .Data.Updated}}
|
||||
<strong>Updated by:</strong> {{.Data.Updated}}{{end}}
|
||||
<br><br>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
{{.Data.Index}}
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
{{if .Login}}
|
||||
<div class="row" style="padding-bottom: 14px">
|
||||
<div class="col-xs-6 {{if ne .Data.LinkTitle "Index"}}col-xs-offset-4{{else}}col-xs-offset-6{{end}}">
|
||||
<a href="/wiki/{{.Data.LinkTitle}}/edit" class="btn btn-sm btn-primary lg-pull-right">Edit</a>
|
||||
</div>
|
||||
{{if ne .Data.LinkTitle "Index"}}
|
||||
<div class="col-xs-2">
|
||||
<form class="form-horizontal" action="/wiki/{{.Data.LinkTitle}}/del" method="post">
|
||||
<input type="hidden" name="token" value="{{.Token}}">
|
||||
<button class="btn btn-sm btn-danger lg-pull-right" type="submit" name="submit" value="Delete">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-xs-offset-6">
|
||||
<a href="/wiki/{{.Data.LinkTitle}}/md" class="btn btn-sm btn-primary lg-pull-right">Markdown</a>
|
||||
</div>
|
||||
<form class="form-horizontal" action="/user/new" method="post">
|
||||
<input type="hidden" name="token" value="{{.Token}}">
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label class="col-lg-3 control-label">Username</label>
|
||||
<div class="col-lg-6">
|
||||
<input class="form-control input-sm" type="text" name="user" value required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{.Data.Text}}
|
||||
{{end}}`
|
||||
wikiMD = `{{define "body"}}
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<h2>{{.BodyTitle}}</h2>
|
||||
<div class="form-group">
|
||||
<label class="col-lg-3 control-label">Password</label>
|
||||
<div class="col-lg-6">
|
||||
<input class="form-control input-sm" type="password" name="password" value required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<textarea class="form-control input-sm md-text" rows="30" spellcheck="false" autofocus>{{if .Data}}{{.Data.MD}}{{end}}</textarea>
|
||||
{{end}}`
|
||||
wikiNew = `{{define "body"}}
|
||||
<div class="page-header">
|
||||
<div class="row">
|
||||
<h2>{{.BodyTitle}}</h2>
|
||||
<div class="form-group">
|
||||
<label class="col-lg-3 control-label">Repeat</label>
|
||||
<div class="col-lg-6">
|
||||
<input class="form-control input-sm" type="password" name="repeat" value required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form class="form-horizontal" action="/new" method="post">
|
||||
<input type="hidden" name="token" value="{{.Token}}">
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label class="col-xs-1 control-label">Title</label>
|
||||
<div class="col-xs-11">
|
||||
<input class="form-control input-sm" type="text" placeholder="Title" name="title" value="{{if .Data}}{{.Data.Title}}{{end}}" spellcheck="false" autofocus required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-lg-3 control-label">Admin Privileges</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="checkbox" name="admin" value="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-xs-1 control-label">Text</label>
|
||||
<div class="col-xs-11">
|
||||
<textarea class="form-control input-sm md-text" rows="30" name="text" spellcheck="false">{{if .Data}}{{.Data.MD}}{{end}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-lg-6 col-lg-offset-3">
|
||||
<button class="btn btn-sm btn-primary" type="submit" name="submit" value="Submit">Submit</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-xs-11">
|
||||
<div class="custom-control custom-radio">
|
||||
<input type="radio" id="perm1" name="perm" value="0" class="custom-control-input" checked="">
|
||||
<label class="custom-control-label" for="perm1">Public</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio">
|
||||
<input type="radio" id="perm2" name="perm" value="1" class="custom-control-input">
|
||||
<label class="custom-control-label" for="perm2">Internal</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio">
|
||||
<input type="radio" id="perm3" name="perm" value="2" class="custom-control-input">
|
||||
<label class="custom-control-label" for="perm3">Private</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-xs-11 col-xs-offset-1">
|
||||
<button class="btn btn-sm btn-primary" type="submit" name="submit" value="Submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{{end}}`
|
||||
bootstrapCSS = `@import url("https://fonts.googleapis.com/css?family=Lato:400,700,400italic");/*!
|
||||
|
|
@ -693,12 +756,15 @@ func (s *HTTPServer) loadTemplates() {
|
|||
s.templ["loginHandler"] = parse(index, menu, login)
|
||||
s.templ["searchHandler"] = parse(index, menu, search)
|
||||
s.templ["allHandler"] = parse(index, menu, all)
|
||||
s.templ["wikiNewHandler"] = parse(index, menu, wikiNew)
|
||||
s.templ["wikiEditHandler"] = parse(index, menu, wikiEdit)
|
||||
s.templ["wikiHandler"] = parse(index, menu, wiki)
|
||||
s.templ["wikiMDHandler"] = parse(index, menu, wikiMD)
|
||||
s.templ["pageHandler"] = parse(index, menu, page)
|
||||
s.templ["pageNewHandler"] = parse(index, menu, pageNew)
|
||||
s.templ["pageEditHandler"] = parse(index, menu, pageEdit)
|
||||
s.templ["pageViewHandler"] = parse(index, menu, pageView)
|
||||
s.templ["pageMDHandler"] = parse(index, menu, pageMD)
|
||||
s.templ["pageShareHandler"] = parse(index, menu, pageShare)
|
||||
s.templ["pageDelHandler"] = parse(index, menu, pageDel)
|
||||
// users.go
|
||||
s.templ["usersHandler"] = parse(index, menu, users)
|
||||
s.templ["userHandler"] = parse(index, menu, user)
|
||||
s.templ["userNewHandler"] = parse(index, menu, userNew)
|
||||
s.templ["userEditHandler"] = parse(index, menu, userEdit)
|
||||
s.templ["userDelHandler"] = parse(index, menu, userDel)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const keySize = jwt.KeySize / 2
|
|||
|
||||
func genKey(length int) (bytes []byte) {
|
||||
bytes = make([]byte, length)
|
||||
if _, err := io.ReadFull(rand.Reader, bytes); err != nil {
|
||||
if _, err := rand.Reader.Read(bytes); err != nil {
|
||||
log.Println("genKey:", err)
|
||||
}
|
||||
return
|
||||
|
|
@ -31,13 +31,14 @@ func checkXsrf(xsrf string, secret []byte) bool {
|
|||
return subtle.ConstantTimeCompare(secret, xor(t[keySize:], t[:keySize])) == 1
|
||||
}
|
||||
|
||||
func xor(a, b []byte) []byte {
|
||||
func xor(a, b []byte) (c []byte) {
|
||||
n := len(a)
|
||||
if len(b) != n {
|
||||
if len(b) < n {
|
||||
return nil
|
||||
}
|
||||
c = make([]byte, n)
|
||||
for i := 0; i < n; i++ {
|
||||
a[i] = a[i] ^ b[i]
|
||||
c[i] = a[i] ^ b[i]
|
||||
}
|
||||
return a
|
||||
return
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue