{"record":{"id":"48d74e942080f60b","repo":"antiwork/gumroad","slug":"deleted-user","errorCode":"deleted_user","errorMessage":"deleted_user","messagePattern":"deleted_user","errorType":"error_code","errorClass":"VerificationError","httpStatus":422,"severity":"error","filePath":"app/controllers/logins/passkeys_controller.rb","lineNumber":24,"sourceCode":"  AUTHENTICATION_ERROR_MESSAGE = \"We couldn't sign you in with that passkey. Please try again or use your password.\"\n\n  skip_before_action :check_suspended\n  skip_before_action :invalidate_session_if_necessary\n\n  def options\n    render json: { success: true, options: build_webauthn_authentication_options }\n  end\n\n  def create\n    Rails.logger.info(\"passkey.authentication.started\")\n\n    challenge = session.delete(AUTHENTICATION_CHALLENGE_SESSION_KEY)\n    raise VerificationError, \"missing_challenge\" if challenge.blank?\n\n    stored_credential = verified_credential(challenge)\n\n    user = stored_credential.user\n    raise VerificationError, \"deleted_user\" if user.deleted?\n\n    stored_credential.save!\n\n    user.remember_me = true\n    sign_in(user)\n    reset_two_factor_auth_login_session\n    merge_guest_cart_with_user_cart\n    refresh_passkey_setup_prompt(user)\n\n    Rails.logger.info(\"passkey.authentication.succeeded user_id=#{user.id} webauthn_credential_id=#{stored_credential.id}\")\n\n    render json: { success: true, redirect_location: login_path_for(user) }\n  rescue VerificationError => e\n    log_authentication_failure(e.reason)\n    render json: { success: false, error_message: AUTHENTICATION_ERROR_MESSAGE }, status: :unprocessable_entity\n  end\n\n  private","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/controllers/logins/passkeys_controller.rb#L6-L42","documentation":"Raised in `Logins::PasskeysController#create` (logins/passkeys_controller.rb:24): the WebAuthn assertion fully verified (signature, challenge, sign count all valid) and resolved to a stored `WebauthnCredential`, but that credential's user is soft-deleted (`user.deleted?`). Gumroad keeps passkey rows after account deletion, so authentication is refused at the last step with the generic 422 error message.","triggerScenarios":"A user with a registered passkey deletes their Gumroad account and later tries passkey login; an admin deletes/deactivates the account while its passkey remains stored; the credential was registered to an account that was subsequently removed in moderation.","commonSituations":"Returning users after account deletion attempts; shared devices where an old account's passkey still surfaces in the browser's autofill; automated tests exercising deleted-user login paths.","solutions":["If the account should exist, restore/undelete the user (support action) — the same passkey will then work again.","If the account is intentionally gone, sign in flow should guide to account recovery/creation rather than passkey retry; retrying cannot succeed.","On account deletion flows, consider destroying webauthn_credentials so deleted users' passkeys never surface in browser autofill.","Frontend: treat the 422 as terminal for this credential and fall back to password/other methods, not a retry."],"exampleFix":"# before — deletion leaves the passkey behind, login later fails opaquely\nuser.destroy # soft delete; webauthn_credentials remain\n\n# after — remove credentials when the account is deleted\nuser.destroy\nuser.webauthn_credentials.destroy_all","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  user = stored_credential.user\n  raise VerificationError, \"deleted_user\" if user.deleted?\nrescue VerificationError => e\n  log_authentication_failure(e.reason)\n  render json: { success: false, error_message: AUTHENTICATION_ERROR_MESSAGE }, status: :unprocessable_entity\nend","preventionTips":["Treat the 422 as terminal for this credential — offer password/recovery sign-in, not retry.","Destroy webauthn_credentials on account deletion so stale passkeys leave browser autofill.","Surface a distinct in-product message for deleted accounts at the sign-in entry point when email is known.","Keep the reason in server logs (reason=deleted_user) for support triage."],"tags":["webauthn","passkeys","deleted-user","authentication","account-lifecycle"],"backgroundTag":"webauthn-user-not-found","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}