diff --git a/cli/usercreate_test.go b/cli/usercreate_test.go
index 5f29f28970..2c8d69fe14 100644
--- a/cli/usercreate_test.go
+++ b/cli/usercreate_test.go
@@ -134,6 +134,7 @@ func TestUserCreate(t *testing.T) {
{
name: "ServiceAccount",
args: []string{"--service-account", "-u", "dean"},
+ err: "Premium feature",
},
{
name: "ServiceAccountLoginType",
diff --git a/coderd/coderdtest/users.go b/coderd/coderdtest/users.go
index f5a798b06e..b77dc5ab5d 100644
--- a/coderd/coderdtest/users.go
+++ b/coderd/coderdtest/users.go
@@ -123,6 +123,10 @@ func UsersPagination(
require.Contains(t, gotUsers[0].Name, "after")
}
+type UsersFilterOptions struct {
+ CreateServiceAccounts bool
+}
+
// UsersFilter creates a set of users to run various filters against for
// testing. It can be used to test filtering both users and group members.
func UsersFilter(
@@ -130,11 +134,16 @@ func UsersFilter(
t *testing.T,
client *codersdk.Client,
db database.Store,
+ options *UsersFilterOptions,
setup func(users []codersdk.User),
fetch func(ctx context.Context, req codersdk.UsersRequest) []codersdk.ReducedUser,
) {
t.Helper()
+ if options == nil {
+ options = &UsersFilterOptions{}
+ }
+
firstUser, err := client.User(setupCtx, codersdk.Me)
require.NoError(t, err, "fetch me")
@@ -211,11 +220,13 @@ func UsersFilter(
}
// Add some service accounts.
- for range 3 {
- _, user := CreateAnotherUserMutators(t, client, orgID, nil, func(r *codersdk.CreateUserRequestWithOrgs) {
- r.ServiceAccount = true
- })
- users = append(users, user)
+ if options.CreateServiceAccounts {
+ for range 3 {
+ _, user := CreateAnotherUserMutators(t, client, orgID, nil, func(r *codersdk.CreateUserRequestWithOrgs) {
+ r.ServiceAccount = true
+ })
+ users = append(users, user)
+ }
}
hashedPassword, err := userpassword.Hash("SomeStrongPassword!")
diff --git a/coderd/members_test.go b/coderd/members_test.go
index 0eee9d1db0..c2bf219c1e 100644
--- a/coderd/members_test.go
+++ b/coderd/members_test.go
@@ -148,7 +148,7 @@ func TestGetOrgMembersFilter(t *testing.T) {
setupCtx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
- coderdtest.UsersFilter(setupCtx, t, client, api.Database, nil, func(testCtx context.Context, req codersdk.UsersRequest) []codersdk.ReducedUser {
+ coderdtest.UsersFilter(setupCtx, t, client, api.Database, nil, nil, func(testCtx context.Context, req codersdk.UsersRequest) []codersdk.ReducedUser {
res, err := client.OrganizationMembersPaginated(testCtx, first.OrganizationID, req)
require.NoError(t, err)
reduced := make([]codersdk.ReducedUser, len(res.Members))
diff --git a/coderd/users.go b/coderd/users.go
index 07603e0b0e..dc883c8953 100644
--- a/coderd/users.go
+++ b/coderd/users.go
@@ -475,6 +475,14 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
}
req.UserLoginType = codersdk.LoginTypeNone
+
+ // Service accounts are a Premium feature.
+ if !api.Entitlements.Enabled(codersdk.FeatureServiceAccounts) {
+ httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
+ Message: fmt.Sprintf("%s is a Premium feature. Contact sales!", codersdk.FeatureServiceAccounts.Humanize()),
+ })
+ return
+ }
} else if req.UserLoginType == "" {
// Default to password auth
req.UserLoginType = codersdk.LoginTypePassword
diff --git a/coderd/users_test.go b/coderd/users_test.go
index c47b1a5293..9f6d297541 100644
--- a/coderd/users_test.go
+++ b/coderd/users_test.go
@@ -979,7 +979,7 @@ func TestPostUsers(t *testing.T) {
require.Equal(t, found.LoginType, codersdk.LoginTypeOIDC)
})
- t.Run("ServiceAccount/OK", func(t *testing.T) {
+ t.Run("ServiceAccount/Unlicensed", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
first := coderdtest.CreateFirstUser(t, client)
@@ -987,98 +987,16 @@ func TestPostUsers(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
- user, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
+ _, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
OrganizationIDs: []uuid.UUID{first.OrganizationID},
Username: "service-acct-ok",
UserLoginType: codersdk.LoginTypeNone,
ServiceAccount: true,
})
- require.NoError(t, err)
- require.Equal(t, codersdk.LoginTypeNone, user.LoginType)
- require.Empty(t, user.Email)
- require.Equal(t, "service-acct-ok", user.Username)
- require.Equal(t, codersdk.UserStatusDormant, user.Status)
- })
-
- t.Run("ServiceAccount/WithEmail", func(t *testing.T) {
- t.Parallel()
- client := coderdtest.New(t, nil)
- first := coderdtest.CreateFirstUser(t, client)
-
- ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
- defer cancel()
-
- _, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
- OrganizationIDs: []uuid.UUID{first.OrganizationID},
- Username: "service-acct-email",
- Email: "should-not-have@email.com",
- ServiceAccount: true,
- })
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
- require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
- require.Contains(t, apiErr.Message, "Email cannot be set for service accounts")
- })
-
- t.Run("ServiceAccount/WithPassword", func(t *testing.T) {
- t.Parallel()
- client := coderdtest.New(t, nil)
- first := coderdtest.CreateFirstUser(t, client)
-
- ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
- defer cancel()
-
- _, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
- OrganizationIDs: []uuid.UUID{first.OrganizationID},
- Username: "service-acct-password",
- Password: "ShouldNotHavePassword123!",
- ServiceAccount: true,
- })
- var apiErr *codersdk.Error
- require.ErrorAs(t, err, &apiErr)
- require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
- require.Contains(t, apiErr.Message, "Password cannot be set for service accounts")
- })
-
- t.Run("ServiceAccount/WithInvalidLoginType", func(t *testing.T) {
- t.Parallel()
- client := coderdtest.New(t, nil)
- first := coderdtest.CreateFirstUser(t, client)
-
- ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
- defer cancel()
-
- _, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
- OrganizationIDs: []uuid.UUID{first.OrganizationID},
- Username: "service-acct-login-type",
- UserLoginType: codersdk.LoginTypePassword,
- ServiceAccount: true,
- })
- var apiErr *codersdk.Error
- require.ErrorAs(t, err, &apiErr)
- require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
- require.Contains(t, apiErr.Message, "Service accounts must use login type 'none'")
- })
-
- t.Run("ServiceAccount/DefaultLoginType", func(t *testing.T) {
- t.Parallel()
- client := coderdtest.New(t, nil)
- first := coderdtest.CreateFirstUser(t, client)
-
- ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
- defer cancel()
-
- user, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
- OrganizationIDs: []uuid.UUID{first.OrganizationID},
- Username: "service-acct-default-login",
- ServiceAccount: true,
- })
- require.NoError(t, err)
-
- found, err := client.User(ctx, user.ID.String())
- require.NoError(t, err)
- require.Equal(t, codersdk.LoginTypeNone, found.LoginType)
- require.Empty(t, found.Email)
+ require.Equal(t, http.StatusForbidden, apiErr.StatusCode())
+ require.Contains(t, apiErr.Message, "Premium feature")
})
t.Run("NonServiceAccount/WithoutEmail", func(t *testing.T) {
@@ -1098,32 +1016,6 @@ func TestPostUsers(t *testing.T) {
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
})
-
- t.Run("ServiceAccount/MultipleWithoutEmail", func(t *testing.T) {
- t.Parallel()
- client := coderdtest.New(t, nil)
- first := coderdtest.CreateFirstUser(t, client)
-
- ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
- defer cancel()
-
- user1, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
- OrganizationIDs: []uuid.UUID{first.OrganizationID},
- Username: "service-acct-multi-1",
- ServiceAccount: true,
- })
- require.NoError(t, err)
- require.Empty(t, user1.Email)
-
- user2, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
- OrganizationIDs: []uuid.UUID{first.OrganizationID},
- Username: "service-acct-multi-2",
- ServiceAccount: true,
- })
- require.NoError(t, err)
- require.Empty(t, user2.Email)
- require.NotEqual(t, user1.ID, user2.ID)
- })
}
func TestNotifyCreatedUser(t *testing.T) {
@@ -1832,7 +1724,7 @@ func TestGetUsersFilter(t *testing.T) {
setupCtx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
- coderdtest.UsersFilter(setupCtx, t, client, api.Database, nil, func(testCtx context.Context, req codersdk.UsersRequest) []codersdk.ReducedUser {
+ coderdtest.UsersFilter(setupCtx, t, client, api.Database, nil, nil, func(testCtx context.Context, req codersdk.UsersRequest) []codersdk.ReducedUser {
res, err := client.Users(testCtx, req)
require.NoError(t, err)
reduced := make([]codersdk.ReducedUser, len(res.Users))
diff --git a/codersdk/deployment.go b/codersdk/deployment.go
index d277fba53f..1c975c19f3 100644
--- a/codersdk/deployment.go
+++ b/codersdk/deployment.go
@@ -196,6 +196,7 @@ const (
FeatureWorkspaceExternalAgent FeatureName = "workspace_external_agent"
FeatureAIBridge FeatureName = "aibridge"
FeatureBoundary FeatureName = "boundary"
+ FeatureServiceAccounts FeatureName = "service_accounts"
FeatureAIGovernanceUserLimit FeatureName = "ai_governance_user_limit"
)
@@ -227,6 +228,7 @@ var (
FeatureWorkspaceExternalAgent,
FeatureAIBridge,
FeatureBoundary,
+ FeatureServiceAccounts,
FeatureAIGovernanceUserLimit,
}
@@ -275,6 +277,7 @@ func (n FeatureName) AlwaysEnable() bool {
FeatureWorkspacePrebuilds: true,
FeatureWorkspaceExternalAgent: true,
FeatureBoundary: true,
+ FeatureServiceAccounts: true,
}[n]
}
@@ -282,7 +285,7 @@ func (n FeatureName) AlwaysEnable() bool {
func (n FeatureName) Enterprise() bool {
switch n {
// Add all features that should be excluded in the Enterprise feature set.
- case FeatureMultipleOrganizations, FeatureCustomRoles:
+ case FeatureMultipleOrganizations, FeatureCustomRoles, FeatureServiceAccounts:
return false
default:
return true
diff --git a/docs/admin/users/headless-auth.md b/docs/admin/users/headless-auth.md
index 6aa780288a..e61124b7e5 100644
--- a/docs/admin/users/headless-auth.md
+++ b/docs/admin/users/headless-auth.md
@@ -1,31 +1,38 @@
# Headless Authentication
-Headless user accounts that cannot use the web UI to log in to Coder. This is
-useful for creating accounts for automated systems, such as CI/CD pipelines or
-for users who only consume Coder via another client/API.
+> [!NOTE]
+> Creating service accounts requires a [Premium license](https://coder.com/pricing).
-You must have the User Admin role or above to create headless users.
+Service accounts are headless user accounts that cannot use the web UI to log in
+to Coder. This is useful for creating accounts for automated systems, such as
+CI/CD pipelines or for users who only consume Coder via another client/API. Service accounts do not have passwords or associated email addresses.
-## Create a headless user
+You must have the User Admin role or above to create service accounts.
+
+## Create a service account
## CLI
+Use the `--service-account` flag to create a dedicated service account:
+
```sh
coder users create \
- --email="coder-bot@coder.com" \
--username="coder-bot" \
- --login-type="none" \
+ --service-account
```
## UI
-Navigate to the `Users` > `Create user` in the topbar
+Navigate to **Deployment** > **Users** > **Create user**, then select
+**Service account** as the login type.

+## Authenticate as a service account
+
To make API or CLI requests on behalf of the headless user, learn how to
[generate API tokens on behalf of a user](./sessions-tokens.md#generate-a-long-lived-api-token-on-behalf-of-another-user).
diff --git a/docs/manifest.json b/docs/manifest.json
index 30b238efbc..57d5ae6e8c 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -495,7 +495,8 @@
{
"title": "Headless Authentication",
"description": "Create and manage headless service accounts for automated systems and API integrations",
- "path": "./admin/users/headless-auth.md"
+ "path": "./admin/users/headless-auth.md",
+ "state": ["premium"]
},
{
"title": "Groups \u0026 Roles",
diff --git a/enterprise/coderd/groups_test.go b/enterprise/coderd/groups_test.go
index ac20c372f5..59335e91c5 100644
--- a/enterprise/coderd/groups_test.go
+++ b/enterprise/coderd/groups_test.go
@@ -1161,7 +1161,8 @@ func TestGetGroupMembersFilter(t *testing.T) {
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
- codersdk.FeatureTemplateRBAC: 1,
+ codersdk.FeatureTemplateRBAC: 1,
+ codersdk.FeatureServiceAccounts: 1,
},
},
})
@@ -1191,7 +1192,8 @@ func TestGetGroupMembersFilter(t *testing.T) {
require.NoError(t, err)
return res.Users
}
- coderdtest.UsersFilter(setupCtx, t, client, db, setup, fetch)
+ options := &coderdtest.UsersFilterOptions{CreateServiceAccounts: true}
+ coderdtest.UsersFilter(setupCtx, t, client, db, options, setup, fetch)
}
func TestGetGroupMembersPagination(t *testing.T) {
diff --git a/enterprise/coderd/users_test.go b/enterprise/coderd/users_test.go
index d6c8324a07..564065d259 100644
--- a/enterprise/coderd/users_test.go
+++ b/enterprise/coderd/users_test.go
@@ -614,4 +614,168 @@ func TestEnterprisePostUser(t *testing.T) {
require.Len(t, memberedOrgs, 2)
require.ElementsMatch(t, []uuid.UUID{second.ID, third.ID}, []uuid.UUID{memberedOrgs[0].ID, memberedOrgs[1].ID})
})
+
+ t.Run("ServiceAccount/OK", func(t *testing.T) {
+ t.Parallel()
+ client, first := coderdenttest.New(t, &coderdenttest.Options{
+ LicenseOptions: &coderdenttest.LicenseOptions{
+ Features: license.Features{
+ codersdk.FeatureServiceAccounts: 1,
+ },
+ },
+ })
+
+ ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
+ defer cancel()
+
+ //nolint:gocritic
+ user, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
+ OrganizationIDs: []uuid.UUID{first.OrganizationID},
+ Username: "service-acct-ok",
+ UserLoginType: codersdk.LoginTypeNone,
+ ServiceAccount: true,
+ })
+ require.NoError(t, err)
+ require.Equal(t, codersdk.LoginTypeNone, user.LoginType)
+ require.Empty(t, user.Email)
+ require.Equal(t, "service-acct-ok", user.Username)
+ require.Equal(t, codersdk.UserStatusDormant, user.Status)
+ })
+
+ t.Run("ServiceAccount/WithEmail", func(t *testing.T) {
+ t.Parallel()
+ client, first := coderdenttest.New(t, &coderdenttest.Options{
+ LicenseOptions: &coderdenttest.LicenseOptions{
+ Features: license.Features{
+ codersdk.FeatureServiceAccounts: 1,
+ },
+ },
+ })
+
+ ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
+ defer cancel()
+
+ //nolint:gocritic
+ _, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
+ OrganizationIDs: []uuid.UUID{first.OrganizationID},
+ Username: "service-acct-email",
+ Email: "should-not-have@email.com",
+ ServiceAccount: true,
+ })
+ var apiErr *codersdk.Error
+ require.ErrorAs(t, err, &apiErr)
+ require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
+ require.Contains(t, apiErr.Message, "Email cannot be set for service accounts")
+ })
+
+ t.Run("ServiceAccount/WithPassword", func(t *testing.T) {
+ t.Parallel()
+ client, first := coderdenttest.New(t, &coderdenttest.Options{
+ LicenseOptions: &coderdenttest.LicenseOptions{
+ Features: license.Features{
+ codersdk.FeatureServiceAccounts: 1,
+ },
+ },
+ })
+
+ ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
+ defer cancel()
+
+ //nolint:gocritic
+ _, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
+ OrganizationIDs: []uuid.UUID{first.OrganizationID},
+ Username: "service-acct-password",
+ Password: "ShouldNotHavePassword123!",
+ ServiceAccount: true,
+ })
+ var apiErr *codersdk.Error
+ require.ErrorAs(t, err, &apiErr)
+ require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
+ require.Contains(t, apiErr.Message, "Password cannot be set for service accounts")
+ })
+
+ t.Run("ServiceAccount/WithInvalidLoginType", func(t *testing.T) {
+ t.Parallel()
+ client, first := coderdenttest.New(t, &coderdenttest.Options{
+ LicenseOptions: &coderdenttest.LicenseOptions{
+ Features: license.Features{
+ codersdk.FeatureServiceAccounts: 1,
+ },
+ },
+ })
+
+ ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
+ defer cancel()
+
+ //nolint:gocritic
+ _, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
+ OrganizationIDs: []uuid.UUID{first.OrganizationID},
+ Username: "service-acct-login-type",
+ UserLoginType: codersdk.LoginTypePassword,
+ ServiceAccount: true,
+ })
+ var apiErr *codersdk.Error
+ require.ErrorAs(t, err, &apiErr)
+ require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
+ require.Contains(t, apiErr.Message, "Service accounts must use login type 'none'")
+ })
+
+ t.Run("ServiceAccount/DefaultLoginType", func(t *testing.T) {
+ t.Parallel()
+ client, first := coderdenttest.New(t, &coderdenttest.Options{
+ LicenseOptions: &coderdenttest.LicenseOptions{
+ Features: license.Features{
+ codersdk.FeatureServiceAccounts: 1,
+ },
+ },
+ })
+
+ ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
+ defer cancel()
+
+ //nolint:gocritic
+ user, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
+ OrganizationIDs: []uuid.UUID{first.OrganizationID},
+ Username: "service-acct-default-login",
+ ServiceAccount: true,
+ })
+ require.NoError(t, err)
+
+ found, err := client.User(ctx, user.ID.String())
+ require.NoError(t, err)
+ require.Equal(t, codersdk.LoginTypeNone, found.LoginType)
+ require.Empty(t, found.Email)
+ })
+
+ t.Run("ServiceAccount/MultipleWithoutEmail", func(t *testing.T) {
+ t.Parallel()
+ client, first := coderdenttest.New(t, &coderdenttest.Options{
+ LicenseOptions: &coderdenttest.LicenseOptions{
+ Features: license.Features{
+ codersdk.FeatureServiceAccounts: 1,
+ },
+ },
+ })
+
+ ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
+ defer cancel()
+
+ //nolint:gocritic
+ user1, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
+ OrganizationIDs: []uuid.UUID{first.OrganizationID},
+ Username: "service-acct-multi-1",
+ ServiceAccount: true,
+ })
+ require.NoError(t, err)
+ require.Empty(t, user1.Email)
+
+ user2, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
+ OrganizationIDs: []uuid.UUID{first.OrganizationID},
+ Username: "service-acct-multi-2",
+ ServiceAccount: true,
+ })
+ require.NoError(t, err)
+ require.Empty(t, user2.Email)
+ require.NotEqual(t, user1.ID, user2.ID)
+ })
}
diff --git a/enterprise/coderd/workspacesharing_test.go b/enterprise/coderd/workspacesharing_test.go
index ac37b58914..76f6fe1881 100644
--- a/enterprise/coderd/workspacesharing_test.go
+++ b/enterprise/coderd/workspacesharing_test.go
@@ -231,7 +231,13 @@ func TestWorkspaceSharingDisabled(t *testing.T) {
t.Run("ACLEndpointsForbiddenServiceAccountsMode", func(t *testing.T) {
t.Parallel()
- client, db, owner := coderdenttest.NewWithDatabase(t, nil)
+ client, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
+ LicenseOptions: &coderdenttest.LicenseOptions{
+ Features: license.Features{
+ codersdk.FeatureServiceAccounts: 1,
+ },
+ },
+ })
regularClient, regularUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
regularWS := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
@@ -444,7 +450,8 @@ func TestWorkspaceSharingDisabled(t *testing.T) {
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
- codersdk.FeatureTemplateRBAC: 1,
+ codersdk.FeatureTemplateRBAC: 1,
+ codersdk.FeatureServiceAccounts: 1,
},
},
})
diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts
index 3fe98238fe..36d75c217e 100644
--- a/site/src/api/typesGenerated.ts
+++ b/site/src/api/typesGenerated.ts
@@ -3511,6 +3511,7 @@ export type FeatureName =
| "multiple_external_auth"
| "multiple_organizations"
| "scim"
+ | "service_accounts"
| "task_batch_actions"
| "template_rbac"
| "user_limit"
@@ -3539,6 +3540,7 @@ export const FeatureNames: FeatureName[] = [
"multiple_external_auth",
"multiple_organizations",
"scim",
+ "service_accounts",
"task_batch_actions",
"template_rbac",
"user_limit",
diff --git a/site/src/pages/CreateUserPage/CreateUserForm.stories.tsx b/site/src/pages/CreateUserPage/CreateUserForm.stories.tsx
index 3c0e42745b..5631923bb4 100644
--- a/site/src/pages/CreateUserPage/CreateUserForm.stories.tsx
+++ b/site/src/pages/CreateUserPage/CreateUserForm.stories.tsx
@@ -17,6 +17,7 @@ const meta: Meta = {
onCancel: action("cancel"),
onSubmit: action("submit"),
isLoading: false,
+ serviceAccountsEnabled: true,
},
};
diff --git a/site/src/pages/CreateUserPage/CreateUserForm.tsx b/site/src/pages/CreateUserPage/CreateUserForm.tsx
index 46f641701d..e1ded99de3 100644
--- a/site/src/pages/CreateUserPage/CreateUserForm.tsx
+++ b/site/src/pages/CreateUserPage/CreateUserForm.tsx
@@ -87,6 +87,7 @@ interface CreateUserFormProps {
onCancel: () => void;
authMethods?: TypesGen.AuthMethods;
showOrganizations: boolean;
+ serviceAccountsEnabled: boolean;
}
export const CreateUserForm: FC = ({
@@ -96,12 +97,13 @@ export const CreateUserForm: FC = ({
onCancel,
showOrganizations,
authMethods,
+ serviceAccountsEnabled,
}) => {
const availableLoginTypes = [
authMethods?.password.enabled && "password",
authMethods?.oidc.enabled && "oidc",
authMethods?.github.enabled && "github",
- "none",
+ serviceAccountsEnabled && "none",
].filter(Boolean) as Array;
const defaultLoginType = availableLoginTypes[0];
diff --git a/site/src/pages/CreateUserPage/CreateUserPage.tsx b/site/src/pages/CreateUserPage/CreateUserPage.tsx
index ba92e1a033..0af2dca856 100644
--- a/site/src/pages/CreateUserPage/CreateUserPage.tsx
+++ b/site/src/pages/CreateUserPage/CreateUserPage.tsx
@@ -6,6 +6,7 @@ import { getErrorDetail, getErrorMessage } from "#/api/errors";
import { authMethods, createUser } from "#/api/queries/users";
import { Margins } from "#/components/Margins/Margins";
import { useDashboard } from "#/modules/dashboard/useDashboard";
+import { useFeatureVisibility } from "#/modules/dashboard/useFeatureVisibility";
import { pageTitle } from "#/utils/page";
import { CreateUserForm } from "./CreateUserForm";
@@ -15,6 +16,7 @@ const CreateUserPage: FC = () => {
const createUserMutation = useMutation(createUser(queryClient));
const authMethodsQuery = useQuery(authMethods());
const { showOrganizations } = useDashboard();
+ const { service_accounts: serviceAccountsEnabled } = useFeatureVisibility();
return (
@@ -58,6 +60,7 @@ const CreateUserPage: FC = () => {
}}
authMethods={authMethodsQuery.data}
showOrganizations={showOrganizations}
+ serviceAccountsEnabled={serviceAccountsEnabled}
/>
);