Skip to content

chore(auth): drop the unused User.Salt field and stop reading or writing users.salt - #1514

Merged
IAmJSD merged 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-313-next
Oct 26, 2026
Merged

IAmJSD merged 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-313-next

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator

User.Salt is a leftover from before bcrypt, which stores its own salt inside the hash. Every writer set it to "" and no code read it. The store still selected, inserted and updated the users.salt column on every user read and credential write, and the bootstrap admin INSERTs in migrate.go named the column too. This PR removes the field and every reference to the column from application code. It does not drop the column. That is deferred on purpose, as explained below.

Refs #313

Root cause and fix

#1271 left User.Salt for later: the issue says to delete the field "once the column is dropped", and the store still read and wrote the column. This PR removes:

  • internal/auth/types.go: the Salt field.
  • internal/auth/service_user.go and service_password.go: the five Salt: "" / user.Salt = "" assignments.
  • internal/auth/store_postgres.go:
    • salt from the four scanUser SELECTs (GetUserByID, GetUserByEmail, ListUsers, GetUserByResetToken) and from the scanUser destinations.
    • salt from the CreateUser INSERT and the CreateAdminIfNone conditional INSERT. The placeholders are renumbered, and the NOT EXISTS binding moves from $20 to $19.
  • internal/auth/store_postgres_credentials.go: salt = $n from UpdateUserCredentials and CompletePasswordReset, with the placeholders renumbered. The compare-and-set WHERE clauses are unchanged.
  • internal/database/postgres/migrations/migrate.go: salt from both ensureAdminUser and ensureAdminUserWithPassword INSERTs.

The column keeps its schema, salt VARCHAR(255) NOT NULL DEFAULT '' (000001), so INSERTs that omit it still produce valid rows. Existing values are no longer rewritten. Legacy rows keep whatever they held, which was always '' for rows written by any bcrypt-era code, and nothing reads them.

Why the column is not dropped here

The brief allowed dropping the column only "if nothing reads or writes it and dropping is safe". After this PR nothing reads or writes it, but dropping it in the same release is not safe:

  • Migrations run at server startup (internal/server/app.go runMigrationsFunc: migrations.RunMigrations).
  • During a rolling deploy, old instances keep serving while the new one migrates. Every old-release user read (SELECT ... salt ...) would then fail with column "salt" does not exist. That breaks login, session validation and admin user pages until the old instances drain.
  • A code rollback past this PR would hit the same failure permanently, because a code rollback doesn't run the down migration.

So the drop belongs in a follow-up PR once a release containing this change is everywhere. Migration 000148 was reserved for this work and is not used by this PR. The follow-up drop migration should take whatever number is next when it lands. That PR also has to update the raw-SQL test fixtures that still insert salt on a fully migrated database:

  • internal/api/handler_plans_concurrency_integration_test.go
  • internal/api/combined_plan_purchase_integration_test.go
  • internal/api/handler_purchases_run_update_any_integration_test.go
  • internal/api/handler_purchases_retry_submit_race_integration_test.go
  • internal/api/handler_purchases_submit_dedupe_integration_test.go

Migration tests that insert at their own older version (000057, 000059, 000064, 000065, 000096, 000099, 000103, 000130, 000138) are unaffected.

#313 status after this PR

Item Status
A03-022 role constants, UpdateLastUsed, ResolveGCPCredentials, SendWelcomeEmail Done in #1271
A03-022 User.Salt field This PR. The field and all code reads and writes are gone. The column drop is a follow-up (see above).
A03-023 loadUser routing, DeleteUser/checkLastAdminConstraint, roleSessionName Done in #1271
A03-023 redactEmail ×3 plus redactEmailLocal Open, owner decision. The copies mask differently, so picking one changes what the logs show. Not touched here.
A03-023 MatchesAccount vs AccountScope.Allows Deferred in #1271 to #1116. Not touched here.
A03-020 post-fetch constant-time compares Comments corrected in #1271, compares kept
A03-020 correct #104's "verified sound" note Needs a maintainer edit. See below.
A16-014 permissionCoveredBy carry-through Already covered on main (TestGrantCeiling_CarvedOutMayBeKeptNotWidened). See #1271.

Note for #104 (maintainer action; #104's body is the maintainer's, so I haven't edited it)

