diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4606566df..aec97a1d8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -170,7 +170,7 @@ jobs: - name: Checkout code uses: actions/checkout@v2 ########################################################################## - # BUILD NGINX DOCKER IMAGE ############################################# + # BUILD NGINX DOCKER IMAGE ############################################### ########################################################################## - name: nginx | Build `test` image run: | @@ -182,6 +182,35 @@ jobs: name: docker-nginx-test path: /tmp/nginx.tar + ############################################################################## + # JOB: LOCALES FRONTEND ###################################################### + ############################################################################## + locales_frontend: + name: Locales - Frontend + runs-on: ubuntu-latest + needs: [build_test_frontend] + steps: + ########################################################################## + # CHECKOUT CODE ########################################################## + ########################################################################## + - name: Checkout code + uses: actions/checkout@v2 + ########################################################################## + # DOWNLOAD DOCKER IMAGE ################################################## + ########################################################################## + - name: Download Docker Image (Frontend) + uses: actions/download-artifact@v2 + with: + name: docker-frontend-test + path: /tmp + - name: Load Docker Image + run: docker load < /tmp/frontend.tar + ########################################################################## + # LOCALES FRONTEND ####################################################### + ########################################################################## + - name: frontend | Locales + run: docker run --rm gradido/frontend:test yarn run locales + ############################################################################## # JOB: LINT FRONTEND ######################################################### ############################################################################## @@ -206,7 +235,7 @@ jobs: - name: Load Docker Image run: docker load < /tmp/frontend.tar ########################################################################## - # LINT FRONTEND ########################################################### + # LINT FRONTEND ########################################################## ########################################################################## - name: frontend | Lint run: docker run --rm gradido/frontend:test yarn run lint @@ -316,7 +345,7 @@ jobs: report_name: Coverage Frontend type: lcov result_path: ./coverage/lcov.info - min_coverage: 60 + min_coverage: 66 token: ${{ github.token }} ############################################################################## diff --git a/README.md b/README.md index a3dc7b746..fe6f2e7f3 100644 --- a/README.md +++ b/README.md @@ -39,4 +39,3 @@ git submodule update --recursive --init ## Useful Links - [Gradido.net](https://gradido.net/) -- [Discord](https://discord.gg/kA3zBAKQDC) diff --git a/docu/Concepts/Snippets/Authorization/concept.md b/docu/Concepts/Snippets/Authorization/concept.md new file mode 100644 index 000000000..5d189b3be --- /dev/null +++ b/docu/Concepts/Snippets/Authorization/concept.md @@ -0,0 +1,28 @@ +# Authorization and Private Keys +## Keys +For creating transactions ed25519 keys are used for signing. +As long the user is the only controlling the private key he is the only one +how can sign transactions on his behalf. +It is a core concept of all crypto currencies and important for the concept, +that the user has full control over his data. + +Usually crypto currencies like bitcoin or iota save the keys on local system, +maybe additional protected with a password which is used to encrypt the keys. + +## Gradido +Gradido should be easy to use, so we must offer a solution for everyone not that fit +with computer, as easy to use like paypal. +For that role we have the Login-Server. +It stores the private keys of the user encrypted with there email and password. +Additional it stores the passphrase which can be used to generate the private key, +encryted with server admin public key. So only the server admin can access the keys +with his private key. [not done yet] +It is needed for passwort reset if a user has forgetten his password. + +But for the entire concept Login-Server isn't the only way to store the private keys. +For users which has more experience with computer and especially with crypto currencies +it should be a way to keep there private keys by themselfs. + +For example a Desktop- or Handy-App which store the keys locally maybe additional encrypted. +Maybe it is possible to use Stronghold from iota for that. +With that the user don't need to use the Login-Server. \ No newline at end of file diff --git a/docu/Concepts/Snippets/Authorization/jwt.md b/docu/Concepts/Snippets/Authorization/jwt.md new file mode 100644 index 000000000..158613e36 --- /dev/null +++ b/docu/Concepts/Snippets/Authorization/jwt.md @@ -0,0 +1,29 @@ +# How JWT could be used for authorization with and without Login-Server +## What we need +The only encrypted data in db are the private key. +Every other data could be accessed without login, depending on frontend and backend code. +So we need only a way to prove the backend that we have access to the private key. + +## JWT +JWT is perfect for that. +We can use JWT to store the public key of the user as UUID for finding his data in db, +signing it with the private key. So even if the backend is running in multiple instances, +on every request is it possible to check the JWT token, that the signature is signed with +the private key, belonging to the public key. +The only thing the backend cannot do with that is signing a transaction. +That can only be done by the Login-Server or a Desktop or Handy-App storing the private key locally. +With that we have universal way for authorization against the backend. +We could additional store if we like to sign transactions local or with Login-Server and the Login-Server url. + +## JWT and Login-Server +Login-Server uses Poco version 1.9.4 but unfortunately Poco only introduces jwt from version 1.10. +And Updating to 1.10 needs some work because some things have changed in Poco 1.10. + +## JWT signature algorithms +In JWT standard ed25519 don't seemd to play a role. +We must find out if we can use the ed25519 keys together with one of the signature algorithms +in JWT standard or we must use **crypto_sign_verify_detached** from libsodium even it is nonstandard +to verify signature created with ed25519 keys and libsodiums **crypto_sign_detached** function. + + + diff --git a/docu/Concepts/Snippets/Authorization/session_id.md b/docu/Concepts/Snippets/Authorization/session_id.md new file mode 100644 index 000000000..c8c64a87a --- /dev/null +++ b/docu/Concepts/Snippets/Authorization/session_id.md @@ -0,0 +1,16 @@ +# Session Id Authorization +## Login-Server +With every login, the Login-Server creates a session with a random id, +storing it in memory. For Login email and password are needed. +From email and an additional app-secret (**crypto.app_secret** in Login-Server config) a sha512 hash will be genereted, named **hash512_salt**. +With sodium function *crypto_pwhash* with **hash512_salt** and user password a secret encryption key will be calculated. +*crypto_pwhash* uses argon2 algorithmus to have a CPU hard calculation. Currently it is configured for < 0.5s. +So it is harder to use brute-force attacks to guess the password. Even if someone gets hands on the data saved in db. + +With sodium function *crypto_shorthash* a hash will be calculated from the secret encryption key and server crypto key (**crypto.server_key** in Login-Server config, hex encoded, 16 Bytes, 32 Character hex encoded) +and compared against saved hash in db. If they identical user has successfull logged in. +The secret encryption key will be stored in memory together with the user session and client ip from which login call came. +The session_id will be returned. +The session will be hold in memory for 15 minutes default, can be changed in Login-Server config field **session.timeout** + + diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 5ec90fe81..879fdf4e0 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -72,6 +72,9 @@ RUN yarn run build ################################################################################## FROM build as test +# Install Additional Software +RUN apk add --no-cache bash jq + # Run command CMD /bin/sh -c "yarn run dev" diff --git a/frontend/src/components/Collapse/Collapse.vue b/frontend/src/components/Collapse/Collapse.vue deleted file mode 100644 index e0ea57945..000000000 --- a/frontend/src/components/Collapse/Collapse.vue +++ /dev/null @@ -1,79 +0,0 @@ - - - - - diff --git a/frontend/src/components/Collapse/CollapseItem.vue b/frontend/src/components/Collapse/CollapseItem.vue deleted file mode 100644 index 7d6b8ded1..000000000 --- a/frontend/src/components/Collapse/CollapseItem.vue +++ /dev/null @@ -1,91 +0,0 @@ - - - diff --git a/frontend/src/components/PaginationButtons.spec.js b/frontend/src/components/PaginationButtons.spec.js index 7a03d0443..c6ae816cc 100644 --- a/frontend/src/components/PaginationButtons.spec.js +++ b/frontend/src/components/PaginationButtons.spec.js @@ -3,11 +3,17 @@ import PaginationButtons from './PaginationButtons' const localVue = global.localVue +const propsData = { + totalRows: 42, + perPage: 12, + value: 1, +} + describe('PaginationButtons', () => { let wrapper const Wrapper = () => { - return mount(PaginationButtons, { localVue }) + return mount(PaginationButtons, { localVue, propsData }) } describe('mount', () => { @@ -19,34 +25,20 @@ describe('PaginationButtons', () => { expect(wrapper.find('div.pagination-buttons').exists()).toBeTruthy() }) - it('has previous page button disabled by default', () => { - expect(wrapper.find('button.previous-page').attributes('disabled')).toBe('disabled') - }) - - it('has bext page button disabled by default', () => { - expect(wrapper.find('button.next-page').attributes('disabled')).toBe('disabled') - }) - - it('shows the text "1 / 1" by default"', () => { - expect(wrapper.find('p.text-center').text()).toBe('1 / 1') - }) - describe('with active buttons', () => { - beforeEach(async () => { - await wrapper.setProps({ - hasNext: true, - hasPrevious: true, - }) - }) - - it('emits show-previous when previous page button is clicked', () => { - wrapper.find('button.previous-page').trigger('click') - expect(wrapper.emitted('show-previous')).toBeTruthy() - }) - - it('emits show-next when next page button is clicked', () => { + it('emits input next page button is clicked', async () => { wrapper.find('button.next-page').trigger('click') - expect(wrapper.emitted('show-next')).toBeTruthy() + await wrapper.vm.$nextTick() + expect(wrapper.emitted().input[0]).toEqual([2]) + }) + + it('emits input when previous page button is clicked', async () => { + wrapper.setProps({ value: 2 }) + wrapper.setData({ currentValue: 2 }) + await wrapper.vm.$nextTick() + wrapper.find('button.previous-page').trigger('click') + await wrapper.vm.$nextTick() + expect(wrapper.emitted().input[0]).toEqual([1]) }) }) }) diff --git a/frontend/src/components/PaginationButtons.vue b/frontend/src/components/PaginationButtons.vue index 252301388..3b80481c6 100644 --- a/frontend/src/components/PaginationButtons.vue +++ b/frontend/src/components/PaginationButtons.vue @@ -1,16 +1,16 @@ @@ -52,23 +48,12 @@ export default { }, data() { return { - transactionsGdt: { default: () => [] }, + transactionsGdt: [], transactionGdtCount: { type: Number, default: 0 }, currentPage: 1, pageSize: 25, } }, - computed: { - hasNext() { - return this.currentPage * this.pageSize < this.transactionGdtCount - }, - hasPrevious() { - return this.currentPage > 1 - }, - totalPages() { - return Math.ceil(this.transactionGdtCount / this.pageSize) - }, - }, methods: { async updateGdt() { this.$apollo @@ -85,25 +70,21 @@ export default { } = result this.transactionsGdt = listGDTEntries.gdtEntries this.transactionGdtCount = listGDTEntries.count + window.scrollTo(0, 0) }) .catch((error) => { this.$toasted.error(error.message) }) }, - showNext() { - this.currentPage++ - this.updateGdt() - window.scrollTo(0, 0) - }, - showPrevious() { - this.currentPage-- - this.updateGdt() - window.scrollTo(0, 0) - }, }, mounted() { this.updateGdt() }, + watch: { + currentPage() { + this.updateGdt() + }, + }, }