44 "fmt"
55 "os"
66 "slices"
7+ "sort"
78 "strings"
89 "time"
910
@@ -27,6 +28,10 @@ func (r *RootCmd) tokens() *serpent.Command {
2728 Description : "List your tokens" ,
2829 Command : "coder tokens ls" ,
2930 },
31+ Example {
32+ Description : "Create a scoped token" ,
33+ Command : "coder tokens create --scope workspace:read --allow workspace:<uuid>" ,
34+ },
3035 Example {
3136 Description : "Remove a token by ID" ,
3237 Command : "coder tokens rm WuoWs4ZsMX" ,
@@ -39,6 +44,7 @@ func (r *RootCmd) tokens() *serpent.Command {
3944 Children : []* serpent.Command {
4045 r .createToken (),
4146 r .listTokens (),
47+ r .viewToken (),
4248 r .removeToken (),
4349 },
4450 }
@@ -50,6 +56,8 @@ func (r *RootCmd) createToken() *serpent.Command {
5056 tokenLifetime string
5157 name string
5258 user string
59+ scopes []codersdk.APIKeyScope
60+ allowList []codersdk.APIAllowListTarget
5361 )
5462 cmd := & serpent.Command {
5563 Use : "create" ,
@@ -88,10 +96,18 @@ func (r *RootCmd) createToken() *serpent.Command {
8896 }
8997 }
9098
91- res , err := client . CreateToken ( inv . Context (), userID , codersdk.CreateTokenRequest {
99+ req := codersdk.CreateTokenRequest {
92100 Lifetime : parsedLifetime ,
93101 TokenName : name ,
94- })
102+ }
103+ if len (scopes ) > 0 {
104+ req .Scopes = append ([]codersdk.APIKeyScope (nil ), scopes ... )
105+ }
106+ if len (allowList ) > 0 {
107+ req .AllowList = append ([]codersdk.APIAllowListTarget (nil ), allowList ... )
108+ }
109+
110+ res , err := client .CreateToken (inv .Context (), userID , req )
95111 if err != nil {
96112 return xerrors .Errorf ("create tokens: %w" , err )
97113 }
@@ -123,6 +139,16 @@ func (r *RootCmd) createToken() *serpent.Command {
123139 Description : "Specify the user to create the token for (Only works if logged in user is admin)." ,
124140 Value : serpent .StringOf (& user ),
125141 },
142+ {
143+ Flag : "scope" ,
144+ Description : "Repeatable scope to attach to the token (e.g. workspace:read)." ,
145+ Value : newScopeFlag (& scopes ),
146+ },
147+ {
148+ Flag : "allow" ,
149+ Description : "Repeatable allow-list entry (<type>:<uuid>, e.g. workspace:1234-...)." ,
150+ Value : newAllowListFlag (& allowList ),
151+ },
126152 }
127153
128154 return cmd
@@ -136,27 +162,59 @@ type tokenListRow struct {
136162 // For table format:
137163 ID string `json:"-" table:"id,default_sort"`
138164 TokenName string `json:"token_name" table:"name"`
165+ Scopes string `json:"-" table:"scopes"`
166+ Allow string `json:"-" table:"allow list"`
139167 LastUsed time.Time `json:"-" table:"last used"`
140168 ExpiresAt time.Time `json:"-" table:"expires at"`
141169 CreatedAt time.Time `json:"-" table:"created at"`
142170 Owner string `json:"-" table:"owner"`
143171}
144172
145173func tokenListRowFromToken (token codersdk.APIKeyWithOwner ) tokenListRow {
174+ return tokenListRowFromKey (token .APIKey , token .Username )
175+ }
176+
177+ func tokenListRowFromKey (token codersdk.APIKey , owner string ) tokenListRow {
146178 return tokenListRow {
147- APIKey : token . APIKey ,
179+ APIKey : token ,
148180 ID : token .ID ,
149181 TokenName : token .TokenName ,
182+ Scopes : joinScopes (token .Scopes ),
183+ Allow : joinAllowList (token .AllowList ),
150184 LastUsed : token .LastUsed ,
151185 ExpiresAt : token .ExpiresAt ,
152186 CreatedAt : token .CreatedAt ,
153- Owner : token . Username ,
187+ Owner : owner ,
154188 }
155189}
156190
191+ func joinScopes (scopes []codersdk.APIKeyScope ) string {
192+ if len (scopes ) == 0 {
193+ return ""
194+ }
195+ vals := make ([]string , len (scopes ))
196+ for i , scope := range scopes {
197+ vals [i ] = string (scope )
198+ }
199+ sort .Strings (vals )
200+ return strings .Join (vals , ", " )
201+ }
202+
203+ func joinAllowList (entries []codersdk.APIAllowListTarget ) string {
204+ if len (entries ) == 0 {
205+ return ""
206+ }
207+ vals := make ([]string , len (entries ))
208+ for i , entry := range entries {
209+ vals [i ] = entry .String ()
210+ }
211+ sort .Strings (vals )
212+ return strings .Join (vals , ", " )
213+ }
214+
157215func (r * RootCmd ) listTokens () * serpent.Command {
158216 // we only display the 'owner' column if the --all argument is passed in
159- defaultCols := []string {"id" , "name" , "last used" , "expires at" , "created at" }
217+ defaultCols := []string {"id" , "name" , "scopes" , "allow list" , " last used" , "expires at" , "created at" }
160218 if slices .Contains (os .Args , "-a" ) || slices .Contains (os .Args , "--all" ) {
161219 defaultCols = append (defaultCols , "owner" )
162220 }
@@ -226,6 +284,48 @@ func (r *RootCmd) listTokens() *serpent.Command {
226284 return cmd
227285}
228286
287+ func (r * RootCmd ) viewToken () * serpent.Command {
288+ formatter := cliui .NewOutputFormatter (
289+ cliui .TableFormat ([]tokenListRow {}, []string {"id" , "name" , "scopes" , "allow list" , "last used" , "expires at" , "created at" , "owner" }),
290+ cliui .JSONFormat (),
291+ )
292+
293+ cmd := & serpent.Command {
294+ Use : "view <name|id>" ,
295+ Short : "Display detailed information about a token" ,
296+ Middleware : serpent .Chain (
297+ serpent .RequireNArgs (1 ),
298+ ),
299+ Handler : func (inv * serpent.Invocation ) error {
300+ client , err := r .InitClient (inv )
301+ if err != nil {
302+ return err
303+ }
304+
305+ tokenName := inv .Args [0 ]
306+ token , err := client .APIKeyByName (inv .Context (), codersdk .Me , tokenName )
307+ if err != nil {
308+ maybeID := strings .Split (tokenName , "-" )[0 ]
309+ token , err = client .APIKeyByID (inv .Context (), codersdk .Me , maybeID )
310+ if err != nil {
311+ return xerrors .Errorf ("fetch api key by name or id: %w" , err )
312+ }
313+ }
314+
315+ row := tokenListRowFromKey (* token , "" )
316+ out , err := formatter .Format (inv .Context (), []tokenListRow {row })
317+ if err != nil {
318+ return err
319+ }
320+ _ , err = fmt .Fprintln (inv .Stdout , out )
321+ return err
322+ },
323+ }
324+
325+ formatter .AttachOptions (& cmd .Options )
326+ return cmd
327+ }
328+
229329func (r * RootCmd ) removeToken () * serpent.Command {
230330 cmd := & serpent.Command {
231331 Use : "remove <name|id|token>" ,
0 commit comments