PHP reports, PHP mailer using SMTP config from DB, password recovery beta

This commit is contained in:
Juan Ferrer Toribio 2016-10-13 17:07:48 +02:00
parent b05cf79591
commit cfb3419fd6
18 changed files with 362 additions and 206 deletions

View File

@ -23,77 +23,15 @@ module.exports = new Class
window.onunload = this._onWindowUnload.bind (this); window.onunload = this._onWindowUnload.bind (this);
Vn.Hash.initialize (); Vn.Hash.initialize ();
this._isGuest = new Vn.HashParam
({
type: Boolean,
key: 'guest'
});
this._isGuest.on ('changed', this._onGuestChange, this);
var conn = new Db.Connection (); var conn = new Db.Connection ();
this.link ({_conn: conn}, {'error': this._onConnError}); this.link ({_conn: conn}, {'error': this._onConnError});
} }
,run: function () ,run: function ()
{ {
var guest = localStorage.getItem ('hederaGuest'); var login = this._login = new Login ({conn: this._conn});
login.on ('login', this._onLogin, this);
if (guest) login.show ();
localStorage.removeItem ('hederaGuest');
if (localStorage.getItem ('vnToken'))
{
this._conn.open (null, null, null,
this._onAutoLogin.bind (this));
}
else if (this._isGuest.value || guest)
{
this._guestLogin ();
}
else
{
var login = this._login = new Login ({conn: this._conn});
login.on ('login', this._onLogin, this);
login.show ();
}
}
,_onGuestChange: function ()
{
if (this._isGuest.value)
this._guestLogin ();
}
,_guestLogin: function ()
{
this._conn.open (
null,
null,
null,
this._onGuestLogin.bind (this),
true
);
}
,_onGuestLogin: function (conn, success)
{
this._isGuest.value = undefined;
if (success)
{
localStorage.setItem ('hederaGuest', true);
this._onLogin ();
}
else
this.run ();
}
,_onAutoLogin: function (success)
{
if (!success)
this.run ();
else
this._onLogin ();
} }
,_onLogin: function () ,_onLogin: function ()
@ -107,8 +45,8 @@ module.exports = new Class
,_onLogout: function (gui) ,_onLogout: function (gui)
{ {
this._freeGui ();
localStorage.removeItem ('hederaGuest'); localStorage.removeItem ('hederaGuest');
this._freeGui ();
this.run (); this.run ();
} }

View File

@ -266,26 +266,15 @@
padding: 0.7em 20%; padding: 0.7em 20%;
} }
/* Links */ /* Social */
.vn-gui .links .vn-gui .social
{ {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
right: 0; right: 0;
padding: 0.8em; padding: 0.8em;
} }
.vn-gui .links a
{
padding: 0.1em;
display: block;
float: left;
max-width: 2.2em;
}
.vn-gui .links img
{
height: 1.8em;
}
/* Body */ /* Body */

View File

@ -28,19 +28,8 @@
<a id="test-link" class="test-link" href="#"></a> <a id="test-link" class="test-link" href="#"></a>
<ul id="main-menu" class="main-menu"></ul> <ul id="main-menu" class="main-menu"></ul>
</div> </div>
<div class="links"> <div class="social">
<a target="_blank" href="http://www.facebook.com/verdnatura"> <!-- <htk-social-bar/> -->
<img alt="Facebook" src="image/social/facebook.svg" title="Facebook"/>
</a>
<a target="_blank" href="https://twitter.com/Verdnatura">
<img alt="Twitter" src="image/social/twitter.svg" title="Twitter"/>
</a>
<a target="_blank" href="https://www.instagram.com/verdnatura">
<img alt="Instagram" src="image/social/instagram.svg" title="Instagram"/>
</a>
<a target="_blank" href="http://www.youtube.com/user/verdnatura">
<img alt="YouTube" src="image/social/youtube.svg" title="YouTube"/>
</a>
</div> </div>
</div> </div>
<div class="body"> <div class="body">

View File

