@terreno/mcp 0.31.0 → 56.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +15 -16
- package/dist/bootstrap.js.map +1 -1
- package/dist/docs/templates/bootstrap/backend/types/models/userTypes.template.md +4 -5
- package/dist/docs/templates/bootstrap/frontend/app/_layout.template.tsx +1 -1
- package/dist/docs/templates/tools/model.template.md +2 -4
- package/dist/docs/versioned/next/README.md +1 -0
- package/dist/docs/versioned/next/explanation/README.md +1 -7
- package/dist/docs/versioned/next/how-to/README.md +2 -8
- package/dist/docs/versioned/next/how-to/deploy-to-gcp.md +228 -20
- package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +445 -0
- package/dist/docs/versioned/next/implementationPlans/PLAN_INDEX.md +4 -57
- package/dist/docs/versioned/next/implementationPlans/README.md +30 -56
- package/dist/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +1 -2
- package/dist/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/build-terreno-app-validation.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/deploy-to-gcp.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/deploy-to-vercel.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/deployment-foundation.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/docs-reference-coverage.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/docs-tutorials-ai-first.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/examples-demo-coverage.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/oss-governance-baseline.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/public-roadmap-github.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/rtk-to-syncdb-migration-docs.md +52 -47
- package/dist/docs/versioned/next/implementationPlans/syncdb-api-inventory.md +303 -0
- package/dist/docs/versioned/next/implementationPlans/syncdb-codegen.md +252 -0
- package/dist/docs/versioned/next/implementationPlans/syncdb-local-first.md +468 -420
- package/dist/docs/versioned/next/implementationPlans/syncdb-phase-c-design.md +425 -0
- package/dist/docs/versioned/next/implementationPlans/terreno-syncdb-2.md +800 -0
- package/dist/docs/versioned/next/implementationPlans/upgrade-guides-and-skill.md +0 -1
- package/dist/docs/versioned/next/implementationPlans/web-ssr-and-admin-spa.md +0 -1
- package/dist/docs/versioned/next/reference/README.md +1 -4
- package/dist/docs/versioned/next/reference/environment-variables.md +382 -220
- package/dist/docs/versioned/next/reference/syncdb.md +456 -0
- package/dist/docs/versioned/next/superpowers/plans/2026-07-28-admin-mobile-navigation.md +97 -0
- package/dist/docs/versioned/next/superpowers/specs/2026-07-28-admin-mobile-navigation-design.md +36 -0
- package/dist/docs/versioned/next/tasks/agentic-sdlc-plugin.md +5 -5
- package/dist/docs/versioned/next/tasks/deploy-to-gcp.md +11 -11
- package/dist/docs/versioned/next/tasks/deployment-foundation.md +12 -12
- package/dist/docs/versioned/next/tasks/docs-reference-coverage.md +4 -4
- package/dist/docs/versioned/next/tasks/oss-governance-baseline.md +18 -18
- package/dist/docs/versioned/next/tasks/positioning-django-rails-universal.md +4 -4
- package/dist/docs/versioned/next/tasks/public-roadmap-github.md +15 -15
- package/dist/docs/versioned/next/tasks/rtk-to-syncdb-migration-docs.md +10 -10
- package/dist/docs/versioned/next/tasks/syncdb-codegen.md +65 -0
- package/dist/docs/versioned/next/tasks/syncdb-local-first.md +434 -117
- package/package.json +4 -5
- package/src/docs/templates/bootstrap/backend/types/models/userTypes.template.md +4 -5
- package/src/docs/templates/bootstrap/frontend/app/_layout.template.tsx +1 -1
- package/src/docs/templates/tools/model.template.md +2 -4
- package/src/docs/versioned/next/README.md +1 -0
- package/src/docs/versioned/next/explanation/README.md +1 -7
- package/src/docs/versioned/next/how-to/README.md +2 -8
- package/src/docs/versioned/next/how-to/deploy-to-gcp.md +228 -20
- package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +445 -0
- package/src/docs/versioned/next/implementationPlans/PLAN_INDEX.md +4 -57
- package/src/docs/versioned/next/implementationPlans/README.md +30 -56
- package/src/docs/versioned/next/implementationPlans/agentic-sdlc-plugin.md +1 -2
- package/src/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +0 -1
- package/src/docs/versioned/next/implementationPlans/build-terreno-app-validation.md +0 -1
- package/src/docs/versioned/next/implementationPlans/deploy-to-gcp.md +0 -1
- package/src/docs/versioned/next/implementationPlans/deploy-to-vercel.md +0 -1
- package/src/docs/versioned/next/implementationPlans/deployment-foundation.md +0 -1
- package/src/docs/versioned/next/implementationPlans/docs-reference-coverage.md +0 -1
- package/src/docs/versioned/next/implementationPlans/docs-tutorials-ai-first.md +0 -1
- package/src/docs/versioned/next/implementationPlans/examples-demo-coverage.md +0 -1
- package/src/docs/versioned/next/implementationPlans/oss-governance-baseline.md +0 -1
- package/src/docs/versioned/next/implementationPlans/positioning-django-rails-universal.md +0 -1
- package/src/docs/versioned/next/implementationPlans/public-roadmap-github.md +0 -1
- package/src/docs/versioned/next/implementationPlans/rtk-to-syncdb-migration-docs.md +52 -47
- package/src/docs/versioned/next/implementationPlans/syncdb-api-inventory.md +303 -0
- package/src/docs/versioned/next/implementationPlans/syncdb-codegen.md +252 -0
- package/src/docs/versioned/next/implementationPlans/syncdb-local-first.md +468 -420
- package/src/docs/versioned/next/implementationPlans/syncdb-phase-c-design.md +425 -0
- package/src/docs/versioned/next/implementationPlans/terreno-syncdb-2.md +800 -0
- package/src/docs/versioned/next/implementationPlans/upgrade-guides-and-skill.md +0 -1
- package/src/docs/versioned/next/implementationPlans/web-ssr-and-admin-spa.md +0 -1
- package/src/docs/versioned/next/reference/README.md +1 -4
- package/src/docs/versioned/next/reference/environment-variables.md +382 -220
- package/src/docs/versioned/next/reference/syncdb.md +456 -0
- package/src/docs/versioned/next/superpowers/plans/2026-07-28-admin-mobile-navigation.md +97 -0
- package/src/docs/versioned/next/superpowers/specs/2026-07-28-admin-mobile-navigation-design.md +36 -0
- package/src/docs/versioned/next/tasks/agentic-sdlc-plugin.md +5 -5
- package/src/docs/versioned/next/tasks/deploy-to-gcp.md +11 -11
- package/src/docs/versioned/next/tasks/deployment-foundation.md +12 -12
- package/src/docs/versioned/next/tasks/docs-reference-coverage.md +4 -4
- package/src/docs/versioned/next/tasks/oss-governance-baseline.md +18 -18
- package/src/docs/versioned/next/tasks/positioning-django-rails-universal.md +4 -4
- package/src/docs/versioned/next/tasks/public-roadmap-github.md +15 -15
- package/src/docs/versioned/next/tasks/rtk-to-syncdb-migration-docs.md +10 -10
- package/src/docs/versioned/next/tasks/syncdb-codegen.md +65 -0
- package/src/docs/versioned/next/tasks/syncdb-local-first.md +434 -117
- package/LICENSE +0 -21
- package/dist/__tests__/bootstrap.test.d.ts +0 -2
- package/dist/__tests__/bootstrap.test.d.ts.map +0 -1
- package/dist/__tests__/bootstrap.test.js +0 -299
- package/dist/__tests__/bootstrap.test.js.map +0 -1
- package/dist/__tests__/databaseQuery.test.d.ts +0 -2
- package/dist/__tests__/databaseQuery.test.d.ts.map +0 -1
- package/dist/__tests__/databaseQuery.test.js +0 -83
- package/dist/__tests__/databaseQuery.test.js.map +0 -1
- package/dist/__tests__/docsRoot.test.d.ts +0 -2
- package/dist/__tests__/docsRoot.test.d.ts.map +0 -1
- package/dist/__tests__/docsRoot.test.js +0 -59
- package/dist/__tests__/docsRoot.test.js.map +0 -1
- package/dist/__tests__/inferPackages.test.d.ts +0 -2
- package/dist/__tests__/inferPackages.test.d.ts.map +0 -1
- package/dist/__tests__/inferPackages.test.js +0 -35
- package/dist/__tests__/inferPackages.test.js.map +0 -1
- package/dist/__tests__/preload.d.ts +0 -2
- package/dist/__tests__/preload.d.ts.map +0 -1
- package/dist/__tests__/preload.js +0 -12
- package/dist/__tests__/preload.js.map +0 -1
- package/dist/__tests__/prompts.test.d.ts +0 -2
- package/dist/__tests__/prompts.test.d.ts.map +0 -1
- package/dist/__tests__/prompts.test.js +0 -295
- package/dist/__tests__/prompts.test.js.map +0 -1
- package/dist/__tests__/resources.test.d.ts +0 -2
- package/dist/__tests__/resources.test.d.ts.map +0 -1
- package/dist/__tests__/resources.test.js +0 -352
- package/dist/__tests__/resources.test.js.map +0 -1
- package/dist/__tests__/search.test.d.ts +0 -2
- package/dist/__tests__/search.test.d.ts.map +0 -1
- package/dist/__tests__/search.test.js +0 -163
- package/dist/__tests__/search.test.js.map +0 -1
- package/dist/__tests__/tools.test.d.ts +0 -2
- package/dist/__tests__/tools.test.d.ts.map +0 -1
- package/dist/__tests__/tools.test.js +0 -540
- package/dist/__tests__/tools.test.js.map +0 -1
- package/dist/__tests__/upgradeGuide.test.d.ts +0 -2
- package/dist/__tests__/upgradeGuide.test.d.ts.map +0 -1
- package/dist/__tests__/upgradeGuide.test.js +0 -22
- package/dist/__tests__/upgradeGuide.test.js.map +0 -1
- package/dist/docs/upgrades/0.31.0.md +0 -60
- package/dist/docs/versioned/next/explanation/deployment-architecture-gcp.md +0 -65
- package/dist/docs/versioned/next/explanation/deployment-baseline.md +0 -78
- package/dist/docs/versioned/next/explanation/positioning.md +0 -164
- package/dist/docs/versioned/next/explanation/repository-settings.md +0 -44
- package/dist/docs/versioned/next/explanation/roadmap-process.md +0 -330
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +0 -866
- package/dist/docs/versioned/next/how-to/build-for-web.md +0 -69
- package/dist/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +0 -115
- package/dist/docs/versioned/next/how-to/deploy-web-to-gcs-cdn.md +0 -183
- package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +0 -114
- package/dist/docs/versioned/next/implementationPlans/billing-stripe.md +0 -158
- package/dist/docs/versioned/next/implementationPlans/comms-abstraction.md +0 -240
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-expo-push.md +0 -107
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +0 -107
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-twilio-sms.md +0 -102
- package/dist/docs/versioned/next/implementationPlans/comms-adapter-twilio-verify.md +0 -93
- package/dist/docs/versioned/next/implementationPlans/infra-mcp-research.md +0 -188
- package/dist/docs/versioned/next/implementationPlans/infra-mcp.md +0 -269
- package/dist/docs/versioned/next/implementationPlans/native-module-baseline.md +0 -105
- package/dist/docs/versioned/next/implementationPlans/orgs-and-teams.md +0 -158
- package/dist/docs/versioned/next/implementationPlans/password-reset-and-email-verification.md +0 -147
- package/dist/docs/versioned/next/reference/admin-spa.md +0 -130
- package/dist/docs/versioned/next/reference/ai.md +0 -519
- package/dist/docs/versioned/next/reference/test.md +0 -108
- package/dist/docs/versioned/next/roadmap.md +0 -9
- package/dist/docs/versioned/next/tasks/README.md +0 -42
- package/dist/docs/versioned/next/tasks/billing-stripe.md +0 -75
- package/dist/docs/versioned/next/tasks/comms-abstraction.md +0 -77
- package/dist/docs/versioned/next/tasks/comms-adapter-expo-push.md +0 -29
- package/dist/docs/versioned/next/tasks/comms-adapter-sendgrid.md +0 -29
- package/dist/docs/versioned/next/tasks/comms-adapter-twilio-sms.md +0 -29
- package/dist/docs/versioned/next/tasks/comms-adapter-twilio-verify.md +0 -19
- package/dist/docs/versioned/next/tasks/infra-mcp.md +0 -43
- package/dist/docs/versioned/next/tasks/native-module-baseline.md +0 -42
- package/dist/docs/versioned/next/tasks/orgs-and-teams.md +0 -70
- package/dist/docs/versioned/next/tasks/password-reset-and-email-verification.md +0 -60
- package/src/docs/upgrades/0.31.0.md +0 -60
- package/src/docs/versioned/next/explanation/deployment-architecture-gcp.md +0 -65
- package/src/docs/versioned/next/explanation/deployment-baseline.md +0 -78
- package/src/docs/versioned/next/explanation/positioning.md +0 -164
- package/src/docs/versioned/next/explanation/repository-settings.md +0 -44
- package/src/docs/versioned/next/explanation/roadmap-process.md +0 -330
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +0 -866
- package/src/docs/versioned/next/how-to/build-for-web.md +0 -69
- package/src/docs/versioned/next/how-to/deploy-backend-to-cloud-run.md +0 -115
- package/src/docs/versioned/next/how-to/deploy-web-to-gcs-cdn.md +0 -183
- package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +0 -114
- package/src/docs/versioned/next/implementationPlans/billing-stripe.md +0 -158
- package/src/docs/versioned/next/implementationPlans/comms-abstraction.md +0 -240
- package/src/docs/versioned/next/implementationPlans/comms-adapter-expo-push.md +0 -107
- package/src/docs/versioned/next/implementationPlans/comms-adapter-sendgrid.md +0 -107
- package/src/docs/versioned/next/implementationPlans/comms-adapter-twilio-sms.md +0 -102
- package/src/docs/versioned/next/implementationPlans/comms-adapter-twilio-verify.md +0 -93
- package/src/docs/versioned/next/implementationPlans/infra-mcp-research.md +0 -188
- package/src/docs/versioned/next/implementationPlans/infra-mcp.md +0 -269
- package/src/docs/versioned/next/implementationPlans/native-module-baseline.md +0 -105
- package/src/docs/versioned/next/implementationPlans/orgs-and-teams.md +0 -158
- package/src/docs/versioned/next/implementationPlans/password-reset-and-email-verification.md +0 -147
- package/src/docs/versioned/next/reference/admin-spa.md +0 -130
- package/src/docs/versioned/next/reference/ai.md +0 -519
- package/src/docs/versioned/next/reference/test.md +0 -108
- package/src/docs/versioned/next/roadmap.md +0 -9
- package/src/docs/versioned/next/tasks/README.md +0 -42
- package/src/docs/versioned/next/tasks/billing-stripe.md +0 -75
- package/src/docs/versioned/next/tasks/comms-abstraction.md +0 -77
- package/src/docs/versioned/next/tasks/comms-adapter-expo-push.md +0 -29
- package/src/docs/versioned/next/tasks/comms-adapter-sendgrid.md +0 -29
- package/src/docs/versioned/next/tasks/comms-adapter-twilio-sms.md +0 -29
- package/src/docs/versioned/next/tasks/comms-adapter-twilio-verify.md +0 -19
- package/src/docs/versioned/next/tasks/infra-mcp.md +0 -43
- package/src/docs/versioned/next/tasks/native-module-baseline.md +0 -42
- package/src/docs/versioned/next/tasks/orgs-and-teams.md +0 -70
- package/src/docs/versioned/next/tasks/password-reset-and-email-verification.md +0 -60
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
# Implementation Plan: Organizations, teams, and multi-tenant scoping
|
|
2
|
-
|
|
3
|
-
**Status:** Draft
|
|
4
|
-
**Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1024
|
|
5
|
-
**Priority:** High
|
|
6
|
-
**Effort:** Big batch
|
|
7
|
-
**Owner:** unassigned
|
|
8
|
-
**Created:** 2026-08-09
|
|
9
|
-
**Program:** [B2B platform](b2b-platform-program.md)
|
|
10
|
-
**Depends on:** none (soft: [rbac-permissions](rbac-permissions.md) for role vocabulary; this IP ships coarse org roles that RBAC later subsumes)
|
|
11
|
-
**RTK deprecation flag:** None — backend only; frontend surfaces are `org-management-ui`
|
|
12
|
-
|
|
13
|
-
## Goal
|
|
14
|
-
|
|
15
|
-
Terreno's entire access story is per-user: `ownerId` fields, `OwnerQueryFilter`,
|
|
16
|
-
`Permissions.IsOwner`. A B2B app needs data owned by an **organization** and accessed by
|
|
17
|
-
its **members**. This IP adds the tenant analog of the owner stack to `@terreno/api`:
|
|
18
|
-
Organization and Membership models, an org-context middleware, an `orgScopedPlugin` for
|
|
19
|
-
schemas, `OrgQueryFilter` and `Permissions.IsOrgMember`/`IsOrgAdmin` for modelRouter, and
|
|
20
|
-
CRUD routes for orgs and memberships — so `modelRouter(Todo, {queryFilter: OrgQueryFilter,
|
|
21
|
-
permissions: {...IsOrgMember}})` is all an app needs for tenant-safe endpoints.
|
|
22
|
-
|
|
23
|
-
## Non-Goals
|
|
24
|
-
|
|
25
|
-
- Invitations, seat counting (`invitations-and-seats` item).
|
|
26
|
-
- Fine-grained RBAC (roles beyond `member`/`admin` — [rbac-permissions](rbac-permissions.md)).
|
|
27
|
-
- Org switcher and management screens (`org-management-ui` item).
|
|
28
|
-
- Billing linkage (`billing-stripe` maps customers to orgs later).
|
|
29
|
-
- Cross-org data sharing, nested teams/sub-orgs (future work).
|
|
30
|
-
|
|
31
|
-
## Decisions
|
|
32
|
-
|
|
33
|
-
| Question | Decision |
|
|
34
|
-
|----------|----------|
|
|
35
|
-
| **D6 (resolved 2026-08-09):** native Mongoose models vs Better Auth `organization` plugin | **Native models.** The JWT/passport path (still supported and the example default) cannot use a Better-Auth-only construct; native models serve both auth paths, keep modelRouter/query-filter integration first-class, and can sync to Better Auth's plugin later if wanted |
|
|
36
|
-
| Org context transport | `X-Organization-Id` header, resolved by middleware into `req.organization` + `req.membership`; explicit query/body override rejected |
|
|
37
|
-
| One org or many per user? | Many (Membership join collection); apps wanting single-org enforce via option `maxOrgsPerUser: 1` |
|
|
38
|
-
| Coarse roles now | `Membership.role: "admin" \| "member"` — deliberately minimal; RBAC IP replaces this enum with role references, migration documented there |
|
|
39
|
-
| Scoping mechanism | `orgScopedPlugin` adds required indexed `organizationId`; `OrgQueryFilter` filters lists by active memberships; `preCreate` helper injects the context org |
|
|
40
|
-
| Personal orgs? | No auto-created personal org; apps opt in via `createDefaultOrgOnSignup` option |
|
|
41
|
-
|
|
42
|
-
## Architecture
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
api/src/orgs/
|
|
46
|
-
organizationModel.ts # Organization + Membership schemas/models
|
|
47
|
-
orgContext.ts # middleware: header → req.organization/req.membership
|
|
48
|
-
orgPlugin.ts # orgScopedPlugin(schema) — organizationId field + index
|
|
49
|
-
orgPermissions.ts # IsOrgMember, IsOrgAdmin, OrgQueryFilter
|
|
50
|
-
orgsApp.ts # OrgsApp TerrenoPlugin — routes + options
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
`OrgsApp` options:
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
new OrgsApp({
|
|
57
|
-
maxOrgsPerUser?: number;
|
|
58
|
-
createDefaultOrgOnSignup?: boolean;
|
|
59
|
-
onOrgCreated?: (org, owner) => Promise<void>;
|
|
60
|
-
})
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Request flow: `authenticateMiddleware` → `orgContextMiddleware` (loads org by header,
|
|
64
|
-
verifies an active membership for `req.user`, 403 otherwise) → route. Routes that take
|
|
65
|
-
`Permissions.IsOrgMember`/`IsOrgAdmin` require the context; `OrgQueryFilter` works with or
|
|
66
|
-
without a context (no header → all orgs the user belongs to; header → that org only).
|
|
67
|
-
|
|
68
|
-
## Models
|
|
69
|
-
|
|
70
|
-
Both use the five-type pattern, `description` on every field, `createdUpdatedPlugin` +
|
|
71
|
-
`isDeletedPlugin`, `strict: "throw"`.
|
|
72
|
-
|
|
73
|
-
**Organization** — `name` (string, required), `slug` (string, unique, indexed, generated),
|
|
74
|
-
`ownerId` (ref User, required — the creating user; ownership transferable), `settings`
|
|
75
|
-
(Mixed, app-defined), `disabled` (boolean).
|
|
76
|
-
|
|
77
|
-
**Membership** — `organizationId` (ref Organization, required, indexed), `userId` (ref
|
|
78
|
-
User, required, indexed), `role` ("admin" | "member", default "member"), `status`
|
|
79
|
-
("active" | "suspended", default "active"), compound unique index on
|
|
80
|
-
`(organizationId, userId)`. Statics: `findActiveForUser`, `isMember`, `isOrgAdmin`.
|
|
81
|
-
|
|
82
|
-
## APIs
|
|
83
|
-
|
|
84
|
-
| Method | Path | Permissions | Notes |
|
|
85
|
-
|---|---|---|---|
|
|
86
|
-
| POST | `/orgs` | IsAuthenticated | Creates org + admin membership for creator; respects `maxOrgsPerUser` |
|
|
87
|
-
| GET | `/orgs` | IsAuthenticated | Lists orgs the user belongs to (membership join) |
|
|
88
|
-
| GET | `/orgs/:id` | IsOrgMember | |
|
|
89
|
-
| PATCH | `/orgs/:id` | IsOrgAdmin | name/settings; ownership transfer admin-only |
|
|
90
|
-
| DELETE | `/orgs/:id` | IsOrgAdmin | Soft delete; memberships cascade to suspended |
|
|
91
|
-
| GET | `/orgs/:id/members` | IsOrgMember | Paginated membership list (populated user name/email) |
|
|
92
|
-
| PATCH | `/orgs/:id/members/:memberId` | IsOrgAdmin | Role/status change; cannot demote last admin |
|
|
93
|
-
| DELETE | `/orgs/:id/members/:memberId` | IsOrgAdmin | Remove member; cannot remove last admin |
|
|
94
|
-
|
|
95
|
-
Member creation is **not** exposed here — members arrive via `invitations-and-seats`
|
|
96
|
-
(or `createDefaultOrgOnSignup`). All routes via modelRouter where shapes fit, custom
|
|
97
|
-
`createOpenApiBuilder` routes for member management.
|
|
98
|
-
|
|
99
|
-
## Notifications
|
|
100
|
-
|
|
101
|
-
None in this IP (invitation emails belong to `invitations-and-seats`).
|
|
102
|
-
|
|
103
|
-
## UI
|
|
104
|
-
|
|
105
|
-
None in this IP (`org-management-ui`). example-backend gains an org-scoped demo model
|
|
106
|
-
(`Project`) exercising `orgScopedPlugin` + `OrgQueryFilter` end to end.
|
|
107
|
-
|
|
108
|
-
## Phases
|
|
109
|
-
|
|
110
|
-
1. **Models + plugin:** Organization, Membership, `orgScopedPlugin`, statics, unit tests.
|
|
111
|
-
2. **Context + permissions:** middleware, `IsOrgMember`/`IsOrgAdmin`, `OrgQueryFilter`,
|
|
112
|
-
modelRouter integration tests (member of A cannot read B's rows — the critical suite).
|
|
113
|
-
3. **Routes + app:** `OrgsApp`, org/member routes, last-admin guards, OpenAPI, supertest.
|
|
114
|
-
4. **Example + docs:** example-backend `Project` model + routes, seeds, admin panel model
|
|
115
|
-
registration, `docs/how-to/add-organizations.md`, reference docs, SDK regen.
|
|
116
|
-
|
|
117
|
-
## Feature Flags & Migrations
|
|
118
|
-
|
|
119
|
-
No migration for existing apps (new collections; opt-in plugin). Adopting apps that
|
|
120
|
-
backfill `organizationId` onto existing collections get a documented backfill recipe —
|
|
121
|
-
formal tooling arrives with `mongo-migrations`.
|
|
122
|
-
|
|
123
|
-
## Activity Log & User Updates
|
|
124
|
-
|
|
125
|
-
Org create/delete and membership role changes fire an `onOrgAudit` hook (mirroring
|
|
126
|
-
`onAdminAudit`) so apps can log them; framework-wide audit persistence is the
|
|
127
|
-
`framework-audit-log` item.
|
|
128
|
-
|
|
129
|
-
## Not Included / Future Work
|
|
130
|
-
|
|
131
|
-
- Teams within orgs (the Membership schema reserves a `teamIds` extension point but ships
|
|
132
|
-
without it — YAGNI until a consumer needs it).
|
|
133
|
-
- Org-level settings UI, domain capture/auto-join (SSO IP touches this).
|
|
134
|
-
- Per-org rate limits, quotas.
|
|
135
|
-
|
|
136
|
-
## Files to Create / Modify
|
|
137
|
-
|
|
138
|
-
- `api/src/orgs/*` (new), `api/src/index.ts` exports
|
|
139
|
-
- `api/src/permissions.ts` — export org permissions alongside existing ones
|
|
140
|
-
- `example-backend/src/models/project.ts`, `api/projects.ts`, seeds, `server.ts`
|
|
141
|
-
- `docs/how-to/add-organizations.md`, `docs/reference/api.md`
|
|
142
|
-
- `example-frontend` SDK regen (`bun run sdk`) after backend routes land
|
|
143
|
-
|
|
144
|
-
## Task List
|
|
145
|
-
|
|
146
|
-
See [docs/tasks/orgs-and-teams.md](../tasks/orgs-and-teams.md).
|
|
147
|
-
|
|
148
|
-
## Acceptance Criteria
|
|
149
|
-
|
|
150
|
-
- [ ] A user can create an org, becoming its admin member; `maxOrgsPerUser` is enforced.
|
|
151
|
-
- [ ] With two orgs seeded, a member of org A listing an org-scoped model with
|
|
152
|
-
`OrgQueryFilter` never receives org B documents — including via crafted `$or` /
|
|
153
|
-
query-param attempts.
|
|
154
|
-
- [ ] Requests with an `X-Organization-Id` the user is not an active member of get 403.
|
|
155
|
-
- [ ] The last admin of an org cannot be demoted or removed.
|
|
156
|
-
- [ ] Org routes appear in `/openapi.json`; regenerated SDK compiles; admin panel can
|
|
157
|
-
browse Organizations and Memberships.
|
|
158
|
-
- [ ] Soft-deleting an org suspends its memberships and hides its scoped data from lists.
|
package/src/docs/versioned/next/implementationPlans/password-reset-and-email-verification.md
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
# Implementation Plan: Password reset and email verification
|
|
2
|
-
|
|
3
|
-
**Status:** Draft
|
|
4
|
-
**Roadmap issue:** https://github.com/FlourishHealth/terreno/issues/1023
|
|
5
|
-
**Priority:** High
|
|
6
|
-
**Effort:** Big batch
|
|
7
|
-
**Owner:** unassigned
|
|
8
|
-
**Created:** 2026-08-09
|
|
9
|
-
**Program:** [B2B platform](b2b-platform-program.md)
|
|
10
|
-
**Depends on:** [comms-abstraction](comms-abstraction.md), [comms-adapter-sendgrid](comms-adapter-sendgrid.md)
|
|
11
|
-
**RTK deprecation flag:** None — the RTK client's existing `resetPassword` endpoint gets a
|
|
12
|
-
backend at last; new client work is a thin screen pair noted in UI
|
|
13
|
-
|
|
14
|
-
## Goal
|
|
15
|
-
|
|
16
|
-
The JWT auth path has no way to recover an account or verify an email address: the
|
|
17
|
-
`@terreno/rtk` client ships a `resetPassword` mutation with **no backend route**, and
|
|
18
|
-
nothing sets or checks email verification. This IP adds forgot/reset password and email
|
|
19
|
-
verification flows to `@terreno/api`'s JWT auth, wires the Better Auth equivalents to the
|
|
20
|
-
same mail templates, and sends everything through `@terreno/comms`.
|
|
21
|
-
|
|
22
|
-
## Non-Goals
|
|
23
|
-
|
|
24
|
-
- MFA / step-up auth (`mfa-step-up-auth` item).
|
|
25
|
-
- Magic-link login.
|
|
26
|
-
- SMS-based recovery (possible later via the Twilio Verify adapter).
|
|
27
|
-
- Account lockout / brute-force throttling beyond token single-use + expiry
|
|
28
|
-
(`rate-limiting` item hardens the endpoints).
|
|
29
|
-
|
|
30
|
-
## Decisions
|
|
31
|
-
|
|
32
|
-
| Question | Decision |
|
|
33
|
-
|----------|----------|
|
|
34
|
-
| Token design | 32-byte crypto-random token, stored **hashed** (sha256) with expiry (`1h` reset / `24h` verify) and single-use consumption; separate `AuthToken` collection rather than fields on User |
|
|
35
|
-
| Enumeration safety | `POST /auth/forgotPassword` always returns 202 regardless of whether the email exists |
|
|
36
|
-
| Password set mechanism | `passport-local-mongoose`'s `setPassword` on the user document, then invalidate all refresh tokens |
|
|
37
|
-
| RTK client compatibility | The existing RTK `resetPassword` endpoint's path/shape is the contract — verify at implementation time and alias if it differs from `/auth/resetPassword` |
|
|
38
|
-
| Verification gating | Opt-in `requireEmailVerification` auth option: unverified users get 403 with code `email-not-verified` on login; default off (non-breaking) |
|
|
39
|
-
| Better Auth path | Enable Better Auth's built-in reset/verification, providing `sendResetPassword` / `sendVerificationEmail` hooks that call the same comms templates so both auth paths send identical mail |
|
|
40
|
-
| Link targets | Deep-link/URL base from a new `authOptions.publicAppUrl`; web route examples in example-frontend |
|
|
41
|
-
|
|
42
|
-
## Architecture
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
api/src/authTokens.ts # AuthToken model + issue/consume helpers
|
|
46
|
-
api/src/authRecovery.ts # forgot/reset/verify routes + options
|
|
47
|
-
# registered by addAuthRoutes when comms is available
|
|
48
|
-
comms templates # resetPassword + verifyEmail templates (subject/text/html)
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Flow (reset): `POST /auth/forgotPassword {email}` → 202 always; if user exists, issue
|
|
52
|
-
token, send mail with `${publicAppUrl}/resetPassword?token=...` → `POST
|
|
53
|
-
/auth/resetPassword {token, password}` → validate + consume token, `setPassword`,
|
|
54
|
-
invalidate refresh tokens, 200.
|
|
55
|
-
|
|
56
|
-
Flow (verify): token issued on signup (and via `POST /auth/sendVerification` for
|
|
57
|
-
re-sends, authenticated); `POST /auth/verifyEmail {token}` sets `user.emailVerified =
|
|
58
|
-
true`.
|
|
59
|
-
|
|
60
|
-
## Models
|
|
61
|
-
|
|
62
|
-
**AuthToken** — `userId` (ref User, required, indexed), `tokenHash` (string, unique),
|
|
63
|
-
`type` ("passwordReset" | "emailVerification"), `expiresAt` (date, TTL-indexed),
|
|
64
|
-
`consumedAt` (date, optional). Statics: `issueFor(user, type)`, `consume(token, type)`
|
|
65
|
-
(atomic findOneAndUpdate guarding single-use).
|
|
66
|
-
|
|
67
|
-
**User** — adds `emailVerified` (boolean, default false) via a small
|
|
68
|
-
`emailVerificationPlugin` so existing user schemas opt in without breaking.
|
|
69
|
-
|
|
70
|
-
## APIs
|
|
71
|
-
|
|
72
|
-
| Method | Path | Permissions | Notes |
|
|
73
|
-
|---|---|---|---|
|
|
74
|
-
| POST | `/auth/forgotPassword` | anonymous | `{email}` → always 202 |
|
|
75
|
-
| POST | `/auth/resetPassword` | anonymous | `{token, password}`; path/shape matched to the RTK client contract |
|
|
76
|
-
| POST | `/auth/sendVerification` | IsAuthenticated | Re-send verification mail |
|
|
77
|
-
| POST | `/auth/verifyEmail` | anonymous | `{token}` |
|
|
78
|
-
|
|
79
|
-
All registered by `addAuthRoutes` when a mail-capable `CommsApp` is present; without one,
|
|
80
|
-
routes 501 in production and console-send in development (inherited comms behavior).
|
|
81
|
-
|
|
82
|
-
## Notifications
|
|
83
|
-
|
|
84
|
-
Two mail templates (`resetPassword`, `verifyEmail`) shipped in `@terreno/comms` templates
|
|
85
|
-
with app-override hooks.
|
|
86
|
-
|
|
87
|
-
## UI
|
|
88
|
-
|
|
89
|
-
example-frontend: `/resetPassword` (token from URL, new-password form using the existing
|
|
90
|
-
RTK `resetPassword` mutation) and a "verify your email" banner + re-send button on the
|
|
91
|
-
profile screen. `LoginScreen` gains an optional `onForgotPassword` link prop in
|
|
92
|
-
`@terreno/ui`.
|
|
93
|
-
|
|
94
|
-
## Phases
|
|
95
|
-
|
|
96
|
-
1. **Token infrastructure:** AuthToken model, issue/consume, `emailVerificationPlugin`,
|
|
97
|
-
unit tests.
|
|
98
|
-
2. **JWT routes + mail:** four routes, templates, refresh-token invalidation,
|
|
99
|
-
enumeration-safety + single-use/expiry test suite.
|
|
100
|
-
3. **Better Auth parity:** wire `sendResetPassword`/`sendVerificationEmail` hooks to the
|
|
101
|
-
same templates; parity tests.
|
|
102
|
-
4. **Client + docs:** example-frontend screens, `@terreno/ui` login link prop, SDK regen,
|
|
103
|
-
`docs/how-to/password-reset.md`, env/reference docs.
|
|
104
|
-
|
|
105
|
-
## Feature Flags & Migrations
|
|
106
|
-
|
|
107
|
-
`requireEmailVerification` defaults off; enabling it for an existing app is documented
|
|
108
|
-
(backfill `emailVerified: true` for existing users or accept re-verification). No schema
|
|
109
|
-
migration otherwise.
|
|
110
|
-
|
|
111
|
-
## Activity Log & User Updates
|
|
112
|
-
|
|
113
|
-
Token issue/consume and verification events logged via `logger` and `CommsMessage`
|
|
114
|
-
rows; no user-facing activity feed.
|
|
115
|
-
|
|
116
|
-
## Not Included / Future Work
|
|
117
|
-
|
|
118
|
-
- Password strength policy hooks (exists via `PasswordRequirements` UI; server policy is
|
|
119
|
-
future work).
|
|
120
|
-
- Admin-triggered forced resets (admin panel action — future).
|
|
121
|
-
- Endpoint rate limiting (`rate-limiting` item).
|
|
122
|
-
|
|
123
|
-
## Files to Create / Modify
|
|
124
|
-
|
|
125
|
-
- `api/src/authTokens.ts`, `api/src/authRecovery.ts` (new), `api/src/auth.ts`,
|
|
126
|
-
`api/src/plugins.ts` (emailVerificationPlugin), `api/src/index.ts`
|
|
127
|
-
- `comms/src/templates.ts` — two templates
|
|
128
|
-
- `example-frontend/app/resetPassword.tsx`, profile banner, SDK regen
|
|
129
|
-
- `ui/src/login/LoginScreen.tsx` — `onForgotPassword` prop
|
|
130
|
-
- `docs/how-to/password-reset.md`, `docs/reference/environment-variables.md`
|
|
131
|
-
|
|
132
|
-
## Task List
|
|
133
|
-
|
|
134
|
-
See [docs/tasks/password-reset-and-email-verification.md](../tasks/password-reset-and-email-verification.md).
|
|
135
|
-
|
|
136
|
-
## Acceptance Criteria
|
|
137
|
-
|
|
138
|
-
- [ ] `POST /auth/forgotPassword` returns 202 for existing and non-existing emails alike,
|
|
139
|
-
sending mail only for existing ones.
|
|
140
|
-
- [ ] A reset token works exactly once, fails after expiry, and a successful reset
|
|
141
|
-
invalidates outstanding refresh tokens.
|
|
142
|
-
- [ ] The unmodified RTK client `resetPassword` mutation completes the flow end to end.
|
|
143
|
-
- [ ] With `requireEmailVerification` on, an unverified user gets 403 code
|
|
144
|
-
`email-not-verified` on login and can complete verification to proceed.
|
|
145
|
-
- [ ] Better Auth mode sends the same two templates through comms.
|
|
146
|
-
- [ ] example-frontend can complete forgot → email link (console adapter output in dev) →
|
|
147
|
-
reset → login with the new password; UI verification evidence attached to the PR.
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
# @terreno/admin-spa
|
|
2
|
-
|
|
3
|
-
Standalone Expo Router admin web SPA plus an Express plugin (`AdminSpaServeApp`) that serves the pre-built static export from a Terreno backend on the same origin.
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Install](#install)
|
|
8
|
-
- [Commands](#commands)
|
|
9
|
-
- [Architecture](#architecture)
|
|
10
|
-
- [AdminSpaServeApp](#adminspaserveapp)
|
|
11
|
-
- [App config](#app-config)
|
|
12
|
-
- [Serving and caching](#serving-and-caching)
|
|
13
|
-
- [Mount path constraint](#mount-path-constraint)
|
|
14
|
-
- [Standalone SPA vs embedded admin-frontend](#standalone-spa-vs-embedded-admin-frontend)
|
|
15
|
-
|
|
16
|
-
## Install
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
bun add @terreno/admin-spa @terreno/api
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
`@terreno/api` is a peer dependency (TerrenoPlugin registration).
|
|
23
|
-
|
|
24
|
-
## Commands
|
|
25
|
-
|
|
26
|
-
From the `@terreno/admin-spa` package directory:
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
bun run compile # Compile the server plugin (src/ -> src/dist)
|
|
30
|
-
bun run build:web # Expo static export into dist/
|
|
31
|
-
bun run dev # expo start --web --port 8083
|
|
32
|
-
bun run test:ci # Serve-plugin unit tests
|
|
33
|
-
bun run smoke # Smoke test over built dist/
|
|
34
|
-
bun run test:e2e # Playwright e2e over built dist/
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Architecture
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
admin-spa/
|
|
41
|
-
src/
|
|
42
|
-
serve.ts # AdminSpaServeApp TerrenoPlugin
|
|
43
|
-
appConfig.ts # AdminSpaAppConfig defaults + merge helper
|
|
44
|
-
index.ts # Public exports
|
|
45
|
-
app/ # Expo Router SPA (Better Auth + admin screens)
|
|
46
|
-
store/ # Redux + Better Auth client
|
|
47
|
-
dist/ # Pre-built static export (produced by build:web)
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Boot flow in the SPA: `AppConfigGate` fetches `${basePath}/app-config.json` → `StoreProvider` builds Better Auth + Redux → `AdminGate` redirects anonymous users to `/login` and non-admins to `/forbidden`.
|
|
51
|
-
|
|
52
|
-
## AdminSpaServeApp
|
|
53
|
-
|
|
54
|
-
`AdminSpaServeApp` implements `TerrenoPlugin`. Register it on `TerrenoApp` alongside `AdminApp` and auth plugins:
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
import {AdminSpaServeApp} from "@terreno/admin-spa";
|
|
58
|
-
import {AdminApp} from "@terreno/admin-backend";
|
|
59
|
-
import {BetterAuthApp, TerrenoApp} from "@terreno/api";
|
|
60
|
-
|
|
61
|
-
new TerrenoApp({userModel: User})
|
|
62
|
-
.register(new BetterAuthApp({config: betterAuthConfig, userModel: User}))
|
|
63
|
-
.register(new AdminApp({models: [...]}))
|
|
64
|
-
.register(
|
|
65
|
-
new AdminSpaServeApp({
|
|
66
|
-
basePath: "/console",
|
|
67
|
-
appConfig: {brandName: "Acme Admin", providers: ["email", "google"]},
|
|
68
|
-
devProxyTarget: process.env.ADMIN_SPA_DEV_PROXY,
|
|
69
|
-
})
|
|
70
|
-
)
|
|
71
|
-
.start();
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
`example-backend` registers the plugin when `ADMIN_SPA_ENABLED=true`, with optional `ADMIN_SPA_DEV_PROXY` and `ADMIN_SPA_DIST_DIR` overrides.
|
|
75
|
-
|
|
76
|
-
### AdminSpaServeOptions
|
|
77
|
-
|
|
78
|
-
| Option | Default | Description |
|
|
79
|
-
|--------|---------|-------------|
|
|
80
|
-
| `basePath` | `"/console"` | URL prefix where the SPA is mounted. Admin API stays at `/admin`. |
|
|
81
|
-
| `appConfig` | see [App config](#app-config) | Partial config merged over defaults; served at `${basePath}/app-config.json`. |
|
|
82
|
-
| `distDir` | `<package>/dist` | Directory containing the pre-built static export (tests/custom builds). |
|
|
83
|
-
| `devProxyTarget` | — | When set, proxy all SPA paths to a running `expo start --web` server (e.g. `http://localhost:8083`). `app-config.json` is still served by the plugin. |
|
|
84
|
-
|
|
85
|
-
### Utility exports
|
|
86
|
-
|
|
87
|
-
- `rewriteIndexHtml(html, basePath)` — Rewrites absolute `/_expo/` and `/assets/` refs for sub-path mounts.
|
|
88
|
-
- `injectBaseGlobal(html, basePath)` — Injects `window.__ADMIN_SPA_BASE__` before `</head>`.
|
|
89
|
-
- `resolveAppConfig(overrides?)` — Merge partial config with `DEFAULT_APP_CONFIG`.
|
|
90
|
-
- `DEFAULT_APP_CONFIG` — Default `app-config.json` values.
|
|
91
|
-
|
|
92
|
-
## App config
|
|
93
|
-
|
|
94
|
-
`AdminSpaAppConfig` fields (from `appConfig.ts`):
|
|
95
|
-
|
|
96
|
-
| Field | Default | Description |
|
|
97
|
-
|-------|---------|-------------|
|
|
98
|
-
| `brandName` | `"Terreno Admin"` | Header brand name |
|
|
99
|
-
| `logoUrl` | — | Optional logo URL (absolute or relative) |
|
|
100
|
-
| `primaryColor` | `"#2563EB"` | Primary brand color (hex) |
|
|
101
|
-
| `providers` | `["email"]` | Login providers: `"email"`, `"google"`, `"github"`, `"apple"` |
|
|
102
|
-
| `authBasePath` | `"/api/auth"` | Better Auth routes on this origin |
|
|
103
|
-
| `adminApiBasePath` | `"/admin"` | Admin API base path on this origin |
|
|
104
|
-
|
|
105
|
-
## Serving and caching
|
|
106
|
-
|
|
107
|
-
**Production (no `devProxyTarget`):**
|
|
108
|
-
|
|
109
|
-
1. `${basePath}/_expo` and `${basePath}/assets` — `express.static` with `maxAge: 365d`, `immutable: true`.
|
|
110
|
-
2. `${basePath}/app-config.json` — `Cache-Control: no-store`.
|
|
111
|
-
3. `${basePath}` and `${basePath}/*splat` — SPA fallback returns rewritten `index.html` with `Cache-Control: no-store`.
|
|
112
|
-
|
|
113
|
-
`index.html` is read once at startup; asset paths are rewritten to `${basePath}/...` and `window.__ADMIN_SPA_BASE__` is injected for deep-link config fetches.
|
|
114
|
-
|
|
115
|
-
**Development (`devProxyTarget` set):**
|
|
116
|
-
|
|
117
|
-
- `${basePath}/app-config.json` served locally (no cache).
|
|
118
|
-
- All other `${basePath}/*` requests proxied to the Expo dev server (WebSocket enabled).
|
|
119
|
-
|
|
120
|
-
If no bundle exists at `distDir/index.html`, the plugin logs a warning suggesting `bun run --filter '@terreno/admin-spa' build:web` or `devProxyTarget`.
|
|
121
|
-
|
|
122
|
-
## Mount path constraint
|
|
123
|
-
|
|
124
|
-
The default web export is built with `experiments.baseUrl: "/console"`, so `basePath` must match. To mount elsewhere, rebuild with a matching base (e.g. `EXPO_BASE_URL=/admin-ui bun run build:web`) and set `basePath` accordingly. Mounting at `/` requires a root-base build and `basePath: "/"`.
|
|
125
|
-
|
|
126
|
-
## Standalone SPA vs embedded admin-frontend
|
|
127
|
-
|
|
128
|
-
Use **`@terreno/admin-spa`** when you want a complete admin UI served from the API process (same-origin Better Auth cookies, no separate static deploy). The backend image stays lean until you opt in by registering `AdminSpaServeApp`.
|
|
129
|
-
|
|
130
|
-
Use **`@terreno/admin-frontend`** when admin screens live inside your main Expo app (e.g. `example-frontend/app/admin/*`). You bring your own routing, auth, and deploy; the package supplies reusable list/table/form components and hooks against `/admin` CRUD routes from `@terreno/admin-backend`.
|