mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-26 22:37:03 +00:00
1a041b33ff
The rotate endpoint (POST /auth/tokens/{id}/rotate) existed server-side
with no CLI in front of it; the #1237 review asked for it as its own PR.
- exact id only, same discipline as revoke: a typo is the server's 404
surfaced, never a wrong token rotated (which kills a live secret
somewhere)
- the new secret prints exactly once (count-pinned) with the store-it-now
notice, and the output says plainly that the old secret already stopped
working — RotateAPIToken replaces the hash in place, no grace window,
so the wording must not imply one
- --expires-in <days> posts a new expiry (server-capped by max lifetime);
without it no expires_in is posted and the original expiry is preserved
(test-pinned both ways)
- README documents rotate beside create/list/revoke, including that
rotate shares create's session_required gate (#1267)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
69 lines
2.8 KiB
Go
69 lines
2.8 KiB
Go
package cli
|
|
|
|
import (
|
|
"net/url"
|
|
|
|
"github.com/PerpetualSoftware/pad/internal/models"
|
|
)
|
|
|
|
// User-scoped API-token endpoints (GET/POST /auth/tokens,
|
|
// DELETE /auth/tokens/{id}). These are the mint/list/revoke calls behind
|
|
// `pad token` — the CLI counterpart to the web settings page, so a headless
|
|
// agent setup can mint from a terminal (`pad auth login -i` prompts for
|
|
// email/password rather than opening a browser).
|
|
//
|
|
// NOT usable end-to-end when PAD_TOKEN carries a `pad_` API token, which this
|
|
// comment used to claim without that qualifier: #879 added the override and
|
|
// left minting web-only, #1237 added this group so the CLI could mint at all,
|
|
// and #1267 then gated minting on a session. The server refuses a mint
|
|
// authenticated by an API token with 403 `session_required`, because the tokens
|
|
// such a mint produces outlive the revocation of the token that made them.
|
|
// `CreateUserToken` therefore needs a session — a cookie, or a `padsess_`
|
|
// bearer, which PAD_TOKEN also accepts — while list and revoke stay reachable
|
|
// by a PAT. See `handlers_tokens.go::requireInteractiveSession`.
|
|
|
|
// ListUserTokens returns the caller's API tokens. Metadata only — the
|
|
// server never returns secret material on list.
|
|
func (c *Client) ListUserTokens() ([]models.APIToken, error) {
|
|
var out []models.APIToken
|
|
if err := c.get("/auth/tokens", &out); err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// CreateUserToken mints a new API token owned by the authenticated user.
|
|
// The response carries the plaintext secret exactly once; it is never
|
|
// retrievable again.
|
|
func (c *Client) CreateUserToken(input models.APITokenCreate) (*models.APITokenWithSecret, error) {
|
|
var out models.APITokenWithSecret
|
|
if err := c.post("/auth/tokens", input, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
return &out, nil
|
|
}
|
|
|
|
// RevokeUserToken deletes an API token by id. The server verifies the
|
|
// token belongs to the caller; an unknown or foreign id is a 404.
|
|
func (c *Client) RevokeUserToken(id string) error {
|
|
return c.delete("/auth/tokens/" + url.PathEscape(id))
|
|
}
|
|
|
|
// RotateUserToken generates a new secret for an existing token, keeping
|
|
// its metadata (name, scopes, workspace). The OLD secret dies with the
|
|
// UPDATE — RotateAPIToken replaces the hash in place, so there is no
|
|
// grace window. expiresIn > 0 sets a new expiry in days (capped by the
|
|
// platform max lifetime); 0 preserves the original. Like minting, this
|
|
// requires an interactive session (#1267) — a PAT-authenticated call is
|
|
// refused 403 session_required.
|
|
func (c *Client) RotateUserToken(id string, expiresIn int) (*models.APITokenWithSecret, error) {
|
|
input := struct {
|
|
ExpiresIn int `json:"expires_in,omitempty"`
|
|
}{ExpiresIn: expiresIn}
|
|
var out models.APITokenWithSecret
|
|
if err := c.post("/auth/tokens/"+url.PathEscape(id)+"/rotate", input, &out); err != nil {
|
|
return nil, err
|
|
}
|
|
return &out, nil
|
|
}
|