diff --git a/cli/usercreate.go b/cli/usercreate.go index 1a904582593..24c8ea606a3 100644 --- a/cli/usercreate.go +++ b/cli/usercreate.go @@ -49,6 +49,12 @@ func (r *RootCmd) userCreate() *serpent.Command { if disableLogin && loginType != "" { return xerrors.New("You cannot specify both --disable-login and --login-type") } + if disableLogin && !serviceAccount { + return xerrors.New("--disable-login is deprecated. Use --service-account for machine-to-machine access.") + } + if loginType == string(codersdk.LoginTypeNone) && !serviceAccount { + return xerrors.New("Login type 'none' requires --service-account.") + } client, err := r.InitClient(inv) if err != nil { diff --git a/coderd/database/migrations/000534_legacy_none_to_service_account.down.sql b/coderd/database/migrations/000534_legacy_none_to_service_account.down.sql new file mode 100644 index 00000000000..954c160ba2b --- /dev/null +++ b/coderd/database/migrations/000534_legacy_none_to_service_account.down.sql @@ -0,0 +1 @@ +-- Irreversible: original login_type and emails aren't recorded, nothing to restore. diff --git a/coderd/database/migrations/000534_legacy_none_to_service_account.up.sql b/coderd/database/migrations/000534_legacy_none_to_service_account.up.sql new file mode 100644 index 00000000000..42e7e73d935 --- /dev/null +++ b/coderd/database/migrations/000534_legacy_none_to_service_account.up.sql @@ -0,0 +1,6 @@ +-- Convert legacy login_type='none' users into service accounts. +-- Service accounts must have an empty email (see 000433), so this blanks the +-- email of each converted user. Destructive and one-way. System users skipped. +UPDATE users +SET is_service_account = true, email = '' +WHERE login_type = 'none' AND is_service_account = false AND is_system = false; diff --git a/coderd/userauth_test.go b/coderd/userauth_test.go index 8e4c230c4f3..7e7f748af43 100644 --- a/coderd/userauth_test.go +++ b/coderd/userauth_test.go @@ -153,13 +153,19 @@ func TestUserLogin(t *testing.T) { t.Run("LoginTypeNone", func(t *testing.T) { t.Parallel() - anotherClient, anotherUser := coderdtest.CreateAnotherUserMutators(t, client, user.OrganizationID, nil, func(r *codersdk.CreateUserRequestWithOrgs) { - r.Password = "" - r.UserLoginType = codersdk.LoginTypeNone + client, db := coderdtest.NewWithDatabase(t, nil) + first := coderdtest.CreateFirstUser(t, client) + + noneUser := dbgen.User(t, db, database.User{ + LoginType: database.LoginTypeNone, + }) + dbgen.OrganizationMember(t, db, database.OrganizationMember{ + OrganizationID: first.OrganizationID, + UserID: noneUser.ID, }) - _, err := anotherClient.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{ - Email: anotherUser.Email, + _, err := client.LoginWithPassword(context.Background(), codersdk.LoginWithPasswordRequest{ + Email: noneUser.Email, Password: "SomeSecurePassword!", }) require.Error(t, err) diff --git a/coderd/users.go b/coderd/users.go index d067a637347..8725598598a 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -488,6 +488,13 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) { req.UserLoginType = codersdk.LoginTypePassword } + if !req.ServiceAccount && req.UserLoginType == codersdk.LoginTypeNone { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "Login type 'none' requires a service account.", + }) + return + } + if req.UserLoginType != codersdk.LoginTypePassword && req.Password != "" { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Password cannot be set for non-password (%q) authentication.", req.UserLoginType), diff --git a/coderd/users_test.go b/coderd/users_test.go index 2893c7126b4..2a621e2772d 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -952,18 +952,17 @@ 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}, Email: "another@user.org", Username: "someone-else", Password: "", UserLoginType: codersdk.LoginTypeNone, }) - require.NoError(t, err) - - found, err := client.User(ctx, user.ID.String()) - require.NoError(t, err) - require.Equal(t, found.LoginType, codersdk.LoginTypeNone) + var apiErr *codersdk.Error + require.ErrorAs(t, err, &apiErr) + require.Equal(t, http.StatusBadRequest, apiErr.StatusCode()) + require.Contains(t, apiErr.Message, "service account") }) t.Run("CreateOIDCLoginType", func(t *testing.T) {