Skip to content

Commit

Permalink
Upgrade keytar because Linux API seems asynchronous
Browse files Browse the repository at this point in the history
  • Loading branch information
bengotow committed Oct 9, 2017
1 parent f780877 commit 5828090
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 63 deletions.
15 changes: 11 additions & 4 deletions app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"jasmine-reporters": "1.x.x",
"jasmine-tagged": "^1.1.2",
"juice": "^1.4",
"keytar": "3.0.0",
"keytar": "4.0.4",
"less-cache": "0.21",
"lru-cache": "^4.0.1",
"marked": "^0.3",
Expand Down
4 changes: 2 additions & 2 deletions app/src/flux/mailsync-bridge.es6
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,8 @@ export default class MailsyncBridge {

// Private

_launchClient(account, { force } = {}) {
const fullAccountJSON = KeyManager.insertAccountSecrets(account).toJSON();
async _launchClient(account, { force } = {}) {
const fullAccountJSON = (await KeyManager.insertAccountSecrets(account)).toJSON();
const identity = IdentityStore.identity();
const id = account.id;

Expand Down
4 changes: 2 additions & 2 deletions app/src/flux/stores/account-store.es6
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,14 @@ class AccountStore extends MailspringStore {
this._save();
};

addAccount = account => {
addAccount = async account => {
if (!account.emailAddress || !account.provider || !(account instanceof Account)) {
throw new Error(`Returned account data is invalid: ${JSON.stringify(account)}`);
}

// send the account JSON and cloud token to the KeyManager,
// which gives us back a version with no secrets.
const cleanAccount = KeyManager.extractAccountSecrets(account);
const cleanAccount = await KeyManager.extractAccountSecrets(account);

this._loadAccounts();

Expand Down
4 changes: 2 additions & 2 deletions app/src/flux/stores/identity-store.es6
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ class IdentityStore extends MailspringStore {
* When the identity changes in the database, update our local store
* cache and set the token from the keychain.
*/
_onIdentityChanged = () => {
_onIdentityChanged = async () => {
this._identity = AppEnv.config.get('identity') || {};
this._identity.token = KeyManager.getPassword(KEYCHAIN_NAME);
this._identity.token = await KeyManager.getPassword(KEYCHAIN_NAME);
this.trigger();
};

Expand Down
107 changes: 55 additions & 52 deletions app/src/key-manager.es6
Original file line number Diff line number Diff line change
Expand Up @@ -11,94 +11,97 @@ import keytar from 'keytar';
*/
class KeyManager {
constructor() {
this.SERVICE_NAME = 'Mailspring';
if (AppEnv.inDevMode()) {
this.SERVICE_NAME = 'Mailspring Dev';
}
this.SERVICE_NAME = AppEnv.inDevMode() ? 'Mailspring Dev' : 'Mailspring';
this.KEY_NAME = 'Mailspring Keys';
}

deleteAccountSecrets(account) {
this._try(() => {
const keys = this._getKeyHash();
async deleteAccountSecrets(account) {
try {
const keys = await this._getKeyHash();
delete keys[`${account.emailAddress}-imap`];
delete keys[`${account.emailAddress}-smtp`];
delete keys[`${account.emailAddress}-refresh-token`];
return this._writeKeyHash(keys);
});
await this._writeKeyHash(keys);
} catch (err) {
this._handleError(err);
}
}

extractAccountSecrets(account) {
async extractAccountSecrets(account) {
try {
const keys = await this._getKeyHash();
keys[`${account.emailAddress}-imap`] = account.settings.imap_password;
keys[`${account.emailAddress}-smtp`] = account.settings.smtp_password;
keys[`${account.emailAddress}-refresh-token`] = account.settings.refresh_token;
await this._writeKeyHash(keys);
} catch (err) {
this._handleError(err);
}
const next = account.clone();
this._try(() => {
const keys = this._getKeyHash();
keys[`${account.emailAddress}-imap`] = next.settings.imap_password;
delete next.settings.imap_password;
keys[`${account.emailAddress}-smtp`] = next.settings.smtp_password;
delete next.settings.smtp_password;
keys[`${account.emailAddress}-refresh-token`] = next.settings.refresh_token;
delete next.settings.refresh_token;
return this._writeKeyHash(keys);
});
delete next.settings.imap_password;
delete next.settings.smtp_password;
delete next.settings.refresh_token;
return next;
}

insertAccountSecrets(account) {
async insertAccountSecrets(account) {
const next = account.clone();
const keys = this._getKeyHash();
const keys = await this._getKeyHash();
next.settings.imap_password = keys[`${account.emailAddress}-imap`];
next.settings.smtp_password = keys[`${account.emailAddress}-smtp`];
next.settings.refresh_token = keys[`${account.emailAddress}-refresh-token`];
return next;
}

replacePassword(keyName, newVal) {
this._try(() => {
const keys = this._getKeyHash();
async replacePassword(keyName, newVal) {
try {
const keys = await this._getKeyHash();
keys[keyName] = newVal;
return this._writeKeyHash(keys);
});
await this._writeKeyHash(keys);
} catch (err) {
this._handleError(err);
}
}

deletePassword(keyName) {
this._try(() => {
const keys = this._getKeyHash();
async deletePassword(keyName) {
try {
const keys = await this._getKeyHash();
delete keys[keyName];
return this._writeKeyHash(keys);
});
await this._writeKeyHash(keys);
} catch (err) {
this._handleError(err);
}
}

getPassword(keyName) {
const keys = this._getKeyHash();
return keys[keyName];
async getPassword(keyName) {
try {
const keys = await this._getKeyHash();
return keys[keyName];
} catch (err) {
this._handleError(err);
}
}

_getKeyHash() {
const raw = keytar.getPassword(this.SERVICE_NAME, this.KEY_NAME) || '{}';
async _getKeyHash() {
const raw = (await keytar.getPassword(this.SERVICE_NAME, this.KEY_NAME)) || '{}';
try {
return JSON.parse(raw);
} catch (err) {
return {};
}
}

_writeKeyHash(keys) {
// returns true if successful
return keytar.replacePassword(this.SERVICE_NAME, this.KEY_NAME, JSON.stringify(keys));
async _writeKeyHash(keys) {
await keytar.setPassword(this.SERVICE_NAME, this.KEY_NAME, JSON.stringify(keys));
}

_try(fn) {
const ERR_MSG =
"We couldn't store your password securely! For more information, visit http://support.getmailspring.com/hc/en-us/articles/115001875571";
try {
if (!fn()) {
remote.dialog.showErrorBox('Password Management Error', ERR_MSG);
AppEnv.reportError(new Error(`Password Management Error: ${ERR_MSG}`));
}
} catch (err) {
remote.dialog.showErrorBox('Password Management Error', ERR_MSG);
AppEnv.reportError(err);
}
_handleError(err) {
remote.dialog.showErrorBox(
'Password Management Error',
"We couldn't store your password securely! For more information, visit http://support.getmailspring.com/hc/en-us/articles/115001875571"
);
AppEnv.reportError(err);
}
}

export default new KeyManager();

0 comments on commit 5828090

Please sign in to comment.