token.go

  1package cmd
  2
  3import (
  4	"strconv"
  5	"strings"
  6	"time"
  7
  8	"github.com/caarlos0/duration"
  9	"github.com/charmbracelet/lipgloss/v2/table"
 10	"github.com/charmbracelet/soft-serve/pkg/backend"
 11	"github.com/charmbracelet/soft-serve/pkg/proto"
 12	"github.com/dustin/go-humanize"
 13	"github.com/spf13/cobra"
 14)
 15
 16// TokenCommand returns a command that manages user access tokens.
 17func TokenCommand() *cobra.Command {
 18	cmd := &cobra.Command{
 19		Use:     "token",
 20		Aliases: []string{"access-token"},
 21		Short:   "Manage access tokens",
 22	}
 23
 24	var createExpiresIn string
 25	createCmd := &cobra.Command{
 26		Use:   "create NAME",
 27		Short: "Create a new access token",
 28		Args:  cobra.MinimumNArgs(1),
 29		RunE: func(cmd *cobra.Command, args []string) error {
 30			ctx := cmd.Context()
 31			be := backend.FromContext(ctx)
 32			name := strings.Join(args, " ")
 33
 34			user := proto.UserFromContext(ctx)
 35			if user == nil {
 36				return proto.ErrUserNotFound
 37			}
 38
 39			var expiresAt time.Time
 40			var expiresIn time.Duration
 41			if createExpiresIn != "" {
 42				d, err := duration.Parse(createExpiresIn)
 43				if err != nil {
 44					return err //nolint:wrapcheck
 45				}
 46
 47				expiresIn = d
 48				expiresAt = time.Now().Add(d)
 49			}
 50
 51			token, err := be.CreateAccessToken(ctx, user, name, expiresAt)
 52			if err != nil {
 53				return err //nolint:wrapcheck
 54			}
 55
 56			notice := "Access token created"
 57			if expiresIn != 0 {
 58				notice += " (expires in " + humanize.Time(expiresAt) + ")"
 59			}
 60
 61			cmd.PrintErrln(notice)
 62			cmd.Println(token)
 63
 64			return nil
 65		},
 66	}
 67
 68	createCmd.Flags().StringVar(&createExpiresIn, "expires-in", "", "Token expiration time (e.g. 1y, 3mo, 2w, 5d4h, 1h30m)")
 69
 70	listCmd := &cobra.Command{
 71		Use:     "list",
 72		Aliases: []string{"ls"},
 73		Short:   "List access tokens",
 74		Args:    cobra.NoArgs,
 75		RunE: func(cmd *cobra.Command, _ []string) error {
 76			ctx := cmd.Context()
 77			be := backend.FromContext(ctx)
 78
 79			user := proto.UserFromContext(ctx)
 80			if user == nil {
 81				return proto.ErrUserNotFound
 82			}
 83
 84			tokens, err := be.ListAccessTokens(ctx, user)
 85			if err != nil {
 86				return err //nolint:wrapcheck
 87			}
 88
 89			if len(tokens) == 0 {
 90				cmd.Println("No tokens found")
 91				return nil
 92			}
 93
 94			now := time.Now()
 95			table := table.New().Headers("ID", "Name", "Created At", "Expires In")
 96			for _, token := range tokens {
 97				expiresAt := "-"
 98				if !token.ExpiresAt.IsZero() {
 99					if now.After(token.ExpiresAt) {
100						expiresAt = "expired"
101					} else {
102						expiresAt = humanize.Time(token.ExpiresAt)
103					}
104				}
105
106				table = table.Row(strconv.FormatInt(token.ID, 10),
107					token.Name,
108					humanize.Time(token.CreatedAt),
109					expiresAt,
110				)
111			}
112			cmd.Println(table)
113			return nil
114		},
115	}
116
117	deleteCmd := &cobra.Command{
118		Use:     "delete ID",
119		Aliases: []string{"rm", "remove"},
120		Short:   "Delete an access token",
121		Args:    cobra.ExactArgs(1),
122		RunE: func(cmd *cobra.Command, args []string) error {
123			ctx := cmd.Context()
124			be := backend.FromContext(ctx)
125
126			user := proto.UserFromContext(ctx)
127			if user == nil {
128				return proto.ErrUserNotFound
129			}
130
131			id, err := strconv.ParseInt(args[0], 10, 64)
132			if err != nil {
133				return err //nolint:wrapcheck
134			}
135
136			if err := be.DeleteAccessToken(ctx, user, id); err != nil {
137				return err //nolint:wrapcheck
138			}
139
140			cmd.PrintErrln("Access token deleted")
141			return nil
142		},
143	}
144
145	cmd.AddCommand(
146		createCmd,
147		listCmd,
148		deleteCmd,
149	)
150
151	return cmd
152}