# auth: burn all PAT machinery; sessions/logout target entire-core only

`c36b9bb`→[main](/content/gh/entireio/cli/commits/main/index.html)·

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

```go
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
}
```

```go
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)
	}
}
```
