# Sign-in and security

P-Entrar's passkey-first sign-in, Apple and Google setup, P-Seguranca, new sign-in alerts, the app switcher cover and the household's 2FA requirement for sharing.

## What it does [#what-it-does]

Privacy spec §3 ([spec](https://github.com/CarlosZiegler/autofolio/blob/main/docs/specs/privacy-and-people.md)), no flag:

- **P-Entrar** (`/sign-in`): Continue with passkey (the white pill), Continue with Apple and Continue with Google when the server has their credentials, then _Use email and password_, which opens the email form (`/sign-in-email`). On iOS Google shows only beside Apple (App Store guideline 4.8); Apple shows only on iOS. A device without passkeys makes the email link the white pill. Sign-up keeps Apple, Google and email.
- **P-Seguranca** (`/security`, from P-Central and Account): passkeys by name, two-step verification, _Apple and Google_ (the linked accounts, `/linked-accounts`), signed-in devices with the last new device or country, the new sign-in alert switch, this device's Face ID lock (iOS) and _Hide amounts in the app switcher_, and for the household's owners and admins the 2FA requirement for sharing.

## New sign-in alerts [#new-sign-in-alerts]

Better Auth's `session.create` hook records each User's device (`deviceLabel`: browser and system, e.g. "Safari · macOS", or "iOS"/"Android" for the app) and country (`cf-ipcountry`, on Workers only) in `sign_in_device`, one row per pair. A new pair after the first sends the alert email in the account's language (`@autofolio/email/sign-in-alert`): device, country, time, and where to sign that device out. The first sign-in never alerts, and `accountPreference.signInAlerts` (on by default) turns the email off. No IP address or city is stored or sent.

## The 2FA requirement [#the-2fa-requirement]

`organization.require_second_factor`, off by default, is switched by owners and admins with `security.setRequirement` (audited as `org.second_factor_changed`, metadata `secondFactor`). While it is on, `requireSecondFactorToShare` (`platform/rpc/require-second-factor.ts`) answers `SECOND_FACTOR_REQUIRED` to a User with neither two-step verification nor a passkey on `passport.create`, `showcase.create`, `inviteLink.create` and `vehicleMember.set` when it grants a car (taking access back is never blocked). The app's error toast for that code opens Security; P-Seguranca shows a notice while the User lacks both. A solo User is never asked unless they turn it on.

## App switcher cover [#app-switcher-cover]

A device setting like the Face ID lock (`app-lock.switcher.ts`, SecureStore): while it is on, `SwitcherCover` covers the app whenever it is not active, so the iOS switcher's snapshot shows no amounts. Off by default; not offered on web.

## Apple and Google setup [#apple-and-google-setup]

The buttons stay hidden until the server has credentials: `appConfig.get` returns `socialProviders`, the providers whose id and secret are both set (`packages/auth/src/social-providers.ts`). Per environment (local, preview, production), set these as Worker secrets or in `apps/web/.env`:

| Variable                      | Value                                                                                              |
| ----------------------------- | -------------------------------------------------------------------------------------------------- |
| `GOOGLE_CLIENT_ID`            | OAuth client of type _Web application_ (Google Cloud console → Credentials)                        |
| `GOOGLE_CLIENT_SECRET`        | That client's secret                                                                               |
| `APPLE_CLIENT_ID`             | The Services ID (e.g. `io.zietec.autofolio.signin`)                                                |
| `APPLE_CLIENT_SECRET`         | The client secret JWT signed with the Sign in with Apple key (`.p8`); it expires within six months |
| `APPLE_APP_BUNDLE_IDENTIFIER` | The iOS bundle id (`io.zietec.autofolio`), so a native Apple ID token is accepted                  |

Redirect URIs to register, one per origin the server answers on (`BETTER_AUTH_URL`):

- Google, _Authorized redirect URIs_: `https://<origin>/api/auth/callback/google` (and `http://localhost:3005/api/auth/callback/google` for local).
- Apple, the Services ID's _Return URLs_: `https://<origin>/api/auth/callback/apple`; its _Domains_ list the origin's host. Apple refuses `localhost`: test it on a preview or a tunnel.

The app needs nothing per provider: Better Auth's Expo plugin opens the provider in an auth session and returns to the app scheme (`NATIVE_APP_SCHEME`, trusted through `nativeTrustedOrigins`). Apple posts back from `https://appleid.apple.com`, which `social-providers.ts` adds to the trusted origins. An Apple or Google sign-in with an email that already has an account links to it when the provider vouches for the email (`account.accountLinking`, no trusted providers); a User links or unlinks one later from Security → Apple and Google, and can never unlink the last way to sign in.

## Procedures [#procedures]

| Procedure                 | Who                     | What                                                                                                                                                     |
| ------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `security.overview`       | Member                  | Signed-in devices, the last new device (country named in `locale`), the requirement, whether the User holds a passkey, whether they manage the household |
| `security.setRequirement` | Owner or admin, audited | Turns the household's 2FA requirement on or off                                                                                                          |

## Key files [#key-files]

- `packages/api/src/features/security/` and `packages/api/src/platform/rpc/require-second-factor.ts`
- `packages/auth/src/sign-in-device.ts`, `packages/email/src/sign-in-alert.ts`, `packages/db/src/schema/security.ts`
- `apps/mobile/src/features/auth/auth.entry.screen.tsx`, `apps/mobile/src/features/settings/settings.security.screen.tsx`, `apps/mobile/src/features/settings/settings.linked-accounts.screen.tsx`, `apps/mobile/src/features/app-lock/app-lock.switcher-cover.tsx`

