feat: email OAuth callback page gains a native-app fallback - when there is no web opener to postMessage (an ASWebAuthenticationSession has no popup parent) it redirects to warmbly://email-oauth with provider/code/state/error so the iOS app can finish the connect; web popup behavior is unchanged and the docs describe both delivery paths

This commit is contained in:
Matthew Meszaros
2026-07-11 10:13:10 +02:00
parent c12a621d06
commit 07a6777536
2 changed files with 15 additions and 0 deletions
@@ -503,6 +503,8 @@ Response:
}
```
After the user approves, the provider redirects to the API's callback page, which hands `code` and `state` back to the client: a web opener receives them via `postMessage`, and when there is no opener (a native in-app browser session) the page redirects to `warmbly://email-oauth?provider=...&code=...&state=...&error=...` instead. Either way, the client then calls Finish OAuth.
### Finish OAuth
`POST /emails/onboarding/oauth/finish`
@@ -13,6 +13,10 @@ import (
// The opener (the SPA) is expected to POST the code/state to
// /emails/onboarding/oauth/finish with the user's bearer token.
//
// Without an opener (the native app's ASWebAuthenticationSession, which has
// no popup parent) it instead redirects to the app's warmbly:// scheme; the
// session intercepts that navigation and the app calls oauth/finish itself.
//
// We keep this on the API rather than the SPA so that the provider's
// registered redirect_uri stays under our control and survives front-end
// reshuffles.
@@ -39,11 +43,20 @@ var callbackPage = template.Must(template.New("oauth-cb").Parse(`<!doctype html>
error: {{.Error}}
};
var origin = {{.AppOrigin}};
var delivered = false;
try {
if (window.opener) {
window.opener.postMessage(payload, origin || "*");
delivered = true;
}
} catch (e) { /* ignore */ }
if (!delivered) {
var q = "provider=" + encodeURIComponent(payload.provider || "") +
"&code=" + encodeURIComponent(payload.code || "") +
"&state=" + encodeURIComponent(payload.state || "") +
"&error=" + encodeURIComponent(payload.error || "");
try { window.location.replace("warmbly://email-oauth?" + q); } catch (e) { /* ignore */ }
}
setTimeout(function(){ try { window.close(); } catch(e){} }, 400);
})();
</script>