fireproof icon indicating copy to clipboard operation
fireproof copied to clipboard

Implement cross-domain logout for Fireproof Cloud authentication

Open jchris opened this issue 3 months ago • 1 comments

Problem

Users remain authenticated on connect.fireproof.direct after logging out locally, causing confusion and potential security concerns. The authentication persists across the domain boundary because:

  1. Clerk manages authentication on connect.fireproof.direct
  2. Local logout only clears Fireproof tokens, not the Clerk session
  3. No logout endpoint exists to clear the remote session

Proposed Solution

Implement a comprehensive cross-domain logout mechanism that clears both local and remote sessions.

Implementation Plan

1. Enhance TokenStrategie Interface

Add optional performLogout method to the TokenStrategie interface:

export interface TokenStrategie {
  // ... existing methods
  performLogout?(sthis: SuperThis, logger: Logger, opts: ToCloudOpts): Promise<boolean>;
}

2. Implement performLogout in Strategy Classes

RedirectStrategy - Use popup-based logout with enhanced error handling:

async performLogout(sthis: SuperThis, logger: Logger, opts: ToCloudOpts): Promise<boolean> {
  this.stop();
  
  if (this.overlayNode) {
    this.overlayNode.style.display = 'none';
  }
  
  const webCtx = opts.context.get(WebCtx) as WebToCloudCtx;
  const logoutUrl = webCtx.dashboardURI.replace('/fp/cloud/api/token', '/fp/cloud/api/logout');
  
  return new Promise((resolve) => {
    let popup: Window | null = null;
    
    try {
      popup = window.open(logoutUrl, 'FireproofLogout', 'width=400,height=300,popup=yes');
      
      if (!popup) {
        console.warn('Popup blocked - logout may be incomplete');
        resolve(false);
        return;
      }
    } catch (error) {
      console.error('Failed to open logout popup:', error);
      resolve(false);
      return;
    }
    
    const handleMessage = (event: MessageEvent) => {
      if (event.data?.type === 'fireproof-logout-complete') {
        cleanup();
        resolve(true);
      }
    };
    
    // Handle manual popup closure
    const checkClosed = setInterval(() => {
      if (popup?.closed) {
        cleanup();
        resolve(false);
      }
    }, 1000);
    
    const cleanup = () => {
      window.removeEventListener('message', handleMessage);
      clearInterval(checkClosed);
      popup?.close();
    };
    
    window.addEventListener('message', handleMessage);
    
    // Timeout fallback
    setTimeout(() => {
      cleanup();
      resolve(false);
    }, 30000);
  });
}

IframeStrategy - Similar implementation using iframe communication SimpleTokenStrategy - Basic implementation returning true

3. Create performCompleteLogout Helper

Add to WebCtxImpl for coordinating the full logout process:

async performCompleteLogout(strategy?: TokenStrategie): Promise<void> {
  // Call strategy's performLogout if available
  if (strategy?.performLogout) {
    await strategy.performLogout(this.sthis, logger, this.opts);
  }
  
  // Clear local tokens
  await this.resetToken();
  
  // Notify listeners
  this.onAction();
}

4. Add Dashboard Logout Endpoint

Create /fp/cloud/api/logout endpoint that:

  • Clears Clerk session cookies
  • Posts completion message to parent window
  • Returns appropriate CORS headers

5. Integration Points

Update existing disableSync() functionality to use the new logout mechanism when available.

Benefits

  • Complete session cleanup across domains
  • Consistent with existing popup-based auth flow
  • Backward compatible (optional interface method)
  • Proper error handling for popup blockers
  • Clear user feedback on logout status

Questions to Address

  1. Should logout URL pattern (/token → /logout) be configurable?
  2. Should logout invalidate tokens for all apps or just current ledger?
  3. How to handle offline logout attempts?
  4. Token invalidation scope (ledger vs account level)?

Related Issues

  • #1166 (Database name parameter in auth URLs)
  • #1188 (merge() without save() persistence)
  • #1208 (Original cross-domain logout issue)

Priority

High - This is a security and UX concern affecting all Fireproof Cloud users.

jchris avatar Oct 01 '25 13:10 jchris

This looks perfect for our use case! We're using a custom ManualRedirectStrategy that extends RedirectStrategy, so we'll automatically inherit the performLogout() method once this is implemented.

Our current disableSync() function in vibes.diy calls tokenAndClaims.reset() but this leaves users authenticated on connect.fireproof.direct, causing confusion when they try to re-enable sync later.

The popup-based logout approach is consistent with our existing auth flow and the PostMessage communication pattern will work well with our architecture.

Looking forward to integrating this - it will solve a significant UX issue for our users! 🎉

jchris avatar Oct 01 '25 13:10 jchris