merge master

This commit is contained in:
ogerly 2021-03-24 06:17:00 +01:00
commit 15bb6f4448
34 changed files with 2706 additions and 350 deletions

View File

@ -214,41 +214,34 @@ jobs:
# run: docker-compose exec -T backend yarn test
##############################################################################
# JOB: UNIT TEST WEBAPP ######################################################
# JOB: UNIT TEST FRONTEND ###################################################
##############################################################################
#unit_test_webapp:
# name: Unit tests - webapp
# runs-on: ubuntu-latest
# needs: [build_test_webapp]
# steps:
# ##########################################################################
# # CHECKOUT CODE ##########################################################
# ##########################################################################
# - name: Checkout code
# uses: actions/checkout@v2
# ##########################################################################
# # DOWNLOAD DOCKER IMAGES #################################################
# ##########################################################################
# - name: Download Docker Image (Webapp)
# uses: actions/download-artifact@v2
# with:
# name: docker-webapp-test
# path: /tmp
# - name: Load Docker Image
# run: docker load < /tmp/webapp.tar
# ##########################################################################
# # UNIT TESTS WEBAPP #####################################################
# ##########################################################################
# # TODO: Why do we need those .envs?
# - name: backend | copy env files webapp
# run: cp webapp/.env.template webapp/.env
# - name: backend | copy env files backend
# run: cp backend/.env.template backend/.env
# - name: backend | docker-compose
# run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp
# - name: webapp | Unit tests
# #run: docker run --rm ocelotsocialnetwork/webapp:build yarn run test
# run: docker-compose exec -T webapp yarn test
unit_test_frontend:
name: Unit tests - Frontend
runs-on: ubuntu-latest
needs: [build_test_frontend]
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v2
##########################################################################
# DOWNLOAD DOCKER IMAGES #################################################
##########################################################################
- name: Download Docker Image (Webapp)
uses: actions/download-artifact@v2
with:
name: docker-frontend-test
path: /tmp
- name: Load Docker Image
run: docker load < /tmp/frontend.tar
##########################################################################
# UNIT TESTS FRONTEND ####################################################
##########################################################################
- name: frontend | Unit tests
run: docker run --rm gradido/frontend:test yarn run test
#test:
# runs-on: ubuntu-latest

View File

