mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-10-06 03:14:39 +08:00
fix(config): Move account tokens to system keychain
Summary: This diff also adds an account version number to the config so that the AccountStore can tell whether it should reload accounts (depending on whether it was the instance making tthe changes.) This diff also fixes a tiny issue where un-opened composers threw an exception if you changed accounts. Test Plan: New tests Reviewers: evan, drew, juan Reviewed By: juan Subscribers: juan Differential Revision: https://phab.nylas.com/D2726
This commit is contained in:
parent
dbf3610c88
commit
734a52aa6a
7 changed files with 120 additions and 69 deletions
|
@ -1,7 +1,6 @@
|
||||||
Package: <%= name %>
|
Package: <%= name %>
|
||||||
Version: <%= version %>
|
Version: <%= version %>
|
||||||
Depends: git, gconf2, gconf-service, libgtk2.0-0, libudev0 | libudev1, libgcrypt11 | libgcrypt20, libnotify4, libxtst6, libnss3, python, gvfs-bin, xdg-utils
|
Depends: libgnome-keyring0, gir1.2-gnomekeyring-1.0, git, gconf2, gconf-service, libgtk2.0-0, libudev0 | libudev1, libgcrypt11 | libgcrypt20, libnotify4, libxtst6, libnss3, python, gvfs-bin, xdg-utils
|
||||||
Suggests: libgnome-keyring0, gir1.2-gnomekeyring-1.0
|
|
||||||
Section: <%= section %>
|
Section: <%= section %>
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Architecture: <%= arch %>
|
Architecture: <%= arch %>
|
||||||
|
|
|
@ -2,10 +2,12 @@ Name: <%= name %>
|
||||||
Version: <%= version %>
|
Version: <%= version %>
|
||||||
Release: 0.1%{?dist}
|
Release: 0.1%{?dist}
|
||||||
Summary: <%= description %>
|
Summary: <%= description %>
|
||||||
License: Proprietary
|
License: GPLv3
|
||||||
URL: https://nylas.com/N1
|
URL: https://nylas.com/N1
|
||||||
AutoReqProv: no # Avoid libchromiumcontent.so missing dependency
|
AutoReqProv: no # Avoid libchromiumcontent.so missing dependency
|
||||||
|
|
||||||
|
requires: libgnome-keyring0, gir1.2-gnomekeyring-1.0
|
||||||
|
|
||||||
%description
|
%description
|
||||||
<%= description %>
|
<%= description %>
|
||||||
|
|
||||||
|
|
|
@ -561,6 +561,7 @@ class ComposerView extends React.Component
|
||||||
# When the account store changes, the From field may or may not still
|
# When the account store changes, the From field may or may not still
|
||||||
# be in scope. We need to make sure to update our enabled fields.
|
# be in scope. We need to make sure to update our enabled fields.
|
||||||
_onAccountStoreChanged: =>
|
_onAccountStoreChanged: =>
|
||||||
|
return unless @_proxy
|
||||||
accounts = @_getAccountsForSend()
|
accounts = @_getAccountsForSend()
|
||||||
enabledFields = if @_shouldShowFromField(@_proxy?.draft())
|
enabledFields = if @_shouldShowFromField(@_proxy?.draft())
|
||||||
@state.enabledFields.concat [Fields.From]
|
@state.enabledFields.concat [Fields.From]
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
"jasmine-tagged": "^1.1.2",
|
"jasmine-tagged": "^1.1.2",
|
||||||
"jquery": "^2.1.1",
|
"jquery": "^2.1.1",
|
||||||
"juice": "^1.4",
|
"juice": "^1.4",
|
||||||
|
"keytar": "^3.0.0",
|
||||||
"less-cache": "0.21",
|
"less-cache": "0.21",
|
||||||
"marked": "^0.3",
|
"marked": "^0.3",
|
||||||
"mkdirp": "^0.5",
|
"mkdirp": "^0.5",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
_ = require 'underscore'
|
_ = require 'underscore'
|
||||||
|
keytar = require 'keytar'
|
||||||
AccountStore = require '../../src/flux/stores/account-store'
|
AccountStore = require '../../src/flux/stores/account-store'
|
||||||
Account = require '../../src/flux/models/account'
|
Account = require '../../src/flux/models/account'
|
||||||
Actions = require '../../src/flux/actions'
|
Actions = require '../../src/flux/actions'
|
||||||
|
@ -7,37 +8,71 @@ describe "AccountStore", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@instance = null
|
@instance = null
|
||||||
@constructor = AccountStore.constructor
|
@constructor = AccountStore.constructor
|
||||||
|
@keys = {}
|
||||||
|
spyOn(keytar, 'getPassword').andCallFake (service, account) =>
|
||||||
|
@keys[account]
|
||||||
|
spyOn(keytar, 'deletePassword').andCallFake (service, account) =>
|
||||||
|
delete @keys[account]
|
||||||
|
spyOn(keytar, 'replacePassword').andCallFake (service, account, pass) =>
|
||||||
|
@keys[account] = pass
|
||||||
|
|
||||||
afterEach ->
|
afterEach ->
|
||||||
@instance.stopListeningToAll()
|
@instance.stopListeningToAll()
|
||||||
|
|
||||||
it "should initialize using data saved in config", ->
|
describe "initialization", ->
|
||||||
accounts =
|
beforeEach ->
|
||||||
[{
|
@configTokens = null
|
||||||
"id": "123",
|
@configVersion = 1
|
||||||
"client_id" : 'local-4f9d476a-c173',
|
@configAccounts =
|
||||||
"server_id" : '123',
|
[{
|
||||||
"email_address":"bengotow@gmail.com",
|
"id": "A",
|
||||||
"object":"account"
|
"client_id" : 'local-4f9d476a-c173',
|
||||||
"organization_unit": "label"
|
"server_id" : 'A',
|
||||||
},{
|
"email_address":"bengotow@gmail.com",
|
||||||
"id": "1234",
|
"object":"account"
|
||||||
"client_id" : 'local-4f9d476a-c175',
|
"organization_unit": "label"
|
||||||
"server_id" : '1234',
|
},{
|
||||||
"email_address":"ben@nylas.com",
|
"id": "B",
|
||||||
"object":"account"
|
"client_id" : 'local-4f9d476a-c175',
|
||||||
"organization_unit": "label"
|
"server_id" : 'B',
|
||||||
}]
|
"email_address":"ben@nylas.com",
|
||||||
|
"object":"account"
|
||||||
|
"organization_unit": "label"
|
||||||
|
}]
|
||||||
|
|
||||||
spyOn(NylasEnv.config, 'get').andCallFake (key) ->
|
spyOn(NylasEnv.config, 'set')
|
||||||
return accounts if key is 'nylas.accounts'
|
spyOn(NylasEnv.config, 'get').andCallFake (key) =>
|
||||||
return null
|
return @configAccounts if key is 'nylas.accounts'
|
||||||
@instance = new @constructor
|
return @configVersion if key is 'nylas.accountsVersion'
|
||||||
|
return @configTokens if key is 'nylas.accountTokens'
|
||||||
|
return null
|
||||||
|
|
||||||
expect(@instance.accounts()).toEqual([
|
it "should initialize the accounts and version from config", ->
|
||||||
(new Account).fromJSON(accounts[0]),
|
@instance = new @constructor
|
||||||
(new Account).fromJSON(accounts[1])
|
expect(@instance._version).toEqual(@configVersion)
|
||||||
])
|
expect(@instance.accounts()).toEqual([
|
||||||
|
(new Account).fromJSON(@configAccounts[0]),
|
||||||
|
(new Account).fromJSON(@configAccounts[1])
|
||||||
|
])
|
||||||
|
|
||||||
|
it "should initialize tokens from config, if present, save them to keytar, and remove them from config", ->
|
||||||
|
@configTokens = {'A': 'A-TOKEN'}
|
||||||
|
@instance = new @constructor
|
||||||
|
expect(@instance.tokenForAccountId('A')).toEqual('A-TOKEN')
|
||||||
|
expect(@instance.tokenForAccountId('B')).toEqual(undefined)
|
||||||
|
expect(keytar.replacePassword).toHaveBeenCalledWith('Nylas', 'bengotow@gmail.com', 'A-TOKEN')
|
||||||
|
expect(NylasEnv.config.set).toHaveBeenCalledWith('nylas.accountTokens', null)
|
||||||
|
|
||||||
|
it "should initialize tokens from keytar", ->
|
||||||
|
@configTokens = null
|
||||||
|
jasmine.unspy(keytar, 'getPassword')
|
||||||
|
spyOn(keytar, 'getPassword').andCallFake (service, account) =>
|
||||||
|
return 'A-TOKEN' if account is 'bengotow@gmail.com'
|
||||||
|
return 'B-TOKEN' if account is 'ben@nylas.com'
|
||||||
|
return null
|
||||||
|
@instance = new @constructor
|
||||||
|
expect(@instance.tokenForAccountId('A')).toEqual('A-TOKEN')
|
||||||
|
expect(@instance.tokenForAccountId('B')).toEqual('B-TOKEN')
|
||||||
|
|
||||||
describe "accountForEmail", ->
|
describe "accountForEmail", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
|
@ -58,33 +93,32 @@ describe "AccountStore", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
spyOn(NylasEnv.config, "set")
|
spyOn(NylasEnv.config, "set")
|
||||||
@json =
|
@json =
|
||||||
"id": "1234",
|
"id": "B",
|
||||||
"client_id" : 'local-4f9d476a-c175',
|
"client_id" : 'local-4f9d476a-c175',
|
||||||
"server_id" : '1234',
|
"server_id" : 'B',
|
||||||
"email_address":"ben@nylas.com",
|
"email_address":"ben@nylas.com",
|
||||||
"provider":"gmail",
|
"provider":"gmail",
|
||||||
"object":"account"
|
"object":"account"
|
||||||
"auth_token": "auth-123"
|
"auth_token": "B-NEW-TOKEN"
|
||||||
"organization_unit": "label"
|
"organization_unit": "label"
|
||||||
@instance = new @constructor
|
@instance = new @constructor
|
||||||
spyOn(Actions, 'focusDefaultMailboxPerspectiveForAccounts')
|
spyOn(Actions, 'focusDefaultMailboxPerspectiveForAccounts')
|
||||||
spyOn(@instance, "trigger")
|
spyOn(@instance, "trigger")
|
||||||
@instance.addAccountFromJSON(@json)
|
@instance.addAccountFromJSON(@json)
|
||||||
|
|
||||||
it "sets the tokens", ->
|
it "saves the token to keytar and to the loaded tokens cache", ->
|
||||||
expect(@instance._tokens["1234"]).toBe "auth-123"
|
expect(@instance._tokens["B"]).toBe("B-NEW-TOKEN")
|
||||||
|
expect(keytar.replacePassword).toHaveBeenCalledWith("Nylas", "ben@nylas.com", "B-NEW-TOKEN")
|
||||||
|
|
||||||
it "sets the accounts", ->
|
it "saves the account to the accounts cache and saves", ->
|
||||||
account = (new Account).fromJSON(@json)
|
account = (new Account).fromJSON(@json)
|
||||||
expect(@instance._accounts.length).toBe 1
|
expect(@instance._accounts.length).toBe 1
|
||||||
expect(@instance._accounts[0]).toEqual account
|
expect(@instance._accounts[0]).toEqual account
|
||||||
|
|
||||||
it "saves the config", ->
|
|
||||||
expect(NylasEnv.config.save).toHaveBeenCalled()
|
expect(NylasEnv.config.save).toHaveBeenCalled()
|
||||||
expect(NylasEnv.config.set.calls.length).toBe 2
|
expect(NylasEnv.config.set.calls.length).toBe 2
|
||||||
|
|
||||||
it "selects the account", ->
|
it "selects the account", ->
|
||||||
expect(Actions.focusDefaultMailboxPerspectiveForAccounts).toHaveBeenCalledWith(["1234"])
|
expect(Actions.focusDefaultMailboxPerspectiveForAccounts).toHaveBeenCalledWith(["B"])
|
||||||
|
|
||||||
it "triggers", ->
|
it "triggers", ->
|
||||||
expect(@instance.trigger).toHaveBeenCalled()
|
expect(@instance.trigger).toHaveBeenCalled()
|
||||||
|
|
|
@ -171,7 +171,7 @@ class Actions
|
||||||
Actions.updateAccount(account.id, {accountName: 'new'})
|
Actions.updateAccount(account.id, {accountName: 'new'})
|
||||||
```
|
```
|
||||||
###
|
###
|
||||||
@updateAccount: ActionScopeGlobal
|
@updateAccount: ActionScopeWindow
|
||||||
|
|
||||||
###
|
###
|
||||||
Public: Re-order the provided account in the account list.
|
Public: Re-order the provided account in the account list.
|
||||||
|
|
|
@ -4,10 +4,12 @@ Actions = require '../actions'
|
||||||
Account = require '../models/account'
|
Account = require '../models/account'
|
||||||
Utils = require '../models/utils'
|
Utils = require '../models/utils'
|
||||||
DatabaseStore = require './database-store'
|
DatabaseStore = require './database-store'
|
||||||
|
keytar = require 'keytar'
|
||||||
|
|
||||||
|
configAccountsKey = "nylas.accounts"
|
||||||
saveObjectsKey = "nylas.accounts"
|
configVersionKey = "nylas.accountsVersion"
|
||||||
saveTokensKey = "nylas.accountTokens"
|
configTokensKey = "nylas.accountTokens"
|
||||||
|
keytarServiceName = 'Nylas'
|
||||||
|
|
||||||
###
|
###
|
||||||
Public: The AccountStore listens to changes to the available accounts in
|
Public: The AccountStore listens to changes to the available accounts in
|
||||||
|
@ -18,31 +20,45 @@ Section: Stores
|
||||||
class AccountStore extends NylasStore
|
class AccountStore extends NylasStore
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
@_load()
|
@_loadAccounts()
|
||||||
@listenTo Actions.removeAccount, @_onRemoveAccount
|
@listenTo Actions.removeAccount, @_onRemoveAccount
|
||||||
@listenTo Actions.updateAccount, @_onUpdateAccount
|
@listenTo Actions.updateAccount, @_onUpdateAccount
|
||||||
@listenTo Actions.reorderAccount, @_onReorderAccount
|
@listenTo Actions.reorderAccount, @_onReorderAccount
|
||||||
|
|
||||||
@_caches = {}
|
NylasEnv.config.onDidChange configVersionKey, (change) =>
|
||||||
|
# If we already have this version of the accounts config, it means we
|
||||||
|
# are the ones who saved the change, and we don't need to reload.
|
||||||
|
return if @_version / 1 is change.newValue / 1
|
||||||
|
oldAccountIds = _.pluck(@_accounts, 'id')
|
||||||
|
@_loadAccounts()
|
||||||
|
newAccountIds = _.pluck(@_accounts, 'id')
|
||||||
|
newAccountIds = _.without(newAccountIds, oldAccountIds)
|
||||||
|
|
||||||
NylasEnv.config.onDidChange saveTokensKey, (change) =>
|
|
||||||
updatedTokens = change.newValue
|
|
||||||
return if _.isEqual(updatedTokens, @_tokens)
|
|
||||||
newAccountIds = _.keys(_.omit(updatedTokens, _.keys(@_tokens)))
|
|
||||||
@_load()
|
|
||||||
if newAccountIds.length > 0
|
if newAccountIds.length > 0
|
||||||
Actions.focusDefaultMailboxPerspectiveForAccounts([newAccountIds[0]])
|
Actions.focusDefaultMailboxPerspectiveForAccounts([newAccountIds[0]])
|
||||||
|
|
||||||
if NylasEnv.isComposerWindow() or NylasEnv.isWorkWindow()
|
_loadAccounts: =>
|
||||||
NylasEnv.config.onDidChange saveObjectsKey, => @_load()
|
|
||||||
|
|
||||||
_load: =>
|
|
||||||
@_caches = {}
|
@_caches = {}
|
||||||
|
@_version = NylasEnv.config.get(configVersionKey) || 0
|
||||||
|
|
||||||
@_accounts = []
|
@_accounts = []
|
||||||
for json in NylasEnv.config.get(saveObjectsKey) || []
|
for json in NylasEnv.config.get(configAccountsKey) || []
|
||||||
@_accounts.push((new Account).fromJSON(json))
|
@_accounts.push((new Account).fromJSON(json))
|
||||||
|
|
||||||
@_tokens = NylasEnv.config.get(saveTokensKey) || {}
|
# Load tokens using the old config method and save them into the keychain
|
||||||
|
oldTokens = NylasEnv.config.get(configTokensKey)
|
||||||
|
if oldTokens
|
||||||
|
for key, val of oldTokens
|
||||||
|
account = @accountForId(key)
|
||||||
|
continue unless account
|
||||||
|
keytar.replacePassword(keytarServiceName, account.emailAddress, val)
|
||||||
|
NylasEnv.config.set(configTokensKey, null)
|
||||||
|
|
||||||
|
# Load tokens using the new keytar method
|
||||||
|
@_tokens = {}
|
||||||
|
for account in @_accounts
|
||||||
|
@_tokens[account.id] = keytar.getPassword(keytarServiceName, account.emailAddress)
|
||||||
|
|
||||||
@_trigger()
|
@_trigger()
|
||||||
|
|
||||||
_trigger: ->
|
_trigger: ->
|
||||||
|
@ -54,9 +70,11 @@ class AccountStore extends NylasStore
|
||||||
@trigger()
|
@trigger()
|
||||||
|
|
||||||
_save: =>
|
_save: =>
|
||||||
NylasEnv.config.set(saveObjectsKey, @_accounts)
|
@_version += 1
|
||||||
NylasEnv.config.set(saveTokensKey, @_tokens)
|
NylasEnv.config.set(configVersionKey, @_version)
|
||||||
|
NylasEnv.config.set(configAccountsKey, @_accounts)
|
||||||
NylasEnv.config.save()
|
NylasEnv.config.save()
|
||||||
|
@_trigger()
|
||||||
|
|
||||||
# Inbound Events
|
# Inbound Events
|
||||||
|
|
||||||
|
@ -68,23 +86,21 @@ class AccountStore extends NylasStore
|
||||||
account = _.extend(account, updated)
|
account = _.extend(account, updated)
|
||||||
@_caches = {}
|
@_caches = {}
|
||||||
@_accounts[idx] = account
|
@_accounts[idx] = account
|
||||||
NylasEnv.config.set(saveObjectsKey, @_accounts)
|
@_save()
|
||||||
@_trigger()
|
|
||||||
|
|
||||||
_onRemoveAccount: (id) =>
|
_onRemoveAccount: (id) =>
|
||||||
idx = _.findIndex @_accounts, (a) -> a.id is id
|
account = _.findWhere(@_accounts, {id})
|
||||||
return if idx is -1
|
return unless account
|
||||||
|
keytar.deletePassword(keytarServiceName, account.emailAddress)
|
||||||
|
|
||||||
delete @_tokens[id]
|
|
||||||
@_caches = {}
|
@_caches = {}
|
||||||
@_accounts.splice(idx, 1)
|
@_accounts = _.without(@_accounts, account)
|
||||||
@_save()
|
@_save()
|
||||||
|
|
||||||
if @_accounts.length is 0
|
if @_accounts.length is 0
|
||||||
ipc = require('electron').ipcRenderer
|
ipc = require('electron').ipcRenderer
|
||||||
ipc.send('command', 'application:reset-config-and-relaunch')
|
ipc.send('command', 'application:reset-config-and-relaunch')
|
||||||
else
|
else
|
||||||
@_trigger()
|
|
||||||
Actions.focusDefaultMailboxPerspectiveForAccounts(@_accounts)
|
Actions.focusDefaultMailboxPerspectiveForAccounts(@_accounts)
|
||||||
|
|
||||||
_onReorderAccount: (id, newIdx) =>
|
_onReorderAccount: (id, newIdx) =>
|
||||||
|
@ -95,7 +111,6 @@ class AccountStore extends NylasStore
|
||||||
@_accounts.splice(existingIdx, 1)
|
@_accounts.splice(existingIdx, 1)
|
||||||
@_accounts.splice(newIdx, 0, account)
|
@_accounts.splice(newIdx, 0, account)
|
||||||
@_save()
|
@_save()
|
||||||
@_trigger()
|
|
||||||
|
|
||||||
addAccountFromJSON: (json) =>
|
addAccountFromJSON: (json) =>
|
||||||
if not json.email_address or not json.provider
|
if not json.email_address or not json.provider
|
||||||
|
@ -103,10 +118,11 @@ class AccountStore extends NylasStore
|
||||||
console.log JSON.stringify(json)
|
console.log JSON.stringify(json)
|
||||||
throw new Error("Returned account data is invalid")
|
throw new Error("Returned account data is invalid")
|
||||||
|
|
||||||
@_load()
|
@_loadAccounts()
|
||||||
@_tokens[json.id] = json.auth_token
|
|
||||||
|
@_tokens[json.id] = json.auth_token
|
||||||
|
keytar.replacePassword(keytarServiceName, json.email_address, json.auth_token)
|
||||||
|
|
||||||
@_caches = {}
|
|
||||||
existingIdx = _.findIndex @_accounts, (a) -> a.id is json.id
|
existingIdx = _.findIndex @_accounts, (a) -> a.id is json.id
|
||||||
if existingIdx is -1
|
if existingIdx is -1
|
||||||
account = (new Account).fromJSON(json)
|
account = (new Account).fromJSON(json)
|
||||||
|
@ -116,8 +132,6 @@ class AccountStore extends NylasStore
|
||||||
account.fromJSON(json)
|
account.fromJSON(json)
|
||||||
|
|
||||||
@_save()
|
@_save()
|
||||||
|
|
||||||
@_trigger()
|
|
||||||
Actions.focusDefaultMailboxPerspectiveForAccounts([account.id])
|
Actions.focusDefaultMailboxPerspectiveForAccounts([account.id])
|
||||||
|
|
||||||
# Exposed Data
|
# Exposed Data
|
||||||
|
|
Loading…
Add table
Reference in a new issue