Add design for Cloudflare timezone detection on signup
Documents approach to automatically populate user timezone from cf-timezone header during registration, with UTC fallback when header is not present.
This commit is contained in:
parent
ad753cf0c6
commit
45f8bc7459
1 changed files with 169 additions and 0 deletions
169
docs/plans/2026-02-01-cloudflare-timezone-detection-design.md
Normal file
169
docs/plans/2026-02-01-cloudflare-timezone-detection-design.md
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
# 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
|
||||
|
||||
1. **NEW**: `lib/towerops_web/plugs/capture_timezone.ex`
|
||||
- Plug that extracts `cf-timezone` header
|
||||
- Stores in session as `:detected_timezone`
|
||||
- Fallback: "UTC" if header missing
|
||||
|
||||
2. **MODIFY**: `lib/towerops_web/endpoint.ex`
|
||||
- Add `CaptureTimezone` plug to browser pipeline
|
||||
- Runs on all browser requests (minimal overhead)
|
||||
|
||||
3. **MODIFY**: `lib/towerops_web/live/user_registration_live.ex`
|
||||
- Read `:detected_timezone` from session in `mount/3`
|
||||
- Store in socket assigns
|
||||
- Merge into user params in `handle_event("save")`
|
||||
|
||||
4. **MODIFY**: `lib/towerops/accounts/user.ex`
|
||||
- Add `:timezone` to `registration_changeset/3` cast fields
|
||||
- No validation needed (internal value, already validated by Cloudflare)
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Plug Implementation
|
||||
|
||||
```elixir
|
||||
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:**
|
||||
```elixir
|
||||
detected_timezone = get_session(socket, :detected_timezone) || "UTC"
|
||||
assign(socket, :detected_timezone, detected_timezone)
|
||||
```
|
||||
|
||||
**In handle_event("save"):**
|
||||
```elixir
|
||||
user_params = Map.put(user_params, "timezone", socket.assigns.detected_timezone)
|
||||
```
|
||||
|
||||
### Schema Changes
|
||||
|
||||
```elixir
|
||||
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 `Timex` and `Calendar` libraries
|
||||
- No conversion needed
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
1. **Unit Tests**: Plug extracts header correctly, handles missing header
|
||||
2. **Integration Tests**: LiveView receives timezone, registration includes timezone
|
||||
3. **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
|
||||
|
||||
1. Deploy plug and schema changes
|
||||
2. Verify in production logs that `cf-timezone` header is present
|
||||
3. Monitor new user registrations for correct timezone population
|
||||
4. 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
|
||||
|
||||
1. **JavaScript-based detection**: Less reliable, requires client-side code
|
||||
2. **GeoIP-based timezone**: Less accurate, requires additional lookups
|
||||
3. **Ask user during signup**: Additional friction, worse UX
|
||||
|
||||
## References
|
||||
|
||||
- Cloudflare cf-timezone header documentation
|
||||
- IANA Timezone Database
|
||||
- Phoenix LiveView session handling
|
||||
Loading…
Add table
Reference in a new issue