# Android Cast — Email agent config & 2FA / mobile auth flow _Date: 2026-06-07 — FR §9 (email) + FR §10 (registration, 2FA, mobile) — **alpha must-have**_ --- --- --- --- ## Table of contents - [Document history](#document-history) - [Executive summary](#executive-summary) - [Current state (repo)](#current-state-repo) - [§9 — Email addresses & agent config](#9-email-addresses-agent-config) - [§9.1 Prerequisites](#91-prerequisites) - [§9.2 Register addresses (free — DNS, not “buy mailboxes”)](#92-register-addresses-free-dns-not-buy-mailboxes) - [§9.3 Forward / duplicate to Gmail (info@ → foxxspambox0@gmail.com)](#93-forward-duplicate-to-gmail-info-foxxspambox0gmailcom) - [§9.4 Recommended prod layout for this project](#94-recommended-prod-layout-for-this-project) - [§9.5 Outbound mail from BE (verification emails)](#95-outbound-mail-from-be-verification-emails) - [§10 — Registration, 2FA, mobile auth](#10-registration-2fa-mobile-auth) - [§10.1 Registration & login stages](#101-registration-login-stages) - [§10.2 Open-source building blocks (recommended)](#102-open-source-building-blocks-recommended) - [§10.3 Users DB migration (privacy-first)](#103-users-db-migration-privacy-first) - [§10.4 On-demand user maintenance (admin + self-service)](#104-on-demand-user-maintenance-admin-self-service) - [§10.5 Operator checklist (track progress)](#105-operator-checklist-track-progress) - [§10.6 Mobile-friendly UX (not BE-only)](#106-mobile-friendly-ux-not-be-only) - [§10.7 Alpha implementation plan](#107-alpha-implementation-plan) - [UI/UX alignment (existing consoles)](#uiux-alignment-existing-consoles) - [Security & compliance notes](#security-compliance-notes) - [Open questions](#open-questions) - [Source linkage](#source-linkage) **Documentation index:** [README.md](README.md) --- ## Document history | Rev | Date | Focus | |-----|------|--------| | **1** | 2026-06-07 | Initial FR capture: email forwarding + registration/2FA for alpha; infra + BE + mobile flows. | **Alpha gate:** agreed with project owners — user registration with verified email + second factor is **must-have for full alpha** (alongside RSSH remote access). LAN-only cast demo may still skip account flows. --- ## Executive summary | Track | Goal | |-------|------| | **§9 Email** | Public-facing addresses `info@`, `admin@`, `root@` on **`apps.f0xx.org`** (and optional aliases on **`f0xx.org`**) that **forward** to personal Gmail; no paid mailbox SKU required. | | **§10 Auth** | Replace “Register (coming soon)” with **email verification + flexible 2FA** (TOTP QR, optional WebAuthn/passkey on Android), captcha on abuse-prone endpoints, **privacy-preserving** audit tables, admin tools for root/slug admins. | | **Mobile** | Same flows usable on phone browsers **and** deep links / Custom Tabs from the Android app where appropriate. | | **Licenses** | Prefer **MIT / BSD / Apache-2.0** PHP libraries; avoid proprietary-only auth SaaS for core path. | --- ## Current state (repo) | Area | Today | |------|--------| | BE login | Username + `password_hash` only — [`Auth.php`](../examples/crash_reporter/backend/src/Auth.php) | | Registration | UI placeholder: “Register (coming soon)” — [`login.php`](../examples/crash_reporter/backend/views/login.php) | | Users table | `username`, `password_hash`, `role` — no email, no 2FA — [`schema.mariadb.sql`](../examples/crash_reporter/backend/sql/schema.mariadb.sql) | | Outbound email | **Not configured** — no SMTP in [`config.example.php`](../examples/crash_reporter/backend/config/config.example.php) | | Mobile app | No account linking to crashes console yet | | Roadmap | “User registration + email verification” — [`examples/crash_reporter/ROADMAP.md`](../examples/crash_reporter/ROADMAP.md) | --- ## §9 — Email addresses & agent config Target addresses (any one canonical pair is fine; document both for flexibility): | Role | Preferred | Alias | |------|-----------|--------| | Public / info | `info@apps.f0xx.org` | `info@f0xx.org` | | Admin / ops | `admin@apps.f0xx.org` | `admin@f0xx.org`, `root@apps.f0xx.org` | **Agent config** (future): BE `config.php` → `mail.from`, `mail.reply_to`, `mail.envelope_from` pointing at these addresses once DNS and forwarding work. ### §9.1 Prerequisites You do **not** need to buy Google Workspace or Microsoft 365 mailboxes. You **do** need: | # | Prerequisite | Why | |---|--------------|-----| | 1 | **DNS control** for `f0xx.org` and `apps.f0xx.org` | MX, SPF, DKIM, DMARC live at the DNS host (registrar or Cloudflare). | | 2 | **Decide canonical mail domain** | Recommend **`apps.f0xx.org`** for product mail; keep `f0xx.org` as forward/alias only if desired. | | 3 | **Inbound path** | MX records → free forwarder **or** self-hosted Postfix alias on FE/BE. | | 4 | **Outbound path (BE)** | SMTP relay for verification/reset mail (see §9.5) — can be Gmail SMTP, SendGrid free tier, or local Postfix with SPF alignment. | | 5 | **TLS on web** | Already have `https://apps.f0xx.org` — verification links use same vhost. | | 6 | **Gmail destination** | e.g. `foxxspambox0@gmail.com` — receives forwarded + BCC copies. | **Deliverability minimum (avoid spam folder):** ```text SPF TXT @apps.f0xx.org "v=spf1 include: ~all" DKIM TXT (provider gives name/value) DMARC TXT _dmarc.apps.f0xx.org "v=DMARC1; p=none; rua=mailto:admin@apps.f0xx.org" ``` Start with `p=none`; tighten after monitoring. ### §9.2 Register addresses (free — DNS, not “buy mailboxes”) **“Register” here = DNS + forwarder rules**, not purchasing seats. #### Option A — **Cloudflare Email Routing** (recommended if DNS is on Cloudflare) 1. Cloudflare dashboard → **Email** → **Email Routing** → enable for `apps.f0xx.org`. 2. Add **destination address** `foxxspambox0@gmail.com` (verify Gmail link once). 3. Create **Custom addresses**: - `info@apps.f0xx.org` → forward to Gmail - `admin@apps.f0xx.org` → forward to Gmail - `root@apps.f0xx.org` → forward to Gmail (or same rule as admin) 4. Cloudflare adds MX automatically; copy **SPF** hint from dashboard. 5. Repeat for **`f0xx.org`** zone if you want `info@f0xx.org` (separate zone rules or redirect-only). **Cost:** $0 on Cloudflare free plan. #### Option B — **ImprovMX** (DNS at any registrar) 1. Sign up at [improvmx.com](https://improvmx.com) (free tier: limited aliases). 2. Add domain `apps.f0xx.org` → set MX to ImprovMX hosts (they show exact records). 3. Create aliases `info`, `admin`, `root` → `foxxspambox0@gmail.com`. 4. Add SPF include for ImprovMX. #### Option C — **Self-hosted alias** (Gentoo FE or Alpine BE) If you already run Postfix on FE: ```text /etc/postfix/virtual: info@apps.f0xx.org foxxspambox0@gmail.com admin@apps.f0xx.org foxxspambox0@gmail.com root@apps.f0xx.org foxxspambox0@gmail.com ``` Then `postmap /etc/postfix/virtual && systemctl reload postfix`. Requires **MX** pointing to FE public IP and port **25** reachable (often blocked on residential; OK on HVM with firewall rule). **Do first:** pick **A or B** unless you explicitly want to operate SMTP on FE. ### §9.3 Forward / duplicate to Gmail (info@ → foxxspambox0@gmail.com) | Method | Inbox copy | Send-as From | IMAP on apps domain | |--------|------------|--------------|---------------------| | **Forward only** (Cloudflare / ImprovMX) | Yes — same subject/body in Gmail | Reply-from Gmail unless configured | N/A — read in Gmail | | **Gmail “Send mail as”** | N/A | Can send as `info@apps.f0xx.org` if SMTP creds exist | Optional | | **BCC archive** | Forward + hidden copy to second Gmail | — | Rarely needed | For FR §9.3 (“same topic and body redirected”): **plain forward** is enough. Gmail shows original `To:` header; you can filter with Gmail rule `to:info@apps.f0xx.org`. **Optional Gmail filter:** ```text To: info@apps.f0xx.org → label: androidcast-info ``` No IMAP/POP3 server on `apps.f0xx.org` is required for this FR. ### §9.4 Recommended prod layout for this project ```text Inbound (public): info@apps.f0xx.org ──forward──► foxxspambox0@gmail.com admin@apps.f0xx.org ──forward──► foxxspambox0@gmail.com root@apps.f0xx.org ──forward──► foxxspambox0@gmail.com (same inbox or +tag) Outbound (BE PHP): From: Android Cast Issues Reply-To: info@apps.f0xx.org SMTP: transactional relay (see §9.5) ``` Document chosen provider in [`INFRA.md`](INFRA.md) § changelog once live. ### §9.5 Outbound mail from BE (verification emails) Add to `config.example.php` (implementation phase): ```php 'mail' => [ 'transport' => 'smtp', // smtp | sendmail 'from' => 'Android Cast Issues ', 'reply_to' => 'info@apps.f0xx.org', 'smtp' => [ 'host' => 'smtp.example.com', 'port' => 587, 'encryption' => 'tls', 'username' => '', 'password' => '', // env / secrets file, not git ], ], ``` **Free/low-cost SMTP options:** | Provider | Notes | |----------|--------| | Gmail SMTP + App Password | OK for low volume; From may need “Send as” alignment | | Brevo / SendGrid free tier | Better for `noreply@apps.f0xx.org` alignment | | Local Postfix on BE | Full control; must publish SPF for BE egress IP | PHP library: **Symfony Mailer** (MIT) — already common in PHP ecosystems. --- ## §10 — Registration, 2FA, mobile auth ### §10.1 Registration & login stages Proposed **alpha** flow (flexible — toggles in config): ```mermaid sequenceDiagram participant U as User (mobile browser) participant BE as apps.f0xx.org PHP participant M as Mail / Gmail participant A as Android app (optional) U->>BE: POST /register (email, username, password) BE->>BE: captcha verify, rate limit BE->>BE: store pending registration (hashed secrets) BE->>M: Send verify link (24h token) M->>U: Email with https://apps.f0xx.org/.../verify?token= U->>BE: GET verify (or Custom Tab from app) BE->>U: Prompt enroll 2FA (TOTP QR and/or WebAuthn) U->>BE: POST /2fa/enroll (TOTP secret or passkey) BE->>U: Account active → login Note over U,BE: Login thereafter U->>BE: username + password BE->>U: 2FA challenge (TOTP code or WebAuthn) U->>BE: verify second factor BE->>U: Session cookie (existing ac_crash_sess) ``` | Stage | Method | Mobile-friendly | |-------|--------|-----------------| | **10.1.2 Email confirmation** | HTTPS link with signed token | Email app → Chrome Custom Tab; large tap target | | **10.1.3 TOTP / QR** | Standard `otpauth://` QR + manual key | Scan with Google Authenticator / Aegis; or type 6 digits | | **10.1.4 WebAuthn / passkey** | Platform authenticator (fingerprint) | **Android Chrome** + future app WebView if needed | | **Captcha** | ALTCHA (MIT, self-hosted) or rate limit only at alpha | Invisible/PoW — no Google reCAPTCHA dependency | | **Recovery** | Backup codes + optional second recovery email | Printable one-time codes; store hashed | **Roles:** new self-registered users default to `viewer` + default company membership (existing RBAC). Elevation to `admin`/`root` remains **admin-only**. ### §10.2 Open-source building blocks (recommended) | Function | Library | License | Notes | |----------|---------|---------|-------| | SMTP | [symfony/mailer](https://github.com/symfony/mailer) | MIT | Transport abstraction | | TOTP | [spomky-labs/otphp](https://github.com/spomky-labs/otphp) | MIT | RFC 6238; QR via endroid/qr-code | | WebAuthn | [web-auth/webauthn-lib](https://github.com/web-auth/webauthn-lib) | MIT | Passkeys; PHP 8.1+ | | Password hash | `password_hash()` | PHP core | Already used | | Captcha | [altcha-org/altcha](https://github.com/altcha-org/altcha) | MIT | Self-hosted PoW widget | | JWT / tokens | Signed random + HMAC in DB | — | Prefer opaque tokens stored hashed | **Avoid as hard dependency for alpha:** proprietary Auth0/Okta-only flows (OK as optional future SSO). **Android app (phase 2 of alpha auth):** | Feature | Approach | |---------|----------| | Open verify link | `CustomTabsIntent` → `https://apps.f0xx.org/.../verify` | | TOTP | Optional in-app entry (Web login sufficient for alpha) | | Passkey | Chrome on device; link account in settings later | | Deep link | `androidcast://auth/verify?token=` optional — not required if HTTPS works | ### §10.3 Users DB migration (privacy-first) New migration **`008_auth_email_2fa.sql`** (sketch — apply on MariaDB prod once implemented): **Extend `users`:** | Column | Type | Notes | |--------|------|-------| | `email_normalized` | VARCHAR(254) NULL UNIQUE | Lowercase email | | `email_verified_at` | TIMESTAMP NULL | NULL until link clicked | | `status` | ENUM | `pending`, `active`, `locked`, `disabled` | | `recovery_email_normalized` | VARCHAR(254) NULL | Optional second address | **New tables (anonymous / hashed where possible):** ```sql -- auth_pending_registrations: delete after verify or TTL -- auth_factors: user_id, type ENUM('totp','webauthn','backup_code'), secret_or_credential_encrypted, label, created_at -- auth_tokens: purpose ENUM('verify_email','reset_password','login_magic'), token_hash, expires_at, used_at -- auth_attempts: ip_hash, username_hash, outcome, created_at (no raw IP in prod if avoidable) -- auth_audit: actor_user_id, action, meta_json, created_at ``` **Privacy rules:** - Store **hashed** opaque tokens (SHA-256), never raw token in DB. - Store TOTP secrets **encrypted at rest** (libsodium `secretbox`, key in `config.php` env). - `auth_attempts.ip_hash` = HMAC(server_pepper, ip) — supports rate limit without retaining IP. - WebAuthn credentials: store `credential_id` + `public_key` only (standard). SQLite dev: mirror schema in `Database::ensureSchema` like existing RBAC migrations. ### §10.4 On-demand user maintenance (admin + self-service) | Action | Who | UI location | |--------|-----|-------------| | Add / change **recovery email** | User | `?view=account_security` (new) | | Re-enroll TOTP / add passkey | User | Same; requires recent password + 2FA | | Revoke passkeys / backup codes | User | Same | | Clear failed login/register attempts | `root`, company `admin` | `?view=rbac` → user row → “Clear auth lockouts” | | Disable user / force password reset | `root`, `platform_admin` | RBAC admin | | View auth audit tail | `root` | `?view=auth_audit` (read-only) | Session cookie path stays **`/app/androidcast_project`** (shared across crashes/build consoles). ### §10.5 Operator checklist (track progress) Use this table during implementation (copy to ticket **#9 / #10**): | # | Task | Owner | Done | |---|------|-------|------| | E1 | Enable Cloudflare Email Routing (or ImprovMX) for `apps.f0xx.org` | Ops | ☐ | | E2 | Forward `info@`, `admin@`, `root@` → Gmail | Ops | ☐ | | E3 | SPF + DKIM + DMARC records live | Ops | ☐ | | E4 | Test inbound: send mail to `info@apps.f0xx.org`, arrives Gmail | Ops | ☐ | | E5 | Configure BE SMTP + send test from staging | Dev | ☐ | | A1 | Migration `008_auth_email_2fa.sql` + `Database::ensureSchema` | Dev | ☐ | | A2 | Register + verify email views (EN/RU, `app.css`) | Dev | ☐ | | A3 | TOTP enroll + login challenge | Dev | ☐ | | A4 | WebAuthn enroll (optional alpha) | Dev | ☐ | | A5 | ALTCHA on register/login | Dev | ☐ | | A6 | Admin: clear lockouts + audit view | Dev | ☐ | | A7 | Mobile soak: register on phone Chrome, verify, login | QA | ☐ | | A8 | Document secrets in INFRA + config.example.php | Dev | ☐ | ### §10.6 Mobile-friendly UX (not BE-only) | Requirement | Implementation | |-------------|----------------| | Viewport | Already `width=device-width` in [`layout.php`](../examples/crash_reporter/backend/views/layout.php) | | Touch targets | Buttons ≥ 44px; reuse `.login-card`, `.btn` from [`app.css`](../examples/crash_reporter/backend/public/assets/css/app.css) | | i18n | Extend [`en.json` / `ru.json`](../examples/crash_reporter/backend/public/assets/i18n/) | | Email links | Single primary button “Confirm email” — no raw URL only | | 2FA QR | Responsive `` or SVG; “Can’t scan?” reveals manual secret | | Errors | Same `.alert` pattern as login | | Android app | Settings → “Project account” → opens Custom Tab to account/security (post-alpha polish OK) | ### §10.7 Alpha implementation plan | Phase | Scope | Estimate | |-------|--------|----------| | **P0 — Email infra** | §9 DNS + forward + outbound SMTP test | 0.5–1 day ops | | **P1 — Register + verify** | Pending table, mailer, verify link, captcha | 2–3 days | | **P2 — TOTP login** | Enroll QR + challenge on login | 1–2 days | | **P3 — Admin tools** | Recovery email, clear lockouts, audit | 1 day | | **P4 — WebAuthn** | Passkey enroll/login (Chrome/Android) | 1–2 days optional for alpha | | **P5 — Mobile app link** | Custom Tab entry points | 0.5 day | **Alpha sign-off (auth):** new user can register on phone, verify email, enroll TOTP, log in with 2FA; admin can clear lockout; inbound `info@` mail reaches Gmail. Update [`ALPHA.md`](ALPHA.md) § G (auth) and [`ROADMAP.md`](ROADMAP.md) alpha backend checklist when P1 starts. --- ## UI/UX alignment (existing consoles) Do **not** introduce a new design system. Reuse: | Asset | Path | |-------|------| | Styles | `public/assets/css/app.css` — `.login-page`, `.login-card`, `.alert`, `.muted`, `.locale-picker` | | Layout shell | `views/layout.php` — nav, hub card styling | | i18n | `public/assets/js/i18n.js` + JSON catalogs | | Analytics hook | `AnalyticsHead::render()` on auth pages (optional) | New views (planned): - `views/register.php` - `views/verify_email.php` - `views/account_security.php` - `views/two_factor_challenge.php` Hub landing contact form (`landing-pages.inc.php`) should eventually use **`info@apps.f0xx.org`** as `mailto:` — align when email live. --- ## Security & compliance notes - Enforce **rate limits** on register, login, verify resend (e.g. 5/min/IP hash). - Lock account after N failed 2FA attempts; unlock via email or admin. - Do **not** log passwords or TOTP codes. - Backup codes: generate 10 one-time codes, display once, store bcrypt hashes. - GDPR-style minimization: optional `auth_attempts` retention job (purge > 90 days). --- ## Open questions | # | Question | Default proposal | |---|----------|------------------| | Q1 | Require WebAuthn for alpha or TOTP-only? | **TOTP required**; WebAuthn optional P4 | | Q2 | Allow registration on prod immediately or invite-only? | **Open register** with captcha; `viewer` role | | Q3 | Single Gmail inbox for all aliases? | Yes for now (`foxxspambox0@gmail.com`) | | Q4 | Separate `noreply@` for outbound? | Yes — no forward needed; SPF only | --- ## Source linkage | Topic | Doc / code | |-------|------------| | Infra / DNS | [INFRA.md](INFRA.md) | | Crash console | [CRASH_REPORTER.md](CRASH_REPORTER.md), [backend README](../examples/crash_reporter/backend/README.md) | | RBAC | [backend README § RBAC](../examples/crash_reporter/backend/README.md#rbac-phase-1--admin-ui) | | Alpha gates | [ALPHA.md](ALPHA.md), [ROADMAP.md](ROADMAP.md) | | PDF build | [`scripts/build-all-docs-pdf.sh`](../scripts/build-all-docs-pdf.sh) |