# 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
