ispmail-on-rails 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +61 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +6 -0
- data/app/assets/images/.keep +0 -0
- data/app/assets/javascripts/application.js +25 -0
- data/app/assets/javascripts/virtual_aliases.coffee +3 -0
- data/app/assets/javascripts/virtual_domains.coffee +3 -0
- data/app/assets/javascripts/virtual_users.coffee +3 -0
- data/app/assets/stylesheets/_settings.scss +566 -0
- data/app/assets/stylesheets/application.css +21 -0
- data/app/assets/stylesheets/clearance.scss +6 -0
- data/app/assets/stylesheets/foundation_and_overrides.scss +51 -0
- data/app/assets/stylesheets/virtual_aliases.scss +3 -0
- data/app/assets/stylesheets/virtual_domains.scss +3 -0
- data/app/assets/stylesheets/virtual_users.scss +3 -0
- data/app/controllers/api/base_controller.rb +90 -0
- data/app/controllers/api/virtual_aliases_controller.rb +12 -0
- data/app/controllers/api/virtual_domains_controller.rb +15 -0
- data/app/controllers/api/virtual_users_controller.rb +15 -0
- data/app/controllers/application_controller.rb +9 -0
- data/app/controllers/concerns/.keep +0 -0
- data/app/controllers/virtual_aliases_controller.rb +6 -0
- data/app/controllers/virtual_domains_controller.rb +6 -0
- data/app/controllers/virtual_users_controller.rb +6 -0
- data/app/helpers/application_helper.rb +33 -0
- data/app/helpers/virtual_aliases_helper.rb +2 -0
- data/app/helpers/virtual_domains_helper.rb +2 -0
- data/app/helpers/virtual_users_helper.rb +2 -0
- data/app/mailers/.keep +0 -0
- data/app/models/.keep +0 -0
- data/app/models/concerns/.keep +0 -0
- data/app/models/user.rb +3 -0
- data/app/models/virtual_alias.rb +16 -0
- data/app/models/virtual_domain.rb +22 -0
- data/app/models/virtual_user.rb +35 -0
- data/app/validators/domain_name_validator.rb +21 -0
- data/app/validators/password_validator.rb +10 -0
- data/app/views/api/virtual_aliases/index.json.jbuilder +6 -0
- data/app/views/api/virtual_aliases/show.json.jbuilder +6 -0
- data/app/views/api/virtual_domains/index.json.jbuilder +4 -0
- data/app/views/api/virtual_domains/show.json.jbuilder +4 -0
- data/app/views/api/virtual_users/index.json.jbuilder +5 -0
- data/app/views/api/virtual_users/show.json.jbuilder +5 -0
- data/app/views/application/_heading.html.erb +12 -0
- data/app/views/clearance_mailer/change_password.html.erb +8 -0
- data/app/views/clearance_mailer/change_password.text.erb +5 -0
- data/app/views/layouts/application.html.erb +41 -0
- data/app/views/passwords/create.html.erb +3 -0
- data/app/views/passwords/edit.html.erb +18 -0
- data/app/views/passwords/new.html.erb +16 -0
- data/app/views/sessions/_form.html.erb +22 -0
- data/app/views/sessions/new.html.erb +6 -0
- data/app/views/users/_form.html.erb +9 -0
- data/app/views/users/new.html.erb +15 -0
- data/app/views/virtual_aliases/index.html.erb +58 -0
- data/app/views/virtual_domains/index.html.erb +56 -0
- data/app/views/virtual_users/_password_form.html.erb +23 -0
- data/app/views/virtual_users/index.html.erb +57 -0
- data/bin/bundle +3 -0
- data/bin/console +14 -0
- data/bin/rails +9 -0
- data/bin/rake +9 -0
- data/bin/setup +29 -0
- data/bin/spring +15 -0
- data/config.ru +4 -0
- data/config/application.rb +30 -0
- data/config/boot.rb +3 -0
- data/config/database.yml +31 -0
- data/config/environment.rb +5 -0
- data/config/environments/development.rb +42 -0
- data/config/environments/production.rb +82 -0
- data/config/environments/test.rb +43 -0
- data/config/initializers/assets.rb +11 -0
- data/config/initializers/backtrace_silencers.rb +7 -0
- data/config/initializers/clearance.rb +4 -0
- data/config/initializers/cookies_serializer.rb +3 -0
- data/config/initializers/filter_parameter_logging.rb +4 -0
- data/config/initializers/inflections.rb +16 -0
- data/config/initializers/mime_types.rb +4 -0
- data/config/initializers/session_store.rb +3 -0
- data/config/initializers/wrap_parameters.rb +14 -0
- data/config/locales/clearance.en.yml +59 -0
- data/config/locales/en.yml +23 -0
- data/config/routes.rb +74 -0
- data/config/secrets.yml +22 -0
- data/db/development.sqlite3 +0 -0
- data/db/migrate/20160321130230_create_virtual_domains.rb +13 -0
- data/db/migrate/20160321130250_create_virtual_users.rb +16 -0
- data/db/migrate/20160321130353_create_virtual_aliases.rb +15 -0
- data/db/migrate/20160321133546_create_users.rb +14 -0
- data/db/schema.rb +59 -0
- data/db/seeds.rb +8 -0
- data/ispmail-on-rails.gemspec +25 -0
- data/lib/assets/.keep +0 -0
- data/lib/ispmail/on/rails.rb +9 -0
- data/lib/ispmail/on/rails/version.rb +7 -0
- data/lib/tasks/.keep +0 -0
- data/log/.keep +0 -0
- data/public/404.html +67 -0
- data/public/422.html +67 -0
- data/public/500.html +66 -0
- data/public/favicon.ico +0 -0
- data/public/robots.txt +5 -0
- data/tmp/.keep +0 -0
- data/vendor/assets/javascripts/.keep +0 -0
- data/vendor/assets/stylesheets/.keep +0 -0
- metadata +198 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*= require foundation_and_overrides
|
16
|
+
*= require clearance
|
17
|
+
|
18
|
+
*/
|
19
|
+
.domain-heading .columns p {
|
20
|
+
margin-top: 1.35em;
|
21
|
+
}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
@charset 'utf-8';
|
2
|
+
|
3
|
+
@import 'settings';
|
4
|
+
@import 'foundation';
|
5
|
+
|
6
|
+
// If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package.
|
7
|
+
//
|
8
|
+
// @import 'motion-ui/motion-ui';
|
9
|
+
|
10
|
+
// We include everything by default. To slim your CSS, remove components you don't use.
|
11
|
+
|
12
|
+
@include foundation-global-styles;
|
13
|
+
@include foundation-grid;
|
14
|
+
@include foundation-typography;
|
15
|
+
@include foundation-button;
|
16
|
+
@include foundation-forms;
|
17
|
+
@include foundation-visibility-classes;
|
18
|
+
@include foundation-float-classes;
|
19
|
+
@include foundation-accordion;
|
20
|
+
@include foundation-accordion-menu;
|
21
|
+
@include foundation-badge;
|
22
|
+
@include foundation-breadcrumbs;
|
23
|
+
@include foundation-button-group;
|
24
|
+
@include foundation-callout;
|
25
|
+
@include foundation-close-button;
|
26
|
+
@include foundation-drilldown-menu;
|
27
|
+
@include foundation-dropdown;
|
28
|
+
@include foundation-dropdown-menu;
|
29
|
+
@include foundation-flex-video;
|
30
|
+
@include foundation-label;
|
31
|
+
@include foundation-media-object;
|
32
|
+
@include foundation-menu;
|
33
|
+
@include foundation-off-canvas;
|
34
|
+
@include foundation-orbit;
|
35
|
+
@include foundation-pagination;
|
36
|
+
@include foundation-progress-bar;
|
37
|
+
@include foundation-slider;
|
38
|
+
@include foundation-sticky;
|
39
|
+
@include foundation-reveal;
|
40
|
+
@include foundation-switch;
|
41
|
+
@include foundation-table;
|
42
|
+
@include foundation-tabs;
|
43
|
+
@include foundation-thumbnail;
|
44
|
+
@include foundation-title-bar;
|
45
|
+
@include foundation-tooltip;
|
46
|
+
@include foundation-top-bar;
|
47
|
+
|
48
|
+
// If you'd like to include motion-ui the foundation-rails gem comes prepackaged with it, uncomment the 3 @imports, if you are not using the gem you need to install the motion-ui sass package.
|
49
|
+
//
|
50
|
+
// @include motion-ui-transitions;
|
51
|
+
// @include motion-ui-animations;
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# See https://blog.codelation.com/rails-restful-api-just-add-water/
|
2
|
+
module Api
|
3
|
+
class BaseController < ApplicationController
|
4
|
+
protect_from_forgery with: :null_session
|
5
|
+
before_action :set_resource, only: [:destroy, :show, :update]
|
6
|
+
|
7
|
+
# POST /api/{plural_resource_name}
|
8
|
+
def create
|
9
|
+
set_resource(resource_class.new(resource_params))
|
10
|
+
|
11
|
+
if get_resource.save
|
12
|
+
render :show, status: :created
|
13
|
+
else
|
14
|
+
render json: get_resource.errors.full_messages, status: :unprocessable_entity
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# DELETE /api/{plural_resource_name}/1
|
19
|
+
def destroy
|
20
|
+
get_resource.destroy
|
21
|
+
head :no_content
|
22
|
+
end
|
23
|
+
|
24
|
+
# GET /api/{plural_resource_name}
|
25
|
+
def index
|
26
|
+
plural_resource_name = "@#{resource_name.pluralize}"
|
27
|
+
resources = resource_class.where(query_params)
|
28
|
+
|
29
|
+
instance_variable_set(plural_resource_name, resources)
|
30
|
+
instance_variable_get(plural_resource_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
# GET /api/{plural_resource_name}/1
|
34
|
+
def show
|
35
|
+
get_resource
|
36
|
+
end
|
37
|
+
|
38
|
+
# PATCH/PUT /api/{plural_resource_name}/1
|
39
|
+
def update
|
40
|
+
if get_resource.update(resource_params)
|
41
|
+
render :show
|
42
|
+
else
|
43
|
+
render json: get_resource.errors.full_messages, status: :unprocessable_entity
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Returns the resource from the created instance variable
|
50
|
+
# @return [Object]
|
51
|
+
def get_resource
|
52
|
+
instance_variable_get("@#{resource_name}")
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the allowed parameters for searching
|
56
|
+
# Override this method in each API controller
|
57
|
+
# to permit additional parameters to search on
|
58
|
+
# @return [Hash]
|
59
|
+
def query_params
|
60
|
+
{}
|
61
|
+
end
|
62
|
+
|
63
|
+
# The resource class based on the controller
|
64
|
+
# @return [Class]
|
65
|
+
def resource_class
|
66
|
+
@resource_class ||= resource_name.classify.constantize
|
67
|
+
end
|
68
|
+
|
69
|
+
# The singular name for the resource class based on the controller
|
70
|
+
# @return [String]
|
71
|
+
def resource_name
|
72
|
+
@resource_name ||= self.controller_name.singularize
|
73
|
+
end
|
74
|
+
|
75
|
+
# Only allow a trusted parameter "white list" through.
|
76
|
+
# If a single resource is loaded for #create or #update,
|
77
|
+
# then the controller for the resource must implement
|
78
|
+
# the method "#{resource_name}_params" to limit permitted
|
79
|
+
# parameters for the individual model.
|
80
|
+
def resource_params
|
81
|
+
@resource_params ||= self.send("#{resource_name}_params")
|
82
|
+
end
|
83
|
+
|
84
|
+
# Use callbacks to share common setup or constraints between actions.
|
85
|
+
def set_resource(resource = nil)
|
86
|
+
resource ||= resource_class.find(params[:id])
|
87
|
+
instance_variable_set("@#{resource_name}", resource)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Api
|
2
|
+
class VirtualUsersController < Api::BaseController
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def virtual_user_params
|
7
|
+
params.require(:virtual_user).permit(:email, :domain_id, :new_password)
|
8
|
+
end
|
9
|
+
|
10
|
+
def query_params
|
11
|
+
params.permit(:email)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class ApplicationController < ActionController::Base
|
2
|
+
include Clearance::Controller
|
3
|
+
|
4
|
+
before_action :require_login
|
5
|
+
|
6
|
+
# Prevent CSRF attacks by raising an exception.
|
7
|
+
# For APIs, you may want to use :null_session instead.
|
8
|
+
protect_from_forgery with: :exception
|
9
|
+
end
|
File without changes
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module ApplicationHelper
|
2
|
+
def form_error_callout(form_id)
|
3
|
+
form_callout = <<-ajax_error
|
4
|
+
$('form##{form_id}').bind('ajax:error', function(evt, data, status, xhr){
|
5
|
+
$("#messages").html(
|
6
|
+
'#{flash_messages('data.responseJSON.join("<br/>")', true, true)}'
|
7
|
+
);
|
8
|
+
$('#messages').foundation('open');
|
9
|
+
})
|
10
|
+
ajax_error
|
11
|
+
form_callout.html_safe
|
12
|
+
end
|
13
|
+
|
14
|
+
# Render HTML flash messages callout
|
15
|
+
def flash_messages(text, warning=false, javascript=false)
|
16
|
+
html = <<-messages
|
17
|
+
<div id="flash" class="callout #{warning ? 'warning' : 'secondary' }" data-closable>
|
18
|
+
REPLACEME
|
19
|
+
<button class="close-button" aria-label="Dismiss alert" type="button" data-close>
|
20
|
+
<span aria-hidden="true">×</span>
|
21
|
+
</button>
|
22
|
+
</div>
|
23
|
+
messages
|
24
|
+
if javascript
|
25
|
+
js = html.split("\n").map(&:strip).join("'+'")
|
26
|
+
return js.sub(/'REPLACEME'/, text)
|
27
|
+
else
|
28
|
+
html.sub!(/REPLACEME/, text)
|
29
|
+
return html.html_safe
|
30
|
+
end
|
31
|
+
html.html_safe
|
32
|
+
end
|
33
|
+
end
|
data/app/mailers/.keep
ADDED
File without changes
|
data/app/models/.keep
ADDED
File without changes
|
File without changes
|
data/app/models/user.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
class VirtualAlias < ActiveRecord::Base
|
2
|
+
validates :source, presence: true, domain_name: true, email: true
|
3
|
+
validates :destination, presence: true, email: true
|
4
|
+
validates :domain_id, presence: true
|
5
|
+
|
6
|
+
belongs_to :virtual_domain, foreign_key: :domain_id
|
7
|
+
|
8
|
+
before_validation :complete_emails
|
9
|
+
|
10
|
+
private
|
11
|
+
def complete_emails
|
12
|
+
self.source += "@#{virtual_domain.name}" unless source =~ /@/
|
13
|
+
self.destination += "@#{virtual_domain.name}" unless destination =~ /@/
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Email domains
|
2
|
+
class VirtualDomain < ActiveRecord::Base
|
3
|
+
validates :name, presence: true, uniqueness: true, domain_name: true
|
4
|
+
|
5
|
+
has_many :virtual_users, foreign_key: :domain_id
|
6
|
+
has_many :virtual_aliases, foreign_key: :domain_id
|
7
|
+
|
8
|
+
attr_reader :mx_records
|
9
|
+
|
10
|
+
# What are the DNS MX records for this VirtualDomain
|
11
|
+
def mx_records
|
12
|
+
@mx_records ||= Resolv::DNS.open do |dns|
|
13
|
+
records = dns.getresources(name, Resolv::DNS::Resource::IN::MX)
|
14
|
+
records.map(&:exchange).join(" ")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Does this VirtualDomain's MX record resolve to the local machine?
|
19
|
+
def does_resolve_to_host?
|
20
|
+
mx_records.include? Socket.gethostname
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# These are the actual email addresses with passwords
|
2
|
+
class VirtualUser < ActiveRecord::Base
|
3
|
+
validates_presence_of :domain_id
|
4
|
+
validates :email, uniqueness: true, presence: true, domain_name: true, email: true
|
5
|
+
validates :new_password, password: true
|
6
|
+
|
7
|
+
belongs_to :virtual_domain, foreign_key: :domain_id
|
8
|
+
|
9
|
+
attr_accessor :new_password
|
10
|
+
|
11
|
+
before_validation :complete_email
|
12
|
+
before_save :encrypt_new_password
|
13
|
+
|
14
|
+
# Capture any misplaced password changes
|
15
|
+
def password=(value)
|
16
|
+
self.new_password = value
|
17
|
+
end
|
18
|
+
|
19
|
+
def password_changing?
|
20
|
+
!new_password.blank?
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def complete_email
|
25
|
+
unless email =~ /@/
|
26
|
+
self.email += "@#{virtual_domain.name}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def encrypt_new_password
|
31
|
+
if password_changing?
|
32
|
+
self["password"] = UnixCrypt::SHA256.build(new_password)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Validates the attribute's value includes the virtual_domain.name after an at symbol
|
2
|
+
class DomainNameValidator < ActiveModel::EachValidator
|
3
|
+
def validate_each(record, attribute, value)
|
4
|
+
if record.class == VirtualDomain
|
5
|
+
domain_check = Regexp.new "^[a-zA-Z0-9][a-zA-Z0-9-_]{0,61}[a-zA-Z0-9]{0,1}\.([a-zA-Z]{1,6}|[a-zA-Z0-9-]{1,30}\.[a-zA-Z]{2,})$"
|
6
|
+
tld = value.split(".").last
|
7
|
+
unless IANA::TLD.valid?(tld)
|
8
|
+
record.errors[attribute] << (options[:message] || ".#{tld} is not accepted by IANA as a Top Level Domain")
|
9
|
+
end
|
10
|
+
unless record.name =~ domain_check
|
11
|
+
record.errors[attribute] << (options[:message] || "is not a valid domain name")
|
12
|
+
end
|
13
|
+
else
|
14
|
+
domain = record.virtual_domain.name
|
15
|
+
domain_check = Regexp.new "@"+domain+"$"
|
16
|
+
unless value =~ domain_check
|
17
|
+
record.errors[attribute] << (options[:message] || "is not under #{domain}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# Validates the attribute's value is an acceptable password
|
2
|
+
class PasswordValidator < ActiveModel::EachValidator
|
3
|
+
def validate_each(record, attribute, value)
|
4
|
+
if record.password_changing? && value.to_s.size <= 10
|
5
|
+
record.errors[attribute] << (options[:message] || "must be minimum 10 characters long")
|
6
|
+
elsif record.password.blank? && !record.password_changing?
|
7
|
+
record.errors[attribute] << (options[:message] || "must be set")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|