Skip to content

Declining OAuth consent is reported as "Popup was blocked by the browser" #390

Description

@BSalaeddin

Version: @useupup/core@3.3.0, @useupup/react@3.3.0 (the only version published on either scope, 2026-09-02).

Reproduce. Mount UpupUploader with sources: ['local','oneDrive'] and a oneDrive.clientId whose tenant requires admin consent (any Microsoft work account with the default user-consent policy will do). Open the OneDrive tile, then decline or close the Microsoft consent screen.

Expected. Something that names what happened: sign-in was cancelled / not granted.

Actual. The picker shows Couldn't load files: Popup was blocked by the browser. Nothing was blocked, and the message sends the user into their browser's popup settings over a choice they made themselves.

Seen on production on 2026-09-08, after Microsoft answered the consent request with a tenant admin-approval wall and the person declined it.

Why it happens — three things compounding, all read out of the published dist:

  1. packages/react/src/components/shared/DriveAuthFallback.tsx (dist/chunk-6TFTTLOD.js:31) auto-triggers auth on mount:
    useEffect(() => {
      if (attemptedRef.current || error) return
      attemptedRef.current = true
      onRetry()
    }, [error, onRetry])
  2. packages/react/src/components/OneDriveUploader.tsx (dist/OneDriveUploader-I25Y6AJP.js:151) renders that fallback without passing error — it stays inside the ...uploaderProps rest and is never forwarded — so the || error guard can never fire. Dropbox and Box render the same way. A decline drives isLoading false, the fallback re-mounts with a fresh attemptedRef, and it retries by itself.
  3. That retry's window.open runs with no transient user activation left, returns null, and packages/core/src/drives/popup-oauth-plugin.ts (dist/chunk-PMBR3OUW.js:944-956) reports the block — truthful about its own call, misleading about the user.

Two smaller things in the same path:

  • The decline is never surfaced at all. The popup poll only accepts href.startsWith(redirectUri) && href.includes('code='); an ?error=access_denied redirect falls through, and closing the window resolve()s silently with no error and no state.
  • popup-oauth-plugin.ts throws the English literal 'Popup was blocked by the browser' while the catalogue already ships errors.popupBlocked ("Popup blocked", translated into nine locales) — so this string is never localised, and it is the one users actually see through driveBrowser.loadError's {message}.

Host apps cannot work around it. onError is (errorMessage: string) => void and is subscribed to upload failures only (dist/internal.js:543); the six drive events are bound to DriveBrowserController's internal handler (dist/internal.js:1187) and never reach the host, so there is no typed error, no UpupErrorCode, and nothing to branch on. Overriding driveBrowser.loadError through i18n.overrides only changes the wrapper around the same misleading {message}.

Suggested fix.

  1. Forward error into DriveAuthFallback from all four drive components so its existing guard works.
  2. Do not auto-retry a popup after the first attempt — a second window.open outside a gesture cannot succeed, so retrying only manufactures a false popup-blocked report.
  3. Read error / error_description off the redirect and emit a distinct cancellation, and treat a closed popup as a cancellation rather than a silent resolve.
  4. Throw errors.popupBlocked through the translator rather than an English literal.

What the reporter did instead. An i18n.overrides entry on driveBrowser.loadError that still prints the library's {message} verbatim and asserts no cause of its own — copy, not a fix, and marked as such in the source. It is deleted the day upstream reports a decline as a decline. No node_modules patch and no fork.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions