auth: remove cluster-binding model and command surface · Entire

auth: remove cluster-binding model and command surface

5a4163f→main·

toothbrush·1mo ago·9 files·+24 added/-346 removed

Removes the cluster→context binding now that account selection is recomputed per operation:

Tests updated accordingly; RemoveAllContexts still clears the legacy field (seeded directly now that BindCluster is gone).

Sessions

1a18bfd708fcView transcript

Changes

9

66 unmodified lines

67
68
69
70

66 unmodified lines

tmp/
.tmp/
.superpowers/
/git-remote-entire

M.gitignore+1

153 unmodified lines

154
155
156
157
157
158
159

153 unmodified lines

cmd.AddCommand(newAuthRevokeCmd())
    cmd.AddCommand(newAuthContextsCmd())
    cmd.AddCommand(newAuthUseCmd())
    cmd.AddCommand(newAuthUnbindCmd())
    return cmd
}

Mcmd/entire/cli/auth.go-1

115 unmodified lines

116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
119
120
121

115 unmodified lines

return f.Contexts, f.CurrentContext, nil

// ClusterBindings returns the cluster_host → context_name map from
// contexts.json. Surfaced by `entire auth contexts` so users can audit
// which cluster hosts auto-authenticate git operations with a stored
// context — and spot any they don't recognise.
func ClusterBindings() (map[string]string, error) {
    f, err := contexts.Load(contexts.DefaultConfigDir())
    if err != nil {
        return nil, fmt.Errorf("load contexts: %w", err)
    }
    return f.ClusterContexts, nil
}

// UnbindCluster removes the cluster→context binding for host, reporting
// whether one existed. Used by `entire auth unbind` to revoke a binding
// without touching the underlying login context.
func UnbindCluster(host string) (bool, error) {
    var existed bool
    if err := contexts.Modify(contexts.DefaultConfigDir(), func(f *contexts.File) (bool, error) {
        if _, ok := f.ClusterContexts[host]; !ok {
            return false, nil
        }
        delete(f.ClusterContexts, host)
        existed = true
        return true, nil
    }); err != nil {
        return false, fmt.Errorf("unbind cluster: %w", err)
    }
    return existed, nil
}

// ContextStore wraps the legacy keyring Store so token *reads* prefer the
// active contexts.json context, falling back to the legacy
// entire-cli/<authBaseURL> entry. Writes are inherited from Store

Mcmd/entire/cli/auth/context_store.go-30

229 unmodified lines

230
231
232
233
234
233
234
235
236
237
238
239
240
241
242

229 unmodified lines

if _, err := RecordLoginContext(makeJWT(t, fmt.Sprintf(`{"iss":"https://b.example.com","handle":"bob","exp":%d}`, exp)), true); err != nil {
        t.Fatalf("record b: %v", err)
    }
    if err := contexts.BindCluster(cfgDir, "cluster.example.com", a); err != nil {
        t.Fatalf("bind cluster: %v", err)
    }

// Seed a legacy cluster_contexts entry directly to prove RemoveAllContexts
    // still clears the inert field on a full logout.
    if err := contexts.Modify(cfgDir, func(f *contexts.File) (bool, error) {
        f.ClusterContexts = map[string]string{"cluster.example.com": a}
        return true, nil
    }); err != nil {
        t.Fatalf("seed legacy binding: %v", err)
    }

n, err := RemoveAllContexts()

Mcmd/entire/cli/auth/contexts_test.go+7/-2

2 unmodified lines

3
4
5
6
6
7
8
65 unmodified lines

74
75
76
78
77
78
79
80
6 unmodified lines

87
88
89
91
92
93
94
90
91
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
92
93
124
125
126
94
95
96
97
98
99
100
128
129
130
131
132
133
101
102
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159

2 unmodified lines

import (
    "fmt"
    "io"
    "sort"

"github.com/entireio/cli/cmd/entire/cli/api"
    "github.com/entireio/cli/cmd/entire/cli/auth"
65 unmodified lines

func newAuthContextsCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "contexts",
        Short: "List stored login contexts and cluster bindings",
        Short: "List stored login contexts",
        Args:  cobra.NoArgs,
        RunE: func(cmd *cobra.Command, _ []string) error {
            return runAuthContexts(cmd.OutOrStdout())
        }
    }
}

// printClusterBindings lists any cluster_contexts bindings so users can
// audit which hosts skip /.well-known discovery and resolve straight to a
// stored context. A binding does not hand that host your login JWT — that
// only ever goes to the bound context's core, and the host receives a
// repo-scoped, audience-pinned token (see repocreds). What a binding pins
// is which core authenticates that host, so an entry you don't recognise
// is still worth revoking with `entire auth unbind`.
func printClusterBindings(w io.Writer) error {
    bindings, err := auth.ClusterBindings()
    if err != nil {
        return err //nolint:wrapcheck // already a user-facing message
    }
    if len(bindings) == 0 {
        return nil
    }
    hosts := make([]string, 0, len(bindings))
    for h := range bindings {
        hosts = append(hosts, h)
    }
    sort.Strings(hosts)
    fmt.Fprintln(w, "\nCluster bindings (these hosts auto-authenticate with a stored context):")
    for _, h := range hosts {
        fmt.Fprintf(w, "  %s\t-> %s\n", h, bindings[h])
    }
    fmt.Fprintln(w, "\nRevoke a binding you don't recognise with 'entire auth unbind <host>'.")
    return nil
}

// newAuthUnbindCmd removes a cluster→context binding. Purely local.
func newAuthUnbindCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "unbind <cluster-host>",
        Short: "Remove a cluster→context binding",
        Long: "Remove the stored binding that makes git operations against a cluster host\nauto-authenticate with a saved context. The login context itself is left\nintact. List current bindings with 'entire auth contexts'.",
        Args: cobra.ExactArgs(1),
        RunE: func(cmd *cobra.Command, args []string) error {
            existed, err := auth.UnbindCluster(args[0])
            if err != nil {
                return err //nolint:wrapcheck // already a user-facing message
            }
            if !existed {
                fmt.Fprintf(cmd.OutOrStdout(), "No cluster binding for %q.\n", args[0])
                return nil
            }
            fmt.Fprintf(cmd.OutOrStdout(), "Removed cluster binding for %q.\n", args[0])
            return nil
        }
    }
}