mirror of
https://github.com/lobehub/lobehub.git
synced 2026-09-20 04:56:13 +08:00
c3a7642295
* ✨ feat(oauth-apps): support web applications with redirect URIs OAuth apps created from the settings page were device-flow only: no redirect URIs, no response types, and no client secret. That covers CLIs but leaves no way to register a server-side app that signs users in with a LobeHub account through the authorization code flow. Adds a `web` application type alongside the existing device type. A web app gets `authorization_code` + `refresh_token` grants, a `code` response type, the redirect URIs its owner registers, and a confidential client secret issued once at creation with a rotate action afterwards. The secret is encrypted at rest with `KeyVaultsGateKeeper`, mirroring how API keys are stored, and decrypted in the OIDC adapter because the token endpoint compares it in plaintext. A secret that fails to decrypt is dropped rather than passed through, so the request fails as `invalid_client` instead of making ciphertext the credential. Redirect URIs are matched exactly: no wildcards, no fragments, no embedded credentials, and https everywhere except loopback hosts. The same rules run in the form and again in the tRPC input, since only the server-side pass is binding. No migration: `oidc_clients` already carries every column this needs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 🐛 fix(oauth-apps): keep the adapter's secret round-trip symmetric Two follow-ups from review. `Client.upsert` still wrote the secret in plaintext while `find` now expects ciphertext, so anything persisting a client through the adapter would read back a credential it could not authenticate with. Encrypt on the way in too, and leave the stored secret untouched when an update payload omits it. The conflict branch also referenced a camelCase key that never existed on the payload, so it silently wrote nothing. An `update` carrying an empty redirect URI list passed validation and stripped a web app of every callback, contradicting the create-time requirement. Reject a list that normalizes to empty wherever one is supplied. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * 💄 feat(oauth-apps): address acceptance review on create, detail and consent flows Application type is now picked from two icon cards that explain each kind, instead of a bare segmented toggle. Redirect URIs move out of the create dialog and are configured on the app page afterwards; the type stays at creation because it decides whether a client secret is issued and cannot change once the app exists. A freshly created web app shows that it has no redirect URIs yet and cannot sign anyone in until one is added. The detail page separates reading from editing. Basic info and redirect URIs each render read-only with an Edit action, and open a form with Save and Cancel only on request. Redirect URIs are edited as a list of rows rather than a newline-separated textarea. Authorization errors the provider cannot redirect back with, such as an unregistered redirect URI, now land on a dedicated page with a plain explanation per error code instead of a raw JSON dump. Only `error` and `error_description` are forwarded. The consent and sign-in confirmation cards center their title and subtitle so the text lines up with the centered application logo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ♿ fix(oauth-apps): label redirect URI row removal and color the list error The icon-only remove button on each redirect URI row had no accessible name, so assistive tech announced it as an unlabeled button. The "at least one redirect URI" message rendered in body color: antd's ErrorList only picks up error styling inside a Form.Item with an error status, and the list-level message sits outside one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>