# app/controllers/secret_messages_controller.rb
class SecretMessagesController < ApplicationController
def create
@secret_message_form = SecretMessageForm.new(secret_message_form_params)
if @secret_message_form.valid?
encrypted_content = encrypt_content(
seed_phrase: params[:secret_message_form][:seed_phrase],
password: params[:secret_message_form][:password],
content: @secret_message_form.content
)
secret_message = SecretMessage.new(
content: encrypted_content,
expires_at: @secret_message_form.expires_at
)
redirect_to secret_message_path(
verifier.generate(
secret_message,
expires_at: secret_message.expires_at,
purpose: :view_secret_message
)
)
else
render :new, status: :unprocessable_entity
end
end
def decrypt
@secret_message = verifier.verify(params[:signed_message], purpose: :view_secret_message)
decrypted_content = decrypt_content(
seed_phrase: params[:secret_message][:seed_phrase],
password: params[:secret_message][:password],
content: @secret_message.content
)
render plain: decrypted_content, status: :ok
rescue ActiveSupport::MessageEncryptor::InvalidMessage
redirect_to secret_message_path(params[:signed_message]), alert: "Incorrect password or seed phrase"
end
def new
@secret_message_form = SecretMessageForm.new
end
def show
@secret_message = verifier.verify(params[:signed_message], purpose: :view_secret_message)
rescue ActiveSupport::MessageVerifier::InvalidSignature
render plain: "Invalid URL", status: 404
end
private
def crypt(seed_phrase:, password:)
len = Rails.application.credentials.message_encryptor_length!
key = ActiveSupport::KeyGenerator.new(password).generate_key(seed_phrase, len)
ActiveSupport::MessageEncryptor.new(key)
end
def decrypt_content(seed_phrase:, password:, content:)
crypt(seed_phrase: seed_phrase, password: password).decrypt_and_verify(content)
end
def encrypt_content(seed_phrase:, password:, content:)
crypt(seed_phrase: seed_phrase, password: password).encrypt_and_sign(content)
end
def secret_message_form_params
params.require(:secret_message_form)
.permit(
:content,
:expires_at,
:seed_phrase,
:seed_phrase_confirmation,
:password,
:password_confirmation
)
end
def verifier
ActiveSupport::MessageVerifier.new Rails.application.credentials.message_verifier_secret!
end
end
# app/models/secret_message_form.rb
class SecretMessageForm
include ActiveModel::Model
include ActiveModel::Attributes
include ActiveModel::Validations
attribute :content, :string
attribute :expires_at, :datetime
attribute :seed_phrase, :string
attribute :password, :string
validates :content, :expires_at, :seed_phrase, :seed_phrase_confirmation, :password, :password_confirmation, presence: true
validates :seed_phrase, :password, confirmation: true
validate :expires_at_must_be_in_the_future, if: :expires_at_is_present?
private
def expires_at_must_be_in_the_future
if expires_at <= Time.current
errors.add(:expires_at, "must by in the future")
end
end
def expires_at_is_present?
expires_at.presence
end
end
# app/models/secret_message.rb
class SecretMessage
attr_accessor :content, :expires_at
def initialize(content:, expires_at:)
@content = content
@expires_at = expires_at
end
end