Files
2026-04-16 08:27:07 -04:00

112 lines
4.6 KiB
JavaScript

/**
* extension/bridge/bridge.js
*
* Content script injected ONLY on pwkeeper.ngodanguyen.tech.
*
* Web app → Extension:
* Listens for the 'passkeeper:session' custom event dispatched by auth.js
* after login, and forwards access_token + refresh_token + enc_key_salt to
* the background service worker via chrome.runtime.sendMessage.
*
* Extension → Web app:
* Listens for INJECT_SESSION messages from the background (triggered when the
* user logs in through the popup). Writes tokens into the web app's storage
* and fires 'passkeeper:ext-login' so auth.js can unlock the vault overlay.
*/
(() => {
'use strict';
// ── Web app → Extension ────────────────────────────────────────────────────
function syncToExtension(access_token, refresh_token, enc_key_salt) {
if (!access_token || !refresh_token) return;
chrome.runtime.sendMessage({
type: 'WEB_SESSION_SYNC',
access_token,
refresh_token,
enc_key_salt: enc_key_salt || '',
}).catch(() => {});
}
// Fired by auth.js completeLogin
window.addEventListener('passkeeper:session', e => {
const { access_token, refresh_token, enc_key_salt } = e.detail || {};
syncToExtension(access_token, refresh_token, enc_key_salt);
});
// Fired by auth.js on logout / sign-out
window.addEventListener('passkeeper:logout', () => {
chrome.runtime.sendMessage({ type: 'WEB_SESSION_CLEAR' }).catch(() => {});
});
// On initial page load: sync in both directions
// Web → Extension: if the web app is already logged in, forward tokens to extension
const existing_token = sessionStorage.getItem('access_token');
const existing_refresh = localStorage.getItem('refresh_token');
const existing_salt = sessionStorage.getItem('enc_key_salt');
if (existing_token && existing_refresh) {
syncToExtension(existing_token, existing_refresh, existing_salt);
}
// Extension → Web: if the web app has no session but the extension does,
// refresh the access token first (so we inject a guaranteed-fresh pair) then
// fire passkeeper:ext-login so vault.js can proceed without hitting 401.
if (!existing_token) {
(async () => {
try {
const local = await chrome.storage.local.get(['refresh_token', 'enc_key_salt']);
const refresh = local.refresh_token;
const salt = local.enc_key_salt ||
(await chrome.storage.session.get('enc_key_salt')).enc_key_salt || '';
if (!refresh || !salt) return;
// Exchange the stored refresh token for a fresh access token
const res = await fetch(`${location.origin}/api/auth/refresh`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: refresh }),
});
if (!res.ok) return;
const data = await res.json();
const freshAccess = data.access_token;
const freshRefresh = data.refresh_token || refresh;
// Write fresh tokens into the page session so vault.js works immediately
sessionStorage.setItem('access_token', freshAccess);
localStorage.setItem('refresh_token', freshRefresh);
sessionStorage.setItem('enc_key_salt', salt);
// Keep extension storage in sync with the rotated tokens
chrome.storage.session.set({ access_token: freshAccess, enc_key_salt: salt });
chrome.storage.local.set({ refresh_token: freshRefresh, enc_key_salt: salt });
window.dispatchEvent(new CustomEvent('passkeeper:ext-login', {
detail: { enc_key_salt: salt },
}));
} catch {}
})();
}
// ── Extension → Web app ────────────────────────────────────────────────────
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
if (msg.type === 'INJECT_SESSION') {
// Write tokens so the web app is effectively logged in
sessionStorage.setItem('access_token', msg.access_token);
localStorage.setItem('refresh_token', msg.refresh_token);
if (msg.enc_key_salt) sessionStorage.setItem('enc_key_salt', msg.enc_key_salt);
// Tell the web app a session was injected — vault.js listens for this
// to dismiss the unlock overlay and load the vault
window.dispatchEvent(new CustomEvent('passkeeper:ext-login', {
detail: { enc_key_salt: msg.enc_key_salt },
}));
sendResponse({ ok: true });
}
return false;
});
})();