Browse Source

WIP: Predefined users

binwiederhier 7 months ago
parent
commit
efef587671
5 changed files with 48 additions and 21 deletions
  1. 3 0
      cmd/serve.go
  2. 0 1
      cmd/user.go
  3. 1 0
      server/config.go
  4. 8 1
      server/server.go
  5. 36 19
      user/manager.go

+ 3 - 0
cmd/serve.go

@@ -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-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.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-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)"}),
@@ -157,6 +158,7 @@ func execServe(c *cli.Context) error {
 	authFile := c.String("auth-file")
 	authStartupQueries := c.String("auth-startup-queries")
 	authDefaultAccess := c.String("auth-default-access")
+	authUsers := c.StringSlice("auth-users")
 	attachmentCacheDir := c.String("attachment-cache-dir")
 	attachmentTotalSizeLimitStr := c.String("attachment-total-size-limit")
 	attachmentFileSizeLimitStr := c.String("attachment-file-size-limit")
@@ -406,6 +408,7 @@ func execServe(c *cli.Context) error {
 	conf.AuthFile = authFile
 	conf.AuthStartupQueries = authStartupQueries
 	conf.AuthDefault = authDefault
+	conf.AuthUsers = nil // FIXME
 	conf.AttachmentCacheDir = attachmentCacheDir
 	conf.AttachmentTotalSizeLimit = attachmentTotalSizeLimit
 	conf.AttachmentFileSizeLimit = attachmentFileSizeLimit

+ 0 - 1
cmd/user.go

@@ -94,7 +94,6 @@ Example:
 
 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.
-
 `,
 		},
 		{

+ 1 - 0
server/config.go

@@ -93,6 +93,7 @@ type Config struct {
 	AuthFile                             string
 	AuthStartupQueries                   string
 	AuthDefault                          user.Permission
+	AuthUsers                            []user.User
 	AuthBcryptCost                       int
 	AuthStatsQueueWriterInterval         time.Duration
 	AttachmentCacheDir                   string

+ 8 - 1
server/server.go

@@ -189,7 +189,14 @@ func New(conf *Config) (*Server, error) {
 	}
 	var userManager *user.Manager
 	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 {
 			return nil, err
 		}

+ 36 - 19
user/manager.go

@@ -441,36 +441,53 @@ var (
 // Manager is an implementation of Manager. It stores users and access control list
 // in a SQLite database.
 type Manager struct {
-	db            *sql.DB
-	defaultAccess Permission              // Default permission if no ACL matches
-	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)
-	bcryptCost    int                     // Makes testing easier
-	mu            sync.Mutex
+	config     *Config
+	db         *sql.DB
+	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)
+	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)
 
 // NewManager creates a new Manager instance
-func NewManager(filename, startupQueries string, defaultAccess Permission, bcryptCost int, queueWriterInterval time.Duration) (*Manager, error) {
-	db, err := sql.Open("sqlite3", filename)
+func NewManager(config *Config) (*Manager, error) {
+	// 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 {
 		return nil, err
 	}
 	if err := setupDB(db); err != nil {
 		return nil, err
 	}
-	if err := runStartupQueries(db, startupQueries); err != nil {
+	if err := runStartupQueries(db, config.StartupQueries); err != nil {
 		return nil, err
 	}
 	manager := &Manager{
-		db:            db,
-		defaultAccess: defaultAccess,
-		statsQueue:    make(map[string]*Stats),
-		tokenQueue:    make(map[string]*TokenUpdate),
-		bcryptCost:    bcryptCost,
+		db:         db,
+		config:     config,
+		statsQueue: make(map[string]*Stats),
+		tokenQueue: make(map[string]*TokenUpdate),
 	}
-	go manager.asyncQueueWriter(queueWriterInterval)
+	go manager.asyncQueueWriter(config.QueueWriterInterval)
 	return manager, nil
 }
 
@@ -843,7 +860,7 @@ func (a *Manager) Authorize(user *User, topic string, perm Permission) error {
 	}
 	defer rows.Close()
 	if !rows.Next() {
-		return a.resolvePerms(a.defaultAccess, perm)
+		return a.resolvePerms(a.config.DefaultAccess, perm)
 	}
 	var read, write bool
 	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 {
 		hash = []byte(password)
 	} else {
-		hash, err = bcrypt.GenerateFromPassword([]byte(password), a.bcryptCost)
+		hash, err = bcrypt.GenerateFromPassword([]byte(password), a.config.BcryptCost)
 		if err != nil {
 			return err
 		}
@@ -1205,7 +1222,7 @@ func (a *Manager) ChangePassword(username, password string, hashed bool) error {
 	if hashed {
 		hash = []byte(password)
 	} else {
-		hash, err = bcrypt.GenerateFromPassword([]byte(password), a.bcryptCost)
+		hash, err = bcrypt.GenerateFromPassword([]byte(password), a.config.BcryptCost)
 		if err != nil {
 			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
 func (a *Manager) DefaultAccess() Permission {
-	return a.defaultAccess
+	return a.config.DefaultAccess
 }
 
 // AddTier creates a new tier in the database