Sign in through the server's single sign-on, with a code
The sign-in screen asks the server how it can be signed in to (GET /api/auth/config) and offers what it finds: the password form, and "Sign in with <provider>" when the server can do a device login. The TUI shows a link and a short code, the person approves it in any browser, and the next poll hands over the ordinary session, so it works over SSH where no browser can be opened. The terminal never talks to the identity provider. The password form is hidden when the server has turned password login off. `auth: sso` in config.yaml starts the SSO login straight away, but not right after signing out, where that would sign the person straight back in; any other value is refused when the config is read. Polling honours the server's interval, backs off on slow_down, and gives up after repeated failures rather than retrying forever. A server without /api/auth/config answers 404 and is treated as passwords only, so the sign-in screen is the one it had. Needs terdut-server v0.29.0 for SSO.
This commit is contained in:
@@ -24,6 +24,12 @@ type Config struct {
|
||||
// Team is the team to start on, by name or id. Empty shows every team the
|
||||
// key's user belongs to.
|
||||
Team string
|
||||
|
||||
// Auth is how to sign in when the server offers a choice: "sso" starts a
|
||||
// single sign-on login straight away, "password" (or empty) shows the
|
||||
// password form. The server decides what is on offer; this only picks the
|
||||
// default among it.
|
||||
Auth string
|
||||
}
|
||||
|
||||
type rawConfig struct {
|
||||
@@ -33,6 +39,7 @@ type rawConfig struct {
|
||||
RefreshInterval int `yaml:"refresh_interval,omitempty"` // seconds
|
||||
Theme string `yaml:"theme,omitempty"`
|
||||
Team string `yaml:"team,omitempty"`
|
||||
Auth string `yaml:"auth,omitempty"`
|
||||
}
|
||||
|
||||
func Load() (*Config, error) {
|
||||
@@ -45,7 +52,7 @@ func Load() (*Config, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("config file not found at %s\n\nCreate it with:\n server_url: https://terdut.example.com\n username: <your-username> # optional, prefills the sign-in form\n theme: gruvbox-dark # optional\n team: Ops # optional, team to start on", path)
|
||||
return nil, fmt.Errorf("config file not found at %s\n\nCreate it with:\n server_url: https://terdut.example.com\n username: <your-username> # optional, prefills the sign-in form\n theme: gruvbox-dark # optional\n team: Ops # optional, team to start on\n auth: sso # optional, sso or password: how to sign in by default", path)
|
||||
}
|
||||
return nil, fmt.Errorf("cannot read config file: %w", err)
|
||||
}
|
||||
@@ -59,6 +66,12 @@ func Load() (*Config, error) {
|
||||
return nil, fmt.Errorf("config: 'server_url' is required")
|
||||
}
|
||||
|
||||
switch raw.Auth {
|
||||
case "", "password", "sso":
|
||||
default:
|
||||
return nil, fmt.Errorf("config: 'auth' must be sso or password, not %q", raw.Auth)
|
||||
}
|
||||
|
||||
interval := defaultRefreshInterval
|
||||
if raw.RefreshInterval > 0 {
|
||||
interval = time.Duration(raw.RefreshInterval) * time.Second
|
||||
@@ -71,5 +84,6 @@ func Load() (*Config, error) {
|
||||
RefreshInterval: interval,
|
||||
Theme: raw.Theme,
|
||||
Team: raw.Team,
|
||||
Auth: raw.Auth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -59,3 +59,26 @@ func TestLoad_NoAPIKeyNeeded(t *testing.T) {
|
||||
t.Error("expected the leftover api_key to be noted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_AuthIsOptionalAndChecked(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
yaml, want string
|
||||
bad bool
|
||||
}{
|
||||
{"", "", false},
|
||||
{"auth: password\n", "password", false},
|
||||
{"auth: sso\n", "sso", false},
|
||||
{"auth: oidc\n", "", true},
|
||||
} {
|
||||
writeConfig(t, "server_url: https://terdut.example.com\n"+tc.yaml)
|
||||
cfg, err := Load()
|
||||
switch {
|
||||
case tc.bad && err == nil:
|
||||
t.Errorf("%q: expected an error", tc.yaml)
|
||||
case !tc.bad && err != nil:
|
||||
t.Errorf("%q: %v", tc.yaml, err)
|
||||
case !tc.bad && cfg.Auth != tc.want:
|
||||
t.Errorf("%q: auth %q, want %q", tc.yaml, cfg.Auth, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user