The "Verified sound" section of #104 says that ValidateSession, validateResetToken and ValidateUserAPIKey "all add a subtle.ConstantTimeCompare after the SQL fetch". It lists this as a timing defense. Suggested replacement for that sentence:

Session, reset and API-key lookups are SQL equality on the SHA-256 of a 32-byte CSPRNG value, so lookup timing reveals nothing usable. The post-fetch subtle.ConstantTimeCompare in ValidateSession, ResetTokenStatus/validateResetToken and ValidateUserAPIKey compares the row against the same hash it was selected by. It is a store-integrity check, not a timing defense (#313, #1271). ValidateCSRFToken's compare is a genuine constant-time HMAC comparison.

Verification

Unit, mock and local-Postgres evidence on branch head a748143d, which sits on main 3bfc2fe5. Run with AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true.

New regression tests, both integration-tagged against a real migrated Postgres. Each drops users.salt and then drives the statements that used to name it:

  • internal/auth/store_postgres_no_salt_db_test.go, TestIntegration_UserStoreWorksWithoutSaltColumn runs, in order:
    • CreateAdminIfNone, CreateUser, GetUserByID, GetUserByEmail, ListUsers
    • UpdateUserCredentials, SetPasswordResetToken, GetUserByResetToken, CompletePasswordReset
    • a final read of the row.
  • internal/database/postgres/migrations/ensure_admin_user_no_salt_test.go, TestEnsureAdminUser_WithoutSaltColumn: reruns RunMigrations with an admin email on both bootstrap paths (no password, and with password). It asserts the admin row exists with the Administrators group and the expected active.

Fail before, pass after:

  • With the six production files reverted to main, go test -tags integration -count=1 -timeout 20m -run 'TestIntegration_UserStoreWorksWithoutSaltColumn' ./internal/auth/ fails at the first statement: ERROR: column "salt" of relation "users" does not exist (SQLSTATE 42703).
  • With migrate.go reverted, go test -tags integration -count=1 -timeout 20m -run 'TestEnsureAdminUser_WithoutSaltColumn' ./internal/database/postgres/migrations/ fails both subtests (user row for ... must exist). RunMigrations only logs WARNING: admin bootstrap failed ... column "salt" ... does not exist and returns nil, so the missing row is what shows the failure.
  • On the branch, both pass. Both were confirmed run, not skipped, with -v.

Existing integration tests I edited (their Salt references were removed), run narrowly with go test -tags integration -count=1 -timeout 20m -run '^(TestIntegration_LoginPreservesSecurityChanges|TestIntegration_CredentialWritesPreserveConcurrentMFA|TestIntegration_ResetConfirmPreservesConcurrentMFA|TestIntegration_CreateAdminIfNone_.*|TestIntegration_UserStoreWorksWithoutSaltColumn)$' ./internal/auth/: all PASS, including the concurrent bootstrap and stress variants. With the column still present they also show the new SQL works against today's schema.

Checks on the branch:

  • go build ./..., go vet ./... and go vet -tags integration ./... are clean.
  • go test -count=1 passes for ./internal/auth/, ./internal/database/postgres/migrations/, ./internal/mocks/, ./internal/server/ and ./internal/api/, run one package at a time. The pgxmock column list and argument expectations and the positional fake-row helper in store_postgres_test.go were updated to the 20-column scan.
  • golangci-lint v2.10.1 run --allow-parallel-runners on ./internal/auth/ ./internal/database/postgres/migrations/, with and without --build-tags integration, reports 0 issues.
  • gocyclo -over 10 -ignore "_test\.go" on the six changed non-test files reports nothing.
  • No open PR touches the changed files (checked with gh pr list --state open --json files).

Combined-tree verification at local queue commit 2be43e1e = origin/main b9271e88 + fix/issue-363-next, fix/issue-177-next, fix/issue-919-next, fix/issue-313-next, fix/issue-532-next and fix/issue-316-next merged in that order. Passed: go build ./...; go vet with and without -tags integration; go test -race -short -count=1 ./...; golangci-lint v2.10.1 with and without the integration tag; gocyclo -over 10; go test -race -tags integration on internal/api, internal/auth, internal/config, internal/database/postgres/migrations, internal/purchase, internal/server and internal/testutil; the git-secrets scan and scripts/test-git-secrets-allowlist.sh.

Concurrency

No new read-then-write is introduced. The credential compare-and-set statements keep their WHERE guards and only lose an unrelated SET column. CreateAdminIfNone keeps its advisory lock and NOT EXISTS predicate (the concurrent bootstrap tests above still pass).

Out of scope

Maintainer: please mirror the issue's triage labels onto this PR: triaged, priority/p3, severity/low, urgency/eventually, impact/internal, effort/m, type/chore.

🤖 Generated with Claude Code

…salt

User.Salt predates bcrypt (which embeds its own salt in the hash). Every
writer set it to "" and nothing read it, but the store still SELECTed,
INSERTed and UPDATEd the users.salt column, and the bootstrap admin
INSERTs in migrate.go named it too.

Remove the field and every reference to the column from application
code: the scanUser SELECT lists, CreateUser, CreateAdminIfNone,
UpdateUserCredentials, CompletePasswordReset and both ensureAdminUser
INSERTs. The column stays (NOT NULL DEFAULT ''), so rows inserted
without it remain valid and code still running from the previous
release keeps working during a rolling deploy. Dropping the column is
left to a later migration once no deployed release references it.

New integration tests drop the column from a migrated database and
drive every one of those statements, so they fail on main with
`column "salt" does not exist`.

Refs LeanerCloud#313

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD IAmJSD added impact/internal Team-internal only type/chore Maintenance / non-user-visible triaged Item has been triaged priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/eventually No deadline effort/m Days labels Oct 26, 2026
@coderabbitai

coderabbitai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 15 billable files and costs up to $3.75.

Or wait 59 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between 3317da7 and a748143.

Files selected for processing: 15
  • internal/auth/service_credentials_db_test.go
  • internal/auth/service_login_db_test.go
  • internal/auth/service_password.go
  • internal/auth/service_test.go
  • internal/auth/service_user.go
  • internal/auth/store_postgres.go
  • internal/auth/store_postgres_credentials.go
  • internal/auth/store_postgres_db_test.go
  • internal/auth/store_postgres_no_salt_db_test.go
  • internal/auth/store_postgres_pgxmock_test.go
  • internal/auth/store_postgres_test.go
  • internal/auth/test_helpers.go
  • internal/auth/types.go
  • internal/database/postgres/migrations/ensure_admin_user_no_salt_test.go
  • internal/database/postgres/migrations/migrate.go
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@IAmJSD

IAmJSD commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE a748143d885e7e3f2582a416d4ab1b26537173cf

Blocking:

  • none

Non-blocking:

  • Five internal/api integration fixtures still INSERT salt. The PR body lists them, and they have to change in the follow-up PR that drops the column. Nothing breaks now because the column is still there.
  • Keeping the column for one release before dropping it is the right call for a rolling deploy (migrations run at startup, so old instances would still be reading it). The follow-up PR needs to wait until this change has shipped everywhere.

Attacked:

  • Grepped for salt/Salt outside tests and old migrations. The only hits left are the 000001 schema line (salt VARCHAR(255) NOT NULL DEFAULT '', so INSERTs that leave the column out stay valid), comments, and an unrelated HKDF nil /* salt */. No DynamoDB or other store references the field. The placeholder renumbering in CreateUser, CreateAdminIfNone (NOT EXISTS now on $19), UpdateUserCredentials and CompletePasswordReset lines up with the argument lists, and the compare-and-set WHEREs are unchanged.
  • The new tests use per-test databases (RequireMigratedDatabase clones a template), so DROP COLUMN salt doesn't leak into other tests.
  • Fail-before, store: I reverted store_postgres.go, store_postgres_credentials.go and types.go to main. TestIntegration_UserStoreWorksWithoutSaltColumn then fails with column "salt" of relation "users" does not exist.
  • Fail-before, migrations: I reverted migrate.go to main. Both subtests of TestEnsureAdminUser_WithoutSaltColumn then fail. I restored both changes.
  • Ran: go build ./...; go vet ./... with and without -tags integration; go test -race -short -count=1 on auth and migrations; go test -race -tags integration -count=1 ./internal/auth/ ./internal/database/postgres/migrations/ (both ok); golangci-lint v2.10.1 with and without the integration tag (0 issues).
  • Refs chore(auth): role-model leftovers, inline loadUser copies, inert constant-time compares #313, not Closes, is right: the redactEmail and MatchesAccount items and the column drop are still open.

Body-stale:

Reviewer: rF6. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths.

@IAmJSD

IAmJSD commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE a748143d885e7e3f2582a416d4ab1b26537173cf

Blocking:

  • none

Non-blocking:

Attacked:

  • SQL: checked the placeholder renumbering in all six statements. CreateAdminIfNone passes 19 args, and $19 is DefaultAdminGroupID in the NOT EXISTS. The UpdateUserCredentials and CompletePasswordReset compare-and-set WHERE clauses are unchanged. The column keeps NOT NULL DEFAULT '' (000001), and no later migration alters it, so INSERTs that omit it stay valid. Old binaries still read and write the column during a rolling deploy.
  • Grepped for leftover salt in Go, SQL, sh, py and ts. Only historical migrations 000005 and 000006 and an unrelated HKDF comment remain. No DynamoDB user store reads the dropped dynamodbav:"Salt" tag. The new tests' DROP COLUMN runs on per-test databases cloned by RequireMigratedDatabase, so it can't leak into other tests.
  • Fail-before: with the six production files reverted to main, TestIntegration_UserStoreWorksWithoutSaltColumn fails with column "salt" of relation "users" does not exist (SQLSTATE 42703), and both subtests of TestEnsureAdminUser_WithoutSaltColumn fail. All restored and passing afterwards.
  • Ran on a748143:
    • go build ./... and go vet ./..., with and without -tags integration: clean.
    • go test -race -short -count=1 on auth, migrations, server and mocks: ok.
    • go test -race -tags integration -count=1 ./internal/auth/ ./internal/database/postgres/migrations/: ok (whole packages).
    • golangci-lint v2.10.1 on both packages, with and without the integration tag: 0 issues.
    • gocyclo -over 10 on the changed non-test files: silent.
    • I did not run the internal/api integration suite. Its fixtures write salt directly, which still works while the column exists.

Body-stale:

  • none

Reviewer: rF8. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths.

@cristim

cristim commented Oct 26, 2026

Copy link
Copy Markdown
Member

VERDICT: APPROVE a748143
PR #1514 is OPEN. Base is origin/main 3bfc2fe (one commit, no merges).

Findings (none blocking):

  • nit internal/api/{handler_plans_concurrency,combined_plan_purchase,handler_purchases_run_update_any,handler_purchases_retry_submit_race,handler_purchases_submit_dedupe}_integration_test.go: raw-SQL fixtures still INSERT salt. That is harmless while the column exists, and the PR body lists them for the follow-up drop PR. The follow-up has to update them, or those suites break when the column goes.
  • nit internal/auth/store_postgres_credentials.go:63: when CompletePasswordReset is changed to stop writing password_history, no unit (pgxmock) test fails. Only the integration tests catch it. This gap predates the PR and the PR does not widen it.

Attacked:

  • Legacy hash formats: on the head, verifyPassword (service_password.go:91-94) and the MFA recovery code compare use bcrypt.CompareHashAndPassword only. No sha256, pbkdf2, scrypt or argon2 password path exists. In git history, the first auth commit ef59fb1 already wrote Salt: "" at every site, with the comment "Not used anymore", and its generateSalt() was never wired to User.Salt. Migrations 000005 and 000006 insert '' as the salt. No hash format has ever needed the column, so no stored hash depends on it.
  • Remaining readers and writers: git grep -i salt at the head shows no non-test Go, SQL, script, frontend or terraform reference except migrations 000001, 000005 and 000006 (historic, already applied) and the HKDF nil salt (unrelated). There is no DynamoDB user store: the dynamodbav tags on User are never marshalled for users, since only handler_dashboard.go and config/types.go use attributevalue. The field was json:"-", so no API response changes. There is no export, import, SELECT * or RETURNING * on users in production code. I scanned the added lines of every open PR that touches .go files and found no new salt or .Salt reference.
  • NOT NULL and default: 000001_initial_schema.up.sql:131 defines salt VARCHAR(255) NOT NULL DEFAULT '', and no later migration alters the column. INSERTs that omit it get ''. The integration suites, which run on the live schema with the column present, confirm this.
  • Placeholder renumbering: I checked by hand that the 20 SELECT columns match the 20 scanUser destinations in the same order (GetUserByID, GetUserByEmail, ListUsers and GetUserByResetToken). CreateUser has 18 columns, 18 placeholders and 18 args. CreateAdminIfNone has $1..$18 plus the NOT EXISTS predicate on $19, bound to DefaultAdminGroupID. UpdateUserCredentials uses $1..$6 with 6 args, and CompletePasswordReset uses $1..$6 with 6 args. The WHERE compare-and-set guards are unchanged.
  • Rollback and rolling deploy: the column stays with its default. Old code still SELECTs and writes salt successfully. New code omits it. Mixed fleets and code rollback are both safe. No migration is added, so there is no version collision. The drop is correctly deferred.
  • Build and tests on the archived head: go build ./... and go vet -tags integration (auth, migrations) are clean. go test -race -count=1 ./internal/auth/ ./internal/database/postgres/migrations/ passes (1144 tests). go test -race -tags integration -count=1 on both packages, run under the heavy lock, passes: auth 52.6s, migrations 111.2s.
  • Mutation probes (scratch copy, each restored and checked byte-identical with cmp):
    M1, re-add salt = '' to UpdateUserCredentials: TestIntegration_UserStoreWorksWithoutSaltColumn fails with column "salt" of relation "users" does not exist. Killed.
    M2, re-add salt to the ensureAdminUserWithPassword INSERT: TestEnsureAdminUser_WithoutSaltColumn/with_password fails with "user row ... must exist". Killed.
    M3, CompletePasswordReset writes password_history = password_history: unit tests pass (see the nit), and 4 integration tests fail (ResetConfirmPreservesConcurrentMFA, ResetConfirmRejectsStaleRead, CompletePasswordResetEnforcesExpiryInSQL, UserStoreWorksWithoutSaltColumn). Killed.
  • Scope: 15 files, all in auth and the migration bootstrap, matching the PR description. The claims in the PR body were checked against the code and hold. The PR uses "Refs chore(auth): role-model leftovers, inline loadUser copies, inert constant-time compares #313", not a closing keyword, which is correct because the column drop and other chore(auth): role-model leftovers, inline loadUser copies, inert constant-time compares #313 items remain open.

@IAmJSD

IAmJSD commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Local verification at a748143d885e7e3f2582a416d4ab1b26537173cf merged onto main (fe8b2136), together with #1513, #1514, #1516 in that order (combined tree f683b14a, Linux, Go 1.26.10): go build ./..., go vet with and without -tags integration, the full go test -race -short -count=1 ./..., and go test -race -tags integration -count=1 on every changed package, against Docker Postgres. All pass. CI Success and pre-commit are green on this head, with 3 approving independent review(s) on it. Nothing was run against any cloud.

@IAmJSD
IAmJSD marked this pull request as ready for review October 26, 2026 22:38
@IAmJSD
IAmJSD requested a review from cristim as a code owner October 26, 2026 22:38
@IAmJSD
IAmJSD merged commit 5b2b759 into LeanerCloud:main Oct 26, 2026
41 checks passed
@IAmJSD

IAmJSD commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Merged by IAmJSD's agent at IAmJSD's request (IAmJSD is acting owner while the maintainers are away), under the bar IAmJSD set: 3 approving independent review(s) on a748143d (two required: money, auth or security path), green CI, and the combined-tree verification above. Squash-merged at exactly a748143d885e7e3f2582a416d4ab1b26537173cf.

@cristim

cristim commented Oct 26, 2026

Copy link
Copy Markdown
Member

Independent review 2 (claude-fleet, cold): APPROVE a748143
Note: already MERGED. origin/main has it as 5b2b759 (#1514). Its patch-id matches base..head (34bd8a0c), and merge-tree of the head onto origin/main 2faf67f gives tree b66c4bfd, which is origin/main's own tree. Reviewed the head anyway.

Findings:

  • [non-blocking] internal/api/integration_test.go (5 files) and internal/database/postgres/migrations/0000{57,59,64,65,96,99}_test.go, 000103/000130/000138 tests, backfill_admin_group_ids_test.go: test-only raw INSERT INTO users (..., salt, ...) statements remain. They are valid while the column exists. The follow-up migration that drops users.salt must update them, or those suites fail with column "salt" does not exist. The PR body defers the drop explicitly, so this is follow-up scope.
  • [non-blocking, pre-existing] internal/auth/store_postgres.go:47,62,212,251: four copies of the same 20-column SELECT list are hand-aligned with scanUser (:727). A swap of two same-typed columns in one copy passes the whole suite (probe P6 below). The PR keeps all four copies aligned with scanUser. I checked each column against each Scan target. A shared column const would remove that risk. The PR did not introduce this.

Attacked:

  • "Nothing read Salt": I grepped the base tree for salt in Go, SQL, TS/frontend, Terraform, scripts and docs. Salt was only ever written as "" and only round-tripped by the store. The field is json:"-", so it is in no API response or frontend type. No dynamodb marshalling of User exists. It has no audit or log use. OIDC is workload identity only, and there is no SCIM/SSO user provisioning. Every password check goes through bcrypt.CompareHashAndPassword (service_password.go:91). There is no legacy SHA or salted hash path and no upgrade-on-login. History, squashed at fix(purchase): preserve unknown provider upfront costs #450, shows no non-empty Salt write. Rows with a non-bcrypt hash could not log in on base either, so login behaviour is unchanged.
  • Rolling deploy: 000001_initial_schema.up.sql:131 creates salt VARCHAR(255) NOT NULL DEFAULT '', and no later migration alters the column. Old code that writes '' and new code that omits the column both satisfy NOT NULL. No new migration (highest on main is 000147).
  • Completeness: each base non-test statement that names salt is gone in head: 4 SELECTs, CreateUser, CreateAdminIfNone (placeholders renumbered to $19, args 19), UpdateUserCredentials, CompletePasswordReset and both ensureAdminUser INSERTs. Kept on purpose: migrations 000005/000006 (historic, they run before any drop). scanUser has 20 targets in the same order as all 4 SELECTs.
  • Tests ran (not skipped, -v PASS). They cover CreateAdminIfNone, CreateUser, all 4 scanUser SELECTs, UpdateUserCredentials, SetPasswordResetToken, CompletePasswordReset, and both ensureAdminUser paths. Note: RunMigrations only logs a bootstrap failure. The migrations test still catches it, because its row query fails.
  • Fail-before: copied both new tests into the base export. Both fail with column "salt" of relation "users" does not exist. Removed the copies afterwards.
  • Probes (head export, each restored, cmp verified, and re-checked against git show of the head):
    P1 salt back in UpdateUserCredentials: FAIL (caught). P2 salt in CompletePasswordReset: FAIL. P3 salt in CreateUser INSERT: FAIL. P4 salt in ensureAdminUserWithPassword INSERT: with_password subtest FAIL.
    P5 swap email/password_hash in GetUserByID SELECT: unit tests pass (gap), the new integration test FAILs (caught).
    P6 swap mfa_secret/mfa_pending_secret in ListUsers SELECT: unit and the full auth integration suite pass (pre-existing gap, see Findings).
    P7 drop &user.PasswordVersion from scanUser: TestPGXMock_ListUsers_Success FAIL.

Verified locally (GOTOOLCHAIN=go1.26.10, no creds, heavy runs under lockf):

  • go build ./...: ok
  • go vet, with and without -tags integration, on ./internal/auth/ and ./internal/database/postgres/migrations/ (plus ./internal/api/ tagged): ok
  • gofmt -l on the changed .go files: clean
  • go test -race -count=1 on both packages: ok
  • go test -race -tags integration -count=1 on both packages: ok (auth 43.8s, migrations 83.6s)
  • go test -tags integration -v -run on the two new tests: PASS (3 runs, none skipped)
  • golangci-lint 2.11.4 run, with and without --build-tags integration, on both packages: 0 issues
  • git fetch, then merge-tree onto origin/main 2faf67f: clean, and identical to origin/main's tree

Reviewed by an independent agent with no author context (second reviewer). Review only: nothing was pushed, and the GitHub review state is unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/m Days impact/internal Team-internal only priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/chore Maintenance / non-user-visible urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants