@terreno/mcp 57.19.2 → 57.20.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/CHANGELOG.md +393 -0
- package/README.md +32 -269
- package/dist/bootstrap.d.ts +12 -0
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +77 -122
- package/dist/bootstrap.js.map +1 -1
- package/dist/docs/guidelines/ui/guidelines/core.md +1 -1
- package/dist/docs/resources/overview.md +4 -2
- package/dist/docs/resources/patterns.md +237 -176
- package/dist/docs/resources/ui.md +48 -17
- package/dist/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +0 -26
- package/dist/docs/templates/bootstrap/frontend/app/_layout.template.tsx +3 -2
- package/dist/docs/templates/bootstrap/frontend/app/login.template.tsx +1 -1
- package/dist/docs/upgrades/57.20.0.md +238 -0
- package/dist/docs/versioned/next/approval-rubric.md +30 -12
- package/dist/docs/versioned/next/explanation/README.md +5 -0
- package/dist/docs/versioned/next/explanation/account-linking.md +169 -0
- package/dist/docs/versioned/next/explanation/admin-interface.md +1 -1
- package/dist/docs/versioned/next/explanation/agent-guidelines.md +173 -0
- package/dist/docs/versioned/next/explanation/ai-development-loop.md +196 -0
- package/dist/docs/versioned/next/explanation/authentication.md +45 -1
- package/dist/docs/versioned/next/explanation/consent-forms.md +105 -0
- package/dist/docs/versioned/next/explanation/example-coverage.md +1 -0
- package/dist/docs/versioned/next/explanation/local-first-data.md +1 -1
- package/dist/docs/versioned/next/explanation/organizations.md +37 -0
- package/dist/docs/versioned/next/explanation/planning-plugins.md +4 -0
- package/dist/docs/versioned/next/explanation/responsive-design.md +143 -0
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +11 -0
- package/dist/docs/versioned/next/explanation/schema-metadata.md +5 -0
- package/dist/docs/versioned/next/how-to/README.md +7 -2
- package/dist/docs/versioned/next/how-to/audit-log.md +22 -3
- package/dist/docs/versioned/next/how-to/automate-terreno-upgrades.md +169 -0
- package/dist/docs/versioned/next/how-to/build-list-detail-screens.md +160 -0
- package/dist/docs/versioned/next/how-to/circleci.md +15 -5
- package/dist/docs/versioned/next/how-to/configure-better-auth.md +673 -53
- package/dist/docs/versioned/next/how-to/create-a-terreno-app.md +8 -5
- package/dist/docs/versioned/next/how-to/debug-with-mcp.md +393 -23
- package/dist/docs/versioned/next/how-to/enable-mcp-oauth.md +54 -3
- package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +9 -0
- package/dist/docs/versioned/next/how-to/password-reset.md +1 -1
- package/dist/docs/versioned/next/how-to/recover-locked-out-user.md +112 -0
- package/dist/docs/versioned/next/how-to/run-tests-locally.md +37 -8
- package/dist/docs/versioned/next/how-to/set-up-social-login.md +424 -0
- package/dist/docs/versioned/next/how-to/set-up-terreno-mcp.md +241 -36
- package/dist/docs/versioned/next/how-to/sign-consent-on-behalf.md +139 -0
- package/dist/docs/versioned/next/how-to/upgrade-terreno.md +4 -0
- package/dist/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +47 -32
- package/dist/docs/versioned/next/implementationPlans/auth-experience.md +952 -0
- package/dist/docs/versioned/next/implementationPlans/consent-forms.md +8 -0
- package/dist/docs/versioned/next/implementationPlans/list-detail-layout.md +297 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/HANDOFF.md +211 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/AnotherWay.html +121 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/BackupCode.html +72 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/Cancelled.html +87 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/ContinueEmail.html +64 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/FirstVisit.html +81 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/Main.html +88 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/ManageAccounts.html +77 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/NotLinked.html +92 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/SigningIn.html +60 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactor.html +76 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactorError.html +81 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/WrongPassword.html +69 -0
- package/dist/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -1
- package/dist/docs/versioned/next/plans/2026-10-23-provisional-consent.md +297 -0
- package/dist/docs/versioned/next/plans/2026-10-29-fix-mcp-oauth-native-and-machine.md +68 -0
- package/dist/docs/versioned/next/reference/README.md +1 -0
- package/dist/docs/versioned/next/reference/account-security.md +700 -0
- package/dist/docs/versioned/next/reference/admin-backend.md +3 -1
- package/dist/docs/versioned/next/reference/admin-frontend.md +225 -1
- package/dist/docs/versioned/next/reference/api.md +461 -35
- package/dist/docs/versioned/next/reference/cli.md +1 -1
- package/dist/docs/versioned/next/reference/comms.md +8 -0
- package/dist/docs/versioned/next/reference/create-terreno-app.md +3 -1
- package/dist/docs/versioned/next/reference/environment-variables.md +9 -3
- package/dist/docs/versioned/next/reference/legacy/rtk.md +19 -3
- package/dist/docs/versioned/next/reference/lifecycle-plugin.md +6 -0
- package/dist/docs/versioned/next/reference/mcp-server.md +1158 -441
- package/dist/docs/versioned/next/reference/syncdb.md +8 -1
- package/dist/docs/versioned/next/reference/ui.md +1271 -27
- package/dist/docs/versioned/next/tasks/ai-dev-loop-boost.md +28 -16
- package/dist/docs/versioned/next/tutorials/README.md +4 -0
- package/dist/httpApp.d.ts +14 -0
- package/dist/httpApp.d.ts.map +1 -0
- package/dist/httpApp.js +99 -0
- package/dist/httpApp.js.map +1 -0
- package/dist/index.js +8 -90
- package/dist/index.js.map +1 -1
- package/dist/local/localTools.d.ts.map +1 -1
- package/dist/local/localTools.js +44 -14
- package/dist/local/localTools.js.map +1 -1
- package/dist/local/metro/metroDevSession.d.ts +18 -0
- package/dist/local/metro/metroDevSession.d.ts.map +1 -1
- package/dist/local/metro/metroDevSession.js +42 -3
- package/dist/local/metro/metroDevSession.js.map +1 -1
- package/dist/local/tools/databaseQuery.d.ts.map +1 -1
- package/dist/local/tools/databaseQuery.js +3 -13
- package/dist/local/tools/databaseQuery.js.map +1 -1
- package/dist/local/tools/databaseSchema.d.ts.map +1 -1
- package/dist/local/tools/databaseSchema.js +3 -13
- package/dist/local/tools/databaseSchema.js.map +1 -1
- package/dist/local/tools/localMongo.d.ts +10 -0
- package/dist/local/tools/localMongo.d.ts.map +1 -0
- package/dist/local/tools/localMongo.js +30 -0
- package/dist/local/tools/localMongo.js.map +1 -0
- package/dist/local/tools/runtime.d.ts +18 -2
- package/dist/local/tools/runtime.d.ts.map +1 -1
- package/dist/local/tools/runtime.js +75 -20
- package/dist/local/tools/runtime.js.map +1 -1
- package/dist/local/tools/syncDb.d.ts +11 -0
- package/dist/local/tools/syncDb.d.ts.map +1 -1
- package/dist/local/tools/syncDb.js +15 -3
- package/dist/local/tools/syncDb.js.map +1 -1
- package/dist/rateLimit.d.ts +35 -0
- package/dist/rateLimit.d.ts.map +1 -0
- package/dist/rateLimit.js +112 -0
- package/dist/rateLimit.js.map +1 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +42 -60
- package/dist/tools.js.map +1 -1
- package/package.json +4 -3
- package/src/docs/guidelines/ui/guidelines/core.md +1 -1
- package/src/docs/resources/overview.md +4 -2
- package/src/docs/resources/patterns.md +237 -176
- package/src/docs/resources/ui.md +48 -17
- package/src/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +0 -26
- package/src/docs/templates/bootstrap/frontend/app/_layout.template.tsx +3 -2
- package/src/docs/templates/bootstrap/frontend/app/login.template.tsx +1 -1
- package/src/docs/upgrades/57.20.0.md +238 -0
- package/src/docs/versioned/next/approval-rubric.md +30 -12
- package/src/docs/versioned/next/explanation/README.md +5 -0
- package/src/docs/versioned/next/explanation/account-linking.md +169 -0
- package/src/docs/versioned/next/explanation/admin-interface.md +1 -1
- package/src/docs/versioned/next/explanation/agent-guidelines.md +173 -0
- package/src/docs/versioned/next/explanation/ai-development-loop.md +196 -0
- package/src/docs/versioned/next/explanation/authentication.md +45 -1
- package/src/docs/versioned/next/explanation/consent-forms.md +105 -0
- package/src/docs/versioned/next/explanation/example-coverage.md +1 -0
- package/src/docs/versioned/next/explanation/local-first-data.md +1 -1
- package/src/docs/versioned/next/explanation/organizations.md +37 -0
- package/src/docs/versioned/next/explanation/planning-plugins.md +4 -0
- package/src/docs/versioned/next/explanation/responsive-design.md +143 -0
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +11 -0
- package/src/docs/versioned/next/explanation/schema-metadata.md +5 -0
- package/src/docs/versioned/next/how-to/README.md +7 -2
- package/src/docs/versioned/next/how-to/audit-log.md +22 -3
- package/src/docs/versioned/next/how-to/automate-terreno-upgrades.md +169 -0
- package/src/docs/versioned/next/how-to/build-list-detail-screens.md +160 -0
- package/src/docs/versioned/next/how-to/circleci.md +15 -5
- package/src/docs/versioned/next/how-to/configure-better-auth.md +673 -53
- package/src/docs/versioned/next/how-to/create-a-terreno-app.md +8 -5
- package/src/docs/versioned/next/how-to/debug-with-mcp.md +393 -23
- package/src/docs/versioned/next/how-to/enable-mcp-oauth.md +54 -3
- package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +9 -0
- package/src/docs/versioned/next/how-to/password-reset.md +1 -1
- package/src/docs/versioned/next/how-to/recover-locked-out-user.md +112 -0
- package/src/docs/versioned/next/how-to/run-tests-locally.md +37 -8
- package/src/docs/versioned/next/how-to/set-up-social-login.md +424 -0
- package/src/docs/versioned/next/how-to/set-up-terreno-mcp.md +241 -36
- package/src/docs/versioned/next/how-to/sign-consent-on-behalf.md +139 -0
- package/src/docs/versioned/next/how-to/upgrade-terreno.md +4 -0
- package/src/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +47 -32
- package/src/docs/versioned/next/implementationPlans/auth-experience.md +952 -0
- package/src/docs/versioned/next/implementationPlans/consent-forms.md +8 -0
- package/src/docs/versioned/next/implementationPlans/list-detail-layout.md +297 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/HANDOFF.md +211 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/AnotherWay.html +121 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/BackupCode.html +72 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/Cancelled.html +87 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/ContinueEmail.html +64 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/FirstVisit.html +81 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/Main.html +88 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/ManageAccounts.html +77 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/NotLinked.html +92 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/SigningIn.html +60 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactor.html +76 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactorError.html +81 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/WrongPassword.html +69 -0
- package/src/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -1
- package/src/docs/versioned/next/plans/2026-10-23-provisional-consent.md +297 -0
- package/src/docs/versioned/next/plans/2026-10-29-fix-mcp-oauth-native-and-machine.md +68 -0
- package/src/docs/versioned/next/reference/README.md +1 -0
- package/src/docs/versioned/next/reference/account-security.md +700 -0
- package/src/docs/versioned/next/reference/admin-backend.md +3 -1
- package/src/docs/versioned/next/reference/admin-frontend.md +225 -1
- package/src/docs/versioned/next/reference/api.md +461 -35
- package/src/docs/versioned/next/reference/cli.md +1 -1
- package/src/docs/versioned/next/reference/comms.md +8 -0
- package/src/docs/versioned/next/reference/create-terreno-app.md +3 -1
- package/src/docs/versioned/next/reference/environment-variables.md +9 -3
- package/src/docs/versioned/next/reference/legacy/rtk.md +19 -3
- package/src/docs/versioned/next/reference/lifecycle-plugin.md +6 -0
- package/src/docs/versioned/next/reference/mcp-server.md +1158 -441
- package/src/docs/versioned/next/reference/syncdb.md +8 -1
- package/src/docs/versioned/next/reference/ui.md +1271 -27
- package/src/docs/versioned/next/tasks/ai-dev-loop-boost.md +28 -16
- package/src/docs/versioned/next/tutorials/README.md +4 -0
|
@@ -5,14 +5,15 @@ import {Stack} from "expo-router";
|
|
|
5
5
|
import * as SplashScreen from "expo-splash-screen";
|
|
6
6
|
import {useEffect} from "react";
|
|
7
7
|
import "react-native-reanimated";
|
|
8
|
-
import {baseUrl,
|
|
8
|
+
import {baseUrl, selectBetterAuthUserId} from "@terreno/rtk";
|
|
9
|
+
import {installTerrenoDevConsoleLogger} from "@terreno/syncdb";
|
|
9
10
|
import {TerrenoProvider} from "@terreno/ui";
|
|
10
11
|
import {Provider, useSelector} from "react-redux";
|
|
11
12
|
import {PersistGate} from "redux-persist/integration/react";
|
|
12
13
|
import {useColorScheme} from "@/components/useColorScheme";
|
|
13
14
|
import store, {persistor} from "@/store/index";
|
|
14
15
|
|
|
15
|
-
installTerrenoDevConsoleLogger();
|
|
16
|
+
installTerrenoDevConsoleLogger({baseUrl});
|
|
16
17
|
|
|
17
18
|
export {ErrorBoundary} from "expo-router";
|
|
18
19
|
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Upgrading to 57.20.0
|
|
2
|
+
|
|
3
|
+
**Action required:** Yes
|
|
4
|
+
**Affected packages:** @terreno/api, @terreno/rtk, @terreno/ui, @terreno/admin-frontend, @terreno/mcp
|
|
5
|
+
|
|
6
|
+
## Summary
|
|
7
|
+
|
|
8
|
+
This release ships the Better Auth account suite: social login, account linking, passkeys,
|
|
9
|
+
two-factor with backup codes, closed sign-up, and staff account recovery (`AccountSecurityApp`,
|
|
10
|
+
org-admin recovery). It adds drop-in `TerrenoLoginScreen` and `TerrenoAccountSecurityScreen`
|
|
11
|
+
screens, `ListDetailLayout`, provisional consent signing, and MCP OAuth client credentials. The
|
|
12
|
+
app `User` claim guard now runs before a Better Auth user is created, which can refuse sign-ups
|
|
13
|
+
that 57.17.0 accepted. Several defaults also change: the desktop layout floor, sign-out timing,
|
|
14
|
+
web autofill, and the hosted MCP URL.
|
|
15
|
+
|
|
16
|
+
## Breaking changes
|
|
17
|
+
|
|
18
|
+
### Better Auth claims an existing app `User` only with a verified email or its password
|
|
19
|
+
|
|
20
|
+
**What changed**
|
|
21
|
+
|
|
22
|
+
A Better Auth sign-up or sign-in links to an unlinked app `User` with the same email (ignoring
|
|
23
|
+
case) only when the provider verified the email, or when the password verifies against that
|
|
24
|
+
`User`'s passport-local-mongoose hash. Otherwise the request answers 409 with
|
|
25
|
+
`code: "VERIFY_EMAIL_TO_USE_ACCOUNT"` and creates no Better Auth user, account, or verification
|
|
26
|
+
email. OAuth callbacks redirect with that `error`. A `User` already bound to another Better Auth
|
|
27
|
+
account is refused with `code: "ACCOUNT_ALREADY_EXISTS"`. `syncBetterAuthUser` throws `APIError`
|
|
28
|
+
409 instead of inserting a duplicate `User`, and takes a new `password` option.
|
|
29
|
+
|
|
30
|
+
`BetterAuthApp` registered without a `userModel` now adopts `TerrenoApp`'s user model, and the
|
|
31
|
+
`terreno-app-user-sync` plugin syncs the app `User` when each session is created. A
|
|
32
|
+
password-proven link that fails to save fails the sign-in with 500 `APP_USER_LINK_FAILED`.
|
|
33
|
+
|
|
34
|
+
**Why**
|
|
35
|
+
|
|
36
|
+
57.17.0 checked the claim only after the Better Auth user existed, so a later email verification
|
|
37
|
+
could still hand that user someone else's `User`.
|
|
38
|
+
|
|
39
|
+
**Migration**
|
|
40
|
+
|
|
41
|
+
Users who sign up with their existing password, or sign in with a provider that verified their
|
|
42
|
+
email, link at once. Users with no password hash (for example GitHub JWT sign-in) must reset their
|
|
43
|
+
password through JWT `POST /auth/forgotPassword` first, or sign in with a verified provider. Keep
|
|
44
|
+
the JWT auth routes mounted while users move. Handle the new 409 codes on your login screen:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import {getLoginFeedback} from "@terreno/rtk";
|
|
48
|
+
|
|
49
|
+
// Before: a generic error for any refused sign-up
|
|
50
|
+
setError(error.message);
|
|
51
|
+
|
|
52
|
+
// After: VERIFY_EMAIL_TO_USE_ACCOUNT reads as "verify your email"
|
|
53
|
+
const {notice, error: errorLine} = getLoginFeedback({
|
|
54
|
+
failure: {code: error.code, message: error.message, source: "response"},
|
|
55
|
+
mode: methods?.accountLinking.mode,
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`TerrenoLoginScreen` from `@terreno/rtk` handles these codes for you. See
|
|
60
|
+
`docs/explanation/account-linking.md`.
|
|
61
|
+
|
|
62
|
+
### `accountLinking: {mode: "never"}` refuses `link-social` up front
|
|
63
|
+
|
|
64
|
+
**What changed**
|
|
65
|
+
|
|
66
|
+
With linking off (`accountLinking: {mode: "never"}` or
|
|
67
|
+
`options.account.accountLinking.enabled: false`), `POST {basePath}/link-social` answers 403
|
|
68
|
+
`LINKING_DISABLED` instead of failing at the provider callback.
|
|
69
|
+
|
|
70
|
+
**Why**
|
|
71
|
+
|
|
72
|
+
The user learns linking is off before leaving for the provider.
|
|
73
|
+
|
|
74
|
+
**Migration**
|
|
75
|
+
|
|
76
|
+
Hide the "Link" action when `useAuthMethods(authClient)` reports linking is off, or treat 403
|
|
77
|
+
`LINKING_DISABLED` as a final answer:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// Before
|
|
81
|
+
await authClient.linkSocial({provider: "google"}); // failed later at the callback
|
|
82
|
+
|
|
83
|
+
// After
|
|
84
|
+
const {methods} = useAuthMethods(authClient);
|
|
85
|
+
const canLink = methods?.accountLinking.mode !== "never";
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Desktop layout floor moves from 768pt to 1024pt
|
|
89
|
+
|
|
90
|
+
**What changed**
|
|
91
|
+
|
|
92
|
+
`Card`, `MarkdownEditor`, the admin shell, and the admin filter drawer use the narrow layout
|
|
93
|
+
below 1024pt instead of 768pt. Admin home stacks its panes below that floor.
|
|
94
|
+
`ADMIN_FILTER_MOBILE_BREAKPOINT` is now 1024, and `isSupportedDesktopWidth({width})` reports the
|
|
95
|
+
floor.
|
|
96
|
+
|
|
97
|
+
**Why**
|
|
98
|
+
|
|
99
|
+
Tablets between 768pt and 1023pt got cramped desktop layouts. See
|
|
100
|
+
`docs/explanation/responsive-design.md`.
|
|
101
|
+
|
|
102
|
+
**Migration**
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// Before
|
|
106
|
+
const isDesktop = width >= 768;
|
|
107
|
+
|
|
108
|
+
// After
|
|
109
|
+
import {isSupportedDesktopWidth} from "@terreno/ui";
|
|
110
|
+
const isDesktop = isSupportedDesktopWidth({width});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Check screens you tuned for 768–1023pt widths, such as iPad portrait.
|
|
114
|
+
|
|
115
|
+
### `logout()` waits for the server before clearing the session
|
|
116
|
+
|
|
117
|
+
**What changed**
|
|
118
|
+
|
|
119
|
+
In `@terreno/rtk` `generateBetterAuthSlice`, `logout()` keeps the session until
|
|
120
|
+
`authClient.signOut()` finishes (`signOutTimeoutMs`, default 10 seconds). Meanwhile
|
|
121
|
+
`isSigningOut` (`selectBetterAuthIsSigningOut`) is true. A sign-out that fails while the session
|
|
122
|
+
is still live stays signed in and sets `error` to `SIGN_OUT_FAILED_MESSAGE`. With `apisToReset`,
|
|
123
|
+
the cache purges when the session clears, not at dispatch.
|
|
124
|
+
|
|
125
|
+
**Why**
|
|
126
|
+
|
|
127
|
+
A login screen that opened at once could find the still-live session and sign the user straight
|
|
128
|
+
back in.
|
|
129
|
+
|
|
130
|
+
**Migration**
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// Before: navigate as soon as logout is dispatched
|
|
134
|
+
dispatch(betterAuth.actions.logout());
|
|
135
|
+
router.replace("/login");
|
|
136
|
+
|
|
137
|
+
// After: navigate when the session clears; show progress meanwhile
|
|
138
|
+
const isSigningOut = useSelector(selectBetterAuthIsSigningOut);
|
|
139
|
+
const isAuthenticated = useSelector(selectBetterAuthIsAuthenticated);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### `TextField` `autoComplete` now reaches the browser
|
|
143
|
+
|
|
144
|
+
**What changed**
|
|
145
|
+
|
|
146
|
+
`TextField` sets `autoComplete` as the input's `autocomplete` attribute on web, and as
|
|
147
|
+
`autoComplete` / `textContentType` on native. `autoComplete="off"` now turns autofill off on web.
|
|
148
|
+
`LoginScreen` and `SignUpScreen` default email to `"username"`.
|
|
149
|
+
|
|
150
|
+
**Why**
|
|
151
|
+
|
|
152
|
+
Password managers could not offer saved credentials or new passwords.
|
|
153
|
+
|
|
154
|
+
**Migration**
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
// Before
|
|
158
|
+
<TextField autoComplete="off" type="email" ... />
|
|
159
|
+
<TextField type="password" ... /> // on a set-new-password form
|
|
160
|
+
|
|
161
|
+
// After
|
|
162
|
+
<TextField autoComplete="username" type="email" ... /> // "email" on sign-up
|
|
163
|
+
<TextField autoComplete="new-password" type="password" ... />
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Hosted Terreno MCP server moves to `https://mcp.terreno.app/mcp`
|
|
167
|
+
|
|
168
|
+
**What changed**
|
|
169
|
+
|
|
170
|
+
Docs and `create-terreno-app` now use `https://mcp.terreno.app/mcp`.
|
|
171
|
+
`https://mcp.terreno.flourish.health/mcp` keeps serving for at least one more release.
|
|
172
|
+
|
|
173
|
+
**Why**
|
|
174
|
+
|
|
175
|
+
The hosted server has a Terreno-owned domain.
|
|
176
|
+
|
|
177
|
+
**Migration**
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
// Before (.cursor/mcp.json, .mcp.json)
|
|
181
|
+
{"url": "https://mcp.terreno.flourish.health/mcp"}
|
|
182
|
+
|
|
183
|
+
// After
|
|
184
|
+
{"url": "https://mcp.terreno.app/mcp"}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Re-run `claude mcp add` registrations with the new URL. See `docs/how-to/set-up-terreno-mcp.md`.
|
|
188
|
+
|
|
189
|
+
### `SyncStatusBanner` waits before showing Offline
|
|
190
|
+
|
|
191
|
+
**What changed**
|
|
192
|
+
|
|
193
|
+
The Offline badge appears only after `isOnline` stays false for `offlineGraceMs` (default 3000 ms).
|
|
194
|
+
|
|
195
|
+
**Why**
|
|
196
|
+
|
|
197
|
+
It flashed Offline during the first socket connect and brief drops.
|
|
198
|
+
|
|
199
|
+
**Migration**
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
// Before: shown immediately
|
|
203
|
+
<SyncStatusBanner />
|
|
204
|
+
|
|
205
|
+
// After: pass 0 to keep the old behavior
|
|
206
|
+
<SyncStatusBanner offlineGraceMs={0} />
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Deprecations
|
|
210
|
+
|
|
211
|
+
- `installTerrenoDevConsoleLogger` moves to `@terreno/syncdb` (also
|
|
212
|
+
`@terreno/syncdb/devConsoleLogger`) and takes `{baseUrl}`. The `@terreno/rtk` export is a
|
|
213
|
+
deprecated wrapper that keeps working with no arguments.
|
|
214
|
+
- `terreno-mcp-local` `get_rtk_state` is a deprecated alias of the new `get_client_state` for one
|
|
215
|
+
release. It returns the same wrapped response, now with a `layer` field.
|
|
216
|
+
|
|
217
|
+
## New capabilities
|
|
218
|
+
|
|
219
|
+
- Better Auth features as config: `accountLinking`, `twoFactor`, `passkey`, `signUp: {mode: "closed"}`,
|
|
220
|
+
`oauthProxy`, `oauthHandoff` linking, and the Sign in with Apple key form. Login screens read
|
|
221
|
+
`GET {basePath}/terreno/methods` through `useAuthMethods`.
|
|
222
|
+
- Drop-in screens in `@terreno/rtk`: `TerrenoLoginScreen` (remembered accounts, passkeys, inline
|
|
223
|
+
two-factor) and `TerrenoAccountSecurityScreen`, built on new `@terreno/ui` security cards,
|
|
224
|
+
`OtpField`, `TwoFactorChallengeScreen`, and `FormContainer`.
|
|
225
|
+
- `AccountSecurityApp` for staff account recovery under `/users/:id/security`, org-admin recovery
|
|
226
|
+
under `/orgs/:orgId/members/:userId/security`, security notification emails, and
|
|
227
|
+
`UserSecurityScreen` / `MemberSecurityPanel` in `@terreno/admin-frontend`.
|
|
228
|
+
- `ListDetailLayout` in `@terreno/ui`, provisional consent signing, and MCP OAuth
|
|
229
|
+
`client_credentials` with loopback redirect ports.
|
|
230
|
+
|
|
231
|
+
## Verification
|
|
232
|
+
|
|
233
|
+
- `bun run compile` and `bun run lint` at the app root.
|
|
234
|
+
- `cd <your frontend> && bun run sdk` to pick up the new auth and security routes.
|
|
235
|
+
- Sign up with an email that matches an existing unverified `User` and confirm a 409
|
|
236
|
+
`VERIFY_EMAIL_TO_USE_ACCOUNT`; sign up with that `User`'s password and confirm it links.
|
|
237
|
+
- Sign out on web and confirm the app does not sign back in before the server answers.
|
|
238
|
+
- Load key screens at 900pt width and confirm the narrow layout reads well.
|
|
@@ -5,11 +5,25 @@ enforced in code before and after this judgment; your level moves only inside th
|
|
|
5
5
|
|
|
6
6
|
## Levels
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
|
|
|
8
|
+
Most features are medium. High is reserved for security-related code.
|
|
9
|
+
|
|
10
|
+
| Level | Human approvals | Meaning | Examples |
|
|
11
|
+
| --- | --- | --- | --- |
|
|
12
|
+
| low | None, once automated review and security checks are green | A mistake is cheap and visible; the PR proves itself | docs; copy and i18n strings; tests, snapshots, fixtures; internal-only features (admin screens, dev tooling, scripts no user runs); additive changes behind a feature flag that defaults off; lint autofix; regenerated files whose source changed in the same PR; changelog fragments; dependency patch bumps with green CI; one-line bug fixes with a regression test; small presentational UI tweaks (see below) |
|
|
13
|
+
| medium | One person | Ordinary product changes, and changes to sensitive areas | new or changed behavior with tests; UI changes with screenshots beyond a small presentational tweak; new endpoints with permission tests; schema changes, migrations, and backfills; caching (RTK tags, syncdb, HTTP/CDN caching); app updates (expo-updates, `eas.json`, `app.json`, release channels); AI system prompts and tool definitions; billing; infra, Dockerfiles, CI; public API shape (removed, renamed, or retyped exports, props, or endpoints); dependency minor and major bumps; feature-flag removals; hotfix branches; deleted or skipped tests |
|
|
14
|
+
| high | One person | Security-related code, where a mistake is costly, hidden, or hard to undo | auth, sessions, tokens, Better Auth and Passport config; permissions, RBAC, `queryFilter`s that scope data to a user; MCP OAuth; `socketAuth` and `sessionRevalidation`; secrets and env files; crypto, hashing, signature verification (webhook verifiers); PHI handling, consent, and audit logging; rate limits on unauthenticated routes; CORS and trusted origins; IAM, network, and firewall rules in infra; dependencies added for auth, crypto, or parsing untrusted input; reverts of security changes |
|
|
15
|
+
|
|
16
|
+
A PR is high only when it changes how the system decides who may see or do something,
|
|
17
|
+
or how secrets, credentials, or PHI are handled. Touching a security-adjacent file for an
|
|
18
|
+
unrelated reason (a typo, a log message, a rename) is judged by what the change does.
|
|
19
|
+
|
|
20
|
+
A **small presentational UI tweak** is low. It changes how one component looks or when it
|
|
21
|
+
shows something (layout, spacing, copy, color, a display delay or debounce, an animation)
|
|
22
|
+
and nothing else: no data fetching or syncdb/RTK changes, no navigation, no change to what
|
|
23
|
+
a user can submit or do, no auth or consent screens. It may add an optional prop with a
|
|
24
|
+
default, and that default may change what every consumer sees, as long as the old look is
|
|
25
|
+
one prop away. It still needs tests that would fail without the change and before/after
|
|
26
|
+
screenshots or video. A changelog fragment and docs update do not raise it.
|
|
13
27
|
|
|
14
28
|
## Dimensions
|
|
15
29
|
|
|
@@ -27,7 +41,8 @@ Score each 0–3 (0 absent, 1 weak, 2 adequate, 3 strong) and give reasons a rev
|
|
|
27
41
|
or weakened; snapshot updates explained; a coverage gate check green when present.
|
|
28
42
|
- **Evidence**: the `Verification` table names commands and outcomes, not just commands;
|
|
29
43
|
UI changes carry screenshots or video; CI is green on this head; `⚠️ Not tested` rows
|
|
30
|
-
are absent or
|
|
44
|
+
are absent or each carries an explicit reviewer instruction (the blocked behavior and the
|
|
45
|
+
exact steps to check it); each plan acceptance criterion
|
|
31
46
|
has its named verification present.
|
|
32
47
|
|
|
33
48
|
## Architecture: Terreno rules
|
|
@@ -99,13 +114,14 @@ A finding here is blocking when it is certain and should-fix when it is likely.
|
|
|
99
114
|
- A new runtime dependency is named in the PR body with the reason and is on the catalog
|
|
100
115
|
in `.rulesync/rules/01-dependency-catalog.md` or justified against it; a missing reason
|
|
101
116
|
lowers Architecture. Dev-only additions need no reason.
|
|
102
|
-
- Patch bumps of catalog packages with green CI stay low. Minor bumps
|
|
103
|
-
|
|
104
|
-
|
|
117
|
+
- Patch bumps of catalog packages with green CI stay low. Minor and major bumps, Expo SDK
|
|
118
|
+
and React Native upgrades, Mongoose majors, and lockfile-only changes without a
|
|
119
|
+
`package.json` change are medium. Better Auth and other auth or crypto majors are high.
|
|
105
120
|
- A bump that changes generated output (`bun.lock`, snapshots, `openApiSdk.ts`,
|
|
106
121
|
`plugins/terreno-claude/`) ships the regenerated output in the same PR.
|
|
107
122
|
- A new dependency's license passes `bun run check:licenses`.
|
|
108
|
-
- A `patches/` change is high
|
|
123
|
+
- A `patches/` change is medium (high when it patches an auth, crypto, or parsing
|
|
124
|
+
dependency): it edits a third party and needs the upstream issue linked.
|
|
109
125
|
- Removing a dependency the code still imports is a correctness finding; `knip` must stay
|
|
110
126
|
at zero findings.
|
|
111
127
|
|
|
@@ -136,11 +152,13 @@ A finding here is blocking when it is certain and should-fix when it is likely.
|
|
|
136
152
|
|
|
137
153
|
## Signals that move the level
|
|
138
154
|
|
|
139
|
-
- Raise one level
|
|
155
|
+
- Raise one level, never above medium unless the change is security-related: more than
|
|
156
|
+
two packages touched; deletions outnumber additions in
|
|
140
157
|
source; a `TODO`, `FIXME`, or commented-out code added; explicit `any` added; PR body
|
|
141
158
|
missing `Why`, `What changed`, or `Verification`; `WIP` in the title; the plan edited
|
|
142
159
|
in the same PR to match the code.
|
|
143
|
-
- Never low: new logic without a test; a `⚠️ Not tested` row without
|
|
160
|
+
- Never low: new logic without a test; a `⚠️ Not tested` row without an explicit reviewer instruction
|
|
161
|
+
(a row that does not name the blocked behavior and the exact steps to check it); an unresolved bot thread;
|
|
144
162
|
a revert.
|
|
145
163
|
- Size never moves the level. Changed lines and file counts say nothing about risk on
|
|
146
164
|
their own: judge what the lines do. Pure renames and moves, regenerated SDK or
|
|
@@ -15,12 +15,15 @@ Understanding-oriented documentation: concepts, architecture, and context.
|
|
|
15
15
|
- [Loop engineering](loop-engineering.md) — Fresh-invocation lifecycle, state, evidence, and orchestration boundaries
|
|
16
16
|
- [GitHub issue lifecycle](../how-to/github-issue-lifecycle.md) — Pick-ready issues and plan comments for Pick/Roast
|
|
17
17
|
- [Install agent skills](../how-to/install-agent-skills.md) — `npx skills add TerrenoLabs/terreno`
|
|
18
|
+
- [AI development loop](ai-development-loop.md) — The five-stage Ask, Generate, Run, Observe, Fix loop, what the agent can and cannot see, and how it composes with expo-mcp, Playwright MCP, and Maestro
|
|
19
|
+
- [Agent guidelines](agent-guidelines.md) — How hosted docs, per-package `.ai/` guidelines, and `.rulesync/` rules reach an agent, and how apps extend them
|
|
18
20
|
- [AI observability](ai-observability.md) — Two planes (telemetry vs control), plugins vs LangfuseApp, SOP loop
|
|
19
21
|
- [Durable agent harness](durable-agent-harness.md) — Phases, checkpoints, and why the audit span shares the checkpoint transaction
|
|
20
22
|
- [AI-powered workflows](ai-workflows.md) — Autonomous documentation, testing, and maintenance workflows
|
|
21
23
|
- [Agent UI Asks](agent-ui-asks.md) — Why agent questions are client-side tool calls, the pause and resume round trip, why cards carry exact answers, the watch paths, asks vs blocks
|
|
22
24
|
- [Agent UI blocks](agent-ui-blocks.md) — Why chat replies use a closed YAML catalog instead of free-form UI, why steppers and checklists call the server, and the rich-blocks rollout
|
|
23
25
|
- [Authentication architecture](authentication.md) — Better Auth, JWT, and optional MCP service tokens
|
|
26
|
+
- [Account linking](account-linking.md) — Linking modes, why both emails must be verified, Apple relay emails, and migrating JWT users
|
|
24
27
|
- [Organization tenancy](organizations.md) — Optional Membership-backed tenancy, context, and isolation boundaries
|
|
25
28
|
- [Configuration system](configuration-system.md) — Runtime configuration with database persistence
|
|
26
29
|
- [Dependency management](dependency-management.md) — Catalog pins, exercise tests, fingerprint freeze
|
|
@@ -34,8 +37,10 @@ Understanding-oriented documentation: concepts, architecture, and context.
|
|
|
34
37
|
- [Versioning policy](versioning-policy.md) — Lockstep `@terreno/*` versions, pre-1.0 breaks, deprecation window
|
|
35
38
|
- [How admin interfaces are shaped](admin-interface.md) — Screens, sidebar, `apiBase` vs `routeBase`
|
|
36
39
|
- [Admin plugin frontend widgets](admin-plugin-frontend.md) — Widget IDs from backend plugins
|
|
40
|
+
- [Consent forms](consent-forms.md) — Versions, responses, the consent gate, and why provisional signatures don't clear it
|
|
37
41
|
- [Consent admin migration](admin-consent-migration.md) — Which consent screens stay hand-written
|
|
38
42
|
- [Public roadmap process](roadmap-process.md) — GitHub roadmap vs Linear execution
|
|
39
43
|
- [Example app coverage](example-coverage.md) — Which framework capabilities the example apps exercise
|
|
40
44
|
- [Repository settings](repository-settings.md) — Maintainer GitHub settings that cannot be committed
|
|
41
45
|
- [Why Terreno owns chart SVG](charts.md) — Owned `react-native-svg` vs victory-native
|
|
46
|
+
- [Responsive design](responsive-design.md) — Support levels, breakpoints, and the desktop floor
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Account linking
|
|
2
|
+
|
|
3
|
+
One person can sign in to a Terreno app in several ways: email and password, Google, GitHub,
|
|
4
|
+
Apple, or a passkey. Account linking decides what happens when a new sign-in method arrives with
|
|
5
|
+
an email that already belongs to an account. Done carelessly, it is an account takeover. This page
|
|
6
|
+
explains the three modes, why Terreno asks for proof of the email before it links anything, and
|
|
7
|
+
how apps that started on JWT auth move their users over.
|
|
8
|
+
|
|
9
|
+
Setup: [Auth features](../how-to/configure-better-auth.md#auth-features). Reference:
|
|
10
|
+
[`GET {basePath}/terreno/methods`](../reference/api.md#better-auth-authentication).
|
|
11
|
+
|
|
12
|
+
## Two layers of accounts
|
|
13
|
+
|
|
14
|
+
A Terreno app with Better Auth has two records per person:
|
|
15
|
+
|
|
16
|
+
- **The Better Auth user** (`user` collection) owns sign-in. Each sign-in method is an `account`
|
|
17
|
+
row under it: `credential` for email and password, `google`, `github`, and so on. Better Auth
|
|
18
|
+
keeps emails unique, so there is at most one Better Auth user per email.
|
|
19
|
+
- **The app `User`** (your Mongoose model) owns everything else: permissions, `admin`,
|
|
20
|
+
`disabled`, org memberships, and your data. It points at its Better Auth user through
|
|
21
|
+
`betterAuthId`.
|
|
22
|
+
|
|
23
|
+
Linking happens at both layers. Better Auth decides whether a new provider `account` joins an
|
|
24
|
+
existing Better Auth user. Terreno decides whether a Better Auth user may claim an existing app
|
|
25
|
+
`User` (`syncBetterAuthUser`).
|
|
26
|
+
|
|
27
|
+
## The three modes
|
|
28
|
+
|
|
29
|
+
Set `accountLinking: {mode}` on `BetterAuthConfig`. The mode controls the Better Auth layer.
|
|
30
|
+
|
|
31
|
+
| Mode | A same-email sign-in with a new method | `POST {basePath}/link-social` |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `verified-email` (default) | Joins the account when both emails are verified | Links |
|
|
34
|
+
| `explicit` | Refused with `account_not_linked`; nothing is created | Links, from a signed-in session |
|
|
35
|
+
| `never` | Refused with `account_not_linked`; nothing is created | Refused with 403 `LINKING_DISABLED` |
|
|
36
|
+
|
|
37
|
+
- **`verified-email`** is the least friction: someone who signed up with email and password and
|
|
38
|
+
later taps "Continue with Google" on the same address lands in the same account.
|
|
39
|
+
- **`explicit`** makes the user prove they own the existing account first. They sign in the old
|
|
40
|
+
way, open the Security screen, and link the provider there. A login screen should turn
|
|
41
|
+
`account_not_linked` into "sign in with your original method, then link this one".
|
|
42
|
+
- **`never`** keeps every sign-in method separate. Because Better Auth keeps emails unique, a
|
|
43
|
+
second method on the same email cannot sign in at all. Pick it when a shared inbox must not
|
|
44
|
+
join two people's records.
|
|
45
|
+
|
|
46
|
+
`/terreno/methods` reports the mode so clients can word their guidance. Unlinking the only
|
|
47
|
+
remaining sign-in method is refused in every mode (400 `FAILED_TO_UNLINK_LAST_ACCOUNT`), so a user
|
|
48
|
+
cannot lock themselves out. rtk's `TerrenoAccountSecurityScreen` checks the same rule before it
|
|
49
|
+
asks, and a social-only user can add a password there (`POST {basePath}/terreno/set-password`) to
|
|
50
|
+
gain a second method before unlinking a provider.
|
|
51
|
+
|
|
52
|
+
### What the refusals look like
|
|
53
|
+
|
|
54
|
+
| Path | `account_not_linked` | Unverified email matches an app `User` |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| OAuth redirect (`/callback/{provider}`) | Redirect with `?error=account_not_linked` | Redirect with `?error=VERIFY_EMAIL_TO_USE_ACCOUNT` |
|
|
57
|
+
| ID-token sign-in (`/sign-in/social` with `idToken`) | 401, `message: "account not linked"` | 409, `code: "VERIFY_EMAIL_TO_USE_ACCOUNT"` |
|
|
58
|
+
| Email sign-up or sign-in | n/a | 409, `code: "VERIFY_EMAIL_TO_USE_ACCOUNT"`, `message: "Verify your email to use this account"` |
|
|
59
|
+
|
|
60
|
+
## Why both emails must be verified
|
|
61
|
+
|
|
62
|
+
Linking by email trusts that whoever controls the email controls the account. That trust has to
|
|
63
|
+
hold on both sides:
|
|
64
|
+
|
|
65
|
+
- **The new method's email.** A provider that reports an unverified email has not checked that
|
|
66
|
+
the person owns it. Linking on it lets anyone who types your address into that provider into
|
|
67
|
+
your account. An unverified provider email never links, in any mode. `trustedProviders` turns
|
|
68
|
+
this check off for the providers it lists, so list only providers that prove email ownership.
|
|
69
|
+
- **The existing account's email.** If the existing account was itself created without proving
|
|
70
|
+
the email, an attacker can **pre-register**: sign up with the victim's address and a password
|
|
71
|
+
the attacker knows, wait for the victim to sign in with Google, and share the account.
|
|
72
|
+
Terreno keeps Better Auth's `requireLocalEmailVerified: true` in every mode, so an unverified
|
|
73
|
+
local account never absorbs a new method.
|
|
74
|
+
|
|
75
|
+
The same rule applies one layer down. A Better Auth user claims an existing app `User` only when
|
|
76
|
+
the provider verified its email, or when the user proves they own that `User` another way (see
|
|
77
|
+
the migration below). Terreno checks this **before the Better Auth user is created**. An
|
|
78
|
+
unverified sign-up for someone else's address gets 409 "Verify your email to use this account"
|
|
79
|
+
and leaves nothing behind: no Better Auth user, no `account` row, no verification email. That
|
|
80
|
+
matters because a Better Auth user left behind would be a pre-registration one layer down: once
|
|
81
|
+
anyone verified that email (the victim clicking the mail the attacker's sign-up triggered, or an
|
|
82
|
+
admin), the attacker's password would open the victim's `User`. Terreno never inserts a second
|
|
83
|
+
`User` for the same email either.
|
|
84
|
+
|
|
85
|
+
Two more rules hold whichever way the claim is proven. A `User` that already carries another
|
|
86
|
+
Better Auth account's `betterAuthId` is never relinked by email (409 `ACCOUNT_ALREADY_EXISTS`).
|
|
87
|
+
And when a verified email links a `User` whose own `emailVerified` is not `true`, the `User`'s
|
|
88
|
+
legacy password is removed and its `tokenEpoch` bumped: whoever set that password never proved
|
|
89
|
+
they own the mailbox, so it and the JWT refresh tokens issued from it stop working. A link proven
|
|
90
|
+
by the legacy password itself keeps that password. Operator seeds pass `trustEmail` to
|
|
91
|
+
`syncBetterAuthUser`, which skips these checks for accounts the seed created itself.
|
|
92
|
+
|
|
93
|
+
Emails are compared ignoring case, so a legacy `User` stored as `Jane@Example.com` is protected
|
|
94
|
+
from a sign-up as `jane@example.com`, and claiming it never creates a second `User`.
|
|
95
|
+
|
|
96
|
+
**Trade-off: the 409 reveals that an account exists.** A sign-up that gets 409
|
|
97
|
+
`VERIFY_EMAIL_TO_USE_ACCOUNT` tells the caller that an app `User` already has that email. Terreno
|
|
98
|
+
accepts this account-existence signal: refusing silently would either leave the
|
|
99
|
+
pre-registration hole open or leave legitimate users stuck with no explanation, and Better Auth's
|
|
100
|
+
own sign-up already answers `USER_ALREADY_EXISTS` for emails it knows. Apps that must hide
|
|
101
|
+
membership (for example, where an account implies care) should rate-limit sign-up (the `auth`
|
|
102
|
+
bucket) and word the client message neutrally.
|
|
103
|
+
|
|
104
|
+
The guard needs a user model on the Better Auth instance. `BetterAuthApp` uses its own
|
|
105
|
+
`userModel`, or adopts `TerrenoApp`'s when it was given none, before it creates Better Auth. If you
|
|
106
|
+
call `createBetterAuth` yourself, pass `userModel` to it whenever a session middleware links app
|
|
107
|
+
`User`s.
|
|
108
|
+
|
|
109
|
+
Terreno then syncs the app `User` as each Better Auth session is created, while the request that
|
|
110
|
+
proves who the user is (its password, its provider) is still in hand. That is also when
|
|
111
|
+
`oauthProvider` is recorded. The session check repeats the claim rule as a second guard, and a
|
|
112
|
+
password-proven link that fails to save fails the sign-in (500 `APP_USER_LINK_FAILED`) instead of
|
|
113
|
+
leaving a session that every request would refuse.
|
|
114
|
+
|
|
115
|
+
## Apple private-relay emails
|
|
116
|
+
|
|
117
|
+
Sign in with Apple lets users hide their address. Apple then reports a relay address such as
|
|
118
|
+
`abc123@privaterelay.appleid.com`, marked verified, that forwards to the real inbox. It never
|
|
119
|
+
matches the email the person used elsewhere, so Apple sign-in creates a separate account in every
|
|
120
|
+
mode. Users who want one account sign in their usual way and link Apple from the Security screen
|
|
121
|
+
(`/link-social`). That link works only when Apple shares the real address ("Share My Email"):
|
|
122
|
+
Better Auth refuses a link whose email differs from the account's unless `allowDifferentEmails`
|
|
123
|
+
is on. Do not merge relay accounts by name: a name proves nothing.
|
|
124
|
+
|
|
125
|
+
## Several accounts on one device
|
|
126
|
+
|
|
127
|
+
Linking answers "is this the same person?". Some people really have several accounts, for example
|
|
128
|
+
a parent with their own login and a child's. They should not merge them. `multiSession` lets one
|
|
129
|
+
device hold several signed-in accounts and switch between them without signing out, and
|
|
130
|
+
`signUp: {mode: "closed"}` stops new accounts altogether. Together with the linking mode, these
|
|
131
|
+
are the three knobs an app has over "multiple accounts".
|
|
132
|
+
|
|
133
|
+
## Migrating users from JWT auth
|
|
134
|
+
|
|
135
|
+
Apps that started on Terreno's JWT and passport auth already have app `User`s with
|
|
136
|
+
passport-local-mongoose password hashes. When such a user first signs in through Better Auth with
|
|
137
|
+
the same email, Terreno links the new Better Auth user to their existing `User` if either:
|
|
138
|
+
|
|
139
|
+
- the sign-in comes from a provider (Google, GitHub, Apple, ...) that reports the email as
|
|
140
|
+
verified, or
|
|
141
|
+
- the password they sign up with verifies against the `User`'s existing passport hash. The
|
|
142
|
+
plaintext is checked only during that request and never stored. The check does not count as a
|
|
143
|
+
failed JWT login, so with passport's `limitAttempts` a stranger signing up with the victim's
|
|
144
|
+
email cannot lock the victim out.
|
|
145
|
+
|
|
146
|
+
Otherwise the sign-up answers 409 "Verify your email to use this account" and nothing is created.
|
|
147
|
+
Verifying the email afterwards cannot link anything, because there is no Better Auth user to
|
|
148
|
+
verify. Before this change, any unverified sign-up claimed the `User` with the same email, which
|
|
149
|
+
let anyone take over a legacy account by signing up with its address.
|
|
150
|
+
|
|
151
|
+
Users who signed in with GitHub through JWT have no password hash. They get in one of three ways:
|
|
152
|
+
|
|
153
|
+
- a provider sign-in whose email the provider verified;
|
|
154
|
+
- a password reset sent to their email through the JWT forgot-password flow
|
|
155
|
+
(`POST /auth/forgotPassword`), then a Better Auth sign-up with the new password;
|
|
156
|
+
- an admin-issued account recovery link (see [account security](../reference/api.md#better-auth-authentication)
|
|
157
|
+
once recovery is enabled) that sets their password and creates the Better Auth user.
|
|
158
|
+
|
|
159
|
+
To migrate smoothly:
|
|
160
|
+
|
|
161
|
+
1. Keep the JWT auth routes mounted while users move over, so forgot-password still works.
|
|
162
|
+
2. Tell users to sign up with the email and password they already use; those link at once.
|
|
163
|
+
3. Point users without a password at a provider sign-in or at JWT forgot-password.
|
|
164
|
+
|
|
165
|
+
## Learn more
|
|
166
|
+
|
|
167
|
+
- [Authentication architecture](authentication.md)
|
|
168
|
+
- [Configure Better Auth](../how-to/configure-better-auth.md)
|
|
169
|
+
- [Better Auth: users and accounts](https://www.better-auth.com/docs/concepts/users-accounts#account-linking)
|
|
@@ -55,7 +55,7 @@ row is `AuditEvent` when `AuditApp` is registered. `isAuditLogModel` also matche
|
|
|
55
55
|
legacy `AdminAuditLog`, `audit-log`, and `audit-events` names. Jobs is a custom
|
|
56
56
|
screen (`name: "jobs"`); the shell lifts it the same way.
|
|
57
57
|
|
|
58
|
-
Below
|
|
58
|
+
Below the desktop floor (1024pt) the rail becomes a hamburger drawer. The main column is a body-style
|
|
59
59
|
canvas (`neutral-050`, or `neutral-900` in dark mode so `surface.base` cards stand out).
|
|
60
60
|
The colorful teal rail has no dark palette, so dark mode always uses the clinical rail.
|
|
61
61
|
Host apps must give React Navigation a theme that follows the Terreno color scheme
|