◐ Shell
clean mode source ↗

fix: use a random value for a simulated hash for built-in users (#262… · coder/coder@027cf9a

@@ -21,6 +21,7 @@ import (

2121

"github.com/coder/coder/v2/coderd/coderdtest"

2222

"github.com/coder/coder/v2/coderd/coderdtest/oidctest"

2323

"github.com/coder/coder/v2/coderd/database"

24+

"github.com/coder/coder/v2/coderd/database/dbauthz"

2425

"github.com/coder/coder/v2/coderd/database/dbfake"

2526

"github.com/coder/coder/v2/coderd/database/dbgen"

2627

"github.com/coder/coder/v2/coderd/database/dbtime"

@@ -233,6 +234,59 @@ func TestPostLogin(t *testing.T) {

233234

require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action)

234235

})

235236237+

// "hunter2" was the input of the previous hardcoded simulated hash, which

238+

// an empty stored hash wrongly matched; this is a regression test.

239+

t.Run("NonexistentUser401", func(t *testing.T) {

240+

t.Parallel()

241+

client := coderdtest.New(t, nil)

242+

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)

243+

defer cancel()

244+245+

_, err := client.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{

246+

Email: "does-not-exist@coder.com",

247+

Password: "hunter2",

248+

})

249+

var apiErr *codersdk.Error

250+

require.ErrorAs(t, err, &apiErr)

251+

require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())

252+

require.Equal(t, "Incorrect email or password.", apiErr.Message)

253+

})

254+255+

// Attempting built-in login as an SSO user returns a 401 to avoid

256+

// divulging login type.

257+

t.Run("SSOReturns401", func(t *testing.T) {

258+

t.Parallel()

259+

client, db := coderdtest.NewWithDatabase(t, nil)

260+

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)

261+

defer cancel()

262+263+

// An SSO user has no password hash stored. Create one directly in the

264+

// database since the API requires OIDC to be configured. dbgen.User

265+

// substitutes a random hash for an empty one, so clear it explicitly.

266+

ssoUser := dbgen.User(t, db, database.User{

267+

Email: "sso-user@coder.com",

268+

LoginType: database.LoginTypeOIDC,

269+

})

270+

//nolint:gocritic // Test setup requires a system context to clear the hash.

271+

err := db.UpdateUserHashedPassword(dbauthz.AsSystemRestricted(ctx), database.UpdateUserHashedPasswordParams{

272+

ID: ssoUser.ID,

273+

HashedPassword: []byte{},

274+

})

275+

require.NoError(t, err)

276+277+

anonClient := codersdk.New(client.URL)

278+

_, err = anonClient.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{

279+

Email: ssoUser.Email,

280+

Password: "hunter2",

281+

})

282+

var apiErr *codersdk.Error

283+

require.ErrorAs(t, err, &apiErr)

284+

require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())

285+

require.Equal(t, "Incorrect email or password.", apiErr.Message)

286+

// The login type must not be leaked.

287+

require.NotContains(t, apiErr.Message, string(codersdk.LoginTypeOIDC))

288+

})

289+236290

t.Run("Suspended", func(t *testing.T) {

237291

t.Parallel()

238292

auditor := audit.NewMock()