1package cmd
2
3import (
4 "strconv"
5 "strings"
6 "time"
7
8 "github.com/caarlos0/duration"
9 "github.com/caarlos0/tablewriter"
10 "github.com/charmbracelet/soft-serve/server/backend"
11 "github.com/charmbracelet/soft-serve/server/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
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
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
87 }
88
89 if len(tokens) == 0 {
90 cmd.Println("No tokens found")
91 return nil
92 }
93
94 now := time.Now()
95 return tablewriter.Render(
96 cmd.OutOrStdout(),
97 tokens,
98 []string{"ID", "Name", "Created At", "Expires In"},
99 func(t proto.AccessToken) ([]string, error) {
100 expiresAt := "-"
101 if !t.ExpiresAt.IsZero() {
102 if now.After(t.ExpiresAt) {
103 expiresAt = "expired"
104 } else {
105 expiresAt = humanize.Time(t.ExpiresAt)
106 }
107 }
108
109 return []string{
110 strconv.FormatInt(t.ID, 10),
111 t.Name,
112 humanize.Time(t.CreatedAt),
113 expiresAt,
114 }, nil
115 },
116 )
117 },
118 }
119
120 deleteCmd := &cobra.Command{
121 Use: "delete ID",
122 Aliases: []string{"rm", "remove"},
123 Short: "Delete an access token",
124 Args: cobra.ExactArgs(1),
125 RunE: func(cmd *cobra.Command, args []string) error {
126 ctx := cmd.Context()
127 be := backend.FromContext(ctx)
128
129 user := proto.UserFromContext(ctx)
130 if user == nil {
131 return proto.ErrUserNotFound
132 }
133
134 id, err := strconv.ParseInt(args[0], 10, 64)
135 if err != nil {
136 return err
137 }
138
139 if err := be.DeleteAccessToken(ctx, user, id); err != nil {
140 return err
141 }
142
143 cmd.PrintErrln("Access token deleted")
144 return nil
145 },
146 }
147
148 cmd.AddCommand(
149 createCmd,
150 listCmd,
151 deleteCmd,
152 )
153
154 return cmd
155}