aboutsummaryrefslogtreecommitdiff
path: root/webext/firefox
diff options
context:
space:
mode:
authorsrdusr <trevorgray@srdusr.com>2025-09-26 12:40:58 +0200
committersrdusr <trevorgray@srdusr.com>2025-09-26 12:40:58 +0200
commita996f78277d5ba5adccb0daa535bc2494350975c (patch)
tree08d594ba144f41fb14ebd2354beb2a8cda9be101 /webext/firefox
parent91499edd42cc50ee0543e11e08a6b653f3475262 (diff)
downloadcerberus-a996f78277d5ba5adccb0daa535bc2494350975c.tar.gz
cerberus-a996f78277d5ba5adccb0daa535bc2494350975c.zip
Initial Commit
Diffstat (limited to 'webext/firefox')
-rw-r--r--webext/firefox/background.js69
-rw-r--r--webext/firefox/content.js57
-rw-r--r--webext/firefox/manifest.json35
-rw-r--r--webext/firefox/popup.html65
4 files changed, 226 insertions, 0 deletions
diff --git a/webext/firefox/background.js b/webext/firefox/background.js
new file mode 100644
index 0000000..d05cd37
--- /dev/null
+++ b/webext/firefox/background.js
@@ -0,0 +1,69 @@
+// Basic background script for Cerberus Firefox extension
+
+browser.runtime.onInstalled.addListener(() => {
+ console.log("Cerberus extension installed");
+});
+
+function connectNative() {
+ try {
+ return browser.runtime.connectNative('com.cerberus.pm');
+ } catch (e) {
+ console.error('Native host connection failed', e);
+ return null;
+ }
+}
+
+function nativeRequest(payload) {
+ return new Promise((resolve, reject) => {
+ const port = connectNative();
+ if (!port) {
+ reject(new Error('no_native_host'));
+ return;
+ }
+ const onMessage = (resp) => {
+ port.onMessage.removeListener(onMessage);
+ port.disconnect();
+ resolve(resp);
+ };
+ const onDisconnect = () => {
+ port.onMessage.removeListener(onMessage);
+ reject(new Error('disconnected'));
+ };
+ port.onMessage.addListener(onMessage);
+ port.onDisconnect.addListener(onDisconnect);
+ port.postMessage(payload);
+ });
+}
+
+async function getOriginForTab(tabId) {
+ const tab = await browser.tabs.get(tabId);
+ try {
+ const url = new URL(tab.url);
+ return url.origin;
+ } catch (e) {
+ return tab.url;
+ }
+}
+
+// Message router between popup/content and native host
+browser.runtime.onMessage.addListener(async (message, sender) => {
+ if (!message || !message.type) return;
+ if (message.type === 'GET_PAGE_FORMS') {
+ return browser.tabs.sendMessage(sender.tab.id, { type: 'SCAN_FORMS' });
+ }
+ if (message.type === 'FILL_CREDENTIALS') {
+ return browser.tabs.sendMessage(sender.tab.id, {
+ type: 'FILL_CREDENTIALS',
+ payload: message.payload,
+ });
+ }
+ if (message.type === 'GET_CREDENTIALS_FOR_TAB') {
+ const origin = await getOriginForTab(sender.tab.id);
+ const resp = await nativeRequest({ type: 'get_for_origin', origin, include_password: true }).catch(err => ({ ok: false, error: String(err) }));
+ return resp;
+ }
+ if (message.type === 'PING_NATIVE') {
+ const resp = await nativeRequest({ type: 'ping' }).catch(err => ({ ok: false, error: String(err) }));
+ return resp;
+ }
+});
diff --git a/webext/firefox/content.js b/webext/firefox/content.js
new file mode 100644
index 0000000..9db4005
--- /dev/null
+++ b/webext/firefox/content.js
@@ -0,0 +1,57 @@
+// Content script: detects login/change-password forms and can fill them
+
+function findLoginForms() {
+ const forms = Array.from(document.querySelectorAll('form'));
+ const results = [];
+ for (const f of forms) {
+ const inputs = Array.from(f.querySelectorAll('input'));
+ const hasPassword = inputs.some(i => (i.type || '').toLowerCase() === 'password');
+ const username = inputs.find(i => ['text','email','tel','username'].includes((i.type || '').toLowerCase()) || /user|email|login/i.test(i.name || i.id || ''));
+ const password = inputs.find(i => (i.type || '').toLowerCase() === 'password');
+ if (hasPassword && (username || password)) {
+ results.push({
+ action: f.getAttribute('action') || location.href,
+ usernameName: username && (username.name || username.id) || null,
+ passwordName: password && (password.name || password.id) || null,
+ });
+ }
+ }
+ return results;
+}
+
+function fillCredentials(payload) {
+ const { username, password } = payload || {};
+ if (!username && !password) return false;
+ // Try to fill the first reasonable form
+ const forms = Array.from(document.querySelectorAll('form'));
+ for (const f of forms) {
+ const inputs = Array.from(f.querySelectorAll('input'));
+ const u = inputs.find(i => ['text','email','tel','username'].includes((i.type || '').toLowerCase()) || /user|email|login/i.test(i.name || i.id || ''));
+ const p = inputs.find(i => (i.type || '').toLowerCase() === 'password');
+ if (u || p) {
+ if (u && username) {
+ u.focus();
+ u.value = username;
+ u.dispatchEvent(new Event('input', { bubbles: true }));
+ }
+ if (p && password) {
+ p.focus();
+ p.value = password;
+ p.dispatchEvent(new Event('input', { bubbles: true }));
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+browser.runtime.onMessage.addListener((message) => {
+ if (!message || !message.type) return;
+ if (message.type === 'SCAN_FORMS') {
+ return Promise.resolve({ forms: findLoginForms() });
+ }
+ if (message.type === 'FILL_CREDENTIALS') {
+ const ok = fillCredentials(message.payload || {});
+ return Promise.resolve({ ok });
+ }
+});
diff --git a/webext/firefox/manifest.json b/webext/firefox/manifest.json
new file mode 100644
index 0000000..12b2e7a
--- /dev/null
+++ b/webext/firefox/manifest.json
@@ -0,0 +1,35 @@
+{
+ "manifest_version": 2,
+ "name": "Cerberus Password Manager",
+ "version": "0.1.0",
+ "description": "Auto-fill and manage passwords with Cerberus.",
+ "permissions": [
+ "activeTab",
+ "storage",
+ "contextMenus",
+ "tabs",
+ "nativeMessaging",
+ "<all_urls>"
+ ],
+ "background": {
+ "scripts": ["background.js"],
+ "persistent": false
+ },
+ "content_scripts": [
+ {
+ "matches": ["<all_urls>"],
+ "js": ["content.js"],
+ "run_at": "document_end"
+ }
+ ],
+ "browser_action": {
+ "default_title": "Cerberus",
+ "default_popup": "popup.html"
+ }
+ ,
+ "browser_specific_settings": {
+ "gecko": {
+ "id": "cerberus@example.com"
+ }
+ }
+}
diff --git a/webext/firefox/popup.html b/webext/firefox/popup.html
new file mode 100644
index 0000000..0c51b06
--- /dev/null
+++ b/webext/firefox/popup.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8" />
+ <title>Cerberus</title>
+ <style>
+ body { font-family: sans-serif; width: 280px; margin: 10px; }
+ button { width: 100%; padding: 8px; margin: 6px 0; }
+ input { width: 100%; padding: 6px; margin: 6px 0; }
+ </style>
+</head>
+<body>
+ <h3>Cerberus</h3>
+ <input id="username" placeholder="Username" />
+ <input id="password" placeholder="Password" type="password" />
+ <button id="fetch">Fetch from Vault</button>
+ <button id="fill">Fill on Page</button>
+ <div id="status" style="font-size: 12px; color: #666; margin-top: 6px;"></div>
+ <script>
+ async function getActiveTab() {
+ const tabs = await browser.tabs.query({ active: true, currentWindow: true });
+ return tabs[0];
+ }
+
+ async function fill(username, password) {
+ const tab = await getActiveTab();
+ await browser.tabs.sendMessage(tab.id, { type: 'FILL_CREDENTIALS', payload: { username, password } });
+ }
+
+ document.getElementById('fill').addEventListener('click', async () => {
+ const username = document.getElementById('username').value;
+ const password = document.getElementById('password').value;
+ try {
+ await fill(username, password);
+ window.close();
+ } catch (e) { console.error(e); }
+ });
+
+ document.getElementById('fetch').addEventListener('click', async () => {
+ const status = document.getElementById('status');
+ status.textContent = 'Fetching credentials from vault...';
+ try {
+ const resp = await browser.runtime.sendMessage({ type: 'GET_CREDENTIALS_FOR_TAB' });
+ if (!resp || !resp.ok) {
+ status.textContent = 'Failed to fetch (is native host installed and unlocked?)';
+ return;
+ }
+ const results = resp.result || [];
+ if (results.length === 0) {
+ status.textContent = 'No entries matched for this origin';
+ return;
+ }
+ // Choose the first match (later: add a dropdown)
+ const { username, password } = results[0];
+ if (username) document.getElementById('username').value = username;
+ if (password) document.getElementById('password').value = password;
+ status.textContent = 'Fetched from vault';
+ } catch (e) {
+ console.error(e);
+ status.textContent = 'Error fetching credentials';
+ }
+ });
+ </script>
+</body>
+</html>