0
1
Fork 0

Merge pull request 'test' (#4) from test into master

Reviewed-on: verdnatura/hedera-web#4
This commit is contained in:
Juan Ferrer 2022-05-18 16:28:22 +00:00
commit 1d782a28ed
42 changed files with 457 additions and 686 deletions

6
Jenkinsfile vendored
View File

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

View File

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

75
app.js
View File

@ -1,71 +1,60 @@
__webpack_public_path__ = _PUBLIC_PATH;
var assetsPath;
require('hedera/hedera');
if (_DEV_MODE)
{
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);
window.onload = function() {
loadLocale(main);
}
function main (req)
{
function main(req) {
if (req)
onLocaleLoad (req);
onLocaleLoad(req);
hederaWeb = new Hedera.App ();
hederaWeb.run ();
hederaWeb = new Hedera.App();
hederaWeb.run();
}
function loadLocale (cb)
{
Vn.Locale.init ();
function loadLocale(cb) {
Vn.Locale.init();
var lang = Vn.Locale.language;
var req = require.context ('js', true, /locale\/en.yml$/);
onLocaleLoad (req);
var req = require.context('js', true, /locale\/en.yml$/);
onLocaleLoad(req);
switch (lang)
{
switch (lang) {
case 'ca':
require ([], function () {
cb (require.context ('js', true, /locale\/ca.yml$/)); });
require([], function() {
cb(require.context('js', true, /locale\/ca.yml$/));
});
break;
case 'es':
require ([], function () {
cb (require.context ('js', true, /locale\/es.yml$/)); });
require([], function() {
cb(require.context('js', true, /locale\/es.yml$/));
});
break;
case 'fr':
require ([], function () {
cb (require.context ('js', true, /locale\/fr.yml$/)); });
require([], function() {
cb(require.context('js', true, /locale\/fr.yml$/));
});
break;
case 'mn':
require ([], function () {
cb (require.context ('js', true, /locale\/mn.yml$/)); });
require([], function() {
cb(require.context('js', true, /locale\/mn.yml$/));
});
break;
case 'pt':
require ([], function () {
cb (require.context ('js', true, /locale\/pt.yml$/)); });
require([], function() {
cb(require.context('js', true, /locale\/pt.yml$/));
});
break;
default:
cb ();
cb();
}
}
function onLocaleLoad (req)
{
var keys = req.keys ();
function onLocaleLoad(req) {
var keys = req.keys();
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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,18 +1,15 @@
Hedera.Users = new Class
({
Hedera.Users = new Class({
Extends: Hedera.Form
,onAccessLogClick: function (button, form)
{
this.hash.set ({
,onAccessLogClick: function(button, form) {
this.hash.set({
'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')
scope.$('disabled').style.display = isEnabled ?
'none' : 'block';
@ -20,15 +17,13 @@ Hedera.Users = new Class
'block' : 'none';
}
,onChangeUserClick: function (button, form)
{
this.gui.supplantUser (form.get ('name'),
this.onUserSupplant.bind (this));
,onChangeUserClick: function(button, form) {
this.gui.supplantUser(form.get('name'),
this.onUserSupplant.bind(this));
}
,onUserSupplant: function ()
{
this.hash.set ({form: 'ecomerce/orders'});
,onUserSupplant: function() {
this.hash.set({form: 'ecomerce/orders'});
}
});

View File

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

View File

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

View File

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

View File

@ -27,7 +27,7 @@
<htk-loader class="head" form="ticket">
<div>
<p class="important ticket-id">
@<htk-text column="id" form="ticket"/>
#<htk-text column="id" form="ticket"/>
</p>
<p>
<t>Preparation</t> <htk-text form="ticket" column="shipped" format="%D"/>
@ -151,7 +151,7 @@
<htk-text form="iter" column="name"/>
</h2>
<p>
@<htk-text form="iter" column="id"/>
#<htk-text form="iter" column="id"/>
</p>
<p class="amount">
<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.
**/
module.exports = new Class
({
module.exports = new Class({
results: null
,error: null
/**
* Initilizes the resultset object.
**/
,initialize: function (results, error)
{
,initialize: function(results, error) {
this.results = results;
this.error = error;
}
@ -23,19 +21,17 @@ module.exports = new Class
*
* @return {Db.Err} the error or null if no errors hapened
**/
,getError: function ()
{
,getError: function() {
return this.error;
}
,fetch: function ()
{
,fetch: function() {
if (this.error)
throw this.error;
if (this.results !== null
&& this.results.length > 0)
return this.results.shift ();
return this.results.shift();
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
**/
,fetchResult: function ()
{
var result = this.fetch ();
,fetchResult: function() {
var result = this.fetch();
if (result !== null)
{
if (result !== null) {
if (result.data instanceof Array)
return new Result (result);
return new Result(result);
else
return true;
}
@ -65,9 +59,8 @@ module.exports = new Class
*
* @return {Array} the row if success, %null otherwise
**/
,fetchRow: function ()
{
var result = this.fetch ();
,fetchRow: function() {
var result = this.fetch();
if (result !== null
&& result.data instanceof Array
@ -77,14 +70,29 @@ module.exports = new Class
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.
*
* @return {Object} the value if success, %null otherwise
**/
,fetchValue: function ()
{
var row = this.fetchRow ();
,fetchValue: function() {
var row = this.fetchRow();
if (row instanceof Array && row.length > 0)
return row[0];

View File

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

View File

@ -48,7 +48,7 @@ module.exports = new Class({
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 url FROM imageConfig;'
+'SELECT dbproduccion FROM vn2008.tblContadores;'
@ -121,8 +121,8 @@ module.exports = new Class({
,onMainQueryDone: function(resultSet) {
// Retrieving the user name
var userName = resultSet.fetchValue();
Vn.Node.setText(this.$('user-name'), userName);
this.user = resultSet.fetchObject();
Vn.Node.setText(this.$('user-name'), this.user.nickname);
// Retrieving configuration parameters

View File

@ -9,7 +9,8 @@ Login phone: +34 607 562 391
Password forgotten? Push here: ¿Has oblidat la teva contrasenya?
Yet you are not a customer?: Encara no ets client?
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: >-
Usuari o contrasenya incorrectes, recorda que s'hi distingeix entre majú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?
Sign up: Sign up
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'
User disabled: >-
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?
Sign up: Registrarme
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: >-
Usuario o contraseña incorrectos, recuerda que se hace distinción entre
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?
Sign up: Inscription
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: >-
Utilisateur ou mot de passe incorrect, n'oubliez pas de distinction entre
majuscules et minuscules

View File

@ -9,7 +9,6 @@ Login phone: +34 607 562 391
Password forgotten? Push here: Нууц үг мартсан? энд түлхэх
Yet you are not a customer?: Гэсэн хэдий ч та хэрэглэгчийн биш гэж үү?
Sign up: бүртгүүлэх
You've been too idle: 'Та нар ч бас зогссон байсан, чуулган хугацаа дууссан байна'
Invalid login: 'Хэрэглэгчийн нэр эсвэл нууц үг буруу, Тэр хэргийг мэдрэмтгий гэдгийг санаарай'
Please write your user name: Хэрэглэгчийн нэрээ бичнэ үү
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
Yet you are not a customer?: Ainda não és cliente?
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: >-
Usuário ou Palavra-Passe incorreto, lembre-se de diferenciar maiusculas e
minusculas

View File

@ -1,129 +1,112 @@
var Css = require ('./login.css');
var Tpl = require ('./login.xml');
require('./login.css');
var Tpl = require('./login.xml');
module.exports = new Class
({
module.exports = new Class({
Extends: Htk.Component,
Properties:
{
conn:
{
type: Db.Connection
,set: function (x)
{
this.link ({_conn: x}, {'loading-changed': this._onConnLoadChange});
,set: function(x) {
this.link({_conn: x}, {'loading-changed': this._onConnLoadChange});
}
,get: function ()
{
,get: function() {
return this._conn;
}
}
}
,initialize: function (props)
{
this.parent (props);
this.builderInitString (Tpl);
,initialize: function(props) {
this.parent(props);
this.builderInitString(Tpl);
this.$('social-bar').conn = this._conn;
var self = this;
this.$('form').onsubmit = function ()
{
self._onSubmit ();
this.$('form').onsubmit = function() {
self._onSubmit();
return false;
};
}
,_onConnLoadChange: function (conn, isLoading)
{
,_onConnLoadChange: function(conn, isLoading) {
if (isLoading)
this.$('spinner').start ();
this.$('spinner').start();
else
this.$('spinner').stop ();
this.$('spinner').stop();
}
,show: function ()
{
document.body.appendChild (this.node);
,show: function() {
document.body.appendChild(this.node);
var lastUser = localStorage.getItem ('hederaLastUser');
var lastUser = localStorage.getItem('hederaLastUser');
if (lastUser)
this.$('user').value = lastUser;
this._focusUserInput ();
this._focusUserInput();
}
,_onSubmit: function ()
{
this._conn.open (
,_onSubmit: function() {
this._conn.open(
this.$('user').value,
this.$('pass').value,
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._disableUi (false);
this._disableUi(false);
if (success)
{
if (success) {
var user = this.$('user').value;
if (user)
localStorage.setItem ('hederaLastUser', user);
localStorage.setItem('hederaLastUser', user);
this.signalEmit ('login');
}
else
{
this._focusUserInput ();
this.signalEmit('login');
} else {
this._focusUserInput();
throw error;
}
}
,hide: function ()
{
Vn.Node.remove (this.node);
,hide: function() {
Vn.Node.remove(this.node);
}
,_focusUserInput: function ()
{
,_focusUserInput: function() {
var userEntry = this.$('user');
userEntry.focus ();
userEntry.select ();
userEntry.focus();
userEntry.select();
}
,_disableUi: function (disabled)
{
,_disableUi: function(disabled) {
this.$('user').disabled = disabled;
this.$('pass').disabled = disabled;
this.$('submit').disabled = disabled;
}
,onPasswordLost: function ()
{
,onPasswordLost: function() {
var user = this.$('user').value;
if (!user)
Htk.Toast.showError (_('Please write your user name'));
Htk.Toast.showError(_('Please write your user name'));
else
this._conn.send ('core/recover-password', {recoverUser: user},
this._onPasswordRecovered.bind (this));
this._conn.send('core/recover-password', {recoverUser: user},
this._onPasswordRecovered.bind(this));
}
,_onPasswordRecovered: function (json, error)
{
,_onPasswordRecovered: function(json, error) {
if (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 JsonException = require ('./json-exception');
var Object = require('./object');
var JsonException = require('./json-exception');
/**
* Handler for JSON rest connections.
**/
module.exports = new Class
({
module.exports = new Class({
Extends: Object
,_connected: false
@ -16,29 +15,26 @@ module.exports = new Class
/**
* Initilizes the connection object.
**/
,initialize: function ()
{
this.parent ();
this.fetchToken ();
,initialize: function() {
this.parent();
this.fetchToken();
}
,fetchToken: function ()
{
,fetchToken: function() {
var token = null;
if (sessionStorage.getItem ('vnToken'))
token = sessionStorage.getItem ('vnToken');
if (localStorage.getItem ('vnToken'))
token = localStorage.getItem ('vnToken');
if (sessionStorage.getItem('vnToken'))
token = sessionStorage.getItem('vnToken');
if (localStorage.getItem('vnToken'))
token = localStorage.getItem('vnToken');
this.token = token;
}
,clearToken: function ()
{
,clearToken: function() {
this.token = null;
localStorage.removeItem ('vnToken');
sessionStorage.removeItem ('vnToken');
localStorage.removeItem('vnToken');
sessionStorage.removeItem('vnToken');
}
/**
@ -49,43 +45,37 @@ module.exports = new Class
* @param {Boolean} remember Specifies if the user should be remembered
* @param {Function} openCallback The function to call when operation is done
**/
,open: function (user, pass, remember, callback)
{
if (user !== null && user !== undefined)
{
,open: function(user, pass, remember, callback) {
if (user !== null && user !== undefined) {
var params = {
user: user
,password: pass
,remember: remember
};
}
else
} else
var params = null;
this.send ('core/login', params,
this._onOpen.bind (this, callback, remember));
this.lbSend('POST', 'Accounts/login', params,
this._onOpen.bind(this, callback, remember));
}
/*
* Called when open operation is done.
*/
,_onOpen: function (callback, remember, json, error)
{
if (json && json.login)
{
,_onOpen: function(callback, remember, json, error) {
if (json) {
this._connected = true;
this.token = json.token;
var storage = remember ? localStorage : sessionStorage;
storage.setItem ('vnToken', this.token);
storage.setItem('vnToken', this.token);
this.signalEmit ('openned');
}
else
this._closeClient ();
this.signalEmit('openned');
} else
this._closeClient();
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
**/
,close: function (callback)
{
this._closeClient ();
this.send ('core/logout', null,
this._onClose.bind (this, callback));
,close: function(callback) {
this.lbSend('POST', 'Accounts/logout', null,
this._onClose.bind(this, callback));
this._closeClient();
}
/*
* Called when close operation is done.
*/
,_onClose: function (callback, json, error)
{
this.signalEmit ('closed');
,_onClose: function(callback, json, error) {
this.signalEmit('closed');
if (callback)
callback (this, json === true, error);
callback(this, null, error);
}
,_closeClient: function ()
{
,_closeClient: function() {
this._connected = false;
this.clearToken ();
this.clearToken();
}
/**
* Suppants another user.
* Supplants another user.
*
* @param {String} user The user name
* @param {Function} callback The callback function
**/
,supplantUser: function (user, callback)
{
,supplantUser: function(user, callback) {
var params = {supplantUser: user};
this.send ('core/supplant', params,
this._onUserSupplant.bind (this, callback));
this.send('client/supplant', params,
this._onUserSupplant.bind(this, callback));
}
,_onUserSupplant: function (callback, json, error)
{
,_onUserSupplant: function(callback, json) {
if (json)
this.token = json;
if (callback)
callback (json != null);
callback(json != null);
}
/**
* Ends the user supplanting and restores the last login.
**/
,supplantEnd: function ()
{
this.fetchToken ();
,supplantEnd: function() {
this.lbSend('POST', 'Accounts/logout');
this.fetchToken();
}
/**
@ -155,18 +140,16 @@ module.exports = new Class
* @param {Map} params The params to pass to the service
* @param {Function} callback The response callback
**/
,send: function (restService, params, callback)
{
,send: function(restService, params, callback) {
if (!params)
params = {};
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 elements = form.elements;
@ -174,83 +157,88 @@ module.exports = new Class
if (elements[i].name)
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)
{
var formData = new FormData (form);
,sendFormMultipart: function(form, callback) {
var formData = new FormData(form);
var request = new XMLHttpRequest();
request.open('POST', form.action, true);
if (this.token)
formData.append ('token', this.token);
var request = new XMLHttpRequest ();
request.open ('POST', form.action, true);
request.setRequestHeader('Authorization', this.token);
request.onreadystatechange =
this._onStateChange.bind (this, request, callback);
request.send (formData);
this._onStateChange.bind(this, request, callback);
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)
formData.append ('token', this.token);
var request = new XMLHttpRequest ();
request.open ('POST', '', true);
request.setRequestHeader('Authorization', this.token);
request.onreadystatechange =
this._onStateChange.bind (this, request, callback);
request.send (formData);
this._onStateChange.bind(this, request, callback);
request.send(formData);
this._addRequest ();
this._addRequest();
}
/*
* Called when REST response is received.
*/
,sendWithUrl: function (params, callback, method, url)
{
if (this.token)
params['token'] = this.token;
var request = new XMLHttpRequest ();
request.open (method, url, true);
request.setRequestHeader ('Content-Type',
,sendWithUrl: function(params, callback, method, url) {
var request = new XMLHttpRequest();
request.open(method, url, true);
request.setRequestHeader('Content-Type',
'application/x-www-form-urlencoded');
if (this.token)
request.setRequestHeader('Authorization', this.token);
request.onreadystatechange =
this._onStateChange.bind (this, request, callback);
request.send (Vn.Url.makeUri (params));
this._onStateChange.bind(this, request, callback);
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++;
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)
return;
this._requestsCount--;
if (this._requestsCount === 0)
this.signalEmit ('loading-changed', false);
this.signalEmit('loading-changed', false);
var data = null;
var error = null;
try {
if (request.status == 0)
{
var ex = new JsonException ();
if (request.status == 0) {
var ex = new JsonException();
ex.message = _('The server does not respond, please check your Internet connection');
throw ex;
}
@ -259,70 +247,75 @@ module.exports = new Class
try {
contentType = request
.getResponseHeader ('Content-Type')
.split (';')[0]
.trim ();
.getResponseHeader('Content-Type')
.split(';')[0]
.trim();
} catch (err) {
console.warn(err);
}
catch (e) {}
if (contentType != 'application/json')
{
var ex = new JsonException ();
if (contentType != 'application/json') {
var ex = new JsonException();
ex.message = request.statusText;
ex.code = request.status;
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 ();
ex.exception = exception;
ex.message = jsData.message;
ex.code = jsData.code;
ex.file = jsData.file;
ex.line = jsData.line;
ex.trace = jsData.trace;
var json;
var jsData;
if (request.responseText)
json = JSON.parse(request.responseText);
if (json)
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;
}
}
catch (e)
{
} catch (e) {
data = null;
error = e;
}
if (callback)
try {
callback (data, error);
callback(data, error);
error = null;
}
catch (e)
{
} catch (e) {
error = e;
}
if (error)
{
if (error) {
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",
"version": "1.407.66",
"version": "1.407.68",
"description": "Verdnatura web page",
"license": "GPL-3.0",
"repository": {

View File

@ -8,7 +8,7 @@
</vn-group>
<div id="report" class="sheet">
<h2>
@<htk-text column="id" form="ticket"/>
#<htk-text column="id" form="ticket"/>
</h2>
<div class="header">
<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']);
}
}

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');
$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) {
$isAuthorized = $db->getValue('SELECT myUser_checkRestPriv(#)',
[$_REQUEST['method']]);
if (!$isAuthorized)
throw new UserException(s('You don\'t have enough privileges'));
$methodDb = $db;
} else
$methodDb = $this->getUserDb($_SESSION['user']);
@ -71,6 +70,8 @@ class RestService extends Service {
$status = 401;
} catch (BadLoginException $e) {
$status = 401;
} catch (ForbiddenException $e) {
$status = 403;
} catch (Lib\UserException $e) {
$status = 400;
} catch (\Exception $e) {

View File

@ -2,6 +2,8 @@
namespace Vn\Web;
include __DIR__.'/uid.php';
use Vn\Db;
use Vn\Lib\Locale;
use Vn\Lib\UserException;
@ -21,6 +23,11 @@ class SessionExpiredException extends UserException {}
*/
class BadLoginException extends UserException {}
/**
* Thrown when user credentials are invalid.
*/
class ForbiddenException extends UserException {}
/**
* Thrown when user credentials are invalid.
*/
@ -130,62 +137,26 @@ abstract class Service {
*/
function login() {
$db = $this->db;
$anonymousUser = FALSE;
if (isset($_POST['user']) && !empty($_POST['password'])) {
$user = strtolower($_POST['user']);
$anonymousUser = TRUE;
$passwordHash = $db->getValue(
'SELECT bcryptPassword FROM account.user WHERE `name` = #',
[$user]
if (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
$userId = $db->getValue(
'SELECT userId FROM salix.AccessToken
WHERE id = #
AND NOW() <= TIMESTAMPADD(SECOND, ttl, created)',
[$_SERVER['HTTP_AUTHORIZATION']]
);
$passwordOk = !empty($passwordHash)
&& password_verify($_POST['password'], $passwordHash);
if (!$userId)
throw new SessionExpiredException();
// XXX: Compatibility with old MD5 passwords
if (empty($passwordHash)) {
$md5Password = $db->getValue(
'SELECT `password` FROM account.user
WHERE active AND `name` = #',
[$user]
);
$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;
}
}
$anonymousUser = FALSE;
$user = $db->getValue(
'SELECT `name` FROM account.user WHERE id = #',
[$userId]
);
} else
$user = $db->getValue('SELECT guestUser FROM config');
if (!$anonymousUser) {
$isActive = $db->getValue(
@ -217,9 +188,15 @@ abstract class Service {
* Logouts the current user. Cleans the last saved used credentials.
*/
function logout() {
if (!empty($_SERVER['HTTP_AUTHORIZATION']))
$db->query(
'DELETE FROM salix.AccessToken WHERE id = #',
[$_SERVER['HTTP_AUTHORIZATION']]
);
unset($_SESSION['user']);
}
/**
* Creates or returns a database connection where the authenticated user
* is the role of the current logged user.
@ -248,15 +225,45 @@ abstract class Service {
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.
*
* @param {string} $user The user name
* @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
*/
function createToken($user, $remember = FALSE) {
function createJwtToken($user, $remember = FALSE) {
if ($remember)
$tokenLife = WEEK;
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({
_DEV_MODE: devMode,
_DEV_SERVER_PORT: wpConfig.devServerPort,
_PUBLIC_PATH: JSON.stringify (publicPath)
_PUBLIC_PATH: JSON.stringify(publicPath)
})
],
optimization: {
runtimeChunk: true,
splitChunks: {
chunks: 'all',
}
},
optimization: {
runtimeChunk: true,
splitChunks: {
chunks: 'all',
}
},
watchOptions: {
ignored: /node_modules/
}
@ -78,8 +78,15 @@ var devConfig = {
devServer: {
host: '0.0.0.0',
port: wpConfig.devServerPort,
headers: { 'Access-Control-Allow-Origin': '*' },
stats: { chunks: false }
headers: {'Access-Control-Allow-Origin': '*'},
stats: { chunks: false },
proxy: {
'/api': 'http://localhost:3000',
'/': {
target: 'http://localhost/projects/hedera-web',
bypass: (req) => req.path !== '/' ? req.path : null
}
}
},
devtool: 'eval'
};