mirror of
https://github.com/binwiederhier/ntfy.git
synced 2026-11-10 14:44:04 +08:00
Open the SQLite user database in the server and pass it to the user manager and the usage store
Like the Postgres pool: user.OpenSQLite opens the auth-file (case-sensitive LIKE), NewSQLiteManager takes the handle and migrates it, and the usage store gets the same handle instead of reaching into the manager (Manager.DB is gone). The usage table stays part of the user schema.
This commit is contained in:
1 parent
6c32bee44e
commit
521ca530b5
7 files changed
+58
-35
No files matched your search
+5
-1
@@ -475,7 +475,11 @@ func createUserManager(c *cli.Context) (*user.Manager, error) {
|
||||
if !util.FileExists(authFile) {
|
||||
return nil, errors.New("auth-file does not exist; please start the server at least once to create it")
|
||||
}
|
||||
return user.NewSQLiteManager(authFile, authStartupQueries, authConfig)
|
||||
userDB, err := user.OpenSQLite(authFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user.NewSQLiteManager(userDB, authStartupQueries, authConfig)
|
||||
}
|
||||
return nil, errors.New("option database-url or auth-file not set; auth is unconfigured for this server")
|
||||
}
|
||||
|
||||
+3
-1
@@ -170,7 +170,9 @@ func TestCLI_User_ResetPass_ProvisionedRejected(t *testing.T) {
|
||||
defer test.StopServer(t, s, port)
|
||||
|
||||
// Seed a provisioned user into the auth database via config provisioning
|
||||
m, err := user.NewSQLiteManager(conf.AuthFile, "", &user.Config{
|
||||
d, err := user.OpenSQLite(conf.AuthFile)
|
||||
require.Nil(t, err)
|
||||
m, err := user.NewSQLiteManager(d, "", &user.Config{
|
||||
ProvisionEnabled: true,
|
||||
Users: []*user.User{
|
||||
{Name: "provuser", Hash: "$2a$10$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C", Role: user.RoleUser},
|
||||
|
||||
+4
-2
@@ -36,12 +36,14 @@ func forEachBackend(t *testing.T, f func(t *testing.T, newStore func() *Store))
|
||||
|
||||
// newTestUserDB returns a fresh SQLite user database, which holds the usage table
|
||||
func newTestUserDB(t testing.TB) *db.DB {
|
||||
manager, err := user.NewSQLiteManager(filepath.Join(t.TempDir(), "user.db"), "", &user.Config{})
|
||||
d, err := user.OpenSQLite(filepath.Join(t.TempDir(), "user.db"))
|
||||
require.Nil(t, err)
|
||||
manager, err := user.NewSQLiteManager(d, "", &user.Config{})
|
||||
require.Nil(t, err)
|
||||
t.Cleanup(func() {
|
||||
manager.Close()
|
||||
})
|
||||
return manager.DB()
|
||||
return d
|
||||
}
|
||||
|
||||
// readAll returns the rows written at or after since, by key
|
||||
|
||||
+14
-9
@@ -306,6 +306,7 @@ func New(conf *Config) (*Server, error) {
|
||||
}
|
||||
s.priceCache = util.NewLookupCache(s.fetchStripePrices, conf.StripePriceCacheDuration)
|
||||
var userManager *user.Manager
|
||||
var userDB *db.DB // The shared pool on Postgres, the auth-file on SQLite; also holds the usage table
|
||||
if conf.AuthFile != "" || pool != nil {
|
||||
authConfig := &user.Config{
|
||||
Filename: conf.AuthFile,
|
||||
@@ -324,9 +325,13 @@ func New(conf *Config) (*Server, error) {
|
||||
AccessWrittenFunc: s.broadcastAccessChanged,
|
||||
}
|
||||
if pool != nil {
|
||||
userManager, err = user.NewPostgresManager(pool, authConfig)
|
||||
userDB = pool
|
||||
userManager, err = user.NewPostgresManager(userDB, authConfig)
|
||||
} else {
|
||||
userManager, err = user.NewSQLiteManager(conf.AuthFile, conf.AuthStartupQueries, authConfig)
|
||||
if userDB, err = user.OpenSQLite(conf.AuthFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userManager, err = user.NewSQLiteManager(userDB, conf.AuthStartupQueries, authConfig)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -348,7 +353,7 @@ func New(conf *Config) (*Server, error) {
|
||||
}
|
||||
s.userManager = userManager
|
||||
s.limiter = limit.New(&limit.Config{
|
||||
Store: createUsageStore(pool, userManager),
|
||||
Store: createUsageStore(pool, userDB),
|
||||
})
|
||||
s.visitors = newVisitorEnv(conf, s.limiter, messageCache, userManager)
|
||||
s.firebaseClient = firebaseClient
|
||||
@@ -397,14 +402,14 @@ func (s *Server) dbPoolStats() []*metrics.DBPool {
|
||||
return pools
|
||||
}
|
||||
|
||||
// createUsageStore returns the store of the usage table, which is part of the user database: the
|
||||
// shared pool on Postgres, the auth-file on SQLite. Without a user database, the daily counts are
|
||||
// kept in memory only.
|
||||
func createUsageStore(pool *db.DB, userManager *user.Manager) *limit.Store {
|
||||
// createUsageStore returns the store of the usage table, which is part of the user schema (the
|
||||
// user manager creates and migrates it): on the shared pool on Postgres, in the auth-file on
|
||||
// SQLite. Without a user database, the daily counts are kept in memory only.
|
||||
func createUsageStore(pool, userDB *db.DB) *limit.Store {
|
||||
if pool != nil {
|
||||
return limit.NewPostgresStore(pool)
|
||||
} else if userManager != nil {
|
||||
return limit.NewSQLiteStore(userManager.DB())
|
||||
} else if userDB != nil {
|
||||
return limit.NewSQLiteStore(userDB)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1890,12 +1890,6 @@ func (a *Manager) maybeProvisionTokens(tx *sql.Tx, provisionUsernames []string,
|
||||
return nil
|
||||
}
|
||||
|
||||
// DB returns the manager's database. It also holds the usage table, which package limit reads and
|
||||
// writes (see the usage table in the schema files).
|
||||
func (a *Manager) DB() *db.DB {
|
||||
return a.db
|
||||
}
|
||||
|
||||
// Close stops background goroutines and closes the underlying database.
|
||||
func (a *Manager) Close() error {
|
||||
select {
|
||||
|
||||
+12
-5
@@ -322,8 +322,9 @@ var sqliteQueries = queries{
|
||||
updateBilling: sqliteUpdateBillingQuery,
|
||||
}
|
||||
|
||||
// NewSQLiteManager creates a new Manager backed by a SQLite database
|
||||
func NewSQLiteManager(filename, startupQueries string, config *Config) (*Manager, error) {
|
||||
// OpenSQLite opens the SQLite user database (auth-file), to pass to NewSQLiteManager and to the
|
||||
// other stores whose tables are part of the user schema (the usage table, see package limit)
|
||||
func OpenSQLite(filename string) (*db.DB, error) {
|
||||
parentDir := filepath.Dir(filename)
|
||||
if !util.FileExists(parentDir) {
|
||||
return nil, fmt.Errorf("user database directory %s does not exist or is not accessible", parentDir)
|
||||
@@ -338,14 +339,20 @@ func NewSQLiteManager(filename, startupQueries string, config *Config) (*Manager
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db.New(&db.Host{DB: d}, nil), nil
|
||||
}
|
||||
|
||||
// NewSQLiteManager creates a new Manager on the SQLite user database (see OpenSQLite), creating
|
||||
// or migrating its schema
|
||||
func NewSQLiteManager(d *db.DB, startupQueries string, config *Config) (*Manager, error) {
|
||||
// Migrations must run before the startup queries: the 5 -> 6 table rebuilds rely on
|
||||
// foreign keys being OFF, which is only guaranteed on fresh connections (the foreign_keys
|
||||
// pragma is enabled as part of the builtin startup queries below)
|
||||
if err := schema.Migrate(d, schema.SQLite, schemaStore, sqliteCreateTables, sqliteMigrations); err != nil {
|
||||
if err := schema.Migrate(d.Primary(), schema.SQLite, schemaStore, sqliteCreateTables, sqliteMigrations); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := runSQLiteStartupQueries(d, startupQueries); err != nil {
|
||||
if err := runSQLiteStartupQueries(d.Primary(), startupQueries); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newManager(db.New(&db.Host{DB: d}, nil), sqliteQueries, config)
|
||||
return newManager(d, sqliteQueries, config)
|
||||
}
|
||||
+20
-11
@@ -33,7 +33,7 @@ func forEachBackend(t *testing.T, f func(t *testing.T, newManager newManagerFunc
|
||||
t.Run("sqlite", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
f(t, func(config *Config) *Manager {
|
||||
a, err := NewSQLiteManager(filepath.Join(dir, "user.db"), "", config)
|
||||
a, err := newTestSQLiteManager(filepath.Join(dir, "user.db"), "", config)
|
||||
require.Nil(t, err)
|
||||
return a
|
||||
})
|
||||
@@ -1918,7 +1918,7 @@ func newTestManager(t *testing.T, newManager newManagerFunc, defaultAccess Permi
|
||||
}
|
||||
|
||||
func newTestManagerFromFile(t *testing.T, filename, startupQueries string, defaultAccess Permission, bcryptCost int, statsWriterInterval time.Duration) *Manager {
|
||||
a, err := NewSQLiteManager(filename, startupQueries, &Config{
|
||||
a, err := newTestSQLiteManager(filename, startupQueries, &Config{
|
||||
DefaultAccess: defaultAccess,
|
||||
BcryptCost: bcryptCost,
|
||||
QueueWriterInterval: statsWriterInterval,
|
||||
@@ -1939,7 +1939,7 @@ func testDB(a *Manager) *db.DB {
|
||||
|
||||
func forEachStoreBackend(t *testing.T, f func(t *testing.T, manager *Manager)) {
|
||||
t.Run("sqlite", func(t *testing.T) {
|
||||
manager, err := NewSQLiteManager(filepath.Join(t.TempDir(), "user.db"), "", &Config{})
|
||||
manager, err := newTestSQLiteManager(filepath.Join(t.TempDir(), "user.db"), "", &Config{})
|
||||
require.Nil(t, err)
|
||||
t.Cleanup(func() { manager.Close() })
|
||||
f(t, manager)
|
||||
@@ -2658,7 +2658,7 @@ func TestManager_AccessWritesTellPeers(t *testing.T) {
|
||||
// topic matching being case-insensitive. SQLite's LIKE is case-insensitive for
|
||||
// ASCII by default, which would let a request for "SECRET" match an ACL rule
|
||||
// for "secret" -- a security hole. PostgreSQL's LIKE is already case-sensitive.
|
||||
// NewSQLiteManager opens the database with case_sensitive_like enabled to close
|
||||
// OpenSQLite opens the database with case_sensitive_like enabled to close
|
||||
// this gap. This exercises the direct-DB path (cache disabled), which is the
|
||||
// path that runs the LIKE query; the in-memory cache is independently
|
||||
// case-sensitive (Go map keys / case-sensitive regex).
|
||||
@@ -3640,11 +3640,11 @@ func TestMigrationFrom9To10Postgres(t *testing.T) {
|
||||
func testMigrationFrom9To10(t *testing.T, a *Manager, setVersionQuery, setStatsQuery string) {
|
||||
t.Helper()
|
||||
require.Nil(t, a.AddUser("phil", "phil", RoleUser, false))
|
||||
_, err := a.DB().Exec(`DROP TABLE IF EXISTS usage`)
|
||||
_, err := a.db.Exec(`DROP TABLE IF EXISTS usage`)
|
||||
require.Nil(t, err)
|
||||
_, err = a.DB().Exec(setVersionQuery)
|
||||
_, err = a.db.Exec(setVersionQuery)
|
||||
require.Nil(t, err)
|
||||
_, err = a.DB().Exec(setStatsQuery)
|
||||
_, err = a.db.Exec(setStatsQuery)
|
||||
require.Nil(t, err)
|
||||
require.Nil(t, a.Close())
|
||||
}
|
||||
@@ -3653,14 +3653,23 @@ func testMigrationFrom9To10(t *testing.T, a *Manager, setVersionQuery, setStatsQ
|
||||
func checkMigrated9To10(t *testing.T, a *Manager, selectVersionQuery, selectStatsQuery string) {
|
||||
t.Helper()
|
||||
var version int
|
||||
require.Nil(t, a.DB().QueryRow(selectVersionQuery).Scan(&version))
|
||||
require.Nil(t, a.db.QueryRow(selectVersionQuery).Scan(&version))
|
||||
require.Equal(t, 10, version)
|
||||
var messages, emails, calls int64
|
||||
require.Nil(t, a.DB().QueryRow(selectStatsQuery).Scan(&messages, &emails, &calls))
|
||||
require.Nil(t, a.db.QueryRow(selectStatsQuery).Scan(&messages, &emails, &calls))
|
||||
require.Equal(t, []int64{0, 0, 0}, []int64{messages, emails, calls})
|
||||
_, err := a.DB().Exec(`INSERT INTO usage (key, period_start, messages, emails, calls, updated_at) VALUES ('ip:1.2.3.4', 1793344380, 37, 0, 0, 1793380805)`)
|
||||
_, err := a.db.Exec(`INSERT INTO usage (key, period_start, messages, emails, calls, updated_at) VALUES ('ip:1.2.3.4', 1793344380, 37, 0, 0, 1793380805)`)
|
||||
require.Nil(t, err)
|
||||
var count int64
|
||||
require.Nil(t, a.DB().QueryRow(`SELECT messages FROM usage WHERE key = 'ip:1.2.3.4' AND updated_at >= 1793380805`).Scan(&count))
|
||||
require.Nil(t, a.db.QueryRow(`SELECT messages FROM usage WHERE key = 'ip:1.2.3.4' AND updated_at >= 1793380805`).Scan(&count))
|
||||
require.Equal(t, int64(37), count)
|
||||
}
|
||||
|
||||
// newTestSQLiteManager opens the SQLite user database and creates a manager on it
|
||||
func newTestSQLiteManager(filename, startupQueries string, config *Config) (*Manager, error) {
|
||||
d, err := OpenSQLite(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewSQLiteManager(d, startupQueries, config)
|
||||
}
|
||||
Reference in new issue
Block a user