mirror of
https://github.com/keepassxreboot/keepassxc-browser.git
synced 2026-03-11 08:54:43 +00:00
New draft
This commit is contained in:
parent
a6cbf580b9
commit
db8cd1b1f0
28 changed files with 2252 additions and 1168 deletions
|
|
@ -88,6 +88,7 @@
|
|||
"assertSearchField": "readonly",
|
||||
"assertSearchForm": "readonly",
|
||||
"assertTOTPField": "readonly",
|
||||
"AddCredentials": "readonly",
|
||||
"AssociatedAction": "readonly",
|
||||
"AuthenticatorAssertionResponse": "readonly",
|
||||
"AuthenticatorAttestationResponse": "readonly",
|
||||
|
|
@ -126,6 +127,7 @@
|
|||
"isFirefox": "readonly",
|
||||
"keepass": "readonly",
|
||||
"keepassClient": "readonly",
|
||||
"keepassProtocol": "readonly",
|
||||
"kpActions": "readonly",
|
||||
"kpErrors": "readonly",
|
||||
"kpxc": "readonly",
|
||||
|
|
@ -163,6 +165,8 @@
|
|||
"nacl": "readonly",
|
||||
"ORANGE_BUTTON": "readonly",
|
||||
"page": "readonly",
|
||||
"protocol": "readonly",
|
||||
"protocolClient": "readonly",
|
||||
"Pixels": "readonly",
|
||||
"PublicKeyCredential": "readonly",
|
||||
"PREDEFINED_SITELIST": "readonly",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This repository does not use prettier for formatting but if you want to use it for testing then you can comment out the following line:
|
||||
*
|
||||
#*
|
||||
|
||||
# Should never format these files:
|
||||
*.min.js
|
||||
|
|
|
|||
7
dist/manifest_firefox.json
vendored
7
dist/manifest_firefox.json
vendored
|
|
@ -36,14 +36,17 @@
|
|||
"common/sites.js",
|
||||
"background/nacl.min.js",
|
||||
"background/nacl-util.min.js",
|
||||
"background/client.js",
|
||||
"background/keepass.js",
|
||||
"background/httpauth.js",
|
||||
"background/offscreen.js",
|
||||
"background/browserAction.js",
|
||||
"background/page.js",
|
||||
"background/event.js",
|
||||
"background/init.js"
|
||||
"background/init.js",
|
||||
"background/protocol.js",
|
||||
"background/protocolClient.js",
|
||||
"background/legacyProtocol.js",
|
||||
"background/legacyProtocolClient.js"
|
||||
]
|
||||
},
|
||||
"content_scripts": [
|
||||
|
|
|
|||
|
|
@ -159,6 +159,10 @@
|
|||
"message": "No logins found.",
|
||||
"description": "No logins found."
|
||||
},
|
||||
"errorActionTimeout": {
|
||||
"message": "Action timeout.",
|
||||
"description": "Action timeout."
|
||||
},
|
||||
"errorMessagePaswordLengthExceeded": {
|
||||
"message": "Filled password is longer than field's allowed max length.",
|
||||
"description": "Error notification text shown when filled password is longer than defined maxLength of the input field."
|
||||
|
|
@ -1393,6 +1397,14 @@
|
|||
"lockDatabase": {
|
||||
"message": "Lock database",
|
||||
"description": "Lock database button title text."
|
||||
},
|
||||
"lockAllDatabases": {
|
||||
"message": "Lock all databases",
|
||||
"description": "Lock all databases button title text."
|
||||
},
|
||||
"noAction": {
|
||||
"message": "No action",
|
||||
"description": "No action text."
|
||||
},
|
||||
"welcomeText": {
|
||||
"message": "Welcome to KeePassXC-Browser!",
|
||||
|
|
|
|||
|
|
@ -7,14 +7,17 @@ try {
|
|||
'../common/sites.js',
|
||||
'nacl.min.js',
|
||||
'nacl-util.min.js',
|
||||
'client.js',
|
||||
'keepass.js',
|
||||
'httpauth.js',
|
||||
'offscreen.js',
|
||||
'browserAction.js',
|
||||
'page.js',
|
||||
'event.js',
|
||||
'init.js'
|
||||
'init.js',
|
||||
'protocol.js',
|
||||
'protocolClient.js',
|
||||
'legacyProtocol.js',
|
||||
'legacyProtocolClient.js'
|
||||
);
|
||||
} catch (e) {
|
||||
console.log('Cannot import background scripts: ', e);
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ browserAction.showDefault = async function(tab) {
|
|||
|
||||
// Get the current tab if no tab given
|
||||
tab ??= await getCurrentTab();
|
||||
if (!tab) {
|
||||
if (!tab || tab.id < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,415 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const keepassClient = {};
|
||||
keepassClient.keySize = 24;
|
||||
keepassClient.messageTimeout = 500; // Milliseconds
|
||||
keepassClient.nativeHostName = 'org.keepassxc.keepassxc_browser';
|
||||
keepassClient.nativePort = null;
|
||||
|
||||
const kpErrors = {
|
||||
UNKNOWN_ERROR: 0,
|
||||
DATABASE_NOT_OPENED: 1,
|
||||
DATABASE_HASH_NOT_RECEIVED: 2,
|
||||
CLIENT_PUBLIC_KEY_NOT_RECEIVED: 3,
|
||||
CANNOT_DECRYPT_MESSAGE: 4,
|
||||
TIMEOUT_OR_NOT_CONNECTED: 5,
|
||||
ACTION_CANCELLED_OR_DENIED: 6,
|
||||
PUBLIC_KEY_NOT_FOUND: 7,
|
||||
ASSOCIATION_FAILED: 8,
|
||||
KEY_CHANGE_FAILED: 9,
|
||||
ENCRYPTION_KEY_UNRECOGNIZED: 10,
|
||||
NO_SAVED_DATABASES_FOUND: 11,
|
||||
INCORRECT_ACTION: 12,
|
||||
EMPTY_MESSAGE_RECEIVED: 13,
|
||||
NO_URL_PROVIDED: 14,
|
||||
NO_LOGINS_FOUND: 15,
|
||||
NO_GROUPS_FOUND: 16,
|
||||
CANNOT_CREATE_NEW_GROUP: 17,
|
||||
NO_VALID_UUID_PROVIDED: 18,
|
||||
ACCESS_TO_ALL_ENTRIES_DENIED: 19,
|
||||
PASSKEYS_ATTESTATION_NOT_SUPPORTED: 20,
|
||||
PASSKEYS_CREDENTIAL_IS_EXCLUDED: 21,
|
||||
PASSKEYS_REQUEST_CANCELED: 22,
|
||||
PASSKEYS_INVALID_USER_VERIFICATION: 23,
|
||||
PASSKEYS_EMPTY_PUBLIC_KEY: 24,
|
||||
PASSKEYS_INVALID_URL_PROVIDED: 25,
|
||||
PASSKEYS_ORIGIN_NOT_ALLOWED: 26,
|
||||
PASSKEYS_DOMAIN_IS_NOT_VALID: 27,
|
||||
PASSKEYS_DOMAIN_RPID_MISMATCH: 28,
|
||||
PASSKEYS_NO_SUPPORTED_ALGORITHMS: 29,
|
||||
PASSKEYS_WAIT_FOR_LIFETIMER: 30,
|
||||
PASSKEYS_UNKNOWN_ERROR: 31,
|
||||
PASSKEYS_INVALID_CHALLENGE: 32,
|
||||
PASSKEYS_INVALID_USER_ID: 33,
|
||||
|
||||
errorMessages: {
|
||||
0: { msg: tr('errorMessageUnknown') },
|
||||
1: { msg: tr('errorMessageDatabaseNotOpened') },
|
||||
2: { msg: tr('errorMessageDatabaseHash') },
|
||||
3: { msg: tr('errorMessageClientPublicKey') },
|
||||
4: { msg: tr('errorMessageDecrypt') },
|
||||
5: { msg: tr('errorMessageTimeout') },
|
||||
6: { msg: tr('errorMessageCanceled') },
|
||||
7: { msg: tr('errorMessageEncrypt') },
|
||||
8: { msg: tr('errorMessageAssociate') },
|
||||
9: { msg: tr('errorMessageKeyExchange') },
|
||||
10: { msg: tr('errorMessageEncryptionKey') },
|
||||
11: { msg: tr('errorMessageSavedDatabases') },
|
||||
12: { msg: tr('errorMessageIncorrectAction') },
|
||||
13: { msg: tr('errorMessageEmptyMessage') },
|
||||
14: { msg: tr('errorMessageNoURL') },
|
||||
15: { msg: tr('errorMessageNoLogins') },
|
||||
16: { msg: tr('errorMessageNoGroupsFound') },
|
||||
17: { msg: tr('errorMessageCannotCreateNewGroup') },
|
||||
18: { msg: tr('errorMessageNoValidUuidProvided') },
|
||||
19: { msg: tr('errorMessageAccessToAllEntriesDenied') },
|
||||
20: { msg: tr('errorMessagePasskeysAttestationNotSupported') },
|
||||
21: { msg: tr('errorMessagePasskeysCredentialIsExcluded') },
|
||||
22: { msg: tr('errorMessagePasskeysRequestCanceled') },
|
||||
23: { msg: tr('errorMessagePasskeysInvalidUserVerification') },
|
||||
24: { msg: tr('errorMessagePasskeysEmptyPublicKey') },
|
||||
25: { msg: tr('errorMessagePasskeysInvalidUrlProvided') },
|
||||
26: { msg: tr('errorMessagePasskeysOriginNotAllowed') },
|
||||
27: { msg: tr('errorMessagePasskeysDomainNotValid') },
|
||||
28: { msg: tr('errorMessagePasskeysDomainRpIdMismatch') },
|
||||
29: { msg: tr('errorMessagePasskeysNoSupportedAlgorithms') },
|
||||
30: { msg: tr('errorMessagePasskeysWaitforLifeTimer') },
|
||||
31: { msg: tr('errorMessagePasskeysUnknownError') },
|
||||
32: { msg: tr('errorMessagePasskeysInvalidChallenge') },
|
||||
33: { msg: tr('errorMessagePasskeysInvalidUserId') },
|
||||
},
|
||||
|
||||
getError(errorCode) {
|
||||
return this.errorMessages[errorCode].msg;
|
||||
}
|
||||
};
|
||||
|
||||
const messageBuffer = {
|
||||
buffer: [],
|
||||
|
||||
addMessage(message) {
|
||||
this.buffer.push(message);
|
||||
},
|
||||
|
||||
// Returns corresponding message from the response. If the response is an error,
|
||||
// return the first matching action from the buffer.
|
||||
getMessage(response) {
|
||||
const isError = Boolean(!response.nonce && response.error && response.errorCode);
|
||||
return this.buffer.find(message => {
|
||||
if (keepassClient.incrementedNonce(message.request.nonce) === response.nonce
|
||||
|| (isError && message.request?.action === response?.action)) {
|
||||
// Cancel timeout
|
||||
if (message.enableTimeout) {
|
||||
message.cancelTimeout();
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
removeMessage(message) {
|
||||
const index = this.buffer.indexOf(message);
|
||||
if (index >= 0 && index < this.buffer.length) {
|
||||
this.buffer.splice(index, 1);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Basic class for a message to be sent. The Promise inside the class will be resolved when
|
||||
// the response to the message is received.
|
||||
class Message {
|
||||
constructor(request, enableTimeout, timeoutValue) {
|
||||
this.enableTimeout = enableTimeout;
|
||||
this.request = request;
|
||||
this.timeout = undefined;
|
||||
|
||||
this.promise = new Promise((resolve, reject) => {
|
||||
this.reject = reject;
|
||||
this.resolve = resolve;
|
||||
|
||||
const messageTimeout = timeoutValue || keepassClient.messageTimeout;
|
||||
|
||||
// Handle timeout
|
||||
if (this.enableTimeout) {
|
||||
this.timeout = setTimeout(() => {
|
||||
const errorMessage = {
|
||||
action: request.action,
|
||||
error: kpErrors.getError(kpErrors.TIMEOUT_OR_NOT_CONNECTED),
|
||||
errorCode: kpErrors.TIMEOUT_OR_NOT_CONNECTED
|
||||
};
|
||||
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
resolve(errorMessage);
|
||||
}, messageTimeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cancelTimeout() {
|
||||
this.enableTimeout = false;
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Messaging
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
keepassClient.sendNativeMessage = async function(request, enableTimeout = false, timeoutValue) {
|
||||
if (!keepassClient.nativePort) {
|
||||
logError('No native messaging port defined.');
|
||||
return;
|
||||
}
|
||||
|
||||
const message = new Message(request, enableTimeout, timeoutValue);
|
||||
await navigator.locks.request('messageBuffer', async (lock) => {
|
||||
messageBuffer.addMessage(message);
|
||||
});
|
||||
|
||||
keepassClient.nativePort.postMessage(request);
|
||||
|
||||
const response = await message.promise;
|
||||
|
||||
// Remove a timeouted message
|
||||
if (response.error && response?.errorCode === kpErrors.TIMEOUT_OR_NOT_CONNECTED) {
|
||||
messageBuffer.removeMessage(message);
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
keepassClient.handleNativeMessage = async function(response) {
|
||||
// Parse through the message buffer to find the corresponding Promise.
|
||||
await navigator.locks.request('messageBuffer', async (lock) => {
|
||||
const message = messageBuffer.getMessage(response);
|
||||
if (message) {
|
||||
message.resolve(response);
|
||||
messageBuffer.removeMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
debugLogMessage('Corresponding request not found in the message buffer for response: ', response);
|
||||
});
|
||||
};
|
||||
|
||||
keepassClient.handleResponse = function(response, incrementedNonce, tab) {
|
||||
if (response.message && response.nonce) {
|
||||
const res = keepassClient.decrypt(response.message, response.nonce);
|
||||
if (!res) {
|
||||
keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
|
||||
if (keepassClient.verifyResponse(parsed, incrementedNonce)) {
|
||||
return parsed;
|
||||
}
|
||||
} else if (response.error && response.errorCode) {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
keepassClient.buildRequest = function(action, encrypted, nonce, clientID, triggerUnlock = false) {
|
||||
const request = {
|
||||
action: action,
|
||||
message: encrypted,
|
||||
nonce: nonce,
|
||||
clientID: clientID
|
||||
};
|
||||
|
||||
if (triggerUnlock) {
|
||||
request.triggerUnlock = 'true';
|
||||
}
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
keepassClient.sendMessage = async function(kpAction, tab, messageData, nonce, enableTimeout = false, triggerUnlock = false) {
|
||||
const request = keepassClient.buildRequest(kpAction, keepassClient.encrypt(messageData, nonce), nonce, keepass.clientID, triggerUnlock);
|
||||
if (messageData.requestID) {
|
||||
request['requestID'] = messageData.requestID;
|
||||
}
|
||||
|
||||
const response = await keepassClient.sendNativeMessage(request, enableTimeout);
|
||||
const incrementedNonce = keepassClient.incrementedNonce(nonce);
|
||||
|
||||
return keepassClient.handleResponse(response, incrementedNonce, tab);
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Utils
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
keepassClient.getNonce = function() {
|
||||
return nacl.util.encodeBase64(nacl.randomBytes(keepassClient.keySize));
|
||||
};
|
||||
|
||||
// Creates a random 8 character string for Request ID
|
||||
keepassClient.getRequestId = function() {
|
||||
return Math.random().toString(16).substring(2, 10);
|
||||
};
|
||||
|
||||
keepassClient.incrementedNonce = function(nonce) {
|
||||
const oldNonce = nacl.util.decodeBase64(nonce);
|
||||
const newNonce = oldNonce.slice(0);
|
||||
|
||||
// from libsodium/utils.c
|
||||
let i = 0;
|
||||
let c = 1;
|
||||
for (; i < newNonce.length; ++i) {
|
||||
c += newNonce[i];
|
||||
newNonce[i] = c;
|
||||
c >>= 8;
|
||||
}
|
||||
|
||||
return nacl.util.encodeBase64(newNonce);
|
||||
};
|
||||
|
||||
keepassClient.getNonces = function() {
|
||||
const nonce = keepassClient.getNonce();
|
||||
const incrementedNonce = keepassClient.incrementedNonce(nonce);
|
||||
return [ nonce, incrementedNonce ];
|
||||
};
|
||||
|
||||
keepassClient.verifyKeyResponse = function(response, key, nonce) {
|
||||
if (!response.success || !response.publicKey) {
|
||||
keepass.associated.hash = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!keepassClient.checkNonceLength(response.nonce)) {
|
||||
logError('Invalid nonce length.');
|
||||
return false;
|
||||
}
|
||||
|
||||
const reply = (response.nonce === nonce);
|
||||
if (response.publicKey && reply) {
|
||||
keepass.serverPublicKey = nacl.util.decodeBase64(response.publicKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
return reply;
|
||||
};
|
||||
|
||||
keepassClient.verifyResponse = function(response, nonce, id) {
|
||||
keepass.associated.value = response.success;
|
||||
if (response.success !== 'true') {
|
||||
keepass.associated.hash = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
keepass.associated.hash = keepass.databaseHash;
|
||||
|
||||
if (!keepassClient.checkNonceLength(response.nonce)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
keepass.associated.value = (response.nonce === nonce);
|
||||
if (keepass.associated.value === false) {
|
||||
logError('Nonce compare failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (id) {
|
||||
keepass.associated.value = (keepass.associated.value && id === response.id);
|
||||
}
|
||||
|
||||
keepass.associated.hash = (keepass.associated.value) ? keepass.databaseHash : null;
|
||||
return keepass.isAssociated();
|
||||
};
|
||||
|
||||
keepassClient.verifyDatabaseResponse = function(response, nonce) {
|
||||
if (response.success !== 'true') {
|
||||
keepass.associated.hash = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!keepassClient.checkNonceLength(response.nonce)) {
|
||||
logError('Invalid nonce length.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.nonce !== nonce) {
|
||||
logError('Nonce compare failed.');
|
||||
return false;
|
||||
}
|
||||
|
||||
keepass.associated.hash = response.hash;
|
||||
return response.hash !== '' && response.success === 'true';
|
||||
};
|
||||
|
||||
keepassClient.checkNonceLength = function(nonce) {
|
||||
return nacl.util.decodeBase64(nonce).length === nacl.secretbox.nonceLength;
|
||||
};
|
||||
|
||||
keepassClient.encrypt = function(input, nonce) {
|
||||
const messageData = nacl.util.decodeUTF8(JSON.stringify(input));
|
||||
const messageNonce = nacl.util.decodeBase64(nonce);
|
||||
|
||||
if (keepass.serverPublicKey) {
|
||||
const message = nacl.box(messageData, messageNonce, keepass.serverPublicKey, keepass.keyPair.secretKey);
|
||||
if (message) {
|
||||
return nacl.util.encodeBase64(message);
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
keepassClient.decrypt = function(input, nonce) {
|
||||
const m = nacl.util.decodeBase64(input);
|
||||
const n = nacl.util.decodeBase64(nonce);
|
||||
const res = nacl.box.open(m, n, keepass.serverPublicKey, keepass.keyPair.secretKey);
|
||||
return res;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Native Messaging related
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
keepassClient.connectToNative = function() {
|
||||
if (keepassClient.nativePort) {
|
||||
keepassClient.nativePort.disconnect();
|
||||
}
|
||||
keepassClient.nativeConnect();
|
||||
};
|
||||
|
||||
keepassClient.nativeConnect = function() {
|
||||
console.log(`${EXTENSION_NAME}: Connecting to native messaging host ${keepassClient.nativeHostName}`);
|
||||
keepassClient.nativePort = browser.runtime.connectNative(keepassClient.nativeHostName);
|
||||
keepassClient.nativePort.onMessage.addListener(keepassClient.onNativeMessage);
|
||||
keepassClient.nativePort.onDisconnect.addListener(onDisconnected);
|
||||
keepass.isConnected = true;
|
||||
return keepassClient.nativePort;
|
||||
};
|
||||
|
||||
function onDisconnected() {
|
||||
keepassClient.nativePort = null;
|
||||
keepass.isConnected = false;
|
||||
keepass.isDatabaseClosed = true;
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
keepass.associated.value = false;
|
||||
keepass.associated.hash = null;
|
||||
keepass.databaseHash = '';
|
||||
|
||||
page.clearAllLogins();
|
||||
keepass.updatePopup('cross');
|
||||
keepass.updateDatabaseHashToContent();
|
||||
logError(`Failed to connect: ${(browser.runtime.lastError === null ? 'Unknown error' : browser.runtime.lastError.message)}`);
|
||||
}
|
||||
|
||||
keepassClient.onNativeMessage = function(response) {
|
||||
// Handle database lock/unlock status
|
||||
if (response.action === kpActions.DATABASE_LOCKED || response.action === kpActions.DATABASE_UNLOCKED) {
|
||||
keepass.updateDatabase();
|
||||
return;
|
||||
}
|
||||
|
||||
// Generic response handling
|
||||
keepassClient.handleNativeMessage(response);
|
||||
};
|
||||
|
|
@ -30,34 +30,40 @@ kpxcEvent.showStatus = async function(tab, configured, internalPoll) {
|
|||
|
||||
return {
|
||||
associated: keepass.isAssociated(),
|
||||
|
||||
configured: configured,
|
||||
databaseClosed: keepass.isDatabaseClosed,
|
||||
databaseAssociationStatuses: keepass.databaseAssociationStatuses,
|
||||
encryptionKeyUnrecognized: keepass.isEncryptionKeyUnrecognized,
|
||||
error: errorMessage,
|
||||
iframeDetected: iframeDetected,
|
||||
identifier: keyId,
|
||||
keePassXCAvailable: keepass.isKeePassXCAvailable,
|
||||
protocolV2: keepass.protocolV2,
|
||||
showGettingStartedGuideAlert: page.settings.showGettingStartedGuideAlert,
|
||||
showTroubleshootingGuideAlert: page.settings.showTroubleshootingGuideAlert,
|
||||
usernameFieldDetected: usernameFieldDetected
|
||||
};
|
||||
};
|
||||
|
||||
kpxcEvent.onLoadSettings = async function() {
|
||||
kpxcEvent.loadSettings = async function() {
|
||||
return await page.initSettings().catch((err) => {
|
||||
logError('onLoadSettings error: ' + err);
|
||||
logError('loadSettings error: ' + err);
|
||||
return Promise.reject();
|
||||
});
|
||||
};
|
||||
|
||||
kpxcEvent.onLoadKeyRing = async function() {
|
||||
kpxcEvent.isProtocolV2 = async function(tab) {
|
||||
return keepass.protocolV2;
|
||||
};
|
||||
|
||||
kpxcEvent.loadKeyRing = async function() {
|
||||
const item = await browser.storage.local.get({ 'keyRing': {} }).catch((err) => {
|
||||
logError('kpxcEvent.onLoadKeyRing error: ' + err);
|
||||
logError('kpxcEvent.loadKeyRing error: ' + err);
|
||||
return Promise.reject();
|
||||
});
|
||||
|
||||
keepass.keyRing = item.keyRing;
|
||||
// TODO: What to do here?
|
||||
if (keepass.isAssociated() && !keepass.keyRing[keepass.associated.hash]) {
|
||||
keepass.associated = {
|
||||
value: false,
|
||||
|
|
@ -68,23 +74,33 @@ kpxcEvent.onLoadKeyRing = async function() {
|
|||
return item.keyRing;
|
||||
};
|
||||
|
||||
kpxcEvent.onSaveSettings = async function(tab, settings) {
|
||||
kpxcEvent.saveSettings = async function(tab, settings) {
|
||||
browser.storage.local.set({ 'settings': settings });
|
||||
kpxcEvent.onLoadSettings(tab);
|
||||
kpxcEvent.loadSettings(tab);
|
||||
};
|
||||
|
||||
kpxcEvent.onGetStatus = async function(tab, args = []) {
|
||||
kpxcEvent.getStatus = async function(tab, args = []) {
|
||||
// When internalPoll is true the event is triggered from content script in intervals -> don't poll KeePassXC
|
||||
try {
|
||||
const [ internalPoll = false, triggerUnlock = false ] = args;
|
||||
let configured = false;
|
||||
|
||||
if (keepass.protocolV2) {
|
||||
configured = internalPoll
|
||||
? keepass.databaseAssociationStatuses?.isAnyAssociated
|
||||
: await protocol.testAssociationFromDatabaseStatuses(tab, [ true, triggerUnlock ])?.isAnyAssociated;
|
||||
return kpxcEvent.showStatus(tab, configured, internalPoll);
|
||||
}
|
||||
|
||||
// Protocol V1
|
||||
if (!internalPoll) {
|
||||
const response = await keepass.testAssociation(tab, [ true, triggerUnlock ]);
|
||||
const response = await keepassProtocol.testAssociation(tab, [ true, triggerUnlock ]);
|
||||
if (!response) {
|
||||
return kpxcEvent.showStatus(tab, false);
|
||||
}
|
||||
}
|
||||
|
||||
const configured = await keepass.isConfigured();
|
||||
configured = await keepass.isConfigured();
|
||||
return kpxcEvent.showStatus(tab, configured, internalPoll);
|
||||
} catch (err) {
|
||||
logError('No status shown: ' + err);
|
||||
|
|
@ -92,7 +108,7 @@ kpxcEvent.onGetStatus = async function(tab, args = []) {
|
|||
}
|
||||
};
|
||||
|
||||
kpxcEvent.onReconnect = async function(tab) {
|
||||
kpxcEvent.reconnect = async function(tab) {
|
||||
const configured = await keepass.reconnect(tab);
|
||||
if (configured) {
|
||||
browser.tabs.sendMessage(tab?.id, {
|
||||
|
|
@ -116,19 +132,19 @@ kpxcEvent.lockDatabase = async function(tab) {
|
|||
}
|
||||
};
|
||||
|
||||
kpxcEvent.onGetTabInformation = async function(tab) {
|
||||
kpxcEvent.getTabInformation = async function(tab) {
|
||||
const id = tab?.id || page.currentTabId;
|
||||
return page.tabs[id];
|
||||
};
|
||||
|
||||
kpxcEvent.onGetConnectedDatabase = async function() {
|
||||
kpxcEvent.getConnectedDatabase = async function() {
|
||||
return Promise.resolve({
|
||||
count: Object.keys(keepass.keyRing).length,
|
||||
identifier: (keepass.keyRing[keepass.associated.hash]) ? keepass.keyRing[keepass.associated.hash].id : null
|
||||
});
|
||||
};
|
||||
|
||||
kpxcEvent.onGetKeePassXCVersions = async function(tab) {
|
||||
kpxcEvent.getKeePassXCVersions = async function(tab) {
|
||||
if (keepass.currentKeePassXC === '') {
|
||||
await keepass.getDatabaseHash(tab);
|
||||
return { 'current': keepass.currentKeePassXC, 'latest': keepass.latestKeePassXC.version };
|
||||
|
|
@ -137,22 +153,22 @@ kpxcEvent.onGetKeePassXCVersions = async function(tab) {
|
|||
return { 'current': keepass.currentKeePassXC, 'latest': keepass.latestKeePassXC.version };
|
||||
};
|
||||
|
||||
kpxcEvent.onCheckUpdateKeePassXC = async function() {
|
||||
kpxcEvent.checkUpdateKeePassXC = async function() {
|
||||
await keepass.checkForNewKeePassXCVersion();
|
||||
return { current: keepass.currentKeePassXC, latest: keepass.latestKeePassXC.version };
|
||||
};
|
||||
|
||||
kpxcEvent.onUpdateAvailableKeePassXC = async function() {
|
||||
kpxcEvent.updateAvailableKeePassXC = async function() {
|
||||
return (Number(page.settings.checkUpdateKeePassXC) !== CHECK_UPDATE_NEVER) ? await keepass.keePassXCUpdateAvailable() : false;
|
||||
};
|
||||
|
||||
kpxcEvent.onRemoveCredentialsFromTabInformation = async function(tab) {
|
||||
kpxcEvent.removeCredentialsFromTabInformation = async function(tab) {
|
||||
const id = tab?.id || page.currentTabId;
|
||||
page.clearCredentials(id);
|
||||
page.clearSubmittedCredentials();
|
||||
};
|
||||
|
||||
kpxcEvent.onLoginPopup = async function(tab, logins) {
|
||||
kpxcEvent.initLoginPopup = async function(tab, logins) {
|
||||
const popupData = {
|
||||
iconType: 'normal',
|
||||
popup: 'popup_login'
|
||||
|
|
@ -168,7 +184,7 @@ kpxcEvent.initHttpAuth = async function() {
|
|||
httpAuth.init();
|
||||
};
|
||||
|
||||
kpxcEvent.onHTTPAuthPopup = async function(tab, data) {
|
||||
kpxcEvent.initHttpAuthPopup = async function(tab, data) {
|
||||
const popupData = {
|
||||
iconType: 'normal',
|
||||
popup: 'popup_httpauth'
|
||||
|
|
@ -178,7 +194,7 @@ kpxcEvent.onHTTPAuthPopup = async function(tab, data) {
|
|||
await browserAction.show(tab, popupData);
|
||||
};
|
||||
|
||||
kpxcEvent.onUsernameFieldDetected = async function(tab, detected) {
|
||||
kpxcEvent.usernameFieldDetected = async function(tab, detected) {
|
||||
if (tab?.id) {
|
||||
page.tabs[tab.id].usernameFieldDetected = detected;
|
||||
}
|
||||
|
|
@ -221,17 +237,17 @@ kpxcEvent.getIsKeePassXCAvailable = async function() {
|
|||
};
|
||||
|
||||
kpxcEvent.hideGettingStartedGuideAlert = async function(tab) {
|
||||
const settings = await kpxcEvent.onLoadSettings();
|
||||
const settings = await kpxcEvent.loadSettings();
|
||||
settings.showGettingStartedGuideAlert = false;
|
||||
|
||||
await kpxcEvent.onSaveSettings(tab, settings);
|
||||
await kpxcEvent.saveSettings(tab, settings);
|
||||
};
|
||||
|
||||
kpxcEvent.hideTroubleshootingGuideAlert = async function(tab) {
|
||||
const settings = await kpxcEvent.onLoadSettings();
|
||||
const settings = await kpxcEvent.loadSettings();
|
||||
settings.showTroubleshootingGuideAlert = false;
|
||||
|
||||
await kpxcEvent.onSaveSettings(tab, settings);
|
||||
await kpxcEvent.saveSettings(tab, settings);
|
||||
};
|
||||
|
||||
// Bounce message back to all frames
|
||||
|
|
@ -243,13 +259,13 @@ kpxcEvent.sendBackToTabs = async function(tab, args = []) {
|
|||
|
||||
// All methods named in this object have to be declared BEFORE this!
|
||||
kpxcEvent.messageHandlers = {
|
||||
'add_credentials': keepass.addCredentials,
|
||||
'associate': keepass.associate,
|
||||
'banner_get_position': page.getBannerPosition,
|
||||
'banner_set_position': page.setBannerPosition,
|
||||
'check_database_hash': keepass.checkDatabaseHash,
|
||||
'check_update_keepassxc': kpxcEvent.onCheckUpdateKeePassXC,
|
||||
'check_update_keepassxc': kpxcEvent.checkUpdateKeePassXC,
|
||||
'compare_versions': kpxcEvent.compareMultipleVersions,
|
||||
'create_credentials': keepass.createCredentials,
|
||||
'create_new_group': keepass.createNewGroup,
|
||||
'enable_automatic_reconnect': keepass.enableAutomaticReconnect,
|
||||
'disable_automatic_reconnect': keepass.disableAutomaticReconnect,
|
||||
|
|
@ -257,14 +273,14 @@ kpxcEvent.messageHandlers = {
|
|||
'frame_message': kpxcEvent.sendBackToTabs,
|
||||
'generate_password': keepass.generatePassword,
|
||||
'get_color_theme': kpxcEvent.getColorTheme,
|
||||
'get_connected_database': kpxcEvent.onGetConnectedDatabase,
|
||||
'get_database_hash': keepass.getDatabaseHash,
|
||||
'get_connected_database': kpxcEvent.getConnectedDatabase,
|
||||
'get_database_hash': keepass.getDatabaseHash, // TODO ?
|
||||
'get_database_groups': keepass.getDatabaseGroups,
|
||||
'get_error_message': keepass.getErrorMessage,
|
||||
'get_keepassxc_versions': kpxcEvent.onGetKeePassXCVersions,
|
||||
'get_keepassxc_versions': kpxcEvent.getKeePassXCVersions,
|
||||
'get_login_list': page.getLoginList,
|
||||
'get_status': kpxcEvent.onGetStatus,
|
||||
'get_tab_information': kpxcEvent.onGetTabInformation,
|
||||
'get_status': kpxcEvent.getStatus,
|
||||
'get_tab_information': kpxcEvent.getTabInformation,
|
||||
'get_totp': keepass.getTotp,
|
||||
'hide_getting_started_guide_alert': kpxcEvent.hideGettingStartedGuideAlert,
|
||||
'hide_troubleshooting_guide_alert': kpxcEvent.hideTroubleshootingGuideAlert,
|
||||
|
|
@ -272,12 +288,15 @@ kpxcEvent.messageHandlers = {
|
|||
'init_http_auth': kpxcEvent.initHttpAuth,
|
||||
'is_connected': kpxcEvent.getIsKeePassXCAvailable,
|
||||
'is_iframe_allowed': page.isIframeAllowed,
|
||||
'is_protocol_v2': kpxcEvent.isProtocolV2,
|
||||
'is_site_ignored': page.isSiteIgnored,
|
||||
'load_keyring': kpxcEvent.onLoadKeyRing,
|
||||
'load_settings': kpxcEvent.onLoadSettings,
|
||||
'load_keyring': kpxcEvent.loadKeyRing,
|
||||
'load_settings': kpxcEvent.loadSettings,
|
||||
'lock_database': kpxcEvent.lockDatabase,
|
||||
'page_clear_auto_lock_requested': page.clearAutoLockRequested,
|
||||
'page_clear_logins': kpxcEvent.pageClearLogins,
|
||||
'page_clear_submitted': page.clearSubmittedCredentials,
|
||||
'page_get_auto_lock_requested': page.getAutoLockRequested,
|
||||
'page_get_autosubmit_performed': page.getAutoSubmitPerformed,
|
||||
'page_get_login_id': page.getLoginId,
|
||||
'page_get_manual_fill': page.getManualFill,
|
||||
|
|
@ -292,16 +311,16 @@ kpxcEvent.messageHandlers = {
|
|||
'passkeys_register': keepass.passkeysRegister,
|
||||
'password_get_filled': kpxcEvent.passwordGetFilled,
|
||||
'password_set_filled': kpxcEvent.passwordSetFilled,
|
||||
'popup_login': kpxcEvent.onLoginPopup,
|
||||
'reconnect': kpxcEvent.onReconnect,
|
||||
'remove_credentials_from_tab_information': kpxcEvent.onRemoveCredentialsFromTabInformation,
|
||||
'popup_login': kpxcEvent.initLoginPopup,
|
||||
'reconnect': kpxcEvent.reconnect,
|
||||
'remove_credentials_from_tab_information': kpxcEvent.removeCredentialsFromTabInformation,
|
||||
'request_autotype': keepass.requestAutotype,
|
||||
'retrieve_credentials': page.retrieveCredentials,
|
||||
'show_default_browseraction': browserAction.showDefault,
|
||||
'update_credentials': keepass.updateCredentials,
|
||||
'username_field_detected': kpxcEvent.onUsernameFieldDetected,
|
||||
'save_settings': kpxcEvent.onSaveSettings,
|
||||
'update_available_keepassxc': kpxcEvent.onUpdateAvailableKeePassXC,
|
||||
'username_field_detected': kpxcEvent.usernameFieldDetected,
|
||||
'save_settings': kpxcEvent.saveSettings,
|
||||
'update_available_keepassxc': kpxcEvent.updateAvailableKeePassXC,
|
||||
'update_context_menu': page.updateContextMenu,
|
||||
'update_popup': page.updatePopup
|
||||
};
|
||||
|
|
|
|||
|
|
@ -47,9 +47,9 @@ httpAuth.handleRequestCallback = function(details, callback) {
|
|||
httpAuth.processPendingCallbacks(details, callback, callback);
|
||||
};
|
||||
|
||||
httpAuth.retrieveCredentials = async function(tabId, url, submitUrl) {
|
||||
return await keepass.retrieveCredentials(tabId, [ url, submitUrl, false, true ]).catch((err) => {
|
||||
logError('httpAuth.retrieveCredentials error: ' + err);
|
||||
httpAuth.getCredentials = async function(tabId, url, submitUrl) {
|
||||
return await keepass.getCredentials(tabId, [ url, submitUrl, false, true ]).catch((err) => {
|
||||
logError('httpAuth.getCredentials error: ' + err);
|
||||
return Promise.reject();
|
||||
});
|
||||
};
|
||||
|
|
@ -72,7 +72,7 @@ httpAuth.processPendingCallbacks = async function(details, resolve, reject) {
|
|||
|
||||
details.searchUrl = (details.isProxy && details.proxyUrl) ? details.proxyUrl : details.url;
|
||||
|
||||
const logins = await httpAuth.retrieveCredentials({ 'id': details.tabId }, details.searchUrl, details.searchUrl);
|
||||
const logins = await httpAuth.getCredentials({ 'id': details.tabId }, details.searchUrl, details.searchUrl);
|
||||
httpAuth.loginOrShowCredentials(logins, details, resolve, reject);
|
||||
};
|
||||
|
||||
|
|
@ -90,7 +90,10 @@ httpAuth.loginOrShowCredentials = function(logins, details, resolve, reject) {
|
|||
if (page.settings.showNotifications) {
|
||||
showNotification(tr('multipleCredentialsDetected'));
|
||||
}
|
||||
kpxcEvent.onHTTPAuthPopup({ 'id': details.tabId }, { 'logins': logins, 'url': details.searchUrl, 'resolve': resolve });
|
||||
kpxcEvent.initHttpAuthPopup(
|
||||
{ id: details.tabId },
|
||||
{ logins: logins, url: details.searchUrl, resolve: resolve },
|
||||
);
|
||||
}
|
||||
} else {
|
||||
logError('No logins found for HTTP Basic Auth.');
|
||||
|
|
|
|||
|
|
@ -1,21 +1,24 @@
|
|||
'use strict';
|
||||
|
||||
const keepass = {};
|
||||
keepass.associated = { 'value': false, 'hash': null };
|
||||
keepass.keyPair = { publicKey: null, secretKey: null };
|
||||
keepass.serverPublicKey = '';
|
||||
keepass.associated = { value: false, hash: null };
|
||||
keepass.cacheTimeout = 30 * 1000; // Milliseconds
|
||||
keepass.clientID = '';
|
||||
keepass.currentKeePassXC = '';
|
||||
keepass.databaseAssociationStatuses = {};
|
||||
keepass.databaseHash = ''; // Hash of the active database
|
||||
keepass.databaseStatuses = [];
|
||||
keepass.isConnected = false;
|
||||
keepass.isDatabaseClosed = true;
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
keepass.isEncryptionKeyUnrecognized = false;
|
||||
keepass.currentKeePassXC = '';
|
||||
keepass.requiredKeePassXC = '2.3.1';
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
keepass.keyPair = { publicKey: null, secretKey: null };
|
||||
keepass.latestVersionUrl = 'https://api.github.com/repos/keepassxreboot/keepassxc/releases/latest';
|
||||
keepass.cacheTimeout = 30 * 1000; // Milliseconds
|
||||
keepass.databaseHash = '';
|
||||
keepass.previousDatabaseHash = '';
|
||||
keepass.protocolV2 = false;
|
||||
keepass.reconnectLoop = null;
|
||||
keepass.requiredKeePassXC = '2.3.1';
|
||||
keepass.serverPublicKey = '';
|
||||
|
||||
const kpActions = {
|
||||
SET_LOGIN: 'set-login',
|
||||
|
|
@ -33,7 +36,91 @@ const kpActions = {
|
|||
GET_TOTP: 'get-totp',
|
||||
REQUEST_AUTOTYPE: 'request-autotype',
|
||||
PASSKEYS_REGISTER: 'passkeys-register',
|
||||
PASSKEYS_GET: 'passkeys-get'
|
||||
PASSKEYS_GET: 'passkeys-get',
|
||||
// Protocol V2
|
||||
CREATE_CREDENTIALS: 'create-credentials',
|
||||
GET_CREDENTIALS: 'get-credentials',
|
||||
GET_DATABASE_STATUSES: 'get-database-statuses'
|
||||
};
|
||||
|
||||
const kpErrors = {
|
||||
UNKNOWN_ERROR: 0,
|
||||
DATABASE_NOT_OPENED: 1,
|
||||
DATABASE_HASH_NOT_RECEIVED: 2,
|
||||
CLIENT_PUBLIC_KEY_NOT_RECEIVED: 3,
|
||||
CANNOT_DECRYPT_MESSAGE: 4,
|
||||
TIMEOUT_OR_NOT_CONNECTED: 5,
|
||||
ACTION_CANCELLED_OR_DENIED: 6,
|
||||
PUBLIC_KEY_NOT_FOUND: 7,
|
||||
ASSOCIATION_FAILED: 8,
|
||||
KEY_CHANGE_FAILED: 9,
|
||||
ENCRYPTION_KEY_UNRECOGNIZED: 10,
|
||||
NO_SAVED_DATABASES_FOUND: 11,
|
||||
INCORRECT_ACTION: 12,
|
||||
EMPTY_MESSAGE_RECEIVED: 13,
|
||||
NO_URL_PROVIDED: 14,
|
||||
NO_LOGINS_FOUND: 15,
|
||||
NO_GROUPS_FOUND: 16,
|
||||
CANNOT_CREATE_NEW_GROUP: 17,
|
||||
NO_VALID_UUID_PROVIDED: 18,
|
||||
ACCESS_TO_ALL_ENTRIES_DENIED: 19,
|
||||
PASSKEYS_ATTESTATION_NOT_SUPPORTED: 20,
|
||||
PASSKEYS_CREDENTIAL_IS_EXCLUDED: 21,
|
||||
PASSKEYS_REQUEST_CANCELED: 22,
|
||||
PASSKEYS_INVALID_USER_VERIFICATION: 23,
|
||||
PASSKEYS_EMPTY_PUBLIC_KEY: 24,
|
||||
PASSKEYS_INVALID_URL_PROVIDED: 25,
|
||||
PASSKEYS_ORIGIN_NOT_ALLOWED: 26,
|
||||
PASSKEYS_DOMAIN_IS_NOT_VALID: 27,
|
||||
PASSKEYS_DOMAIN_RPID_MISMATCH: 28,
|
||||
PASSKEYS_NO_SUPPORTED_ALGORITHMS: 29,
|
||||
PASSKEYS_WAIT_FOR_LIFETIMER: 30,
|
||||
PASSKEYS_UNKNOWN_ERROR: 31,
|
||||
PASSKEYS_INVALID_CHALLENGE: 32,
|
||||
PASSKEYS_INVALID_USER_ID: 33,
|
||||
ACTION_TIMEOUT: 34,
|
||||
|
||||
errorMessages: {
|
||||
0: { msg: tr('errorMessageUnknown') },
|
||||
1: { msg: tr('errorMessageDatabaseNotOpened') },
|
||||
2: { msg: tr('errorMessageDatabaseHash') },
|
||||
3: { msg: tr('errorMessageClientPublicKey') },
|
||||
4: { msg: tr('errorMessageDecrypt') },
|
||||
5: { msg: tr('errorMessageTimeout') },
|
||||
6: { msg: tr('errorMessageCanceled') },
|
||||
7: { msg: tr('errorMessageEncrypt') },
|
||||
8: { msg: tr('errorMessageAssociate') },
|
||||
9: { msg: tr('errorMessageKeyExchange') },
|
||||
10: { msg: tr('errorMessageEncryptionKey') },
|
||||
11: { msg: tr('errorMessageSavedDatabases') },
|
||||
12: { msg: tr('errorMessageIncorrectAction') },
|
||||
13: { msg: tr('errorMessageEmptyMessage') },
|
||||
14: { msg: tr('errorMessageNoURL') },
|
||||
15: { msg: tr('errorMessageNoLogins') },
|
||||
16: { msg: tr('errorMessageNoGroupsFound') },
|
||||
17: { msg: tr('errorMessageCannotCreateNewGroup') },
|
||||
18: { msg: tr('errorMessageNoValidUuidProvided') },
|
||||
19: { msg: tr('errorMessageAccessToAllEntriesDenied') },
|
||||
20: { msg: tr('errorMessagePasskeysAttestationNotSupported') },
|
||||
21: { msg: tr('errorMessagePasskeysCredentialIsExcluded') },
|
||||
22: { msg: tr('errorMessagePasskeysRequestCanceled') },
|
||||
23: { msg: tr('errorMessagePasskeysInvalidUserVerification') },
|
||||
24: { msg: tr('errorMessagePasskeysEmptyPublicKey') },
|
||||
25: { msg: tr('errorMessagePasskeysInvalidUrlProvided') },
|
||||
26: { msg: tr('errorMessagePasskeysOriginNotAllowed') },
|
||||
27: { msg: tr('errorMessagePasskeysDomainNotValid') },
|
||||
28: { msg: tr('errorMessagePasskeysDomainRpIdMismatch') },
|
||||
29: { msg: tr('errorMessagePasskeysNoSupportedAlgorithms') },
|
||||
30: { msg: tr('errorMessagePasskeysWaitforLifeTimer') },
|
||||
31: { msg: tr('errorMessagePasskeysUnknownError') },
|
||||
32: { msg: tr('errorMessagePasskeysInvalidChallenge') },
|
||||
33: { msg: tr('errorMessagePasskeysInvalidUserId') },
|
||||
34: { msg: tr('errorActionTimeout') },
|
||||
},
|
||||
|
||||
getError(errorCode) {
|
||||
return this.errorMessages[errorCode].msg;
|
||||
}
|
||||
};
|
||||
|
||||
browser.storage.local.get({ 'latestKeePassXC': { 'version': '', 'lastChecked': null }, 'keyRing': {} }).then((item) => {
|
||||
|
|
@ -42,626 +129,75 @@ browser.storage.local.get({ 'latestKeePassXC': { 'version': '', 'lastChecked': n
|
|||
});
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Commands
|
||||
// Command wrappers for events
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
keepass.addCredentials = async function(tab, args = []) {
|
||||
const [ username, password, url, group, groupUuid ] = args;
|
||||
return keepass.updateCredentials(tab, [ null, username, password, url, group, groupUuid ]);
|
||||
keepass.associate = async function(tab, args = []) {
|
||||
return keepass.protocolV2 ? await protocol.associate(tab, args) : await keepassProtocol.associate(tab, args);
|
||||
};
|
||||
|
||||
keepass.updateCredentials = async function(tab, args = []) {
|
||||
try {
|
||||
const [ entryId, username, password, url, group, groupUuid ] = args;
|
||||
const taResponse = await keepass.testAssociation(tab);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
const kpAction = kpActions.SET_LOGIN;
|
||||
const [ dbid ] = keepass.getCryptoKey();
|
||||
const nonce = keepassClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
id: dbid,
|
||||
login: username,
|
||||
password: password,
|
||||
url: url,
|
||||
submitUrl: url
|
||||
};
|
||||
|
||||
if (entryId) {
|
||||
messageData.uuid = entryId;
|
||||
}
|
||||
|
||||
if (!entryId && page.settings.downloadFaviconAfterSave) {
|
||||
messageData.downloadFavicon = 'true';
|
||||
}
|
||||
|
||||
if (group && groupUuid) {
|
||||
messageData.group = group;
|
||||
messageData.groupUuid = groupUuid;
|
||||
}
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
// KeePassXC versions lower than 2.5.0 will have an empty parsed.error
|
||||
let successMessage = response.error;
|
||||
if (response.error === 'success' || response.error === '') {
|
||||
successMessage = entryId ? 'updated' : 'created';
|
||||
}
|
||||
|
||||
return successMessage;
|
||||
} else {
|
||||
return 'error';
|
||||
}
|
||||
} catch (err) {
|
||||
logError(`updateCredentials failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
keepass.retrieveCredentials = async function(tab, args = []) {
|
||||
try {
|
||||
const [ url, submiturl, triggerUnlock = false, httpAuth = false ] = args;
|
||||
const taResponse = await keepass.testAssociation(tab, [ false, triggerUnlock ]);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let entries = [];
|
||||
const kpAction = kpActions.GET_LOGINS;
|
||||
const nonce = keepassClient.getNonce();
|
||||
const [ dbid ] = keepass.getCryptoKey();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
id: dbid,
|
||||
url: url,
|
||||
keys: keepass.getCryptoKeys()
|
||||
};
|
||||
|
||||
if (submiturl) {
|
||||
messageData.submitUrl = submiturl;
|
||||
}
|
||||
|
||||
if (httpAuth) {
|
||||
messageData.httpAuth = 'true';
|
||||
}
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
entries = removeDuplicateEntries(response.entries);
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
|
||||
if (entries.length === 0) {
|
||||
// Questionmark-icon is not triggered, so we have to trigger for the normal symbol
|
||||
browserAction.showDefault(tab);
|
||||
}
|
||||
|
||||
logDebug(`Found ${entries.length} entries for url ${url}`);
|
||||
return entries;
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`retrieveCredentials failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
keepass.generatePassword = async function(tab) {
|
||||
if (!keepass.isConnected) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const taResponse = await keepass.testAssociation(tab);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!compareVersion(keepass.requiredKeePassXC, keepass.currentKeePassXC)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let password;
|
||||
const kpAction = kpActions.GENERATE_PASSWORD;
|
||||
const nonce = keepassClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID,
|
||||
requestID: keepassClient.getRequestId()
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
password = response.entries ?? response.password;
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
} else {
|
||||
logError('generatePassword rejected');
|
||||
}
|
||||
|
||||
return password;
|
||||
} catch (err) {
|
||||
logError(`generatePassword failed: ${err}`);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
keepass.associate = async function(tab) {
|
||||
if (keepass.isAssociated()) {
|
||||
return AssociatedAction.ASSOCIATED;
|
||||
}
|
||||
|
||||
try {
|
||||
await keepass.getDatabaseHash(tab);
|
||||
if (keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable) {
|
||||
return AssociatedAction.NOT_ASSOCIATED;
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
const kpAction = kpActions.ASSOCIATE;
|
||||
const key = nacl.util.encodeBase64(keepass.keyPair.publicKey);
|
||||
const nonce = keepassClient.getNonce();
|
||||
const idKeyPair = nacl.box.keyPair();
|
||||
const idKey = nacl.util.encodeBase64(idKeyPair.publicKey);
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
key: key,
|
||||
idKey: idKey
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce, false, true);
|
||||
if (response) {
|
||||
// Use public key as identification key with older KeePassXC releases
|
||||
const savedKey = compareVersion('2.3.4', keepass.currentKeePassXC) ? idKey : key;
|
||||
keepass.setCryptoKey(response.id, savedKey); // Save the new identification public key as id key for the database
|
||||
keepass.associated.value = true;
|
||||
keepass.associated.hash = response.hash || 0;
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return AssociatedAction.NEW_ASSOCIATION;
|
||||
}
|
||||
|
||||
keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED);
|
||||
return AssociatedAction.NOT_ASSOCIATED;
|
||||
} catch (err) {
|
||||
logError(`associate failed: ${err}`);
|
||||
}
|
||||
|
||||
return AssociatedAction.NOT_ASSOCIATED;
|
||||
};
|
||||
|
||||
keepass.testAssociation = async function(tab, args = []) {
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
try {
|
||||
const [ enableTimeout = false, triggerUnlock = false ] = args;
|
||||
const dbHash = await keepass.getDatabaseHash(tab, [ enableTimeout, triggerUnlock ]);
|
||||
if (!dbHash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!keepass.serverPublicKey) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
keepass.handleError(tab, kpErrors.PUBLIC_KEY_NOT_FOUND);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.TEST_ASSOCIATE;
|
||||
const nonce = keepassClient.getNonce();
|
||||
const [ dbid, dbkey ] = keepass.getCryptoKey();
|
||||
|
||||
if (dbkey === null || dbid === null) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
keepass.handleError(tab, kpErrors.NO_SAVED_DATABASES_FOUND);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
id: dbid,
|
||||
key: dbkey
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce, enableTimeout);
|
||||
if (!response) {
|
||||
const hash = response.hash || 0;
|
||||
keepass.deleteKey(hash);
|
||||
keepass.isEncryptionKeyUnrecognized = true;
|
||||
keepass.handleError(tab, kpErrors.ENCRYPTION_KEY_UNRECOGNIZED);
|
||||
keepass.associated.value = false;
|
||||
keepass.associated.hash = null;
|
||||
} else if (!keepass.isAssociated()) {
|
||||
keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED);
|
||||
} else {
|
||||
keepass.isEncryptionKeyUnrecognized = false;
|
||||
keepass.clearErrorMessage(tab);
|
||||
}
|
||||
|
||||
return keepass.isAssociated();
|
||||
} catch (err) {
|
||||
logError(`testAssociation failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
keepass.getDatabaseHash = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!keepass.serverPublicKey) {
|
||||
keepass.changePublicKeys(tab);
|
||||
}
|
||||
|
||||
const [ enableTimeout = false, triggerUnlock = false ] = args;
|
||||
const kpAction = kpActions.GET_DATABASE_HASH;
|
||||
const [ nonce, incrementedNonce ] = keepassClient.getNonces();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
connectedKeys: Object.keys(keepass.keyRing) // This will be removed in the future
|
||||
};
|
||||
|
||||
const encrypted = keepassClient.encrypt(messageData, nonce);
|
||||
if (encrypted.length <= 0) {
|
||||
keepass.handleError(tab, kpErrors.PUBLIC_KEY_NOT_FOUND);
|
||||
keepass.updateDatabaseHashToContent();
|
||||
return keepass.databaseHash;
|
||||
}
|
||||
|
||||
try {
|
||||
const request = keepassClient.buildRequest(kpAction, keepassClient.encrypt(messageData, nonce), nonce, keepass.clientID, triggerUnlock);
|
||||
const response = await keepassClient.sendNativeMessage(request, enableTimeout);
|
||||
if (response.message && response.nonce) {
|
||||
const res = keepassClient.decrypt(response.message, response.nonce);
|
||||
if (!res) {
|
||||
keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
|
||||
return '';
|
||||
}
|
||||
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
if (keepassClient.verifyDatabaseResponse(parsed, incrementedNonce) && parsed.hash) {
|
||||
const oldDatabaseHash = keepass.databaseHash;
|
||||
keepass.setcurrentKeePassXCVersion(parsed.version);
|
||||
keepass.databaseHash = parsed.hash || '';
|
||||
|
||||
if (oldDatabaseHash && oldDatabaseHash !== keepass.databaseHash) {
|
||||
keepass.associated.value = false;
|
||||
keepass.associated.hash = null;
|
||||
}
|
||||
|
||||
keepass.isDatabaseClosed = false;
|
||||
keepass.isKeePassXCAvailable = true;
|
||||
|
||||
// Update the databaseHash from legacy hash
|
||||
if (parsed.oldHash) {
|
||||
keepass.updateDatabaseHash(parsed.oldHash, parsed.hash);
|
||||
}
|
||||
|
||||
return parsed.hash;
|
||||
} else if (parsed.errorCode) {
|
||||
keepass.databaseHash = '';
|
||||
keepass.isDatabaseClosed = true;
|
||||
keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED);
|
||||
return keepass.databaseHash;
|
||||
}
|
||||
|
||||
return keepass.databaseHash;
|
||||
}
|
||||
|
||||
keepass.databaseHash = '';
|
||||
keepass.isDatabaseClosed = true;
|
||||
if ((response.message && response.message === '') || response.errorCode === kpErrors.TIMEOUT_OR_NOT_CONNECTED) {
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
keepass.isConnected = false;
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
} else {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
}
|
||||
return keepass.databaseHash;
|
||||
} catch (err) {
|
||||
logError(`getDatabaseHash failed: ${err}`);
|
||||
return keepass.databaseHash;
|
||||
}
|
||||
};
|
||||
|
||||
keepass.changePublicKeys = async function(tab, enableTimeout = false, connectionTimeout) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.CHANGE_PUBLIC_KEYS;
|
||||
const key = nacl.util.encodeBase64(keepass.keyPair.publicKey);
|
||||
const [ nonce, incrementedNonce ] = keepassClient.getNonces();
|
||||
keepass.clientID = nacl.util.encodeBase64(nacl.randomBytes(keepassClient.keySize));
|
||||
|
||||
const request = {
|
||||
action: kpAction,
|
||||
publicKey: key,
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendNativeMessage(request, enableTimeout, connectionTimeout);
|
||||
keepass.setcurrentKeePassXCVersion(response.version);
|
||||
|
||||
if (!keepassClient.verifyKeyResponse(response, key, incrementedNonce)) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
keepass.handleError(tab, kpErrors.KEY_CHANGE_FAILED);
|
||||
}
|
||||
|
||||
keepass.updateDatabaseHashToContent();
|
||||
return false;
|
||||
}
|
||||
|
||||
keepass.isKeePassXCAvailable = true;
|
||||
console.log(`${EXTENSION_NAME}: Server public key: ${nacl.util.encodeBase64(keepass.serverPublicKey)}`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
logError(`changePublicKeys failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
keepass.lockDatabase = async function(tab) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.LOCK_DATABASE;
|
||||
const nonce = keepassClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
keepass.isDatabaseClosed = true;
|
||||
keepass.updateDatabase();
|
||||
|
||||
// Display error message in the popup
|
||||
keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED);
|
||||
return true;
|
||||
} else {
|
||||
keepass.isDatabaseClosed = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (err) {
|
||||
logError(`ockDatabase failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
keepass.getDatabaseGroups = async function(tab) {
|
||||
try {
|
||||
const taResponse = await keepass.testAssociation(tab, [ false ]);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let groups = [];
|
||||
const kpAction = kpActions.GET_DATABASE_GROUPS;
|
||||
const nonce = keepassClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
groups = response.groups;
|
||||
groups.defaultGroup = page.settings.defaultGroup;
|
||||
groups.defaultGroupAlwaysAsk = page.settings.defaultGroupAlwaysAsk;
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
return groups;
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`getDatabaseGroups failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
keepass.createCredentials = async function(tab, args = []) {
|
||||
return keepass.protocolV2
|
||||
? await protocol.createCredentials(tab, args)
|
||||
: await keepassProtocol.addCredentials(tab, args);
|
||||
};
|
||||
|
||||
keepass.createNewGroup = async function(tab, args = []) {
|
||||
try {
|
||||
const [ groupName ] = args;
|
||||
const taResponse = await keepass.testAssociation(tab, [ false ]);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
return keepass.protocolV2
|
||||
? await protocol.createNewGroup(tab, args)
|
||||
: await keepassProtocol.createNewGroup(tab, args);
|
||||
};
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
keepass.generatePassword = async function(tab, args = []) {
|
||||
return keepass.protocolV2
|
||||
? await protocol.generatePassword(tab, args)
|
||||
: await keepassProtocol.generatePassword(tab, args);
|
||||
};
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
keepass.getCredentials = async function(tab, args = []) {
|
||||
return keepass.protocolV2
|
||||
? await protocol.getCredentials(tab, args)
|
||||
: await keepassProtocol.retrieveCredentials(tab, args);
|
||||
};
|
||||
|
||||
const kpAction = kpActions.CREATE_NEW_GROUP;
|
||||
const nonce = keepassClient.getNonce();
|
||||
keepass.getDatabaseGroups = async function(tab, args = []) {
|
||||
return keepass.protocolV2
|
||||
? await protocol.getDatabaseGroups(tab, args)
|
||||
: await keepassProtocol.getDatabaseGroups(tab, args);
|
||||
};
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
groupName: groupName
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
return response;
|
||||
} else {
|
||||
logError('getDatabaseGroups rejected');
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`createNewGroup failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
keepass.getDatabaseHash = async function(tab, args = []) {
|
||||
return keepass.protocolV2
|
||||
? await protocol.getDatabaseStatuses(tab, args)
|
||||
: await keepassProtocol.getDatabaseHash(tab, args);
|
||||
};
|
||||
|
||||
keepass.getTotp = async function(tab, args = []) {
|
||||
const [ uuid, oldTotp ] = args;
|
||||
if (!compareVersion('2.6.1', keepass.currentKeePassXC, true)) {
|
||||
return oldTotp;
|
||||
}
|
||||
|
||||
const taResponse = await keepass.testAssociation(tab, [ false ]);
|
||||
if (!taResponse || !keepass.isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.GET_TOTP;
|
||||
const nonce = keepassClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
uuid: uuid
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
return response.totp;
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (err) {
|
||||
logError(`getTotp failed: ${err}`);
|
||||
}
|
||||
return keepass.protocolV2 ? await protocol.getTotp(tab, args) : await keepassProtocol.getTotp(tab, args);
|
||||
};
|
||||
|
||||
keepass.requestAutotype = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.REQUEST_AUTOTYPE;
|
||||
const nonce = keepassClient.getNonce();
|
||||
const search = await page.getBaseDomainFromUrl(args[0]);
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
search: search
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
return response;
|
||||
} catch (err) {
|
||||
logError(`requestAutotype failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
keepass.passkeysRegister = async function(tab, args = []) {
|
||||
try {
|
||||
const taResponse = await keepass.testAssociation(tab, [ false ]);
|
||||
if (!taResponse || !keepass.isConnected || args.length < 2) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
const kpAction = kpActions.PASSKEYS_REGISTER;
|
||||
const nonce = keepassClient.getNonce();
|
||||
const [ publicKey, origin ] = args;
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
publicKey: JSON.parse(JSON.stringify(publicKey)),
|
||||
origin: origin,
|
||||
groupName: page?.settings?.defaultPasskeyGroup,
|
||||
keys: keepass.getCryptoKeys()
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`passkeysRegister failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
keepass.lockDatabase = async function(tab, args = []) {
|
||||
return keepass.protocolV2 ? await protocol.lockDatabase(tab, args) : await keepassProtocol.lockDatabase(tab, args);
|
||||
};
|
||||
|
||||
keepass.passkeysGet = async function(tab, args = []) {
|
||||
try {
|
||||
const taResponse = await keepass.testAssociation(tab, [ false ]);
|
||||
if (!taResponse || !keepass.isConnected || args.length < 2) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
return keepass.protocolV2 ? await protocol.passkeysGet(tab, args) : await keepassProtocol.passkeysGet(tab, args);
|
||||
};
|
||||
|
||||
const kpAction = kpActions.PASSKEYS_GET;
|
||||
const nonce = keepassClient.getNonce();
|
||||
const publicKey = args[0];
|
||||
const origin = args[1];
|
||||
keepass.passkeysRegister = async function(tab, args = []) {
|
||||
return keepass.protocolV2 ? await protocol.passkeysGet(tab, args) : await keepassProtocol.passkeysGet(tab, args);
|
||||
};
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
publicKey: JSON.parse(JSON.stringify(publicKey)),
|
||||
origin: origin,
|
||||
keys: keepass.getCryptoKeys()
|
||||
};
|
||||
keepass.requestAutotype = async function (tab, args = []) {
|
||||
return keepass.protocolV2
|
||||
? await protocol.requestAutotype(tab, args)
|
||||
: await keepassProtocol.requestAutotype(tab, args);
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`passkeysGet failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
keepass.updateCredentials = async function (tab, args = []) {
|
||||
return keepass.protocolV2
|
||||
? await protocol.updateCredentials(tab, args)
|
||||
: await keepassProtocol.updateCredentials(tab, args);
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
|
@ -805,26 +341,55 @@ keepass.disableAutomaticReconnect = function() {
|
|||
};
|
||||
|
||||
keepass.reconnect = async function(tab = null, connectionTimeout = 1500) {
|
||||
keepassClient.connectToNative();
|
||||
keepass.generateNewKeyPair();
|
||||
const keyChangeResult = await keepass.changePublicKeys(tab, !!connectionTimeout, connectionTimeout).catch(() => false);
|
||||
protocolClient.connectToNative();
|
||||
protocolClient.generateNewKeyPair();
|
||||
|
||||
const keyChangeResult = await protocol
|
||||
.changePublicKeys(tab, !!connectionTimeout, connectionTimeout)
|
||||
.catch(() => false);
|
||||
|
||||
// Change public keys timeout
|
||||
if (!keyChangeResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hash = await keepass.getDatabaseHash(tab);
|
||||
if (hash !== '') {
|
||||
keepass.clearErrorMessage(tab);
|
||||
if (!keepass.protocolV2) {
|
||||
const hash = await keepass.getDatabaseHash(tab);
|
||||
if (hash !== '') {
|
||||
keepass.clearErrorMessage(tab);
|
||||
}
|
||||
|
||||
await keepassProtocol.testAssociation();
|
||||
await keepass.isConfigured();
|
||||
}
|
||||
|
||||
await keepass.testAssociation();
|
||||
await keepass.isConfigured();
|
||||
// TODO: What to do with Protocol V2?
|
||||
|
||||
keepass.updateDatabaseHashToContent();
|
||||
return true;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Error handling
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
keepass.clearErrorMessage = function(tab) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
page.tabs[tab.id].errorMessage = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
keepass.handleError = function(tab, errorCode, errorMessage = '') {
|
||||
if (errorMessage.length === 0) {
|
||||
errorMessage = kpErrors.getError(errorCode);
|
||||
}
|
||||
|
||||
logError(`${errorCode}: ${errorMessage}`);
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
page.tabs[tab.id].errorMessage = errorMessage;
|
||||
}
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Utils
|
||||
//--------------------------------------------------------------------------
|
||||
|
|
@ -863,7 +428,9 @@ keepass.setcurrentKeePassXCVersion = function(version) {
|
|||
keepass.keePassXCUpdateAvailable = async function() {
|
||||
const checkUpdate = Number(page.settings.checkUpdateKeePassXC);
|
||||
if (checkUpdate !== CHECK_UPDATE_NEVER) {
|
||||
const lastChecked = (keepass.latestKeePassXC.lastChecked) ? new Date(keepass.latestKeePassXC.lastChecked) : new Date(1986, 11, 21);
|
||||
const lastChecked = keepass.latestKeePassXC.lastChecked
|
||||
? new Date(keepass.latestKeePassXC.lastChecked)
|
||||
: new Date(1986, 11, 21);
|
||||
const daysSinceLastCheck = Math.floor(((new Date()).getTime() - lastChecked.getTime()) / 86400000);
|
||||
if (daysSinceLastCheck >= checkUpdate) {
|
||||
await keepass.checkForNewKeePassXCVersion();
|
||||
|
|
@ -891,23 +458,6 @@ keepass.checkForNewKeePassXCVersion = async function() {
|
|||
keepass.latestKeePassXC.lastChecked = new Date().valueOf();
|
||||
};
|
||||
|
||||
keepass.clearErrorMessage = function(tab) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
page.tabs[tab.id].errorMessage = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
keepass.handleError = function(tab, errorCode, errorMessage = '') {
|
||||
if (errorMessage.length === 0) {
|
||||
errorMessage = kpErrors.getError(errorCode);
|
||||
}
|
||||
|
||||
logError(`${errorCode}: ${errorMessage}`);
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
page.tabs[tab.id].errorMessage = errorMessage;
|
||||
}
|
||||
};
|
||||
|
||||
keepass.updatePopup = function() {
|
||||
if (page && page.tabs.length > 0) {
|
||||
browserAction.showDefault();
|
||||
|
|
@ -915,14 +465,23 @@ keepass.updatePopup = function() {
|
|||
};
|
||||
|
||||
// Updates the database hashes to content script
|
||||
keepass.updateDatabase = async function() {
|
||||
keepass.updateDatabase = async function(tab) {
|
||||
keepass.associated.value = false;
|
||||
keepass.associated.hash = null;
|
||||
page.clearAllLogins();
|
||||
|
||||
await keepass.testAssociation(null, [ true ]);
|
||||
if (keepass.protocolV2) {
|
||||
// TODO: Only show "Connect" if the active database is not connected?
|
||||
// TODO: What if there are credentials from another database but the selected one is not connected?
|
||||
const result = await protocol.testAssociationFromDatabaseStatuses();
|
||||
keepass.updatePopup(tab);
|
||||
keepass.updateDatabaseHashToContent(result);
|
||||
return;
|
||||
}
|
||||
|
||||
keepass.updatePopup();
|
||||
// Legacy protocol
|
||||
await keepassProtocol.testAssociation(null, [ true ]);
|
||||
keepass.updatePopup(tab);
|
||||
keepass.updateDatabaseHashToContent();
|
||||
};
|
||||
|
||||
|
|
@ -959,7 +518,7 @@ keepass.compareMultipleVersions = function(versions, current, canBeEqual = true)
|
|||
return result;
|
||||
};
|
||||
|
||||
const removeDuplicateEntries = function(arr) {
|
||||
keepass.removeDuplicateEntries = function(arr) {
|
||||
const newArray = [];
|
||||
|
||||
for (const a of arr) {
|
||||
|
|
|
|||
588
keepassxc-browser/background/legacyProtocol.js
Normal file
588
keepassxc-browser/background/legacyProtocol.js
Normal file
|
|
@ -0,0 +1,588 @@
|
|||
'use strict';
|
||||
|
||||
// Legacy protocol for KeePassXC 2.7.x and older
|
||||
const keepassProtocol = {};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Commands
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
keepassProtocol.addCredentials = async function(tab, args = []) {
|
||||
const [ username, password, url, group, groupUuid ] = args;
|
||||
return keepass.updateCredentials(tab, [ null, username, password, url, group, groupUuid ]);
|
||||
};
|
||||
|
||||
keepassProtocol.associate = async function(tab) {
|
||||
if (keepass.isAssociated()) {
|
||||
return AssociatedAction.ASSOCIATED;
|
||||
}
|
||||
|
||||
try {
|
||||
await keepassProtocol.getDatabaseHash(tab);
|
||||
if (keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable) {
|
||||
return AssociatedAction.NOT_ASSOCIATED;
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
const kpAction = kpActions.ASSOCIATE;
|
||||
const key = nacl.util.encodeBase64(keepass.keyPair.publicKey);
|
||||
const nonce = protocolClient.getNonce();
|
||||
const idKeyPair = nacl.box.keyPair();
|
||||
const idKey = nacl.util.encodeBase64(idKeyPair.publicKey);
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
key: key,
|
||||
idKey: idKey
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce, false, true);
|
||||
if (response) {
|
||||
// Use public key as identification key with older KeePassXC releases
|
||||
const savedKey = compareVersion('2.3.4', keepass.currentKeePassXC) ? idKey : key;
|
||||
keepass.setCryptoKey(response.id, savedKey); // Save the new identification public key as id key for the database
|
||||
keepass.associated.value = true;
|
||||
keepass.associated.hash = response.hash || 0;
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return AssociatedAction.NEW_ASSOCIATION;
|
||||
}
|
||||
|
||||
keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED);
|
||||
return AssociatedAction.NOT_ASSOCIATED;
|
||||
} catch (err) {
|
||||
logError(`associate failed: ${err}`);
|
||||
}
|
||||
|
||||
return AssociatedAction.NOT_ASSOCIATED;
|
||||
};
|
||||
|
||||
keepassProtocol.createNewGroup = async function(tab, args = []) {
|
||||
try {
|
||||
const [ groupName ] = args;
|
||||
const taResponse = await keepassProtocol.testAssociation(tab, [ false ]);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const kpAction = kpActions.CREATE_NEW_GROUP;
|
||||
const nonce = protocolClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
groupName: groupName
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
return response;
|
||||
} else {
|
||||
logError('getDatabaseGroups rejected');
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`createNewGroup failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.generatePassword = async function(tab) {
|
||||
if (!keepass.isConnected) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const taResponse = await keepassProtocol.testAssociation(tab);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!compareVersion(keepass.requiredKeePassXC, keepass.currentKeePassXC)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let password;
|
||||
const kpAction = kpActions.GENERATE_PASSWORD;
|
||||
const nonce = protocolClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
nonce: nonce,
|
||||
clientID: keepass.clientID,
|
||||
requestID: protocolClient.getRequestId()
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
password = response.entries ?? response.password;
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
} else {
|
||||
logError('generatePassword rejected');
|
||||
}
|
||||
|
||||
return password;
|
||||
} catch (err) {
|
||||
logError(`generatePassword failed: ${err}`);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.getDatabaseGroups = async function(tab) {
|
||||
try {
|
||||
const taResponse = await keepassProtocol.testAssociation(tab, [ false ]);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let groups = [];
|
||||
const kpAction = kpActions.GET_DATABASE_GROUPS;
|
||||
const nonce = protocolClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
groups = response.groups;
|
||||
groups.defaultGroup = page.settings.defaultGroup;
|
||||
groups.defaultGroupAlwaysAsk = page.settings.defaultGroupAlwaysAsk;
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
return groups;
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`getDatabaseGroups failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.getDatabaseHash = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!keepass.serverPublicKey) {
|
||||
protocol.changePublicKeys(tab);
|
||||
}
|
||||
|
||||
const [ enableTimeout = false, triggerUnlock = false ] = args;
|
||||
const kpAction = kpActions.GET_DATABASE_HASH;
|
||||
const [ nonce, incrementedNonce ] = protocolClient.getNonces();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
connectedKeys: Object.keys(keepass.keyRing) // This will be removed in the future
|
||||
};
|
||||
|
||||
const encrypted = protocolClient.encrypt(messageData, nonce);
|
||||
if (encrypted.length <= 0) {
|
||||
keepass.handleError(tab, kpErrors.PUBLIC_KEY_NOT_FOUND);
|
||||
keepass.updateDatabaseHashToContent();
|
||||
return keepass.databaseHash;
|
||||
}
|
||||
|
||||
try {
|
||||
const request = keepassClient.buildRequest(kpAction, protocolClient.encrypt(messageData, nonce), nonce, keepass.clientID, triggerUnlock);
|
||||
const response = await keepassClient.sendNativeMessage(request, enableTimeout);
|
||||
if (response.message && response.nonce) {
|
||||
const res = protocolClient.decrypt(response.message, response.nonce);
|
||||
if (!res) {
|
||||
keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
|
||||
return '';
|
||||
}
|
||||
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
if (keepassClient.verifyDatabaseResponse(parsed, incrementedNonce) && parsed.hash) {
|
||||
const oldDatabaseHash = keepass.databaseHash;
|
||||
keepass.setcurrentKeePassXCVersion(parsed.version);
|
||||
keepass.databaseHash = parsed.hash || '';
|
||||
|
||||
if (oldDatabaseHash && oldDatabaseHash !== keepass.databaseHash) {
|
||||
keepass.associated.value = false;
|
||||
keepass.associated.hash = null;
|
||||
}
|
||||
|
||||
keepass.isDatabaseClosed = false;
|
||||
keepass.isKeePassXCAvailable = true;
|
||||
|
||||
// Update the databaseHash from legacy hash
|
||||
if (parsed.oldHash) {
|
||||
keepass.updateDatabaseHash(parsed.oldHash, parsed.hash);
|
||||
}
|
||||
|
||||
return parsed.hash;
|
||||
} else if (parsed.errorCode) {
|
||||
keepass.databaseHash = '';
|
||||
keepass.isDatabaseClosed = true;
|
||||
keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED);
|
||||
return keepass.databaseHash;
|
||||
}
|
||||
|
||||
return keepass.databaseHash;
|
||||
}
|
||||
|
||||
keepass.databaseHash = '';
|
||||
keepass.isDatabaseClosed = true;
|
||||
if ((response.message && response.message === '') || response.errorCode === kpErrors.TIMEOUT_OR_NOT_CONNECTED) {
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
keepass.isConnected = false;
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
} else {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
}
|
||||
return keepass.databaseHash;
|
||||
} catch (err) {
|
||||
logError(`getDatabaseHash failed: ${err}`);
|
||||
return keepass.databaseHash;
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.getTotp = async function(tab, args = []) {
|
||||
const [ uuid, oldTotp ] = args;
|
||||
if (!compareVersion('2.6.1', keepass.currentKeePassXC, true)) {
|
||||
return oldTotp;
|
||||
}
|
||||
|
||||
const taResponse = await keepassProtocol.testAssociation(tab, [ false ]);
|
||||
if (!taResponse || !keepass.isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.GET_TOTP;
|
||||
const nonce = protocolClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
uuid: uuid
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
return response.totp;
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (err) {
|
||||
logError(`getTotp failed: ${err}`);
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.lockDatabase = async function(tab) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.LOCK_DATABASE;
|
||||
const nonce = protocolClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
keepass.isDatabaseClosed = true;
|
||||
keepass.updateDatabase();
|
||||
|
||||
// Display error message in the popup
|
||||
keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED);
|
||||
return true;
|
||||
} else {
|
||||
keepass.isDatabaseClosed = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (err) {
|
||||
logError(`ockDatabase failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.passkeysGet = async function(tab, args = []) {
|
||||
try {
|
||||
const taResponse = await keepassProtocol.testAssociation(tab, [ false ]);
|
||||
if (!taResponse || !keepass.isConnected || args.length < 2) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
const kpAction = kpActions.PASSKEYS_GET;
|
||||
const nonce = protocolClient.getNonce();
|
||||
const publicKey = args[0];
|
||||
const origin = args[1];
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
publicKey: JSON.parse(JSON.stringify(publicKey)),
|
||||
origin: origin,
|
||||
keys: keepass.getCryptoKeys()
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`passkeysGet failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.passkeysRegister = async function(tab, args = []) {
|
||||
try {
|
||||
const taResponse = await keepassProtocol.testAssociation(tab, [ false ]);
|
||||
if (!taResponse || !keepass.isConnected || args.length < 2) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
const kpAction = kpActions.PASSKEYS_REGISTER;
|
||||
const nonce = protocolClient.getNonce();
|
||||
const [ publicKey, origin ] = args;
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
publicKey: JSON.parse(JSON.stringify(publicKey)),
|
||||
origin: origin,
|
||||
groupName: page?.settings?.defaultPasskeyGroup,
|
||||
keys: keepass.getCryptoKeys()
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`passkeysRegister failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.requestAutotype = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.REQUEST_AUTOTYPE;
|
||||
const nonce = protocolClient.getNonce();
|
||||
const search = await page.getBaseDomainFromUrl(args[0]);
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
search: search
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
return response;
|
||||
} catch (err) {
|
||||
logError(`requestAutotype failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.retrieveCredentials = async function(tab, args = []) {
|
||||
try {
|
||||
const [ url, submiturl, triggerUnlock = false, httpAuth = false ] = args;
|
||||
const taResponse = await keepassProtocol.testAssociation(tab, [ false, triggerUnlock ]);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let entries = [];
|
||||
const kpAction = kpActions.GET_LOGINS;
|
||||
const nonce = protocolClient.getNonce();
|
||||
const [ dbid ] = keepass.getCryptoKey();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
id: dbid,
|
||||
url: url,
|
||||
keys: keepass.getCryptoKeys()
|
||||
};
|
||||
|
||||
if (submiturl) {
|
||||
messageData.submitUrl = submiturl;
|
||||
}
|
||||
|
||||
if (httpAuth) {
|
||||
messageData.httpAuth = 'true';
|
||||
}
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
entries = keepass.removeDuplicateEntries(response.entries);
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
|
||||
if (entries.length === 0) {
|
||||
// Questionmark-icon is not triggered, so we have to trigger for the normal symbol
|
||||
browserAction.showDefault(tab);
|
||||
}
|
||||
|
||||
logDebug(`Found ${entries.length} entries for url ${url}`);
|
||||
return entries;
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`retrieveCredentials failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.testAssociation = async function(tab, args = []) {
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
try {
|
||||
const [ enableTimeout = false, triggerUnlock = false ] = args;
|
||||
const dbHash = await keepassProtocol.getDatabaseHash(tab, [ enableTimeout, triggerUnlock ]);
|
||||
if (!dbHash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keepass.isDatabaseClosed || !keepass.isKeePassXCAvailable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!keepass.serverPublicKey) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
keepass.handleError(tab, kpErrors.PUBLIC_KEY_NOT_FOUND);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.TEST_ASSOCIATE;
|
||||
const nonce = protocolClient.getNonce();
|
||||
const [ dbid, dbkey ] = keepass.getCryptoKey();
|
||||
|
||||
if (dbkey === null || dbid === null) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
keepass.handleError(tab, kpErrors.NO_SAVED_DATABASES_FOUND);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
id: dbid,
|
||||
key: dbkey
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce, enableTimeout);
|
||||
if (!response) {
|
||||
const hash = response.hash || 0;
|
||||
keepass.deleteKey(hash);
|
||||
keepass.isEncryptionKeyUnrecognized = true;
|
||||
keepass.handleError(tab, kpErrors.ENCRYPTION_KEY_UNRECOGNIZED);
|
||||
keepass.associated.value = false;
|
||||
keepass.associated.hash = null;
|
||||
} else if (!keepass.isAssociated()) {
|
||||
keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED);
|
||||
} else {
|
||||
keepass.isEncryptionKeyUnrecognized = false;
|
||||
keepass.clearErrorMessage(tab);
|
||||
}
|
||||
|
||||
return keepass.isAssociated();
|
||||
} catch (err) {
|
||||
logError(`testAssociation failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
keepassProtocol.updateCredentials = async function(tab, args = []) {
|
||||
try {
|
||||
const [ entryId, username, password, url, group, groupUuid ] = args;
|
||||
const taResponse = await keepassProtocol.testAssociation(tab);
|
||||
if (!taResponse) {
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
}
|
||||
|
||||
const kpAction = kpActions.SET_LOGIN;
|
||||
const [ dbid ] = keepass.getCryptoKey();
|
||||
const nonce = protocolClient.getNonce();
|
||||
|
||||
const messageData = {
|
||||
action: kpAction,
|
||||
id: dbid,
|
||||
login: username,
|
||||
password: password,
|
||||
url: url,
|
||||
submitUrl: url
|
||||
};
|
||||
|
||||
if (entryId) {
|
||||
messageData.uuid = entryId;
|
||||
}
|
||||
|
||||
if (!entryId && page.settings.downloadFaviconAfterSave) {
|
||||
messageData.downloadFavicon = 'true';
|
||||
}
|
||||
|
||||
if (group && groupUuid) {
|
||||
messageData.group = group;
|
||||
messageData.groupUuid = groupUuid;
|
||||
}
|
||||
|
||||
const response = await keepassClient.sendMessage(kpAction, tab, messageData, nonce);
|
||||
if (response) {
|
||||
// KeePassXC versions lower than 2.5.0 will have an empty parsed.error
|
||||
let successMessage = response.error;
|
||||
if (response.error === 'success' || response.error === '') {
|
||||
successMessage = entryId ? AddCredentials.UPDATED : AddCredentials.CREATED;
|
||||
}
|
||||
|
||||
return successMessage;
|
||||
} else {
|
||||
return AddCredentials.ERROR;
|
||||
}
|
||||
} catch (err) {
|
||||
logError(`updateCredentials failed: ${err}`);
|
||||
return AddCredentials.ERROR;
|
||||
}
|
||||
};
|
||||
|
||||
232
keepassxc-browser/background/legacyProtocolClient.js
Normal file
232
keepassxc-browser/background/legacyProtocolClient.js
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
'use strict';
|
||||
|
||||
const messageBuffer = {
|
||||
buffer: [],
|
||||
|
||||
addMessage(message) {
|
||||
this.buffer.push(message);
|
||||
},
|
||||
|
||||
// Returns corresponding message from the response. If the response is an error,
|
||||
// return the first matching action from the buffer.
|
||||
getMessage(response) {
|
||||
const isError = Boolean(!response.nonce && response.error && response.errorCode);
|
||||
return this.buffer.find(message => {
|
||||
if (protocolClient.incrementedNonce(message.request.nonce) === response.nonce
|
||||
|| (isError && message.request?.action === response?.action)) {
|
||||
// Cancel timeout
|
||||
if (message.enableTimeout) {
|
||||
message.cancelTimeout();
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
removeMessage(message) {
|
||||
const index = this.buffer.indexOf(message);
|
||||
if (index >= 0 && index < this.buffer.length) {
|
||||
this.buffer.splice(index, 1);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Basic class for a message to be sent. The Promise inside the class will be resolved when
|
||||
// the response to the message is received.
|
||||
class Message {
|
||||
constructor(request, enableTimeout, timeoutValue) {
|
||||
this.enableTimeout = enableTimeout;
|
||||
this.request = request;
|
||||
this.timeout = undefined;
|
||||
|
||||
this.promise = new Promise((resolve, reject) => {
|
||||
this.reject = reject;
|
||||
this.resolve = resolve;
|
||||
|
||||
const messageTimeout = timeoutValue || protocolClient.messageTimeout;
|
||||
|
||||
// Handle timeout
|
||||
if (this.enableTimeout) {
|
||||
this.timeout = setTimeout(() => {
|
||||
const errorMessage = {
|
||||
action: request.action,
|
||||
error: kpErrors.getError(kpErrors.TIMEOUT_OR_NOT_CONNECTED),
|
||||
errorCode: kpErrors.TIMEOUT_OR_NOT_CONNECTED
|
||||
};
|
||||
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
resolve(errorMessage);
|
||||
}, messageTimeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cancelTimeout() {
|
||||
this.enableTimeout = false;
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy client for KeePassXC 2.7.x and older
|
||||
const keepassClient = {};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Messaging
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
keepassClient.sendNativeMessage = async function(request, enableTimeout = false, timeoutValue) {
|
||||
if (!protocolClient.nativePort) {
|
||||
logError('No native messaging port defined.');
|
||||
return;
|
||||
}
|
||||
|
||||
const message = new Message(request, enableTimeout, timeoutValue);
|
||||
await navigator.locks.request('messageBuffer', async (lock) => {
|
||||
messageBuffer.addMessage(message);
|
||||
});
|
||||
|
||||
protocolClient.nativePort.postMessage(request);
|
||||
|
||||
const response = await message.promise;
|
||||
|
||||
// Remove a timeouted message
|
||||
if (response.error && response?.errorCode === kpErrors.TIMEOUT_OR_NOT_CONNECTED) {
|
||||
messageBuffer.removeMessage(message);
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
keepassClient.handleNativeMessage = async function(response) {
|
||||
// Parse through the message buffer to find the corresponding Promise.
|
||||
await navigator.locks.request('messageBuffer', async (lock) => {
|
||||
const message = messageBuffer.getMessage(response);
|
||||
if (message) {
|
||||
message.resolve(response);
|
||||
messageBuffer.removeMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
debugLogMessage('Corresponding request not found in the message buffer for response: ', response);
|
||||
});
|
||||
};
|
||||
|
||||
keepassClient.handleResponse = function(response, incrementedNonce, tab) {
|
||||
if (response.message && response.nonce) {
|
||||
const res = protocolClient.decrypt(response.message, response.nonce);
|
||||
if (!res) {
|
||||
keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
|
||||
if (keepassClient.verifyResponse(parsed, incrementedNonce)) {
|
||||
return parsed;
|
||||
}
|
||||
} else if (response.error && response.errorCode) {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
keepassClient.buildRequest = function(action, encrypted, nonce, clientID, triggerUnlock = false) {
|
||||
const request = {
|
||||
action: action,
|
||||
message: encrypted,
|
||||
nonce: nonce,
|
||||
clientID: clientID
|
||||
};
|
||||
|
||||
if (triggerUnlock) {
|
||||
request.triggerUnlock = 'true';
|
||||
}
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
keepassClient.sendMessage = async function(kpAction, tab, messageData, nonce, enableTimeout = false, triggerUnlock = false) {
|
||||
const request = keepassClient.buildRequest(kpAction, protocolClient.encrypt(messageData, nonce), nonce, keepass.clientID, triggerUnlock);
|
||||
if (messageData.requestID) {
|
||||
request['requestID'] = messageData.requestID;
|
||||
}
|
||||
|
||||
const response = await keepassClient.sendNativeMessage(request, enableTimeout);
|
||||
const incrementedNonce = protocolClient.incrementedNonce(nonce);
|
||||
|
||||
return keepassClient.handleResponse(response, incrementedNonce, tab);
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Utils
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
keepassClient.verifyKeyResponse = function(response, key, nonce) {
|
||||
if (!response.success || !response.publicKey) {
|
||||
keepass.associated.hash = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!protocolClient.checkNonceLength(response.nonce)) {
|
||||
logError('Invalid nonce length.');
|
||||
return false;
|
||||
}
|
||||
|
||||
const reply = (response.nonce === nonce);
|
||||
if (response.publicKey && reply) {
|
||||
keepass.serverPublicKey = nacl.util.decodeBase64(response.publicKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
return reply;
|
||||
};
|
||||
|
||||
keepassClient.verifyResponse = function(response, nonce, id) {
|
||||
keepass.associated.value = response.success;
|
||||
if (response.success !== 'true') {
|
||||
keepass.associated.hash = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
keepass.associated.hash = keepass.databaseHash;
|
||||
|
||||
if (!protocolClient.checkNonceLength(response.nonce)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
keepass.associated.value = (response.nonce === nonce);
|
||||
if (keepass.associated.value === false) {
|
||||
logError('Nonce compare failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (id) {
|
||||
keepass.associated.value = (keepass.associated.value && id === response.id);
|
||||
}
|
||||
|
||||
keepass.associated.hash = (keepass.associated.value) ? keepass.databaseHash : null;
|
||||
return keepass.isAssociated();
|
||||
};
|
||||
|
||||
keepassClient.verifyDatabaseResponse = function(response, nonce) {
|
||||
if (response.success !== 'true') {
|
||||
keepass.associated.hash = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!protocolClient.checkNonceLength(response.nonce)) {
|
||||
logError('Invalid nonce length.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.nonce !== nonce) {
|
||||
logError('Nonce compare failed.');
|
||||
return false;
|
||||
}
|
||||
|
||||
keepass.associated.hash = response.hash;
|
||||
return response.hash !== '' && response.success === 'true';
|
||||
};
|
||||
|
|
@ -43,6 +43,7 @@ const defaultSettings = {
|
|||
const AUTO_SUBMIT_TIMEOUT = 5000;
|
||||
|
||||
const page = {};
|
||||
page.autoLockRequested = false;
|
||||
page.autoSubmitPerformed = false;
|
||||
page.attributeMenuItems = [];
|
||||
page.blockedTabs = [];
|
||||
|
|
@ -214,6 +215,14 @@ page.clearSubmittedCredentials = async function() {
|
|||
page.submittedCredentials = {};
|
||||
};
|
||||
|
||||
page.clearAutoLockRequested = async function() {
|
||||
page.autoLockRequested = false;
|
||||
};
|
||||
|
||||
page.getAutoLockRequested = async function() {
|
||||
return page.autoLockRequested;
|
||||
};
|
||||
|
||||
page.createTabEntry = async function(tabId) {
|
||||
page.tabs[tabId] = {
|
||||
allowIframes: false,
|
||||
|
|
@ -252,8 +261,14 @@ page.retrieveCredentials = async function(tab, args = []) {
|
|||
page.currentRequest.tabId = tab.id;
|
||||
}
|
||||
|
||||
const credentials = await keepass.retrieveCredentials(tab, args);
|
||||
// TODO: Make keepass.js to handle protocol/protocolClient and legacyProtocol/legacyProcotolClient
|
||||
const credentials = await keepass.getCredentials(tab, args);
|
||||
page.tabs[tab.id].credentials = credentials;
|
||||
|
||||
if (credentials.autoLockRequested) {
|
||||
page.autoLockRequested = true;
|
||||
}
|
||||
|
||||
return credentials;
|
||||
};
|
||||
|
||||
|
|
|
|||
546
keepassxc-browser/background/protocol.js
Normal file
546
keepassxc-browser/background/protocol.js
Normal file
|
|
@ -0,0 +1,546 @@
|
|||
'use strict';
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Protocol V2
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
const protocol = {};
|
||||
|
||||
protocol.associate = async function(tab, args = []) {
|
||||
if (!keepass.isKeePassXCAvailable) {
|
||||
return AssociatedAction.NOT_ASSOCIATED;
|
||||
}
|
||||
|
||||
try {
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
const publicKey = protocolClient.getPublicConnectionKey();
|
||||
const idKey = protocolClient.generateIdKey();
|
||||
|
||||
const messageData = {
|
||||
action: kpActions.ASSOCIATE,
|
||||
idKey: idKey,
|
||||
publicKey: publicKey
|
||||
};
|
||||
|
||||
const response = await protocolClient.sendMessage(tab, messageData, false, true);
|
||||
if (response && response.id && response.hash) {
|
||||
keepass.setCryptoKey(response.id, idKey);
|
||||
|
||||
browserAction.show(tab);
|
||||
return AssociatedAction.NEW_ASSOCIATION;
|
||||
}
|
||||
|
||||
keepass.handleError(tab, kpErrors.ASSOCIATION_FAILED);
|
||||
return AssociatedAction.NOT_ASSOCIATED;
|
||||
} catch (err) {
|
||||
logError(`associate failed: ${err}`);
|
||||
}
|
||||
|
||||
return AssociatedAction.NOT_ASSOCIATED;
|
||||
};
|
||||
|
||||
// Unencrypted
|
||||
protocol.changePublicKeys = async function(tab, enableTimeout = false, connectionTimeout) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
const kpAction = kpActions.CHANGE_PUBLIC_KEYS;
|
||||
const key = protocolClient.getPublicConnectionKey();
|
||||
const [ nonce, incrementedNonce ] = protocolClient.getNonces();
|
||||
keepass.clientID = protocolClient.generateClientId();
|
||||
|
||||
const request = {
|
||||
action: kpAction,
|
||||
clientID: keepass.clientID,
|
||||
nonce: nonce,
|
||||
publicKey: key,
|
||||
requestID: protocolClient.getRequestId()
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await keepassClient.sendNativeMessage(request, enableTimeout, connectionTimeout);
|
||||
if (response.error && response.errorCode) {
|
||||
keepass.handleError(tab, kpErrors.KEY_CHANGE_FAILED);
|
||||
return false;
|
||||
}
|
||||
|
||||
keepass.setcurrentKeePassXCVersion(response.version);
|
||||
keepass.protocolV2 = response?.protocolVersion === 2;
|
||||
|
||||
const verified = keepass.protocolV2
|
||||
? protocolClient.verifyNonce(response, incrementedNonce)
|
||||
: keepassClient.verifyKeyResponse(response, key, incrementedNonce);
|
||||
if (!response?.publicKey || !verified) {
|
||||
if (tab && page.tabs[tab.id]) {
|
||||
keepass.handleError(tab, kpErrors.KEY_CHANGE_FAILED);
|
||||
}
|
||||
|
||||
keepass.updateDatabaseHashToContent();
|
||||
return false;
|
||||
}
|
||||
|
||||
keepass.serverPublicKey = nacl.util.decodeBase64(response.publicKey);
|
||||
keepass.isKeePassXCAvailable = true;
|
||||
console.log(`${EXTENSION_NAME}: Server public key: ${nacl.util.encodeBase64(keepass.serverPublicKey)}`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
logError(`changePublicKeys failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
protocol.createCredentials = async function(tab, args = []) {
|
||||
const [ username, password, url, group, groupUuid ] = args;
|
||||
return protocol.updateCredentials(tab, [ null, username, password, url, group, groupUuid ]);
|
||||
};
|
||||
|
||||
protocol.createNewGroup = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
const [ groupName ] = args;
|
||||
|
||||
const messageData = {
|
||||
action: kpActions.CREATE_NEW_GROUP,
|
||||
groupName: groupName,
|
||||
keys: protocol.getCurrentKey()
|
||||
};
|
||||
|
||||
try {
|
||||
// TODO: Handle errors
|
||||
const response = await protocolClient.sendMessage(tab, messageData);
|
||||
if (response) {
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
return response;
|
||||
} else {
|
||||
logError('getDatabaseGroups rejected');
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`createNewGroup failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
protocol.generatePassword = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!compareVersion(keepass.requiredKeePassXC, keepass.currentKeePassXC)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const messageData = {
|
||||
action: kpActions.GENERATE_PASSWORD,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await protocolClient.sendMessage(tab, messageData);
|
||||
if (response) {
|
||||
if (response.error && response.errorCode) {
|
||||
keepass.handleError(tab, response.errorCode);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const password = response.entries ?? response.password;
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
return password;
|
||||
} else {
|
||||
logError('generatePassword rejected');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
} catch (err) {
|
||||
logError(`generatePassword failed: ${err}`);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
protocol.getCredentials = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
const [ url, submiturl, triggerUnlock = false, httpAuth = false ] = args;
|
||||
let entries = [];
|
||||
|
||||
const messageData = {
|
||||
action: kpActions.GET_CREDENTIALS,
|
||||
keys: protocol.getKeys(),
|
||||
url: url
|
||||
};
|
||||
|
||||
if (submiturl) {
|
||||
messageData.submitUrl = submiturl;
|
||||
}
|
||||
|
||||
if (httpAuth) {
|
||||
messageData.httpAuth = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await protocolClient.sendMessage(tab, messageData, false, triggerUnlock);
|
||||
if (response) {
|
||||
if (response.error && response.errorCode) {
|
||||
keepass.handleError(tab, response.errorCode);
|
||||
return [];
|
||||
}
|
||||
|
||||
entries = keepass.removeDuplicateEntries(response.entries);
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
|
||||
if (entries.length === 0) {
|
||||
// Questionmark-icon is not triggered, so we have to trigger for the normal symbol
|
||||
browserAction.showDefault(tab);
|
||||
}
|
||||
|
||||
logDebug(`Found ${entries.length} entries for url ${url}`);
|
||||
return entries;
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`getCredentials failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
protocol.getDatabaseGroups = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
keepass.clearErrorMessage(tab);
|
||||
|
||||
let groups = [];
|
||||
|
||||
const messageData = {
|
||||
action: kpActions.GET_DATABASE_GROUPS,
|
||||
keys: protocol.getCurrentKey()
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await protocolClient.sendMessage(tab, messageData);
|
||||
if (response) {
|
||||
if (response.error && response.errorCode) {
|
||||
keepass.handleError(tab, response.errorCode);
|
||||
return [];
|
||||
}
|
||||
|
||||
groups = response.groups;
|
||||
groups.defaultGroup = page.settings.defaultGroup;
|
||||
groups.defaultGroupAlwaysAsk = page.settings.defaultGroupAlwaysAsk;
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
return groups;
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
} catch (err) {
|
||||
logError(`getDatabaseGroups failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
protocol.getDatabaseStatuses = async function(tab, args = []) {
|
||||
if (!keepass.isKeePassXCAvailable) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!keepass.serverPublicKey) {
|
||||
await protocol.changePublicKeys(tab);
|
||||
}
|
||||
|
||||
const [ enableTimeout = false, triggerUnlock = false ] = args;
|
||||
|
||||
const messageData = {
|
||||
action: kpActions.GET_DATABASE_STATUSES,
|
||||
keys: protocol.getKeys()
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await protocolClient.sendMessage(tab, messageData, enableTimeout, triggerUnlock);
|
||||
if (response) {
|
||||
keepass.databaseHash = response?.hash;
|
||||
|
||||
// Return this error only if all databases are closed
|
||||
if (response?.statuses.every(s => s.locked)) {
|
||||
keepass.databaseHash = '';
|
||||
keepass.isDatabaseClosed = true;
|
||||
keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
keepass.handleError(tab, kpErrors.ACTION_TIMEOUT);
|
||||
} catch (err) {
|
||||
logError(`getDatabaseStatuses failed: ${err}`);
|
||||
}
|
||||
};
|
||||
|
||||
protocol.getTotp = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const messageData = {
|
||||
action: kpActions.GET_TOTP,
|
||||
keys: protocol.getKeys(),
|
||||
uuids: args
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await protocolClient.sendMessage(tab, messageData);
|
||||
if (response) {
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
return response.totpList;
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (err) {
|
||||
logError(`getTotp failed: ${err}`);
|
||||
}
|
||||
};
|
||||
|
||||
protocol.lockDatabase = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
const [ lockSingle ] = args;
|
||||
|
||||
const messageData = {
|
||||
action: kpActions.LOCK_DATABASE,
|
||||
lockSingle: lockSingle
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await protocolClient.sendMessage(tab, messageData);
|
||||
if (response) {
|
||||
keepass.updateDatabase(tab);
|
||||
return true;
|
||||
}
|
||||
|
||||
keepass.isDatabaseClosed = true;
|
||||
return false;
|
||||
} catch (err) {
|
||||
logError(`lockDatabase failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
protocol.passkeysRegister = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const [ publicKey, origin ] = args;
|
||||
|
||||
const messageData = {
|
||||
action: kpActions.PASSKEYS_REGISTER,
|
||||
publicKey: JSON.parse(JSON.stringify(publicKey)),
|
||||
origin: origin,
|
||||
groupName: page?.settings?.defaultPasskeyGroup,
|
||||
keys: keepass.getCryptoKeys()
|
||||
};
|
||||
|
||||
const response = await protocolClient.sendMessage(tab, messageData);
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
};
|
||||
|
||||
protocol.passkeysGet = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const [ publicKey, origin ] = args;
|
||||
|
||||
const messageData = {
|
||||
action: kpActions.PASSKEYS_GET,
|
||||
publicKey: JSON.parse(JSON.stringify(publicKey)),
|
||||
origin: origin,
|
||||
keys: keepass.getCryptoKeys()
|
||||
};
|
||||
|
||||
const response = await keepassClient.sendMessage(tab, messageData);
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
browserAction.showDefault(tab);
|
||||
return [];
|
||||
};
|
||||
|
||||
|
||||
protocol.requestAutotype = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
const messageData = {
|
||||
action: kpActions.REQUEST_AUTOTYPE,
|
||||
search: getTopLevelDomainFromUrl(args[0])
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await protocolClient.sendMessage(tab, messageData);
|
||||
return response?.result;
|
||||
} catch (err) {
|
||||
logError(`requestAutotype failed: ${err}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
protocol.testAssociationFromDatabaseStatuses = async function(tab, args = []) {
|
||||
const databaseStatuses = await protocol.getDatabaseStatuses(tab, args);
|
||||
console.log(databaseStatuses);
|
||||
if (!databaseStatuses) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const result = {
|
||||
areAllLocked: true,
|
||||
associationNeeded: false,
|
||||
databaseHash: undefined,
|
||||
isAnyAssociated: false,
|
||||
isCurrentLocked: true
|
||||
};
|
||||
|
||||
if (!databaseStatuses || databaseStatuses.statuses.length === 0) {
|
||||
keepass.handleError(tab, kpErrors.DATABASE_NOT_OPENED);
|
||||
return result;
|
||||
}
|
||||
|
||||
const currentDatabaseStatus = databaseStatuses.statuses.filter(s => s.hash === databaseStatuses.hash);
|
||||
const isCurrentAssociated = currentDatabaseStatus[0]?.associated;
|
||||
const isCurrentLocked = currentDatabaseStatus[0]?.locked;
|
||||
|
||||
const isAnyAssociated = databaseStatuses.statuses.some(s => s.associated);
|
||||
const areAllLocked = databaseStatuses.statuses.every(s => s.locked);
|
||||
|
||||
// TODO: Add a warning notification if two databases with identical hashes are regognized.
|
||||
// To where? DOM? KeePassXC? Popup? Maybe this feature should be in KeePassXC instead when making the request and not here.
|
||||
if (currentDatabaseStatus.length > 1) {
|
||||
console.log('Identical databases found.');
|
||||
}
|
||||
|
||||
// TODO: If the current one is not associated, activate the Connect button in the popup?
|
||||
// But only if the current database is not locked..
|
||||
if (!isCurrentAssociated && !isCurrentLocked) {
|
||||
console.log('Current one is not associated');
|
||||
}
|
||||
|
||||
// Current association status
|
||||
keepass.associated.hash = currentDatabaseStatus[0]?.hash;
|
||||
keepass.associated.value = isCurrentAssociated;
|
||||
|
||||
// This should be true only if all databases are locked
|
||||
keepass.isDatabaseClosed = areAllLocked;
|
||||
|
||||
result.areAllLocked = areAllLocked;
|
||||
result.associationNeeded = !isCurrentAssociated && !isCurrentLocked;
|
||||
result.databaseHash = databaseStatuses.hash;
|
||||
result.isAnyAssociated = isAnyAssociated;
|
||||
result.isCurrentLocked = isCurrentLocked;
|
||||
|
||||
keepass.databaseStatuses = databaseStatuses;
|
||||
keepass.databaseAssociationStatuses = result;
|
||||
return result;
|
||||
};
|
||||
|
||||
protocol.updateCredentials = async function(tab, args = []) {
|
||||
if (!keepass.isConnected) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const [ entryId, username, password, url, group, groupUuid ] = args;
|
||||
|
||||
const messageData = {
|
||||
action: kpActions.CREATE_CREDENTIALS,
|
||||
keys: protocol.getCurrentKey(),
|
||||
login: username,
|
||||
password: password,
|
||||
submitUrl: url,
|
||||
url: url,
|
||||
};
|
||||
|
||||
if (entryId) {
|
||||
messageData.uuid = entryId;
|
||||
}
|
||||
|
||||
if (!entryId && page.settings.downloadFaviconAfterSave) {
|
||||
messageData.downloadFavicon = true;
|
||||
}
|
||||
|
||||
if (group && groupUuid) {
|
||||
messageData.group = group;
|
||||
messageData.groupUuid = groupUuid;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await protocolClient.sendMessage(tab, messageData);
|
||||
if (response) {
|
||||
keepass.updateLastUsed(keepass.databaseHash);
|
||||
|
||||
if (response?.result === true) {
|
||||
return entryId ? AddCredentials.UPDATED : AddCredentials.CREATED;
|
||||
}
|
||||
|
||||
return AddCredentials.CANCELED;
|
||||
} else {
|
||||
return AddCredentials.ERROR;
|
||||
}
|
||||
} catch (err) {
|
||||
logError(`updateCredentials failed: ${err}`);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Utils
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
protocol.getKeys = function() {
|
||||
const keys = [];
|
||||
|
||||
for (const keyHash in keepass.keyRing) {
|
||||
keys.push({
|
||||
id: keepass.keyRing[keyHash].id,
|
||||
key: keepass.keyRing[keyHash].key
|
||||
});
|
||||
}
|
||||
|
||||
return keys;
|
||||
};
|
||||
|
||||
// Gets the key only from the current active database
|
||||
protocol.getCurrentKey = function() {
|
||||
const [ id, key ] = keepass.getCryptoKey();
|
||||
return [
|
||||
{
|
||||
id: id,
|
||||
key: key
|
||||
}
|
||||
];
|
||||
};
|
||||
316
keepassxc-browser/background/protocolClient.js
Normal file
316
keepassxc-browser/background/protocolClient.js
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
'use strict';
|
||||
|
||||
const protocolBuffer = {
|
||||
buffer: [],
|
||||
|
||||
addMessage(message) {
|
||||
this.buffer.push(message);
|
||||
},
|
||||
|
||||
// Returns corresponding message from the response. If the response is an error,
|
||||
// return the first matching action from the buffer.
|
||||
getMessage(response) {
|
||||
const isError = Boolean(!response.nonce && response.error && response.errorCode);
|
||||
return this.buffer.find(message => {
|
||||
if (message.request.requestID === response.requestID
|
||||
|| (isError && message.request?.action === response?.action)) {
|
||||
// Cancel timeout
|
||||
if (message.enableTimeout) {
|
||||
message.cancelTimeout();
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
removeMessage(message) {
|
||||
const index = this.buffer.indexOf(message);
|
||||
if (index >= 0 && index < this.buffer.length) {
|
||||
this.buffer.splice(index, 1);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// Basic class for a message to be sent. The Promise inside the class will be resolved when
|
||||
// the response to the message is received.
|
||||
class ProtocolMessage {
|
||||
constructor(request, enableTimeout, timeoutValue) {
|
||||
this.enableTimeout = enableTimeout;
|
||||
this.request = request;
|
||||
this.timeout = undefined;
|
||||
|
||||
this.promise = new Promise((resolve, reject) => {
|
||||
this.reject = reject;
|
||||
this.resolve = resolve;
|
||||
|
||||
const messageTimeout = timeoutValue || keepassClient.messageTimeout;
|
||||
|
||||
// Handle timeout
|
||||
if (this.enableTimeout) {
|
||||
this.timeout = setTimeout(() => {
|
||||
// The error is action timeout if action is not change-public-keys
|
||||
let error = kpErrors.ACTION_TIMEOUT;
|
||||
if (request.action === kpActions.CHANGE_PUBLIC_KEYS) {
|
||||
error = kpErrors.TIMEOUT_OR_NOT_CONNECTED;
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
}
|
||||
|
||||
resolve({
|
||||
action: request.action,
|
||||
error: kpErrors.getError(error),
|
||||
errorCode: error
|
||||
});
|
||||
}, messageTimeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cancelTimeout() {
|
||||
this.enableTimeout = false;
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Protocol V2
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
const protocolClient = {};
|
||||
protocolClient.keySize = 24;
|
||||
protocolClient.messageTimeout = 500; // Milliseconds
|
||||
protocolClient.nativeHostName = 'org.keepassxc.keepassxc_browser';
|
||||
protocolClient.nativePort = null;
|
||||
|
||||
protocolClient.sendNativeMessage = async function(request, enableTimeout = false, timeoutValue = protocolClient.messageTimeout) {
|
||||
if (!protocolClient.nativePort) {
|
||||
logError('No native messaging port defined.');
|
||||
return;
|
||||
}
|
||||
|
||||
const message = new ProtocolMessage(request, enableTimeout, timeoutValue);
|
||||
await navigator.locks.request('messageBuffer', async (lock) => {
|
||||
protocolBuffer.addMessage(message);
|
||||
});
|
||||
|
||||
protocolClient.nativePort.postMessage(request);
|
||||
|
||||
const response = await message.promise;
|
||||
|
||||
// Remove a timeouted message
|
||||
if (response.error && response?.errorCode === kpErrors.TIMEOUT_OR_NOT_CONNECTED) {
|
||||
protocolBuffer.matchAndRemove(message);
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
protocolClient.sendMessage = async function(tab, messageData, enableTimeout = false, triggerUnlock = false) {
|
||||
const nonce = protocolClient.getNonce();
|
||||
const encryptedMessage = protocolClient.encrypt(messageData, nonce);
|
||||
const request = protocolClient.buildRequest(encryptedMessage, nonce, keepass.clientID, triggerUnlock);
|
||||
const response = await protocolClient.sendNativeMessage(request, enableTimeout);
|
||||
const incrementedNonce = protocolClient.incrementedNonce(nonce);
|
||||
|
||||
return protocolClient.handleResponse(response, incrementedNonce, request.requestID, tab);
|
||||
};
|
||||
|
||||
protocolClient.buildRequest = function(encryptedMessage, nonce, clientID, triggerUnlock = false) {
|
||||
const request = {
|
||||
message: encryptedMessage,
|
||||
nonce: nonce,
|
||||
clientID: clientID,
|
||||
requestID: protocolClient.getRequestId()
|
||||
};
|
||||
|
||||
if (triggerUnlock) {
|
||||
request.triggerUnlock = true;
|
||||
}
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
protocolClient.handleNativeMessage = async function(response) {
|
||||
// Parse through the message buffer to find the corresponding Promise.
|
||||
await navigator.locks.request('messageBuffer', async (lock) => {
|
||||
const message = protocolBuffer.getMessage(response);
|
||||
if (message) {
|
||||
message.resolve(response);
|
||||
protocolBuffer.removeMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
debugLogMessage('Corresponding request not found in the message buffer for response: ', response);
|
||||
});
|
||||
};
|
||||
|
||||
// Verifies nonces, decrypts and parses the response
|
||||
protocolClient.handleResponse = function(response, incrementedNonce, requestID, tab) {
|
||||
if (response.message && protocolClient.verifyNonce(response, incrementedNonce)) {
|
||||
const res = protocolClient.decrypt(response.message, response.nonce);
|
||||
if (!res) {
|
||||
keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
|
||||
protocolBuffer.matchAndRemove({ requestID: requestID });
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const message = nacl.util.encodeUTF8(res);
|
||||
const parsed = JSON.parse(message);
|
||||
return parsed;
|
||||
} else if (response.error && response.errorCode) {
|
||||
keepass.handleError(tab, response.errorCode, response.error);
|
||||
protocolBuffer.matchAndRemove({ requestID: requestID });
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
protocolClient.verifyNonce = function(response, nonce) {
|
||||
if (!response.nonce) {
|
||||
logError('No nonce in response');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!protocolClient.checkNonceLength(response.nonce)) {
|
||||
logError('Incorrect nonce length');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.nonce !== nonce) {
|
||||
logError('Nonce compare failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Utils
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
protocolClient.getNonce = function() {
|
||||
return nacl.util.encodeBase64(nacl.randomBytes(protocolClient.keySize));
|
||||
};
|
||||
|
||||
// Creates a random 8 character string for Request ID
|
||||
protocolClient.getRequestId = function() {
|
||||
return Math.random().toString(16).substring(2, 10);
|
||||
};
|
||||
|
||||
protocolClient.incrementedNonce = function(nonce) {
|
||||
const oldNonce = nacl.util.decodeBase64(nonce);
|
||||
const newNonce = oldNonce.slice(0);
|
||||
|
||||
// from libsodium/utils.c
|
||||
let i = 0;
|
||||
let c = 1;
|
||||
for (; i < newNonce.length; ++i) {
|
||||
c += newNonce[i];
|
||||
newNonce[i] = c;
|
||||
c >>= 8;
|
||||
}
|
||||
|
||||
return nacl.util.encodeBase64(newNonce);
|
||||
};
|
||||
|
||||
protocolClient.getNonces = function() {
|
||||
const nonce = protocolClient.getNonce();
|
||||
const incrementedNonce = protocolClient.incrementedNonce(nonce);
|
||||
return [ nonce, incrementedNonce ];
|
||||
};
|
||||
|
||||
protocolClient.checkNonceLength = function(nonce) {
|
||||
return nacl.util.decodeBase64(nonce).length === nacl.secretbox.nonceLength;
|
||||
};
|
||||
|
||||
protocolClient.generateNewKeyPair = function() {
|
||||
keepass.keyPair = nacl.box.keyPair();
|
||||
};
|
||||
|
||||
protocolClient.getPublicConnectionKey = function() {
|
||||
return nacl.util.encodeBase64(keepass.keyPair.publicKey);
|
||||
};
|
||||
|
||||
protocolClient.generateIdKey = function() {
|
||||
const idKeyPair = nacl.box.keyPair();
|
||||
return nacl.util.encodeBase64(idKeyPair.publicKey);
|
||||
};
|
||||
|
||||
protocolClient.generateClientId = function() {
|
||||
return nacl.util.encodeBase64(nacl.randomBytes(protocolClient.keySize));
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Encrypt/Decrypt
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
protocolClient.encrypt = function(input, nonce) {
|
||||
const messageData = nacl.util.decodeUTF8(JSON.stringify(input));
|
||||
const messageNonce = nacl.util.decodeBase64(nonce);
|
||||
|
||||
if (keepass.serverPublicKey) {
|
||||
const message = nacl.box(messageData, messageNonce, keepass.serverPublicKey, keepass.keyPair.secretKey);
|
||||
if (message) {
|
||||
return nacl.util.encodeBase64(message);
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
protocolClient.decrypt = function(input, nonce) {
|
||||
const m = nacl.util.decodeBase64(input);
|
||||
const n = nacl.util.decodeBase64(nonce);
|
||||
const res = nacl.box.open(m, n, keepass.serverPublicKey, keepass.keyPair.secretKey);
|
||||
return res;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Native Messaging related
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
protocolClient.connectToNative = function() {
|
||||
if (protocolClient.nativePort) {
|
||||
protocolClient.nativePort.disconnect();
|
||||
}
|
||||
protocolClient.nativeConnect();
|
||||
};
|
||||
|
||||
protocolClient.nativeConnect = function() {
|
||||
console.log(`${EXTENSION_NAME}: Connecting to native messaging host ${protocolClient.nativeHostName}`);
|
||||
protocolClient.nativePort = browser.runtime.connectNative(protocolClient.nativeHostName);
|
||||
protocolClient.nativePort.onMessage.addListener(protocolClient.onNativeMessage);
|
||||
protocolClient.nativePort.onDisconnect.addListener(onDisconnected);
|
||||
keepass.isConnected = true;
|
||||
return protocolClient.nativePort;
|
||||
};
|
||||
|
||||
function onDisconnected() {
|
||||
protocolClient.nativePort = null;
|
||||
keepass.isConnected = false;
|
||||
keepass.isDatabaseClosed = true;
|
||||
keepass.isKeePassXCAvailable = false;
|
||||
keepass.associated.value = false;
|
||||
keepass.associated.hash = null;
|
||||
keepass.databaseHash = '';
|
||||
|
||||
page.clearAllLogins();
|
||||
keepass.updatePopup();
|
||||
keepass.updateDatabaseHashToContent();
|
||||
logError(`Failed to connect: ${(browser.runtime.lastError === null ? 'Unknown error' : browser.runtime.lastError.message)}`);
|
||||
}
|
||||
|
||||
protocolClient.onNativeMessage = function(response) {
|
||||
// Handle database lock/unlock status
|
||||
if (response.action === kpActions.DATABASE_LOCKED || response.action === kpActions.DATABASE_UNLOCKED) {
|
||||
keepass.updateDatabase();
|
||||
}
|
||||
|
||||
// Generic response handling
|
||||
if (response.action === kpActions.CHANGE_PUBLIC_KEYS || !keepass.protocolV2) {
|
||||
keepassClient.handleNativeMessage(response);
|
||||
} else {
|
||||
protocolClient.handleNativeMessage(response);
|
||||
}
|
||||
};
|
||||
|
|
@ -43,6 +43,13 @@ const showNotification = function(message) {
|
|||
});
|
||||
};
|
||||
|
||||
const AddCredentials = {
|
||||
CANCELED: 0,
|
||||
CREATED: 1,
|
||||
ERROR: 2,
|
||||
UPDATED: 3
|
||||
};
|
||||
|
||||
const AssociatedAction = {
|
||||
NOT_ASSOCIATED: 0,
|
||||
ASSOCIATED: 1,
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ kpxcBanner.create = async function(credentials = {}) {
|
|||
kpxcBanner.saveNewCredentials = async function(credentials = {}) {
|
||||
const saveToDefaultGroup = async function(creds) {
|
||||
const args = [ creds.username, creds.password, creds.url ];
|
||||
const res = await sendMessage('add_credentials', args);
|
||||
const res = await sendMessage('create_credentials', args);
|
||||
kpxcBanner.verifyResult(res);
|
||||
};
|
||||
|
||||
|
|
@ -202,7 +202,7 @@ kpxcBanner.saveNewCredentials = async function(credentials = {}) {
|
|||
// Create a new group
|
||||
const newGroup = await sendMessage('create_new_group', [ result.defaultGroup ]);
|
||||
if (newGroup.name && newGroup.uuid) {
|
||||
const res = await sendMessage('add_credentials', [
|
||||
const res = await sendMessage('create_credentials', [
|
||||
credentials.username,
|
||||
credentials.password,
|
||||
credentials.url,
|
||||
|
|
@ -218,7 +218,7 @@ kpxcBanner.saveNewCredentials = async function(credentials = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
const res = await sendMessage('add_credentials', [
|
||||
const res = await sendMessage('create_credentials', [
|
||||
credentials.username,
|
||||
credentials.password,
|
||||
credentials.url,
|
||||
|
|
@ -256,7 +256,7 @@ kpxcBanner.saveNewCredentials = async function(credentials = {}) {
|
|||
return;
|
||||
}
|
||||
|
||||
const res = await sendMessage('add_credentials', [
|
||||
const res = await sendMessage('create_credentials', [
|
||||
credentials.username,
|
||||
credentials.password,
|
||||
credentials.url,
|
||||
|
|
@ -341,7 +341,7 @@ kpxcBanner.updateCredentials = async function(credentials = {}) {
|
|||
args: [ url, '', true ] // Sets triggerUnlock to true
|
||||
}).then(async creds => {
|
||||
if (!creds || creds.length !== credentials.list.length) {
|
||||
kpxcBanner.verifyResult('error');
|
||||
kpxcBanner.verifyResult(AddCredentials.ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -367,21 +367,21 @@ kpxcBanner.updateCredentials = async function(credentials = {}) {
|
|||
};
|
||||
|
||||
kpxcBanner.verifyResult = async function(code) {
|
||||
if (code === 'error') {
|
||||
if (code === AddCredentials.ERROR) {
|
||||
kpxcUI.createNotification('error', tr('rememberErrorCannotSaveCredentials'));
|
||||
} else if (code === 'created') {
|
||||
} else if (code === AddCredentials.CREATED) {
|
||||
kpxcUI.createNotification(
|
||||
'success',
|
||||
tr('rememberCredentialsSaved', kpxcBanner.credentials.username || tr('rememberEmptyUsername')),
|
||||
);
|
||||
await kpxc.retrieveCredentials(true); // Forced reload
|
||||
} else if (code === 'updated') {
|
||||
} else if (code === AddCredentials.UPDATED) {
|
||||
kpxcUI.createNotification(
|
||||
'success',
|
||||
tr('rememberCredentialsUpdated', kpxcBanner.credentials.username || tr('rememberEmptyUsername')),
|
||||
);
|
||||
await kpxc.retrieveCredentials(true); // Forced reload
|
||||
} else if (code === 'canceled') {
|
||||
} else if (code === AddCredentials.CANCELED) {
|
||||
kpxcUI.createNotification('warning', tr('rememberCredentialsNotSaved'));
|
||||
} else {
|
||||
kpxcUI.createNotification('error', tr('rememberErrorDatabaseClosed'));
|
||||
|
|
|
|||
|
|
@ -144,24 +144,39 @@ kpxcFill.fillTOTPFromUuid = async function(el, uuid) {
|
|||
return;
|
||||
}
|
||||
|
||||
let totpFound = false;
|
||||
if (user.totp?.length > 0) {
|
||||
const protocolV2 = await sendMessage('is_protocol_v2');
|
||||
|
||||
// Retrieve a new TOTP value
|
||||
const totp = await sendMessage('get_totp', [ user.uuid, user.totp ]);
|
||||
const totp = await sendMessage('get_totp', (protocolV2 ? [ user.uuid ] : [ user.uuid, user.totp ]));
|
||||
if (!totp) {
|
||||
kpxcUI.createNotification('warning', tr('credentialsNoTOTPFound'));
|
||||
return;
|
||||
}
|
||||
|
||||
kpxcFill.setTOTPValue(el, totp);
|
||||
if (protocolV2) {
|
||||
const result = totp.find(t => t.uuid === uuid);
|
||||
kpxcFill.setTOTPValue(el, result?.totp);
|
||||
} else {
|
||||
kpxcFill.setTOTPValue(el, totp);
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (user.stringFields?.length > 0) {
|
||||
const stringFields = user.stringFields;
|
||||
for (const s of stringFields) {
|
||||
const val = s['KPH: {TOTP}'];
|
||||
if (val) {
|
||||
kpxcFill.setTOTPValue(el, val);
|
||||
totpFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!totpFound) {
|
||||
kpxcUI.createNotification('warning', tr('credentialsNoTOTPFound'));
|
||||
}
|
||||
};
|
||||
|
||||
// Set normal or segmented TOTP value
|
||||
|
|
@ -251,7 +266,7 @@ kpxcFill.fillInCredentials = async function(combination, predefinedUsername, uui
|
|||
if (combination.password.maxLength
|
||||
&& combination.password.maxLength > 0
|
||||
&& selectedCredentials.password.length > combination.password.maxLength) {
|
||||
kpxcUI.createNotification('warning', tr('errorMessagePaswordLengthExceeded'));
|
||||
kpxcUI.createNotification('warning', tr('errorMessagePasswordLengthExceeded'));
|
||||
}
|
||||
|
||||
// Prevent filling password to plain text input field
|
||||
|
|
@ -292,6 +307,12 @@ kpxcFill.fillInCredentials = async function(combination, predefinedUsername, uui
|
|||
await sendMessage('page_set_manual_fill', ManualFill.NONE);
|
||||
|
||||
await kpxcFill.performAutoSubmit(combination, skipAutoSubmit);
|
||||
|
||||
// Auto-lock database when requested
|
||||
if (await sendMessage('page_get_auto_lock_requested')) {
|
||||
await sendMessage('page_clear_auto_lock_requested');
|
||||
sendMessage('lock_database');
|
||||
}
|
||||
};
|
||||
|
||||
// Fills StringFields defined in Custom Fields
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ const sendMessage = async function(action, args) {
|
|||
* The main content script object.
|
||||
*/
|
||||
const kpxc = {};
|
||||
kpxc.associationStatus = undefined;
|
||||
kpxc.combinations = [];
|
||||
kpxc.credentials = [];
|
||||
kpxc.databaseState = DatabaseState.DISCONNECTED;
|
||||
|
|
@ -23,8 +24,8 @@ kpxc.improvedFieldDetectionEnabledForPage = false;
|
|||
kpxc.inputs = [];
|
||||
kpxc.settings = {};
|
||||
kpxc.singleInputEnabledForPage = false;
|
||||
kpxc.submitUrl = null;
|
||||
kpxc.url = null;
|
||||
kpxc.submitUrl = undefined;
|
||||
kpxc.url = undefined;
|
||||
|
||||
// Add page to Site Preferences with a selected option enabled. Set from the popup.
|
||||
kpxc.addToSitePreferences = async function(optionName, addWildcard = false) {
|
||||
|
|
@ -111,6 +112,7 @@ kpxc.createCombination = async function(activeElement, passOnly) {
|
|||
|
||||
// Switch credentials if database is changed or closed
|
||||
kpxc.detectDatabaseChange = async function(response) {
|
||||
kpxc.associationStatus = response?.associateResult;
|
||||
kpxc.databaseState = DatabaseState.LOCKED;
|
||||
kpxc.clearAllFromPage();
|
||||
kpxcIcons.switchIcons();
|
||||
|
|
@ -120,7 +122,8 @@ kpxc.detectDatabaseChange = async function(response) {
|
|||
_called.retrieveCredentials = false;
|
||||
const settings = await sendMessage('load_settings');
|
||||
kpxc.settings = settings;
|
||||
kpxc.databaseState = DatabaseState.UNLOCKED;
|
||||
kpxc.databaseState = response?.associateResult?.areAllLocked
|
||||
? DatabaseState.LOCKED : DatabaseState.UNLOCKED;
|
||||
|
||||
await kpxc.initCredentialFields();
|
||||
kpxcIcons.switchIcons();
|
||||
|
|
@ -128,7 +131,7 @@ kpxc.detectDatabaseChange = async function(response) {
|
|||
// If user has requested a manual fill through context menu the actual credential filling
|
||||
// is handled here when the opened database has been regognized. It's not a pretty hack.
|
||||
const manualFill = await sendMessage('page_get_manual_fill');
|
||||
if (manualFill !== ManualFill.NONE && kpxc.combinations.length > 0) {
|
||||
if (manualFill !== ManualFill.NONE) {
|
||||
await kpxcFill.fillInFromActiveElement(manualFill === ManualFill.PASSWORD);
|
||||
await sendMessage('page_set_manual_fill', ManualFill.NONE);
|
||||
}
|
||||
|
|
@ -385,6 +388,16 @@ kpxc.initCredentialFields = async function() {
|
|||
|
||||
await kpxcIcons.initIcons(kpxc.combinations);
|
||||
|
||||
// TODO: In optimal case, this should only trigger when a database is opened. How to detect that?
|
||||
// Protocol V2
|
||||
if (kpxc.associationStatus) {
|
||||
if (!kpxc.associationStatus.isCurrentLocked) {
|
||||
await kpxc.retrieveCredentials();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Protocol V1
|
||||
if (kpxc.databaseState === DatabaseState.UNLOCKED) {
|
||||
await kpxc.retrieveCredentials();
|
||||
}
|
||||
|
|
@ -828,6 +841,8 @@ kpxc.updateDatabaseState = async function() {
|
|||
};
|
||||
|
||||
// Updates the TOTP Autocomplete Menu
|
||||
// TODO: Check this against https://github.com/keepassxreboot/keepassxc-browser/compare/develop...feature/protocol_v2
|
||||
// Which one is the correct implementation?
|
||||
kpxc.updateTOTPList = async function() {
|
||||
let uuid = await sendMessage('page_get_login_id');
|
||||
if (uuid === undefined || kpxc.credentials.length === 0) {
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ const iconClicked = async function(field, icon) {
|
|||
if (kpxc.databaseState !== DatabaseState.UNLOCKED) {
|
||||
// Triggers database unlock
|
||||
await sendMessage('page_set_manual_fill', ManualFill.BOTH);
|
||||
// TODO: Replace with open-database or get-database-statuses? With legacyProtocol keepass.getDatabaseHash must be used.
|
||||
await sendMessage('get_database_hash', [ false, true ]); // Set triggerUnlock to true
|
||||
field.focus();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,37 @@ code {
|
|||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.dropdown-toggle {
|
||||
display: none;
|
||||
height: 31px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.kpxc-dropdown-menu {
|
||||
border: var(--kpxc-card-border-color);
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-top-left-radius: 4px;
|
||||
box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2) !important;
|
||||
overflow: hidden;;
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 40px;
|
||||
z-index: 2147483646;
|
||||
}
|
||||
|
||||
.kpxc-dropdown-item {
|
||||
background: var(--kpxc-background-color);
|
||||
color: var(--kpxc-text-color);
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.kpxc-dropdown-item:hover {
|
||||
background-color: #28a745 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
#list {
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
|
@ -196,6 +227,15 @@ code {
|
|||
background-color: var(--kpxc-table-hover-color) !important;
|
||||
}
|
||||
|
||||
.kpxc-dropdown-menu {
|
||||
border: var(--kpxc-card-border-color);
|
||||
}
|
||||
|
||||
.kpxc-dropdown-item {
|
||||
background: var(--kpxc-background-color);
|
||||
color: var(--kpxc-text-color);
|
||||
}
|
||||
|
||||
span.bg-success {
|
||||
background-color: var(--kpxc-table-hover-color) !important;
|
||||
color: var(--kpxc-text-color) !important;
|
||||
|
|
|
|||
|
|
@ -24,10 +24,14 @@
|
|||
</button>
|
||||
<button id="choose-custom-login-fields-button" class="btn btn-sm btn-warning" data-i18n="[title]popupChooseCredentialsText"></button>
|
||||
</div>
|
||||
<div class="lock-button-area">
|
||||
<button id="lock-database-button" class="btn btn-sm btn-danger" data-i18n="[title]lockDatabase">
|
||||
<i class="fa fa-lock" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="btn-group lock-button-area">
|
||||
<button id="lock-database-button" class="btn btn-sm btn-danger" data-i18n="[title]lockDatabase"><i class="fa fa-lock" aria-hidden="true"></i></button>
|
||||
<select id="dropdown-button" class="btn btn-sm btn-danger dropdown-toggle" style="display: none"></select>
|
||||
</div>
|
||||
<div class="kpxc-dropdown-menu" style="display: none">
|
||||
<div class="kpxc-dropdown-item">
|
||||
<span id="kpxc-dropdown-item" data-i18n="lockAllDatabases"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ HTMLElement.prototype.hide = function() {
|
|||
this.style.display = 'none';
|
||||
};
|
||||
|
||||
function statusResponse(r) {
|
||||
function statusResponse(status) {
|
||||
$('#initial-state').hide();
|
||||
$('#error-encountered').hide();
|
||||
$('#need-reconfigure').hide();
|
||||
|
|
@ -21,43 +21,77 @@ function statusResponse(r) {
|
|||
$('#getting-started-guide').hide();
|
||||
$('#database-not-opened').hide();
|
||||
|
||||
if (!r.keePassXCAvailable) {
|
||||
$('#error-message').textContent = r.error;
|
||||
if (!status.keePassXCAvailable) {
|
||||
$('#error-message').textContent = status.error;
|
||||
$('#error-encountered').show();
|
||||
|
||||
if (r.showGettingStartedGuideAlert) {
|
||||
if (status.showGettingStartedGuideAlert) {
|
||||
$('#getting-started-guide').show();
|
||||
}
|
||||
|
||||
if (r.showTroubleshootingGuideAlert && reloadCount >= 2) {
|
||||
if (status.showTroubleshootingGuideAlert && reloadCount >= 2) {
|
||||
$('#troubleshooting-guide').show();
|
||||
} else {
|
||||
$('#troubleshooting-guide').hide();
|
||||
}
|
||||
} else if (r.keePassXCAvailable && r.databaseClosed) {
|
||||
$('#database-error-message').textContent = r.error;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Only supported with Protocol V2
|
||||
if (status.protocolV2
|
||||
&& status.databaseAssociationStatuses
|
||||
&& Object.keys(status.databaseAssociationStatuses).length > 0) {
|
||||
// This can be also shown when isAnyAssociated is true?
|
||||
if (status.databaseAssociationStatuses.associationNeeded) {
|
||||
$('#not-configured').show();
|
||||
}
|
||||
|
||||
if (status.keePassXCAvailable && status.databaseAssociationStatuses.areAllLocked) {
|
||||
$('#database-error-message').textContent = status.error;
|
||||
$('#database-not-opened').show();
|
||||
}
|
||||
|
||||
if (status.databaseAssociationStatuses.isAnyAssociated) {
|
||||
$('#configured-and-associated').show();
|
||||
$('#associated-identifier').textContent = status.identifier;
|
||||
$('#lock-database-button').show();
|
||||
showDropdownButton(status.protocolV2);
|
||||
|
||||
if (status.usernameFieldDetected) {
|
||||
$('#username-field-detected').show();
|
||||
}
|
||||
|
||||
reloadCount = 0;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.keePassXCAvailable && status.databaseClosed) {
|
||||
$('#database-error-message').textContent = status.error;
|
||||
$('#database-not-opened').show();
|
||||
} else if (!r.configured) {
|
||||
} else if (!status.configured) {
|
||||
$('#not-configured').show();
|
||||
} else if (r.encryptionKeyUnrecognized) {
|
||||
} else if (status.encryptionKeyUnrecognized) {
|
||||
$('#need-reconfigure').show();
|
||||
$('#need-reconfigure-message').textContent = r.error;
|
||||
} else if (!r.associated) {
|
||||
$('#need-reconfigure-message').textContent = status.error;
|
||||
} else if (!status.associated) {
|
||||
$('#need-reconfigure').show();
|
||||
$('#need-reconfigure-message').textContent = r.error;
|
||||
} else if (r.error) {
|
||||
$('#need-reconfigure-message').textContent = status.error;
|
||||
} else if (status.error) {
|
||||
$('#error-encountered').show();
|
||||
$('#error-message').textContent = r.error;
|
||||
$('#error-message').textContent = status.error;
|
||||
} else {
|
||||
$('#configured-and-associated').show();
|
||||
$('#associated-identifier').textContent = r.identifier;
|
||||
$('#associated-identifier').textContent = status.identifier;
|
||||
$('#lock-database-button').show();
|
||||
|
||||
if (r.usernameFieldDetected) {
|
||||
if (status.usernameFieldDetected) {
|
||||
$('#username-field-detected').show();
|
||||
}
|
||||
|
||||
if (r.iframeDetected) {
|
||||
if (status.iframeDetected) {
|
||||
$('#iframe-detected').show();
|
||||
}
|
||||
|
||||
|
|
@ -130,9 +164,11 @@ const sendMessageToTab = async function(message) {
|
|||
});
|
||||
|
||||
$('#lock-database-button').addEventListener('click', async () => {
|
||||
statusResponse(await browser.runtime.sendMessage({
|
||||
action: 'lock_database'
|
||||
}));
|
||||
statusResponse(await lockDatabase());
|
||||
});
|
||||
|
||||
$('.kpxc-dropdown-item').addEventListener('click', async () => {
|
||||
statusResponse(await lockDatabase(false));
|
||||
});
|
||||
|
||||
$('#username-only-button').addEventListener('click', async () => {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,40 @@ async function getLoginData() {
|
|||
return logins;
|
||||
}
|
||||
|
||||
async function lockDatabase(lockSingle = true) {
|
||||
return await browser.runtime.sendMessage({
|
||||
action: 'lock_database',
|
||||
args: [ lockSingle ]
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Show the dropdown button if protocol is supported
|
||||
async function showDropdownButton(isV2) {
|
||||
const isProtocolV2 = isV2 || await browser.runtime.sendMessage({ action: 'is_protocol_v2' });
|
||||
if (isProtocolV2) {
|
||||
$('.lock-button-area')?.classList.add('btn-group');
|
||||
$('#dropdown-button')?.show();
|
||||
} else {
|
||||
$('.lock-button-area')?.classList.remove('btn-group');
|
||||
$('#dropdown-button')?.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function hideDropdownButton() {
|
||||
$('.lock-button-area')?.classList.remove('btn-group');
|
||||
$('#dropdown-button')?.hide();
|
||||
}
|
||||
|
||||
function hideElementsOnDatabaseLock() {
|
||||
$('.credentials').hide();
|
||||
$('#database-not-opened').show();
|
||||
$('#lock-database-button').hide();
|
||||
$('#dropdown-button').hide();
|
||||
$('#btn-dismiss')?.hide();
|
||||
$('#database-error-message').textContent = tr('errorMessageDatabaseNotOpened');
|
||||
}
|
||||
|
||||
(async () => {
|
||||
if (document.readyState === 'complete' || (document.readyState !== 'loading' && !document.documentElement.doScroll)) {
|
||||
await initSettings();
|
||||
|
|
@ -60,7 +94,39 @@ async function getLoginData() {
|
|||
document.addEventListener('DOMContentLoaded', initSettings);
|
||||
}
|
||||
|
||||
document.addEventListener('mouseup', function(e) {
|
||||
if (!e.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.target.id !== 'kpxc-dropdown-item' && e.target.id !== 'dropdown-button') {
|
||||
$('.kpxc-dropdown-menu')?.hide();
|
||||
$('#dropdown-button').style.borderBottomRightRadius = '4px';
|
||||
}
|
||||
});
|
||||
|
||||
updateAvailableResponse(await browser.runtime.sendMessage({
|
||||
action: 'update_available_keepassxc'
|
||||
}));
|
||||
|
||||
$('#dropdown-button').addEventListener('click', (e) => {
|
||||
const dropdownMenu = $('.kpxc-dropdown-menu');
|
||||
if (!dropdownMenu) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dropdownMenu.style.display === 'none') {
|
||||
dropdownMenu.show();
|
||||
$('#dropdown-button').style.borderBottomRightRadius = '0px';
|
||||
} else {
|
||||
dropdownMenu.hide();
|
||||
$('#dropdown-button').style.borderBottomRightRadius = '4px';
|
||||
}
|
||||
|
||||
e.target.blur();
|
||||
});
|
||||
|
||||
$('.kpxc-dropdown-item').addEventListener('click', () => {
|
||||
$('.kpxc-dropdown-menu')?.hide();
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -24,10 +24,14 @@
|
|||
</button>
|
||||
<button id="choose-custom-login-fields-button" class="btn btn-sm btn-warning" data-i18n="[title]popupChooseCredentialsText"></button>
|
||||
</div>
|
||||
<div class="lock-button-area">
|
||||
<button id="lock-database-button" class="btn btn-sm btn-danger" data-i18n="[title]lockDatabase">
|
||||
<i class="fa fa-lock" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="btn-group lock-button-area">
|
||||
<button id="lock-database-button" class="btn btn-sm btn-danger" data-i18n="[title]lockDatabase"><i class="fa fa-lock" aria-hidden="true"></i></button>
|
||||
<select id="dropdown-button" class="btn btn-sm btn-danger dropdown-toggle" style="display: none"></select>
|
||||
</div>
|
||||
<div class="kpxc-dropdown-menu" style="display: none">
|
||||
<div class="kpxc-dropdown-item">
|
||||
<span id="dropdown-item" data-i18n="lockAllDatabases"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
await initColorTheme();
|
||||
|
||||
$('#lock-database-button').show();
|
||||
await showDropdownButton();
|
||||
|
||||
const data = await getLoginData();
|
||||
const ll = document.getElementById('login-list');
|
||||
|
|
@ -30,16 +31,14 @@
|
|||
ll.appendChild(a);
|
||||
}
|
||||
|
||||
$('#lock-database-button').addEventListener('click', function() {
|
||||
browser.runtime.sendMessage({
|
||||
action: 'lock_database'
|
||||
});
|
||||
$('#lock-database-button').addEventListener('click', () => {
|
||||
lockDatabase();
|
||||
hideElementsOnDatabaseLock();
|
||||
});
|
||||
|
||||
$('.credentials').hide();
|
||||
$('#btn-dismiss').hide();
|
||||
$('#database-not-opened').show();
|
||||
$('#lock-database-button').hide();
|
||||
$('#database-error-message').textContent = tr('errorMessageDatabaseNotOpened');
|
||||
$('.kpxc-dropdown-item').addEventListener('click', () => {
|
||||
lockDatabase(false);
|
||||
hideElementsOnDatabaseLock();
|
||||
});
|
||||
|
||||
$('#btn-dismiss').addEventListener('click', async () => {
|
||||
|
|
|
|||
|
|
@ -24,10 +24,14 @@
|
|||
</button>
|
||||
<button id="choose-custom-login-fields-button" class="btn btn-sm btn-warning" data-i18n="[title]popupChooseCredentialsText"></button>
|
||||
</div>
|
||||
<div class="lock-button-area">
|
||||
<button id="lock-database-button" class="btn btn-sm btn-danger" data-i18n="[title]lockDatabase">
|
||||
<i class="fa fa-lock" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="btn-group lock-button-area">
|
||||
<button id="lock-database-button" class="btn btn-sm btn-danger" data-i18n="[title]lockDatabase"><i class="fa fa-lock" aria-hidden="true"></i></button>
|
||||
<select id="dropdown-button" class="btn btn-sm btn-danger dropdown-toggle" style="display: none"></select>
|
||||
</div>
|
||||
<div class="kpxc-dropdown-menu" style="display: none">
|
||||
<div class="kpxc-dropdown-item">
|
||||
<span id="dropdown-item" data-i18n="lockAllDatabases"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@
|
|||
return [];
|
||||
}
|
||||
|
||||
$('#lock-database-button').show();
|
||||
showDropdownButton();
|
||||
|
||||
const logins = await getLoginData();
|
||||
const ll = document.getElementById('login-list');
|
||||
|
||||
|
|
@ -61,20 +64,19 @@
|
|||
}
|
||||
|
||||
$('#lock-database-button').addEventListener('click', (e) => {
|
||||
browser.runtime.sendMessage({
|
||||
action: 'lock_database'
|
||||
});
|
||||
lockDatabase();
|
||||
hideElementsOnDatabaseLock();
|
||||
});
|
||||
|
||||
$('#credentialsList').hide();
|
||||
$('#database-not-opened').show();
|
||||
$('#lock-database-button').hide();
|
||||
$('#database-error-message').textContent = tr('errorMessageDatabaseNotOpened');
|
||||
$('.kpxc-dropdown-item').addEventListener('click', () => {
|
||||
lockDatabase(false);
|
||||
hideElementsOnDatabaseLock();
|
||||
});
|
||||
|
||||
$('#reopen-database-button').addEventListener('click', (e) => {
|
||||
browser.runtime.sendMessage({
|
||||
action: 'get_status',
|
||||
args: [ false, true ] // Set forcePopup to true
|
||||
args: [ false, true ] // Set triggerUnlock to true
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in a new issue