Predefined users
This commit is contained in:
@@ -1,76 +1,70 @@
|
|||||||
|
version: 2
|
||||||
before:
|
before:
|
||||||
hooks:
|
hooks:
|
||||||
- go mod download
|
- go mod download
|
||||||
- go mod tidy
|
- go mod tidy
|
||||||
builds:
|
builds:
|
||||||
-
|
- id: ntfy_linux_amd64
|
||||||
id: ntfy_linux_amd64
|
|
||||||
binary: ntfy
|
binary: ntfy
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=1 # required for go-sqlite3
|
- CGO_ENABLED=1 # required for go-sqlite3
|
||||||
tags: [sqlite_omit_load_extension,osusergo,netgo]
|
tags: [ sqlite_omit_load_extension,osusergo,netgo ]
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-linkmode=external -extldflags=-static -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
|
- "-linkmode=external -extldflags=-static -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
|
||||||
goos: [linux]
|
goos: [ linux ]
|
||||||
goarch: [amd64]
|
goarch: [ amd64 ]
|
||||||
-
|
- id: ntfy_linux_armv6
|
||||||
id: ntfy_linux_armv6
|
|
||||||
binary: ntfy
|
binary: ntfy
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=1 # required for go-sqlite3
|
- CGO_ENABLED=1 # required for go-sqlite3
|
||||||
- CC=arm-linux-gnueabi-gcc # apt install gcc-arm-linux-gnueabi
|
- CC=arm-linux-gnueabi-gcc # apt install gcc-arm-linux-gnueabi
|
||||||
tags: [sqlite_omit_load_extension,osusergo,netgo]
|
tags: [ sqlite_omit_load_extension,osusergo,netgo ]
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-linkmode=external -extldflags=-static -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
|
- "-linkmode=external -extldflags=-static -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
|
||||||
goos: [linux]
|
goos: [ linux ]
|
||||||
goarch: [arm]
|
goarch: [ arm ]
|
||||||
goarm: [6]
|
goarm: [ 6 ]
|
||||||
-
|
- id: ntfy_linux_armv7
|
||||||
id: ntfy_linux_armv7
|
|
||||||
binary: ntfy
|
binary: ntfy
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=1 # required for go-sqlite3
|
- CGO_ENABLED=1 # required for go-sqlite3
|
||||||
- CC=arm-linux-gnueabi-gcc # apt install gcc-arm-linux-gnueabi
|
- CC=arm-linux-gnueabi-gcc # apt install gcc-arm-linux-gnueabi
|
||||||
tags: [sqlite_omit_load_extension,osusergo,netgo]
|
tags: [ sqlite_omit_load_extension,osusergo,netgo ]
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-linkmode=external -extldflags=-static -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
|
- "-linkmode=external -extldflags=-static -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
|
||||||
goos: [linux]
|
goos: [ linux ]
|
||||||
goarch: [arm]
|
goarch: [ arm ]
|
||||||
goarm: [7]
|
goarm: [ 7 ]
|
||||||
-
|
- id: ntfy_linux_arm64
|
||||||
id: ntfy_linux_arm64
|
|
||||||
binary: ntfy
|
binary: ntfy
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=1 # required for go-sqlite3
|
- CGO_ENABLED=1 # required for go-sqlite3
|
||||||
- CC=aarch64-linux-gnu-gcc # apt install gcc-aarch64-linux-gnu
|
- CC=aarch64-linux-gnu-gcc # apt install gcc-aarch64-linux-gnu
|
||||||
tags: [sqlite_omit_load_extension,osusergo,netgo]
|
tags: [ sqlite_omit_load_extension,osusergo,netgo ]
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-linkmode=external -extldflags=-static -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
|
- "-linkmode=external -extldflags=-static -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
|
||||||
goos: [linux]
|
goos: [ linux ]
|
||||||
goarch: [arm64]
|
goarch: [ arm64 ]
|
||||||
-
|
- id: ntfy_windows_amd64
|
||||||
id: ntfy_windows_amd64
|
|
||||||
binary: ntfy
|
binary: ntfy
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0 # explicitly disable, since we don't need go-sqlite3
|
- CGO_ENABLED=0 # explicitly disable, since we don't need go-sqlite3
|
||||||
tags: [noserver] # don't include server files
|
tags: [ noserver ] # don't include server files
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
|
- "-X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
|
||||||
goos: [windows]
|
goos: [ windows ]
|
||||||
goarch: [amd64]
|
goarch: [ amd64 ]
|
||||||
-
|
- id: ntfy_darwin_all
|
||||||
id: ntfy_darwin_all
|
|
||||||
binary: ntfy
|
binary: ntfy
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0 # explicitly disable, since we don't need go-sqlite3
|
- CGO_ENABLED=0 # explicitly disable, since we don't need go-sqlite3
|
||||||
tags: [noserver] # don't include server files
|
tags: [ noserver ] # don't include server files
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
|
- "-X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}"
|
||||||
goos: [darwin]
|
goos: [ darwin ]
|
||||||
goarch: [amd64, arm64] # will be combined to "universal binary" (see below)
|
goarch: [ amd64, arm64 ] # will be combined to "universal binary" (see below)
|
||||||
nfpms:
|
nfpms:
|
||||||
-
|
- package_name: ntfy
|
||||||
package_name: ntfy
|
|
||||||
homepage: https://heckel.io/ntfy
|
homepage: https://heckel.io/ntfy
|
||||||
maintainer: Philipp C. Heckel <philipp.heckel@gmail.com>
|
maintainer: Philipp C. Heckel <philipp.heckel@gmail.com>
|
||||||
description: Simple pub-sub notification service
|
description: Simple pub-sub notification service
|
||||||
@@ -106,9 +100,8 @@ nfpms:
|
|||||||
preremove: "scripts/prerm.sh"
|
preremove: "scripts/prerm.sh"
|
||||||
postremove: "scripts/postrm.sh"
|
postremove: "scripts/postrm.sh"
|
||||||
archives:
|
archives:
|
||||||
-
|
- id: ntfy_linux
|
||||||
id: ntfy_linux
|
ids:
|
||||||
builds:
|
|
||||||
- ntfy_linux_amd64
|
- ntfy_linux_amd64
|
||||||
- ntfy_linux_armv6
|
- ntfy_linux_armv6
|
||||||
- ntfy_linux_armv7
|
- ntfy_linux_armv7
|
||||||
@@ -122,19 +115,17 @@ archives:
|
|||||||
- client/client.yml
|
- client/client.yml
|
||||||
- client/ntfy-client.service
|
- client/ntfy-client.service
|
||||||
- client/user/ntfy-client.service
|
- client/user/ntfy-client.service
|
||||||
-
|
- id: ntfy_windows
|
||||||
id: ntfy_windows
|
ids:
|
||||||
builds:
|
|
||||||
- ntfy_windows_amd64
|
- ntfy_windows_amd64
|
||||||
format: zip
|
formats: [ zip ]
|
||||||
wrap_in_directory: true
|
wrap_in_directory: true
|
||||||
files:
|
files:
|
||||||
- LICENSE
|
- LICENSE
|
||||||
- README.md
|
- README.md
|
||||||
- client/client.yml
|
- client/client.yml
|
||||||
-
|
- id: ntfy_darwin
|
||||||
id: ntfy_darwin
|
ids:
|
||||||
builds:
|
|
||||||
- ntfy_darwin_all
|
- ntfy_darwin_all
|
||||||
wrap_in_directory: true
|
wrap_in_directory: true
|
||||||
files:
|
files:
|
||||||
@@ -142,14 +133,13 @@ archives:
|
|||||||
- README.md
|
- README.md
|
||||||
- client/client.yml
|
- client/client.yml
|
||||||
universal_binaries:
|
universal_binaries:
|
||||||
-
|
- id: ntfy_darwin_all
|
||||||
id: ntfy_darwin_all
|
|
||||||
replace: true
|
replace: true
|
||||||
name_template: ntfy
|
name_template: ntfy
|
||||||
checksum:
|
checksum:
|
||||||
name_template: 'checksums.txt'
|
name_template: 'checksums.txt'
|
||||||
snapshot:
|
snapshot:
|
||||||
name_template: "{{ .Tag }}-next"
|
version_template: "{{ .Tag }}-next"
|
||||||
changelog:
|
changelog:
|
||||||
sort: asc
|
sort: asc
|
||||||
filters:
|
filters:
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -220,7 +220,7 @@ cli-deps-static-sites:
|
|||||||
touch server/docs/index.html server/site/app.html
|
touch server/docs/index.html server/site/app.html
|
||||||
|
|
||||||
cli-deps-all:
|
cli-deps-all:
|
||||||
go install github.com/goreleaser/goreleaser@latest
|
go install github.com/goreleaser/goreleaser/v2@latest
|
||||||
|
|
||||||
cli-deps-gcc-armv6-armv7:
|
cli-deps-gcc-armv6-armv7:
|
||||||
which arm-linux-gnueabi-gcc || { echo "ERROR: ARMv6/ARMv7 cross compiler not installed. On Ubuntu, run: apt install gcc-arm-linux-gnueabi"; exit 1; }
|
which arm-linux-gnueabi-gcc || { echo "ERROR: ARMv6/ARMv7 cross compiler not installed. On Ubuntu, run: apt install gcc-arm-linux-gnueabi"; exit 1; }
|
||||||
|
|||||||
32
cmd/serve.go
32
cmd/serve.go
@@ -52,7 +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.NewStringSliceFlag(&cli.StringSliceFlag{Name: "auth-provisioned-users", Aliases: []string{"auth_provisioned_users"}, EnvVars: []string{"NTFY_AUTH_PROVISIONED_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)"}),
|
||||||
@@ -158,7 +158,8 @@ 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")
|
authProvisionedUsersRaw := c.StringSlice("auth-provisioned-users")
|
||||||
|
//authProvisionedAccessRaw := c.StringSlice("auth-provisioned-access")
|
||||||
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")
|
||||||
@@ -348,11 +349,33 @@ func execServe(c *cli.Context) error {
|
|||||||
webRoot = "/" + webRoot
|
webRoot = "/" + webRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default auth permissions
|
// Convert default auth permission, read provisioned users
|
||||||
authDefault, err := user.ParsePermission(authDefaultAccess)
|
authDefault, err := user.ParsePermission(authDefaultAccess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("if set, auth-default-access must start set to 'read-write', 'read-only', 'write-only' or 'deny-all'")
|
return errors.New("if set, auth-default-access must start set to 'read-write', 'read-only', 'write-only' or 'deny-all'")
|
||||||
}
|
}
|
||||||
|
authProvisionedUsers := make([]*user.User, 0)
|
||||||
|
for _, userLine := range authProvisionedUsersRaw {
|
||||||
|
parts := strings.Split(userLine, ":")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return fmt.Errorf("invalid provisioned user %s, expected format: 'name:hash:role'", userLine)
|
||||||
|
}
|
||||||
|
username := strings.TrimSpace(parts[0])
|
||||||
|
passwordHash := strings.TrimSpace(parts[1])
|
||||||
|
role := user.Role(strings.TrimSpace(parts[2]))
|
||||||
|
if !user.AllowedUsername(username) {
|
||||||
|
return fmt.Errorf("invalid provisioned user %s, username invalid", userLine)
|
||||||
|
} else if passwordHash == "" {
|
||||||
|
return fmt.Errorf("invalid provisioned user %s, password hash cannot be empty", userLine)
|
||||||
|
} else if !user.AllowedRole(role) {
|
||||||
|
return fmt.Errorf("invalid provisioned user %s, role %s is not allowed, allowed roles are 'admin' or 'user'", userLine, role)
|
||||||
|
}
|
||||||
|
authProvisionedUsers = append(authProvisionedUsers, &user.User{
|
||||||
|
Name: username,
|
||||||
|
Hash: passwordHash,
|
||||||
|
Role: role,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Special case: Unset default
|
// Special case: Unset default
|
||||||
if listenHTTP == "-" {
|
if listenHTTP == "-" {
|
||||||
@@ -408,7 +431,8 @@ 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.AuthProvisionedUsers = authProvisionedUsers
|
||||||
|
conf.AuthProvisionedAccess = nil // FIXME
|
||||||
conf.AttachmentCacheDir = attachmentCacheDir
|
conf.AttachmentCacheDir = attachmentCacheDir
|
||||||
conf.AttachmentTotalSizeLimit = attachmentTotalSizeLimit
|
conf.AttachmentTotalSizeLimit = attachmentTotalSizeLimit
|
||||||
conf.AttachmentFileSizeLimit = attachmentFileSizeLimit
|
conf.AttachmentFileSizeLimit = attachmentFileSizeLimit
|
||||||
|
|||||||
19
cmd/user.go
19
cmd/user.go
@@ -224,7 +224,7 @@ func execUserDel(c *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := manager.User(username); err == user.ErrUserNotFound {
|
if _, err := manager.User(username); errors.Is(err, user.ErrUserNotFound) {
|
||||||
return fmt.Errorf("user %s does not exist", username)
|
return fmt.Errorf("user %s does not exist", username)
|
||||||
}
|
}
|
||||||
if err := manager.RemoveUser(username); err != nil {
|
if err := manager.RemoveUser(username); err != nil {
|
||||||
@@ -250,7 +250,7 @@ func execUserChangePass(c *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := manager.User(username); err == user.ErrUserNotFound {
|
if _, err := manager.User(username); errors.Is(err, user.ErrUserNotFound) {
|
||||||
return fmt.Errorf("user %s does not exist", username)
|
return fmt.Errorf("user %s does not exist", username)
|
||||||
}
|
}
|
||||||
if password == "" {
|
if password == "" {
|
||||||
@@ -278,7 +278,7 @@ func execUserChangeRole(c *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := manager.User(username); err == user.ErrUserNotFound {
|
if _, err := manager.User(username); errors.Is(err, user.ErrUserNotFound) {
|
||||||
return fmt.Errorf("user %s does not exist", username)
|
return fmt.Errorf("user %s does not exist", username)
|
||||||
}
|
}
|
||||||
if err := manager.ChangeRole(username, role); err != nil {
|
if err := manager.ChangeRole(username, role); err != nil {
|
||||||
@@ -302,7 +302,7 @@ func execUserChangeTier(c *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := manager.User(username); err == user.ErrUserNotFound {
|
if _, err := manager.User(username); errors.Is(err, user.ErrUserNotFound) {
|
||||||
return fmt.Errorf("user %s does not exist", username)
|
return fmt.Errorf("user %s does not exist", username)
|
||||||
}
|
}
|
||||||
if tier == tierReset {
|
if tier == tierReset {
|
||||||
@@ -344,7 +344,16 @@ func createUserManager(c *cli.Context) (*user.Manager, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("if set, auth-default-access must start set to 'read-write', 'read-only', 'write-only' or 'deny-all'")
|
return nil, errors.New("if set, auth-default-access must start set to 'read-write', 'read-only', 'write-only' or 'deny-all'")
|
||||||
}
|
}
|
||||||
return user.NewManager(authFile, authStartupQueries, authDefault, user.DefaultUserPasswordBcryptCost, user.DefaultUserStatsQueueWriterInterval)
|
authConfig := &user.Config{
|
||||||
|
Filename: authFile,
|
||||||
|
StartupQueries: authStartupQueries,
|
||||||
|
DefaultAccess: authDefault,
|
||||||
|
ProvisionedUsers: nil, //FIXME
|
||||||
|
ProvisionedAccess: nil, //FIXME
|
||||||
|
BcryptCost: user.DefaultUserPasswordBcryptCost,
|
||||||
|
QueueWriterInterval: user.DefaultUserStatsQueueWriterInterval,
|
||||||
|
}
|
||||||
|
return user.NewManager(authConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func readPasswordAndConfirm(c *cli.Context) (string, error) {
|
func readPasswordAndConfirm(c *cli.Context) (string, error) {
|
||||||
|
|||||||
@@ -93,7 +93,8 @@ type Config struct {
|
|||||||
AuthFile string
|
AuthFile string
|
||||||
AuthStartupQueries string
|
AuthStartupQueries string
|
||||||
AuthDefault user.Permission
|
AuthDefault user.Permission
|
||||||
AuthUsers []user.User
|
AuthProvisionedUsers []*user.User
|
||||||
|
AuthProvisionedAccess map[string][]*user.Grant
|
||||||
AuthBcryptCost int
|
AuthBcryptCost int
|
||||||
AuthStatsQueueWriterInterval time.Duration
|
AuthStatsQueueWriterInterval time.Duration
|
||||||
AttachmentCacheDir string
|
AttachmentCacheDir string
|
||||||
|
|||||||
@@ -193,6 +193,8 @@ func New(conf *Config) (*Server, error) {
|
|||||||
Filename: conf.AuthFile,
|
Filename: conf.AuthFile,
|
||||||
StartupQueries: conf.AuthStartupQueries,
|
StartupQueries: conf.AuthStartupQueries,
|
||||||
DefaultAccess: conf.AuthDefault,
|
DefaultAccess: conf.AuthDefault,
|
||||||
|
ProvisionedUsers: conf.AuthProvisionedUsers,
|
||||||
|
ProvisionedAccess: conf.AuthProvisionedAccess,
|
||||||
BcryptCost: conf.AuthBcryptCost,
|
BcryptCost: conf.AuthBcryptCost,
|
||||||
QueueWriterInterval: conf.AuthStatsQueueWriterInterval,
|
QueueWriterInterval: conf.AuthStatsQueueWriterInterval,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -449,13 +449,13 @@ type Manager struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Filename string
|
Filename string // Database filename, e.g. "/var/lib/ntfy/user.db"
|
||||||
StartupQueries string
|
StartupQueries string // Queries to run on startup, e.g. to create initial users or tiers
|
||||||
DefaultAccess Permission // Default permission if no ACL matches
|
DefaultAccess Permission // Default permission if no ACL matches
|
||||||
ProvisionedUsers []*User // Predefined users to create on startup
|
ProvisionedUsers []*User // Predefined users to create on startup
|
||||||
ProvisionedAccess map[string][]*Grant // Predefined access grants to create on startup
|
ProvisionedAccess map[string][]*Grant // Predefined access grants to create on startup
|
||||||
BcryptCost int // Makes testing easier
|
QueueWriterInterval time.Duration // Interval for the async queue writer to flush stats and token updates to the database
|
||||||
QueueWriterInterval time.Duration
|
BcryptCost int // Cost of generated passwords; lowering makes testing faster
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Auther = (*Manager)(nil)
|
var _ Auther = (*Manager)(nil)
|
||||||
@@ -469,7 +469,6 @@ func NewManager(config *Config) (*Manager, error) {
|
|||||||
if config.QueueWriterInterval.Seconds() <= 0 {
|
if config.QueueWriterInterval.Seconds() <= 0 {
|
||||||
config.QueueWriterInterval = DefaultUserStatsQueueWriterInterval
|
config.QueueWriterInterval = DefaultUserStatsQueueWriterInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open DB and run setup queries
|
// Open DB and run setup queries
|
||||||
db, err := sql.Open("sqlite3", config.Filename)
|
db, err := sql.Open("sqlite3", config.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -487,6 +486,9 @@ func NewManager(config *Config) (*Manager, error) {
|
|||||||
statsQueue: make(map[string]*Stats),
|
statsQueue: make(map[string]*Stats),
|
||||||
tokenQueue: make(map[string]*TokenUpdate),
|
tokenQueue: make(map[string]*TokenUpdate),
|
||||||
}
|
}
|
||||||
|
if err := manager.provisionUsers(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
go manager.asyncQueueWriter(config.QueueWriterInterval)
|
go manager.asyncQueueWriter(config.QueueWriterInterval)
|
||||||
return manager, nil
|
return manager, nil
|
||||||
}
|
}
|
||||||
@@ -1522,6 +1524,22 @@ func (a *Manager) Close() error {
|
|||||||
return a.db.Close()
|
return a.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Manager) provisionUsers() error {
|
||||||
|
for _, user := range a.config.ProvisionedUsers {
|
||||||
|
if err := a.AddUser(user.Name, user.Hash, user.Role, true); err != nil && !errors.Is(err, ErrUserExists) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for username, grants := range a.config.ProvisionedAccess {
|
||||||
|
for _, grant := range grants {
|
||||||
|
if err := a.AllowAccess(username, grant.TopicPattern, grant.Allow); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// toSQLWildcard converts a wildcard string to a SQL wildcard string. It only allows '*' as wildcards,
|
// toSQLWildcard converts a wildcard string to a SQL wildcard string. It only allows '*' as wildcards,
|
||||||
// and escapes '_', assuming '\' as escape character.
|
// and escapes '_', assuming '\' as escape character.
|
||||||
func toSQLWildcard(s string) string {
|
func toSQLWildcard(s string) string {
|
||||||
|
|||||||
@@ -731,7 +731,14 @@ func TestManager_Token_MaxCount_AutoDelete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestManager_EnqueueStats_ResetStats(t *testing.T) {
|
func TestManager_EnqueueStats_ResetStats(t *testing.T) {
|
||||||
a, err := NewManager(filepath.Join(t.TempDir(), "db"), "", PermissionReadWrite, bcrypt.MinCost, 1500*time.Millisecond)
|
conf := &Config{
|
||||||
|
Filename: filepath.Join(t.TempDir(), "db"),
|
||||||
|
StartupQueries: "",
|
||||||
|
DefaultAccess: PermissionReadWrite,
|
||||||
|
BcryptCost: bcrypt.MinCost,
|
||||||
|
QueueWriterInterval: 1500 * time.Millisecond,
|
||||||
|
}
|
||||||
|
a, err := NewManager(conf)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
|
||||||
|
|
||||||
@@ -773,7 +780,14 @@ func TestManager_EnqueueStats_ResetStats(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestManager_EnqueueTokenUpdate(t *testing.T) {
|
func TestManager_EnqueueTokenUpdate(t *testing.T) {
|
||||||
a, err := NewManager(filepath.Join(t.TempDir(), "db"), "", PermissionReadWrite, bcrypt.MinCost, 500*time.Millisecond)
|
conf := &Config{
|
||||||
|
Filename: filepath.Join(t.TempDir(), "db"),
|
||||||
|
StartupQueries: "",
|
||||||
|
DefaultAccess: PermissionReadWrite,
|
||||||
|
BcryptCost: bcrypt.MinCost,
|
||||||
|
QueueWriterInterval: 500 * time.Millisecond,
|
||||||
|
}
|
||||||
|
a, err := NewManager(conf)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
|
||||||
|
|
||||||
@@ -806,7 +820,14 @@ func TestManager_EnqueueTokenUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestManager_ChangeSettings(t *testing.T) {
|
func TestManager_ChangeSettings(t *testing.T) {
|
||||||
a, err := NewManager(filepath.Join(t.TempDir(), "db"), "", PermissionReadWrite, bcrypt.MinCost, 1500*time.Millisecond)
|
conf := &Config{
|
||||||
|
Filename: filepath.Join(t.TempDir(), "db"),
|
||||||
|
StartupQueries: "",
|
||||||
|
DefaultAccess: PermissionReadWrite,
|
||||||
|
BcryptCost: bcrypt.MinCost,
|
||||||
|
QueueWriterInterval: 1500 * time.Millisecond,
|
||||||
|
}
|
||||||
|
a, err := NewManager(conf)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser, false))
|
||||||
|
|
||||||
@@ -1075,6 +1096,24 @@ func TestManager_Topic_Wildcard_With_Underscore(t *testing.T) {
|
|||||||
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopicX", PermissionWrite))
|
require.Equal(t, ErrUnauthorized, a.Authorize(nil, "mytopicX", PermissionWrite))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestManager_WithProvisionedUsers(t *testing.T) {
|
||||||
|
f := filepath.Join(t.TempDir(), "user.db")
|
||||||
|
conf := &Config{
|
||||||
|
Filename: f,
|
||||||
|
DefaultAccess: PermissionReadWrite,
|
||||||
|
ProvisionedUsers: []*User{
|
||||||
|
{Name: "phil", Hash: "$2a$10$YLiO8U21sX1uhZamTLJXHuxgVC0Z/GKISibrKCLohPgtG7yIxSk4C", Role: RoleAdmin},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a, err := NewManager(conf)
|
||||||
|
require.Nil(t, err)
|
||||||
|
users, err := a.Users()
|
||||||
|
require.Nil(t, err)
|
||||||
|
for _, u := range users {
|
||||||
|
fmt.Println(u.ID, u.Name, u.Role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestToFromSQLWildcard(t *testing.T) {
|
func TestToFromSQLWildcard(t *testing.T) {
|
||||||
require.Equal(t, "up%", toSQLWildcard("up*"))
|
require.Equal(t, "up%", toSQLWildcard("up*"))
|
||||||
require.Equal(t, "up\\_%", toSQLWildcard("up_*"))
|
require.Equal(t, "up\\_%", toSQLWildcard("up_*"))
|
||||||
@@ -1336,7 +1375,14 @@ func newTestManager(t *testing.T, defaultAccess Permission) *Manager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newTestManagerFromFile(t *testing.T, filename, startupQueries string, defaultAccess Permission, bcryptCost int, statsWriterInterval time.Duration) *Manager {
|
func newTestManagerFromFile(t *testing.T, filename, startupQueries string, defaultAccess Permission, bcryptCost int, statsWriterInterval time.Duration) *Manager {
|
||||||
a, err := NewManager(filename, startupQueries, defaultAccess, bcryptCost, statsWriterInterval)
|
conf := &Config{
|
||||||
|
Filename: filename,
|
||||||
|
StartupQueries: startupQueries,
|
||||||
|
DefaultAccess: defaultAccess,
|
||||||
|
BcryptCost: bcryptCost,
|
||||||
|
QueueWriterInterval: statsWriterInterval,
|
||||||
|
}
|
||||||
|
a, err := NewManager(conf)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user