dmails, emails: refactor to use Rails signed_id.

Refactor email verification links and Dmail share links to use the new
Rails signed_id mechanism, rather than our own handrolled mechanism.

For Dmail share links, we have to override some Rails internal methods
so that our old links still work. For email verification links, this
will invalidate existing links, but this isn't a huge deal since these
links are short-lived anyway.

https://api.rubyonrails.org/classes/ActiveRecord/SignedId.html
https://api.rubyonrails.org/classes/ActiveRecord/SignedId/ClassMethods.html
This commit is contained in:
evazion
2021-01-17 00:24:02 -06:00
parent 6ca007ee1f
commit 6671711784
7 changed files with 23 additions and 28 deletions

View File

@@ -99,6 +99,8 @@ class ApplicationController < ActionController::Base
render_error_page(401, exception, template: "sessions/new") render_error_page(401, exception, template: "sessions/new")
when ActionController::InvalidAuthenticityToken, ActionController::UnpermittedParameters, ActionController::InvalidCrossOriginRequest when ActionController::InvalidAuthenticityToken, ActionController::UnpermittedParameters, ActionController::InvalidCrossOriginRequest
render_error_page(403, exception) render_error_page(403, exception)
when ActiveSupport::MessageVerifier::InvalidSignature # raised by `find_signed!`
render_error_page(403, exception, template: "static/access_denied", message: "Access denied")
when User::PrivilegeError, Pundit::NotAuthorizedError when User::PrivilegeError, Pundit::NotAuthorizedError
render_error_page(403, exception, template: "static/access_denied", message: "Access denied") render_error_page(403, exception, template: "static/access_denied", message: "Access denied")
when ActiveRecord::RecordNotFound when ActiveRecord::RecordNotFound

View File

@@ -20,7 +20,11 @@ class DmailsController < ApplicationController
end end
def show def show
@dmail = authorize Dmail.find(params[:id]) if params[:key].present?
@dmail = Dmail.find_signed!(params[:key], purpose: "dmail_link")
else
@dmail = authorize Dmail.find(params[:id])
end
if request.format.html? && @dmail.owner == CurrentUser.user if request.format.html? && @dmail.owner == CurrentUser.user
@dmail.update!(is_read: true) @dmail.update!(is_read: true)

View File

@@ -48,8 +48,7 @@ class EmailsController < ApplicationController
if @email_address.blank? if @email_address.blank?
redirect_to edit_user_email_path(@user) redirect_to edit_user_email_path(@user)
elsif params[:email_verification_key].present? elsif params[:email_verification_key].present? && @email_address == EmailAddress.find_signed!(params[:email_verification_key], purpose: "verify")
authorize @email_address
@email_address.verify! @email_address.verify!
flash[:notice] = "Email address verified" flash[:notice] = "Email address verified"
redirect_to @email_address.user redirect_to @email_address.user

View File

@@ -109,16 +109,20 @@ class Dmail < ApplicationRecord
end end
concerning :AuthorizationMethods do concerning :AuthorizationMethods do
def verifier class_methods do
@verifier ||= Danbooru::MessageVerifier.new(:dmail_link) # XXX hack so that rails' signed_id mechanism works with our pre-existing dmail keys.
# https://github.com/rails/rails/blob/main/activerecord/lib/active_record/signed_id.rb
def signed_id_verifier_secret
Rails.application.key_generator.generate_key("dmail_link")
end
def combine_signed_id_purposes(purpose)
purpose
end
end end
def key def key
verifier.generate(id) signed_id(purpose: "dmail_link")
end
def valid_key?(key)
id == verifier.verified(key)
end end
end end

View File

@@ -69,17 +69,7 @@ class EmailAddress < ApplicationRecord
end end
end end
concerning :VerificationMethods do def verification_key
def verifier signed_id(purpose: "verify")
@verifier ||= Danbooru::MessageVerifier.new(:email_verification_key)
end
def verification_key
verifier.generate(id)
end
def valid_key?(key)
id == verifier.verified(key)
end
end end
end end

View File

@@ -17,7 +17,7 @@ class DmailPolicy < ApplicationPolicy
def show? def show?
return true if user.is_owner? return true if user.is_owner?
!user.is_anonymous? && (record.owner_id == user.id || record.valid_key?(request.params[:key])) !user.is_anonymous? && record.owner_id == user.id
end end
def reportable? def reportable?

View File

@@ -13,11 +13,7 @@ class EmailAddressPolicy < ApplicationPolicy
end end
def verify? def verify?
if request.params[:email_verification_key].present? record.user_id == user.id
record.valid_key?(request.params[:email_verification_key])
else
record.user_id == user.id
end
end end
def send_confirmation? def send_confirmation?