mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-30 12:39:20 +03:00
Merge branch 'upstream/master' into dev/sync-nextcloud-addressbook
This commit is contained in:
commit
957654c746
1528 changed files with 85567 additions and 32765 deletions
|
|
@ -2,11 +2,7 @@ import { addObservablesTo, koArrayWithDestroy } from 'External/ko';
|
|||
|
||||
export const AccountUserStore = koArrayWithDestroy();
|
||||
|
||||
AccountUserStore.loading = ko.observable(false).extend({ debounce: 100 });
|
||||
|
||||
AccountUserStore.getEmailAddresses = () => AccountUserStore.map(item => item.email);
|
||||
|
||||
addObservablesTo(AccountUserStore, {
|
||||
email: '',
|
||||
signature: ''
|
||||
loading: false
|
||||
});
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ ContactUserStore.sync = fResultFunc => {
|
|||
line = JSON.parse(line);
|
||||
if ('ContactsSync' === line.Action) {
|
||||
ContactUserStore.syncing(false);
|
||||
fResultFunc?.(line.ErrorCode, line);
|
||||
fResultFunc?.(line.code, line);
|
||||
}
|
||||
} catch (e) {
|
||||
ContactUserStore.syncing(false);
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ FolderUserStore = new class {
|
|||
*/
|
||||
displaySpecSetting: false,
|
||||
|
||||
// sortMode: '',
|
||||
sortMode: '',
|
||||
|
||||
quotaLimit: 0,
|
||||
quotaUsage: 0,
|
||||
|
|
@ -70,8 +70,8 @@ FolderUserStore = new class {
|
|||
trashFolder: '',
|
||||
archiveFolder: '',
|
||||
|
||||
folderListOptimized: false,
|
||||
folderListError: '',
|
||||
optimized: false,
|
||||
error: '',
|
||||
|
||||
foldersLoading: false,
|
||||
foldersCreating: false,
|
||||
|
|
@ -81,8 +81,6 @@ FolderUserStore = new class {
|
|||
foldersInboxUnreadCount: 0
|
||||
});
|
||||
|
||||
self.sortMode = ko.observable('');
|
||||
|
||||
self.namespace = '';
|
||||
|
||||
self.folderList = ko.observableArray(/*new FolderCollectionModel*/);
|
||||
|
|
|
|||
|
|
@ -9,25 +9,17 @@ import Remote from 'Remote/User/Fetch';
|
|||
|
||||
import { showScreenPopup } from 'Knoin/Knoin';
|
||||
import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey';
|
||||
import { AskPopupView } from 'View/Popup/Ask';
|
||||
|
||||
import { Passphrases } from 'Storage/Passphrases';
|
||||
|
||||
const
|
||||
askPassphrase = async (privateKey, btnTxt = 'LABEL_SIGN') => {
|
||||
const key = privateKey.id,
|
||||
pass = Passphrases.has(key)
|
||||
? {password:Passphrases.get(key), remember:false}
|
||||
: await AskPopupView.password('GnuPG key<br>' + key + ' ' + privateKey.emails[0], 'OPENPGP/'+btnTxt);
|
||||
pass && pass.remember && Passphrases.set(key, pass.password);
|
||||
return pass.password;
|
||||
},
|
||||
import { baseCollator } from 'Common/Translator';
|
||||
|
||||
const
|
||||
findGnuPGKey = (keys, query/*, sign*/) =>
|
||||
keys.find(key =>
|
||||
// key[sign ? 'can_sign' : 'can_decrypt']
|
||||
(key.can_sign || key.can_decrypt)
|
||||
&& (key.emails.includes(query) || key.subkeys.find(key => query == key.keyid || query == key.fingerprint))
|
||||
&& (key.for(query) || key.subkeys.find(key => query == key.keyid || query == key.fingerprint))
|
||||
);
|
||||
|
||||
export const GnuPGUserStore = new class {
|
||||
|
|
@ -45,7 +37,8 @@ export const GnuPGUserStore = new class {
|
|||
this.keyring = null;
|
||||
this.publicKeys([]);
|
||||
this.privateKeys([]);
|
||||
Remote.request('GnupgGetKeys',
|
||||
SettingsCapa('GnuPG')
|
||||
&& Remote.request('GnupgGetKeys',
|
||||
(iError, oData) => {
|
||||
if (oData?.Result) {
|
||||
this.keyring = oData.Result;
|
||||
|
|
@ -55,6 +48,7 @@ export const GnuPGUserStore = new class {
|
|||
key.fingerprint = key.subkeys[0].fingerprint;
|
||||
key.uids.forEach(uid => uid.email && aEmails.push(uid.email));
|
||||
key.emails = aEmails;
|
||||
key.for = email => aEmails.includes(IDN.toASCII(email));
|
||||
key.askDelete = ko.observable(false);
|
||||
key.openForDeletion = ko.observable(null).askDeleteHelper();
|
||||
key.remove = () => {
|
||||
|
|
@ -63,7 +57,7 @@ export const GnuPGUserStore = new class {
|
|||
(iError, oData) => {
|
||||
if (oData) {
|
||||
if (iError) {
|
||||
alert(oData.ErrorMessage);
|
||||
alert(oData.message);
|
||||
} else if (oData.Result) {
|
||||
isPrivate
|
||||
? this.privateKeys.remove(key)
|
||||
|
|
@ -77,33 +71,49 @@ export const GnuPGUserStore = new class {
|
|||
);
|
||||
}
|
||||
};
|
||||
key.view = () => {
|
||||
const fetch = pass => Remote.request('GnupgExportKey',
|
||||
(iError, oData) => {
|
||||
if (oData?.Result) {
|
||||
key.armor = oData.Result;
|
||||
showScreenPopup(OpenPgpKeyPopupView, [key]);
|
||||
} else {
|
||||
Passphrases.delete(key.id);
|
||||
}
|
||||
}, {
|
||||
keyId: key.id,
|
||||
isPrivate: isPrivate,
|
||||
passphrase: pass
|
||||
}
|
||||
if (isPrivate) {
|
||||
key.password = async btnTxt => {
|
||||
const pass = await Passphrases.ask(key,
|
||||
'GnuPG key<br>' + key.id + ' ' + key.emails[0],
|
||||
btnTxt
|
||||
);
|
||||
if (isPrivate) {
|
||||
askPassphrase(key, 'POPUP_VIEW_TITLE').then(passphrase => {
|
||||
(null !== passphrase) && fetch(passphrase);
|
||||
});
|
||||
pass && pass.remember && Passphrases.handle(key, pass.password);
|
||||
return pass?.password;
|
||||
};
|
||||
}
|
||||
key.fetch = async callback => {
|
||||
if (key.armor) {
|
||||
callback && callback();
|
||||
} else {
|
||||
fetch('');
|
||||
let pass = isPrivate ? await key.password('OPENPGP/POPUP_VIEW_TITLE') : '';
|
||||
if (null != pass) try {
|
||||
const result = await Remote.post('GnupgExportKey', null, {
|
||||
keyId: key.id,
|
||||
isPrivate: isPrivate,
|
||||
passphrase: pass
|
||||
});
|
||||
if (result?.Result) {
|
||||
key.armor = result.Result;
|
||||
callback && callback();
|
||||
} else {
|
||||
Passphrases.delete(key);
|
||||
}
|
||||
} catch (e) {
|
||||
Passphrases.delete(key);
|
||||
alert(e.message);
|
||||
}
|
||||
}
|
||||
return key.armor;
|
||||
};
|
||||
key.view = () => key.fetch(() => showScreenPopup(OpenPgpKeyPopupView, [key]));
|
||||
return key;
|
||||
};
|
||||
this.publicKeys(oData.Result.public.map(key => initKey(key, 0)));
|
||||
this.privateKeys(oData.Result.private.map(key => initKey(key, 1)));
|
||||
},
|
||||
collator = baseCollator(),
|
||||
sort = keys => keys.sort(
|
||||
(a, b) => collator.compare(a.emails[0], b.emails[0]) || collator.compare(a.id, b.id)
|
||||
);
|
||||
this.publicKeys(sort(oData.Result.public.map(key => initKey(key, 0))));
|
||||
this.privateKeys(sort(oData.Result.private.map(key => initKey(key, 1))));
|
||||
console.log('gnupg ready');
|
||||
}
|
||||
}
|
||||
|
|
@ -117,19 +127,6 @@ export const GnuPGUserStore = new class {
|
|||
return SettingsCapa('GnuPG');
|
||||
}
|
||||
|
||||
importKey(key, callback) {
|
||||
Remote.request('GnupgImportKey',
|
||||
(iError, oData) => {
|
||||
if (oData?.Result/* && (oData.Result.imported || oData.Result.secretimported)*/) {
|
||||
this.loadKeyrings();
|
||||
}
|
||||
callback?.(iError, oData);
|
||||
}, {
|
||||
key: key
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
keyPair.privateKey
|
||||
keyPair.publicKey
|
||||
|
|
@ -155,7 +152,7 @@ export const GnuPGUserStore = new class {
|
|||
const count = recipients.length,
|
||||
length = count ? recipients.filter(email =>
|
||||
// (key.can_verify || key.can_encrypt) &&
|
||||
this.publicKeys.find(key => key.emails.includes(email))
|
||||
this.publicKeys.find(key => key.for(email))
|
||||
).length : 0;
|
||||
return length && length === count;
|
||||
}
|
||||
|
|
@ -163,13 +160,13 @@ export const GnuPGUserStore = new class {
|
|||
getPublicKeyFingerprints(recipients) {
|
||||
const fingerprints = [];
|
||||
recipients.forEach(email => {
|
||||
fingerprints.push(this.publicKeys.find(key => key.emails.includes(email)).fingerprint);
|
||||
fingerprints.push(this.publicKeys.find(key => key.for(email)).fingerprint);
|
||||
});
|
||||
return fingerprints;
|
||||
}
|
||||
|
||||
getPrivateKeyFor(query, sign) {
|
||||
return findGnuPGKey(this.privateKeys, query, sign);
|
||||
getPrivateKeyFor(query/*, sign*/) {
|
||||
return findGnuPGKey(this.privateKeys, query/*, sign*/);
|
||||
}
|
||||
|
||||
async decrypt(message) {
|
||||
|
|
@ -191,22 +188,27 @@ export const GnuPGUserStore = new class {
|
|||
uid: message.uid,
|
||||
partId: pgpInfo.partId,
|
||||
keyId: key.id,
|
||||
passphrase: await askPassphrase(key, 'BUTTON_DECRYPT'),
|
||||
passphrase: await key.password('CRYPTO/DECRYPT'),
|
||||
data: '' // message.plain() optional
|
||||
}
|
||||
if (null !== params.passphrase) {
|
||||
const result = await Remote.post('GnupgDecrypt', null, params);
|
||||
if (result?.Result && false !== result.Result.data) {
|
||||
return result.Result;
|
||||
if (null != params.passphrase) {
|
||||
try {
|
||||
const response = await Remote.post('GnupgDecrypt', null, params);
|
||||
if (response?.Result?.data) {
|
||||
return response.Result;
|
||||
}
|
||||
throw response;
|
||||
} catch (e) {
|
||||
Passphrases.delete(key);
|
||||
throw e;
|
||||
}
|
||||
Passphrases.delete(key.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async verify(message) {
|
||||
let data = message.pgpSigned(); // { bodyPartId: "1", sigPartId: "2", micAlg: "pgp-sha256" }
|
||||
let data = message.pgpSigned(); // { partId: "1", sigPartId: "2", micAlg: "pgp-sha256" }
|
||||
if (data) {
|
||||
data = { ...data }; // clone
|
||||
// const sender = message.from[0].email;
|
||||
|
|
@ -217,18 +219,19 @@ export const GnuPGUserStore = new class {
|
|||
data.bodyPart = data.bodyPart.raw;
|
||||
data.sigPart = data.sigPart.body;
|
||||
}
|
||||
let response = await Remote.post('MessagePgpVerify', null, data);
|
||||
let response = await Remote.post('PgpVerifyMessage', null, data);
|
||||
if (response?.Result) {
|
||||
return {
|
||||
fingerprint: response.Result.fingerprint,
|
||||
success: 0 == response.Result.status // GOODSIG
|
||||
success: 0 == response.Result.status, // GOODSIG
|
||||
error: response.Result.message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async sign(privateKey) {
|
||||
return await askPassphrase(privateKey);
|
||||
return await privateKey.password('CRYPTO/SIGN');
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
import { koArrayWithDestroy } from 'External/ko';
|
||||
import { koArrayWithDestroy, koComputable } from 'External/ko';
|
||||
import { isArray } from 'Common/Utils';
|
||||
|
||||
export const IdentityUserStore = koArrayWithDestroy();
|
||||
|
||||
IdentityUserStore.loading = ko.observable(false).extend({ debounce: 100 });
|
||||
|
||||
/** Returns main (login) identity */
|
||||
IdentityUserStore.main = koComputable(() => {
|
||||
const list = IdentityUserStore();
|
||||
return isArray(list) ? list.find(item => item && !item.id()) : null;
|
||||
});
|
||||
|
|
|
|||
124
dev/Stores/User/Mailvelope.js
Normal file
124
dev/Stores/User/Mailvelope.js
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import { SettingsGet } from 'Common/Globals';
|
||||
|
||||
// https://mailvelope.github.io/mailvelope/Keyring.html
|
||||
let mailvelopeKeyring = null;
|
||||
|
||||
export const MailvelopeUserStore = {
|
||||
keyring: null,
|
||||
|
||||
loadKeyring(identifier) {
|
||||
identifier = identifier || SettingsGet('Email');
|
||||
if (window.mailvelope) {
|
||||
const fn = keyring => {
|
||||
mailvelopeKeyring = keyring;
|
||||
console.log('mailvelope ready');
|
||||
};
|
||||
mailvelope.getKeyring().then(fn, err => {
|
||||
if (identifier) {
|
||||
// attempt to create a new keyring for this app/user
|
||||
mailvelope.createKeyring(identifier).then(fn, err => console.error(err));
|
||||
} else {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
addEventListener('mailvelope-disconnect', event => {
|
||||
alert('Mailvelope is updated to version ' + event.detail.version + '. Reload page');
|
||||
}, false);
|
||||
} else {
|
||||
addEventListener('mailvelope', () => this.loadKeyring(identifier));
|
||||
}
|
||||
},
|
||||
|
||||
async hasPublicKeyForEmails(recipients) {
|
||||
const
|
||||
mailvelope = mailvelopeKeyring && await mailvelopeKeyring.validKeyForAddress(recipients)
|
||||
/*.then(LookupResult => Object.entries(LookupResult))*/,
|
||||
entries = mailvelope && Object.entries(mailvelope);
|
||||
return entries && entries.filter(value => value[1]).length === recipients.length;
|
||||
},
|
||||
|
||||
async getPrivateKeyFor(email/*, sign*/) {
|
||||
if (mailvelopeKeyring && await mailvelopeKeyring.hasPrivateKey({email:email})) {
|
||||
return ['mailvelope', email];
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
async decrypt(message) {
|
||||
// Try Mailvelope (does not support inline images)
|
||||
if (mailvelopeKeyring) {
|
||||
const sender = message.from[0].email,
|
||||
armoredText = message.plain();
|
||||
try {
|
||||
let emails = [...message.from,...message.to,...message.cc].validUnique(),
|
||||
i = emails.length;
|
||||
while (i--) {
|
||||
if (await this.getPrivateKeyFor(emails[i].email)) {
|
||||
/**
|
||||
* https://mailvelope.github.io/mailvelope/Mailvelope.html#createEncryptedFormContainer
|
||||
* Creates an iframe to display an encrypted form
|
||||
*/
|
||||
// mailvelope.createEncryptedFormContainer('#mailvelope-form');
|
||||
/**
|
||||
* https://mailvelope.github.io/mailvelope/Mailvelope.html#createDisplayContainer
|
||||
* Creates an iframe to display the decrypted content of the encrypted mail.
|
||||
*/
|
||||
const body = message.body;
|
||||
body.textContent = '';
|
||||
let result = await mailvelope.createDisplayContainer(
|
||||
'#'+body.id,
|
||||
armoredText,
|
||||
mailvelopeKeyring,
|
||||
{
|
||||
senderAddress: sender
|
||||
// emails[i].email
|
||||
}
|
||||
);
|
||||
if (result) {
|
||||
if (result.error?.message) {
|
||||
if ('PWD_DIALOG_CANCEL' !== result.error.code) {
|
||||
alert(result.error.code + ': ' + result.error.message);
|
||||
}
|
||||
} else {
|
||||
body.classList.add('mailvelope');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns headers that should be added to an outgoing email.
|
||||
* So far this is only the autocrypt header.
|
||||
*/
|
||||
/*
|
||||
mailvelopeKeyring.additionalHeadersForOutgoingEmail(from: 'abc@web.de')
|
||||
.then(function(additional) {
|
||||
console.log('additionalHeadersForOutgoingEmail', additional);
|
||||
// logs: {autocrypt: "addr=abc@web.de; prefer-encrypt=mutual; keydata=..."}
|
||||
});
|
||||
|
||||
mailvelopeKeyring.addSyncHandler(syncHandlerObj)
|
||||
mailvelopeKeyring.createKeyBackupContainer(selector, options)
|
||||
mailvelopeKeyring.createKeyGenContainer(selector, {
|
||||
// userIds: [],
|
||||
keySize: 4096
|
||||
})
|
||||
|
||||
mailvelopeKeyring.exportOwnPublicKey(emailAddr).then(<AsciiArmored, Error>)
|
||||
mailvelopeKeyring.importPublicKey(armored)
|
||||
|
||||
// https://mailvelope.github.io/mailvelope/global.html#SyncHandlerObject
|
||||
mailvelopeKeyring.addSyncHandler({
|
||||
uploadSync
|
||||
downloadSync
|
||||
backup
|
||||
restore
|
||||
});
|
||||
*/
|
||||
};
|
||||
|
|
@ -32,8 +32,11 @@ import { SettingsGet } from 'Common/Globals';
|
|||
import { SUB_QUERY_PREFIX } from 'Common/Links';
|
||||
import { AppUserStore } from 'Stores/User/App';
|
||||
|
||||
import { baseCollator } from 'Common/Translator';
|
||||
|
||||
const
|
||||
isChecked = item => item.checked(),
|
||||
isDeleted = item => item.isDeleted(),
|
||||
replaceHash = hash => {
|
||||
rl.route.off();
|
||||
hasher.replaceHash(hash);
|
||||
|
|
@ -105,15 +108,15 @@ addComputablesTo(MessagelistUserStore, {
|
|||
listCheckedOrSelected: () => {
|
||||
const
|
||||
selectedMessage = MessagelistUserStore.selectedMessage(),
|
||||
focusedMessage = MessagelistUserStore.focusedMessage(),
|
||||
checked = MessagelistUserStore.filter(item => isChecked(item));
|
||||
return checked.length ? checked : (selectedMessage || focusedMessage ? [selectedMessage || focusedMessage] : []);
|
||||
return checked.length ? checked : (selectedMessage ? [selectedMessage] : []);
|
||||
},
|
||||
|
||||
listCheckedOrSelectedUidsWithSubMails: () => {
|
||||
let result = new Set;
|
||||
MessagelistUserStore.listCheckedOrSelected().forEach(message => {
|
||||
result.add(message.uid);
|
||||
result.folder = message.folder;
|
||||
if (1 < message.threadsLen()) {
|
||||
message.threads().forEach(result.add, result);
|
||||
}
|
||||
|
|
@ -134,11 +137,18 @@ MessagelistUserStore.hasChecked = koComputable(
|
|||
|
||||
MessagelistUserStore.hasCheckedOrSelected = koComputable(() =>
|
||||
!!MessagelistUserStore.selectedMessage()
|
||||
| !!MessagelistUserStore.focusedMessage()
|
||||
// Issue: not all are observed?
|
||||
| !!MessagelistUserStore.find(isChecked)
|
||||
).extend({ rateLimit: 50 });
|
||||
|
||||
MessagelistUserStore.hasCheckedOrSelectedAndDeleted = koComputable(
|
||||
() => !!MessagelistUserStore.listCheckedOrSelected().find(isDeleted)
|
||||
).extend({ rateLimit: 50 });
|
||||
|
||||
MessagelistUserStore.hasCheckedOrSelectedAndUndeleted = koComputable(
|
||||
() => !!MessagelistUserStore.listCheckedOrSelected().find(item => !item?.isDeleted())
|
||||
).extend({ rateLimit: 50 });
|
||||
|
||||
MessagelistUserStore.notifyNewMessages = (folder, newMessages) => {
|
||||
if (getFolderInboxName() === folder && arrayLength(newMessages)) {
|
||||
|
||||
|
|
@ -165,7 +175,7 @@ MessagelistUserStore.notifyNewMessages = (folder, newMessages) => {
|
|||
}
|
||||
}
|
||||
|
||||
MessagelistUserStore.canAutoSelect = () =>
|
||||
MessagelistUserStore.canSelect = () =>
|
||||
!disableAutoSelect()
|
||||
&& SettingsUserStore.usePreviewPane();
|
||||
// && !SettingsUserStore.showNextMessage();
|
||||
|
|
@ -253,17 +263,14 @@ MessagelistUserStore.reload = (bDropPagePosition = false, bDropCurrentFolderCach
|
|||
|
||||
let flags = folderInfo.permanentFlags || [];
|
||||
if (flags.includes('\\*')) {
|
||||
/** Add Thunderbird labels */
|
||||
let i = 6;
|
||||
while (--i) {
|
||||
flags.includes('$label'+i) || flags.push('$label'+i);
|
||||
}
|
||||
/** TODO: add others by default? */
|
||||
}
|
||||
flags.sort((a, b) => {
|
||||
a = a.toUpperCase();
|
||||
b = b.toUpperCase();
|
||||
return (a < b) ? -1 : ((a > b) ? 1 : 0);
|
||||
});
|
||||
folder.permanentFlags(flags);
|
||||
folder.permanentFlags(flags.sort(baseCollator().compare));
|
||||
|
||||
MessagelistUserStore.notifyNewMessages(folder.fullName, collection.newMessages);
|
||||
}
|
||||
|
|
@ -313,6 +320,7 @@ MessagelistUserStore.reload = (bDropPagePosition = false, bDropCurrentFolderCach
|
|||
|
||||
if (AppUserStore.threadsAllowed() && SettingsUserStore.useThreads()) {
|
||||
params.useThreads = 1;
|
||||
params.threadAlgorithm = SettingsUserStore.threadAlgorithm();
|
||||
params.threadUid = MessagelistUserStore.threadUid();
|
||||
} else {
|
||||
params.threadUid = 0;
|
||||
|
|
@ -344,13 +352,23 @@ MessagelistUserStore.setAction = (sFolderFullName, iSetAction, messages) => {
|
|||
length;
|
||||
|
||||
if (iSetAction == MessageSetAction.SetSeen) {
|
||||
messages.forEach(oMessage =>
|
||||
oMessage.isUnseen() && rootUids.push(oMessage.uid) && oMessage.flags.push('\\seen')
|
||||
);
|
||||
messages.forEach(oMessage => {
|
||||
if (oMessage.isUnseen() && rootUids.push(oMessage.uid)) {
|
||||
oMessage.flags.push('\\seen');
|
||||
if (oMessage.threads().length > 0 && oMessage.threadUnseen().includes(oMessage.uid)) {
|
||||
oMessage.threadUnseen.remove(oMessage.uid);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (iSetAction == MessageSetAction.UnsetSeen) {
|
||||
messages.forEach(oMessage =>
|
||||
!oMessage.isUnseen() && rootUids.push(oMessage.uid) && oMessage.flags.remove('\\seen')
|
||||
);
|
||||
messages.forEach(oMessage => {
|
||||
if (!oMessage.isUnseen() && rootUids.push(oMessage.uid)) {
|
||||
oMessage.flags.remove('\\seen');
|
||||
if (oMessage.threads().length > 0 && !oMessage.threadUnseen().includes(oMessage.uid)) {
|
||||
oMessage.threadUnseen.push(oMessage.uid);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (iSetAction == MessageSetAction.SetFlag) {
|
||||
messages.forEach(oMessage =>
|
||||
!oMessage.isFlagged() && rootUids.push(oMessage.uid) && oMessage.flags.push('\\flagged')
|
||||
|
|
@ -359,6 +377,14 @@ MessagelistUserStore.setAction = (sFolderFullName, iSetAction, messages) => {
|
|||
messages.forEach(oMessage =>
|
||||
oMessage.isFlagged() && rootUids.push(oMessage.uid) && oMessage.flags.remove('\\flagged')
|
||||
);
|
||||
} else if (iSetAction == MessageSetAction.SetDeleted) {
|
||||
messages.forEach(oMessage =>
|
||||
!oMessage.isDeleted() && rootUids.push(oMessage.uid) && oMessage.flags.push('\\deleted')
|
||||
);
|
||||
} else if (iSetAction == MessageSetAction.UnsetDeleted) {
|
||||
messages.forEach(oMessage =>
|
||||
oMessage.isDeleted() && rootUids.push(oMessage.uid) && oMessage.flags.remove('\\deleted')
|
||||
);
|
||||
}
|
||||
rootUids = rootUids.validUnique();
|
||||
length = rootUids.length;
|
||||
|
|
@ -388,6 +414,15 @@ MessagelistUserStore.setAction = (sFolderFullName, iSetAction, messages) => {
|
|||
setAction: iSetAction == MessageSetAction.SetFlag ? 1 : 0
|
||||
});
|
||||
break;
|
||||
|
||||
case MessageSetAction.SetDeleted:
|
||||
case MessageSetAction.UnsetDeleted:
|
||||
Remote.request('MessageSetDeleted', null, {
|
||||
folder: sFolderFullName,
|
||||
uids: rootUids.join(','),
|
||||
setAction: iSetAction == MessageSetAction.SetDeleted ? 1 : 0
|
||||
});
|
||||
break;
|
||||
// no default
|
||||
}
|
||||
}
|
||||
|
|
@ -433,23 +468,6 @@ MessagelistUserStore.moveMessages = (
|
|||
if (page > MessagelistUserStore.pageCount()) {
|
||||
setPage = MessagelistUserStore.pageCount();
|
||||
}
|
||||
if (MessagelistUserStore.threadUid()
|
||||
&& MessagelistUserStore.length
|
||||
&& MessagelistUserStore.find(item => item?.deleted() && item.uid == MessagelistUserStore.threadUid())
|
||||
) {
|
||||
const message = MessagelistUserStore.find(item => item && !item.deleted());
|
||||
if (!message) {
|
||||
if (1 < page) {
|
||||
setPage = page - 1;
|
||||
} else {
|
||||
MessagelistUserStore.threadUid(0);
|
||||
setPage = MessagelistUserStore.pageBeforeThread();
|
||||
}
|
||||
} else if (MessagelistUserStore.threadUid() != message.uid) {
|
||||
MessagelistUserStore.threadUid(message.uid);
|
||||
setPage = page;
|
||||
}
|
||||
}
|
||||
if (setPage) {
|
||||
MessagelistUserStore.page(setPage);
|
||||
replaceHash(
|
||||
|
|
@ -507,7 +525,6 @@ MessagelistUserStore.moveMessages = (
|
|||
currentMessage = null;
|
||||
MessageUserStore.message(null);
|
||||
}
|
||||
item.deleted(true);
|
||||
MessagelistUserStore.remove(item);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,38 +10,48 @@ import Remote from 'Remote/User/Fetch';
|
|||
|
||||
import { showScreenPopup } from 'Knoin/Knoin';
|
||||
import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey';
|
||||
import { AskPopupView } from 'View/Popup/Ask';
|
||||
|
||||
import { Passphrases } from 'Storage/Passphrases';
|
||||
|
||||
import { baseCollator } from 'Common/Translator';
|
||||
|
||||
const
|
||||
loaded = () => !!window.openpgp,
|
||||
|
||||
findOpenPGPKey = (keys, query/*, sign*/) =>
|
||||
keys.find(key =>
|
||||
key.emails.includes(query) || query == key.id || query == key.fingerprint
|
||||
key.for(query) || query == key.id || query == key.fingerprint
|
||||
),
|
||||
|
||||
decryptKey = async (privateKey, btnTxt = 'LABEL_SIGN') => {
|
||||
decryptKey = async (privateKey, btnTxt = 'SIGN') => {
|
||||
if (privateKey.key.isDecrypted()) {
|
||||
return privateKey.key;
|
||||
}
|
||||
const key = privateKey.id,
|
||||
pass = Passphrases.has(key)
|
||||
? {password:Passphrases.get(key), remember:false}
|
||||
: await AskPopupView.password(
|
||||
'OpenPGP.js key<br>' + key + ' ' + privateKey.emails[0],
|
||||
'OPENPGP/'+btnTxt
|
||||
);
|
||||
pass = await Passphrases.ask(privateKey,
|
||||
'OpenPGP.js key<br>' + key + ' ' + privateKey.emails[0],
|
||||
'CRYPTO/'+btnTxt
|
||||
);
|
||||
if (pass) {
|
||||
const passphrase = pass.password,
|
||||
result = await openpgp.decryptKey({
|
||||
privateKey: privateKey.key,
|
||||
passphrase
|
||||
});
|
||||
result && pass.remember && Passphrases.set(key, passphrase);
|
||||
result && pass.remember && Passphrases.handle(privateKey, passphrase);
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
collator = baseCollator(),
|
||||
sort = keys => keys.sort(
|
||||
// (a, b) => collator.compare(a.emails[0], b.emails[0]) || collator.compare(a.fingerprint, b.fingerprint)
|
||||
(a, b) => collator.compare(a.emails[0], b.emails[0]) || collator.compare(a.id, b.id)
|
||||
),
|
||||
dedup = keys => sort((keys || [])
|
||||
.filter((v, i, a) => a.findIndex(entry => entry.fingerprint == v.fingerprint) === i)
|
||||
),
|
||||
|
||||
/**
|
||||
* OpenPGP.js v5 removed the localStorage (keyring)
|
||||
* This should be compatible with the old OpenPGP.js v2
|
||||
|
|
@ -53,9 +63,11 @@ const
|
|||
let keys = [], key,
|
||||
armoredKeys = JSON.parse(storage.getItem(itemname)),
|
||||
i = arrayLength(armoredKeys);
|
||||
while (i--) {
|
||||
while (i--) try {
|
||||
key = await openpgp.readKey({armoredKey:armoredKeys[i]});
|
||||
key.err || keys.push(new OpenPgpKeyModel(armoredKeys[i], key));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return keys;
|
||||
},
|
||||
|
|
@ -71,15 +83,11 @@ const
|
|||
class OpenPgpKeyModel {
|
||||
constructor(armor, key) {
|
||||
this.key = key;
|
||||
const aEmails = [];
|
||||
if (key.users) {
|
||||
key.users.forEach(user => user.userID.email && aEmails.push(user.userID.email));
|
||||
}
|
||||
this.id = key.getKeyID().toHex().toUpperCase();
|
||||
this.fingerprint = key.getFingerprint();
|
||||
this.can_encrypt = !!key.getEncryptionKey();
|
||||
this.can_sign = !!key.getSigningKey();
|
||||
this.emails = aEmails;
|
||||
this.emails = key.users.map(user => IDN.toASCII(user.userID.email)).filter(email => email);
|
||||
this.armor = armor;
|
||||
this.askDelete = ko.observable(false);
|
||||
this.openForDeletion = ko.observable(null).askDeleteHelper();
|
||||
|
|
@ -87,6 +95,18 @@ class OpenPgpKeyModel {
|
|||
// key.getPrimaryUser()
|
||||
}
|
||||
|
||||
/*
|
||||
get id() { return this.key.getKeyID().toHex().toUpperCase(); }
|
||||
get fingerprint() { return this.key.getFingerprint(); }
|
||||
get can_encrypt() { return !!this.key.getEncryptionKey(); }
|
||||
get can_sign() { return !!this.key.getSigningKey(); }
|
||||
get emails() { return this.key.users.map(user => IDN.toASCII(user.userID.email)).filter(email => email); }
|
||||
get armor() { return this.key.armor(); }
|
||||
*/
|
||||
for(email) {
|
||||
return this.emails.includes(IDN.toASCII(email));
|
||||
}
|
||||
|
||||
view() {
|
||||
showScreenPopup(OpenPgpKeyPopupView, [this]);
|
||||
}
|
||||
|
|
@ -116,37 +136,62 @@ export const OpenPGPUserStore = new class {
|
|||
}
|
||||
|
||||
loadKeyrings() {
|
||||
if (window.openpgp) {
|
||||
loadOpenPgpKeys(publicKeysItem).then(keys => {
|
||||
this.publicKeys(keys || []);
|
||||
if (loaded()) {
|
||||
loadOpenPgpKeys(publicKeysItem)
|
||||
.then(keys => {
|
||||
this.publicKeys(dedup(keys));
|
||||
console.log('openpgp.js public keys loaded');
|
||||
});
|
||||
loadOpenPgpKeys(privateKeysItem).then(keys => {
|
||||
this.privateKeys(keys || [])
|
||||
console.log('openpgp.js private keys loaded');
|
||||
})
|
||||
.finally(() => {
|
||||
loadOpenPgpKeys(privateKeysItem)
|
||||
.then(keys => {
|
||||
this.privateKeys(dedup(keys));
|
||||
console.log('openpgp.js private keys loaded');
|
||||
})
|
||||
.finally(() => {
|
||||
/*SettingsGet('loadBackupKeys') && */this.loadBackupKeys();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
loadBackupKeys() {
|
||||
Remote.request('GetPGPKeys',
|
||||
(iError, oData) => !iError && oData.Result && this.importKeys(oData.Result)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isSupported() {
|
||||
return !!window.openpgp;
|
||||
return loaded();
|
||||
}
|
||||
|
||||
importKey(armoredKey) {
|
||||
window.openpgp && openpgp.readKey({armoredKey:armoredKey}).then(key => {
|
||||
if (!key.err) {
|
||||
if (key.isPrivate()) {
|
||||
this.privateKeys.push(new OpenPgpKeyModel(armoredKey, key));
|
||||
storeOpenPgpKeys(this.privateKeys, privateKeysItem);
|
||||
} else {
|
||||
this.publicKeys.push(new OpenPgpKeyModel(armoredKey, key));
|
||||
storeOpenPgpKeys(this.publicKeys, publicKeysItem);
|
||||
this.importKeys([armoredKey]);
|
||||
}
|
||||
|
||||
async importKeys(keys) {
|
||||
if (loaded()) {
|
||||
const privateKeys = this.privateKeys(),
|
||||
publicKeys = this.publicKeys();
|
||||
for (const armoredKey of keys) try {
|
||||
let key = await openpgp.readKey({armoredKey:armoredKey});
|
||||
if (!key.err) {
|
||||
key = new OpenPgpKeyModel(armoredKey, key);
|
||||
const keys = key.key.isPrivate() ? privateKeys : publicKeys;
|
||||
keys.find(entry => entry.fingerprint == key.fingerprint)
|
||||
|| keys.push(key);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e, armoredKey);
|
||||
}
|
||||
});
|
||||
this.privateKeys(sort(privateKeys));
|
||||
this.publicKeys(sort(publicKeys));
|
||||
storeOpenPgpKeys(privateKeys, privateKeysItem);
|
||||
storeOpenPgpKeys(publicKeys, publicKeysItem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -155,7 +200,7 @@ export const OpenPGPUserStore = new class {
|
|||
keyPair.revocationCertificate
|
||||
*/
|
||||
storeKeyPair(keyPair) {
|
||||
if (window.openpgp) {
|
||||
if (loaded()) {
|
||||
openpgp.readKey({armoredKey:keyPair.publicKey}).then(key => {
|
||||
this.publicKeys.push(new OpenPgpKeyModel(keyPair.publicKey, key));
|
||||
storeOpenPgpKeys(this.publicKeys, publicKeysItem);
|
||||
|
|
@ -173,7 +218,7 @@ export const OpenPGPUserStore = new class {
|
|||
hasPublicKeyForEmails(recipients) {
|
||||
const count = recipients.length,
|
||||
length = count ? recipients.filter(email =>
|
||||
this.publicKeys().find(key => key.emails.includes(email))
|
||||
this.publicKeys().find(key => key.for(email))
|
||||
).length : 0;
|
||||
return length && length === count;
|
||||
}
|
||||
|
|
@ -201,7 +246,7 @@ export const OpenPGPUserStore = new class {
|
|||
}
|
||||
}
|
||||
if (privateKey) try {
|
||||
const decryptedKey = await decryptKey(privateKey, 'BUTTON_DECRYPT');
|
||||
const decryptedKey = await decryptKey(privateKey, 'DECRYPT');
|
||||
if (decryptedKey) {
|
||||
const publicKey = findOpenPGPKey(this.publicKeys, sender/*, sign*/);
|
||||
return await openpgp.decrypt({
|
||||
|
|
@ -222,15 +267,15 @@ export const OpenPGPUserStore = new class {
|
|||
* https://docs.openpgpjs.org/#sign-and-verify-cleartext-messages
|
||||
*/
|
||||
async verify(message) {
|
||||
const data = message.pgpSigned(), // { bodyPartId: "1", sigPartId: "2", micAlg: "pgp-sha256" }
|
||||
publicKey = this.publicKeys().find(key => key.emails.includes(message.from[0].email));
|
||||
const data = message.pgpSigned(), // { partId: "1", sigPartId: "2", micAlg: "pgp-sha256" }
|
||||
publicKey = this.publicKeys().find(key => key.for(message.from[0].email));
|
||||
if (data && publicKey) {
|
||||
data.folder = message.folder;
|
||||
data.uid = message.uid;
|
||||
data.tryGnuPG = 0;
|
||||
let response;
|
||||
if (data.sigPartId) {
|
||||
response = await Remote.post('MessagePgpVerify', null, data);
|
||||
response = await Remote.post('PgpVerifyMessage', null, data);
|
||||
} else if (data.bodyPart) {
|
||||
// MimePart
|
||||
response = { Result: { text: data.bodyPart.raw, signature: data.sigPart.body } };
|
||||
|
|
@ -282,7 +327,7 @@ export const OpenPGPUserStore = new class {
|
|||
*/
|
||||
async encrypt(text, recipients, signPrivateKey) {
|
||||
const count = recipients.length;
|
||||
recipients = recipients.map(email => this.publicKeys().find(key => key.emails.includes(email))).filter(key => key);
|
||||
recipients = recipients.map(email => this.publicKeys().find(key => key.for(email))).filter(key => key);
|
||||
if (count === recipients.length) {
|
||||
if (signPrivateKey) {
|
||||
signPrivateKey = await decryptKey(signPrivateKey);
|
||||
|
|
|
|||
|
|
@ -8,14 +8,16 @@ import { SettingsCapa, SettingsGet } from 'Common/Globals';
|
|||
|
||||
import { GnuPGUserStore } from 'Stores/User/GnuPG';
|
||||
import { OpenPGPUserStore } from 'Stores/User/OpenPGP';
|
||||
import { MailvelopeUserStore } from 'Stores/User/Mailvelope';
|
||||
|
||||
// https://mailvelope.github.io/mailvelope/Keyring.html
|
||||
let mailvelopeKeyring = null;
|
||||
import Remote from 'Remote/User/Fetch';
|
||||
|
||||
export const
|
||||
BEGIN_PGP_MESSAGE = '-----BEGIN PGP MESSAGE-----',
|
||||
// BEGIN_PGP_SIGNATURE = '-----BEGIN PGP SIGNATURE-----',
|
||||
// BEGIN_PGP_SIGNED = '-----BEGIN PGP SIGNED MESSAGE-----',
|
||||
// BEGIN_PGP_PUBLIC_KEY = '-----BEGIN PGP PUBLIC KEY BLOCK-----',
|
||||
// END_PGP_PUBLIC_KEY = '-----END PGP PUBLIC KEY BLOCK-----',
|
||||
|
||||
PgpUserStore = new class {
|
||||
init() {
|
||||
|
|
@ -33,32 +35,9 @@ export const
|
|||
}
|
||||
|
||||
loadKeyrings(identifier) {
|
||||
identifier = identifier || SettingsGet('Email');
|
||||
if (window.mailvelope) {
|
||||
const fn = keyring => {
|
||||
mailvelopeKeyring = keyring;
|
||||
console.log('mailvelope ready');
|
||||
};
|
||||
mailvelope.getKeyring().then(fn, err => {
|
||||
if (identifier) {
|
||||
// attempt to create a new keyring for this app/user
|
||||
mailvelope.createKeyring(identifier).then(fn, err => console.error(err));
|
||||
} else {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
addEventListener('mailvelope-disconnect', event => {
|
||||
alert('Mailvelope is updated to version ' + event.detail.version + '. Reload page');
|
||||
}, false);
|
||||
} else {
|
||||
addEventListener('mailvelope', () => this.loadKeyrings(identifier));
|
||||
}
|
||||
|
||||
MailvelopeUserStore.loadKeyring(identifier);
|
||||
OpenPGPUserStore.loadKeyrings();
|
||||
|
||||
if (SettingsCapa('GnuPG')) {
|
||||
GnuPGUserStore.loadKeyrings();
|
||||
}
|
||||
GnuPGUserStore.loadKeyrings();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -75,21 +54,28 @@ export const
|
|||
return 0 === text.trim().indexOf(BEGIN_PGP_MESSAGE);
|
||||
}
|
||||
|
||||
async mailvelopeHasPublicKeyForEmails(recipients) {
|
||||
const
|
||||
mailvelope = mailvelopeKeyring && await mailvelopeKeyring.validKeyForAddress(recipients)
|
||||
/*.then(LookupResult => Object.entries(LookupResult))*/,
|
||||
entries = mailvelope && Object.entries(mailvelope);
|
||||
return entries && entries.filter(value => value[1]).length === recipients.length;
|
||||
importKey(key, gnuPG, backup) {
|
||||
if (gnuPG || backup) {
|
||||
Remote.request('PgpImportKey',
|
||||
(iError, oData) => {
|
||||
if (gnuPG && oData?.Result/* && (oData.Result.imported || oData.Result.secretimported)*/) {
|
||||
GnuPGUserStore.loadKeyrings();
|
||||
}
|
||||
iError && alert(oData.message);
|
||||
}, {
|
||||
key, gnuPG, backup
|
||||
}
|
||||
);
|
||||
}
|
||||
OpenPGPUserStore.importKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if verifying/encrypting a message is possible with given email addresses.
|
||||
* Returns the first library that can.
|
||||
*/
|
||||
async hasPublicKeyForEmails(recipients) {
|
||||
const count = recipients.length;
|
||||
if (count) {
|
||||
hasPublicKeyForEmails(recipients) {
|
||||
if (recipients.length) {
|
||||
if (GnuPGUserStore.hasPublicKeyForEmails(recipients)) {
|
||||
return 'gnupg';
|
||||
}
|
||||
|
|
@ -100,40 +86,15 @@ export const
|
|||
return false;
|
||||
}
|
||||
|
||||
async getMailvelopePrivateKeyFor(email/*, sign*/) {
|
||||
if (mailvelopeKeyring && await mailvelopeKeyring.hasPrivateKey({email:email})) {
|
||||
return ['mailvelope', email];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if signing a message is possible with given email address.
|
||||
* Returns the first library that can.
|
||||
*/
|
||||
async getKeyForSigning(email) {
|
||||
let key = OpenPGPUserStore.getPrivateKeyFor(email, 1);
|
||||
if (key) {
|
||||
return ['openpgp', key];
|
||||
}
|
||||
|
||||
key = GnuPGUserStore.getPrivateKeyFor(email, 1);
|
||||
if (key) {
|
||||
return ['gnupg', key];
|
||||
}
|
||||
|
||||
// return await this.getMailvelopePrivateKeyFor(email, 1);
|
||||
}
|
||||
|
||||
async decrypt(message) {
|
||||
const sender = message.from[0].email,
|
||||
armoredText = message.plain();
|
||||
const armoredText = message.plain();
|
||||
if (!this.isEncrypted(armoredText)) {
|
||||
throw Error('Not armored text');
|
||||
}
|
||||
|
||||
// Try OpenPGP.js
|
||||
if (OpenPGPUserStore.isSupported()) {
|
||||
const sender = message.from[0].email;
|
||||
let result = await OpenPGPUserStore.decrypt(armoredText, sender);
|
||||
if (result) {
|
||||
return result;
|
||||
|
|
@ -141,68 +102,20 @@ export const
|
|||
}
|
||||
|
||||
// Try Mailvelope (does not support inline images)
|
||||
if (mailvelopeKeyring) {
|
||||
try {
|
||||
let emails = [...message.from,...message.to,...message.cc].validUnique(),
|
||||
i = emails.length;
|
||||
while (i--) {
|
||||
if (await this.getMailvelopePrivateKeyFor(emails[i].email)) {
|
||||
/**
|
||||
* https://mailvelope.github.io/mailvelope/Mailvelope.html#createEncryptedFormContainer
|
||||
* Creates an iframe to display an encrypted form
|
||||
*/
|
||||
// mailvelope.createEncryptedFormContainer('#mailvelope-form');
|
||||
/**
|
||||
* https://mailvelope.github.io/mailvelope/Mailvelope.html#createDisplayContainer
|
||||
* Creates an iframe to display the decrypted content of the encrypted mail.
|
||||
*/
|
||||
const body = message.body;
|
||||
body.textContent = '';
|
||||
let result = await mailvelope.createDisplayContainer(
|
||||
'#'+body.id,
|
||||
armoredText,
|
||||
mailvelopeKeyring,
|
||||
{
|
||||
senderAddress: sender
|
||||
// emails[i].email
|
||||
}
|
||||
);
|
||||
if (result) {
|
||||
if (result.error?.message) {
|
||||
if ('PWD_DIALOG_CANCEL' !== result.error.code) {
|
||||
alert(result.error.code + ': ' + result.error.message);
|
||||
}
|
||||
} else {
|
||||
body.classList.add('mailvelope');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Now try GnuPG
|
||||
return GnuPGUserStore.decrypt(message);
|
||||
return (await MailvelopeUserStore.decrypt(message))
|
||||
// Or try GnuPG
|
||||
|| GnuPGUserStore.decrypt(message);
|
||||
}
|
||||
|
||||
async verify(message) {
|
||||
const signed = message.pgpSigned();
|
||||
const signed = message.pgpSigned(),
|
||||
sender = message.from[0].email;
|
||||
if (signed) {
|
||||
const sender = message.from[0].email,
|
||||
gnupg = GnuPGUserStore.hasPublicKeyForEmails([sender]),
|
||||
openpgp = OpenPGPUserStore.hasPublicKeyForEmails([sender]);
|
||||
// Detached signature use GnuPG first, else we must download whole message
|
||||
if (gnupg && signed.sigPartId) {
|
||||
return GnuPGUserStore.verify(message);
|
||||
}
|
||||
if (openpgp) {
|
||||
// OpenPGP only when inline, else we must download the whole message
|
||||
if (!signed.sigPartId && OpenPGPUserStore.hasPublicKeyForEmails([sender])) {
|
||||
return OpenPGPUserStore.verify(message);
|
||||
}
|
||||
if (gnupg) {
|
||||
if (GnuPGUserStore.hasPublicKeyForEmails([sender])) {
|
||||
return GnuPGUserStore.verify(message);
|
||||
}
|
||||
// Mailvelope can't
|
||||
|
|
@ -210,29 +123,23 @@ export const
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns headers that should be added to an outgoing email.
|
||||
* So far this is only the autocrypt header.
|
||||
*/
|
||||
/*
|
||||
mailvelopeKeyring.additionalHeadersForOutgoingEmail(headers)
|
||||
mailvelopeKeyring.addSyncHandler(syncHandlerObj)
|
||||
mailvelopeKeyring.createKeyBackupContainer(selector, options)
|
||||
mailvelopeKeyring.createKeyGenContainer(selector, {
|
||||
// userIds: [],
|
||||
keySize: 4096
|
||||
})
|
||||
|
||||
mailvelopeKeyring.exportOwnPublicKey(emailAddr).then(<AsciiArmored, Error>)
|
||||
mailvelopeKeyring.importPublicKey(armored)
|
||||
|
||||
// https://mailvelope.github.io/mailvelope/global.html#SyncHandlerObject
|
||||
mailvelopeKeyring.addSyncHandler({
|
||||
uploadSync
|
||||
downloadSync
|
||||
backup
|
||||
restore
|
||||
});
|
||||
*/
|
||||
|
||||
getPublicKeyOfEmails(recipients) {
|
||||
if (recipients.length) {
|
||||
let result = {};
|
||||
recipients.forEach(email => {
|
||||
OpenPGPUserStore.publicKeys().forEach(key => {
|
||||
if (key.for(email)) {
|
||||
result[email] = key.armor;
|
||||
}
|
||||
});
|
||||
GnuPGUserStore.publicKeys.map(async key => {
|
||||
if (!result[email] && key.for(email)) {
|
||||
result[email] = await key.fetch();
|
||||
}
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
21
dev/Stores/User/SMime.js
Normal file
21
dev/Stores/User/SMime.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { addObservablesTo, koArrayWithDestroy } from 'External/ko';
|
||||
import { baseCollator } from 'Common/Translator';
|
||||
import Remote from 'Remote/User/Fetch';
|
||||
|
||||
export const SMimeUserStore = koArrayWithDestroy();
|
||||
|
||||
addObservablesTo(SMimeUserStore, {
|
||||
loading: false
|
||||
});
|
||||
|
||||
SMimeUserStore.loadCertificates = () => {
|
||||
SMimeUserStore([]);
|
||||
SMimeUserStore.loading(true);
|
||||
Remote.request('SMimeGetCertificates', (iError, oData) => {
|
||||
SMimeUserStore.loading(false);
|
||||
const collator = baseCollator();
|
||||
iError || SMimeUserStore(oData.Result.sort(
|
||||
(a, b) => collator.compare(a.emailAddress, b.emailAddress) || (b.validTo_time_t - a.validTo_time_t)
|
||||
));
|
||||
});
|
||||
};
|
||||
|
|
@ -29,12 +29,16 @@ export const SettingsUserStore = new class {
|
|||
showNextMessage: 0,
|
||||
allowDraftAutosave: 1,
|
||||
useThreads: 0,
|
||||
threadAlgorithm: '',
|
||||
replySameFolder: 0,
|
||||
hideUnsubscribed: 0,
|
||||
hideDeleted: 1,
|
||||
unhideKolabFolders: 0,
|
||||
autoLogout: 0,
|
||||
keyPassForget: 15,
|
||||
showUnreadCount: 0,
|
||||
messageNewWindow: 0,
|
||||
messageReadAuto: 0,
|
||||
|
||||
requestReadReceipt: 0,
|
||||
requestDsn: 0,
|
||||
|
|
@ -45,6 +49,7 @@ export const SettingsUserStore = new class {
|
|||
|
||||
layout: 1,
|
||||
editorDefaultType: 'Html',
|
||||
editorWysiwyg: 'Squire',
|
||||
msgDefaultAction: 1
|
||||
});
|
||||
|
||||
|
|
@ -76,41 +81,83 @@ export const SettingsUserStore = new class {
|
|||
|
||||
init() {
|
||||
const self = this;
|
||||
self.editorDefaultType(SettingsGet('EditorDefaultType'));
|
||||
|
||||
[
|
||||
'EditorDefaultType',
|
||||
'editorWysiwyg',
|
||||
'messageNewWindow',
|
||||
'messageReadAuto',
|
||||
'MsgDefaultAction',
|
||||
'ViewHTML',
|
||||
'ViewImages',
|
||||
'ViewImagesWhitelist',
|
||||
'RemoveColors',
|
||||
'AllowStyles',
|
||||
'CollapseBlockquotes',
|
||||
'MaxBlockquotesLevel',
|
||||
'ListInlineAttachments',
|
||||
'simpleAttachmentsList',
|
||||
'UseCheckboxesInList',
|
||||
'listGrouped',
|
||||
'showNextMessage',
|
||||
'AllowDraftAutosave',
|
||||
'useThreads',
|
||||
'threadAlgorithm',
|
||||
'ReplySameFolder',
|
||||
'HideUnsubscribed',
|
||||
'HideDeleted',
|
||||
'ShowUnreadCount',
|
||||
'UnhideKolabFolders',
|
||||
'requestReadReceipt',
|
||||
'requestDsn',
|
||||
'requireTLS',
|
||||
'pgpSign',
|
||||
'pgpEncrypt',
|
||||
'allowSpellcheck'
|
||||
/*
|
||||
'MessagesPerPage',
|
||||
'MessageReadDelay',
|
||||
'SoundNotification',
|
||||
'NotificationSound',
|
||||
'DesktopNotifications',
|
||||
'Layout',
|
||||
'AutoLogout',
|
||||
'ContactsAutosave',
|
||||
'contactsAllowed',
|
||||
'CheckMailInterval',
|
||||
'SentFolder',
|
||||
'DraftsFolder',
|
||||
'JunkFolder',
|
||||
'TrashFolder',
|
||||
'ArchiveFolder',
|
||||
'hourCycle',
|
||||
'Resizer4Width',
|
||||
'Resizer5Width',
|
||||
'Resizer5Height',
|
||||
'fontSansSerif',
|
||||
'fontSerif',
|
||||
'fontMono',
|
||||
'userBackgroundName',
|
||||
'userBackgroundHash',
|
||||
'autoVerifySignatures',
|
||||
'allowLanguagesOnSettings',
|
||||
'attachmentLimit',
|
||||
'Theme',
|
||||
'language',
|
||||
'clientLanguage',
|
||||
'StaticLibsJs',
|
||||
*/
|
||||
].forEach(name => {
|
||||
let value = SettingsGet(name);
|
||||
name = name[0].toLowerCase() + name.slice(1);
|
||||
self[name](value);
|
||||
});
|
||||
|
||||
self.layout(pInt(SettingsGet('Layout')));
|
||||
self.messagesPerPage(pInt(SettingsGet('MessagesPerPage')));
|
||||
self.checkMailInterval(pInt(SettingsGet('CheckMailInterval')));
|
||||
self.messageReadDelay(pInt(SettingsGet('MessageReadDelay')));
|
||||
self.autoLogout(pInt(SettingsGet('AutoLogout')));
|
||||
self.msgDefaultAction(SettingsGet('MsgDefaultAction'));
|
||||
|
||||
self.viewHTML(SettingsGet('ViewHTML'));
|
||||
self.viewImages(SettingsGet('ViewImages'));
|
||||
self.viewImagesWhitelist(SettingsGet('ViewImagesWhitelist'));
|
||||
self.removeColors(SettingsGet('RemoveColors'));
|
||||
self.allowStyles(SettingsGet('AllowStyles'));
|
||||
self.collapseBlockquotes(SettingsGet('CollapseBlockquotes'));
|
||||
self.maxBlockquotesLevel(SettingsGet('MaxBlockquotesLevel'));
|
||||
self.listInlineAttachments(SettingsGet('ListInlineAttachments'));
|
||||
self.simpleAttachmentsList(SettingsGet('simpleAttachmentsList'));
|
||||
self.useCheckboxesInList(SettingsGet('UseCheckboxesInList'));
|
||||
self.listGrouped(SettingsGet('listGrouped'));
|
||||
self.showNextMessage(SettingsGet('showNextMessage'));
|
||||
self.allowDraftAutosave(SettingsGet('AllowDraftAutosave'));
|
||||
self.useThreads(SettingsGet('UseThreads'));
|
||||
self.replySameFolder(SettingsGet('ReplySameFolder'));
|
||||
|
||||
self.hideUnsubscribed(SettingsGet('HideUnsubscribed'));
|
||||
self.hideDeleted(SettingsGet('HideDeleted'));
|
||||
self.showUnreadCount(SettingsGet('ShowUnreadCount'));
|
||||
self.unhideKolabFolders(SettingsGet('UnhideKolabFolders'));
|
||||
|
||||
self.requestReadReceipt(SettingsGet('requestReadReceipt'));
|
||||
self.requestDsn(SettingsGet('requestDsn'));
|
||||
self.requireTLS(SettingsGet('requireTLS'));
|
||||
self.pgpSign(SettingsGet('pgpSign'));
|
||||
self.pgpEncrypt(SettingsGet('pgpEncrypt'));
|
||||
self.allowSpellcheck(SettingsGet('allowSpellcheck'));
|
||||
self.keyPassForget(pInt(SettingsGet('keyPassForget')));
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue