Skip to content

feat(auth): add get-access-token cmd #598

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/stackit_auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ stackit auth [flags]

* [stackit](./stackit.md) - Manage STACKIT resources using the command line
* [stackit auth activate-service-account](./stackit_auth_activate-service-account.md) - Authenticates using a service account
* [stackit auth get-access-token](./stackit_auth_get-access-token.md) - Prints a short-lived access token.
* [stackit auth login](./stackit_auth_login.md) - Logs in to the STACKIT CLI
* [stackit auth logout](./stackit_auth_logout.md) - Logs the user account out of the STACKIT CLI

40 changes: 40 additions & 0 deletions docs/stackit_auth_get-access-token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## stackit auth get-access-token

Prints a short-lived access token.

### Synopsis

Prints a short-lived access token which can be used e.g. for API calls.

```
stackit auth get-access-token [flags]
```

### Examples

```
Print a short-lived access token
$ stackit auth get-access-token
```

### Options

```
-h, --help Help for "stackit auth get-access-token"
```

### Options inherited from parent commands

```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```

### SEE ALSO

* [stackit auth](./stackit_auth.md) - Authenticates the STACKIT CLI

2 changes: 2 additions & 0 deletions internal/cmd/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package auth

import (
activateserviceaccount "github.com/stackitcloud/stackit-cli/internal/cmd/auth/activate-service-account"
getaccesstoken "github.com/stackitcloud/stackit-cli/internal/cmd/auth/get-access-token"
"github.com/stackitcloud/stackit-cli/internal/cmd/auth/login"
"github.com/stackitcloud/stackit-cli/internal/cmd/auth/logout"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
Expand All @@ -27,4 +28,5 @@ func addSubcommands(cmd *cobra.Command, p *print.Printer) {
cmd.AddCommand(login.NewCmd(p))
cmd.AddCommand(logout.NewCmd(p))
cmd.AddCommand(activateserviceaccount.NewCmd(p))
cmd.AddCommand(getaccesstoken.NewCmd(p))
}
50 changes: 50 additions & 0 deletions internal/cmd/auth/get-access-token/get_access_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package getaccesstoken

import (
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/auth"
cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
)

func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: "get-access-token",
Short: "Prints a short-lived access token.",
Long: "Prints a short-lived access token which can be used e.g. for API calls.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`Print a short-lived access token`,
"$ stackit auth get-access-token"),
),
RunE: func(_ *cobra.Command, _ []string) error {
userSessionExpired, err := auth.UserSessionExpired()
if err != nil {
return err
}
if userSessionExpired {
return &cliErr.SessionExpiredError{}
}

accessToken, err := auth.GetAccessToken()
if err != nil {
return err
}

accessTokenExpired, err := auth.TokenExpired(accessToken)
if err != nil {
return err
}
if accessTokenExpired {
return &cliErr.AccessTokenExpiredError{}
}

p.Info("%s\n", accessToken)
return nil
},
}
return cmd
}
8 changes: 4 additions & 4 deletions internal/pkg/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func AuthenticationConfig(p *print.Printer, reauthorizeUserRoutine func(p *print
return nil, fmt.Errorf("authentication flow not set")
}

userSessionExpired, err := userSessionExpired()
userSessionExpired, err := UserSessionExpired()
if err != nil {
return nil, fmt.Errorf("check if user session expired: %w", err)
}
Expand All @@ -42,7 +42,7 @@ func AuthenticationConfig(p *print.Printer, reauthorizeUserRoutine func(p *print
if userSessionExpired {
return nil, fmt.Errorf("session expired")
}
accessToken, err := getAccessToken()
accessToken, err := GetAccessToken()
if err != nil {
return nil, fmt.Errorf("get service account access token: %w", err)
}
Expand Down Expand Up @@ -73,7 +73,7 @@ func AuthenticationConfig(p *print.Printer, reauthorizeUserRoutine func(p *print
return authCfgOption, nil
}

func userSessionExpired() (bool, error) {
func UserSessionExpired() (bool, error) {
sessionExpiresAtString, err := GetAuthField(SESSION_EXPIRES_AT_UNIX)
if err != nil {
return false, fmt.Errorf("get %s: %w", SESSION_EXPIRES_AT_UNIX, err)
Expand All @@ -87,7 +87,7 @@ func userSessionExpired() (bool, error) {
return now.After(sessionExpiresAt), nil
}

func getAccessToken() (string, error) {
func GetAccessToken() (string, error) {
accessToken, err := GetAuthField(ACCESS_TOKEN)
if err != nil {
return "", fmt.Errorf("get %s: %w", ACCESS_TOKEN, err)
Expand Down
4 changes: 2 additions & 2 deletions internal/pkg/auth/user_token_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (utf *userTokenFlow) RoundTrip(req *http.Request) (*http.Response, error) {
}

accessTokenValid := false
accessTokenExpired, err := tokenExpired(utf.accessToken)
accessTokenExpired, err := TokenExpired(utf.accessToken)
if err != nil {
return nil, fmt.Errorf("check if access token has expired: %w", err)
} else if !accessTokenExpired {
Expand Down Expand Up @@ -108,7 +108,7 @@ func reauthenticateUser(utf *userTokenFlow) error {
return nil
}

func tokenExpired(token string) (bool, error) {
func TokenExpired(token string) (bool, error) {
// We can safely use ParseUnverified because we are not authenticating the user at this point.
// We're just checking the expiration time
tokenParsed, _, err := jwt.NewParser().ParseUnverified(token, &jwt.RegisteredClaims{})
Expand Down
28 changes: 28 additions & 0 deletions internal/pkg/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ You can authenticate as a user by running:
or use a service account by running:
$ stackit auth activate-service-account`

SESSION_EXPIRED = `Session is expired. Please log in again first.

You can authenticate as a user by running:
$ stackit auth login

or use a service account by running:
$ stackit auth activate-service-account`

ACCESS_TOKEN_EXPIRED = `Access token is expired. Please log in again first.

You can authenticate as a user by running:
$ stackit auth login

or use a service account by running:
$ stackit auth activate-service-account`

FAILED_SERVICE_ACCOUNT_ACTIVATION = `could not setup authentication based on the provided service account credentials.
Please double check if they are correctly configured.

Expand Down Expand Up @@ -230,6 +246,18 @@ func (e *AuthError) Error() string {
return FAILED_AUTH
}

type SessionExpiredError struct{}

func (e *SessionExpiredError) Error() string {
return SESSION_EXPIRED
}

type AccessTokenExpiredError struct{}

func (e *AccessTokenExpiredError) Error() string {
return ACCESS_TOKEN_EXPIRED
}

type ActivateServiceAccountError struct{}

func (e *ActivateServiceAccountError) Error() string {
Expand Down