test #4

Merged
juan merged 10 commits from test into master 2022-05-18 16:28:23 +00:00
42 changed files with 457 additions and 686 deletions

6
Jenkinsfile vendored
View File

@ -16,11 +16,13 @@ pipeline {
switch (env.BRANCH_NAME) { switch (env.BRANCH_NAME) {
case 'master': case 'master':
env.NODE_ENV = 'production' env.NODE_ENV = 'production'
env.REPLICAS = 3 env.MAIN_REPLICAS = 3
env.CRON_REPLICAS = 1
break break
case 'test': case 'test':
env.NODE_ENV = 'test' env.NODE_ENV = 'test'
env.REPLICAS = 1 env.MAIN_REPLICAS = 1
env.CRON_REPLICAS = 0
break break
} }
} }

View File

@ -16,6 +16,7 @@
Options -Indexes -FollowSymLinks Options -Indexes -FollowSymLinks
AllowOverride None AllowOverride None
Require all granted Require all granted
SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0
<FilesMatch "\.(css|js|json|yml|php|xml|html|svg)$"> <FilesMatch "\.(css|js|json|yml|php|xml|html|svg)$">
SetOutputFilter DEFLATE SetOutputFilter DEFLATE

75
app.js
View File

@ -1,71 +1,60 @@
__webpack_public_path__ = _PUBLIC_PATH;
var assetsPath; require('hedera/hedera');
if (_DEV_MODE) window.onload = function() {
{ loadLocale(main);
var host = window.location.host.split(':')[0];
assetsPath = 'http://'+ host +':'+ _DEV_SERVER_PORT +'/'+ _PUBLIC_PATH;
}
else
assetsPath = _PUBLIC_PATH;
__webpack_public_path__ = assetsPath;
require ('hedera/hedera');
window.onload = function ()
{
loadLocale (main);
} }
function main (req) function main(req) {
{
if (req) if (req)
onLocaleLoad (req); onLocaleLoad(req);
hederaWeb = new Hedera.App (); hederaWeb = new Hedera.App();
hederaWeb.run (); hederaWeb.run();
} }
function loadLocale (cb) function loadLocale(cb) {
{ Vn.Locale.init();
Vn.Locale.init ();
var lang = Vn.Locale.language; var lang = Vn.Locale.language;
var req = require.context ('js', true, /locale\/en.yml$/); var req = require.context('js', true, /locale\/en.yml$/);
onLocaleLoad (req); onLocaleLoad(req);
switch (lang) switch (lang) {
{
case 'ca': case 'ca':
require ([], function () { require([], function() {
cb (require.context ('js', true, /locale\/ca.yml$/)); }); cb(require.context('js', true, /locale\/ca.yml$/));
});
break; break;
case 'es': case 'es':
require ([], function () { require([], function() {
cb (require.context ('js', true, /locale\/es.yml$/)); }); cb(require.context('js', true, /locale\/es.yml$/));
});
break; break;
case 'fr': case 'fr':
require ([], function () { require([], function() {
cb (require.context ('js', true, /locale\/fr.yml$/)); }); cb(require.context('js', true, /locale\/fr.yml$/));
});
break; break;
case 'mn': case 'mn':
require ([], function () { require([], function() {
cb (require.context ('js', true, /locale\/mn.yml$/)); }); cb(require.context('js', true, /locale\/mn.yml$/));
});
break; break;
case 'pt': case 'pt':
require ([], function () { require([], function() {
cb (require.context ('js', true, /locale\/pt.yml$/)); }); cb(require.context('js', true, /locale\/pt.yml$/));
});
break; break;
default: default:
cb (); cb();
} }
} }
function onLocaleLoad (req) function onLocaleLoad(req) {
{ var keys = req.keys();
var keys = req.keys ();
for (var i = 0; i < keys.length; i++) for (var i = 0; i < keys.length; i++)
Vn.Locale.add (req (keys[i])); Vn.Locale.add(req(keys[i]));
} }

View File

@ -1,18 +0,0 @@
CREATE OR REPLACE
ALGORITHM = UNDEFINED
DEFINER=`root`@`%`
VIEW `hedera`.`myTicketService` AS
select
`s`.`id` AS `id`,
`s`.`description` AS `description`,
`s`.`quantity` AS `quantity`,
`s`.`price` AS `price`,
`s`.`taxClassFk` AS `taxClassFk`,
`s`.`ticketFk` AS `ticketFk`,
`s`.`ticketServiceTypeFk` AS `ticketServiceTypeFk`
from
(`vn`.`ticketService` `s`
join `hedera`.`myTicket` `t` on
(`s`.`ticketFk` = `t`.`id`)) WITH CASCADED CHECK OPTION;
GRANT SELECT ON TABLE hedera.myTicketService TO account@'localhost';

View File

@ -1,18 +0,0 @@
DROP PROCEDURE IF EXISTS hedera.myTicket_getServices;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `hedera`.`myTicket_getServices`(vSelf INT)
BEGIN
/**
* Returns a current user ticket services.
*
* @param vSelf The ticket identifier
* @select The ticket services
*/
SELECT id, description, quantity, price
FROM myTicketService
WHERE ticketFk = vSelf;
END$$
DELIMITER ;
GRANT EXECUTE ON PROCEDURE hedera.myTicket_getServices TO account@'localhost';

View File

@ -1 +0,0 @@
CALL account.role_sync;

2
debian/changelog vendored
View File

@ -1,4 +1,4 @@
hedera-web (1.407.66) stable; urgency=low hedera-web (1.407.68) stable; urgency=low
* Initial Release. * Initial Release.

View File

@ -17,7 +17,7 @@ services:
- /mnt/appdata/image:/var/lib/hedera-web/image-db - /mnt/appdata/image:/var/lib/hedera-web/image-db
- /mnt/appdata/vn-access:/var/lib/hedera-web/vn-access - /mnt/appdata/vn-access:/var/lib/hedera-web/vn-access
deploy: deploy:
replicas: ${REPLICAS:?} replicas: ${MAIN_REPLICAS:?}
placement: placement:
constraints: constraints:
- node.role == worker - node.role == worker
@ -31,6 +31,7 @@ services:
- /mnt/appdata:/mnt/storage - /mnt/appdata:/mnt/storage
- /mnt/appdata/image:/var/lib/hedera-web/image-db - /mnt/appdata/image:/var/lib/hedera-web/image-db
deploy: deploy:
replicas: ${CRON_REPLICAS:?}
placement: placement:
constraints: constraints:
- node.role == worker - node.role == worker

View File

