ソースを参照

Docs for access tokens

binwiederhier 3 年 前
コミット
70aa384bc3
5 ファイル変更227 行追加12 行削除
  1. 5 0
      client/options.go
  2. 12 0
      cmd/publish.go
  3. 33 0
      docs/config.md
  4. 177 11
      docs/publish.md
  5. 0 1
      server/server.go

+ 5 - 0
client/options.go

@@ -87,6 +87,11 @@ func WithBasicAuth(user, pass string) PublishOption {
 	return WithHeader("Authorization", util.BasicAuth(user, pass))
 	return WithHeader("Authorization", util.BasicAuth(user, pass))
 }
 }
 
 
+// WithBearerAuth adds the Authorization header for Bearer auth to the request
+func WithBearerAuth(token string) PublishOption {
+	return WithHeader("Authorization", fmt.Sprintf("Bearer %s", token))
+}
+
 // 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")

+ 12 - 0
cmd/publish.go

@@ -35,6 +35,7 @@ var flagsPublish = append(
 	&cli.StringFlag{Name: "file", Aliases: []string{"f"}, EnvVars: []string{"NTFY_FILE"}, 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{"mail", "e"}, EnvVars: []string{"NTFY_EMAIL"}, 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.StringFlag{Name: "user", Aliases: []string{"u"}, EnvVars: []string{"NTFY_USER"}, Usage: "username[:password] used to auth against the server"},
 	&cli.StringFlag{Name: "user", Aliases: []string{"u"}, EnvVars: []string{"NTFY_USER"}, Usage: "username[:password] used to auth against the server"},
+	&cli.StringFlag{Name: "token", Aliases: []string{"k"}, EnvVars: []string{"NTFY_TOKEN"}, Usage: "access token used to auth against the server"},
 	&cli.IntFlag{Name: "wait-pid", Aliases: []string{"wait_pid", "pid"}, EnvVars: []string{"NTFY_WAIT_PID"}, Usage: "wait until PID exits before publishing"},
 	&cli.IntFlag{Name: "wait-pid", Aliases: []string{"wait_pid", "pid"}, EnvVars: []string{"NTFY_WAIT_PID"}, Usage: "wait until PID exits before publishing"},
 	&cli.BoolFlag{Name: "wait-cmd", Aliases: []string{"wait_cmd", "cmd", "done"}, EnvVars: []string{"NTFY_WAIT_CMD"}, Usage: "run command and wait until it finishes before publishing"},
 	&cli.BoolFlag{Name: "wait-cmd", Aliases: []string{"wait_cmd", "cmd", "done"}, EnvVars: []string{"NTFY_WAIT_CMD"}, Usage: "run command and wait until it finishes before publishing"},
 	&cli.BoolFlag{Name: "no-cache", Aliases: []string{"no_cache", "C"}, EnvVars: []string{"NTFY_NO_CACHE"}, Usage: "do not cache message server-side"},
 	&cli.BoolFlag{Name: "no-cache", Aliases: []string{"no_cache", "C"}, EnvVars: []string{"NTFY_NO_CACHE"}, Usage: "do not cache message server-side"},
@@ -99,10 +100,18 @@ func execPublish(c *cli.Context) error {
 	file := c.String("file")
 	file := c.String("file")
 	email := c.String("email")
 	email := c.String("email")
 	user := c.String("user")
 	user := c.String("user")
+	token := c.String("token")
 	noCache := c.Bool("no-cache")
 	noCache := c.Bool("no-cache")
 	noFirebase := c.Bool("no-firebase")
 	noFirebase := c.Bool("no-firebase")
 	quiet := c.Bool("quiet")
 	quiet := c.Bool("quiet")
 	pid := c.Int("wait-pid")
 	pid := c.Int("wait-pid")
+
+	// Checks
+	if user != "" && token != "" {
+		return errors.New("cannot set both --user and --token")
+	}
+
+	// Do the things
 	topic, message, command, err := parseTopicMessageCommand(c)
 	topic, message, command, err := parseTopicMessageCommand(c)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -144,6 +153,9 @@ func execPublish(c *cli.Context) error {
 	if noFirebase {
 	if noFirebase {
 		options = append(options, client.WithNoFirebase())
 		options = append(options, client.WithNoFirebase())
 	}
 	}
+	if token != "" {
+		options = append(options, client.WithBearerAuth(token))
+	}
 	if user != "" {
 	if user != "" {
 		var pass string
 		var pass string
 		parts := strings.SplitN(user, ":", 2)
 		parts := strings.SplitN(user, ":", 2)

+ 33 - 0
docs/config.md

@@ -222,6 +222,39 @@ User `ben` has three topic-specific entries. He can read, but not write to topic
 to topic `garagedoor` and all topics starting with the word `alerts` (wildcards). Clients that are not authenticated
 to topic `garagedoor` and all topics starting with the word `alerts` (wildcards). Clients that are not authenticated
 (called `*`/`everyone`) only have read access to the `announcements` and `server-stats` topics.
 (called `*`/`everyone`) only have read access to the `announcements` and `server-stats` topics.
 
 
+### Access tokens
+In addition to username/password auth, ntfy also provides authentication via access tokens. Access tokens are useful
+to avoid having to configure your password across multiple publishing/subscribing applications. For instance, you may
+want to use a dedicated token to publish from your backup host, and one from your home automation system.
+
+!!! info
+    As of today, access tokens grant users **full access to the user account**. Aside from changing the password,
+    and deleting the account, every action can be performed with a token. Granular access tokens are on the roadmap,
+    but not yet implemented.
+
+The `ntfy token` command can be used to manage access tokens for users. Tokens can have labels, and they can expire
+automatically (or never expire). Each user can have up to 20 tokens (hardcoded). 
+
+**Example commands** (type `ntfy token --help` or `ntfy token COMMAND --help` for more details):
+```
+ntfy token list                      # Shows list of tokens for all users
+ntfy token list phil                 # Shows list of tokens for user phil
+ntfy token add phil                  # Create token for user phil which never expires
+ntfy token add --expires=2d phil     # Create token for user phil which expires in 2 days
+ntfy token remove phil tk_th2sxr...  # Delete token
+```
+
+**Creating an access token:**
+```
+$ ntfy token add --expires=30d --label="backups" phil
+$ ntfy token list
+user phil
+- tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 (backups), expires 15 Mar 23 14:33 EDT, accessed from 0.0.0.0 at 13 Feb 23 13:33 EST
+```
+
+Once an access token is created, you can **use it to authenticate against the ntfy server, e.g. when you publish or
+subscribe to topics**. To learn how, check out [authenticate via access tokens](publish.md#access-tokens).
+
 ### Example: Private instance
 ### 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`:
 
 

+ 177 - 11
docs/publish.md

@@ -2591,23 +2591,22 @@ title `You've Got Mail` to topic `sometopic` (see [ntfy.sh/sometopic](https://nt
   <figcaption>Publishing a message via e-mail</figcaption>
   <figcaption>Publishing a message via e-mail</figcaption>
 </figure>
 </figure>
 
 
-## Advanced features
-
-### Authentication
+## Authentication
 Depending on whether the server is configured to support [access control](config.md#access-control), some topics
 Depending on whether the server is configured to support [access control](config.md#access-control), some topics
 may be read/write protected so that only users with the correct credentials can subscribe or publish to them.
 may be read/write protected so that only users with the correct credentials can subscribe or publish to them.
 To publish/subscribe to protected topics, you can: 
 To publish/subscribe to protected topics, you can: 
 
 
-* Use [basic auth](#basic-auth), e.g. `Authorization: Basic dGVzdHVzZXI6ZmFrZXBhc3N3b3Jk`
-* or use the [`auth` query parameter](#query-param), e.g. `?auth=QmFzaWMgZEdWemRIVnpaWEk2Wm1GclpYQmhjM04zYjNKaw`
+* Use [username & password](#username-password) via Basic auth, e.g. `Authorization: Basic dGVzdHVzZXI6ZmFrZXBhc3N3b3Jk`
+* Use [access tokens](#bearer-auth) via Bearer/Basic auth, e.g. `Authorization: Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2`
+* or use either with the [`auth` query parameter](#query-param), e.g. `?auth=QmFzaWMgZEdWemRIVnpaWEk2Wm1GclpYQmhjM04zYjNKaw`
 
 
 !!! warning
 !!! warning
-    Base64 only encodes username and password. It **is not encrypting it**. For your self-hosted server, 
-    **be sure to use HTTPS to avoid eavesdropping** and exposing your password. 
+    When using Basic auth, base64 only encodes username and password. It **is not encrypting it**. For your 
+    self-hosted server, **be sure to use HTTPS to avoid eavesdropping** and exposing your password. 
 
 
-#### Basic auth
-Here's an example using [Basic auth](https://en.wikipedia.org/wiki/Basic_access_authentication), with a user `testuser` 
-and password `fakepassword`:
+### Username & password
+The simplest way to authenticate against a ntfy server is to use [Basic auth](https://en.wikipedia.org/wiki/Basic_access_authentication).
+Here's an example with a user `testuser` and password `fakepassword`:
 
 
 === "Command line (curl)"
 === "Command line (curl)"
     ```
     ```
@@ -2701,7 +2700,172 @@ The following command will generate the appropriate value for you on *nix system
 echo "Basic $(echo -n 'testuser:fakepassword' | base64)"
 echo "Basic $(echo -n 'testuser:fakepassword' | base64)"
 ```
 ```
 
 
-#### Query param
+### Access tokens
+In addition to username/password auth, ntfy also provides authentication via access tokens. Access tokens are useful
+to avoid having to configure your password across multiple publishing/subscribing applications. For instance, you may
+want to use a dedicated token to publish from your backup host, and one from your home automation system.
+
+You can create access tokens using the `ntfy token` command, or in the web app in the "Account" section (when logged in).
+See [access tokens](config.md#access-tokens) for details.
+
+Once an access token is created, you can use it to authenticate against the ntfy server, e.g. when you publish or 
+subscribe to topics. Here's an example using [Bearer auth](https://swagger.io/docs/specification/authentication/bearer-authentication/),
+with the token `tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2`:
+
+=== "Command line (curl)"
+    ```
+    curl \
+      -H "Authorization: Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2" \
+      -d "Look ma, with auth" \
+      https://ntfy.example.com/mysecrets
+    ```
+
+=== "ntfy CLI"
+    ```
+    ntfy publish \
+      --token tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 \
+      ntfy.example.com/mysecrets \
+      "Look ma, with auth"
+    ```
+
+=== "HTTP"
+    ``` http
+    POST /mysecrets HTTP/1.1
+    Host: ntfy.example.com
+    Authorization: Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2
+
+    Look ma, with auth
+    ```
+
+=== "JavaScript"
+    ``` javascript
+    fetch('https://ntfy.example.com/mysecrets', {
+        method: 'POST', // PUT works too
+        body: 'Look ma, with auth',
+        headers: {
+            'Authorization': 'Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2'
+        }
+    })
+    ```
+
+=== "Go"
+    ``` go
+    req, _ := http.NewRequest("POST", "https://ntfy.example.com/mysecrets",
+    strings.NewReader("Look ma, with auth"))
+    req.Header.Set("Authorization", "Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2")
+    http.DefaultClient.Do(req)
+    ```
+
+=== "PowerShell"
+    ``` powershell
+    $uri = "https://ntfy.example.com/mysecrets"
+    $headers = @{Authorization="Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2"}
+    $message = "Look ma, with auth"
+    Invoke-RestMethod -Uri $uri -Body $message -Headers $headers -Method "Post" -UseBasicParsing
+    ```
+
+=== "Python"
+    ``` python
+    requests.post("https://ntfy.example.com/mysecrets",
+    data="Look ma, with auth",
+    headers={
+        "Authorization": "Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2"
+    })
+    ```
+
+=== "PHP"
+    ``` php-inline
+    file_get_contents('https://ntfy.example.com/mysecrets', false, stream_context_create([
+        'http' => [
+            'method' => 'POST', // PUT also works
+            'header' =>
+                'Content-Type: text/plain\r\n' .
+                'Authorization: Bearer tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2',
+            'content' => 'Look ma, with auth'
+        ]
+    ]));
+    ```
+
+Alternatively, you can use [Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication) to send the 
+access token. When sending an empty username, the basic auth password is treated by the ntfy server as an 
+access token. This is primarily useful to make `curl` calls easier, e.g. `curl -u:tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 ...`:
+
+=== "Command line (curl)"
+    ```
+    curl \
+      -u :tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 \
+      -d "Look ma, with auth" \
+      https://ntfy.example.com/mysecrets
+    ```
+
+=== "ntfy CLI"
+    ```
+    ntfy publish \
+      --token tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 \
+      ntfy.example.com/mysecrets \
+      "Look ma, with auth"
+    ```
+
+=== "HTTP"
+    ``` http
+    POST /mysecrets HTTP/1.1
+    Host: ntfy.example.com
+    Authorization: Basic OnRrX0FnUWRxN21WQm9GRDM3elFWTjI5Umh1TXpOSXoy
+
+    Look ma, with auth
+    ```
+
+=== "JavaScript"
+    ``` javascript
+    fetch('https://ntfy.example.com/mysecrets', {
+        method: 'POST', // PUT works too
+        body: 'Look ma, with auth',
+        headers: {
+            'Authorization': 'Basic OnRrX0FnUWRxN21WQm9GRDM3elFWTjI5Umh1TXpOSXoy'
+        }
+    })
+    ```
+
+=== "Go"
+    ``` go
+    req, _ := http.NewRequest("POST", "https://ntfy.example.com/mysecrets",
+    strings.NewReader("Look ma, with auth"))
+    req.Header.Set("Authorization", "Basic OnRrX0FnUWRxN21WQm9GRDM3elFWTjI5Umh1TXpOSXoy")
+    http.DefaultClient.Do(req)
+    ```
+
+=== "PowerShell"
+    ``` powershell
+    $uri = "https://ntfy.example.com/mysecrets"
+    $headers = @{Authorization="Basic OnRrX0FnUWRxN21WQm9GRDM3elFWTjI5Umh1TXpOSXoy"}
+    $message = "Look ma, with auth"
+    Invoke-RestMethod -Uri $uri -Body $message -Headers $headers -Method "Post" -UseBasicParsing
+    ```
+
+=== "Python"
+    ``` python
+    requests.post("https://ntfy.example.com/mysecrets",
+    data="Look ma, with auth",
+    headers={
+        "Authorization": "Basic OnRrX0FnUWRxN21WQm9GRDM3elFWTjI5Umh1TXpOSXoy"
+    })
+    ```
+
+=== "PHP"
+    ``` php-inline
+    file_get_contents('https://ntfy.example.com/mysecrets', false, stream_context_create([
+        'http' => [
+            'method' => 'POST', // PUT also works
+            'header' =>
+                'Content-Type: text/plain\r\n' .
+                'Authorization: Basic OnRrX0FnUWRxN21WQm9GRDM3elFWTjI5Umh1TXpOSXoy',
+            'content' => 'Look ma, with auth'
+        ]
+    ]));
+    ```
+
+
+### Query param
 Here's an example using the `auth` query parameter:
 Here's an example using the `auth` query parameter:
 
 
 === "Command line (curl)"
 === "Command line (curl)"
@@ -2786,6 +2950,8 @@ The following command will generate the appropriate value for you on *nix system
 echo -n "Basic `echo -n 'testuser:fakepassword' | base64`" | base64 | tr -d '='
 echo -n "Basic `echo -n 'testuser:fakepassword' | base64`" | base64 | tr -d '='
 ```
 ```
 
 
+## Advanced features
+
 ### Message caching
 ### Message caching
 !!! info
 !!! info
     If `Cache: no` is used, messages will only be delivered to connected subscribers, and won't be re-delivered if a 
     If `Cache: no` is used, messages will only be delivered to connected subscribers, and won't be re-delivered if a 

+ 0 - 1
server/server.go

@@ -38,7 +38,6 @@ import (
 - HIGH Docs
 - HIGH Docs
   - tiers
   - tiers
   - api
   - api
-  - tokens
 
 
 */
 */