mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-10 15:05:40 +00:00
4a4a912fa6
Since #1267 the server refuses a mint authenticated by an API token with HTTP 403 `session_required` — "Creating or rotating API tokens requires an interactive session, not an API token" — because the tokens such a mint produces outlive the revocation of the token that made them: each has its own name and expiry, and nothing in `pad token list` records which token minted which. `list` and `revoke` stay reachable by a PAT deliberately, since revocation is the response to a compromised credential and should not need a fresh login. Verified against `handlers_tokens.go::requireInteractiveSession` before writing it, as the item asked: the code and the message are quoted from there, and the guard is `isAPITokenAuth`, which is false for a session cookie AND for a saved `padsess_` CLI bearer — a CLI session IS an interactive session. Three artifacts a reader consumes carried the now-false implication; two are fixed here. - The README's PAD_TOKEN section said the override authenticates "without `pad auth login`" and named `pad token create` as where tokens come from, which together read as "you can mint under the override". It now names the one exception and links to the paragraph. - The token-management section gets that paragraph: which subcommands need a session, the exact code and message, why, and that `pad auth login -i` is the headless path. - `internal/cli/client_tokens.go` claimed the PAD_TOKEN override was "usable end-to-end without a browser". True when written and false after the gate. Corrected in place with the reason and a pointer to the guard, rather than deleted — a comment that was true once is worth more as a dated correction than as a gap. THREE CLAIMS I HAD WRONG, all caught before merge and all by checking rather than by rereading: - "needs a terminal but not a browser" — `pad auth login` DEFAULTS to browser-based auth; `-i` is the email/password prompt. The README and the comment name the flag now. - "fails when `PAD_TOKEN` is set" — too broad. `PAD_TOKEN` accepts a `padsess_` session token as well as a `pad_` API token (`env_token.go:21-23`), and the session form mints normally. The gate is on the CREDENTIAL KIND, not on the variable. - The history was compressed into "#879, before #1267". #879 added the override and left minting web-only; #1237 (f262449b) added `pad token` so the CLI could mint at all; #1267 (cdc5b301) then gated it. Both commits verified by `git log -S` rather than taken from the review that flagged it. Deliberately not done: no help text added to `pad token create`. The 403's own message already names the cause and the fix at the point of failure, and the item scoped this to documentation. `CLAUDE.md:127` describes `pad auth login` as an email/password prompt, which is wrong about today's code — that is a separate one-line change, not this one. Gates: go test exit 0 with no FAIL, golangci-lint 0 issues, gofmt clean, vitest 153 files / 2342 tests passed (unaffected — the only code change is a comment). Claude-Session: https://claude.ai/code/session_01WS9QAnxk1gA3LBha3PvKVm
51 lines
2.0 KiB
Go
51 lines
2.0 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))
|
|
}
|