One Rails application, several domain names - ruby-on-rails

I want to point several domain names to the same Rails application. The content is different for each domain, but the functionality and the structure of the application is the same.
What is the best way to do this when it comes to server set up and routing? I will use nginx as a web server.

if layout needs to be changed only:
add to application controller
layout :setup_layout
def setup_layout
if request.host == "site1.host.tld"
"layout1"
else
"layout2"
end
end
the same logic you can use to get content, this is true if all sites will use one database.
In nginx conf add more hosts to server_name directive:
server_name site1.host.tld site2.host.tld

Related

Conditionally Implementing HSTS, SSL, and Secure Cookies in Rails Based on Domain

I run an application that hosts websites from multiple domains from a single application and server. I am moving some of those domains to SSL, but others are staying at http. I'm running Rails 4.x. I believe I CAN'T just use the
config.force_ssl = true
because that would implement it for all domains, which I don't want.
I know in the ApplicationController I can do something like
force_ssl if: :ssl_required?
def ssl_required?
return [secure_domain1, domain2, domain3].include? request.domain
end
But as I understand it, that doesn't implement HSTS or secure cookies. My two questions are:
Is there a better way to implement it than what I have above?
If I do go the above route, is there a way to conditionally send secure
cooking and implement HSTS for only those domains?
If there is no easy way to enable HSTS or secure cookies, and having those is worth the hassle, I can always split my app and host it on two different servers, with one instance containing all https domains and the other containing only http domains.
Thanks for your thoughts
Using Rails to do that is actually not a bad idea, but using NGINX for that would be even better as comments have pointed out. If you really want to pursuit the Rails solution you could do something like that in the very same def:
force_ssl if: :ssl_required?
def ssl_required?
if [secure_domain1, domain2, domain3].include? request.domain
#HSTS for Rails <=4
response.headers['Strict-Transport-Security'] = 'max-age=315360000; includeSubdomains; preload'
#HSTS for Rails >=5
response.set_header('Strict-Transport-Security', 'max-age=315360000; includeSubdomains; preload')
cookies[:secure] = true
true
else
false
end
end
You could always tune your HSTS header to the desired max-age or use a more idiomatic approach putting #{365.days.to_i} instead of the simple string header.

Changing admin route from 'admin/' to 'customadmin/'

All. I've googled this quite a bit and I haven't found anything that'll work.
I want to change the admin route to something other than "admin". In this case, we'll just use "customadmin" as the desired name.
I've been toying with two solutions. One uses rewrites with Nginx, the other uses the routes in the spree application. Maybe, I should use both.
I'm doing this for the SSL layer, so in my 443 server section, (for the Nginx Solution) I tried the following:
locaton /customadmin/
{
rewrite ^/customadmin/(.*)$ /admin/$1 permanent;
}
Cool. That maps customadmin to all the admin routes. But What I don't want is for any random user to even get to the /admin section. Also, If I'm mapping 'customadmin' to 'admin' it'll probably create some sort of error.
Ideas:
*Should I use the 'internal' property? That is, in the admin location block, only go somewhere if the request is internal (or from a rewrite)
*Is there a way to namespace /admin in my spree application so that it uses 'customadmin' instead? I know it can be done (somewhat) by doing the following:
Spree::Core::Engine.routes.append do
scope :customadmin do
namespace :admin do
resources :my_model
resources :my_other_model
end
end
end
Environment Specs:
Spree: 2.1.2
Ruby: ruby-2.0.0-p353
rails: 4

Redirect http://domain.com/subpage to http://www.domain.com/subpage in Rails

