Files
forge/cmd/admin_user_list_access_tokens.go
zeekayandHanzo Dev d85a6e9498 List and delete a user's access tokens from the CLI
Generating a token was a command; seeing what existed and revoking one were
not, so a token could only be created and never audited or withdrawn. Both
gaps are the same shape and both are closed here: list prints id, name, scope
and dates for one named user, and delete takes exactly one of --token-name or
--id and refuses an id that belongs to somebody else.

Scope stays narrow by construction: ListAccessTokensOptions requires a UserID,
so one user's tokens are the widest set either command can reach.

generate-access-token gains the initDB guard its siblings already had. Without
it the test harness — which supplies its own database — re-enters MustInstalled
and log.Fatal kills the process, so the new tests could not run against it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-20 13:50:06 -07:00

70 lines
1.8 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"errors"
"fmt"
"os"
"text/tabwriter"
auth_model "github.com/hanzoai/git/models/auth"
"github.com/hanzoai/git/models/db"
user_model "github.com/hanzoai/git/models/user"
"github.com/hanzoai/git/modules/setting"
"github.com/urfave/cli/v3"
)
func newUserListAccessTokensCommand() *cli.Command {
return &cli.Command{
Name: "list-access-tokens",
Usage: "List the access tokens of a specific user",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "username",
Aliases: []string{"u"},
Usage: "Username",
},
},
Action: runListAccessTokens,
}
}
func runListAccessTokens(ctx context.Context, c *cli.Command) error {
if !c.IsSet("username") {
return errors.New("you must provide a username to list the tokens of")
}
// The test harness builds its own database, so initDB there re-enters
// MustInstalled and kills the process before the command runs. Every other
// admin command guards it the same way.
if !setting.IsInTesting {
if err := initDB(ctx); err != nil {
return err
}
}
user, err := user_model.GetUserByName(ctx, c.String("username"))
if err != nil {
return err
}
// UserID is required by ListAccessTokensOptions.ToConds, so one user's
// tokens are the widest set this can ever return.
tokens, err := db.Find[auth_model.AccessToken](ctx, auth_model.ListAccessTokensOptions{UserID: user.ID})
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0)
fmt.Fprint(w, "ID\tName\tScope\tCreated\tLastUsed\n")
for _, t := range tokens {
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\n", t.ID, t.Name, t.Scope,
t.CreatedUnix.FormatDate(), t.UpdatedUnix.FormatDate())
}
return w.Flush()
}