{"record":{"id":"862907cf7ea55dab","repo":"lynndylanhurley/devise_token_auth","slug":"not-found-862907","errorCode":null,"errorMessage":"Not Found","messagePattern":"Not Found","errorType":"http","errorClass":"ActionController::RoutingError","httpStatus":404,"severity":"error","filePath":"app/controllers/devise_token_auth/passwords_controller.rb","lineNumber":149,"sourceCode":"      render_error(422, message, response)\n    end\n\n    def render_create_success\n      render json: {\n        success: true,\n        message: success_message('passwords', @email)\n      }\n    end\n\n    def render_create_error(errors)\n      render json: {\n        success: false,\n        errors: errors\n      }, status: 400\n    end\n\n    def render_edit_error\n      raise ActionController::RoutingError, 'Not Found'\n    end\n\n    def render_update_error_unauthorized\n      render_error(401, 'Unauthorized')\n    end\n\n    def render_update_error_password_not_required\n      render_error(422, I18n.t('devise_token_auth.passwords.password_not_required', provider: @resource.provider.humanize))\n    end\n\n    def render_update_error_missing_password\n      render_error(422, I18n.t('devise_token_auth.passwords.missing_passwords'))\n    end\n\n    def render_update_success\n      render json: {\n        success: true,\n        data: resource_data,","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/lynndylanhurley/devise_token_auth/blob/b02076a930f7d0a4a155e25feaa7a50b86dbc625/app/controllers/devise_token_auth/passwords_controller.rb#L131-L167","documentation":"Raised by the GET /auth/password/edit endpoint (PasswordsController#edit, the landing action for password-reset email links; render_edit_error at passwords_controller.rb:149). The action resolves the user with Devise's with_reset_password_token and checks reset_password_period_valid?; when the reset_password_token is blank, unknown, already consumed, or older than Devise.reset_password_within, render_edit_error raises ActionController::RoutingError and Rails returns 404. Unlike the confirmations controller, this path has no redirect-with-flag alternative: every invalid token is a 404 regardless of redirect_url.","triggerScenarios":"GET /auth/password/edit?reset_password_token=X&redirect_url=Y where X is expired (past Devise.reset_password_within, 6 hours by default in Devise), already used (each token authorizes one reset and is cleared afterwards), belongs to an older email after a newer reset was requested, is blank, or was altered in transit. Note a missing or non-whitelisted redirect_url fails earlier with 401/422, not this raise.","commonSituations":"Users digging up an old reset email after several newer requests (only the newest token is valid); long delays between email generation and click exceeding reset_password_within; SPA routers dropping the query string when opening the reset route; tokens truncated or escaped by mail clients; Devise.paranoid making POST /auth/password always report success so stale links keep circulating.","solutions":["Request a fresh reset email (POST /auth/password with email and redirect_url) and open only the link from the newest message","Check Devise.reset_password_within in config/initializers/devise.rb and the user's reset_password_sent_at value; widen the window if legitimate users regularly exceed it","Make sure the client preserves the full query string (reset_password_token and redirect_url) when routing the user from the emailed link","Compare the token against User.find_by(email: ...).reset_password_token in the console to detect mangling","For API-only apps, override DeviseTokenAuth::PasswordsController#render_edit_error to render a JSON error body instead of raising RoutingError"],"exampleFix":"# before - devise_token_auth default\ndef render_edit_error\n  raise ActionController::RoutingError, 'Not Found'\nend\n\n# after - app/controllers/passwords_controller.rb (host app override)\nclass PasswordsController < DeviseTokenAuth::PasswordsController\n  def render_edit_error\n    render json: { success: false, errors: ['Invalid or expired reset password token.'] }, status: :not_found\n  end\nend\n\n# config/routes.rb\nmount_devise_token_auth_for 'User', at: 'auth', controllers: { passwords: 'passwords' }","handlingStrategy":"fallback","validationCode":"// before routing the user to the reset landing page\nfunction assertResetLinkParams(query) {\n  if (!query.get('reset_password_token')) throw new Error('reset_password_token missing from link');\n  if (!query.get('redirect_url')) throw new Error('redirect_url missing from link');\n  return query;\n}","typeGuard":"const isPlausibleResetToken = (t) =>\n  typeof t === 'string' && /^[A-Za-z0-9_-]{10,}$/.test(t);","tryCatchPattern":"// client handling of the reset landing request\nconst res = await fetch(resetUrl, { redirect: 'manual' });\nif (res.status === 404) {\n  // token invalid, expired, or consumed: fall back to requesting a new email, do not retry the same link\n  await requestPasswordReset(email, redirectUrl);\n}","preventionTips":["Open only the newest reset email; each new request makes older links dead ends","Complete reset flows well inside Devise.reset_password_within","Never log or store reset links: tokens get consumed and saved copies 404 later","Preserve the full query string when the SPA routes the user away from the emailed link"],"tags":["devise-token-auth","rails","password-reset","reset-password-token","http-404","authentication"],"backgroundTag":"invalid-or-expired-verification-token","analyzedSha":"b02076a930f7d0a4a155e25feaa7a50b86dbc625","analyzedAt":"2026-08-23T09:41:47.905Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}