diff --git a/dev/App/User.js b/dev/App/User.js index 2d1b10de8..3edb406d6 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -152,41 +152,29 @@ export class AppUser extends AbstractApp { IdentityUserStore.loading(false); if (!iError) { - const -// counts = {}, - accounts = oData.Result.Accounts, - mainEmail = SettingsGet('MainEmail'); + let items = oData.Result.Accounts; + AccountUserStore(isArray(items) + ? items.map(oValue => new AccountModel(oValue.email, oValue.name)) + : [] + ); + AccountUserStore.unshift(new AccountModel(SettingsGet('MainEmail'), '', false)); - if (isArray(accounts)) { -// AccountUserStore.accounts.forEach(oAccount => counts[oAccount.email] = oAccount.count()); - - AccountUserStore.accounts( - accounts.map( - sValue => new AccountModel(sValue/*, counts[sValue]*/) - ) - ); -// accounts.length && - AccountUserStore.accounts.unshift(new AccountModel(mainEmail/*, counts[mainEmail]*/, false)); - } - - if (isArray(oData.Result.Identities)) { - IdentityUserStore( - oData.Result.Identities.map(identityData => { - const identity = new IdentityModel( - pString(identityData.Id), - pString(identityData.Email) - ); - - identity.name(pString(identityData.Name)); - identity.replyTo(pString(identityData.ReplyTo)); - identity.bcc(pString(identityData.Bcc)); - identity.signature(pString(identityData.Signature)); - identity.signatureInsertBefore(!!identityData.SignatureInsertBefore); - - return identity; - }) - ); - } + items = oData.Result.Identities; + IdentityUserStore(isArray(items) + ? items.map(identityData => { + const identity = new IdentityModel( + pString(identityData.Id), + pString(identityData.Email) + ); + identity.name(pString(identityData.Name)); + identity.replyTo(pString(identityData.ReplyTo)); + identity.bcc(pString(identityData.Bcc)); + identity.signature(pString(identityData.Signature)); + identity.signatureInsertBefore(!!identityData.SignatureInsertBefore); + return identity; + }) + : [] + ); } }); } diff --git a/dev/Model/Account.js b/dev/Model/Account.js index 7367c579f..7b7eae8e3 100644 --- a/dev/Model/Account.js +++ b/dev/Model/Account.js @@ -7,11 +7,14 @@ export class AccountModel extends AbstractModel { * @param {boolean=} canBeDelete = true * @param {number=} count = 0 */ - constructor(email/*, count = 0*/, isAdditional = true) { + constructor(email, name/*, count = 0*/, isAdditional = true) { super(); + this.name = name; this.email = email; + this.displayName = name ? name + ' <' + email + '>' : email; + addObservablesTo(this, { // count: count || 0, askDelete: false, diff --git a/dev/Screen/User/MailBox.js b/dev/Screen/User/MailBox.js index 44d9380ad..5040305d7 100644 --- a/dev/Screen/User/MailBox.js +++ b/dev/Screen/User/MailBox.js @@ -101,7 +101,7 @@ export class MailBoxUserScreen extends AbstractScreen { FolderUserStore.foldersInboxUnreadCount(e.detail); /* // Disabled in SystemDropDown.html const email = AccountUserStore.email(); - AccountUserStore.accounts.forEach(item => + AccountUserStore.forEach(item => email === item?.email && item?.count(e.detail) ); */ diff --git a/dev/Settings/User/Accounts.js b/dev/Settings/User/Accounts.js index 5ef77bf8a..454d8311d 100644 --- a/dev/Settings/User/Accounts.js +++ b/dev/Settings/User/Accounts.js @@ -16,7 +16,7 @@ export class UserSettingsAccounts /*extends AbstractViewSettings*/ { this.allowAdditionalAccount = SettingsCapa('AdditionalAccounts'); this.allowIdentities = SettingsCapa('Identities'); - this.accounts = AccountUserStore.accounts; + this.accounts = AccountUserStore; this.loading = AccountUserStore.loading; this.identities = IdentityUserStore; this.mainEmail = SettingsGet('MainEmail'); diff --git a/dev/Stores/User/Account.js b/dev/Stores/User/Account.js index 4dc389326..b29b55666 100644 --- a/dev/Stores/User/Account.js +++ b/dev/Stores/User/Account.js @@ -1,11 +1,10 @@ import { addObservablesTo, koArrayWithDestroy } from 'External/ko'; -export const AccountUserStore = { - accounts: koArrayWithDestroy(), - loading: ko.observable(false).extend({ debounce: 100 }), +export const AccountUserStore = koArrayWithDestroy(); - getEmailAddresses: () => AccountUserStore.accounts.map(item => item.email) -}; +AccountUserStore.loading = ko.observable(false).extend({ debounce: 100 }); + +AccountUserStore.getEmailAddresses = () => AccountUserStore.map(item => item.email); addObservablesTo(AccountUserStore, { email: '', diff --git a/dev/View/Popup/Account.js b/dev/View/Popup/Account.js index 7bf1604a2..779a43737 100644 --- a/dev/View/Popup/Account.js +++ b/dev/View/Popup/Account.js @@ -12,6 +12,7 @@ export class AccountPopupView extends AbstractViewPopup { addObservablesTo(this, { isNew: true, + name: '', email: '', password: '', @@ -40,18 +41,17 @@ export class AccountPopupView extends AbstractViewPopup { } } - onShow(account) { - if (account?.isAdditional()) { - this.isNew(false); - this.email(account.email); - } else { - this.isNew(true); - this.email(''); - } + onHide() { this.password(''); - this.submitRequest(false); this.submitError(''); this.submitErrorAdditional(''); } + + onShow(account) { + let edit = account?.isAdditional(); + this.isNew(!edit); + this.name(edit ? account.name : ''); + this.email(edit ? account.email : ''); + } } diff --git a/dev/View/User/SystemDropDown.js b/dev/View/User/SystemDropDown.js index 1bd4d230d..e847df172 100644 --- a/dev/View/User/SystemDropDown.js +++ b/dev/View/User/SystemDropDown.js @@ -30,11 +30,11 @@ export class SystemDropDownUserView extends AbstractViewRight { this.accountEmail = AccountUserStore.email; - this.accounts = AccountUserStore.accounts; + this.accounts = AccountUserStore; this.accountsLoading = AccountUserStore.loading; /* this.accountsUnreadCount = : koComputable(() => 0); - this.accountsUnreadCount = : koComputable(() => AccountUserStore.accounts().reduce((result, item) => result + item.count(), 0)); + this.accountsUnreadCount = : koComputable(() => AccountUserStore().reduce((result, item) => result + item.count(), 0)); */ addObservablesTo(this, { @@ -91,8 +91,10 @@ export class SystemDropDownUserView extends AbstractViewRight { return true; } - emailTitle() { - return AccountUserStore.email(); + accountName() { + let email = AccountUserStore.email(), + account = AccountUserStore.find(account => account.email == email); + return account?.name || email; } settingsClick() { diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php index 84c20a709..49dc6a07f 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php @@ -41,7 +41,8 @@ trait Accounts StorageType::CONFIG, 'additionalaccounts' ); - $aAccounts = $sAccounts ? \json_decode($sAccounts, true) : \SnappyMail\Upgrade::ConvertInsecureAccounts($this, $oAccount); + $aAccounts = $sAccounts ? \json_decode($sAccounts, true) + : \SnappyMail\Upgrade::ConvertInsecureAccounts($this, $oAccount); if ($aAccounts && \is_array($aAccounts)) { return $aAccounts; } @@ -84,6 +85,7 @@ trait Accounts $sEmail = \trim($this->GetActionParam('Email', '')); $sPassword = $this->GetActionParam('Password', ''); + $sName = $this->GetActionParam('Name', ''); $bNew = '1' === (string)$this->GetActionParam('New', '1'); $sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true); @@ -93,10 +95,17 @@ trait Accounts throw new ClientException(Notifications::AccountDoesNotExist); } - $oNewAccount = $this->LoginProcess($sEmail, $sPassword, false, false); + if ($bNew || $sPassword) { + $oNewAccount = $this->LoginProcess($sEmail, $sPassword, false, false); + $aAccounts[$sEmail] = $oNewAccount->asTokenArray($oMainAccount); + } else { + $aAccounts[$sEmail] = \RainLoop\Model\AdditionalAccount::convertArray($aAccounts[$sEmail]); + } - $aAccounts[$oNewAccount->Email()] = $oNewAccount->asTokenArray($oMainAccount); - $this->SetAccounts($oMainAccount, $aAccounts); + if ($aAccounts[$sEmail]) { + $aAccounts[$sEmail]['name'] = $sName; + $this->SetAccounts($oMainAccount, $aAccounts); + } return $this->TrueResponse(__FUNCTION__); } @@ -241,11 +250,16 @@ trait Accounts */ public function DoAccountsAndIdentities(): array { + // https://github.com/the-djmaze/snappymail/issues/571 return $this->DefaultResponse(__FUNCTION__, array( - 'Accounts' => \array_map( - 'MailSo\\Base\\Utils::IdnToUtf8', - \array_keys($this->GetAccounts($this->getMainAccountFromToken())) - ), + 'Accounts' => \array_values(\array_map(function($value){ + return [ + 'email' => \MailSo\Base\Utils::IdnToUtf8($value['email'] ?? $value[1]), + 'name' => $value['name'] ?? '' + ]; + }, + $this->GetAccounts($this->getMainAccountFromToken()) + )), 'Identities' => $this->GetIdentities($this->getAccountFromToken()) )); } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php index 0adf20248..8ad441a4d 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php @@ -8,41 +8,30 @@ use RainLoop\Exceptions\ClientException; abstract class Account implements \JsonSerializable { - /** - * @var string - */ - private $sEmail; + private string $sName = ''; - /** - * @var string - */ - private $sLogin; + private string $sEmail = ''; - /** - * @var string - */ - private $sPassword; + private string $sLogin = ''; - /** - * @var string - */ - private $sProxyAuthUser = ''; + private string $sPassword = ''; - /** - * @var string - */ - private $sProxyAuthPassword = ''; + private string $sProxyAuthUser = ''; - /** - * @var \RainLoop\Model\Domain - */ - private $oDomain; + private string $sProxyAuthPassword = ''; + + private Domain $oDomain; public function Email() : string { return $this->sEmail; } + public function Name() : string + { + return $this->sName; + } + public function ProxyAuthUser() : string { return $this->sProxyAuthUser; @@ -123,15 +112,21 @@ abstract class Account implements \JsonSerializable #[\ReturnTypeWillChange] public function jsonSerialize() { - return array( - 'account', // 0 - $this->sEmail, // 1 - $this->sLogin, // 2 - $this->sPassword, // 3 - '', // 4 sClientCert - $this->sProxyAuthUser, // 5 - $this->sProxyAuthPassword // 6 - ); + $result = [ +// 'account', // 0 + 'email' => $this->sEmail, // 1 + 'login' => $this->sLogin, // 2 + 'pass' => $this->sPassword, // 3 +// '', // 4 sClientCert + 'name' => $this->sName + ]; + if ($this->sProxyAuthUser && $this->sProxyAuthPassword) { + $result['proxy'] = [ + 'user' => $this->sProxyAuthUser, // 5 + 'pass' => $this->sProxyAuthPassword // 6 + ]; + } + return $result; } public static function NewInstanceFromCredentials(\RainLoop\Actions $oActions, @@ -165,34 +160,60 @@ abstract class Account implements \JsonSerializable return $oAccount; } + /** + * Converts old numeric array to new associative array + */ + public static function convertArray(array $aAccount) : array + { + if (isset($aAccount['email'])) { + return $aAccount; + } + if (empty($aAccount[0]) || 'account' != $aAccount[0] || 7 > \count($aAccount)) { + return []; + } + $aResult = [ + 'email' => $aAccount[1] ?: '', + 'login' => $aAccount[2] ?: '', + 'pass' => $aAccount[3] ?: '' + ]; + if ($aAccount[5] && $aAccount[6]) { + $aResult['proxy'] = [ + 'user' => $aAccount[5], + 'pass' => $aAccount[6] + ]; + } + return $aResult; + } + public static function NewInstanceFromTokenArray( \RainLoop\Actions $oActions, array $aAccountHash, bool $bThrowExceptionOnFalse = false): ?self { - if (!empty($aAccountHash[0]) && 'account' === $aAccountHash[0] && 7 <= \count($aAccountHash)) { + $oAccount = null; + $aAccountHash = static::convertArray($aAccountHash); + if (!empty($aAccountHash['email']) && 3 <= \count($aAccountHash)) { $oAccount = static::NewInstanceFromCredentials( $oActions, - $aAccountHash[1] ?: '', - $aAccountHash[2] ?: '', - $aAccountHash[3] ?: '', + $aAccountHash['email'], + $aAccountHash['login'], + $aAccountHash['pass'], $bThrowExceptionOnFalse ); - if ($oAccount) { - // init proxy user/password - if (!empty($aAccountHash[5]) && !empty($aAccountHash[6])) { - $oAccount->SetProxyAuthUser($aAccountHash[5]); - $oAccount->SetProxyAuthPassword($aAccountHash[6]); + if (isset($aAccountHash['name'])) { + $oAccount->sName = $aAccountHash['name']; + } + // init proxy user/password + if (isset($aAccountHash['proxy'])) { + $oAccount->sProxyAuthUser = $aAccountHash['proxy']['user']; + $oAccount->sProxyAuthPassword = $aAccountHash['proxy']['pass']; } - $oActions->Logger()->AddSecret($oAccount->Password()); $oActions->Logger()->AddSecret($oAccount->ProxyAuthPassword()); - - return $oAccount; } } - return null; + return $oAccount; } public function ImapConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Mail\MailClient $oMailClient, \RainLoop\Config\Application $oConfig) : bool diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/AdditionalAccount.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/AdditionalAccount.php index fb03624a5..d076b016d 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/AdditionalAccount.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/AdditionalAccount.php @@ -17,13 +17,25 @@ class AdditionalAccount extends Account return \md5(parent::Hash() . $this->ParentEmail()); } + public static function convertArray(array $aAccount) : array + { + $aResult = parent::convertArray($aAccount); + $iCount = \count($aAccount); + if ($aResult && 7 < $iCount && 9 >= $iCount) { + $aResult['hmac'] = \array_pop($aAccount); + } + return $aResult; + } + public function asTokenArray(MainAccount $oMainAccount) : array { $sHash = $oMainAccount->CryptKey(); $aData = $this->jsonSerialize(); - $aData[3] = \SnappyMail\Crypt::EncryptUrlSafe($aData[3], $sHash); // sPassword - $aData[6] = \SnappyMail\Crypt::EncryptUrlSafe($aData[6], $sHash); // sProxyAuthPassword - $aData[] = \hash_hmac('sha1', $aData[3], $sHash); + $aData['pass'] = \SnappyMail\Crypt::EncryptUrlSafe($aData['pass'], $sHash); // sPassword + if (isset($aAccountHash['proxy'])) { + $aData['proxy']['pass'] = \SnappyMail\Crypt::EncryptUrlSafe($aData['proxy']['pass'], $sHash); // sProxyAuthPassword + } + $aData['hmac'] = \hash_hmac('sha1', $aData['pass'], $sHash); return $aData; } @@ -32,13 +44,16 @@ class AdditionalAccount extends Account array $aAccountHash, bool $bThrowExceptionOnFalse = false) : ?Account /* PHP7.4: ?self*/ { - $iCount = \count($aAccountHash); - if (!empty($aAccountHash[0]) && 'account' === $aAccountHash[0] && 7 <= $iCount && 9 >= $iCount) { + $aAccountHash = static::convertArray($aAccountHash); + if (!empty($aAccountHash['email'])) { $sHash = $oActions->getMainAccountFromToken()->CryptKey(); - $sPasswordHMAC = (7 < $iCount) ? \array_pop($aAccountHash) : null; - if ($sPasswordHMAC && $sPasswordHMAC === \hash_hmac('sha1', $aAccountHash[3], $sHash)) { - $aAccountHash[3] = \SnappyMail\Crypt::DecryptUrlSafe($aAccountHash[3], $sHash); - $aAccountHash[6] = \SnappyMail\Crypt::DecryptUrlSafe($aAccountHash[6], $sHash); + // hmac only set when asTokenArray() was used + $sPasswordHMAC = $aAccountHash['hmac'] ?? null; + if ($sPasswordHMAC && $sPasswordHMAC === \hash_hmac('sha1', $aAccountHash['pass'], $sHash)) { + $aAccountHash['pass'] = \SnappyMail\Crypt::DecryptUrlSafe($aAccountHash['pass'], $sHash); + if (isset($aAccountHash['proxy'])) { + $aAccountHash['proxy']['pass'] = \SnappyMail\Crypt::DecryptUrlSafe($aAccountHash['proxy']['pass'], $sHash); + } } return parent::NewInstanceFromTokenArray($oActions, $aAccountHash, $bThrowExceptionOnFalse); } diff --git a/snappymail/v/0.0.0/app/templates/Views/User/PopupsAccount.html b/snappymail/v/0.0.0/app/templates/Views/User/PopupsAccount.html index 9483b2709..987a499ff 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/PopupsAccount.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/PopupsAccount.html @@ -19,7 +19,11 @@
+ data-bind="value: password, attr: {required:isNew}"> +
+
+ +