2023-01-29 21:42:40 -05:00
//go:build !noserver
package cmd
import (
"errors"
"fmt"
"github.com/urfave/cli/v2"
2023-11-16 20:54:58 -05:00
"heckel.io/ntfy/v2/user"
"heckel.io/ntfy/v2/util"
2023-01-29 21:42:40 -05:00
"net/netip"
"time"
)
func init ( ) {
commands = append ( commands , cmdToken )
}
2023-02-05 23:34:27 -05:00
var flagsToken = append ( [ ] cli . Flag { } , flagsUser ... )
2023-01-29 21:42:40 -05:00
var cmdToken = & cli . Command {
Name : "token" ,
Usage : "Create, list or delete user tokens" ,
UsageText : "ntfy token [list|add|remove] ..." ,
Flags : flagsToken ,
Before : initConfigFileInputSourceFunc ( "config" , flagsToken , initLogFunc ) ,
Category : categoryServer ,
Subcommands : [ ] * cli . Command {
{
Name : "add" ,
Aliases : [ ] string { "a" } ,
Usage : "Create a new token" ,
UsageText : "ntfy token add [--expires=<duration>] [--label=..] USERNAME" ,
Action : execTokenAdd ,
Flags : [ ] cli . Flag {
& cli . StringFlag { Name : "expires" , Aliases : [ ] string { "e" } , Value : "" , Usage : "token expires after" } ,
& cli . StringFlag { Name : "label" , Aliases : [ ] string { "l" } , Value : "" , Usage : "token label" } ,
} ,
Description : ` Create a new user access token .
User access tokens can be used to publish , subscribe , or perform any other user - specific tasks .
Tokens have full access , and can perform any task a user can do . They are meant to be used to
avoid spreading the password to various places .
2023-02-09 15:24:12 -05:00
This is a server - only command . It directly reads from user . db as defined in the server config
file server . yml . The command only works if ' auth - file ' is properly defined .
2023-01-29 21:42:40 -05:00
Examples :
ntfy token add phil # Create token for user phil which never expires
ntfy token add -- expires = 2 d phil # Create token for user phil which expires in 2 days
ntfy token add - e "tuesday, 8pm" phil # Create token for user phil which expires next Tuesday
2023-01-30 12:19:51 -05:00
ntfy token add - l backups phil # Create token for user phil with label "backups" ` ,
2023-01-29 21:42:40 -05:00
} ,
{
Name : "remove" ,
Aliases : [ ] string { "del" , "rm" } ,
Usage : "Removes a token" ,
UsageText : "ntfy token remove USERNAME TOKEN" ,
Action : execTokenDel ,
Description : ` Remove a token from the ntfy user database .
Example :
2023-01-30 12:19:51 -05:00
ntfy token del phil tk_th2srHVlxrANQHAso5t0HuQ1J1TjN ` ,
2023-01-29 21:42:40 -05:00
} ,
{
Name : "list" ,
Aliases : [ ] string { "l" } ,
Usage : "Shows a list of tokens" ,
Action : execTokenList ,
Description : ` Shows a list of all tokens .
2023-02-09 15:24:12 -05:00
This is a server - only command . It directly reads from user . db as defined in the server config
2023-01-30 12:19:51 -05:00
file server . yml . The command only works if ' auth - file ' is properly defined . ` ,
2023-01-29 21:42:40 -05:00
} ,
2025-07-31 07:08:35 +02:00
{
Name : "generate" ,
Usage : "Generates a random token" ,
Action : execTokenGenerate ,
Description : ` Randomly generate a token to be used in provisioned tokens .
This command only generates the token value , but does not persist it anywhere .
The output can be used in the ' auth - tokens ' config option . ` ,
} ,
2023-01-29 21:42:40 -05:00
} ,
Description : ` Manage access tokens for individual users .
User access tokens can be used to publish , subscribe , or perform any other user - specific tasks .
Tokens have full access , and can perform any task a user can do . They are meant to be used to
avoid spreading the password to various places .
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 .
Examples :
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 = 2 d phil # Create token for user phil which expires in 2 days
2023-01-30 12:19:51 -05:00
ntfy token remove phil tk_th2srHVlxr ... # Delete token ` ,
2023-01-29 21:42:40 -05:00
}
func execTokenAdd ( c * cli . Context ) error {
username := c . Args ( ) . Get ( 0 )
expiresStr := c . String ( "expires" )
label := c . String ( "label" )
if username == "" {
return errors . New ( "username expected, type 'ntfy token add --help' for help" )
} else if username == userEveryone || username == user . Everyone {
return errors . New ( "username not allowed" )
}
expires := time . Unix ( 0 , 0 )
if expiresStr != "" {
var err error
expires , err = util . ParseFutureTime ( expiresStr , time . Now ( ) )
if err != nil {
return err
}
}
manager , err := createUserManager ( c )
if err != nil {
return err
}
u , err := manager . User ( username )
2025-07-31 07:08:35 +02:00
if errors . Is ( err , user . ErrUserNotFound ) {
2023-01-29 21:42:40 -05:00
return fmt . Errorf ( "user %s does not exist" , username )
} else if err != nil {
return err
}
2025-07-31 07:08:35 +02:00
token , err := manager . CreateToken ( u . ID , label , expires , netip . IPv4Unspecified ( ) , false )
2023-01-29 21:42:40 -05:00
if err != nil {
return err
}
if expires . Unix ( ) == 0 {
2025-07-31 10:46:41 +02:00
fmt . Fprintf ( c . App . Writer , "token %s created for user %s, never expires\n" , token . Value , u . Name )
2023-01-29 21:42:40 -05:00
} else {
2025-07-31 10:46:41 +02:00
fmt . Fprintf ( c . App . Writer , "token %s created for user %s, expires %v\n" , token . Value , u . Name , expires . Format ( time . UnixDate ) )
2023-01-29 21:42:40 -05:00
}
return nil
}
func execTokenDel ( c * cli . Context ) error {
username , token := c . Args ( ) . Get ( 0 ) , c . Args ( ) . Get ( 1 )
if username == "" || token == "" {
2023-01-30 12:19:51 -05:00
return errors . New ( "username and token expected, type 'ntfy token remove --help' for help" )
2023-01-29 21:42:40 -05:00
} else if username == userEveryone || username == user . Everyone {
return errors . New ( "username not allowed" )
}
manager , err := createUserManager ( c )
if err != nil {
return err
}
u , err := manager . User ( username )
2025-07-31 07:08:35 +02:00
if errors . Is ( err , user . ErrUserNotFound ) {
2023-01-29 21:42:40 -05:00
return fmt . Errorf ( "user %s does not exist" , username )
} else if err != nil {
return err
}
if err := manager . RemoveToken ( u . ID , token ) ; err != nil {
return err
}
2025-07-31 10:46:41 +02:00
fmt . Fprintf ( c . App . Writer , "token %s for user %s removed\n" , token , username )
2023-01-29 21:42:40 -05:00
return nil
}
func execTokenList ( c * cli . Context ) error {
username := c . Args ( ) . Get ( 0 )
if username == userEveryone || username == user . Everyone {
return errors . New ( "username not allowed" )
}
manager , err := createUserManager ( c )
if err != nil {
return err
}
var users [ ] * user . User
if username != "" {
u , err := manager . User ( username )
2025-07-31 07:08:35 +02:00
if errors . Is ( err , user . ErrUserNotFound ) {
2023-01-29 21:42:40 -05:00
return fmt . Errorf ( "user %s does not exist" , username )
} else if err != nil {
return err
}
users = append ( users , u )
} else {
users , err = manager . Users ( )
if err != nil {
return err
}
}
2023-01-30 12:19:51 -05:00
usersWithTokens := 0
for _ , u := range users {
tokens , err := manager . Tokens ( u . ID )
if err != nil {
return err
} else if len ( tokens ) == 0 && username != "" {
2025-07-31 10:46:41 +02:00
fmt . Fprintf ( c . App . Writer , "user %s has no access tokens\n" , username )
2023-01-30 12:19:51 -05:00
return nil
} else if len ( tokens ) == 0 {
continue
}
usersWithTokens ++
2025-07-31 10:46:41 +02:00
fmt . Fprintf ( c . App . Writer , "user %s\n" , u . Name )
2023-01-30 12:19:51 -05:00
for _ , t := range tokens {
2025-07-31 07:08:35 +02:00
var label , expires , provisioned string
2023-01-30 12:19:51 -05:00
if t . Label != "" {
label = fmt . Sprintf ( " (%s)" , t . Label )
2023-01-29 21:42:40 -05:00
}
2023-01-30 12:19:51 -05:00
if t . Expires . Unix ( ) == 0 {
expires = "never expires"
} else {
expires = fmt . Sprintf ( "expires %s" , t . Expires . Format ( time . RFC822 ) )
2023-01-29 21:42:40 -05:00
}
2025-07-31 07:08:35 +02:00
if t . Provisioned {
provisioned = " (server config)"
}
2025-07-31 10:46:41 +02:00
fmt . Fprintf ( c . App . Writer , "- %s%s, %s, accessed from %s at %s%s\n" , t . Value , label , expires , t . LastOrigin . String ( ) , t . LastAccess . Format ( time . RFC822 ) , provisioned )
2023-01-29 21:42:40 -05:00
}
}
2023-01-30 12:19:51 -05:00
if usersWithTokens == 0 {
2025-07-31 10:46:41 +02:00
fmt . Fprintf ( c . App . Writer , "no users with tokens\n" )
2023-01-30 12:19:51 -05:00
}
2023-01-29 21:42:40 -05:00
return nil
}
2025-07-31 07:08:35 +02:00
func execTokenGenerate ( c * cli . Context ) error {
2025-07-31 07:33:11 +02:00
fmt . Fprintln ( c . App . Writer , user . GenerateToken ( ) )
2025-07-31 07:08:35 +02:00
return nil
}