[Employer Sync 4.5] Re-hash legacy (non-bcrypt) passwords on login
TL;DR: When authenticate fails and the stored hash is not bcrypt, verify via JodGig::PasswordService and re-save as bcrypt. Low priority — the legacy-hash cohort (1,363 users) is dormant — but the fix is small and makes any re-engaged employer's first login clean.
Context
1,363 employer accounts (42% of the migration universe) carry a non-bcrypt password hash — almost certainly the original PHP MD5 / SHA from before JodGig's bcrypt cutover (Jun–Jul 2023, derived from last_login_at distribution). Ruby's has_secure_password.authenticate cannot verify these. Without a fallback, an employer who types the right password is told it is wrong.
Problem
The data shows this cohort is overwhelmingly dormant — none have logged in within the last 2 years; 99% never logged in at all. So this fix is low priority for day-one launch (the active cohort all carries bcrypt). It is, however, the difference between a clean and a broken first-login experience for any dormant employer the business successfully re-engages later — and that is the whole reason we migrate them.
Identities::Sessions::CreateManager does only the bcrypt path. The talent JIT (JodGig::Users::JitMigrationService) already uses JodGig::PasswordService.valid?, which understands the legacy hash formats.
Direction
Add a single fallback step to Identities::Sessions::CreateManager. After identities_user.authenticate(password) returns false AND the stored password_digest does not start with a bcrypt prefix ($2a$, $2b$, $2y$):
JodGig::PasswordService.valid?(hashed_password: identities_user.password_digest, password: password).- If
true:identities_user.password = passwordandidentities_user.save!—has_secure_passwordwrites a fresh bcrypt hash.- Continue login as if
authenticatehad returnedtrue.
- If
false: login fails with the same generic error as before.
Keep the change tightly scoped — one branch in one method. Do not refactor JodGig::PasswordService.
Acceptance
- Test: non-bcrypt employer with correct password — logs in;
password_digestafterwards starts with$2a$. - Test: non-bcrypt employer with wrong password — rejected with the standard error.
- Test: bcrypt user — unaffected.
- Test: a
$2y$hash — still verifies (theJodGig::PasswordServicealready swaps the prefix).