History

Lead with manual entry when a server rejects automatic registration

After transparent DCR is rejected (e.g. a server that only approves loopback redirect URIs), the recovery "Register an OAuth app" form still led with "Register automatically", sending the user straight back into the path that just failed. Pass the rejection reason into OAuthClientForm so it suppresses the auto path, restates why, and leads with manual client id/secret entry plus the callback URL to allow-list.

Rhys Sullivan committed 4 months ago

Commit 8fe2a971 parent a414b712 files · +22 −3Browse files

Unified · Split

2 files
  1. packages/react/src/components/add-account-modal.tsx +1 −0
  2. packages/react/src/components/oauth-client-form.tsx +21 −3
packages/react/src/components/add-account-modal.tsx+1−0
@@ -1348,6 +1348,7 @@ function AddAccountModalView(props: AddAccountModalProps) { · 1347 unmodified lines
13481348 existingSlugs={[...oauthApps, ...oauthOtherApps].map((app: OAuthClientOption) =>
13491349 String(app.slug),
13501350 )}
1351+ autoRegisterRejectedReason={dcrFallbackMessage}
13511352 prefill={{
13521353 authorizationUrl:
13531354 oauthHandoffPrefill?.authorizationUrl ??
packages/react/src/components/oauth-client-form.tsx+21−3
@@ -88,6 +88,11 @@ export function OAuthClientForm(props: { · 87 unmodified lines
8888 readonly onCreated: (result: { readonly owner: Owner; readonly slug: OAuthClientSlug }) => void;
8989 readonly onCancel?: () => void;
9090 readonly surface?: "card" | "plain";
91+ /** When set, the server's automatic (DCR) registration is known to be rejected
92+ * for this host (e.g. it refused our redirect URI). The form then suppresses
93+ * the "Register automatically" path and leads with manual client entry; the
94+ * string is the actionable reason shown to the user. */
95+ readonly autoRegisterRejectedReason?: string | null;
9196 }) {
9297 const {
9398 integrationName,
@@ -98,6 +103,7 @@ export function OAuthClientForm(props: { · 4 unmodified lines
98103 onCreated,
99104 onCancel,
100105 surface = "card",
106+ autoRegisterRejectedReason = null,
101107 } = props;
102108 // Non-org hosts (local/desktop) have one local workspace. Offer only Local,
103109 // so the owner dropdown (which hides on a single option) disappears.
@@ -178,6 +184,11 @@ export function OAuthClientForm(props: { · 74 unmodified lines
178184 tokenUrl.trim().length > 0 &&
179185 grant === "authorization_code";
180186
187+ // When the server already rejected automatic registration for this host (e.g.
188+ // it refused our non-loopback redirect URI), don't lead the user back into the
189+ // path that just failed: suppress the auto CTA and lead with manual entry.
190+ const showAutoRegister = canRegisterDynamic && autoRegisterRejectedReason === null;
191+
181192 const handleDiscover = async () => {
182193 const url = issuerUrl.trim();
183194 if (url.length === 0) {
@@ -283,12 +294,19 @@ export function OAuthClientForm(props: { · 99 unmodified lines
283294 <div className="space-y-1">
284295 <p className="text-sm font-medium">Register an OAuth app</p>
285296 <p className="text-xs text-muted-foreground">
286− {canRegisterDynamic
297+ {showAutoRegister
287298 ? "Register automatically below, or enter a client id/secret manually."
288299 : "Paste a client id/secret. We only ask for endpoints when they aren't already known."}
289300 </p>
290301 </div>
291302
303+ {autoRegisterRejectedReason ? (
304+ <div className="space-y-1 rounded-lg border border-destructive/40 bg-destructive/5 p-3">
305+ <p className="text-sm font-medium text-destructive">Automatic registration unavailable</p>
306+ <p className="text-xs text-muted-foreground">{autoRegisterRejectedReason}</p>
307+ </div>
308+ ) : null}
309+
292310 {/* app name */}
293311 <div className="space-y-1.5">
294312 <Label htmlFor="oauth-app-name" className="text-xs text-muted-foreground">
@@ -305,7 +323,7 @@ export function OAuthClientForm(props: { · 10 unmodified lines
305323
306324 {/* register automatically (RFC 7591 DCR) — the primary path when the
307325 server advertises a registration endpoint: no client id/secret needed */}
308− {canRegisterDynamic ? (
326+ {showAutoRegister ? (
309327 <div className="space-y-2 rounded-lg border border-ring/40 bg-accent/30 p-3">
310328 <p className="text-sm font-medium">No client ID needed</p>
311329 <p className="text-xs text-muted-foreground">
@@ -365,7 +383,7 @@ export function OAuthClientForm(props: { · 53 unmodified lines
365383 </div>
366384
367385 {/* divider before the manual (secondary) path when DCR is available */}
368− {canRegisterDynamic ? (
386+ {showAutoRegister ? (
369387 <div className="flex items-center gap-2 text-xs text-muted-foreground">
370388 <span className="h-px flex-1 bg-border/60" />
371389 or enter a client ID manually

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t