Browse Source

Supported 2-step verifications

Disabled webpage attachment
master
Igor Zhukov 10 years ago
parent
commit
8464acdfdc
  1. 246
      app/js/controllers.js
  2. 3
      app/js/directives.js
  3. 8
      app/js/lib/ng_utils.js
  4. 6
      app/js/lib/tl_utils.js
  5. 47
      app/js/locales/en-us.json
  6. 94
      app/js/services.js
  7. 27
      app/less/app.less
  8. 6
      app/partials/desktop/confirm_modal.html
  9. 4
      app/partials/desktop/error_modal.html
  10. 27
      app/partials/desktop/login.html
  11. 4
      app/partials/desktop/message.html
  12. 32
      app/partials/desktop/password_recovery_modal.html
  13. 35
      app/partials/desktop/password_update_modal.html
  14. 15
      app/partials/desktop/settings_modal.html

246
app/js/controllers.js

@ -30,7 +30,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
LayoutSwitchService.start(); LayoutSwitchService.start();
}) })
.controller('AppLoginController', function ($scope, $rootScope, $location, $timeout, $modal, $modalStack, MtpApiManager, ErrorService, NotificationsManager, ChangelogNotifyService, IdleManager, LayoutSwitchService, TelegramMeWebService, _) { .controller('AppLoginController', function ($scope, $rootScope, $location, $timeout, $modal, $modalStack, MtpApiManager, ErrorService, NotificationsManager, PasswordManager, ChangelogNotifyService, IdleManager, LayoutSwitchService, TelegramMeWebService, _) {
$modalStack.dismissAll(); $modalStack.dismissAll();
IdleManager.start(); IdleManager.start();
@ -156,6 +156,7 @@ angular.module('myApp.controllers', ['myApp.i18n'])
var callTimeout; var callTimeout;
var updatePasswordTimeout = false;
function saveAuth (result) { function saveAuth (result) {
MtpApiManager.setUserAuth(options.dcID, { MtpApiManager.setUserAuth(options.dcID, {
@ -204,7 +205,8 @@ angular.module('myApp.controllers', ['myApp.i18n'])
phone_number: $scope.credentials.phone_full, phone_number: $scope.credentials.phone_full,
// sms_type: 5, // sms_type: 5,
api_id: Config.App.id, api_id: Config.App.id,
api_hash: Config.App.hash api_hash: Config.App.hash,
lang_code: navigator.language || 'en'
}, options).then(function (sentCode) { }, options).then(function (sentCode) {
$scope.progress.enabled = false; $scope.progress.enabled = false;
@ -290,6 +292,16 @@ angular.module('myApp.controllers', ['myApp.i18n'])
} else if (error.code == 400 && error.type == 'PHONE_NUMBER_OCCUPIED') { } else if (error.code == 400 && error.type == 'PHONE_NUMBER_OCCUPIED') {
error.handled = true; error.handled = true;
return $scope.logIn(false); return $scope.logIn(false);
} else if (error.code == 401 && error.type == 'SESSION_PASSWORD_NEEDED') {
$scope.progress.enabled = true;
updatePasswordState().then(function () {
$scope.progress.enabled = false;
$scope.credentials.phone_code_valid = true;
$scope.credentials.password_needed = true;
$scope.about = {};
});
error.handled = true;
return;
} }
@ -312,6 +324,82 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}; };
$scope.checkPassword = function () {
return PasswordManager.check($scope.password, $scope.credentials.password, options).then(saveAuth, function (error) {
switch (error.type) {
case 'PASSWORD_HASH_INVALID':
$scope.error = {field: 'password'};
error.handled = true;
break;
}
});
};
$scope.forgotPassword = function (event) {
PasswordManager.requestRecovery($scope.password, options).then(function (emailRecovery) {
var scope = $rootScope.$new();
scope.recovery = emailRecovery;
scope.options = options;
var modal = $modal.open({
scope: scope,
templateUrl: templateUrl('password_recovery_modal'),
controller: 'PasswordRecoveryModalController',
windowClass: 'md_simple_modal_window mobile_modal'
});
modal.result.then(function (result) {
if (result && result.user) {
saveAuth(result);
} else {
$scope.canReset = true;
}
});
}, function (error) {
switch (error.type) {
case 'PASSWORD_EMPTY':
$scope.logIn();
break;
case 'PASSWORD_RECOVERY_NA':
$timeout(function () {
$scope.canReset = true;
}, 1000);
break;
}
})
return cancelEvent(event);
};
$scope.resetAccount = function () {
ErrorService.confirm({
type: 'RESET_ACCOUNT'
}).then(function () {
$scope.progress.enabled = true;
MtpApiManager.invokeApi('account.deleteAccount', {
reason: 'Forgot password'
}, options).then(function () {
delete $scope.progress.enabled;
delete $scope.credentials.password_needed;
$scope.credentials.phone_unoccupied = true;
}, function () {
delete $scope.progress.enabled;
})
});
};
function updatePasswordState () {
// $timeout.cancel(updatePasswordTimeout);
// updatePasswordTimeout = false;
return PasswordManager.getState(options).then(function (result) {
return $scope.password = result;
// if (result._ == 'account.noPassword' && result.email_unconfirmed_pattern) {
// updatePasswordTimeout = $timeout(updatePasswordState, 5000);
// }
});
}
ChangelogNotifyService.checkUpdate(); ChangelogNotifyService.checkUpdate();
LayoutSwitchService.start(); LayoutSwitchService.start();
}) })
@ -364,6 +452,8 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}); });
}; };
// setTimeout($scope.openSettings, 1000);
$scope.openFaq = function () { $scope.openFaq = function () {
var url = 'https://telegram.org/faq'; var url = 'https://telegram.org/faq';
switch (Config.I18n.locale) { switch (Config.I18n.locale) {
@ -2535,10 +2625,17 @@ angular.module('myApp.controllers', ['myApp.i18n'])
$scope.password = {_: 'account.noPassword'}; $scope.password = {_: 'account.noPassword'};
updatePasswordState(); updatePasswordState();
var updatePasswordTimeout = false;
$scope.changePassword = function (options) { $scope.changePassword = function (options) {
options = options || {}; options = options || {};
if (options.action == 'cancel_email') {
return ErrorService.confirm({type: 'PASSWORD_ABORT_SETUP'}).then(function () {
PasswordManager.updateSettings($scope.password, {email: ''}).then(updatePasswordState);
});
}
var scope = $rootScope.$new(); var scope = $rootScope.$new();
scope.password = $scope.password;
angular.extend(scope, options); angular.extend(scope, options);
var modal = $modal.open({ var modal = $modal.open({
scope: scope, scope: scope,
@ -2550,9 +2647,14 @@ angular.module('myApp.controllers', ['myApp.i18n'])
modal.result['finally'](updatePasswordState); modal.result['finally'](updatePasswordState);
}; };
function updatePasswordState (argument) { function updatePasswordState () {
PasswordManager.getPasswordState().then(function (result) { $timeout.cancel(updatePasswordTimeout);
updatePasswordTimeout = false;
PasswordManager.getState().then(function (result) {
$scope.password = result; $scope.password = result;
if (result._ == 'account.noPassword' && result.email_unconfirmed_pattern) {
updatePasswordTimeout = $timeout(updatePasswordState, 5000);
}
}); });
} }
@ -2882,7 +2984,141 @@ angular.module('myApp.controllers', ['myApp.i18n'])
}) })
}) })
.controller('PasswordUpdateModalController', function ($scope, PasswordManager, MtpApiManager) { .controller('PasswordUpdateModalController', function ($scope, $q, _, PasswordManager, MtpApiManager, ErrorService, $modalInstance) {
$scope.passwordSettings = {};
$scope.updatePassword = function () {
delete $scope.passwordSettings.error_field;
var confirmPromise;
if ($scope.action == 'disable') {
confirmPromise = $q.when();
}
else {
if (!$scope.passwordSettings.new_password) {
$scope.passwordSettings.error_field = 'new_password';
$scope.$broadcast('new_password_focus');
return false;
}
if ($scope.passwordSettings.new_password != $scope.passwordSettings.confirm_password) {
$scope.passwordSettings.error_field = 'confirm_password';
$scope.$broadcast('confirm_password_focus');
return false;
}
confirmPromise = $scope.passwordSettings.email
? $q.when()
: ErrorService.confirm({type: 'RECOVERY_EMAIL_EMPTY'});
}
$scope.passwordSettings.loading = true;
confirmPromise.then(function () {
PasswordManager.updateSettings($scope.password, {
cur_password: $scope.passwordSettings.cur_password || '',
new_password: $scope.passwordSettings.new_password,
email: $scope.passwordSettings.email,
hint: $scope.passwordSettings.hint
}).then(function (result) {
delete $scope.passwordSettings.loading;
$modalInstance.close(true);
if ($scope.action == 'disable') {
ErrorService.alert(
_('error_modal_password_disabled_title'),
_('error_modal_password_disabled_descripion')
);
} else {
ErrorService.alert(
_('error_modal_password_success_title'),
_('error_modal_password_success_descripion')
);
}
}, function (error) {
switch (error.type) {
case 'PASSWORD_HASH_INVALID':
case 'NEW_PASSWORD_BAD':
$scope.passwordSettings.error_field = 'cur_password';
error.handled = true;
$scope.$broadcast('cur_password_focus');
break;
case 'NEW_PASSWORD_BAD':
$scope.passwordSettings.error_field = 'new_password';
error.handled = true;
break;
case 'EMAIL_INVALID':
$scope.passwordSettings.error_field = 'email';
error.handled = true;
break;
case 'EMAIL_UNCONFIRMED':
ErrorService.alert(
_('error_modal_email_unconfirmed_title'),
_('error_modal_email_unconfirmed_descripion')
);
$modalInstance.close(true);
error.handled = true;
break;
}
delete $scope.passwordSettings.loading;
});
})
}
switch ($scope.action) {
case 'disable':
$scope.passwordSettings.new_password = '';
break;
case 'create':
onContentLoaded(function () {
$scope.$broadcast('new_password_focus');
});
break;
}
$scope.$watch('passwordSettings.new_password', function (newValue) {
var len = newValue && newValue.length || 0;
if (!len) {
$scope.passwordSettings.hint = '';
}
else if (len <= 3) {
$scope.passwordSettings.hint = '***';
}
else {
$scope.passwordSettings.hint = newValue.charAt(0) + (new Array(len - 1)).join('*') + newValue.charAt(len - 1);
}
$scope.$broadcast('value_updated');
})
})
.controller('PasswordRecoveryModalController', function ($scope, $q, _, PasswordManager, MtpApiManager, ErrorService, $modalInstance) {
$scope.checkCode = function () {
$scope.recovery.updating = true;
PasswordManager.recover($scope.recovery.code, $scope.options).then(function (result) {
ErrorService.alert(
_('error_modal_password_disabled_title'),
_('error_modal_password_disabled_descripion')
);
$modalInstance.close(result);
}, function (error) {
delete $scope.recovery.updating;
switch (error.type) {
case 'CODE_EMPTY':
case 'CODE_INVALID':
$scope.recovery.error_field = 'code';
error.handled = true;
break;
case 'PASSWORD_EMPTY':
case 'PASSWORD_RECOVERY_NA':
case 'PASSWORD_RECOVERY_EXPIRED':
$modalInstance.dismiss();
error.handled = true;
break;
}
});
};
}) })

3
app/js/directives.js

@ -2840,7 +2840,7 @@ angular.module('myApp.directives', ['myApp.filters'])
}); });
} }
$scope.$on('value_updated', function (event, args) { $scope.$on('value_updated', function () {
setZeroTimeout(function () { setZeroTimeout(function () {
updateHasValueClass(); updateHasValueClass();
}); });
@ -2858,6 +2858,7 @@ angular.module('myApp.directives', ['myApp.filters'])
element.on('keydown', function (event) { element.on('keydown', function (event) {
if (event.keyCode == 13) { if (event.keyCode == 13) {
element.trigger('submit'); element.trigger('submit');
return cancelEvent(event);
} }
}); });
}; };

8
app/js/lib/ng_utils.js

@ -651,7 +651,7 @@ angular.module('izhukov.utils', [])
awaiting = {}, awaiting = {},
webCrypto = Config.Modes.webcrypto && window.crypto && (window.crypto.subtle || window.crypto.webkitSubtle)/* || window.msCrypto && window.msCrypto.subtle*/, webCrypto = Config.Modes.webcrypto && window.crypto && (window.crypto.subtle || window.crypto.webkitSubtle)/* || window.msCrypto && window.msCrypto.subtle*/,
useSha1Crypto = webCrypto && webCrypto.digest !== undefined, useSha1Crypto = webCrypto && webCrypto.digest !== undefined,
useSha2Crypto = webCrypto && webCrypto.digest !== undefined, useSha256Crypto = webCrypto && webCrypto.digest !== undefined,
finalizeTask = function (taskID, result) { finalizeTask = function (taskID, result) {
var deferred = awaiting[taskID]; var deferred = awaiting[taskID];
if (deferred !== undefined) { if (deferred !== undefined) {
@ -729,8 +729,8 @@ angular.module('izhukov.utils', [])
return sha1HashSync(bytes); return sha1HashSync(bytes);
}); });
}, },
sha2Hash: function (bytes) { sha256Hash: function (bytes) {
if (useSha2Crypto) { if (useSha256Crypto) {
var deferred = $q.defer(), var deferred = $q.defer(),
bytesTyped = Array.isArray(bytes) ? convertToUint8Array(bytes) : bytes; bytesTyped = Array.isArray(bytes) ? convertToUint8Array(bytes) : bytes;
// console.log(dT(), 'Native sha1 start'); // console.log(dT(), 'Native sha1 start');
@ -739,7 +739,7 @@ angular.module('izhukov.utils', [])
deferred.resolve(digest); deferred.resolve(digest);
}, function (e) { }, function (e) {
console.error('Crypto digest error', e); console.error('Crypto digest error', e);
useSha2Crypto = false; useSha256Crypto = false;
deferred.resolve(sha256HashSync(bytes)); deferred.resolve(sha256HashSync(bytes));
}); });

6
app/js/lib/tl_utils.js

@ -122,6 +122,9 @@ TLSerialization.prototype.storeDouble = function (f) {
TLSerialization.prototype.storeString = function (s, field) { TLSerialization.prototype.storeString = function (s, field) {
this.debug && console.log('>>>', s, (field || '') + ':string'); this.debug && console.log('>>>', s, (field || '') + ':string');
if (s === undefined) {
s = '';
}
var sUTF8 = unescape(encodeURIComponent(s)); var sUTF8 = unescape(encodeURIComponent(s));
this.checkLength(sUTF8.length + 8); this.checkLength(sUTF8.length + 8);
@ -151,6 +154,9 @@ TLSerialization.prototype.storeBytes = function (bytes, field) {
if (bytes instanceof ArrayBuffer) { if (bytes instanceof ArrayBuffer) {
bytes = new Uint8Array(bytes); bytes = new Uint8Array(bytes);
} }
else if (bytes === undefined) {
bytes = [];
}
this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':bytes'); this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':bytes');
var len = bytes.byteLength || bytes.length; var len = bytes.byteLength || bytes.length;

47
app/js/locales/en-us.json

@ -54,6 +54,26 @@
"settings_modal_follow_us_twitter": "Follow us on Twitter!", "settings_modal_follow_us_twitter": "Follow us on Twitter!",
"settings_modal_recent_updates": "Recent updates (ver. {version})", "settings_modal_recent_updates": "Recent updates (ver. {version})",
"settings_modal_set_password": "Set Additional Password",
"settings_modal_change_password": "Change password",
"settings_modal_disable_password": "Turn off",
"settings_modal_password_email_pending": "Click the link in {email} to complete Two-Step Verification setup.",
"settings_modal_password_email_pending_cancel": "Abort",
"password_delete_title": "Turn Password Off",
"password_change_title": "Two-Step Verification",
"password_current_placeholder": "Enter current password",
"password_create_placeholder": "Enter a password",
"password_new_placeholder": "Enter new password",
"password_confirm_placeholder": "Re-enter new password",
"password_hint_placeholder": "Enter password hint",
"password_email_placeholder": "Enter recovery e-mail",
"password_create_description": "This password will be required when you log in on a new device in addition to the pin code.",
"password_create_active": "Saving...",
"password_create_submit": "Save",
"password_delete_active": "Deleting...",
"password_delete_submit": "Delete password",
"page_title_pluralize_notifications": "{'0': 'No notifications', 'one': '1 notification', 'other': '{} notifications'}", "page_title_pluralize_notifications": "{'0': 'No notifications', 'one': '1 notification', 'other': '{} notifications'}",
"profile_edit_modal_title": "Edit profile", "profile_edit_modal_title": "Edit profile",
@ -147,6 +167,9 @@
"confirm_modal_migrate_to_https_md": "Telegram Web now supports additional SSL encryption. Would you like to switch to HTTPS?\nThe HTTP version will be disabled soon.", "confirm_modal_migrate_to_https_md": "Telegram Web now supports additional SSL encryption. Would you like to switch to HTTPS?\nThe HTTP version will be disabled soon.",
"confirm_modal_resize_desktop_md": "Would you like to switch to desktop version?", "confirm_modal_resize_desktop_md": "Would you like to switch to desktop version?",
"confirm_modal_resize_mobile_md": "Would you like to switch to mobile version?", "confirm_modal_resize_mobile_md": "Would you like to switch to mobile version?",
"confirm_modal_recovery_email_empty_md": "Warning! Are you sure you don't want to add a password recovery e-mail?\n\nIf you forget your password, you will lose access to your Telegram account",
"confirm_modal_abort_password_setup": "Abort two-step verification setup?",
"confirm_modal_reset_account_md": "Are you sure?\nThis action can not be undone.\n\nYou will lose all your chats and messages, along with any media and files you shared, if you proceed with resetting your account.",
"confirm_modal_are_u_sure": "Are you sure?", "confirm_modal_are_u_sure": "Are you sure?",
"confirm_modal_logout_submit": "Log Out", "confirm_modal_logout_submit": "Log Out",
@ -161,6 +184,7 @@
"confirm_modal_share_video_submit": "Forward video", "confirm_modal_share_video_submit": "Forward video",
"confirm_modal_share_contact_submit": "Send contact", "confirm_modal_share_contact_submit": "Send contact",
"confirm_modal_share_file_submit": "Share file", "confirm_modal_share_file_submit": "Share file",
"confirm_modal_reset_account_submit": "Reset my account",
"contacts_modal_edit_list": "Edit", "contacts_modal_edit_list": "Edit",
"contacts_modal_edit_cancel": "Cancel", "contacts_modal_edit_cancel": "Cancel",
@ -232,7 +256,12 @@
"error_modal_flood_title": "Too fast", "error_modal_flood_title": "Too fast",
"error_modal_internal_title": "Server error", "error_modal_internal_title": "Server error",
"error_modal_alert": "Alert", "error_modal_alert": "Alert",
"error_modal_email_unconfirmed_title": "Almost there!",
"error_modal_email_unconfirmed_descripion": "Please check your e-mail (don't forget the spam folder) to complete Two-Step Verification setup.",
"error_modal_password_success_title": "Success!",
"error_modal_password_disabled_title": "Password deactivated",
"error_modal_media_not_supported_title": "Unsupported media", "error_modal_media_not_supported_title": "Unsupported media",
"error_modal_recovery_na_title": "Sorry",
"error_modal_network_description": "Please check your internet connection.", "error_modal_network_description": "Please check your internet connection.",
"error_modal_firstname_invali_description": "The first name you entered is invalid.", "error_modal_firstname_invali_description": "The first name you entered is invalid.",
@ -258,6 +287,9 @@
"error_modal_internal_description": "Internal server error occured. Please try again later.", "error_modal_internal_description": "Internal server error occured. Please try again later.",
"error_modal_tech_details": "Technical details here", "error_modal_tech_details": "Technical details here",
"error_modal_multiple_open_tabs": "Please close other Telegram app tabs.", "error_modal_multiple_open_tabs": "Please close other Telegram app tabs.",
"error_modal_recovery_na_description": "Since you haven't provided a recovery e-mail when setting up your password, your remaining options are either to remember your password or to reset your account.",
"error_modal_password_success_descripion": "Your password for Two-Step Verification is now active.",
"error_modal_password_disabled_descripion": "You have disabled Two-Step Verification.",
"head_telegram": "Telegram", "head_telegram": "Telegram",
@ -367,6 +399,21 @@
"login_about_desc3_md": "Our {source-link: source code} is open, so everyone can make a contribution.", "login_about_desc3_md": "Our {source-link: source code} is open, so everyone can make a contribution.",
"login_about_intro": "Welcome to the official Telegram web-client.", "login_about_intro": "Welcome to the official Telegram web-client.",
"login_about_learn": "Learn more", "login_about_learn": "Learn more",
"login_password_title": "Password",
"login_password_label": "You have enabled Two-Step Verification, so your account is protected with an additional password.",
"login_password_forgot_link": "Forgot password?",
"login_account_reset": "Reset account",
"login_password": "Your Password",
"login_incorrect_password": "Incorrect password",
"login_checking_password": "Checking",
"login_recovery_title": "Forgot password?",
"login_code_placeholder": "Code",
"login_code_incorrect": "Incorrect code",
"login_recovery_description_md": "We have sent a recovery code to the e-mail you provided:\n\n{email}\n\nPlease check your e-mail and enter the 6-digit code we have sent here.",
"password_recover_active": "Checking...",
"password_recover_submit": "Submit",
"login_controller_unknown_country": "Unknown", "login_controller_unknown_country": "Unknown",

94
app/js/services.js

@ -4537,18 +4537,104 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}) })
.service('PasswordManager', function ($timeout, $rootScope, MtpApiManager) { .service('PasswordManager', function ($timeout, $q, $rootScope, MtpApiManager, CryptoWorker, MtpSecureRandom) {
return { return {
getPasswordState: getPasswordState check: check,
getState: getState,
requestRecovery: requestRecovery,
recover: recover,
updateSettings: updateSettings
}; };
function getPasswordState () { function getState (options) {
return MtpApiManager.invokeApi('account.getPassword').then(function (result) { return MtpApiManager.invokeApi('account.getPassword', {}, options).then(function (result) {
return result; return result;
}); });
} }
function updateSettings (state, settings) {
var currentHashPromise;
var newHashPromise;
var params = {
new_settings: {
_: 'account.passwordInputSettings',
flags: 0,
hint: settings.hint || ''
}
};
if (typeof settings.cur_password === 'string' &&
settings.cur_password.length > 0) {
currentHashPromise = makePasswordHash(state.current_salt, settings.cur_password);
} else {
currentHashPromise = $q.when([]);
}
if (typeof settings.new_password === 'string' &&
settings.new_password.length > 0) {
var saltRandom = new Array(8);
var newSalt = bufferConcat(state.new_salt, saltRandom);
MtpSecureRandom.nextBytes(saltRandom);
newHashPromise = makePasswordHash(newSalt, settings.new_password);
params.new_settings.new_salt = newSalt;
params.new_settings.flags |= 1;
} else {
if (typeof settings.new_password === 'string') {
params.new_settings.flags |= 1;
params.new_settings.new_salt = [];
}
newHashPromise = $q.when([]);
}
if (typeof settings.email === 'string') {
params.new_settings.flags |= 2;
params.new_settings.email = settings.email || '';
}
return $q.all([currentHashPromise, newHashPromise]).then(function (hashes) {
params.current_password_hash = hashes[0];
params.new_settings.new_password_hash = hashes[1];
return MtpApiManager.invokeApi('account.updatePasswordSettings', params);
});
}
function check (state, password, options) {
return makePasswordHash(state.current_salt, password).then(function (passwordHash) {
return MtpApiManager.invokeApi('auth.checkPassword', {
password_hash: passwordHash
}, options);
});
}
function requestRecovery (state, options) {
return MtpApiManager.invokeApi('auth.requestPasswordRecovery', {}, options);
}
function recover (code, options) {
return MtpApiManager.invokeApi('auth.recoverPassword', {
code: code
}, options);
}
function makePasswordHash (salt, password) {
var passwordUTF8 = unescape(encodeURIComponent(password));
var buffer = new ArrayBuffer(passwordUTF8.length);
var byteView = new Uint8Array(buffer);
for (var i = 0, len = passwordUTF8.length; i < len; i++) {
byteView[i] = passwordUTF8.charCodeAt(i);
}
buffer = bufferConcat(bufferConcat(salt, byteView), salt);
return CryptoWorker.sha256Hash(buffer);
}
}) })

