single db with prefixes, dump/restore, blacklisting on user deletion

This commit is contained in:
ston1th 2018-09-23 23:56:12 +02:00
commit d4971cda89
20 changed files with 264 additions and 163 deletions

2
go.mod
View file

@ -2,7 +2,7 @@ module git.giftfish.de/ston1th/gowiki
require ( require (
git.giftfish.de/ston1th/godrop/v2 v2.0.1 git.giftfish.de/ston1th/godrop/v2 v2.0.1
git.giftfish.de/ston1th/jwt/v3 v3.0.0 git.giftfish.de/ston1th/jwt/v3 v3.1.0
github.com/RoaringBitmap/roaring v0.4.16 // indirect github.com/RoaringBitmap/roaring v0.4.16 // indirect
github.com/Smerity/govarint v0.0.0-20150407073650-7265e41f48f1 // indirect github.com/Smerity/govarint v0.0.0-20150407073650-7265e41f48f1 // indirect
github.com/blevesearch/bleve v0.7.0 github.com/blevesearch/bleve v0.7.0

4
go.sum
View file

@ -1,7 +1,7 @@
git.giftfish.de/ston1th/godrop/v2 v2.0.1 h1:Ka7P1VcC11n4mDXhCA/qaM1Bi6IBhcGFk6J9OO6c2vs= git.giftfish.de/ston1th/godrop/v2 v2.0.1 h1:Ka7P1VcC11n4mDXhCA/qaM1Bi6IBhcGFk6J9OO6c2vs=
git.giftfish.de/ston1th/godrop/v2 v2.0.1/go.mod h1:QPMNHbTi2clDHyq8Chie49wFG78LkwKnw5lkx/RBkKQ= git.giftfish.de/ston1th/godrop/v2 v2.0.1/go.mod h1:QPMNHbTi2clDHyq8Chie49wFG78LkwKnw5lkx/RBkKQ=
git.giftfish.de/ston1th/jwt/v3 v3.0.0 h1:zywHtrYmEpU+osig6ujvHdettLuuDC4K7HHaiGigD/k= git.giftfish.de/ston1th/jwt/v3 v3.1.0 h1:SaSpVyl98iqnsRIh9tA8/XJpU6nCYIGa4L85rLvQWC0=
git.giftfish.de/ston1th/jwt/v3 v3.0.0/go.mod h1:jDALrW7OKeIaqYuMxs3GWyPRnQZmk/R+Lu9p/Em3Ziw= git.giftfish.de/ston1th/jwt/v3 v3.1.0/go.mod h1:jDALrW7OKeIaqYuMxs3GWyPRnQZmk/R+Lu9p/Em3Ziw=
github.com/RoaringBitmap/roaring v0.4.16 h1:NholfewybRLOwACgfqfzn/N5xa6keKNs4fP00t0cwLo= github.com/RoaringBitmap/roaring v0.4.16 h1:NholfewybRLOwACgfqfzn/N5xa6keKNs4fP00t0cwLo=
github.com/RoaringBitmap/roaring v0.4.16/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w= github.com/RoaringBitmap/roaring v0.4.16/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
github.com/Smerity/govarint v0.0.0-20150407073650-7265e41f48f1 h1:G/NOANWMQev0CftoyxQwtRakdyNNNMB3qxkt/tj1HGs= github.com/Smerity/govarint v0.0.0-20150407073650-7265e41f48f1 h1:G/NOANWMQev0CftoyxQwtRakdyNNNMB3qxkt/tj1HGs=

View file

@ -168,6 +168,11 @@ func serverFlags() []cli.Flag {
Usage: "enable secure cookie", Usage: "enable secure cookie",
Destination: &conf.SecureCookie, Destination: &conf.SecureCookie,
}, },
cli.BoolFlag{
Name: "debug",
Usage: "enable debugging",
Destination: &log.DebugEnabled,
},
} }
} }

View file

@ -25,6 +25,7 @@ func (p Pages) Less(i, j int) bool { return p[i].StoreTitle < p[j].StoreTitle }
type User struct { type User struct {
Username string Username string
Password string Password string
Created string
Admin bool Admin bool
Locked int Locked int
} }

36
pkg/db/blacklist.go Normal file
View file