@ -5,8 +5,6 @@ Hedera.Conf = new Class({
,activate: function() { ,activate: function() {
this.$('user-model').setInfo('c', 'myClient', 'hedera'); this.$('user-model').setInfo('c', 'myClient', 'hedera');
console.log(this.hash.get('verificationToken'));
if (this.hash.get('verificationToken')) if (this.hash.get('verificationToken'))
this.onPassChangeClick(); this.onPassChangeClick();
} }
@ -45,17 +43,20 @@ Hedera.Conf = new Class({
this.conn.send('core/restore-password', params, this.conn.send('core/restore-password', params,
this._onPassChange.bind(this)); this._onPassChange.bind(this));
} else { } else {
let userId = this.gui.user.id;
params.oldPassword = oldPassword; params.oldPassword = oldPassword;
this.conn.send('core/change-password', params, this.conn.lbSend('PATCH',
this._onPassChange.bind(this)); `Accounts/${userId}/changePassword`, params,
this._onPassChange.bind(this)
);
} }
} catch (e) { } catch (e) {
Htk.Toast.showError(e.message); Htk.Toast.showError(e.message);
} }
} }
,_onPassChange: function(json, error) { ,_onPassChange: function(json, error) {
if (json) { if (!error) {
this.$('change-password').hide(); this.$('change-password').hide();
this.hash.unset('verificationToken'); this.hash.unset('verificationToken');
Htk.Toast.showMessage(_('Password changed!')); Htk.Toast.showMessage(_('Password changed!'));

View File

@ -1,52 +1,44 @@
Hedera.Connections = new Class Hedera.Connections = new Class({
({
Extends: Hedera.Form Extends: Hedera.Form
,_timeoutId: null ,_timeoutId: null
,onModelStatusChange: function (model, status) ,onModelStatusChange: function(model) {
{
if (!model.ready) if (!model.ready)
return; return;
if (this._timeoutId) if (this._timeoutId)
clearTimeout (this._timeoutId); clearTimeout(this._timeoutId);
this._timeoutId = setTimeout (this.onRefreshClick.bind (this), 60000); this._timeoutId = setTimeout(this.onRefreshClick.bind(this), 60000);
} }
,deactivate: function () ,deactivate: function() {
{ clearTimeout(this._timeoutId);
clearTimeout (this._timeoutId);
} }
,onRefreshClick: function () ,onRefreshClick: function() {
{ this.$('sessions').refresh();
this.$('sessions').refresh ();
} }
,onAccessLogClick: function (button, form) ,onAccessLogClick: function(button, form) {
{ this.hash.set({
this.hash.set ({ form: 'admin/access-log'
'form': 'admin/access-log' ,user: form.get('userId')
,'user': form.get ('userId')
}); });
} }
,onChangeUserClick: function (button, form) ,onChangeUserClick: function(button, form) {
{ this.gui.supplantUser(form.get('user'),
this.gui.supplantUser (form.get ('user'), this._onUserSupplant.bind(this));
this._onUserSupplant.bind (this));
} }
,_onUserSupplant: function (userName) ,_onUserSupplant: function() {
{ this.hash.set({form: 'ecomerce/orders'});
this.hash.set ({'form': 'ecomerce/orders'});
} }
,sessionsFunc: function () ,sessionsFunc: function() {
{
return 1; return 1;
} }
}); });

View File

@ -50,7 +50,7 @@
<htk-text form="iter" column="value7"/> <htk-text form="iter" column="value7"/>
</p> </p>
<p> <p>
@<htk-text form="iter" column="id"/> #<htk-text form="iter" column="id"/>
</p> </p>
<p> <p>
<htk-text form="iter" column="image"/> <htk-text form="iter" column="image"/>

View File

@ -50,7 +50,7 @@
<htk-text form="iter" column="nickname"/> <htk-text form="iter" column="nickname"/>
</p> </p>
<p> <p>
@<htk-text form="iter" column="id"/> - #<htk-text form="iter" column="id"/> -
<htk-text form="iter" column="name"/> <htk-text form="iter" column="name"/>
</p> </p>
<div class="clear"/> <div class="clear"/>

View File

@ -1,18 +1,15 @@
Hedera.Users = new Class Hedera.Users = new Class({
({
Extends: Hedera.Form Extends: Hedera.Form
,onAccessLogClick: function (button, form) ,onAccessLogClick: function(button, form) {
{ this.hash.set({
this.hash.set ({
'form': 'admin/access-log' 'form': 'admin/access-log'
,'user': form.get ('id') ,'user': form.get('id')
}); });
} }
,rendererFunc: function (scope, form) ,rendererFunc: function(scope, form) {
{
var isEnabled = form.get('active') var isEnabled = form.get('active')
scope.$('disabled').style.display = isEnabled ? scope.$('disabled').style.display = isEnabled ?
'none' : 'block'; 'none' : 'block';
@ -20,15 +17,13 @@ Hedera.Users = new Class
'block' : 'none'; 'block' : 'none';
} }
,onChangeUserClick: function (button, form) ,onChangeUserClick: function(button, form) {
{ this.gui.supplantUser(form.get('name'),
this.gui.supplantUser (form.get ('name'), this.onUserSupplant.bind(this));
this.onUserSupplant.bind (this));
} }
,onUserSupplant: function () ,onUserSupplant: function() {
{ this.hash.set({form: 'ecomerce/orders'});
this.hash.set ({form: 'ecomerce/orders'});
} }
}); });

View File

@ -406,7 +406,7 @@
<htk-text form="card" column="subName"/> <htk-text form="card" column="subName"/>
</p> </p>
<p> <p>
@<htk-text form="card" column="id"/> #<htk-text form="card" column="id"/>
</p> </p>
<p> <p>
<htk-text form="card" column="stems" format="_%.0d Units"/> <htk-text form="card" column="stems" format="_%.0d Units"/>

View File

@ -56,7 +56,7 @@
<htk-text form="iter" column="landed" format="%D"/> <htk-text form="iter" column="landed" format="%D"/>
</p> </p>
<p> <p>
@<htk-text form="iter" column="id"/> #<htk-text form="iter" column="id"/>
</p> </p>
<p> <p>
<htk-text form="iter" column="nickname"/> <htk-text form="iter" column="nickname"/>

View File

@ -19,11 +19,15 @@ Hedera.Ticket = new Class({
}, },
onPrintClick: function() { onPrintClick: function() {
var batch = new Sql.Batch(); let params = Vn.Url.makeUri({
batch.addValue('ticket', this.$('ticket-id').value); authorization: this.conn.token,
this.gui.openReport('delivery-note', batch); ticketId: this.$('ticket-id').value,
recipientId: this.gui.user.id,
type: 'deliveryNote'
});
window.open(`/api/report/delivery-note?${params}`);
}, },
repeaterFunc: function(res, form) { repeaterFunc: function(res, form) {
var discount = res.$('discount'); var discount = res.$('discount');
discount.style.display = form.get('discount') ? 'inline' : 'none'; discount.style.display = form.get('discount') ? 'inline' : 'none';

View File

@ -27,7 +27,7 @@
<htk-loader class="head" form="ticket"> <htk-loader class="head" form="ticket">
<div> <div>
<p class="important ticket-id"> <p class="important ticket-id">
@<htk-text column="id" form="ticket"/> #<htk-text column="id" form="ticket"/>
</p> </p>
<p> <p>
<t>Preparation</t> <htk-text form="ticket" column="shipped" format="%D"/> <t>Preparation</t> <htk-text form="ticket" column="shipped" format="%D"/>
@ -151,7 +151,7 @@
<htk-text form="iter" column="name"/> <htk-text form="iter" column="name"/>
</h2> </h2>
<p> <p>
@<htk-text form="iter" column="id"/> #<htk-text form="iter" column="id"/>
</p> </p>
<p class="amount"> <p class="amount">
<htk-text form="iter" column="quantity"/> <htk-text form="iter" column="quantity"/>

View File

@ -1,19 +1,17 @@
var Result = require ('./result'); var Result = require('./result');
/** /**
* This class stores the database results. * This class stores the database results.
**/ **/
module.exports = new Class module.exports = new Class({
({
results: null results: null
,error: null ,error: null
/** /**
* Initilizes the resultset object. * Initilizes the resultset object.
**/ **/
,initialize: function (results, error) ,initialize: function(results, error) {
{
this.results = results; this.results = results;
this.error = error; this.error = error;
} }
@ -23,19 +21,17 @@ module.exports = new Class
* *
* @return {Db.Err} the error or null if no errors hapened * @return {Db.Err} the error or null if no errors hapened
**/ **/
,getError: function () ,getError: function() {
{
return this.error; return this.error;
} }
,fetch: function () ,fetch: function() {
{
if (this.error) if (this.error)
throw this.error; throw this.error;
if (this.results !== null if (this.results !== null
&& this.results.length > 0) && this.results.length > 0)
return this.results.shift (); return this.results.shift();
return null; return null;
} }
@ -45,14 +41,12 @@ module.exports = new Class
* *
* @return {Db.Result} the result or %null if error or there are no more results * @return {Db.Result} the result or %null if error or there are no more results
**/ **/
,fetchResult: function () ,fetchResult: function() {
{ var result = this.fetch();
var result = this.fetch ();
if (result !== null) if (result !== null) {
{
if (result.data instanceof Array) if (result.data instanceof Array)
return new Result (result); return new Result(result);
else else
return true; return true;
} }
@ -65,9 +59,8 @@ module.exports = new Class
* *
* @return {Array} the row if success, %null otherwise * @return {Array} the row if success, %null otherwise
**/ **/
,fetchRow: function () ,fetchRow: function() {
{ var result = this.fetch();
var result = this.fetch ();
if (result !== null if (result !== null
&& result.data instanceof Array && result.data instanceof Array
@ -77,14 +70,29 @@ module.exports = new Class
return null; return null;
} }
,fetchObject: function() {
var result = this.fetch();
if (result !== null
&& result.data instanceof Array
&& result.data.length > 0) {
var row = result.data[0];
var object = {};
for(var i = 0; i < row.length; i++)
object[result.columns[i].name] = row[i];
return object;
}
return null;
}
/** /**
* Fetchs the first row and column value from the next resultset. * Fetchs the first row and column value from the next resultset.
* *
* @return {Object} the value if success, %null otherwise * @return {Object} the value if success, %null otherwise
**/ **/
,fetchValue: function () ,fetchValue: function() {
{ var row = this.fetchRow();
var row = this.fetchRow ();
if (row instanceof Array && row.length > 0) if (row instanceof Array && row.length > 0)
return row[0]; return row[0];

View File

@ -54,7 +54,7 @@ module.exports = new Class({
this.unref(); this.unref();
} }
,_onWindowError: function(message, file, line, col, err) { ,_onWindowError: function(message, file, line) {
var error = new Error(message); var error = new Error(message);
error.fileName = file; error.fileName = file;
error.lineNumber = line; error.lineNumber = line;
@ -68,12 +68,15 @@ module.exports = new Class({
Htk.Toast.showError(_('Invalid login')); Htk.Toast.showError(_('Invalid login'));
this._logout(); this._logout();
break; break;
case 'Forbidden':
Htk.Toast.showError(_('You don\'t have enough privileges'));
break;
case 'UserDisabled': case 'UserDisabled':
Htk.Toast.showError(_('User disabled')); Htk.Toast.showError(_('User disabled'));
this._logout(); this._logout();
break; break;
case 'SessionExpired': case 'SessionExpired':
Htk.Toast.showError(_('You\'ve been too idle')); Htk.Toast.showError(_('Session expired'));
this._logout(); this._logout();
break; break;
case 'OutdatedVersion': case 'OutdatedVersion':
@ -81,6 +84,14 @@ module.exports = new Class({
break; break;
default: default:
Htk.Toast.showError(error.message); Htk.Toast.showError(error.message);
} else if (error.statusCode)
switch (error.statusCode) {
case 401:
Htk.Toast.showError(_('Invalid login'));
this._logout();
break;
default:
Htk.Toast.showError(error.message);
} }
else { else {
console.error(error); console.error(error);

View File

@ -48,7 +48,7 @@ module.exports = new Class({
this.$('social-bar').conn = this._conn; this.$('social-bar').conn = this._conn;
var sql = 'SELECT nickname FROM account.myUser;' var sql = 'SELECT id, name, nickname FROM account.myUser;'
+'SELECT defaultForm FROM config;' +'SELECT defaultForm FROM config;'
+'SELECT url FROM imageConfig;' +'SELECT url FROM imageConfig;'
+'SELECT dbproduccion FROM vn2008.tblContadores;' +'SELECT dbproduccion FROM vn2008.tblContadores;'
@ -121,8 +121,8 @@ module.exports = new Class({
,onMainQueryDone: function(resultSet) { ,onMainQueryDone: function(resultSet) {
// Retrieving the user name // Retrieving the user name
var userName = resultSet.fetchValue(); this.user = resultSet.fetchObject();
Vn.Node.setText(this.$('user-name'), userName); Vn.Node.setText(this.$('user-name'), this.user.nickname);
// Retrieving configuration parameters // Retrieving configuration parameters

View File

@ -9,7 +9,8 @@ Login phone: +34 607 562 391
Password forgotten? Push here: ¿Has oblidat la teva contrasenya? Password forgotten? Push here: ¿Has oblidat la teva contrasenya?
Yet you are not a customer?: Encara no ets client? Yet you are not a customer?: Encara no ets client?
Sign up: Registrarme Sign up: Registrarme
You've been too idle: Has estat massa temps inactiu i la sessió ha expirat You don't have enough privileges: No tens prou privilegis
Session expired: La sessió ha expirat
Invalid login: >- Invalid login: >-
Usuari o contrasenya incorrectes, recorda que s'hi distingeix entre majúscula Usuari o contrasenya incorrectes, recorda que s'hi distingeix entre majúscula
i minúscula i minúscula

View File

@ -10,7 +10,8 @@ Password forgotten? Push here: Password forgotten? Push here
Yet you are not a customer?: Yet you are not a customer? Yet you are not a customer?: Yet you are not a customer?
Sign up: Sign up Sign up: Sign up
Sign up link: http://bit.ly/2wLntMl Sign up link: http://bit.ly/2wLntMl
You've been too idle: You have been idle too long and your session has expired You don't have enough privileges: You don't have enough privileges
Session expired: Your session has expired
Invalid login: 'Username or password incorrect, remember that it is case-sensitive' Invalid login: 'Username or password incorrect, remember that it is case-sensitive'
User disabled: >- User disabled: >-
Authentication is correct but the user account has been disabled, please Authentication is correct but the user account has been disabled, please

View File

@ -10,7 +10,8 @@ Password forgotten? Push here: ¿Has olvidado tu contraseña?
Yet you are not a customer?: ¿Todavía no eres cliente? Yet you are not a customer?: ¿Todavía no eres cliente?
Sign up: Registrarme Sign up: Registrarme
Sign up link: http://bit.ly/2wLntMl Sign up link: http://bit.ly/2wLntMl
You've been too idle: Has estado demasiado tiempo inactivo y la sesión ha expirado You don't have enough privileges: No tienes suficientes privilegios
Session expired: La sesión ha expirado
Invalid login: >- Invalid login: >-
Usuario o contraseña incorrectos, recuerda que se hace distinción entre Usuario o contraseña incorrectos, recuerda que se hace distinción entre
mayúsculas y minúsculas mayúsculas y minúsculas

View File

@ -10,7 +10,8 @@ Password forgotten? Push here: as tu oublié ton mot de passe?
Yet you are not a customer?: Êtes-vous Pas encore client? Yet you are not a customer?: Êtes-vous Pas encore client?
Sign up: Inscription Sign up: Inscription
Sign up link: http://bit.ly/2msCil1 Sign up link: http://bit.ly/2msCil1
You've been too idle: Il a eu le temps de trop paresseux et votre session a expiré You don't have enough privileges: Vous n'avez pas assez de privilèges
Session expired: Et votre session a expiré
Invalid login: >- Invalid login: >-
Utilisateur ou mot de passe incorrect, n'oubliez pas de distinction entre Utilisateur ou mot de passe incorrect, n'oubliez pas de distinction entre
majuscules et minuscules majuscules et minuscules

View File

@ -9,7 +9,6 @@ Login phone: +34 607 562 391
Password forgotten? Push here: Нууц үг мартсан? энд түлхэх Password forgotten? Push here: Нууц үг мартсан? энд түлхэх
Yet you are not a customer?: Гэсэн хэдий ч та хэрэглэгчийн биш гэж үү? Yet you are not a customer?: Гэсэн хэдий ч та хэрэглэгчийн биш гэж үү?
Sign up: бүртгүүлэх Sign up: бүртгүүлэх
You've been too idle: 'Та нар ч бас зогссон байсан, чуулган хугацаа дууссан байна'
Invalid login: 'Хэрэглэгчийн нэр эсвэл нууц үг буруу, Тэр хэргийг мэдрэмтгий гэдгийг санаарай' Invalid login: 'Хэрэглэгчийн нэр эсвэл нууц үг буруу, Тэр хэргийг мэдрэмтгий гэдгийг санаарай'
Please write your user name: Хэрэглэгчийн нэрээ бичнэ үү Please write your user name: Хэрэглэгчийн нэрээ бичнэ үү
A mail has been sent wich you can recover your password: Мэйл та нууц үгээ сэргээх боломжтой А байна илгээсэн A mail has been sent wich you can recover your password: Мэйл та нууц үгээ сэргээх боломжтой А байна илгээсэн

View File

@ -9,7 +9,8 @@ Login phone: +34 963 242 100
Password forgotten? Push here: Não lembro minha palavra-passe Password forgotten? Push here: Não lembro minha palavra-passe
Yet you are not a customer?: Ainda não és cliente? Yet you are not a customer?: Ainda não és cliente?
Sign up: Cadastrar-se Sign up: Cadastrar-se
You've been too idle: 'Muito tempo de inatividade, a sessão foi finalizada' You don't have enough privileges: Você não tem privilégios suficientes
Session expired: 'A sessão foi finalizada'
Invalid login: >- Invalid login: >-
Usuário ou Palavra-Passe incorreto, lembre-se de diferenciar maiusculas e Usuário ou Palavra-Passe incorreto, lembre-se de diferenciar maiusculas e
minusculas minusculas

View File

@ -1,129 +1,112 @@
var Css = require ('./login.css'); require('./login.css');
var Tpl = require ('./login.xml'); var Tpl = require('./login.xml');
module.exports = new Class module.exports = new Class({
({
Extends: Htk.Component, Extends: Htk.Component,
Properties: Properties:
{ {
conn: conn:
{ {
type: Db.Connection type: Db.Connection
,set: function (x) ,set: function(x) {
{ this.link({_conn: x}, {'loading-changed': this._onConnLoadChange});
this.link ({_conn: x}, {'loading-changed': this._onConnLoadChange});
} }
,get: function () ,get: function() {
{
return this._conn; return this._conn;
} }
} }
} }
,initialize: function (props) ,initialize: function(props) {
{ this.parent(props);
this.parent (props); this.builderInitString(Tpl);
this.builderInitString (Tpl);
this.$('social-bar').conn = this._conn; this.$('social-bar').conn = this._conn;
var self = this; var self = this;
this.$('form').onsubmit = function () this.$('form').onsubmit = function() {
{ self._onSubmit();
self._onSubmit ();
return false; return false;
}; };
} }
,_onConnLoadChange: function (conn, isLoading) ,_onConnLoadChange: function(conn, isLoading) {
{
if (isLoading) if (isLoading)
this.$('spinner').start (); this.$('spinner').start();
else else
this.$('spinner').stop (); this.$('spinner').stop();
} }
,show: function () ,show: function() {
{ document.body.appendChild(this.node);
document.body.appendChild (this.node);
var lastUser = localStorage.getItem ('hederaLastUser'); var lastUser = localStorage.getItem('hederaLastUser');
if (lastUser) if (lastUser)
this.$('user').value = lastUser; this.$('user').value = lastUser;
this._focusUserInput (); this._focusUserInput();
} }
,_onSubmit: function () ,_onSubmit: function() {
{ this._conn.open(
this._conn.open (
this.$('user').value, this.$('user').value,
this.$('pass').value, this.$('pass').value,
this.$('remember').checked, this.$('remember').checked,
this._onConnOpen.bind (this) this._onConnOpen.bind(this)
); );
this._disableUi (true); this._disableUi(true);
} }
,_onConnOpen: function (conn, success, error) ,_onConnOpen: function(conn, success, error) {
{
this.$('pass').value = ''; this.$('pass').value = '';
this._disableUi (false); this._disableUi(false);
if (success) if (success) {
{
var user = this.$('user').value; var user = this.$('user').value;
if (user) if (user)
localStorage.setItem ('hederaLastUser', user); localStorage.setItem('hederaLastUser', user);
this.signalEmit ('login'); this.signalEmit('login');
} } else {
else this._focusUserInput();
{
this._focusUserInput ();
throw error; throw error;
} }
} }
,hide: function () ,hide: function() {
{ Vn.Node.remove(this.node);
Vn.Node.remove (this.node);
} }
,_focusUserInput: function () ,_focusUserInput: function() {
{
var userEntry = this.$('user'); var userEntry = this.$('user');
userEntry.focus (); userEntry.focus();
userEntry.select (); userEntry.select();
} }
,_disableUi: function (disabled) ,_disableUi: function(disabled) {
{
this.$('user').disabled = disabled; this.$('user').disabled = disabled;
this.$('pass').disabled = disabled; this.$('pass').disabled = disabled;
this.$('submit').disabled = disabled; this.$('submit').disabled = disabled;
} }
,onPasswordLost: function () ,onPasswordLost: function() {
{
var user = this.$('user').value; var user = this.$('user').value;
if (!user) if (!user)
Htk.Toast.showError (_('Please write your user name')); Htk.Toast.showError(_('Please write your user name'));
else else
this._conn.send ('core/recover-password', {recoverUser: user}, this._conn.send('core/recover-password', {recoverUser: user},
this._onPasswordRecovered.bind (this)); this._onPasswordRecovered.bind(this));
} }
,_onPasswordRecovered: function (json, error) ,_onPasswordRecovered: function(json, error) {
{
if (error) if (error)
throw error; throw error;
Htk.Toast.showMessage (_('A mail has been sent wich you can recover your password')); Htk.Toast.showMessage(_('A mail has been sent wich you can recover your password'));
} }
}); });

View File

@ -1,12 +1,11 @@
var Object = require ('./object'); var Object = require('./object');
var JsonException = require ('./json-exception'); var JsonException = require('./json-exception');
/** /**
* Handler for JSON rest connections. * Handler for JSON rest connections.
**/ **/
module.exports = new Class module.exports = new Class({
({
Extends: Object Extends: Object
,_connected: false ,_connected: false
@ -16,29 +15,26 @@ module.exports = new Class
/** /**
* Initilizes the connection object. * Initilizes the connection object.
**/ **/
,initialize: function () ,initialize: function() {
{ this.parent();
this.parent (); this.fetchToken();
this.fetchToken ();
} }
,fetchToken: function () ,fetchToken: function() {
{
var token = null; var token = null;
if (sessionStorage.getItem ('vnToken')) if (sessionStorage.getItem('vnToken'))
token = sessionStorage.getItem ('vnToken'); token = sessionStorage.getItem('vnToken');
if (localStorage.getItem ('vnToken')) if (localStorage.getItem('vnToken'))
token = localStorage.getItem ('vnToken'); token = localStorage.getItem('vnToken');
this.token = token; this.token = token;
} }
,clearToken: function () ,clearToken: function() {
{
this.token = null; this.token = null;
localStorage.removeItem ('vnToken'); localStorage.removeItem('vnToken');
sessionStorage.removeItem ('vnToken'); sessionStorage.removeItem('vnToken');
} }
/** /**
@ -49,43 +45,37 @@ module.exports = new Class
* @param {Boolean} remember Specifies if the user should be remembered * @param {Boolean} remember Specifies if the user should be remembered
* @param {Function} openCallback The function to call when operation is done * @param {Function} openCallback The function to call when operation is done
**/ **/
,open: function (user, pass, remember, callback) ,open: function(user, pass, remember, callback) {
{ if (user !== null && user !== undefined) {
if (user !== null && user !== undefined)
{
var params = { var params = {
user: user user: user
,password: pass ,password: pass
,remember: remember ,remember: remember
}; };
} } else
else
var params = null; var params = null;
this.send ('core/login', params, this.lbSend('POST', 'Accounts/login', params,
this._onOpen.bind (this, callback, remember)); this._onOpen.bind(this, callback, remember));
} }
/* /*
* Called when open operation is done. * Called when open operation is done.
*/ */
,_onOpen: function (callback, remember, json, error) ,_onOpen: function(callback, remember, json, error) {
{ if (json) {
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');
} } else
else this._closeClient();
this._closeClient ();
if (callback) if (callback)
callback (this, this._connected, error); callback(this, this._connected, error);
} }
/** /**
@ -93,58 +83,53 @@ module.exports = new Class
* *
* @param {Function} callback The function to call when operation is done * @param {Function} callback The function to call when operation is done
**/ **/
,close: function (callback) ,close: function(callback) {
{ this.lbSend('POST', 'Accounts/logout', null,
this._closeClient (); this._onClose.bind(this, callback));
this.send ('core/logout', null, this._closeClient();
this._onClose.bind (this, callback));
} }
/* /*
* Called when close operation is done. * Called when close operation is done.
*/ */
,_onClose: function (callback, json, error) ,_onClose: function(callback, json, error) {
{ this.signalEmit('closed');
this.signalEmit ('closed');
if (callback) if (callback)
callback (this, json === true, error); callback(this, null, error);
} }
,_closeClient: function () ,_closeClient: function() {
{
this._connected = false; this._connected = false;
this.clearToken (); this.clearToken();
} }
/** /**
* Suppants another user. * Supplants another user.
* *
* @param {String} user The user name * @param {String} user The user name
* @param {Function} callback The callback function * @param {Function} callback The callback function
**/ **/
,supplantUser: function (user, callback) ,supplantUser: function(user, callback) {
{
var params = {supplantUser: user}; var params = {supplantUser: user};
this.send ('core/supplant', params, this.send('client/supplant', params,
this._onUserSupplant.bind (this, callback)); this._onUserSupplant.bind(this, callback));
} }
,_onUserSupplant: function (callback, json, error) ,_onUserSupplant: function(callback, json) {
{
if (json) if (json)
this.token = json; this.token = json;
if (callback) if (callback)
callback (json != null); callback(json != null);
} }
/** /**
* Ends the user supplanting and restores the last login. * Ends the user supplanting and restores the last login.
**/ **/
,supplantEnd: function () ,supplantEnd: function() {
{ this.lbSend('POST', 'Accounts/logout');
this.fetchToken (); this.fetchToken();
} }
/** /**
@ -155,18 +140,16 @@ module.exports = new Class
* @param {Map} params The params to pass to the service * @param {Map} params The params to pass to the service
* @param {Function} callback The response callback * @param {Function} callback The response callback
**/ **/
,send: function (restService, params, callback) ,send: function(restService, params, callback) {
{
if (!params) if (!params)
params = {}; params = {};
params.srv = 'json:'+ restService; params.srv = 'json:'+ restService;
this.sendWithUrl (params, callback, 'POST', '.'); this.sendWithUrl(params, callback, 'POST', '.');
} }
,sendForm: function (form, callback) ,sendForm: function(form, callback) {
{
var params = {}; var params = {};
var elements = form.elements; var elements = form.elements;
@ -174,83 +157,88 @@ module.exports = new Class
if (elements[i].name) if (elements[i].name)
params[elements[i].name] = elements[i].value; params[elements[i].name] = elements[i].value;
this.sendWithUrl (params, callback, 'POST', form.action); this.sendWithUrl(params, callback, 'POST', form.action);
} }
,sendFormMultipart: function (form, callback) ,sendFormMultipart: function(form, callback) {
{ var formData = new FormData(form);
var formData = new FormData (form);
var request = new XMLHttpRequest();
request.open('POST', form.action, true);
if (this.token) if (this.token)
formData.append ('token', this.token); request.setRequestHeader('Authorization', this.token);
var request = new XMLHttpRequest ();
request.open ('POST', form.action, true);
request.onreadystatechange = request.onreadystatechange =
this._onStateChange.bind (this, request, callback); this._onStateChange.bind(this, request, callback);
request.send (formData); request.send(formData);
this._addRequest (); this._addRequest();
} }
,sendFormData: function (formData, callback) ,sendFormData: function(formData, callback) {
{ var request = new XMLHttpRequest();
request.open('POST', '', true);
if (this.token) if (this.token)
formData.append ('token', this.token); request.setRequestHeader('Authorization', this.token);
var request = new XMLHttpRequest ();
request.open ('POST', '', true);
request.onreadystatechange = request.onreadystatechange =
this._onStateChange.bind (this, request, callback); this._onStateChange.bind(this, request, callback);
request.send (formData); request.send(formData);
this._addRequest (); this._addRequest();
} }
/* /*
* Called when REST response is received. * Called when REST response is received.
*/ */
,sendWithUrl: function (params, callback, method, url) ,sendWithUrl: function(params, callback, method, url) {
{ var request = new XMLHttpRequest();
if (this.token) request.open(method, url, true);
params['token'] = this.token; request.setRequestHeader('Content-Type',
var request = new XMLHttpRequest ();
request.open (method, url, true);
request.setRequestHeader ('Content-Type',
'application/x-www-form-urlencoded'); 'application/x-www-form-urlencoded');
if (this.token)
request.setRequestHeader('Authorization', this.token);
request.onreadystatechange = request.onreadystatechange =
this._onStateChange.bind (this, request, callback); this._onStateChange.bind(this, request, callback);
request.send (Vn.Url.makeUri (params)); request.send(Vn.Url.makeUri(params));
this._addRequest (); this._addRequest();
} }
,_addRequest: function () ,lbSend: function(method, url, params, callback) {
{ var request = new XMLHttpRequest();
request.open(method, `api/${url}`, true);
request.setRequestHeader('Content-Type',
'application/json;charset=utf-8');
if (this.token)
request.setRequestHeader('Authorization', this.token);
request.onreadystatechange =
this._onStateChange.bind(this, request, callback);
request.send(params && JSON.stringify(params));
this._addRequest();
}
,_addRequest: function() {
this._requestsCount++; this._requestsCount++;
if (this._requestsCount === 1) if (this._requestsCount === 1)
this.signalEmit ('loading-changed', true); this.signalEmit('loading-changed', true);
} }
,_onStateChange: function (request, callback) ,_onStateChange: function(request, callback) {
{
if (request.readyState !== 4) if (request.readyState !== 4)
return; return;
this._requestsCount--; this._requestsCount--;
if (this._requestsCount === 0) if (this._requestsCount === 0)
this.signalEmit ('loading-changed', false); this.signalEmit('loading-changed', false);
var data = null; var data = null;
var error = null; var error = null;
try { try {
if (request.status == 0) if (request.status == 0) {
{ var ex = new JsonException();
var ex = new JsonException ();
ex.message = _('The server does not respond, please check your Internet connection'); ex.message = _('The server does not respond, please check your Internet connection');
throw ex; throw ex;
} }
@ -259,70 +247,75 @@ module.exports = new Class
try { try {
contentType = request contentType = request
.getResponseHeader ('Content-Type') .getResponseHeader('Content-Type')
.split (';')[0] .split(';')[0]
.trim (); .trim();
} catch (err) {
console.warn(err);
} }
catch (e) {}
if (contentType != 'application/json') if (contentType != 'application/json') {
{ var ex = new JsonException();
var ex = new JsonException ();
ex.message = request.statusText; ex.message = request.statusText;
ex.code = request.status; ex.code = request.status;
throw ex; throw ex;
} }
var json = JSON.parse (request.responseText);
var jsData = json.data;
//var jsWarns = json.warnings;
if (request.status == 200)
{
data = jsData;
}
else
{
var exception = jsData.exception;
if (exception)
exception = exception
.replace (/\\/g, '.')
.replace (/Exception$/, '')
.replace (/^Vn\.Web\./, '');
var ex = new JsonException (); var json;
ex.exception = exception; var jsData;
ex.message = jsData.message;
ex.code = jsData.code; if (request.responseText)
ex.file = jsData.file; json = JSON.parse(request.responseText);
ex.line = jsData.line; if (json)
ex.trace = jsData.trace; jsData = json.data || json;
if (request.status >= 200 && request.status < 300) {
data = jsData;
} else {
var exception = jsData.exception;
var error = jsData.error;
if (exception) {
exception = exception
.replace(/\\/g, '.')
.replace(/Exception$/, '')
.replace(/^Vn\.Web\./, '');
var ex = new JsonException();
ex.exception = exception;
ex.message = jsData.message;
ex.code = jsData.code;
ex.file = jsData.file;
ex.line = jsData.line;
ex.trace = jsData.trace;
} else if (error) {
var ex = new Error();
ex.name = error.name;
ex.message = error.message;
ex.code = error.code;
ex.statusCode = request.status;
}
throw ex; throw ex;
} }
} } catch (e) {
catch (e)
{
data = null; data = null;
error = e; error = e;
} }
if (callback) if (callback)
try { try {
callback (data, error); callback(data, error);
error = null; error = null;
} } catch (e) {
catch (e)
{
error = e; error = e;
} }
if (error) if (error) {
{
if (error.exception == 'SessionExpired') if (error.exception == 'SessionExpired')
this.clearToken (); this.clearToken();
this.signalEmit ('error', error); this.signalEmit('error', error);
} }
} }
}); });

View File

@ -1,6 +1,6 @@
{ {
"name": "hedera-web", "name": "hedera-web",
"version": "1.407.66", "version": "1.407.68",
"description": "Verdnatura web page", "description": "Verdnatura web page",
"license": "GPL-3.0", "license": "GPL-3.0",
"repository": { "repository": {

View File

@ -8,7 +8,7 @@
</vn-group> </vn-group>
<div id="report" class="sheet"> <div id="report" class="sheet">
<h2> <h2>
@<htk-text column="id" form="ticket"/> #<htk-text column="id" form="ticket"/>
</h2> </h2>
<div class="header"> <div class="header">
<div> <div>

30
rest/client/supplant.php Normal file
View File

@ -0,0 +1,30 @@
<?php
use Vn\Web;
class Supplant extends Vn\Web\JsonRequest {
const PARAMS = ['supplantUser'];
function run($db) {
$userId = $db->getValue(
'SELECT id FROM account.user WHERE `name` = #',
[$_REQUEST['supplantUser']]
);
$isClient = $db->getValue(
'SELECT COUNT(*) > 0 FROM vn.client WHERE id = #',
[$userId]
);
if (!$isClient)
throw new Web\ForbiddenException(s('The user is not a client'));
$isWorker = $db->getValue(
'SELECT COUNT(*) > 0 FROM vn.worker WHERE id = #',
[$userId]
);
if ($isWorker)
throw new Web\ForbiddenException(s('Workers cannot be supplanted'));
return $this->service->createToken($_REQUEST['supplantUser']);
}
}

View File

@ -1,21 +0,0 @@
<?php
include __DIR__.'/account.php';
/**
* Updates the user password.
**/
class ChangePassword extends Vn\Web\JsonRequest {
const PARAMS = ['oldPassword', 'newPassword'];
function run($db) {
$oldPassword = $_REQUEST['oldPassword'];
$newPassword = $_REQUEST['newPassword'];
$db->query('CALL account.myUser_changePassword(#, #)',
[$oldPassword, $newPassword]);
Account::sync($db, $_SESSION['user'], $newPassword);
return TRUE;
}
}

View File

@ -1,30 +0,0 @@
<?php
include __DIR__.'/account.php';
class Login extends Vn\Web\JsonRequest {
function run($db) {
if (!$_POST['user'] || !$_POST['password'])
throw new Vn\Web\BadLoginException();
try {
Account::trySync($db
,strtolower($_POST['user'])
,$_POST['password']
);
} catch (Exception $e) {
error_log($e->getMessage());
}
$token = $this->service->createToken(
$_SESSION['user'],
!empty($_POST['remember'])
);
return [
'login' => TRUE,
'token' => $token
];
}
}

View File

@ -1,9 +0,0 @@
<?php
class Logout extends Vn\Web\JsonRequest {
function run($db) {
$this->service->logout();
return TRUE;
}
}

View File

@ -7,4 +7,3 @@ class Supplant extends Vn\Web\JsonRequest {
return $this->service->createToken($_REQUEST['supplantUser']); return $this->service->createToken($_REQUEST['supplantUser']);
} }
} }

View File

@ -1,17 +0,0 @@
<?php
include __DIR__.'/account.php';
/**
* Updates the user credentials on external systems like Samba, create
* home directory, create mailbox, etc.
**/
class SyncUser extends Vn\Web\JsonRequest {
const PARAMS = ['syncUser'];
function run($db) {
Account::sync($db, $_REQUEST['syncUser'], NULL);
return TRUE;
}
}

View File

@ -1,105 +0,0 @@
<?php
use Vn\Lib;
/**
* Adds a document to the Document Management System.
**/
class Add extends Vn\Web\JsonRequest {
function run($db) {
// XXX: Uncomment only to test the script
//$_REQUEST['description'] = 'description';
$description = empty($_REQUEST['description']) ?
NULL : $_REQUEST['description'];
$baseDir = _DATA_DIR .'/'. $this->app->getName();
$docsDir = "$baseDir/dms";
$tempDir = "$baseDir/.dms";
$digXDir = 3;
$zerosDir = '';
for ($i = 0; $i < $digXDir; $i++)
$zerosDir .= '0';
// Checks document restrictions
if (empty($_FILES['doc']['name']))
throw new Lib\UserException('File not choosed');
$maxSize = $db->getValue('SELECT max_size FROM dms_config');
if ($_FILES['doc']['size'] > $maxSize * 1048576)
throw new Lib\UserException(sprintf('File size exceeds size: %d MB', $maxSize));
try {
// Registers the document in the database
$db->query('START TRANSACTION');
$db->query('INSERT INTO dms_document SET description = #', [$description]);
$docId =(string) $db->getValue('SELECT LAST_INSERT_ID()');
$len = strlen($docId);
$neededLevels = ceil($len / $digXDir) - 1;
$dirLevels = $db->getValue(
'SELECT dir_levels FROM dms_config LOCK IN SHARE MODE');
if ($dirLevels > $neededLevels)
$neededLevels = $dirLevels;
// Reorganizes the file repository if necessary
if ($dirLevels < $neededLevels)
$dirLevels = $db->getValue(
'SELECT dir_levels FROM dms_config FOR UPDATE');
if ($dirLevels < $neededLevels) {
if (is_dir($docsDir)) {
$dif =($neededLevels - $dirLevels) - 1;
$newDir = $docsDir;
for ($i = 0; $i < $dif; $i++)
$newDir .= "/$zerosDir";
$success = rename($docsDir, $tempDir)
&& mkdir($newDir, 0770, TRUE)
&& rename($tempDir, "$newDir/$zerosDir");
if (!$success)
throw new Exception('Error while reorganizing directory tree');
}
$curLevels = $db->query('UPDATE dms_config SET dir_levels = #',
[$neededLevels]);
}
// Saves the document to the repository
$padLen =($neededLevels + 1) * $digXDir;
$paddedId = str_pad($docId, $padLen, '0', STR_PAD_LEFT);
$saveDir = $docsDir;
for ($i = 0; $i < $neededLevels; $i++)
$saveDir .= '/'. substr($paddedId, $i * $digXDir, $digXDir);
if (!file_exists($saveDir))
mkdir($saveDir, 0770, TRUE);
$savePath = "$saveDir/". substr($paddedId, -$digXDir);
move_uploaded_file($_FILES['doc']['tmp_name'], $savePath);
$db->query('COMMIT');
return $docId;
} catch (Exception $e) {
$db->query('ROLLBACK');
throw $e;
}
}
}

View File

@ -1,52 +0,0 @@
<?php
use Vn\Lib;
class Sms extends Vn\Web\JsonRequest {
const PARAMS = [
'destination'
,'message'
];
const OK_STATES = [
0, // Ok
200 // Processing
];
function run($db) {
$smsConfig = $db->getObject('SELECT uri, user, password, title FROM vn.smsConfig');
$sClient = new SoapClient($smsConfig->uri);
$xmlString = $sClient->sendSMS(
$smsConfig->user
,$smsConfig->password
,$smsConfig->title
,$_REQUEST['destination']
,$_REQUEST['message']
);
$xmlResponse = new SimpleXMLElement($xmlString);
$res = $xmlResponse->sms;
$db->query(
'INSERT INTO vn.sms SET
`senderFk` = account.myUser_getId(),
`destinationFk` = #,
`destination` = #,
`message` = #,
`statusCode` = #,
`status` = #',
[
empty($_REQUEST['destinationId']) ? NULL : $_REQUEST['destinationId']
,$_REQUEST['destination']
,$_REQUEST['message']
,$res->codigo
,$res->descripcion
]
);
if (!in_array((int) $res->codigo, self::OK_STATES))
throw new Lib\UserException($res->descripcion);
return TRUE;
}
}

View File

@ -31,13 +31,12 @@ class RestService extends Service {
$_REQUEST['method'], $class, './rest'); $_REQUEST['method'], $class, './rest');
$method->service = $this; $method->service = $this;
$isAuthorized = $db->getValue('SELECT myUser_checkRestPriv(#)',
[$_REQUEST['method']]);
if (!$isAuthorized)
throw new ForbiddenException(s('You don\'t have enough privileges'));
if ($method::SECURITY == Security::DEFINER) { if ($method::SECURITY == Security::DEFINER) {
$isAuthorized = $db->getValue('SELECT myUser_checkRestPriv(#)',
[$_REQUEST['method']]);
if (!$isAuthorized)
throw new UserException(s('You don\'t have enough privileges'));
$methodDb = $db; $methodDb = $db;
} else } else
$methodDb = $this->getUserDb($_SESSION['user']); $methodDb = $this->getUserDb($_SESSION['user']);
@ -71,6 +70,8 @@ class RestService extends Service {
$status = 401; $status = 401;
} catch (BadLoginException $e) { } catch (BadLoginException $e) {
$status = 401; $status = 401;
} catch (ForbiddenException $e) {
$status = 403;
} catch (Lib\UserException $e) { } catch (Lib\UserException $e) {
$status = 400; $status = 400;
} catch (\Exception $e) { } catch (\Exception $e) {

View File

@ -2,6 +2,8 @@
namespace Vn\Web; namespace Vn\Web;
include __DIR__.'/uid.php';
use Vn\Db; use Vn\Db;
use Vn\Lib\Locale; use Vn\Lib\Locale;
use Vn\Lib\UserException; use Vn\Lib\UserException;
@ -21,6 +23,11 @@ class SessionExpiredException extends UserException {}
*/ */
class BadLoginException extends UserException {} class BadLoginException extends UserException {}
/**
* Thrown when user credentials are invalid.
*/
class ForbiddenException extends UserException {}
/** /**
* Thrown when user credentials are invalid. * Thrown when user credentials are invalid.
*/ */
@ -130,62 +137,26 @@ abstract class Service {
*/ */
function login() { function login() {
$db = $this->db; $db = $this->db;
$anonymousUser = FALSE; $anonymousUser = TRUE;
if (isset($_POST['user']) && !empty($_POST['password'])) {
$user = strtolower($_POST['user']);
$passwordHash = $db->getValue( if (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
'SELECT bcryptPassword FROM account.user WHERE `name` = #', $userId = $db->getValue(
[$user] 'SELECT userId FROM salix.AccessToken
WHERE id = #
AND NOW() <= TIMESTAMPADD(SECOND, ttl, created)',
[$_SERVER['HTTP_AUTHORIZATION']]
); );
$passwordOk = !empty($passwordHash) if (!$userId)
&& password_verify($_POST['password'], $passwordHash); throw new SessionExpiredException();
// XXX: Compatibility with old MD5 passwords $anonymousUser = FALSE;
if (empty($passwordHash)) { $user = $db->getValue(
$md5Password = $db->getValue( 'SELECT `name` FROM account.user WHERE id = #',
'SELECT `password` FROM account.user [$userId]
WHERE active AND `name` = #', );
[$user] } else
); $user = $db->getValue('SELECT guestUser FROM config');
$passwordOk = !empty($md5Password)
&& $md5Password == md5($_POST['password']);
}
if (!$passwordOk) {
sleep(3);
throw new BadLoginException();
}
} else {
if (isset($_POST['token']) || isset($_GET['token'])) {
if (isset($_POST['token']))
$token = $_POST['token'];
if (isset($_GET['token']))
$token = $_GET['token'];
$key = $db->getValue('SELECT jwtKey FROM config');
try {
$jwtPayload = Jwt::decode($token, $key);
} catch (\Exception $e) {
throw new BadLoginException($e->getMessage());
}
$expiration = $jwtPayload['exp'];
if (empty($expiration) || $expiration <= time())
throw new SessionExpiredException();
$user = $jwtPayload['sub'];
}
else {
$user = $db->getValue('SELECT guestUser FROM config');
$anonymousUser = TRUE;
}
}
if (!$anonymousUser) { if (!$anonymousUser) {
$isActive = $db->getValue( $isActive = $db->getValue(
@ -217,9 +188,15 @@ abstract class Service {
* Logouts the current user. Cleans the last saved used credentials. * Logouts the current user. Cleans the last saved used credentials.
*/ */
function logout() { function logout() {
if (!empty($_SERVER['HTTP_AUTHORIZATION']))
$db->query(
'DELETE FROM salix.AccessToken WHERE id = #',
[$_SERVER['HTTP_AUTHORIZATION']]
);
unset($_SESSION['user']); unset($_SESSION['user']);
} }
/** /**
* Creates or returns a database connection where the authenticated user * Creates or returns a database connection where the authenticated user
* is the role of the current logged user. * is the role of the current logged user.
@ -248,15 +225,45 @@ abstract class Service {
return $userDb; return $userDb;
} }
/**
* Generates a authentication token for the specified $user.
*
* @param {string} $user The user name
* @param {boolean} $remember Wether to create long live token
* @return {string} The generated token
*/
function createToken($user, $remember = FALSE) {
if ($remember)
$tokenLife = 2 * WEEK;
else
$tokenLife = 30 * MIN;
$token = uid(DEFAULT_TOKEN_LEN);
$userId = $this->db->getValue(
'SELECT id FROM account.user WHERE `name` = #',
[$user]
);
$this->db->query(
'INSERT INTO salix.AccessToken
SET id = #,
ttl = #,
created = NOW(),
userId = #',
[$token, $tokenLife, $userId]
);
return $token;
}
/** /**
* Generates a JWT authentication token for the specified $user. * Generates a JWT authentication token for the specified $user.
* *
* @param {string} $user The user name * @param {string} $user The user name
* @param {boolean} $remember Wether to create long live token * @param {boolean} $remember Wether to create long live token
* @param {boolean} $recover Wether to enable recovery mode on login
* @return {string} The JWT generated token * @return {string} The JWT generated token
*/ */
function createToken($user, $remember = FALSE) { function createJwtToken($user, $remember = FALSE) {
if ($remember) if ($remember)
$tokenLife = WEEK; $tokenLife = WEEK;
else else

14
web/uid.php Normal file
View File

@ -0,0 +1,14 @@
<?php
namespace Vn\Web;
const DEFAULT_TOKEN_LEN = 64;
const UIDCHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
function uid($length) {
$bytes = bin2hex(random_bytes($length));
$r = '';
for ($i = 0; $i < $length; $i++)
$r .= UIDCHARS[hexdec(substr($bytes, $i * 2, 2)) % 64];
return $r;
}

View File

@ -42,15 +42,15 @@ var baseConfig = {
new webpack.DefinePlugin({ new webpack.DefinePlugin({
_DEV_MODE: devMode, _DEV_MODE: devMode,
_DEV_SERVER_PORT: wpConfig.devServerPort, _DEV_SERVER_PORT: wpConfig.devServerPort,
_PUBLIC_PATH: JSON.stringify (publicPath) _PUBLIC_PATH: JSON.stringify(publicPath)
}) })
], ],
optimization: { optimization: {
runtimeChunk: true, runtimeChunk: true,
splitChunks: { splitChunks: {
chunks: 'all', chunks: 'all',
} }
}, },
watchOptions: { watchOptions: {
ignored: /node_modules/ ignored: /node_modules/
} }
@ -78,8 +78,15 @@ var devConfig = {
devServer: { devServer: {
host: '0.0.0.0', host: '0.0.0.0',
port: wpConfig.devServerPort, port: wpConfig.devServerPort,
headers: { 'Access-Control-Allow-Origin': '*' }, headers: {'Access-Control-Allow-Origin': '*'},
stats: { chunks: false } stats: { chunks: false },
proxy: {
'/api': 'http://localhost:3000',
'/': {
target: 'http://localhost/projects/hedera-web',
bypass: (req) => req.path !== '/' ? req.path : null
}
}
}, },
devtool: 'eval' devtool: 'eval'
}; };