I assume this is not really a difficult issue but I am using so many different solutions and don't really know what it best.
I am using Ruby on Rails and have my apps on Heroku and I want to 301 redirect everything on my naked domain (#) to my www-domain. E.g.
http://domain.com --> http://www.domain.com
http://domain.com/subpage --> http://www.domain.com/subpage
Right now I am handling this with my DNS by first deleting both # and www entries. Then I set a redirect of the entire website to http://www.domain.com (which re-creates the DNS entries for both # and www). Lastly I change my www DNS-entry to CNAME and the name of the heroku-app (http://myapp.herokuapp.com).
This seems to be forwarding http://domain.com/subpage to http://www.domain.com (without the subpage).
What I am looking for now is the proper/recommended way to handle this in a simple/elegant way.
DNS?
Routes?
.htaccess? (if so, how do I alter .htaccess in RubyOnRails)
Thanks in advance!
These questions may be related to your question, maybe have a look at those. It seems (from what I have read), that heroku does not allow access to anything like .htaccess or anything, so it seems like Rack middleware would be the best option.
Where is your domain registered? If with GoDaddy, they offer a service that will handle the 301 redirect for you, but it requires that you sign up for one of their hosting plans. The lowest cost will do (~$5 per month). Not free, but painless and requires no coding, etc.
(notice : did not try anything of this, but it should work)
One might argue that the nice way is to use the DNS or your webservers capabilities to do that. However, it is possible to do it with rails if you need it.
All in all, it has the advantage that you will easily keep any params / path in the process, as you told you wanted. Plus, the logic is inside the app and won't be lost if you need to scale up / change domain name. On the bad side, your full stack will be hit anytime someone uses the bare domain name.
you can try a before_filter in your application controller :
before_filter :redirect_to_www
def redirect_to_www
redirect_to subdomain: 'www' unless request.subdomain == 'www'
end
if you want to avoid a "magic redirection" and make it clear for everyone, on rails 3 you can do this directly in the routes:
constraints ->(request){ request.subdomain != 'www' } do
match '(*all)' => redirect( subdomain: 'www' )
end
you should also add the subdomain to your default url options (application controller):
def default_url_options( option = {} )
{subdomain: 'www'}
end
In the end what I had to do was to point the #-DNS to my normal web host (since # demands an IP and Heroku will only accept CNAME) and the www-DNS to Heroku.
At my normal webhost I put a .htaccess that redirected all traffic there to my www-domain like this:
RewriteEngine On
### re-direct to www
RewriteCond %{HTTP_HOST} ^domain.com
RewriteRule (.*) http://www.domain.com/$1 [R=301,L]

Correct DNS setup for custom domain with subdomains and CNAMES pointing at them

I have a multi-tenant Rails app, hosted with Heroku at http://myapp.herokuapp.com. The tenants/accounts are separated via subdomains (and Postgresql schemas). I have then added my own domain as custom domain to the Heroku app. Since Heroku really advice against using A records because of "uptime implications" (https://devcenter.heroku.com/articles/avoiding-naked-domains-dns-arecords) I first tried to put a CNAME record from my custom domain to myapp.herokuapp.com. That worked fine also with subdomains like http://clientaccount.mydomain.com.
The problem began when my client wanted her own domain pointing to their account so that http://www.clientdomain.com showed http://clientaccount.mydomain.com, the most obvious solution seemed to be to create a CNAME record in my clients DNS to point to http://clientaccount.mydomain.com. That did not work, when visiting the address an error message stated that "There is no app configured at that hostname", also host www.clientdomain.comgave:
www.clientdomain.com is an alias for clientaccount.mydomain.com
clientaccount.mydomain.com is an alias for myapp.herokuapp.com
myapp.herokuapp.com is an alias for ar.herokuapp.com
After some VERY confused support from Heroku, they advised my to use A records instead pointed to their three apex IPs. So changed it but it still didn't work. They then told me to add my clients domain as a custom domain in my Heroku settings which I have done without good result.
So my current configuration is as follows:
Myapp at Heroku has following custom domains:
*.mydomain.com
www.clientdomain.com
mydomain.com DNS
*.mydomain.com has three A records pointing at Heroku apex IPs
clientdomain.com DNS
In the DNS for clientdomain.com, clientdomain.com (without www) is redirected to www.clientdomain.com (Not sure how they do it but it seems to work.)
For www.clientdomain.com there's a CNAME record pointing at clientaccount.mydomain.com
Current state
www.mydomain.com resolves correctly.
clientaccount.mydomain.com resolves correctly.
www.clientdomain.com goes to www.mydomain.com (without subdomain)
So the problem is obvously either in the DNS somehow, does the subdomain fall away somewhere since my application obviously doesn't receive it? Or do I have to change some code in Rails in order to handle this?
The subdomain is handled as route constraint, Subdomain class:
class Subdomain
def self.matches?(request)
request.subdomain.present? && Account.find_by_subdomain(request.subdomain)
end
end
Any input is appreciated!
EDIT
I have done everything that is suggested, do I have to change my application controller as well?
application_controller.rb
def handle_subdomain
if #account = Account.find_by_subdomain(request.subdomain)
PgTools.set_search_path #account.id
#current_account = #account
else
#current_account = nil
PgTools.restore_default_search_path
end
end
Your best bet here is to first setup your main domain as follows:
*.mydomain.com as a CNAME to heroku-appname.herokuapp.com
And add *.mydomain.com as a domain name to your app:
$ heroku domains:add *.mydomain.com
Next You'll want to setup an easy way for your users/clients to add their custom domains to your multi-tenant app. This requires two things, adding the domain name to your app and having them setup DNS. Here's the best approach:
add www.myclientdomain.com to your app:
$ heroku domains:add www.myclientdomain.com
Then setup DNS to point into Heroku. You're best off taking a lesson from Heroku's setup and having your clients CNAME to a domain name that you own. This will help avoid lock-in and gives you much more control over where you're directing traffic. so:
CNAME www.myclientdomain.com to proxy.mydomain.com
The CNAME will follow proxy.mydomain.com to heroku-appname.herokuapp.com and then resolve to the Heroku IPs.
Then you should be all set.
Ideally you'll be taking on new customers with custom domains faster than you can add domain names manually so you'll want to automate that step. Using the heroku api and client you can manage your custom domains, programmatically, fron within your app.
You need to do something like:
def handle_subdomain
if #account = Account.find_by_subdomain(request.subdomain) || Account.find_by_domain(request.domain)
PgTools.set_search_path #account.id
#current_account = #account
else
#current_account = nil
PgTools.restore_default_search_path
end
end
Notice Account.find_by_domain(request.domain)—although you might need request.host for the full host, not just the domain component.

multi tenant with custom domain on rails

I'm creating a multi tenant application like shopify and wanna know how can I create custom domain on server that points to the same application instance?
For example:
app1.mysystem.com == www.mystore.com
app2.mystem.com == www.killerstore.com
I need to do that config on CNAME like Google Apps? If so, how can I do that? Is there some good paper showing how this works ?
PS: app1 AND app2 points to the same application!
Thanks
I have a similar setup and am using nginX. What I did for ease of maintenance was accepted all the connections from nginx and did the filtering in my app.
# application_controller.rb
before_filter :current_client
private
def current_client
# I am using MongoDB with Mongoid, so change the syntax of query accordingly
#current_client ||= Client.where(:host => request.host).first
render('/public/404.html', :status => :not_found, :layout => false) unless #current_client
end
You can have your clients have a domain record with there domain/subdomain pointing to you_ip or your_domain_pointing_to_your_ip.com and capture that in a form and save in database. Then alter the query in current_client like:
#current_client ||= Client.or(:host => request.host).or(:alias => request.host).first
I am currently working through something similar and just did the Nginx configuration. This is the way that I did it.
server {
listen 80;
server_name domain1.com domain2.com domain3.com;
rails_env production;
passenger_enabled on;
root /var/www/your_site_folder/current/public;
}
Also make sure to run passenger_pre_start if you are using passenger.
ex: passenger_pre_start http://your_domain.com;
Add one line for each domain that you add into the above config block.
The key here is under server_name. Normally I would use this for a domain using www.domain.com or without the 'www', domain.com. But in this case you can point all domains that you want to hit your app from here and you have your Nginx setup for multi-tenancy.

Resources