Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions doc/PKCE_AUTH_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
| Extension PKCE implementation | ✅ Done | `OAuthService.ts`, `PKCEManager.ts` |
| Chrome redirect URI registered | ✅ Done | `chromiumapp.org` |
| Sign-out race condition fix | ✅ Done | `JwtManager.ts` |
| Firefox PKCE support | ⚠️ Partial | Falls back to cookie-based auth |
| Firefox PKCE support | ✅ Done | Tab-based PKCE via `TabBasedPKCEAuth.ts` |
| Refresh token rotation | ❓ Untested | Needs verification |

---
Expand Down Expand Up @@ -210,12 +210,16 @@ The server must whitelist these redirect URI patterns for `client_id=saypi-exten
|-------------|--------------|--------|
| Chrome (published) | `https://pepnjahikiccmajdphdcgeemmedjdgij.chromiumapp.org/` | ✅ Registered |
| Chrome (development) | May vary - extension ID changes in dev mode | ⚠️ Not registered |
| Firefox | `https://<addon-id>.extensions.allizom.org/` | ❌ Not registered |
| Firefox | `https://gecko@saypi.ai.extensions.allizom.org/` | ⚠️ Needs registration |

**Note**: Firefox supports `browser.identity.launchWebAuthFlow()` but the extension currently falls back to cookie-based authentication. Enabling Firefox PKCE would require:
1. Registering the Firefox redirect URI on the server
2. Testing that `hasIdentityAPI()` returns true on Firefox
3. Verifying the flow works with Google/GitHub OAuth (known issues with some providers)
**Firefox PKCE Implementation**: Firefox uses tab-based PKCE authentication via `TabBasedPKCEAuth.ts`:
1. Opens authorization URL in a new browser tab
2. Monitors tab URL changes via `browser.tabs.onUpdated`
3. Detects redirect to `extensions.allizom.org`
4. Extracts authorization code and exchanges for tokens
5. Closes the auth tab

**Server requirement**: Register the Firefox redirect URI to enable PKCE authentication.

### Testing the Fix

Expand Down
19 changes: 16 additions & 3 deletions src/UserAgentModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ export function isFirefox(): boolean {
return /Firefox/.test(navigator.userAgent);
}

/**
* Check if the browser is Firefox on Android
* Used for mobile-specific auth and UI handling
*/
export function isFirefoxAndroid(): boolean {
return isFirefox() && /Android/.test(navigator.userAgent);
}

/**
* Check if the browser is Kiwi Browser (Android Chromium with extension support)
*/
export function isKiwiBrowser(): boolean {
return /Kiwi/.test(navigator.userAgent) && /Android/.test(navigator.userAgent);
}

export function isMobileDevice(): boolean {
const userAgent =
typeof navigator !== "undefined" && typeof navigator.userAgent === "string"
Expand Down Expand Up @@ -125,11 +140,9 @@ export function getTTSCompatibilityIssue(chatbotType: string): {
}

export function addUserAgentFlags(): void {
const isFirefoxAndroid: boolean =
/Firefox/.test(navigator.userAgent) && /Android/.test(navigator.userAgent);
const element: HTMLElement = document.documentElement;

if (isFirefoxAndroid) {
if (isFirefoxAndroid()) {
element.classList.add("firefox-android");
}

Expand Down
27 changes: 20 additions & 7 deletions src/auth/OAuthService.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/**
* OAuthService - OAuth 2.1 + PKCE authentication flow for browser extensions
*
* Handles the complete OAuth authorization flow using browser.identity.launchWebAuthFlow
* for Chrome and fallback to tab-based flow for Firefox.
* Handles the complete OAuth authorization flow:
* - Chrome/Kiwi: Uses browser.identity.launchWebAuthFlow with PKCE
* - Firefox: Uses tab-based PKCE flow with browser.tabs.onUpdated
*
* @see https://developer.chrome.com/docs/extensions/reference/identity/
* @see ./TabBasedPKCEAuth.ts for Firefox implementation
*/

import { browser } from 'wxt/browser';
Expand All @@ -18,6 +20,10 @@ import {
clearPKCEState,
type PKCEState,
} from './PKCEManager';
import {
authenticateWithTabBasedPKCE,
shouldUseTabBasedPKCE,
} from './TabBasedPKCEAuth';

/**
* OAuth client ID for the SayPi extension
Expand Down Expand Up @@ -351,23 +357,30 @@ async function authenticateWithTabFlow(): Promise<OAuthResult> {

/**
* Start the OAuth authentication flow
* Uses identity API for Chrome, falls back to tab flow for Firefox
* Uses identity API for Chrome/Kiwi, tab-based PKCE for Firefox
*/
export async function authenticate(): Promise<OAuthResult> {
if (hasIdentityAPI()) {
logger.debug('[OAuthService] Using identity API for authentication');
return authenticateWithIdentityAPI();
} else {
logger.debug('[OAuthService] Falling back to tab-based authentication');
return authenticateWithTabFlow();
}

if (shouldUseTabBasedPKCE()) {
logger.debug('[OAuthService] Using tab-based PKCE for authentication');
return authenticateWithTabBasedPKCE();
}

// Ultimate fallback to cookie-based flow (should rarely be needed)
logger.debug('[OAuthService] Falling back to cookie-based authentication');
return authenticateWithTabFlow();
}

/**
* Check if PKCE authentication is supported on this browser
* Returns true for both identity API (Chrome) and tab-based PKCE (Firefox)
*/
export function isPKCESupported(): boolean {
return hasIdentityAPI();
return hasIdentityAPI() || shouldUseTabBasedPKCE();
}

/**
Expand Down
Loading