@lowdefy/docs-content 0.0.0-experimental-20260928120643 → 6.1.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/README.md +0 -1
- package/content/actions/link.md +0 -17
- package/content/actions/login.md +5 -23
- package/content/actions/logout.md +5 -5
- package/content/actions/pdfmake.md +2 -2
- package/content/actions/setdarkmode.md +2 -2
- package/content/agents/agent-properties.md +2 -4
- package/content/agents/agentchat.md +6 -21
- package/content/agents/agentconversations.md +2 -2
- package/content/concepts/ai-agent-docs.md +101 -313
- package/content/concepts/cli.md +3 -34
- package/content/concepts/connections-and-requests.md +0 -402
- package/content/concepts/custom-html.md +0 -40
- package/content/concepts/custom-styling.md +5 -50
- package/content/concepts/events-and-actions.md +2 -64
- package/content/concepts/i18n.md +2 -7
- package/content/concepts/keyboard-shortcuts.md +2 -2
- package/content/concepts/layout-overview.md +1 -1
- package/content/concepts/logger.md +0 -81
- package/content/concepts/lowdefy-schema.md +1 -4
- package/content/concepts/menus.md +7 -7
- package/content/concepts/module-authoring.md +2 -2
- package/content/connections/anthropic.md +2 -162
- package/content/connections/awss3.md +0 -54
- package/content/connections/google.md +2 -162
- package/content/connections/mongodb.md +3 -121
- package/content/connections/openai-connection.md +2 -162
- package/content/container-blocks/affix.md +7 -47
- package/content/container-blocks/alert.md +24 -76
- package/content/container-blocks/badge.md +24 -100
- package/content/container-blocks/box.md +9 -9
- package/content/container-blocks/card.md +25 -80
- package/content/container-blocks/carousel.md +7 -60
- package/content/container-blocks/collapse.md +9 -87
- package/content/container-blocks/configprovider.md +13 -35
- package/content/container-blocks/confirmmodal.md +23 -69
- package/content/container-blocks/content.md +98 -62
- package/content/container-blocks/descriptions.md +4 -45
- package/content/container-blocks/drawer.md +28 -92
- package/content/container-blocks/dropdownmenu.md +31 -119
- package/content/container-blocks/flex.md +62 -40
- package/content/container-blocks/footer.md +11 -15
- package/content/container-blocks/googlemapsheatmap.md +1 -1
- package/content/container-blocks/googlemapsscript.md +2 -2
- package/content/container-blocks/header.md +36 -71
- package/content/container-blocks/label.md +7 -34
- package/content/container-blocks/layout.md +34 -52
- package/content/container-blocks/masonry.md +71 -17
- package/content/container-blocks/mobilemenu.md +88 -131
- package/content/container-blocks/modal.md +26 -114
- package/content/container-blocks/pageheadermenu.md +31 -81
- package/content/container-blocks/pagesidebarlayout.md +26 -169
- package/content/container-blocks/pagesidermenu.md +42 -95
- package/content/container-blocks/popover.md +31 -34
- package/content/container-blocks/result.md +21 -32
- package/content/container-blocks/sider.md +122 -85
- package/content/container-blocks/span.md +7 -7
- package/content/container-blocks/spinner.md +3 -3
- package/content/container-blocks/splitter.md +68 -150
- package/content/container-blocks/tabs.md +12 -64
- package/content/container-blocks/tooltip.md +46 -22
- package/content/container-blocks/watermark.md +4 -22
- package/content/controls/reject.md +2 -4
- package/content/controls/throw.md +0 -1
- package/content/controls/try.md +2 -3
- package/content/deployment/deployment-environments.md +6 -8
- package/content/deployment/docker.md +7 -22
- package/content/deployment/node-server.md +0 -4
- package/content/deployment/vercel.md +6 -13
- package/content/display-blocks/aggrid.md +11 -26
- package/content/display-blocks/anchor.md +15 -15
- package/content/display-blocks/avatar.md +37 -57
- package/content/display-blocks/breadcrumb.md +21 -70
- package/content/display-blocks/button.md +18 -92
- package/content/display-blocks/calendar.md +0 -12
- package/content/display-blocks/dangeroushtml.md +1 -1
- package/content/display-blocks/difflist.md +1 -1
- package/content/display-blocks/difftimeline.md +1 -1
- package/content/display-blocks/divider.md +3 -64
- package/content/display-blocks/dropdownbutton.md +28 -79
- package/content/display-blocks/echart.md +1 -35
- package/content/display-blocks/floatbutton.md +42 -129
- package/content/display-blocks/html.md +1 -61
- package/content/display-blocks/icon.md +67 -250
- package/content/display-blocks/menu.md +73 -145
- package/content/display-blocks/message.md +124 -38
- package/content/display-blocks/notification.md +222 -94
- package/content/display-blocks/paragraph.md +9 -59
- package/content/display-blocks/progress.md +17 -96
- package/content/display-blocks/qrcode.md +4 -19
- package/content/display-blocks/s3download.md +1 -1
- package/content/display-blocks/search.md +7 -17
- package/content/display-blocks/skeletonbutton.md +0 -1
- package/content/display-blocks/skeletoninput.md +0 -1
- package/content/display-blocks/statistic.md +17 -75
- package/content/display-blocks/steps.md +25 -58
- package/content/display-blocks/tag.md +17 -102
- package/content/display-blocks/title.md +11 -50
- package/content/display-blocks/tour.md +23 -33
- package/content/input-blocks/autocomplete.md +9 -81
- package/content/input-blocks/buttonselector.md +8 -45
- package/content/input-blocks/checkboxselector.md +7 -97
- package/content/input-blocks/checkboxswitch.md +3 -33
- package/content/input-blocks/colorselector.md +14 -63
- package/content/input-blocks/daterangeselector.md +45 -165
- package/content/input-blocks/dateselector.md +41 -139
- package/content/input-blocks/datetimeselector.md +49 -158
- package/content/input-blocks/inputgallery.md +3 -3
- package/content/input-blocks/monthselector.md +52 -120
- package/content/input-blocks/multipleselector.md +39 -150
- package/content/input-blocks/numberinput.md +12 -98
- package/content/input-blocks/pagination.md +14 -70
- package/content/input-blocks/paragraphinput.md +37 -51
- package/content/input-blocks/passwordinput.md +6 -60
- package/content/input-blocks/phonenumberinput.md +26 -59
- package/content/input-blocks/radioselector.md +7 -83
- package/content/input-blocks/ratingslider.md +26 -57
- package/content/input-blocks/s3uploadbutton.md +24 -24
- package/content/input-blocks/s3uploaddragger.md +3 -3
- package/content/input-blocks/s3uploadphoto.md +5 -5
- package/content/input-blocks/segmentedselector.md +32 -72
- package/content/input-blocks/selector.md +21 -109
- package/content/input-blocks/switch.md +17 -65
- package/content/input-blocks/textarea.md +3 -32
- package/content/input-blocks/textinput.md +28 -69
- package/content/input-blocks/tiptapmentioninput.md +1 -1
- package/content/input-blocks/titleinput.md +12 -39
- package/content/input-blocks/treeinput.md +25 -49
- package/content/input-blocks/treemultipleselector.md +10 -127
- package/content/input-blocks/treeselector.md +9 -97
- package/content/input-blocks/upload.md +15 -64
- package/content/input-blocks/uploaddragger.md +1 -17
- package/content/input-blocks/uploadphoto.md +1 -18
- package/content/input-blocks/weekselector.md +55 -120
- package/content/list-blocks/controlledlist.md +15 -24
- package/content/list-blocks/list.md +11 -11
- package/content/list-blocks/masonrylist.md +34 -7
- package/content/migration/upgrade-guide.md +0 -1
- package/content/operators/_app.md +1 -2
- package/content/operators/_error.md +1 -23
- package/content/operators/_get.md +6 -6
- package/content/operators/_operator.md +3 -17
- package/content/operators/_ref.md +0 -258
- package/content/operators/_type.md +3 -14
- package/content/operators/_user.md +3 -3
- package/content/other/introduction.md +1 -1
- package/content/plugins/plugins-dev.md +0 -66
- package/content/plugins/plugins-introduction.md +1 -27
- package/content/testing/e2e-configuration.md +1 -8
- package/content/testing/e2e-getting-started.md +1 -1
- package/content/testing/e2e-introduction.md +1 -1
- package/content/tutorial/tutorial-actions-operators.md +2 -2
- package/content/tutorial/tutorial-add-blocks.md +2 -2
- package/content/tutorial/tutorial-create-page.md +2 -48
- package/content/tutorial/tutorial-display-data-page.md +3 -3
- package/content/tutorial/tutorial-requests-sql.md +0 -153
- package/content/tutorial/tutorial-start.md +1 -66
- package/content/user-authentication/login-and-logout.md +8 -158
- package/content/user-authentication/users-introduction.md +0 -8
- package/index.json +2 -6065
- package/package.json +1 -7
- package/scripts/extractAgentDocs.js +4 -36
- package/content/actions/emailotpsend.md +0 -58
- package/content/actions/emailotpverify.md +0 -77
- package/content/actions/getlocalstorage.md +0 -41
- package/content/actions/magiclinkverify.md +0 -113
- package/content/actions/posthogalias.md +0 -29
- package/content/actions/posthogcapture.md +0 -49
- package/content/actions/posthogcapturepageleave.md +0 -38
- package/content/actions/posthogfeatureflag.md +0 -50
- package/content/actions/posthoggroup.md +0 -37
- package/content/actions/posthogidentify.md +0 -54
- package/content/actions/posthoginit.md +0 -66
- package/content/actions/posthogoptin.md +0 -29
- package/content/actions/posthogoptout.md +0 -29
- package/content/actions/posthogpageview.md +0 -37
- package/content/actions/posthogreloadfeatureflags.md +0 -47
- package/content/actions/posthogreset.md +0 -28
- package/content/actions/posthogsetpersonproperties.md +0 -36
- package/content/actions/removelocalstorage.md +0 -26
- package/content/actions/setlocalstorage.md +0 -32
- package/content/adapter-reference/mongodbauthadapter.md +0 -46
- package/content/agents/agents-introduction.md +0 -107
- package/content/callback-reference/auth0logoutcallback.md +0 -54
- package/content/concepts/blocks.md +0 -150
- package/content/concepts/dynamic-page-content.md +0 -304
- package/content/concepts/html-attributes.md +0 -285
- package/content/concepts/lowdefy-api.md +0 -1110
- package/content/concepts/notifications.md +0 -155
- package/content/concepts/page-and-app-state.md +0 -244
- package/content/concepts/theming.md +0 -582
- package/content/connections/aigateway.md +0 -368
- package/content/connections/axioshttp.md +0 -181
- package/content/controls/while.md +0 -46
- package/content/display-blocks/clickablehtml.md +0 -239
- package/content/display-blocks/eventlog.md +0 -363
- package/content/display-blocks/pollingtimer.md +0 -263
- package/content/input-blocks/captcha.md +0 -40
- package/content/input-blocks/placesautocomplete.md +0 -319
- package/content/input-blocks/tagmultipleselector.md +0 -114
- package/content/input-blocks/tagselector.md +0 -114
- package/content/migration/auth-upgrade.md +0 -327
- package/content/migration/v3-to-v4.md +0 -417
- package/content/migration/v6-to-v7.md +0 -222
- package/content/operators/_cron.md +0 -292
- package/content/other/mongodbadapter.md +0 -3
- package/content/plugins/plugins-icon-sets.md +0 -267
- package/content/plugins/posthog.md +0 -275
- package/content/provider-reference/auth-js-providers.md +0 -107
- package/content/provider-reference/openidconnectprovider.md +0 -44
- package/content/testing/config-tests.md +0 -278
- package/content/tutorial/tutorial-requests-api.md +0 -155
- package/content/user-authentication/auth-configuration.md +0 -266
- package/content/user-authentication/auth-steps.md +0 -138
- package/content/user-authentication/mcp-oauth.md +0 -292
- package/content/user-authentication/organizations.md +0 -221
- package/content/user-authentication/protected-pages-apis.md +0 -97
- package/content/user-authentication/roles.md +0 -49
- package/content/user-authentication/two-factor.md +0 -229
- package/content/user-authentication/user-object.md +0 -58
- package/content/websockets/websocket-publish.md +0 -108
- package/content/websockets/websocket-subscriptions.md +0 -100
- package/content/websockets/websockets-introduction.md +0 -97
- package/hazards.json +0 -44
- package/scripts/hashDocSources.js +0 -38
- package/scripts/recordSourceHashes.js +0 -88
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
# Auth Configuration
|
|
2
|
-
|
|
3
|
-
The `auth` section configures user authentication. Lowdefy's auth is built on [BetterAuth](https://www.better-auth.com/): sessions are database-backed, the app owns its auth UI, and roles live on organization memberships. If you are moving from the previous NextAuth-shaped config, start with the [Auth Upgrade guide](/auth-upgrade) — several keys were renamed or removed.
|
|
4
|
-
|
|
5
|
-
The `_secret` operator is evaluated over the entire `auth` section, so any value can be a secret reference.
|
|
6
|
-
|
|
7
|
-
For a login method to work you must configure **at least one mechanism** (`emailAndPassword`, `magicLink`, `emailOTP`, `phoneNumber`, an OAuth `providers` entry, or an API `strategies` entry), a `secret`, and a `database`. An `auth` block with none of these fails the build.
|
|
8
|
-
|
|
9
|
-
## Secret
|
|
10
|
-
|
|
11
|
-
A required signing secret, given as a `_secret` reference:
|
|
12
|
-
|
|
13
|
-
```yaml
|
|
14
|
-
lowdefy: 6.0.0
|
|
15
|
-
auth:
|
|
16
|
-
secret:
|
|
17
|
-
_secret: BETTER_AUTH_SECRET
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Also pin the app's canonical origin — the `url` of the current environment in [`config.environments`](/lowdefy-schema), or the `BETTER_AUTH_URL` environment variable, which wins — auth builds password-reset, magic-link and verification links, and its CSRF origin allowlist, from it. With `auth.email` configured, a production server refuses to start without it. See the [migration guide](/auth-upgrade#3-pin-the-canonical-url-with-better-auth-url).
|
|
21
|
-
|
|
22
|
-
## Database
|
|
23
|
-
|
|
24
|
-
Sessions, users, accounts and organizations are stored through an adapter:
|
|
25
|
-
|
|
26
|
-
```yaml
|
|
27
|
-
lowdefy: 6.0.0
|
|
28
|
-
auth:
|
|
29
|
-
database:
|
|
30
|
-
id: auth_db
|
|
31
|
-
type: MongoDBAuthAdapter
|
|
32
|
-
properties:
|
|
33
|
-
uri:
|
|
34
|
-
_secret: AUTH_DATABASE_URI
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
See the [MongoDBAuthAdapter](/MongoDBAuthAdapter) reference. Collection names follow the `user-*` convention and are fixed by the adapter.
|
|
38
|
-
|
|
39
|
-
## Login mechanisms
|
|
40
|
-
|
|
41
|
-
- **Email & password** — `auth.emailAndPassword` (`enabled`, `requireEmailVerification`, `minPasswordLength` (default 8), `disableSignUp`).
|
|
42
|
-
- **Magic link** — `auth.magicLink` (`enabled`, `expiresIn` seconds (default 300), `disableSignUp`), which needs `auth.email` (below).
|
|
43
|
-
- **Email one-time code** — `auth.emailOTP` (`enabled`, `otpLength` (default 6), `expiresIn` seconds (default 300), `allowedAttempts` (default 3), `disableSignUp` (default `false`)), which needs `auth.email` (below). The person is emailed a code and types it into the tab they started from, so the sign-in survives mail security that pre-fetches links, and works when the email is opened on another device.
|
|
44
|
-
- **OAuth providers** — `auth.providers`; see [Providers](/auth-providers).
|
|
45
|
-
- **Phone number** — `auth.phoneNumber` (OTP sign-in); needs a `phone.otp.send` hook to deliver the SMS.
|
|
46
|
-
- **Passkeys** — `auth.passkey` (`enabled`, `rpId`, `rpName`).
|
|
47
|
-
|
|
48
|
-
```yaml
|
|
49
|
-
lowdefy: 6.0.0
|
|
50
|
-
auth:
|
|
51
|
-
emailAndPassword:
|
|
52
|
-
enabled: true
|
|
53
|
-
requireEmailVerification: true
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Auth email
|
|
57
|
-
|
|
58
|
-
`auth.email` references an **SMTP connection** by id — the connection owns `from`, `replyTo`, the transport and the delivery filter, and is shared by every auth email flow. There is no inline transport shape. Optionally map individual flows to your own notification templates:
|
|
59
|
-
|
|
60
|
-
```yaml
|
|
61
|
-
lowdefy: 6.0.0
|
|
62
|
-
auth:
|
|
63
|
-
email:
|
|
64
|
-
connectionId: email # an SMTP connection in connections[]
|
|
65
|
-
templates: # all optional; unset → branded stock template
|
|
66
|
-
verifyEmail: verify-email-notification
|
|
67
|
-
resetPassword: reset-password-notification
|
|
68
|
-
magicLink: magic-link-notification
|
|
69
|
-
invitation: invite-notification
|
|
70
|
-
emailOTP: email-otp-notification
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Link and code in one email
|
|
74
|
-
|
|
75
|
-
With both `magicLink` and `emailOTP` enabled, the magic-link email also carries the one-time code, so a single email serves both paths: the person clicks the button, or types the code into the tab they asked for the link from. Nothing extra is sent — the `emailOTP` template is only used for a code-only send.
|
|
76
|
-
|
|
77
|
-
The `magicLink` template then has two more variables available: `otp` (the code) and `expiresIn` (the code's lifetime in seconds, from `auth.emailOTP.expiresIn`). The stock template renders them below the sign-in button; a template of your own can place them wherever it likes. With `emailOTP` off both are absent and the link email is unchanged.
|
|
78
|
-
|
|
79
|
-
## Auth pages
|
|
80
|
-
|
|
81
|
-
BetterAuth ships no UI — your app owns its auth pages, and `auth.authPages` points at them. All are Lowdefy page paths:
|
|
82
|
-
|
|
83
|
-
| Key | Default | When required |
|
|
84
|
-
| --- | ------- | ------------- |
|
|
85
|
-
| `signIn` | `/login` | |
|
|
86
|
-
| `signUp` | `/signup` | |
|
|
87
|
-
| `error` | `/auth/error` | receives `?error=` code |
|
|
88
|
-
| `forgotPassword` | `/forgot-password` | |
|
|
89
|
-
| `resetPassword` | `/reset-password` | where the emailed reset link lands, with `?token=` (or `?error=INVALID_TOKEN`); the default `redirectTo` of `RequestPasswordReset` |
|
|
90
|
-
| `verifyEmail` | `/verify-email` | where the emailed verification link lands; receives `?error=` code when the link is invalid or expired |
|
|
91
|
-
| `twoFactor` | — | **required** when `twoFactor.enabled` |
|
|
92
|
-
| `twoFactorEnrol` | — | **required** when `twoFactor.required` |
|
|
93
|
-
| `acceptInvitation` | — | consumes an `?invitationId=` link |
|
|
94
|
-
| `magicLink` | — | landing page for the sign-in email (opt-in; see below) |
|
|
95
|
-
|
|
96
|
-
```yaml
|
|
97
|
-
lowdefy: 6.0.0
|
|
98
|
-
auth:
|
|
99
|
-
authPages:
|
|
100
|
-
signIn: /login
|
|
101
|
-
signUp: /signup
|
|
102
|
-
error: /auth-error
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### The email verification page
|
|
106
|
-
|
|
107
|
-
A verification email links to Better Auth's verify endpoint, which confirms the address and then redirects to `authPages.verifyEmail`. When the link is invalid or has expired, it redirects there with `?error=` set (`INVALID_TOKEN`, `TOKEN_EXPIRED`), so the page can offer to resend with the `SendVerificationEmail` action. A `callbackUrl` on the `SignUp` or `SendVerificationEmail` action sends that link somewhere else instead.
|
|
108
|
-
|
|
109
|
-
### The magic link landing page
|
|
110
|
-
|
|
111
|
-
`authPages.magicLink` is opt-in, and it exists for one reason: Better Auth's magic-link verify endpoint consumes the single-use token on the **first** `GET` of the URL. Corporate mail security — Microsoft Defender Safe Links, Proofpoint URL Defense, Mimecast — fetches every link in a message at delivery time, so that first `GET` is a scanner and the person clicking arrives to `INVALID_TOKEN`.
|
|
112
|
-
|
|
113
|
-
Set the key and the sign-in email links to a page of your own instead, with the token and the callback destinations carried on its URL query. Fetching a Lowdefy page is harmless, so the token stays unspent until a real click runs [`MagicLinkVerify`](/MagicLinkVerify):
|
|
114
|
-
|
|
115
|
-
```yaml
|
|
116
|
-
lowdefy: 6.0.0
|
|
117
|
-
auth:
|
|
118
|
-
magicLink:
|
|
119
|
-
enabled: true
|
|
120
|
-
authPages:
|
|
121
|
-
magicLink: /magic-link
|
|
122
|
-
pages:
|
|
123
|
-
public:
|
|
124
|
-
- magic-link
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
```yaml
|
|
128
|
-
id: magic-link
|
|
129
|
-
type: PageHeaderMenu
|
|
130
|
-
blocks:
|
|
131
|
-
- id: content
|
|
132
|
-
type: Box
|
|
133
|
-
blocks:
|
|
134
|
-
- id: title
|
|
135
|
-
type: Title
|
|
136
|
-
properties:
|
|
137
|
-
content: Sign in
|
|
138
|
-
level: 3
|
|
139
|
-
- id: description
|
|
140
|
-
type: Paragraph
|
|
141
|
-
properties:
|
|
142
|
-
content: Click the button below to finish signing in.
|
|
143
|
-
- id: verify_button
|
|
144
|
-
type: Button
|
|
145
|
-
properties:
|
|
146
|
-
title: Sign in
|
|
147
|
-
events:
|
|
148
|
-
onClick:
|
|
149
|
-
- id: verify
|
|
150
|
-
type: MagicLinkVerify
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
The page must **not** call `MagicLinkVerify` in `onMount`, and must not auto-redirect to the verify endpoint. Scanning sandboxes that render the page and execute its JavaScript would consume the token anyway, which puts you back where you started. The protection is the click, not the page.
|
|
154
|
-
|
|
155
|
-
Leave the key unset and magic-link sign-in keeps working exactly as before, linking straight at the verify endpoint.
|
|
156
|
-
|
|
157
|
-
See [Two-Factor Authentication](/two-factor) for the two-factor pages, and the [MCP Server & OAuth](/mcp-oauth) page for `oauthProvider.consentPage` and `postLoginPage`.
|
|
158
|
-
|
|
159
|
-
## Session
|
|
160
|
-
|
|
161
|
-
Database sessions. Length is `expiresIn` (seconds, default 604800 = 7 days); `updateAge` (default 86400) is how often an active session's expiry is refreshed:
|
|
162
|
-
|
|
163
|
-
```yaml
|
|
164
|
-
lowdefy: 6.0.0
|
|
165
|
-
auth:
|
|
166
|
-
session:
|
|
167
|
-
expiresIn: 43200 # 12 hours in seconds
|
|
168
|
-
updateAge: 3600
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
`session.cookieCache` (off by default) trades a short window of stale session reads for fewer database lookups — leave it off unless you understand the [revocation-latency trade](/two-factor#recovering-a-user-who-has-lost-their-factor).
|
|
172
|
-
|
|
173
|
-
## Roles, organizations, and API strategies
|
|
174
|
-
|
|
175
|
-
- **Roles** — declare app role names in `auth.roles` and gate pages/endpoints with `auth.pages.roles` / `auth.api.roles`. See [Roles](/roles).
|
|
176
|
-
- **Organizations** — `auth.organizations.policy` (`pinned` or `tenant`) decides the whole multi-tenancy model. See [Organizations & Multi-Tenancy](/organizations).
|
|
177
|
-
- **API strategies** — `auth.strategies` authenticates non-session callers (server-to-server) with an `apiKey` or a `jwt`:
|
|
178
|
-
|
|
179
|
-
```yaml
|
|
180
|
-
lowdefy: 6.0.0
|
|
181
|
-
auth:
|
|
182
|
-
strategies:
|
|
183
|
-
- id: service-key
|
|
184
|
-
type: apiKey
|
|
185
|
-
roles:
|
|
186
|
-
- integrations
|
|
187
|
-
properties:
|
|
188
|
-
keys:
|
|
189
|
-
- value:
|
|
190
|
-
_secret: SERVICE_API_KEY
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
A strategy caller carries the configured `roles` and `attributes` and its `auth_method` is the strategy type. A `jwt` strategy needs exactly one of `properties.secret` or `properties.jwksUri`, and a non-empty `algorithms` allowlist (which blocks `alg: none` downgrades).
|
|
194
|
-
|
|
195
|
-
## Captcha
|
|
196
|
-
|
|
197
|
-
`auth.captcha` protects auth endpoints with Cloudflare Turnstile. The `siteKey` is public (a plain string, never a `_secret`); the `secretKey` is a `_secret` reference. See the [Captcha block](/Captcha) for the client side.
|
|
198
|
-
|
|
199
|
-
## Rate limiting and account linking
|
|
200
|
-
|
|
201
|
-
- `auth.rateLimit` — brute-force protection, on by default (`enabled`, `window` seconds, `max`). Sign-in and sign-up allow 3 attempts per 10 seconds, and password-reset and verification emails 3 per minute. Limits count per client address: the connection's address on a self-hosted server, or the address a trusted proxy forwarded when the app lists its proxies in [`config.trustedProxies`](/docker#behind-a-reverse-proxy). `X-Forwarded-For` from anyone else is ignored.
|
|
202
|
-
- `auth.account.accountLinking` — link a new OAuth sign-in to an existing account by verified email. `trustedProviders` lists providers whose email claim you trust for linking. **This is unrelated to `provider.twoFactorTrusted`** — see [Two-Factor Authentication](/two-factor#trusting-an-oauth-provider).
|
|
203
|
-
|
|
204
|
-
## Mock User for Testing (Dev Server Only)
|
|
205
|
-
|
|
206
|
-
When developing and testing Lowdefy apps, you can bypass the login flow by configuring a mock user. This is useful for testing authenticated flows without going through OAuth login.
|
|
207
|
-
|
|
208
|
-
The mock user can be configured in three ways:
|
|
209
|
-
|
|
210
|
-
### CLI Flag
|
|
211
|
-
|
|
212
|
-
Pass `--mock-user` to `lowdefy dev` to start the server as a mock user for that run. Supply a JSON user object to set the identity and roles, or use the bare flag for a default user with no roles:
|
|
213
|
-
|
|
214
|
-
```bash
|
|
215
|
-
lowdefy dev --mock-user '{"id":"test-user","email":"test@example.com","roles":["admin"]}'
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
The flag sets `LOWDEFY_DEV_USER` for the dev server process, so it takes precedence over `auth.dev.mockUser` in the config file.
|
|
219
|
-
|
|
220
|
-
### Environment Variable
|
|
221
|
-
|
|
222
|
-
Set the `LOWDEFY_DEV_USER` environment variable to a JSON string containing the mock user object:
|
|
223
|
-
|
|
224
|
-
```bash
|
|
225
|
-
LOWDEFY_DEV_USER='{"id":"test-user","email":"test@example.com","roles":["admin"]}'
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### Config File
|
|
229
|
-
|
|
230
|
-
Add the `auth.dev.mockUser` section to your `lowdefy.yaml`:
|
|
231
|
-
|
|
232
|
-
###### Configure a mock user for development
|
|
233
|
-
```yaml
|
|
234
|
-
lowdefy: 6.0.0
|
|
235
|
-
|
|
236
|
-
auth:
|
|
237
|
-
providers:
|
|
238
|
-
- id: google
|
|
239
|
-
type: Google
|
|
240
|
-
properties:
|
|
241
|
-
clientId:
|
|
242
|
-
_secret: GOOGLE_CLIENT_ID
|
|
243
|
-
clientSecret:
|
|
244
|
-
_secret: GOOGLE_CLIENT_SECRET
|
|
245
|
-
dev:
|
|
246
|
-
mockUser:
|
|
247
|
-
id: test-user
|
|
248
|
-
email: test@example.com
|
|
249
|
-
name: Test User
|
|
250
|
-
roles:
|
|
251
|
-
- admin
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
When a mock user is configured:
|
|
255
|
-
- The environment variable takes precedence over the config file if both are set
|
|
256
|
-
- A warning is logged at dev server startup: "Mock user active - login bypassed"
|
|
257
|
-
- The mock user is injected as a pre-resolved caller — its `roles` are authoritative
|
|
258
|
-
- The `_user` operator returns values from the mock user, on the server and in the browser client — the dev server serves the mock session to both
|
|
259
|
-
- Protected pages are accessible based on the mock user's roles
|
|
260
|
-
- The dev server's headless renderer (used by the AI-agent screenshot and state-inspection tools) renders as the mock user, so it can capture pages with roles the default user lacks
|
|
261
|
-
|
|
262
|
-
> **Note:** Mock users only work with the development server (`lowdefy dev`). The production server ignores mock user configuration for security.
|
|
263
|
-
|
|
264
|
-
> **Note:** `dev.mockUser` bypasses the auth engine rather than exercising it, so [auth steps](/auth-steps) (which need the running auth engine) are unavailable under a mock user.
|
|
265
|
-
|
|
266
|
-
`auth.dev` needs no auth stack behind it. An `auth` block whose only key is `dev` is not an auth configuration: it demands no login method, no `secret` and no `database`, the app builds and runs signed out in production, and in the dev server the browser is the `dev.mockUser` caller. Adding any other `auth` key declares a real auth stack, and the build then requires a mechanism, a secret and (for a login method) a database as usual.
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
# Auth Steps
|
|
2
|
-
|
|
3
|
-
Auth steps are routine steps that administer users, organizations and memberships — inviting a member, updating an app role, banning a user, creating an organization. They run **inside an `Api` or `InternalApi` endpoint routine**, not as client actions, because administering *other* people's memberships is a server operation that must be authorized against the caller, name its target organization explicitly, and be visible in a diff.
|
|
4
|
-
|
|
5
|
-
> A **client action** mutates the caller's *own* session and nothing else — `SetActiveOrganization`, `AcceptInvitation`, `LeaveOrganization`. Anything that reaches another person's membership is an auth **step** in a routine, which is what earns it the per-step authority floor below. See the [Auth Upgrade guide](/auth-upgrade#retired-client-actions) for the actions that became steps.
|
|
6
|
-
|
|
7
|
-
```yaml
|
|
8
|
-
id: invite-member
|
|
9
|
-
type: Api
|
|
10
|
-
routine:
|
|
11
|
-
- id: invite
|
|
12
|
-
type: InviteMember
|
|
13
|
-
properties:
|
|
14
|
-
organizationId:
|
|
15
|
-
_user: organization_id
|
|
16
|
-
email:
|
|
17
|
-
_payload: email
|
|
18
|
-
appRoles:
|
|
19
|
-
_payload: roles
|
|
20
|
-
- ':return':
|
|
21
|
-
_step: invite
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## The authority model
|
|
25
|
-
|
|
26
|
-
Every auth step declares, in its own code, the authority it requires — and the engine's **authority floor** enforces that declaration mechanically before the step runs. This is not optional decoration: a step with no declared authority is a build/runtime error, so an app can never expose a member mutation by forgetting a check. The reason the floor exists in one place is that most steps go adapter-direct or through BetterAuth's admin plugin, whose check reads a single deployment-wide `user.role` field and **cannot** answer "is the caller an administrator of *this* organization". For those steps, the floor is the only authorization that can express per-organization authority.
|
|
27
|
-
|
|
28
|
-
A step declares one of three **scopes**.
|
|
29
|
-
|
|
30
|
-
### `org` — authorized against the caller's membership in the target organization
|
|
31
|
-
|
|
32
|
-
The common case. The floor:
|
|
33
|
-
|
|
34
|
-
1. **Resolves the target organization** (see below).
|
|
35
|
-
2. **Finds the caller's `member` row** in that organization and checks its role against the step's required **permissions**. The caller's authority is their membership in the *target* organization — an administrator of the team organization holds nothing in the customer organization.
|
|
36
|
-
3. For steps that write the deployment-wide `user` row, additionally requires the **target user** to be a member of that organization (`targetUser`), so org authority alone can never reach a user who is not the caller's business.
|
|
37
|
-
|
|
38
|
-
Permissions map to the organization tier via BetterAuth's organization access control: `owner` and `admin` are granted the member-management permissions out of the box; `member` holds none of them. A refused caller gets a `ConfigError` naming exactly what was required.
|
|
39
|
-
|
|
40
|
-
### `system` — caller-less, trusted runs only
|
|
41
|
-
|
|
42
|
-
A `system`-scoped step (`CreateOrganization`, `ListUsers`) has no organization to be authorized in, so a caller can never satisfy it. It runs **only** where the run itself is trusted — a caller-less system routine. Mark the step `system: true`, or run it from a system context (cron, a verified webhook, an auth hook):
|
|
43
|
-
|
|
44
|
-
```yaml
|
|
45
|
-
- id: create_org
|
|
46
|
-
type: CreateOrganization
|
|
47
|
-
system: true
|
|
48
|
-
properties:
|
|
49
|
-
name:
|
|
50
|
-
_payload: name
|
|
51
|
-
slug:
|
|
52
|
-
_payload: slug
|
|
53
|
-
userId:
|
|
54
|
-
_payload: ownerUserId
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### `caller` — acts on the caller's own records
|
|
58
|
-
|
|
59
|
-
A `caller`-scoped step (`RevokeMcpGrant`, `LeaveOrganization`) acts only on rows the caller owns, so it needs no organization authority — but it is meaningless without a caller, and the system has no rows of its own. It requires a real caller and refuses `system: true`.
|
|
60
|
-
|
|
61
|
-
### Leaving an organization from a routine
|
|
62
|
-
|
|
63
|
-
`LeaveOrganization` ends the caller's own membership of the organization named by `organizationId`. The property is required: the step has no org scope, so nothing defaults it. The organization plugin's own checks run, so a caller who is not a member there, or who is its only owner, is refused. The refusal throws with the plugin's message, which the routine catches with `:try` to branch on:
|
|
64
|
-
|
|
65
|
-
```yaml
|
|
66
|
-
id: leave
|
|
67
|
-
type: Api
|
|
68
|
-
routine:
|
|
69
|
-
- :try:
|
|
70
|
-
- id: leave
|
|
71
|
-
type: LeaveOrganization
|
|
72
|
-
properties:
|
|
73
|
-
organizationId:
|
|
74
|
-
_payload: organization_id
|
|
75
|
-
:catch:
|
|
76
|
-
- :return:
|
|
77
|
-
refused:
|
|
78
|
-
message:
|
|
79
|
-
_error: message
|
|
80
|
-
# Runs only when the leave went through.
|
|
81
|
-
- :return:
|
|
82
|
-
success: true
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
On success the step returns the member row it removed. The caller's session still names the organization they left as active, so their next request resolves unauthenticated: follow the call on the page with `SetActiveOrganization` for another organization, or with `Logout`. The `LeaveOrganization` client action leaves the session's active organization from the browser and clears it; use it when nothing else has to happen in the same request as the leave. Step and action types resolve from separate maps, so the shared name is never ambiguous.
|
|
86
|
-
|
|
87
|
-
## `system: true` — the two doors to a caller-less run
|
|
88
|
-
|
|
89
|
-
The floor's caller requirement passes on either of two independent doors:
|
|
90
|
-
|
|
91
|
-
- a **run-level** system context (`context.system === true`) — a trusted, caller-less run such as a cron endpoint, an auth hook, or a verified webhook; or
|
|
92
|
-
- a **per-step** `system: true` on a single step in an otherwise user-driven run — a waiver visible in the diff, for the one privileged operation in a member-facing routine.
|
|
93
|
-
|
|
94
|
-
Either door makes the step act caller-less **as the system**, bypassing the org/permission checks. That is an explicit trust decision, not attribution — use it deliberately.
|
|
95
|
-
|
|
96
|
-
## How the target organization is resolved
|
|
97
|
-
|
|
98
|
-
For every `org`-scoped step the floor resolves the target organization once, and the step writes into the *same* one it authorized — they cannot drift. The rule:
|
|
99
|
-
|
|
100
|
-
1. An explicit **`organizationId`** property always wins. This is what a multi-organization admin routine passes, and it is what lets a session pinned to the team organization administer the customer organization.
|
|
101
|
-
2. `ListMembers` also accepts an **`organizationSlug`**, resolved to an id here.
|
|
102
|
-
3. Otherwise the **pinned** organization is the default — **but under `policy: tenant` there is no pinned organization**, so an omitted `organizationId` is a runtime error naming the fix. Under `tenant`, always pass `organizationId` explicitly.
|
|
103
|
-
|
|
104
|
-
## `targetUser` and `selfTargetExempt`
|
|
105
|
-
|
|
106
|
-
Two refinements the floor reads from a step's declaration:
|
|
107
|
-
|
|
108
|
-
- **`targetUser`** names the property holding the id of the user being acted on. Steps that write the shared `user` row declare it, so the floor can require that user to be a member of the target organization. An absent target-user property is the step's own required-property error, not the floor's.
|
|
109
|
-
- **`selfTargetExempt`** lets a caller act on **their own** row without holding org authority — this is what makes `UpdateUserProfile` a self-service "save my profile" step as well as an admin one. When the named property equals the caller's own id, the org authority check is skipped.
|
|
110
|
-
|
|
111
|
-
## Step reference
|
|
112
|
-
|
|
113
|
-
Scope, required permission, and target for every auth step. Permissions are checked against the caller's `member.role` in the target organization; `owner`/`admin` hold them by default.
|
|
114
|
-
|
|
115
|
-
| Step | Scope | Permission | Target | What it does |
|
|
116
|
-
| ---- | ----- | ---------- | ------ | ------------ |
|
|
117
|
-
| `InviteMember` | org | `invitation: [create]` | — | Invite a member by email; carries `appRoles`, `orgRole`, `attributes`, `contactId`, `profile`; `resend` refreshes. |
|
|
118
|
-
| `CancelInvitation` | org | `invitation: [cancel]` | — | Cancel a pending invitation. |
|
|
119
|
-
| `ListMembers` | org | `member: [list]` | — | List members of the organization (accepts `organizationSlug`). |
|
|
120
|
-
| `RemoveMember` | org | `member: [delete]` | — | Remove a member from the organization. |
|
|
121
|
-
| `UpdateMemberRoles` | org | `member: [update]` | — | Set the membership's **app roles** (`appRoles` array; empty clears). |
|
|
122
|
-
| `UpdateMemberOrgRole` | org | `member: [update]` | — | Set the **org tier** (`owner`/`admin`/`member`). |
|
|
123
|
-
| `UpdateMemberAttributes` | org | `member: [update]` | — | Set the membership's per-organization attributes. |
|
|
124
|
-
| `UpdateOrganization` | org | `organization: [update]` | — | Update the organization row (name, slug, metadata). |
|
|
125
|
-
| `UpdateUserProfile` | org | `user: [update]` | `userId` (self-exempt) | Update a member's display name/image (per-organization copy). Self-service save is exempt. |
|
|
126
|
-
| `UpdateUserAttributes` | org | `user: [set-attributes]` | `userId` | Set global user attributes. |
|
|
127
|
-
| `BanUser` | org | `user: [ban]` | `userId` | Ban a user. |
|
|
128
|
-
| `UnbanUser` | org | `user: [ban]` | `userId` | Lift a ban. |
|
|
129
|
-
| `DeleteUser` | org | `user: [delete]` | `userId` | Delete a user. |
|
|
130
|
-
| `RevokeUserSessions` | org | `session: [revoke]` | `userId` | Sign a user out of all sessions. |
|
|
131
|
-
| `ResetUserTwoFactor` | org | `user: [reset-two-factor]` | `userId` | Clear a user's two-factor enrolment and trust-device records. |
|
|
132
|
-
| `RevokeUserPasskeys` | org | `user: [revoke-passkeys]` | `userId` | Remove a user's passkeys (`passkeyId` to target one). |
|
|
133
|
-
| `CreateOrganization` | system | — | — | Create an organization (tenant provisioning). Needs `userId`, or `name`+`slug` for creator-less provisioning. |
|
|
134
|
-
| `ListUsers` | system | — | — | List every user in the deployment. |
|
|
135
|
-
| `LeaveOrganization` | caller | — | — | End the caller's own membership of the named organization (`organizationId`, required). [Leaving from a routine](#leaving-an-organization-from-a-routine). |
|
|
136
|
-
| `RevokeMcpGrant` | caller | — | — | Revoke the calling assistant's own [MCP grant](/mcp-oauth#switching-organization-from-the-assistant). |
|
|
137
|
-
|
|
138
|
-
The two role tiers a member carries — the `owner`/`admin`/`member` org tier and the app's own role strings — are explained in [Organizations & Multi-Tenancy](/organizations#the-owner-admin-member-tier-vs-app-roles). Recovering a user who lost their second factor is a worked routine on the [Two-Factor Authentication](/two-factor#recovering-a-user-who-has-lost-their-factor) page.
|