{"record":{"id":"e36c1611d4637452","repo":"Freika/dawarich","slug":"that-phrase-didn-t-work-try-again","errorCode":null,"errorMessage":"That phrase didn't work. Try again.","messagePattern":"That phrase didn't work\\. Try again\\.","errorType":"http","errorClass":null,"httpStatus":401,"severity":"warning","filePath":"app/controllers/shared/links_controller.rb","lineNumber":23,"sourceCode":"\n  skip_before_action :verify_authenticity_token, only: %i[show unlock]\n  before_action :set_noindex\n  before_action :load_link\n  before_action :verify_phrase, only: :show\n\n  def show\n    @link.touch_access!\n    @resource = @link.resource\n    render :show\n  end\n\n  def unlock\n    if ActiveSupport::SecurityUtils.secure_compare(@link.magic_phrase.to_s, params[:phrase].to_s)\n      set_unlock_cookie\n      redirect_to public_shared_link_path(@link.id)\n    else\n      Rails.logger.warn(\"Shared link unlock failed: link=#{@link.id} ip=#{request.remote_ip}\")\n      flash.now[:error] = I18n.t('controllers.shared.links.incorrect_phrase')\n      render :phrase_prompt, status: :unauthorized\n    end\n  end\n\n  private\n\n  def load_link\n    @link = SharedLink.active.find_by(id: params[:id])\n    return if @link\n\n    render 'shared/links/not_found', status: :not_found, layout: 'shared'\n  end\n\n  def verify_phrase\n    return if @link.magic_phrase.blank?\n    return if cookies.encrypted[unlock_cookie_key] == @link.unlock_token\n\n    render :phrase_prompt, status: :unauthorized","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/Freika/dawarich/blob/97fad417c5a11b0eb11157890635e015723a2e97/app/controllers/shared/links_controller.rb#L5-L41","documentation":"This is a user-facing flash message, not a log: when someone submits the wrong magic phrase on a shared link's unlock screen, ActiveSupport::SecurityUtils.secure_compare against @link.magic_phrase fails, Rails logs 'Shared link unlock failed' with the link id and requester IP, and the phrase prompt re-renders with HTTP 401. It is the expected incorrect-credentials path for Dawarich's shared-link protection; the constant-time compare prevents phrase-guessing timing attacks.","triggerScenarios":"A typo'd or wrong-case phrase (the compare is exact and case-sensitive); the link owner rotated the magic phrase after the URL was shared; trailing whitespace or autocomplete mangling the submitted input; brute-force attempts against a discovered link id.","commonSituations":"Recipients copy-pasting a phrase with a stray space or smart quote from a messaging app; owners regenerating the phrase and forgetting to re-share it; publicly posted links attracting guessing attempts visible as 'Shared link unlock failed' log lines.","solutions":["Re-enter the phrase exactly as shared — it is case-sensitive and not trimmed server-side","If you own the link, open its settings, confirm the current magic phrase, and re-share it","Check the Rails warn line for attempt volume and IPs if you suspect guessing","Rotate the phrase or deactivate the link if unlock attempts look hostile"],"exampleFix":"<!-- before: browser autofill/copy-paste can submit trailing spaces -->\n<input type=\"text\" name=\"phrase\" id=\"phrase\">\n<!-- after: trim on the client; server still compares exactly -->\n<input type=\"text\" name=\"phrase\" id=\"phrase\" autocomplete=\"off\"\n       oninput=\"this.value = this.value.trim()\">","handlingStrategy":"validation","validationCode":"const phrase = phraseInput.value\nif (phrase.trim().length === 0) {\n  showError(\"Enter the phrase\")\n  return false\n}\nsubmitForm(phrase.trim()) // sender-side trim; the server still compares exactly","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Trim input on the client before submit; the server compares byte-for-byte","Share phrases via copyable text, not screenshots — case matters","Rate-limit unlock attempts at the proxy/Rack layer to blunt phrase guessing","Rotate the magic phrase if 'Shared link unlock failed' log lines spike"],"tags":["rails","shared-links","auth","magic-phrase","i18n","timing-safe"],"backgroundTag":"invalid-credentials","analyzedSha":"97fad417c5a11b0eb11157890635e015723a2e97","analyzedAt":"2026-08-21T17:04:17.778Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}