-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Summary
Add automatic token forwarding from the OAuth broker to the PinShare frontend using the postMessage API. This will streamline the OAuth flow for both hosted (share.episkopos.community) and self-hosted instances by eliminating the manual copy/paste step, while maintaining full backward compatibility.
Current Implementation
OAuth Broker Flow (oauth-broker/main.go):
- User clicks "Get Token from OAuth Broker" in frontend
- Opens broker URL in new tab:
https://oauth.episkopos.community - User authorizes with Google
- Broker displays JSON token in browser
- User manually copies entire JSON token
- User pastes into textarea in PinShare UI
- Frontend validates and POSTs to
/api/google-drive/set-token
Pain Points:
- Manual copy/paste is tedious and error-prone
- Not user-friendly for non-technical users
- Token briefly visible in browser (though ephemeral)
- Extra steps reduce conversion
Proposed Solution
postMessage-based Auto-Forward
Use the browser's postMessage API to securely send the token from the OAuth broker popup directly to the frontend window.
New Flow:
- Frontend opens broker URL in popup window (not new tab)
- User authorizes with Google
- Broker detects popup context via
window.opener - Broker uses
window.opener.postMessage()to send token to frontend - Frontend receives message, validates origin, auto-submits token to backend
- Popup closes automatically
- Frontend shows "Token received!" and proceeds
Fallback Flow (backward compatible):
- If popup blocked → show instructions + copy/paste option
- If
window.openerunavailable → show copy/paste UI - If postMessage fails → show copy/paste UI (existing behavior)
- Old broker version → copy/paste works as today
Why postMessage?
✅ Works for both cloud-hosted and self-hosted:
- Cloud frontend → Cloud broker (cross-origin)
- Self-hosted frontend → Cloud broker (cross-origin)
- Self-hosted frontend → Self-hosted broker (same-origin)
✅ Secure:
- Origin validation on both sender and receiver
- Token never exposed in URL
- No CORS configuration changes needed
✅ Backward compatible:
- Copy/paste flow remains available
- Works with old frontend or old broker versions
- Self-hosted instances without frontend UI still work
Implementation Details
1. OAuth Broker Changes (oauth-broker/main.go)
Modify renderSuccess() function (lines ~276-380):
Add JavaScript to success page:
// Detect if opened as popup
if (window.opener && !window.opener.closed) {
// Attempt postMessage
const message = {
type: 'PINSHARE_OAUTH_TOKEN',
token: tokenData, // { access_token, refresh_token, expiry, token_type }
source: 'pinshare-oauth-broker'
};
// Send to opener (frontend)
window.opener.postMessage(message, '*'); // Will be validated by receiver
// Wait for acknowledgment (5 second timeout)
let acknowledged = false;
window.addEventListener('message', (event) => {
if (event.data.type === 'PINSHARE_TOKEN_RECEIVED') {
acknowledged = true;
// Show brief success message
document.body.innerHTML = '<h2>✓ Token sent! Closing...</h2>';
setTimeout(() => window.close(), 1000);
}
});
setTimeout(() => {
if (!acknowledged) {
// Fallback to copy/paste UI
showCopyPasteUI();
}
}, 5000);
} else {
// Not a popup - show copy/paste UI immediately
showCopyPasteUI();
}Message format:
{
"type": "PINSHARE_OAUTH_TOKEN",
"token": {
"access_token": "ya29.a0...",
"token_type": "Bearer",
"refresh_token": "1//0g...",
"expiry": "2024-11-17T23:45:00Z"
},
"source": "pinshare-oauth-broker"
}2. Frontend Changes (pinshare-ui/src/pages/GoogleDriveImport.jsx)
Update "Get Token from OAuth Broker" button handler (currently ~line 314):
const handleGetTokenFromBroker = () => {
const brokerUrl = `${OAUTH_BASE}/authorize`;
// Open in popup instead of new tab
const popup = window.open(
brokerUrl,
'pinshare-oauth',
'width=600,height=700,scrollbars=yes'
);
if (!popup || popup.closed) {
// Popup blocked - show instructions
setMessage('Please allow popups for automatic token flow, or use the manual copy/paste method below.');
return;
}
// Set up postMessage listener
const handleMessage = async (event) => {
// Validate origin
const allowedOrigin = new URL(OAUTH_BASE).origin;
if (event.origin !== allowedOrigin) {
console.warn('Received message from unexpected origin:', event.origin);
return;
}
// Validate message structure
if (event.data?.type !== 'PINSHARE_OAUTH_TOKEN' ||
event.data?.source !== 'pinshare-oauth-broker') {
return;
}
// Send acknowledgment
popup.postMessage({ type: 'PINSHARE_TOKEN_RECEIVED' }, allowedOrigin);
// Clean up listener
window.removeEventListener('message', handleMessage);
clearTimeout(timeoutId);
// Auto-submit token
try {
const response = await fetch('/api/google-drive/set-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(event.data.token)
});
if (response.ok) {
setMessage('✓ Token received and authenticated!');
checkAuthStatus(); // Refresh auth status
popup.close();
} else {
throw new Error('Token validation failed');
}
} catch (error) {
setMessage('Error storing token: ' + error.message);
}
};
window.addEventListener('message', handleMessage);
// Timeout after 30 seconds
const timeoutId = setTimeout(() => {
window.removeEventListener('message', handleMessage);
setMessage('Token auto-forward timed out. Please use the manual copy/paste method.');
}, 30000);
};3. Security Considerations
Origin Validation:
- Frontend validates message origin matches
VITE_OAUTH_BASE - Broker sends to
*but frontend validates (postMessage API design pattern) - Message structure validation (type, source fields)
Token Exposure:
- Token never in URL (unlike redirect-based flows)
- Only in memory during postMessage transfer
- Same security as existing copy/paste (clipboard also in memory)
CORS:
- No CORS changes needed (postMessage works cross-origin by design)
- Current
PS_ALLOWED_ORIGINSconfiguration unchanged
Popup Blockers:
- Graceful degradation if popup blocked
- User-initiated action (button click) usually allows popups
- Fallback instructions if blocked
4. UI/UX Flow Diagram
Happy Path (Automatic):
User clicks "Get Token from OAuth Broker"
↓
Popup opens → User authorizes Google
↓
Broker sends token via postMessage
↓
Frontend receives token → Auto-submits to backend
↓
Success message → Popup closes → Auth status updates
Fallback Path (Manual):
Popup blocked OR postMessage fails
↓
Show "Please allow popups or use manual flow"
↓
User copies token from broker page
↓
User pastes into textarea → Submits manually
↓
(Existing copy/paste flow)
5. Backward Compatibility Matrix
| Frontend Version | Broker Version | Result |
|---|---|---|
| New | New | ✅ Automatic postMessage flow |
| New | Old | ✅ Fallback to copy/paste (timeout) |
| Old | New | ✅ Copy/paste (no popup listener) |
| Old | Old | ✅ Copy/paste (existing behavior) |
Edge Cases:
- Self-hosted without frontend UI: Copy/paste works (existing behavior)
- Direct OAuth flow: Unchanged, still supported
- Multiple instances: Each validates its own
VITE_OAUTH_BASEorigin
Testing Checklist
- Cloud hosted → Cloud broker: Automatic flow works (cross-origin)
- Self-hosted → Cloud broker: Automatic flow works (cross-origin)
- Self-hosted → Self-hosted broker: Automatic flow works (same-origin)
- Popup blocked: Graceful fallback with instructions
- Old broker (no postMessage): Timeout → fallback to copy/paste
- Old frontend: Copy/paste works as today
- Origin validation: Reject messages from wrong origin
- Malformed message: Ignore invalid message structure
- Multiple auth attempts: Listener cleanup works correctly
- Browser compatibility: Test Chrome, Firefox, Safari, Edge
Files to Modify
-
oauth-broker/main.go(~line 276-380)- Update
renderSuccess()function - Add postMessage JavaScript to success page
- Keep existing copy/paste UI as fallback
- Update
-
pinshare-ui/src/pages/GoogleDriveImport.jsx(~line 314+)- Update "Get Token" button handler
- Change
window.open()from new tab to popup - Add
messageevent listener with origin validation - Add timeout and cleanup logic
- Keep existing manual textarea flow
-
oauth-broker/README.md(optional)- Document new automatic flow
- Document fallback behavior
Rollout Strategy
-
Phase 1: Deploy updated broker
- Backward compatible (works with old frontends)
- Test in staging with manual copy/paste
-
Phase 2: Deploy updated frontend
- Progressive enhancement (works with old brokers)
- Test automatic flow with both hosted and self-hosted
-
Phase 3: Monitor and iterate
- Track popup blocker frequency
- Collect user feedback
- Add analytics if needed
Benefits
✅ Better UX: No manual copy/paste for 95%+ of users
✅ Works everywhere: Cloud-hosted, self-hosted, cross-origin
✅ Secure: Origin validation, no URL token exposure
✅ Backward compatible: Existing flows unchanged
✅ Low risk: Graceful degradation if anything fails
Related Issues
- Original broker design: Centralized OAuth credentials for self-hosted instances
- CORS configuration: Recent commits
a94ff64ande353a42
Priority: Medium - Improves UX but existing flow works fine
Effort: Small - ~2-3 hours implementation + testing
Risk: Low - Fallback to existing behavior if anything fails