diff --git a/README.md b/README.md index 4155bae..5d1ac34 100644 --- a/README.md +++ b/README.md @@ -515,7 +515,8 @@ Production uses `backup-prod.sh` with a systemd timer, seven-day rotation, and m | `E2B_API_KEY` / `DAYTONA_API_KEY` / `DAYTONA_API_URL` / `DAYTONA_TARGET` / `BOX_API_KEY` / `BOX_API_URL` | Computer-provider credentials. | | `COMPOSIO_API_KEY` | Optional plugins / connectors. | | `EXPO_PUBLIC_API_URL` / `SENTRABOT_WEB_URL` | Mobile production origin and Electron web-URL override. | -| `SMTP_URL` / `VAPID_*` | Optional email and push. | +| `SMTP_URL` / `SMTP_FROM` | Optional SMTP for verification and password-reset email; omit and those flows stay unavailable while the API boots. | +| `VAPID_*` | Optional web push. | | `OTEL_EXPORTER_OTLP_ENDPOINT` / `LOG_LEVEL` | Observability. | --- diff --git a/docs/README.md b/docs/README.md index d26be44..a21ac4b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,7 +18,7 @@ Public product copy lives in `apps/docs` (Mintlify) and `apps/site`. Implementat | Document | Role | | --- | --- | -| [architecture.md](architecture.md) | Runtime topology and decision log. Single source of truth for *how the system is wired today*. | +| [architecture.md](architecture.md) | Runtime topology, auth/sessions, and decision log. Single source of truth for *how the system is wired today*. | | [requirements/](requirements/) | ISO/IEC/IEEE 29148 requirements package. Single source of truth for *what the product must do*. | | [self-host.md](self-host.md) | Operator runbook for local, Compose, published images, and production. | | [computer-runtime.md](computer-runtime.md) | Sandbox provider contract and workspace durability. | @@ -43,4 +43,4 @@ Public product copy lives in `apps/docs` (Mintlify) and `apps/site`. Implementat ## Verification date -This map was reconciled against the repository on **2026-09-03**. +This map was reconciled against the repository on **2026-10-07**. diff --git a/docs/architecture.md b/docs/architecture.md index 6c831ca..420e39b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,7 +1,7 @@ # Sentra Bot runtime architecture This document describes what the repository does today, verified against the code on -2026-09-03. It is the single source of truth for the runtime topology; other documents link here +2026-10-07. It is the single source of truth for the runtime topology; other documents link here instead of restating it. Product *requirements* (what the system must do) live in `docs/requirements/`. The assessment behind the 2026-09-02 topology decisions lives in `docs/superpowers/plans/2026-09-02-convergence-directive.md`. @@ -91,6 +91,63 @@ persisted on Windows. None of these is required to run the product. Transcripts, memory, files, audit events, approval rules, and locally managed credentials stay in the self-hosted PostgreSQL and `DATA_DIR`. +## Authentication and sessions + +Identity is **Better Auth** (`packages/auth/src/index.ts`), mounted at `/api/auth/*` from +`apps/api/src/app.ts`. Sessions live in PostgreSQL through the Prisma adapter. No hosted auth +vendor is required. + +| Surface | Transport | Notes | +|---|---|---| +| Web (Vite) | Session cookie | Proxied to the API; `organizationClient` only on the client | +| Mobile (Expo) | `Authorization: Bearer` | Bearer value is the raw session token; the API rewrites it to a synthetic `better-auth.session_token` cookie before session resolution (`sessionHeaders` in `app.ts`) | +| Desktop (Electron) | Same as web | Loads the web origin | + +**Workspace model.** Each user gets a personal organization at signup (`bootstrapUserWorkspace`). +Multi-user team flows (create org, invite, change roles, leave) are blocked at the HTTP layer +(`blockedAuthPaths`) and in the organization plugin (`allowUserToCreateOrganization: false`). +Workspace isolation for RPC uses `requireMembership` and query scoping on `workspaceId`, not RBAC. + +**Signup policy.** `SIGNUPS_ENABLED` and `SIGNUP_ALLOWLIST` seed the first deployment settings; +after initialization, the deployment owner's Settings values win (`resolveSignupPolicy` in +`packages/db/src/signup-gate.ts`). The sign-up hook rejects closed signups and emails outside +the allowlist. + +**Email and password.** + +- Sign-up sends a verification email when SMTP is configured (`emailVerification.sendOnSignUp`). + `requireEmailVerification` stays **off** so users can sign in before clicking the link; only + `User.emailVerified` flips when the token is consumed. Per-action gates on `emailVerified` + (checkout, WhatsApp pairing, connectors) are planned but **not wired yet** in the oRPC router. +- Password reset uses `sendResetPassword`; completing reset revokes all other sessions + (`revokeSessionsOnPasswordReset`). The web app exposes `/reset-password` for the reset form. +- Auth email copy is Indonesian (`packages/auth/src/auth-emails.ts`). + +**Transactional email.** Vendor-neutral `EmailSender` lives in `packages/adapter-kit/src/email.ts`. +The composition root builds `SmtpEmailSender` from `SMTP_URL` and optional `SMTP_FROM` +(`packages/adapters/src/smtp-email.ts`). When SMTP is absent, the API still boots; verification +and reset attempt delivery through `deliverAuthEmail`, which logs via `onEmailUnavailable` and +does not leak delivery state to the client (Better Auth already uses uniform responses for +unknown addresses). Set `BETTER_AUTH_URL` to the **web origin** so verification and reset links +land in the SPA, not on the API port. + +**Rate limiting.** + +- Better Auth: `rateLimit.enabled` is pinned on in production code (not only when the library + detects `NODE_ENV=production`). Vitest sets `VITEST=true`; operators can set + `AUTH_RATE_LIMIT_ENABLED=false` for debugging. Storage is in-process memory (fine for a single + API instance; multi-instance would need shared storage). Stricter built-in rules apply to + sign-in, sign-up, and password-reset paths per the library defaults. +- Managed AI: `POST /v1/managed-ai/responses` uses a separate fixed-window limiter + (`apps/api/src/rate-limiter.ts`): **60 requests per minute per client IP** (proxy headers first, + then socket address), returning `429` with `Retry-After`. This route is for trusted runtimes + calling a paid provider, not browser traffic. + +**CORS.** `isTrustedOrigin` (`app.ts`) decides which `Origin` values receive credentialed CORS +responses. It accepts configured public origins, exact `sentrabot://`, dev Expo origins when not +in production, and loopback aliases of declared origins. It is **only** used for CORS — not CSRF +or auth decisions. + ## Decision log ### 2026-09-02 — `apps/api` is the harness @@ -141,6 +198,22 @@ boundary is needed. `always_allow` rule for the tool. - Migration consequence: none; rules are evaluated per call. +### 2026-09-10 — auth email, verification, reset, and explicit rate limits + +- Problem: sign-up and password recovery had no email path; verification never flipped + `emailVerified`; auth rate limiting depended on Better Auth's production detection; managed AI + had no application-level throttle. +- Decision: add `EmailSender` + SMTP adapter; enable verification on sign-up and password reset + with honest degradation when SMTP is unset; pin Better Auth `rateLimit.enabled`; add a + 60/min/IP limiter on `POST /v1/managed-ai/responses`. Keep `requireEmailVerification` off; + defer per-action `emailVerified` gates to a follow-up router change. +- Rejected: blocking sign-in until verified (hurts activation); hosted auth/email vendors + (repository policy). +- Trade-off: unverified accounts can use the product until action-level gates exist; in-memory + limiters do not coordinate across multiple API replicas. +- Migration consequence: operators who want recovery and verification must configure + `SMTP_URL` (and usually `SMTP_FROM`); existing sessions unchanged. + ### 2026-09-04 — always_allow rules no longer apply to host execution - Problem: an `always_allow` rule is keyed on the bare tool name with no bot, computer-kind, or diff --git a/docs/plans/user-journey-audit-2026-09-10.md b/docs/plans/user-journey-audit-2026-09-10.md index ef85b80..99b3a8e 100644 --- a/docs/plans/user-journey-audit-2026-09-10.md +++ b/docs/plans/user-journey-audit-2026-09-10.md @@ -1,6 +1,6 @@ # Sentrabot — Audit Produk & Rancangan User Journey End-to-End -Tanggal: 2026-09-10 · Status: audit **+ implementasi Fase A** (branch `feat/auth-foundation`, belum di-commit) +Tanggal: 2026-09-10 · Status: audit **+ implementasi Fase A** (branch `feat/auth-foundation`). **As-built auth** (SMTP, verification, reset, rate limits) is maintained in [`docs/architecture.md`](../architecture.md#authentication-and-sessions); this file keeps the audit narrative and gap analysis. Metode: pembacaan langsung codebase. Setiap klaim disertai `file:line`. Yang tidak terbukti ditandai **NOT VERIFIED**. §1–§10 adalah audit terhadap kondisi **sebelum** implementasi. **§11 mencatat apa yang sudah dibangun** — baca itu lebih dulu bila Anda hanya butuh status terkini. diff --git a/docs/self-host.md b/docs/self-host.md index 0e11d27..c25a1d9 100644 --- a/docs/self-host.md +++ b/docs/self-host.md @@ -74,6 +74,24 @@ API_URL=https://app.example.com Cookies and CORS follow those origins. `SIGNUPS_ENABLED` / `SIGNUP_ALLOWLIST` seed the initial deployment settings. After initialization, the deployment owner's Settings values are the effective signup policy. +### Transactional auth email (verification and password reset) + +Verification on sign-up and password reset email links only when SMTP is configured. Without it, +the API still starts; auth flows log `[auth] SMTP_URL is not configured…` and users cannot receive +links until you add a relay. + +```env +# smtp://user:pass@host:587 or smtps://user:pass@host:465 +SMTP_URL=smtp://user:pass@mail.example.com:587 +SMTP_FROM=noreply@example.com +``` + +Use a sender address your provider accepts. When `SMTP_URL` includes credentials, `SMTP_FROM` +defaults to the URL username; set `SMTP_FROM` explicitly for credential-free relays. Keep +`BETTER_AUTH_URL` aligned with `WEB_ORIGIN` so links open the web app (`/reset-password` for +resets). Copy and subjects are Indonesian (`packages/auth/src/auth-emails.ts`). Details: +[architecture.md — Authentication and sessions](architecture.md#authentication-and-sessions). + Optional: ```env