@ -5,6 +5,7 @@ Vn.includeCss ('js/hedera/style.css');
Hedera = module.exports = { Hedera = module.exports = {
Login : require ('./login') Login : require ('./login')
,SocialBar : require ('./social-bar')
,Gui : require ('./gui') ,Gui : require ('./gui')
,Module : require ('./module') ,Module : require ('./module')
,Form : require ('./form') ,Form : require ('./form')

View File

@ -196,20 +196,6 @@ hr
{ {
margin-top: 3em; margin-top: 3em;
} }
.vn-login .social
{
text-align: center;
}
.vn-login .social a
{
display: inline-block;
margin: 0 .1em;
}
.vn-login .social img
{
height: 1.8em;
width: 1.8em;
}
.vn-login .contact .vn-login .contact
{ {
text-align: center; text-align: center;

View File

@ -13,10 +13,6 @@ module.exports = new Class
,set: function (x) ,set: function (x)
{ {
this.link ({_conn: x}, {'loading-changed': this._onConnLoadChange}); this.link ({_conn: x}, {'loading-changed': this._onConnLoadChange});
x.execQuery (
'SELECT title, link, icon FROM social '
+'WHERE priority ORDER BY priority',
this._onSocialQueryDone.bind (this));
} }
,get: function () ,get: function ()
{ {
@ -27,6 +23,7 @@ module.exports = new Class
,initialize: function (props) ,initialize: function (props)
{ {
this.parent (props);
this.builderInitString (Tpl); this.builderInitString (Tpl);
var self = this; var self = this;
@ -35,8 +32,6 @@ module.exports = new Class
self._onSubmit (); self._onSubmit ();
return false; return false;
}; };
this.parent (props);
} }
,_onConnLoadChange: function (conn, isLoading) ,_onConnLoadChange: function (conn, isLoading)
@ -51,16 +46,103 @@ module.exports = new Class
{ {
document.body.appendChild (this.node); document.body.appendChild (this.node);
var lastUser = localStorage.getItem ('hederaLastUser'); var isGuest = new Vn.HashParam
({
type: Boolean,
key: 'guest'
});
this.link ({_isGuest: isGuest}, {'changed': this._onGuestChange});
if (lastUser) var token = new Vn.HashParam
this.$('user').value = lastUser; ({
type: String,
key: 'token'
});
this.link ({_token: token}, {'changed': this._onTokenChange});
this._onGuestChange ();
this._onTokenChange ();
if (!this._loginStarted
&& (localStorage.getItem ('vnToken')
|| sessionStorage.getItem ('vnToken')))
this.login ();
this._focusUserInput (); if (!this._loginStarted)
{
var lastUser = localStorage.getItem ('hederaLastUser');
if (lastUser)
this.$('user').value = lastUser;
this._focusUserInput ();
}
}
,_onTokenChange: function ()
{
if (!this.loginStarted && this._token.value)
{
this._conn.token = this._token.value;
this.login ();
}
}
,_onGuestChange: function ()
{
var guest = localStorage.getItem ('hederaGuest');
if (!this.loginStarted && (this._isGuest.value || guest))
{
localStorage.setItem ('hederaGuest', true);
this.login ();
}
}
,_onSubmit: function ()
{
this.login (
this.$('user').value,
this.$('pass').value,
this.$('remember').checked
);
}
,login: function (user, pass, remember)
{
this._loginStarted = true;
this._conn.open (user, pass, remember,
this._onConnOpen.bind (this));
this._disableUi (true);
}
,_onConnOpen: function (conn, success, error)
{
this._token.value = undefined;
this._isGuest.value = undefined;
this._loginStarted = false;
this.$('pass').value = '';
this._disableUi (false);
if (success)
{
var user = this.$('user').value;
if (user && !localStorage.getItem ('hederaGuest'))
localStorage.setItem ('hederaLastUser', user);
this.signalEmit ('login');
}
else
{
localStorage.removeItem ('hederaGuest');
this._focusUserInput ();
}
} }
,hide: function () ,hide: function ()
{ {
this._isGuest.unref ();
this._token.unref ();
Vn.Node.remove (this.node); Vn.Node.remove (this.node);
} }
@ -78,32 +160,6 @@ module.exports = new Class
this.$('submit').disabled = disabled; this.$('submit').disabled = disabled;
} }
,_onSubmit: function ()
{
this._conn.open (
this.$('user').value,
this.$('pass').value,
this.$('remember').checked,
this._onConnOpen.bind (this)
);
this._disableUi (true);
}
,_onConnOpen: function (conn, success, error)
{
this.$('pass').value = '';
this._disableUi (false);
if (success)
{
localStorage.setItem ('hederaLastUser', this.$('user').value);
this.signalEmit ('login');
}
else
this._focusUserInput ();
}
,onPasswordLost: function () ,onPasswordLost: function ()
{ {
var user = this.$('user').value; var user = this.$('user').value;
@ -122,25 +178,5 @@ module.exports = new Class
else else
Htk.Toast.showError (error.message); Htk.Toast.showError (error.message);
} }
,_onSocialQueryDone: function (resultSet)
{
var res = resultSet.fetchResult ();
var social = this.$('social');
while (res.next ())
{
var a = document.createElement ('a');
a.href = res.get ('link');
a.target = '_blank';
social.appendChild (a);
var img = document.createElement ('img');
img.src = 'image/social/'+ res.get ('icon');
img.alt = res.get ('title');
img.title = res.get ('title');
a.appendChild (img);
}
}
}); });

