{"record":{"id":"86cc30fbfd9bc192","repo":"bitwarden/server","slug":"newmasterpasswordhash-and-key-are-required","errorCode":null,"errorMessage":"NewMasterPasswordHash and Key are required.","messagePattern":"NewMasterPasswordHash and Key are required\\.","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Api/Auth/Controllers/AccountsController.cs","lineNumber":181,"sourceCode":"        // check and keep only the SelfServiceChangeEmailCommand call.\n        if (_featureService.IsEnabled(FeatureFlagKeys.PM30806_SelfServiceChangeEmailCommand))\n        {\n            await _selfServiceChangeEmailCommand.ChangeEmailAsync(\n                user, model.MasterPasswordHash, model.NewEmail, model.Token);\n            return;\n        }\n\n        if (user.UsesKeyConnector)\n        {\n            throw new BadRequestException(\"You cannot change your email when using Key Connector.\");\n        }\n\n        // Legacy path still rotates the master password and wrapped user key alongside the\n        // email change; those fields are optional on the model so we have to enforce them here.\n        if (string.IsNullOrEmpty(model.NewMasterPasswordHash) || string.IsNullOrEmpty(model.Key))\n        {\n            ModelState.AddModelError(string.Empty, \"NewMasterPasswordHash and Key are required.\");\n            throw new BadRequestException(ModelState);\n        }\n\n        var result = await _userService.ChangeEmailAsync(user, model.MasterPasswordHash, model.NewEmail,\n            model.NewMasterPasswordHash, model.Token, model.Key);\n        if (result.Succeeded)\n        {\n            return;\n        }\n\n        foreach (var error in result.Errors)\n        {\n            ModelState.AddModelError(string.Empty, error.Description);\n        }\n\n        await Task.Delay(2000);\n        throw new BadRequestException(ModelState);\n    }\n","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/src/Api/Auth/Controllers/AccountsController.cs#L163-L199","documentation":"Thrown as a 400 BadRequestException(ModelState) from the legacy path of POST accounts/email when model.NewMasterPasswordHash or model.Key is null/empty. These fields are optional on the EmailRequestModel, but the legacy email-change flow requires both a new master-password hash and a wrapped user key, so the controller enforces them before calling ChangeEmailAsync. The ModelState error description is 'NewMasterPasswordHash and Key are required.'","triggerScenarios":"A non-Key Connector user (flag OFF) calls POST /accounts/email with UsesKeyConnector false but omits NewMasterPasswordHash or Key from the legacy request body. The missing required field triggers the precondition throw.","commonSituations":"Client built for the newer self-service flow omits the legacy-only fields while hitting a server with the flag still off; partial form submission; model binding dropped the fields due to casing/content-type mismatch.","solutions":["Send both NewMasterPasswordHash (the new master-password hash) and Key (the user key wrapped for the new master password) in the request body on the legacy path.","Enable the PM30806_SelfServiceChangeEmailCommand flag so the client can use the flow that does not require these legacy fields.","Verify the content-type is JSON and field names/casing match the EmailRequestModel so binding populates both fields."],"exampleFix":"// before: legacy fields omitted\nawait api.post('accounts/email', { newEmail, masterPasswordHash, token });\n// after: include the legacy required fields\nawait api.post('accounts/email', {\n  newEmail, masterPasswordHash, token,\n  newMasterPasswordHash: newHash,\n  key: wrappedKey,\n});","handlingStrategy":"validation","validationCode":"function legacyEmailBodyComplete(b) {\n  return !!b && !!b.newMasterPasswordHash && !!b.key;\n}","typeGuard":"function isLegacyEmailRequest(v): v is { newMasterPasswordHash: string; key: string; token: string; newEmail: string; masterPasswordHash: string } {\n  return !!v && typeof v.newMasterPasswordHash === 'string' && typeof v.key === 'string';\n}","tryCatchPattern":null,"preventionTips":["Send NewMasterPasswordHash and Key on the legacy email-change path.","Verify JSON content-type and field casing so binding populates both fields.","Enable the self-service flag to avoid the legacy required fields."],"tags":["bitwarden","email-change","validation","required-fields","aspnet"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}