change password 1
This commit is contained in:
@@ -25,11 +25,12 @@ namespace Espo\Controllers;
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
use \Espo\Core\Exceptions\NotFound;
|
||||
use \Espo\Core\Exceptions\Forbidden;
|
||||
use \Espo\Core\Exceptions\BadRequest;
|
||||
|
||||
class User extends \Espo\Core\Controllers\Record
|
||||
{
|
||||
{
|
||||
public function actionAcl($params, $data, $request)
|
||||
{
|
||||
{
|
||||
$userId = $request->get('id');
|
||||
if (empty($userId)) {
|
||||
throw new Error();
|
||||
@@ -46,12 +47,28 @@ class User extends \Espo\Core\Controllers\Record
|
||||
|
||||
$acl = new \Espo\Core\Acl($user, $this->getConfig(), $this->getContainer()->get('fileManager'), $this->getMetadata());
|
||||
|
||||
return $acl->toArray();
|
||||
return $acl->toArray();
|
||||
}
|
||||
|
||||
public function actionChangeOwnPassword($params, $data)
|
||||
{
|
||||
return $this->getService('User')->changePassword($this->getUser()->id, $data['password']);
|
||||
}
|
||||
|
||||
public function actionPasswordChangeRequest($params, $data, $request)
|
||||
{
|
||||
if (!$request->isPost()) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
if (empty($data['userName']) || empty($data['emailAddress'])) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$userName = $data['userName'];
|
||||
$emailAddress = $data['emailAddress'];
|
||||
|
||||
return $this->getService('User')->passwordChangeRequest($userName, $emailAddress);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Entities;
|
||||
|
||||
class PasswordChangeRequest extends \Espo\Core\ORM\Entity
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -129,7 +129,8 @@
|
||||
"See more": "See more",
|
||||
"Today": "Today",
|
||||
"Tomorrow": "Tomorrow",
|
||||
"Yesterday": "Yesterday"
|
||||
"Yesterday": "Yesterday",
|
||||
"Submit": "Submit"
|
||||
},
|
||||
"messages": {
|
||||
"notModified": "You have not modified the record",
|
||||
|
||||
@@ -23,7 +23,13 @@
|
||||
"Access": "Access",
|
||||
"Preferences": "Preferences",
|
||||
"Change Password": "Change Password",
|
||||
"Teams and Access Control": "Teams and Access Control"
|
||||
"Teams and Access Control": "Teams and Access Control",
|
||||
"Forgot Password?": "Forgot Password?",
|
||||
"Password Change Request": "Password Change Request",
|
||||
"Username": "Username",
|
||||
"Email Address": "Email Address",
|
||||
"Password": "Password",
|
||||
"Login": "Login"
|
||||
},
|
||||
"tooltips": {
|
||||
"defaultTeam": "All records created by this user will be related to this team by default.",
|
||||
@@ -36,6 +42,8 @@
|
||||
"passwordWillBeSent": "Password will be sent to user's email address.",
|
||||
"accountInfoEmailSubject": "Account info",
|
||||
"accountInfoEmailBody": "Your account information:\n\nUsername: {userName}\nPassword: {password}\n\n{siteUrl}",
|
||||
"passwordChangeLinkEmailSubject": "EspoCRM: Change Password Request",
|
||||
"passwordChangeLinkEmailBody": "Open {link} to change your password. This unique url will be exprired soon.",
|
||||
"passwordChanged": "Password has been changed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
["teams","title"]
|
||||
["teams", "emailAddress", "title"]
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"fields": {
|
||||
"requestId": {
|
||||
"type": "varchar",
|
||||
"maxLength": 24,
|
||||
"index": true
|
||||
},
|
||||
"user": {
|
||||
"type": "link",
|
||||
"readOnly": true,
|
||||
"index": true
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "datetime",
|
||||
"readOnly": true
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"user": {
|
||||
"type": "belongsTo",
|
||||
"entity": "User"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"entity": true,
|
||||
"layouts": false,
|
||||
"tab": false,
|
||||
"acl": false,
|
||||
"customizable": false
|
||||
}
|
||||
@@ -49,6 +49,18 @@
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"route":"/PasswordChangeRequest",
|
||||
"method": "post",
|
||||
"params": {
|
||||
"controller":"User",
|
||||
"action": "passwordChangeRequest"
|
||||
},
|
||||
"conditions": {
|
||||
"auth":false
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"route":"/Stream",
|
||||
"method":"get",
|
||||
|
||||
@@ -30,22 +30,28 @@ use \Espo\ORM\Entity;
|
||||
|
||||
class User extends Record
|
||||
{
|
||||
const PASSWORD_CHANGE_REQUEST_LIFETIME = 360; // minutes
|
||||
|
||||
protected function init()
|
||||
{
|
||||
$this->dependencies[] = 'mailSender';
|
||||
$this->dependencies[] = 'language';
|
||||
$this->dependencies[] = 'container';
|
||||
}
|
||||
|
||||
protected $internalFields = array('password');
|
||||
|
||||
protected function getMailSender()
|
||||
{
|
||||
return $this->injections['mailSender'];
|
||||
return $this->getContainer()->get('mailSender');
|
||||
}
|
||||
|
||||
protected function getLanguage()
|
||||
{
|
||||
return $this->injections['language'];
|
||||
return $this->getContainer()->get('language');
|
||||
}
|
||||
|
||||
protected function getContainer()
|
||||
{
|
||||
return $this->injections['container'];
|
||||
}
|
||||
|
||||
public function getEntity($id)
|
||||
@@ -91,6 +97,73 @@ class User extends Record
|
||||
return true;
|
||||
}
|
||||
|
||||
public function passwordChangeRequest($userName, $emailAddress)
|
||||
{
|
||||
$user = $this->getEntityManager()->getRepository('User')->where(array(
|
||||
'userName' => $userName,
|
||||
'emailAddress' => $emailAddress
|
||||
))->findOne();
|
||||
|
||||
if (empty($user)) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$userId = $user->id;
|
||||
|
||||
$passwordChangeRequest = $this->getEntityManager()->getRepository('PasswordChangeRequest')->where(array(
|
||||
'userId' => $userId
|
||||
))->findOne();
|
||||
if ($passwordChangeRequest) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$requestId = uniqid();
|
||||
|
||||
$passwordChangeRequest = $this->getEntityManager()->getEntity('PasswordChangeRequest');
|
||||
$passwordChangeRequest->set(array(
|
||||
'userId' => $userId,
|
||||
'requestId' => $requestId
|
||||
));
|
||||
|
||||
$this->sendChangePasswordLink($requestId, $emailAddress);
|
||||
|
||||
$this->getEntityManager()->saveEntity($passwordChangeRequest);
|
||||
|
||||
if (!$passwordChangeRequest->id) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$dt = new \DateTime();
|
||||
$dt->add(\DateTimeInterval('P'. self::PASSWORD_CHANGE_REQUEST_LIFETIME . 'i'));
|
||||
|
||||
$job->set(array(
|
||||
'serviceName' => 'User',
|
||||
'method' => 'removeChangePasswordRequestJob',
|
||||
'data' => json_encode(array(
|
||||
'id' => $passwordChangeRequest->id,
|
||||
)),
|
||||
'executeTime' => $dt->format('Y-m-d H:i:s') ,
|
||||
));
|
||||
|
||||
$this->getEntityManager()->saveEntity($job);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function removeChangePasswordRequestJob($data)
|
||||
{
|
||||
$id = $data->id;
|
||||
if (empty($id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$p = $this->getEntityManager()->getEntity('PasswordChangeRequest', $data->id);
|
||||
if ($p) {
|
||||
$this->getEntityManager()->removeEntity($p);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function hashPassword($password)
|
||||
{
|
||||
$config = $this->getConfig();
|
||||
@@ -168,6 +241,35 @@ class User extends Record
|
||||
$this->getMailSender()->send($email);
|
||||
}
|
||||
|
||||
protected function sendChangePasswordLink($requestId, $emailAddress, Entity $user = null)
|
||||
{
|
||||
if (empty($emailAddress)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$email = $this->getEntityManager()->getEntity('Email');
|
||||
|
||||
if (!$this->getConfig()->get('smtpServer')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subject = $this->getLanguage()->translate('passwordChangeLinkEmailSubject', 'messages', 'User');
|
||||
$body = $this->getLanguage()->translate('passwordChangeLinkEmailBody', 'messages', 'User');
|
||||
|
||||
$link = $this->getConfig()->get('siteUrl') . '?entryPoint=changePassword&id=' . $requestId;
|
||||
|
||||
$body = str_replace('{link}', $this->getConfig()->get('siteUrl'), $body);
|
||||
|
||||
$email->set(array(
|
||||
'subject' => $subject,
|
||||
'body' => $body,
|
||||
'isHtml' => false,
|
||||
'to' => $emailAddress
|
||||
));
|
||||
|
||||
$this->getMailSender()->send($email);
|
||||
}
|
||||
|
||||
public function deleteEntity($id)
|
||||
{
|
||||
if ($id == 'system') {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="container content">
|
||||
<div class="col-md-4 col-md-offset-4 col-sm-8 col-sm-offset-2">
|
||||
<div class="col-md-4 col-md-offset-4 col-sm-8 col-sm-offset-2">
|
||||
<div id="login" class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<img src="{{logoSrc}}"></img>
|
||||
@@ -8,15 +8,16 @@
|
||||
<div>
|
||||
<form id="login-form" onsubmit="return false;">
|
||||
<div class="form-group">
|
||||
<label for="field-username">{{translate 'Username'}}</label>
|
||||
<label for="field-username">{{translate 'Username' scope='User'}}</label>
|
||||
<input type="text" name="username" id="field-userName" class="form-control" autocapitalize="off" autocorrect="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="login">{{translate 'Password'}}</label>
|
||||
<label for="login">{{translate 'Password' scope='User'}}</label>
|
||||
<input type="password" name="password" id="field-password" class="form-control">
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn btn-primary" id="btn-login">{{translate 'Login'}}</button>
|
||||
<a href="javascript:" class="btn btn-link pull-right" data-action="passwordChangeRequest">{{translate 'Forgot Password?' scope='User'}}</a>
|
||||
<button type="submit" class="btn btn-primary" id="btn-login">{{translate 'Login' scope='User'}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
<div class="cell cell-userName form-group">
|
||||
<label class="field-label-userName control-label">{{translate 'Username' scope='User'}}</label>
|
||||
<div class="field field-userName">
|
||||
<input type="text" name="userName" class="form-control" autocomplete="off" autocorrect="off" autocapitalize="off">
|
||||
</div>
|
||||
</div>
|
||||
<div class="cell cell-emailAddress form-group">
|
||||
<label class="field-label-emailAddress control-label">{{translate 'Email Address' scope='User'}}</label>
|
||||
<div class="field field-emailAddress">
|
||||
<input type="text" name="emailAddress" class="form-control" autocomplete="off" autocorrect="off" autocapitalize="off">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="msg-box hidden"></div>
|
||||
@@ -36,6 +36,9 @@ Espo.define('Views.Login', 'View', function (Dep) {
|
||||
'submit #login-form': function (e) {
|
||||
this.login();
|
||||
return false;
|
||||
},
|
||||
'click a[data-action="passwordChangeRequest"]': function (e) {
|
||||
this.showPasswordChangeRequest();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -62,17 +65,17 @@ Espo.define('Views.Login', 'View', function (Dep) {
|
||||
if (userName == '') {
|
||||
var $el = $("#field-userName");
|
||||
|
||||
var message = this.getLanguage().translate('Username can not be empty!');
|
||||
var message = this.getLanguage().translate('Username can not be empty', 'labels', 'User');
|
||||
$el.popover({
|
||||
placement: 'bottom',
|
||||
content: message,
|
||||
trigger: 'manual',
|
||||
}).popover('show');
|
||||
|
||||
var cell = $el.closest('.form-group');
|
||||
cell.addClass('has-error');
|
||||
var $cell = $el.closest('.form-group');
|
||||
$cell.addClass('has-error');
|
||||
this.$el.one('mousedown click', function () {
|
||||
cell.removeClass('has-error');
|
||||
$cell.removeClass('has-error');
|
||||
$el.popover('destroy');
|
||||
});
|
||||
return;
|
||||
@@ -118,6 +121,15 @@ Espo.define('Views.Login', 'View', function (Dep) {
|
||||
});
|
||||
this.notify('Wrong username/password', 'error');
|
||||
},
|
||||
|
||||
showPasswordChangeRequest: function () {
|
||||
this.notify('Please wait...');
|
||||
this.createView('passwordChangeRequest', 'Modals.PasswordChangeRequest', {
|
||||
}, function (view) {
|
||||
view.render();
|
||||
view.notify(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
************************************************************************/
|
||||
|
||||
Espo.define('Views.Modals.PasswordChangeRequest', 'Views.Modal', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
cssName: 'password-change-request',
|
||||
|
||||
template: 'modals.password-change-request',
|
||||
|
||||
setup: function () {
|
||||
|
||||
this.buttons = [
|
||||
{
|
||||
name: 'submit',
|
||||
label: 'Submit',
|
||||
style: 'danger',
|
||||
onClick: function (dialog) {
|
||||
this.submit();
|
||||
}.bind(this)
|
||||
},
|
||||
{
|
||||
name: 'cancel',
|
||||
label: 'Cancel',
|
||||
onClick: function (dialog) {
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
this.header = this.translate('Password Change Request', 'labels', 'User');
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
var $userName = this.$el.find('input[name="userName"]');
|
||||
var $emailAddress = this.$el.find('input[name="emailAddress"]');
|
||||
|
||||
$userName.popover('destroy');
|
||||
$emailAddress.popover('destroy');
|
||||
|
||||
var userName = $userName.val();
|
||||
var emailAddress = $emailAddress.val();
|
||||
|
||||
var isValid = true;
|
||||
if (userName == '') {
|
||||
isValid = false;
|
||||
|
||||
var message = this.getLanguage().translate('Username can not be empty', 'labels', 'User');
|
||||
|
||||
$userName.popover({
|
||||
placement: 'bottom',
|
||||
content: message,
|
||||
trigger: 'manual',
|
||||
}).popover('show');
|
||||
|
||||
var $cellUserName = $userName.closest('.form-group');
|
||||
$cellUserName.addClass('has-error');
|
||||
|
||||
|
||||
$userName.one('mousedown click', function () {
|
||||
$cellUserName.removeClass('has-error');
|
||||
$userName.popover('destroy');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
var isValid = true;
|
||||
if (emailAddress == '') {
|
||||
isValid = false;
|
||||
|
||||
var message = this.getLanguage().translate('Email Address can not be empty', 'labels', 'User');
|
||||
|
||||
$emailAddress.popover({
|
||||
placement: 'bottom',
|
||||
content: message,
|
||||
trigger: 'manual',
|
||||
}).popover('show');
|
||||
|
||||
var $cellEmailAddress = $emailAddress.closest('.form-group');
|
||||
$cellEmailAddress.addClass('has-error');
|
||||
|
||||
$emailAddress.one('mousedown click', function () {
|
||||
$cellEmailAddress.removeClass('has-error');
|
||||
$emailAddress.popover('destroy');
|
||||
});
|
||||
}
|
||||
|
||||
if (!isValid) return;
|
||||
|
||||
$submit = this.$el.find('.msg-box');
|
||||
$submit.addClass('disabled');
|
||||
this.notify('Please wait...');
|
||||
|
||||
$.ajax({
|
||||
url: 'PasswordChangeRequest',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({
|
||||
userName: userName,
|
||||
emailAddress: emailAddress
|
||||
}),
|
||||
error: function (xhr) {
|
||||
if (xhr.status == 404) {
|
||||
this.notify(this.translate('Username/Email Address not found', 'labels', 'User'), 'error');
|
||||
xhr.errorIsHandled = true;
|
||||
}
|
||||
$submit.removeClass('disabled');
|
||||
}.bind(this)
|
||||
}).done(function () {
|
||||
var msg = this.translate('The unique link has been sent to the specified email address.', 'labels', 'User');
|
||||
|
||||
this.$el.find('.cell-userName').addClass('hidden');
|
||||
this.$el.find('.cell-emailAddress').addClass('hidden');
|
||||
$submit.addClass('hidden');
|
||||
this.$el.find('.msg-box').removeClass('hidden');
|
||||
|
||||
this.$el.find('.msg-box').html('<span class="text-success">' + msg + '</span>');
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user