minor bugfixes and cleanup
This commit is contained in:
parent
2a203f6f29
commit
97d914bce4
26 changed files with 198 additions and 221 deletions
2
pkg/cache/cache.go
vendored
2
pkg/cache/cache.go
vendored
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (C) 2018 Marius Schellenberger
|
||||||
|
|
||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"git.giftfish.de/ston1th/godrop"
|
"git.giftfish.de/ston1th/godrop"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
stdlog "log"
|
stdlog "log"
|
||||||
|
|
@ -13,13 +12,12 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||||
"git.giftfish.de/ston1th/gowiki/pkg/log"
|
"git.giftfish.de/ston1th/gowiki/pkg/log"
|
||||||
"git.giftfish.de/ston1th/gowiki/pkg/server"
|
"git.giftfish.de/ston1th/gowiki/pkg/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
blevePath = "bleve"
|
|
||||||
|
|
||||||
defDataDir = "/var/lib/gowiki"
|
defDataDir = "/var/lib/gowiki"
|
||||||
defListen = "127.0.0.1:8090"
|
defListen = "127.0.0.1:8090"
|
||||||
defLogFile = "gowiki.log"
|
defLogFile = "gowiki.log"
|
||||||
|
|
@ -28,7 +26,7 @@ const (
|
||||||
defRunGroup = "wiki"
|
defRunGroup = "wiki"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initServer(conf serverConfig) (err error) {
|
func initServer(conf core.Config) (err error) {
|
||||||
dropCfg := godrop.Config{
|
dropCfg := godrop.Config{
|
||||||
User: conf.User,
|
User: conf.User,
|
||||||
Group: conf.Group,
|
Group: conf.Group,
|
||||||
|
|
@ -51,7 +49,7 @@ func initServer(conf serverConfig) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := server.NewHTTPServer(l, conf.Version, conf.DataDir+"/"+blevePath, conf.SecureCookie)
|
srv := server.NewHTTPServer(l, conf)
|
||||||
err = srv.Start()
|
err = srv.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|
@ -63,8 +61,7 @@ func initServer(conf serverConfig) (err error) {
|
||||||
log.Println("caught " + sig.String())
|
log.Println("caught " + sig.String())
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(time.Second * 20)
|
time.Sleep(time.Second * 20)
|
||||||
//TODO error needed?
|
log.Fatal("stop timed out: killing")
|
||||||
log.Fatal(errors.New("stop timed out: killing"))
|
|
||||||
}()
|
}()
|
||||||
err = srv.Stop()
|
err = srv.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -76,7 +73,7 @@ func initServer(conf serverConfig) (err error) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
verbose bool
|
verbose bool
|
||||||
conf serverConfig
|
conf core.Config
|
||||||
)
|
)
|
||||||
|
|
||||||
func Run(version string) {
|
func Run(version string) {
|
||||||
|
|
@ -131,17 +128,6 @@ func Run(version string) {
|
||||||
app.Run(os.Args)
|
app.Run(os.Args)
|
||||||
}
|
}
|
||||||
|
|
||||||
type serverConfig struct {
|
|
||||||
DataDir string
|
|
||||||
User string
|
|
||||||
Group string
|
|
||||||
ListenAddr string
|
|
||||||
LogFile string
|
|
||||||
Version string
|
|
||||||
Foreground bool
|
|
||||||
SecureCookie bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func serverFlags() []cli.Flag {
|
func serverFlags() []cli.Flag {
|
||||||
return []cli.Flag{
|
return []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
|
|
@ -168,6 +154,11 @@ func serverFlags() []cli.Flag {
|
||||||
Usage: "log file (leave empty for stdout, this only works in combination with -f)",
|
Usage: "log file (leave empty for stdout, this only works in combination with -f)",
|
||||||
Destination: &conf.LogFile,
|
Destination: &conf.LogFile,
|
||||||
},
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "secret",
|
||||||
|
Usage: "static hmac secret (at least 64 chars, used for JWT signing)",
|
||||||
|
Destination: &conf.Secret,
|
||||||
|
},
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "foreground, f",
|
Name: "foreground, f",
|
||||||
Usage: "do not fork into the background",
|
Usage: "do not fork into the background",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (C) 2018 Marius Schellenberger
|
||||||
|
|
||||||
package core
|
package core
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (C) 2018 Marius Schellenberger
|
||||||
|
|
||||||
package core
|
package core
|
||||||
|
|
||||||
type Permission int
|
type Permission int
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (C) 2018 Marius Schellenberger
|
||||||
|
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import "html/template"
|
import "html/template"
|
||||||
|
|
@ -36,5 +38,17 @@ func (u Users) Less(i, j int) bool { return u[i].Username < u[j].Username }
|
||||||
type Result struct {
|
type Result struct {
|
||||||
Title string
|
Title string
|
||||||
StoreTitle string
|
StoreTitle string
|
||||||
Text string
|
HTML template.HTML
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
DataDir string
|
||||||
|
User string
|
||||||
|
Group string
|
||||||
|
ListenAddr string
|
||||||
|
LogFile string
|
||||||
|
Version string
|
||||||
|
Secret string
|
||||||
|
Foreground bool
|
||||||
|
SecureCookie bool
|
||||||
}
|
}
|
||||||
|
|
|
||||||
20
pkg/db/db.go
20
pkg/db/db.go
|
|
@ -21,10 +21,7 @@ This is the [Index](/wiki/Index) page.
|
||||||
You can customize it how you like.`
|
You can customize it how you like.`
|
||||||
)
|
)
|
||||||
|
|
||||||
/*const (
|
const blevePath = "bleve"
|
||||||
maxResult = 1e6
|
|
||||||
maxSearchResult = 101
|
|
||||||
)*/
|
|
||||||
|
|
||||||
func dbErr(i interface{}) error {
|
func dbErr(i interface{}) error {
|
||||||
return fmt.Errorf("db: %s", i)
|
return fmt.Errorf("db: %s", i)
|
||||||
|
|
@ -37,13 +34,12 @@ type DB struct {
|
||||||
Index *index.Index
|
Index *index.Index
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(indexPath string) (db *DB, err error) {
|
func New(datadir string) (db *DB, err error) {
|
||||||
db = &DB{cache: cache.NewCache()}
|
db = &DB{cache: cache.NewCache()}
|
||||||
//TODO
|
db.Index, err = index.NewIndex(datadir + "/" + blevePath)
|
||||||
//db.Index, err = index.NewIndex(indexPath)
|
if err != nil {
|
||||||
//if err != nil {
|
return
|
||||||
// return
|
}
|
||||||
//}
|
|
||||||
|
|
||||||
db.users, err = store.NewBoltStore(usersFile, nil)
|
db.users, err = store.NewBoltStore(usersFile, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -80,7 +76,5 @@ func (db *DB) Close() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return db.Index.Close()
|
||||||
//TODO
|
|
||||||
//return db.Index.Close()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
// Copyright (C) 2018 Marius Schellenberger
|
||||||
|
|
||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/crypto/sha3"
|
"crypto/sha256"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -31,7 +33,7 @@ func validatePassword(pw string) (b []byte) {
|
||||||
if len(pw) <= 56 {
|
if len(pw) <= 56 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hash := sha3.New384()
|
hash := sha256.New()
|
||||||
hash.Write(b)
|
hash.Write(b)
|
||||||
b = hash.Sum(nil)
|
b = hash.Sum(nil)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/gob"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Marshaler interface {
|
|
||||||
Marshal(v interface{}) ([]byte, error)
|
|
||||||
Unmarshal(data []byte, v interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type gobMarshaler struct{}
|
|
||||||
|
|
||||||
func NewGOB() Marshaler {
|
|
||||||
return gobMarshaler{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gobMarshaler) Marshal(v interface{}) (b []byte, err error) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
err = gob.NewEncoder(buf).Encode(v)
|
|
||||||
b = buf.Bytes()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gobMarshaler) Unmarshal(data []byte, v interface{}) error {
|
|
||||||
return gob.NewDecoder(bytes.NewBuffer(data)).Decode(v)
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (C) 2018 Marius Schellenberger
|
||||||
|
|
||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -81,19 +83,17 @@ func (db *DB) CreatePage(title, section, markdown, username string, p core.Permi
|
||||||
Owner: owner,
|
Owner: owner,
|
||||||
Perm: p,
|
Perm: p,
|
||||||
}
|
}
|
||||||
//search := render.Render(page)
|
search := render.Render(page)
|
||||||
render.Render(page)
|
|
||||||
|
|
||||||
err = db.pages.Set(st, page)
|
err = db.pages.Set(st, page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
err = db.Index.Add(title, st, search)
|
||||||
//err = db.Index.Add(title, st, search)
|
if err != nil {
|
||||||
//if err != nil {
|
return
|
||||||
// return
|
}
|
||||||
//}
|
|
||||||
db.cache.Add(st, page)
|
db.cache.Add(st, page)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -119,20 +119,17 @@ func (db *DB) UpdatePage(title, section, markdown, username string, p core.Permi
|
||||||
page.Markdown = markdown
|
page.Markdown = markdown
|
||||||
page.Updated = created(username)
|
page.Updated = created(username)
|
||||||
page.Perm = p
|
page.Perm = p
|
||||||
//TODO
|
search := render.Render(page)
|
||||||
//search := render.Render(page)
|
|
||||||
render.Render(page)
|
|
||||||
|
|
||||||
err = db.pages.Set(st, page)
|
err = db.pages.Set(st, page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
err = db.Index.Add(title, st, search)
|
||||||
//err = db.Index.Add(title, st, search)
|
if err != nil {
|
||||||
//if err != nil {
|
return
|
||||||
// return
|
}
|
||||||
//}
|
|
||||||
db.cache.Add(st, page)
|
db.cache.Add(st, page)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -155,7 +152,5 @@ func (db *DB) DeletePage(title, section, username string) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
db.cache.Delete(st)
|
db.cache.Delete(st)
|
||||||
return nil
|
return db.Index.Delete(st)
|
||||||
//TODO
|
|
||||||
//return db.Index.Delete(st)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (C) 2018 Marius Schellenberger
|
||||||
|
|
||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -166,6 +168,7 @@ func (db *DB) AdminUpdateUser(username, password string, admin bool) error {
|
||||||
return db.users.Set(username, u)
|
return db.users.Set(username, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO delete all user owned pages
|
||||||
func (db *DB) DeleteUser(username string) error {
|
func (db *DB) DeleteUser(username string) error {
|
||||||
if username == defUser {
|
if username == defUser {
|
||||||
return errors.New("user '" + defUser + "' cannot be deleted")
|
return errors.New("user '" + defUser + "' cannot be deleted")
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,14 @@ package index
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||||
"github.com/blevesearch/bleve"
|
"github.com/blevesearch/bleve"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const maxSearchResult = 101
|
||||||
maxSearchResult = 100
|
|
||||||
)
|
|
||||||
|
|
||||||
type indexPage struct {
|
type indexPage struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
|
@ -89,7 +88,7 @@ func (i *Index) Search(search string) (results []core.Result, err error) {
|
||||||
}
|
}
|
||||||
if fragField == "title" || fragField == "search" {
|
if fragField == "title" || fragField == "search" {
|
||||||
for _, f := range frags {
|
for _, f := range frags {
|
||||||
r.Text += f
|
r.HTML += template.HTML(f)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ func makeFilter() analysis.CharFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
//reHeader = regexp.MustCompile("(<h[1-6])>(.+)(</h[1-6]>)")
|
|
||||||
reHeader = regexp.MustCompile("(<h[1-6]>)(.+)(</h[1-6]>)")
|
reHeader = regexp.MustCompile("(<h[1-6]>)(.+)(</h[1-6]>)")
|
||||||
reLink = regexp.MustCompile(`id="(.+?)"`)
|
reLink = regexp.MustCompile(`id="(.+?)"`)
|
||||||
whiteSpace = regexp.MustCompile(`\s+`)
|
whiteSpace = regexp.MustCompile(`\s+`)
|
||||||
|
|
@ -64,7 +63,6 @@ func buildIndex(html string) (string, string) {
|
||||||
var ret string
|
var ret string
|
||||||
m := make(map[string]int)
|
m := make(map[string]int)
|
||||||
hn := reIndex.FindAllStringSubmatch(html, -1)
|
hn := reIndex.FindAllStringSubmatch(html, -1)
|
||||||
//html = reHeader.ReplaceAllString(html, `${1} id="${2}">${2}${3}`)
|
|
||||||
html = reHeader.ReplaceAllString(html, `<a id="${2}" class="anchor"></a>${1}${2}${3}`)
|
html = reHeader.ReplaceAllString(html, `<a id="${2}" class="anchor"></a>${1}${2}${3}`)
|
||||||
html = reLink.ReplaceAllStringFunc(html, spaceReplace)
|
html = reLink.ReplaceAllStringFunc(html, spaceReplace)
|
||||||
for k, v := range hn {
|
for k, v := range hn {
|
||||||
|
|
@ -126,11 +124,3 @@ func closeTag(j int) (ret string) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderResult(res []core.Result) (html string) {
|
|
||||||
for _, r := range res {
|
|
||||||
html += `<a href="/` + r.StoreTitle + `"><b>` + r.Title + "</b></a>" +
|
|
||||||
`<pre style="white-space: pre-wrap">` + r.Text + "</pre><br>"
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ func newContext(w http.ResponseWriter, r *http.Request, s *HTTPServer) (ctx *Con
|
||||||
h.Set("X-Frame-Options", "DENY")
|
h.Set("X-Frame-Options", "DENY")
|
||||||
h.Set("X-Content-Type-Options", "nosniff")
|
h.Set("X-Content-Type-Options", "nosniff")
|
||||||
h.Set("X-XSS-Protection", "1; mode=block")
|
h.Set("X-XSS-Protection", "1; mode=block")
|
||||||
h.Set("Content-Security-Policy", "default-src 'none';style-src 'self' 'unsafe-inline';frame-ancestors 'none'")
|
h.Set("Content-Security-Policy", "default-src 'none';style-src 'self';frame-ancestors 'none'")
|
||||||
ctx = &Context{
|
ctx = &Context{
|
||||||
Request: r,
|
Request: r,
|
||||||
Response: w,
|
Response: w,
|
||||||
|
|
@ -101,6 +101,10 @@ func (c *Context) Exec() {
|
||||||
c.Data.Login = c.LoggedOn()
|
c.Data.Login = c.LoggedOn()
|
||||||
c.Data.Time = time.Since(c.Time).Nanoseconds() / 1e5
|
c.Data.Time = time.Since(c.Time).Nanoseconds() / 1e5
|
||||||
|
|
||||||
|
if c.Data.Msg != "" {
|
||||||
|
c.Status = http.StatusBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
c.Err = c.T.Execute(c.Response, c.Data)
|
c.Err = c.T.Execute(c.Response, c.Data)
|
||||||
if c.Err != nil {
|
if c.Err != nil {
|
||||||
c.Status = http.StatusInternalServerError
|
c.Status = http.StatusInternalServerError
|
||||||
|
|
@ -118,6 +122,16 @@ func (c *Context) log() {
|
||||||
log.Printf("%s %s %s %d\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, c.Status)
|
log.Printf("%s %s %s %d\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, c.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Context) Error(i interface{}) {
|
||||||
|
switch v := i.(type) {
|
||||||
|
case string:
|
||||||
|
c.Data.Msg = v
|
||||||
|
case error:
|
||||||
|
c.Data.Msg = v.Error()
|
||||||
|
}
|
||||||
|
c.Exec()
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Context) NotFound() {
|
func (c *Context) NotFound() {
|
||||||
c.Template("notFoundHandler")
|
c.Template("notFoundHandler")
|
||||||
c.Data = webData{Title: "404"}
|
c.Data = webData{Title: "404"}
|
||||||
|
|
@ -170,17 +184,11 @@ func (c *Context) Var(name string) (ret string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove f
|
// CheckXsrf validates the xsrf token
|
||||||
// CheckToken validates the xsrf token
|
func (c *Context) CheckXsrf() (ok bool) {
|
||||||
func (c *Context) CheckXsrf(f func()) (ok bool) {
|
|
||||||
ok = checkXsrf(c.Form("token"), c.Token.RawSig()[:keySize])
|
ok = checkXsrf(c.Form("token"), c.Token.RawSig()[:keySize])
|
||||||
if !ok {
|
if !ok {
|
||||||
if f != nil {
|
c.Error("wrong csrf token")
|
||||||
f()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Data.Msg = "wrong csrf token"
|
|
||||||
c.Exec()
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -190,26 +198,6 @@ func (c *Context) LoggedOn() (ok bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCookie(value string, maxAge int) (cookie *http.Cookie) {
|
|
||||||
cookie = &http.Cookie{
|
|
||||||
Name: cookieName,
|
|
||||||
Value: value,
|
|
||||||
Path: "/",
|
|
||||||
MaxAge: maxAge,
|
|
||||||
//TODO make secure configurable
|
|
||||||
Secure: false,
|
|
||||||
HttpOnly: true,
|
|
||||||
SameSite: http.SameSiteStrictMode,
|
|
||||||
}
|
|
||||||
if maxAge > 0 {
|
|
||||||
d := time.Duration(maxAge) * time.Second
|
|
||||||
cookie.Expires = time.Now().Add(d)
|
|
||||||
} else if maxAge < 0 {
|
|
||||||
cookie.Expires = time.Unix(1, 0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) SetCookie(claims map[string]interface{}) {
|
func (c *Context) SetCookie(claims map[string]interface{}) {
|
||||||
t := jwt.NewToken(claims, nil)
|
t := jwt.NewToken(claims, nil)
|
||||||
if err := c.Srv.JWT.Sign(t); err != nil {
|
if err := c.Srv.JWT.Sign(t); err != nil {
|
||||||
|
|
@ -217,7 +205,22 @@ func (c *Context) SetCookie(claims map[string]interface{}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Token = *t
|
c.Token = *t
|
||||||
http.SetCookie(c.Response, newCookie(c.Token.String(), int(jwt.DefaultExpiry.Seconds())))
|
cookie := &http.Cookie{
|
||||||
|
Name: cookieName,
|
||||||
|
Value: c.Token.String(),
|
||||||
|
Path: "/",
|
||||||
|
MaxAge: int(jwt.DefaultExpiry.Seconds()),
|
||||||
|
Secure: c.Srv.Secure,
|
||||||
|
HttpOnly: true,
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
}
|
||||||
|
if cookie.MaxAge > 0 {
|
||||||
|
d := time.Duration(cookie.MaxAge) * time.Second
|
||||||
|
cookie.Expires = time.Now().Add(d)
|
||||||
|
} else if cookie.MaxAge < 0 {
|
||||||
|
cookie.Expires = time.Unix(1, 0)
|
||||||
|
}
|
||||||
|
http.SetCookie(c.Response, cookie)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) User() string {
|
func (c *Context) User() string {
|
||||||
|
|
|
||||||
|
|
@ -88,21 +88,19 @@ func loginHandler(ctx *Context) {
|
||||||
case "POST":
|
case "POST":
|
||||||
user := ctx.Form("user")
|
user := ctx.Form("user")
|
||||||
password := ctx.Form("password")
|
password := ctx.Form("password")
|
||||||
if !ctx.CheckXsrf(nil) {
|
if !ctx.CheckXsrf() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if user == "" || password == "" {
|
if user == "" || password == "" {
|
||||||
ctx.Data.Msg = "wrong inputs"
|
ctx.Error("wrong inputs")
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
u, err := ctx.Srv.DB.Login(user, password)
|
u, err := ctx.Srv.DB.Login(user, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Data.Msg = err.Error()
|
ctx.Error(err)
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.SetCookie(map[string]interface{}{userClaim: u.Username, adminClaim: u.Admin})
|
ctx.SetCookie(jwt.Claims{userClaim: u.Username, adminClaim: u.Admin})
|
||||||
ctx.Redirect(core.IndexURI, 302)
|
ctx.Redirect(core.IndexURI, 302)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -118,18 +116,16 @@ func searchHandler(ctx *Context) {
|
||||||
ctx.Exec()
|
ctx.Exec()
|
||||||
case "POST":
|
case "POST":
|
||||||
search := ctx.Form("search")
|
search := ctx.Form("search")
|
||||||
if !ctx.CheckXsrf(nil) {
|
if !ctx.CheckXsrf() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
res, err := ctx.Srv.DB.Index.Search(search)
|
||||||
//res, err := ctx.Srv.DB.Index.Search(search)
|
if err != nil {
|
||||||
//if err != nil {
|
ctx.Error(err)
|
||||||
// ctx.Data.Msg = err.Error()
|
return
|
||||||
// ctx.Exec()
|
}
|
||||||
// return
|
ctx.Data.Data = res
|
||||||
//}
|
|
||||||
//ctx.Data.Data = res
|
|
||||||
ctx.Data.Search = search
|
ctx.Data.Search = search
|
||||||
ctx.Exec()
|
ctx.Exec()
|
||||||
}
|
}
|
||||||
|
|
@ -164,7 +160,7 @@ func pageNewHandler(ctx *Context) {
|
||||||
Perm: perm,
|
Perm: perm,
|
||||||
}
|
}
|
||||||
ctx.Data.Data = p
|
ctx.Data.Data = p
|
||||||
if !ctx.CheckXsrf(nil) {
|
if !ctx.CheckXsrf() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if section != core.WikiSection {
|
if section != core.WikiSection {
|
||||||
|
|
@ -172,8 +168,7 @@ func pageNewHandler(ctx *Context) {
|
||||||
}
|
}
|
||||||
page, err := ctx.Srv.DB.CreatePage(title, section, markdown, ctx.User(), perm)
|
page, err := ctx.Srv.DB.CreatePage(title, section, markdown, ctx.User(), perm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Data.Msg = err.Error()
|
ctx.Error(err)
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Redirect(page.StoreTitle, 302)
|
ctx.Redirect(page.StoreTitle, 302)
|
||||||
|
|
@ -242,6 +237,10 @@ func pageShareHandler(ctx *Context) {
|
||||||
ctx.NotFound()
|
ctx.NotFound()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if page.Perm != core.Private {
|
||||||
|
ctx.Error("only private pages can be shared")
|
||||||
|
return
|
||||||
|
}
|
||||||
ctx.Data.Data = st
|
ctx.Data.Data = st
|
||||||
ctx.Data.Title = page.Title
|
ctx.Data.Title = page.Title
|
||||||
ctx.Data.BodyTitle = page.Title
|
ctx.Data.BodyTitle = page.Title
|
||||||
|
|
@ -250,13 +249,12 @@ func pageShareHandler(ctx *Context) {
|
||||||
ctx.Exec()
|
ctx.Exec()
|
||||||
case "POST":
|
case "POST":
|
||||||
duration := ctx.Form("duration")
|
duration := ctx.Form("duration")
|
||||||
if !ctx.CheckXsrf(nil) {
|
if !ctx.CheckXsrf() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
d, err := time.ParseDuration(duration)
|
d, err := time.ParseDuration(duration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Data.Msg = err.Error()
|
ctx.Error(err)
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t := jwt.NewToken(map[string]interface{}{
|
t := jwt.NewToken(map[string]interface{}{
|
||||||
|
|
@ -286,36 +284,31 @@ func pageBlacklistHandler(ctx *Context) {
|
||||||
case "GET":
|
case "GET":
|
||||||
ctx.Exec()
|
ctx.Exec()
|
||||||
case "POST":
|
case "POST":
|
||||||
if !ctx.CheckXsrf(nil) {
|
if !ctx.CheckXsrf() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t, err := jwt.DecodeToken(ctx.Form("share"))
|
t, err := jwt.DecodeToken(ctx.Form("share"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Data.Msg = err.Error()
|
ctx.Error(err)
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = ctx.Srv.JWT.Verify(t)
|
err = ctx.Srv.JWT.Verify(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Data.Msg = err.Error()
|
ctx.Error(err)
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := t.Claims.GetString(sharedClaim)
|
user := t.Claims.GetString(sharedClaim)
|
||||||
if user == "" {
|
if user == "" {
|
||||||
ctx.Data.Msg = "blacklisting denied: no share token"
|
ctx.Error("blacklisting denied: no share token")
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ctx.User() != user {
|
if ctx.User() != user {
|
||||||
ctx.Data.Msg = "blacklisting denied: token was issued by " + user
|
ctx.Error("blacklisting denied: token was issued by " + user)
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = ctx.Srv.JWT.Invalidate(t)
|
err = ctx.Srv.JWT.Invalidate(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Data.Msg = err.Error()
|
ctx.Error(err)
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Exec()
|
ctx.Exec()
|
||||||
|
|
@ -342,13 +335,12 @@ func pageEditHandler(ctx *Context) {
|
||||||
page.Markdown = ctx.Form("markdown")
|
page.Markdown = ctx.Form("markdown")
|
||||||
page.Perm = core.ParsePermString(ctx.Form("perm"))
|
page.Perm = core.ParsePermString(ctx.Form("perm"))
|
||||||
ctx.Data.Data = page
|
ctx.Data.Data = page
|
||||||
if !ctx.CheckXsrf(nil) {
|
if !ctx.CheckXsrf() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = ctx.Srv.DB.UpdatePage(title, section, page.Markdown, ctx.User(), page.Perm)
|
err = ctx.Srv.DB.UpdatePage(title, section, page.Markdown, ctx.User(), page.Perm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Data.Msg = err.Error()
|
ctx.Error(err)
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Redirect(page.StoreTitle, 302)
|
ctx.Redirect(page.StoreTitle, 302)
|
||||||
|
|
@ -367,12 +359,11 @@ func pageDelHandler(ctx *Context) {
|
||||||
case "GET":
|
case "GET":
|
||||||
ctx.Exec()
|
ctx.Exec()
|
||||||
case "POST":
|
case "POST":
|
||||||
if !ctx.CheckXsrf(nil) {
|
if !ctx.CheckXsrf() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := ctx.Srv.DB.DeletePage(title, section, ctx.User()); err != nil {
|
if err := ctx.Srv.DB.DeletePage(title, section, ctx.User()); err != nil {
|
||||||
ctx.Data.Msg = err.Error()
|
ctx.Error(err)
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Redirect(core.IndexURI, 302)
|
ctx.Redirect(core.IndexURI, 302)
|
||||||
|
|
@ -416,17 +407,15 @@ func userNewHandler(ctx *Context) {
|
||||||
if admin == "0" {
|
if admin == "0" {
|
||||||
adm = true
|
adm = true
|
||||||
}
|
}
|
||||||
if !ctx.CheckXsrf(nil) {
|
if !ctx.CheckXsrf() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if user == "" || password == "" || password != repeat {
|
if user == "" || password == "" || password != repeat {
|
||||||
ctx.Data.Msg = "wrong inputs"
|
ctx.Error("wrong inputs")
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := ctx.Srv.DB.CreateUser(user, password, adm); err != nil {
|
if err := ctx.Srv.DB.CreateUser(user, password, adm); err != nil {
|
||||||
ctx.Data.Msg = err.Error()
|
ctx.Error(err)
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Redirect("/user", 302)
|
ctx.Redirect("/user", 302)
|
||||||
|
|
@ -443,8 +432,7 @@ func userEditHandler(ctx *Context) {
|
||||||
case "GET":
|
case "GET":
|
||||||
u, err := ctx.Srv.DB.GetUserWithoutPassword(ctx.Var("user"))
|
u, err := ctx.Srv.DB.GetUserWithoutPassword(ctx.Var("user"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Data.Msg = err.Error()
|
ctx.Error(err)
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Data.Data = u
|
ctx.Data.Data = u
|
||||||
|
|
@ -458,25 +446,22 @@ func userEditHandler(ctx *Context) {
|
||||||
if admin == "0" {
|
if admin == "0" {
|
||||||
adm = true
|
adm = true
|
||||||
}
|
}
|
||||||
if !ctx.CheckXsrf(nil) {
|
if !ctx.CheckXsrf() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if user == "" || password != repeat {
|
if user == "" || password != repeat {
|
||||||
ctx.Data.Msg = "wrong inputs"
|
ctx.Error("wrong inputs")
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ctx.Admin() {
|
if ctx.Admin() {
|
||||||
if err := ctx.Srv.DB.AdminUpdateUser(user, password, adm); err != nil {
|
if err := ctx.Srv.DB.AdminUpdateUser(user, password, adm); err != nil {
|
||||||
ctx.Data.Msg = err.Error()
|
ctx.Error(err)
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if user == ctx.User() {
|
if user == ctx.User() {
|
||||||
if err := ctx.Srv.DB.UpdateUserPassword(user, password); err != nil {
|
if err := ctx.Srv.DB.UpdateUserPassword(user, password); err != nil {
|
||||||
ctx.Data.Msg = err.Error()
|
ctx.Error(err)
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Redirect(core.IndexURI, 302)
|
ctx.Redirect(core.IndexURI, 302)
|
||||||
|
|
@ -505,8 +490,7 @@ func userDelHandler(ctx *Context) {
|
||||||
case "GET":
|
case "GET":
|
||||||
user, err := ctx.Srv.DB.GetUserWithoutPassword(ctx.Var("user"))
|
user, err := ctx.Srv.DB.GetUserWithoutPassword(ctx.Var("user"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Data.Msg = err.Error()
|
ctx.Error(err)
|
||||||
ctx.Exec()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Data.Data = user
|
ctx.Data.Data = user
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (C) 2018 Marius Schellenberger
|
||||||
|
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -9,6 +11,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||||
"git.giftfish.de/ston1th/gowiki/pkg/db"
|
"git.giftfish.de/ston1th/gowiki/pkg/db"
|
||||||
"git.giftfish.de/ston1th/gowiki/pkg/log"
|
"git.giftfish.de/ston1th/gowiki/pkg/log"
|
||||||
"git.giftfish.de/ston1th/jwt"
|
"git.giftfish.de/ston1th/jwt"
|
||||||
|
|
@ -19,10 +22,13 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTPServer struct {
|
type HTTPServer struct {
|
||||||
Version string
|
Version string
|
||||||
indexPath string
|
Secure bool
|
||||||
listener net.Listener
|
Secret string
|
||||||
srv *http.Server
|
DataDir string
|
||||||
|
|
||||||
|
listener net.Listener
|
||||||
|
srv *http.Server
|
||||||
|
|
||||||
DB *db.DB
|
DB *db.DB
|
||||||
JWT *jwt.JWT
|
JWT *jwt.JWT
|
||||||
|
|
@ -31,9 +37,12 @@ type HTTPServer struct {
|
||||||
res map[string][]byte
|
res map[string][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPServer(l net.Listener, version, indexPath string, secCookie bool) (srv *HTTPServer) {
|
func NewHTTPServer(l net.Listener, conf core.Config) (srv *HTTPServer) {
|
||||||
srv = &HTTPServer{
|
srv = &HTTPServer{
|
||||||
Version: version,
|
Version: conf.Version,
|
||||||
|
Secure: conf.SecureCookie,
|
||||||
|
Secret: conf.Secret,
|
||||||
|
DataDir: conf.DataDir,
|
||||||
listener: l,
|
listener: l,
|
||||||
}
|
}
|
||||||
srv.loadTemplates()
|
srv.loadTemplates()
|
||||||
|
|
@ -44,13 +53,15 @@ func NewHTTPServer(l net.Listener, version, indexPath string, secCookie bool) (s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPServer) Start() (err error) {
|
func (s *HTTPServer) Start() (err error) {
|
||||||
//TODO make secret configurable
|
var buf *bytes.Buffer
|
||||||
buf := bytes.NewBufferString("be688838ca8686e5c90689bf2ab585cef1137c999b48c70b92f67a5c34dc15697b5d11c982ed6d71be1e1e7f7b4e0733884aa97c3f7a339a8ed03577cf74be09")
|
if s.Secret != "" {
|
||||||
|
buf = bytes.NewBufferString(s.Secret)
|
||||||
|
}
|
||||||
s.JWT, err = jwt.New(jwt.DefaultExpiry, jwt.NewMemBlacklist(), buf)
|
s.JWT, err = jwt.New(jwt.DefaultExpiry, jwt.NewMemBlacklist(), buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.DB, err = db.New(s.indexPath)
|
s.DB, err = db.New(s.DataDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ const (
|
||||||
<ul type="circle">
|
<ul type="circle">
|
||||||
{{range $item := .Data}}
|
{{range $item := .Data}}
|
||||||
{{if ne $owner $item.Owner}}
|
{{if ne $owner $item.Owner}}
|
||||||
{{$owner := $item.Owner}}
|
{{$owner = $item.Owner}}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
@ -48,9 +48,9 @@ const (
|
||||||
<head>
|
<head>
|
||||||
<title>GoWiki | {{.Title}}</title>
|
<title>GoWiki | {{.Title}}</title>
|
||||||
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link>
|
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link>
|
||||||
<link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-4M0Ran7skk1gZoHDDh/mtuzwXN51uagML7tkWnhE0JI="></link>
|
<link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-1LNAeuiM/rtsY1jIgLovUZ7HbYnOCkj3ZyailNA3rFQ="></link>
|
||||||
</head>
|
</head>
|
||||||
<body style="padding-top:60px">
|
<body>
|
||||||
<div class="navbar navbar-expand fixed-top navbar-dark bg-primary">
|
<div class="navbar navbar-expand fixed-top navbar-dark bg-primary">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a href="/" class="navbar-brand">GoWiki</a>
|
<a href="/" class="navbar-brand">GoWiki</a>
|
||||||
|
|
@ -62,7 +62,7 @@ const (
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div id="back-to-top" class="proper-content">
|
<div id="back-to-top" class="proper-content">
|
||||||
{{if .Msg}}
|
{{if .Msg}}
|
||||||
<div class="alert alert-danger" style="margin-top:30px">
|
<div class="alert alert-danger msg">
|
||||||
<h4>Error!</h4>
|
<h4>Error!</h4>
|
||||||
<p>{{.Msg}}</p>
|
<p>{{.Msg}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -256,7 +256,9 @@ const (
|
||||||
<a href="/{{.Data.StoreTitle}}/md" class="btn btn-sm btn-primary">Markdown</a>
|
<a href="/{{.Data.StoreTitle}}/md" class="btn btn-sm btn-primary">Markdown</a>
|
||||||
{{if .Login}}
|
{{if .Login}}
|
||||||
<a href="/{{.Data.StoreTitle}}/edit" class="btn btn-sm btn-primary">Edit</a>
|
<a href="/{{.Data.StoreTitle}}/edit" class="btn btn-sm btn-primary">Edit</a>
|
||||||
|
{{if eq .Data.Perm 3}}
|
||||||
<a href="/{{.Data.StoreTitle}}/share" class="btn btn-sm btn-primary">Share</a>
|
<a href="/{{.Data.StoreTitle}}/share" class="btn btn-sm btn-primary">Share</a>
|
||||||
|
{{end}}
|
||||||
{{if ne .Data.StoreTitle "wiki/Index"}}
|
{{if ne .Data.StoreTitle "wiki/Index"}}
|
||||||
<a href="/{{.Data.StoreTitle}}/del" class="btn btn-sm btn-primary">Delete</a>
|
<a href="/{{.Data.StoreTitle}}/del" class="btn btn-sm btn-primary">Delete</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
@ -317,6 +319,7 @@ const (
|
||||||
</form>
|
</form>
|
||||||
{{end}}`
|
{{end}}`
|
||||||
pageShare = `{{define "body"}}
|
pageShare = `{{define "body"}}
|
||||||
|
{{if not .Msg}}
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-4 offset-4">
|
<div class="col-xs-4 offset-4">
|
||||||
|
|
@ -346,6 +349,7 @@ const (
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}
|
||||||
{{end}}`
|
{{end}}`
|
||||||
pageView = `{{define "body"}}
|
pageView = `{{define "body"}}
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
|
|
@ -399,7 +403,7 @@ const (
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{{range $item := .Data}}
|
{{range $item := .Data}}
|
||||||
<a href="/{{$item.StoreTitle}}"><b>{{$item.Title}}</b></a>
|
<a href="/{{$item.StoreTitle}}"><b>{{$item.Title}}</b></a>
|
||||||
<pre style="white-space:pre-wrap">{{$item.Text}}</pre><br>
|
<pre class="wrap">{{$item.HTML}}</pre><br>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -603,7 +607,12 @@ const (
|
||||||
float: left !important;
|
float: left !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
body {
|
||||||
|
padding-top: 60px;
|
||||||
|
}
|
||||||
|
.msg {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
a.dark {
|
a.dark {
|
||||||
color: #222222;
|
color: #222222;
|
||||||
}
|
}
|
||||||
|
|
@ -664,8 +673,11 @@ pre {
|
||||||
border: 1px solid #444444;
|
border: 1px solid #444444;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
pre.wrap {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
code {
|
code {
|
||||||
padding: 3px 3px 1px 3px;
|
padding: 3px 3px 1px 0px;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
color: #0cdba6;
|
color: #0cdba6;
|
||||||
background-color: #444444;
|
background-color: #444444;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (C) 2018 Marius Schellenberger
|
||||||
|
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
|
// Copyright (C) 2018 Marius Schellenberger
|
||||||
|
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import "github.com/boltdb/bolt"
|
||||||
"github.com/boltdb/bolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultBoltBucket = "default"
|
const defaultBoltBucket = "default"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (C) 2018 Marius Schellenberger
|
||||||
|
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
|
|
@ -6,21 +6,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
//const (
|
|
||||||
// dbFile = "gowiki.db"
|
|
||||||
// defUser = "admin"
|
|
||||||
// defPassword = "gowiki"
|
|
||||||
// userTable = "user"
|
|
||||||
// pageTable = "page"
|
|
||||||
// snippetTable = "snippet"
|
|
||||||
// bcryptCost = 13
|
|
||||||
// indexName = "Index"
|
|
||||||
// welcome = `# Welcome to GoWiki
|
|
||||||
//This is the [Index](/wiki/Index) page.
|
|
||||||
//You can customize it how you like.`
|
|
||||||
// wikiSection = "wiki"
|
|
||||||
//)
|
|
||||||
|
|
||||||
func storeErr(i interface{}) error {
|
func storeErr(i interface{}) error {
|
||||||
return fmt.Errorf("store: %s", i)
|
return fmt.Errorf("store: %s", i)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<ul type="circle">
|
<ul type="circle">
|
||||||
{{range $item := .Data}}
|
{{range $item := .Data}}
|
||||||
{{if ne $owner $item.Owner}}
|
{{if ne $owner $item.Owner}}
|
||||||
{{$owner := $item.Owner}}
|
{{$owner = $item.Owner}}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,12 @@
|
||||||
float: left !important;
|
float: left !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
body {
|
||||||
|
padding-top: 60px;
|
||||||
|
}
|
||||||
|
.msg {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
a.dark {
|
a.dark {
|
||||||
color: #222222;
|
color: #222222;
|
||||||
}
|
}
|
||||||
|
|
@ -83,8 +88,11 @@ pre {
|
||||||
border: 1px solid #444444;
|
border: 1px solid #444444;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
pre.wrap {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
code {
|
code {
|
||||||
padding: 3px 3px 1px 3px;
|
padding: 3px 3px 1px 0px;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
color: #0cdba6;
|
color: #0cdba6;
|
||||||
background-color: #444444;
|
background-color: #444444;
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
<head>
|
<head>
|
||||||
<title>GoWiki | {{.Title}}</title>
|
<title>GoWiki | {{.Title}}</title>
|
||||||
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link>
|
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-NeLmQ7cX66J4MdtgGlG3O/TgvsSyP1b9WbaQtxYZUbQ="></link>
|
||||||
<link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-4M0Ran7skk1gZoHDDh/mtuzwXN51uagML7tkWnhE0JI="></link>
|
<link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-1LNAeuiM/rtsY1jIgLovUZ7HbYnOCkj3ZyailNA3rFQ="></link>
|
||||||
</head>
|
</head>
|
||||||
<body style="padding-top:60px">
|
<body>
|
||||||
<div class="navbar navbar-expand fixed-top navbar-dark bg-primary">
|
<div class="navbar navbar-expand fixed-top navbar-dark bg-primary">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a href="/" class="navbar-brand">GoWiki</a>
|
<a href="/" class="navbar-brand">GoWiki</a>
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div id="back-to-top" class="proper-content">
|
<div id="back-to-top" class="proper-content">
|
||||||
{{if .Msg}}
|
{{if .Msg}}
|
||||||
<div class="alert alert-danger" style="margin-top:30px">
|
<div class="alert alert-danger msg">
|
||||||
<h4>Error!</h4>
|
<h4>Error!</h4>
|
||||||
<p>{{.Msg}}</p>
|
<p>{{.Msg}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@
|
||||||
<a href="/{{.Data.StoreTitle}}/md" class="btn btn-sm btn-primary">Markdown</a>
|
<a href="/{{.Data.StoreTitle}}/md" class="btn btn-sm btn-primary">Markdown</a>
|
||||||
{{if .Login}}
|
{{if .Login}}
|
||||||
<a href="/{{.Data.StoreTitle}}/edit" class="btn btn-sm btn-primary">Edit</a>
|
<a href="/{{.Data.StoreTitle}}/edit" class="btn btn-sm btn-primary">Edit</a>
|
||||||
|
{{if eq .Data.Perm 3}}
|
||||||
<a href="/{{.Data.StoreTitle}}/share" class="btn btn-sm btn-primary">Share</a>
|
<a href="/{{.Data.StoreTitle}}/share" class="btn btn-sm btn-primary">Share</a>
|
||||||
|
{{end}}
|
||||||
{{if ne .Data.StoreTitle "wiki/Index"}}
|
{{if ne .Data.StoreTitle "wiki/Index"}}
|
||||||
<a href="/{{.Data.StoreTitle}}/del" class="btn btn-sm btn-primary">Delete</a>
|
<a href="/{{.Data.StoreTitle}}/del" class="btn btn-sm btn-primary">Delete</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
{{define "body"}}
|
{{define "body"}}
|
||||||
|
{{if not .Msg}}
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-4 offset-4">
|
<div class="col-xs-4 offset-4">
|
||||||
|
|
@ -29,3 +30,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{{range $item := .Data}}
|
{{range $item := .Data}}
|
||||||
<a href="/{{$item.StoreTitle}}"><b>{{$item.Title}}</b></a>
|
<a href="/{{$item.StoreTitle}}"><b>{{$item.Title}}</b></a>
|
||||||
<pre style="white-space:pre-wrap">{{$item.Text}}</pre><br>
|
<pre class="wrap">{{$item.HTML}}</pre><br>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue