feat(seeder): age seeded users by default - #8200
Conversation
Seeded users previously fell back to the User entity's CreationDate initializer (DateTime.UtcNow), so every account looked brand-new and age-gated scenarios (e.g. the pm-32180-premium-upsell-account-age path) could not be reproduced. UserSeeder.Create now backdates CreationDate for every seeding path. A new nullable UserSeed.AccountAgeDays controls it: null (default) randomizes an aged date between 91 days and 3 years ago, 0 seeds today, and N seeds exactly N days ago. RevisionDate and AccountRevisionDate are left at now. The knob is exposed through the individual CLI command (--account-age-days) and the SingleUserScene request. Org owner, bulk member, and roster paths inherit the aged default automatically.
🤖 Bitwarden Claude Code ReviewOverall Assessment: APPROVE Reviewed the seeder change that backdates Code Review Details
|
| var ageDays = seed.AccountAgeDays ?? Random.Shared.Next(91, 1096); | ||
| user.CreationDate = DateTime.UtcNow.AddDays(-ageDays); |
There was a problem hiding this comment.
❓ QUESTION: Aging by default pushes every seeded account past the 24-hour new-device-verification window — was that interaction evaluated?
Details
HandleNewDeviceVerificationAsync in src/Identity/IdentityServer/RequestValidators/DeviceValidator.cs:146 short-circuits to Success only while DateTime.UtcNow - user.CreationDate < 24h, and User.VerifyDevices defaults to true (the seeder never sets it). Previously every seeded account sat inside that grace window; now none of them do. The first login still succeeds because the user has no devices yet, but a login from a second device identifier returns NewDeviceVerificationRequired and needs an OTP from email.
Same file, line 207: SendNewDeviceLoginEmail suppresses the email only for accounts under 10 minutes old, so seeded logins now also generate new-device emails they previously did not.
If that's the intended realism, a line in util/SeederUtility/README.md pointing multi-device login flows at --account-age-days 0 would save the next person the debugging. Worth noting that preset-driven seeding (util/Seeder/Pipeline/PresetLoader.cs:46) has no equivalent escape hatch.
🎟️ Tracking
https://bitwarden.atlassian.net/browse/QA-2310
📔 Objective
Seeded users previously fell back to the User entity's CreationDate initializer (DateTime.UtcNow), so every account looked brand-new and age-gated scenarios (e.g. the premium upsell path) could not be reproduced.
UserSeeder.Create now backdates CreationDate for every seeding path. A new nullable UserSeed.AccountAgeDays controls it: null (default) randomizes an aged date between 91 days and 3 years ago, 0 seeds today, and N seeds exactly N days ago. RevisionDate and AccountRevisionDate are left at now.
The knob is exposed through the individual CLI command (--account-age-days) and the SingleUserScene request. Org owner, bulk member, and roster paths inherit the aged default automatically.
📸 Screenshots