Skip to content

Commit 1a7e93a

Browse files
committed
Point logout and revoke at this connection, not the whole agent
Basecamp is about to let a personal agent hold several connections, one per computer or profile, each with its own secret. Then "disconnect the agent" is My > Agent's Disconnect all, which ends every computer, and rotating the client secret rewrites only the first connection's. auth logout and auth revoke now say to disconnect this connection in Basecamp. That is right today as well: an agent has one connection, and disconnecting it is what My > Agent's Disconnect does. The README says where to find it for a personal agent and a shared one, and that Disconnect all, once My > Agent lists several, ends every one.
1 parent babca54 commit 1a7e93a

5 files changed

Lines changed: 18 additions & 13 deletions

File tree

‎README.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,11 @@ op read "op://Vault/Item/credential" | basecamp auth login --with-client-credent
239239
The login mints once, which is what proves the client id and secret: a refused
240240
mint stores nothing. `--account` is required when the profile does not exist
241241
yet. `basecamp auth logout` forgets the credential; there is no useful
242-
revocation, since the same client would mint another — disconnect the agent in
243-
Basecamp to end its access, which kills its client secret and every token
244-
minted from it.
242+
revocation, since the same client would mint another. To end its access,
243+
disconnect its connection in Basecamp (on My > Agent for your own agent, in
244+
Adminland for a shared one), which kills that connection's secret and every
245+
token minted from it. Where My > Agent lists several connections, disconnect
246+
the one this profile used: Disconnect all ends every one of them.
245247

246248
### Connecting an agent to this computer
247249

‎internal/auth/agent_test.go‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -392,9 +392,9 @@ func TestAgentMintRefusesARedirect(t *testing.T) {
392392
assert.Zero(t, *sinkCalls, "the client secret followed a redirect")
393393
}
394394

395-
// TestAgentLogoutSaysRotatingTheSecretIsTheRemedy: revoking one self-token
395+
// TestAgentLogoutForgetsWithoutRevoking: revoking one self-token
396396
// accomplishes nothing while the client that minted it can mint another.
397-
func TestAgentLogoutSaysRotatingTheSecretIsTheRemedy(t *testing.T) {
397+
func TestAgentLogoutForgetsWithoutRevoking(t *testing.T) {
398398
as := startDeviceAS(t)
399399
m := newDeviceTestManager(t, as.srv.URL)
400400
key := storeAgent(t, m, agentCredential(as.srv.URL+"/oauth/token", time.Now().Add(time.Hour)))
@@ -421,7 +421,7 @@ func TestAgentRevokeIsRefusedAndKeepsTheCredential(t *testing.T) {
421421
err := m.RevokeStored(context.Background())
422422
require.Error(t, err)
423423
assert.Equal(t, output.CodeUsage, output.AsError(err).Code)
424-
assert.Contains(t, err.Error(), "disconnect the agent in Basecamp")
424+
assert.Contains(t, output.AsError(err).Hint, "disconnect this connection in Basecamp")
425425
assert.Empty(t, as.revokeCalls(), "a revocation was sent for an agent self-token")
426426

427427
creds, loadErr := m.store.Load(key)

‎internal/auth/revoke.go‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,13 @@ const (
4444
RevokeSkippedImported = "imported_token"
4545
// RevokeSkippedAgent: an agent self-token is minted on demand from a
4646
// client id and secret, so revoking one accomplishes nothing — the
47-
// same client mints another on the next command. Ending an agent's
48-
// access means disconnecting it in Basecamp, which kills the client
49-
// secret and every token minted from it. (Basecamp has no separate
50-
// "rotate secret": reconnecting is the rotation.)
47+
// same client mints another on the next command. What ends this
48+
// credential's access is disconnecting its connection in Basecamp,
49+
// which kills that connection's secret and every token minted from it.
50+
// Once an agent can hold several connections, neither older remedy is
51+
// right: rotating the client secret (Basecamp has no control for it)
52+
// rewrites only the first connection's, and disconnecting the whole
53+
// agent ends every other computer's connection too.
5154
RevokeSkippedAgent = "agent"
5255
)
5356

@@ -196,7 +199,7 @@ func (m *Manager) RevokeStored(ctx context.Context) error {
196199
"Forget the credential locally instead: basecamp auth logout")
197200
case RevokeSkippedAgent:
198201
return output.ErrUsageHint("An agent self-token is not worth revoking: the client that minted it can mint another",
199-
"Forget the credential locally instead (basecamp auth logout), and disconnect the agent in Basecamp to end its access")
202+
"Forget the credential locally instead (basecamp auth logout), and disconnect this connection in Basecamp to end its access")
200203
}
201204
if err := m.Revoke(ctx, creds); err != nil {
202205
// Keep the failure's taxonomy — a transport failure or a 5xx

‎internal/commands/auth.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1607,7 +1607,7 @@ func describeLogout(done string, result *auth.LogoutResult) (summary string, fie
16071607
summary = done + " (forgot the imported token; it stays valid until revoked in Basecamp)"
16081608
case result.Skipped == auth.RevokeSkippedAgent:
16091609
fields["reason"] = result.Skipped
1610-
summary = done + " (forgot the agent credential; it stays connected until you disconnect the agent in Basecamp)"
1610+
summary = done + " (forgot the agent credential; this connection stays live until you disconnect it in Basecamp)"
16111611
case result.Err != nil:
16121612
fields["reason"] = result.Err.Error()
16131613
fields["remaining"] = result.Remaining

‎internal/commands/auth_logout_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ func TestAuthLogoutHumanCopy(t *testing.T) {
198198
s := startRevocationServer(t)
199199
app, buf := newLogoutTestApp(t, s, output.FormatStyled, agentLogoutCredentials(s))
200200
require.NoError(t, runLogout(t, app))
201-
assert.Contains(t, buf.String(), "Logged out (forgot the agent credential; it stays connected until you disconnect the agent in Basecamp)")
201+
assert.Contains(t, buf.String(), "Logged out (forgot the agent credential; this connection stays live until you disconnect it in Basecamp)")
202202
assert.NotContains(t, buf.String(), "rotate", "Basecamp has no rotate-secret control to send anyone to")
203203
assert.Empty(t, s.revoked(), "an agent self-token is minted on demand; revoking one ends nothing")
204204
assert.False(t, app.Auth.IsAuthenticated())

0 commit comments

Comments
 (0)