auth: burn all PAT machinery; sessions/logout target entire-core only · Entire
auth: burn all PAT machinery; sessions/logout target entire-core only
c36b9bb→main·
toothbrush·1mo ago·8 files·+316 added/-340 removed
entire.io's ent_ personal-access-token surface (/api/v1/auth/tokens) is being sunset, and the CLI never used it for auth — it authenticates with the core JWT. Remove every trace from this repo:
- Drop the dead Provider.AuthTokensPath field (and its /api/v1/auth/tokens values + tests); nothing references the entire.io PAT path anymore.
- Rename the api.Client session plumbing off PAT-era naming: api/auth_tokens.go -> api/sessions.go, WithAuthTokensPath -> WithSessionsPath, authTokensPath -> sessionsPath, errAuthTokensPathUnset -> errSessionsPathUnset.
- Scrub PAT / ent_ / personal-access-token mentions from comments.
Session management (auth status liveness via /me, logout revocation) targets entire-core's /api/auth/tokens on the auth host (api.AuthBaseURL()) with the session-scoped core JWT — never entire.io's PAT endpoint, so the 400 from that endpoint cannot recur.
Sessions
5969a20f41e3View transcript
Changes
8
cmd/entire/cli
api
Dauth_tokens.go-103
Dauth_tokens_test.go-200
Mclient.go+11/-15
Asessions.go+100
Asessions_test.go+200
Mauth.go+5/-6
auth
Mprovider.go-12
Mprovider_test.go-4
package api
import (
"context"
"errors"
"fmt"
"net/url"
)
// Session is a single active login session — an OAuth refresh-token family —
// returned by the auth-tokens endpoint. One is created per `entire login`,
// across all of a user's devices. Plaintext token values are never returned by
// the server, only metadata. (The wire endpoint is historically named
// "tokens"; these rows are sessions, not personal access tokens.)
type Session struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Name string `json:"name"`
Scope string `json:"scope"`
ExpiresAt string `json:"expires_at"`
LastUsedAt *string `json:"last_used_at"`
CreatedAt string `json:"created_at"`
}
// SessionsResponse is the envelope returned by the list endpoint. The wire key
// stays "tokens" — that is the server's contract.
type SessionsResponse struct {
Sessions []Session `json:"tokens"`
}
// errAuthTokensPathUnset surfaces when a session method is called on a
// Client that wasn't given a base path. Construct via
// NewClientWithBaseURL(...).WithAuthTokensPath(...) — the
// active path lives in cmd/entire/cli/auth.CurrentProvider().AuthTokensPath, the
// single source of truth for provider-version routing.
var errAuthTokensPathUnset = errors.New("api: auth-tokens path is unset (call (*Client).WithAuthTokensPath before list/revoke)")
func (c *Client) authTokensBasePath() (string, error) {
if c.authTokensPath == "" {
return "", errAuthTokensPathUnset
}
return c.authTokensPath, nil
}
// ListSessions returns the authenticated user's active login sessions.
func (c *Client) ListSessions(ctx context.Context) ([]Session, error) {
base, err := c.authTokensBasePath()
if err != nil {
return nil, fmt.Errorf("list sessions: %w", err)
}
resp, err := c.Get(ctx, base)
if err != nil {
return nil, fmt.Errorf("list sessions: %w", err)
}
defer resp.Body.Close()
if err := CheckResponse(resp); err != nil {
return nil, fmt.Errorf("list sessions: %w", err)
}
var out SessionsResponse
if err := DecodeJSON(resp, &out); err != nil {
return nil, fmt.Errorf("list sessions: %w", err)
}
return out.Sessions, nil
}
// RevokeCurrentSession revokes the login session this client is authenticating
// with (the family the current bearer belongs to).
func (c *Client) RevokeCurrentSession(ctx context.Context) error {
base, err := c.authTokensBasePath()
if err != nil {
return fmt.Errorf("revoke current session: %w", err)
}
resp, err := c.Delete(ctx, base+"/current")
if err != nil {
return fmt.Errorf("revoke current session: %w", err)
}
defer resp.Body.Close()
if err := CheckResponse(resp); err != nil {
return fmt.Errorf("revoke current session: %w", err)
}
return nil
}
// RevokeSession revokes the login session with the given id.
func (c *Client) RevokeSession(ctx context.Context, id string) error {
base, err := c.authTokensBasePath()
if err != nil {
return fmt.Errorf("revoke session %s: %w", id, err)
}
resp, err := c.Delete(ctx, base+"/"+url.PathEscape(id))
if err != nil {
return fmt.Errorf("revoke session %s: %w", id, err)
}
defer resp.Body.Close()
if err := CheckResponse(resp); err != nil {
return fmt.Errorf("revoke session %s: %w", id, err)
}
return nil
}
package api
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestClient_RevokeCurrentSession_SendsDeleteWithBearer(t *testing.T) {
t.Parallel()
var gotMethod, gotPath, gotAuth string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotMethod = r.Method
gotPath = r.URL.Path
gotAuth = r.Header.Get("Authorization")
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"success":true}`)) //nolint:errcheck // test handler
}))
defer server.Close()
c := NewClient("tok").WithAuthTokensPath("/api/v1/auth/tokens")
c.baseURL = server.URL
if err := c.RevokeCurrentSession(context.Background()); err != nil {
t.Fatalf("RevokeCurrentSession() error = %v", err)
}
if gotMethod != http.MethodDelete {
t.Errorf("method = %q, want DELETE", gotMethod)
}
if gotPath != "/api/v1/auth/tokens/current" {
t.Errorf("path = %q, want /api/v1/auth/tokens/current", gotPath)
}
if gotAuth != testBearerHeader {
t.Errorf("Authorization = %q, want %q", gotAuth, testBearerHeader)
}
}