bitwarden/server · error · BadRequestException
All existing reset password keys must be included in the rot
Error message
All existing reset password keys must be included in the rotation.
What it means
Thrown by OrganizationUserRotationValidator during key rotation. It loads every organization membership the user has where account-recovery (reset password) is already provisioned (ResetPasswordKey is valid), and requires the rotation request to include each one matched by OrganizationId. Omitting one would leave that org's recovery key encrypted under the old key, so the rotation is rejected.
Source
Thrown at src/Api/KeyManagement/Validators/OrganizationUserRotationValidator.cs:44
}
var result = new List<OrganizationUser>();
var existing = await _organizationUserRepository.GetManyByUserAsync(user.Id);
if (existing == null || existing.Count == 0)
{
return result;
}
// Exclude any account recovery that do not have a key.
existing = existing.Where(o => OrganizationUser.IsValidResetPasswordKey(o.ResetPasswordKey)).ToList();
foreach (var ou in existing)
{
var organizationUser = resetPasswordKeys.FirstOrDefault(a => a.OrganizationId == ou.OrganizationId);
if (organizationUser == null)
{
throw new BadRequestException("All existing reset password keys must be included in the rotation.");
}
// Should be migrated to: if (!OrganizationUser.IsValidResetPasswordKey(organizationUser.ResetPasswordKey))
// after https://bitwarden.atlassian.net/browse/PM-31001 is resolved
if (organizationUser.ResetPasswordKey == null)
{
throw new BadRequestException("Reset Password keys cannot be set to null during rotation.");
}
ou.ResetPasswordKey = organizationUser.ResetPasswordKey;
result.Add(ou);
}
return result;
}
}
View on GitHub (pinned to e93b962371)
Solutions
- Fetch the user's current organization memberships (with reset-password keys) immediately before building the rotation payload.
- Include every OrganizationId that has a valid ResetPasswordKey, re-encrypted with the new user key.
- If account recovery should no longer apply, leave the org or have recovery disabled before rotating.
- Validate the submitted OrganizationId set covers every provisioned-recovery membership before sending.
Example fix
// before
const keys = knownOrgs.map(o => ({ organizationId: o.id, resetPasswordKey: reencrypt(o.key) }));
// after
const me = await api.getMyOrgMemberships();
const keys = me.filter(o => o.resetPasswordKey).map(o => ({ organizationId: o.organizationId, resetPasswordKey: reencrypt(o.resetPasswordKey) })); Defensive patterns
Strategy: validation
Validate before calling
const memberships = (await api.getMyOrgMemberships()).filter(o => o.resetPasswordKey);
const submitted = new Set(payload.resetPasswordKeys.map(k => k.organizationId));
const missing = memberships.filter(o => !submitted.has(o.organizationId));
if (missing.length) {
throw new Error(`Rotation is missing reset-password keys for orgs: ${missing.map(o => o.organizationId).join(', ')}`);
} Type guard
function isCompleteOrgUserRotation(existing: { organizationId: string; resetPasswordKey: string | null }[], submitted: { organizationId: string }[]): boolean {
const have = new Set(submitted.map(s => s.organizationId));
return existing.filter(o => o.resetPasswordKey != null).every(o => have.has(o.organizationId));
} Try / catch
try {
await api.rotateKey(payload);
} catch (e) {
if (e.status === 400 && /reset password keys must be included/i.test(e.message)) {
await refreshOrgMemberships();
payload.resetPasswordKeys = memberships.map(o => ({ organizationId: o.organizationId, resetPasswordKey: reencrypt(o.resetPasswordKey) }));
return api.rotateKey(payload);
}
throw e;
} Prevention
- Re-fetch org memberships with reset-password keys before building the payload.
- Leave the org or have recovery disabled to exclude it from rotation.
- Assert the submitted OrganizationId set covers every membership with a valid reset key.
When it happens
Trigger: Key-rotation request whose reset-password-keys array omits an OrganizationId for which the user has a valid ResetPasswordKey. The user joined an org (or org recovery was enabled) after the client cached memberships; the client only listed orgs it recognized.
Common situations: User accepted an org invite or an admin enabled account recovery between the client's last sync and the rotation; client built the org list from local state; an OrganizationId was dropped.
Related errors
- Reset Password keys cannot be set to null during rotation.
- All existing trusted devices must be included in the rotatio
- All existing emergency access keys must be included in the r
- All existing folders must be included in the rotation.
- All existing sends must be included in the rotation.
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/004a1cbfa71c2254.
Report an issue: GitHub.