Merge branch 'master' into community_decay_inside

This commit is contained in:
Dario Rekowski on RockPI 2021-06-22 07:07:59 +00:00
commit 6756419855
75 changed files with 2030 additions and 536 deletions

View File

@ -50,13 +50,7 @@ jobs:
##########################################################################
- name: login server | Build `release` image
run: |
docker build --target release -t "gradido/login_server:release" -f ./login_server/Dockerfile login_server/
#docker save "gradido/login_server:test" > /tmp/login_server.tar
#- name: Upload Artifact
# uses: actions/upload-artifact@v2
#with:
# name: docker-login-server-test
#path: /tmp/login_server.tar
docker build -t "gradido/login_server:release" -f ./login_server/Dockerfile login_server/
##############################################################################
# JOB: DOCKER BUILD TEST COMMUNITY SERVER ####################################
@ -212,7 +206,7 @@ jobs:
report_name: Coverage Frontend
type: lcov
result_path: ./coverage/lcov.info
min_coverage: 28
min_coverage: 32
token: ${{ github.token }}
##############################################################################
@ -251,7 +245,7 @@ jobs:
##########################################################################
- name: login server | Build `test` image
run: |
docker build --target test -t "gradido/login_server:test" -f ./login_server/Dockerfile login_server/
docker build -t "gradido/login_server:test" -f ./login_server/Dockerfiles/ubuntu/Dockerfile.test login_server/
##########################################################################
# UNIT TESTS BACKEND LOGIN-SERVER #######################################
##########################################################################
@ -268,7 +262,7 @@ jobs:
report_name: Coverage Backend Login
type: lcov
result_path: ./coverage/coverage.info
min_coverage: 13
min_coverage: 25
token: ${{ github.token }}
##############################################################################
@ -300,7 +294,7 @@ jobs:
run: echo "::set-output name=id::$(docker network ls | grep github_network | awk '{ print $1 }')"
id: network
- name: Start Login-Server
run: docker run --network ${{ steps.network.outputs.id }} --name=login-server -d gradido/login_server:default
run: docker run --network ${{ steps.network.outputs.id }} --name=login-server -d gradido/login_server:with-config
- name: get login-server container id
run: echo "::set-output name=id::$(docker container ls | grep login_server | awk '{ print $1 }')"
id: login_server_container
@ -341,7 +335,7 @@ jobs:
report_name: Coverage Backend Community
type: phpunit
result_path: ./coverage/coverage.info
min_coverage: 10
min_coverage: 14
token: ${{ github.token }}
#test:

View File

