diff --git a/TODO.md b/TODO.md index 196aba3..cefc74b 100644 --- a/TODO.md +++ b/TODO.md @@ -20,6 +20,12 @@ regress. # Completed Steps +- 2026-10-03: `make test`, and so `docker build .`, fits a machine with 4 GiB of + memory: tests hash passwords with 1 MiB instead of upaasd's 64 MiB, so + `GOMAXPROCS=4 make test` peaks at about 1.4 GiB instead of 2.7 GiB. upaasd + still hashes with 64 MiB, and one test hashes and verifies a password at that + cost (#261). + - 2026-10-02: In a window too narrow for the top bar, such as 390 px, the New App and Logout buttons move to a second row instead of running into "by @sneak"; the bar keeps a gap between its two sides at every width (#272). diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go index 211d3c6..d784811 100644 --- a/internal/handlers/handlers_test.go +++ b/internal/handlers/handlers_test.go @@ -109,6 +109,9 @@ func createAppServices( }) require.NoError(t, authErr) + // 1 MiB per password hash instead of 64 MiB; see auth.Service.ArgonMemory. + authSvc.ArgonMemory = 1024 + appSvc, appErr := app.New(fx.Lifecycle(nil), app.ServiceParams{ Logger: logInstance, Database: dbInstance, diff --git a/internal/service/auth/auth.go b/internal/service/auth/auth.go index 0277a47..6dd8db8 100644 --- a/internal/service/auth/auth.go +++ b/internal/service/auth/auth.go @@ -59,6 +59,13 @@ type ServiceParams struct { // Service provides authentication functionality. type Service struct { + // ArgonMemory is the memory each argon2id hash takes, in KiB. New sets + // argonMemory, 64 MiB, and upaasd never changes it. Tests lower it, since + // many 64 MiB hashes at once under the race detector need more memory + // than a 4 GiB build machine has. A hash verifies only with the value it + // was made with. + ArgonMemory uint32 + log *slog.Logger db *database.Database store *sessions.CookieStore @@ -77,10 +84,11 @@ func New(_ fx.Lifecycle, params ServiceParams) (*Service, error) { } return &Service{ - log: params.Logger.Get(), - db: params.Database, - store: store, - params: ¶ms, + ArgonMemory: argonMemory, + log: params.Logger.Get(), + db: params.Database, + store: store, + params: ¶ms, }, nil } @@ -97,7 +105,7 @@ func (svc *Service) HashPassword(password string) (string, error) { []byte(password), salt, argonTime, - argonMemory, + svc.ArgonMemory, argonThreads, argonKeyLen, ) @@ -132,7 +140,7 @@ func (svc *Service) VerifyPassword(hashedPassword, password string) bool { []byte(password), salt, argonTime, - argonMemory, + svc.ArgonMemory, argonThreads, argonKeyLen, ) diff --git a/internal/service/auth/auth_test.go b/internal/service/auth/auth_test.go index 3e0c8c2..e284db7 100644 --- a/internal/service/auth/auth_test.go +++ b/internal/service/auth/auth_test.go @@ -65,6 +65,10 @@ func setupTestService(t *testing.T) (*auth.Service, func()) { }) require.NoError(t, err) + // 1 MiB per hash instead of 64 MiB; see Service.ArgonMemory. The tests + // that use setupAuthService keep 64 MiB. + svc.ArgonMemory = 1024 + // t.TempDir() automatically cleans up after test cleanup := func() {} @@ -237,6 +241,21 @@ func TestVerifyPassword(testingT *testing.T) { }) } +// TestHashPasswordWithUpaasdMemory hashes and verifies a password with the +// memory New sets, which upaasd uses. setupTestService lowers it. +func TestHashPasswordWithUpaasdMemory(t *testing.T) { + t.Parallel() + + svc := setupAuthService(t, false) + require.Equal(t, uint32(64*1024), svc.ArgonMemory) + + hash, err := svc.HashPassword("correctpassword") + require.NoError(t, err) + + assert.True(t, svc.VerifyPassword(hash, "correctpassword")) + assert.False(t, svc.VerifyPassword(hash, "wrongpassword")) +} + func TestIsSetupRequired(testingT *testing.T) { testingT.Parallel()