Skip to content

Commit 831dbcd

Browse files
committed
refactor: inject auth server
1 parent abb3924 commit 831dbcd

File tree

6 files changed

+75
-35
lines changed

6 files changed

+75
-35
lines changed

cmd/fastly/main.go

+13
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/fastly/cli/pkg/api"
1616
"github.com/fastly/cli/pkg/app"
17+
"github.com/fastly/cli/pkg/auth"
1718
"github.com/fastly/cli/pkg/config"
1819
"github.com/fastly/cli/pkg/env"
1920
fsterr "github.com/fastly/cli/pkg/errors"
@@ -84,13 +85,25 @@ func main() {
8485
// NOTE: We skip handling the error because not all commands relate to C@E.
8586
_ = md.File.Read(manifest.Filename)
8687

88+
// Configure authentication inputs.
89+
// We do this here so that we can mock the values in our test suite.
90+
result := make(chan auth.AuthorizationResult)
91+
router := http.NewServeMux()
92+
s := &auth.Server{
93+
HTTPClient: httpClient,
94+
Result: result,
95+
Router: router,
96+
}
97+
router.HandleFunc("/callback", s.HandleCallback())
98+
8799
// The `main` function is a shim for calling `app.Run()`.
88100
err = app.Run(app.RunOpts{
89101
APIClient: func(token, endpoint string) (api.Interface, error) {
90102
client, err := fastly.NewClientForEndpoint(token, endpoint)
91103
return client, err
92104
},
93105
Args: args,
106+
AuthServer: s,
94107
ConfigFile: cfg,
95108
ConfigPath: config.FilePath,
96109
Env: e,

pkg/app/commands.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func defineCommands(
9898
aclEntryDescribe := aclentry.NewDescribeCommand(aclEntryCmdRoot.CmdClause, g, m)
9999
aclEntryList := aclentry.NewListCommand(aclEntryCmdRoot.CmdClause, g, m)
100100
aclEntryUpdate := aclentry.NewUpdateCommand(aclEntryCmdRoot.CmdClause, g, m)
101-
authenticateCmdRoot := authenticate.NewRootCommand(app, g, opts.Opener)
101+
authenticateCmdRoot := authenticate.NewRootCommand(app, g, opts.Opener, opts.AuthServer)
102102
authtokenCmdRoot := authtoken.NewRootCommand(app, g)
103103
authtokenCreate := authtoken.NewCreateCommand(authtokenCmdRoot.CmdClause, g, m)
104104
authtokenDelete := authtoken.NewDeleteCommand(authtokenCmdRoot.CmdClause, g, m)

pkg/app/run.go

+3
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ type RunOpts struct {
104104
APIClient APIClientFactory
105105
// Args are the command line arguments provided by the user.
106106
Args []string
107+
// AuthServer is an instance of the authentication server type.
108+
// Used for interacting with Fastly's SSO/OAuth authentication provider.
109+
AuthServer auth.Starter
107110
// ConfigFile is an instance of the CLI application config.
108111
ConfigFile config.File
109112
// ConfigPath is the location of the CLI application config.

pkg/auth/auth.go

+45-11
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,54 @@ const ClientID = "fastly-cli"
2727
// RedirectURL is the endpoint the auth provider will pass an authorization code to.
2828
const RedirectURL = "http://localhost:8080/callback"
2929

30+
// Starter defines the behaviour for the authentication server.
31+
type Starter interface {
32+
// GetResult returns the results channel
33+
GetResult() chan AuthorizationResult
34+
// SetAccountEndpoint sets the account endpoint.
35+
SetAccountEndpoint(endpoint string)
36+
// SetEndpoint sets the API endpoint.
37+
SetAPIEndpoint(endpoint string)
38+
// SetVerifier sets the code verifier.
39+
SetVerifier(verifier *oidc.S256Verifier)
40+
// Start starts a local server for handling authentication processing.
41+
Start() error
42+
}
43+
3044
// Server is a local server responsible for authentication processing.
3145
type Server struct {
46+
// APIEndpoint is the API endpoint.
47+
APIEndpoint string
3248
// AccountEndpoint is the accounts endpoint.
3349
AccountEndpoint string
34-
// HTTPClient is a HTTP client used to call the API to exchange the access
35-
// token for a session token.
50+
// HTTPClient is a HTTP client used to call the API to exchange the access token for a session token.
3651
HTTPClient api.HTTPClient
3752
// Result is a channel that reports the result of authorization.
3853
Result chan AuthorizationResult
3954
// Router is an HTTP request multiplexer.
4055
Router *http.ServeMux
41-
// Verifier represents an OAuth PKCE code verifier that uses the S256 challenge method
56+
// Verifier represents an OAuth PKCE code verifier that uses the S256 challenge method.
4257
Verifier *oidc.S256Verifier
43-
// APIEndpoint is the API endpoint.
44-
APIEndpoint string
58+
}
59+
60+
// GetResult returns the result channel.
61+
func (s Server) GetResult() chan AuthorizationResult {
62+
return s.Result
63+
}
64+
65+
// SetAccountEndpoint sets the account endpoint.
66+
func (s *Server) SetAccountEndpoint(endpoint string) {
67+
s.AccountEndpoint = endpoint
68+
}
69+
70+
// SetAPIEndpoint sets the API endpoint.
71+
func (s *Server) SetAPIEndpoint(endpoint string) {
72+
s.APIEndpoint = endpoint
73+
}
74+
75+
// SetVerifier sets the code verifier endpoint.
76+
func (s *Server) SetVerifier(verifier *oidc.S256Verifier) {
77+
s.Verifier = verifier
4578
}
4679

4780
// Start starts a local server for handling authentication processing.
@@ -63,12 +96,8 @@ func (s *Server) Start() error {
6396
return nil
6497
}
6598

66-
// Routes configures the callback handler.
67-
func (s *Server) Routes() {
68-
s.Router.HandleFunc("/callback", s.handleCallback())
69-
}
70-
71-
func (s *Server) handleCallback() http.HandlerFunc {
99+
// HandleCallback processes the callback from the authentication service.
100+
func (s *Server) HandleCallback() http.HandlerFunc {
72101
return func(w http.ResponseWriter, r *http.Request) {
73102
authorizationCode := r.URL.Query().Get("code")
74103
if authorizationCode == "" {
@@ -153,6 +182,11 @@ type AuthorizationResult struct {
153182
SessionToken string
154183
}
155184

185+
// GenVerifier creates a code verifier.
186+
func GenVerifier() (*oidc.S256Verifier, error) {
187+
return oidc.NewCodeVerifier()
188+
}
189+
156190
// GenURL constructs the required authorization_endpoint path.
157191
func GenURL(accountEndpoint, apiEndpoint string, verifier *oidc.S256Verifier) (string, error) {
158192
challenge, err := oidc.CreateCodeChallenge(verifier)

pkg/commands/authenticate/root.go

+11-23
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@ package authenticate
33
import (
44
"fmt"
55
"io"
6-
"net/http"
76
"time"
87

9-
"github.com/hashicorp/cap/oidc"
10-
118
"github.com/fastly/cli/pkg/auth"
129
"github.com/fastly/cli/pkg/cmd"
1310
"github.com/fastly/cli/pkg/config"
@@ -21,6 +18,7 @@ import (
2118
// It should be installed under the primary root command.
2219
type RootCommand struct {
2320
cmd.Base
21+
authServer auth.Starter
2422
openBrowser func(string) error
2523

2624
// IMPORTANT: The following fields are public to the `profile` subcommands.
@@ -36,8 +34,9 @@ type RootCommand struct {
3634
}
3735

3836
// NewRootCommand returns a new command registered in the parent.
39-
func NewRootCommand(parent cmd.Registerer, g *global.Data, opener func(string) error) *RootCommand {
37+
func NewRootCommand(parent cmd.Registerer, g *global.Data, opener func(string) error, authServer auth.Starter) *RootCommand {
4038
var c RootCommand
39+
c.authServer = authServer
4140
c.openBrowser = opener
4241
c.Globals = g
4342
c.CmdClause = parent.Command("authenticate", "SSO (Single Sign-On) authentication")
@@ -64,37 +63,26 @@ func (c *RootCommand) Exec(in io.Reader, out io.Writer) error {
6463
}
6564
}
6665

67-
verifier, err := oidc.NewCodeVerifier()
66+
accountEndpoint, _ := c.Globals.Account()
67+
apiEndpoint, _ := c.Globals.Endpoint()
68+
verifier, err := auth.GenVerifier()
6869
if err != nil {
6970
return fsterr.RemediationError{
7071
Inner: fmt.Errorf("failed to generate a code verifier: %w", err),
7172
Remediation: auth.Remediation,
7273
}
7374
}
74-
75-
result := make(chan auth.AuthorizationResult)
76-
apiEndpoint, _ := c.Globals.Endpoint()
77-
accountEndpoint, _ := c.Globals.Account()
78-
79-
s := auth.Server{
80-
APIEndpoint: apiEndpoint,
81-
AccountEndpoint: accountEndpoint,
82-
HTTPClient: c.Globals.HTTPClient,
83-
Result: result,
84-
Router: http.NewServeMux(),
85-
Verifier: verifier,
86-
}
87-
s.Routes()
75+
c.authServer.SetAccountEndpoint(accountEndpoint)
76+
c.authServer.SetAPIEndpoint(apiEndpoint)
77+
c.authServer.SetVerifier(verifier)
8878

8979
var serverErr error
90-
9180
go func() {
92-
err := s.Start()
81+
err := c.authServer.Start()
9382
if err != nil {
9483
serverErr = err
9584
}
9685
}()
97-
9886
if serverErr != nil {
9987
return serverErr
10088
}
@@ -117,7 +105,7 @@ func (c *RootCommand) Exec(in io.Reader, out io.Writer) error {
117105
return fmt.Errorf("failed to open your default browser: %w", err)
118106
}
119107

120-
ar := <-result
108+
ar := <-c.authServer.GetResult()
121109
if ar.Err != nil || ar.SessionToken == "" {
122110
return fsterr.RemediationError{
123111
Inner: fmt.Errorf("failed to authorize: %w", ar.Err),

pkg/testutil/args.go

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"time"
1010

1111
"github.com/fastly/cli/pkg/app"
12+
"github.com/fastly/cli/pkg/auth"
1213
"github.com/fastly/cli/pkg/config"
1314
"github.com/fastly/cli/pkg/errors"
1415
"github.com/fastly/cli/pkg/manifest"
@@ -67,6 +68,7 @@ func NewRunOpts(args []string, stdout io.Writer) app.RunOpts {
6768
ConfigPath: "/dev/null",
6869
Args: args,
6970
APIClient: mock.APIClient(mock.API{}),
71+
AuthServer: &auth.Server{},
7072
Env: config.Environment{},
7173
ErrLog: errors.Log,
7274
ConfigFile: config.File{

0 commit comments

Comments
 (0)