Repository navigation
Commit 73a2b52
fix(platform-objects): the OAuth register form offers only Web and Native, and its Redirect URLs help states the rule for each type (#25011)
Part of #24773: half 2b, the Console form's part of the maintainer's
ruling D (`6293344532`). Half 1 landed as #24853. Half 2a (the
`plugin-auth` register wrapper forwards the type as `application_type`)
landed as #24992 (`fbb42d900`), which is merged into this branch. The
19.0 refusal of `user-agent-based` / `public` stays on the card.
Clause-②: no (a form's offering: the register route still accepts every
type value it accepted, and nothing on the server reads the action's
param options, so no accept set or served shape moves)
**Cross-lane:** `packages/platform-objects` is `domain:engine`. This PR
comes from the `domain:services` seat 2 under the claim's widened
half-2b block (`6302570331`).
**The help describes the door as #24992 left it.** The new Redirect URLs
help says a Native application may use `http://localhost`. Before #24992
that was false: the route sent `type`, which the provider drops, so a
Native client was judged as Web. Measured both ways on a live showcase
(`pnpm dev -- --fresh`, `OS_OIDC_PROVIDER_ENABLED=1`, seeded admin),
through `POST /api/v1/auth/sys-oauth-application/register`:
| Registration | At `2dc07452a` (without #24992) | At `fd686be8e` (with
#24992) |
|:--|:--|:--|
| Native + `http://localhost:4001/callback` | 400
`OAUTH_REGISTER_FAILED`, "web clients require https redirect URIs on
non-loopback hosts" | 200, stored `native` |
| Native + `http://127.0.0.1:4001/callback` | not run | 200, stored
`native` |
| Native + `http://[::1]:4001/callback` | not run | 200, stored `native`
|
| Web + `http://localhost:4001/callback` | 400, same refusal | 400, same
refusal |
| Web + `https://app.example.com/callback` | 200, stored `web` | 200,
stored `web` |
| `user-agent-based` + https | 200, stored `web` | 200, stored `web`,
one server warning |
| `public` + https | 200, stored `web` | 200, stored `web`, one server
warning |
## What changes
`sys_oauth_application.create_oauth_application` (Setup › OAuth
Applications › Register), in `sys-oauth-application.object.ts`:
| | Before | After |
|:--|:--|:--|
| Application Type options | Web, Native, User-agent based, Public |
Web, Native (default Web, still required) |
| Redirect URLs help | "One URL per line. Must use https:// in
production." | "One URL per line. Web applications need https:// on a
host other than localhost or another loopback address. Native
applications may also use http:// on exactly localhost, 127.0.0.1 or
[::1], on any port." |
| Application Type help | none | "Choose Native for a desktop or mobile
app, or for an app in local development that redirects to
http://localhost." |
- The four `objects.generated.ts` bundles were regenerated with `pnpm
i18n:extract`. The zh-CN, ja-JP and es-ES values of the two help texts
were translated by hand, then extract was run again: the source-hash
companions end unchanged, because the new leaves are translations, not
copies.
- Not changed: the stored `type` select (`:449` at this head, `:425` at
`4b8f597ba`), the `public` boolean column, the register route, and every
other line of the file.
## Stop condition: does anything on the server refuse a value the form
no longer offers? No.
Measured on the same live showcase at `2dc07452a`, and the
register-route rows again at `fd686be8e` (table above):
- `POST /api/v1/auth/sys-oauth-application/register` with `type:
'user-agent-based'` and with `type: 'public'` (https redirect): both
`200`, `success: true`.
- The generic action door, `POST
/api/v1/actions/sys_oauth_application/create_oauth_application` with
`type: 'user-agent-based'`: `400 VALIDATION_ERROR`, "is `type: 'api'` —
it dispatches on `target`, not through the action registry". The same
answer for `type: 'web'` (control). The door refuses an `api` action by
its type before its param contract runs (`headlessActionTypeError` in
`packages/runtime/src/domains/actions.ts`, ahead of
`enforceActionParams`). The MCP `run_action` door refuses it the same
way (`isHeadlessInvokableAction`, `action-execution.ts`).
- Source reading: the register route reads `body.type` from the raw
request and never reads the object's metadata. The middleware seam hands
middleware no body. Nothing else matches an action's `target`.
- So only the form changes. `GET
/api/v1/meta/object/sys_oauth_application` now serves the `type` param
with options `["web", "native"]` and the new help texts. With
`?locale=zh-CN` it serves the zh-CN texts and options `Web 应用` / `原生应用`.
## The rule the help states, measured on the provider
`@better-auth/oauth-provider` 1.7.3, `validateClientRedirectUri`
(`dist/authorize-9whjxVLJ.mjs:1685`). #24992's pins cover Web +
`http://localhost` refused, Web + https accepted, Native +
`http://localhost:4001` and `http://127.0.0.1:4001` accepted, and Native
+ `http://127.0.0.2:4001` and `http://app.example.com` refused. The rest
of what the help says was measured live through the provider's own `POST
/api/v1/auth/oauth2/create-client`, at `2dc07452a`:
| application_type | redirect URI | Answer |
|:--|:--|:--|
| native | `http://[::1]:4001/callback` | 201, stored `native` |
| native | `http://localhost:59999/callback` | 201 (any port) |
| native | `http://127.0.0.1/callback` (no port) | 201 |
| native | `https://app.example.com/callback` | 201 (Native also takes
what Web takes) |
| native | `https://localhost:4001/callback` | 400
`invalid_redirect_uri`, "native clients must not use https loopback
redirect URIs" |
| native | `http://127.0.0.2:4001/callback` | 400, "may use http only on
the exact loopback hosts localhost, 127.0.0.1, or [::1]" |
| web | `https://localhost:4001/callback` | 400, "web clients require
https redirect URIs on non-loopback hosts" |
| web | `https://127.0.0.1:4001/callback` | 400, same |
| web | `https://app.example.com/callback` | 201 |
The help does not mention Native private-use URI schemes, which the rule
also allows. Nothing here measured them through the route.
## The stored columns, measured (for the 19.0 card)
- **What 1.7.3 writes to `type`.** Only `web` or `native`.
`checkOAuthClient` refuses any other `application_type` (`:1770`). One
more case: a client-metadata-document registration that names no
`application_type` stores `null` (`:1956`). Live, at `2dc07452a`: the
registrations above named `user-agent-based` and `public` were stored
`type: 'web'`, `token_endpoint_auth_method: 'client_secret_basic'` (8
rows read through `GET /api/v1/data/sys_oauth_application`). Again at
`fd686be8e`: 6 rows, the same columns.
- **What 1.7.3 writes to `public`.** Never. The client record has no
such field (`OAUTH_CLIENT_RECORD_FIELD_NAMES`, `:1034`). No row carries
a `public` value (8 at `2dc07452a`, 6 at `fd686be8e`). A public client
is `token_endpoint_auth_method: 'none'`.
- **What the stored `type` select actually declares.** The values are
`web`, `native`, `useragentbased`, `public`. The column uses
`Field.select`'s string-array shorthand, which snake-cases each value
and strips the hyphens. So the third declared value has never been
`user-agent-based`: not the value the form's action param sent, and not
the one the provider's earlier lines accepted (from the SDK and dynamic
registration, for public clients). A row holding `user-agent-based` is
already outside the declared set today. Keeping the column four-valued
in 18.x protects `public`, not `user-agent-based`. The pin below holds
the column exactly as declared. Fixing it, or narrowing it, is the 19.0
card's decision.
## Tests
New
`packages/platform-objects/src/identity/oauth-register-form-types.test.ts`,
7 cases. It reads the object and the four bundles from source.
- The `type` param is a required select, defaults to `web`, and offers
exactly `web` and `native`. Every shipped locale's options are exactly
`web` and `native`.
- The Redirect URLs help names `https://`, `http://`, `localhost`,
`127.0.0.1` and `[::1]`. The Application Type help names Native and
`http://localhost`.
- The en bundle mirrors the source. Every locale's help names the same
scheme and hosts, and each translated locale differs from the English
source.
- The stored `type` select still declares its four options (labels
`web`, `native`, `user-agent-based`, `public`; values as measured
above). The `public` column is still a declared boolean.
Results at `2dc07452a`. `packages/platform-objects` and this PR's
changeset are byte-identical at `fd686be8e`: the second merge brought
only #24992's three `plugin-auth` / changeset files.
- `pnpm --filter @objectstack/platform-objects exec vitest run
--maxWorkers=2`: `Test Files 83 passed (83)`, `Tests 1239 passed
(1239)`.
- `pnpm --filter @objectstack/platform-objects typecheck`: exit 0.
`check:test-typecheck: OK`, 1 file / 3 errors / 2 pinned signatures
held. No debt added. The new file is in the `tsconfig.test.json` program
(`--listFilesOnly`, 1 hit).
## Ablation (one-shot; no permanent file)
Each leg went through `scripts/ablation-replace.mjs`, in a runner with
its own EXIT/INT/TERM trap. Each anchor hit once and fell 1 → 0, with
the blob changed on disk. Each restore was verified as blob == HEAD with
`git diff HEAD` empty (object `240ded11b`, zh-CN bundle `5326f33a2`).
The test reads source, so no `dist` is involved.
| Leg | Mutation | Result |
|:--|:--|:--|
| A | `{ label: 'User-agent based', value: 'user-agent-based' }` put
back on the action's `type` options | 1 failed: `expected [ 'web',
'native', 'user-agent-based' ] to deeply equal [ 'web', 'native' ]` |
| B | Redirect URLs help back to "Must use https:// in production." | 2
failed: the source help no longer names `http://`; the en bundle no
longer mirrors the source |
| C | stored `type` select narrowed to `['web', 'native']` | 1 failed:
the stored-column pin |
| D | zh-CN help back to its old translation | 1 failed: `zh-CN
redirectURLs help ... to contain 'http://'` |
| E | `public: "公共客户端"` put back in zh-CN's action options | 1 failed:
`zh-CN type options: expected [ 'web', 'native', 'public' ]` |
| F | Application Type help deleted | 2 failed: the source help pin and
the en-mirror pin |
| G | `public` column renamed `is_public` | 1 failed: `expected
undefined to be 'boolean'` |
| control | unmutated | 7 passed |
## Gates
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at the final head `fd686be8e` derived 66
commands for 7 paths (208 changed lines, +185 / -23, under the 3000
human-merge threshold). Each ran as `cmd > log 2>&1; rc=$?`, after a
workspace build (`turbo run build --filter=!@objectstack/docs`).
- **Result.** 66/66 exit 0, among them `check:adr-0087-registration
--base origin/main` ("this PR adds no declared-breaking changeset"),
`check:changeset-no-major`, `check:empty-changeset`, `check:i18n` ("all
bundles in sync, no undeclared authoring keys"), `check:i18n-stale-fill`
("0 stale-fill leaf/leaves"), `check:nul-bytes`, `check:doc-authoring`,
`check:closing-keyword-parity`, `check:issue-citations`,
`check:type-check-debt` and `check:dual-build-cjs-loads`.
- **Reconciliation.** `--ran` with every exit code recorded: `Run
reconciliation — 66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN`.
- The same 66 also ran at `2dc07452a`, before #24992 was merged in:
66/66 exit 0, reconciled the same way.
- **Not run locally.** `test:repo` is CI's `Test Core`.
`check:skill-frame-freshness` does not apply: no frame file is touched.
`check:dev-prereqs` passed as the first step of each `pnpm dev` boot. No
exported symbol was renamed or removed, so `check-platform-checklist
--changed-from` does not apply. The workspace type-check lanes and the
CI jobs that `dispatch-gates` lists outside the 66 are CI's.
## Acceptance notes
- **For the 19.0 card.** The stored-column readings above: 1.7.3 writes
`web` / `native` (or `null` for a metadata-document client) to `type`
and never writes `public`. The declared third value is `useragentbased`,
not `user-agent-based`.
- **`Field.select`'s string shorthand strips hyphens from values**
(spaces become underscores; a hyphen is dropped). Measured from built
`dist`: `sys_oauth_application.type` declares `useragentbased`.
`sys_metadata_audit.lock_state`
(`packages/metadata-core/src/objects/sys-metadata-audit.object.ts:190`)
declares `nooverlay` / `nodelete`, while its writer
(`recordMetadataAudit`, `packages/metadata-protocol/src/protocol.ts`)
writes `no-overlay` / `no-delete`. That column is `readonly: true`, so
the record validator judges only its shape and the audit writes are not
refused. Source reading only; no wrong answer was measured through a
door. Reported to the seat in the dev report, not filed here.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent aaee084 commit 73a2b52
7 files changed
Lines changed: 185 additions & 23 deletions
File tree
- .changeset
- packages/platform-objects/src
- apps/translations
- identity
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1530 | 1530 | | |
1531 | 1531 | | |
1532 | 1532 | | |
1533 | | - | |
| 1533 | + | |
1534 | 1534 | | |
1535 | 1535 | | |
1536 | 1536 | | |
| 1537 | + | |
1537 | 1538 | | |
1538 | 1539 | | |
1539 | | - | |
1540 | | - | |
1541 | | - | |
| 1540 | + | |
1542 | 1541 | | |
1543 | 1542 | | |
1544 | 1543 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1530 | 1530 | | |
1531 | 1531 | | |
1532 | 1532 | | |
1533 | | - | |
| 1533 | + | |
1534 | 1534 | | |
1535 | 1535 | | |
1536 | 1536 | | |
| 1537 | + | |
1537 | 1538 | | |
1538 | 1539 | | |
1539 | | - | |
1540 | | - | |
1541 | | - | |
| 1540 | + | |
1542 | 1541 | | |
1543 | 1542 | | |
1544 | 1543 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1530 | 1530 | | |
1531 | 1531 | | |
1532 | 1532 | | |
1533 | | - | |
| 1533 | + | |
1534 | 1534 | | |
1535 | 1535 | | |
1536 | 1536 | | |
| 1537 | + | |
1537 | 1538 | | |
1538 | 1539 | | |
1539 | | - | |
1540 | | - | |
1541 | | - | |
| 1540 | + | |
1542 | 1541 | | |
1543 | 1542 | | |
1544 | 1543 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1530 | 1530 | | |
1531 | 1531 | | |
1532 | 1532 | | |
1533 | | - | |
| 1533 | + | |
1534 | 1534 | | |
1535 | 1535 | | |
1536 | 1536 | | |
| 1537 | + | |
1537 | 1538 | | |
1538 | 1539 | | |
1539 | | - | |
1540 | | - | |
1541 | | - | |
| 1540 | + | |
1542 | 1541 | | |
1543 | 1542 | | |
1544 | 1543 | | |
| |||
| 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 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
147 | 147 | | |
148 | 148 | | |
149 | 149 | | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
150 | 162 | | |
151 | 163 | | |
152 | | - | |
153 | | - | |
154 | | - | |
155 | | - | |
156 | | - | |
157 | | - | |
158 | | - | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
159 | 183 | | |
160 | 184 | | |
161 | 185 | | |
| |||
0 commit comments