fix(coderd): use a random value for a simulated hash for built-in use… · coder/coder@0951f90
@@ -167,6 +167,59 @@ func TestPostLogin(t *testing.T) {
167167require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action)
168168 })
169169170+// "hunter2" was the input of the previous hardcoded simulated hash, which
171+// an empty stored hash wrongly matched; this is a regression test.
172+t.Run("NonexistentUser401", func(t *testing.T) {
173+t.Parallel()
174+client := coderdtest.New(t, nil)
175+ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
176+defer cancel()
177+178+_, err := client.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{
179+Email: "does-not-exist@coder.com",
180+Password: "hunter2",
181+ })
182+var apiErr *codersdk.Error
183+require.ErrorAs(t, err, &apiErr)
184+require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
185+require.Equal(t, "Incorrect email or password.", apiErr.Message)
186+ })
187+188+// Attempting built-in login as an SSO user returns a 401 to avoid
189+// divulging login type.
190+t.Run("SSOReturns401", func(t *testing.T) {
191+t.Parallel()
192+client, db := coderdtest.NewWithDatabase(t, nil)
193+ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
194+defer cancel()
195+196+// An SSO user has no password hash stored. Create one directly in the
197+// database since the API requires OIDC to be configured. dbgen.User
198+// substitutes a random hash for an empty one, so clear it explicitly.
199+ssoUser := dbgen.User(t, db, database.User{
200+Email: "sso-user@coder.com",
201+LoginType: database.LoginTypeOIDC,
202+ })
203+//nolint:gocritic // Test setup requires a system context to clear the hash.
204+err := db.UpdateUserHashedPassword(dbauthz.AsSystemRestricted(ctx), database.UpdateUserHashedPasswordParams{
205+ID: ssoUser.ID,
206+HashedPassword: []byte{},
207+ })
208+require.NoError(t, err)
209+210+anonClient := codersdk.New(client.URL)
211+_, err = anonClient.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{
212+Email: ssoUser.Email,
213+Password: "hunter2",
214+ })
215+var apiErr *codersdk.Error
216+require.ErrorAs(t, err, &apiErr)
217+require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
218+require.Equal(t, "Incorrect email or password.", apiErr.Message)
219+// The login type must not be leaked.
220+require.NotContains(t, apiErr.Message, string(codersdk.LoginTypeOIDC))
221+ })
222+170223t.Run("Suspended", func(t *testing.T) {
171224t.Parallel()
172225auditor := audit.NewMock()