Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |

---
Expand Down
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand All @@ -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**.
75 changes: 74 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
@@ -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`.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/plans/user-journey-audit-2026-09-10.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
18 changes: 18 additions & 0 deletions docs/self-host.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading