diff --git a/app/assets/javascripts/application/application.coffee b/app/assets/javascripts/application/application.coffee index c0ea875..b13efb0 100644 --- a/app/assets/javascripts/application/application.coffee +++ b/app/assets/javascripts/application/application.coffee @@ -53,3 +53,21 @@ $ -> $(element, $link.parent()).toggle() false + toggleCredentialInputs = (type)-> + $('[data-credential-key-type]').hide() + $('[data-credential-key-type] input').attr('disabled', true) + if type == 'SMTP-IP' + $('[data-credential-key-type=smtp-ip]').show() + $('[data-credential-key-type=smtp-ip] input').attr('disabled', false) + else + $('[data-credential-key-type=all]').show() + + + $(document).on 'change', 'select#credential_type', -> + value = $(this).val() + toggleCredentialInputs(value) + + $(document).on 'turbolinks:load', -> + credentialTypeInput = $('select#credential_type') + if credentialTypeInput.length + toggleCredentialInputs(credentialTypeInput.val()) diff --git a/app/assets/stylesheets/application/elements/_label.scss b/app/assets/stylesheets/application/elements/_label.scss index 8899f15..e875263 100644 --- a/app/assets/stylesheets/application/elements/_label.scss +++ b/app/assets/stylesheets/application/elements/_label.scss @@ -95,6 +95,10 @@ background-color:$turquoise; } +.label--credentialType-smtp_ip { + background-color:$orange; +} + .label--spamStatus-not_checked { background:#aaa; } diff --git a/app/controllers/credentials_controller.rb b/app/controllers/credentials_controller.rb index c44336a..449d839 100644 --- a/app/controllers/credentials_controller.rb +++ b/app/controllers/credentials_controller.rb @@ -3,7 +3,7 @@ class CredentialsController < ApplicationController include WithinOrganization before_action { @server = organization.servers.present.find_by_permalink!(params[:server_id]) } - before_action { params[:id] && @credential = @server.credentials.find_by_key!(params[:id]) } + before_action { params[:id] && @credential = @server.credentials.find_by_uuid!(params[:id]) } def index @credentials = @server.credentials.order(:name).to_a @@ -14,7 +14,7 @@ class CredentialsController < ApplicationController end def create - @credential = @server.credentials.build(params.require(:credential).permit(:type, :name, :hold)) + @credential = @server.credentials.build(params.require(:credential).permit(:type, :name, :key, :hold)) if @credential.save redirect_to_with_json [organization, @server, :credentials] else @@ -23,7 +23,7 @@ class CredentialsController < ApplicationController end def update - if @credential.update(params.require(:credential).permit(:name, :hold)) + if @credential.update(params.require(:credential).permit(:name, :key, :hold)) redirect_to_with_json [organization, @server, :credentials] else render_form_errors 'edit', @credential diff --git a/app/models/credential.rb b/app/models/credential.rb index f01867f..cfc7f73 100644 --- a/app/models/credential.rb +++ b/app/models/credential.rb @@ -12,24 +12,37 @@ # created_at :datetime # updated_at :datetime # hold :boolean default(FALSE) +# uuid :string(255) # class Credential < ApplicationRecord + include HasUUID + belongs_to :server - TYPES = ['SMTP', 'API'] + TYPES = ['SMTP', 'API', 'SMTP-IP'] validates :key, :presence => true, :uniqueness => true validates :type, :inclusion => {:in => TYPES} validates :name, :presence => true - - random_string :key, :type => :chars, :length => 24, :unique => true + validate :validate_key_cannot_be_changed + validate :validate_key_for_smtp_ip serialize :options, Hash + before_validation :generate_key + + + def generate_key + return if self.type == 'SMTP-IP' + return if self.persisted? + + self.key = SecureRandomString.new(24) + end + def to_param - key + uuid end def use @@ -54,4 +67,30 @@ class Credential < ApplicationRecord Base64.encode64("\0XX\0#{self.key}").strip end + def ipaddr + return unless type == 'SMTP-IP' + + @ipaddr ||= IPAddr.new(self.key) + rescue IPAddr::InvalidAddressError + nil + end + + private + + def validate_key_cannot_be_changed + return if new_record? + return unless key_changed? + return if type == 'SMTP-IP' + + errors.add :key, "cannot be changed" + end + + def validate_key_for_smtp_ip + return unless type == 'SMTP-IP' + + IPAddr.new(self.key.to_s) + rescue IPAddr::InvalidAddressError + errors.add :key, "must be a valid IPv4 or IPv6 address" + end + end diff --git a/app/views/credentials/_form.html.haml b/app/views/credentials/_form.html.haml index 901ece0..2ee33c0 100644 --- a/app/views/credentials/_form.html.haml +++ b/app/views/credentials/_form.html.haml @@ -15,14 +15,23 @@ %p.fieldSet__text This is a friendly name so you can identify this credential later. You can enter anything you want here, the more descriptive the better. - .fieldSet__field + + .fieldSet__field{data: {credential_key_type: 'all'}} = f.label :key, :class => 'fieldSet__label' .fieldSet__input - = f.text_field :key, :readonly => true, :class => 'input input--text input--code', :placeholder => "Automatically generated", :tabindex => 1000, :value => (@credential.new_record? ? '' : @credential.key) + = f.text_field :key, :readonly => false, :class => 'input input--text input--code', :placeholder => "Automatically generated", :tabindex => 1000, :value => (@credential.new_record? ? '' : @credential.key) %p.fieldSet__text - This is the unique key which will be used to authenticate any requests to the API or our SMTP servers. + This is the unique key which will be used to authenticate any requests to the API or the SMTP servers. It will be generated randomly and cannot be changed. If you need a new token, you can create a new one and then delete the old one when you're ready. + + .fieldSet__field{data: {credential_key_type: 'smtp-ip'}} + = f.label :key, "Network", :class => 'fieldSet__label' + .fieldSet__input + = f.text_field :key, :class => 'input input--text input--code' + %p.fieldSet__text + This is the IP address or network that you wish to allow to authenticate to this mail server. + .fieldSet__field = f.label :hold, :class => 'fieldSet__label' .fieldSet__input diff --git a/app/views/credentials/index.html.haml b/app/views/credentials/index.html.haml index cf9c6b6..bc77d27 100644 --- a/app/views/credentials/index.html.haml +++ b/app/views/credentials/index.html.haml @@ -23,7 +23,7 @@ %li.credentialList__item = link_to [:edit, organization, @server, credential], :class => 'credentialList__link' do .credentialList__type - %span.label{:class => "label--credentialType-#{credential.type.underscore}"}= credential.type + %span.label{:class => "label--credentialType-#{credential.type.underscore}"}= credential.type.split('-').last .credentialList__properties %p.credentialList__name = credential.name diff --git a/db/migrate/20200717083943_add_uuid_to_credentials.rb b/db/migrate/20200717083943_add_uuid_to_credentials.rb new file mode 100644 index 0000000..b3bb1b6 --- /dev/null +++ b/db/migrate/20200717083943_add_uuid_to_credentials.rb @@ -0,0 +1,8 @@ +class AddUUIDToCredentials < ActiveRecord::Migration[5.2] + def change + add_column :credentials, :uuid, :string + Credential.find_each do |c| + c.update_column(:uuid, SecureRandom.uuid) + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 344fff1..d649b24 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2018_02_16_114344) do +ActiveRecord::Schema.define(version: 2020_07_17_083943) do create_table "additional_route_endpoints", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "route_id" @@ -68,6 +68,7 @@ ActiveRecord::Schema.define(version: 2018_02_16_114344) do t.datetime "created_at", precision: 6 t.datetime "updated_at", precision: 6 t.boolean "hold", default: false + t.string "uuid" end create_table "domains", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| diff --git a/lib/postal/smtp_server/client.rb b/lib/postal/smtp_server/client.rb index 82653d4..b02b0d1 100644 --- a/lib/postal/smtp_server/client.rb +++ b/lib/postal/smtp_server/client.rb @@ -312,8 +312,18 @@ module Postal end else - # This is unaccepted mail - '530 Authentication required' + # User is trying to relay but is not authenticated. Try to authenticate by IP address + @credential = Credential.where(:type => 'SMTP-IP').all.sort_by { |c| c.ipaddr&.prefix || 0 }.reverse.find do |credential| + credential.ipaddr.include?(@ip_address) + end + + if @credential + # Retry with credential + @credential.use + rcpt_to(data) + else + '530 Authentication required' + end end end