hydra/cmd/cmd_perform_device_flow.go

116 lines
3.8 KiB
Go

// Copyright © 2022 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"context"
"fmt"
"os"
"strings"
"github.com/ory/hydra/v2/cmd/cliclient"
"github.com/spf13/cobra"
"golang.org/x/oauth2"
"github.com/ory/x/cmdx"
"github.com/ory/x/flagx"
"github.com/ory/x/urlx"
)
func NewPerformDeviceCodeCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "device-code",
Example: "{{ .CommandPath }} --client-id ...",
Short: "An exemplary OAuth 2.0 Client performing the OAuth 2.0 Device Code Flow",
Long: `Performs the device code flow. Useful for getting an access token and an ID token in machines without a browser.
The client that will be used MUST use the "none" or "client_secret_post" token-endpoint-auth-method.`,
RunE: func(cmd *cobra.Command, args []string) error {
client, endpoint, err := cliclient.NewClient(cmd)
if err != nil {
return err
}
endpoint = cliclient.GetOAuth2URLOverride(cmd, endpoint)
ctx := context.WithValue(cmd.Context(), oauth2.HTTPClient, client)
scopes := flagx.MustGetStringSlice(cmd, "scope")
deviceAuthUrl := flagx.MustGetString(cmd, "device-auth-url")
tokenUrl := flagx.MustGetString(cmd, "token-url")
audience := flagx.MustGetStringSlice(cmd, "audience")
clientID := flagx.MustGetString(cmd, "client-id")
if clientID == "" {
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), cmd.UsageString())
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), "Please provide a Client ID using --client-id flag, or OAUTH2_CLIENT_ID environment variable.")
return cmdx.FailSilently(cmd)
}
clientSecret := flagx.MustGetString(cmd, "client-secret")
if deviceAuthUrl == "" {
deviceAuthUrl = urlx.AppendPaths(endpoint, "/oauth2/device/auth").String()
}
if tokenUrl == "" {
tokenUrl = urlx.AppendPaths(endpoint, "/oauth2/token").String()
}
conf := oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
Endpoint: oauth2.Endpoint{
DeviceAuthURL: deviceAuthUrl,
TokenURL: tokenUrl,
},
Scopes: scopes,
}
params := []oauth2.AuthCodeOption{oauth2.SetAuthURLParam("audience", strings.Join(audience, "+"))}
if clientSecret != "" {
params = append(params, oauth2.SetAuthURLParam("client_secret", clientSecret))
}
deviceAuthResponse, err := conf.DeviceAuth(
ctx,
params...,
)
if err != nil {
_, _ = fmt.Fprintf(
cmd.ErrOrStderr(), "Failed to perform the device authorization request: %s\n", err)
return cmdx.FailSilently(cmd)
}
_, _ = fmt.Fprintln(
cmd.ErrOrStderr(),
"To login please go to:\n\t",
deviceAuthResponse.VerificationURIComplete,
)
token, err := conf.DeviceAccessToken(ctx, deviceAuthResponse)
if err != nil {
_, _ = fmt.Fprintf(
cmd.ErrOrStderr(), "Failed to perform the device token request: %s\n", err)
return cmdx.FailSilently(cmd)
}
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), "Successfully signed in!")
cmdx.PrintRow(cmd, outputOAuth2Token(*token))
return nil
},
}
cmd.Flags().StringSlice("scope", []string{"offline", "openid"}, "Request OAuth2 scope")
cmd.Flags().String("client-id", os.Getenv("OAUTH2_CLIENT_ID"), "Use the provided OAuth 2.0 Client ID, defaults to environment variable OAUTH2_CLIENT_ID")
cmd.Flags().String("client-secret", os.Getenv("OAUTH2_CLIENT_SECRET"), "Use the provided OAuth 2.0 Client Secret, defaults to environment variable OAUTH2_CLIENT_SECRET")
cmd.Flags().StringSlice("audience", []string{}, "Request a specific OAuth 2.0 Access Token Audience")
cmd.Flags().String("device-auth-url", "", "Usually it is enough to specify the `endpoint` flag, but if you want to force the device authorization url, use this flag")
cmd.Flags().String("token-url", "", "Usually it is enough to specify the `endpoint` flag, but if you want to force the token url, use this flag")
return cmd
}