@ -0,0 +1,36 @@
package db
import "git.giftfish.de/ston1th/jwt/v3"
const blacklistPrefix = "blacklist/"
func (db *DB) Add(sig string, exp int64) error {
return db.store.Set(blacklistPrefix+sig, exp)
}
func (db *DB) Remove(sig string) error {
return db.store.Delete(blacklistPrefix + sig)
}
func (db *DB) Check(sig string) (ok bool) {
if db.store.Get(blacklistPrefix+sig, nil) == nil {
return true
}
return false
}
func (db *DB) Map() (list jwt.BlacklistMap, err error) {
list = make(jwt.BlacklistMap)
err = db.store.ForEach(func(k string, v []byte) error {
if trim, ok := hasTrimPrefix(k, blacklistPrefix); ok {
var exp int64
err := db.store.Unmarshal(v, &exp)
if err != nil {
return err
}
list[trim] = exp
}
return nil
})
return
}

View file

@ -13,8 +13,7 @@ import (
const ( const (
defUser = "admin" defUser = "admin"
defPassword = "gowiki" defPassword = "gowiki"
usersFile = "users.db" storeFile = "store.db"
pagesFile = "pages.db"
bcryptCost = 13 bcryptCost = 13
welcome = `# Welcome to GoWiki welcome = `# Welcome to GoWiki
This is the [Index](/wiki/Index) page. This is the [Index](/wiki/Index) page.
@ -28,8 +27,7 @@ func dbErr(i interface{}) error {
} }
type DB struct { type DB struct {
pages store.Store store store.Store
users store.Store
cache *cache.Cache cache *cache.Cache
Index *index.Index Index *index.Index
} }
@ -41,7 +39,7 @@ func New(datadir string) (db *DB, err error) {
return return
} }
db.users, err = store.NewBoltStore(usersFile, nil) db.store, err = store.NewBoltStore(storeFile, nil)
if err != nil { if err != nil {
return return
} }
@ -56,10 +54,6 @@ func New(datadir string) (db *DB, err error) {
return return
} }
db.pages, err = store.NewBoltStore(pagesFile, nil)
if err != nil {
return
}
_, err = db.CreatePage(core.IndexPage, core.WikiSection, welcome, defUser, core.Public) _, err = db.CreatePage(core.IndexPage, core.WikiSection, welcome, defUser, core.Public)
if err != nil && err != store.ErrKeyNotFound { if err != nil && err != store.ErrKeyNotFound {
return return
@ -68,11 +62,7 @@ func New(datadir string) (db *DB, err error) {
} }
func (db *DB) Close() error { func (db *DB) Close() error {
err := db.pages.Close() err := db.store.Close()
if err != nil {
return err
}
err = db.users.Close()
if err != nil { if err != nil {
return err return err
} }

View file

@ -19,6 +19,14 @@ var reservedUsers = []string{
"wiki", "wiki",
} }
// Parts taken from strings.TrimPrefix
func hasTrimPrefix(s, prefix string) (string, bool) {
if len(s) >= len(prefix) && s[0:len(prefix)] == prefix {
return s[len(prefix):], true
}
return s, false
}
func reservedUser(username string) bool { func reservedUser(username string) bool {
for _, v := range reservedUsers { for _, v := range reservedUsers {
if username == v { if username == v {

11
pkg/db/lockout.go Normal file
View file

@ -0,0 +1,11 @@
package db
const lockoutPrefix = "lockout/"
func (db *DB) lockout(username, created string) error {
return db.store.Set(lockoutPrefix+username+created, nil)
}
func (db *DB) LockedOut(username, created string) bool {
return db.store.Get(lockoutPrefix+username+created, nil) == nil
}

View file

@ -9,6 +9,8 @@ import (
"sort" "sort"
) )
const pagePrefix = "page/"
var ( var (
errPageNotFound = store.ErrKeyNotFound errPageNotFound = store.ErrKeyNotFound
errPermissionDenied = dbErr("permission denied") errPermissionDenied = dbErr("permission denied")
@ -18,14 +20,16 @@ var (
) )
func (db *DB) GetAllPages(username string) (pages core.Pages) { func (db *DB) GetAllPages(username string) (pages core.Pages) {
db.pages.ForEach(func(title string, _ []byte) error { db.store.ForEach(func(k string, _ []byte) error {
if title == core.IndexURI { if trim, ok := hasTrimPrefix(k, pagePrefix); ok {
if trim == core.IndexURI {
return nil return nil
} }
p, err := db.GetPage(title, username) p, err := db.GetPage(trim, username)
if err == nil { if err == nil {
pages = append(pages, *p) pages = append(pages, *p)
} }
}
return nil return nil
}) })
sort.Sort(pages) sort.Sort(pages)
@ -47,7 +51,7 @@ func (db *DB) GetPage(title, username string) (p *core.Page, err error) {
func (db *DB) getPage(title, username string) (p *core.Page, err error) { func (db *DB) getPage(title, username string) (p *core.Page, err error) {
p = new(core.Page) p = new(core.Page)
err = db.pages.Get(title, p) err = db.store.Get(pagePrefix+title, p)
if err != nil { if err != nil {
return return
} }
@ -85,7 +89,7 @@ func (db *DB) CreatePage(title, section, markdown, username string, p core.Permi
} }
search := render.Render(page) search := render.Render(page)
err = db.pages.Set(st, page) err = db.store.Set(pagePrefix+st, page)
if err != nil { if err != nil {
return return
} }
@ -121,7 +125,7 @@ func (db *DB) UpdatePage(title, section, markdown, username string, p core.Permi
page.Perm = p page.Perm = p
search := render.Render(page) search := render.Render(page)
err = db.pages.Set(st, page) err = db.store.Set(pagePrefix+st, page)
if err != nil { if err != nil {
return return
} }
@ -134,6 +138,14 @@ func (db *DB) UpdatePage(title, section, markdown, username string, p core.Permi
return return
} }
func (db *DB) deletePage(st string) (err error) {
if err = db.store.Delete(pagePrefix + st); err != nil {
return
}
db.cache.Delete(st)
return db.Index.Delete(st)
}
func (db *DB) DeletePage(title, section, username string) (err error) { func (db *DB) DeletePage(title, section, username string) (err error) {
var page *core.Page var page *core.Page
if section == core.WikiSection && title == core.IndexPage { if section == core.WikiSection && title == core.IndexPage {
@ -147,10 +159,5 @@ func (db *DB) DeletePage(title, section, username string) (err error) {
if !core.WritePerm(username, section, page) { if !core.WritePerm(username, section, page) {
return errPermissionDenied return errPermissionDenied
} }
return db.deletePage(st)
if err = db.pages.Delete(st); err != nil {
return
}
db.cache.Delete(st)
return db.Index.Delete(st)
} }

View file

@ -5,10 +5,16 @@ package db
import ( import (
"errors" "errors"
"git.giftfish.de/ston1th/gowiki/pkg/core" "git.giftfish.de/ston1th/gowiki/pkg/core"
"git.giftfish.de/ston1th/gowiki/pkg/log"
"git.giftfish.de/ston1th/jwt/v3"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"sort" "sort"
"strconv"
"strings"
) )
const userPrefix = "user/"
var ( var (
errUserNotFound = dbErr("user not found") errUserNotFound = dbErr("user not found")
errUserExists = dbErr("user already exist") errUserExists = dbErr("user already exist")
@ -16,11 +22,13 @@ var (
) )
func (db *DB) DumpUsers() (users core.Users, err error) { func (db *DB) DumpUsers() (users core.Users, err error) {
db.users.ForEach(func(username string, _ []byte) error { db.store.ForEach(func(k string, _ []byte) error {
u, err := db.GetUser(username) if trim, ok := hasTrimPrefix(k, userPrefix); ok {
u, err := db.GetUser(trim)
if err == nil { if err == nil {
users = append(users, u) users = append(users, u)
} }
}
return nil return nil
}) })
return return
@ -42,12 +50,12 @@ func (db *DB) GetUserWithoutPassword(username string) (u core.User, err error) {
} }
func (db *DB) GetUser(username string) (u core.User, err error) { func (db *DB) GetUser(username string) (u core.User, err error) {
err = db.users.Get(username, &u) err = db.store.Get(userPrefix+username, &u)
return return
} }
func (db *DB) UserExists(username string) error { func (db *DB) UserExists(username string) error {
return db.users.Get(username, nil) return db.store.Get(userPrefix+username, nil)
} }
// TODO // TODO
@ -85,9 +93,10 @@ func (db *DB) CreateUser(username, password string, admin bool) error {
if err != nil { if err != nil {
return err return err
} }
return db.users.Set(username, &core.User{ return db.store.Set(userPrefix+username, &core.User{
Username: username, Username: username,
Password: string(hash), Password: string(hash),
Created: strconv.Itoa(int(jwt.Now())),
Admin: admin, Admin: admin,
}) })
} }
@ -103,7 +112,7 @@ func (db *DB) Login(username, password string) (u core.User, err error) {
} }
if err = bcrypt.CompareHashAndPassword([]byte(u.Password), validatePassword(password)); err != nil { if err = bcrypt.CompareHashAndPassword([]byte(u.Password), validatePassword(password)); err != nil {
u.Locked++ u.Locked++
err = db.users.Set(username, u) err = db.store.Set(userPrefix+username, u)
if err != nil { if err != nil {
return return
} }
@ -112,7 +121,7 @@ func (db *DB) Login(username, password string) (u core.User, err error) {
} }
if u.Locked > 0 { if u.Locked > 0 {
u.Locked = 0 u.Locked = 0
err = db.users.Set(username, u) err = db.store.Set(userPrefix+username, u)
if err != nil { if err != nil {
return return
} }
@ -128,7 +137,7 @@ func (db *DB) UnlockUser(username string) (err error) {
} }
if u.Locked > 0 { if u.Locked > 0 {
u.Locked = 0 u.Locked = 0
err = db.users.Set(username, u) err = db.store.Set(userPrefix+username, u)
if err != nil { if err != nil {
return return
} }
@ -146,7 +155,7 @@ func (db *DB) UpdateUserPassword(username, password string) error {
return err return err
} }
u.Password = string(hash) u.Password = string(hash)
return db.users.Set(username, u) return db.store.Set(userPrefix+username, u)
} }
func (db *DB) AdminUpdateUser(username, password string, admin bool) error { func (db *DB) AdminUpdateUser(username, password string, admin bool) error {
@ -165,13 +174,40 @@ func (db *DB) AdminUpdateUser(username, password string, admin bool) error {
} }
u.Password = string(hash) u.Password = string(hash)
} }
return db.users.Set(username, u) return db.store.Set(userPrefix+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")
} }
return db.users.Delete(username) u, err := db.GetUser(username)
if err != nil {
return err
}
err = db.store.Delete(userPrefix + username)
if err != nil {
return err
}
err = db.lockout(username, u.Created)
if err != nil {
return err
}
username += "/"
var pages []string
db.store.ForEach(func(k string, _ []byte) error {
if trim, ok := hasTrimPrefix(k, pagePrefix); ok {
if strings.HasPrefix(trim, username) {
pages = append(pages, trim)
}
}
return nil
})
for _, p := range pages {
err := db.deletePage(p)
if err != nil {
log.Debugf("DeleteUser %s, %s", username, err)
}
}
return nil
} }

View file

@ -7,7 +7,12 @@ import (
"os" "os"
) )
var log *stdlog.Logger const debug = "debug: "
var (
log *stdlog.Logger
DebugEnabled = false
)
func NewLogger(file string) { func NewLogger(file string) {
if file == "" { if file == "" {
@ -33,3 +38,15 @@ func Printf(fmt string, v ...interface{}) {
func Fatal(v ...interface{}) { func Fatal(v ...interface{}) {
log.Fatal(v...) log.Fatal(v...)
} }
func Debug(v ...interface{}) {
if DebugEnabled {
log.Println(append([]interface{}{debug}, v...))
}
}
func Debugf(fmt string, v ...interface{}) {
if DebugEnabled {
log.Printf(debug+fmt, v...)
}
}

View file

@ -15,6 +15,7 @@ import (
const ( const (
userClaim = "user" userClaim = "user"
adminClaim = "admin" adminClaim = "admin"
createdClaim = "created"
sharedClaim = "shared" sharedClaim = "shared"
sectionClaim = "section" sectionClaim = "section"
titleClaim = "title" titleClaim = "title"
@ -40,9 +41,14 @@ func newContext(w http.ResponseWriter, r *http.Request, s *HTTPServer) (ctx *Con
if err = s.JWT.Verify(t); err != nil { if err = s.JWT.Verify(t); err != nil {
log.Println("VerifyToken:", err) log.Println("VerifyToken:", err)
} else { } else {
if !s.DB.LockedOut(t.Claims.GetString(userClaim), t.Claims.GetString(createdClaim)) {
ctx.Token = *t ctx.Token = *t
return return
} }
if err = s.JWT.Invalidate(t); err != nil {
log.Println("Invalidate:", err)
}
}
} }
} }
ctx.SetCookie(nil) ctx.SetCookie(nil)

View file

@ -100,7 +100,7 @@ func loginHandler(ctx *Context) {
ctx.Error(err) ctx.Error(err)
return return
} }
ctx.SetCookie(jwt.Claims{userClaim: u.Username, adminClaim: u.Admin}) ctx.SetCookie(jwt.Claims{userClaim: u.Username, createdClaim: u.Created, adminClaim: u.Admin})
ctx.Redirect(core.IndexURI, 302) ctx.Redirect(core.IndexURI, 302)
} }
} }
@ -195,6 +195,13 @@ func pageHandler(ctx *Context) {
return return
} }
user = t.Claims.GetString(sharedClaim) user = t.Claims.GetString(sharedClaim)
if ctx.Srv.DB.LockedOut(user, t.Claims.GetString(createdClaim)) {
if err = ctx.Srv.JWT.Invalidate(t); err != nil {
log.Println("Invalidate:", err)
}
ctx.NotFound()
return
}
section = t.Claims.GetString(sectionClaim) section = t.Claims.GetString(sectionClaim)
title = t.Claims.GetString(titleClaim) title = t.Claims.GetString(titleClaim)
ctx.Data.Key = key ctx.Data.Key = key
@ -259,6 +266,7 @@ func pageShareHandler(ctx *Context) {
} }
t := jwt.NewToken(map[string]interface{}{ t := jwt.NewToken(map[string]interface{}{
sharedClaim: user, sharedClaim: user,
createdClaim: ctx.Token.Claims.GetString(createdClaim),
sectionClaim: section, sectionClaim: section,
titleClaim: title, titleClaim: title,
jwt.ExpClaim: jwt.NewExp(d), jwt.ExpClaim: jwt.NewExp(d),

View file

@ -56,11 +56,11 @@ func (s *HTTPServer) Start() (err error) {
if s.Secret != "" { if s.Secret != "" {
buf = bytes.NewBufferString(s.Secret) buf = bytes.NewBufferString(s.Secret)
} }
s.JWT, err = jwt.New(jwt.DefaultExpiry, jwt.NewMemBlacklist(), buf) s.DB, err = db.New(s.DataDir)
if err != nil { if err != nil {
return return
} }
s.DB, err = db.New(s.DataDir) s.JWT, err = jwt.New(jwt.DefaultExpiry, s.DB, buf)
if err != nil { if err != nil {
return return
} }

View file

@ -2,7 +2,11 @@
package store package store
import "github.com/boltdb/bolt" import (
"encoding/json"
"github.com/boltdb/bolt"
"io"
)
const defaultBoltBucket = "default" const defaultBoltBucket = "default"
@ -15,15 +19,13 @@ func NewBoltStore(file string, m Marshaler) (bs *BoltStore, err error) {
if m == nil { if m == nil {
m = NewGOB() m = NewGOB()
} }
db, err := bolt.Open(file, 0666, nil) db, err := bolt.Open(file, 0640, nil)
if err != nil { if err != nil {
return return
} }
if err = db.Update(func(tx *bolt.Tx) error { if err = db.Update(func(tx *bolt.Tx) (err error) {
if _, err := tx.CreateBucketIfNotExists([]byte(defaultBoltBucket)); err != nil { _, err = tx.CreateBucketIfNotExists([]byte(defaultBoltBucket))
return err return
}
return nil
}); err != nil { }); err != nil {
return return
} }
@ -31,6 +33,53 @@ func NewBoltStore(file string, m Marshaler) (bs *BoltStore, err error) {
return return
} }
type dump struct {
k string `json:"k"`
v []byte `json:"v"`
}
func (bs *BoltStore) Dump(w io.Writer) (err error) {
if w == nil {
return ErrWriterIsNil
}
var d []dump
err = bs.ForEach(func(k string, v []byte) error {
d = append(d, dump{k, v})
return nil
})
if err != nil {
return
}
return json.NewEncoder(w).Encode(d)
}
func (bs *BoltStore) Restore(r io.Reader) (err error) {
if r == nil {
return ErrReaderIsNil
}
var d []dump
err = json.NewDecoder(r).Decode(&d)
if err != nil {
return
}
if err = bs.db.Update(func(tx *bolt.Tx) (err error) {
if err = tx.DeleteBucket([]byte(defaultBoltBucket)); err != nil {
return
}
_, err = tx.CreateBucket([]byte(defaultBoltBucket))
return
}); err != nil {
return
}
return bs.db.Update(func(tx *bolt.Tx) (err error) {
b := tx.Bucket([]byte(defaultBoltBucket))
for _, v := range d {
b.Put([]byte(v.k), v.v)
}
return nil
})
}
func (bs *BoltStore) Get(key string, v interface{}) (err error) { func (bs *BoltStore) Get(key string, v interface{}) (err error) {
err = bs.db.View(func(tx *bolt.Tx) error { err = bs.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(defaultBoltBucket)).Get([]byte(key)) b := tx.Bucket([]byte(defaultBoltBucket)).Get([]byte(key))
@ -46,10 +95,13 @@ func (bs *BoltStore) Get(key string, v interface{}) (err error) {
} }
func (bs *BoltStore) Set(key string, v interface{}) error { func (bs *BoltStore) Set(key string, v interface{}) error {
return bs.db.Update(func(tx *bolt.Tx) error { return bs.db.Update(func(tx *bolt.Tx) (err error) {
b, err := bs.Marshal(v) var b []byte
if v != nil {
b, err = bs.Marshal(v)
if err != nil { if err != nil {
return err return
}
} }
return tx.Bucket([]byte(defaultBoltBucket)).Put([]byte(key), b) return tx.Bucket([]byte(defaultBoltBucket)).Put([]byte(key), b)
}) })

View file

@ -2,12 +2,7 @@
package store package store
/*import ( import "io"
"encoding/json"
"fmt"
"io"
"os"
)
type Dumper interface { type Dumper interface {
Dump(io.Writer) error Dump(io.Writer) error
@ -16,85 +11,3 @@ type Dumper interface {
type Restorer interface { type Restorer interface {
Restore(io.Reader) error Restore(io.Reader) error
} }
type DBDump struct {
Pages []DumpPage `json:"pages"`
Snippets []Snippet `json:"snippets"`
Users []User `json:"users"`
}
type DumpPage struct {
Title string `json:"title"`
LinkTitle string `json:"link_title"`
MD string `json:"md"`
Created string `json:"created"`
Updated string `json:"updated"`
Public bool `json:"public"`
}
func verboseLog(prefix, title string, v bool) {
if v {
fmt.Fprintln(os.Stderr, prefix, title)
}
}
func Dump(path string, v bool) (err error) {
bs, err := NewBoltStore()
if err != nil {
return
}
var dump DBDump
//TODO bs.GetAllPages
var pages []*core.Page
for _, p := range pages {
if err != nil {
return
}
verboseLog("dump:", p.Title, v)
dump.Pages = append(dump.Pages, DumpPage{
Title: p.Title,
LinkTitle: p.LinkTitle,
MD: p.MD,
Created: p.Created,
Updated: p.Updated,
Public: p.Public,
})
}
//TODO snippets
//verboseLog("dump:", "snippets", v)
verboseLog("dump:", "users", v)
dump.Users, err = bs.DumpUsers()
if err != nil {
return
}
return json.NewEncoder(os.Stdout).Encode(dump)
}
func Restore(path string, v bool) (err error) {
i, err := NewIndex(path)
if err != nil {
return
}
defer i.Close()
bs, err := NewBoltStore()
if err != nil {
return
}
var dump DBDump
err = json.NewDecoder(os.Stdin).Decode(&dump)
if err != nil {
return
}
for _, art := range dump.Articles {
verboseLog("restore:", art.Title, v)
err = i.Restore(&art)
if err != nil {
return
}
}
//TODO snippets
//verboseLog("restore:", "snippets", v)
verboseLog("restore:", "users", v)
err = bs.RestoreUsers(dump.Users)
return
}*/

View file

@ -8,10 +8,16 @@ func storeErr(i interface{}) error {
return fmt.Errorf("store: %s", i) return fmt.Errorf("store: %s", i)
} }
var ErrKeyNotFound = storeErr("key not found") var (
ErrKeyNotFound = storeErr("key not found")
ErrWriterIsNil = storeErr("writer is nil")
ErrReaderIsNil = storeErr("reader is nil")
)
type Store interface { type Store interface {
Marshaler Marshaler
Dumper
Restorer
Get(string, interface{}) error Get(string, interface{}) error
Set(string, interface{}) error Set(string, interface{}) error
ForEach(func(string, []byte) error) error ForEach(func(string, []byte) error) error

View file

@ -6,10 +6,10 @@ import "sync"
// Blacklist is the blacklisting storage interface // Blacklist is the blacklisting storage interface
type Blacklist interface { type Blacklist interface {
Add(string, int64) Add(string, int64) error
Remove(string) Remove(string) error
Check(string) bool Check(string) bool
Map() BlacklistMap Map() (BlacklistMap, error)
} }
// BlacklistMap is the blacklist map structure // BlacklistMap is the blacklist map structure
@ -28,17 +28,19 @@ func NewMemBlacklist() *MemBlacklist {
} }
// Add adds a new token signature with expiration time to the blacklist // Add adds a new token signature with expiration time to the blacklist
func (mb *MemBlacklist) Add(sig string, exp int64) { func (mb *MemBlacklist) Add(sig string, exp int64) error {
mb.Lock() mb.Lock()
mb.list[sig] = exp mb.list[sig] = exp
mb.Unlock() mb.Unlock()
return nil
} }
// Remove deletes a token signature from the blacklist // Remove deletes a token signature from the blacklist
func (mb *MemBlacklist) Remove(sig string) { func (mb *MemBlacklist) Remove(sig string) error {
mb.Lock() mb.Lock()
delete(mb.list, sig) delete(mb.list, sig)
mb.Unlock() mb.Unlock()
return nil
} }
// Check returns true if a token signature is blacklisted and false otherwise // Check returns true if a token signature is blacklisted and false otherwise
@ -50,7 +52,7 @@ func (mb *MemBlacklist) Check(sig string) (ok bool) {
} }
// Map returns the blacklist in the form of a iterable map structure for cleanup // Map returns the blacklist in the form of a iterable map structure for cleanup
func (mb *MemBlacklist) Map() (list BlacklistMap) { func (mb *MemBlacklist) Map() (list BlacklistMap, err error) {
list = make(BlacklistMap) list = make(BlacklistMap)
mb.RLock() mb.RLock()
for k, v := range mb.list { for k, v := range mb.list {

View file

@ -143,8 +143,7 @@ func (jwt *JWT) Invalidate(t *Token) error {
if !hmac.Equal(jwt.sum(t.Data(), h), t.RawSig()) { if !hmac.Equal(jwt.sum(t.Data(), h), t.RawSig()) {
return ErrInvalid return ErrInvalid
} }
jwt.blacklist.Add(t.Sig(), exp) return jwt.blacklist.Add(t.Sig(), exp)
return nil
} }
// blacklisted checks if a token is blacklisted // blacklisted checks if a token is blacklisted
@ -169,7 +168,11 @@ func (jwt *JWT) clean() {
return return
} }
now := Now() now := Now()
for k, v := range jwt.blacklist.Map() { m, err := jwt.blacklist.Map()
if err != nil {
continue
}
for k, v := range m {
if now > v { if now > v {
jwt.blacklist.Remove(k) jwt.blacklist.Remove(k)
} }

2
vendor/modules.txt vendored
View file

@ -1,6 +1,6 @@
# git.giftfish.de/ston1th/godrop/v2 v2.0.1 # git.giftfish.de/ston1th/godrop/v2 v2.0.1
git.giftfish.de/ston1th/godrop/v2 git.giftfish.de/ston1th/godrop/v2
# git.giftfish.de/ston1th/jwt/v3 v3.0.0 # git.giftfish.de/ston1th/jwt/v3 v3.1.0
git.giftfish.de/ston1th/jwt/v3 git.giftfish.de/ston1th/jwt/v3
# github.com/RoaringBitmap/roaring v0.4.16 # github.com/RoaringBitmap/roaring v0.4.16
github.com/RoaringBitmap/roaring github.com/RoaringBitmap/roaring