WIP: Predefined users
This commit is contained in:
@@ -52,6 +52,7 @@ var flagsServe = append(
|
|||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-file", Aliases: []string{"auth_file", "H"}, EnvVars: []string{"NTFY_AUTH_FILE"}, Usage: "auth database file used for access control"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-file", Aliases: []string{"auth_file", "H"}, EnvVars: []string{"NTFY_AUTH_FILE"}, Usage: "auth database file used for access control"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-startup-queries", Aliases: []string{"auth_startup_queries"}, EnvVars: []string{"NTFY_AUTH_STARTUP_QUERIES"}, Usage: "queries run when the auth database is initialized"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-startup-queries", Aliases: []string{"auth_startup_queries"}, EnvVars: []string{"NTFY_AUTH_STARTUP_QUERIES"}, Usage: "queries run when the auth database is initialized"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-default-access", Aliases: []string{"auth_default_access", "p"}, EnvVars: []string{"NTFY_AUTH_DEFAULT_ACCESS"}, Value: "read-write", Usage: "default permissions if no matching entries in the auth database are found"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-default-access", Aliases: []string{"auth_default_access", "p"}, EnvVars: []string{"NTFY_AUTH_DEFAULT_ACCESS"}, Value: "read-write", Usage: "default permissions if no matching entries in the auth database are found"}),
|
||||||
|
altsrc.NewStringSliceFlag(&cli.StringSliceFlag{Name: "auth-users", Aliases: []string{"auth_users"}, EnvVars: []string{"NTFY_AUTH_USERS"}, Usage: "pre-provisioned declarative users"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-cache-dir", Aliases: []string{"attachment_cache_dir"}, EnvVars: []string{"NTFY_ATTACHMENT_CACHE_DIR"}, Usage: "cache directory for attached files"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-cache-dir", Aliases: []string{"attachment_cache_dir"}, EnvVars: []string{"NTFY_ATTACHMENT_CACHE_DIR"}, Usage: "cache directory for attached files"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-total-size-limit", Aliases: []string{"attachment_total_size_limit", "A"}, EnvVars: []string{"NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultAttachmentTotalSizeLimit), Usage: "limit of the on-disk attachment cache"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-total-size-limit", Aliases: []string{"attachment_total_size_limit", "A"}, EnvVars: []string{"NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultAttachmentTotalSizeLimit), Usage: "limit of the on-disk attachment cache"}),
|
||||||
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-file-size-limit", Aliases: []string{"attachment_file_size_limit", "Y"}, EnvVars: []string{"NTFY_ATTACHMENT_FILE_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultAttachmentFileSizeLimit), Usage: "per-file attachment size limit (e.g. 300k, 2M, 100M)"}),
|
altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-file-size-limit", Aliases: []string{"attachment_file_size_limit", "Y"}, EnvVars: []string{"NTFY_ATTACHMENT_FILE_SIZE_LIMIT"}, Value: util.FormatSize(server.DefaultAttachmentFileSizeLimit), Usage: "per-file attachment size limit (e.g. 300k, 2M, 100M)"}),
|
||||||
@@ -157,6 +158,7 @@ func execServe(c *cli.Context) error {
|
|||||||
authFile := c.String("auth-file")
|
authFile := c.String("auth-file")
|
||||||
authStartupQueries := c.String("auth-startup-queries")
|
authStartupQueries := c.String("auth-startup-queries")
|
||||||
authDefaultAccess := c.String("auth-default-access")
|
authDefaultAccess := c.String("auth-default-access")
|
||||||
|
authUsers := c.StringSlice("auth-users")
|
||||||
attachmentCacheDir := c.String("attachment-cache-dir")
|
attachmentCacheDir := c.String("attachment-cache-dir")
|
||||||
attachmentTotalSizeLimitStr := c.String("attachment-total-size-limit")
|
attachmentTotalSizeLimitStr := c.String("attachment-total-size-limit")
|
||||||
attachmentFileSizeLimitStr := c.String("attachment-file-size-limit")
|
attachmentFileSizeLimitStr := c.String("attachment-file-size-limit")
|
||||||
@@ -406,6 +408,7 @@ func execServe(c *cli.Context) error {
|
|||||||
conf.AuthFile = authFile
|
conf.AuthFile = authFile
|
||||||
conf.AuthStartupQueries = authStartupQueries
|
conf.AuthStartupQueries = authStartupQueries
|
||||||
conf.AuthDefault = authDefault
|
conf.AuthDefault = authDefault
|
||||||
|
conf.AuthUsers = nil // FIXME
|
||||||
conf.AttachmentCacheDir = attachmentCacheDir
|
conf.AttachmentCacheDir = attachmentCacheDir
|
||||||
conf.AttachmentTotalSizeLimit = attachmentTotalSizeLimit
|
conf.AttachmentTotalSizeLimit = attachmentTotalSizeLimit
|
||||||
conf.AttachmentFileSizeLimit = attachmentFileSizeLimit
|
conf.AttachmentFileSizeLimit = attachmentFileSizeLimit
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ Example:
|
|||||||
|
|
||||||
You may set the NTFY_PASSWORD environment variable to pass the new password or NTFY_PASSWORD_HASH to pass
|
You may set the NTFY_PASSWORD environment variable to pass the new password or NTFY_PASSWORD_HASH to pass
|
||||||
directly the bcrypt hash. This is useful if you are updating users via scripts.
|
directly the bcrypt hash. This is useful if you are updating users via scripts.
|
||||||
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ type Config struct {
|
|||||||
AuthFile string
|
AuthFile string
|
||||||
AuthStartupQueries string
|
AuthStartupQueries string
|
||||||
AuthDefault user.Permission
|
AuthDefault user.Permission
|
||||||
|
AuthUsers []user.User
|
||||||
AuthBcryptCost int
|
AuthBcryptCost int
|
||||||
AuthStatsQueueWriterInterval time.Duration
|
AuthStatsQueueWriterInterval time.Duration
|
||||||
AttachmentCacheDir string
|
AttachmentCacheDir string
|
||||||
|
|||||||
@@ -189,7 +189,14 @@ func New(conf *Config) (*Server, error) {
|
|||||||
}
|
}
|
||||||
var userManager *user.Manager
|
var userManager *user.Manager
|
||||||
if conf.AuthFile != "" {
|
if conf.AuthFile != "" {
|
||||||
userManager, err = user.NewManager(conf.AuthFile, conf.AuthStartupQueries, conf.AuthDefault, conf.AuthBcryptCost, conf.AuthStatsQueueWriterInterval)
|
authConfig := &user.Config{
|
||||||
|
Filename: conf.AuthFile,
|
||||||
|
StartupQueries: conf.AuthStartupQueries,
|
||||||
|
DefaultAccess: conf.AuthDefault,
|
||||||
|
BcryptCost: conf.AuthBcryptCost,
|
||||||
|
QueueWriterInterval: conf.AuthStatsQueueWriterInterval,
|
||||||
|
}
|
||||||
|
userManager, err = user.NewManager(authConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -441,36 +441,53 @@ var (
|
|||||||
// Manager is an implementation of Manager. It stores users and access control list
|
// Manager is an implementation of Manager. It stores users and access control list
|
||||||
// in a SQLite database.
|
// in a SQLite database.
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
db *sql.DB
|
config *Config
|
||||||
defaultAccess Permission // Default permission if no ACL matches
|
db *sql.DB
|
||||||
statsQueue map[string]*Stats // "Queue" to asynchronously write user stats to the database (UserID -> Stats)
|
statsQueue map[string]*Stats // "Queue" to asynchronously write user stats to the database (UserID -> Stats)
|
||||||
tokenQueue map[string]*TokenUpdate // "Queue" to asynchronously write token access stats to the database (Token ID -> TokenUpdate)
|
tokenQueue map[string]*TokenUpdate // "Queue" to asynchronously write token access stats to the database (Token ID -> TokenUpdate)
|
||||||
bcryptCost int // Makes testing easier
|
mu sync.Mutex
|
||||||
mu sync.Mutex
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Filename string
|
||||||
|
StartupQueries string
|
||||||
|
DefaultAccess Permission // Default permission if no ACL matches
|
||||||
|
ProvisionedUsers []*User // Predefined users to create on startup
|
||||||
|
ProvisionedAccess map[string][]*Grant // Predefined access grants to create on startup
|
||||||
|
BcryptCost int // Makes testing easier
|
||||||
|
QueueWriterInterval time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Auther = (*Manager)(nil)
|
var _ Auther = (*Manager)(nil)
|
||||||
|
|
||||||
// NewManager creates a new Manager instance
|
// NewManager creates a new Manager instance
|
||||||
func NewManager(filename, startupQueries string, defaultAccess Permission, bcryptCost int, queueWriterInterval time.Duration) (*Manager, error) {
|
func NewManager(config *Config) (*Manager, error) {
|
||||||
db, err := sql.Open("sqlite3", filename)
|
// Set defaults
|
||||||
|
if config.BcryptCost <= 0 {
|
||||||
|
config.BcryptCost = DefaultUserPasswordBcryptCost
|
||||||
|
}
|
||||||
|
if config.QueueWriterInterval.Seconds() <= 0 {
|
||||||
|
config.QueueWriterInterval = DefaultUserStatsQueueWriterInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open DB and run setup queries
|
||||||
|
db, err := sql.Open("sqlite3", config.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := setupDB(db); err != nil {
|
if err := setupDB(db); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := runStartupQueries(db, startupQueries); err != nil {
|
if err := runStartupQueries(db, config.StartupQueries); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
manager := &Manager{
|
manager := &Manager{
|
||||||
db: db,
|
db: db,
|
||||||
defaultAccess: defaultAccess,
|
config: config,
|
||||||
statsQueue: make(map[string]*Stats),
|
statsQueue: make(map[string]*Stats),
|
||||||
tokenQueue: make(map[string]*TokenUpdate),
|
tokenQueue: make(map[string]*TokenUpdate),
|
||||||
bcryptCost: bcryptCost,
|
|
||||||
}
|
}
|
||||||
go manager.asyncQueueWriter(queueWriterInterval)
|
go manager.asyncQueueWriter(config.QueueWriterInterval)
|
||||||
return manager, nil
|
return manager, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -843,7 +860,7 @@ func (a *Manager) Authorize(user *User, topic string, perm Permission) error {
|
|||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
if !rows.Next() {
|
if !rows.Next() {
|
||||||
return a.resolvePerms(a.defaultAccess, perm)
|
return a.resolvePerms(a.config.DefaultAccess, perm)
|
||||||
}
|
}
|
||||||
var read, write bool
|
var read, write bool
|
||||||
if err := rows.Scan(&read, &write); err != nil {
|
if err := rows.Scan(&read, &write); err != nil {
|
||||||
@@ -873,7 +890,7 @@ func (a *Manager) AddUser(username, password string, role Role, hashed bool) err
|
|||||||
if hashed {
|
if hashed {
|
||||||
hash = []byte(password)
|
hash = []byte(password)
|
||||||
} else {
|
} else {
|
||||||
hash, err = bcrypt.GenerateFromPassword([]byte(password), a.bcryptCost)
|
hash, err = bcrypt.GenerateFromPassword([]byte(password), a.config.BcryptCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1205,7 +1222,7 @@ func (a *Manager) ChangePassword(username, password string, hashed bool) error {
|
|||||||
if hashed {
|
if hashed {
|
||||||
hash = []byte(password)
|
hash = []byte(password)
|
||||||
} else {
|
} else {
|
||||||
hash, err = bcrypt.GenerateFromPassword([]byte(password), a.bcryptCost)
|
hash, err = bcrypt.GenerateFromPassword([]byte(password), a.config.BcryptCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1387,7 +1404,7 @@ func (a *Manager) RemoveReservations(username string, topics ...string) error {
|
|||||||
|
|
||||||
// DefaultAccess returns the default read/write access if no access control entry matches
|
// DefaultAccess returns the default read/write access if no access control entry matches
|
||||||
func (a *Manager) DefaultAccess() Permission {
|
func (a *Manager) DefaultAccess() Permission {
|
||||||
return a.defaultAccess
|
return a.config.DefaultAccess
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddTier creates a new tier in the database
|
// AddTier creates a new tier in the database
|
||||||
|
|||||||
Reference in New Issue
Block a user