View File

@ -50,6 +50,7 @@
</a> </a>
</div> </div>
<div class="footer"> <div class="footer">
<!-- <htk-social-bar/> -->
<div id="social" class="social"/> <div id="social" class="social"/>
<div class="contact"> <div class="contact">
<p><t>Login mail</t></p> <p><t>Login mail</t></p>

73
js/hedera/social-bar.js Normal file
View File

@ -0,0 +1,73 @@
module.exports = new Class
({
Extends: Htk.Widget
,Tag: 'htk-social-bar'
,Properties:
{
conn:
{
type: Db.Connection
,set: function (x)
{
this._conn = x;
}
,get: function ()
{
return this._conn;
}
},
priority:
{
type: Number
,set: function (x)
{
this._priority = x;
}
,get: function ()
{
return this._priority;
}
},
}
,_priority: null
,initialize: function ()
{
this.createElement ('div');
this._node.className = 'htk-social-bar';
}
,_refresh: function ()
{
if (!this._conn || this._priority === null)
return;
var query = 'SELECT title, link, icon FROM social '
+'WHERE priority ORDER BY priority';
this._conn.execQuery (query, this._onQueryDone.bind (this));
}
,_onQueryDone: function (resultSet)
{
Vn.Node.removeChilds (this._node);
var res = resultSet.fetchResult ();
while (res.next ())
{
var a = document.createElement ('a');
a.href = res.get ('link');
a.target = '_blank';
this._node.appendChild (a);
var img = document.createElement ('img');
img.src = 'image/social/'+ res.get ('icon');
img.alt = res.get ('title');
img.title = res.get ('title');
a.appendChild (img);
}
}
});

View File

@ -487,4 +487,20 @@ img.icon
margin-top: .5em; margin-top: .5em;
} }
/* Social bar */
.htk-social-bar
{
text-align: center;
}
.htk-social-bar a
{
display: inline-block;
margin: 0 .1em;
}
.htk-social-bar img
{
height: 1.8em;
width: 1.8em;
}

View File

