Documents approach to automatically populate user timezone from cf-timezone header during registration, with UTC fallback when header is not present.
5.4 KiB
Cloudflare Timezone Detection on Signup
Date: 2026-02-01 Status: Approved Author: Claude Code
Overview
Automatically populate the user's timezone during registration by reading Cloudflare's cf-timezone header. This provides a better default than "UTC" for users behind Cloudflare's network.
Problem Statement
Currently, all new users get timezone: "UTC" as their default, even though Cloudflare provides accurate timezone detection via the cf-timezone header. Users must manually update their timezone in settings after registration.
Proposed Solution
Capture the cf-timezone header during the registration flow and use it to set the user's timezone field automatically.
Architecture
Component Design
Browser Request → Plug (CaptureTimezone) → Session[:detected_timezone]
↓
LiveView mount reads session
↓
Form submit merges timezone into params
↓
Accounts.register_user_with_organization
↓
User created with correct timezone
Files to Modify
-
NEW:
lib/towerops_web/plugs/capture_timezone.ex- Plug that extracts
cf-timezoneheader - Stores in session as
:detected_timezone - Fallback: "UTC" if header missing
- Plug that extracts
-
MODIFY:
lib/towerops_web/endpoint.ex- Add
CaptureTimezoneplug to browser pipeline - Runs on all browser requests (minimal overhead)
- Add
-
MODIFY:
lib/towerops_web/live/user_registration_live.ex- Read
:detected_timezonefrom session inmount/3 - Store in socket assigns
- Merge into user params in
handle_event("save")
- Read
-
MODIFY:
lib/towerops/accounts/user.ex- Add
:timezonetoregistration_changeset/3cast fields - No validation needed (internal value, already validated by Cloudflare)
- Add
Implementation Details
Plug Implementation
defmodule ToweropsWeb.Plugs.CaptureTimezone do
@moduledoc """
Captures the cf-timezone header from Cloudflare and stores it in session.
Used to pre-populate timezone during user registration.
"""
import Plug.Conn
def init(opts), do: opts
def call(conn, _opts) do
timezone =
case get_req_header(conn, "cf-timezone") do
[tz] when is_binary(tz) -> tz
_ -> "UTC"
end
put_session(conn, :detected_timezone, timezone)
end
end
LiveView Changes
In mount/3:
detected_timezone = get_session(socket, :detected_timezone) || "UTC"
assign(socket, :detected_timezone, detected_timezone)
In handle_event("save"):
user_params = Map.put(user_params, "timezone", socket.assigns.detected_timezone)
Schema Changes
def registration_changeset(user, attrs, opts \\ []) do
user
|> cast(attrs, [:email, :password, :timezone, :privacy_policy_consent, :terms_of_service_consent])
# ... rest unchanged
end
Behavior
Header Present
- Cloudflare sends:
cf-timezone: America/Chicago - User registered with:
timezone: "America/Chicago"
Header Missing (Development/Non-Cloudflare)
- No header detected
- User registered with:
timezone: "UTC"(fallback)
Cloudflare Timezone Format
- Uses IANA timezone database format (e.g., "America/Chicago", "Europe/London")
- Compatible with Elixir's
TimexandCalendarlibraries - No conversion needed
Testing Strategy
- Unit Tests: Plug extracts header correctly, handles missing header
- Integration Tests: LiveView receives timezone, registration includes timezone
- Manual Testing:
- Development (no header): Should default to "UTC"
- Production (with header): Should use Cloudflare's detected timezone
Edge Cases
- Multiple timezones: Cloudflare sends single value, no issue
- Invalid timezone: Trust Cloudflare's validation (they use IANA database)
- User changes timezone later: Users can still update in settings
- Invitation flow: Works identically for both invitation and normal registration
Performance Impact
- Plug overhead: 1 header lookup + 1 session write per request (~negligible)
- Session size: +1 small string value (~15-30 bytes)
- LiveView mount: 1 session read (already happening)
Security Considerations
- Header is read-only from Cloudflare (users cannot spoof it easily)
- Timezone is non-sensitive data (no privacy concerns)
- No validation needed (trust Cloudflare's IANA-compliant values)
Rollout Plan
- Deploy plug and schema changes
- Verify in production logs that
cf-timezoneheader is present - Monitor new user registrations for correct timezone population
- No user communication needed (transparent improvement)
Future Enhancements
- Display detected timezone in registration form (optional)
- Allow users to override detected timezone before submitting (optional)
- Use timezone for email scheduling and notification preferences
Alternatives Considered
- JavaScript-based detection: Less reliable, requires client-side code
- GeoIP-based timezone: Less accurate, requires additional lookups
- Ask user during signup: Additional friction, worse UX
References
- Cloudflare cf-timezone header documentation
- IANA Timezone Database
- Phoenix LiveView session handling