bugfixes and updated go.mod
This commit is contained in:
parent
9424084a36
commit
6e317eb08b
300 changed files with 53136 additions and 9719 deletions
|
|
@ -135,6 +135,7 @@ func Run(version string) {
|
|||
if err = loadConfig(); err != nil {
|
||||
stdlog.Fatal("dump: ", err)
|
||||
}
|
||||
log.Stderr()
|
||||
if dumpFile != "-" {
|
||||
file, err = os.OpenFile(dumpFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0640)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -12,4 +12,8 @@ const (
|
|||
LogoutURI = "/logout"
|
||||
TotpURI = "/totp"
|
||||
SectionsURI = "/sections"
|
||||
|
||||
BootstrapCSS = "/bootstrap.css"
|
||||
CustomCSS = "/custom.css"
|
||||
Favicon = "/favicon.ico"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ func (db *DB) UpdatePage(title, section, markdown, username string, p core.Permi
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !db.WritePerm(title, username, page.Perm) {
|
||||
if !db.WritePerm(section, username, page.Perm) {
|
||||
return errPermissionDenied
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,16 @@ import (
|
|||
"git.giftfish.de/ston1th/godrop/v2"
|
||||
"git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||
"github.com/blevesearch/bleve"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
"html/template"
|
||||
"os"
|
||||
)
|
||||
|
||||
const maxSearchResult = 101
|
||||
const (
|
||||
maxSearchResult = 101
|
||||
|
||||
docType = "page"
|
||||
)
|
||||
|
||||
type indexPage struct {
|
||||
Title string `json:"title"`
|
||||
|
|
@ -19,6 +24,10 @@ type indexPage struct {
|
|||
Search string `json:"search"`
|
||||
}
|
||||
|
||||
func (*indexPage) Type() string {
|
||||
return docType
|
||||
}
|
||||
|
||||
type Index struct {
|
||||
i bleve.Index
|
||||
}
|
||||
|
|
@ -30,8 +39,22 @@ func NewIndex(path string) (i *Index, err error) {
|
|||
return
|
||||
}
|
||||
if _, err = os.Stat(path); os.IsNotExist(err) {
|
||||
mapping := bleve.NewIndexMapping()
|
||||
bi, err = bleve.New(path, mapping)
|
||||
err = os.Mkdir(path, 0700)
|
||||
if err != nil {
|
||||
err = errors.New("index: " + err.Error())
|
||||
return
|
||||
}
|
||||
err = godrop.Unveil(path, "rwc")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var m *mapping.IndexMappingImpl
|
||||
m, err = createMapping()
|
||||
if err != nil {
|
||||
err = errors.New("mapping: " + err.Error())
|
||||
return
|
||||
}
|
||||
bi, err = bleve.New(path, m)
|
||||
if err != nil {
|
||||
err = errors.New("index: " + err.Error())
|
||||
return
|
||||
|
|
@ -59,7 +82,7 @@ func (i *Index) Add(title, storeTitle, search string) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
return i.i.Index(storeTitle, indexPage{title, storeTitle, search})
|
||||
return i.i.Index(storeTitle, &indexPage{title, storeTitle, search})
|
||||
}
|
||||
|
||||
func (i *Index) Delete(storeTitle string) error {
|
||||
|
|
@ -67,7 +90,7 @@ func (i *Index) Delete(storeTitle string) error {
|
|||
}
|
||||
|
||||
func (i *Index) Search(search string) (results []core.Result, err error) {
|
||||
req := bleve.NewSearchRequest(bleve.NewQueryStringQuery("title:" + search + " search:" + search))
|
||||
req := bleve.NewSearchRequest(bleve.NewQueryStringQuery(search))
|
||||
req.Highlight = bleve.NewHighlightWithStyle("html")
|
||||
req.Size = maxSearchResult
|
||||
res, err := i.i.Search(req)
|
||||
|
|
|
|||
51
pkg/index/mapping.go
Normal file
51
pkg/index/mapping.go
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright (C) 2019 Marius Schellenberger
|
||||
|
||||
package index
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve"
|
||||
"github.com/blevesearch/bleve/analysis/analyzer/custom"
|
||||
"github.com/blevesearch/bleve/analysis/token/edgengram"
|
||||
"github.com/blevesearch/bleve/analysis/token/lowercase"
|
||||
"github.com/blevesearch/bleve/analysis/tokenizer/unicode"
|
||||
"github.com/blevesearch/bleve/mapping"
|
||||
)
|
||||
|
||||
func createMapping() (m *mapping.IndexMappingImpl, err error) {
|
||||
m = bleve.NewIndexMapping()
|
||||
|
||||
err = m.AddCustomTokenFilter("bigram_tokenfilter", map[string]interface{}{
|
||||
"type": edgengram.Name,
|
||||
"side": edgengram.FRONT,
|
||||
"min": 3.0,
|
||||
"max": 25.0,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = m.AddCustomAnalyzer("fulltext_ngram", map[string]interface{}{
|
||||
"type": custom.Name,
|
||||
"tokenizer": unicode.Name,
|
||||
"token_filters": []string{
|
||||
lowercase.Name,
|
||||
"bigram_tokenfilter",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
doc := bleve.NewDocumentMapping()
|
||||
ti := bleve.NewTextFieldMapping()
|
||||
ti.Analyzer = "fulltext_ngram"
|
||||
doc.AddFieldMappingsAt("title", ti)
|
||||
st := bleve.NewTextFieldMapping()
|
||||
st.Analyzer = "fulltext_ngram"
|
||||
doc.AddFieldMappingsAt("store_title", st)
|
||||
s := bleve.NewTextFieldMapping()
|
||||
s.Analyzer = "fulltext_ngram"
|
||||
doc.AddFieldMappingsAt("search", s)
|
||||
|
||||
m.AddDocumentMapping(docType, doc)
|
||||
return
|
||||
}
|
||||
|
|
@ -15,6 +15,10 @@ var (
|
|||
debug = false
|
||||
)
|
||||
|
||||
func Stderr() {
|
||||
log = stdlog.New(os.Stderr, "", stdlog.LstdFlags)
|
||||
}
|
||||
|
||||
func InitLogger(cfg core.Config) {
|
||||
debug = cfg.Debug
|
||||
if cfg.LogFile == "-" {
|
||||
|
|
|
|||
|
|
@ -27,53 +27,19 @@ const (
|
|||
refererClaim = "referer"
|
||||
)
|
||||
|
||||
func newContext(w http.ResponseWriter, r *http.Request, s *HTTPServer) (ctx *Context) {
|
||||
func newContext(w http.ResponseWriter, r *http.Request, s *HTTPServer) *Context {
|
||||
h := w.Header()
|
||||
h.Set("X-Frame-Options", "DENY")
|
||||
h.Set("X-Content-Type-Options", "nosniff")
|
||||
h.Set("X-XSS-Protection", "1; mode=block")
|
||||
h.Set("Content-Security-Policy", "default-src 'none';style-src 'self';img-src 'self' https: data:;connect-src 'self';frame-ancestors 'none'")
|
||||
h.Set("Referrer-Policy", "same-origin")
|
||||
ctx = &Context{
|
||||
return &Context{
|
||||
Request: r,
|
||||
Response: w,
|
||||
Srv: s,
|
||||
Time: time.Now(),
|
||||
}
|
||||
path := ctx.Path()
|
||||
if path == "/bootstrap.css" || path == "/custom.css" {
|
||||
return
|
||||
}
|
||||
|
||||
if c, err := r.Cookie(cookieName); err == nil {
|
||||
t, err := jwt.DecodeToken(c.Value)
|
||||
if err != nil {
|
||||
ctx.LogSetCookie("DecodeToken:", err)
|
||||
return
|
||||
}
|
||||
if err = s.JWT.Verify(t); err != nil {
|
||||
ctx.LogSetCookie("VerifyToken:", err)
|
||||
return
|
||||
}
|
||||
if t.Claims.GetString(totpClaim) != "" {
|
||||
if path == core.TotpURI || path == core.LoginURI || path == core.LogoutURI {
|
||||
ctx.Token = *t
|
||||
return
|
||||
}
|
||||
ctx.Redirect(core.TotpURI, http.StatusFound)
|
||||
return nil
|
||||
|
||||
}
|
||||
if !s.DB.LockedOut(t.Claims.GetString(userClaim), t.Claims.GetString(userCreatedClaim)) {
|
||||
ctx.Token = *t
|
||||
return
|
||||
}
|
||||
if err = s.JWT.Invalidate(t); err != nil {
|
||||
log.Println("Invalidate:", err)
|
||||
}
|
||||
}
|
||||
ctx.SetCookie(nil, 0)
|
||||
return
|
||||
}
|
||||
|
||||
type Context struct {
|
||||
|
|
@ -132,7 +98,10 @@ func (c *Context) Exec() {
|
|||
c.Err = errors.New("template is nil")
|
||||
return
|
||||
}
|
||||
c.Data.Token = newXsrf(c.Token.RawSig()[:keySize])
|
||||
sig := c.Token.RawSig()
|
||||
if len(sig) >= keySize {
|
||||
c.Data.Token = newXsrf(sig[:keySize])
|
||||
}
|
||||
|
||||
c.Data.Version = c.Srv.Config.Version
|
||||
if c.Data.BodyTitle == "" {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,43 @@ func (nf *notFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
newContext(w, r, nf.s).NotFound()
|
||||
}
|
||||
|
||||
func jwtHandler(h ctxHandler) ctxHandler {
|
||||
return func(ctx *Context) {
|
||||
if c, err := ctx.Request.Cookie(cookieName); err == nil {
|
||||
t, err := jwt.DecodeToken(c.Value)
|
||||
if err != nil {
|
||||
ctx.LogSetCookie("DecodeToken:", err)
|
||||
return
|
||||
}
|
||||
if err = ctx.Srv.JWT.Verify(t); err != nil {
|
||||
ctx.LogSetCookie("VerifyToken:", err)
|
||||
return
|
||||
}
|
||||
if t.Claims.GetString(totpClaim) != "" {
|
||||
path := ctx.Path()
|
||||
if path == core.TotpURI || path == core.LoginURI || path == core.LogoutURI {
|
||||
ctx.Token = *t
|
||||
h(ctx)
|
||||
return
|
||||
}
|
||||
ctx.Redirect(core.TotpURI, http.StatusFound)
|
||||
return
|
||||
|
||||
}
|
||||
if !ctx.Srv.DB.LockedOut(t.Claims.GetString(userClaim), t.Claims.GetString(userCreatedClaim)) {
|
||||
ctx.Token = *t
|
||||
h(ctx)
|
||||
return
|
||||
}
|
||||
if err = ctx.Srv.JWT.Invalidate(t); err != nil {
|
||||
log.Println("Invalidate:", err)
|
||||
}
|
||||
}
|
||||
ctx.SetCookie(nil, 0)
|
||||
h(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func totpAuthHandler(h ctxHandler) ctxHandler {
|
||||
return func(ctx *Context) {
|
||||
if ctx.Totp() != "" {
|
||||
|
|
@ -69,11 +106,11 @@ func staticHandler(ctx *Context) {
|
|||
ctx.SetHeader("Content-Type", "text/css; charset=utf-8")
|
||||
ctx.SetHeader("Expires", time.Now().UTC().Add(max).Format(http.TimeFormat))
|
||||
switch ctx.Path() {
|
||||
case "/bootstrap.css":
|
||||
case core.BootstrapCSS:
|
||||
err = ctx.Write(ctx.Srv.res["bootstrap.css"])
|
||||
case "/custom.css":
|
||||
case core.CustomCSS:
|
||||
err = ctx.Write(ctx.Srv.res["custom.css"])
|
||||
case "/favicon.ico":
|
||||
case core.Favicon:
|
||||
ctx.SetHeader("Content-Type", "image/x-icon")
|
||||
err = ctx.Write(ctx.Srv.res["favicon.ico"])
|
||||
}
|
||||
|
|
@ -458,8 +495,8 @@ func pageHandler(ctx *Context) {
|
|||
return
|
||||
}
|
||||
ctx.Data.Data = page
|
||||
ctx.Data.Title = page.Title
|
||||
ctx.Data.BodyTitle = page.Title
|
||||
ctx.Data.Title = section + " | " + page.Title
|
||||
ctx.Data.BodyTitle = section
|
||||
ctx.Exec()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,167 +2,197 @@
|
|||
|
||||
package server
|
||||
|
||||
import "git.giftfish.de/ston1th/gowiki/pkg/core"
|
||||
|
||||
type route struct {
|
||||
Path string
|
||||
Handler ctxHandler
|
||||
Methods []string
|
||||
}
|
||||
|
||||
var static = []route{
|
||||
{
|
||||
core.Favicon,
|
||||
staticHandler,
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
core.BootstrapCSS,
|
||||
staticHandler,
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
core.CustomCSS,
|
||||
staticHandler,
|
||||
[]string{"GET"},
|
||||
},
|
||||
}
|
||||
|
||||
var routes = []route{
|
||||
{
|
||||
"/",
|
||||
indexHandler,
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/favicon.ico",
|
||||
staticHandler,
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/bootstrap.css",
|
||||
staticHandler,
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/custom.css",
|
||||
staticHandler,
|
||||
jwtHandler(
|
||||
indexHandler),
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/sections",
|
||||
sectionsHandler,
|
||||
jwtHandler(
|
||||
sectionsHandler),
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/section/new",
|
||||
adminAuthHandler(
|
||||
sectionNewHandler),
|
||||
jwtHandler(
|
||||
adminAuthHandler(
|
||||
sectionNewHandler)),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/section/edit/{section:[a-zA-Z0-9]+$}",
|
||||
adminAuthHandler(
|
||||
sectionEditHandler),
|
||||
jwtHandler(
|
||||
adminAuthHandler(
|
||||
sectionEditHandler)),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/section/del/{section:[a-zA-Z0-9]+$}",
|
||||
adminAuthHandler(
|
||||
sectionDelHandler),
|
||||
jwtHandler(
|
||||
adminAuthHandler(
|
||||
sectionDelHandler)),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/search",
|
||||
searchHandler,
|
||||
jwtHandler(
|
||||
searchHandler),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/new",
|
||||
authHandler(
|
||||
pageNewHandler),
|
||||
jwtHandler(
|
||||
authHandler(
|
||||
pageNewHandler)),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/login",
|
||||
loginHandler,
|
||||
jwtHandler(
|
||||
loginHandler),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/totp",
|
||||
totpAuthHandler(
|
||||
loginTotpHandler),
|
||||
jwtHandler(
|
||||
totpAuthHandler(
|
||||
loginTotpHandler)),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/blacklist",
|
||||
authHandler(
|
||||
pageBlacklistHandler),
|
||||
jwtHandler(
|
||||
authHandler(
|
||||
pageBlacklistHandler)),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/users",
|
||||
adminAuthHandler(
|
||||
usersHandler),
|
||||
jwtHandler(
|
||||
adminAuthHandler(
|
||||
usersHandler)),
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/user/new",
|
||||
adminAuthHandler(
|
||||
userNewHandler),
|
||||
jwtHandler(
|
||||
adminAuthHandler(
|
||||
userNewHandler)),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/user/edit/{user:[a-zA-Z0-9]+$}",
|
||||
userAuthHandler(
|
||||
userEditHandler),
|
||||
jwtHandler(
|
||||
userAuthHandler(
|
||||
userEditHandler)),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/user/del/{user:[a-zA-Z0-9]+$}",
|
||||
userAuthHandler(
|
||||
userDelHandler),
|
||||
jwtHandler(
|
||||
userAuthHandler(
|
||||
userDelHandler)),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/user/totp/{user:[a-zA-Z0-9]+$}",
|
||||
userAuthHandler(
|
||||
userTotpHandler),
|
||||
jwtHandler(
|
||||
userAuthHandler(
|
||||
userTotpHandler)),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/user/unlock/{user:[a-zA-Z0-9]+$}",
|
||||
adminAuthHandler(
|
||||
userUnlockHandler),
|
||||
jwtHandler(
|
||||
adminAuthHandler(
|
||||
userUnlockHandler)),
|
||||
[]string{"POST"},
|
||||
},
|
||||
{
|
||||
"/logout",
|
||||
logoutHandler,
|
||||
jwtHandler(
|
||||
logoutHandler),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/view/{key}",
|
||||
pageHandler,
|
||||
jwtHandler(
|
||||
pageHandler),
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/view/{key}/md",
|
||||
pageMDHandler(
|
||||
pageHandler),
|
||||
jwtHandler(
|
||||
pageMDHandler(
|
||||
pageHandler)),
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/{section:[a-zA-Z0-9]+}",
|
||||
sectionHandler,
|
||||
jwtHandler(
|
||||
sectionHandler),
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/{section:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}",
|
||||
pageHandler,
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/{section:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}/md",
|
||||
pageMDHandler(
|
||||
jwtHandler(
|
||||
pageHandler),
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/{section:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}/md",
|
||||
jwtHandler(
|
||||
pageMDHandler(
|
||||
pageHandler)),
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/{section:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}/share",
|
||||
pageShareHandler,
|
||||
jwtHandler(
|
||||
pageShareHandler),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/{section:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}/edit",
|
||||
authHandler(
|
||||
pageEditHandler),
|
||||
jwtHandler(
|
||||
authHandler(
|
||||
pageEditHandler)),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
{
|
||||
"/{section:[a-zA-Z0-9]+}/{title:[a-zA-Z0-9+-]+}/del",
|
||||
authHandler(
|
||||
pageDelHandler),
|
||||
jwtHandler(
|
||||
authHandler(
|
||||
pageDelHandler)),
|
||||
[]string{"GET", "POST"},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
cookieName = "gosession"
|
||||
cookieName = "gowiki_session"
|
||||
)
|
||||
|
||||
type HTTPServer struct {
|
||||
|
|
@ -81,6 +81,9 @@ func (s *HTTPServer) Stop() error {
|
|||
func (s *HTTPServer) buildRoutes() http.Handler {
|
||||
m := mux.NewRouter()
|
||||
m.NotFoundHandler = ¬FoundHandler{s}
|
||||
for _, v := range static {
|
||||
m.HandleFunc(v.Path, s.contextWrapper(v.Handler)).Methods(v.Methods...)
|
||||
}
|
||||
for _, v := range routes {
|
||||
m.HandleFunc(v.Path, s.contextWrapper(v.Handler)).Methods(v.Methods...)
|
||||
}
|
||||
|
|
@ -88,10 +91,6 @@ func (s *HTTPServer) buildRoutes() http.Handler {
|
|||
}
|
||||
func (s *HTTPServer) contextWrapper(h ctxHandler) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(w, r, s)
|
||||
if ctx == nil {
|
||||
return
|
||||
}
|
||||
h(ctx)
|
||||
h(newContext(w, r, s))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue