diff --git a/config/zentaopms.php b/config/zentaopms.php index 9cce4c78d7..ab04468a96 100644 --- a/config/zentaopms.php +++ b/config/zentaopms.php @@ -188,6 +188,7 @@ $config->openMethods[] = 'user.deny'; $config->openMethods[] = 'user.reset'; $config->openMethods[] = 'user.forgetpassword'; $config->openMethods[] = 'user.refreshrandom'; +$config->openMethods[] = 'user.resetpassword'; $config->openMethods[] = 'api.getsessionid'; $config->openMethods[] = 'misc.checktable'; $config->openMethods[] = 'misc.qrcode'; diff --git a/db/update17.1.sql b/db/update17.1.sql new file mode 100644 index 0000000000..f04fd43858 --- /dev/null +++ b/db/update17.1.sql @@ -0,0 +1 @@ +ALTER TABLE `zt_user` ADD `resetToken` varchar(50) NOT NULL AFTER `scoreLevel`; diff --git a/db/zentao.sql b/db/zentao.sql index 61a99aecd5..36e6320c41 100755 --- a/db/zentao.sql +++ b/db/zentao.sql @@ -1717,6 +1717,7 @@ CREATE TABLE IF NOT EXISTS `zt_user` ( `ranzhi` char(30) NOT NULL default '', `score` INT(11) NOT NULL DEFAULT '0', `scoreLevel` INT(11) NOT NULL DEFAULT '0', + `whatsapp` varchar(50) NOT NULL, `deleted` enum('0','1') NOT NULL default '0', PRIMARY KEY (`id`), UNIQUE KEY `account` (`account`), diff --git a/module/admin/lang/en.php b/module/admin/lang/en.php index 588ccac8d3..afeddcd82c 100644 --- a/module/admin/lang/en.php +++ b/module/admin/lang/en.php @@ -27,7 +27,7 @@ $lang->admin->api = 'API'; $lang->admin->log = 'Log'; $lang->admin->setting = 'Setting'; $lang->admin->days = 'Valid Day'; -$lang->admin->resetPWDByMail = 'Reset the password through the email'; +$lang->admin->resetPWDByMail = 'Reset the password via the email'; $lang->admin->info = new stdclass(); $lang->admin->info->version = 'Current Version is %s. '; diff --git a/module/mail/lang/en.php b/module/mail/lang/en.php index 4014966e46..8c363ee2e3 100755 --- a/module/mail/lang/en.php +++ b/module/mail/lang/en.php @@ -114,5 +114,11 @@ $lang->mail->ztCloudNotice = <<mail->forgetPassword = <<Hello:

+

You are applying to reset your ZenTao password. The link is valid for three minutes. If it expires, please apply again.

+

Click here to jump

+EOT; + $lang->mail->placeholder = new stdclass(); $lang->mail->placeholder->password = 'Some Email servers require auth code, refer to your Email service provider.'; diff --git a/module/mail/lang/zh-cn.php b/module/mail/lang/zh-cn.php index 2e31f6f213..f90a6bf78b 100755 --- a/module/mail/lang/zh-cn.php +++ b/module/mail/lang/zh-cn.php @@ -114,5 +114,11 @@ $lang->mail->ztCloudNotice = <<如果不同意以上条款,就不能该服务。

EOD; +$lang->mail->forgetPassword = <<您好:

+

您正在申请重置禅道密码,重置链接三分钟内有效,过期请重新申请。

+

点击此处重置

+EOT; + $lang->mail->placeholder = new stdclass(); $lang->mail->placeholder->password = '有些邮箱需要填写单独申请的授权码,具体请到邮箱相关设置查询。'; diff --git a/module/user/config.php b/module/user/config.php index 46484e1b85..dda17731f3 100644 --- a/module/user/config.php +++ b/module/user/config.php @@ -13,10 +13,11 @@ $config->user->custom = new stdclass(); $config->user->custom->batchCreateFields = 'dept,join,email,gender'; $config->user->custom->batchEditFields = 'dept,join,email,commiter'; -$config->user->contactField = 'mobile,phone,qq,dingding,weixin,skype,whatsapp,slack'; -$config->user->failTimes = 6; -$config->user->lockMinutes = 10; -$config->user->batchCreate = 10; +$config->user->contactField = 'mobile,phone,qq,dingding,weixin,skype,whatsapp,slack'; +$config->user->failTimes = 6; +$config->user->lockMinutes = 10; +$config->user->batchCreate = 10; +$config->user->resetPasswordTimeout = 3; /* User detail fields for API against JIHU GitLab. */ $config->user->detailFields = 'id,account,realname,avatar'; diff --git a/module/user/control.php b/module/user/control.php index 16f3d59c7c..78ccdb68a0 100755 --- a/module/user/control.php +++ b/module/user/control.php @@ -1042,8 +1042,6 @@ class user extends control */ public function reset() { - if(isset($this->config->resetPWDByMail) and $this->config->resetPWDByMail) $this->locate($this->createLink('user', 'forgetPassword')); - if(!isset($_SESSION['resetFileName'])) { $resetFileName = $this->app->getBasePath() . 'tmp' . DIRECTORY_SEPARATOR . uniqid('reset_') . '.txt'; @@ -1088,9 +1086,86 @@ class user extends control */ public function forgetPassword() { + $this->app->loadLang('admin'); + $this->loadModel('mail'); + + if(!empty($_POST)) + { + /* Check account and email. */ + $account = $_POST['account']; + $email = $_POST['email']; + if(empty($account)) return $this->send(array('result' => 'fail', 'message' => $this->lang->user->error->accountEmpty)); + if(empty($email)) return $this->send(array('result' => 'fail', 'message' => $this->lang->user->error->emailEmpty)); + + $user = $this->dao->select('*')->from(TABLE_USER)->where('account')->eq($account)->fetch(); + if(empty($user)) return $this->send(array('result' => 'fail', 'message' => $this->lang->user->error->noUser)); + if(empty($user->email)) return $this->send(array('result' => 'fail', 'message' => $this->lang->user->error->noEmail)); + + if($user->email != $email) return $this->send(array('result' => 'fail', 'message' => $this->lang->user->error->errorEmail)); + if(!$this->config->mail->turnon) return $this->send(array('result' => 'fail', 'message' => $this->lang->user->error->emailSetting)); + + $code = uniqid(); + $this->dao->update(TABLE_USER)->set('resetToken')->eq(json_encode(array('code' => $code, 'endTime' => strtotime("+{$this->config->user->resetPasswordTimeout} minutes"))))->where('account')->eq($account)->exec(); + + $result = $this->mail->send($account, $this->lang->user->resetPWD, sprintf($this->lang->mail->forgetPassword, commonModel::getSysURL() . $this->inlink('resetPassword', 'code=' . $code)), true); + if(strstr($result, 'ERROR')) return $this->send(array('result' => 'fail', 'message' => $this->lang->user->error->sendMailFail), true); + + return $this->send(array('result' => 'success', 'message' => $this->lang->user->sendEmailSuccess)); + } + + $this->view->title = $this->lang->user->resetPassword; $this->display(); } + /** + * Reset password. + * + * @param string $code + * @access public + * @return void + */ + public function resetPassword($code) + { + $expired = true; + $user = $this->dao->select('account, resetToken')->from(TABLE_USER)->where('resetToken')->like('%"' . $code . '"%')->fetch(); + if($user) + { + $resetToken = json_decode($user->resetToken); + if($resetToken->endTime >= time()) $expired = false; + } + + if(!empty($_POST)) + { + if($expired) return $this->send(array('result' => 'fail', 'message' => $this->lang->user->linkExpired)); + + $this->user->resetPassword(); + if(dao::isError()) + { + if(empty($_POST['password2'])) dao::$errors['password2'][] = sprintf($this->lang->error->notempty, $this->lang->user->password); + + $response['result'] = 'fail'; + $response['message'] = dao::getError(); + + return $this->send($response); + } + + $this->dao->update(TABLE_USER)->set('resetToken')->eq('')->where('account')->eq($this->post->account)->exec(); + + $response['result'] = 'success'; + $response['message'] = $this->lang->saveSuccess; + $response['locate'] = $this->createLink('index'); + + return $this->send($response); + } + + $this->view->title = $this->lang->user->resetPWD; + $this->view->isLogon = $this->user->isLogon(); + $this->view->expired = $expired; + $this->view->user = empty($user) ? '' : $user; + + $this->display(); + } + /** * User dynamic. * diff --git a/module/user/css/forgetpassword.css b/module/user/css/forgetpassword.css new file mode 100644 index 0000000000..e0fceea585 --- /dev/null +++ b/module/user/css/forgetpassword.css @@ -0,0 +1,12 @@ +body {background: #1183fb linear-gradient(-90deg, #0a48d1 0%, #1183fb 100%); background-color: #1183fb;} +#forget {max-width: 400px !important; margin: 0 auto; margin-top: 13%;} +#forgetPanel {background: #fff; overflow: hidden; box-shadow: 0 0 20px 0 rgba(0,0,0,.1); border-radius: 3px;} +#title {font-size: 16px; font-weight: bold; text-align: center; margin: 20px 0; margin-bottom: 0px; line-height: 26px; padding: 0 50px;} +#forgetForm {padding: 0 40px; margin-top: 12px;} +#forgetForm label {margin-bottom: 8px; font-size: 13px; color: #3C4353;} +#forgetForm .form-group + .form-group {margin-top: 18px;} +#forgetForm .form-control {border-color: #B2B6BF; background: transparent; border-radius: 2px; height: 30px;} +#forgetForm .form-control:focus {border-color: #2272eb; background: #fff;} +#forgetForm .form-actions {padding-bottom: 20px;} +.required:after {top: 32px; right: -15px;} +.resetBox {float: right;} diff --git a/module/user/css/resetpassword.css b/module/user/css/resetpassword.css new file mode 100644 index 0000000000..6b4f07ad45 --- /dev/null +++ b/module/user/css/resetpassword.css @@ -0,0 +1,11 @@ +body {background: #1183fb linear-gradient(-90deg, #0a48d1 0%, #1183fb 100%); background-color: #1183fb;} +#reset {max-width: 400px !important; margin: 0 auto; margin-top: 13%;} +#resetPanel {background: #fff; overflow: hidden; box-shadow: 0 0 20px 0 rgba(0,0,0,.1); border-radius: 3px;} +#title {font-size: 16px; font-weight: bold; text-align: center; margin: 20px 0; margin-bottom: 0px; line-height: 26px; padding: 0 50px;} +#resetForm {padding: 0 40px; margin-top: 12px;} +#resetForm label {margin-bottom: 8px; font-size: 13px; color: #3C4353;} +#resetForm .form-group + .form-group {margin-top: 18px;} +#resetForm .form-control {border-color: #B2B6BF; background: transparent; border-radius: 2px; height: 30px;} +#resetForm .form-control:focus {border-color: #2272eb; background: #fff;} +#resetForm .form-actions {padding-bottom: 20px;} +.required:after {top: 32px; right: -15px;} diff --git a/module/user/js/forgetpassword.js b/module/user/js/forgetpassword.js new file mode 100644 index 0000000000..8776fd5f8d --- /dev/null +++ b/module/user/js/forgetpassword.js @@ -0,0 +1,10 @@ +$('#submit').click(function() +{ + $(this).attr('disabled', true); + url = createLink('user', 'forgetPassword'); + $.post(url, {account: $('#account').val(), email: $('#email').val()}, function(data) + { + $('#submit').attr('disabled', false); + bootbox.alert(data.message); + }, 'json') +}) diff --git a/module/user/js/resetpassword.js b/module/user/js/resetpassword.js new file mode 100644 index 0000000000..b6cad7026b --- /dev/null +++ b/module/user/js/resetpassword.js @@ -0,0 +1,12 @@ +$(function() +{ + if(expired) + { + setInterval(function() + { + var time = $('#time').text(); + if(time == 0) window.location.href = $('.alert .btn').attr('href'); + $('#time').text(time-1 <=0 ? 0 : time -1); + }, 1000); + } +}) diff --git a/module/user/lang/en.php b/module/user/lang/en.php index 407457c5cd..80dacf410e 100644 --- a/module/user/lang/en.php +++ b/module/user/lang/en.php @@ -18,6 +18,7 @@ $lang->user->dept = 'Department'; $lang->user->account = 'Account'; $lang->user->password = 'Password'; $lang->user->password2 = 'Repeat Password'; +$lang->user->password2AB = 'Repeat Password'; $lang->user->role = 'Role'; $lang->user->group = 'Privilege Group'; $lang->user->realname = 'Name'; @@ -74,6 +75,8 @@ $lang->user->projects = 'Project'; $lang->user->sprints = $lang->execution->common; $lang->user->identity = 'Identity'; $lang->user->switchVision = 'Switch to %s'; +$lang->user->submit = 'Submit'; +$lang->user->resetPWD = 'Reset Password'; $lang->user->legendBasic = 'Basic Information'; $lang->user->legendContribution = 'Contribution'; @@ -111,6 +114,8 @@ $lang->user->applyTemplate = 'Templates'; $lang->user->confirmDeleteTemplate = 'Do you want to delete this template?'; $lang->user->setPublicTemplate = 'Set as Public Template'; $lang->user->tplContentNotEmpty = 'The template content cannot be empty!'; +$lang->user->sendEmailSuccess = 'An email has been sent to your mailbox. Please check it.'; +$lang->user->linkExpired = 'The link has expired, please apply again.'; $lang->user->profile = 'Profile'; $lang->user->project = $lang->executionCommon . 's'; @@ -212,6 +217,7 @@ $lang->user->placeholder->verify = 'Please enter your ZenTao login password t $lang->user->placeholder->loginPassword = 'Enter your password'; $lang->user->placeholder->loginAccount = 'Enter your account'; $lang->user->placeholder->loginUrl = 'Enter your ZenTao address'; +$lang->user->placeholder->email = 'Enter your email'; $lang->user->placeholder->passwordStrength[1] = '>= 6 letters and numbers'; $lang->user->placeholder->passwordStrength[2] = '>= 10 letters, numbers and special characters'; @@ -233,6 +239,13 @@ $lang->user->error->verifyPassword = "Verification failed. Please enter your L $lang->user->error->originalPassword = "Old password is incorrect."; $lang->user->error->companyEmpty = "Company name must be not empty."; $lang->user->error->noAccess = "This user is not from your department. You have no access to this user information."; +$lang->user->error->accountEmpty = 'Account must be not empty !'; +$lang->user->error->emailEmpty = 'Email must be not empty !'; +$lang->user->error->noUser = 'Invalid account.'; +$lang->user->error->noEmail = 'The user does not register email. Please get in touch with the administrator to reset the password.'; +$lang->user->error->errorEmail = 'The account does not match the email. Please enter a new one.'; +$lang->user->error->emailSetting = 'No email is configured in the system. Contact the admin to reset the email.'; +$lang->user->error->sendMailFail = 'Message sending failed, please try again!'; $lang->user->contactFieldList['phone'] = $lang->user->phone; $lang->user->contactFieldList['mobile'] = $lang->user->mobile; @@ -293,3 +306,5 @@ $lang->user->mkdirLinux = <<Commond: chmod 777 -R %s. EOT; + +$lang->user->jumping = "This page will redirect to the previous page in 3 seconds. Redirect Now"; diff --git a/module/user/lang/zh-cn.php b/module/user/lang/zh-cn.php index 82e6f10579..0b3c94188f 100644 --- a/module/user/lang/zh-cn.php +++ b/module/user/lang/zh-cn.php @@ -18,6 +18,7 @@ $lang->user->dept = '部门'; $lang->user->account = '用户名'; $lang->user->password = '密码'; $lang->user->password2 = '请重复密码'; +$lang->user->password2AB = '重复密码'; $lang->user->role = '职位'; $lang->user->group = '权限分组'; $lang->user->realname = '姓名'; @@ -74,6 +75,8 @@ $lang->user->projects = '项目'; $lang->user->sprints = $lang->execution->common; $lang->user->identity = '身份'; $lang->user->switchVision = '切换到 %s'; +$lang->user->submit = '提交'; +$lang->user->resetPWD = '重置密码'; $lang->user->legendBasic = '基本资料'; $lang->user->legendContribution = '个人贡献'; @@ -111,6 +114,8 @@ $lang->user->applyTemplate = '应用模板'; $lang->user->confirmDeleteTemplate = '您确认要删除该模板吗?'; $lang->user->setPublicTemplate = '设为公共模板'; $lang->user->tplContentNotEmpty = '模板内容不能为空!'; +$lang->user->sendEmailSuccess = '已发送一封邮件至您的邮箱,请注意查收。'; +$lang->user->linkExpired = '链接已过期,请重新申请。'; $lang->user->profile = '档案'; $lang->user->project = $lang->executionCommon; @@ -212,6 +217,7 @@ $lang->user->placeholder->verify = '请输入您的系统登录密码'; $lang->user->placeholder->loginPassword = '请输入密码'; $lang->user->placeholder->loginAccount = '请输入用户名'; $lang->user->placeholder->loginUrl = '请输入禅道系统网址'; +$lang->user->placeholder->email = '请输入邮箱'; $lang->user->placeholder->passwordStrength[1] = '6位以上,包含大小写字母,数字。'; $lang->user->placeholder->passwordStrength[2] = '10位以上,包含大小写字母,数字,特殊字符。'; @@ -233,6 +239,13 @@ $lang->user->error->verifyPassword = "验证失败,请检查您的系统登 $lang->user->error->originalPassword = "原密码不正确"; $lang->user->error->companyEmpty = "公司名称不能为空!"; $lang->user->error->noAccess = "该人员和你不是同一部门,你无权访问该人员的工作信息。"; +$lang->user->error->accountEmpty = '用户名不能为空!'; +$lang->user->error->emailEmpty = '邮箱不能为空!'; +$lang->user->error->noUser = '用户不存在'; +$lang->user->error->noEmail = '该用户未绑定邮箱,请联系管理员以重置密码。'; +$lang->user->error->errorEmail = '用户名和邮箱不匹配,请重新输入。'; +$lang->user->error->emailSetting = '系统未配置发信邮箱,请联系管理员重置。'; +$lang->user->error->sendMailFail = '邮件发送失败,请重试!'; $lang->user->contactFieldList['phone'] = $lang->user->phone; $lang->user->contactFieldList['mobile'] = $lang->user->mobile; @@ -294,5 +307,7 @@ $lang->user->mkdirLinux = << EOT; +$lang->user->jumping = "10秒钟后页面将自动跳转登录页。 立即跳转"; + $lang->user->zentaoapp = new stdclass(); $lang->user->zentaoapp->logout = '退出登录'; diff --git a/module/user/view/forgetpassword.html.php b/module/user/view/forgetpassword.html.php new file mode 100644 index 0000000000..21df43dc61 --- /dev/null +++ b/module/user/view/forgetpassword.html.php @@ -0,0 +1,45 @@ + + * @package ZenTaoPMS + * @version $Id: forgetpassword.html.php 5084 2022-06-20 15:00:38Z $ + */ +include '../../common/view/header.lite.html.php'; +?> +
+
+
+

admin->resetPWDByMail;?>

+
+
+ + +
+ + user->placeholder->loginAccount}'");?> +
+
+ + user->placeholder->email}'");?> +
+ + + + + +
+ user->submit); + echo html::backButton(); + ?> +
+
+
+
+
+ + diff --git a/module/user/view/login.html.php b/module/user/view/login.html.php index d70aeca9e7..5defd8e49e 100644 --- a/module/user/view/login.html.php +++ b/module/user/view/login.html.php @@ -63,7 +63,8 @@ if(empty($config->notMd5Pwd))js::import($jsRoot . 'md5.js'); echo html::submitButton($lang->login, '', 'btn btn-primary'); if($app->company->guest) echo html::linkButton($lang->user->asGuest, $this->createLink($config->default->module)); echo html::hidden('referer', $referer); - echo html::a(inlink('reset'), $lang->user->resetPassword); + $resetLink = (isset($this->config->resetPWDByMail) and $this->config->resetPWDByMail) ? inlink('forgetPassword') : inlink('reset'); + echo html::a($resetLink, $lang->user->resetPassword); ?> diff --git a/module/user/view/resetpassword.html.php b/module/user/view/resetpassword.html.php new file mode 100644 index 0000000000..424932c2bd --- /dev/null +++ b/module/user/view/resetpassword.html.php @@ -0,0 +1,53 @@ + + * @package ZenTaoPMS + * @version $Id: resetpassword.html.php 5084 2022-06-21 09:47:38Z $ + */ +include '../../common/view/header.lite.html.php'; +?> + + + +
+
user->linkExpired . sprintf($lang->user->jumping, $isLogon ? helper::createLink('my', 'index') : inlink('login'));?>
+
+ +
+
+
+

user->resetPWD;?>

+
+
+ + +
+ + user->placeholder->loginPassword}' id='password'");?> +
+
+ + user->placeholder->loginPassword}'");?> +
+ + + + +
+ account); + echo html::submitButton($lang->user->submit); + echo html::backButton(); + ?> +
+
+
+
+
+ + +