From 86d641e2864f8f4914bf37def119158700b79ae3 Mon Sep 17 00:00:00 2001 From: Patrick Kang <1489148+patrickkang@users.noreply.github.com> Date: Wed, 26 Nov 2025 11:29:32 -0500 Subject: [PATCH 1/5] feat: add interactive api selection for resource server --- internal/cli/apps.go | 45 ++++++++++++++++++++++++------------ internal/cli/apps_test.go | 48 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 14 deletions(-) diff --git a/internal/cli/apps.go b/internal/cli/apps.go index 097f2bd23..039a00725 100644 --- a/internal/cli/apps.go +++ b/internal/cli/apps.go @@ -35,6 +35,10 @@ var ( Name: "Client ID", Help: "Id of the application.", } + apiIdentifierArg = Argument{ + Name: "API Identifier", + Help: "Resource server that this client should be associated with.", + } appName = Flag{ Name: "Name", LongForm: "name", @@ -513,8 +517,24 @@ func createAppCmd(cli *cli) *cobra.Command { // Prompt for resource server identifier if app type is resource_server. if appIsResourceServer { - if err := appResourceServerIdentifier.Ask(cmd, &inputs.ResourceServerIdentifier, nil); err != nil { - return err + if !appResourceServerIdentifier.IsSet(cmd) { + var selectedAPIID string + if err := apiIdentifierArg.Pick(cmd, &selectedAPIID, cli.apiPickerOptions); err != nil { + return err + } + + var selectedAPI *management.ResourceServer + if err := ansi.Waiting(func() error { + var err error + selectedAPI, err = cli.api.ResourceServer.Read(cmd.Context(), selectedAPIID) + return err + }); err != nil { + return fmt.Errorf("failed to read selected API: %w", err) + } + + inputs.ResourceServerIdentifier = selectedAPI.GetIdentifier() + } else if strings.TrimSpace(inputs.ResourceServerIdentifier) == "" { + return fmt.Errorf("resource-server-identifier cannot be empty for resource_server app type") } } @@ -525,14 +545,15 @@ func createAppCmd(cli *cli) *cobra.Command { // Load values into a fresh app instance. a := &management.Client{ - Name: &inputs.Name, - Description: &inputs.Description, - AppType: auth0.String(apiTypeFor(inputs.Type)), - AllowedOrigins: stringSliceToPtr(inputs.AllowedOrigins), - WebOrigins: stringSliceToPtr(inputs.AllowedWebOrigins), - OIDCConformant: &oidcConformant, - JWTConfiguration: &management.ClientJWTConfiguration{Algorithm: &algorithm}, - ClientMetadata: &clientMetadata, + Name: &inputs.Name, + Description: &inputs.Description, + AppType: auth0.String(apiTypeFor(inputs.Type)), + AllowedOrigins: stringSliceToPtr(inputs.AllowedOrigins), + WebOrigins: stringSliceToPtr(inputs.AllowedWebOrigins), + OIDCConformant: &oidcConformant, + JWTConfiguration: &management.ClientJWTConfiguration{Algorithm: &algorithm}, + ClientMetadata: &clientMetadata, + ResourceServerIdentifier: &inputs.ResourceServerIdentifier, } callback := stringSliceToPtr(inputs.Callbacks) @@ -546,10 +567,6 @@ func createAppCmd(cli *cli) *cobra.Command { a.AllowedLogoutURLs = allowedLogoutURLs } - if appIsResourceServer && inputs.ResourceServerIdentifier != "" { - a.ResourceServerIdentifier = &inputs.ResourceServerIdentifier - } - if len(inputs.RefreshToken) != 0 { if err := json.Unmarshal([]byte(inputs.RefreshToken), &a.RefreshToken); err != nil { return fmt.Errorf("apps: %s refreshToken invalid JSON", err) diff --git a/internal/cli/apps_test.go b/internal/cli/apps_test.go index 904b73f5b..a37779ad2 100644 --- a/internal/cli/apps_test.go +++ b/internal/cli/apps_test.go @@ -90,6 +90,54 @@ func TestAppsListCmd(t *testing.T) { } } +func TestAppsCreateCmd(t *testing.T) { + tests := []struct { + name string + args []string + expectedError string + }{ + { + name: "Resource Server - resource-server-identifier is empty string", + args: []string{ + "--name", "My Resource Server App", + "--type", "resource_server", + "--resource-server-identifier", "", + }, + expectedError: "resource-server-identifier cannot be empty for resource_server app type", + }, + { + name: "Resource Server - resource-server-identifier is whitespace only", + args: []string{ + "--name", "My Resource Server App", + "--type", "resource_server", + "--resource-server-identifier", " ", + }, + expectedError: "resource-server-identifier cannot be empty for resource_server app type", + }, + { + name: "Resource Server - resource-server-identifier is tab/newline", + args: []string{ + "--name", "My Resource Server App", + "--type", "resource_server", + "--resource-server-identifier", "\t\n", + }, + expectedError: "resource-server-identifier cannot be empty for resource_server app type", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cli := &cli{} + cli.noInput = true // non-interactive mode + cmd := createAppCmd(cli) + cmd.SetArgs(test.args) + err := cmd.Execute() + + assert.EqualError(t, err, test.expectedError) + }) + } +} + func TestFormatAppSettingsPath(t *testing.T) { assert.Empty(t, formatAppSettingsPath("")) assert.Equal(t, "applications/app-id-1/settings", formatAppSettingsPath("app-id-1")) From 33fffa4fa7a31bb6a59694cef31451f0c40c79ce Mon Sep 17 00:00:00 2001 From: Patrick Kang <1489148+patrickkang@users.noreply.github.com> Date: Wed, 26 Nov 2025 11:39:43 -0500 Subject: [PATCH 2/5] fix lint --- internal/cli/apps_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cli/apps_test.go b/internal/cli/apps_test.go index a37779ad2..7860361a5 100644 --- a/internal/cli/apps_test.go +++ b/internal/cli/apps_test.go @@ -128,7 +128,7 @@ func TestAppsCreateCmd(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { cli := &cli{} - cli.noInput = true // non-interactive mode + cli.noInput = true // Non-interactive mode cmd := createAppCmd(cli) cmd.SetArgs(test.args) err := cmd.Execute() From 37ac2c121426f0f727137d46984e149cce099424 Mon Sep 17 00:00:00 2001 From: Patrick Kang <1489148+patrickkang@users.noreply.github.com> Date: Wed, 26 Nov 2025 11:56:28 -0500 Subject: [PATCH 3/5] fix lint --- internal/cli/apps_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cli/apps_test.go b/internal/cli/apps_test.go index 7860361a5..ad6f3474e 100644 --- a/internal/cli/apps_test.go +++ b/internal/cli/apps_test.go @@ -128,7 +128,7 @@ func TestAppsCreateCmd(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { cli := &cli{} - cli.noInput = true // Non-interactive mode + cli.noInput = true // Non-interactive mode. cmd := createAppCmd(cli) cmd.SetArgs(test.args) err := cmd.Execute() From 65c892f76452174f5779c8be4dfccb91ee6115f6 Mon Sep 17 00:00:00 2001 From: Patrick Kang <1489148+patrickkang@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:14:15 -0500 Subject: [PATCH 4/5] use existing flag --- internal/cli/apps.go | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/internal/cli/apps.go b/internal/cli/apps.go index 039a00725..2247cf96c 100644 --- a/internal/cli/apps.go +++ b/internal/cli/apps.go @@ -35,10 +35,6 @@ var ( Name: "Client ID", Help: "Id of the application.", } - apiIdentifierArg = Argument{ - Name: "API Identifier", - Help: "Resource server that this client should be associated with.", - } appName = Flag{ Name: "Name", LongForm: "name", @@ -519,7 +515,7 @@ func createAppCmd(cli *cli) *cobra.Command { if appIsResourceServer { if !appResourceServerIdentifier.IsSet(cmd) { var selectedAPIID string - if err := apiIdentifierArg.Pick(cmd, &selectedAPIID, cli.apiPickerOptions); err != nil { + if err := appResourceServerIdentifier.Pick(cmd, &selectedAPIID, cli.apiPickerOptions); err != nil { return err } @@ -545,15 +541,14 @@ func createAppCmd(cli *cli) *cobra.Command { // Load values into a fresh app instance. a := &management.Client{ - Name: &inputs.Name, - Description: &inputs.Description, - AppType: auth0.String(apiTypeFor(inputs.Type)), - AllowedOrigins: stringSliceToPtr(inputs.AllowedOrigins), - WebOrigins: stringSliceToPtr(inputs.AllowedWebOrigins), - OIDCConformant: &oidcConformant, - JWTConfiguration: &management.ClientJWTConfiguration{Algorithm: &algorithm}, - ClientMetadata: &clientMetadata, - ResourceServerIdentifier: &inputs.ResourceServerIdentifier, + Name: &inputs.Name, + Description: &inputs.Description, + AppType: auth0.String(apiTypeFor(inputs.Type)), + AllowedOrigins: stringSliceToPtr(inputs.AllowedOrigins), + WebOrigins: stringSliceToPtr(inputs.AllowedWebOrigins), + OIDCConformant: &oidcConformant, + JWTConfiguration: &management.ClientJWTConfiguration{Algorithm: &algorithm}, + ClientMetadata: &clientMetadata, } callback := stringSliceToPtr(inputs.Callbacks) @@ -567,6 +562,10 @@ func createAppCmd(cli *cli) *cobra.Command { a.AllowedLogoutURLs = allowedLogoutURLs } + if appIsResourceServer && inputs.ResourceServerIdentifier != "" { + a.ResourceServerIdentifier = &inputs.ResourceServerIdentifier + } + if len(inputs.RefreshToken) != 0 { if err := json.Unmarshal([]byte(inputs.RefreshToken), &a.RefreshToken); err != nil { return fmt.Errorf("apps: %s refreshToken invalid JSON", err) From 1848deeedf428edd94a9c089f40b540d335687a5 Mon Sep 17 00:00:00 2001 From: Patrick Kang <1489148+patrickkang@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:16:19 -0500 Subject: [PATCH 5/5] use apiPickerOptionsWithoutAuth0 to filter out management api --- internal/cli/apps.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cli/apps.go b/internal/cli/apps.go index 2247cf96c..e84abe225 100644 --- a/internal/cli/apps.go +++ b/internal/cli/apps.go @@ -515,7 +515,7 @@ func createAppCmd(cli *cli) *cobra.Command { if appIsResourceServer { if !appResourceServerIdentifier.IsSet(cmd) { var selectedAPIID string - if err := appResourceServerIdentifier.Pick(cmd, &selectedAPIID, cli.apiPickerOptions); err != nil { + if err := appResourceServerIdentifier.Pick(cmd, &selectedAPIID, cli.apiPickerOptionsWithoutAuth0); err != nil { return err }