diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index d8bd387..a289acf 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,3 +1,89 @@ class ApplicationMailer < ActionMailer::Base layout 'mailer' + + private + + def send_mail + @template ||= "#{self.class.name.underscore}/#{caller[0][/`([^']*)'/, 1]}" + headers['Message-ID'] = message_id + + if @user.pgp_pubkey.present? + mail(to: @user.email, subject: "...", content_type: pgp_content_type) do |format| + format.text { render plain: pgp_content } + end + else + mail(to: @user.email, subject: @subject) do |format| + format.text { render @template } + end + end + end + + def from_address + ENV.fetch('SMTP_FROM_ADDRESS', 'accounts@localhost') + end + + def from_domain + Mail::Address.new(from_address).domain + end + + def message_id + @message_id ||= "#{SecureRandom.uuid}@#{from_domain}" + end + + def boundary + @boundary ||= SecureRandom.hex(8) + end + + def pgp_content_type + "multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=\"------------#{boundary}\"" + end + + def pgp_nested_content + message_content = render_to_string(template: @template) + message_content_base64 = Base64.encode64(message_content) + nested_boundary = SecureRandom.hex(8) + + <<~NESTED_CONTENT + Content-Type: multipart/mixed; boundary="------------#{nested_boundary}"; protected-headers="v1" + Subject: #{@subject} + From: <#{from_address}> + To: #{@user.display_name || @user.cn} <#{@user.email}> + Message-ID: <#{message_id}> + + --------------#{nested_boundary} + Content-Type: text/plain; charset=UTF-8; format=flowed + Content-Transfer-Encoding: base64 + + #{message_content_base64} + + --------------#{nested_boundary}-- + NESTED_CONTENT + end + + def pgp_content + encrypted_content = UserManager::PgpEncrypt.call(user: @user, text: pgp_nested_content) + encrypted_base64 = Base64.encode64(encrypted_content.to_s) + + <<~EMAIL_CONTENT + This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156) + --------------#{boundary} + Content-Type: application/pgp-encrypted + Content-Description: PGP/MIME version identification + + Version: 1 + + --------------#{boundary} + Content-Type: application/octet-stream; name="encrypted.asc" + Content-Description: OpenPGP encrypted message + Content-Disposition: inline; filename="encrypted.asc" + + -----BEGIN PGP MESSAGE----- + + #{encrypted_base64} + + -----END PGP MESSAGE----- + + --------------#{boundary}-- + EMAIL_CONTENT + end end diff --git a/app/mailers/custom_mailer.rb b/app/mailers/custom_mailer.rb index 325a620..fd35185 100644 --- a/app/mailers/custom_mailer.rb +++ b/app/mailers/custom_mailer.rb @@ -18,6 +18,6 @@ class CustomMailer < ApplicationMailer @user = params[:user] @subject = params[:subject] @body = params[:body] - mail(to: @user.email, subject: @subject) + send_mail end end diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index b4ec37d..d281380 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -3,7 +3,7 @@ class NotificationMailer < ApplicationMailer @user = params[:user] @amount_sats = params[:amount_sats] @subject = "Sats received" - mail to: @user.email, subject: @subject + send_mail end def remotestorage_auth_created @@ -15,19 +15,19 @@ class NotificationMailer < ApplicationMailer "#{access} #{directory}" end @subject = "New app connected to your storage" - mail to: @user.email, subject: @subject + send_mail end def new_invitations_available @user = params[:user] @subject = "New invitations added to your account" - mail to: @user.email, subject: @subject + send_mail end def bitcoin_donation_confirmed @user = params[:user] @donation = params[:donation] @subject = "Donation confirmed" - mail to: @user.email, subject: @subject + send_mail end end diff --git a/app/services/user_manager/pgp_encrypt.rb b/app/services/user_manager/pgp_encrypt.rb new file mode 100644 index 0000000..afb24b3 --- /dev/null +++ b/app/services/user_manager/pgp_encrypt.rb @@ -0,0 +1,19 @@ +require 'gpgme' + +module UserManager + class PgpEncrypt < UserManagerService + def initialize(user:, text:) + @user = user + @text = text + end + + def call + crypto = GPGME::Crypto.new + crypto.encrypt( + @text, + recipients: @user.gnupg_key, + always_trust: true + ) + end + end +end