@ -4,8 +4,52 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [1.1.0](https://github.com/gradido/gradido/compare/1.0.2...1.1.0)
- fix: API Call for Reset Password [`#571`](https://github.com/gradido/gradido/pull/571)
- Login reset password [`#570`](https://github.com/gradido/gradido/pull/570)
- Hide unuse components in profil [`#566`](https://github.com/gradido/gradido/pull/566)
- fix: Thousend Dividers on GDD Send Amount Field [`#567`](https://github.com/gradido/gradido/pull/567)
- reorganisiere Dockerfiles [`#550`](https://github.com/gradido/gradido/pull/550)
- hot fixes shown by test [`#551`](https://github.com/gradido/gradido/pull/551)
- fix to big text ths [`#565`](https://github.com/gradido/gradido/pull/565)
- feat: Trim Email on Blur [`#556`](https://github.com/gradido/gradido/pull/556)
- Mobile UI send gdd form [`#562`](https://github.com/gradido/gradido/pull/562)
- fix: Change Password Form [`#561`](https://github.com/gradido/gradido/pull/561)
- change userdata button disable rules [`#548`](https://github.com/gradido/gradido/pull/548)
- Change password require old password [`#519`](https://github.com/gradido/gradido/pull/519)
- feat: Test Forget Password [`#546`](https://github.com/gradido/gradido/pull/546)
- feat: Validate Change Username [`#545`](https://github.com/gradido/gradido/pull/545)
- Remove base input example login vue [`#524`](https://github.com/gradido/gradido/pull/524)
- Feature: Change Username [`#490`](https://github.com/gradido/gradido/pull/490)
- feat: Toaster to Display Messages [`#512`](https://github.com/gradido/gradido/pull/512)
- fix: Validation of GDD Send Amount Field [`#525`](https://github.com/gradido/gradido/pull/525)
- Community coverage [`#496`](https://github.com/gradido/gradido/pull/496)
- compare with last transaction sended [`#523`](https://github.com/gradido/gradido/pull/523)
- remove check decays for being at least 100 GDD cent [`#526`](https://github.com/gradido/gradido/pull/526)
- fix: Remove Target Date in Send Coins Request [`#518`](https://github.com/gradido/gradido/pull/518)
- Feature profile page bugs [`#511`](https://github.com/gradido/gradido/pull/511)
- fix: GDD Send Amount Input Field [`#491`](https://github.com/gradido/gradido/pull/491)
- change transfer confirmation email [`#485`](https://github.com/gradido/gradido/pull/485)
- Login wait on passwords with missing chars [`#487`](https://github.com/gradido/gradido/pull/487)
- fix problem with create User [`#486`](https://github.com/gradido/gradido/pull/486)
- Feature: Profile Page + Update API [`#474`](https://github.com/gradido/gradido/pull/474)
- add new API Call checkUsername [`#482`](https://github.com/gradido/gradido/pull/482)
- feat: Pagination Buttons for Transaction List [`#473`](https://github.com/gradido/gradido/pull/473)
- Login-Server & Community-Server Coverage [`#472`](https://github.com/gradido/gradido/pull/472)
- login without hedera [`#478`](https://github.com/gradido/gradido/pull/478)
- fix: Show Correct Version Number in Footer [`#475`](https://github.com/gradido/gradido/pull/475)
- refactor: Remove Element-UI [`#476`](https://github.com/gradido/gradido/pull/476)
- remove components Charts, Notification, SearchUser, ButtonCheckbox, Button RadioGroup, Breadcrumb [`159bff7`](https://github.com/gradido/gradido/commit/159bff71df20a5c48f93389b2f990f7fe54e53b9)
- fix bug, update dockerfiles to use dependencies without grpc [`dedcebd`](https://github.com/gradido/gradido/commit/dedcebdb95ee0f3dfd2ad62074d4181af38476a2)
- add warning to able to forward warnings from community server to client [`2fc3fe9`](https://github.com/gradido/gradido/commit/2fc3fe94a09bae199bf2f34f9df90e8fc3879c2b)
#### [1.0.2](https://github.com/gradido/gradido/compare/1.0.1...1.0.2)
> 27 May 2021
- feat: Test Transaction List [`#470`](https://github.com/gradido/gradido/pull/470)
- fixed problem with finding cpsp parse binary under windows with conan [`#471`](https://github.com/gradido/gradido/pull/471)
- fix: GDD Amount is Always Displayed with Two Digits [`#468`](https://github.com/gradido/gradido/pull/468)
- fix: Date Time Formats [`#469`](https://github.com/gradido/gradido/pull/469)
- Community ipv6 localhost [`#466`](https://github.com/gradido/gradido/pull/466)

View File

@ -289,7 +289,7 @@ class AppController extends Controller
}
} else {
if(!$redirect) {
return ['state' => 'not found', 'msg' => 'invalid session'];
return ['state' => 'not found', 'msg' => 'invalid session', 'details' => $json];
}
if ($json['state'] === 'not found') {
$this->Flash->error(__('invalid session'));

View File

@ -192,7 +192,7 @@ class TransactionsTable extends Table
$calculated_decay = $stateBalancesTable->calculateDecay($prev->balance, $prev->balance_date, $current->balance_date, true);
$balance = floatval($prev->balance - $calculated_decay['balance']);
if($balance)
if($balance > 100)
{
$final_transactions['decay'] = [
'balance' => $balance,

View File

@ -109,9 +109,11 @@ class AppRequestControllerTest extends TestCase
'public_hex' => '8190bda585ee5f1d9fbf7d06e81e69ec18e13376104cff54b7457eb7d3ef710d'
]
]);
$this->getAndParse('/api/get-balance/' . 1211,
['state' => 'not found', 'msg' => 'invalid session']
['state' => 'not found', 'msg' => 'invalid session',
'details' => ['msg' => 'session not found', 'state' => 'not found']
]
);
}
@ -128,7 +130,9 @@ class AppRequestControllerTest extends TestCase
]);
$this->getAndParse('/api/get-balance/' ,
['state' => 'not found', 'msg' => 'invalid session']
['state' => 'not found', 'msg' => 'invalid session',
'details' => ['msg' => 'session not found', 'state' => 'not found']
]
);
}

View File

@ -26,8 +26,7 @@ services:
#########################################################
login-server:
build:
target: login_server_alpine_debug
dockerfile: Dockerfile.alpine-debug
dockerfile: Dockerfiles/alpine/Dockerfile.debug
security_opt:
- seccomp:unconfined
cap_add:

View File

@ -26,7 +26,7 @@ services:
login-server:
build:
context: ./login_server/
target: test
dockerfile: Dockerfiles/ubuntu/Dockerfile.test
security_opt:
- seccomp:unconfined
cap_add:

View File

@ -55,7 +55,6 @@ services:
login-server:
build:
context: ./login_server/
target: login_server
depends_on:
- mariadb
networks:

View File

@ -230,7 +230,8 @@ with:
"User.description" : "Tischler",
"User.disabled": 0,
"User.language": "de",
"User.password": "1234"
"User.password": "1234",
"User.password_old": "4321"
}
}
```
@ -240,6 +241,7 @@ Notes:
- User will be disabled if he wants his account deleted, 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 if we delete this entry, validating all transactions is no longer possible.
- Disabled Users can neither login nor receive transactions.
- It is not required to provide all fields of `update`, it can be a subset depending on what you intend to change.
- `User.password`: to change user password, needed current passwort in `User.password_old` (only if user was logged in with his password, not by reset password email code)
### Response
In case of success:
@ -252,7 +254,7 @@ In case of success:
}
```
- `valid_values`: should contain count of entries in update if no error occurred (User.password will not be counted)
- `valid_values`: should contain count of entries in update if no error occurred (User.password will now be counted also)
- `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
@ -503,6 +505,29 @@ The link can be modified in the Login-Server config:
For the docker build, you can find the config here: `configs/login_server/grd_login.properties`
### Request
`POST http://localhost/login_api/resetPassword`
with:
```json
{
"session_id": 12452361,
"password":"hasu/282?sjS"
}
```
### Response
In case of success returns:
```json
{
"state":"success"
}
```
## Check Running Transactions / password encryption
Check if transactions on login-server for user are processed

View File

@ -1,6 +1,6 @@
{
"name": "bootstrap-vue-gradido-wallet",
"version": "1.0.2",
"version": "1.1.0",
"private": true,
"scripts": {
"start": "node run/server.js",

View File

@ -15,7 +15,7 @@ const apiGet = async (url) => {
if (result.status !== 200) {
throw new Error('HTTP Status Error ' + result.status)
}
if (result.data.state !== 'success') {
if (!['success', 'warning'].includes(result.data.state)) {
throw new Error(result.data.msg)
}
return { success: true, result }
@ -102,19 +102,17 @@ const loginAPI = {
const payload = {
session_id: sessionId,
email,
update: {
'User.password': password,
},
password,
}
return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload)
return apiPost(CONFIG.LOGIN_API_URL + 'resetPassword', payload)
},
changePasswordProfile: async (sessionId, email, password, passwordNew) => {
const payload = {
session_id: sessionId,
email,
update: {
'User.password': password,
'User.passwordNew': passwordNew,
'User.password_old': password,
'User.password': passwordNew,
},
}
return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload)
@ -139,6 +137,9 @@ const loginAPI = {
}
return apiPost(CONFIG.LOGIN_API_URL + 'updateUserInfos', payload)
},
checkUsername: async (username, groupId = 1) => {
return apiGet(CONFIG.LOGIN_API_URL + `checkUsername?username=${username}&group_id=${groupId}`)
},
}
export default loginAPI

View File

@ -85,7 +85,7 @@ export default {
default: 'img/brand/green.png',
description: 'Gradido Sidebar app logo',
},
value: { type: Array },
value: { type: String },
autoClose: {
type: Boolean,
default: true,

View File

@ -38,6 +38,12 @@ const numberFormats = {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
},
ungroupedDecimal: {
style: 'decimal',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
useGrouping: false,
},
},
de: {
decimal: {
@ -45,6 +51,12 @@ const numberFormats = {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
},
ungroupedDecimal: {
style: 'decimal',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
useGrouping: false,
},
},
}

View File

@ -39,6 +39,7 @@
"password_new":"neues Passwort",
"password_new_repeat":"neues Passwort wiederholen",
"change": "ändern",
"change-password": "Passwort ändern",
"amount":"Betrag",
"memo":"Nachricht für den Empfänger",
"message":"Nachricht",
@ -57,12 +58,15 @@
"send_transaction_error":"Leider konnte die Transaktion nicht ausgeführt werden!",
"validation": {
"gddSendAmount": "Das Feld {_field_} muss eine Zahl zwischen {min} und {max} mit höchstens zwei Nachkommastellen sein",
"is-not": "Du kannst dir selbst keine Gradidos überweisen"
"is-not": "Du kannst dir selbst keine Gradidos überweisen",
"usernmae-unique": "Der Username ist bereits vergeben.",
"usernmae-regex": "Der Username muss mit einem Buchstaben beginnen auf den mindestens zwei alfanumerische Zeichen folgen müssen."
},
"change_username_info": "Das ändern des Usernamens bedarf mehrerer Schritte."
"change_username_info": "Einmal gespeichert, kann der Username ncht mehr geändert werden!"
},
"error": {
"error":"Fehler"
"error":"Fehler",
"change-password": "Fehler beim Ändern des Passworts"
},
"transaction":{
"show_all":"Alle <strong>{count}</strong> Transaktionen ansehen",

View File

@ -39,6 +39,7 @@
"password_new":"New password",
"password_new_repeat":"Repeat new password",
"change": "change",
"change-password": "Change password",
"amount":"Amount",
"memo":"Message for the recipient",
"message":"Message",
@ -57,12 +58,15 @@
"send_transaction_error":"Unfortunately, the transaction could not be executed!",
"validation": {
"gddSendAmount": "The {_field_} field must be a number between {min} and {max} with at most two digits",
"is-not": "You cannot send Gradidos to yourself"
"is-not": "You cannot send Gradidos to yourself",
"usernmae-unique": "The username is already taken.",
"usernmae-regex": "The username must start with a letter, followed by at least two alphanumeric characters."
},
"change_username_info": "Changing the username requires several steps."
"change_username_info": "Once saved, the username cannot be changed again!"
},
"error": {
"error":"Error"
"error":"Error",
"change-password": "Error while changing password"
},
"transaction":{
"show_all":"View all <strong>{count}</strong> transactions.",

View File

@ -9,6 +9,8 @@ import { required, email, min, max, is_not } from 'vee-validate/dist/rules'
// store
import { store } from './store/store'
import loginAPI from './apis/loginAPI'
// router setup
import router from './routes/router'
@ -58,12 +60,27 @@ extend('gddSendAmount', {
},
params: ['min', 'max'],
message: (_, values) => {
values.min = i18n.n(values.min)
values.max = i18n.n(values.max)
values.min = i18n.n(values.min, 'ungroupedDecimal')
values.max = i18n.n(values.max, 'ungroupedDecimal')
return i18n.t('form.validation.gddSendAmount', values)
},
})
extend('gddUsernameUnique', {
async validate(value) {
const result = await loginAPI.checkUsername(value)
return result.result.data.state === 'success'
},
message: (_, values) => i18n.t('form.validation.usernmae-unique', values),
})
extend('gddUsernameRgex', {
validate(value) {
return !!value.match(/^[a-zA-Z][-_a-zA-Z0-9]{2,}$/)
},
message: (_, values) => i18n.t('form.validation.usernmae-regex', values),
})
// eslint-disable-next-line camelcase
extend('is_not', {
// eslint-disable-next-line camelcase

View File

@ -1,16 +1,12 @@
import { mount, RouterLinkStub } from '@vue/test-utils'
import VueRouter from 'vue-router'
import Vuex from 'vuex'
import flushPromises from 'flush-promises'
import routes from '../../routes/routes'
import DashboardLayoutGdd from './DashboardLayout_gdd'
jest.useFakeTimers()
const localVue = global.localVue
const router = new VueRouter({ routes })
const transitionStub = () => ({
render(h) {
return this.$options._renderChildren
@ -26,6 +22,14 @@ describe('DashboardLayoutGdd', () => {
},
$t: jest.fn((t) => t),
$n: jest.fn(),
$route: {
meta: {
hideFooter: false,
},
},
$router: {
push: jest.fn(),
},
}
const state = {
@ -40,6 +44,7 @@ describe('DashboardLayoutGdd', () => {
const stubs = {
RouterLink: RouterLinkStub,
FadeTransition: transitionStub(),
RouterView: transitionStub(),
}
const store = new Vuex.Store({
@ -50,7 +55,7 @@ describe('DashboardLayoutGdd', () => {
})
const Wrapper = () => {
return mount(DashboardLayoutGdd, { localVue, mocks, router, store, stubs })
return mount(DashboardLayoutGdd, { localVue, mocks, store, stubs })
}
describe('mount', () => {

View File

@ -144,9 +144,6 @@ export default {
mounted() {
this.initScrollbar()
},
created() {
this.updateTransactions({ firstPage: 1, items: 5 })
},
}
</script>
<style lang="scss">

View File

@ -45,7 +45,7 @@ describe('GddSend', () => {
})
it('has a label form.receiver', () => {
expect(wrapper.findAll('div.text-left').at(0).text()).toBe('form.receiver')
expect(wrapper.find('label.input-1').text()).toBe('form.receiver')
})
it('has a placeholder "E-Mail"', () => {
@ -61,11 +61,11 @@ describe('GddSend', () => {
})
it('has an GDD text icon', () => {
expect(wrapper.find('#input-group-2').find('div.h3').text()).toBe('GDD')
expect(wrapper.find('#input-group-2').find('div.m-1').text()).toBe('GDD')
})
it('has a label form.amount', () => {
expect(wrapper.findAll('div.text-left').at(1).text()).toBe('form.amount')
expect(wrapper.find('label.input-2').text()).toBe('form.amount')
})
it('has a placeholder "0.01"', () => {
@ -87,7 +87,7 @@ describe('GddSend', () => {
})
it('has a label form.memo', () => {
expect(wrapper.findAll('div.text-left').at(2).text()).toBe('form.memo')
expect(wrapper.find('label.input-3').text()).toBe('form.memo')
})
})

View File

@ -1,7 +1,7 @@
<template>
<b-row class="transaction-form">
<b-col xl="12" md="12">
<b-card class="p-0 p-md-3" style="background-color: #ebebeba3 !important">
<b-col xl="12" md="12" class="p-0">
<b-card class="p-0 m-0" style="background-color: #ebebeba3 !important">
<!-- -<QrCode @set-transaction="setTransaction"></QrCode> -->
<validation-observer v-slot="{ handleSubmit }" ref="formValidator">
<b-form role="form" @submit.prevent="handleSubmit(onSubmit)" @reset="onReset">
@ -10,6 +10,7 @@
</div>
<br />
-->
<div>
<validation-provider
name="Email"
@ -21,33 +22,37 @@
v-slot="{ errors }"
>
<b-row>
<b-col class="text-left p-3 p-sm-1">{{ $t('form.receiver') }}</b-col>
<b-col v-if="errors" class="text-right p-3 p-sm-1">
<span v-for="error in errors" :key="error" class="errors">{{ error }}</span>
</b-col>
</b-row>
<label class="input-1" for="input-1">{{ $t('form.receiver') }}</label>
<b-input-group
id="input-group-1"
label="Empfänger:"
label-for="input-1"
class="border border-default"
description="We'll never share your email with anyone else."
size="lg"
class="mb-3"
>
<b-input-group-prepend class="p-3 d-none d-md-block">
<b-icon icon="envelope" class="display-3"></b-icon>
<b-input-group-prepend class="d-none d-md-block">
<b-icon icon="envelope" class="display-4 m-3"></b-icon>
</b-input-group-prepend>
<b-form-input
id="input-1"
v-model="form.email"
v-focus="emailFocused"
@focus="emailFocused = true"
@blur="normalizeEmail()"
type="email"
placeholder="E-Mail"
style="font-size: xx-large; padding-left: 20px"
style="font-size: large"
class="pl-3"
></b-form-input>
</b-input-group>
</validation-provider>
</div>
<br />
<div>
<validation-provider
:name="$t('form.amount')"
@ -58,33 +63,32 @@
v-slot="{ errors, valid }"
>
<b-row>
<b-col class="text-left p-3 p-sm-1">{{ $t('form.amount') }}</b-col>
<b-col v-if="errors" class="text-right p-3 p-sm-1">
<b-col v-if="errors" class="col-12 text-right p-3 p-sm-1">
<span v-for="error in errors" class="errors" :key="error">{{ error }}</span>
</b-col>
</b-row>
<b-input-group
id="input-group-2"
label="Betrag:"
label-for="input-2"
size="lg"
class="mb-3"
>
<label class="input-2" for="input-2">{{ $t('form.amount') }}</label>
<b-input-group id="input-group-2" class="border border-default" size="lg">
<b-input-group-prepend class="p-2 d-none d-md-block">
<div class="h3 pt-3 pr-3">GDD</div>
<div class="m-1 mt-2">GDD</div>
</b-input-group-prepend>
<b-form-input
id="input-2"
v-model="form.amount"
type="text"
v-focus="amountFocused"
@focus="amountFocused = !amountFocused"
@focus="amountFocused = true"
@blur="normalizeAmount(valid)"
:placeholder="$n(0.01)"
style="font-size: xx-large; padding-left: 20px"
style="font-size: large"
class="pl-3"
></b-form-input>
</b-input-group>
</validation-provider>
</div>
<div class="mt-4">
<validation-provider
:rules="{
required: true,
@ -95,20 +99,20 @@
v-slot="{ errors }"
>
<b-row>
<b-col class="text-left p-3 p-sm-1">{{ $t('form.memo') }}</b-col>
<b-col v-if="errors" class="text-right p-3 p-sm-1">
<span v-for="error in errors" class="errors" :key="error">{{ error }}</span>
</b-col>
</b-row>
<b-input-group id="input-group-3">
<b-input-group-prepend class="p-3 d-none d-md-block">
<b-icon icon="chat-right-text" class="display-3"></b-icon>
<label class="input-3" for="input-3">{{ $t('form.memo') }}</label>
<b-input-group id="input-group-3" class="border border-default">
<b-input-group-prepend class="d-none d-md-block">
<b-icon icon="chat-right-text" class="display-4 m-3 mt-4"></b-icon>
</b-input-group-prepend>
<b-form-textarea
id="input-3"
rows="3"
v-model="form.memo"
class="pl-3"
style="font-size: x-large"
></b-form-textarea>
</b-input-group>
</validation-provider>
@ -153,6 +157,7 @@ export default {
data() {
return {
amountFocused: false,
emailFocused: false,
form: {
email: '',
amount: '',
@ -181,10 +186,14 @@ export default {
this.form.amount = data.amount
},
normalizeAmount(isValid) {
this.amountFocused = !this.amountFocused
this.amountFocused = false
if (!isValid) return
this.form.amountValue = Number(this.form.amount.replace(',', '.'))
this.form.amount = this.$n(this.form.amountValue, 'decimal')
this.form.amount = this.$n(this.form.amountValue, 'ungroupedDecimal')
},
normalizeEmail() {
this.emailFocused = false
this.form.email = this.form.email.trim()
},
},
}

View File

@ -1,13 +1,13 @@
<template>
<b-row v-if="!error">
<b-col>
<b-card class="p-0 p-md-3" style="background-color: #ebebeba3 !important">
<div class="display-2 p-4">
<b-card class="p-0" style="background-color: #ebebeba3 !important">
<div class="p-4">
{{ $t('form.thx') }}
<hr />
{{ $t('form.send_transaction_success') }}
</div>
<p class="text-center">
<p class="text-center mt-3">
<b-button variant="success" @click="$emit('on-reset')">{{ $t('form.close') }}</b-button>
</p>
</b-card>
@ -15,13 +15,13 @@
</b-row>
<b-row v-else>
<b-col>
<b-card class="p-0 p-md-3" style="background-color: #ebebeba3 !important">
<div class="display-2 p-4">
<b-card class="p-0" style="background-color: #ebebeba3 !important">
<div class="p-4" style="font-size: 1.5rem">
{{ $t('form.sorry') }}
<hr />
{{ $t('form.send_transaction_error') }}
</div>
<p class="text-center">
<p class="text-center mt-3">
<b-button variant="success" @click="$emit('on-reset')">{{ $t('form.close') }}</b-button>
</p>
</b-card>

View File

@ -1,13 +1,13 @@
<template>
<div>
<b-row>
<b-col>
<b-card style="background-color: #ebebeba3 !important">
<b-col class="p-0">
<b-card class="p-0" style="background-color: #ebebeba3 !important">
{{ pending ? '—' : $n(balance, 'decimal') }} GDD
</b-card>
</b-col>
<b-col>
<b-card class="lg-h2 text-right" style="background-color: #ebebeba3 !important">
<b-col class="pr-0">
<b-card class="p-0 text-right" style="background-color: #ebebeba3 !important">
{{ pending ? '—' : $n(GdtBalance, 'decimal') }} GDT
</b-card>
</b-col>

View File

@ -0,0 +1,114 @@
import { mount, RouterLinkStub } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import loginAPI from '../../apis/loginAPI.js'
import ForgotPassword from './ForgotPassword'
jest.mock('../../apis/loginAPI.js')
const mockAPIcall = jest.fn()
loginAPI.sendEmail = mockAPIcall
const localVue = global.localVue
const mockRouterPush = jest.fn()
describe('ForgotPassword', () => {
let wrapper
const mocks = {
$t: jest.fn((t) => t),
$router: {
push: mockRouterPush,
},
}
const stubs = {
RouterLink: RouterLinkStub,
}
const Wrapper = () => {
return mount(ForgotPassword, { localVue, mocks, stubs })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the component', () => {
expect(wrapper.find('div.forgot-password').exists()).toBeTruthy()
})
it('has a title', () => {
expect(wrapper.find('h1').text()).toEqual('site.password.title')
})
it('has a subtitle', () => {
expect(wrapper.find('p.text-lead').text()).toEqual('site.password.subtitle')
})
describe('back button', () => {
it('has a "back" button', () => {
expect(wrapper.findComponent(RouterLinkStub).text()).toEqual('back')
})
it('links to login', () => {
expect(wrapper.findComponent(RouterLinkStub).props().to).toEqual('/Login')
})
})
describe('input form', () => {
let form
beforeEach(() => {
form = wrapper.find('form')
})
it('has the label "Email"', () => {
expect(form.find('label').text()).toEqual('Email')
})
it('has the placeholder "Email"', () => {
expect(form.find('input').attributes('placeholder')).toEqual('Email')
})
it('has a submit button', () => {
expect(form.find('button[type="submit"]').exists()).toBeTruthy()
})
describe('invalid Email', () => {
beforeEach(async () => {
await form.find('input').setValue('no-email')
await flushPromises()
})
it('displays an error', () => {
expect(form.find('#reset-pwd--live-feedback').text()).toEqual(
'The Email field must be a valid email',
)
})
it('does not call the API', () => {
expect(mockAPIcall).not.toHaveBeenCalled()
})
})
describe('valid Email', () => {
beforeEach(async () => {
await form.find('input').setValue('user@example.org')
form.trigger('submit')
await wrapper.vm.$nextTick()
await flushPromises()
})
it('calls the API', () => {
expect(mockAPIcall).toHaveBeenCalledWith('user@example.org')
})
it('pushes "/thx/password" to the route', () => {
expect(mockRouterPush).toHaveBeenCalledWith('/thx/password')
})
})
})
})
})

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="forgot-password">
<div class="header p-4">
<b-container class="container">
<div class="header-body text-center mb-7">
@ -70,7 +70,6 @@ export default {
},
}
},
created() {},
methods: {
getValidationState({ dirty, validated, valid = null }) {
return dirty || validated ? valid : null

View File

@ -1,30 +1,74 @@
import { mount } from '@vue/test-utils'
import VueRouter from 'vue-router'
import routes from '../../routes/routes'
import { mount, RouterLinkStub } from '@vue/test-utils'
import loginAPI from '../../apis/loginAPI'
import ResetPassword from './ResetPassword'
import flushPromises from 'flush-promises'
jest.mock('../../apis/loginAPI')
const localVue = global.localVue
const router = new VueRouter({ routes })
const successResponseObject = {
success: true,
result: {
data: {
session_id: 1,
user: {
email: 'user@example.org',
},
},
},
}
const emailVerificationMock = jest.fn()
const changePasswordMock = jest.fn()
const toasterMock = jest.fn()
const routerPushMock = jest.fn()
emailVerificationMock
.mockReturnValueOnce({ success: false, result: { message: 'error' } })
.mockReturnValueOnce({ success: false, result: { message: 'error' } })
.mockReturnValueOnce({ success: false, result: { message: 'error' } })
.mockReturnValue(successResponseObject)
changePasswordMock
.mockReturnValueOnce({ success: false, result: { message: 'error' } })
.mockReturnValue({ success: true })
loginAPI.loginViaEmailVerificationCode = emailVerificationMock
loginAPI.changePassword = changePasswordMock
describe('ResetPassword', () => {
let wrapper
const emailVerification = jest.fn()
const mocks = {
$i18n: {
locale: 'en',
},
$t: jest.fn((t) => t),
loginAPI: {
loginViaEmailVerificationCode: emailVerification,
$route: {
params: {
optin: '123',
},
},
$toast: {
error: toasterMock,
},
$router: {
push: routerPushMock,
},
$loading: {
show: jest.fn(() => {
return { hide: jest.fn() }
}),
},
}
const stubs = {
RouterLink: RouterLinkStub,
}
const Wrapper = () => {
return mount(ResetPassword, { localVue, mocks, router })
return mount(ResetPassword, { localVue, mocks, stubs })
}
describe('mount', () => {
@ -32,84 +76,94 @@ describe('ResetPassword', () => {
wrapper = Wrapper()
})
/*
it('calls the email verification when created', () => {
const spy = jest.spyOn(wrapper.vm, 'authenticate')
expect(spy).toBeCalled()
expect(emailVerificationMock).toHaveBeenCalledWith('123')
})
*/
it('does not render the Reset Password form when not authenticated', async () => {
it('does not render the Reset Password form when not authenticated', () => {
expect(wrapper.find('div.resetpwd-form').exists()).toBeFalsy()
})
it('renders the Reset Password form', async () => {
it('toasts an error when no valid optin is given', () => {
expect(toasterMock).toHaveBeenCalledWith('error')
})
it('renders the Reset Password form when authenticated', async () => {
wrapper.setData({ authenticated: true })
await wrapper.vm.$nextTick()
expect(wrapper.find('div.resetpwd-form').exists()).toBeTruthy()
})
// describe('Register header', () => {
// it('has a welcome message', () => {
// expect(wrapper.find('div.header').text()).toBe('site.signup.title site.signup.subtitle')
// })
// })
describe('Register header', () => {
it('has a welcome message', () => {
expect(wrapper.find('div.header').text()).toBe('reset-password.title reset-password.text')
})
})
// describe('links', () => {
// it('has a link "Back"', () => {
// expect(wrapper.findAllComponents(RouterLinkStub).at(0).text()).toEqual('back')
// })
/* there is no back button, why?
describe('links', () => {
it('has a link "Back"', () => {
expect(wrapper.findAllComponents(RouterLinkStub).at(0).text()).toEqual('back')
})
// it('links to /login when clicking "Back"', () => {
// expect(wrapper.findAllComponents(RouterLinkStub).at(0).props().to).toBe('/login')
// })
// })
it('links to /login when clicking "Back"', () => {
expect(wrapper.findAllComponents(RouterLinkStub).at(0).props().to).toBe('/login')
})
})
*/
// describe('Register form', () => {
// it('has a register form', () => {
// expect(wrapper.find('form').exists()).toBeTruthy()
// })
describe('reset password form', () => {
it('has a register form', () => {
expect(wrapper.find('form').exists()).toBeTruthy()
})
// it('has 3 text input fields', () => {
// expect(wrapper.findAll('input[type="text"]').length).toBe(3)
// })
it('has 2 password input fields', () => {
expect(wrapper.findAll('input[type="password"]').length).toBe(2)
})
// it('has 2 password input fields', () => {
// expect(wrapper.findAll('input[type="password"]').length).toBe(2)
// })
it('has no submit button when not completely filled', () => {
expect(wrapper.find('button[type="submit"]').exists()).toBe(false)
})
// it('has 1 checkbox input fields', () => {
// expect(wrapper.findAll('input[type="checkbox"]').length).toBe(1)
// })
it('toggles the first input field to text when eye icon is clicked', async () => {
wrapper.findAll('button').at(0).trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.findAll('input').at(0).attributes('type')).toBe('text')
})
// it('has no submit button when not completely filled', () => {
// expect(wrapper.find('button[type="submit"]').exists()).toBe(false)
// })
it('toggles the second input field to text when eye icon is clicked', async () => {
wrapper.findAll('button').at(1).trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.findAll('input').at(1).attributes('type')).toBe('text')
})
})
// it('shows a warning when no valid Email is entered', async () => {
// wrapper.findAll('input[type="text"]').at(2).setValue('no_valid@Email')
// await flushPromises()
// await expect(wrapper.find('.invalid-feedback').text()).toEqual(
// 'The Email field must be a valid email',
// )
// })
describe('submit form', () => {
beforeEach(async () => {
wrapper.findAll('input').at(0).setValue('Aa123456')
wrapper.findAll('input').at(1).setValue('Aa123456')
await wrapper.vm.$nextTick()
await flushPromises()
wrapper.find('form').trigger('submit')
})
// it('shows 4 warnings when no password is set', async () => {
// const passwords = wrapper.findAll('input[type="password"]')
// passwords.at(0).setValue('')
// passwords.at(1).setValue('')
// await flushPromises()
// await expect(wrapper.find('div.hints').text()).toContain(
// 'site.signup.lowercase',
// 'site.signup.uppercase',
// 'site.signup.minimum',
// 'site.signup.one_number',
// )
// })
describe('server response with error', () => {
it('toasts an error message', async () => {
expect(toasterMock).toHaveBeenCalledWith('error')
})
})
// //TODO test different invalid password combinations
// })
describe('server response with success', () => {
it('calls the API', async () => {
await wrapper.vm.$nextTick()
await flushPromises()
expect(changePasswordMock).toHaveBeenCalledWith(1, 'user@example.org', 'Aa123456')
})
// TODO test submit button
it('redirects to "/thx/reset"', () => {
expect(routerPushMock).toHaveBeenCalledWith('/thx/reset')
})
})
})
})
})

View File

@ -121,6 +121,7 @@ export default {
},
password: '',
passwordVisible: false,
passwordVisibleRepeat: false,
submitted: false,
authenticated: false,
sessionId: null,
@ -134,6 +135,9 @@ export default {
togglePasswordVisibility() {
this.passwordVisible = !this.passwordVisible
},
togglePasswordRepeatVisibility() {
this.passwordVisibleRepeat = !this.passwordVisibleRepeat
},
async onSubmit() {
const result = await loginAPI.changePassword(this.sessionId, this.email, this.form.password)
if (result.success) {
@ -150,6 +154,9 @@ export default {
}
},
async authenticate() {
const loader = this.$loading.show({
container: this.$refs.submitButton,
})
const optin = this.$route.params.optin
const result = await loginAPI.loginViaEmailVerificationCode(optin)
if (result.success) {
@ -159,6 +166,7 @@ export default {
} else {
this.$toast.error(result.result.message)
}
loader.hide()
},
},
computed: {

View File

@ -8,7 +8,7 @@
<div class="card-profile-stats d-flex justify-content-center mt-md-5">
<div>
<span class="heading">
{{ $n(balance) }}
{{ $n(balance, 'decimal') }}
</span>
<span class="description">GDD</span>
</div>

View File

@ -31,7 +31,7 @@
<b-container>
<b-form @keyup.prevent="loadSubmitButton">
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-12 col-sm-12 text-md-left text-lg-right">
<b-col class="col-12 col-lg-3 col-md-12 col-sm-12 text-md-left text-lg-right">
<small>{{ $t('form.firstname') }}</small>
</b-col>
<b-col v-if="showUserData" class="col-sm-10 col-md-9">
@ -42,7 +42,7 @@
</b-col>
</b-row>
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-12 col-sm-12 text-md-left text-lg-right">
<b-col class="col-12 col-lg-3 col-md-12 col-sm-12 text-md-left text-lg-right">
<small>{{ $t('form.lastname') }}</small>
</b-col>
<b-col v-if="showUserData" class="col-sm-10 col-md-9">
@ -52,8 +52,8 @@
<b-input type="text" v-model="form.lastName"></b-input>
</b-col>
</b-row>
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<b-row class="mb-3" v-show="false">
<b-col class="col-12 col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.description') }}</small>
</b-col>
<b-col v-if="showUserData" class="col-sm-10 col-md-9">
@ -68,7 +68,7 @@
<b-col>
<div class="text-right" ref="submitButton">
<b-button
variant="info"
:variant="loading ? 'default' : 'success'"
@click="onSubmit"
type="submit"
class="mt-4"

View File

@ -0,0 +1,127 @@
import { mount } from '@vue/test-utils'
import UserCardFormPasswort from './UserCard_FormUserPasswort'
import loginAPI from '../../../apis/loginAPI'
// import flushPromises from 'flush-promises'
jest.mock('../../../apis/loginAPI')
const localVue = global.localVue
const changePasswordProfileMock = jest.fn()
loginAPI.changePasswordProfile = changePasswordProfileMock
const toastSuccessMock = jest.fn()
const toastErrorMock = jest.fn()
describe('UserCardFormUserPasswort', () => {
let wrapper
const mocks = {
$t: jest.fn((t) => t),
$store: {
state: {
sessionId: 1,
email: 'user@example.org',
},
},
$toast: {
success: toastSuccessMock,
error: toastErrorMock,
},
}
const Wrapper = () => {
return mount(UserCardFormPasswort, { localVue, mocks })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the component', () => {
expect(wrapper.find('div#change_pwd').exists()).toBeTruthy()
})
it('has a change password button', () => {
expect(wrapper.find('a').exists()).toBeTruthy()
})
it('has a change password button with text "form.change-password"', () => {
expect(wrapper.find('a').text()).toEqual('form.change-password')
})
it('has a change password button with a pencil icon', () => {
expect(wrapper.find('svg.bi-pencil').exists()).toBeTruthy()
})
describe('change password from', () => {
let form
beforeEach(async () => {
wrapper.find('a').trigger('click')
await wrapper.vm.$nextTick()
form = wrapper.find('form')
})
it('has a change password form', () => {
expect(form.exists()).toBeTruthy()
})
it('has a cancel button', () => {
expect(form.find('svg.bi-x-circle').exists()).toBeTruthy()
})
it('closes the form when cancel button is clicked', async () => {
form.find('svg.bi-x-circle').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.find('input').exists()).toBeFalsy()
})
it('has three input fields', () => {
expect(form.findAll('input')).toHaveLength(3)
})
it('switches the first input type to text when show password is clicked', async () => {
form.findAll('button').at(0).trigger('click')
await wrapper.vm.$nextTick()
expect(form.findAll('input').at(0).attributes('type')).toEqual('text')
})
it('switches the second input type to text when show password is clicked', async () => {
form.findAll('button').at(1).trigger('click')
await wrapper.vm.$nextTick()
expect(form.findAll('input').at(1).attributes('type')).toEqual('text')
})
it('switches the third input type to text when show password is clicked', async () => {
form.findAll('button').at(2).trigger('click')
await wrapper.vm.$nextTick()
expect(form.findAll('input').at(2).attributes('type')).toEqual('text')
})
it('has a submit button', () => {
expect(form.find('button[type="submit"]').exists()).toBeTruthy()
})
/*
describe('submit', () => {
beforeEach(async () => {
await form.findAll('input').at(0).setValue('1234')
await form.findAll('input').at(1).setValue('Aa123456')
await form.findAll('input').at(2).setValue('Aa123456')
form.trigger('submit')
await wrapper.vm.$nextTick()
await flushPromises()
})
it('calls the API', async () => {
await wrapper.vm.$nextTick()
await flushPromises()
expect(changePasswordProfileMock).toHaveBeenCalledWith(1, 'user@example.org', '1234', 'Aa123456')
})
})
*/
})
})
})

View File

@ -4,14 +4,14 @@
<b-form @keyup.prevent="loadSubmitButton">
<b-row class="mb-4 text-right">
<b-col class="text-right">
<a href="#change_pwd" v-if="edit_pwd" @click="edit_pwd = !edit_pwd">
<span>{{ $t('form.password') }} {{ $t('form.change') }}</span>
<a href="#change_pwd" v-if="!editPassword" @click="editPassword = !editPassword">
<span>{{ $t('form.change-password') }}</span>
<b-icon class="pointer ml-3" icon="pencil" />
</a>
<b-icon
v-else
@click="edit_pwd = !edit_pwd"
@click="cancelEdit()"
class="pointer"
icon="x-circle"
variant="danger"
@ -19,9 +19,9 @@
</b-col>
</b-row>
<div v-if="!edit_pwd">
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<div v-if="editPassword">
<b-row class="mb-5">
<b-col class="col-12 col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.password_old') }}</small>
</b-col>
<b-col class="col-md-9 col-sm-10">
@ -43,8 +43,9 @@
</b-input-group>
</b-col>
</b-row>
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<b-col class="col-12 col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.password_new') }}</small>
</b-col>
<b-col class="col-md-9 col-sm-10">
@ -67,7 +68,7 @@
</b-col>
</b-row>
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<b-col class="col-12 col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.password_new_repeat') }}</small>
</b-col>
<b-col class="col-md-9 col-sm-10">
@ -89,10 +90,31 @@
</b-input-group>
</b-col>
</b-row>
<b-row class="text-right" v-if="!edit_pwd">
<b-row>
<b-col></b-col>
<b-col class="col-12">
<transition name="hint" appear>
<div v-if="passwordValidation.errors.length > 0" class="hints">
<ul>
<li v-for="error in passwordValidation.errors" :key="error">
<small>{{ error }}</small>
</li>
</ul>
</div>
</transition>
</b-col>
</b-row>
<b-row class="text-right" v-if="editPassword">
<b-col>
<div class="text-right" ref="submitButton">
<b-button variant="info" @click="onSubmit" class="mt-4">
<b-button
:variant="loading ? 'default' : 'success'"
@click="onSubmit"
type="submit"
class="mt-4"
:disabled="loading"
>
{{ $t('form.save') }}
</b-button>
</div>
@ -110,7 +132,7 @@ export default {
name: 'FormUserPasswort',
data() {
return {
edit_pwd: true,
editPassword: false,
email: null,
password: '',
passwordNew: '',
@ -122,6 +144,12 @@ export default {
}
},
methods: {
cancelEdit() {
this.editPassword = false
this.password = ''
this.passwordNew = ''
this.passwordNewRepeat = ''
},
togglePasswordVisibilityNewPwd() {
this.passwordVisibleNewPwd = !this.passwordVisibleNewPwd
},
@ -132,27 +160,58 @@ export default {
this.passwordVisibleOldPwd = !this.passwordVisibleOldPwd
},
loadSubmitButton() {
if (this.passwordVisibleNewPwd === this.passwordVisibleNewPwdRepeat) {
if (
this.password !== '' &&
this.passwordNew !== '' &&
this.passwordNewRepeat !== '' &&
this.passwordNew === this.passwordNewRepeat
) {
this.loading = false
} else {
this.loading = true
}
},
async onSubmit() {
// console.log(this.data)
async onSubmit(event) {
event.preventDefault()
const result = await loginAPI.changePasswordProfile(
this.$store.state.sessionId,
this.email,
this.$store.state.email,
this.password,
this.passwordNew,
)
if (result.success) {
alert('changePassword success')
this.$toast.success(this.$t('site.thx.reset'))
this.cancelEdit()
} else {
alert(result.result.message)
this.$toast.error(result.result.message)
}
},
},
computed: {
samePasswords() {
return this.password === this.passwordNew
},
rules() {
return [
{ message: this.$t('site.signup.lowercase'), regex: /[a-z]+/ },
{ message: this.$t('site.signup.uppercase'), regex: /[A-Z]+/ },
{ message: this.$t('site.signup.minimum'), regex: /.{8,}/ },
{ message: this.$t('site.signup.one_number'), regex: /[0-9]+/ },
]
},
passwordValidation() {
const errors = []
for (const condition of this.rules) {
if (!condition.regex.test(this.passwordNew)) {
errors.push(condition.message)
}
}
if (errors.length === 0) {
return { valid: true, errors }
}
return { valid: false, errors }
},
},
}
</script>
<style></style>

View File

@ -0,0 +1,121 @@
import { mount } from '@vue/test-utils'
import { extend } from 'vee-validate'
import UserCardFormUsername from './UserCard_FormUsername'
import loginAPI from '../../../apis/loginAPI'
import flushPromises from 'flush-promises'
jest.mock('../../../apis/loginAPI')
extend('gddUsernameRgex', {
validate(value) {
return true
},
})
extend('gddUsernameUnique', {
validate(value) {
return true
},
})
const localVue = global.localVue
const mockAPIcall = jest.fn((args) => {
return { success: true }
})
loginAPI.changeUsernameProfile = mockAPIcall
describe('UserCard_FormUsername', () => {
let wrapper
const mocks = {
$t: jest.fn((t) => t),
$store: {
state: {
sessionId: 1,
email: 'user@example.org',
username: '',
},
commit: jest.fn(),
},
$toast: {
success: jest.fn(),
},
}
const Wrapper = () => {
return mount(UserCardFormUsername, { localVue, mocks })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the component', () => {
expect(wrapper.find('div#formusername').exists()).toBeTruthy()
})
describe('username in store is empty', () => {
it('renders an empty username', () => {
expect(wrapper.find('div.display-username').text()).toEqual('@')
})
it('has an edit icon', () => {
expect(wrapper.find('svg.bi-pencil').exists()).toBeTruthy()
})
describe('edit username', () => {
beforeEach(async () => {
await wrapper.find('svg.bi-pencil').trigger('click')
})
it('shows an input field for the username', () => {
expect(wrapper.find('input[placeholder="Username"]').exists()).toBeTruthy()
})
it('shows an cancel icon', () => {
expect(wrapper.find('svg.bi-x-circle').exists()).toBeTruthy()
})
it('closes the input when cancel icon is clicked', async () => {
wrapper.find('svg.bi-x-circle').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.find('input[placeholder="Username"]').exists()).toBeFalsy()
})
it('does not change the username when cancel is clicked', async () => {
wrapper.find('input[placeholder="Username"]').setValue('username')
wrapper.find('svg.bi-x-circle').trigger('click')
await wrapper.vm.$nextTick()
expect(wrapper.find('div.display-username').text()).toEqual('@')
})
it('has a submit button', () => {
expect(wrapper.find('button[type="submit"]').exists()).toBeTruthy()
})
describe('successfull submit', () => {
beforeEach(async () => {
await wrapper.find('input[placeholder="Username"]').setValue('username')
await wrapper.find('form').trigger('submit')
await flushPromises()
})
it('calls the loginAPI', () => {
expect(mockAPIcall).toHaveBeenCalledWith(1, 'user@example.org', 'username')
})
it('displays the new username', () => {
expect(wrapper.find('div.display-username').text()).toEqual('@username')
})
it('has no edit button anymore', () => {
expect(wrapper.find('svg.bi-pencil').exists()).toBeFalsy()
})
})
})
})
})
})

View File

@ -1,20 +1,19 @@
<template>
<b-card id="formusername" class="bg-transparent" style="background-color: #ebebeba3 !important">
<b-container>
<b-container v-if="username === ''">
<b-row class="text-right">
<b-col class="mb-3">
<b-icon
v-if="editUsername"
@click="editUsername = !editUsername"
v-if="showUsername"
@click="showUsername = !showUsername"
class="pointer"
icon="pencil"
>
{{ $t('form.change') }}
</b-icon>
<b-icon
v-else
@click="editUsername = !editUsername"
@click="cancelEdit"
class="pointer"
icon="x-circle"
variant="danger"
@ -22,40 +21,48 @@
</b-col>
</b-row>
</b-container>
<b-container v-if="editUsername">
<b-container v-if="showUsername">
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.username') }}</small>
</b-col>
<b-col class="col-md-9 col-sm-10">@{{ username }}</b-col>
<b-col class="col-md-9 col-sm-10 display-username">@{{ username }}</b-col>
</b-row>
</b-container>
<b-container v-else>
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.username') }}</small>
</b-col>
<b-col class="col-md-9 col-sm-10">
<validation-observer ref="formValidator">
<b-form role="form">
<b-form-input v-model="form.username" :placeholder="username"></b-form-input>
<div>
{{ $t('form.change_username_info') }}
<validation-observer ref="formValidator" v-slot="{ handleSubmit, valid }">
<b-form role="form" @submit.stop.prevent="handleSubmit(onSubmit)">
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('form.username') }}</small>
</b-col>
<b-col class="col-md-9 col-sm-10">
<validation-provider
name="Username"
:rules="{ gddUsernameRgex: true, gddUsernameUnique: true }"
v-slot="{ errors }"
>
<div v-if="errors" class="text-right p-3 p-sm-1">
<span v-for="error in errors" :key="error" class="errors">{{ error }}</span>
</div>
<b-form-input v-model="form.username" placeholder="Username"></b-form-input>
<div>
{{ $t('form.change_username_info') }}
</div>
</validation-provider>
</b-col>
</b-row>
<b-row class="text-right">
<b-col>
<div class="text-right" ref="submitButton">
<b-button variant="info" type="submit" class="mt-4" :disabled="!valid">
{{ $t('form.save') }}
</b-button>
</div>
</b-form>
</validation-observer>
</b-col>
</b-row>
<b-row class="text-right">
<b-col>
<div class="text-right" ref="submitButton">
<b-button variant="info" @click="onSubmit" class="mt-4">
{{ $t('form.save') }}
</b-button>
</div>
</b-col>
</b-row>
</b-col>
</b-row>
</b-form>
</validation-observer>
</b-container>
</b-card>
</template>
@ -66,7 +73,7 @@ export default {
name: 'FormUsername',
data() {
return {
editUsername: true,
showUsername: true,
username: this.$store.state.username,
form: {
username: this.$store.state.username,
@ -74,6 +81,10 @@ export default {
}
},
methods: {
cancelEdit() {
this.username = this.$store.state.username
this.showUsername = true
},
async onSubmit() {
const result = await loginAPI.changeUsernameProfile(
this.$store.state.sessionId,
@ -82,13 +93,21 @@ export default {
)
if (result.success) {
this.$store.commit('username', this.form.username)
this.editUserdata = this.editUsername = !this.editUsername
alert('Dein Username wurde geändert.')
this.username = this.form.username
this.showUsername = true
this.$toast.success(this.$t('site.profil.user-data.change-success'))
} else {
alert(result.result.message)
this.$toast.error(result.result.message)
this.showUsername = true
this.username = this.$store.state.username
this.form.username = this.$store.state.username
}
},
},
}
</script>
<style></style>
<style>
span.errors {
color: red;
}
</style>

View File

@ -27,9 +27,9 @@ describe('UserProfileOverview', () => {
expect(wrapper.findComponent({ name: 'FormUserData' }).exists()).toBeTruthy()
})
it('has a user name form', () => {
expect(wrapper.findComponent({ name: 'FormUsername' }).exists()).toBeTruthy()
})
// it('has a user name form', () => {
// expect(wrapper.findComponent({ name: 'FormUsername' }).exists()).toBeTruthy()
// })
it('has a user password form', () => {
expect(wrapper.findComponent({ name: 'FormUserPasswort' }).exists()).toBeTruthy()

View File

@ -2,21 +2,21 @@
<b-container fluid>
<user-card :balance="balance" :transactionCount="transactionCount"></user-card>
<form-user-data />
<form-username />
<!--<form-username />-->
<form-user-passwort />
</b-container>
</template>
<script>
import UserCard from './UserProfile/UserCard.vue'
import FormUserData from './UserProfile/UserCard_FormUserData.vue'
import FormUsername from './UserProfile/UserCard_FormUsername.vue'
// import FormUsername from './UserProfile/UserCard_FormUsername.vue'
import FormUserPasswort from './UserProfile/UserCard_FormUserPasswort.vue'
export default {
components: {
UserCard,
FormUserData,
FormUsername,
// FormUsername,
FormUserPasswort,
},
props: {

View File

@ -7,7 +7,6 @@ import * as rules from 'vee-validate/dist/rules'
import { messages } from 'vee-validate/dist/locale/en.json'
import RegeneratorRuntime from 'regenerator-runtime'
import SideBar from '@/components/SidebarPlugin'
import VueRouter from 'vue-router'
import VueQrcode from 'vue-qrcode'
import VueMoment from 'vue-moment'
@ -29,7 +28,6 @@ global.localVue.use(Vuex)
global.localVue.use(IconsPlugin)
global.localVue.use(RegeneratorRuntime)
global.localVue.use(SideBar)
global.localVue.use(VueRouter)
global.localVue.use(VueQrcode)
global.localVue.use(VueMoment)
global.localVue.component('validation-provider', ValidationProvider)

View File

@ -129,6 +129,7 @@ FILE(GLOB TEST_CRYPTO "src/cpp/test/crypto/*.cpp" "src/cpp/test/crypto/*.h")
FILE(GLOB TEST_MODEL "src/cpp/test/model/*.cpp" "src/cpp/test/model/*.h")
FILE(GLOB TEST_MODEL_TABLE "src/cpp/test/model/table/*.cpp" "src/cpp/test/model/table/*.h")
FILE(GLOB TEST_CONTROLLER "src/cpp/test/controller/*.cpp" "src/cpp/test/controller/*.h")
FILE(GLOB TEST_JSON_INTERFACE "src/cpp/test/JSONInterface/*.cpp" "src/cpp/test/JSONInterface/*.h")
SET(LOCAL_SRCS
${CONTROLLER} ${TINF} ${MAIN} ${HTTPInterface} ${COMPILED_PAGES}
@ -138,7 +139,7 @@ SET(LOCAL_SRCS
${PROTO_GRADIDO} ${PROTO_HEDERA}
)
SET(LOCAL_TEST_SRC
${TEST} ${TEST_CRYPTO} ${TEST_MODEL} ${TEST_MODEL_TABLE} ${TEST_CONTROLLER}
${TEST} ${TEST_CRYPTO} ${TEST_MODEL} ${TEST_MODEL_TABLE} ${TEST_CONTROLLER} ${TEST_JSON_INTERFACE}
)
aux_source_directory("src/cpp" LOCAL_SRCS)

View File

@ -1,85 +1,4 @@
#########################################################################################################
# Prepare debug
#########################################################################################################
FROM gradido/login_dependencies:gcc9-debug-3 as prepare_debug
ENV DOCKER_WORKDIR="/code"
WORKDIR ${DOCKER_WORKDIR}
RUN echo '/usr/local/lib' >> /etc/ld.so.conf && ldconfig
COPY ./CMakeLists.txt.lib ./CMakeLists.txt
RUN ln -s /usr/local/googletest ./googletest
COPY ./src ./src
COPY ./cmake/CodeCoverage.cmake ./cmake/CodeCoverage.cmake
COPY ./dependencies/cmake-modules ./dependencies/cmake-modules
COPY ./dependencies/spirit-po ./dependencies/spirit-po
COPY ./dependencies/tinf ./dependencies/tinf
COPY ./scripts ./scripts
#########################################################################################################
# Install Coverage tool
#########################################################################################################
FROM prepare_debug as coverage
RUN apt-get update && \
apt-get install -y --no-install-recommends python3-pip && \
apt-get autoclean && \
apt-get autoremove && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
RUN pip3 install gcovr setuptools wheel && \
pip3 install fastcov
#########################################################################################################
# Build test
#########################################################################################################
FROM coverage as test
ENV DOCKER_WORKDIR="/code"
WORKDIR ${DOCKER_WORKDIR}
RUN if [ ! -d "./build_cov" ] ; then mkdir build_cov; fi
RUN cd build_cov && \
cmake -DCMAKE_BUILD_TYPE=Debug -DCOLLECT_COVERAGE_DATA=ON -DCOVERAGE_TOOL=fastcov ..
#make -j$(nproc) Gradido_LoginServer_Test
#RUN chmod +x build_cov/bin/Gradido_LoginServer_Test
#CMD gdb -ex run ./build_cov/bin/Gradido_LoginServer_Test
#CMD ./build_cov/bin/Gradido_LoginServer_Test
#ENTRYPOINT make -C build_cov coverage
CMD cd build_cov && make -j$(nproc) Gradido_LoginServer_Test && make coverage && \
if [ ! -d "./coverage" ] ; then mkdir coverage; fi && \
cp coverage.info ./coverage/
#########################################################################################################
# Build debug
#########################################################################################################
FROM prepare_debug as debug
ENV DOCKER_WORKDIR="/code"
WORKDIR ${DOCKER_WORKDIR}
RUN mkdir build && \
cd build && \
cmake -DCMAKE_BUILD_TYPE=Debug .. && \
make -j$(nproc) Gradido_LoginServer
RUN cd scripts && \
chmod +x compile_pot.sh && \
./compile_pot.sh
RUN chmod +x build/bin/Gradido_LoginServer
ENTRYPOINT ["build/bin/Gradido_LoginServer"]
#########################################################################################################
# Build release
#########################################################################################################

View File

@ -1,3 +1,14 @@
# Login-Server Build dependencies for alpine
# Uploaded to hub.docker.com with the tag:
# gradido/login_dependencies:alpine-debug-3 for debug build
# and
# gradido/login_dependencies:alpine-release-3 for release build
# Update tag when dependencies are added or removed
# Control Build Type with ARG BUILD_TYPE
# Valid values do you find here: https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
# Default is set to Debug
##### BUILD-ENV #####
FROM alpine:3.13.5 as alpine-build

View File

@ -0,0 +1,49 @@
#########################################################################################################
# Build release
#########################################################################################################
FROM gradido/login_dependencies:alpine-release-3 as release
ENV DOCKER_WORKDIR="/code"
WORKDIR ${DOCKER_WORKDIR}
COPY ./CMakeLists.txt.lib ./CMakeLists.txt
COPY ./src ./src
RUN ln -s /usr/local/googletest ./googletest
COPY ./dependencies/cmake-modules ./dependencies/cmake-modules
COPY ./dependencies/spirit-po ./dependencies/spirit-po
COPY ./dependencies/tinf ./dependencies/tinf
COPY ./scripts ./scripts
RUN mkdir build && \
cd build && \
cmake -DCMAKE_BUILD_TYPE=Release .. && \
make -j$(nproc) Gradido_LoginServer
RUN cd scripts && \
chmod +x compile_pot.sh && \
./compile_pot.sh
#########################################################################################################
# run release
#########################################################################################################
#From alpine:latest as login_server
FROM alpine:3.13.5 as login_server
USER root
WORKDIR "/usr/bin"
COPY --from=release /code/build/bin/Gradido_LoginServer /usr/bin/
COPY --from=release /usr/local/lib/mariadb/libmariadb.so.3 /usr/local/lib/
COPY --from=release /usr/local/lib/libPoco* /usr/local/lib/
COPY --from=release /usr/local/lib/libproto* /usr/local/lib/
COPY --from=release /usr/lib/libsodium.so.23 /usr/lib/
COPY --from=release /usr/lib/libstdc++.so.6 /usr/lib/
COPY --from=release /usr/lib/libgcc_s.so.1 /usr/lib/
RUN chmod +x /usr/bin/Gradido_LoginServer
ENTRYPOINT ["/usr/bin/Gradido_LoginServer"]
#CMD Gradido_LoginServer

View File

@ -1,4 +1,9 @@
# Login Server build which contain the config file, found on docker hub with tag:
# gradido/login_server:with-config
# Used for community-server tests on staging
#########################################################################################################
# Build release
#########################################################################################################

View File

@ -0,0 +1,39 @@
#########################################################################################################
# Prepare debug
#########################################################################################################
FROM gradido/login_dependencies:gcc9-debug-3 as prepare_debug
ENV DOCKER_WORKDIR="/code"
WORKDIR ${DOCKER_WORKDIR}
RUN echo '/usr/local/lib' >> /etc/ld.so.conf && ldconfig
COPY ./CMakeLists.txt.lib ./CMakeLists.txt
RUN ln -s /usr/local/googletest ./googletest
COPY ./src ./src
COPY ./cmake/CodeCoverage.cmake ./cmake/CodeCoverage.cmake
COPY ./dependencies/cmake-modules ./dependencies/cmake-modules
COPY ./dependencies/spirit-po ./dependencies/spirit-po
COPY ./dependencies/tinf ./dependencies/tinf
COPY ./scripts ./scripts
#########################################################################################################
# Build debug
#########################################################################################################
FROM prepare_debug as debug
ENV DOCKER_WORKDIR="/code"
WORKDIR ${DOCKER_WORKDIR}
RUN mkdir build && \
cd build && \
cmake -DCMAKE_BUILD_TYPE=Debug ..
RUN cd scripts && \
chmod +x compile_pot.sh && \
./compile_pot.sh
CMD cd build && cmake .. && make -j$(nproc) Gradido_LoginServer && ./bin/Gradido_LoginServer

View File

@ -1,3 +1,12 @@
# Login-Server Build dependencies for ubuntu
# Uploaded to hub.docker.com with the tag:
# gradido/login_dependencies:gcc9-debug-3 for debug build
# Update tag when dependencies are added or removed
# Control Build Type with ARG BUILD_TYPE
# Valid values do you find here: https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
# Default is set to Debug
##### BUILD-ENV #####
FROM gcc:9 as gcc9_build

View File

@ -0,0 +1,49 @@
#########################################################################################################
# Build release
#########################################################################################################
FROM gradido/login_dependencies:ubuntu-release-3 as release
ENV DOCKER_WORKDIR="/code"
WORKDIR ${DOCKER_WORKDIR}
COPY ./CMakeLists.txt.lib ./CMakeLists.txt
COPY ./src ./src
RUN ln -s /usr/local/googletest ./googletest
COPY ./dependencies/cmake-modules ./dependencies/cmake-modules
COPY ./dependencies/spirit-po ./dependencies/spirit-po
COPY ./dependencies/tinf ./dependencies/tinf
COPY ./scripts ./scripts
RUN mkdir build && \
cd build && \
cmake -DCMAKE_BUILD_TYPE=Release .. && \
make -j$(nproc) Gradido_LoginServer
RUN cd scripts && \
chmod +x compile_pot.sh && \
./compile_pot.sh
#########################################################################################################
# run release
#########################################################################################################
#From alpine:latest as login_server
FROM ubuntu:latest as login_server
USER root
WORKDIR "/usr/bin"
COPY --from=release /code/build/bin/Gradido_LoginServer /usr/bin/
COPY --from=release /usr/local/lib/mariadb/libmariadb.so.3 /usr/local/lib/
COPY --from=release /usr/local/lib/libPoco* /usr/local/lib/
COPY --from=release /usr/local/lib/libproto* /usr/local/lib/
COPY --from=release /usr/lib/libsodium.so.23 /usr/lib/
COPY --from=release /usr/lib/libstdc++.so.6 /usr/lib/
COPY --from=release /usr/lib/libgcc_s.so.1 /usr/lib/
RUN chmod +x /usr/bin/Gradido_LoginServer
ENTRYPOINT ["/usr/bin/Gradido_LoginServer"]
#CMD Gradido_LoginServer

View File

@ -0,0 +1,58 @@
#########################################################################################################
# Prepare debug
#########################################################################################################
FROM gradido/login_dependencies:gcc9-debug-3 as prepare_debug
ENV DOCKER_WORKDIR="/code"
WORKDIR ${DOCKER_WORKDIR}
RUN echo '/usr/local/lib' >> /etc/ld.so.conf && ldconfig
COPY ./CMakeLists.txt.lib ./CMakeLists.txt
RUN ln -s /usr/local/googletest ./googletest
COPY ./src ./src
COPY ./cmake/CodeCoverage.cmake ./cmake/CodeCoverage.cmake
COPY ./dependencies/cmake-modules ./dependencies/cmake-modules
COPY ./dependencies/spirit-po ./dependencies/spirit-po
COPY ./dependencies/tinf ./dependencies/tinf
COPY ./scripts ./scripts
#########################################################################################################
# Install Coverage tool
#########################################################################################################
FROM prepare_debug as coverage
RUN apt-get update && \
apt-get install -y --no-install-recommends python3-pip && \
apt-get autoclean && \
apt-get autoremove && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# fastcov need gcovr to work
RUN pip3 install gcovr setuptools wheel && \
pip3 install fastcov
#########################################################################################################
# Build test
#########################################################################################################
FROM coverage as test
ENV DOCKER_WORKDIR="/code"
WORKDIR ${DOCKER_WORKDIR}
RUN if [ ! -d "./build_cov" ] ; then mkdir build_cov; fi
RUN cd build_cov && \
cmake -DCMAKE_BUILD_TYPE=Debug -DCOLLECT_COVERAGE_DATA=ON -DCOVERAGE_TOOL=fastcov .. && \
make -j$(nproc) Gradido_LoginServer_Test
#ENTRYPOINT make -C build_cov coverage
CMD cd build_cov && make coverage && \
if [ ! -d "./coverage" ] ; then mkdir coverage; fi && \
cp coverage.info ./coverage/

View File

@ -12,6 +12,7 @@
#include "CheckEmailPage.h"
#include "PassphrasePage.h"
#include "SaveKeysPage.h"
#include "TestUserGenerator.h"
#include "ElopageWebhook.h"
#include "ElopageWebhookLight.h"
#include "UserUpdatePasswordPage.h"
@ -229,6 +230,11 @@ Poco::Net::HTTPRequestHandler* PageRequestHandlerFactory::createRequestHandler(c
return basicSetup(new LoginPage(nullptr), request, timeUsed);
}
}
if (ServerConfig::g_ServerSetupType != ServerConfig::SERVER_TYPE_PRODUCTION) {
if (url_first_part == "/testUserGenerator") {
return basicSetup(new TestUserGenerator, request, timeUsed);
}
}
return basicSetup(new LoginPage(nullptr), request, timeUsed);
//return new HandleFileRequest;
//return new PageRequestHandlerFactory;

View File

@ -10,8 +10,6 @@
Poco::JSON::Object* JsonGetLogin::handle(Poco::Dynamic::Var params)
{
int session_id = 0;
auto sm = SessionManager::getInstance();
auto pt = PendingTasksManager::getInstance();
auto observer = SingletonTaskObserver::getInstance();
@ -58,4 +56,4 @@ Poco::JSON::Object* JsonGetLogin::handle(Poco::Dynamic::Var params)
//printf("[JsonGetLogin] %s\n", user_string.data());
return result;
}
}

View File

@ -227,6 +227,19 @@ Poco::JSON::Object* JsonRequestHandler::checkAndLoadSession(Poco::Dynamic::Var p
return stateError("error parsing query params, Poco Error", ex.displayText());
}
}
else if (params.type() == typeid(Poco::JSON::Object::Ptr)) {
try {
Poco::JSON::Object::Ptr paramJsonObject = params.extract<Poco::JSON::Object::Ptr>();
auto session_id_obj = paramJsonObject->get("session_id");
if (session_id_obj.isEmpty()) {
return stateError("missing session_id");
}
session_id_obj.convert(session_id);
}
catch (Poco::Exception& ex) {
return stateError("Poco Exception by reading session_id", ex.what());
}
}
if (!session_id) {
return stateError("empty session id");

View File

@ -19,6 +19,7 @@
#include "JsonLoginViaEmailVerificationCode.h"
#include "JsonLogout.h"
#include "JsonNetworkInfos.h"
#include "JsonResetPassword.h"
#include "JsonSendEmail.h"
#include "JsonAdminEmailVerificationResend.h"
#include "JsonGetUserInfos.h"
@ -65,7 +66,7 @@ Poco::Net::HTTPRequestHandler* JsonRequestHandlerFactory::createRequestHandler(c
auto sm = SessionManager::getInstance();
Session* s = nullptr;
if (!session_id) {
if (session_id) {
s = sm->getSession(session_id);
}
@ -100,7 +101,7 @@ Poco::Net::HTTPRequestHandler* JsonRequestHandlerFactory::createRequestHandler(c
return new JsonGetUserInfos;
}
else if (url_first_part == "/updateUserInfos") {
return new JsonUpdateUserInfos;
return new JsonUpdateUserInfos(s);
}
else if (url_first_part == "/search") {
return new JsonSearch;
@ -114,6 +115,9 @@ Poco::Net::HTTPRequestHandler* JsonRequestHandlerFactory::createRequestHandler(c
else if (url_first_part == "/sendEmail") {
return new JsonSendEmail;
}
else if (url_first_part == "/resetPassword") {
return new JsonResetPassword;
}
else if (url_first_part == "/logout") {
return new JsonLogout(client_host);
}

View File

@ -0,0 +1,53 @@
#include "JsonResetPassword.h"
#include "SingletonManager/SessionManager.h"
#include "SingletonManager/SingletonTaskObserver.h"
Poco::JSON::Object* JsonResetPassword::handle(Poco::Dynamic::Var params)
{
auto result_session_check = checkAndLoadSession(params, false);
if (result_session_check) {
return result_session_check;
}
std::string password;
// if is json object
if (params.type() == typeid(Poco::JSON::Object::Ptr)) {
Poco::JSON::Object::Ptr paramJsonObject = params.extract<Poco::JSON::Object::Ptr>();
try {
auto password_obj = paramJsonObject->get("password");
if (password_obj.isEmpty()) {
return stateError("password missing");
}
password_obj.convert(password);
}
catch (Poco::Exception& ex) {
return stateError("error parsing json", ex.what());
}
}
auto sm = SessionManager::getInstance();
NotificationList errors;
if (!sm->checkPwdValidation(password, &errors, LanguageManager::getInstance()->getFreeCatalog(LANG_EN))) {
return stateError("password isn't valid", &errors);
}
auto user = mSession->getNewUser();
if (user.isNull() || user->getModel().isNull()) {
return stateError("invalid user");
}
auto observer = SingletonTaskObserver::getInstance();
auto email_hash = observer->makeHash(user->getModel()->getEmail());
if (observer->getTaskCount(email_hash, TASK_OBSERVER_PASSWORD_CREATION) > 0) {
return stateError("password encryption is already running");
}
auto update_password_result = user->setNewPassword(password);
if (update_password_result == 2) {
KeyPairEd25519* key_pair = NULL;
if (!user->tryLoadPassphraseUserBackup(&key_pair)) {
user->setGradidoKeyPair(key_pair);
}
}
return stateSuccess();
}

View File

@ -0,0 +1,20 @@
#ifndef __JSON_INTERFACE_JSON_RESET_PASSWORD_
#define __JSON_INTERFACE_JSON_RESET_PASSWORD_
#include "JsonRequestHandler.h"
/*!
* @author Dario Rekowski
* @date 2021-06-16
* @brief reset password, if user has forgetten his password
*
*/
class JsonResetPassword : public JsonRequestHandler
{
public:
Poco::JSON::Object* handle(Poco::Dynamic::Var params);
};
#endif // __JSON_INTERFACE_JSON_RESET_PASSWORD_

View File

@ -99,10 +99,10 @@ Poco::JSON::Object* JsonSendEmail::handle(Poco::Dynamic::Var params)
return stateError("invalid session");
}
}
Poco::Thread::sleep(ServerConfig::g_FakeLoginSleepTime);
auto receiver_user = controller::User::create();
if (1 != receiver_user->load(email)) {
return stateError("invalid email");
return stateSuccess();
}
auto receiver_user_id = receiver_user->getModel()->getID();
std::string checkEmailUrl = receiver_user->getGroupBaseUrl() + ServerConfig::g_frontend_checkEmailPath;

View File

@ -4,6 +4,13 @@
#include "../SingletonManager/LanguageManager.h"
#include "../tasks/AuthenticatedEncryptionCreateKeyTask.h"
JsonUpdateUserInfos::JsonUpdateUserInfos(Session* session)
: JsonRequestHandler(session)
{
}
Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
{
/*
@ -28,7 +35,11 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
/// Throws InvalidAccessException if Var is empty.
try {
paramJsonObject->get("email").convert(email);
paramJsonObject->get("session_id").convert(session_id);
auto session_id_obj = paramJsonObject->get("session_id");
if (!session_id_obj.isEmpty()) {
session_id_obj.convert(session_id);
}
updates = paramJsonObject->getObject("update");
}
catch (Poco::Exception& ex) {
@ -39,18 +50,21 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
return stateError("parameter format unknown");
}
if (!session_id) {
if (!session_id && !mSession) {
return stateError("session_id invalid");
}
if (updates.isNull()) {
return stateError("update is zero or not an object");
}
auto session = sm->getSession(session_id);
if (!session) {
if (session_id) {
mSession = sm->getSession(session_id);
}
if (!mSession) {
return customStateError("not found", "session not found");
}
auto user = session->getNewUser();
auto user = mSession->getNewUser();
auto user_model = user->getModel();
if (user_model->getEmail() != email) {
return customStateError("not same", "email don't belong to logged in user");
@ -61,6 +75,7 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
Poco::JSON::Array jsonErrorsArray;
int extractet_values = 0;
bool password_changed = false;
//['User.first_name' => 'first_name', 'User.last_name' => 'last_name', 'User.disabled' => 0|1, 'User.language' => 'de']
for (auto it = updates->begin(); it != updates->end(); it++) {
std::string name = it->first;
@ -71,7 +86,7 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
if ( "User.first_name" == name) {
std::string str_val = validateString(value, "User.first_name", jsonErrorsArray);
if (str_val.size() > 0) {
if (str_val.size() > 0 && user_model->getFirstName() != str_val) {
user_model->setFirstName(str_val);
extractet_values++;
}
@ -79,7 +94,7 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
else if ("User.last_name" == name ) {
std::string str_val = validateString(value, "User.last_name", jsonErrorsArray);
if (str_val.size() > 0) {
if (str_val.size() > 0 && user_model->getLastName() != str_val) {
user_model->setLastName(str_val);
extractet_values++;
}
@ -88,14 +103,18 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
else if ("User.username" == name) {
std::string str_val = validateString(value, "User.username", jsonErrorsArray);
if (str_val.size() > 0) {
if (str_val.size() > 0 && user_model->getUsername() != str_val) {
if (user_model->getUsername() != "") {
jsonErrorsArray.add("change username currently not supported!");
}
else if (user_model->getUsername() != str_val) {
else
{
if (user->isUsernameAlreadyUsed(str_val)) {
jsonErrorsArray.add("username already used");
}
else if (!sm->isValid(str_val, VALIDATE_USERNAME)) {
jsonErrorsArray.add("username must start with [a-z] or [A-Z] and than can contain also [0-9], - and _");
}
else {
user_model->setUsername(str_val);
extractet_values++;
@ -106,33 +125,35 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
else if ("User.description" == name) {
std::string str_val = validateString(value, "User.description", jsonErrorsArray);
if (str_val.size() > 0) {
if (str_val.size() > 0 && str_val != user_model->getDescription()) {
user_model->setDescription(str_val);
extractet_values++;
}
}
else if ("User.disabled" == name) {
if (value.isBoolean()) {
bool disabled;
bool disabled;
if (value.isInteger()) {
int idisabled;
value.convert(idisabled);
disabled = static_cast<bool>(idisabled);
} else if (value.isBoolean()) {
value.convert(disabled);
user_model->setDisabled(disabled);
extractet_values++;
}
else if (value.isInteger()) {
int disabled;
value.convert(disabled);
user_model->setDisabled(static_cast<bool>(disabled));
extractet_values++;
}
else {
jsonErrorsArray.add("User.disabled isn't a boolean or integer");
}
if (user_model->isDisabled() != disabled) {
user_model->setDisabled(disabled);
extractet_values++;
}
}
else if ("User.language" == name && value.size() > 0) {
else if ("User.language" == name && value.size() > 0)
{
std::string str_val = validateString(value, "User.language", jsonErrorsArray);
if (str_val.size() > 0) {
if (str_val.size() > 0 && user_model->getLanguageKey() != str_val) {
auto lang = LanguageManager::languageFromString(str_val);
if (LANG_NULL == lang) {
jsonErrorsArray.add("User.language isn't a valid language");
@ -144,32 +165,46 @@ 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) {
else if ("User.password" == name && (ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_PASSWORD_REQUESTS) == ServerConfig::UNSECURE_PASSWORD_REQUESTS)
{
std::string str_val = validateString(value, "User.password", jsonErrorsArray);
if (str_val.size() > 0) {
NotificationList errors;
if (!sm->checkPwdValidation(value.toString(), &errors, LanguageManager::getInstance()->getFreeCatalog(LANG_EN))) {
jsonErrorsArray.add("User.password isn't valid");
jsonErrorsArray.add(errors.getErrorsArray());
if (str_val.size() > 0)
{
if (!user->hasPassword()) {
return stateError("login state invalid");
}
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;
if (isOldPasswordValid(updates, jsonErrorsArray))
{
NotificationList errors;
if (!sm->checkPwdValidation(value.toString(), &errors, LanguageManager::getInstance()->getFreeCatalog(LANG_EN))) {
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
// 1 = password changed, private key re-encrypted and saved into db
case 1:
extractet_values++;
password_changed = true;
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");
extractet_values++;
password_changed = true;
break;
// -1 = stored pubkey and private key didn't match
case -1: jsonErrorsArray.add("stored pubkey and private key didn't match"); break;
}
}
}
}
}
}
}
@ -179,7 +214,9 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
jsonErrorsArray.add(error_message);
}
}
if (extractet_values > 0) {
// if only password was changed, no need to call an additional db update
// password db entry will be updated inside of controller::User::setNewPassword method
if (extractet_values - (int)password_changed > 0) {
if (1 != user_model->updateFieldsFromCommunityServer()) {
user_model->addError(new Error("JsonUpdateUserInfos", "error by saving update to db"));
user_model->sendErrorsAsEmail();
@ -188,7 +225,13 @@ Poco::JSON::Object* JsonUpdateUserInfos::handle(Poco::Dynamic::Var params)
}
result->set("errors", jsonErrorsArray);
result->set("valid_values", extractet_values);
result->set("state", "success");
if (!jsonErrorsArray.size()) {
result->set("state", "success");
}
else {
result->set("msg", jsonErrorsArray.get(0));
result->set("state", "error");
}
return result;
}
@ -206,8 +249,51 @@ std::string JsonUpdateUserInfos::validateString(Poco::Dynamic::Var value, const
if (string_value.size() == 0) {
errorMessage += " is empty";
errorArray.add(errorArray);
errorArray.add(errorMessage);
return "";
}
return string_value;
}
bool JsonUpdateUserInfos::isOldPasswordValid(Poco::JSON::Object::Ptr updates, Poco::JSON::Array& errors)
{
auto sm = SessionManager::getInstance();
auto user = mSession->getNewUser();
std::string old_password;
auto old_password_obj = updates->get("User.password_old");
if (old_password_obj.isEmpty()) {
errors.add("User.password_old not found");
}
else if (!old_password_obj.isString()) {
errors.add("User.password_old isn't a string");
}
else {
old_password_obj.convert(old_password);
}
NotificationList local_errors;
if (old_password.size())
{
if (!sm->checkPwdValidation(old_password, &local_errors, LanguageManager::getInstance()->getFreeCatalog(LANG_EN))) {
errors.add("User.password_old didn't match");
Poco::Thread::sleep(ServerConfig::g_FakeLoginSleepTime);
}
else
{
auto secret_key = user->createSecretKey(old_password);
if (secret_key->getKeyHashed() == user->getModel()->getPasswordHashed()) {
return true;
}
else if (secret_key.isNull()) {
errors.add("Password calculation for this user already running, please try again later");
}
else {
errors.add("User.password_old didn't match");
}
}
}
return false;
}

View File

@ -14,11 +14,13 @@
class JsonUpdateUserInfos : public JsonRequestHandler
{
public:
JsonUpdateUserInfos(Session* session);
Poco::JSON::Object* handle(Poco::Dynamic::Var params);
protected:
std::string validateString(Poco::Dynamic::Var value, const char* fieldName, Poco::JSON::Array& errorArray);
bool isOldPasswordValid(Poco::JSON::Object::Ptr updates, Poco::JSON::Array& errors);
};

View File

@ -1,17 +0,0 @@
#include "HederaTaskManager.h"
HederaTaskManager* HederaTaskManager::getInstance()
{
static HederaTaskManager one;
return &one;
}
HederaTaskManager::HederaTaskManager()
{
}
HederaTaskManager::~HederaTaskManager()
{
}

View File

@ -1,23 +0,0 @@
#ifndef __GRADIDO_LOGIN_SINGLETON_MANAGER_HEDERA_TASK_MANAGER_H
#define __GRADIDO_LOGIN_SINGLETON_MANAGER_HEDERA_TASK_MANAGER_H
/*!
* @author: Dario Rekowski
*
* @date: 11.09.2020
*
* @brief: Manage Hedera Task, waiting on Consensus for Hedera Transactions
*
*/
class HederaTaskManager
{
public:
~HederaTaskManager();
static HederaTaskManager* getInstance();
protected:
HederaTaskManager();
};
#endif //__GRADIDO_LOGIN_SINGLETON_MANAGER_HEDERA_TASK_MANAGER_H

View File

@ -42,6 +42,7 @@ int PendingTasksManager::addTask(Poco::AutoPtr<controller::PendingTask> task)
if (task.isNull() || !task->getModel()) {
return -1;
}
auto model = task->getModel();
Poco::ScopedLock<Poco::Mutex> _lock(mWorkMutex);
auto pending_task_list = getTaskListForUser(model->getUserId());
@ -235,30 +236,3 @@ Poco::AutoPtr<controller::PendingTask> PendingTasksManager::getPendingTask(int p
return nullptr;
}
void PendingTasksManager::reportErrorToCommunityServer(Poco::AutoPtr<controller::PendingTask> task, std::string error, std::string errorDetails)
{
// TODO: choose user specific server
JsonRequest phpServerRequest(ServerConfig::g_php_serverHost, ServerConfig::g_phpServerPort);
//Poco::Net::NameValueCollection payload;
Poco::JSON::Object payload;
auto task_model = task->getModel();
auto user_model = task->getUser()->getModel();
payload.set("created", task_model->getCreated());
payload.set("id", task_model->getID());
payload.set("type", task_model->getTaskTypeString());
payload.set("public_key", user_model->getPublicKeyHex());
payload.set("error", error);
payload.set("errorMessage", errorDetails);
auto ret = phpServerRequest.request("errorInTransaction", payload);
if (ret == JSON_REQUEST_RETURN_ERROR)
{
auto em = ErrorManager::getInstance();
em->addError(new Error("PendingTasksManager::reportErrorToCommunityServer", "php server error"));
em->getErrors(&phpServerRequest);
em->sendErrorsAsEmail();
}
}

View File

@ -50,7 +50,6 @@ public:
std::vector<Poco::AutoPtr<controller::PendingTask>> getTransactionsUserMustSign(Poco::AutoPtr<controller::User> user);
std::vector<Poco::AutoPtr<controller::PendingTask>> getTransactionSomeoneMustSign(Poco::AutoPtr<controller::User> user);
void reportErrorToCommunityServer(Poco::AutoPtr<controller::PendingTask> task, std::string error, std::string errorDetails);
protected:
PendingTasksManager();

View File

@ -44,6 +44,7 @@ bool SessionManager::init()
switch (i) {
//case VALIDATE_NAME: mValidations[i] = new Poco::RegularExpression("/^[a-zA-Z_ -]{3,}$/"); break;
case VALIDATE_NAME: mValidations[i] = new Poco::RegularExpression("^[^<>&;]{3,}$"); break;
case VALIDATE_USERNAME: mValidations[i] = new Poco::RegularExpression("^[a-zA-Z][a-zA-Z0-9_-]*$"); break;
case VALIDATE_EMAIL: mValidations[i] = new Poco::RegularExpression("^[a-zA-Z0-9.!#$%&?*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$"); break;
case VALIDATE_PASSWORD: mValidations[i] = new Poco::RegularExpression("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[@$!%*?&+-_])[A-Za-z0-9@$!%*?&+-_]{8,}$"); break;
case VALIDATE_PASSPHRASE: mValidations[i] = new Poco::RegularExpression("^(?:[a-z]* ){23}[a-z]*\s*$"); break;
@ -56,17 +57,17 @@ bool SessionManager::init()
//case VALIDATE_ONLY_URL: mValidations[i] = new Poco::RegularExpression("^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}$"); break;
case VALIDATE_ONLY_URL: mValidations[i] = new Poco::RegularExpression("^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\/?"); break;
case VALIDATE_HAS_SPECIAL_CHARACTER: mValidations[i] = new Poco::RegularExpression(".*[@$!%*?&+-].*"); break;
case VALIDATE_HAS_UPPERCASE_LETTER:
mValidations[i] = new Poco::RegularExpression(".*[A-Z].*");
case VALIDATE_HAS_UPPERCASE_LETTER:
mValidations[i] = new Poco::RegularExpression(".*[A-Z].*");
ServerConfig::g_ServerKeySeed->put(i, DRRandom::r64());
break;
case VALIDATE_HAS_LOWERCASE_LETTER: mValidations[i] = new Poco::RegularExpression(".*[a-z].*"); break;
default: printf("[SessionManager::%s] unknown validation type\n", __FUNCTION__);
}
}
mInitalized = true;
mInitalized = true;
mWorkingMutex.unlock();
return true;
}
@ -97,7 +98,7 @@ void SessionManager::deinitalize()
}
printf("[SessionManager::deinitalize] count of dead locked sessions: %d\n", mDeadLockedSessionCount);
mInitalized = false;
mWorkingMutex.unlock();
}
@ -141,7 +142,7 @@ Session* SessionManager::getNewSession(int* handle)
// first check if we have any timeouted session to directly reuse it
checkTimeoutSession();
// lock
// lock
try {
//Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
mWorkingMutex.tryLock(500);
@ -155,8 +156,8 @@ Session* SessionManager::getNewSession(int* handle)
//UniLib::controller::TaskPtr checkSessionTimeout(new CheckSessionTimeouted);
//checkSessionTimeout->scheduleTask(checkSessionTimeout);
// check if we have an existing session ready to use
while (mEmptyRequestStack.size() > 0) {
// check if we have an existing session ready to use
while (mEmptyRequestStack.size() > 0) {
int local_handle = mEmptyRequestStack.top();
mEmptyRequestStack.pop();
auto resultIt = mRequestSessionMap.find(local_handle);
@ -185,10 +186,10 @@ Session* SessionManager::getNewSession(int* handle)
mRequestSessionMap.erase(local_handle);
}
}
}
// else create new RequestSession Object
// calculate random handle
// check if already exist, if get new
@ -210,7 +211,7 @@ Session* SessionManager::getNewSession(int* handle)
//printf("[SessionManager::getNewSession] handle: %ld, sum: %u\n", newHandle, mRequestSessionMap.size());
mWorkingMutex.unlock();
return requestSession;
//return nullptr;
}
@ -230,7 +231,7 @@ bool SessionManager::releaseSession(int requestHandleSession)
return false;
}
//mWorkingMutex.lock();
auto it = mRequestSessionMap.find(requestHandleSession);
if (it == mRequestSessionMap.end()) {
//printf("[SessionManager::releaseRequestSession] requestSession with handle: %d not found\n", requestHandleSession);
@ -241,16 +242,15 @@ bool SessionManager::releaseSession(int requestHandleSession)
// delete session, not reuse as workaround for server freeze bug
mRequestSessionMap.erase(requestHandleSession);
/*mRequestSessionMap.erase(requestHandleSession);
delete session;
mWorkingMutex.unlock();
return true;
*/
// check if dead locked
if (session->tryLock()) {
session->unlock();
if (!session->isDeadLocked()) {
session->reset();
session->setActive(false);
}
@ -263,9 +263,9 @@ bool SessionManager::releaseSession(int requestHandleSession)
mWorkingMutex.unlock();
return true;
}
// change request handle we don't want session hijacking
// hardcoded disabled session max
if (mEmptyRequestStack.size() > 100) {
mRequestSessionMap.erase(requestHandleSession);
@ -284,11 +284,11 @@ bool SessionManager::releaseSession(int requestHandleSession)
mWorkingMutex.unlock();
return true;
}
session->setHandle(newHandle);
mRequestSessionMap.insert(std::pair<int, Session*>(newHandle, session));
mEmptyRequestStack.push(newHandle);
mWorkingMutex.unlock();
return true;
}
@ -353,13 +353,11 @@ Session* SessionManager::getSession(int handle)
}
if (0 == handle) return nullptr;
Session* result = nullptr;
try {
//Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
mWorkingMutex.tryLock(500);
}
catch (Poco::TimeoutException &ex) {
printf("[SessionManager::getSession] exception timout mutex: %s\n", ex.displayText().data());
return result;
if(!mWorkingMutex.tryLock(500)) {
printf("[SessionManager::getSession] exception timout mutex: \n");
return result;
}
//mWorkingMutex.lock();
auto it = mRequestSessionMap.find(handle);
@ -375,14 +373,12 @@ Session* SessionManager::getSession(int handle)
return nullptr;
}
if (0 == iResult) {
//printf("[SessionManager::getSession] session isn't active\n");
mWorkingMutex.unlock();
return nullptr;
}
//result->setActive(true);
result->updateTimeout();
}
//printf("[SessionManager::getSession] handle: %ld\n", handle);
mWorkingMutex.unlock();
return result;
}
@ -417,8 +413,8 @@ Session* SessionManager::findByUserId(int userId)
}
//mWorkingMutex.lock();
for (auto it = mRequestSessionMap.begin(); it != mRequestSessionMap.end(); it++) {
while (it->second->isDeadLocked())
{
while (it->second->isDeadLocked())
{
it = mRequestSessionMap.erase(it);
mDeadLockedSessionCount++;
auto em = ErrorManager::getInstance();
@ -483,7 +479,7 @@ std::vector<Session*> SessionManager::findAllByUserId(int userId)
Session* SessionManager::findByEmail(const std::string& email)
{
assert(email.size() > 0);
try {
//Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
mWorkingMutex.tryLock(500);
@ -604,23 +600,23 @@ bool SessionManager::checkPwdValidation(const std::string& pwd, NotificationList
if (!isValid(pwd, VALIDATE_PASSWORD)) {
errorReciver->addError(new Error(
lang->gettext("Password"),
lang->gettext("Password"),
lang->gettext("Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character (@$!%*?&+-_)!")));
// @$!%*?&+-
if (pwd.size() < 8) {
errorReciver->addError(new Error(
lang->gettext("Password"),
lang->gettext("Password"),
lang->gettext("Your password is to short!")));
}
else if (!isValid(pwd, VALIDATE_HAS_LOWERCASE_LETTER)) {
errorReciver->addError(new Error(
lang->gettext("Password"),
lang->gettext("Password"),
lang->gettext("Your password does not contain lowercase letters!")));
}
else if (!isValid(pwd, VALIDATE_HAS_UPPERCASE_LETTER)) {
errorReciver->addError(new Error(
lang->gettext("Password"),
lang->gettext("Password"),
lang->gettext("Your password does not contain any capital letters!")));
}
else if (!isValid(pwd, VALIDATE_HAS_NUMBER)) {

View File

@ -24,6 +24,7 @@
enum SessionValidationTypes {
VALIDATE_NAME,
VALIDATE_USERNAME,
VALIDATE_EMAIL,
VALIDATE_PASSWORD,
VALIDATE_PASSPHRASE,

View File

@ -194,20 +194,14 @@ namespace controller {
return json;
}
int User::login(const std::string& password)
Poco::AutoPtr<SecretKeyCryptography> User::createSecretKey(const std::string& password)
{
if (!mPassword.isNull() && mPassword->hasKey()) {
return 2;
}
auto observer = SingletonTaskObserver::getInstance();
std::unique_lock<std::shared_mutex> _lock(mSharedMutex);
assert(mPassword.isNull());
auto model = getModel();
auto email_hash = observer->makeHash(model->getEmail());
if (observer->getTaskCount(email_hash, TASK_OBSERVER_PASSWORD_CREATION) > 0) {
return -3;
return nullptr;
}
observer->addTask(email_hash, TASK_OBSERVER_PASSWORD_CREATION);
Poco::AutoPtr<SecretKeyCryptography> authenticated_encryption(new SecretKeyCryptography);
@ -215,7 +209,23 @@ namespace controller {
authenticated_encryption->createKey(model->getEmail(), password);
observer->removeTask(email_hash, TASK_OBSERVER_PASSWORD_CREATION);
return authenticated_encryption;
}
int User::login(const std::string& password)
{
std::unique_lock<std::shared_mutex> _lock(mSharedMutex);
if (!mPassword.isNull() && mPassword->hasKey()) {
return 2;
}
assert(mPassword.isNull());
auto authenticated_encryption = createSecretKey(password);
if (authenticated_encryption.isNull()) {
return -3;
}
auto model = getModel();
if (authenticated_encryption->getKeyHashed() == model->getPasswordHashed())
{
// printf("[User::login] password key hashed is the same as saved password hash\n");

View File

@ -96,6 +96,9 @@ namespace controller {
//! - create authenticated encryption key from password and email
//! - compare hash with in db saved hash
int login(const std::string& password);
//! \brief simply check if password is correct, independent if user is already logged in or not
Poco::AutoPtr<SecretKeyCryptography> createSecretKey(const std::string& password);
// ***********************************************************************************
// password related

View File

@ -21,7 +21,7 @@
***************************************************************************/
/*!
\brief Container Wrapper class for mutex protected container
changed to poco mutex for gradido login server
default mutex from poco is recursive so it is some heavy thing
@ -47,7 +47,7 @@ namespace UniLib {
// \return false if mutex was locked from another thread
bool tryLock();
inline void unlock() { mLastSucceededLock = ""; mWorkMutex.unlock(); }
inline void unlock() { mWorkMutex.unlock(); mLastSucceededLock = ""; }
inline const std::string& getLastSucceededLock() { return mLastSucceededLock; }
protected:
@ -58,4 +58,4 @@ namespace UniLib {
}
}
#endif //__DR_UNIVERSUM_LIB_LIB_MULTITHREAD_CONTAINER_H__
#endif //__DR_UNIVERSUM_LIB_LIB_MULTITHREAD_CONTAINER_H__

View File

@ -84,29 +84,30 @@ void Session::reset()
int Session::isActive()
{
int ret = 0;
try {
mWorkMutex.tryLock(100);
}
catch (Poco::TimeoutException &ex) {
return -1;
}
if(!mWorkMutex.tryLock(100)) {
return -1;
}
ret = (int)mActive;
unlock();
try {
unlock();
} catch(Poco::SystemException& ex) {
addError(new ParamError("Session::isActive", "exception unlocking mutex", ex.what()));
return -1;
}
return ret;
}
bool Session::isDeadLocked()
{
try {
mWorkMutex.tryLock(200);
unlock();
return false;
}
catch (Poco::Exception& ex) {
}
return true;
if(!mWorkMutex.tryLock(200)) {
return true;
};
unlock();
return false;
}
bool Session::setActive(bool active)
@ -922,12 +923,11 @@ bool Session::useOrGeneratePassphrase(const std::string& passphase)
bool Session::lastTransactionTheSame(Poco::AutoPtr<model::gradido::Transaction> newTransaction)
{
assert(!newTransaction.isNull());
lock();
Poco::ScopedLock<Poco::Mutex> _lock(mWorkMutex);
if (mLastTransaction.isNull()) {
return false;
}
bool result = mLastTransaction->isTheSameTransaction(newTransaction);
unlock();
return result;
}

View File

@ -358,6 +358,9 @@ namespace model {
}
//UniLib::controller::TaskPtr transaction_send_task(new SendTransactionTask(Poco::AutoPtr<Transaction>(this, true)));
//transaction_send_task->scheduleTask(transaction_send_task);
auto pt = PendingTasksManager::getInstance();
pt->removeTask(Poco::AutoPtr<Transaction>(this, true));
return 1 == runSendTransaction();
//return true;
}
@ -507,9 +510,6 @@ namespace model {
addError(new ParamError(function_name, "unknown error", TransactionValidationToString(result)));
//sendErrorsAsEmail();
}
auto pt = PendingTasksManager::getInstance();
pt->reportErrorToCommunityServer(Poco::AutoPtr<Transaction>(this, true), error_name, error_description);
addError(new ParamError(function_name, error_name, error_description));
}
return -1;
@ -563,13 +563,13 @@ namespace model {
auto result = json_request.request("putTransaction", param);
json_request.getWarnings(&json_request);
if (JSON_REQUEST_RETURN_OK == result)
{
if (JSON_REQUEST_RETURN_OK == result)
{
if (!json_request.errorCount()) {
finishSuccess();
}
else {
getErrors(&json_request);
getErrors(&json_request);
return -1;
}
return 1;

View File

@ -56,9 +56,9 @@ namespace model {
try {
auto res = select.execute();
if (1 == res) { return true; }
}
catch (Poco::Exception& ex) {
catch (Poco::Exception& ex) {
addError(new ParamError(getTableName(), "mysql error by select id", ex.displayText().data()));
addError(new ParamError(getTableName(), "data set: ", toString().data()));
}
@ -126,20 +126,22 @@ namespace model {
{
Poco::ScopedLock<Poco::Mutex> _lock(mWorkMutex);
mReferenceCount++;
//printf("[ModelBase::duplicate] new value: %d\n", mReferenceCount);
}
void ModelBase::release()
{
Poco::ScopedLock<Poco::Mutex> _lock(mWorkMutex);
if(mReferenceCount <= 0) {
throw Poco::Exception("ModelBase already released", getTableName());
}
Poco::ScopedLock<Poco::Mutex> _lock(mWorkMutex);
mReferenceCount--;
//printf("[ModelBase::release] new value: %d\n", mReferenceCount);
if (0 == mReferenceCount) {
delete this;
return;
}
}
Poco::Data::Statement ModelBase::_loadFromDB(Poco::Data::Session session, const std::vector<std::string>& fieldNames, MysqlConditionType conditionType/* = MYSQL_CONDITION_AND*/)
@ -180,7 +182,7 @@ namespace model {
Poco::Mutex& timeMutex = ServerConfig::g_TimeMutex;
int year, month, day, hour, minute, second;
// ex: 2009-10-29
// ex: 2009-10-29
if (sscanf(decodedDateString.data(), "%d-%d-%dT%d:%dZ", &year, &month, &day, &hour, &minute) != EOF) {
time_t rawTime;
time(&rawTime);

View File

@ -70,7 +70,11 @@ namespace model
{
SHARED_LOCK;
temp = mResultJsonString;
if(!mResultJsonString.size()) {
return new Poco::JSON::Object;
}
}
Poco::JSON::Parser parser;
Poco::Dynamic::Var result;
try

View File

@ -68,7 +68,6 @@ TEST(TestJsonCheckUsername, UsernameWithoutGroup)
ASSERT_TRUE(msg.isString());
ASSERT_EQ(msg.toString(), "no group given");
delete result;
}
@ -89,6 +88,8 @@ TEST(TestJsonCheckUsername, ExistingUsername)
ASSERT_FALSE(msg.isEmpty());
ASSERT_TRUE(msg.isString());
ASSERT_EQ(msg.toString(), "username already in use");
delete result;
}
TEST(TestJsonCheckUsername, NewUsername)
@ -103,6 +104,8 @@ TEST(TestJsonCheckUsername, NewUsername)
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
ASSERT_EQ(state.toString(), "success");
delete result;
}
TEST(TestJsonCheckUsername, UsernameExistInOtherGroup)
@ -118,6 +121,7 @@ TEST(TestJsonCheckUsername, UsernameExistInOtherGroup)
ASSERT_TRUE(state.isString());
ASSERT_EQ(state.toString(), "success");
delete result;
}

View File

@ -0,0 +1,100 @@
#include "gtest/gtest.h"
#include "JSONInterface/JsonResetPassword.h"
#include "TestJsonResetPassword.h"
#include "lib/Profiler.h"
void TestJsonResetPassword::SetUp()
{
auto sm = SessionManager::getInstance();
//sm->init();
mUserSession = sm->getNewSession();
auto user = controller::User::create();
user->load("Nikola_Tesla@email.de");
mUserSession->setUser(user);
}
void TestJsonResetPassword::TearDown()
{
auto sm = SessionManager::getInstance();
if (!mUserSession) {
sm->releaseSession(mUserSession);
}
}
TEST_F(TestJsonResetPassword, WithoutSession)
{
JsonResetPassword jsonCall;
Poco::JSON::Object::Ptr params = new Poco::JSON::Object;
params->set("password", "ashze_Sja/63");
auto result = jsonCall.handle(params);
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
ASSERT_EQ(state.toString(), "error");
auto msg = result->get("msg");
ASSERT_FALSE(msg.isEmpty());
ASSERT_TRUE(msg.isString());
ASSERT_EQ(msg.toString(), "missing session_id");
}
TEST_F(TestJsonResetPassword, WithoutPassword)
{
JsonResetPassword jsonCall;
Poco::JSON::Object::Ptr params = new Poco::JSON::Object;
params->set("session_id", mUserSession->getHandle());
auto result = jsonCall.handle(params);
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
ASSERT_EQ(state.toString(), "error");
auto msg = result->get("msg");
ASSERT_FALSE(msg.isEmpty());
ASSERT_TRUE(msg.isString());
ASSERT_EQ(msg.toString(), "password missing");
}
TEST_F(TestJsonResetPassword, InvalidPassword)
{
JsonResetPassword jsonCall;
Poco::JSON::Object::Ptr params = new Poco::JSON::Object;
params->set("session_id", mUserSession->getHandle());
params->set("password", "ash");
auto result = jsonCall.handle(params);
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
if ((ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_ALLOW_ALL_PASSWORDS) == ServerConfig::UNSECURE_ALLOW_ALL_PASSWORDS) {
ASSERT_EQ(state.toString(), "success");
}
else {
ASSERT_EQ(state.toString(), "error");
auto msg = result->get("msg");
ASSERT_FALSE(msg.isEmpty());
ASSERT_TRUE(msg.isString());
ASSERT_EQ(msg.toString(), "password isn't valid");
}
}
TEST_F(TestJsonResetPassword, ValidPassword)
{
JsonResetPassword jsonCall;
Poco::JSON::Object::Ptr params = new Poco::JSON::Object;
params->set("session_id", mUserSession->getHandle());
params->set("password", "hath6/&Sja");
auto result = jsonCall.handle(params);
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
ASSERT_EQ(state.toString(), "success");
}

View File

@ -0,0 +1,20 @@
#ifndef __GRADIDO_LOGIN_SERVER_TEST_JSON_INTERFACE_TEST_JSON_RESET_PASSWORD_H
#define __GRADIDO_LOGIN_SERVER_TEST_JSON_INTERFACE_TEST_JSON_RESET_PASSWORD_H
#include "gtest/gtest.h"
#include "SingletonManager/SessionManager.h"
#include "Poco/JSON/Object.h"
class TestJsonResetPassword : public ::testing::Test
{
protected:
void SetUp() override;
void TearDown() override;
Session* mUserSession;
};
#endif //__GRADIDO_LOGIN_SERVER_TEST_JSON_INTERFACE_TEST_JSON_RESET_PASSWORD_H

View File

@ -0,0 +1,317 @@
#include "gtest/gtest.h"
#include "JSONInterface/JsonUpdateUserInfos.h"
#include "TestJsonUpdateUserInfos.h"
#include "lib/Profiler.h"
void TestJsonUpdateUserInfos::SetUp()
{
auto sm = SessionManager::getInstance();
//sm->init();
mUserSession = sm->getNewSession();
auto user = controller::User::create();
user->load("Jeet_bb@gmail.com");
mUserSession->setUser(user);
}
void TestJsonUpdateUserInfos::TearDown()
{
auto sm = SessionManager::getInstance();
if (!mUserSession) {
sm->releaseSession(mUserSession);
}
}
Poco::JSON::Object::Ptr TestJsonUpdateUserInfos::chooseAccount(const Poco::JSON::Object::Ptr update)
{
Poco::JSON::Object::Ptr params = new Poco::JSON::Object;
params->set("email", mUserSession->getNewUser()->getModel()->getEmail());
params->set("update", update);
return params;
}
TEST_F(TestJsonUpdateUserInfos, EmptyOldPassword)
{
JsonUpdateUserInfos jsonCall(mUserSession);
ASSERT_EQ(mUserSession->loadUser("Jeet_bb@gmail.com", "TestP4ssword&H"), USER_COMPLETE);
Poco::JSON::Object::Ptr update = new Poco::JSON::Object;
update->set("User.password", "haLL1o_/%s");
auto params = chooseAccount(update);
Profiler timeUsed;
auto result = jsonCall.handle(params);
ASSERT_LE(timeUsed.millis(), 300);
auto errors = result->get("errors");
ASSERT_TRUE(errors.isArray());
auto valid_values_obj = result->get("valid_values");
ASSERT_TRUE(valid_values_obj.isInteger());
int valid_values = 0;
valid_values_obj.convert(valid_values);
ASSERT_EQ(valid_values, 0);
//User.password_old not found
Poco::JSON::Array error_array = errors.extract<Poco::JSON::Array>();
ASSERT_EQ(error_array.size(), 1);
ASSERT_EQ(error_array.getElement<std::string>(0), "User.password_old not found");
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
ASSERT_EQ(state.toString(), "error");
delete result;
}
TEST_F(TestJsonUpdateUserInfos, OnlyOldPassword)
{
JsonUpdateUserInfos jsonCall(mUserSession);
Poco::JSON::Object::Ptr update = new Poco::JSON::Object;
update->set("User.password_old", "TestP4ssword&H");
auto params = chooseAccount(update);
Profiler timeUsed;
auto result = jsonCall.handle(params);
ASSERT_LE(timeUsed.millis(), 200);
auto errors = result->get("errors");
ASSERT_TRUE(errors.isArray());
auto valid_values_obj = result->get("valid_values");
ASSERT_TRUE(valid_values_obj.isInteger());
int valid_values = 0;
valid_values_obj.convert(valid_values);
ASSERT_EQ(valid_values, 0);
Poco::JSON::Array error_array = errors.extract<Poco::JSON::Array>();
ASSERT_EQ(error_array.size(), 0);
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
ASSERT_EQ(state.toString(), "success");
delete result;
}
TEST_F(TestJsonUpdateUserInfos, WrongPassword)
{
JsonUpdateUserInfos jsonCall(mUserSession);
ASSERT_EQ(mUserSession->loadUser("Jeet_bb@gmail.com", "TestP4ssword&H"), USER_COMPLETE);
Poco::JSON::Object::Ptr update = new Poco::JSON::Object;
update->set("User.password", "newPassword");
update->set("User.password_old", "TestP4sswordH");
auto params = chooseAccount(update);
Profiler timeUsed;
auto result = jsonCall.handle(params);
ASSERT_GE(timeUsed.millis(), ServerConfig::g_FakeLoginSleepTime * 0.75);
auto errors = result->get("errors");
ASSERT_TRUE(errors.isArray());
auto valid_values_obj = result->get("valid_values");
ASSERT_TRUE(valid_values_obj.isInteger());
int valid_values = 0;
valid_values_obj.convert(valid_values);
ASSERT_EQ(valid_values, 0);
Poco::JSON::Array error_array = errors.extract<Poco::JSON::Array>();
ASSERT_EQ(error_array.size(), 1);
ASSERT_EQ(error_array.getElement<std::string>(0), "User.password_old didn't match");
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
ASSERT_EQ(state.toString(), "error");
delete result;
}
TEST_F(TestJsonUpdateUserInfos, EmptyPassword)
{
JsonUpdateUserInfos jsonCall(mUserSession);
Poco::JSON::Object::Ptr update = new Poco::JSON::Object;
update->set("User.password", "");
update->set("User.password_old", "TestP4sswordH");
auto params = chooseAccount(update);
Profiler timeUsed;
auto result = jsonCall.handle(params);
ASSERT_LE(timeUsed.millis(), 200);
auto errors = result->get("errors");
ASSERT_TRUE(errors.isArray());
auto valid_values_obj = result->get("valid_values");
ASSERT_TRUE(valid_values_obj.isInteger());
int valid_values = 0;
valid_values_obj.convert(valid_values);
ASSERT_EQ(valid_values, 0);
Poco::JSON::Array error_array = errors.extract<Poco::JSON::Array>();
ASSERT_EQ(error_array.size(), 1);
ASSERT_EQ(error_array.getElement<std::string>(0), "User.password is empty");
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
ASSERT_EQ(state.toString(), "error");
delete result;
}
TEST_F(TestJsonUpdateUserInfos, NewPasswordSameAsOldPassword)
{
JsonUpdateUserInfos jsonCall(mUserSession);
ASSERT_EQ(mUserSession->loadUser("Jeet_bb@gmail.com", "TestP4ssword&H"), USER_COMPLETE);
Poco::JSON::Object::Ptr update = new Poco::JSON::Object;
update->set("User.password", "TestP4ssword&H");
update->set("User.password_old", "TestP4ssword&H");
auto params = chooseAccount(update);
Profiler timeUsed;
auto result = jsonCall.handle(params);
ASSERT_GE(timeUsed.millis(), ServerConfig::g_FakeLoginSleepTime * 0.75);
auto errors = result->get("errors");
ASSERT_TRUE(errors.isArray());
auto valid_values_obj = result->get("valid_values");
ASSERT_TRUE(valid_values_obj.isInteger());
int valid_values = 0;
valid_values_obj.convert(valid_values);
Poco::JSON::Array error_array = errors.extract<Poco::JSON::Array>();
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
EXPECT_EQ(valid_values, 0);
ASSERT_EQ(error_array.size(), 0);
ASSERT_EQ(state.toString(), "success");
delete result;
}
TEST_F(TestJsonUpdateUserInfos, PasswordNotSecureEnough)
{
JsonUpdateUserInfos jsonCall(mUserSession);
ASSERT_EQ(mUserSession->loadUser("Jeet_bb@gmail.com", "TestP4ssword&H"), USER_COMPLETE);
Poco::JSON::Object::Ptr update = new Poco::JSON::Object;
update->set("User.password", "newPassword");
update->set("User.password_old", "TestP4ssword&H");
auto params = chooseAccount(update);
Profiler timeUsed;
auto result = jsonCall.handle(params);
ASSERT_GE(timeUsed.millis(), ServerConfig::g_FakeLoginSleepTime * 0.75);
auto errors = result->get("errors");
ASSERT_TRUE(errors.isArray());
auto valid_values_obj = result->get("valid_values");
ASSERT_TRUE(valid_values_obj.isInteger());
int valid_values = 0;
valid_values_obj.convert(valid_values);
Poco::JSON::Array error_array = errors.extract<Poco::JSON::Array>();
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
if ((ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_ALLOW_ALL_PASSWORDS) == ServerConfig::UNSECURE_ALLOW_ALL_PASSWORDS) {
EXPECT_EQ(valid_values, 1);
ASSERT_EQ(error_array.size(), 0);
ASSERT_EQ(state.toString(), "success");
}
else {
EXPECT_EQ(valid_values, 0);
ASSERT_EQ(error_array.size(), 2);
ASSERT_EQ(error_array.getElement<std::string>(0), "User.password isn't valid");
ASSERT_EQ(state.toString(), "error");
}
delete result;
}
TEST_F(TestJsonUpdateUserInfos, PasswordCorrect)
{
JsonUpdateUserInfos jsonCall(mUserSession);
Poco::JSON::Object::Ptr update = new Poco::JSON::Object;
update->set("User.password", "uasjUs7ZS/as12");
if ((ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_ALLOW_ALL_PASSWORDS) == ServerConfig::UNSECURE_ALLOW_ALL_PASSWORDS) {
ASSERT_EQ(mUserSession->loadUser("Jeet_bb@gmail.com", "newPassword"), USER_COMPLETE);
update->set("User.password_old", "newPassword");
}
else {
ASSERT_EQ(mUserSession->loadUser("Jeet_bb@gmail.com", "TestP4ssword&H"), USER_COMPLETE);
update->set("User.password_old", "TestP4ssword&H");
}
auto params = chooseAccount(update);
Profiler timeUsed;
auto result = jsonCall.handle(params);
ASSERT_GE(timeUsed.millis(), ServerConfig::g_FakeLoginSleepTime * 0.75);
auto errors = result->get("errors");
ASSERT_TRUE(errors.isArray());
auto valid_values_obj = result->get("valid_values");
ASSERT_TRUE(valid_values_obj.isInteger());
int valid_values = 0;
valid_values_obj.convert(valid_values);
Poco::JSON::Array error_array = errors.extract<Poco::JSON::Array>();
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
EXPECT_EQ(valid_values, 1);
ASSERT_EQ(error_array.size(), 0);
ASSERT_EQ(state.toString(), "success");
delete result;
}
//*/
TEST_F(TestJsonUpdateUserInfos, NoChanges)
{
JsonUpdateUserInfos jsonCall(mUserSession);
Poco::JSON::Object::Ptr update = new Poco::JSON::Object;
update->set("User.first_name", "Darios");
update->set("User.last_name", "Bruder");
auto params = chooseAccount(update);
Profiler timeUsed;
auto result = jsonCall.handle(params);
auto errors = result->get("errors");
ASSERT_TRUE(errors.isArray());
auto valid_values_obj = result->get("valid_values");
ASSERT_TRUE(valid_values_obj.isInteger());
int valid_values = 0;
valid_values_obj.convert(valid_values);
Poco::JSON::Array error_array = errors.extract<Poco::JSON::Array>();
auto state = result->get("state");
ASSERT_FALSE(state.isEmpty());
ASSERT_TRUE(state.isString());
EXPECT_EQ(valid_values, 0);
ASSERT_EQ(error_array.size(), 0);
ASSERT_EQ(state.toString(), "success");
delete result;
}

View File

@ -0,0 +1,23 @@
#ifndef __GRADIDO_LOGIN_SERVER_TEST_JSON_INTERFACE_TEST_JSON_UPDATE_USER_INFOS_H
#define __GRADIDO_LOGIN_SERVER_TEST_JSON_INTERFACE_TEST_JSON_UPDATE_USER_INFOS_H
#include "gtest/gtest.h"
#include "SingletonManager/SessionManager.h"
#include "Poco/JSON/Object.h"
class TestJsonUpdateUserInfos : public ::testing::Test
{
protected:
void SetUp() override;
void TearDown() override;
Poco::JSON::Object::Ptr chooseAccount(const Poco::JSON::Object::Ptr update);
Session* mUserSession;
std::string mEmail;
};
#endif //__GRADIDO_LOGIN_SERVER_TEST_JSON_INTERFACE_TEST_JSON_UPDATE_USER_INFOS_H

View File

@ -13,9 +13,12 @@
#include "Poco/SplitterChannel.h"
#include "../SingletonManager/ConnectionManager.h"
#include "../SingletonManager/SessionManager.h"
#include "../lib/Profiler.h"
#include "Crypto/SecretKeyCryptography.h"
std::list<Test*> gTests;
@ -27,7 +30,7 @@ void fillTests()
// gTests.push_back(new LoginTest());
}
void runMysql(std::string sqlQuery)
int runMysql(std::string sqlQuery)
{
auto cm = ConnectionManager::getInstance();
auto session = cm->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
@ -38,8 +41,10 @@ void runMysql(std::string sqlQuery)
mysqlStatement.execute(true);
}
catch (Poco::Exception& ex) {
printf("exception in runMysql: %s\n", ex.displayText().data());
std::clog << "exception in runMysql: " << ex.displayText() << std::endl;
return -1;
}
return 0;
}
int load(int argc, char* argv[]) {
@ -47,6 +52,9 @@ int load(int argc, char* argv[]) {
std::clog << "[load]" << std::endl;
Poco::AutoPtr<Poco::Util::LayeredConfiguration> test_config(new Poco::Util::LayeredConfiguration);
std::string config_file_name = Poco::Path::config() + "grd_login/grd_login_test.properties";
#ifdef WIN32
config_file_name = "Gradido_LoginServer_Test.properties";
#endif
if(argc > 1 && strlen(argv[1]) > 4) {
config_file_name = argv[1];
}
@ -66,11 +74,11 @@ int load(int argc, char* argv[]) {
if (!ServerConfig::initServerCrypto(*test_config)) {
//printf("[Gradido_LoginServer::%s] error init server crypto\n", __FUNCTION__);
printf("[load] error init server crypto");
std::clog << "[load] error init server crypto" << std::endl;
return -1;
}
if (!ServerConfig::loadMnemonicWordLists()) {
printf("[load] error in loadMnemonicWordLists");
std::clog << "[load] error in loadMnemonicWordLists" << std::endl;
return -2;
}
@ -79,6 +87,9 @@ int load(int argc, char* argv[]) {
ServerConfig::g_CPUScheduler = new UniLib::controller::CPUSheduler(worker_count, "Default Worker");
ServerConfig::g_CryptoCPUScheduler = new UniLib::controller::CPUSheduler(2, "Crypto Worker");
ServerConfig::g_disableEmail = true;
SessionManager::getInstance()->init();
// load up connection configs
// register MySQL connector
@ -95,7 +106,7 @@ int load(int argc, char* argv[]) {
}
catch (Poco::Exception& ex) {
// maybe we in docker environment and db needs some time to start up
printf("Poco Exception by connecting to db: %s, let's try again\n", ex.displayText().data());
std::clog << "Poco Exception by connecting to db: " << ex.displayText() << ", let's try again" << std::endl;
}
if(!connected) {
// let's wait 10 seconds
@ -110,7 +121,7 @@ int load(int argc, char* argv[]) {
connected = true;
}
} catch(Poco::Exception& ex) {
printf("Poco Exception by connecting to db: %s, let's wait another 10 seconds\n", ex.displayText().data());
std::clog << "Poco Exception by connecting to db: " << ex.displayText() << ", let's wait another 10 seconds" << std::endl;
}
}
if(!connected) {
@ -118,10 +129,21 @@ int load(int argc, char* argv[]) {
try {
conn->setConnectionsFromConfig(*test_config, CONNECTION_MYSQL_LOGIN_SERVER);
} catch(Poco::Exception& ex) {
printf("Poco Exception by connecting to db: %s, exit\n", ex.displayText().data());
std::clog << "Poco Exception by connecting to db: " << ex.displayText() << ", exit" << std::endl;
return -4;
}
}
// password hash from user 2 in test user entry
Poco::UInt64 precalculated_password_hash = 10417562666175322069;
std::clog << "measure Time for secret key generation..." << std::endl;
Profiler timeForArgon2;
SecretKeyCryptography secret_cryptografie;
secret_cryptografie.createKey("Jeet_bb@gmail.com", "TestP4ssword&H");
ServerConfig::g_FakeLoginSleepTime = timeForArgon2.millis();
std::clog << "time for secret key generation: " << timeForArgon2.string() << std::endl;
std::string log_Path = "/var/log/grd_login/";
//#ifdef _WIN32
@ -144,6 +166,7 @@ int load(int argc, char* argv[]) {
log.error("Test Error");
//errorLog
//printf("try connect php server mysql \n");
@ -157,31 +180,47 @@ int load(int argc, char* argv[]) {
"users"
};
for (int i = 0; i < 2; i++) {
runMysql("TRUNCATE " + tables[i]);
runMysql("ALTER TABLE " + tables[i] + " AUTO_INCREMENT = 1");
if (runMysql("TRUNCATE " + tables[i])) {
return -1;
}
if (runMysql("ALTER TABLE " + tables[i] + " AUTO_INCREMENT = 1")) {
return -1;
}
}
std::stringstream ss;
// password = TestP4ssword&H
ss << "INSERT INTO `users` (`id`, `email`, `first_name`, `last_name`, `username`, `password`, `pubkey`, `privkey`, `created`, `email_checked`, `passphrase_shown`, `language`, `disabled`, `group_id`) VALUES "
<< "(1, 'd_schultz32@gmx.de', 'DDD', 'Schultz', 'Diddel', 13134558453895551556, 0x146d3fb9e88abc0fca0b0091c1ab1b32b399be037436f340befa8bf004461889, 0x0dcc08960f45f631fe23bc7ddee0724cedc9ec0c861ce30f5091d20ffd96062d08ca215726fb9bd64860c754772e945eea4cc872ed0a36c7b640e8b0bf7a873ec6765fa510711622341347ce2307b5ce, '2020-02-20 16:05:44', 1, 0, 'de', 0, 1), "
<< "(2, 'Jeet_bb@gmail.com', 'Darios', 'Bruder', 'Jeet', 12910944485867375321, 0x952e215a21d4376b4ac252c4bf41e156e1498e1b6b8ccf2a6826d96712f4f461, 0x4d40bf0860655f728312140dc3741e897bc2d13d00ea80a63e2961046a5a7bd8315397dfb488b89377087bc1a5f4f3af8ffdcf203329ae23ba04be7d38ad3852699d90ff1fc00e5b1ca92b64cc59c01f, '2020-02-20 16:05:44', 1, 0, 'de', 0, 1), "
<< "(3, 'Tiger_231@yahoo.com', 'Dieter', 'Schultz', 'Tiger', 13528673707291575501, 0xb539944bf6444a2bfc988244f0c0c9dc326452be9b8a2a43fcd90663719f4f6d, 0x5461fda60b719b65ba00bd6298e48410c4cbf0e89deb13cc784ba8978cf047454e8556ee3eddc8487ee835c33a83163bc8d8babbf2a5c431876bc0a0c114ff0a0d6b57baa12cf8f23c64fb642c862db5, '2020-02-20 16:05:45', 1, 0, 'de', 0, 1), "
<< "(4, 'Nikola_Tesla@email.de', 'Nikola', 'Tesla', 'Erfinder', 15522411320147607375, 0x476b059744f08b0995522b484c90f8d2f47d9b59f4b3c96d9dc0ae6ab7b84979, 0x5277bf044cba4fec64e6f4d38da132755b029161231daefc9a7b4692ad37e05cdd88e0a2c2215baf854dd3a813578c214167af1113607e9f999ca848a7598ba5068e38f2a1afb097e4752a88024d79c8, '2020-02-20 16:05:46', 1, 0, 'de', 0, 1), "
<< "(5, 'Elfenhausen@arcor.de', 'Thomas', 'Markuk', 'Elf', 7022671043835614958, 0xb1584e169d60a7e771d3a348235dfd7b5f9e8235fcc26090761a0264b0daa6ff, 0xb46fb7110bf91e28f367aa80f84d1bbd639b6f689f4b0aa28c0f71529232df9bf9ee0fb02fa4c1b9f5a6799c82d119e5646f7231d011517379faaacf6513d973ac3043d4c786490ba62d56d75b86164d, '2020-02-20 16:05:46', 1, 0, 'de', 0, 1), "
<< "(6, 'coin-info12@gradido.net', 'coin-info12', 'Test', 'Test Username', 1548398919826089202, 0x4046ae49c1b620f2a321aba0c874fa2bc7ba844ab808bb0eeb18a908d468db14, 0x9522657ecd7456eedf86d065aa087ba7a94a8961a8e4950d044136155d38fe0840f2c0a2876ce055b3eaa6e9ab95c5feba89e535e0434fb2648d94d6e6ec68211aa2ea9e42d1ccd40b6b3c31e41f848e, '2020-02-20 16:05:47', 1, 0, 'de', 0, 1), "
<< "(7, 'AlexWesper@gmail.com', 'Alex', 'Wesper', 'Wespe', 5822761891727948301, 0xb13ede3402abb8f29722b14fec0a2006ae7a3a51fb677cd6a2bbd797ac6905a5, 0x6aa39d7670c64a31639c7d89b874ad929b2eaeb2e5992dbad71b6cea700bf9e3c6cf866d0f0fdc22b44a0ebf51a860799e880ef86266199931dd0a301e5552db44b9b7fa99ed5945652bc7b31eff767c, '2020-02-20 16:05:47', 1, 0, 'de', 0, 1); ";
runMysql(ss.str());
<< "(1, 'd_schultz32@gmx.de', 'DDD', 'Schultz', 'Diddel', 18242007140018938940, 0x69f2fefd6fa6947a370b9f8d3147f6617cf67416517ce25cb2d63901c666933c, 0x567f3e623a1899d1f8d69190c5799433c134ce0137c0c38cc0347874586d6234a19f2a0b484e6cc1863502e580ae6c17db1131f29a35eba45a46be29c7ee592940a3bd3ad519075fdeed6e368f0eb818, '2020-02-20 16:05:44', 1, 0, 'de', 0, 1), ";
// if this isn't the same, some tests will fail, so we update the test data here.
if (secret_cryptografie.getKeyHashed() != precalculated_password_hash) {
ss << "(2, 'Jeet_bb@gmail.com', 'Darios', 'Bruder', 'Jeet', " << secret_cryptografie.getKeyHashed() << ", 0, 0, '2020-02-20 16:05:44', 1, 0, 'de', 0, 1), ";
}
else {
ss << "(2, 'Jeet_bb@gmail.com', 'Darios', 'Bruder', 'Jeet', 10417562666175322069, 0x6afd24f46eb79a839281fe537a1888155b102d4fbe0613ea92d51845bd8036cb, 0xe7aed71cd4ae2d1aba9343ffb3822b759f972e41b63a6032b7f6c69f566217784c2e7bcdaeaa2f7dd16bf3b6f1540b22afa65fc054550a9296454c6ecdbd4131eac7f9c703318a867e666691e1808a6e, '2020-02-20 16:05:44', 1, 0, 'de', 0, 1), ";
}
ss << "(3, 'Tiger_231@yahoo.com', 'Dieter', 'Schultz', 'Tiger', 13790258844849208764, 0x9a79a5daea92218608fa1e3a657d78961dc04c97ff996cc0ea17d6896b5368e6, 0x4993a156a120728f0fa93fc63ab01482ed85ecf433c729c8426c4bb93f0b7ce6142fda531b11f5d5e925acd1d2e55fdfef94fe07dbb78d43322f7df1234c7251aa58946c96ec6e551395f0fb5e87decf, '2020-02-20 16:05:45', 1, 0, 'de', 0, 1), "
<< "(4, 'Nikola_Tesla@email.de', 'Nikola', 'Tesla', 'Erfinder', 1914014100253540772, 0x1c199421a66070afb28cb7c37de98865b28924bff26161bb65faaf5695050ee3, 0xe38ca460ca748954b29d79f0e943eed3ba85e7e13b18f69349666e31a8e3b06c9df105171796b37b4201895a2f3fe8ec8bf58a181700caaa5752a94a968c50e90ebb6280002a056126b2055ff75d69d1, '2020-02-20 16:05:46', 1, 0, 'de', 0, 1), "
<< "(5, 'Elfenhausen@arcor.de', 'Thomas', 'Markuk', 'Elf', 8105871797752167168, 0x98d703f0ea1def3ef9e6265a76281d125a94c80665425bd7a844580ec1a2ce98, 0x63612a1d07d78a0c945d765a10a30d9de2be602e79e3f39268d731bc6f7fa945d7d04c638000bae089ac058263f52e7c1f2c3550b35b5727e41523f2f592781add65d12b8b8c0b3226f32174cfa1bcee, '2020-02-20 16:05:46', 1, 0, 'de', 0, 1), "
<< "(6, 'coin-info12@gradido.net', 'coin-info12', 'Test', 'Test Username', 9005874071610817324, 0xb3ee1c82a9877f664d05364106e259621b2e203bfbb5323edb7b597051efecc2, 0xa039da7d59e2475dd1aaa635f803ec1aeffc2506e7a96a934bf8d7cf4ac2a96dc962d4e1bdf8e11c5ce7e18189edc36014b89e9e72628004ec5901be6c407a955efb5142a1ee9a2f3aed888125a44aa2, '2020-02-20 16:05:47', 1, 0, 'de', 0, 1), "
<< "(7, 'AlexWesper@gmail.com', 'Alex', 'Wesper', 'Wespe', 7264393213873828644, 0x735a5c22ebe84ab1d6453991d50019b677b82b0663b023c30127ec906ee9b59a, 0xaec30051ad3ab2d2132a76e9dfe5a396d2dfbcc83a4eb27223b4da8803893959af9e29c6963f9e73eddc447cb3d3995527b94054e7fdecd7d5f8cb45c3954ff9bb2c9e0374f2124b3170301f990c5d7d, '2020-02-20 16:05:47', 1, 0, 'de', 0, 1); ";
if (runMysql(ss.str())) {
return -1;
}
ss.str(std::string());
ss << "INSERT INTO `groups` (`id`, `alias`, `name`, `url`, `description`) VALUES"
<< "(1, 'gdd1', 'Gradido1', 'gdd1.gradido.com', 'Der erste offizielle Gradido Server (zum Testen)'), "
<< "(2, 'gdd_test', 'Gradido Test', 'gdd1.gradido.com', 'Testgroup (zum Testen)'); ";
runMysql(ss.str());
if (runMysql(ss.str())) {
return -1;
}
ss.str(std::string());
printf("init db in : %s\n", timeUsed.string().data());
std::clog << "init db in : " << timeUsed.string() << std::endl;
fillTests();
@ -198,10 +237,10 @@ int run()
std::clog << "[Gradido_LoginServer_Test::run]" << std::endl;
for (std::list<Test*>::iterator it = gTests.begin(); it != gTests.end(); it++)
{
//printf("running: %s\n", it->getName());
printf("running test: %s\n", (*it)->getName());
std::string name = (*it)->getName();
std::clog << "running test: " << name << std::endl;
try {
if (!(*it)->test()) printf("success\n");
if (!(*it)->test()) std::clog << "Success" << std::endl;
} catch(std::exception& ex) {
std::clog << "exception in running test: " << ex.what() << std::endl;
}
@ -209,7 +248,7 @@ int run()
return 0;
}
void ende()
void endegTests()
{
for (std::list<Test*>::iterator it = gTests.begin(); it != gTests.end(); it++)
{
@ -219,6 +258,7 @@ void ende()
}
gTests.clear();
}
@ -226,20 +266,24 @@ int main(int argc, char** argv)
{
try {
if (load(argc, argv) < 0) {
printf("early exit\n");
std::clog << "early exit" << std::endl;
return -42;
}
} catch(std::exception& ex) {
printf("no catched exception while loading: %s\n", ex.what());
std::string exception_text = ex.what();
std::clog << "no catched exception while loading: " << exception_text << std::endl;
}
//printf ("\nStack Limit = %ld and %ld max\n", limit.rlim_cur, limit.rlim_max);
run();
ende();
::testing::InitGoogleTest(&argc, argv);
endegTests();
::testing::InitGoogleTest(&argc, argv);
auto result = RUN_ALL_TESTS();
SessionManager::getInstance()->deinitalize();
ServerConfig::unload();
return result;
}

View File

@ -0,0 +1,102 @@
<%@ page class="TestUserGenerator" %>
<%@ page form="true" %>
<%@ page baseClass="PageRequestMessagedHandler" %>
<%@ header include="HTTPInterface/PageRequestMessagedHandler.h" %>
<%!
#include "Crypto/SecretKeyCryptography.h"
#include "Crypto/KeyPairEd25519.h"
#include "ServerConfig.h"
#include "lib/DataTypeConverter.h"
#include "controller/User.h"
%>
<%%
const char* pageName = "Test User Generator";
// needed for header_large
auto user = controller::User::create();
std::string email;
std::string password_hashed;
std::string pubkey_hex;
std::string privkey_hex_encrypted;
std::string passphrase_str;
bool user_created = false;
// add
if(!form.empty()) {
email = form.get("email", "");
auto password = form.get("password", "");
if(email == "") {
addError(new Error("Create User", "E-Mail is empty!"));
}
else if(password == "") {
addError(new Error("Create User", "Password is empty!"));
}
else
{
auto passphrase = Passphrase::generate(&ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER]);
passphrase_str = passphrase->getString();
auto key_pair = KeyPairEd25519::create(passphrase);
Poco::AutoPtr<SecretKeyCryptography> secret_key = new SecretKeyCryptography;
secret_key->createKey(email, password);
password_hashed = std::to_string(secret_key->getKeyHashed());
auto privkey_encrypted = key_pair->getCryptedPrivKey(secret_key);
privkey_hex_encrypted = DataTypeConverter::binToHex(privkey_encrypted);
pubkey_hex = key_pair->getPublicKeyHex();
user_created = true;
delete key_pair;
}
}
// select all
auto groups = controller::Group::listAll();
//auto groups = controller::Group::load("gdd1");
//std::vector<Poco::SharedPtr<controller::Group>> groups;
%><%@ include file="include/header_large.cpsp" %>
<%= getErrorsHtml() %>
<div class="center-form-container">
<div class="center-form-title">
<h3>Einen neuen User anlegen</h3>
</div>
<div class="center-form-form">
<form method="POST">
<label class="form-label" for="email">Email</label>
<input class="form-control" id="email" type="text" name="email"/>
<label class="form-label" for="password">Password</label>
<input class="form-control" id="password" type="text" name="password"/>
<input class="center-form-submit form-button" type="submit" name="submit" value="Create User">
</form>
</div>
<% if(user_created) { %>
<div class="content-list">
<div class="content-list-title">
<h2>Generierte Daten</h2>
</div>
<div class="content-list-table">
<div class="row">
<div class="cell header-cell c4">E-Mail</div>
<div class="cell c4"><%= email %></div>
</div>
<div class="row">
<div class="cell header-cell c4">Password hash</div>
<div class="cell c3"><%= password_hashed %></div>
</div>
<div class="row">
<div class="cell header-cell c4">public key</div>
<div class="cell c5">0x<%= pubkey_hex %></div>
</div>
<div class="row">
<div class="cell header-cell c4">private key encrypted</div>
<div class="cell c6">0x<%= privkey_hex_encrypted %></div>
</div>
<div class="row">
<div class="cell header-cell c4">Passphrase</div>
<div class="cell c10"><%= passphrase_str %></div>
</div>
</div>
</div>
<pre>'<%= email %>', <%= password_hashed %>, 0x<%= pubkey_hex %>, 0x<%= privkey_hex_encrypted %></pre>
<% } %>
</div>
<%@ include file="include/footer.cpsp" %>

View File

@ -1,6 +1,6 @@
{
"name": "gradido",
"version": "1.0.2",
"version": "1.1.0",
"description": "Gradido",
"main": "index.js",
"repository": "git@github.com:gradido/gradido.git",