refactor to combine admin and key store
This commit is contained in:
@@ -24,7 +24,6 @@ import (
|
||||
"github.com/umputun/remark/backend/app/store/admin"
|
||||
"github.com/umputun/remark/backend/app/store/avatar"
|
||||
"github.com/umputun/remark/backend/app/store/engine"
|
||||
"github.com/umputun/remark/backend/app/store/keys"
|
||||
"github.com/umputun/remark/backend/app/store/service"
|
||||
)
|
||||
|
||||
@@ -34,7 +33,6 @@ type ServerCommand struct {
|
||||
Avatar AvatarGroup `group:"avatar" namespace:"avatar" env-namespace:"AVATAR"`
|
||||
Cache CacheGroup `group:"cache" namespace:"cache" env-namespace:"CACHE"`
|
||||
Mongo MongoGroup `group:"mongo" namespace:"mongo" env-namespace:"MONGO"`
|
||||
Key KeyGroup `group:"key" namespace:"key" env-namespace:"KEY"`
|
||||
Admin AdminGroup `group:"admin" namespace:"admin" env-namespace:"ADMIN"`
|
||||
|
||||
Sites []string `long:"site" env:"SITE" default:"remark" description:"site names" env-delim:","`
|
||||
@@ -105,11 +103,6 @@ type MongoGroup struct {
|
||||
DB string `long:"db" env:"DB" default:"remark42" description:"mongo database"`
|
||||
}
|
||||
|
||||
// KeyGroup defines options group for key params
|
||||
type KeyGroup struct {
|
||||
Type string `long:"type" env:"TYPE" description:"type of key store" choice:"shared" choice:"mongo" default:"shared"`
|
||||
}
|
||||
|
||||
// AdminGroup defines options group for admin params
|
||||
type AdminGroup struct {
|
||||
Type string `long:"type" env:"TYPE" description:"type of admin store" choice:"shared" choice:"mongo" default:"shared"`
|
||||
@@ -174,12 +167,6 @@ func (s *ServerCommand) newServerApp() (*serverApp, error) {
|
||||
return nil, errors.Wrap(err, "failed to make data store engine")
|
||||
}
|
||||
|
||||
keyStore, err := s.makeKeyStore()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to make key store")
|
||||
|
||||
}
|
||||
|
||||
adminStore, err := s.makeAdminStore()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to make admin store")
|
||||
@@ -188,7 +175,6 @@ func (s *ServerCommand) newServerApp() (*serverApp, error) {
|
||||
dataService := &service.DataStore{
|
||||
Interface: storeEngine,
|
||||
EditDuration: s.EditDuration,
|
||||
KeyStore: keyStore,
|
||||
AdminStore: adminStore,
|
||||
MaxCommentSize: s.MaxCommentSize,
|
||||
}
|
||||
@@ -199,7 +185,7 @@ func (s *ServerCommand) newServerApp() (*serverApp, error) {
|
||||
}
|
||||
|
||||
// token TTL is 5 minutes, inactivity interval 7+ days by default
|
||||
jwtService := auth.NewJWT(keyStore, strings.HasPrefix(s.RemarkURL, "https://"), s.Auth.TTL.JWT, s.Auth.TTL.Cookie)
|
||||
jwtService := auth.NewJWT(adminStore, strings.HasPrefix(s.RemarkURL, "https://"), s.Auth.TTL.JWT, s.Auth.TTL.Cookie)
|
||||
|
||||
avatarStore, err := s.makeAvatarStore()
|
||||
if err != nil {
|
||||
@@ -219,7 +205,7 @@ func (s *ServerCommand) newServerApp() (*serverApp, error) {
|
||||
DisqusImporter: &migrator.Disqus{DataStore: dataService},
|
||||
WordPressImporter: &migrator.WordPress{DataStore: dataService},
|
||||
NativeExported: &migrator.Remark{DataStore: dataService},
|
||||
KeyStore: keyStore,
|
||||
KeyStore: adminStore,
|
||||
}
|
||||
|
||||
authProviders := s.makeAuthProviders(jwtService, avatarProxy, dataService)
|
||||
@@ -243,7 +229,6 @@ func (s *ServerCommand) newServerApp() (*serverApp, error) {
|
||||
Providers: authProviders,
|
||||
DevPasswd: s.DevPasswd,
|
||||
PermissionChecker: dataService,
|
||||
KeyStore: keyStore,
|
||||
},
|
||||
Cache: loadingCache,
|
||||
}
|
||||
@@ -359,24 +344,6 @@ func (s *ServerCommand) makeAvatarStore() (avatar.Store, error) {
|
||||
return nil, errors.Errorf("unsupported avatar store type %s", s.Avatar.Type)
|
||||
}
|
||||
|
||||
func (s *ServerCommand) makeKeyStore() (keys.Store, error) {
|
||||
log.Printf("[INFO] make key store, type=%s", s.Admin.Type)
|
||||
|
||||
switch s.Key.Type {
|
||||
case "shared":
|
||||
return keys.NewStaticStore(s.SharedSecret), nil
|
||||
case "mongo":
|
||||
mgServer, e := s.makeMongo()
|
||||
if e != nil {
|
||||
return nil, errors.Wrap(e, "failed to create mongo server")
|
||||
}
|
||||
conn := mongo.NewConnection(mgServer, s.Mongo.DB, "admin")
|
||||
return keys.NewMongoStore(conn), nil
|
||||
default:
|
||||
return nil, errors.Errorf("unsupported key store type %s", s.Key.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServerCommand) makeAdminStore() (admin.Store, error) {
|
||||
log.Printf("[INFO] make admin store, type=%s", s.Admin.Type)
|
||||
|
||||
@@ -387,7 +354,7 @@ func (s *ServerCommand) makeAdminStore() (admin.Store, error) {
|
||||
s.Admin.Shared.Email = "admin@" + u.Host
|
||||
}
|
||||
}
|
||||
return admin.NewStaticStore(s.Admin.Shared.Admins, s.Admin.Shared.Email), nil
|
||||
return admin.NewStaticStore(s.SharedSecret, s.Admin.Shared.Admins, s.Admin.Shared.Email), nil
|
||||
case "mongo":
|
||||
mgServer, e := s.makeMongo()
|
||||
if e != nil {
|
||||
@@ -396,7 +363,7 @@ func (s *ServerCommand) makeAdminStore() (admin.Store, error) {
|
||||
conn := mongo.NewConnection(mgServer, s.Mongo.DB, "admin")
|
||||
return admin.NewMongoStore(conn), nil
|
||||
default:
|
||||
return nil, errors.Errorf("unsupported admin store type %s", s.Key.Type)
|
||||
return nil, errors.Errorf("unsupported admin store type %s", s.Admin.Type)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,8 +91,7 @@ func TestServerApp_WithMongo(t *testing.T) {
|
||||
// prepare options
|
||||
p := flags.NewParser(&opts, flags.Default)
|
||||
_, err := p.ParseArgs([]string{"--dev-passwd=password", "--cache.type=mongo", "--store.type=mongo",
|
||||
"--avatar.type=mongo", "--mongo.url=" + mongoURL, "--mongo.db=test_remark", "--port=12345",
|
||||
"--key.type=mongo", "--admin.type=mongo"})
|
||||
"--avatar.type=mongo", "--mongo.url=" + mongoURL, "--mongo.db=test_remark", "--port=12345", "--admin.type=mongo"})
|
||||
require.Nil(t, err)
|
||||
opts.Auth.Github.CSEC, opts.Auth.Github.CID = "csec", "cid"
|
||||
opts.BackupLocation = "/tmp"
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"github.com/coreos/bbolt"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/umputun/remark/backend/app/store"
|
||||
"github.com/umputun/remark/backend/app/store/admin"
|
||||
"github.com/umputun/remark/backend/app/store/engine"
|
||||
"github.com/umputun/remark/backend/app/store/keys"
|
||||
"github.com/umputun/remark/backend/app/store/service"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -20,7 +20,7 @@ func TestDisqus_Import(t *testing.T) {
|
||||
defer os.Remove("/tmp/remark-test.db")
|
||||
b, err := engine.NewBoltDB(bolt.Options{}, engine.BoltSite{FileName: "/tmp/remark-test.db", SiteID: "test"})
|
||||
require.Nil(t, err, "create store")
|
||||
dataStore := service.DataStore{Interface: b, KeyStore: keys.NewStaticStore("12345")}
|
||||
dataStore := service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}
|
||||
d := Disqus{DataStore: &dataStore}
|
||||
size, err := d.Import(strings.NewReader(xmlTestDisqus), "test")
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
"github.com/coreos/bbolt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/umputun/remark/backend/app/store/keys"
|
||||
"github.com/umputun/remark/backend/app/store/service"
|
||||
|
||||
"github.com/umputun/remark/backend/app/store/admin"
|
||||
"github.com/umputun/remark/backend/app/store/engine"
|
||||
"github.com/umputun/remark/backend/app/store/service"
|
||||
)
|
||||
|
||||
func TestMigrator_ImportDisqus(t *testing.T) {
|
||||
@@ -25,7 +25,7 @@ func TestMigrator_ImportDisqus(t *testing.T) {
|
||||
|
||||
b, err := engine.NewBoltDB(bolt.Options{}, engine.BoltSite{FileName: "/tmp/remark-test.db", SiteID: "test"})
|
||||
require.Nil(t, err, "create store")
|
||||
dataStore := &service.DataStore{Interface: b, KeyStore: keys.NewStaticStore("12345")}
|
||||
dataStore := &service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}
|
||||
size, err := ImportComments(ImportParams{
|
||||
DataStore: dataStore,
|
||||
InputFile: "/tmp/disqus-test.xml",
|
||||
@@ -51,7 +51,7 @@ func TestMigrator_ImportWordPress(t *testing.T) {
|
||||
|
||||
b, err := engine.NewBoltDB(bolt.Options{}, engine.BoltSite{FileName: "/tmp/remark-test.db", SiteID: "test"})
|
||||
require.Nil(t, err, "create store")
|
||||
dataStore := &service.DataStore{Interface: b, KeyStore: keys.NewStaticStore("12345")}
|
||||
dataStore := &service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}
|
||||
size, err := ImportComments(ImportParams{
|
||||
DataStore: dataStore,
|
||||
InputFile: "/tmp/wordpress-test.xml",
|
||||
@@ -80,7 +80,7 @@ func TestMigrator_ImportRemark(t *testing.T) {
|
||||
|
||||
b, err := engine.NewBoltDB(bolt.Options{}, engine.BoltSite{FileName: "/tmp/remark-test.db", SiteID: "radio-t"})
|
||||
require.Nil(t, err, "create store")
|
||||
dataStore := &service.DataStore{Interface: b, KeyStore: keys.NewStaticStore("12345")}
|
||||
dataStore := &service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}
|
||||
|
||||
size, err := ImportComments(ImportParams{
|
||||
DataStore: dataStore,
|
||||
|
||||
@@ -10,9 +10,10 @@ import (
|
||||
|
||||
"github.com/coreos/bbolt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/umputun/remark/backend/app/store"
|
||||
"github.com/umputun/remark/backend/app/store/admin"
|
||||
"github.com/umputun/remark/backend/app/store/engine"
|
||||
"github.com/umputun/remark/backend/app/store/keys"
|
||||
"github.com/umputun/remark/backend/app/store/service"
|
||||
)
|
||||
|
||||
@@ -50,7 +51,7 @@ func TestRemark_Import(t *testing.T) {
|
||||
os.Remove(testDb)
|
||||
b, err := engine.NewBoltDB(bolt.Options{}, engine.BoltSite{SiteID: "radio-t", FileName: testDb})
|
||||
assert.Nil(t, err)
|
||||
r := Remark{DataStore: &service.DataStore{Interface: b, KeyStore: keys.NewStaticStore("12345")}}
|
||||
r := Remark{DataStore: &service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}}
|
||||
size, err := r.Import(buf, "radio-t")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, size)
|
||||
@@ -78,7 +79,7 @@ func TestRemark_ImportManyWithError(t *testing.T) {
|
||||
os.Remove(testDb)
|
||||
b, err := engine.NewBoltDB(bolt.Options{}, engine.BoltSite{SiteID: "radio-t", FileName: testDb})
|
||||
assert.Nil(t, err)
|
||||
r := Remark{DataStore: &service.DataStore{Interface: b, KeyStore: keys.NewStaticStore("12345")}}
|
||||
r := Remark{DataStore: &service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}}
|
||||
n, err := r.Import(buf, "radio-t")
|
||||
assert.EqualError(t, err, "failed to save 2 comments")
|
||||
assert.Equal(t, 1200, n)
|
||||
@@ -94,7 +95,7 @@ func prep(t *testing.T) *service.DataStore {
|
||||
boltStore, err := engine.NewBoltDB(bolt.Options{}, engine.BoltSite{SiteID: "radio-t", FileName: testDb})
|
||||
assert.Nil(t, err)
|
||||
|
||||
b := &service.DataStore{Interface: boltStore, KeyStore: keys.NewStaticStore("12345")}
|
||||
b := &service.DataStore{Interface: boltStore, AdminStore: admin.NewStaticStore("12345", []string{}, "")}
|
||||
|
||||
comment := store.Comment{
|
||||
ID: "efbc17f177ee1a1c0ee6e1e025749966ec071adc",
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
|
||||
"github.com/coreos/bbolt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/umputun/remark/backend/app/store/keys"
|
||||
|
||||
"github.com/umputun/remark/backend/app/store"
|
||||
"github.com/umputun/remark/backend/app/store/admin"
|
||||
"github.com/umputun/remark/backend/app/store/engine"
|
||||
"github.com/umputun/remark/backend/app/store/service"
|
||||
)
|
||||
@@ -21,7 +21,7 @@ func TestWordPress_Import(t *testing.T) {
|
||||
b, err := engine.NewBoltDB(bolt.Options{}, engine.BoltSite{FileName: "/tmp/remark-test.db", SiteID: siteID})
|
||||
assert.Nil(t, err, "create store")
|
||||
|
||||
dataStore := service.DataStore{Interface: b, KeyStore: keys.NewStaticStore("12345")}
|
||||
dataStore := service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}
|
||||
wp := WordPress{DataStore: &dataStore}
|
||||
size, err := wp.Import(strings.NewReader(xmlTestWP), siteID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/umputun/remark/backend/app/migrator"
|
||||
"github.com/umputun/remark/backend/app/rest"
|
||||
"github.com/umputun/remark/backend/app/rest/cache"
|
||||
"github.com/umputun/remark/backend/app/store/keys"
|
||||
)
|
||||
|
||||
// Migrator rest with import and export controllers
|
||||
@@ -24,7 +23,12 @@ type Migrator struct {
|
||||
DisqusImporter migrator.Importer
|
||||
WordPressImporter migrator.Importer
|
||||
NativeExported migrator.Exporter
|
||||
KeyStore keys.Store
|
||||
KeyStore KeyStore
|
||||
}
|
||||
|
||||
// KeyStore defines sub-interface for consumers needed just a key
|
||||
type KeyStore interface {
|
||||
Key(siteID string) (key string, err error)
|
||||
}
|
||||
|
||||
func (m *Migrator) withRoutes(router chi.Router) chi.Router {
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"github.com/umputun/remark/backend/app/store"
|
||||
adminstore "github.com/umputun/remark/backend/app/store/admin"
|
||||
"github.com/umputun/remark/backend/app/store/engine"
|
||||
"github.com/umputun/remark/backend/app/store/keys"
|
||||
"github.com/umputun/remark/backend/app/store/service"
|
||||
)
|
||||
|
||||
@@ -153,29 +152,28 @@ func TestMigrator_Export(t *testing.T) {
|
||||
func prepImportSrv(t *testing.T) (svc *Migrator, ds *service.DataStore, ts *httptest.Server) {
|
||||
b, err := engine.NewBoltDB(bolt.Options{}, engine.BoltSite{FileName: testDb, SiteID: "radio-t"})
|
||||
require.Nil(t, err)
|
||||
ks := keys.NewStaticStore("123456")
|
||||
dataStore := &service.DataStore{Interface: b, KeyStore: ks}
|
||||
adminStore := adminstore.NewStaticStore("123456", []string{"a1", "a2"}, "admin@remark-42.com")
|
||||
dataStore := &service.DataStore{Interface: b, AdminStore: adminStore}
|
||||
svc = &Migrator{
|
||||
DisqusImporter: &migrator.Disqus{DataStore: dataStore},
|
||||
WordPressImporter: &migrator.WordPress{DataStore: dataStore},
|
||||
NativeImporter: &migrator.Remark{DataStore: dataStore},
|
||||
NativeExported: &migrator.Remark{DataStore: dataStore},
|
||||
Cache: &cache.Nop{},
|
||||
KeyStore: ks,
|
||||
KeyStore: adminStore,
|
||||
}
|
||||
a := auth.Authenticator{
|
||||
DevPasswd: "password",
|
||||
Providers: nil,
|
||||
AdminStore: adminstore.NewStaticStore([]string{"a1", "a2"}, "admin@remark-42.com"),
|
||||
JWTService: auth.NewJWT(keys.NewStaticStore("123456"), false, time.Minute, time.Hour),
|
||||
KeyStore: ks,
|
||||
AdminStore: adminStore,
|
||||
JWTService: auth.NewJWT(adminStore, false, time.Minute, time.Hour),
|
||||
}
|
||||
routes := svc.withRoutes(chi.NewRouter().With(a.Auth(true)).With(a.AdminOnly))
|
||||
ts = httptest.NewServer(routes)
|
||||
return svc, dataStore, ts
|
||||
}
|
||||
|
||||
func cleanupImportSrv(m *Migrator, ts *httptest.Server) {
|
||||
func cleanupImportSrv(_ *Migrator, ts *httptest.Server) {
|
||||
ts.Close()
|
||||
os.Remove(testDb)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
adminstore "github.com/umputun/remark/backend/app/store/admin"
|
||||
"github.com/umputun/remark/backend/app/store/avatar"
|
||||
"github.com/umputun/remark/backend/app/store/engine"
|
||||
"github.com/umputun/remark/backend/app/store/keys"
|
||||
"github.com/umputun/remark/backend/app/store/service"
|
||||
)
|
||||
|
||||
@@ -92,13 +91,12 @@ func prep(t *testing.T) (srv *Rest, ts *httptest.Server) {
|
||||
b, err := engine.NewBoltDB(bolt.Options{}, engine.BoltSite{FileName: testDb, SiteID: "radio-t"})
|
||||
require.Nil(t, err)
|
||||
|
||||
adminStore := adminstore.NewStaticStore([]string{"a1", "a2"}, "admin@remark-42.com")
|
||||
adminStore := adminstore.NewStaticStore("123456", []string{"a1", "a2"}, "admin@remark-42.com")
|
||||
|
||||
dataStore := &service.DataStore{
|
||||
Interface: b,
|
||||
EditDuration: 5 * time.Minute,
|
||||
MaxCommentSize: 4000,
|
||||
KeyStore: keys.NewStaticStore("123456"),
|
||||
AdminStore: adminStore,
|
||||
}
|
||||
srv = &Rest{
|
||||
@@ -107,7 +105,7 @@ func prep(t *testing.T) (srv *Rest, ts *httptest.Server) {
|
||||
DevPasswd: "password",
|
||||
Providers: nil,
|
||||
AdminStore: adminStore,
|
||||
JWTService: auth.NewJWT(keys.NewStaticStore("123456"), false, time.Minute, time.Hour),
|
||||
JWTService: auth.NewJWT(adminStore, false, time.Minute, time.Hour),
|
||||
},
|
||||
Cache: &cache.Nop{},
|
||||
WebRoot: "/tmp",
|
||||
@@ -122,7 +120,7 @@ func prep(t *testing.T) (srv *Rest, ts *httptest.Server) {
|
||||
NativeImporter: &migrator.Remark{DataStore: dataStore},
|
||||
NativeExported: &migrator.Remark{DataStore: dataStore},
|
||||
Cache: &cache.Nop{},
|
||||
KeyStore: keys.NewStaticStore("123456"),
|
||||
KeyStore: adminStore,
|
||||
},
|
||||
}
|
||||
srv.ScoreThresholds.Low, srv.ScoreThresholds.Critical = -5, -10
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/umputun/remark/backend/app/rest"
|
||||
"github.com/umputun/remark/backend/app/store"
|
||||
"github.com/umputun/remark/backend/app/store/admin"
|
||||
"github.com/umputun/remark/backend/app/store/keys"
|
||||
)
|
||||
|
||||
// Authenticator is top level auth object providing middlewares
|
||||
@@ -18,7 +17,6 @@ type Authenticator struct {
|
||||
JWTService *JWT
|
||||
Providers []Provider
|
||||
AdminStore admin.Store
|
||||
KeyStore keys.Store
|
||||
DevPasswd string
|
||||
PermissionChecker PermissionChecker
|
||||
}
|
||||
@@ -114,14 +112,14 @@ func (a *Authenticator) Auth(reqAuth bool) func(http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
func (a *Authenticator) checkSecretKey(r *http.Request) bool {
|
||||
if a.KeyStore == nil {
|
||||
if a.AdminStore == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
siteID := r.URL.Query().Get("site")
|
||||
secret := r.URL.Query().Get("secret")
|
||||
|
||||
skey, err := a.KeyStore.Get(siteID)
|
||||
skey, err := a.AdminStore.Key(siteID)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/umputun/remark/backend/app/store/keys"
|
||||
"github.com/umputun/remark/backend/app/store/admin"
|
||||
)
|
||||
|
||||
var testJwtUserBlocked = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjI3ODkxOTE4MjIsImp0aSI6InJhbmRvbSBpZCIsImlzcyI6InJlbWFyazQyIiwibmJmIjoxNTI2ODg0MjIyLCJ1c2VyIjp7Im5hbWUiOiJuYW1lMSIsImlkIjoiaWQxIiwicGljdHVyZSI6IiIsImFkbWluIjpmYWxzZSwiYmxvY2siOnRydWV9LCJzdGF0ZSI6IjEyMzQ1NiIsImZyb20iOiJmcm9tIn0.6P_OwGf8CUJRtvNSlW20GmaMb5pFvCNemP94fHCqb5Q"
|
||||
@@ -19,7 +19,7 @@ var testJwtUserBlocked = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjI3ODkxO
|
||||
var testJwtDeleteMe = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjI3ODkxOTE4MjIsImp0aSI6InJhbmRvbSBpZCIsImlzcyI6InJlbWFyazQyIiwibmJmIjoxNTI2ODg0MjIyLCJ1c2VyIjp7Im5hbWUiOiJuYW1lMSIsImlkIjoiaWQxIiwicGljdHVyZSI6IiIsImFkbWluIjpmYWxzZSwiYmxvY2siOmZhbHNlfSwiZmxhZ3MiOnsiZGVsZXRlbWUiOnRydWV9fQ.SLh1QpFytWZqcT99VgcdAOtgFKhvpKCcZwqWTvAd63g"
|
||||
|
||||
func TestAuthJWTCookie(t *testing.T) {
|
||||
a := Authenticator{DevPasswd: "123456", JWTService: NewJWT(keys.NewStaticStore("xyz 12345"), false, time.Hour, time.Hour),
|
||||
a := Authenticator{DevPasswd: "123456", JWTService: NewJWT(admin.NewStaticKeyStore("xyz 12345"), false, time.Hour, time.Hour),
|
||||
PermissionChecker: &mockUserPermissions{}}
|
||||
router := chi.NewRouter()
|
||||
router.With(a.Auth(true)).Get("/auth", func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -57,7 +57,7 @@ func TestAuthJWTCookie(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthJWTHeader(t *testing.T) {
|
||||
a := Authenticator{DevPasswd: "123456", JWTService: NewJWT(keys.NewStaticStore("xyz 12345"), false, time.Hour, time.Hour)}
|
||||
a := Authenticator{DevPasswd: "123456", JWTService: NewJWT(admin.NewStaticKeyStore("xyz 12345"), false, time.Hour, time.Hour)}
|
||||
router := chi.NewRouter()
|
||||
router.With(a.Auth(true)).Get("/auth", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(201)
|
||||
@@ -84,7 +84,7 @@ func TestAuthJWTHeader(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthJWtBlocked(t *testing.T) {
|
||||
a := Authenticator{DevPasswd: "123456", JWTService: NewJWT(keys.NewStaticStore("xyz 12345"), false, time.Hour, time.Hour)}
|
||||
a := Authenticator{DevPasswd: "123456", JWTService: NewJWT(admin.NewStaticKeyStore("xyz 12345"), false, time.Hour, time.Hour)}
|
||||
router := chi.NewRouter()
|
||||
router.With(a.Auth(true)).Get("/auth", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(201)
|
||||
@@ -104,7 +104,7 @@ func TestAuthJWtBlocked(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthJWtFlags(t *testing.T) {
|
||||
a := Authenticator{DevPasswd: "123456", JWTService: NewJWT(keys.NewStaticStore("xyz 12345"), false, time.Hour, time.Hour)}
|
||||
a := Authenticator{DevPasswd: "123456", JWTService: NewJWT(admin.NewStaticKeyStore("xyz 12345"), false, time.Hour, time.Hour)}
|
||||
router := chi.NewRouter()
|
||||
router.With(a.Auth(true)).Get("/auth", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(201)
|
||||
@@ -213,7 +213,7 @@ func TestAdminRequired(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthWithSecret(t *testing.T) {
|
||||
a := Authenticator{DevPasswd: "123456", KeyStore: keys.NewStaticStore("secretkey")}
|
||||
a := Authenticator{DevPasswd: "123456", AdminStore: admin.NewStaticKeyStore("secretkey")}
|
||||
router := chi.NewRouter()
|
||||
router.With(a.Auth(true), a.AdminOnly).Get("/auth", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(201)
|
||||
|
||||
@@ -13,12 +13,12 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/umputun/remark/backend/app/store"
|
||||
"github.com/umputun/remark/backend/app/store/keys"
|
||||
"github.com/umputun/remark/backend/app/store/admin"
|
||||
)
|
||||
|
||||
func TestDevProvider(t *testing.T) {
|
||||
params := Params{RemarkURL: "http://127.0.0.1:8080", Cid: "cid", Csecret: "csecret",
|
||||
JwtService: NewJWT(keys.NewStaticStore("12345"), false, time.Hour, time.Hour*24*31),
|
||||
JwtService: NewJWT(admin.NewStaticKeyStore("12345"), false, time.Hour, time.Hour*24*31),
|
||||
PermissionChecker: &mockUserPermissions{admin: "dev_user"},
|
||||
}
|
||||
srv := DevAuthServer{Provider: NewDev(params), nonInteractive: true, username: "dev_user"}
|
||||
|
||||
@@ -8,13 +8,12 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/umputun/remark/backend/app/store"
|
||||
"github.com/umputun/remark/backend/app/store/keys"
|
||||
)
|
||||
|
||||
// JWT wraps jwt operations
|
||||
// supports both header and cookie jwt
|
||||
type JWT struct {
|
||||
keyStore keys.Store
|
||||
keyStore KeyStore
|
||||
secureCookies bool
|
||||
tokenDuration time.Duration
|
||||
cookieDuration time.Duration
|
||||
@@ -43,8 +42,13 @@ const jwtHeaderKey = "X-JWT"
|
||||
const xsrfCookieName = "XSRF-TOKEN"
|
||||
const xsrfHeaderKey = "X-XSRF-TOKEN"
|
||||
|
||||
// KeyStore defines sub-interface for consumers needed just a key
|
||||
type KeyStore interface {
|
||||
Key(siteID string) (key string, err error)
|
||||
}
|
||||
|
||||
// NewJWT makes JWT service
|
||||
func NewJWT(keyStore keys.Store, secureCookies bool, tokenDuration time.Duration, cookieDuration time.Duration) *JWT {
|
||||
func NewJWT(keyStore KeyStore, secureCookies bool, tokenDuration time.Duration, cookieDuration time.Duration) *JWT {
|
||||
res := JWT{
|
||||
keyStore: keyStore,
|
||||
secureCookies: secureCookies,
|
||||
@@ -58,7 +62,7 @@ func NewJWT(keyStore keys.Store, secureCookies bool, tokenDuration time.Duration
|
||||
func (j *JWT) Token(claims *CustomClaims) (string, error) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
secret, err := j.keyStore.Get(claims.SiteID)
|
||||
secret, err := j.keyStore.Key(claims.SiteID)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "can't get secret")
|
||||
}
|
||||
@@ -96,7 +100,7 @@ func (j *JWT) Parse(tokenString string) (*CustomClaims, error) {
|
||||
return nil, errors.Wrap(err, "failed to get siteID from jwt token")
|
||||
}
|
||||
|
||||
secret, err := j.keyStore.Get(siteID)
|
||||
secret, err := j.keyStore.Key(siteID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't get secret")
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/umputun/remark/backend/app/store/keys"
|
||||
"github.com/umputun/remark/backend/app/store/admin"
|
||||
|
||||
"github.com/umputun/remark/backend/app/store"
|
||||
)
|
||||
@@ -30,7 +30,7 @@ var testJwtBadSign = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjI3ODkxOTE4M
|
||||
var days31 = time.Hour * 24 * 31
|
||||
|
||||
func TestJWT_Token(t *testing.T) {
|
||||
j := NewJWT(keys.NewStaticStore("xyz 12345"), false, time.Hour, days31)
|
||||
j := NewJWT(admin.NewStaticKeyStore("xyz 12345"), false, time.Hour, days31)
|
||||
|
||||
claims := &CustomClaims{
|
||||
State: "123456",
|
||||
@@ -53,7 +53,7 @@ func TestJWT_Token(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJWT_Parse(t *testing.T) {
|
||||
j := NewJWT(keys.NewStaticStore("xyz 12345"), false, time.Hour, days31)
|
||||
j := NewJWT(admin.NewStaticKeyStore("xyz 12345"), false, time.Hour, days31)
|
||||
claims, err := j.Parse(testJwtValid)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, j.IsExpired(claims))
|
||||
@@ -71,7 +71,7 @@ func TestJWT_Parse(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJWT_Set(t *testing.T) {
|
||||
j := NewJWT(keys.NewStaticStore("xyz 12345"), false, time.Hour, days31)
|
||||
j := NewJWT(admin.NewStaticKeyStore("xyz 12345"), false, time.Hour, days31)
|
||||
|
||||
claims := &CustomClaims{
|
||||
State: "123456",
|
||||
@@ -116,7 +116,7 @@ func TestJWT_Set(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJWT_GetFromHeader(t *testing.T) {
|
||||
j := NewJWT(keys.NewStaticStore("xyz 12345"), false, time.Hour, days31)
|
||||
j := NewJWT(admin.NewStaticKeyStore("xyz 12345"), false, time.Hour, days31)
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req.Header.Add(jwtHeaderKey, testJwtValid)
|
||||
@@ -141,7 +141,7 @@ func TestJWT_GetFromHeader(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJWT_SetAndGetWithCookies(t *testing.T) {
|
||||
j := NewJWT(keys.NewStaticStore("xyz 12345"), false, time.Hour, days31)
|
||||
j := NewJWT(admin.NewStaticKeyStore("xyz 12345"), false, time.Hour, days31)
|
||||
|
||||
claims := &CustomClaims{
|
||||
State: "123456",
|
||||
@@ -183,7 +183,7 @@ func TestJWT_SetAndGetWithCookies(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJWT_SetAndGetWithXsrfMismatch(t *testing.T) {
|
||||
j := NewJWT(keys.NewStaticStore("xyz 12345"), false, time.Hour, days31)
|
||||
j := NewJWT(admin.NewStaticKeyStore("xyz 12345"), false, time.Hour, days31)
|
||||
|
||||
claims := &CustomClaims{
|
||||
State: "123456",
|
||||
@@ -220,7 +220,7 @@ func TestJWT_SetAndGetWithXsrfMismatch(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJWT_SetAndGetWithCookiesExpired(t *testing.T) {
|
||||
j := NewJWT(keys.NewStaticStore("xyz 12345"), false, time.Hour, days31)
|
||||
j := NewJWT(admin.NewStaticKeyStore("xyz 12345"), false, time.Hour, days31)
|
||||
|
||||
claims := &CustomClaims{
|
||||
State: "123456",
|
||||
|
||||
@@ -13,10 +13,10 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/umputun/remark/backend/app/store/keys"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/umputun/remark/backend/app/store"
|
||||
"github.com/umputun/remark/backend/app/store/admin"
|
||||
)
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
@@ -54,7 +54,7 @@ func TestLogin(t *testing.T) {
|
||||
Admin: false, Blocked: true, IP: ""}, u)
|
||||
|
||||
token := resp.Cookies()[0].Value
|
||||
jwtSvc := NewJWT(keys.NewStaticStore("12345"), false, time.Hour, time.Hour*24*31)
|
||||
jwtSvc := NewJWT(admin.NewStaticKeyStore("12345"), false, time.Hour, time.Hour*24*31)
|
||||
|
||||
claims, err := jwtSvc.Parse(token)
|
||||
require.NoError(t, err)
|
||||
@@ -103,7 +103,7 @@ func TestLoginSessionOnly(t *testing.T) {
|
||||
req.AddCookie(resp.Cookies()[1])
|
||||
req.Header.Add("X-XSRF-TOKEN", resp.Cookies()[1].Value)
|
||||
|
||||
jwtService := NewJWT(keys.NewStaticStore("12345"), false, time.Hour, time.Hour)
|
||||
jwtService := NewJWT(admin.NewStaticKeyStore("12345"), false, time.Hour, time.Hour)
|
||||
res, err := jwtService.Get(req)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, true, res.SessionOnly)
|
||||
@@ -169,7 +169,7 @@ func mockProvider(t *testing.T, loginPort, authPort int) (*http.Server, *http.Se
|
||||
}
|
||||
|
||||
params := Params{RemarkURL: "url", Cid: "cid", Csecret: "csecret",
|
||||
JwtService: NewJWT(keys.NewStaticStore("12345"), false, time.Hour, time.Hour*24*31),
|
||||
JwtService: NewJWT(admin.NewStaticKeyStore("12345"), false, time.Hour, time.Hour*24*31),
|
||||
// AvatarProxy: &proxy.Avatar{Store: &mockAvatarStore, RoutePath: "/v1/avatar"},
|
||||
PermissionChecker: &mockUserPermissions{admin: "mock_myuser2", verified: "mock_myuser2", blocked: "mock_myuser1"},
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// Package admin defines and implements store for admin-level data like secret key, list of admins and so on
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"github.com/go-pkgz/mongo"
|
||||
)
|
||||
|
||||
// Store defines interface returning admins info for given site
|
||||
type Store interface {
|
||||
Key(siteID string) (key string, err error)
|
||||
Admins(siteID string) (ids []string)
|
||||
Email(siteID string) (email string)
|
||||
}
|
||||
@@ -18,12 +18,26 @@ type Store interface {
|
||||
type StaticStore struct {
|
||||
admins []string
|
||||
email string
|
||||
key string
|
||||
}
|
||||
|
||||
// Key returns static key for all sites, allows empty site
|
||||
func (s *StaticStore) Key(siteID string) (key string, err error) {
|
||||
if s.key == "" {
|
||||
return "", errors.New("empty key for static key store")
|
||||
}
|
||||
return s.key, nil
|
||||
}
|
||||
|
||||
// NewStaticStore makes StaticStore instance with given key
|
||||
func NewStaticStore(admins []string, email string) *StaticStore {
|
||||
func NewStaticStore(key string, admins []string, email string) *StaticStore {
|
||||
log.Printf("[DEBUG] admin users %+v, email %s", admins, email)
|
||||
return &StaticStore{admins: admins, email: email}
|
||||
return &StaticStore{key: key, admins: admins, email: email}
|
||||
}
|
||||
|
||||
// NewStaticKeyStore is a shortcut for making StaticStore for key consumers only
|
||||
func NewStaticKeyStore(key string) *StaticStore {
|
||||
return &StaticStore{key: key, admins: []string{}, email: ""}
|
||||
}
|
||||
|
||||
// Admins returns static list of admin's ids, the same for all sites
|
||||
@@ -35,46 +49,3 @@ func (s *StaticStore) Admins(string) (ids []string) {
|
||||
func (s *StaticStore) Email(string) (email string) {
|
||||
return s.email
|
||||
}
|
||||
|
||||
// MongoStore implements admin.Store with mongo backend
|
||||
type MongoStore struct {
|
||||
connection *mongo.Connection
|
||||
}
|
||||
|
||||
// NewMongoStore makes admin Store for mongo's connection
|
||||
func NewMongoStore(conn *mongo.Connection) *MongoStore {
|
||||
log.Printf("[DEBUG] make mongo admin store with %+v", conn)
|
||||
return &MongoStore{connection: conn}
|
||||
}
|
||||
|
||||
// Admins executes find by siteID and returns admins ids
|
||||
func (m *MongoStore) Admins(siteID string) (ids []string) {
|
||||
resp := struct {
|
||||
SiteID string `bson:"site"`
|
||||
IDs []string `bson:"admin_ids"`
|
||||
Email string `bson:"admin_email"`
|
||||
}{}
|
||||
err := m.connection.WithCollection(func(coll *mgo.Collection) error {
|
||||
return coll.Find(bson.M{"site": siteID}).One(&resp)
|
||||
})
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
return resp.IDs
|
||||
}
|
||||
|
||||
// Email executes find by siteID and returns admin's email
|
||||
func (m *MongoStore) Email(siteID string) (email string) {
|
||||
resp := struct {
|
||||
SiteID string `bson:"site"`
|
||||
IDs []string `bson:"admin_ids"`
|
||||
Email string `bson:"admin_email"`
|
||||
}{}
|
||||
err := m.connection.WithCollection(func(coll *mgo.Collection) error {
|
||||
return coll.Find(bson.M{"site": siteID}).One(&resp)
|
||||
})
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return resp.Email
|
||||
}
|
||||
|
||||
@@ -10,13 +10,21 @@ import (
|
||||
)
|
||||
|
||||
func TestStaticStore_Get(t *testing.T) {
|
||||
var ks Store = NewStaticStore([]string{"123", "xyz"}, "aa@example.com")
|
||||
var ks Store = NewStaticStore("key123", []string{"123", "xyz"}, "aa@example.com")
|
||||
|
||||
k, err := ks.Key("any")
|
||||
assert.NoError(t, err, "valid store")
|
||||
assert.Equal(t, "key123", k, "valid site")
|
||||
|
||||
a := ks.Admins("any")
|
||||
assert.Equal(t, []string{"123", "xyz"}, a)
|
||||
|
||||
email := ks.Email("blah")
|
||||
assert.Equal(t, "aa@example.com", email)
|
||||
|
||||
ks = NewStaticStore("", []string{"123", "xyz"}, "aa@example.com")
|
||||
_, err = ks.Key("any")
|
||||
assert.NotNil(t, err, "invalid (empty key) store")
|
||||
}
|
||||
|
||||
func TestMongoStore_Get(t *testing.T) {
|
||||
@@ -24,13 +32,9 @@ func TestMongoStore_Get(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
var ms Store = NewMongoStore(conn)
|
||||
|
||||
recs := []struct {
|
||||
SiteID string `bson:"site"`
|
||||
IDs []string `bson:"admin_ids"`
|
||||
Email string `bson:"admin_email"`
|
||||
}{
|
||||
{"site1", []string{"i11", "i12"}, "e1"},
|
||||
{"site2", []string{"i21", "i22"}, "e2"},
|
||||
recs := []mongoRec{
|
||||
{"site1", "secret1", []string{"i11", "i12"}, "e1"},
|
||||
{"site2", "secret2", []string{"i21", "i22"}, "e2"},
|
||||
}
|
||||
err = conn.WithCollection(func(coll *mgo.Collection) error {
|
||||
if e1 := coll.Insert(recs[0]); e1 != nil {
|
||||
@@ -47,14 +51,22 @@ func TestMongoStore_Get(t *testing.T) {
|
||||
assert.Equal(t, []string{"i11", "i12"}, admins)
|
||||
email := ms.Email("site1")
|
||||
assert.Equal(t, "e1", email)
|
||||
key, err := ms.Key("site1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "secret1", key)
|
||||
|
||||
admins = ms.Admins("site2")
|
||||
assert.Equal(t, []string{"i21", "i22"}, admins)
|
||||
email = ms.Email("site2")
|
||||
assert.Equal(t, "e2", email)
|
||||
key, err = ms.Key("site2")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "secret2", key)
|
||||
|
||||
admins = ms.Admins("no-site-in-db")
|
||||
assert.Equal(t, []string{}, admins)
|
||||
email = ms.Email("no-site-in-db")
|
||||
assert.Equal(t, "", email)
|
||||
_, err = ms.Key("no-site-in-db")
|
||||
assert.Error(t, err, "can't get secret for site no-site-in-db")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"github.com/go-pkgz/mongo"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// MongoStore implements admin.Store with mongo backend
|
||||
type MongoStore struct {
|
||||
connection *mongo.Connection
|
||||
}
|
||||
|
||||
type mongoRec struct {
|
||||
SiteID string `bson:"site"`
|
||||
SecretKey string `bson:"secret"`
|
||||
IDs []string `bson:"admin_ids"`
|
||||
Email string `bson:"admin_email"`
|
||||
}
|
||||
|
||||
// NewMongoStore makes admin Store for mongo's connection
|
||||
func NewMongoStore(conn *mongo.Connection) *MongoStore {
|
||||
log.Printf("[DEBUG] make mongo admin store with %+v", conn)
|
||||
return &MongoStore{connection: conn}
|
||||
}
|
||||
|
||||
// Key executes find by siteID and returns substructure with secret key
|
||||
func (m *MongoStore) Key(siteID string) (key string, err error) {
|
||||
resp := mongoRec{}
|
||||
err = m.connection.WithCollection(func(coll *mgo.Collection) error {
|
||||
return coll.Find(bson.M{"site": siteID}).One(&resp)
|
||||
})
|
||||
return resp.SecretKey, errors.Wrapf(err, "can't get secret for site %s", siteID)
|
||||
}
|
||||
|
||||
// Admins executes find by siteID and returns admins ids
|
||||
func (m *MongoStore) Admins(siteID string) (ids []string) {
|
||||
resp := mongoRec{}
|
||||
err := m.connection.WithCollection(func(coll *mgo.Collection) error {
|
||||
return coll.Find(bson.M{"site": siteID}).One(&resp)
|
||||
})
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
return resp.IDs
|
||||
}
|
||||
|
||||
// Email executes find by siteID and returns admin's email
|
||||
func (m *MongoStore) Email(siteID string) (email string) {
|
||||
resp := mongoRec{}
|
||||
err := m.connection.WithCollection(func(coll *mgo.Collection) error {
|
||||
return coll.Find(bson.M{"site": siteID}).One(&resp)
|
||||
})
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return resp.Email
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"github.com/go-pkgz/mongo"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Store defines interface returning key for given site
|
||||
// this key used for JWT and HMAC hashes
|
||||
type Store interface {
|
||||
Get(siteID string) (key string, err error)
|
||||
}
|
||||
|
||||
// StaticStore implements keys.Store with a single, predefined key
|
||||
type StaticStore struct {
|
||||
key string
|
||||
}
|
||||
|
||||
// NewStaticStore makes StaticStore instance with given key
|
||||
func NewStaticStore(key string) *StaticStore {
|
||||
return &StaticStore{key: key}
|
||||
}
|
||||
|
||||
// Get returns static key for all sites, allows empty site
|
||||
func (s *StaticStore) Get(siteID string) (key string, err error) {
|
||||
if s.key == "" {
|
||||
return "", errors.New("empty key for static key store")
|
||||
}
|
||||
return s.key, nil
|
||||
}
|
||||
|
||||
// MongoStore implements keys.Store with mongo backend
|
||||
type MongoStore struct {
|
||||
connection *mongo.Connection
|
||||
}
|
||||
|
||||
// NewMongoStore makes keys Store for mongo's connection
|
||||
func NewMongoStore(conn *mongo.Connection) *MongoStore {
|
||||
log.Printf("[DEBUG] make mongo keys store with %+v", conn)
|
||||
return &MongoStore{connection: conn}
|
||||
}
|
||||
|
||||
// Get executes find by siteID and returns substructure with secret key
|
||||
func (m *MongoStore) Get(siteID string) (key string, err error) {
|
||||
resp := struct {
|
||||
SiteID string `bson:"site"`
|
||||
SecretKey string `bson:"secret"`
|
||||
}{}
|
||||
err = m.connection.WithCollection(func(coll *mgo.Collection) error {
|
||||
return coll.Find(bson.M{"site": siteID}).One(&resp)
|
||||
})
|
||||
return resp.SecretKey, errors.Wrapf(err, "can't get secret for site %s", siteID)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/go-pkgz/mongo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestStaticStore_Get(t *testing.T) {
|
||||
var ks Store = NewStaticStore("key123")
|
||||
|
||||
k, err := ks.Get("any")
|
||||
assert.NoError(t, err, "valid store")
|
||||
assert.Equal(t, "key123", k, "valid site")
|
||||
|
||||
ks = NewStaticStore("")
|
||||
|
||||
_, err = ks.Get("any")
|
||||
assert.NotNil(t, err, "invalid (empty key) store")
|
||||
}
|
||||
|
||||
func TestMongoStore_Get(t *testing.T) {
|
||||
conn, err := mongo.MakeTestConnection(t)
|
||||
require.NoError(t, err)
|
||||
var ms Store = NewMongoStore(conn)
|
||||
|
||||
recs := []struct {
|
||||
SiteID string `bson:"site"`
|
||||
SecretKey string `bson:"secret"`
|
||||
}{
|
||||
{"site1", "secret1"},
|
||||
{"site2", "secret2"},
|
||||
}
|
||||
err = conn.WithCollection(func(coll *mgo.Collection) error {
|
||||
if e1 := coll.Insert(recs[0]); e1 != nil {
|
||||
return e1
|
||||
}
|
||||
if e2 := coll.Insert(recs[1]); e2 != nil {
|
||||
return e2
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
r, err := ms.Get("site1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "secret1", r)
|
||||
|
||||
r, err = ms.Get("site2")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "secret2", r)
|
||||
|
||||
_, err = ms.Get("no-site-in-db")
|
||||
assert.Error(t, err, "can't get secret for site no-site-in-db")
|
||||
}
|
||||
@@ -6,18 +6,16 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/umputun/remark/backend/app/store/admin"
|
||||
|
||||
"github.com/umputun/remark/backend/app/store"
|
||||
"github.com/umputun/remark/backend/app/store/admin"
|
||||
"github.com/umputun/remark/backend/app/store/engine"
|
||||
"github.com/umputun/remark/backend/app/store/keys"
|
||||
)
|
||||
|
||||
// DataStore wraps store.Interface with additional methods
|
||||
type DataStore struct {
|
||||
engine.Interface
|
||||
EditDuration time.Duration
|
||||
KeyStore keys.Store
|
||||
AdminStore admin.Store
|
||||
MaxCommentSize int
|
||||
|
||||
@@ -56,7 +54,7 @@ func (s *DataStore) prepareNewComment(comment store.Comment) (store.Comment, err
|
||||
}
|
||||
comment.Sanitize() // clear potentially dangerous js from all parts of comment
|
||||
|
||||
secret, err := s.KeyStore.Get(comment.Locator.SiteID)
|
||||
secret, err := s.AdminStore.Key(comment.Locator.SiteID)
|
||||
if err != nil {
|
||||
return store.Comment{}, errors.Wrapf(err, "can't get secret for site %s", comment.Locator.SiteID)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/umputun/remark/backend/app/store/keys"
|
||||
"github.com/umputun/remark/backend/app/store/admin"
|
||||
|
||||
"github.com/umputun/remark/backend/app/store"
|
||||
"github.com/umputun/remark/backend/app/store/engine"
|
||||
@@ -23,8 +23,8 @@ var testDb = "/tmp/test-remark.db"
|
||||
|
||||
func TestService_CreateFromEmpty(t *testing.T) {
|
||||
defer os.Remove(testDb)
|
||||
ks := keys.NewStaticStore("secret 123")
|
||||
b := DataStore{Interface: prepStoreEngine(t), KeyStore: ks}
|
||||
ks := admin.NewStaticKeyStore("secret 123")
|
||||
b := DataStore{Interface: prepStoreEngine(t), AdminStore: ks}
|
||||
comment := store.Comment{
|
||||
Text: "text",
|
||||
User: store.User{IP: "192.168.1.1", ID: "user", Name: "name"},
|
||||
@@ -47,8 +47,8 @@ func TestService_CreateFromEmpty(t *testing.T) {
|
||||
|
||||
func TestService_CreateFromPartial(t *testing.T) {
|
||||
defer os.Remove(testDb)
|
||||
ks := keys.NewStaticStore("secret 123")
|
||||
b := DataStore{Interface: prepStoreEngine(t), KeyStore: ks}
|
||||
ks := admin.NewStaticKeyStore("secret 123")
|
||||
b := DataStore{Interface: prepStoreEngine(t), AdminStore: ks}
|
||||
comment := store.Comment{
|
||||
Text: "text",
|
||||
Timestamp: time.Date(2018, 3, 25, 16, 34, 33, 0, time.UTC),
|
||||
@@ -73,7 +73,7 @@ func TestService_CreateFromPartial(t *testing.T) {
|
||||
|
||||
func TestService_Vote(t *testing.T) {
|
||||
defer os.Remove(testDb)
|
||||
b := DataStore{Interface: prepStoreEngine(t), KeyStore: keys.NewStaticStore("secret 123")}
|
||||
b := DataStore{Interface: prepStoreEngine(t), AdminStore: admin.NewStaticKeyStore("secret 123")}
|
||||
|
||||
comment := store.Comment{
|
||||
Text: "text",
|
||||
@@ -118,7 +118,7 @@ func TestService_Vote(t *testing.T) {
|
||||
|
||||
func TestService_VoteAggressive(t *testing.T) {
|
||||
defer os.Remove(testDb)
|
||||
b := DataStore{Interface: prepStoreEngine(t), KeyStore: keys.NewStaticStore("secret 123")}
|
||||
b := DataStore{Interface: prepStoreEngine(t), AdminStore: admin.NewStaticKeyStore("secret 123")}
|
||||
|
||||
comment := store.Comment{
|
||||
Text: "text",
|
||||
@@ -178,7 +178,7 @@ func TestService_VoteAggressive(t *testing.T) {
|
||||
func TestService_VoteConcurrent(t *testing.T) {
|
||||
|
||||
defer os.Remove(testDb)
|
||||
b := DataStore{Interface: prepStoreEngine(t), KeyStore: keys.NewStaticStore("secret 123")}
|
||||
b := DataStore{Interface: prepStoreEngine(t), AdminStore: admin.NewStaticKeyStore("secret 123")}
|
||||
|
||||
comment := store.Comment{
|
||||
Text: "text",
|
||||
@@ -209,7 +209,7 @@ func TestService_VoteConcurrent(t *testing.T) {
|
||||
|
||||
func TestService_Pin(t *testing.T) {
|
||||
defer os.Remove(testDb)
|
||||
b := DataStore{Interface: prepStoreEngine(t), KeyStore: keys.NewStaticStore("secret 123")}
|
||||
b := DataStore{Interface: prepStoreEngine(t), AdminStore: admin.NewStaticKeyStore("secret 123")}
|
||||
|
||||
res, err := b.Last("radio-t", 0)
|
||||
t.Logf("%+v", res[0])
|
||||
@@ -233,7 +233,7 @@ func TestService_Pin(t *testing.T) {
|
||||
|
||||
func TestService_EditComment(t *testing.T) {
|
||||
defer os.Remove(testDb)
|
||||
b := DataStore{Interface: prepStoreEngine(t), KeyStore: keys.NewStaticStore("secret 123")}
|
||||
b := DataStore{Interface: prepStoreEngine(t), AdminStore: admin.NewStaticKeyStore("secret 123")}
|
||||
|
||||
res, err := b.Last("radio-t", 0)
|
||||
t.Logf("%+v", res[0])
|
||||
@@ -260,7 +260,7 @@ func TestService_EditComment(t *testing.T) {
|
||||
|
||||
func TestService_DeleteComment(t *testing.T) {
|
||||
defer os.Remove(testDb)
|
||||
b := DataStore{Interface: prepStoreEngine(t), KeyStore: keys.NewStaticStore("secret 123")}
|
||||
b := DataStore{Interface: prepStoreEngine(t), AdminStore: admin.NewStaticKeyStore("secret 123")}
|
||||
|
||||
res, err := b.Last("radio-t", 0)
|
||||
t.Logf("%+v", res[0])
|
||||
@@ -279,7 +279,7 @@ func TestService_DeleteComment(t *testing.T) {
|
||||
|
||||
func TestService_EditCommentDurationFailed(t *testing.T) {
|
||||
defer os.Remove(testDb)
|
||||
b := DataStore{Interface: prepStoreEngine(t), EditDuration: 100 * time.Millisecond, KeyStore: keys.NewStaticStore("secret 123")}
|
||||
b := DataStore{Interface: prepStoreEngine(t), EditDuration: 100 * time.Millisecond, AdminStore: admin.NewStaticKeyStore("secret 123")}
|
||||
|
||||
res, err := b.Last("radio-t", 0)
|
||||
t.Logf("%+v", res[0])
|
||||
@@ -296,7 +296,7 @@ func TestService_EditCommentDurationFailed(t *testing.T) {
|
||||
|
||||
func TestService_ValidateComment(t *testing.T) {
|
||||
|
||||
b := DataStore{MaxCommentSize: 2000, KeyStore: keys.NewStaticStore("secret 123")}
|
||||
b := DataStore{MaxCommentSize: 2000, AdminStore: admin.NewStaticKeyStore("secret 123")}
|
||||
longText := fmt.Sprintf("%4000s", "X")
|
||||
|
||||
tbl := []struct {
|
||||
|
||||
Reference in New Issue
Block a user