Repository navigation
Commit fbb42d9
fix(plugin-auth): the register form's Application Type reaches the OAuth provider as application_type (#24992)
Part of #24773: half 2a, the `plugin-auth` part of the maintainer's
ruling D (`6293344532`). Half 1 (a refusal answered 500) landed as
#24853. Half 2b (the form's options and help text in `platform-objects`)
stays on the card.
Clause-②: no — the declared type now takes effect under the OAuth
provider's own redirect-URI rule, an authentication behaviour the
maintainer ruled on; nothing published widens.
## What changes
`POST /api/v1/auth/sys-oauth-application/register` (Setup → OAuth
Applications → Register) forwarded the form's Application Type as
`type`. The installed `@better-auth/oauth-provider` 1.7.3 declares
`application_type` (`web` | `native`) on its `/oauth2/create-client`
body and no `type`, so the member was stripped and every client was
judged and stored as `web`.
The register wrapper now maps the value onto `application_type`:
| Form value | Before | After |
|:--|:--|:--|
| `web` | judged and stored as web | unchanged (now sent as
`application_type: 'web'`) |
| `native` | judged and stored as **web**:
`http://localhost:4001/callback` refused | judged as native: http on
exactly `localhost`, `127.0.0.1` or `[::1]` (any port) accepted and
stored as `native`; other http refused |
| `user-agent-based`, `public`, any other value | web, silently | web,
as before, plus **one server warning** per registration naming the value
|
| absent | web | web, no warning |
- The provider's own rule is the only rule. The wrapper does not judge a
URI. Web still needs https on a non-loopback host.
- Half 1's error mapping is unchanged: `resolveThrownHttpError(err,
500)` and the `400 OAUTH_REGISTER_FAILED` refusal, with
`details.upstreamError`.
- The warning is a server log line (`ctx.logger.warn`, meta `{
requestedType, registeredAs: 'web' }`). No response field was added.
This door answers `{ success, data: { client } }`, which has no warnings
member. The `warnings` member on the data-layer write answers
(`CreateDataResponseSchema`, `packages/spec/src/api/protocol.zod.ts`) is
declared as advisory validation-rule hits, so it does not fit here. The
warning does not reach the Console caller.
- Diff: `auth-plugin.ts` (the register wrapper only, +32 / -3), one new
test file, one changeset. 382 changed lines in total.
## 「甲」 reading
Standing rule 「甲」 (#6015 `6292005854`) applied as ruled in `6293344532`:
- **Ships in 18.x.** Honouring the declared type is a fix. `web` and
`native` are forwarded, and Native gets the provider's RFC 8252 loopback
rule. Other values get the loud half: one warning per registration.
- **Waits for 19.0.** A 400 for `user-agent-based` / `public` / any
other value narrows what the door accepts, and no ADR-0087 conversion
carries it. Removing the two options from the form is half 2b, not this
PR.
- **`public`, measured first.** 1.7.3 has a real counterpart. A public
client is `token_endpoint_auth_method: 'none'`, and
`persistOAuthClientRegistration` then issues and stores no client
secret. It is a separate axis from `application_type`, which has no
`public` value. Per the dispatch, it is not forwarded or mapped. The
mapping question goes to the seat, with options, in the dev report on
the card. Here `public` behaves like `user-agent-based`: registered as a
confidential web client with a secret, plus the warning.
- **The exception's reading, for the seat to record and rule.** The
question is whether any stored row or any caller ever sent
`user-agent-based` / `public`. The answer does not establish the
exception.
- **Stored rows, console door.** Measured on a real ObjectQL + SQLite
engine with the auth manifest's objects: on base `78ab6474c` and on this
head, a registration with `user-agent-based`, `public` or `spa` stores
`type = 'web'`, `public` null and `token_endpoint_auth_method =
'client_secret_basic'`. No column holds the form's value. 1.7.3 accepts
only `web` / `native` for `application_type` on create and on update.
- **Earlier provider lines,** read from their npm tarballs, not
installed here:
- `@better-auth/oauth-provider` 1.7.0-rc.2 and 1.6.33 declared `type`
(`web` | `native` | `user-agent-based`) but refused any non-`web` type
on a confidential client. The door never sends
`token_endpoint_auth_method`, so `user-agent-based` was refused there,
and the wrapper coerced `public` to `web`.
- better-auth 1.6.33's `oidc-provider` wrote `type: "web"` on every
registration.
- **Limits of this reading.**
- Repo history before 2026-09-07 (`ac76425f0`, this checkout's shallow
root) was not read.
- Rows that other doors wrote under the older lines cannot be measured.
Under rc.2 and 1.6.x, the SDK and DCR accepted `user-agent-based` for
public clients.
- **Callers.**
- In this repo, zero producers send either value to the door: tests,
examples, `packages/qa`, docs and the SDK (which never builds this URL).
- HotCRM `main` `1cc6861a3`: zero hits for the door path,
`user-agent-based`, `application_type`, `oauth2/create-client` and
`applications.register` (control: case-insensitive `oauth`, 18 files).
- objectui `origin/main` `12cde0492`: zero hardcoded values (control:
`sys_oauth_application`, 5 files).
- The one live producer is the Console form itself.
`sys-oauth-application.object.ts:153-158` offers both values to every
admin. Whether an admin ever picked one cannot be measured, because
there is no request log.
## Tests
New
`packages/plugins/plugin-auth/src/oauth-register-application-type.test.ts`,
15 cases. It runs real better-auth (OIDC provider on) behind the
plugin's real route registration, on a real Hono app, over a real
ObjectQL engine on SQLite. "Stored as" is read from
`sys_oauth_application.type` at driver level.
- Preconditions on the provider itself:
- `type: 'native'` is stripped: the loopback URI is refused with
`invalid_redirect_uri`.
- `application_type: 'native'` accepts the same URI and stores `native`.
- Web + `http://localhost:4001/callback`: refused, `400
OAUTH_REGISTER_FAILED`, `invalid_redirect_uri`. Nothing stored, no
warning.
- Native + `http://localhost:4001/callback` and +
`http://127.0.0.1:4001/callback`: `200`, stored `native`, no warning.
- Native + `http://app.example.com/callback`: refused, `400`, nothing
stored. The provider's body schema (`SafeUrlSchema`, https except on
loopback hosts) refuses it before its per-type rule.
- Native + `http://127.0.0.2:4001/callback`: refused by the provider's
exact-host native rule (`invalid_redirect_uri`).
- Web + https and no type: stored `web`, no warning.
- `user-agent-based`, `public`, `spa` + https: stored `web`, no column
holds the value, no public client (no secret-less row), exactly one
warning with meta `{ requestedType, registeredAs: 'web' }`.
- `user-agent-based`, `public`, `spa` + loopback: refused as web,
nothing stored, exactly one warning.
Before the fix, on `78ab6474c`: `Tests 9 failed | 5 passed (14)`. The
native-localhost and native-127.0.0.1 cases got the web refusal
(`expected 400 to be 200`). The six unhonoured-type cases failed only on
the warning count (`expected [] to have a length of 1 but got +0`),
after their storage assertions had passed. The ninth failure was this
file's own first assertion on the non-loopback native case. It expected
`invalid_redirect_uri`, but the provider's body schema refuses that URI
before its per-type rule, both before and after the fix. The assertion
was corrected after this run.
After, at head `e9f4debc1`:
- This file and half 1's `oauth-register-refusal-status.test.ts`: `Test
Files 2 passed (2)`, `Tests 32 passed (32)`.
- `pnpm --filter @objectstack/plugin-auth exec vitest run`, at
`d674628b9`: `Test Files 174 passed (174)`, `Tests 3334 passed | 10
skipped (3344)`. The plugin-auth tree is byte-identical at `e9f4debc1`;
the merge of `main` brought only the objectui pin.
- `pnpm --filter @objectstack/plugin-auth typecheck`: exit 0.
`check:test-typecheck: OK`, 23 pinned signatures held, no new debt. 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`: the anchor hit
exactly once, and the mutation was verified on disk (anchor 1 → 0, blob
changed). The runner had its own EXIT/INT/TERM trap. Each restore was
verified as blob `01004ce16` == HEAD, with `git diff HEAD` empty. The
test imports `./auth-plugin` from source, so no `dist` is involved.
| Leg | Mutation (the forbidden behaviour put back) | Result |
|:--|:--|:--|
| 1 | `application_type: applicationType` → `type: applicationType` (the
old `type` forwarding) | `Tests 2 failed \| 13 passed`: native +
localhost / 127.0.0.1, `expected 400 to be 200` (the web refusal) |
| 2 | `applicationType = type` → `applicationType = 'native'` (web
judged as native) | `2 failed`: web + localhost `expected 200 to be
400`; web + https stored `native` |
| 3 | unhonoured values forwarded as `native` | `6 failed`: all six
unhonoured-type cases |
| 4 | the door rewrites the URI to one the provider accepts
(`http://app.` → `https://app.`, `127.0.0.2` → `127.0.0.1`) | `2
failed`: both native negative pins, `expected 200 to be 400` |
The first attempt at leg 3 used an anchor that the replacement still
contained. The tool refused it (`anchor count moved 1 -> 1`), restored,
and ran nothing. It was redone with a non-overlapping anchor. The
reading above is from the redone run.
## Gates
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at head `e9f4debc1` derived 72 commands.
That list is identical to the seat's derivation at `78ab6474c`. Each
command was run with `cmd > log 2>&1; rc=$?`.
- **Result.** 72/72 exit 0. This includes `check:adr-0087-registration
--base origin/main`, `check:changeset-no-major`,
`check:empty-changeset`, `check:closing-keyword-parity`,
`check:error-status-conformance`, `check:route-envelope`,
`check:auth-mount-ledger`, `check:durability-log-level`,
`check:issue-citations`, `check:nul-bytes`, `check:test-source-alias`,
`check:engine-double-contract` and `check:type-check-debt`.
- **`check:dual-build-cjs-loads`.** It first exited 3 (`PREREQUISITE NOT
MET`, no whole-repo `dist`). It passed after `turbo run build
--filter=!@objectstack/docs` (76 tasks, 75 from cache): 112 require
entry points across 69 packages load.
- **Reconciliation.** `--ran` printed `Run reconciliation — 72 derived,
72 run, 0 NOT-MEASURED, 0 UNRUN`.
- **Not run locally.**
- `test:repo`: CI `Test Core`.
- `check:dev-prereqs` and `check:skill-frame-freshness`: no
`packages/spec` build input or frame file is touched.
- The workspace type-check lanes and the CI-only jobs that
`dispatch-gates` lists outside the 72: CI.
## Acceptance notes
- **`user-agent-based` has a partial counterpart too.** In the
provider's rc.2 and 1.6.x lines, `user-agent-based` was valid only for a
public client (`token_endpoint_auth_method: 'none'`). In 1.7.x a browser
public client is `application_type: 'web'` plus
`token_endpoint_auth_method: 'none'`. So the `public` mapping question
covers both form values. MCP clients already register as public native
clients through DCR (`auth-manager.mcp-oauth-resource.test.ts:235`).
- **SDK.** `oauth.applications.register`
(`packages/client/src/index.ts:4535`) does not declare
`application_type` on its request type. It does declare
`token_endpoint_auth_method: 'none'`, and its response type
`OAuthApplication` declares `application_type`. The body passes through
unchanged, so a typed caller can register a native client only through a
cast. Noted, not filed. Carrier: none (承接者:无).
- **For half 2b.**
- The stored column `sys_oauth_application.type`
(`sys-oauth-application.object.ts:425`) also lists `user-agent-based`
and `public`, which 1.7.3 never writes.
- The `public` boolean column (`:432`) is never written by 1.7.3: its
record has no such field, and it was null in every stored row here.
- Carrier: #24773 half 2b, for the seat.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 4fe3af9 commit fbb42d9
3 files changed
Lines changed: 379 additions & 3 deletions
File tree
- .changeset
- packages/plugins/plugin-auth/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3637 | 3637 | | |
3638 | 3638 | | |
3639 | 3639 | | |
3640 | | - | |
3641 | | - | |
| 3640 | + | |
| 3641 | + | |
| 3642 | + | |
| 3643 | + | |
| 3644 | + | |
| 3645 | + | |
| 3646 | + | |
| 3647 | + | |
| 3648 | + | |
| 3649 | + | |
| 3650 | + | |
| 3651 | + | |
| 3652 | + | |
| 3653 | + | |
| 3654 | + | |
| 3655 | + | |
| 3656 | + | |
| 3657 | + | |
| 3658 | + | |
| 3659 | + | |
| 3660 | + | |
| 3661 | + | |
| 3662 | + | |
| 3663 | + | |
| 3664 | + | |
| 3665 | + | |
| 3666 | + | |
| 3667 | + | |
| 3668 | + | |
| 3669 | + | |
| 3670 | + | |
3642 | 3671 | | |
3643 | 3672 | | |
3644 | 3673 | | |
| |||
3658 | 3687 | | |
3659 | 3688 | | |
3660 | 3689 | | |
3661 | | - | |
| 3690 | + | |
3662 | 3691 | | |
3663 | 3692 | | |
3664 | 3693 | | |
| |||
| 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 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
0 commit comments