diff --git a/README.md b/README.md index 0a5d419..589cf15 100644 --- a/README.md +++ b/README.md @@ -7,21 +7,27 @@ This package contains a command line interface (CLI) for interacting with [STRM ## Installation +### Builds +The STRM CLI is available for major OS platforms: Linux, Mac and Windows. Please note Windows builds are not tested by us, but should work properly. + ### Manually Download the latest release for your platform from the [releases page](https://github.com/strmprivacy/cli/releases/latest). Put the binary somewhere on your path. +#### Authentication +Authentication is handled through the browser with the `strm auth login` command. If you can't login through browser (e.g. when using the CLI in scripts or on remote machines), a headless auth flow is supported through the `--remote` and `--non-interactive` flags. Note: this requires both a browser-accessible machine to run `--remote` to initiate authentication and the non-browser machine to run `strm auth login --non-interactive`. The help command `strm auth login --help` also provides directions. + #### Shell Completion -In order to set up command completion, please follow the instructions below: +In order to set up command completion, please follow the instructions below: - for `bash` users \ add the following line to your `.bash_profile` or `.bashrc`: `source <(strm completion bash)` or, to load completions for each session, execute once: - - Linux users: `strm completion bash > /etc/bash_completion.d/strm` - - macOS users: `strm completion bash > /usr/local/etc/bash_completion.d/strm` + - Linux users: `strm completion bash > /etc/bash_completion.d/strm` + - macOS users: `strm completion bash > /usr/local/etc/bash_completion.d/strm` - for `zsh` users \ ensure that shell completion is enabled, then run (only needs to be done once): `strm completion zsh > "${fpath[1]}/_strm"` @@ -94,7 +100,7 @@ manual editing. In this directory you can also find all entities that have been `save`d (see the [Save](#configuration) option). These entities are saved in the following files: `//.json`, where `Entity` is the Entity name, -i.e. "Stream" or "DataConnector" and the `name` is the unique name of the created entity, i.e. "MyImportantStream" or +i.e. "Stream" or "DataConnector" and the `name` is the unique name of the created entity, i.e. "MyImportantStream" or "s3-data-connector". ## Getting help @@ -114,4 +120,3 @@ you have to option to include an MWE, please do so. See our [documentation](https://docs.strmprivacy.io) or [reach out to us](https://docs.strmprivacy.io/docs/latest/contact/index.html). - diff --git a/pkg/auth/cmd.go b/pkg/auth/cmd.go index 871abe0..a2dcf17 100644 --- a/pkg/auth/cmd.go +++ b/pkg/auth/cmd.go @@ -14,25 +14,19 @@ const ( nonInteractiveRemoteHostShortFlag = "r" ) -var longDocPrintToken = util.LongDocsUsage(` -Print the current (JWT) access token to the terminal that can be used in a http header. Note that the token is printed -on °stdout°, and the Expiry on °stderr° so it’s easy to capture the token for scripting use with - -°°°bash -export token=$(strm auth print-access-token) -°°° - -Note that this token might be expired, so a refresh may be required. Use token as follows: -'Authorization: Bearer <token>' - -`) - func LoginCmd() *cobra.Command { loginCmd := &cobra.Command{ Use: "login", Short: "Login", - Long: `Log a user in using its Console credentials and save the login token to disk, -to allow the CLI access to the STRM Privacy APIs.`, + Long: util.LongDocsUsage(` +Log a user in using its Console credentials and save the login token to disk, to allow the CLI access to the STRM Privacy APIs. + +Authentication is handled through the browser with the ` + "`" + common.RootCommandName + " auth login`" + ` command. If you +can't login through browser (e.g. when using the CLI in scripts or on remote machines), a headless auth flow is supported +through the ` + "`--remote` and `--non-interactive`" + ` flags. Note: this requires both a browser-accessible machine to +run ` + "`--remote`" + ` to initiate authentication and the non-browser machine to run ` + "`" + common.RootCommandName + " auth login --non-interactive`" + `. +The help command ` + "`" + common.RootCommandName + " auth login --help`" + ` also provides directions. +`), Run: func(cmd *cobra.Command, args []string) { login(cmd) }, @@ -41,7 +35,7 @@ to allow the CLI access to the STRM Privacy APIs.`, } flags := loginCmd.Flags() - flags.BoolP(nonInteractiveTargetHostFlag, nonInteractiveTargetHostShortFlag, false, fmt.Sprintf("is the current host a headless system, without access to a browser? If true, use `%s auth login --%s`", common.RootCommandName, nonInteractiveRemoteHostFlag)) + flags.BoolP(nonInteractiveTargetHostFlag, nonInteractiveTargetHostShortFlag, false, fmt.Sprintf("is the current host a headless system, without access to a browser? If true, use %s auth login --%s", common.RootCommandName, nonInteractiveRemoteHostFlag)) flags.BoolP(nonInteractiveRemoteHostFlag, nonInteractiveRemoteHostShortFlag, false, "should the current host act as a remote login for a headless system? If true, an authorization code flow result will be printed, that can be used for the non-interactive target host.") loginCmd.MarkFlagsMutuallyExclusive(nonInteractiveTargetHostFlag, nonInteractiveRemoteHostFlag) @@ -67,7 +61,18 @@ func PrintTokenCmd() *cobra.Command { cmd := &cobra.Command{ Use: "print-access-token", Short: "Print your current access-token to stdout", - Long: longDocPrintToken, + Long: util.LongDocsUsage(` +Print the current (JWT) access token to the terminal that can be used in a http header. Note that the token is printed +on °stdout°, and the Expiry on °stderr° so it’s easy to capture the token for scripting use with + +°°°bash +export token=$(strm auth print-access-token) +°°° + +Note that this token might be expired, so a refresh may be required. Use token as follows: +'Authorization: Bearer <token>' + +`), Run: func(cmd *cobra.Command, args []string) { printAccessToken() }, diff --git a/pkg/auth/user.go b/pkg/auth/user.go index 911269e..4043460 100644 --- a/pkg/auth/user.go +++ b/pkg/auth/user.go @@ -60,7 +60,7 @@ func (authenticator *Authenticator) AccessToken() *string { tokens, err := authenticator.tokenSource.Token() if err != nil { authenticator.revoke() - common.CliExit(errors.New(fmt.Sprintf("Your session has expired. Please re-login using: `%s auth login`", common.RootCommandName))) + common.CliExit(errors.New(fmt.Sprintf("Your session has expired. Please re-login using: %s auth login", common.RootCommandName))) } if authenticator.storedToken.AccessToken != tokens.AccessToken { authenticator.populateValues(oauthTokenToStoredToken(*tokens)) @@ -87,7 +87,7 @@ func (authenticator *Authenticator) login(cmd *cobra.Command) { if nonInteractiveTarget { baseOauthCliConfig.NonInteractive = true - baseOauthCliConfig.NonInteractivePromptText = fmt.Sprintf("On a machine with access to a browser, use `%s auth login --%s` to retrieve a valid code:\n", common.RootCommandName, nonInteractiveRemoteHostFlag) + baseOauthCliConfig.NonInteractivePromptText = fmt.Sprintf("On a machine with access to a browser, use %s auth login --%s to retrieve a valid code:\n", common.RootCommandName, nonInteractiveRemoteHostFlag) baseOauthCliConfig.OAuth2Config.RedirectURL = "http://localhost:10000" eg.Go(authenticator.handleLogin(ctx, baseOauthCliConfig)) } else { @@ -134,9 +134,9 @@ func (authenticator *Authenticator) handleLogin(ctx context.Context, cfg oauth2c switch rootCauseErr.(type) { case base64.CorruptInputError: - common.CliExit(errors.New(fmt.Sprintf("\nInvalid base64 encoded input. Make sure that the input you provide is retrieved using `%s auth login --%s`", common.RootCommandName, nonInteractiveRemoteHostFlag))) + common.CliExit(errors.New(fmt.Sprintf("\nInvalid base64 encoded input. Make sure that the input you provide is retrieved using %s auth login --%s", common.RootCommandName, nonInteractiveRemoteHostFlag))) case *json.SyntaxError: - common.CliExit(errors.New(fmt.Sprintf("\nMalformed JSON input. Make sure that the input you provide is retrieved using `%s auth login --%s`", common.RootCommandName, nonInteractiveRemoteHostFlag))) + common.CliExit(errors.New(fmt.Sprintf("\nMalformed JSON input. Make sure that the input you provide is retrieved using %s auth login --%s", common.RootCommandName, nonInteractiveRemoteHostFlag))) case *oauth2.RetrieveError: retrieveErr := (rootCauseErr).(*oauth2.RetrieveError) common.CliExit(errors.New(fmt.Sprintf("\nUnable to exchange authorization code with token (HTTP Code: %v, Body: %v)", retrieveErr.Response.Status, string(retrieveErr.Body)))) diff --git a/pkg/common/common.go b/pkg/common/common.go index 53815bd..9b30676 100644 --- a/pkg/common/common.go +++ b/pkg/common/common.go @@ -45,7 +45,7 @@ func Abort(format string, args ...interface{}) { } func UnauthenticatedError() error { - return errors.New(fmt.Sprintf("No login information found. Use: `%v auth login` first.", RootCommandName)) + return errors.New(fmt.Sprintf("No login information found. Use: %v auth login first.", RootCommandName)) } func UnauthenticatedErrorWithExit() { diff --git a/pkg/entity/organization/organization.go b/pkg/entity/organization/organization.go index efa6726..8e8400c 100644 --- a/pkg/entity/organization/organization.go +++ b/pkg/entity/organization/organization.go @@ -23,7 +23,7 @@ func SetupClient(clientConnection *grpc.ClientConn, ctx context.Context) { func inviteUsers(args []string, cmd *cobra.Command) { if apiContext == nil { - common.CliExit(errors.New(fmt.Sprint("No login information found. Use: `strm auth login` first."))) + common.CliExit(errors.New(fmt.Sprintf("No login information found. Use: %s auth login first.", common.RootCommandName))) } emails := getEmails(args, cmd) var invites []*organizations.UserInvite diff --git a/pkg/entity/project/cmd.go b/pkg/entity/project/cmd.go index 622f7e2..764eb52 100644 --- a/pkg/entity/project/cmd.go +++ b/pkg/entity/project/cmd.go @@ -121,7 +121,7 @@ func DeleteCmd() *cobra.Command { func deleteConfirmation(projectName string) bool { prompt := promptui.Prompt{ - Label: "Confirm you want to delete `" + projectName + "` by entering the project name", + Label: "Confirm you want to delete '" + projectName + "' by entering the project name", } result, _ := prompt.Run() return result == projectName diff --git a/pkg/entity/project/project.go b/pkg/entity/project/project.go index 5be748f..f3841c7 100644 --- a/pkg/entity/project/project.go +++ b/pkg/entity/project/project.go @@ -38,7 +38,7 @@ func ListProjects() *projects.ListProjectsResponse { func ListProjectsWithActive() ProjectsAndActiveProject { if projectsAndActiveProject == nil { if apiContext == nil { - common.CliExit(errors.New(fmt.Sprint("No login information found. Use: `" + common.RootCommandName + " auth login` first."))) + common.CliExit(errors.New(fmt.Sprintf("No login information found. Use: %s auth login first.", common.RootCommandName))) } req := &projects.ListProjectsRequest{} diff --git a/pkg/entity/user/cmd.go b/pkg/entity/user/cmd.go index 091b121..a366867 100644 --- a/pkg/entity/user/cmd.go +++ b/pkg/entity/user/cmd.go @@ -21,8 +21,8 @@ func ListCmd() *cobra.Command { strm list users --organization EMAIL FIRST NAME LAST NAME USER ROLES - [...]@strmprivacy.io bob rbac [MEMBER] - [...]@strmprivacy.io Demo STRM [ADMIN MEMBER] + [...]@strmprivacy.io bob rbac [MEMBER] + [...]@strmprivacy.io Demo STRM [ADMIN MEMBER] `), PreRun: func(cmd *cobra.Command, args []string) { printer = configurePrinter(cmd) diff --git a/test/auth_test.go b/test/auth_test.go index 28788ed..3ce12c3 100644 --- a/test/auth_test.go +++ b/test/auth_test.go @@ -11,5 +11,5 @@ func TestAuthAccessTokenOutputsAnErrorWhenNotLoggedIn(t *testing.T) { out := ExecuteCliAndGetOutput(t, tokenFileName, "auth", "print-access-token") - assert.Equal(t, out, "No login information found. Use: `dstrm auth login` first.\n") + assert.Equal(t, out, "No login information found. Use: dstrm auth login first.\n") }