Skip to content

Commit f15261b

Browse files
committed
Initial support for device flow
Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz>
1 parent 0f02251 commit f15261b

File tree

5 files changed

+294
-14
lines changed

5 files changed

+294
-14
lines changed

docs/stackit_auth_login.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Logs in to the STACKIT CLI
55
### Synopsis
66

77
Logs in to the STACKIT CLI using a user account.
8-
The authentication is done via a web-based authorization flow, where the command will open a browser window in which you can login to your STACKIT account.
8+
By default, the authentication uses a web-based authorization flow and opens a browser window where you can login to your STACKIT account. You can alternatively use the OAuth 2.0 device flow for environments where receiving a local callback is not possible.
99

1010
```
1111
stackit auth login [flags]
@@ -16,6 +16,9 @@ stackit auth login [flags]
1616
```
1717
Login to the STACKIT CLI. This command will open a browser window where you can login to your STACKIT account
1818
$ stackit auth login
19+
20+
Login to the STACKIT CLI using OAuth 2.0 device flow
21+
$ stackit auth login --use-device-flow
1922
```
2023

2124
### Options
@@ -24,6 +27,7 @@ stackit auth login [flags]
2427
-h, --help Help for "stackit auth login"
2528
--port int The port on which the callback server will listen to. By default, it tries to bind a port between 8000 and 8020.
2629
When a value is specified, it will only try to use the specified port. Valid values are within the range of 8000 to 8020.
30+
--use-device-flow Use OAuth 2.0 device authorization grant (device flow) instead of the browser callback flow.
2731
```
2832

2933
### Options inherited from parent commands

internal/cmd/auth/login/login.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ import (
1414
)
1515

1616
const (
17-
portFlag = "port"
17+
portFlag = "port"
18+
useDeviceFlowFlag = "use-device-flow"
1819
)
1920

2021
type inputModel struct {
21-
Port *int
22+
Port *int
23+
UseDeviceFlow bool
2224
}
2325

2426
func NewCmd(params *types.CmdParams) *cobra.Command {
@@ -27,12 +29,15 @@ func NewCmd(params *types.CmdParams) *cobra.Command {
2729
Short: "Logs in to the STACKIT CLI",
2830
Long: fmt.Sprintf("%s\n%s",
2931
"Logs in to the STACKIT CLI using a user account.",
30-
"The authentication is done via a web-based authorization flow, where the command will open a browser window in which you can login to your STACKIT account."),
32+
"By default, the authentication uses a web-based authorization flow and opens a browser window where you can login to your STACKIT account. You can alternatively use the OAuth 2.0 device flow for environments where receiving a local callback is not possible."),
3133
Args: args.NoArgs,
3234
Example: examples.Build(
3335
examples.NewExample(
3436
`Login to the STACKIT CLI. This command will open a browser window where you can login to your STACKIT account`,
3537
"$ stackit auth login"),
38+
examples.NewExample(
39+
`Login to the STACKIT CLI using OAuth 2.0 device flow`,
40+
"$ stackit auth login --use-device-flow"),
3641
),
3742
RunE: func(cmd *cobra.Command, args []string) error {
3843
model, err := parseInput(params.Printer, cmd, args)
@@ -43,6 +48,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command {
4348
err = auth.AuthorizeUser(params.Printer, auth.UserAuthConfig{
4449
IsReauthentication: false,
4550
Port: model.Port,
51+
UseDeviceFlow: model.UseDeviceFlow,
4652
})
4753
if err != nil {
4854
return fmt.Errorf("authorization failed: %w", err)
@@ -62,17 +68,21 @@ func configureFlags(cmd *cobra.Command) {
6268
"The port on which the callback server will listen to. By default, it tries to bind a port between 8000 and 8020.\n"+
6369
"When a value is specified, it will only try to use the specified port. Valid values are within the range of 8000 to 8020.",
6470
)
71+
cmd.Flags().Bool(useDeviceFlowFlag, false,
72+
"Use OAuth 2.0 device authorization grant (device flow) instead of the browser callback flow.")
6573
}
6674

6775
func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
6876
port := flags.FlagToIntPointer(p, cmd, portFlag)
77+
useDeviceFlow := flags.FlagToBoolValue(p, cmd, useDeviceFlowFlag)
6978
// For the CLI client only callback URLs with localhost:[8000-8020] are valid. Additional callbacks must be enabled in the backend.
7079
if port != nil && (*port < 8000 || 8020 < *port) {
7180
return nil, fmt.Errorf("port must be between 8000 and 8020")
7281
}
7382

7483
model := inputModel{
75-
Port: port,
84+
Port: port,
85+
UseDeviceFlow: useDeviceFlow,
7686
}
7787

7888
p.DebugInputModel(model)

internal/cmd/auth/login/login_test.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import (
99

1010
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
1111
flagValues := map[string]string{
12-
portFlag: "8010",
12+
portFlag: "8010",
13+
useDeviceFlowFlag: "false",
1314
}
1415
for _, mod := range mods {
1516
mod(flagValues)
@@ -19,7 +20,8 @@ func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]st
1920

2021
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
2122
model := &inputModel{
22-
Port: utils.Ptr(8010),
23+
Port: utils.Ptr(8010),
24+
UseDeviceFlow: false,
2325
}
2426
for _, mod := range mods {
2527
mod(model)
@@ -46,7 +48,19 @@ func TestParseInput(t *testing.T) {
4648
flagValues: map[string]string{},
4749
isValid: true,
4850
expectedModel: &inputModel{
49-
Port: nil,
51+
Port: nil,
52+
UseDeviceFlow: false,
53+
},
54+
},
55+
{
56+
description: "device flow enabled",
57+
flagValues: map[string]string{
58+
useDeviceFlowFlag: "true",
59+
},
60+
isValid: true,
61+
expectedModel: &inputModel{
62+
Port: nil,
63+
UseDeviceFlow: true,
5064
},
5165
},
5266
{
@@ -56,7 +70,8 @@ func TestParseInput(t *testing.T) {
5670
},
5771
isValid: true,
5872
expectedModel: &inputModel{
59-
Port: utils.Ptr(8000),
73+
Port: utils.Ptr(8000),
74+
UseDeviceFlow: false,
6075
},
6176
},
6277
{
@@ -73,7 +88,8 @@ func TestParseInput(t *testing.T) {
7388
},
7489
isValid: true,
7590
expectedModel: &inputModel{
76-
Port: utils.Ptr(8020),
91+
Port: utils.Ptr(8020),
92+
UseDeviceFlow: false,
7793
},
7894
},
7995
{

0 commit comments

Comments
 (0)