@ -11,7 +11,7 @@ module.exports = new Class
,_connected: false ,_connected: false
,_requestsCount: 0 ,_requestsCount: 0
,_token: null ,token: null
/** /**
* Initilizes the connection object. * Initilizes the connection object.
@ -19,7 +19,7 @@ module.exports = new Class
,initialize: function () ,initialize: function ()
{ {
this.parent (); this.parent ();
this._token = this.fetchToken (); this.token = this.fetchToken ();
} }
,fetchToken: function () ,fetchToken: function ()
@ -40,7 +40,7 @@ module.exports = new Class
**/ **/
,open: function (user, pass, remember, callback) ,open: function (user, pass, remember, callback)
{ {
if (user != null) if (user)
{ {
var params = { var params = {
'user': user 'user': user
@ -63,10 +63,10 @@ module.exports = new Class
if (json && json.login) if (json && json.login)
{ {
this._connected = true; this._connected = true;
this._token = json.token; this.token = json.token;
var storage = remember ? localStorage : sessionStorage; var storage = remember ? localStorage : sessionStorage;
storage.setItem ('vnToken', this._token); storage.setItem ('vnToken', this.token);
this.signalEmit ('openned'); this.signalEmit ('openned');
} }
@ -103,7 +103,7 @@ module.exports = new Class
,_closeClient: function () ,_closeClient: function ()
{ {
this._connected = false; this._connected = false;
this._token = null; this.token = null;
localStorage.removeItem ('vnToken'); localStorage.removeItem ('vnToken');
sessionStorage.removeItem ('vnToken'); sessionStorage.removeItem ('vnToken');
@ -126,7 +126,7 @@ module.exports = new Class
{ {
if (json) if (json)
{ {
this._token = json; this.token = json;
this._isSupplant = true; this._isSupplant = true;
} }
@ -140,7 +140,7 @@ module.exports = new Class
,supplantEnd: function () ,supplantEnd: function ()
{ {
this._isSupplant = false; this._isSupplant = false;
this._token = this.fetchToken (); this.token = this.fetchToken ();
} }
/** /**
@ -177,8 +177,8 @@ module.exports = new Class
{ {
var formData = new FormData (form); var formData = new FormData (form);
if (this._token) if (this.token)
formData.append ('token', this._token); formData.append ('token', this.token);
var request = new XMLHttpRequest (); var request = new XMLHttpRequest ();
request.open ('post', form.action, true); request.open ('post', form.action, true);
@ -194,8 +194,8 @@ module.exports = new Class
*/ */
,sendWithUrl: function (params, callback, method, url) ,sendWithUrl: function (params, callback, method, url)
{ {
if (this._token) if (this.token)
params['token'] = this._token; params['token'] = this.token;
var request = new XMLHttpRequest (); var request = new XMLHttpRequest ();
request.open (method, url, true); request.open (method, url, true);

View File

@ -1,11 +0,0 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Recover password</title>
</head>
<body>
<a href="https://verdnatura.es/#!form=account/conf">
Recover password
</a>
</body>
</html>

View File

@ -0,0 +1,7 @@
<?php $title = s('Recover password') ?>
<p>
<?=s('Press on the following link to change your password.')?>
</p>
<a href="<?=$url?>">
<?=s('Recover password')?>
</a>

View File

@ -1,23 +1,42 @@
<?php <?php
use Vn\Web;
class RecoverPassword extends Vn\Web\JsonRequest class RecoverPassword extends Vn\Web\JsonRequest
{ {
const PARAMS = ['user']; const PARAMS = ['user'];
function run ($db)
{
$user = $db->getRow (
'SELECT c.`e-mail` mail, u.active
FROM vn2008.Clientes c
JOIN account.user u ON u.id = c.Id_Cliente
WHERE u.name = #',
[$_REQUEST['user']]
);
if (!($user['active'] && $user['mail']))
return TRUE;
$service = $this->service;
$recover = ['recover' => TRUE];
$token = $service->createToken ($_REQUEST['user'], FALSE, $recover);
$url = $service->getUrl () ."#!form=account/conf&token=$token";
$report = new Vn\Web\Report ($db, 'recover-password', ['url' => $url]);
$report->sendMail ($user['mail']);
return TRUE;
}
const LOWERS = 'abcdefghijklmnopqrstuvwxyz'; const LOWERS = 'abcdefghijklmnopqrstuvwxyz';
const UPPERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const UPPERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const DIGITS = '1234567890'; const DIGITS = '1234567890';
const SYMBOLS = '!$%&()=.'; const SYMBOLS = '!$%&()=.';
function run ($db) function genPassword ($db)
{ {
$isEnabled = $db->getValue (
'SELECT active FROM account.user WHERE name = #',
[$_REQUEST['user']]);
if (!$isEnabled)
return TRUE;
$restrictions = $db->getRow ( $restrictions = $db->getRow (
'SELECT length, nUpper, nDigits, nPunct FROM account.userPassword'); 'SELECT length, nUpper, nDigits, nPunct FROM account.userPassword');
@ -41,15 +60,7 @@ class RecoverPassword extends Vn\Web\JsonRequest
array_splice ($pass, $rand, 1); array_splice ($pass, $rand, 1);
} }
// XXX: Debug return $newPass;
error_log ($newPass);
return TRUE;
$db->query (
'UPDATE account.user SET password = MD5(#) WHERE name = #',
[$randomPass, $_REQUEST['user']]);
return TRUE;
} }
function genRands (&$pass, $chars, $max) function genRands (&$pass, $chars, $max)

View File

@ -48,7 +48,7 @@ class Mail extends Vn\Lib\Method
} }
$mail->setFrom ($conf['sender'], $conf['sender_name']); $mail->setFrom ($conf['sender'], $conf['sender_name']);
$mail->AddReplyTo ($row['reply_to'], 'Att. Cliente'); $mail->AddReplyTo ($row['reply_to'], $conf['sender_name']);
if (strpos ($row['to'], ',')) if (strpos ($row['to'], ','))
$mailList = explode (',', $row['to']); $mailList = explode (',', $row['to']);

49
web/mailer.php Executable file
View File

@ -0,0 +1,49 @@
<?php
namespace Vn\Web;
require_once ('libphp-phpmailer/PHPMailerAutoload.php');
use Vn\Lib\UserException;
class Mailer
{
static function send ($db, $mailTo, $body, $subject)
{
$conf = $db->getRow (
'SELECT host, port, secure, sender, sender_name, user, password
FROM mail_config'
);
$mail = new \PHPMailer ();
$mail->isSMTP ();
$mail->Host = $conf['host'];
if (!empty ($conf['user']))
{
$mail->SMTPAuth = TRUE;
$mail->Username = $conf['user'];
$mail->Password = base64_decode ($conf['password']);
}
else
$mail->SMTPAuth = FALSE;
if ($conf['secure'])
{
$mail->SMTPSecure = 'ssl';
$mail->Port = 465;
}
$mail->setFrom ($conf['sender'], $conf['sender_name']);
$mail->AddAddress ($mailTo);
$mail->IsHTML (TRUE);
$mail->Subject = $subject;
$mail->Body = $body;
$mail->CharSet = 'UTF-8';
if (!$mail->Send ())
throw new UserException ('Send error: '.$mail->ErrorInfo);
}
}

7
web/report.html.php Normal file
View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head></head>
<body>
<?php include "reports/$reportName/ui.php" ?>
</body>
</html>

42
web/report.php Executable file
View File

@ -0,0 +1,42 @@
<?php
namespace Vn\Web;
class Report
{
var $db;
var $name;
var $html;
function __construct ($db, $reportName, $params)
{
$this->db = $db;
$this->name = $reportName;
extract ($params);
ob_start ();
include __DIR__.'/report.html.php';
$this->html = ob_get_contents ();
ob_end_clean ();
if (isset ($title))
$this->title = $title;
}
function getTitle ()
{
return $this->title;
}
function getHtml ()
{
return $this->html;
}
function sendMail ($mail)
{
Mailer::send ($this->db, $mail, $this->html, $this->title);
}
}

View File

@ -166,7 +166,14 @@ abstract class Service
throw new SessionExpiredException (); throw new SessionExpiredException ();
$user = $jwtPayload['sub']; $user = $jwtPayload['sub'];
}
/* if (!empty ($jwtPayload['recover']))
$db->query (
'UPDATE account.user SET password = NULL
WHERE name = #',
[$user]
);
*/ }
else else
$user = $db->getValue ('SELECT guest_user FROM config'); $user = $db->getValue ('SELECT guest_user FROM config');
@ -221,7 +228,7 @@ abstract class Service
* @param {boolean} $remember Wether to create long live token * @param {boolean} $remember Wether to create long live token
* @return {string} The JWT generated token * @return {string} The JWT generated token
**/ **/
function createToken ($user, $remember = FALSE) function createToken ($user, $remember = FALSE, $params = NULL)
{ {
if ($remember) if ($remember)
$tokenLife = WEEK; $tokenLife = WEEK;
@ -232,6 +239,11 @@ abstract class Service
'sub' => $user, 'sub' => $user,
'exp' => time () + $tokenLife 'exp' => time () + $tokenLife
]; ];
if (isset ($params))
foreach ($params as $key => $value)
$payload[$key] = $value;
$key = $this->db->getValue ('SELECT jwtKey FROM config'); $key = $this->db->getValue ('SELECT jwtKey FROM config');
return Jwt::encode ($payload, $key); return Jwt::encode ($payload, $key);
} }
@ -260,7 +272,6 @@ abstract class Service
} }
else else
$methodDb = $this->getUserDb ($_SESSION['user']); $methodDb = $this->getUserDb ($_SESSION['user']);
if ($method::PARAMS !== NULL && !$method->checkParams ($_REQUEST, $method::PARAMS)) if ($method::PARAMS !== NULL && !$method->checkParams ($_REQUEST, $method::PARAMS))
throw new UserException (s('Missing parameters')); throw new UserException (s('Missing parameters'));
@ -281,6 +292,17 @@ abstract class Service
return isset ($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'; return isset ($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on';
} }
/**
* Returns the current URL without the GET part.
*
* @return string The current URL
**/
function getUrl ()
{
$proto = $this->isHttps () ? 'https' : 'http';
return "$proto://{$_SERVER['SERVER_NAME']}{$_SERVER['REQUEST_URI']}";
}
/** /**
* Obtains the application version number. It is based on de last * Obtains the application version number. It is based on de last
* modification date of the main script. * modification date of the main script.