-
Notifications
You must be signed in to change notification settings - Fork 55
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
Polish the login flow #11
Changes from all commits
a13b6c4
ccd9d87
d5ccd73
d8758f5
1e56c8b
8b3ae5f
a201cf3
c0edbaf
a15c820
6f91d54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,111 +6,196 @@ import ( | |
"io/ioutil" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"strings" | ||
"sync" | ||
"time" | ||
|
||
"github.com/auth0/auth0-cli/internal/display" | ||
"gopkg.in/auth0.v5/management" | ||
) | ||
|
||
type data struct { | ||
// config defines the exact set of tenants, access tokens, which only exists | ||
// for a particular user's machine. | ||
type config struct { | ||
Comment on lines
+18
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In retrospect |
||
DefaultTenant string `json:"default_tenant"` | ||
Tenants map[string]tenant `json:"tenants"` | ||
} | ||
|
||
// tenant is the cli's concept of an auth0 tenant. The fields are tailor fit | ||
// specifically for interacting with the management API. | ||
type tenant struct { | ||
Domain string `json:"domain"` | ||
|
||
ClientID string `json:"client_id"` | ||
ClientSecret string `json:"client_secret"` | ||
|
||
// TODO(cyx): This will be what we do with device flow. | ||
BearerToken string `json:"bearer_token,omitempty"` | ||
Name string `json:"name"` | ||
Domain string `json:"domain"` | ||
AccessToken string `json:"access_token,omitempty"` | ||
ExpiresAt time.Time `json:"expires_at"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're not using ExpiresAt yet -- but eventually we can use it to determine when to re-trigger a new login. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Created a backlog item so we don't lose track https://auth0team.atlassian.net/browse/A0CLI-9 |
||
} | ||
|
||
// cli provides all the foundational things for all the commands in the CLI, | ||
// specifically: | ||
// | ||
// 1. A management API instance (e.g. go-auth0/auth0) | ||
// 2. A renderer (which provides ansi, coloring, etc). | ||
// | ||
// In addition, it stores a reference to all the flags passed, e.g.: | ||
// | ||
// 1. --format | ||
// 2. --tenant | ||
// 3. --verbose | ||
// | ||
type cli struct { | ||
// core primitives exposed to command builders. | ||
api *management.Management | ||
renderer *display.Renderer | ||
|
||
// set of flags which are user specified. | ||
verbose bool | ||
tenant string | ||
format string | ||
|
||
// config state management. | ||
initOnce sync.Once | ||
errOnce error | ||
path string | ||
data data | ||
config config | ||
} | ||
|
||
// isLoggedIn encodes the domain logic for determining whether or not we're | ||
// logged in. This might check our config storage, or just in memory. | ||
func (c *cli) isLoggedIn() bool { | ||
// No need to check errors for initializing context. | ||
_ = c.init() | ||
|
||
return c.tenant != "" | ||
} | ||
|
||
// setup will try to initialize the config context, as well as figure out if | ||
// there's a readily available tenant. A management API SDK instance is initialized IFF: | ||
// | ||
// 1. A tenant is found. | ||
// 2. The tenant has an access token. | ||
func (c *cli) setup() error { | ||
if err := c.init(); err != nil { | ||
return err | ||
} | ||
|
||
t, err := c.getTenant() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if t.BearerToken != "" { | ||
c.api, err = management.New(t.Domain, | ||
management.WithStaticToken(t.BearerToken), | ||
management.WithDebug(c.verbose)) | ||
} else { | ||
if t.AccessToken != "" { | ||
c.api, err = management.New(t.Domain, | ||
management.WithClientCredentials(t.ClientID, t.ClientSecret), | ||
management.WithStaticToken(t.AccessToken), | ||
management.WithDebug(c.verbose)) | ||
} | ||
Comment on lines
-50
to
91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Originally we had an if else for M2M or a static token. Given that we've made device flow work already, we can remove this until we decide we need it. |
||
|
||
return err | ||
} | ||
|
||
// getTenant fetches the default tenant configured (or the tenant specified via | ||
// the --tenant flag). | ||
func (c *cli) getTenant() (tenant, error) { | ||
if err := c.init(); err != nil { | ||
return tenant{}, err | ||
} | ||
|
||
t, ok := c.data.Tenants[c.tenant] | ||
t, ok := c.config.Tenants[c.tenant] | ||
if !ok { | ||
return tenant{}, fmt.Errorf("Unable to find tenant: %s", c.tenant) | ||
} | ||
|
||
return t, nil | ||
} | ||
|
||
func (c *cli) init() error { | ||
var err error | ||
c.initOnce.Do(func() { | ||
if c.path == "" { | ||
c.path = path.Join(os.Getenv("HOME"), ".config", "auth0", "config.json") | ||
} | ||
// addTenant assigns an existing, or new tenant. This is expected to be called | ||
// after a login has completed. | ||
func (c *cli) addTenant(ten tenant) error { | ||
// init will fail here with a `no tenant found` error if we're logging | ||
// in for the first time and that's expected. | ||
_ = c.init() | ||
|
||
// If there's no existing DefaultTenant yet, might as well set the | ||
// first successfully logged in tenant during onboarding. | ||
if c.config.DefaultTenant == "" { | ||
c.config.DefaultTenant = ten.Name | ||
} | ||
|
||
var buf []byte | ||
if buf, err = ioutil.ReadFile(c.path); err != nil { | ||
return | ||
} | ||
// If we're dealing with an empty file, we'll need to initialize this | ||
// map. | ||
if c.config.Tenants == nil { | ||
c.config.Tenants = map[string]tenant{} | ||
} | ||
|
||
if err = json.Unmarshal(buf, &c.data); err != nil { | ||
return | ||
} | ||
c.config.Tenants[ten.Name] = ten | ||
|
||
if c.tenant == "" && c.data.DefaultTenant == "" { | ||
err = fmt.Errorf("Not yet configured. Try `auth0 login`.") | ||
return | ||
dir := filepath.Dir(c.path) | ||
if _, err := os.Stat(dir); os.IsNotExist(err) { | ||
if err := os.MkdirAll(dir, 0700); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
buf, err := json.MarshalIndent(c.config, "", " ") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if c.tenant == "" { | ||
c.tenant = c.data.DefaultTenant | ||
if err := ioutil.WriteFile(c.path, buf, 0600); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (c *cli) init() error { | ||
c.initOnce.Do(func() { | ||
// Initialize the context -- e.g. the configuration | ||
// information, tenants, etc. | ||
if c.errOnce = c.initContext(); c.errOnce != nil { | ||
return | ||
} | ||
c.renderer.Tenant = c.tenant | ||
|
||
// Determine what the desired output format is. | ||
format := strings.ToLower(c.format) | ||
if format != "" && format != string(display.OutputFormatJSON) { | ||
err = fmt.Errorf("Invalid format. Use `--format=json` or omit this option to use the default format.") | ||
c.errOnce = fmt.Errorf("Invalid format. Use `--format=json` or omit this option to use the default format.") | ||
return | ||
} | ||
|
||
c.renderer = &display.Renderer{ | ||
Tenant: c.tenant, | ||
MessageWriter: os.Stderr, | ||
ResultWriter: os.Stdout, | ||
Format: display.OutputFormat(format), | ||
} | ||
c.renderer.Format = display.OutputFormat(format) | ||
Comment on lines
-107
to
+166
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we've already initialized a |
||
}) | ||
|
||
return err | ||
// Once initialized, we'll keep returning the same err that was | ||
// originally encountered. | ||
return c.errOnce | ||
Comment on lines
+169
to
+171
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a bug fix in the UX such that you'll occasionally get |
||
} | ||
|
||
func (c *cli) initContext() (err error) { | ||
if c.path == "" { | ||
c.path = path.Join(os.Getenv("HOME"), ".config", "auth0", "config.json") | ||
woloski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
if _, err := os.Stat(c.path); os.IsNotExist(err) { | ||
return fmt.Errorf("Not yet configured. Try `auth0 login`.") | ||
} | ||
|
||
var buf []byte | ||
if buf, err = ioutil.ReadFile(c.path); err != nil { | ||
return err | ||
} | ||
|
||
if err := json.Unmarshal(buf, &c.config); err != nil { | ||
return err | ||
} | ||
|
||
if c.tenant == "" && c.config.DefaultTenant == "" { | ||
return fmt.Errorf("Not yet configured. Try `auth0 login`.") | ||
} | ||
|
||
if c.tenant == "" { | ||
c.tenant = c.config.DefaultTenant | ||
} | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We forgot to include this -- ultimately this is the parameter we thread through to the
management.New
call (e.g. https://github.com/go-auth0/auth0)