# cli: add `entire login --server`, retire ENTIRE_AUTH_BASE_URL (COR-393)

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

toothbrush·1mo ago·8 files·+167 added/-18 removed

`entire login --server <url>` (default https://us.auth.entire.io)
replaces the env var as the way to target a non-default login server.
The value is validated as a bare http(s) origin — userinfo, path,
query, and fragment are rejected rather than silently dropped, since
it becomes the OAuth issuer, exchange target, and keyring key.

A set ENTIRE_AUTH_BASE_URL (even empty) now fails every built-in
command with a hint naming the flag: a removed knob must never be
silently ignored. Internal api.AuthBaseURL() reads survive until the
follow-up demolition PR, but can only ever observe the default now.

auth.NewClient takes the server explicitly instead of reading the env
var; login's TLS check narrows to the server actually being dialled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

## Sessions

5036f0366fb3View transcript

[?\
Auth Refactor: Eliminate Static FallbacksClaude Code·Fable 5.[1m·7 steps](/content/gh/entireio/cli/session/e6146684-ebfa-4f57-beff-34194cac8c2a#timeline-5036f0366fb3/index.html)

## Changes

8

- cmd/entire

- cli

- api

- Mbase_url.go+12

- Mbase_url_test.go+28

- auth

- Mclient.go+7/-6

- Mclient_test.go+6/-6

- Mlogin.go+65/-5

- Mlogin_validate_test.go+38

- Mrepo_mirror_probe.go+1/-1

- Mmain.go+10

```go
32 unmodified lines

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

32 unmodified lines

schemeHTTPS = "https"
// RejectRemovedAuthEnv returns an error when ENTIRE_AUTH_BASE_URL is set
// at all (even empty). The variable is retired in favour of
// `entire login --server`; failing loudly beats silently ignoring an
// override the operator believes is in effect. The remaining internal
// AuthBaseURL() reads only ever see the default once this gate has run.
func RejectRemovedAuthEnv() error {
    if _, ok := os.LookupEnv(AuthBaseURLEnvVar); ok {
        return fmt.Errorf("%s is no longer supported; unset it, and use `entire login --server <url>` to log in to a non-default login server", AuthBaseURLEnvVar)
    }
    return nil
}

// BaseURL returns the effective Entire API base URL.
// ENTIRE_API_BASE_URL takes precedence over the production default.
func BaseURL() string {
```

Mcmd/entire/cli/api/base_url.go+12

```

1 unmodified line

2
3
4
5
6
7
8
9
144 unmodified lines

154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182

1 unmodified line

import (
	"errors"
	"os"
	"strings"
	"testing"
)

144 unmodified lines

t.Fatalf("ResolveURL() = %q, want %q", got, "http://localhost:8787/oauth/device/code")
	}
}

// TestRejectRemovedAuthEnv pins the retired-env gate: any set value — even
// empty — errors with the --server replacement hint; unset passes.
func TestRejectRemovedAuthEnv(t *testing.T) {
    t.Run("unset passes", func(t *testing.T) {
        if os.Getenv(AuthBaseURLEnvVar) != "" {
            t.Skipf("%s set in test environment", AuthBaseURLEnvVar)
        }
        if err := RejectRemovedAuthEnv(); err != nil {
            t.Fatalf("RejectRemovedAuthEnv() with unset var: %v", err)
        }
    })
    t.Run("set errors", func(t *testing.T) {
        t.Setenv(AuthBaseURLEnvVar, "https://custom.example")
        err := RejectRemovedAuthEnv()
        if err == nil || !strings.Contains(err.Error(), "entire login --server") {
            t.Fatalf("err = %v, want --server hint", err)
        }
    })
    t.Run("set-but-empty errors", func(t *testing.T) {
        t.Setenv(AuthBaseURLEnvVar, "")
        if err := RejectRemovedAuthEnv(); err == nil {
            t.Fatal("RejectRemovedAuthEnv() with empty-but-set var: want error")
        }
    })
}
```

Mcmd/entire/cli/api/base_url_test.go+28

```

45 unmodified lines

46
47
48
49
50
51
52
49
50
51
52
53
54
55
56
57
58
59
59
60
61
61
62
63
64
65

45 unmodified lines

browser *authcode.Client
}

// NewClient constructs a Client targeting the active provider version.
// httpClient.Transport is reused when non-nil (its TLS / proxy config
// flows through); a nil httpClient or nil Transport falls back to the
// deviceflow default (http.DefaultTransport).
// NewClient constructs a Client for the device-flow login against server
// (the login-server origin, validated by the caller — `entire login
// --server`). httpClient.Transport is reused when non-nil (its TLS /
// proxy config flows through); a nil httpClient or nil Transport falls
// back to the deviceflow default (http.DefaultTransport).
//
// HTTPS is required by default. Loopback http:// (localhost, 127.0.0.1,
// ::1) is always permitted — see isLoopbackHTTP. allowInsecureHTTP=true
// additionally permits non-loopback http:// for cases like local-dev
// auth hosts on a private network (e.g. http://devbox.internal); the
// CLI plumbs this from the --insecure-http-auth flag.
func NewClient(httpClient *http.Client, allowInsecureHTTP bool) *Client {
func NewClient(server string, httpClient *http.Client, allowInsecureHTTP bool) *Client {
	p := CurrentProvider()
	issuer := api.AuthBaseURL()
	issuer := api.NormalizeOriginURL(server)
	var transport http.RoundTripper
	if httpClient != nil {
		transport = httpClient.Transport
```

Mcmd/entire/cli/auth/client.go+7/-6

```

98 unmodified lines

99
100
101
102
103
104
105
105
106
106
107
108
108
109
110
111
112
113
114
113
114
115
116
116
117
118
119

98 unmodified lines

}

func TestNewClient_AllowInsecureHTTPPermitsNonLoopback(t *testing.T) {
t.Parallel()
// --insecure-http-auth must reach the deviceflow client; without this,
// http://devbox.internal style auth hosts fail with ErrInsecureBaseURL
// even when the operator has explicitly opted in.
t.Setenv("ENTIRE_AUTH_BASE_URL", "http://devbox.internal:8787")
c := NewClient(nil, true)
c := NewClient("http://devbox.internal:8787", nil, true)
if !c.inner.AllowInsecureHTTP {
t.Fatal("NewClient(nil, true) AllowInsecureHTTP = false, want true")
t.Fatal("NewClient(server, nil, true) AllowInsecureHTTP = false, want true")
}
}

func TestNewClient_LoopbackHTTPAlwaysPermitted(t *testing.T) {
t.Setenv("ENTIRE_AUTH_BASE_URL", "http://127.0.0.1:8787")
c := NewClient(nil, false)
t.Parallel()
c := NewClient("http://127.0.0.1:8787", nil, false)
if !c.inner.AllowInsecureHTTP {
t.Fatal("NewClient(nil, false) AllowInsecureHTTP = false for loopback, want true")
t.Fatal("NewClient(server, nil, false) AllowInsecureHTTP = false for loopback, want true")
}
}
```

Mcmd/entire/cli/auth/client_test.go+6/-6

```

9 unmodified lines

10
11
12
13
14
15
16
3 unmodified lines

20
21
22
23
24
25
26
27
28
29
30
39 unmodified lines

70
71
72
67
68
73
74
75
76
77
78
79
80
81
73
82
83
84
85
86
87
88
76
89
90
91
92
9 unmodified lines

102
103
104
105
106
107
108
109
110
111
112
113
114
115
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
149
150
151
152
153
154
155
156
157
158
159
341 unmodified lines

501
502
503
444
504
505
506
507

9 unmodified lines

"os"
	"os/exec"
	"runtime"
	"strings"
	"time"

"github.com/entireio/auth-go/tokens"
3 unmodified lines

"github.com/spf13/cobra"
)

const (
schemeHTTP  = "http"
schemeHTTPS = "https"
)

const fallbackDeviceAuthPollInterval = time.Second
const defaultSlowDownBackoff = 5 * time.Second
const maxPollInterval = 30 * time.Second
39 unmodified lines

}

func newLoginCmd() *cobra.Command {
	var insecureHTTPAuth bool
	var useDevice bool
	var (
		insecureHTTPAuth bool
		useDevice        bool
		server           string
	)
	cmd := &cobra.Command{
		Use:   "login",
		Short: "Log in to Entire",
		RunE: func(cmd *cobra.Command, _ []string) error {
			if err := requireSecureBaseURL(insecureHTTPAuth); err != nil {
				loginServer, err := parseLoginServer(server)
				if err != nil {
					return fmt.Errorf("invalid --server: %w", err)
				}
				if err := requireSecureLoginServer(loginServer, insecureHTTPAuth); err != nil {
					return err
				}
				client := auth.NewClient(nil, insecureHTTPAuth)
				client := auth.NewClient(loginServer, nil, insecureHTTPAuth)
				// Closure adapts the concrete *auth.BrowserAuthFlow result to the
				// browserAuthFlow interface (func types are invariant, so the
				// method value alone won't do). On error the flow is a typed nil,
```

func parseLoginServer(raw string) (string, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return "", errors.New("empty server URL")
	}
	
	// implementation...
}

// requireSecureLoginServer enforces TLS for the chosen login server.
// Unlike requireSecureBaseURL it checks only the server being dialled —
// login never touches the data API. --insecure-http-auth opts in to
// http:// (and enables it process-wide for the token save path).
func requireSecureLoginServer(server string, insecureHTTPAuth bool) error {
	if insecureHTTPAuth {
		auth.EnableInsecureHTTP()
		return nil
	}
	if err := api.RequireSecureURL(server); err != nil {
		return fmt.Errorf("login server check: %w", err)
	}
	return nil
}

func runLogin(ctx context.Context, outW, errW io.Writer, client deviceAuthClient, openURL browserOpenFunc, canPrompt bool) error {
	start, err := client.StartDeviceAuth(ctx)
	if err != nil {
		return err
	}

// implementation...
}`