27
app/less/app.less

@ -242,6 +242,9 @@ input[type="number"] {
padding: 0; padding: 0;
margin: 0 0 22px; margin: 0 0 22px;
} }
.md-input-grouped {
margin-bottom: 12px;
}
.md-input-label { .md-input-label {
font-weight: normal; font-weight: normal;
@ -974,6 +977,12 @@ a.tg_radio_on:hover i.icon-radio {
font-size: 13px; font-size: 13px;
line-height: 160%; line-height: 160%;
} }
.login_form_hint {
color: #999;
margin: 0 0 20px;
font-size: 13px;
line-height: 160%;
}
.login_form_messaging { .login_form_messaging {
color: #999; color: #999;
font-size: 13px; font-size: 13px;
@ -1002,6 +1011,15 @@ a.tg_radio_on:hover i.icon-radio {
} }
} }
.login_forgot_button {
text-align: center;
margin: 30px 0 10px;
}
.login_reset_button {
text-align: center;
margin: 10px 0 0;
}
/* IM page start */ /* IM page start */
/* Dialogs list */ /* Dialogs list */
@ -3363,6 +3381,15 @@ a.countries_modal_search_clear {
&:hover { &:hover {
text-decoration: none; text-decoration: none;
} }
&.pull-right {
color: #3a6d99;
}
}
&_text {
display: block;
padding: 4px 0;
color: #777;
} }
&_version { &_version {

6
app/partials/desktop/confirm_modal.html

@ -43,6 +43,9 @@
<div ng-switch-when="MIGRATE_TO_HTTPS" my-i18n="confirm_modal_migrate_to_https_md"></div> <div ng-switch-when="MIGRATE_TO_HTTPS" my-i18n="confirm_modal_migrate_to_https_md"></div>
<div ng-switch-when="SWITCH_DESKTOP_VERSION" my-i18n="confirm_modal_resize_desktop_md"></div> <div ng-switch-when="SWITCH_DESKTOP_VERSION" my-i18n="confirm_modal_resize_desktop_md"></div>
<div ng-switch-when="SWITCH_MOBILE_VERSION" my-i18n="confirm_modal_resize_mobile_md"></div> <div ng-switch-when="SWITCH_MOBILE_VERSION" my-i18n="confirm_modal_resize_mobile_md"></div>
<div ng-switch-when="RECOVERY_EMAIL_EMPTY" my-i18n="confirm_modal_recovery_email_empty_md"></div>
<div ng-switch-when="PASSWORD_ABORT_SETUP" my-i18n="confirm_modal_abort_password_setup"></div>
<div ng-switch-when="RESET_ACCOUNT" my-i18n="confirm_modal_reset_account_md"></div>
<span ng-switch-default ng-switch="message.length > 0"> <span ng-switch-default ng-switch="message.length > 0">
<span ng-switch-when="true" ng-bind="message"></span> <span ng-switch-when="true" ng-bind="message"></span>
<span ng-switch-default my-i18n="confirm_modal_are_u_sure"></span> <span ng-switch-default my-i18n="confirm_modal_are_u_sure"></span>
@ -55,7 +58,7 @@
<button class="btn btn-md" ng-click="$dismiss()"> <button class="btn btn-md" ng-click="$dismiss()">
<span my-i18n="modal_cancel"></span> <span my-i18n="modal_cancel"></span>
</button> </button>
<button class="btn btn-md btn-md-primary" ng-switch="type" ng-click="$close()" my-focused > <button class="btn btn-md btn-md-primary" ng-switch="type" ng-click="$close()" ng-class="{'btn-md-danger': type == 'RESET_ACCOUNT'}" my-focused >
<span ng-switch-when="LOGOUT" my-i18n="confirm_modal_logout_submit"></span> <span ng-switch-when="LOGOUT" my-i18n="confirm_modal_logout_submit"></span>
<span ng-switch-when="HISTORY_FLUSH" my-i18n="confirm_modal_history_flush_submit"></span> <span ng-switch-when="HISTORY_FLUSH" my-i18n="confirm_modal_history_flush_submit"></span>
<span ng-switch-when="FILES_CLIPBOARD_PASTE" my-i18n="confirm_modal_clipboard_files_send_submit"></span> <span ng-switch-when="FILES_CLIPBOARD_PASTE" my-i18n="confirm_modal_clipboard_files_send_submit"></span>
@ -68,6 +71,7 @@
<span ng-switch-when="VIDEO_SHARE_PEER" my-i18n="confirm_modal_share_video_submit"></span> <span ng-switch-when="VIDEO_SHARE_PEER" my-i18n="confirm_modal_share_video_submit"></span>
<span ng-switch-when="SHARE_CONTACT_PEER" my-i18n="confirm_modal_share_contact_submit"></span> <span ng-switch-when="SHARE_CONTACT_PEER" my-i18n="confirm_modal_share_contact_submit"></span>
<span ng-switch-when="EXT_SHARE_PEER" my-i18n="confirm_modal_share_file_submit"></span> <span ng-switch-when="EXT_SHARE_PEER" my-i18n="confirm_modal_share_file_submit"></span>
<span ng-switch-when="RESET_ACCOUNT" my-i18n="confirm_modal_reset_account_submit"></span>
<span ng-switch-default my-i18n="modal_ok"></span> <span ng-switch-default my-i18n="modal_ok"></span>
</button> </button>
</div> </div>

4
app/partials/desktop/error_modal.html

@ -5,6 +5,7 @@
<h4 ng-if="error" class="md_simple_header" ng-switch="error.type"> <h4 ng-if="error" class="md_simple_header" ng-switch="error.type">
<span ng-switch-when="MEDIA_TYPE_NOT_SUPPORTED" my-i18n="error_modal_media_not_supported_title"></span> <span ng-switch-when="MEDIA_TYPE_NOT_SUPPORTED" my-i18n="error_modal_media_not_supported_title"></span>
<span ng-switch-when="USERNAME_NOT_OCCUPIED" my-i18n="error_modal_not_found_title"></span> <span ng-switch-when="USERNAME_NOT_OCCUPIED" my-i18n="error_modal_not_found_title"></span>
<span ng-switch-when="PASSWORD_RECOVERY_NA" my-i18n="error_modal_recovery_na_title"></span>
<span ng-switch-default ng-switch="error.code"> <span ng-switch-default ng-switch="error.code">
<span ng-switch-when="400" my-i18n="error_modal_bad_request_title"></span> <span ng-switch-when="400" my-i18n="error_modal_bad_request_title"></span>
<span ng-switch-when="401" my-i18n="error_modal_unauthorized_title"></span> <span ng-switch-when="401" my-i18n="error_modal_unauthorized_title"></span>
@ -37,6 +38,7 @@
<span ng-switch-when="USERNAME_OCCUPIED" my-i18n="error_modal_username_occupied_description"></span> <span ng-switch-when="USERNAME_OCCUPIED" my-i18n="error_modal_username_occupied_description"></span>
<span ng-switch-when="MEDIA_TYPE_NOT_SUPPORTED" my-i18n="error_modal_media_not_supported_description"></span> <span ng-switch-when="MEDIA_TYPE_NOT_SUPPORTED" my-i18n="error_modal_media_not_supported_description"></span>
<span ng-switch-when="USERNAME_NOT_OCCUPIED" my-i18n="error_modal_username_not_found_description"></span> <span ng-switch-when="USERNAME_NOT_OCCUPIED" my-i18n="error_modal_username_not_found_description"></span>
<span ng-switch-when="PASSWORD_RECOVERY_NA" my-i18n="error_modal_recovery_na_description"></span>
<div ng-switch-default ng-switch="error.code"> <div ng-switch-default ng-switch="error.code">
@ -66,7 +68,7 @@ Stack: {{error.originalError.stack || error.stack}}</textarea>
</div> </div>
<div class="md_simple_modal_footer"> <div class="md_simple_modal_footer">
<button class="btn btn-md btn-md-primary" ng-click="$dismiss()"> <button class="btn btn-md btn-md-primary" ng-click="$dismiss()" my-focused>
<span my-i18n="modal_ok"></span> <span my-i18n="modal_ok"></span>
</button> </button>
</div> </div>

27
app/partials/desktop/login.html

@ -3,7 +3,7 @@
<div class="login_page"> <div class="login_page">
<div class="login_head_wrap clearfix" ng-switch="progress.enabled"> <div class="login_head_wrap clearfix" ng-switch="progress.enabled">
<div ng-switch-when="true" class="login_head_submit_progress"> <div ng-switch-when="true" class="login_head_submit_progress">
<my-i18n ng-if="!credentials.phone_code_hash" msgid="login_generating_key"></my-i18n><my-i18n ng-if="credentials.phone_code_hash &amp;&amp; !credentials.phone_code_valid" msgid="login_checking_code"></my-i18n><my-i18n ng-if="credentials.phone_code_valid &amp;&amp; credentials.phone_unoccupied" msgid="login_signing_up"></my-i18n><span my-loading-dots></span> <my-i18n ng-if="!credentials.phone_code_hash" msgid="login_generating_key"></my-i18n><my-i18n ng-if="credentials.phone_code_hash &amp;&amp; !credentials.phone_code_valid" msgid="login_checking_code"></my-i18n><my-i18n ng-if="credentials.phone_code_valid &amp;&amp; credentials.phone_unoccupied" msgid="login_signing_up"></my-i18n><my-i18n ng-if="credentials.phone_code_valid &amp;&amp; credentials.password_needed" msgid="login_checking_password"></my-i18n><span my-loading-dots></span>
</div> </div>
<div ng-switch-default class="login_head_submit_wrap"> <div ng-switch-default class="login_head_submit_wrap">
<a class="login_head_submit_btn" ng-if="!credentials.phone_code_hash" ng-click="sendCode()"> <a class="login_head_submit_btn" ng-if="!credentials.phone_code_hash" ng-click="sendCode()">
@ -15,6 +15,9 @@
<a class="login_head_submit_btn" ng-if="credentials.phone_code_valid &amp;&amp; credentials.phone_unoccupied" ng-click="logIn(true)"> <a class="login_head_submit_btn" ng-if="credentials.phone_code_valid &amp;&amp; credentials.phone_unoccupied" ng-click="logIn(true)">
<my-i18n msgid="modal_next"></my-i18n><i class="icon icon-next-submit"></i> <my-i18n msgid="modal_next"></my-i18n><i class="icon icon-next-submit"></i>
</a> </a>
<a class="login_head_submit_btn" ng-if="credentials.phone_code_valid &amp;&amp; credentials.password_needed" ng-click="checkPassword()">
<my-i18n msgid="modal_next"></my-i18n><i class="icon icon-next-submit"></i>
</a>
</div> </div>
<a class="login_head_logo_link" href="https://telegram.org" target="_blank"> <a class="login_head_logo_link" href="https://telegram.org" target="_blank">
<i class="icon icon-tg-logo"></i><i class="icon icon-tg-title"></i> <i class="icon icon-tg-logo"></i><i class="icon icon-tg-title"></i>
@ -98,6 +101,28 @@
</form> </form>
<form name="myPasswordForm" ng-if="credentials.phone_code_valid &amp;&amp; credentials.password_needed" ng-submit="checkPassword()">
<h3 class="login_form_head" my-i18n="login_password_title"></h3>
<p class="login_form_lead" my-i18n="login_password_label"></p>
<div class="md-input-group" ng-class="{'md-input-error': error.field == 'password'}" my-labeled-input ng-switch="error.field == 'password'">
<label ng-switch-when="true" class="md-input-label" my-i18n="login_incorrect_password"></label>
<label ng-switch-default class="md-input-label" my-i18n="login_password"></label>
<input autocomplete="off" class="md-input" my-focused name="password" type="password" ng-model="credentials.password" my-submit-on-enter required />
</div>
<p ng-if="password.hint.length > 0" class="login_form_hint" ng-bind="password.hint"></p>
<div class="login_forgot_button">
<button class="btn btn-md" ng-click="forgotPassword($event)" my-i18n="login_password_forgot_link"></button>
</div>
<div ng-if="canReset" class="login_reset_button">
<button class="btn btn-md btn-md-danger" ng-click="resetAccount($event)" my-i18n="login_account_reset"></button>
</div>
</form>
</div> </div>
<div ng-switch="about.shown"> <div ng-switch="about.shown">

4
app/partials/desktop/message.html

@ -51,7 +51,7 @@
</div> </div>
<div class="im_message_text" ng-if="::historyMessage.message.length || false" ng-bind-html="::historyMessage.richMessage" dir="auto"></div> <div class="im_message_text" ng-if="::historyMessage.message.length || false" ng-bind-html="::historyMessage.richMessage" dir="auto"></div>
<!-- <div class="im_message_external_embed_wrap" ng-if="::historyMessage.richUrlEmbed || false" my-external-embed="historyMessage.richUrlEmbed"></div> --> <div class="im_message_external_embed_wrap" ng-if="::historyMessage.richUrlEmbed || false" my-external-embed="historyMessage.richUrlEmbed"></div>
<div ng-if="::historyMessage.media || historyMessage.id < 0 ? true : false" class="im_message_media" ng-switch="historyMessage.media._"> <div ng-if="::historyMessage.media || historyMessage.id < 0 ? true : false" class="im_message_media" ng-switch="historyMessage.media._">
<div ng-switch-when="messageMediaPhoto" my-message-photo></div> <div ng-switch-when="messageMediaPhoto" my-message-photo></div>
@ -60,7 +60,7 @@
<div ng-switch-when="messageMediaAudio" class="im_message_audio" my-audio-player audio="historyMessage.media.audio"></div> <div ng-switch-when="messageMediaAudio" class="im_message_audio" my-audio-player audio="historyMessage.media.audio"></div>
<div ng-switch-when="messageMediaGeo" my-message-map></div> <div ng-switch-when="messageMediaGeo" my-message-map></div>
<div ng-switch-when="messageMediaContact" class="im_message_contact" my-message-contact></div> <div ng-switch-when="messageMediaContact" class="im_message_contact" my-message-contact></div>
<div ng-switch-when="messageMediaWebPage" class="im_message_webpage" my-message-webpage="historyMessage.media.webpage"></div> <!-- <div ng-switch-when="messageMediaWebPage" class="im_message_webpage" my-message-webpage="historyMessage.media.webpage"></div> -->
<div ng-switch-when="messageMediaPending" my-message-pending></div> <div ng-switch-when="messageMediaPending" my-message-pending></div>
<div ng-switch-when="messageMediaUnsupported"> <div ng-switch-when="messageMediaUnsupported">
<div class="im_message_text"> <div class="im_message_text">

32
app/partials/desktop/password_recovery_modal.html

@ -0,0 +1,32 @@
<div class="md_simple_modal_wrap" my-modal-position>
<div class="md_simple_modal_body">
<form class="modal_simple_form" ng-submit="checkCode()">
<h4 my-i18n="login_recovery_title"></h4>
<div class="md_simple_form_description" my-i18n="login_recovery_description_md">
<my-i18n-param name="email">
<strong ng-bind="recovery.email_pattern"></strong>
</my-i18n-param>
</div>
<div class="md-input-group" ng-class="{'md-input-error': recovery.error_field == 'code'}" my-labeled-input ng-switch="recovery.error_field == 'code'">
<label ng-switch-when="true" class="md-input-label" my-i18n="login_code_incorrect"></label>
<label ng-switch-default class="md-input-label" my-i18n="login_code_placeholder"></label>
<input class="md-input" my-focused type="text" ng-model="recovery.code" name="code" my-focused />
</div>
</form>
</div>
<div class="md_simple_modal_footer">
<button class="btn btn-md" ng-click="$dismiss()" my-i18n="modal_cancel"></button>
<button class="btn btn-md btn-md-primary" ng-class="{disabled: recovery.updating}" ng-click="checkCode()" ng-disabled="recovery.updating" ng-bind="recovery.updating ? 'password_recover_active' : 'password_recover_submit' | i18n"></button>
</div>
</div>

35
app/partials/desktop/password_update_modal.html

@ -4,34 +4,38 @@
<form class="modal_simple_form" ng-submit="updatePassword()"> <form class="modal_simple_form" ng-submit="updatePassword()">
<h4 my-i18n="username_edit_modal_title"></h4> <h4 ng-switch="action">
<my-i18n ng-switch-when="disable" msgid="password_delete_title"></my-i18n>
<my-i18n ng-switch-default msgid="password_change_title"></my-i18n>
</h4>
<div ng-if="password._ != 'account.noPassword'" class="md-input-group" ng-class="{'md-input-error': checked.error}" my-labeled-input> <div ng-if="password._ != 'account.noPassword'" class="md-input-group" ng-class="{'md-input-error': passwordSettings.error_field == 'cur_password'}" my-labeled-input>
<label class="md-input-label" my-i18n="password_current_placeholder"></label> <label class="md-input-label" my-i18n="password_current_placeholder"></label>
<input class="md-input" my-focused type="text" ng-model="passwordSettings.current_password" name="current_password" /> <input class="md-input" my-focused type="password" ng-model="passwordSettings.cur_password" name="cur_password" my-focus-on="cur_password_focus" />
</div> </div>
<div class="md-input-group" ng-class="{'md-input-error': checked.error}" my-labeled-input> <div ng-if="action != 'disable'" class="md-input-group md-input-grouped" ng-class="{'md-input-error': passwordSettings.error_field == 'new_password'}" my-labeled-input>
<label class="md-input-label" my-i18n="password_new_placeholder"></label> <label class="md-input-label" my-i18n="password_new_placeholder"></label>
<input class="md-input" type="text" ng-model="passwordSettings.new_password" name="new_password" /> <input class="md-input" type="password" ng-model="passwordSettings.new_password" name="new_password" my-focus-on="new_password_focus" />
</div> </div>
<div class="md-input-group" ng-class="{'md-input-error': checked.error}" my-labeled-input> <div ng-if="action != 'disable'" class="md-input-group" ng-class="{'md-input-error': passwordSettings.confirm_password && passwordSettings.new_password && passwordSettings.confirm_password != passwordSettings.new_password}" my-labeled-input>
<label class="md-input-label" my-i18n="password_new_placeholder"></label> <label class="md-input-label" my-i18n="password_confirm_placeholder"></label>
<input class="md-input" type="text" ng-model="passwordSettings.confirm_password" name="confirm_password" /> <input class="md-input" type="password" ng-model="passwordSettings.confirm_password" name="confirm_password" my-focus-on="confirm_password_focus" />
</div> </div>
<div class="md-input-group" ng-class="{'md-input-error': checked.error}" my-labeled-input> <div ng-if="action != 'disable'" class="md-input-group" my-labeled-input>
<label class="md-input-label" my-i18n="password_new_placeholder"></label> <label class="md-input-label" my-i18n="password_hint_placeholder"></label>
<input class="md-input" type="text" ng-model="passwordSettings.hint" name="hint" /> <input class="md-input" type="text" ng-model="passwordSettings.hint" name="hint" />
</div> </div>
<div class="md-input-group" ng-class="{'md-input-error': checked.error}" my-labeled-input> <div ng-if="action != 'disable'" class="md_simple_form_description" my-i18n="password_create_description"></div>
<label class="md-input-label" my-i18n="password_new_placeholder"></label>
<div ng-if="action != 'disable'" class="md-input-group" ng-class="{'md-input-error': passwordSettings.error_field == 'email'}" my-labeled-input>
<label class="md-input-label" my-i18n="password_email_placeholder"></label>
<input class="md-input" type="text" ng-model="passwordSettings.email" name="email" /> <input class="md-input" type="text" ng-model="passwordSettings.email" name="email" />
</div> </div>
<div class="md_simple_form_description" my-i18n="username_edit_description_md"></div>
</form> </form>
@ -40,7 +44,10 @@
<div class="md_simple_modal_footer"> <div class="md_simple_modal_footer">
<button class="btn btn-md" ng-click="$dismiss()" my-i18n="modal_cancel"></button> <button class="btn btn-md" ng-click="$dismiss()" my-i18n="modal_cancel"></button>
<button class="btn btn-md btn-md-primary" ng-class="{disabled: passwordSettings.updating}" ng-click="updatePassword()" ng-bind="passwordSettings.updating ? 'username_edit_submit_active' : 'username_edit_submit' | i18n" ng-disabled="passwordSettings.updating"></button> <button class="btn btn-md btn-md-primary" ng-class="{disabled: passwordSettings.updating}" ng-click="updatePassword()" ng-disabled="passwordSettings.updating" ng-switch="action">
<span ng-switch-when="disable" ng-bind="passwordSettings.updating ? 'password_delete_active' : 'password_delete_submit' | i18n"></span>
<span ng-switch-default ng-bind="passwordSettings.updating ? 'password_create_active' : 'password_create_submit' | i18n"></span>
</button>
</div> </div>

15
app/partials/desktop/settings_modal.html

@ -111,10 +111,17 @@
</div> </div>
<div class="md_modal_section_link_wrap"> <div class="md_modal_section_link_wrap">
<a ng-if="password._ == 'account.noPassword' && !password.email_unconfirmed_pattern" class="md_modal_section_link" ng-click="changePassword()">Set password</a> <a ng-if="password._ == 'account.noPassword' && password.email_unconfirmed_pattern.length" class="md_modal_section_link pull-right" ng-click="changePassword({action: 'cancel_email'})" my-i18n="settings_modal_password_email_pending_cancel">
<a ng-if="password._ == 'account.noPassword' && password.email_unconfirmed_pattern.length" class="md_modal_section_link" ng-click="changePassword({cancelEmail: true})">Cancel pending {{password.email_unconfirmed_pattern}}</a> </a>
<a ng-if="password._ == 'account.password'" class="md_modal_section_link" ng-click="updatePassword({disable: true})">Turn off</a> <span class="md_modal_section_text" ng-if="password._ == 'account.noPassword' && password.email_unconfirmed_pattern.length" class="md_modal_section_link" my-i18n="settings_modal_password_email_pending">
<a ng-if="password._ == 'account.password'" class="md_modal_section_link" ng-click="changePassword()">Change password</a> <my-i18n-param name="email">
<span ng-bind="password.email_unconfirmed_pattern"></span>
</my-i18n-param>
</span>
<a ng-if="password._ == 'account.noPassword' && !password.email_unconfirmed_pattern" class="md_modal_section_link" ng-click="changePassword({action: 'create'})" my-i18n="settings_modal_set_password"></a>
<a ng-if="password._ == 'account.password'" class="md_modal_section_link pull-right" ng-click="changePassword({action: 'disable'})" my-i18n="settings_modal_disable_password"></a>
<a ng-if="password._ == 'account.password'" class="md_modal_section_link" ng-click="changePassword({action: 'change'})" my-i18n="settings_modal_change_password"></a>
</div> </div>
<div class="md_modal_section_link_wrap"> <div class="md_modal_section_link_wrap">

Loading…
Cancel
Save