auth: rename session identifiers to AuthSession forms · Entire
auth: rename session identifiers to AuthSession forms
5b21199→main·
toothbrush·1mo ago·7 files·+97 added/-97 removed
Apply the Auth-prefix renames across the moved files and call sites:
api.Session -> AuthSession, ListSessions -> ListAuthSessions, etc., plus
cli helpers (newAuthSessionsClient, defaultListAuthSessions, revokeAllAuthSessions, ...). JSON wire key stays "tokens".
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Sessions
c6c44b763a39View transcript
Changes
7
cmd/entire/cli
api
Mauth_sessions.go+22/-22
Mauth_sessions_test.go+21/-21
Mclient.go+8/-8
Mauth.go+21/-21
Mauth_test.go+6/-6
Mlogout.go+15/-15
Mlogout_test.go+4/-4
6 unmodified lines
7
8
9
10
10
11
12
13
14
15
15
16
17
18
3 unmodified lines
22
23
24
25
26
27
25
26
27
28
29
30
30
31
32
33
32
33
34
35
36
37
35
36
37
38
39
39
40
41
42
43
44
42
43
44
45
46
47
7 unmodified lines
55
56
57
58
58
59
60
61
62
63
64
65
65
66
67
68
67
68
69
70
71
9 unmodified lines
81
82
83
84
85
86
84
85
86
87
88
89
6 unmodified lines
"net/url"
// Session is a single active login session — an OAuth refresh-token family —
// AuthSession is a single active login session — an OAuth refresh-token family —
// returned by entire-core's session 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 list envelope's wire key
// is "tokens"; the rows are sessions.)
type Session struct {
type AuthSession struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Name string `json:"name"`
3 unmodified lines
CreatedAt string `json:"created_at"`
}
}
// SessionsResponse is the envelope returned by the list endpoint.
type SessionsResponse struct {
Sessions []Session `json:"tokens"`
// AuthSessionsResponse is the envelope returned by the list endpoint.
type AuthSessionsResponse struct {
Sessions []AuthSession `json:"tokens"`
}
// errSessionsPathUnset surfaces when a session method is called on a Client
// errAuthSessionsPathUnset surfaces when a session method is called on a Client
// that wasn't given a base path. Construct via
// NewClientWithBaseURL(...).WithSessionsPath(...).
var errSessionsPathUnset = errors.New("api: sessions path is unset (call (*Client).WithSessionsPath before list/revoke)")
// NewClientWithBaseURL(...).WithAuthSessionsPath(...).
var errAuthSessionsPathUnset = errors.New("api: auth sessions path is unset (call (*Client).WithAuthSessionsPath before list/revoke)")
func (c *Client) sessionsBasePath() (string, error) {
if c.sessionsPath == "" {
return "", errSessionsPathUnset
func (c *Client) authSessionsBasePath() (string, error) {
if c.authSessionsPath == "" {
return "", errAuthSessionsPathUnset
}
return c.sessionsPath, nil
return c.authSessionsPath, nil
}
// ListSessions returns the authenticated user's active login sessions.
func (c *Client) ListSessions(ctx context.Context) ([]Session, error) {
base, err := c.sessionsBasePath()
// ListAuthSessions returns the authenticated user's active login sessions.
func (c *Client) ListAuthSessions(ctx context.Context) ([]AuthSession, error) {
base, err := c.authSessionsBasePath()
if err != nil {
return nil, fmt.Errorf("list sessions: %w", err)
}
var out SessionsResponse
var out AuthSessionsResponse
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
// RevokeCurrentAuthSession 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.sessionsBasePath()
func (c *Client) RevokeCurrentAuthSession(ctx context.Context) error {
base, err := c.authSessionsBasePath()
if err != nil {
return fmt.Errorf("revoke current session: %w", err)
}
// RevokeSession revokes the login session with the given id.
func (c *Client) RevokeSession(ctx context.Context, id string) error {
base, err := c.sessionsBasePath()
// RevokeAuthSession revokes the login session with the given id.
func (c *Client) RevokeAuthSession(ctx context.Context, id string) error {
base, err := c.authSessionsBasePath()
if err != nil {
return fmt.Errorf("revoke session %s: %w", id, err)
}
}
Mcmd/entire/cli/api/auth_sessions.go+22/-22
8 unmodified lines
9 10 11 12 12 13 14 15 7 unmodified lines
23 24 25 26 26 27 28 29 30 29 30 31 32 33 7 unmodified lines
41 42 43 44 44 45 46 47 3 unmodified lines
51 52 53 54 54 55 56 57 57 58 59 60 9 unmodified lines
70 71 72 73 73 74 75 76 10 unmodified lines
87 88 89 90 90 91 92 93 93 94 95 95 96 97 98 20 unmodified lines
119 120 121 122 122 123 124 125 3 unmodified lines
129 130 131 132 132 133 134 135 135 136 137 138 2 unmodified lines
141 142 143 144 144 145 146 147 7 unmodified lines
155 156 157 158 158 159 160 161 162 163 162 163 164 165 166 7 unmodified lines
174 175 176 177 177 178 179 180 3 unmodified lines
184 185 186 187 187 188 189 190 190 191 192 193
8 unmodified lines
testing )
func TestClient_RevokeCurrentSession_SendsDeleteWithBearer(t *testing.T) { func TestClient_RevokeCurrentAuthSession_SendsDeleteWithBearer(t *testing.T) { t.Parallel()
var gotMethod, gotPath, gotAuth string 7 unmodified lines
})) defer server.Close()
c := NewClient("tok").WithSessionsPath("/api/auth/tokens") c := NewClient("tok").WithAuthSessionsPath("/api/auth/tokens") c.baseURL = server.URL
if err := c.RevokeCurrentSession(context.Background()); err != nil { t.Fatalf("RevokeCurrentSession() error = %v", err) if err := c.RevokeCurrentAuthSession(context.Background()); err != nil { t.Fatalf("RevokeCurrentAuthSession() error = %v", err) }
if gotMethod != http.MethodDelete { 7 unmodified lines
}
func TestClient_RevokeCurrentSession_ReturnsHTTPErrorOn401(t *testing.T) { func TestClient_RevokeCurrentAuthSession_ReturnsHTTPErrorOn401(t *testing.T) { t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 3 unmodified lines
})) defer server.Close()
c := NewClient("tok").WithSessionsPath("/api/auth/tokens") c := NewClient("tok").WithAuthSessionsPath("/api/auth/tokens") c.baseURL = server.URL
err := c.RevokeCurrentSession(context.Background()) err := c.RevokeCurrentAuthSession(context.Background()) if err == nil { t.Fatal("expected error for 401 response") } }
func TestClient_ListSessions_DecodesResponse(t *testing.T) { func TestClient_ListAuthSessions_DecodesResponse(t *testing.T) { t.Parallel()
var gotMethod, gotPath, gotAuth string 10 unmodified lines
})) defer server.Close()
c := NewClient("tok").WithSessionsPath("/api/auth/tokens") c := NewClient("tok").WithAuthSessionsPath("/api/auth/tokens") c.baseURL = server.URL
tokens, err := c.ListSessions(context.Background()) tokens, err := c.ListAuthSessions(context.Background()) if err != nil { t.Fatalf("ListSessions() error = %v", err) t.Fatalf("ListAuthSessions() error = %v", err) }
if gotMethod != http.MethodGet { 20 unmodified lines
}
func TestClient_ListSessions_ReturnsHTTPErrorOn401(t *testing.T) { func TestClient_ListAuthSessions_ReturnsHTTPErrorOn401(t *testing.T) { t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 3 unmodified lines
})) defer server.Close()
c := NewClient("tok").WithSessionsPath("/api/auth/tokens") c := NewClient("tok").WithAuthSessionsPath("/api/auth/tokens") c.baseURL = server.URL
_, err := c.ListSessions(context.Background()) _, err := c.ListAuthSessions(context.Background()) if err == nil { t.Fatal("expected error for 401") } }
func TestClient_RevokeSession_SendsDeleteWithEscapedID(t *testing.T) { func TestClient_RevokeAuthSession_SendsDeleteWithEscapedID(t *testing.T) { t.Parallel()
var gotMethod, gotEscapedPath, gotDecodedPath string 7 unmodified lines
})) defer server.Close()
c := NewClient("tok").WithSessionsPath("/api/auth/tokens") c := NewClient("tok").WithAuthSessionsPath("/api/auth/tokens") c.baseURL = server.URL
// Use an id that needs URL escaping to verify we don't blindly concat. if err := c.RevokeSession(context.Background(), "abc/def 1"); err != nil { t.Fatalf("RevokeSession() error = %v", err) if err := c.RevokeAuthSession(context.Background(), "abc/def 1"); err != nil { t.Fatalf("RevokeAuthSession() error = %v", err) }
if gotMethod != http.MethodDelete { 7 unmodified lines
}
func TestClient_RevokeSession_ReturnsErrorBody(t *testing.T) { func TestClient_RevokeAuthSession_ReturnsErrorBody(t *testing.T) { t.Parallel()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 3 unmodified lines
})) defer server.Close()
c := NewClient("tok").WithSessionsPath("/api/auth/tokens") c := NewClient("tok").WithAuthSessionsPath("/api/auth/tokens") c.baseURL = server.URL
err := c.RevokeSession(context.Background(), "missing") err := c.RevokeAuthSession(context.Background(), "missing") if err == nil { t.Fatal("expected error for 404") }
Mcmd/entire/cli/api/auth_sessions_test.go+21/-21
21 unmodified lines
22 23 24 25 26 25 26 27 28 29 29 30 31 32 33 32 33 34 35 36 37 38 36 37 38 39 40 41
21 unmodified lines
httpClient *http.Client baseURL string
// sessionsPath is the base path for entire-core's login-session // endpoints (list / revoke / current). Set via WithSessionsPath when the // authSessionsPath is the base path for entire-core's login-session // endpoints (list / revoke / current). Set via WithAuthSessionsPath when the // client targets the auth host; empty otherwise, and the session methods // error out if called against an empty path. sessionsPath string authSessionsPath string }
// WithSessionsPath sets the base path used by ListSessions, // RevokeCurrentSession, and RevokeSession. Returns the receiver for chaining // WithAuthSessionsPath sets the base path used by ListAuthSessions, // RevokeCurrentAuthSession, and RevokeAuthSession. Returns the receiver for chaining // at construction: // // c := api.NewClientWithBaseURL(token, base).WithSessionsPath(p) func (c *Client) WithSessionsPath(path string) *Client { c.sessionsPath = path // c := api.NewClientWithBaseURL(token, base).WithAuthSessionsPath(p) func (c *Client) WithAuthSessionsPath(path string) *Client { c.authSessionsPath = path return c }
Mcmd/entire/cli/api/client.go+8/-8
17 unmodified lines
18 19 20 21 21 22 23 24 25 26 26 27 28 29 36 unmodified lines
66 67 68 69 70 69 70 71 72 73 74 75 74 75 76 77 78 96 unmodified lines
175 176 177 178 178 179 180 181 14 unmodified lines
196 197 198 199 199 200 201 202 201 202 203 204 205 60 unmodified lines
266 267 268 269 270 271 269 270 271 272 273 274 275 276 277 278 278 279 280 281 24 unmodified lines
306 307 308 309 309 310 311 311 312 313 314 102 unmodified lines
417 418 419 420 420 421 422 423 423 424 425 426 12 unmodified lines
439 440 441 442 442 443 444 445 445 446 447 448 6 unmodified lines
455 456 457 458 458 459 460 461
17 unmodified lines
"github.com/spf13/cobra" // coreSessionsPath is entire-core's login-session endpoint family // coreAuthSessionsPath is entire-core's login-session endpoint family // (list / revoke / current) on the auth host. Sessions are OAuth // refresh-token families; the CLI authenticates against them with its core // JWT. Session management must target the auth host (entire-core), never the // data host. const coreSessionsPath = "/api/auth/tokens" const coreAuthSessionsPath = "/api/auth/tokens"
// User-visible placeholder strings. lastUsedJustNow is consumed by // formatRelativeDuration in status.go. 36 unmodified lines
return nil } // newSessionsClient builds an api.Client for entire-core's login-session // endpoints (coreSessionsPath) on coreURL, authenticated with the // newAuthSessionsClient builds an api.Client for entire-core's login-session // endpoints (coreAuthSessionsPath) on coreURL, authenticated with the // session-scoped login JWT. coreURL is the active context's CoreURL (or the // configured auth host when no context is active) — session management always // targets a login server, never the data host. func newSessionsClient(coreURL, token string) *api.Client { return api.NewClientWithBaseURL(token, coreURL).WithSessionsPath(coreSessionsPath) func newAuthSessionsClient(coreURL, token string) *api.Client { return api.NewClientWithBaseURL(token, coreURL).WithAuthSessionsPath(coreAuthSessionsPath) } // resolveAuthHostToken returns a bearer scoped for the auth host (entire-core). 96 unmodified lines
return fmt.Errorf("context core URL check: %w", err) } } return runAuthStatus(cmd.Context(), cmd.OutOrStdout(), defaultFetchProfile, defaultListSessions, target) return runAuthStatus(cmd.Context(), cmd.OutOrStdout(), defaultFetchProfile, defaultListAuthSessions, target) }, } addInsecureHTTPAuthFlag(cmd, &insecureHTTPAuth) // with token. Injected so status stays unit-testable without a live core. type profileFetcher func(ctx context.Context, coreURL, token string) (*authProfile, error) // sessionLister lists the active login sessions on coreURL (the user's // authSessionLister lists the active login sessions on coreURL (the user's // refresh-token families). Injected for testability; production wires // defaultListSessions. type sessionLister func(ctx context.Context, coreURL, token string) ([]api.Session, error) // defaultListAuthSessions. type authSessionLister func(ctx context.Context, coreURL, token string) ([]api.AuthSession, error) // contextsProvider returns the stored login contexts and the active context // name. Injected for testability; production wires auth.Contexts. 60 unmodified lines
return p, nil
// defaultListSessions lists the user's active login sessions on coreURL.
func defaultListSessions(ctx context.Context, coreURL, token string) ([]api.Session, error) {
return newSessionsClient(coreURL, token).ListSessions(ctx) //nolint:wrapcheck // ListSessions already wraps with action context
// defaultListAuthSessions lists the user's active login sessions on coreURL.
func defaultListAuthSessions(ctx context.Context, coreURL, token string) ([]api.AuthSession, error) {
return newAuthSessionsClient(coreURL, token).ListAuthSessions(ctx) //nolint:wrapcheck // ListSessions already wraps with action context
// runAuthStatus reports auth state against the target core: GET /me validates
// the token and supplies the profile header, the active login context is shown
// locally, and the active sessions (refresh-token families) on that core are
// listed so the effect of logout / logout --everywhere is visible.
func runAuthStatus(ctx context.Context, w io.Writer, fetchProfile profileFetcher, listSessions sessionLister, t statusTarget) error {
func runAuthStatus(ctx context.Context, w io.Writer, fetchProfile profileFetcher, listSessions authSessionLister, t statusTarget) error {
if t.token == "" {
fmt.Fprintf(w, "Not logged in to %s\n", t.coreURL)
fmt.Fprintln(w, "Run 'entire login' to authenticate.")
}
case serr != nil:
fmt.Fprintf(w, "\n(could not list active sessions: %v)\n", serr)
case len(sessions) > 0:
sortSessionsByRecency(sessions)
sortAuthSessionsByRecency(sessions)
fmt.Fprintf(w, "\nActive sessions (%d):\n", len(sessions))
renderSessionsTable(w, newAuthTableStyles(w), sessions)
renderAuthSessionsTable(w, newAuthTableStyles(w), sessions)
fmt.Fprintln(w, "\nRun 'entire logout' to end this session, or 'entire logout --everywhere' to end all of them.")
}
102 unmodified lines
return s // renderSessionsTable prints the active login sessions as an aligned table. // renderAuthSessionsTable prints the active login sessions as an aligned table. // No id column: there's no per-session CLI action (revoke-by-id is gone), so // NAME/CREATED/LAST USED/EXPIRES is what's useful. func renderSessionsTable(w io.Writer, sty authTableStyles, sessions []api.Session) { func renderAuthSessionsTable(w io.Writer, sty authTableStyles, sessions []api.AuthSession) { header := []string{ sty.render(sty.header, "NAME"), sty.render(sty.header, "CREATED"), 12 unmodified lines
renderAlignedTable(w, header, rows) // sortSessionsByRecency orders sessions most-recently-used first, then most // sortAuthSessionsByRecency orders sessions most-recently-used first, then most // recently created, then by id — a fully specified order independent of the // server's response ordering. func sortSessionsByRecency(sessions []api.Session) { func sortAuthSessionsByRecency(sessions []api.AuthSession) { sort.Slice(sessions, func(i, j int) bool { li, lj := lastUsedSortKey(sessions[i]), lastUsedSortKey(sessions[j]) if li != lj { 6 unmodified lines }
func lastUsedSortKey(s api.Session) string { func lastUsedSortKey(s api.AuthSession) string { if s.LastUsedAt == nil { return "" }
Mcmd/entire/cli/auth.go+21/-21
47 unmodified lines
48 49 50 51 52 51 52 53 54 55 42 unmodified lines
98 99 100 101 101 102 103 104 105 105 106 107 108 21 unmodified lines
130 131 132 133 133 134 135 136 140 unmodified lines
277 278 279 280 280 281 282 283
47 unmodified lines
return func(context.Context, string, string) (*authProfile, error) { return nil, err } // noSessions is a sessionLister returning an empty list (no table rendered). func noSessions(context.Context, string, string) ([]api.Session, error) { return nil, nil } // noSessions is a authSessionLister returning an empty list (no table rendered). func noSessions(context.Context, string, string) ([]api.AuthSession, error) { return nil, nil }
func TestRunAuthStatus_NotLoggedIn(t *testing.T) { t.Parallel() 42 unmodified lines target := statusTarget{coreURL: testCoreURL, token: "tok", activeContext: "eu.auth.entire.io", totalContexts: 1} lastUsed := "2026-05-01T00:00:00Z" listSessions := func(_ context.Context, coreURL, token string) { listSessions := func(_ context.Context, coreURL, token string) { if coreURL != testCoreURL || token != "tok" { t.Errorf("listSessions called with (%q, %q), want the active core+token", coreURL, token) } return []api.Session{ return []api.AuthSession{ {ID: "fam-1", Name: "OIDC login", CreatedAt: "2026-01-01T00:00:00Z", ExpiresAt: "2026-12-01T00:00:00Z", LastUsedAt: &lastUsed}, {ID: "fam-2", Name: "OIDC login", CreatedAt: "2026-02-01T00:00:00Z", ExpiresAt: "2026-12-15T00:00:00Z"}, }, nil 21 unmodified lines } t.Parallel()
target := statusTarget{coreURL: testCoreURL, token: "tok", activeContext: "eu.auth.entire.io", totalContexts: 1} listSessions := func(context.Context, string, string) ([]api.Session, error) { listSessions := func(context.Context, string, string) ([]api.AuthSession, error) { return nil, errors.New("sessions endpoint unreachable") }
140 unmodified lines
// tokenmanager.Manager via auth.SetManagerForTest and stub only the // STS wire call via SetExchangeForTest. That covers the audience- // matching logic the function-injection tests above can't reach // (defaultRevokeCurrentSession / defaultRevokeAllSessions call // (revokeCurrentAuthSession / revokeAllAuthSessions call // resolveAuthHostToken directly, but unit tests for the surrounding flows // inject fakes that bypass it).
Mcmd/entire/cli/logout.go+15/-15
465 unmodified lines
466 467 468 469 469 470 471 472 472 473 474 475 474 475 476 477 478
465 unmodified lines
rec.mu.Lock()
defer rec.mu.Unlock()
switch {
case r.Method == http.MethodGet && r.URL.Path == coreSessionsPath:
case r.Method == http.MethodGet && r.URL.Path == coreAuthSessionsPath:
rec.listCount++
fmt.Fprint(w, {"tokens":[{"id":"s1"},{"id":"s2"}]})
case r.Method == http.MethodDelete && r.URL.Path == coreSessionsPath+"/current":
case r.Method == http.MethodDelete && r.URL.Path == coreAuthSessionsPath+"/current":
rec.deleteCurrent++
case r.Method == http.MethodDelete && strings.HasPrefix(r.URL.Path, coreSessionsPath+"/"):
rec.deleteByID = append(rec.deleteByID, strings.TrimPrefix(r.URL.Path, coreSessionsPath+"/"))
case r.Method == http.MethodDelete && strings.HasPrefix(r.URL.Path, coreAuthSessionsPath+"/"):
rec.deleteByID = append(rec.deleteByID, strings.TrimPrefix(r.URL.Path, coreAuthSessionsPath+"/"))
default:
w.WriteHeader(http.StatusNotFound)
}