Commit 2ddf2389c0

2ddf2389c07e159ab276b0889c007792799b0124

parent: 03040d2164

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-28 07:42 UTC

control: token list uses expiresText, not relAge, for expiry

relAge clamps a future time to zero and prints "expires just now" for
a token that has not expired yet. expiresText (added for keys list)
renders expiry as an absolute stamp instead.

Closes #286

Layout: unified · split

internal/control/token.go +2 −10
@@ -122,19 +122,11 @@ func runTokenList(c *Ctx, args []string) int {
122122 for _, t := range tokens {
123123 ds = append(ds, out{t.Name, t.Scope, t.CreatedAt, t.ExpiresAt, t.LastUsedAt, t.CreatedBy})
124124 }
125 now := time.Now()
125126 return c.emit(ds, func(w io.Writer) {
126127 tb := c.table(w, "NAME", "SCOPE", "EXPIRES")
127128 for _, d := range ds {
128 exp := "never expires"
129 if d.ExpiresAt != nil {
130 ts := d.ExpiresAt.UTC().Format(time.RFC3339Nano)
131 if c.Term.Cols == 0 {
132 exp = "expires " + stamp(ts)
133 } else {
134 exp = "expires " + relAge(ts, termNow())
135 }
136 }
137 tb.row(cRef(d.Name), cState(d.Scope), cText(exp))
129 tb.row(cRef(d.Name), cState(d.Scope), cText(expiresText(d.ExpiresAt, now)))
138130 }
139131 tb.flush()
140132 })
internal/control/token_test.go +24
@@ -45,6 +45,30 @@ func TestExpiringCredentialCannotMint(t *testing.T) {
4545 }
4646}
4747
48// #286: at a terminal, a token expiring in the future must not render
49// through relAge, which clamps a future time to zero and prints
50// "expires just now".
51func TestTokenListFutureExpiryAtTerminal(t *testing.T) {
52 st, _, uid := newQueueTestRepo(t)
53 exp := time.Now().Add(90 * 24 * time.Hour)
54 if err := st.CreateAPIToken(uid, "laptop", "h-laptop", "read", &exp, 0); err != nil {
55 t.Fatal(err)
56 }
57 c, errOut := pruneCtx(st, t.TempDir(), store.User{ID: uid, Username: "alice"})
58 c.Term = Term{Cols: 80}
59 var out bytes.Buffer
60 c.Stdout = &out
61 if code := Dispatch(c, []string{"token", "list"}); code != protocol.ExitOK {
62 t.Fatalf("exit %d: %s", code, errOut)
63 }
64 if strings.Contains(out.String(), "just now") {
65 t.Fatalf("token list at a terminal printed \"just now\" for a future expiry:\n%s", out.String())
66 }
67 if !strings.Contains(out.String(), "expires ") {
68 t.Fatalf("token list:\n%s", out.String())
69 }
70}
71
4872func TestTokenCreateDefaultsToReadAndRecordsCreator(t *testing.T) {
4973 st, _, uid := newQueueTestRepo(t)
5074 if err := st.CreateAPIToken(uid, "parent", "h-parent", "full", nil, 0); err != nil {