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}