Commit 18a9529
committed
feat(foundation): sign in, tenant scoping and audited mutations
Phase 1. The application runs: four seeded roles sign in through Better
Auth, land on a shell that reflects their permissions, and every mutation
writes its audit row inside the same transaction as the change.
Tenant isolation is the point of this phase, so it is structural rather
than remembered:
- forTenant(ctx) injects the organization on reads and stamps it on
writes, overruling an organizationId the caller supplied. It refuses
findUnique/update/delete outright, because those address a row by
unique key alone and Prisma will not accept a non-unique filter beside
it — a loud error beats a filter that looks applied and is not.
- A registry test parses schema.prisma and fails if a model with an
organizationId column is not scoped. It immediately found Membership
missing, which would have let one organization list another's members.
- authorize() answers 404, not 403, for a record in another tenant. A 403
confirms the record exists.
Two defects were caught by exercising the running server rather than
reading the code. The Command Center listed modules without filtering by
permission, so an Operator could see that an Administration area existed
while the sidebar beside it hid that module. And a page calling
getTenantContext() directly threw an unhandled 401 on anonymous requests,
because a layout's redirect does not stop its pages — Next renders them in
parallel. Pages now use requireTenantContext(), which redirects; queries
and actions keep the throwing variant, where throwing is correct.
Three plan decisions were revised against the environment, each recorded
in DECISIONS.md with its evidence: npm replaces pnpm (corepack cannot
write its shims without administrator rights here), TypeScript is pinned
to 5.9 rather than 7.0 (the ESLint toolchain is not built against 7), and
the PostgreSQL image is pinned exactly — a floating tag restarted the
container against a data directory the newer server refused to open.
Verified: lint, typecheck, 26 tests and build all green; both migrations
apply to an empty database; all 10 CHECK constraints and the partial
unique index exist; the seed is idempotent; a wrong password and an
unknown e-mail return the same 401, and the sixth attempt returns 429.
The Command Center reports "not computable yet" instead of a status,
because three of the four inputs to the readiness score arrive in phases
4 to 6. A green light nobody computed is the one lie this product cannot
afford.1 parent 8321cd4 commit 18a9529
60 files changed
Lines changed: 16021 additions & 794 deletions
File tree
- .github/workflows
- docs
- prisma
- migrations
- 20260726172004_initial_schema
- 20260726172108_domain_constraints
- seed
- src
- app
- (auth)/sign-in
- (platform)
- command-center
- api
- auth/[...all]
- health
- components
- shell
- ui
- config
- features/auth
- actions
- schemas
- lib
- auth
- db
- tests
- helpers
- integration
- stubs
- unit
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
14 | 16 | | |
15 | | - | |
16 | | - | |
17 | | - | |
18 | | - | |
19 | | - | |
| 17 | + | |
20 | 18 | | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
32 | | - | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
33 | 49 | | |
34 | 50 | | |
35 | 51 | | |
| |||
108 | 124 | | |
109 | 125 | | |
110 | 126 | | |
111 | | - | |
112 | | - | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
113 | 130 | | |
114 | | - | |
115 | | - | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
116 | 134 | | |
117 | 135 | | |
118 | 136 | | |
119 | | - | |
120 | | - | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
121 | 166 | | |
122 | 167 | | |
123 | 168 | | |
124 | | - | |
| 169 | + | |
125 | 170 | | |
126 | 171 | | |
127 | 172 | | |
| |||
144 | 189 | | |
145 | 190 | | |
146 | 191 | | |
147 | | - | |
| 192 | + | |
148 | 193 | | |
149 | 194 | | |
150 | 195 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
0 commit comments