diff --git a/management/server/http/handlers/accounts/accounts_handler.go b/management/server/http/handlers/accounts/accounts_handler.go index 9fbadcbf5..c4cba5962 100644 --- a/management/server/http/handlers/accounts/accounts_handler.go +++ b/management/server/http/handlers/accounts/accounts_handler.go @@ -295,6 +295,13 @@ func (h *handler) updateAccountRequestSettings(req api.PutApiAccountsAccountIdJS } } + if returnSettings.AgentNetworkOnly && + (returnSettings.DashboardFeatures == nil || + returnSettings.DashboardFeatures.AgentNetwork == nil || + !*returnSettings.DashboardFeatures.AgentNetwork) { + return nil, status.Errorf(status.InvalidArgument, "agent network only mode requires dashboard_features.agent_network to be enabled") + } + return returnSettings, nil } diff --git a/management/server/http/handlers/accounts/accounts_handler_test.go b/management/server/http/handlers/accounts/accounts_handler_test.go index 49a9848c0..0069efcb7 100644 --- a/management/server/http/handlers/accounts/accounts_handler_test.go +++ b/management/server/http/handlers/accounts/accounts_handler_test.go @@ -288,7 +288,7 @@ func TestAccounts_AccountsHandler(t *testing.T) { expectedBody: true, requestType: http.MethodPut, requestPath: "/api/accounts/" + accountID, - requestBody: bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552000,\"peer_login_expiration_enabled\": true,\"agent_network_only\": true},\"onboarding\": {\"onboarding_flow_pending\": true,\"signup_form_pending\": true}}"), + requestBody: bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552000,\"peer_login_expiration_enabled\": true,\"agent_network_only\": true,\"dashboard_features\": {\"agent_network\": true}},\"onboarding\": {\"onboarding_flow_pending\": true,\"signup_form_pending\": true}}"), expectedStatus: http.StatusOK, expectedSettings: api.AccountSettings{ PeerLoginExpiration: 15552000, @@ -305,13 +305,25 @@ func TestAccounts_AccountsHandler(t *testing.T) { AutoUpdateVersion: sr(""), MetricsPushEnabled: br(false), AgentNetworkOnly: br(true), - EmbeddedIdpEnabled: br(false), - LocalAuthDisabled: br(false), - LocalMfaEnabled: br(false), + DashboardFeatures: &api.AccountDashboardFeatures{ + AgentNetwork: br(true), + }, + EmbeddedIdpEnabled: br(false), + LocalAuthDisabled: br(false), + LocalMfaEnabled: br(false), }, expectedArray: false, expectedID: accountID, }, + { + name: "PutAccount fails enabling agent_network_only without dashboard_features", + expectedBody: true, + requestType: http.MethodPut, + requestPath: "/api/accounts/" + accountID, + requestBody: bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552000,\"peer_login_expiration_enabled\": true,\"agent_network_only\": true},\"onboarding\": {\"onboarding_flow_pending\": true,\"signup_form_pending\": true}}"), + expectedStatus: http.StatusUnprocessableEntity, + expectedArray: false, + }, { name: "PutAccount OK setting dashboard_features agent_network", expectedBody: true, diff --git a/shared/management/http/api/openapi.yml b/shared/management/http/api/openapi.yml index c61dbd2f8..529cd2225 100644 --- a/shared/management/http/api/openapi.yml +++ b/shared/management/http/api/openapi.yml @@ -376,7 +376,7 @@ components: type: boolean example: false agent_network_only: - description: Limits the dashboard to the Agent Network surface for this account. Set for accounts created via netbird.ai signups and can be disabled later. + description: Limits the dashboard to the Agent Network surface for this account. Set for accounts created via netbird.ai signups and can be disabled later. Enabling this requires dashboard_features.agent_network to be true in the same request. type: boolean example: false dashboard_features: @@ -414,7 +414,7 @@ components: type: object properties: agent_network: - description: Controls the Agent Network menu for the account regardless of the deployment feature flag. When true the menu is shown, when false it is hidden, and when omitted the default behavior applies. + description: Controls the Agent Network menu for the account regardless of the deployment feature flag. When true the menu is shown, when false it is hidden, and when omitted the default behavior applies. Must be true when agent_network_only is enabled. type: boolean example: true AccountExtraSettings: diff --git a/shared/management/http/api/types.gen.go b/shared/management/http/api/types.gen.go index 6fc17ef60..4956f9a9b 100644 --- a/shared/management/http/api/types.gen.go +++ b/shared/management/http/api/types.gen.go @@ -1614,7 +1614,7 @@ type Account struct { // AccountDashboardFeatures Per-account dashboard section visibility overrides. Omitted keys follow the default dashboard behavior. type AccountDashboardFeatures struct { - // AgentNetwork Controls the Agent Network menu for the account regardless of the deployment feature flag. When true the menu is shown, when false it is hidden, and when omitted the default behavior applies. + // AgentNetwork Controls the Agent Network menu for the account regardless of the deployment feature flag. When true the menu is shown, when false it is hidden, and when omitted the default behavior applies. Must be true when agent_network_only is enabled. AgentNetwork *bool `json:"agent_network,omitempty"` } @@ -1653,7 +1653,7 @@ type AccountRequest struct { // AccountSettings defines model for AccountSettings. type AccountSettings struct { - // AgentNetworkOnly Limits the dashboard to the Agent Network surface for this account. Set for accounts created via netbird.ai signups and can be disabled later. + // AgentNetworkOnly Limits the dashboard to the Agent Network surface for this account. Set for accounts created via netbird.ai signups and can be disabled later. Enabling this requires dashboard_features.agent_network to be true in the same request. AgentNetworkOnly *bool `json:"agent_network_only,omitempty"` // AutoUpdateAlways When true, updates are installed automatically in the background. When false, updates require user interaction from the UI.