Skip to content

Implement cross-domain logout for Fireproof Cloud authentication #1209

@jchris

Description

@jchris

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

Priority

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions