2022-01-23 05:54:18 +00:00
package cmd
import (
"crypto/subtle"
"errors"
"fmt"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
"heckel.io/ntfy/auth"
"heckel.io/ntfy/util"
"strings"
)
/ *
-- -
dabbling for CLI
ntfy user allow phil mytopic
ntfy user allow phil mytopic -- read - only
ntfy user deny phil mytopic
ntfy user list
phil ( admin )
- read - write access to everything
ben ( user )
- read - write access to a topic alerts
- read access to
everyone ( no user )
- read - only access to topic announcements
* /
var flagsUser = [ ] cli . Flag {
& cli . StringFlag { Name : "config" , Aliases : [ ] string { "c" } , EnvVars : [ ] string { "NTFY_CONFIG_FILE" } , Value : "/etc/ntfy/server.yml" , DefaultText : "/etc/ntfy/server.yml" , Usage : "config file" } ,
altsrc . NewStringFlag ( & cli . StringFlag { Name : "auth-file" , Aliases : [ ] string { "H" } , EnvVars : [ ] string { "NTFY_AUTH_FILE" } , Usage : "auth database file used for access control" } ) ,
2022-01-23 06:00:38 +00:00
altsrc . NewStringFlag ( & cli . StringFlag { Name : "auth-default-access" , Aliases : [ ] string { "p" } , EnvVars : [ ] string { "NTFY_AUTH_DEFAULT_ACCESS" } , Value : "read-write" , Usage : "default permissions if no matching entries in the auth database are found" } ) ,
2022-01-23 05:54:18 +00:00
}
var cmdUser = & cli . Command {
Name : "user" ,
Usage : "Manage users and access to topics" ,
UsageText : "ntfy user [add|del|...] ..." ,
Flags : flagsUser ,
Before : initConfigFileInputSource ( "config" , flagsUser ) ,
2022-01-23 06:00:38 +00:00
Category : categoryServer ,
2022-01-23 05:54:18 +00:00
Subcommands : [ ] * cli . Command {
{
Name : "add" ,
Aliases : [ ] string { "a" } ,
Usage : "add user to auth database" ,
Action : execUserAdd ,
Flags : [ ] cli . Flag {
& cli . StringFlag { Name : "role" , Aliases : [ ] string { "r" } , Value : string ( auth . RoleUser ) , Usage : "user role" } ,
} ,
} ,
{
Name : "remove" ,
Aliases : [ ] string { "del" , "rm" } ,
Usage : "remove user from auth database" ,
Action : execUserDel ,
} ,
{
Name : "change-pass" ,
Aliases : [ ] string { "ch" } ,
Usage : "change user password" ,
Action : execUserChangePass ,
} ,
} ,
}
func execUserAdd ( c * cli . Context ) error {
role := c . String ( "role" )
if c . NArg ( ) == 0 {
return errors . New ( "username expected, type 'ntfy user add --help' for help" )
} else if role != string ( auth . RoleUser ) && role != string ( auth . RoleAdmin ) {
return errors . New ( "role must be either 'user' or 'admin'" )
}
username := c . Args ( ) . Get ( 0 )
password , err := readPassword ( c )
if err != nil {
return err
}
manager , err := createAuthManager ( c )
if err != nil {
return err
}
if err := manager . AddUser ( username , password , auth . Role ( role ) ) ; err != nil {
return err
}
fmt . Fprintf ( c . App . ErrWriter , "User %s added with role %s\n" , username , role )
return nil
}
func execUserDel ( c * cli . Context ) error {
if c . NArg ( ) == 0 {
return errors . New ( "username expected, type 'ntfy user del --help' for help" )
}
username := c . Args ( ) . Get ( 0 )
manager , err := createAuthManager ( c )
if err != nil {
return err
}
if err := manager . RemoveUser ( username ) ; err != nil {
return err
}
fmt . Fprintf ( c . App . ErrWriter , "User %s removed\n" , username )
return nil
}
func execUserChangePass ( c * cli . Context ) error {
if c . NArg ( ) == 0 {
return errors . New ( "username expected, type 'ntfy user change-pass --help' for help" )
}
username := c . Args ( ) . Get ( 0 )
password , err := readPassword ( c )
if err != nil {
return err
}
manager , err := createAuthManager ( c )
if err != nil {
return err
}
if err := manager . ChangePassword ( username , password ) ; err != nil {
return err
}
fmt . Fprintf ( c . App . ErrWriter , "Changed password for user %s\n" , username )
return nil
}
func createAuthManager ( c * cli . Context ) ( auth . Manager , error ) {
authFile := c . String ( "auth-file" )
2022-01-23 06:00:38 +00:00
authDefaultAccess := c . String ( "auth-default-access" )
2022-01-23 05:54:18 +00:00
if authFile == "" {
return nil , errors . New ( "option auth-file not set; auth is unconfigured for this server" )
} else if ! util . FileExists ( authFile ) {
return nil , errors . New ( "auth-file does not exist; please start the server at least once to create it" )
2022-01-23 06:00:38 +00:00
} else if ! util . InStringList ( [ ] string { "read-write" , "read-only" , "deny-all" } , authDefaultAccess ) {
return nil , errors . New ( "if set, auth-default-access must start set to 'read-write', 'read-only' or 'deny-all'" )
2022-01-23 05:54:18 +00:00
}
2022-01-23 06:00:38 +00:00
authDefaultRead := authDefaultAccess == "read-write" || authDefaultAccess == "read-only"
authDefaultWrite := authDefaultAccess == "read-write"
2022-01-23 05:54:18 +00:00
return auth . NewSQLiteAuth ( authFile , authDefaultRead , authDefaultWrite )
}
func readPassword ( c * cli . Context ) ( string , error ) {
fmt . Fprint ( c . App . ErrWriter , "Enter Password: " )
password , err := util . ReadPassword ( c . App . Reader )
if err != nil {
return "" , err
}
fmt . Fprintf ( c . App . ErrWriter , "\r%s\rConfirm: " , strings . Repeat ( " " , 25 ) )
confirm , err := util . ReadPassword ( c . App . Reader )
if err != nil {
return "" , err
}
fmt . Fprintf ( c . App . ErrWriter , "\r%s\r" , strings . Repeat ( " " , 25 ) )
if subtle . ConstantTimeCompare ( confirm , password ) != 1 {
return "" , errors . New ( "passwords do not match: try it again, but this time type slooowwwlly" )
}
return string ( password ) , nil
}