# CTM Phone Access Setup

This is the secure transport setup for using CTM from a phone before the mobile
PWA and device-claim flow are enabled.

## One-Click Tailscale Path

Open CTM setup and go to `Setup -> Access`. If Tailscale is installed and signed
in, CTM detects the Mac's MagicDNS name and Tailscale address, shows the phone
URL, and offers **Set Up**. That button:

- runs `tailscale cert --cert-file ... --key-file ... <mac>.ts.net` with fixed
  arguments, not a shell string;
- writes `CTM_HOST`, `CTM_TLS_CERT`, `CTM_TLS_KEY`, and `CTM_ALLOWED_ORIGINS`;
- restarts CTM so the new HTTPS bind takes effect.

After restart, use the displayed phone URL:

```text
https://<mac>.<tailnet>.ts.net:3456/m/
```

The manual steps below remain as a fallback when Tailscale CLI detection is not
available.

## Manual Tailscale Binding

1. Install Tailscale on the Mac running CTM and on the phone.
2. Confirm the Mac has a Tailscale address:

   ```bash
   tailscale ip -4
   ```

3. Open CTM setup, go to `Setup -> Access`, and choose the `100.x.y.z`
   Tailscale address. Do not use `0.0.0.0`; CTM blocks wildcard and LAN binds.

## HTTPS Certificate

Tailscale can issue a real Let's Encrypt certificate for the Mac's MagicDNS
name:

```bash
tailscale status
tailscale cert ctm-mac.tailNNNN.ts.net
```

Copy the generated certificate and key paths into `Setup -> Access`, set
`Allowed Origins` to the exact CTM origin, for example:

```text
https://ctm-mac.tailNNNN.ts.net:3456
```

Then save and restart CTM. After restart, open:

```text
https://ctm-mac.tailNNNN.ts.net:3456/m/
```

from the phone and verify the browser shows a valid certificate chain.

## Certificate Transparency

Tailscale certificates are public Let's Encrypt certificates, so the hostname is
published to Certificate Transparency logs. Use a non-sensitive MagicDNS machine
name before running `tailscale cert`.

## Expected Security Posture

- CTM binds only to localhost or a specific Tailscale address.
- LAN addresses and wildcard binds are rejected.
- HTTPS is enabled only when both `CTM_TLS_CERT` and `CTM_TLS_KEY` are present.
- Mutating HTTP routes and WebSocket connections reject mismatched browser
  origins.
- Phone passkeys are reused by phone profile and WebAuthn RP ID. See
  [Phone Passkey Identity](phone-passkey-identity.md) for the same-phone
  recovery and duplicate-cleanup contract.

## Cloudflare Tunnel + Access Fallback

Use this only for a public backup URL. Tailscale remains the default path.

Open `Setup -> Access`, expand **Cloudflare Access fallback**, and use
**Cloudflare fallback automation**.

CTM can:

- save the Access hostname, team domain, AUD, allowed owner email, JWKS URL, and
  `CTM_ALLOWED_ORIGINS`;
- create a local `cloudflared` tunnel config file for
  `http://127.0.0.1:3456`;
- if `cloudflared tunnel login` has already completed, run
  `cloudflared tunnel create ...` and `cloudflared tunnel route dns ...`;
- if you provide a Cloudflare API token and account ID, create the Zero Trust
  Access self-hosted application and owner-email allow policy, then fill the AUD.

The API token is used for that request only and is not written to `.env`.

After CTM restarts, run the command shown under **Tunnel run command** or install
that tunnel as a service. Then open:

```text
https://ctm.example.com/m/
```

Manual fallback:

1. Create a Cloudflare Tunnel to the Mac and route a hostname such as
   `ctm.example.com` to `http://127.0.0.1:3456` or the local CTM bind.
2. In Cloudflare Zero Trust, create an Access application for that hostname.
   Restrict it to the owner email and require strong authentication.
3. Copy the Access application AUD tag and team domain into
   `Setup -> Access`, or set these environment values:

   ```text
   CTM_CF_ACCESS_ENABLED=1
   CTM_CF_ACCESS_HOSTS=ctm.example.com
   CTM_CF_ACCESS_TEAM_DOMAIN=team-name.cloudflareaccess.com
   CTM_CF_ACCESS_AUD=<access-application-aud>
   CTM_CF_ACCESS_EMAILS=owner@example.com
   CTM_CF_ACCESS_JWKS_URL=https://team-name.cloudflareaccess.com/cdn-cgi/access/certs
   CTM_ALLOWED_ORIGINS=https://ctm.example.com
   ```

When enabled, CTM verifies `Cf-Access-Jwt-Assertion` for matching hostnames
before it checks CTM device tokens. The JWT must have the expected issuer,
application audience, allowed email, valid RS256 signature, and unexpired
timestamps. A Cloudflare-origin phone still needs its own passkey enrollment
because WebAuthn credentials are scoped to the public origin.
