diff --git a/docs/auth0_apis_create.md b/docs/auth0_apis_create.md index 1e23ac5d0..3de450543 100644 --- a/docs/auth0_apis_create.md +++ b/docs/auth0_apis_create.md @@ -25,7 +25,8 @@ auth0 apis create [flags] auth0 apis create --name myapi --identifier http://my-api --token-lifetime 6100 auth0 apis create --name myapi --identifier http://my-api --token-lifetime 6100 --offline-access=true auth0 apis create --name myapi --identifier http://my-api --token-lifetime 6100 --offline-access=false --scopes "letter:write,letter:read" - auth0 apis create -n myapi -i http://my-api -t 6100 -o false -s "letter:write,letter:read" --json + auth0 apis create --name myapi --identifier http://my-api --token-lifetime 6100 --offline-access=false --scopes "letter:write,letter:read" --signing-alg "RS256" + auth0 apis create -n myapi -i http://my-api -t 6100 -o false -s "letter:write,letter:read" --signing-alg "RS256" --json ``` @@ -37,6 +38,7 @@ auth0 apis create [flags] -n, --name string Name of the API. -o, --offline-access Whether Refresh Tokens can be issued for this API (true) or not (false). -s, --scopes strings Comma-separated list of scopes (permissions). + --signing-alg string Algorithm used to sign JWTs. Can be HS256 or RS256. PS256 available via addon. (default "RS256") -l, --token-lifetime int The amount of time in seconds that the token will be valid after being issued. Default value is 86400 seconds (1 day). ``` diff --git a/docs/auth0_apis_update.md b/docs/auth0_apis_update.md index 1419f0c06..b0b637901 100644 --- a/docs/auth0_apis_update.md +++ b/docs/auth0_apis_update.md @@ -24,8 +24,8 @@ auth0 apis update [flags] auth0 apis update --name myapi auth0 apis update --name myapi --token-lifetime 6100 auth0 apis update --name myapi --token-lifetime 6100 --offline-access=false - auth0 apis update --name myapi --token-lifetime 6100 --offline-access=false --scopes "letter:write,letter:read" - auth0 apis update -n myapi -t 6100 -o false -s "letter:write,letter:read" --json + auth0 apis update --name myapi --token-lifetime 6100 --offline-access=false --scopes "letter:write,letter:read" --signing-alg "RS256" + auth0 apis update -n myapi -t 6100 -o false -s "letter:write,letter:read" --signing-alg "RS256" --json ``` @@ -36,6 +36,7 @@ auth0 apis update [flags] -n, --name string Name of the API. -o, --offline-access Whether Refresh Tokens can be issued for this API (true) or not (false). -s, --scopes strings Comma-separated list of scopes (permissions). + --signing-alg string Algorithm used to sign JWTs. Can be HS256 or RS256. PS256 available via addon. (default "RS256") -l, --token-lifetime int The amount of time in seconds that the token will be valid after being issued. Default value is 86400 seconds (1 day). ``` diff --git a/internal/cli/apis.go b/internal/cli/apis.go index ffb91f9a4..135b699b3 100644 --- a/internal/cli/apis.go +++ b/internal/cli/apis.go @@ -15,6 +15,8 @@ import ( "github.com/auth0/auth0-cli/internal/prompt" ) +const apiDefaultTokenLifetime = 86400 + var ( apiID = Argument{ Name: "Id", @@ -55,6 +57,11 @@ var ( Help: "Whether Refresh Tokens can be issued for this API (true) or not (false).", AlwaysPrompt: true, } + apiSigningAlgorithm = Flag{ + Name: "Signing Algorithm", + LongForm: "signing-alg", + Help: "Algorithm used to sign JWTs. Can be HS256 or RS256. PS256 available via addon.", + } apiNumber = Flag{ Name: "Number", LongForm: "number", @@ -204,6 +211,7 @@ func createAPICmd(cli *cli) *cobra.Command { Scopes []string TokenLifetime int AllowOfflineAccess bool + SigningAlgorithm string } cmd := &cobra.Command{ @@ -220,7 +228,8 @@ func createAPICmd(cli *cli) *cobra.Command { auth0 apis create --name myapi --identifier http://my-api --token-lifetime 6100 auth0 apis create --name myapi --identifier http://my-api --token-lifetime 6100 --offline-access=true auth0 apis create --name myapi --identifier http://my-api --token-lifetime 6100 --offline-access=false --scopes "letter:write,letter:read" - auth0 apis create -n myapi -i http://my-api -t 6100 -o false -s "letter:write,letter:read" --json`, + auth0 apis create --name myapi --identifier http://my-api --token-lifetime 6100 --offline-access=false --scopes "letter:write,letter:read" --signing-alg "RS256" + auth0 apis create -n myapi -i http://my-api -t 6100 -o false -s "letter:write,letter:read" --signing-alg "RS256" --json`, RunE: func(cmd *cobra.Command, args []string) error { if err := apiName.Ask(cmd, &inputs.Name, nil); err != nil { return err @@ -234,7 +243,7 @@ func createAPICmd(cli *cli) *cobra.Command { return err } - defaultTokenLifetime := strconv.Itoa(apiDefaultTokenLifetime()) + defaultTokenLifetime := strconv.Itoa(apiDefaultTokenLifetime) if err := apiTokenLifetime.Ask(cmd, &inputs.TokenLifetime, &defaultTokenLifetime); err != nil { return err } @@ -243,20 +252,24 @@ func createAPICmd(cli *cli) *cobra.Command { return err } + if err := apiSigningAlgorithm.Ask(cmd, &inputs.SigningAlgorithm, auth0.String("RS256")); err != nil { + return err + } + api := &management.ResourceServer{ Name: &inputs.Name, Identifier: &inputs.Identifier, AllowOfflineAccess: &inputs.AllowOfflineAccess, TokenLifetime: &inputs.TokenLifetime, + SigningAlgorithm: &inputs.SigningAlgorithm, } if len(inputs.Scopes) > 0 { api.Scopes = apiScopesFor(inputs.Scopes) } - // Set token lifetime if inputs.TokenLifetime <= 0 { - api.TokenLifetime = auth0.Int(apiDefaultTokenLifetime()) + api.TokenLifetime = auth0.Int(apiDefaultTokenLifetime) } else { api.TokenLifetime = auth0.Int(inputs.TokenLifetime) } @@ -264,10 +277,16 @@ func createAPICmd(cli *cli) *cobra.Command { if err := ansi.Waiting(func() error { return cli.api.ResourceServer.Create(cmd.Context(), api) }); err != nil { - return fmt.Errorf("An unexpected error occurred while attempting to create an API with name '%s' and identifier '%s': %w", inputs.Name, inputs.Identifier, err) + return fmt.Errorf( + "failed to create an API with name '%s' and identifier '%s': %w", + inputs.Name, + inputs.Identifier, + err, + ) } cli.renderer.APICreate(api) + return nil }, } @@ -278,6 +297,7 @@ func createAPICmd(cli *cli) *cobra.Command { apiScopes.RegisterStringSlice(cmd, &inputs.Scopes, nil) apiOfflineAccess.RegisterBool(cmd, &inputs.AllowOfflineAccess, false) apiTokenLifetime.RegisterInt(cmd, &inputs.TokenLifetime, 0) + apiSigningAlgorithm.RegisterString(cmd, &inputs.SigningAlgorithm, "RS256") return cmd } @@ -289,6 +309,7 @@ func updateAPICmd(cli *cli) *cobra.Command { Scopes []string TokenLifetime int AllowOfflineAccess bool + SigningAlgorithm string } cmd := &cobra.Command{ @@ -304,26 +325,23 @@ func updateAPICmd(cli *cli) *cobra.Command { auth0 apis update --name myapi auth0 apis update --name myapi --token-lifetime 6100 auth0 apis update --name myapi --token-lifetime 6100 --offline-access=false - auth0 apis update --name myapi --token-lifetime 6100 --offline-access=false --scopes "letter:write,letter:read" - auth0 apis update -n myapi -t 6100 -o false -s "letter:write,letter:read" --json`, + auth0 apis update --name myapi --token-lifetime 6100 --offline-access=false --scopes "letter:write,letter:read" --signing-alg "RS256" + auth0 apis update -n myapi -t 6100 -o false -s "letter:write,letter:read" --signing-alg "RS256" --json`, RunE: func(cmd *cobra.Command, args []string) error { - var current *management.ResourceServer - if len(args) == 0 { - err := apiID.Pick(cmd, &inputs.ID, cli.apiPickerOptions) - if err != nil { + if err := apiID.Pick(cmd, &inputs.ID, cli.apiPickerOptions); err != nil { return err } } else { inputs.ID = args[0] } - if err := ansi.Waiting(func() error { - var err error - current, err = cli.api.ResourceServer.Read(cmd.Context(), url.PathEscape(inputs.ID)) + var current *management.ResourceServer + if err := ansi.Waiting(func() (err error) { + current, err = cli.api.ResourceServer.Read(cmd.Context(), inputs.ID) return err }); err != nil { - return fmt.Errorf("Unable to load API: %w", err) + return fmt.Errorf("failed to find API with ID %q: %w", inputs.ID, err) } if err := apiName.AskU(cmd, &inputs.Name, current.Name); err != nil { @@ -334,48 +352,55 @@ func updateAPICmd(cli *cli) *cobra.Command { return err } - currentTokenLifetime := strconv.Itoa(auth0.IntValue(current.TokenLifetime)) - if err := apiTokenLifetime.AskU(cmd, &inputs.TokenLifetime, ¤tTokenLifetime); err != nil { + currentTokenLifetime := strconv.Itoa(current.GetTokenLifetime()) + if err := apiTokenLifetime.AskIntU(cmd, &inputs.TokenLifetime, ¤tTokenLifetime); err != nil { return err } if !apiOfflineAccess.IsSet(cmd) { - inputs.AllowOfflineAccess = auth0.BoolValue(current.AllowOfflineAccess) + inputs.AllowOfflineAccess = current.GetAllowOfflineAccess() } if err := apiOfflineAccess.AskBoolU(cmd, &inputs.AllowOfflineAccess, current.AllowOfflineAccess); err != nil { return err } + if err := apiSigningAlgorithm.AskU(cmd, &inputs.SigningAlgorithm, current.SigningAlgorithm); err != nil { + return err + } + api := &management.ResourceServer{ AllowOfflineAccess: &inputs.AllowOfflineAccess, } - if len(inputs.Name) == 0 { - api.Name = current.Name - } else { + api.Name = current.Name + if len(inputs.Name) != 0 { api.Name = &inputs.Name } - if len(inputs.Scopes) == 0 { - api.Scopes = current.Scopes - } else { + api.Scopes = current.Scopes + if len(inputs.Scopes) != 0 { api.Scopes = apiScopesFor(inputs.Scopes) } - if inputs.TokenLifetime == 0 { - api.TokenLifetime = current.TokenLifetime - } else { + api.TokenLifetime = current.TokenLifetime + if inputs.TokenLifetime != 0 { api.TokenLifetime = &inputs.TokenLifetime } + api.SigningAlgorithm = current.SigningAlgorithm + if inputs.SigningAlgorithm != "" { + api.SigningAlgorithm = &inputs.SigningAlgorithm + } + if err := ansi.Waiting(func() error { return cli.api.ResourceServer.Update(cmd.Context(), current.GetID(), api) }); err != nil { - return fmt.Errorf("An unexpected error occurred while trying to update an API with Id '%s': %w", inputs.ID, err) + return fmt.Errorf("failed to update the API with ID %q: %w", inputs.ID, err) } cli.renderer.APIUpdate(api) + return nil }, } @@ -385,6 +410,7 @@ func updateAPICmd(cli *cli) *cobra.Command { apiScopes.RegisterStringSliceU(cmd, &inputs.Scopes, nil) apiOfflineAccess.RegisterBoolU(cmd, &inputs.AllowOfflineAccess, false) apiTokenLifetime.RegisterIntU(cmd, &inputs.TokenLifetime, 0) + apiSigningAlgorithm.RegisterStringU(cmd, &inputs.SigningAlgorithm, "RS256") return cmd } @@ -550,10 +576,6 @@ func apiScopesFor(scopes []string) *[]management.ResourceServerScope { return &models } -func apiDefaultTokenLifetime() int { - return 86400 -} - func (c *cli) apiPickerOptions(ctx context.Context) (pickerOptions, error) { return c.filteredAPIPickerOptions(ctx, func(r *management.ResourceServer) bool { return true diff --git a/internal/display/apis.go b/internal/display/apis.go index 6b00c5921..581c318a4 100644 --- a/internal/display/apis.go +++ b/internal/display/apis.go @@ -13,12 +13,13 @@ import ( ) type apiView struct { - ID string - Name string - Identifier string - Scopes string - TokenLifetime int - OfflineAccess string + ID string + Name string + Identifier string + Scopes string + TokenLifetime int + OfflineAccess string + SigningAlgorithm string raw interface{} } @@ -39,6 +40,7 @@ func (v *apiView) KeyValues() [][]string { {"SCOPES", v.Scopes}, {"TOKEN LIFETIME", strconv.Itoa(v.TokenLifetime)}, {"ALLOW OFFLINE ACCESS", v.OfflineAccess}, + {"SIGNING ALGORITHM", v.SigningAlgorithm}, } } @@ -111,14 +113,14 @@ func (r *Renderer) APIUpdate(api *management.ResourceServer) { func makeAPIView(api *management.ResourceServer) (*apiView, bool) { scopes, scopesTruncated := getScopes(api.GetScopes()) view := &apiView{ - ID: ansi.Faint(api.GetID()), - Name: api.GetName(), - Identifier: api.GetIdentifier(), - Scopes: scopes, - TokenLifetime: api.GetTokenLifetime(), - OfflineAccess: boolean(api.GetAllowOfflineAccess()), - - raw: api, + ID: ansi.Faint(api.GetID()), + Name: api.GetName(), + Identifier: api.GetIdentifier(), + Scopes: scopes, + TokenLifetime: api.GetTokenLifetime(), + OfflineAccess: boolean(api.GetAllowOfflineAccess()), + SigningAlgorithm: api.GetSigningAlgorithm(), + raw: api, } return view, scopesTruncated } diff --git a/test/integration/apis-test-cases.yaml b/test/integration/apis-test-cases.yaml index 9d30e6fc6..780c0932a 100644 --- a/test/integration/apis-test-cases.yaml +++ b/test/integration/apis-test-cases.yaml @@ -22,7 +22,7 @@ tests: - number flag invalid, please pass a number between 1 and 1000 003 - apis create and check data: - command: auth0 apis create --name integration-test-api-def1 --identifier http://integration-test-api-def1 --scopes read:todos,write:todos --json + command: auth0 apis create --name integration-test-api-def1 --identifier http://integration-test-api-def1 --scopes read:todos,write:todos --signing-alg RS256 --json exit-code: 0 stdout: json: @@ -31,9 +31,10 @@ tests: scopes: "[map[value:read:todos] map[value:write:todos]]" token_lifetime: "86400" allow_offline_access: "false" + signing_alg: "RS256" 004 - apis create and check output: - command: auth0 apis create --name integration-test-api-def2 --identifier http://integration-test-api-def2 --scopes read:todos,write:todos + command: auth0 apis create --name integration-test-api-def2 --identifier http://integration-test-api-def2 --scopes read:todos,write:todos --signing-alg RS256 exit-code: 0 stdout: contains: @@ -42,6 +43,7 @@ tests: - SCOPES read:todos write:todos - TOKEN LIFETIME 86400 - ALLOW OFFLINE ACCESS ✗ + - SIGNING ALGORITHM RS256 # Test 'apis create' --token-lifetime flag 005 - apis create token lifetime 1000 and check data: @@ -142,6 +144,13 @@ tests: allow_offline_access: "false" exit-code: 0 + 018 - apis update signing algorithm: + command: auth0 apis update $(./test/integration/scripts/get-api-id.sh) --signing-alg=HS256 --json + stdout: + json: + signing_alg: "HS256" + exit-code: 0 + 018 - it successfully prints out a URL to open: command: auth0 apis open $(./test/integration/scripts/get-api-id.sh) --no-input exit-code: 0