@ -55,7 +55,7 @@ class JsonRequestClientComponent extends Component
]), '/getRunningUserTasks');
}
public function getUsers($session_id, $searchString)
public function getUsers($session_id, $searchString, $accountState)
{
if($searchString == "") {
return ['state' => 'error', 'type' => 'parameter error', 'msg' => 'search string is empty'];
@ -66,7 +66,8 @@ class JsonRequestClientComponent extends Component
return $this->sendRequest(json_encode([
'session_id' => $session_id,
'search' => $searchString
'search' => $searchString,
'account_state' => $accountState,
]), '/getUsers');
}

View File

@ -197,7 +197,7 @@ class StateBalancesController extends AppController
}
uasort($transactions, array($this, 'sortTransactions'));
$this->set('transactions', $transactions);
$this->set('transactionExecutingCount', $session->read('Transaction.executing'));
$this->set('transactionExecutingCount', $session->read('Transactions.executing'));
$this->set('balance', $session->read('StateUser.balance'));
$this->set('timeUsed', microtime(true) - $startTime);
$this->set('gdtSum', $gdtSum);
@ -370,7 +370,7 @@ class StateBalancesController extends AppController
return $this->returnJson([
'state' => 'success',
'transactions' => $transactions,
'transactionExecutingCount' => $session->read('Transaction.executing'),
'transactionExecutingCount' => $session->read('Transactions.executing'),
'count' => count($transactions),
'gdtSum' => $gdtSum,
'timeUsed' => microtime(true) - $startTime

View File

@ -196,7 +196,7 @@ class StateUserTransactionsController extends AppController
return $this->returnJson([
'state' => 'success',
'transactions' => $transactions,
'transactionExecutingCount' => $session->read('Transaction.executing'),
'transactionExecutingCount' => $session->read('Transactions.executing'),
'count' => $all_user_transactions_count,
'timeUsed' => microtime(true) - $startTime
]);

View File

@ -99,9 +99,14 @@ class StateUsersController extends AppController
//$this->set('timeUsed', $timeUsed);
$csfr_token = $this->request->getParam('_csrfToken');
$this->set(compact('timeUsed', 'searchForm', 'csfr_token'));
$empty_string = '... empty ...';
if ($this->request->is('post')) {
$finalUserEntrys = [];
$requestData = $this->request->getData();
$account_state = $requestData['account_state'];
if($requestData['search'] == '' && $account_state != 'all') {
$requestData['search'] = $empty_string;
}
if ($searchForm->validate($requestData)) {
//var_dump($requestData);
@ -111,7 +116,7 @@ class StateUsersController extends AppController
$searchType = 'email';
}
// find users on login server
$resultJson = $this->JsonRequestClient->getUsers($session->read('session_id'), $searchString);
$resultJson = $this->JsonRequestClient->getUsers($session->read('session_id'), $searchString, $account_state);
$loginServerUser = [];
if ($resultJson['state'] == 'success') {
$dataJson = $resultJson['data'];
@ -139,28 +144,42 @@ class StateUsersController extends AppController
}
}
// find user on community server db
$globalSearch = '%' . $searchString . '%';
$communityUsers = $this->StateUsers
->find('all')
->contain(['StateBalances' => ['fields' => ['amount', 'state_user_id']]]);
$communityUsers->where(['OR' => [
'first_name LIKE' => $globalSearch,
'last_name LIKE' => $globalSearch,
//'username LIKE' => $globalSearch,
'email LIKE' => $globalSearch
]]);
//var_dump($communityUsers->toArray());
foreach ($communityUsers as $u) {
$pubkey_hex = bin2hex(stream_get_contents($u->public_key));
$u->public_hex = $pubkey_hex;
if (!isset($pubkeySorted[$pubkey_hex])) {
$pubkeySorted[$pubkey_hex] = ['login' => [], 'community' => []];
if($account_state == 'email not activated') {
if(count($pubkeySorted) > 0) {
$communityUsers->where(['hex(public_key) IN' => array_keys($pubkeySorted)]);
} else {
$communityUsers = null;
}
array_push($pubkeySorted[$pubkey_hex]['community'], $u);
} else {
$globalSearch = '%' . $searchString . '%';
$communityUsers->where(['OR' => [
'first_name LIKE' => $globalSearch,
'last_name LIKE' => $globalSearch,
//'username LIKE' => $globalSearch,
'email LIKE' => $globalSearch
]]);
}
$finalUserEntrys = [];
//var_dump($communityUsers->toArray());
if($communityUsers) {
foreach ($communityUsers as $u) {
$pubkey_hex = bin2hex(stream_get_contents($u->public_key));
$u->public_hex = $pubkey_hex;
if (!isset($pubkeySorted[$pubkey_hex])) {
$pubkeySorted[$pubkey_hex] = ['login' => [], 'community' => []];
}
array_push($pubkeySorted[$pubkey_hex]['community'], $u);
}
}
// detect states
foreach ($pubkeySorted as $pubhex => $user) {
$finalUser = [];

View File

@ -323,7 +323,7 @@ class TransactionCreationsController extends AppController
$this->set('firstDayLastMonth', $firstDayLastMonth);
$this->set('activeUser', $user);
$this->set('creationForm', $creationForm);
$this->set('transactionExecutingCount', $session->read('Transaction.executing'));
$this->set('transactionExecutingCount', $session->read('Transactions.executing'));
$this->set('timeUsed', microtime(true) - $startTime);
$this->set('countUsers', $countUsers);
$this->set('limit', $limit);

View File

@ -11,7 +11,9 @@ class UserSearchForm extends Form
protected function _buildSchema(Schema $schema)
{
return $schema->addField('search', ['type' => 'string']);
return $schema
->addField('search', ['type' => 'string'])
->addField('account_state', ['type' => 'select']);
}
function validationDefault(Validator $validator)

View File

@ -11,6 +11,19 @@ $this->assign('title', __('Benutzer suchen'));
$this->loadHelper('Form', [
'templates' => 'horizontal_form',
]);
$stateOptions = [
'all' => __('Alle'),
//'account created'=>__('Konto angelegt'),
//'account not on login-server' => __('Konto nicht auf Login-Server'),
//'email activated' => __('Konto aktiviert'),
//'account copied to community' => __('Konto auf Gemeinschafts-Server'),
'email not activated' => __('Konto nicht aktiviert'),
//'account multiple times on login-server' => __('Konto mehrfach vorhanden'),
//'account not on community server' => __('Konto nicht auf Gemeinschafts-Server'),
//'no keys' => __('Keine Schlüssel generiert')
];
?>
<?= $this->Html->css([
'loginServer/style.css',
@ -41,7 +54,8 @@ $this->loadHelper('Form', [
<p class="form-header">Benutzer suchen</p>
<div class="form-body">
<?= $this->Form->create($searchForm, []) ?>
<?= $this->Form->control('search', ['label' => __('Suchbegriff'), 'class' => 'form-control', 'id' => 'inlineFormInputGroup', 'placeholder' => __('Vorname/Nachname/E-Mail')]) ?>
<?= $this->Form->control('search', ['label' => __('Suchbegriff'), 'class' => 'form-control', 'id' => 'inlineFormInputGroup', 'placeholder' => __('Vorname/Nachname/E-Mail'), 'required' => false]) ?>
<?= $this->Form->control('account_state', ['label' => __('Konto Status'), 'class' => 'form-control', 'type' => 'select', 'options' => $stateOptions]) ?>
<?= $this->Form->button('<i class="material-icons-outlined">search</i>&nbsp;' . __('Suchen'), ['class' => 'form-button']) ?>
<?= $this->Form->hidden('order_row', ['id' => 'input-order-row']) ?>
</div>
@ -59,7 +73,7 @@ $this->loadHelper('Form', [
csfr_token = '<?= $csfr_token ?>';
</script>
<?= $this->Html->script('userSearch') ?>
<?= $this->Html->script('userSearch.min') ?>
<!-- npm run build im mithril client! -->
<!-- keybase://team/gradido/gradido_mithril_user_search -->

View File

@ -185,7 +185,7 @@ process.chdir = function (dir) {
process.umask = function() { return 0; };
},{}],2:[function(require,module,exports){
(function (setImmediate,clearImmediate){
(function (setImmediate,clearImmediate){(function (){
var nextTick = require('process/browser.js').nextTick;
var apply = Function.prototype.apply;
var slice = Array.prototype.slice;
@ -262,9 +262,9 @@ exports.setImmediate = typeof setImmediate === "function" ? setImmediate : funct
exports.clearImmediate = typeof clearImmediate === "function" ? clearImmediate : function(id) {
delete immediateIds[id];
};
}).call(this,require("timers").setImmediate,require("timers").clearImmediate)
}).call(this)}).call(this,require("timers").setImmediate,require("timers").clearImmediate)
},{"process/browser.js":1,"timers":2}],3:[function(require,module,exports){
(function (global,setImmediate){
(function (global,setImmediate){(function (){
new function() {
function Vnode(tag, key, attrs0, children, text, dom) {
@ -1425,7 +1425,7 @@ m.vnode = Vnode
if (typeof module !== "undefined") module["exports"] = m
else window.m = m
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("timers").setImmediate)
}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("timers").setImmediate)
},{"timers":2}],4:[function(require,module,exports){
/*! @preserve
* numeral.js
@ -2442,10 +2442,10 @@ return numeral;
}));
},{}],5:[function(require,module,exports){
(function (global){
(function (global){(function (){
/**!
* @fileOverview Kickass library to create and place poppers near their reference elements.
* @version 1.16.0
* @version 1.16.1
* @license
* Copyright (c) 2016 Federico Zivolo and contributors
*
@ -2797,7 +2797,7 @@ function getBordersSize(styles, axis) {
var sideA = axis === 'x' ? 'Left' : 'Top';
var sideB = sideA === 'Left' ? 'Right' : 'Bottom';
return parseFloat(styles['border' + sideA + 'Width'], 10) + parseFloat(styles['border' + sideB + 'Width'], 10);
return parseFloat(styles['border' + sideA + 'Width']) + parseFloat(styles['border' + sideB + 'Width']);
}
function getSize(axis, body, html, computedStyle) {
@ -2952,8 +2952,8 @@ function getOffsetRectRelativeToArbitraryNode(children, parent) {
var scrollParent = getScrollParent(children);
var styles = getStyleComputedProperty(parent);
var borderTopWidth = parseFloat(styles.borderTopWidth, 10);
var borderLeftWidth = parseFloat(styles.borderLeftWidth, 10);
var borderTopWidth = parseFloat(styles.borderTopWidth);
var borderLeftWidth = parseFloat(styles.borderLeftWidth);
// In cases where the parent is fixed, we must ignore negative scroll in offset calc
if (fixedPosition && isHTML) {
@ -2974,8 +2974,8 @@ function getOffsetRectRelativeToArbitraryNode(children, parent) {
// differently when margins are applied to it. The margins are included in
// the box of the documentElement, in the other cases not.
if (!isIE10 && isHTML) {
var marginTop = parseFloat(styles.marginTop, 10);
var marginLeft = parseFloat(styles.marginLeft, 10);
var marginTop = parseFloat(styles.marginTop);
var marginLeft = parseFloat(styles.marginLeft);
offsets.top -= borderTopWidth - marginTop;
offsets.bottom -= borderTopWidth - marginTop;
@ -3914,8 +3914,8 @@ function arrow(data, options) {
// Compute the sideValue using the updated popper offsets
// take popper margin in account because we don't have this info available
var css = getStyleComputedProperty(data.instance.popper);
var popperMarginSide = parseFloat(css['margin' + sideCapitalized], 10);
var popperBorderSide = parseFloat(css['border' + sideCapitalized + 'Width'], 10);
var popperMarginSide = parseFloat(css['margin' + sideCapitalized]);
var popperBorderSide = parseFloat(css['border' + sideCapitalized + 'Width']);
var sideValue = center - data.offsets.popper[side] - popperMarginSide - popperBorderSide;
// prevent arrowElement from being placed not contiguously to its popper
@ -5068,11 +5068,11 @@ return Popper;
})));
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],6:[function(require,module,exports){
(function (process){
(function (process){(function (){
/**!
* tippy.js v5.1.4
* tippy.js v5.2.1
* (c) 2017-2020 atomiks
* MIT License
*/
@ -5100,7 +5100,7 @@ function _extends() {
return _extends.apply(this, arguments);
}
var version = "5.1.4";
var version = "5.2.1";
/**
* Triggers reflow
@ -5749,7 +5749,6 @@ function bindGlobalEventListeners() {
var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
var ua = isBrowser ? navigator.userAgent : '';
var isIE = /MSIE |Trident\//.test(ua);
var isUCBrowser = /UCBrowser\//.test(ua);
var isIOS = isBrowser && /iPhone|iPad|iPod/.test(navigator.platform);
function updateIOSClass(isAdd) {
var shouldAdd = isAdd && isIOS && currentInput.isTouch;
@ -5939,8 +5938,9 @@ function updatePopperElement(popper, prevProps, nextProps) {
*/
function updateTransitionEndListener(tooltip, action, listener) {
var eventName = isUCBrowser && document.body.style.webkitTransition !== undefined ? 'webkitTransitionEnd' : 'transitionend';
tooltip[action + 'EventListener'](eventName, listener);
['transitionend', 'webkitTransitionEnd'].forEach(function (event) {
tooltip[action + 'EventListener'](event, listener);
});
}
/**
* Adds/removes theme from tooltip's classList
@ -6070,6 +6070,7 @@ function createTippy(reference, passedProps) {
var pluginsHooks = plugins.map(function (plugin) {
return plugin.fn(instance);
});
var hadAriaExpandedAttributeOnCreate = reference.hasAttribute('aria-expanded');
addListenersToTriggerTarget();
handleAriaExpandedAttribute();
@ -6090,8 +6091,9 @@ function createTippy(reference, passedProps) {
instance.clearDelayTimeouts();
}
});
popper.addEventListener('mouseleave', function () {
popper.addEventListener('mouseleave', function (event) {
if (instance.props.interactive && includes(instance.props.trigger, 'mouseenter')) {
debouncedOnMouseMove(event);
doc.addEventListener('mousemove', debouncedOnMouseMove);
}
});
@ -6170,6 +6172,13 @@ function createTippy(reference, passedProps) {
}
function handleAriaExpandedAttribute() {
// If the user has specified `aria-expanded` on their reference when the
// instance was created, we have to assume they're controlling it externally
// themselves
if (hadAriaExpandedAttributeOnCreate) {
return;
}
var nodes = normalizeToArray(instance.props.triggerTarget || reference);
nodes.forEach(function (node) {
if (instance.props.interactive) {
@ -6300,7 +6309,11 @@ function createTippy(reference, passedProps) {
break;
case 'focus':
on(isIE ? 'focusout' : 'blur', onBlur);
on(isIE ? 'focusout' : 'blur', onBlurOrFocusOut);
break;
case 'focusin':
on('focusout', onBlurOrFocusOut);
break;
}
});
@ -6371,7 +6384,7 @@ function createTippy(reference, passedProps) {
return el === reference || el === popper;
});
if (isCursorOverReferenceOrPopper) {
if (event.type === 'mousemove' && isCursorOverReferenceOrPopper) {
return;
}
@ -6405,14 +6418,15 @@ function createTippy(reference, passedProps) {
doc.body.addEventListener('mouseleave', scheduleHide);
doc.addEventListener('mousemove', debouncedOnMouseMove);
pushIfUnique(mouseMoveListeners, debouncedOnMouseMove);
debouncedOnMouseMove(event);
return;
}
scheduleHide(event);
}
function onBlur(event) {
if (event.target !== getCurrentTarget()) {
function onBlurOrFocusOut(event) {
if (!includes(instance.props.trigger, 'focusin') && event.target !== getCurrentTarget()) {
return;
} // If focus was moved to within the popper
@ -6993,7 +7007,6 @@ exports.hideAll = hideAll;
exports.includes = includes;
exports.isBrowser = isBrowser;
exports.isMouseEvent = isMouseEvent;
exports.isUCBrowser = isUCBrowser;
exports.normalizeToArray = normalizeToArray;
exports.removeProperties = removeProperties;
exports.setVisibilityState = setVisibilityState;
@ -7002,11 +7015,11 @@ exports.useIfDefined = useIfDefined;
exports.warnWhen = warnWhen;
}).call(this,require('_process'))
}).call(this)}).call(this,require('_process'))
},{"_process":1,"popper.js":5}],7:[function(require,module,exports){
(function (process){
(function (process){(function (){
/**!
* tippy.js v5.1.4
* tippy.js v5.2.1
* (c) 2017-2020 atomiks
* MIT License
*/
@ -7256,7 +7269,7 @@ var animateFill = {
var _instance$popperChild = instance.popperChildren,
tooltip = _instance$popperChild.tooltip,
content = _instance$popperChild.content;
var backdrop = instance.props.animateFill && !index.isUCBrowser ? createBackdropElement() : null;
var backdrop = instance.props.animateFill ? createBackdropElement() : null;
function addBackdropToPopperChildren() {
instance.popperChildren.backdrop = backdrop;
@ -7391,7 +7404,7 @@ var followCursor = {
// scroll for "vertical"
if (getIsEnabled() && (getIsInitialBehavior() || instance.props.followCursor !== true)) {
if (getIsEnabled() && getIsInitialBehavior()) {
instance.popperInstance.disableEventListeners();
}
}
@ -7432,7 +7445,6 @@ var followCursor = {
var isCursorOverReference = index.closestCallback(event.target, function (el) {
return el === reference;
});
var rect = reference.getBoundingClientRect();
var followCursor = instance.props.followCursor;
var isHorizontal = followCursor === 'horizontal';
var isVertical = followCursor === 'vertical';
@ -7456,6 +7468,7 @@ var followCursor = {
clientWidth: 0,
clientHeight: 0,
getBoundingClientRect: function getBoundingClientRect() {
var rect = reference.getBoundingClientRect();
return {
width: isVerticalPlacement ? size : 0,
height: isVerticalPlacement ? 0 : size,
@ -7721,7 +7734,7 @@ exports.inlinePositioning = inlinePositioning;
exports.sticky = sticky;
}).call(this,require('_process'))
}).call(this)}).call(this,require('_process'))
},{"./tippy.chunk.cjs.js":6,"_process":1,"popper.js":5}],8:[function(require,module,exports){
'use strict';
@ -7749,7 +7762,7 @@ function launch() {
});
})(document, window, domIsReady);
},{"./texte/de":14,"./view":15,"mithril":3}],9:[function(require,module,exports){
},{"./texte/de":13,"./view":14,"mithril":3}],9:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@ -7906,24 +7919,6 @@ exports["default"] = _default;
},{"mithril":3}],12:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = encode;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
function encode(receiver, subject, body) {
//return encodeURIComponent(receiver + '?subject=' + subject + '&body=' + body)
return encodeURIComponent(receiver) + '?subject=' + encodeURIComponent(subject) + '&body=' + encodeURIComponent(body);
}
},{}],13:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
@ -7949,9 +7944,7 @@ __('account multiple times on login-server');
__('account not on community server');
__('no keys');
*/
var AccountState =
/*#__PURE__*/
function () {
var AccountState = /*#__PURE__*/function () {
function AccountState(stateName) {
_classCallCheck(this, AccountState);
@ -8050,7 +8043,7 @@ function () {
exports["default"] = AccountState;
},{"mithril":3}],14:[function(require,module,exports){
},{"mithril":3}],13:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@ -8226,7 +8219,6 @@ var _default = {
DELETE_FROM_COMMUNITY_SUCCESS: 'Benutzer Konto vom Gemeinschafts-Server erfolgreich gelöscht',
VERIFICATION_EMAIL_RESEND: 'Verification Email erneut zusenden',
VERIFICATION_EMAIL_RESEND_SUCCESS: 'Verification Email wird erneut zugestellt',
MAILTO_VERIFICATION_EMAIL: 'Verification Email selbst verschicken',
COPY_FAILED: 'Fehler beim Kopieren',
DELETE_FAILED: 'Fehler beim löschen',
RESEND_FAILED: 'Senden fehlgeschlagen',
@ -8240,7 +8232,7 @@ var _default = {
};
exports["default"] = _default;
},{}],15:[function(require,module,exports){
},{}],14:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@ -8305,7 +8297,7 @@ var _default = {
};
exports["default"] = _default;
},{"../model/AccountState":13,"./userTable":23,"mithril":3}],16:[function(require,module,exports){
},{"../model/AccountState":12,"./userTable":21,"mithril":3}],15:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@ -8405,7 +8397,7 @@ var _default = {
};
exports["default"] = _default;
},{"../../../lib/dialog":11,"mithril":3}],17:[function(require,module,exports){
},{"../../../lib/dialog":11,"mithril":3}],16:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@ -8450,7 +8442,7 @@ var _default = {
};
exports["default"] = _default;
},{"./actionBase":16,"mithril":3}],18:[function(require,module,exports){
},{"./actionBase":15,"mithril":3}],17:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@ -8575,7 +8567,7 @@ var _default = {
};
exports["default"] = _default;
},{"../../../lib/dialog":11,"./actionBase":16,"mithril":3}],19:[function(require,module,exports){
},{"../../../lib/dialog":11,"./actionBase":15,"mithril":3}],18:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@ -8671,127 +8663,7 @@ var _default = {
};
exports["default"] = _default;
},{"../../../lib/dialog":11,"./actionBase":16,"mithril":3}],20:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _mithril = _interopRequireDefault(require("mithril"));
var _actionBase = _interopRequireDefault(require("./actionBase"));
var _dialog = _interopRequireDefault(require("../../../lib/dialog"));
var _emailToLink = _interopRequireDefault(require("../../../lib/emailToLink"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
/*
* @author: Dario Rekowski
*
* @date: 20.03.20
*
* @brief: getting email verification code in silence
*/
function oninit(vnode) {
vnode.state.loading = true;
vnode.state.results = null;
vnode.state.additionalUserData = [];
vnode.state.serverData = [];
_mithril["default"].request({
method: 'POST',
url: window.location.protocol + '//' + document.domain + '/state-users/ajaxGetUserEmailVerificationCode',
data: vnode.attrs.user,
headers: {
'X-CSRF-Token': csfr_token
}
}).then(function (result) {
vnode.state.loading = false;
if (result.state === 'success') {
vnode.state.copyResult = 'success';
vnode.state.additionalUserData = result.data.userData;
vnode.state.serverData = result.data.server; //console.log("ajax result: %o", result)
} else {//console.log("result error")
}
})["catch"](function (e) {
vnode.state.loading = false;
console.error("ajax error: %s in file: %s in line: %d", e.message, e.fileName, e.lineNumber);
});
}
function getField(vnode, index) {
if (null === vnode.state.results) {
return (0, _mithril["default"])('i.spinner-border.spinner-border-sm');
} else if (index in vnode.state.results) {
return vnode.state.results[index];
} else {
return '0';
}
}
function view(vnode) {
var email = vnode.attrs.user.email;
var first_name = vnode.attrs.user.first_name;
var last_name = vnode.attrs.user.last_name;
var recevier = first_name + ' ' + last_name + ' <' + email + '>';
var userData = vnode.state.additionalUserData;
var serverData = vnode.state.serverData; //console.log('Server data: %o', serverData)
//vnode.state.additionalUserData.verificationCode
var link = serverData['loginServer.path'] + '/checkEmail/' + userData['EmailVerificationCode.Register'];
var body = 'Liebe(r) ' + first_name + ' ' + last_name + ',\n\
\n\
Der Admin hat ein erneutes zusenden deiner Bestätigungsemail angefordert. \n\
Du hast vor einer Weile ein Gradido-Konto mit dieser E-Mail angelegt, aber es noch nicht bestätigt. \n\
\n\
Bitte klicke zur Bestätigung auf den Link: ' + link + '\n\
oder kopiere den obigen Link in Dein Browserfenster.\n\
\n\
Mit freundlichen Grüßen\n\
Dario, Gradido Server Admin\n\
';
if (true === vnode.state.loading) {
return (0, _mithril["default"])('span', [(0, _mithril["default"])('span', [(0, _mithril["default"])('button.btn.btn-secondary.btn-xs', {
title: window.texte.MAILTO_VERIFICATION_EMAIL,
disabled: true
}, (0, _mithril["default"])('i.spinner-border.spinner-border-sm')), window.texte.MAILTO_VERIFICATION_EMAIL])]);
} else {
return (0, _mithril["default"])('span', [(0, _mithril["default"])('span', [(0, _mithril["default"])('a.btn.btn-primary.btn-xs', {
title: window.texte.MAILTO_VERIFICATION_EMAIL,
href: 'mailto:' + (0, _emailToLink["default"])(recevier, 'Gradido: E-Mail Verification', body)
}, (0, _mithril["default"])('i.mdi.mdi-email-outline')), window.texte.MAILTO_VERIFICATION_EMAIL])]);
}
return (0, _mithril["default"])('span', [(0, _mithril["default"])('span', [(0, _mithril["default"])('a.btn.btn-secondary.btn-xs', {
title: window.texte.MAILTO_VERIFICATION_EMAIL,
href: 'mailto:' + (0, _emailToLink["default"])(recevier, 'Gradido: E-Mail Verification', body),
disabled: vnode.state.loading === true
}, vnode.state.loading === true ? (0, _mithril["default"])('i.spinner-border.spinner-border-sm') : (0, _mithril["default"])('i.mdi.mdi-email-outline')), window.texte.MAILTO_VERIFICATION_EMAIL])]);
/*return m('span', [
window.texte.RECEIVE_TRANSACTIONS_COUNT,
getField(vnode, 'receive'),
', ',
window.texte.SENDED_TRANSACTIONS_COUNT,
getField(vnode, 'sended'),
', ',
window.texte.CREATION_TRANSACTIONS_COUNT,
getField(vnode, 'creation')
])*/
}
var _default = {
view: view,
oninit: oninit
};
exports["default"] = _default;
},{"../../../lib/dialog":11,"../../../lib/emailToLink":12,"./actionBase":16,"mithril":3}],21:[function(require,module,exports){
},{"../../../lib/dialog":11,"./actionBase":15,"mithril":3}],19:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@ -8830,12 +8702,12 @@ function oninit(vnode) {
vnode.state.loading = false;
if (result.state === 'success') {
//vnode.state.message = m('div.alert.alert-success', window.texte.DELETE_FROM_COMMUNITY_SUCCESS)
vnode.state.message = (0, _mithril["default"])('div.alert.alert-success', window.texte.DELETE_FROM_COMMUNITY_SUCCESS);
vnode.state.copyResult = 'success';
vnode.state.results = result.counts;
} else {
//console.log("result error")
//vnode.state.message = m('div.alert.alert-danger', window.texte.DELETE_FAILED)
vnode.state.message = (0, _mithril["default"])('div.alert.alert-danger', window.texte.DELETE_FAILED);
vnode.state.copyResult = 'error';
}
})["catch"](function (e) {
@ -8866,7 +8738,7 @@ var _default = {
};
exports["default"] = _default;
},{"../../../lib/dialog":11,"./actionBase":16,"mithril":3}],22:[function(require,module,exports){
},{"../../../lib/dialog":11,"./actionBase":15,"mithril":3}],20:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@ -8896,7 +8768,7 @@ function view(vnode) {
ajaxData: vnode.attrs.user,
alertSuccess: window.texte.VERIFICATION_EMAIL_RESEND_SUCCESS,
alertFailed: window.texte.RESEND_FAILED,
btnColor: 'btn-gradido-orange',
btnColor: 'btn-primary',
btnSymbol: 'mdi-email',
btnTitle: window.texte.VERIFICATION_EMAIL_RESEND,
progessText: window.texte.RESEND_IN_PROGRESS
@ -8908,7 +8780,7 @@ var _default = {
};
exports["default"] = _default;
},{"./actionBase":16,"mithril":3}],23:[function(require,module,exports){
},{"./actionBase":15,"mithril":3}],21:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@ -8932,6 +8804,10 @@ function oninit(vnode) {
}
vnode.state.openedUser = -1;
vnode.state.order = {
field: 'default',
dir: 'DESC'
};
}
function openButtonClick(vnode, index) {
@ -8942,6 +8818,27 @@ function openButtonClick(vnode, index) {
}
}
function changeOrder(vnode, fieldName) {
var field = vnode.state.order.field;
var dir = vnode.state.order.dir;
if (field != fieldName) {
vnode.state.order.field = fieldName;
vnode.state.order.dir = 'DESC';
} else if (field == fieldName) {
var new_dir;
if (dir == 'DESC') {
new_dir = 'ASC';
} else {
new_dir = 'DESC';
}
vnode.state.order.dir = new_dir;
} //console.log("change to %s %s", vnode.state.order.field, vnode.state.order.dir)
}
function updateStateForActiveUser(newState, vnode) {
//console.log('updateStateForActiveUser')
if (-1 !== vnode.state.openedUser) {
@ -8957,12 +8854,154 @@ function deleteActiveUser(vnode) {
}
}
function sortByCreated(var1, var2, dir) {
if (var1.created == var2.created) {
return 0;
}
var var1_date = new Date(var1.created);
var var2_date = new Date(var2.created); // kleiner als null => niedriger index, a kommt zuerst, b kommt als nächstes
// größer als null => höherer index, b kommt zuerst
if (dir == 'DESC') {
// descending
if (var1_date < var2_date) {
return 1;
} else {
return -1;
}
} else {
// ascending
if (var1_date < var2_date) {
return -1;
} else {
return 1;
}
}
}
function sortyByString(var1, var2, dir, field) {
if (var1[field] == var2[field]) {
return 0;
} // kleiner als null => niedriger index, a kommt zuerst, b kommt als nächstes
// größer als null => höherer index, b kommt zuerst
if (dir == 'DESC') {
// descending
if (var1[field] < var2[field]) {
return 1;
} else {
return -1;
}
} else {
// ascending
if (var1[field] < var2[field]) {
return -1;
} else {
return 1;
}
}
}
function sortByBalance(var1, var2, dir) {
if (var1.balance == var2.balance) {
return 0;
}
var var1_balance = parseFloat(var1.balance);
var var2_balance = parseFloat(var2.balance); // kleiner als null => niedriger index, a kommt zuerst, b kommt als nächstes
// größer als null => höherer index, b kommt zuerst
if (dir == 'DESC') {
// descending
if (var1_balance < var2_balance) {
return 1;
} else {
return -1;
}
} else {
// ascending
if (var1_balance < var2_balance) {
return -1;
} else {
return 1;
}
}
} // js sort work in place, so no copy is created
function sort(vnode, user) {
var field = vnode.state.order.field;
var dir = vnode.state.order.dir;
if (field == 'created') {
user.sort(function (var1, var2) {
return sortByCreated(var1, var2, dir);
});
} else if (field == 'name' || field == 'email' || field == 'pubkeyhex') {
user.sort(function (var1, var2) {
return sortyByString(var1, var2, dir, field);
});
} else if (field == 'balance') {
user.sort(function (var1, var2) {
return sortByBalance(var1, var2, dir);
});
}
}
function getArrow(vnode, fieldName) {
// Arrow-up: &#8593;
// Arrow-down: &#8595;
if (vnode.state.order.field == fieldName) {
if (vnode.state.order.dir == 'DESC') {
return _mithril["default"].trust('&#8595&nbsp;');
} else {
return _mithril["default"].trust('&#8593&nbsp;');
}
}
return _mithril["default"].trust('&nbsp;&nbsp;');
}
function view(vnode) {
// js sort work in place, so me made a deep copy first
var user_sorted = JSON.parse(JSON.stringify(vnode.state.orderedUsers));
if (vnode.state.order.field != 'default') {
sort(vnode, user_sorted);
}
return (0, _mithril["default"])('table.table.table-hover.table-sm', [(0, _mithril["default"])('thead', (0, _mithril["default"])('tr.solid-header', [(0, _mithril["default"])('th', {
style: {
'padding-left': '1.5rem'
}
}), (0, _mithril["default"])('th', window.texte.NAME), (0, _mithril["default"])('th', window.texte.EMAIL), (0, _mithril["default"])('th', window.texte.BALANCE), (0, _mithril["default"])('th', _mithril["default"].trust(window.texte.PUBLIC_KEY)), (0, _mithril["default"])('th', window.texte.CREATED)])), (0, _mithril["default"])('tbody', vnode.state.orderedUsers.map(function (value, index) {
}), (0, _mithril["default"])('th', (0, _mithril["default"])('a', {
onclick: function onclick() {
changeOrder(vnode, 'name');
},
className: 'grd_clickable'
}, [getArrow(vnode, 'name'), window.texte.NAME])), (0, _mithril["default"])('th', (0, _mithril["default"])('a', {
onclick: function onclick() {
changeOrder(vnode, 'email');
},
className: 'grd_clickable'
}, [getArrow(vnode, 'email'), window.texte.EMAIL])), (0, _mithril["default"])('th', (0, _mithril["default"])('a', {
onclick: function onclick() {
changeOrder(vnode, 'balance');
},
className: 'grd_clickable'
}, [getArrow(vnode, 'balance'), window.texte.BALANCE])), (0, _mithril["default"])('th', (0, _mithril["default"])('a', {
onclick: function onclick() {
changeOrder(vnode, 'pubkeyhex');
},
className: 'grd_clickable'
}, [getArrow(vnode, 'pubkeyhex'), _mithril["default"].trust(window.texte.PUBLIC_KEY)])), (0, _mithril["default"])('th', (0, _mithril["default"])('a', {
onclick: function onclick() {
changeOrder(vnode, 'created');
},
className: 'grd_clickable'
}, [getArrow(vnode, 'created'), window.texte.CREATED]))])), (0, _mithril["default"])('tbody', user_sorted.map(function (value, index) {
var open = vnode.state.openedUser === index;
return [(0, _mithril["default"])(_rowView["default"], {
user: value,
@ -8989,7 +9028,7 @@ var _default = {
};
exports["default"] = _default;
},{"./rowAction":24,"./rowView":25,"mithril":3}],24:[function(require,module,exports){
},{"./rowAction":22,"./rowView":23,"mithril":3}],22:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@ -9009,8 +9048,6 @@ var _copyCommunityLogin = _interopRequireDefault(require("./actions/copyCommunit
var _verificationResend = _interopRequireDefault(require("./actions/verificationResend"));
var _mailtoVerificationResend = _interopRequireDefault(require("./actions/mailtoVerificationResend"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
var checkTodoAction = new RegExp(/{{([a-z-]*)}}/);
@ -9034,9 +9071,6 @@ function getAction(name) {
case 'verification-resend':
return _verificationResend["default"];
case 'mailto-verification-resend':
return _mailtoVerificationResend["default"];
}
return null;
@ -9077,7 +9111,7 @@ var _default = {
};
exports["default"] = _default;
},{"./actions/copyCommunityLogin":17,"./actions/copyLoginCommunity":18,"./actions/deleteCommunityServer":19,"./actions/mailtoVerificationResend":20,"./actions/userTransactionsOverview":21,"./actions/verificationResend":22,"mithril":3}],25:[function(require,module,exports){
},{"./actions/copyCommunityLogin":16,"./actions/copyLoginCommunity":17,"./actions/deleteCommunityServer":18,"./actions/userTransactionsOverview":19,"./actions/verificationResend":20,"mithril":3}],23:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
@ -9148,4 +9182,4 @@ var _default = {
};
exports["default"] = _default;
},{"../../lib/Gradido":9,"../../lib/Tooltip":10,"../../model/AccountState":13,"mithril":3}]},{},[8]);
},{"../../lib/Gradido":9,"../../lib/Tooltip":10,"../../model/AccountState":12,"mithril":3}]},{},[8]);

