From 24dd7a2f035b19c48ee8bbe431b84c2dface3113 Mon Sep 17 00:00:00 2001
From: Brock Wilcox
Date: Sun, 13 Sep 2026 15:43:03 -0400
Subject: [PATCH] Make generated URLs domain-agnostic
Human Essentials is adding humanessentialsapp.com and humanessentialsapp.org
alongside humanessentials.app, because some networks block the `.app` TLD.
Links rendered during a request already follow the host the visitor arrived on
(the request host takes precedence over routes.default_url_options), but a
number of hard-coded absolute URLs sent people back to humanessentials.app
regardless, which defeats the point for anyone on a blocking network.
- Replace hard-coded in-app links with route helpers, so they follow the
current domain: the partner sign-in link on the account request form, and the
ICS event URL in CalendarService (which also pointed at a stale
/diaper_bank/... path that no longer exists).
- Replace hard-coded links in mailer bodies with root_url, and fix the password
reset logo to resolve against the new action_mailer.asset_host rather than an
absolute humanessentials.app URL that breaks on blocking networks.
- Drive the production/staging host from APP_HOST (defaults unchanged), and
split the SMTP HELO domain out to SMTP_DOMAIN since it tracks where mail is
sent from rather than which domain the site is browsed on.
- Add SiteUrlsHelper + config.x.site_urls for the few places that deliberately
link to a *different* deployment (the staging banner pointing at the live
site, the demo credentials in the account request email), configurable via
PRODUCTION_URL and DEMO_URL.
Note that email links still use a single configured host, since a mailer has no
request to derive one from.
Co-Authored-By: Claude Opus 5 (1M context)
Claude-Session: https://claude.ai/code/session_01Ty2xAxuiBVRpCLoHaR62EU
---
.env.example | 12 +++++++++
app/controllers/distributions_controller.rb | 3 ++-
app/helpers/site_urls_helper.rb | 27 +++++++++++++++++++
app/mailers/application_mailer.rb | 1 +
app/services/calendar_service.rb | 6 +++--
.../confirmation.html.erb | 4 +--
.../confirmation.text.erb | 4 +--
app/views/account_requests/new.html.erb | 4 +--
.../partner_mailer.html.erb | 2 +-
app/views/layouts/_devise_shared.html.erb | 4 +--
.../notify_deadline.html.erb | 2 +-
app/views/user_mailer/role_added.html.erb | 2 +-
app/views/user_mailer/role_added.text.erb | 2 +-
.../mailer/invitation_instructions.html.erb | 2 +-
.../reset_password_instructions.html.erb | 2 +-
app/views/users/passwords/new.html.erb | 2 +-
config/environments/development.rb | 1 +
config/environments/production.rb | 16 ++++++++---
config/environments/staging.rb | 6 ++++-
config/environments/test.rb | 1 +
config/initializers/site_urls.rb | 11 ++++++++
spec/helpers/site_urls_helper_spec.rb | 21 +++++++++++++++
spec/mailers/account_request_mailer_spec.rb | 2 +-
spec/requests/distributions_requests_spec.rb | 3 ++-
spec/services/calendar_service_spec.rb | 22 ++++++++++-----
spec/system/account_request_system_spec.rb | 2 +-
26 files changed, 132 insertions(+), 32 deletions(-)
create mode 100644 app/helpers/site_urls_helper.rb
create mode 100644 config/initializers/site_urls.rb
create mode 100644 spec/helpers/site_urls_helper_spec.rb
diff --git a/.env.example b/.env.example
index 5df0115d4d..fee286d6cc 100644
--- a/.env.example
+++ b/.env.example
@@ -10,3 +10,15 @@ RECAPTCHA_PRIVATE_KEY=6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe
# [OPTIONAL] - Used to fetch copies of production DB
AZURE_STORAGE_ACCOUNT_NAME=
AZURE_STORAGE_ACCESS_KEY=
+
+# [OPTIONAL] - Canonical host for this deployment. Only affects links generated
+# outside of a request (mailers, jobs, rake tasks); in-request links already
+# follow whichever domain the visitor arrived on.
+APP_HOST=
+# [OPTIONAL] - HELO domain used for the SMTP conversation (tied to where mail is
+# sent from / SPF / DKIM, not to which domain the site is browsed on)
+SMTP_DOMAIN=
+# [OPTIONAL] - Used only where we deliberately link to a *different* deployment
+# (the staging banner pointing at the live site, demo credentials in email)
+PRODUCTION_URL=
+DEMO_URL=
diff --git a/app/controllers/distributions_controller.rb b/app/controllers/distributions_controller.rb
index 6328d0b80b..1724d80b2f 100644
--- a/app/controllers/distributions_controller.rb
+++ b/app/controllers/distributions_controller.rb
@@ -259,7 +259,8 @@ def calendar
crypt = ActiveSupport::MessageEncryptor.new(Rails.application.secret_key_base[0..31])
organization_id = crypt.decrypt_and_verify(CGI.unescape(params[:hash]))
- render body: CalendarService.calendar(organization_id), content_type: Mime::Type.lookup("text/calendar")
+ calendar = CalendarService.calendar(organization_id, host: request.host_with_port, protocol: request.protocol)
+ render body: calendar, content_type: Mime::Type.lookup("text/calendar")
rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
head :unauthorized
end
diff --git a/app/helpers/site_urls_helper.rb b/app/helpers/site_urls_helper.rb
new file mode 100644
index 0000000000..85abfe102f
--- /dev/null
+++ b/app/helpers/site_urls_helper.rb
@@ -0,0 +1,27 @@
+# Builds links to a *different* Human Essentials deployment than the one
+# currently serving the request. Anything pointing at the current deployment
+# should use ordinary route helpers instead, so it follows whichever domain the
+# visitor arrived on. See config/initializers/site_urls.rb.
+module SiteUrlsHelper
+ def production_url(path = "/")
+ site_url(site_urls.production, path)
+ end
+
+ def demo_url(path = "/")
+ site_url(site_urls.demo, path)
+ end
+
+ def production_host
+ URI.parse(site_urls.production).host
+ end
+
+ private
+
+ def site_urls
+ Rails.application.config.x.site_urls
+ end
+
+ def site_url(base, path)
+ URI.join(base, path).to_s
+ end
+end
diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
index 56036e02bd..1c80a20ef1 100644
--- a/app/mailers/application_mailer.rb
+++ b/app/mailers/application_mailer.rb
@@ -2,4 +2,5 @@
class ApplicationMailer < ActionMailer::Base
default from: "Please do not reply to this email as this mail box is not monitored — Human Essentials "
layout "mailer"
+ helper SiteUrlsHelper
end
diff --git a/app/services/calendar_service.rb b/app/services/calendar_service.rb
index 1b321da4be..40191a7d62 100644
--- a/app/services/calendar_service.rb
+++ b/app/services/calendar_service.rb
@@ -4,8 +4,10 @@
module CalendarService
# Prints out a calendar in ICS format for use e.g. in adding to Google Calendar.
# @param organization_id [Integer]
+ # @param url_options [Hash] host/protocol to build event links with, so the
+ # calendar points back at the domain the subscriber is using.
# @return [String]
- def self.calendar(organization_id)
+ def self.calendar(organization_id, url_options = {})
distributions = Organization.find(organization_id)
.distributions
.includes(:storage_location, :partner)
@@ -27,7 +29,7 @@ def self.calendar(organization_id)
e.dtend = Icalendar::Values::DateTime.new(dist.issued_at + 15.minutes, "tzid" => tz_id)
e.summary = "Pickup from #{dist.partner.name}"
e.location = dist.storage_location.address
- e.url = "https://humanessentials.app/diaper_bank/distributions/schedule"
+ e.url = Rails.application.routes.url_helpers.schedule_distributions_url(**url_options)
end
end
cal.publish
diff --git a/app/views/account_request_mailer/confirmation.html.erb b/app/views/account_request_mailer/confirmation.html.erb
index 0bf999ff1f..1c57e21ada 100644
--- a/app/views/account_request_mailer/confirmation.html.erb
+++ b/app/views/account_request_mailer/confirmation.html.erb
@@ -23,7 +23,7 @@
- Human Essentials
+ <%= link_to 'Human Essentials', demo_url('/users/sign_in') %>
Bank user login:
@@ -43,7 +43,7 @@
A couple things to know about the sandbox servers before you start using them:
- The development team uses the servers for testing our new features and upgrades before putting them on the live site to ensure that no bugs get pushed through to the live site, so if something looks different than the (real) humanessentials.app site that is probably why! Please don’t enter any sensitive information into the demo servers, several users have access to the demo servers and it will be visible to all users.
+ The development team uses the servers for testing our new features and upgrades before putting them on the live site to ensure that no bugs get pushed through to the live site, so if something looks different than the (real) <%= production_host %> site that is probably why! Please don’t enter any sensitive information into the demo servers, several users have access to the demo servers and it will be visible to all users.
diff --git a/app/views/account_request_mailer/confirmation.text.erb b/app/views/account_request_mailer/confirmation.text.erb
index ea8d6d0751..b371ccfb89 100644
--- a/app/views/account_request_mailer/confirmation.text.erb
+++ b/app/views/account_request_mailer/confirmation.text.erb
@@ -13,7 +13,7 @@ If you'd like to experience the app before continuing, please log in to the sand
***The details below allow you to log into a demo site. All data on the demo site is reset every day at 6 AM EST.***
Human Essentials:
-Link: https://staging.humanessentials.app/users/sign_in
+Link: <%= demo_url('/users/sign_in') %>
Bank user login:
Username: org_admin1@example.com
@@ -24,7 +24,7 @@ Username: verified@example.com
Password: password!
A couple things to know about the sandbox servers before you start using them:
- The development team uses the servers for testing our new features and upgrades before putting them on the live site to ensure that no bugs get pushed through to the live site, so if something looks different than the (real) humanessentials.app site that is probably why! Please don’t enter any sensitive information into the demo servers, several users have access to the demo servers and it will be visible to all users.
+ The development team uses the servers for testing our new features and upgrades before putting them on the live site to ensure that no bugs get pushed through to the live site, so if something looks different than the (real) <%= production_host %> site that is probably why! Please don’t enter any sensitive information into the demo servers, several users have access to the demo servers and it will be visible to all users.
Finally, we made a series of getting started videos with detailed directions on setting up your essentials bank in Human Essentials. These are not being kept up-to-date,
but might still be useful if you prefer video to the User Guide.
diff --git a/app/views/account_requests/new.html.erb b/app/views/account_requests/new.html.erb
index fe73a7524b..b854583877 100644
--- a/app/views/account_requests/new.html.erb
+++ b/app/views/account_requests/new.html.erb
@@ -26,7 +26,7 @@
-
Are you a current partner to diaper and/or period supply banks? If so please go here to login.
+
Are you a current partner to diaper and/or period supply banks? If so please go <%= link_to 'here', new_user_session_path %> to login.
If you are wishing to receive diapers/period supplies and partner to a local diaper/period supply bank please contact your local bank. They are the only ones able create your account.
If you are looking for an essentials bank that distributes diapers, please refer to the NDBN member directory. If you are, instead, looking for an essentials bank that deals in period supplies, please refer to the Alliance for Period Supplies directory.
@@ -90,7 +90,7 @@
Hey there, it looks like you're trying to request an account, but you're not currently on the live version of Human Essentials
-
To request an account, <%= link_to 'click here', 'https://humanessentials.app/account_requests/new' %> to head on over to the Human Essentials website
+
To request an account, <%= link_to 'click here', production_url('/account_requests/new') %> to head on over to the Human Essentials website
diff --git a/app/views/distribution_mailer/partner_mailer.html.erb b/app/views/distribution_mailer/partner_mailer.html.erb
index b2d3f77862..7e20ba032c 100644
--- a/app/views/distribution_mailer/partner_mailer.html.erb
+++ b/app/views/distribution_mailer/partner_mailer.html.erb
@@ -355,7 +355,7 @@
|
- This email was sent with ❤ by Human Essentials.️
+ This email was sent with ❤ by <%= link_to "Human Essentials", root_url %>.️
|
diff --git a/app/views/layouts/_devise_shared.html.erb b/app/views/layouts/_devise_shared.html.erb
index 06675012e2..049aa82891 100644
--- a/app/views/layouts/_devise_shared.html.erb
+++ b/app/views/layouts/_devise_shared.html.erb
@@ -81,10 +81,10 @@
This site is for TEST purposes only!
- You're visiting staging.humanessentials.app, a demo/test site for the full site at
humanessentials.app.
+ You're visiting <%= request.host %>, a demo/test site for the full site at <%= link_to production_host, production_url %>.
It is not safe to upload, enter or save any sensitive data here.
- If you meant to login to your live account, go to
humanessentials.app
+ If you meant to login to your live account, go to <%= link_to production_host, production_url('/users/sign_in') %>