mirror of
https://github.com/binwiederhier/ntfy.git
synced 2024-12-23 17:02:52 +03:00
More docs docs docs
This commit is contained in:
parent
c3a2331b59
commit
1552d8103e
@ -5,6 +5,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"heckel.io/ntfy/util"
|
"heckel.io/ntfy/util"
|
||||||
"io"
|
"io"
|
||||||
@ -105,13 +106,13 @@ func (c *Client) PublishReader(topic string, body io.Reader, options ...PublishO
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("unexpected response %d from server", resp.StatusCode)
|
|
||||||
}
|
|
||||||
b, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseBytes))
|
b, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, errors.New(strings.TrimSpace(string(b)))
|
||||||
|
}
|
||||||
m, err := toMessage(string(b), topicURL, "")
|
m, err := toMessage(string(b), topicURL, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -2,6 +2,7 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"heckel.io/ntfy/util"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -70,6 +71,11 @@ func WithEmail(email string) PublishOption {
|
|||||||
return WithHeader("X-Email", email)
|
return WithHeader("X-Email", email)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithBasicAuth adds the Authorization header for basic auth to the request
|
||||||
|
func WithBasicAuth(user, pass string) PublishOption {
|
||||||
|
return WithHeader("Authorization", util.BasicAuth(user, pass))
|
||||||
|
}
|
||||||
|
|
||||||
// WithNoCache instructs the server not to cache the message server-side
|
// WithNoCache instructs the server not to cache the message server-side
|
||||||
func WithNoCache() PublishOption {
|
func WithNoCache() PublishOption {
|
||||||
return WithHeader("X-Cache", "no")
|
return WithHeader("X-Cache", "no")
|
||||||
|
@ -8,12 +8,6 @@ import (
|
|||||||
"heckel.io/ntfy/util"
|
"heckel.io/ntfy/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
userEveryone = "everyone"
|
userEveryone = "everyone"
|
||||||
)
|
)
|
||||||
@ -46,7 +40,8 @@ Usage:
|
|||||||
ntfy access USERNAME TOPIC PERMISSION # Allow/deny access for USERNAME to TOPIC
|
ntfy access USERNAME TOPIC PERMISSION # Allow/deny access for USERNAME to TOPIC
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
USERNAME an existing user, as created with 'ntfy user add'
|
USERNAME an existing user, as created with 'ntfy user add', or "everyone"/"*"
|
||||||
|
to define access rules for anonymous/unauthenticated clients
|
||||||
TOPIC name of a topic with optional wildcards, e.g. "mytopic*"
|
TOPIC name of a topic with optional wildcards, e.g. "mytopic*"
|
||||||
PERMISSION one of the following:
|
PERMISSION one of the following:
|
||||||
- read-write (alias: rw)
|
- read-write (alias: rw)
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"heckel.io/ntfy/client"
|
"heckel.io/ntfy/client"
|
||||||
|
"heckel.io/ntfy/util"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -15,23 +16,25 @@ var cmdPublish = &cli.Command{
|
|||||||
Name: "publish",
|
Name: "publish",
|
||||||
Aliases: []string{"pub", "send", "trigger"},
|
Aliases: []string{"pub", "send", "trigger"},
|
||||||
Usage: "Send message via a ntfy server",
|
Usage: "Send message via a ntfy server",
|
||||||
UsageText: "ntfy send [OPTIONS..] TOPIC [MESSAGE]",
|
UsageText: "ntfy send [OPTIONS..] TOPIC [MESSAGE]\n NTFY_TOPIC=.. ntfy send [OPTIONS..] -P [MESSAGE]",
|
||||||
Action: execPublish,
|
Action: execPublish,
|
||||||
Category: categoryClient,
|
Category: categoryClient,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, Usage: "client config file"},
|
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG"}, Usage: "client config file"},
|
||||||
&cli.StringFlag{Name: "title", Aliases: []string{"t"}, Usage: "message title"},
|
&cli.StringFlag{Name: "title", Aliases: []string{"t"}, EnvVars: []string{"NTFY_TITLE"}, Usage: "message title"},
|
||||||
&cli.StringFlag{Name: "priority", Aliases: []string{"p"}, Usage: "priority of the message (1=min, 2=low, 3=default, 4=high, 5=max)"},
|
&cli.StringFlag{Name: "priority", Aliases: []string{"p"}, EnvVars: []string{"NTFY_PRIORITY"}, Usage: "priority of the message (1=min, 2=low, 3=default, 4=high, 5=max)"},
|
||||||
&cli.StringFlag{Name: "tags", Aliases: []string{"tag", "T"}, Usage: "comma separated list of tags and emojis"},
|
&cli.StringFlag{Name: "tags", Aliases: []string{"tag", "T"}, EnvVars: []string{"NTFY_TAGS"}, Usage: "comma separated list of tags and emojis"},
|
||||||
&cli.StringFlag{Name: "delay", Aliases: []string{"at", "in", "D"}, Usage: "delay/schedule message"},
|
&cli.StringFlag{Name: "delay", Aliases: []string{"at", "in", "D"}, EnvVars: []string{"NTFY_DELAY"}, Usage: "delay/schedule message"},
|
||||||
&cli.StringFlag{Name: "click", Aliases: []string{"U"}, Usage: "URL to open when notification is clicked"},
|
&cli.StringFlag{Name: "click", Aliases: []string{"U"}, EnvVars: []string{"NTFY_CLICK"}, Usage: "URL to open when notification is clicked"},
|
||||||
&cli.StringFlag{Name: "attach", Aliases: []string{"a"}, Usage: "URL to send as an external attachment"},
|
&cli.StringFlag{Name: "attach", Aliases: []string{"a"}, EnvVars: []string{"NTFY_ATTACH"}, Usage: "URL to send as an external attachment"},
|
||||||
&cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, Usage: "Filename for the attachment"},
|
&cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, EnvVars: []string{"NTFY_FILENAME"}, Usage: "Filename for the attachment"},
|
||||||
&cli.StringFlag{Name: "file", Aliases: []string{"f"}, Usage: "File to upload as an attachment"},
|
&cli.StringFlag{Name: "file", Aliases: []string{"f"}, EnvVars: []string{"NTFY_FILE"}, Usage: "File to upload as an attachment"},
|
||||||
&cli.StringFlag{Name: "email", Aliases: []string{"e-mail", "mail", "e"}, Usage: "also send to e-mail address"},
|
&cli.StringFlag{Name: "email", Aliases: []string{"mail", "e"}, EnvVars: []string{"NTFY_EMAIL"}, Usage: "also send to e-mail address"},
|
||||||
&cli.BoolFlag{Name: "no-cache", Aliases: []string{"C"}, Usage: "do not cache message server-side"},
|
&cli.StringFlag{Name: "user", Aliases: []string{"u"}, EnvVars: []string{"NTFY_USER"}, Usage: "username[:password] used to auth against the server"},
|
||||||
&cli.BoolFlag{Name: "no-firebase", Aliases: []string{"F"}, Usage: "do not forward message to Firebase"},
|
&cli.BoolFlag{Name: "no-cache", Aliases: []string{"C"}, EnvVars: []string{"NTFY_NO_CACHE"}, Usage: "do not cache message server-side"},
|
||||||
&cli.BoolFlag{Name: "quiet", Aliases: []string{"q"}, Usage: "do print message"},
|
&cli.BoolFlag{Name: "no-firebase", Aliases: []string{"F"}, EnvVars: []string{"NTFY_NO_FIREBASE"}, Usage: "do not forward message to Firebase"},
|
||||||
|
&cli.BoolFlag{Name: "env-topic", Aliases: []string{"P"}, EnvVars: []string{"NTFY_ENV_TOPIC"}, Usage: "use topic from NTFY_TOPIC env variable"},
|
||||||
|
&cli.BoolFlag{Name: "quiet", Aliases: []string{"q"}, EnvVars: []string{"NTFY_QUIET"}, Usage: "do print message"},
|
||||||
},
|
},
|
||||||
Description: `Publish a message to a ntfy server.
|
Description: `Publish a message to a ntfy server.
|
||||||
|
|
||||||
@ -46,6 +49,9 @@ Examples:
|
|||||||
ntfy pub --click="https://reddit.com" redd 'New msg' # Opens Reddit when notification is clicked
|
ntfy pub --click="https://reddit.com" redd 'New msg' # Opens Reddit when notification is clicked
|
||||||
ntfy pub --attach="http://some.tld/file.zip" files # Send ZIP archive from URL as attachment
|
ntfy pub --attach="http://some.tld/file.zip" files # Send ZIP archive from URL as attachment
|
||||||
ntfy pub --file=flower.jpg flowers 'Nice!' # Send image.jpg as attachment
|
ntfy pub --file=flower.jpg flowers 'Nice!' # Send image.jpg as attachment
|
||||||
|
ntfy pub -u phil:mypass secret Psst # Publish with username/password
|
||||||
|
NTFY_USER=phil:mypass ntfy pub secret Psst # Use env variables to set username/password
|
||||||
|
NTFY_TOPIC=mytopic ntfy pub -P "some message"" # Use NTFY_TOPIC variable as topic
|
||||||
cat flower.jpg | ntfy pub --file=- flowers 'Nice!' # Same as above, send image.jpg as attachment
|
cat flower.jpg | ntfy pub --file=- flowers 'Nice!' # Same as above, send image.jpg as attachment
|
||||||
ntfy trigger mywebhook # Sending without message, useful for webhooks
|
ntfy trigger mywebhook # Sending without message, useful for webhooks
|
||||||
|
|
||||||
@ -57,9 +63,6 @@ or ~/.config/ntfy/client.yml for all other users.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func execPublish(c *cli.Context) error {
|
func execPublish(c *cli.Context) error {
|
||||||
if c.NArg() < 1 {
|
|
||||||
return errors.New("must specify topic, type 'ntfy publish --help' for help")
|
|
||||||
}
|
|
||||||
conf, err := loadConfig(c)
|
conf, err := loadConfig(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -73,14 +76,26 @@ func execPublish(c *cli.Context) error {
|
|||||||
filename := c.String("filename")
|
filename := c.String("filename")
|
||||||
file := c.String("file")
|
file := c.String("file")
|
||||||
email := c.String("email")
|
email := c.String("email")
|
||||||
|
user := c.String("user")
|
||||||
noCache := c.Bool("no-cache")
|
noCache := c.Bool("no-cache")
|
||||||
noFirebase := c.Bool("no-firebase")
|
noFirebase := c.Bool("no-firebase")
|
||||||
|
envTopic := c.Bool("env-topic")
|
||||||
quiet := c.Bool("quiet")
|
quiet := c.Bool("quiet")
|
||||||
topic := c.Args().Get(0)
|
var topic, message string
|
||||||
message := ""
|
if envTopic {
|
||||||
|
topic = os.Getenv("NTFY_TOPIC")
|
||||||
|
if c.NArg() > 0 {
|
||||||
|
message = strings.Join(c.Args().Slice(), " ")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if c.NArg() < 1 {
|
||||||
|
return errors.New("must specify topic, type 'ntfy publish --help' for help")
|
||||||
|
}
|
||||||
|
topic = c.Args().Get(0)
|
||||||
if c.NArg() > 1 {
|
if c.NArg() > 1 {
|
||||||
message = strings.Join(c.Args().Slice()[1:], " ")
|
message = strings.Join(c.Args().Slice()[1:], " ")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
var options []client.PublishOption
|
var options []client.PublishOption
|
||||||
if title != "" {
|
if title != "" {
|
||||||
options = append(options, client.WithTitle(title))
|
options = append(options, client.WithTitle(title))
|
||||||
@ -112,6 +127,23 @@ func execPublish(c *cli.Context) error {
|
|||||||
if noFirebase {
|
if noFirebase {
|
||||||
options = append(options, client.WithNoFirebase())
|
options = append(options, client.WithNoFirebase())
|
||||||
}
|
}
|
||||||
|
if user != "" {
|
||||||
|
var pass string
|
||||||
|
parts := strings.SplitN(user, ":", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
user = parts[0]
|
||||||
|
pass = parts[1]
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(c.App.ErrWriter, "Enter Password: ")
|
||||||
|
p, err := util.ReadPassword(c.App.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pass = string(p)
|
||||||
|
fmt.Fprintf(c.App.ErrWriter, "\r%s\r", strings.Repeat(" ", 20))
|
||||||
|
}
|
||||||
|
options = append(options, client.WithBasicAuth(user, pass))
|
||||||
|
}
|
||||||
var body io.Reader
|
var body io.Reader
|
||||||
if file == "" {
|
if file == "" {
|
||||||
body = strings.NewReader(message)
|
body = strings.NewReader(message)
|
||||||
|
101
cmd/user.go
101
cmd/user.go
@ -11,29 +11,11 @@ import (
|
|||||||
"strings"
|
"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 = userCommandFlags()
|
var flagsUser = userCommandFlags()
|
||||||
var cmdUser = &cli.Command{
|
var cmdUser = &cli.Command{
|
||||||
Name: "user",
|
Name: "user",
|
||||||
Usage: "Manage users and access to topics",
|
Usage: "Manage/show users",
|
||||||
UsageText: "ntfy user [add|del|...] ...",
|
UsageText: "ntfy user [list|add|remove|change-pass|change-role] ...",
|
||||||
Flags: flagsUser,
|
Flags: flagsUser,
|
||||||
Before: initConfigFileInputSource("config", flagsUser),
|
Before: initConfigFileInputSource("config", flagsUser),
|
||||||
Category: categoryServer,
|
Category: categoryServer,
|
||||||
@ -41,37 +23,96 @@ var cmdUser = &cli.Command{
|
|||||||
{
|
{
|
||||||
Name: "add",
|
Name: "add",
|
||||||
Aliases: []string{"a"},
|
Aliases: []string{"a"},
|
||||||
Usage: "add user to auth database",
|
Usage: "add user",
|
||||||
|
UsageText: "ntfy user add [--role=admin|user] USERNAME",
|
||||||
Action: execUserAdd,
|
Action: execUserAdd,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{Name: "role", Aliases: []string{"r"}, Value: string(auth.RoleUser), Usage: "user role"},
|
&cli.StringFlag{Name: "role", Aliases: []string{"r"}, Value: string(auth.RoleUser), Usage: "user role"},
|
||||||
},
|
},
|
||||||
|
Description: `Add a new user to the ntfy user database.
|
||||||
|
|
||||||
|
A user can be either a regular user, or an admin. A regular user has no read or write access (unless
|
||||||
|
granted otherwise by the auth-default-access setting). An admin user has read and write access to all
|
||||||
|
topics.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
ntfy user add phil # Add regular user phil
|
||||||
|
ntfy user add --role=admin phil # Add admin user phil
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "remove",
|
Name: "remove",
|
||||||
Aliases: []string{"del", "rm"},
|
Aliases: []string{"del", "rm"},
|
||||||
Usage: "remove user from auth database",
|
Usage: "remove user",
|
||||||
|
UsageText: "ntfy user remove USERNAME",
|
||||||
Action: execUserDel,
|
Action: execUserDel,
|
||||||
|
Description: `Remove a user from the ntfy user database.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
ntfy user del phil
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "change-pass",
|
Name: "change-pass",
|
||||||
Aliases: []string{"chp"},
|
Aliases: []string{"chp"},
|
||||||
Usage: "change user password",
|
Usage: "change user password",
|
||||||
|
UsageText: "ntfy user change-pass USERNAME",
|
||||||
Action: execUserChangePass,
|
Action: execUserChangePass,
|
||||||
|
Description: `Change the password for the given user.
|
||||||
|
|
||||||
|
The new password will be read from STDIN, and it'll be confirmed by typing
|
||||||
|
it twice.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
ntfy user change-pass phil
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "change-role",
|
Name: "change-role",
|
||||||
Aliases: []string{"chr"},
|
Aliases: []string{"chr"},
|
||||||
Usage: "change user role",
|
Usage: "change user role",
|
||||||
|
UsageText: "ntfy user change-role USERNAME ROLE",
|
||||||
Action: execUserChangeRole,
|
Action: execUserChangeRole,
|
||||||
|
Description: `Change the role for the given user to admin or user.
|
||||||
|
|
||||||
|
This command can be used to change the role of a user either from a regular user
|
||||||
|
to an admin user, or the other way around:
|
||||||
|
|
||||||
|
- admin: an admin has read/write access to all topics
|
||||||
|
- user: a regular user only has access to what was explicitly granted via 'ntfy access'
|
||||||
|
|
||||||
|
When changing the role of a user to "admin", all access control entries for that
|
||||||
|
user are removed, since they are no longer necessary.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
ntfy user change-role phil admin # Make user phil an admin
|
||||||
|
ntfy user change-role phil user # Remove admin role from user phil
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Aliases: []string{"chr"},
|
Aliases: []string{"l"},
|
||||||
Usage: "change user role",
|
Usage: "list users",
|
||||||
Action: execUserList,
|
Action: execUserList,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Description: `Manage users of the ntfy server.
|
||||||
|
|
||||||
|
This is a server-only command. It directly manages the user.db as defined in the server config
|
||||||
|
file server.yml. The command only works if 'auth-file' is properly defined. Please also refer
|
||||||
|
to the related command 'ntfy access'.
|
||||||
|
|
||||||
|
The command allows you to add/remove/change users in the ntfy user database, as well as change
|
||||||
|
passwords or roles.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
ntfy user list # Shows list of users
|
||||||
|
ntfy user add phil # Add regular user phil
|
||||||
|
ntfy user add --role=admin phil # Add admin user phil
|
||||||
|
ntfy user del phil # Delete user phil
|
||||||
|
ntfy user change-pass phil # Change password for user phil
|
||||||
|
ntfy user change-role phil admin # Make user phil an admin
|
||||||
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
func execUserAdd(c *cli.Context) error {
|
func execUserAdd(c *cli.Context) error {
|
||||||
@ -79,6 +120,8 @@ func execUserAdd(c *cli.Context) error {
|
|||||||
role := auth.Role(c.String("role"))
|
role := auth.Role(c.String("role"))
|
||||||
if username == "" {
|
if username == "" {
|
||||||
return errors.New("username expected, type 'ntfy user add --help' for help")
|
return errors.New("username expected, type 'ntfy user add --help' for help")
|
||||||
|
} else if username == userEveryone {
|
||||||
|
return errors.New("username not allowed")
|
||||||
} else if !auth.AllowedRole(role) {
|
} else if !auth.AllowedRole(role) {
|
||||||
return errors.New("role must be either 'user' or 'admin'")
|
return errors.New("role must be either 'user' or 'admin'")
|
||||||
}
|
}
|
||||||
@ -101,6 +144,8 @@ func execUserDel(c *cli.Context) error {
|
|||||||
username := c.Args().Get(0)
|
username := c.Args().Get(0)
|
||||||
if username == "" {
|
if username == "" {
|
||||||
return errors.New("username expected, type 'ntfy user del --help' for help")
|
return errors.New("username expected, type 'ntfy user del --help' for help")
|
||||||
|
} else if username == userEveryone {
|
||||||
|
return errors.New("username not allowed")
|
||||||
}
|
}
|
||||||
manager, err := createAuthManager(c)
|
manager, err := createAuthManager(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -117,6 +162,8 @@ func execUserChangePass(c *cli.Context) error {
|
|||||||
username := c.Args().Get(0)
|
username := c.Args().Get(0)
|
||||||
if username == "" {
|
if username == "" {
|
||||||
return errors.New("username expected, type 'ntfy user change-pass --help' for help")
|
return errors.New("username expected, type 'ntfy user change-pass --help' for help")
|
||||||
|
} else if username == userEveryone {
|
||||||
|
return errors.New("username not allowed")
|
||||||
}
|
}
|
||||||
password, err := readPassword(c)
|
password, err := readPassword(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -138,6 +185,8 @@ func execUserChangeRole(c *cli.Context) error {
|
|||||||
role := auth.Role(c.Args().Get(1))
|
role := auth.Role(c.Args().Get(1))
|
||||||
if username == "" || !auth.AllowedRole(role) {
|
if username == "" || !auth.AllowedRole(role) {
|
||||||
return errors.New("username and new role expected, type 'ntfy user change-role --help' for help")
|
return errors.New("username and new role expected, type 'ntfy user change-role --help' for help")
|
||||||
|
} else if username == userEveryone {
|
||||||
|
return errors.New("username not allowed")
|
||||||
}
|
}
|
||||||
manager, err := createAuthManager(c)
|
manager, err := createAuthManager(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -169,11 +218,11 @@ func createAuthManager(c *cli.Context) (auth.Manager, error) {
|
|||||||
return nil, errors.New("option auth-file not set; auth is unconfigured for this server")
|
return nil, errors.New("option auth-file not set; auth is unconfigured for this server")
|
||||||
} else if !util.FileExists(authFile) {
|
} else if !util.FileExists(authFile) {
|
||||||
return nil, errors.New("auth-file does not exist; please start the server at least once to create it")
|
return nil, errors.New("auth-file does not exist; please start the server at least once to create it")
|
||||||
} else if !util.InStringList([]string{"read-write", "read-only", "deny-all"}, authDefaultAccess) {
|
} else if !util.InStringList([]string{"read-write", "read-only", "write-only", "deny-all"}, authDefaultAccess) {
|
||||||
return nil, errors.New("if set, auth-default-access must start set to 'read-write', 'read-only' or 'deny-all'")
|
return nil, errors.New("if set, auth-default-access must start set to 'read-write', 'read-only' or 'deny-all'")
|
||||||
}
|
}
|
||||||
authDefaultRead := authDefaultAccess == "read-write" || authDefaultAccess == "read-only"
|
authDefaultRead := authDefaultAccess == "read-write" || authDefaultAccess == "read-only"
|
||||||
authDefaultWrite := authDefaultAccess == "read-write"
|
authDefaultWrite := authDefaultAccess == "read-write" || authDefaultAccess == "write-only"
|
||||||
return auth.NewSQLiteAuth(authFile, authDefaultRead, authDefaultWrite)
|
return auth.NewSQLiteAuth(authFile, authDefaultRead, authDefaultWrite)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ Please also refer to the [rate limiting](#rate-limiting) settings below, specifi
|
|||||||
and `visitor-attachment-daily-bandwidth-limit`. Setting these conservatively is necessary to avoid abuse.
|
and `visitor-attachment-daily-bandwidth-limit`. Setting these conservatively is necessary to avoid abuse.
|
||||||
|
|
||||||
## Access control
|
## Access control
|
||||||
By default, the ntfy server is open for everyone, meaning everyone can read and write to any topic. To restrict access
|
By default, the ntfy server is open for everyone, meaning **everyone can read and write to any topic**. To restrict access
|
||||||
to your own server, you can optionally configure authentication and authorization.
|
to your own server, you can optionally configure authentication and authorization.
|
||||||
|
|
||||||
ntfy's auth is implemented with a simple SQLite-based backend. It implements two roles (`user` and `admin`) and per-topic
|
ntfy's auth is implemented with a simple SQLite-based backend. It implements two roles (`user` and `admin`) and per-topic
|
||||||
@ -135,10 +135,13 @@ To set up auth, simply configure the following two options:
|
|||||||
* `auth-default-access` defines the default/fallback access if no access control entry is found; it can be
|
* `auth-default-access` defines the default/fallback access if no access control entry is found; it can be
|
||||||
set to `read-write` (default), `read-only`, `write-only` or `deny-all`.
|
set to `read-write` (default), `read-only`, `write-only` or `deny-all`.
|
||||||
|
|
||||||
Once configured, you can use the `ntfy user` command to add/modify/delete users (with either a `user` or an `admin` role).
|
### Managing users + access
|
||||||
To control granular access to specific topics, you can use the `ntfy access` command to modify the access control list.
|
Once configured, you can use the `ntfy user` command to add/modify/delete users, and the `ntfy access` command
|
||||||
|
to modify the access control list to allow/deny access to specific topic or topic patterns.
|
||||||
|
|
||||||
### Example: private instance
|
XXXXXXXXXXXXXXXXXXXx
|
||||||
|
|
||||||
|
### Example: Private instance
|
||||||
The easiest way to configure a private instance is to set `auth-default-access` to `deny-all` in the `server.yml`:
|
The easiest way to configure a private instance is to set `auth-default-access` to `deny-all` in the `server.yml`:
|
||||||
|
|
||||||
``` yaml
|
``` yaml
|
||||||
@ -156,72 +159,70 @@ User phil added with role admin
|
|||||||
```
|
```
|
||||||
|
|
||||||
Once you've done that, you can publish and subscribe using [Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication)
|
Once you've done that, you can publish and subscribe using [Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication)
|
||||||
with the given username/password. Here's a simple example:
|
with the given username/password. Be sure to use HTTPS to avoid eavesdropping and exposing your password. Here's a simple example:
|
||||||
|
|
||||||
=== "Command line (curl)"
|
=== "Command line (curl)"
|
||||||
```
|
```
|
||||||
curl \
|
curl \
|
||||||
-u phil:mypass \
|
-u phil:mypass \
|
||||||
-d "Look ma, with auth" \
|
-d "Look ma, with auth" \
|
||||||
ntfy.example.com/secrets
|
https://ntfy.example.com/mysecrets
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "ntfy CLI"
|
=== "ntfy CLI"
|
||||||
```
|
```
|
||||||
ntfy publish ntfy.example.com/mytopic "Look ma, with auth"
|
ntfy publish \
|
||||||
|
-u phil:mypass \
|
||||||
XXXXXXXXXXX
|
ntfy.example.com/mysecrets \
|
||||||
XXXXXXXXXXX
|
"Look ma, with auth"
|
||||||
XXXXXXXXXXX
|
|
||||||
XXXXXXXXXXX
|
|
||||||
XXXXXXXXXXX
|
|
||||||
XXXXXXXXXXX
|
|
||||||
XXXXXXXXXXX
|
|
||||||
XXXXXXXXXXX
|
|
||||||
XXXXXXXXXXX
|
|
||||||
XXXXXXXXXXX
|
|
||||||
XXXXXXXXXXX
|
|
||||||
XXXXXXXXXXX
|
|
||||||
XXXXXXXXXXX
|
|
||||||
XXXXXXXXXXX
|
|
||||||
XXXXXXXXXXX
|
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "HTTP"
|
=== "HTTP"
|
||||||
``` http
|
``` http
|
||||||
POST /mytopic HTTP/1.1
|
POST /mysecrets HTTP/1.1
|
||||||
Host: ntfy.sh
|
Host: ntfy.example.com
|
||||||
Authorization: Basic cGhpbDpteXBhc3M=
|
Authorization: Basic cGhpbDpteXBhc3M=
|
||||||
|
|
||||||
Backup successful 😀
|
Look ma, with auth
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "JavaScript"
|
=== "JavaScript"
|
||||||
``` javascript
|
``` javascript
|
||||||
fetch('https://ntfy.sh/mytopic', {
|
fetch('https://ntfy.example.com/mysecrets', {
|
||||||
method: 'POST', // PUT works too
|
method: 'POST', // PUT works too
|
||||||
body: 'Backup successful 😀'
|
body: 'Look ma, with auth',
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Basic cGhpbDpteXBhc3M='
|
||||||
|
}
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Go"
|
=== "Go"
|
||||||
``` go
|
``` go
|
||||||
http.Post("https://ntfy.sh/mytopic", "text/plain",
|
req, _ := http.NewRequest("POST", "https://ntfy.example.com/mysecrets",
|
||||||
strings.NewReader("Backup successful 😀"))
|
strings.NewReader("Look ma, with auth"))
|
||||||
|
req.Header.Set("Authorization", "Basic cGhpbDpteXBhc3M=")
|
||||||
|
http.DefaultClient.Do(req)
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Python"
|
=== "Python"
|
||||||
``` python
|
``` python
|
||||||
requests.post("https://ntfy.sh/mytopic",
|
requests.post("https://ntfy.example.com/mysecrets",
|
||||||
data="Backup successful 😀".encode(encoding='utf-8'))
|
data="Look ma, with auth",
|
||||||
|
headers={
|
||||||
|
"Authorization": "Basic cGhpbDpteXBhc3M="
|
||||||
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "PHP"
|
=== "PHP"
|
||||||
``` php-inline
|
``` php-inline
|
||||||
file_get_contents('https://ntfy.sh/mytopic', false, stream_context_create([
|
file_get_contents('https://ntfy.example.com/mysecrets', false, stream_context_create([
|
||||||
'http' => [
|
'http' => [
|
||||||
'method' => 'POST', // PUT also works
|
'method' => 'POST', // PUT also works
|
||||||
'header' => 'Content-Type: text/plain',
|
'header' =>
|
||||||
'content' => 'Backup successful 😀'
|
'Content-Type: text/plain\r\n' .
|
||||||
|
'Authorization: Basic cGhpbDpteXBhc3M=',
|
||||||
|
'content' => 'Look ma, with auth'
|
||||||
]
|
]
|
||||||
]));
|
]));
|
||||||
```
|
```
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gabriel-vasile/mimetype"
|
"github.com/gabriel-vasile/mimetype"
|
||||||
@ -240,3 +241,8 @@ func ReadPassword(in io.Reader) ([]byte, error) {
|
|||||||
|
|
||||||
return password, nil
|
return password, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BasicAuth encodes the Authorization header value for basic auth
|
||||||
|
func BasicAuth(user, pass string) string {
|
||||||
|
return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", user, pass))))
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user