File diff suppressed because one or more lines are too long

View File

@ -15,8 +15,10 @@ mit:
```ini
unsercure.allow_cors_all = 1
```
Wird bei allen JSON-Requests zum Header: Access-Control-Allow-Origin:*
hinzugefügt.
Wird bei allen JSON-Requests zum Header hinzugefügt:
- Access-Control-Allow-Origin:*
- Access-Control-Allow-Headers: "Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers"
In diesen Beispielen gehe ich jetzt davon aus, das du das gesamte Gradido Projekt mit Docker gebaut hast und auf dem lokalen Rechner laufen lässt.
@ -81,4 +83,130 @@ data: {"session_id": -127182}
Wenn alles okay ist erhältst du:
```json
{"state":"success"}
```
```
## Update User Data
Update first name, last name, user language and enable/disable user
Language currently supported de and en
User will be disabled if he wants a account delete but has transactions.
Until transactions are saved in real blockchain, we need this data because the public key
is in db only saved in state_users so we wenn delete this entry, validating all transactions not longer possible.
Disabled User cannot login and cannot receive transactions.
In update Object only one of the sets needs to be there.
Update password can only be used if in Login-Server config:
```ini
unsecure.allow_passwort_via_json_request = 1
```
POST http://localhost/login_api/updateUserInfos
```json
{"session_id": -127182, "email": "max.musterman@gmail.de", "update": {
"User.first_name": "Max",
"User.last_name" : "Musterman",
"User.disabled": 0,
"User.language": "de"
"User.password": "1234"
}
}
```
also valid
```json
{"session_id": -127182, "email": "max.musterman@gmail.de", "update": {
"User.last_name" : "Musterman"
}
}
```
It returns if everything is okay
```json
{"state":"success", "valid_values": 4, "errors":[]}
```
- valid_values: should contain count of entrys in update if no error occured (User.password will not be counted)
- errors: contain on error string for every entry in update, which type isn't like expected
- password:
- "new password is the same as old password": no change taking place
- "password changed, coludn"t load private key for re-encryption": password was successfully changed, is at the moment only a warning as long as user_backups are unencrypted, safe to ignore
- "stored pubkey and private key didn't match": error by re-encryption keys, no changes saved
- "User.password isn't valid": if password validation failed, followed by reasons why (additional array in array)
example:
```json
{"errors":[
"User.password isn't valid",[
"Passwort: Dein Passwort ist zu kurz!\n",
"Passwort: Bitte gebe ein g&uuml;ltiges Password ein mit mindestens 8 Zeichen, Gro&szlig;- und Kleinbuchstaben, mindestens einer Zahl und einem Sonderzeichen (@$!%*?&+-_) ein!\n"
]
],
"state":"success",
"valid_values":0
}
```
## Retrieve User Data
Retrieve different user data, in ask only one field is needed, or every possible combination
from the available fields
Normal User can only retrieve data for himself, admins (login-server admin) can retrieve data from every user
Email is also the email address of user from which data are asked
POST http://localhost/login_api/getUserInfos
```json
{"session_id": -127182, "email": "max.musterman@gmail.de", "ask": [
"EmailVerificationCode.Register",
"loginServer.path",
"user.pubkeyhex",
"user.first_name",
"user.last_name",
"user.disabled",
"user.email_checked",
]
}
```
returns if no error occured:
```json
{"state": "success", "userData": {
"EmailVerificationCode.Register": "2718271129122",
"pubkeyhex": "131c7f68dd94b2be4c913400ff7ff4cdc03ac2bda99c2d29edcacb3b065c67e6",
"first_name": "Max",
"last_name": "Musterman",
"disabled": 0,
"email_checked": 1
}, "server": {
"loginServer.path": "http://localhost/account"
},
"errors": []
}
```
Return only the fields which are defined in ask
- EmailVerificationCode.Register: return the email verification code for check email (create one if none exist), work only if logged in user is admin and the email isn't from him
- loginServer.path: the redirect path to login-server, for example for login with login-server html frontend
- user.pubkeyhex: public key of user in hex-format
- user.first_name: first name of user
- user.last_name: last name of user
- user.disabled: User will be disabled if he wants a account delete but has transactions.
Until transactions are saved in real blockchain, we need this data because the public key
is in db only saved in state_users so we wenn delete this entry, validating all transactions not longer possible.
Disabled User cannot login and cannot receive transactions.
- email_checked: If user has clicked on link in verification email (register), can only transfer gradidos if email_checked is 1
- errors: array of strings if error occure
## Login by Email Verification Code
Used for replace http://localhost/account/checkEmail
Can be used to set check_email to 1 (will be done automaticly if called with valid email verification code of type register or registerDirect)
Can be used for password reset (additional step required: call update user info with new password)
GET http://localhost/login_api/loginViaEmailVerificationCode?emailVerificationCode=382738273892983
return
```json
{"state":"success", "email_verification_code_type":"resetPassword","info":[],"session_id":1853761475}
```
- email_verification_code_type
- resetPassword: for password resets, will be deleted immediately, is a only one use code
- registerDirect: code generated by register for check email
- register: code generated by auto-register via elopage for check email
- info can contain additional info strings
- user hasn't password: if user hasn't set a password yet (for example if he was registered via elopage)
- email already activated: if email was already checked
- session_id: session_id for new session

22
frontend/.babelrc Normal file
View File

@ -0,0 +1,22 @@
{
"presets": [
[
"@babel/preset-env"
]
],
"env": {
"test": {
"plugins": ["require-context-hook"],
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "10"
}
}
]
]
}
}
}

2
frontend/.gitignore vendored
View File

@ -18,3 +18,5 @@ package-lock.json
*.ntvs*
*.njsproj
*.sln
*~

34
frontend/jest.config.js Normal file
View File

@ -0,0 +1,34 @@
module.exports = {
verbose: true,
collectCoverageFrom: [
"**/*.{js,vue}",
"!**/node_modules/**",
"!**/?(*.)+(spec|test).js?(x)"
],
moduleFileExtensions: [
'js',
//'jsx',
'json',
'vue',
],
coverageReporters: [
"lcov"
],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
transform: {
'^.+\\.vue$': 'vue-jest',
// '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
"^.+\\.(js|jsx)?$": "babel-jest"
},
//setupFiles: [
// "<rootDir>/test/registerContext.js"
//],
testMatch: [
"**/?(*.)+(spec|test).js?(x)"
],
// snapshotSerializers: ['jest-serializer-vue'],
transformIgnorePatterns: ['<rootDir>/node_modules/']
};

View File

@ -8,10 +8,15 @@
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"dev": "yarn run serve",
"i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'"
"i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'",
"test": "jest"
},
"dependencies": {
"@vue/test-utils": "^1.1.3",
"axios": "^0.21.1",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^26.6.3",
"babel-plugin-require-context-hook": "^1.0.0",
"bootstrap": "4.3.1",
"bootstrap-vue": "^2.5.0",
"chart.js": "^2.9.3",
@ -27,6 +32,7 @@
"flatpickr": "^4.5.7",
"fuse.js": "^3.2.0",
"google-maps": "^3.2.1",
"jest": "^26.6.3",
"nouislider": "^12.1.0",
"particles-bg-vue": "1.2.3",
"perfect-scrollbar": "^1.3.0",
@ -45,6 +51,7 @@
"vue-flatpickr-component": "^8.1.2",
"vue-good-table": "^2.21.3",
"vue-i18n": "^8.22.4",
"vue-jest": "^3.0.7",
"vue-moment": "^4.1.0",
"vue-qrcode": "^0.3.5",
"vue-qrcode-reader": "^2.3.16",

View File

@ -0,0 +1,32 @@
import { mount } from '@vue/test-utils'
import CloseButton from './CloseButton'
const localVue = global.localVue
describe('CloseButton', () => {
let wrapper
let propsData = {
target: "Target",
expanded: false,
}
const Wrapper = () => {
return mount(CloseButton, { localVue, propsData })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('emmits click event', () => {
wrapper.find('.navbar-toggler').trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()
})
})
})

View File

@ -66,7 +66,7 @@ export const store = new Vuex.Store({
state.session_id = session_id
},
user_balance: (state,balance) => {
//console.log('mutation: user_balance')
console.log('mutation: user_balance')
state.user.balance = (balance/10000)
},
user_balance_gdt: (state,balance) => {

File diff suppressed because it is too large Load Diff

View File

@ -71,11 +71,13 @@ Poco::JSON::Object* JsonGetLogin::handle(Poco::Dynamic::Var params)
em->addError(new Error("JsonGetLogin::handle", "generic exception calling userModel->getJson: "));
em->sendErrorsAsEmail();
}
result->set("Transactions.pending", session->getProcessingTransactionCount());
auto executing = observer->getTaskCount(userModel->getEmail(), TASK_OBSERVER_SIGN_TRANSACTION);
if (executing < 0) {
executing = 0;
}
result->set("Transactions.executing", executing);
//printf("pending: %d\n", session->getProcessingTransactionCount());
//std::string user_string = userModel->toString();

View File

@ -7,6 +7,26 @@
#include "../ServerConfig.h"
Poco::UInt64 JsonGetUserInfos::readOrCreateEmailVerificationCode(int user_id, model::table::EmailOptInType type)
{
try {
auto emailVerificationCode = controller::EmailVerificationCode::load(user_id, type);
if (!emailVerificationCode) {
emailVerificationCode = controller::EmailVerificationCode::create(user_id, type);
UniLib::controller::TaskPtr insert = new model::table::ModelInsertTask(emailVerificationCode->getModel(), false);
insert->scheduleTask(insert);
}
return emailVerificationCode->getModel()->getCode();
}
catch (Poco::Exception& ex) {
ErrorList errors;
//printf("exception: %s\n", ex.displayText().data());
errors.addError(new ParamError("JsonGetUserInfos::readOrCreateEmailVerificationCode", "exception: ", ex.displayText()));
errors.sendErrorsAsEmail();
}
return 0;
}
Poco::JSON::Object* JsonGetUserInfos::handle(Poco::Dynamic::Var params)
{
/*
@ -54,11 +74,21 @@ Poco::JSON::Object* JsonGetUserInfos::handle(Poco::Dynamic::Var params)
return customStateError("not found", "session not found");
}
auto session_user = session->getNewUser();
auto session_user_model = session_user->getModel();
bool isAdmin = false;
if (model::table::ROLE_ADMIN == session_user_model->getRole()) {
isAdmin = true;
}
if (session_user_model->getEmail() != email && !isAdmin) {
return customStateError("not same", "email don't belong to logged in user");
}
auto user = controller::User::create();
if (1 != user->load(email)) {
return customStateError("not found", "user not found");
}
auto userModel = user->getModel();
auto user_model = user->getModel();
Poco::JSON::Object* result = new Poco::JSON::Object;
@ -72,42 +102,32 @@ Poco::JSON::Object* JsonGetUserInfos::handle(Poco::Dynamic::Var params)
std::string parameterString;
try {
parameter.convert(parameterString);
if (parameterString == "EmailVerificationCode.Register") {
try {
auto emailVerificationCode = controller::EmailVerificationCode::load(
userModel->getID(), model::table::EMAIL_OPT_IN_REGISTER
);
if (!emailVerificationCode) {
emailVerificationCode = controller::EmailVerificationCode::create(userModel->getID(), model::table::EMAIL_OPT_IN_REGISTER);
UniLib::controller::TaskPtr insert = new model::table::ModelInsertTask(emailVerificationCode->getModel(), false);
insert->scheduleTask(insert);
}
jsonUser.set("EmailVerificationCode.Register", std::to_string(emailVerificationCode->getModel()->getCode()));
}
catch (Poco::Exception& ex) {
printf("exception: %s\n", ex.displayText().data());
if (parameterString == "EmailVerificationCode.Register" && isAdmin && session_user_model->getEmail() != user_model->getEmail()) {
auto code = readOrCreateEmailVerificationCode(user_model->getID(), model::table::EMAIL_OPT_IN_REGISTER_DIRECT);
if (code) {
jsonUser.set("EmailVerificationCode.Register", std::to_string(code));
}
}
else if (parameterString == "loginServer.path") {
jsonServer.set("loginServer.path", ServerConfig::g_serverPath);
}
else if (parameterString == "user.pubkeyhex") {
jsonUser.set("pubkeyhex", userModel->getPublicKeyHex());
jsonUser.set("pubkeyhex", user_model->getPublicKeyHex());
}
else if (parameterString == "user.first_name") {
jsonUser.set("first_name", userModel->getFirstName());
jsonUser.set("first_name", user_model->getFirstName());
}
else if (parameterString == "user.last_name") {
jsonUser.set("last_name", userModel->getLastName());
jsonUser.set("last_name", user_model->getLastName());
}
else if (parameterString == "user.disabled") {
jsonUser.set("disabled", userModel->isDisabled());
jsonUser.set("disabled", user_model->isDisabled());
}
else if (parameterString == "user.email_checked") {
jsonUser.set("email_checked", userModel->isEmailChecked());
jsonUser.set("email_checked", user_model->isEmailChecked());
}
else if (parameterString == "user.identHash") {
auto email = userModel->getEmail();
auto email = user_model->getEmail();
jsonUser.set("identHash", DRMakeStringHash(email.data(), email.size()));
}
}

View File

@ -2,7 +2,7 @@
#define __JSON_INTERFACE_JSON_GET_USER_INFOS_
#include "JsonRequestHandler.h"
#include "../model/table/EmailOptIn.h"
/*!
* @author Dario Rekowski
* @date 2020-03-21
@ -17,7 +17,7 @@ public:
Poco::JSON::Object* handle(Poco::Dynamic::Var params);
protected:
Poco::UInt64 readOrCreateEmailVerificationCode(int user_id, model::table::EmailOptInType type);
};

View File

@ -12,7 +12,8 @@ Poco::JSON::Object* JsonGetUsers::handle(Poco::Dynamic::Var params)
int session_id = 0;
std::string searchString;
std::string accountState = "";
static std::string emptySearchString = "... empty ...";
// if is json object
if (params.type() == typeid(Poco::JSON::Object::Ptr)) {
Poco::JSON::Object::Ptr paramJsonObject = params.extract<Poco::JSON::Object::Ptr>();
@ -23,6 +24,9 @@ Poco::JSON::Object* JsonGetUsers::handle(Poco::Dynamic::Var params)
/// Throws InvalidAccessException if Var is empty.
try {
paramJsonObject->get("search").convert(searchString);
if (paramJsonObject->has("account_state")) {
paramJsonObject->get("account_state").convert(accountState);
}
paramJsonObject->get("session_id").convert(session_id);
}
catch (Poco::Exception& ex) {
@ -67,17 +71,20 @@ Poco::JSON::Object* JsonGetUsers::handle(Poco::Dynamic::Var params)
}
auto user = session->getNewUser();
if (searchString == emptySearchString) {
searchString = "";
}
if (user.isNull()) {
return customStateError("not found", "Session didn't contain user");
}
else if (searchString == "") {
return customStateError("not found", "Search string is empty");
else if (searchString == "" && (accountState == "" || accountState == "all")) {
return customStateError("not found", "Search string is empty and account_state is all or empty");
}
else if (user->getModel()->getRole() != model::table::ROLE_ADMIN) {
return customStateError("wrong role", "User hasn't correct role");
}
auto results = controller::User::search(searchString);
auto results = controller::User::search(searchString, accountState);
if (!results.size()) {
return stateSuccess();
}

View File

@ -0,0 +1,70 @@
#include "JsonLoginViaEmailVerificationCode.h"
#include "JsonUnsecureLogin.h"
#include "../SingletonManager/SessionManager.h"
#include "../SingletonManager/SingletonTaskObserver.h"
#include "../SingletonManager/ErrorManager.h"
#include "../controller/User.h"
#include "../lib/DataTypeConverter.h"
#include "Poco/URI.h"
#include "Poco/JSON/Array.h"
Poco::JSON::Object* JsonLoginViaEmailVerificationCode::handle(Poco::Dynamic::Var params)
{
auto sm = SessionManager::getInstance();
/*
email verification code
*/
// incoming
unsigned long long code = 0;
if (params.isVector()) {
const Poco::URI::QueryParameters queryParams = params.extract<Poco::URI::QueryParameters>();
std::string codeString;
for (auto it = queryParams.begin(); it != queryParams.end(); it++) {
if (it->first == "emailVerificationCode") {
codeString = it->second;
break;
}
}
if (codeString == "") {
return stateError("emailVerificationCode not found");
}
if (DataTypeConverter::NUMBER_PARSE_OKAY != DataTypeConverter::strToInt(codeString, code)) {
return stateError("couldn't parse emailVerificationCode");
}
}
auto session = sm->findByEmailVerificationCode(code);
if (!session) {
session = sm->getNewSession();
if (!session->loadFromEmailVerificationCode(code)) {
return stateError("couldn't login with emailVerificationCode");
}
}
session->setClientIp(mClientIP);
auto result = new Poco::JSON::Object;
result->set("state", "success");
result->set("session_id", session->getHandle());
result->set("email_verification_code_type", model::table::EmailOptIn::typeToString(session->getEmailVerificationType()));
Poco::JSON::Array info;
if (!session->getNewUser()->getModel()->getPasswordHashed()) {
info.add("user hasn't password");
}
auto update_email_verification_result = session->updateEmailVerification(code);
if (1 == update_email_verification_result) {
info.add("email already activated");
}
result->set("info", info);
return result;
}

View File

@ -0,0 +1,18 @@
#ifndef __JSON_INTERFACE_JSON_LOGIN_VIA_EMAIL_VERIFICATION_CODE_
#define __JSON_INTERFACE_JSON_LOGIN_VIA_EMAIL_VERIFICATION_CODE_
#include "JsonRequestHandler.h"
class JsonLoginViaEmailVerificationCode : public JsonRequestHandler
{
public:
JsonLoginViaEmailVerificationCode(Poco::Net::IPAddress ip) : mClientIP(ip) {}
Poco::JSON::Object* handle(Poco::Dynamic::Var params);
protected:
Poco::Net::IPAddress mClientIP;
};
#endif // __JSON_INTERFACE_JSON_LOGIN_VIA_EMAIL_VERIFICATION_CODE_

View File

@ -10,6 +10,7 @@
#include "JsonTransaction.h"
#include "JsonGetRunningUserTasks.h"
#include "JsonGetUsers.h"
#include "JsonLoginViaEmailVerificationCode.h"
#include "JsonAdminEmailVerificationResend.h"
#include "JsonGetUserInfos.h"
#include "JsonUpdateUserInfos.h"
@ -67,6 +68,9 @@ Poco::Net::HTTPRequestHandler* JsonRequestHandlerFactory::createRequestHandler(c
else if (url_first_part == "/unsecureLogin" && (ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_PASSWORD_REQUESTS)) {
return new JsonUnsecureLogin(client_host);
}
else if (url_first_part == "/loginViaEmailVerificationCode") {
return new JsonLoginViaEmailVerificationCode(client_host);
}
else if (url_first_part == "/logout") {
return new JsonLogout(client_host);
}

View File

@ -2,6 +2,7 @@
#include "../SingletonManager/SessionManager.h"
#include "../SingletonManager/LanguageManager.h"
#include "../tasks/AuthenticatedEncryptionCreateKeyTask.h"
Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
{
@ -117,6 +118,34 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
}
}
}
else if ("User.password" == name && (ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_PASSWORD_REQUESTS) == ServerConfig::UNSECURE_PASSWORD_REQUESTS) {
if (!value.isString()) {
jsonErrorsArray.add("User.password isn't string");
}
else {
ErrorList errors;
if (!sm->checkPwdValidation(value.toString(), &errors)) {
jsonErrorsArray.add("User.password isn't valid");
jsonErrorsArray.add(errors.getErrorsArray());
}
else {
auto result_new_password = user->setNewPassword(value.toString());
switch (result_new_password) {
// 0 = new and current passwords are the same
case 0: jsonErrorsArray.add("new password is the same as old password"); break;
// 1 = password changed, private key re-encrypted and saved into db
//case 1: extractet_values++; break;
// 2 = password changed, only hash stored in db, couldn't load private key for re-encryption
case 2: jsonErrorsArray.add("password changed, couldn't load private key for re-encryption"); break;
// -1 = stored pubkey and private key didn't match
case -1: jsonErrorsArray.add("stored pubkey and private key didn't match"); break;
}
}
}
}
}
catch (Poco::Exception& ex) {
jsonErrorsArray.add("update parameter invalid");

View File

@ -92,6 +92,8 @@ int EmailManager::ThreadFunction()
if (mPendingEmails.empty()) return 0;
auto lm = LanguageManager::getInstance();
ErrorList errors;
static const char* function_name = "PrepareEmailTask";
Poco::Net::SecureSMTPClientSession mailClientSession(mEmailAccount.url, mEmailAccount.port);
mailClientSession.login();
@ -100,6 +102,7 @@ int EmailManager::ThreadFunction()
mailClientSession.login(Poco::Net::SMTPClientSession::AUTH_LOGIN, mEmailAccount.username, mEmailAccount.password);
}
catch (Poco::Net::SSLException& ex) {
errors.addError(new ParamError(function_name, "ssl certificate error", ex.displayText()));
printf("[PrepareEmailTask] ssl certificate error: %s\nPlease make sure you have cacert.pem (CA/root certificates) next to binary from https://curl.haxx.se/docs/caextract.html\n", ex.displayText().data());
return -1;
}
@ -151,6 +154,7 @@ int EmailManager::ThreadFunction()
else {
// error drafting email, shouldn't happend
printf("[EmailManager::ThreadFunction] Error drafting email\n");
errors.addError(new Error(function_name, "Error drafting email"));
}
delete email;
email = nullptr;

View File

@ -54,9 +54,15 @@ namespace controller {
Poco::AutoPtr<EmailVerificationCode> EmailVerificationCode::load(int user_id, model::table::EmailOptInType type) {
auto db = new model::table::EmailOptIn();
std::vector<std::string> fields = { "user_id", "email_opt_in_type_id" };
if (db->loadFromDB(fields, user_id, (int)type) == 1) {
return Poco::AutoPtr<EmailVerificationCode>(new EmailVerificationCode(db));
std::vector<int> field_values = { user_id, (int)type };
auto results = db->loadFromDB<int, model::table::EmailOptInTuple>(fields, field_values);
if (results.size() > 0) {
db->release();
return Poco::AutoPtr<EmailVerificationCode>(new EmailVerificationCode(new model::table::EmailOptIn(results[0])));
}
/*if (db->loadFromDB(fields, user_id, (int)type) == 1) {
return Poco::AutoPtr<EmailVerificationCode>(new EmailVerificationCode(db));
}*/
db->release();
return nullptr;
}

View File

@ -46,24 +46,49 @@ namespace controller {
return Poco::AutoPtr<User>(user);
}
std::vector<User*> User::search(const std::string& searchString)
std::vector<User*> User::search(const std::string& searchString, const std::string& accountState /* = "all" */)
{
auto sm = SessionManager::getInstance();
auto cm = ConnectionManager::getInstance();
auto db = new model::table::User();
static const char* functionName = "User::search";
std::string globalSearch = "%" + searchString + "%";
std::vector<model::table::UserTuple> resultFromDB;
// check if search string is email
/*if (sm->isValid(searchString, VALIDATE_EMAIL)) {
resultFromDB = db->loadFromDB <std::string, model::table::UserTuple>("email", globalSearch);
if (accountState == "email not activated") {
std::vector<std::string> fieldNames = { "first_name", "last_name", "email", "email_checked" };
auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
std::vector<model::table::UserTuple> results;
using namespace Poco::Data::Keywords;
Poco::Data::Statement select(session);
// typedef Poco::Tuple<std::string, std::string, std::string, Poco::Nullable<Poco::Data::BLOB>, int> UserTuple;
select << "SELECT id, first_name, last_name, email, pubkey, created, email_checked, disabled FROM " << db->getTableName();
select << " where email_checked = 0 ";
select, into(resultFromDB);
if (searchString != "") {
select << "AND (first_name LIKE ? OR last_name LIKE ? OR email LIKE ?)";
select, useRef(globalSearch), useRef(globalSearch), useRef(globalSearch);
}
try {
select.execute();
}
catch (Poco::Exception& ex) {
ErrorList errors;
errors.addError(new ParamError(functionName, "mysql error ", ex.displayText()));
errors.addError(new ParamError(functionName, "search string", searchString));
errors.addError(new ParamError(functionName, "account state", accountState));
errors.sendErrorsAsEmail();
}
}
else {*/
else {
std::vector<std::string> fieldNames = { "first_name", "last_name", "email" };
std::vector<std::string> fieldValues = { globalSearch, globalSearch, globalSearch };
resultFromDB = db->loadFromDB<std::string, model::table::UserTuple>(fieldNames, fieldValues, model::table::MYSQL_CONDITION_OR);
//}
}
db->release();
db = nullptr;

View File

@ -28,7 +28,7 @@ namespace controller {
static Poco::AutoPtr<User> create();
static Poco::AutoPtr<User> create(const std::string& email, const std::string& first_name, const std::string& last_name, Poco::UInt64 passwordHashed = 0, std::string languageKey = "de");
static std::vector<User*> search(const std::string& searchString);
static std::vector<User*> search(const std::string& searchString, const std::string& accountState = "all");
//! \brief go through whole db and search users with email_checked = false and schedule resend 7 days after email_opt_in created date
//!

View File

@ -90,6 +90,33 @@ namespace model {
return select;
}
Poco::Data::Statement EmailOptIn::_loadMultipleFromDB(Poco::Data::Session session, const std::vector<std::string> fieldNames, MysqlConditionType conditionType/* = MYSQL_CONDITION_AND*/)
{
Poco::Data::Statement select(session);
if (fieldNames.size() <= 1) {
throw Poco::NullValueException("EmailOptIn::_loadFromDB fieldNames empty or contain only one field");
}
select << "SELECT id, user_id, verification_code, email_opt_in_type_id, created, resend_count, updated FROM " << getTableName()
<< " where " << fieldNames[0] << " = ? ";
if (conditionType == MYSQL_CONDITION_AND) {
for (int i = 1; i < fieldNames.size(); i++) {
select << " AND " << fieldNames[i] << " = ? ";
}
}
else if (conditionType == MYSQL_CONDITION_OR) {
for (int i = 1; i < fieldNames.size(); i++) {
select << " OR " << fieldNames[i] << " = ? ";
}
}
else {
addError(new ParamError("EmailOptIn::_loadFromDB", "condition type not implemented", conditionType));
}
return select;
}
Poco::Data::Statement EmailOptIn::_loadFromDB(Poco::Data::Session session, const std::vector<std::string>& fieldNames, MysqlConditionType conditionType/* = MYSQL_CONDITION_AND*/)
{
Poco::Data::Statement select(session);

View File

@ -48,6 +48,7 @@ namespace model {
Poco::Data::Statement _loadFromDB(Poco::Data::Session session, const std::string& fieldName);
Poco::Data::Statement _loadIdFromDB(Poco::Data::Session session);
Poco::Data::Statement _loadMultipleFromDB(Poco::Data::Session session, const std::string& fieldName);
Poco::Data::Statement _loadMultipleFromDB(Poco::Data::Session session, const std::vector<std::string> fieldNames, MysqlConditionType conditionType = MYSQL_CONDITION_AND);
Poco::Data::Statement _loadFromDB(Poco::Data::Session session, const std::vector<std::string>& fieldNames, MysqlConditionType conditionType = MYSQL_CONDITION_AND);
Poco::Data::Statement _insertIntoDB(Poco::Data::Session session);

View File

@ -40,10 +40,19 @@ namespace model {
size_t loadFromDB(const std::string& fieldName, const T& fieldValue);
template<class T>
bool isExistInDB(const std::string& fieldName, const T& fieldValue);
template<class WhereFieldType, class Tuple>
std::vector<Tuple> loadFromDB(const std::string& fieldName, const WhereFieldType& fieldValue, int expectedResults = 0);
template<class T1, class T2>
size_t loadFromDB(const std::vector<std::string>& fieldNames, const T1& field1Value, const T2& field2Value, MysqlConditionType conditionType = MYSQL_CONDITION_AND);
template<class Tuple, class T1, class T2, class T3, class T4>
std::vector<Tuple> loadMultipleFromDB(
const std::vector<std::string>& fieldNames,
const T1& field1Value, const T2& field2Value, const T3& field3Value, const T4& field4Value,
MysqlConditionType conditionType = MYSQL_CONDITION_AND);
template<class WhereFieldType, class Tuple>
std::vector<Tuple> loadFromDB(const std::vector<std::string>& fieldNames, const std::vector<WhereFieldType>& fieldValues, MysqlConditionType conditionType = MYSQL_CONDITION_AND, int expectedResults = 0);
bool insertIntoDB(bool loadId);
@ -211,6 +220,43 @@ namespace model {
return resultCount;
}
template<class Tuple, class T1, class T2, class T3, class T4>
std::vector<Tuple> ModelBase::loadMultipleFromDB(
const std::vector<std::string>& fieldNames,
const T1& field1Value, const T2& field2Value, const T3& field3Value, const T4& field4Value,
MysqlConditionType conditionType/* = MYSQL_CONDITION_AND*/)
{
auto cm = ConnectionManager::getInstance();
std::vector<Tuple> results;
if (fieldNames.size() != 4) {
addError(new Error(getTableName(), "error in loadFromDB with 4 different field values, fieldNames count isn't 4"));
return results;
}
Poco::ScopedLock<Poco::Mutex> _lock(mWorkMutex);
auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
Poco::Data::Statement select = _loadMultipleFromDB(session, fieldNames, conditionType);
select, Poco::Data::Keywords::into(results),
Poco::Data::Keywords::useRef(field1Value), Poco::Data::Keywords::useRef(field2Value),
Poco::Data::Keywords::useRef(field3Value), Poco::Data::Keywords::useRef(field4Value);
size_t resultCount = 0;
try {
resultCount = select.execute();
}
catch (Poco::Exception& ex) {
lock();
addError(new ParamError(getTableName(), "mysql error by selecting with 4 different field types", ex.displayText()));
int count = 0;
for (auto it = fieldNames.begin(); it != fieldNames.end(); it++) {
addError(new ParamError(getTableName(), "field name for select: ", *it));
}
//addError(new ParamError(getTableName(), "field name for select: ", fieldName.data()));
unlock();
}
return resultCount;
}
template<class T>
size_t ModelBase::updateIntoDB(const std::string& fieldName, const T& fieldValue)

View File

@ -1,6 +1,7 @@
//#include "lib/Thread.h"
//#include "UniversumLib.h"
#include "Thread.h"
#include "../lib/ErrorList.h"
namespace UniLib {
namespace lib {
@ -55,6 +56,8 @@ namespace UniLib {
void Thread::run()
{
static const char* function_name = "Thread::run";
ErrorList errors;
//Thread* t = this;
while (true) {
try {
@ -77,6 +80,7 @@ namespace UniLib {
{
//EngineLog.writeToLog("error-code: %d", ret);
printf("[Thread::%s] error running thread functon: %d, exit thread\n", __FUNCTION__, ret);
errors.addError(new ParamError(function_name, "error running thread function, exit thread", mPocoThread->getName()));
return;
}
}
@ -85,13 +89,19 @@ namespace UniLib {
threadUnlock();
//LOG_ERROR("Fehler in Thread, exit", -1);
printf("[Thread::%s] exception: %s\n", __FUNCTION__, e.message().data());
errors.addError(new ParamError(function_name, "poco exception", e.message()));
errors.addError(new ParamError(function_name, "thread name", mPocoThread->getName()));
return;
}
} catch (Poco::TimeoutException& e) {
printf("[Thread::%s] timeout exception\n", __FUNCTION__);
errors.addError(new ParamError(function_name, "poco timeout exception", e.message()));
errors.addError(new ParamError(function_name, "thread name", mPocoThread->getName()));
} catch (Poco::Exception& e) {
printf("[Thread::%s] exception: %s\n", __FUNCTION__, e.message().data());
errors.addError(new ParamError(function_name, "poco exception 2", e.message()));
errors.addError(new ParamError(function_name, "thread name", mPocoThread->getName()));
return;
}
}