Repository navigation
Commit 5872450
fix(plugin-auth)!: the OAuth register door refuses an application type the provider does not judge with 400 INVALID_REQUEST, where it registered it as web (#25038)
Fixes #24773
Clause-②: no (narrowing) — `POST
/api/v1/auth/sys-oauth-application/register` accepted any `type` value
and registered every one other than `web` / `native` as a `web` client
with a secret (with one server warning since half 2a); it now refuses
each of those values with `400 INVALID_REQUEST`, so it accepts exactly
`web`, `native` and no type.
Half 2c, the last half of the maintainer's ruling D (`6293344532`) on
this card, under the claim `6302570331` as widened for half 2c. Half 1
landed as #24853, half 2a (the door forwards `application_type` and
warns) as #24992, half 2b (the form offers Web / Native) as #25011. This
PR turns half 2a's warning branch into the refusal, corrects the
register action's code comment in `platform-objects`, and carries one
BREAKING `minor` changeset. It is a narrowing, so it owes a review at
`CONTRACT_REVIEW_TIER` before the queue.
## 「甲」 reading (amended)
- Ruling D under the standing rule 「甲」 (#6015 `6292005854`, rule 2)
shipped only the loud half in 18.x (the warning, #24992) and left the
400 for 19.0.
- The maintainer's amendment (#6015 `6303865025`, quoting 「暂时不考虑
v19,协议的调整原则上也是尽快,不考虑退役版本。」) changes rule 2: a narrowing that no ADR-0087
conversion carries also ships now, in 18.x, as a BREAKING minor in the
shape of the `24813-adopt-orphans-readonly-package` changeset. No
refusal waits for 19.0, and no `pm:on-hold` card waits on a 19.0 line.
- No conversion carries this narrowing: it is the accept set of one
runtime REST door, and no metadata moves. So the changeset's ADR-0087
disposition is `not-required (no-migration-prescription)`, and it is
shipped as `minor` with `!` in the title, `Clause-②: no (narrowing)` and
the **BREAKING** banner. `check-adr-0087-registration --base
origin/main` reads it as `[BREAKING+bang+clause-②-narrowing]
not-required (no-migration-prescription)`, exit 0.
## What changes
`packages/plugins/plugin-auth/src/auth-plugin.ts`, the register
wrapper's application-type branch only:
| `type` in the body | Before (`73432299c`) | After |
|:--|:--|:--|
| `web`, `native` | forwarded as `application_type` | unchanged |
| absent, `null` | `web`, no warning | unchanged |
| `user-agent-based`, `public`, any other string, `''`, a non-string |
registered and judged as `web`, with a client secret, plus one `warn` |
`400` `{ success: false, error: { code: 'INVALID_REQUEST', message } }`,
the route's existing envelope (its `redirectURLs` refusal uses the same
one). The refusal comes before `createOAuthClient` is called, so nothing
is stored. No warning, no new error code. |
The message names the value (its first 64 characters, then an ellipsis
when cut, as the warning truncated it; a non-string is named by its
type, such as `(number)`) and the two accepted types, and says to choose
`native` for an `http://localhost`, `http://127.0.0.1` or `http://[::1]`
redirect URI:
```text
type "user-agent-based" is not an application type the OAuth provider judges: send 'web' or 'native', or no type for 'web'. Choose 'native' for an http://localhost, http://127.0.0.1 or http://[::1] redirect URI; 'web' needs https on a host that is not a loopback address.
```
`packages/platform-objects/src/identity/sys-oauth-application.object.ts`:
the `create_oauth_application` action's code comment only. It said the
refusal "waits for a major version"; it now states the 18.x refusal and
the stored column's readings (M2). No option, param or field changes.
The comment keeps its line count. The generated bundles do not carry
comments, so the i18n tooling regenerates nothing (`check:i18n` green).
esbuild keeps comments, though: a rebuild changed `dist/index.{js,mjs}`
and `dist/identity/index.{js,mjs}` (the comment text only; the `.map`
and `.d.ts` files are byte-identical). `platform-objects` is not
`private`, so its published output changes, and the changeset names it
with `patch`. That also puts this entry in `platform-objects`'
CHANGELOG, beside half 2b's entry.
### Absent, `null` and the empty string
Measured at `73432299c`: absent and `null` registered `web` with no
warning; `''` registered `web` with the warning, like any other
unhonoured value. This PR refuses `''` and keeps absent and `null`:
- **Absent and `null` stay `web`.** Neither names a type: half 2a's
branch read `null` as absent, and JSON `null` is the body's spelling of
no value. The provider's own default is `web`, and the form's declared
default is `web`. Refusing `null` would narrow the door past the ruling,
which refuses present values.
- **`''` is refused.** It is a present string that names no type, and it
already took the warning branch, whose text said "A future major version
refuses this value". So the set this PR refuses is exactly the set that
warned. No caller sends it: the Console dialog will not submit an empty
required param (M3).
## Measured first
### M1: the register door, value by value
Real stack in-process: real better-auth 1.7.3 with the OIDC provider on,
behind the plugin's real route registration on a Hono app, over a real
ObjectQL engine with `SqlDriver` (better-sqlite3 `:memory:`) and the
auth manifest's objects. Stored rows were read at driver level. Each
value was sent with an https redirect
(`https://app.example.com/callback`) and with a loopback one
(`http://localhost:4001/callback`). The probe was a throwaway file and
is not in this PR.
| `type` | Before, https | Before, loopback | After, https | After,
loopback |
|:--|:--|:--|:--|:--|
| `web` | 200, stored `web` | 400 `OAUTH_REGISTER_FAILED`
(`invalid_redirect_uri`) | same | same |
| `native` | 200, stored `native` | 200, stored `native` | same | same |
| `user-agent-based` | 200, stored `web`, 1 warning | 400
`OAUTH_REGISTER_FAILED`, 1 warning | 400 `INVALID_REQUEST`, nothing
stored, no warning | 400 `INVALID_REQUEST` |
| `public` | 200, stored `web`, 1 warning | 400 `OAUTH_REGISTER_FAILED`,
1 warning | 400 `INVALID_REQUEST` | 400 `INVALID_REQUEST` |
| `spa` (unknown string) | 200, stored `web`, 1 warning | 400, 1 warning
| 400 `INVALID_REQUEST` | 400 `INVALID_REQUEST` |
| `42`, `true`, `{ a: 1 }` | 200, stored `web`, 1 warning each
(`(number)` / `(boolean)` / `(object)`) | 400, 1 warning | 400
`INVALID_REQUEST` | 400 `INVALID_REQUEST` |
| absent | 200, stored `web`, no warning | 400 `OAUTH_REGISTER_FAILED` |
same | same |
| `null` | 200, stored `web`, no warning | 400 `OAUTH_REGISTER_FAILED` |
same | same |
| `''` | 200, stored `web`, 1 warning | 400, 1 warning | 400
`INVALID_REQUEST` | 400 `INVALID_REQUEST` |
| 200 characters | 200, stored `web`, 1 warning (value cut to 64) | 400,
1 warning | 400 `INVALID_REQUEST`, message carries 64 characters | 400
`INVALID_REQUEST` |
Every stored row, before and after, had `public` null and
`token_endpoint_auth_method` `client_secret_basic`, with a client
secret.
### M2: the stored `sys_oauth_application.type` column
Not narrowed: the select and the `public` column are unchanged.
- **What the installed provider writes.** `@better-auth/oauth-provider`
1.7.3 writes only `web` or `native`, or `null` for a
client-metadata-document client that names none
(`dist/authorize-9whjxVLJ.mjs:1956`). Every creation path goes through
`persistOAuthClientRegistration` and `checkOAuthClient`, which refuses
any other `application_type` (`:1770`): dynamic registration
`/oauth2/register` (the MCP clients' route), `/oauth2/create-client`
(this door and the SDK), `/admin/oauth2/create-client`, and the
client-metadata document. Both update paths (`/oauth2/update-client`,
`/admin/oauth2/update-client`) declare `application_type` as `web |
native` (`:2885`, `:2923`). better-auth 1.7.3 itself ships no
`oidc-provider` or `mcp` plugin. The generic data API cannot write the
table (`apiMethods: ['get', 'list']`), and no in-tree code writes the
column except through the provider (the `applicationType: 'type'`
mapping in `auth-schema-config.ts`).
- **What this door wrote before #24992.** It sent `type: safeType`, with
`safeType` one of `web` / `native` / `user-agent-based` or else `web`
(`fbb42d900^:auth-plugin.ts:3640`). 1.7.x strips `type`, so every row
was `web`. Under the earlier lines below, the door sent no
`token_endpoint_auth_method` (a confidential client), so
`user-agent-based` was refused there and `public` became `web`.
- **What earlier provider lines could write, through other doors.**
`@better-auth/oauth-provider` 1.6.33 and 1.7.0-rc.2 (npm tarballs, read
with grep only) declared `type` as `web | native | user-agent-based`,
required `native` or `user-agent-based` for a public client and `web`
for a confidential one, and wrote `public: isPublic`. plugin-auth's
`package.json` history (245 commits, read through the commits API): the
provider arrived as `^1.6.9` on 2026-05-08, ran `^1.6.11`–`^1.6.23` to
2026-07-16, `1.7.0-rc.1` from 2026-07-22 and `1.7.0-rc.2` from
2026-08-19, then `^1.7.1` from 2026-08-27. So a deployment that ran
plugin-auth between 2026-05-08 and 2026-08-27 can hold rows with `type =
'user-agent-based'` (a public client registered by name through dynamic
registration or create-client) and `public` set.
- **`type = 'public'`.** No writer found. No `oauth-provider` line
declares it. better-auth 1.6.x's own `mcp` plugin wrote `type:
'public'`, but plugin-auth never imported it: `auth-manager.ts` history
from 2026-02-01 to 2026-05-09, 31 commits read, 0 hits for `plugins/mcp`
or `mcp(` (control: `oidc-provider` / `oidcProvider(`, 8 hits). The
deprecated core `oidc-provider` wrote `web` on registration.
- **What the select declares.** `web`, `native`, `useragentbased`,
`public` (half 2b's reading: the string-array shorthand strips hyphens).
So the one non-`web`/`native` value a writer could ever produce,
`user-agent-based`, is not a declared value, and the declared
`useragentbased` and `public` have no writer in the tree, in the
installed provider or in the lines above.
- **Readers.** No in-tree reader branches on the column's values (`git
grep`: only the select declaration, the form and this wrapper). The
installed 1.7.3 has no `user-agent-based` token (`grep -c` 0) and reads
no `public` field; a public client is `token_endpoint_auth_method:
'none'`. The earlier lines did branch, requiring PKCE for `native`,
`user-agent-based` or `public === true` (1.6.33
`utils-Cdzs0w5-.mjs:420`). One branch remains on legacy data, from
source reading only: 1.7.3's two update paths re-run `checkOAuthClient`
over the stored client merged with the update, so a legacy row holding
`user-agent-based` would answer `application_type must be web or native`
to an update that does not also set `application_type`. That is not
measured on a row (Acceptance notes).
### M3: who sends a type other than `web` / `native` to this door
None found, so this PR breaks no in-repo sender.
- **This repo, at `73432299c`.** Every caller of the door sends no type
or `web`: `in-process-session-renewal.pin.test.ts`,
`request-json-body-refusal.test.ts`,
`oauth-register-refusal-status.test.ts` (`web`) and this PR's test file.
Docs: `content/docs/protocol/objectui/actions.mdx` shows the action with
no `type` param; the platform checklist (`identity-auth.json`) registers
with no type. `examples/` and `packages/qa`: 0 hits for the door. The
SDK never builds this URL (`disposition: 'server-only'` in the auth
route ledger).
- **The Console form.** Since #25011 it offers only `web` and `native`,
required, default `web`. objectui's `ActionParamDialog` pre-fills
`defaultValue` and refuses to submit a required param that is
`undefined`, `null`, empty or whitespace
(`packages/app-shell/src/views/ActionParamDialog.tsx:503`, `:515`,
`:537`, the same at the pinned `.objectui-sha` `cf153eb2e` and at
objectui `main` `a91f6bbb1`). So the form sends `web` or `native` only.
- **objectui `a91f6bbb1`.** 0 files for the door path,
`user-agent-based` or `application_type` (control:
`sys_oauth_application`, 5 files).
- **HotCRM `d723f6f`.** 0 files for the door path, `user-agent-based`,
`application_type`, `oauth2/create-client` or `applications.register`
(control: `oauth`, case-insensitive, 18 files).
## Tests
`packages/plugins/plugin-auth/src/oauth-register-application-type.test.ts`
(half 2a's file, the same real stack as M1). The provider's
`createOAuthClient` is wrapped in a pass-through spy, and each request
clears it.
- Half 2a's warning describe is replaced by the refusal pins.
`user-agent-based`, `public`, `spa`, `''`, `42`, `true` and an object,
each with an https and with a loopback redirect: `400`, ADR-0112
envelope clean, `error.code` `INVALID_REQUEST`, the provider never
called, no row written, no warning and no error logged.
- The message pin: a 64-character value plus a tail is echoed up to the
64 characters and never the tail; the message names `'web'`, `'native'`,
`http://localhost`, `http://127.0.0.1` and `http://[::1]`; a short value
is named whole; a non-string is named `(number)`.
- Added: `type: null` registers `web` with no warning. Every accepted
registration now also asserts exactly one provider call, carrying the
`application_type` it stored.
- Half 2a's pins (provider preconditions, `web` / `native` forwarding,
the loopback rules, absent → `web`) pass unchanged.
Results:
- At `ee660f7cd` (the fix, before merging `main`): this file plus
`oauth-register-refusal-status.test.ts`: `Test Files 2 passed (2)`,
`Tests 42 passed (42)`. The full `plugin-auth` suite: `Test Files 174
passed (174)`, `Tests 3344 passed | 10 skipped (3354)`.
`platform-objects`: `Test Files 83 passed (83)`, `Tests 1239 passed
(1239)`, half 2b's `oauth-register-form-types.test.ts` included.
`platform-objects` typecheck: exit 0, `check:test-typecheck: OK`, 2
pinned signatures held.
- At `3f934a772` (after merging `main` `4d991588f`, which touched
`plugin-auth`'s `auth-manager.ts`): a rebuilt closure, then the full
`plugin-auth` suite: `Test Files 175 passed (175)`, `Tests 3348 passed |
10 skipped (3358)`. `plugin-auth` typecheck: exit 0,
`check:test-typecheck: OK`, 23 pinned signatures held. No debt was
added, and the test file is in the `tsconfig.test.json` program
(`--listFilesOnly`, 1 hit). `platform-objects` is untouched by the
merge.
## Ablation (one-shot; no permanent file)
Every leg went through `scripts/ablation-replace.mjs` in WRAP mode, in a
runner with its own EXIT/INT/TERM trap that re-proved each restore. Each
anchor hit once and fell from 1 to 0, with the blob changed on disk.
Each restore was proven as blob `d065fc855` == HEAD, with `git diff
HEAD` empty and `git status --porcelain` empty at the end. The test
imports `./auth-plugin` from source, so there is no `dist` leg. The
unmutated control: 25 passed.
| Leg | Mutation | Result |
|:--|:--|:--|
| A | half 2a's warning branch put back: warn, then register as `web` |
15 failed, 10 passed: every refusal case (`expected 200 to be 400`; on
loopback, `expected 'OAUTH_REGISTER_FAILED' to be 'INVALID_REQUEST'`)
and the message pin |
| G | silently mapped to `native` instead of refused | 15 failed, the
same cases |
| B | the refusal kept, plus a warning | 14 failed: `the door still
warns instead of (or as well as) refusing: expected [ [ …(2) ] ] to
deeply equal []` |
| C | the provider called before the refusal | 14 failed: `the door
called the provider for a type it refuses: expected [ { client_name:
'Typed', …(2) } ] to deeply equal []` |
| D | the value echoed untruncated | 1 failed: `the message echoes the
value past its first 64 characters: expected 'type "aaaa…' not to
contain 'TAIL'` |
| E | `null` refused | 1 failed: the `null` → `web` pin, `expected 400
to be 200` |
| F | absent refused too | 2 failed: the absent and `null` pins |
The first runs of legs B and C were refused by the tool and ran nothing
("the anchor count moved 1 -> 1": the replacement contained its anchor).
They were redone with an anchor the replacement does not contain. The
figures above are from the redone runs.
## Gates
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at the final head `3f934a772` derived 74
commands (the seat's 57 at `73432299c`, plus 17 that this diff's paths
and its changeset add). The diff is 190 changed lines (+138 / -52)
across 4 files, under the 3000 human-merge threshold. Each command ran
as `cmd > log 2>&1; status=$?`, recorded byte for byte with `:: exit N`.
- **First pass.** 72 of 74 exited 0. `check:dual-build-cjs-loads` and
`check:i18n` exited 3 (`PREREQUISITE NOT MET`: no whole-workspace
`dist`, and no built CLI), so they measured nothing. After `turbo run
build --filter=!@objectstack/docs --concurrency=2` (76 tasks), both
exited 0: "112 published require entry point(s) across 69 package(s)"
and `check-i18n-bundles: OK (10 package(s) — all bundles in sync, no
undeclared authoring keys)`.
- **Reconciliation.** `--ran`: `Run reconciliation — 74 derived, 74 run,
0 NOT-MEASURED, 0 UNRUN`.
- **Verdicts among the 74.** `check-adr-0087-registration --base
origin/main`: `1 declared-breaking changeset(s), each carrying an
ADR-0087 disposition`, read as `[BREAKING+bang+clause-②-narrowing]
not-required (no-migration-prescription)`. Also green:
`check-changeset-no-major`, and `check-empty-changeset` ("No changeset
from the merge base modified or deleted").
`check:error-status-conformance`: "every derivable runtime status is
documented, and every documented status is reachable".
`check:issue-citations`, `check:doc-authoring`, `check:nul-bytes`,
`check:route-envelope`, `check:auth-mount-ledger`,
`check:closing-keyword-parity`, `check:type-check-debt` and
`check:engine-double-contract` are green too.
- **Not run locally.** `test:repo` is CI's `Test Core`.
`check:dev-prereqs` and `check:skill-frame-freshness` do not apply: no
`packages/spec` build input or frame file is touched. No exported symbol
is renamed or removed, so `check-platform-checklist --changed-from` does
not apply. The type-check lanes and the CI jobs that `dispatch-gates`
lists outside the 74 belong to CI.
- **Dogfood.** This PR has a `(narrowing)` arm, so the full dogfood
battery runs once on this head after the PR opens. Its reading is in the
dev report on #24773.
- **After the patch rounds:** `check-empty-changeset` is red by design
at the final head `48857fcc6` (see `## Pending release notes corrected`
below). The re-verification at `2da95c6da`, after merging `main`
`dfe60e3a0`:
- the full `plugin-auth` suite: `Test Files 175 passed (175)`, `Tests
3348 passed | 10 skipped (3358)`;
- `plugin-auth` typecheck: `check:test-typecheck: OK`, 23 pinned
signatures held;
- `--ran`: `74 derived, 74 run, 0 NOT-MEASURED, 0 UNRUN`. The 21
families that the edited changesets trigger were re-run at `2da95c6da`:
20 exit 0, and `check-empty-changeset` exits 1 as above. The other 53
are carried from `3f934a772`.
- **r3, `8eba09f8c` (prose only, this PR's own changeset).**
`.changeset/24773-oauth-type-refusal.md` now describes the change from
the last published state, 18.0.0, where the door registered such a type
as `web` silently. The **FROM** bullet drops ", and the server logged
one warning naming the value", and the **TO** bullet drops ": the
refusal replaces that warning": half 2a's warning never reached a
published release. `dispatch-gates --commands` over that one path
derived the same 21 families. All 21 re-ran at `8eba09f8c`: 20 exit 0
(`check-adr-0087-registration --base origin/main`: "1 declared-breaking
changeset(s), each carrying an ADR-0087 disposition"), and
`check-empty-changeset --base origin/main` exits 1, the expected red
naming only the two corrected foreign changesets. `--ran`: `74 derived,
74 run, 0 NOT-MEASURED, 0 UNRUN`, with the other 53 carried from
`3f934a772`.
- **r4, `48857fcc6` (prose only, this PR's own changeset).**
`.changeset/24773-oauth-type-refusal.md` was re-read against the last
published state, 18.0.0. In 18.0.0 the door sent the type as `type`,
which the provider stripped, so every client was `web`. Provider
refusals answered `500`, and the form offered four types. Three phrases
stated this release's intermediate behaviour as the FROM state, and were
changed:
- **FROM.** "answered the web rule's `400 OAUTH_REGISTER_FAILED` for a
loopback redirect URI" became "was refused by the web rule
(`OAUTH_REGISTER_FAILED`) for a loopback redirect URI". In 18.0.0 that
refusal answered `500`.
- **Unchanged.** "**Unchanged:** `web` and `native` are forwarded as the
provider's `application_type`, and the provider's own redirect-URI rule
decides. ... The other refusals keep their codes and statuses." was
split into two bullets. The first is "**Accepted:** `web` and `native`
are forwarded as the provider's `application_type` (this release's
Application Type entry), and the provider's own redirect-URI rule
decides." The second is "**Unchanged:** ... The other refusals keep
their codes (their statuses are this release's refusal-status entry)."
The reason: in 18.0.0, `native` was judged and stored as `web`, and the
statuses change in this release's refusal-status entry.
- **The form.** "The form already offers only Web and Native." became
"The form offers only Web and Native (this release's register-form
entry)." In 18.0.0 the form offered four types.
- Gates: `dispatch-gates --commands` over that path derived the same 21
families, re-run at `48857fcc6`. 20 exit 0 (`check-adr-0087-registration
--base origin/main`: "1 declared-breaking changeset(s), each carrying an
ADR-0087 disposition"); `check-empty-changeset --base origin/main` exits
1, the expected red naming only the two corrected foreign changesets.
`--ran`: `74 derived, 74 run, 0 NOT-MEASURED, 0 UNRUN`, with the other
53 carried from `3f934a772`.
- The dogfood battery is not re-run. The merge moved `driver-sql`,
`objectql`, `service-settings`, `service-messaging`, `cli` and `core`,
none of it under this diff, and this round's delta is prose only.
## Pending release notes corrected
This PR's refusal made sentences in two of the card's own pending
changesets false. They are corrected in `2da95c6da` (final head
`48857fcc6`). Every still-true sentence is kept, and each file keeps its
frontmatter (package and bump). `check-empty-changeset` refuses the edit
as a foreign-changeset modification. That is its DELIBERATE CORRECTION
class, red by design at the final head `48857fcc6`, as since `2da95c6da`
(CI `Check Changeset`: "exists on the merge base and was not added by
this PR"), for the seat's same-head contract review to confirm. The
evidence for every correction is M1 AFTER above: `user-agent-based`,
`public`, `spa`, `''` and non-strings answer `400 INVALID_REQUEST`, with
no row stored and no warning logged.
**`.changeset/24773-oauth-application-type.md`**
(`@objectstack/plugin-auth`: patch, unchanged)
- The bullet label changes from "**Unchanged, with a warning.**" to
"**Refused.**".
- BEFORE: "It keeps today's outcome: registered and judged as `web`,
with a client secret. The server now logs one warning per such
registration, naming the value."
- AFTER: "The register route refuses it with `400 INVALID_REQUEST` and
registers nothing (this release's breaking entry for the route)."
- Why the original is false: M1 AFTER. These types are refused with 400;
no `web` client is registered and no warning is logged.
- Kept verbatim: "Any other type, including the form's
`user-agent-based` and `public`, has no `application_type` in the
provider." and "To use a loopback redirect URI, register the client as
`native`."
**`.changeset/24773-oauth-form-types.md`**
(`@objectstack/platform-objects`: patch, unchanged)
- The Clause-② reason.
- BEFORE: "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)"
- AFTER: "Clause-②: no (a form's offering: nothing on the server reads
the action's param options, so this change moves no accept set or served
shape; the register route's refusal of the other types is its own
breaking entry in this release)"
- Why the original is false: M1 AFTER; the route no longer accepts every
value it accepted. The value stays `no`, because the form change itself
moves no accept set. The parenthetical does not start with an arm word,
so the line's reading is unchanged.
- BEFORE: "The OAuth provider judges only `web` and `native`, so the
register route registers the other two as `web`, with a client secret."
- AFTER: "The OAuth provider judges only `web` and `native`, and the
register route refuses any other type with `400 INVALID_REQUEST`."
- Why the original is false: M1 AFTER.
- BEFORE: "The register route accepts the same values as before,
including `user-agent-based` and `public`." (the first sentence of the
**Unchanged.** bullet)
- AFTER: removed. The bullet keeps "The stored
`sys_oauth_application.type` column still lists all four values, so a
row written under an earlier provider line keeps its value. The `public`
column is unchanged."
- Why the original is false: M1 AFTER. The refusal is stated in this
PR's own entry and in the corrected sentence above.
**`.changeset/24773-oauth-register-refusal-status.md`**
(`@objectstack/plugin-auth`: patch) is not edited, because none of its
sentences is false. Each one speaks of the refusals the provider raises
(`400` for `invalid_redirect_uri`, `401` for a missing session, `500`
for a throw that declares no status, the body-schema `400`), and those
are unchanged. The new type refusal comes before the provider is called.
So do the route's own `name` / `redirectURLs` refusals, which this note
never covered either. Its closing sentence, "a `web` client still needs
an `https` URI on a non-loopback host", stays true.
## Acceptance notes
- **Legacy rows and the update paths.** Rows written under
`@better-auth/oauth-provider` 1.6.x / 1.7.0-rc (2026-05-08 to
2026-08-27) can hold `type = 'user-agent-based'` and a `public` value.
1.7.3's update paths re-validate the stored client, so such a row would
answer `application_type must be web or native` to an update that does
not also set `application_type`. This comes from reading source; it was
not measured on a row, and no deployment's data is in reach.
- **The stored select declares two values no writer produces.**
`useragentbased` and `public` (M2). Reported to the seat in the dev
report, not narrowed here.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01UmqzD97e14iws6hmVhgZ5y)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent a55a806 commit 5872450
6 files changed
Lines changed: 143 additions & 56 deletions
File tree
- .changeset
- packages
- platform-objects/src/identity
- plugins/plugin-auth/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
| 12 | + | |
13 | 13 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | | - | |
| 9 | + | |
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
14 | 14 | | |
15 | 15 | | |
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
151 | 151 | | |
152 | 152 | | |
153 | 153 | | |
154 | | - | |
155 | | - | |
156 | | - | |
157 | | - | |
158 | | - | |
159 | | - | |
160 | | - | |
161 | | - | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
162 | 162 | | |
163 | 163 | | |
164 | 164 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3647 | 3647 | | |
3648 | 3648 | | |
3649 | 3649 | | |
3650 | | - | |
3651 | | - | |
3652 | | - | |
3653 | | - | |
3654 | | - | |
3655 | | - | |
3656 | | - | |
| 3650 | + | |
| 3651 | + | |
| 3652 | + | |
| 3653 | + | |
| 3654 | + | |
| 3655 | + | |
| 3656 | + | |
| 3657 | + | |
| 3658 | + | |
| 3659 | + | |
| 3660 | + | |
3657 | 3661 | | |
3658 | 3662 | | |
3659 | 3663 | | |
3660 | 3664 | | |
3661 | | - | |
3662 | | - | |
3663 | | - | |
3664 | | - | |
3665 | | - | |
3666 | | - | |
3667 | | - | |
3668 | | - | |
3669 | | - | |
| 3665 | + | |
| 3666 | + | |
| 3667 | + | |
| 3668 | + | |
| 3669 | + | |
| 3670 | + | |
| 3671 | + | |
| 3672 | + | |
| 3673 | + | |
| 3674 | + | |
| 3675 | + | |
| 3676 | + | |
| 3677 | + | |
| 3678 | + | |
3670 | 3679 | | |
3671 | 3680 | | |
3672 | 3681 | | |
| |||
0 commit comments