mirror of
https://github.com/IT4Change/gradido.git
synced 2026-03-01 12:44:43 +00:00
Merge branch 'master' into fix_small_bugs
This commit is contained in:
commit
3fa05cf2c5
1
.bun-version
Normal file
1
.bun-version
Normal file
@ -0,0 +1 @@
|
||||
1.3.0
|
||||
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@ -403,10 +403,8 @@ jobs:
|
||||
##########################################################################
|
||||
# Push release tag to GitHub #############################################
|
||||
##########################################################################
|
||||
- name: yarn install
|
||||
run: yarn install
|
||||
- name: generate changelog
|
||||
run: yarn auto-changelog --commit-limit 0 --latest-version ${{ env.VERSION }} --unreleased-only
|
||||
run: npx auto-changelog --commit-limit 0 --latest-version ${{ env.VERSION }} --unreleased-only
|
||||
- name: package-version-to-git-release
|
||||
continue-on-error: true # Will fail if tag exists
|
||||
id: create_release
|
||||
|
||||
7
.github/workflows/test_admin_interface.yml
vendored
7
.github/workflows/test_admin_interface.yml
vendored
@ -54,6 +54,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
@ -93,6 +95,9 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Admin Interface | Locales
|
||||
run: cd admin && yarn locales
|
||||
run: cd admin && bun locales
|
||||
9
.github/workflows/test_backend.yml
vendored
9
.github/workflows/test_backend.yml
vendored
@ -56,6 +56,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
@ -81,6 +83,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
@ -98,6 +102,9 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Backend | Locales
|
||||
run: cd backend && yarn locales
|
||||
run: cd backend && bun locales
|
||||
8
.github/workflows/test_config.yml
vendored
8
.github/workflows/test_config.yml
vendored
@ -28,16 +28,18 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: bun install --filter config-schema --frozen-lockfile
|
||||
|
||||
- name: typecheck
|
||||
run: cd config-schema && yarn typecheck
|
||||
run: cd config-schema && bun run typecheck
|
||||
|
||||
- name: unit tests
|
||||
run: cd config-schema && yarn test
|
||||
run: cd config-schema && bun run test
|
||||
|
||||
|
||||
4
.github/workflows/test_core.yml
vendored
4
.github/workflows/test_core.yml
vendored
@ -29,9 +29,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
|
||||
4
.github/workflows/test_database.yml
vendored
4
.github/workflows/test_database.yml
vendored
@ -53,6 +53,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
@ -76,6 +78,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
|
||||
2
.github/workflows/test_dht_node.yml
vendored
2
.github/workflows/test_dht_node.yml
vendored
@ -53,6 +53,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
|
||||
8
.github/workflows/test_e2e.yml
vendored
8
.github/workflows/test_e2e.yml
vendored
@ -17,6 +17,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: Boot up test system | docker-compose mariadb mailserver
|
||||
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach mariadb mailserver
|
||||
@ -70,7 +72,7 @@ jobs:
|
||||
id: e2e-tests
|
||||
run: |
|
||||
cd e2e-tests/
|
||||
yarn run cypress run
|
||||
bun cypress run
|
||||
|
||||
- name: End-to-end tests | if tests failed, compile html report
|
||||
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
|
||||
@ -120,6 +122,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: Boot up test system | docker-compose mariadb mailserver
|
||||
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach mariadb mailserver
|
||||
@ -203,6 +207,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: Boot up test system | docker-compose mariadb mailserver
|
||||
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach mariadb mailserver
|
||||
|
||||
2
.github/workflows/test_federation.yml
vendored
2
.github/workflows/test_federation.yml
vendored
@ -53,6 +53,8 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
|
||||
13
.github/workflows/test_frontend.yml
vendored
13
.github/workflows/test_frontend.yml
vendored
@ -52,12 +52,14 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: bun install --filter frontend --frozen-lockfile
|
||||
|
||||
- name: Frontend | Unit tests
|
||||
run: cd frontend && yarn test
|
||||
run: cd frontend && bun run test
|
||||
|
||||
lint:
|
||||
if: needs.files-changed.outputs.config == 'true' || needs.files-changed.outputs.frontend == 'true'
|
||||
@ -77,7 +79,9 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
bun install --filter frontend --frozen-lockfile
|
||||
@ -106,6 +110,9 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Frontend | Locales
|
||||
run: cd frontend && yarn locales
|
||||
run: cd frontend && bun run locales
|
||||
|
||||
6
.github/workflows/test_shared.yml
vendored
6
.github/workflows/test_shared.yml
vendored
@ -30,13 +30,15 @@ jobs:
|
||||
|
||||
- name: install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: '.bun-version'
|
||||
|
||||
- name: install dependencies
|
||||
run: bun install --filter shared --frozen-lockfile
|
||||
|
||||
- name: typecheck
|
||||
run: cd shared && yarn typecheck
|
||||
run: cd shared && bun run typecheck
|
||||
|
||||
- name: unit tests
|
||||
run: cd shared && yarn test
|
||||
run: cd shared && bun run test
|
||||
|
||||
|
||||
12
.vscode/launch.json
vendored
12
.vscode/launch.json
vendored
@ -9,7 +9,7 @@
|
||||
"request": "launch",
|
||||
"name": "Database Debug Tests",
|
||||
"stopOnEntry": true,
|
||||
"runtimeExecutable": "yarn",
|
||||
"runtimeExecutable": "bun",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"test"
|
||||
@ -25,7 +25,7 @@
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "DHT-Node Debug Tests",
|
||||
"runtimeExecutable": "yarn",
|
||||
"runtimeExecutable": "bun",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"test:debug"
|
||||
@ -42,7 +42,7 @@
|
||||
"request": "launch",
|
||||
"name": "DHT-Node Debug",
|
||||
"stopOnEntry": true,
|
||||
"runtimeExecutable": "yarn",
|
||||
"runtimeExecutable": "bun",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev"
|
||||
@ -58,7 +58,7 @@
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Federation Debug Tests",
|
||||
"runtimeExecutable": "yarn",
|
||||
"runtimeExecutable": "bun",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"test:debug"
|
||||
@ -75,7 +75,7 @@
|
||||
"request": "launch",
|
||||
"name": "Federation Debug",
|
||||
"stopOnEntry": true,
|
||||
"runtimeExecutable": "yarn",
|
||||
"runtimeExecutable": "bun",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev"
|
||||
@ -92,7 +92,7 @@
|
||||
"request": "launch",
|
||||
"name": "Backend Debug",
|
||||
"stopOnEntry": true,
|
||||
"runtimeExecutable": "yarn",
|
||||
"runtimeExecutable": "bun",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev"
|
||||
|
||||
33
CHANGELOG.md
33
CHANGELOG.md
@ -4,8 +4,41 @@ 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).
|
||||
|
||||
#### [v2.7.0](https://github.com/gradido/gradido/compare/v2.7.0...v2.7.0)
|
||||
|
||||
- fixes [`414ff8a`](https://github.com/gradido/gradido/commit/414ff8ac5a7477109f80123ccca5c4c8ed4511b2)
|
||||
|
||||
#### [v2.7.0](https://github.com/gradido/gradido/compare/2.6.1...v2.7.0)
|
||||
|
||||
> 15 October 2025
|
||||
|
||||
- feat(frontend): gradido id under avatar instead of email [`#3543`](https://github.com/gradido/gradido/pull/3543)
|
||||
- refactor(backend): add and use template function updateAllDefinedAndChanged in update user and community [`#3546`](https://github.com/gradido/gradido/pull/3546)
|
||||
- fix(backend): check for openai thread timeout [`#3549`](https://github.com/gradido/gradido/pull/3549)
|
||||
- feat(frontend): paste community and user in recipient field of gdd send dialog [`#3542`](https://github.com/gradido/gradido/pull/3542)
|
||||
- fix(backend): allow reading gmsApiKey admins only [`#3547`](https://github.com/gradido/gradido/pull/3547)
|
||||
- feat(frontend): make link send confirmation dialog more accurate [`#3548`](https://github.com/gradido/gradido/pull/3548)
|
||||
- fix(federation): fix bug [`#3545`](https://github.com/gradido/gradido/pull/3545)
|
||||
- feat(frontend): modify frontend for cross community redeem link disbursement [`#3537`](https://github.com/gradido/gradido/pull/3537)
|
||||
- fix(frontend): decimal comma to point on contribution form [`#3539`](https://github.com/gradido/gradido/pull/3539)
|
||||
- feat(backend): introduce security in disbursement handshake [`#3523`](https://github.com/gradido/gradido/pull/3523)
|
||||
- feat(frontend): update texts for overview and contribution message [`#3532`](https://github.com/gradido/gradido/pull/3532)
|
||||
- feat(frontend): add link to gdd in overview [`#3531`](https://github.com/gradido/gradido/pull/3531)
|
||||
- feat(other): add infos for using logging in tests [`#3530`](https://github.com/gradido/gradido/pull/3530)
|
||||
- feat(frontend): increase memo [`#3527`](https://github.com/gradido/gradido/pull/3527)
|
||||
- feat(admin): add hiero topic id like gms api key [`#3524`](https://github.com/gradido/gradido/pull/3524)
|
||||
- fix(other): publish only on release [`#3529`](https://github.com/gradido/gradido/pull/3529)
|
||||
|
||||
#### [2.6.1](https://github.com/gradido/gradido/compare/2.3.1...2.6.1)
|
||||
|
||||
> 14 August 2025
|
||||
|
||||
- fix(other): remove mariadb from publish workflow [`#3528`](https://github.com/gradido/gradido/pull/3528)
|
||||
- fix(database): docker setup [`#3526`](https://github.com/gradido/gradido/pull/3526)
|
||||
- fix(other): fix problems with bun and e2e [`#3525`](https://github.com/gradido/gradido/pull/3525)
|
||||
- feat(backend): introduce security in x com tx handshake [`#3520`](https://github.com/gradido/gradido/pull/3520)
|
||||
- feat(backend): openid connect routes [`#3518`](https://github.com/gradido/gradido/pull/3518)
|
||||
- chore(release): v2.6.1 beta [`#3521`](https://github.com/gradido/gradido/pull/3521)
|
||||
- refactor(frontend): transaction and contribution form [`#3519`](https://github.com/gradido/gradido/pull/3519)
|
||||
- fix(federation): fix some attack vectors in communities handshake [`#3517`](https://github.com/gradido/gradido/pull/3517)
|
||||
- fix(other): start sh when called from webhook [`#3515`](https://github.com/gradido/gradido/pull/3515)
|
||||
|
||||
@ -48,8 +48,6 @@ RUN bun install --global turbo
|
||||
# Add bun's global bin directory to PATH
|
||||
ENV PATH="/root/.bun/bin:${PATH}"
|
||||
|
||||
#RUN yarn global add turbo
|
||||
|
||||
# Settings
|
||||
## Expose Container Port
|
||||
EXPOSE ${BACKEND_PORT}
|
||||
@ -85,10 +83,6 @@ COPY --chown=app:app ./ ./
|
||||
# yarn install
|
||||
RUN bun install --frozen-lockfile --non-interactive
|
||||
|
||||
# try with bun, use yarn if problems occur
|
||||
# go into admin folder and use yarn to install local dependencies which need to use nohoist for @vee-validate/i18n which isn't supported by bun
|
||||
#RUN bun install --frozen-lockfile
|
||||
|
||||
|
||||
##################################################################################
|
||||
# TEST ###########################################################################
|
||||
@ -136,7 +130,7 @@ WORKDIR ${DOCKER_WORKDIR}
|
||||
# Copy only the build artifacts from the previous build stage
|
||||
COPY --chown=app:app --from=build /app/node_modules ./node_modules
|
||||
COPY --chown=app:app --from=build /app/package.json ./package.json
|
||||
COPY --chown=app:app --from=build /app/yarn.lock ./yarn.lock
|
||||
COPY --chown=app:app --from=build /app/bun.lock ./bun.lock
|
||||
COPY --chown=app:app --from=build /app/turbo.json ./turbo.json
|
||||
# and Turbo cache to prevent rebuilding
|
||||
COPY --chown=app:app --from=build /tmp/turbo ./tmp/turbo
|
||||
|
||||
29
README.md
29
README.md
@ -88,13 +88,6 @@ bun install
|
||||
bun install --global turbo@^2
|
||||
```
|
||||
|
||||
If this does not work, try to use [yarn](https://classic.yarnpkg.com/en/docs/install) instead
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
yarn global add turbo@^2
|
||||
```
|
||||
|
||||
- **Development Mode (Hot-Reload)**:
|
||||
Launches Gradido with hot-reloading for fast iteration.
|
||||
|
||||
@ -124,10 +117,6 @@ The installation of dockers depends on your selected product package from the [d
|
||||
* In case the docker desktop will not start correctly because of previous docker installations, then please clean the used directories of previous docker installation - `C:\Users` - before you retry starting docker desktop. For further problems executing docker desktop please take a look in this description "[logs and trouble shooting](https://docs.docker.com/desktop/windows/troubleshoot/)"
|
||||
* In case your docker desktop installation causes high memory consumption per vmmem process, then please take a look at this description "[vmmen process consuming too much memory (Docker Desktop)](https://dev.to/tallesl/vmmen-process-consuming-too-much-memory-docker-desktop-273p)"
|
||||
|
||||
### yarn
|
||||
|
||||
For the Gradido build process the yarn package manager will be used. Please download and install [yarn for windows](https://phoenixnap.com/kb/yarn-windows) by following the instructions there.
|
||||
|
||||
|
||||
### ⚡ Workspaces and Bun Compatibility
|
||||
The project now uses **Workspaces**, and work is ongoing to make all modules **Bun-compatible**. You can currently use `bun install`, but not all modules are fully Bun-compatible yet.
|
||||
@ -143,12 +132,10 @@ To install dependencies with Bun:
|
||||
bun install
|
||||
```
|
||||
|
||||
Note that some modules are still not fully compatible with Bun. Therefore, continue using **Yarn** for development if you run into any issues.
|
||||
|
||||
### EMFILE: too many open files
|
||||
With
|
||||
```bash
|
||||
yarn docker_dev
|
||||
bun docker_dev
|
||||
```
|
||||
or also
|
||||
```bash
|
||||
@ -161,11 +148,11 @@ which you are working on in dev mode and the rest in production mode.
|
||||
|
||||
For example if you are only working on the frontend, you can start the frontend in dev mode and the rest in production mode:
|
||||
```bash
|
||||
yarn docker_dev frontend
|
||||
bun docker_dev frontend
|
||||
```
|
||||
and in another bash
|
||||
```bash
|
||||
yarn docker backend admin database nginx --no-deps
|
||||
bun docker backend admin database nginx --no-deps
|
||||
```
|
||||
or local with turbo
|
||||
```bash
|
||||
@ -218,10 +205,10 @@ Currently Modules `frontend`, `admin`, `share` and `core` running the tests in p
|
||||
`database`, `backend`, `dht-node` and `federation` are running the tests still serially.
|
||||
|
||||
### Clear
|
||||
In root folder calling `yarn clear` will clear all turbo caches, node_modules and build folders of all workspaces for a clean rebuild.
|
||||
In root folder calling `bun clear` will clear all turbo caches, node_modules and build folders of all workspaces for a clean rebuild.
|
||||
|
||||
```bash
|
||||
yarn clear
|
||||
bun clear
|
||||
```
|
||||
|
||||
|
||||
@ -254,13 +241,13 @@ To generate the Changelog and set a new Version you should use the following com
|
||||
|
||||
```bash
|
||||
git fetch --all
|
||||
yarn release
|
||||
bun release
|
||||
```
|
||||
|
||||
The first command `git fetch --all` will make sure you have all tags previously defined which is required to generate a correct changelog. The second command `yarn release` will execute the changelog tool and set version numbers in the main package and sub-packages. It is required to do `yarn install` before you can use this command.
|
||||
The first command `git fetch --all` will make sure you have all tags previously defined which is required to generate a correct changelog. The second command `bun release` will execute the changelog tool and set version numbers in the main package and sub-packages. It is required to do `bun install` before you can use this command.
|
||||
After generating a new version you should commit the changes. This will be the CHANGELOG.md and several package.json files. This commit will be omitted in the changelog.
|
||||
|
||||
Note: The Changelog will be regenerated with all tags on release on the external builder tool, but will not be checked in there. The Changelog on the github release will therefore always be correct, on the repo it might be incorrect due to missing tags when executing the `yarn release` command.
|
||||
Note: The Changelog will be regenerated with all tags on release on the external builder tool, but will not be checked in there. The Changelog on the github release will therefore always be correct, on the repo it might be incorrect due to missing tags when executing the `bun release` command.
|
||||
|
||||
## How the different .env work on deploy
|
||||
|
||||
|
||||
@ -57,7 +57,9 @@ WORKDIR ${DOCKER_WORKDIR}
|
||||
FROM base as bun-base
|
||||
|
||||
RUN apk update && apk add --no-cache curl tar bash
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
COPY .bun-version .bun-version
|
||||
RUN BUN_VERSION=$(cat .bun-version) && \
|
||||
curl -fsSL https://bun.com/install | bash -s "bun-v${BUN_VERSION}"
|
||||
# Add bun's global bin directory to PATH
|
||||
ENV PATH="/root/.bun/bin:${PATH}"
|
||||
|
||||
|
||||
@ -1,26 +1,48 @@
|
||||
# admin
|
||||
|
||||
## Project setup
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
yarn serve
|
||||
turbo dev
|
||||
```
|
||||
or from root folder:
|
||||
|
||||
```
|
||||
turbo admin#dev
|
||||
```
|
||||
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
yarn build
|
||||
turbo build
|
||||
```
|
||||
or from root folder:
|
||||
|
||||
```
|
||||
turbo admin#build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
yarn lint
|
||||
turbo lint
|
||||
```
|
||||
or from root folder:
|
||||
|
||||
```
|
||||
turbo admin#lint
|
||||
```
|
||||
|
||||
### Unit tests
|
||||
```
|
||||
yarn test
|
||||
turbo test
|
||||
```
|
||||
For filtering out single tests:
|
||||
```
|
||||
turbo test -- <test_name>
|
||||
```
|
||||
Everything after -- will be passed to vitest.
|
||||
|
||||
or from root folder:
|
||||
|
||||
```
|
||||
turbo admin#test
|
||||
```
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"description": "Administration Interface for Gradido",
|
||||
"main": "index.js",
|
||||
"author": "Gradido Academy - https://www.gradido.net",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.0",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@ -61,6 +61,7 @@
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"config-schema": "*",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^10.0.0",
|
||||
"dotenv-webpack": "^7.0.3",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
@ -71,7 +72,7 @@
|
||||
"eslint-plugin-prettier": "^5.2.3",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "8.7.1",
|
||||
"joi": "^17.13.3",
|
||||
"joi": "17.13.3",
|
||||
"jsdom": "^25.0.0",
|
||||
"lightningcss": "^1.30.1",
|
||||
"mock-apollo-client": "^1.2.1",
|
||||
|
||||
@ -54,8 +54,9 @@ WORKDIR ${DOCKER_WORKDIR}
|
||||
FROM base as bun-base
|
||||
|
||||
RUN apt update && apt install -y --no-install-recommends ca-certificates curl bash unzip
|
||||
#RUN apk update && apk add --no-cache curl tar bash
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
COPY .bun-version .bun-version
|
||||
RUN BUN_VERSION=$(cat .bun-version) && \
|
||||
curl -fsSL https://bun.com/install | bash -s "bun-v${BUN_VERSION}"
|
||||
# Add bun's global bin directory to PATH
|
||||
ENV PATH="/root/.bun/bin:${PATH}"
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
## Seed DB
|
||||
|
||||
```bash
|
||||
yarn seed
|
||||
turbo seed
|
||||
```
|
||||
|
||||
Deletes all data in database. Then seeds data in database.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.0",
|
||||
"private": false,
|
||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||
"repository": "https://github.com/gradido/gradido/backend",
|
||||
@ -41,6 +41,7 @@
|
||||
"@swc/cli": "^0.7.3",
|
||||
"@swc/core": "^1.11.24",
|
||||
"@swc/helpers": "^0.5.17",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/email-templates": "^10.0.4",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/faker": "^5.5.9",
|
||||
@ -75,7 +76,7 @@
|
||||
"helmet": "^5.1.1",
|
||||
"i18n": "^0.15.1",
|
||||
"jest": "27.2.4",
|
||||
"joi": "^17.13.3",
|
||||
"joi": "17.13.3",
|
||||
"jose": "^4.14.4",
|
||||
"klicktipp-api": "^1.0.2",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
|
||||
@ -1,78 +1,110 @@
|
||||
import { CommunityLoggingView, Community as DbCommunity, FederatedCommunity as DbFederatedCommunity, FederatedCommunityLoggingView, getHomeCommunity } from 'database'
|
||||
import { validate as validateUUID, version as versionUUID } from 'uuid'
|
||||
import {
|
||||
CommunityHandshakeState as DbCommunityHandshakeState,
|
||||
CommunityHandshakeStateLoggingView,
|
||||
FederatedCommunity as DbFederatedCommunity,
|
||||
findPendingCommunityHandshake,
|
||||
getHomeCommunityWithFederatedCommunityOrFail,
|
||||
CommunityHandshakeStateType,
|
||||
getCommunityByPublicKeyOrFail,
|
||||
} from 'database'
|
||||
import { randombytes_random } from 'sodium-native'
|
||||
import { CONFIG as CONFIG_CORE } from 'core'
|
||||
|
||||
import { AuthenticationClient as V1_0_AuthenticationClient } from '@/federation/client/1_0/AuthenticationClient'
|
||||
import { ensureUrlEndsWithSlash } from 'core'
|
||||
import { ensureUrlEndsWithSlash, getFederatedCommunityWithApiOrFail } from 'core'
|
||||
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { encryptAndSign, OpenConnectionJwtPayloadType } from 'shared'
|
||||
import { communityAuthenticatedSchema, encryptAndSign, OpenConnectionJwtPayloadType } from 'shared'
|
||||
import { getLogger } from 'log4js'
|
||||
import { AuthenticationClientFactory } from './client/AuthenticationClientFactory'
|
||||
import { EncryptedTransferArgs } from 'core'
|
||||
import { CommunityHandshakeStateLogic } from 'core'
|
||||
import { Ed25519PublicKey } from 'shared'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.authenticateCommunities`)
|
||||
const createLogger = (functionName: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.authenticateCommunities.${functionName}`)
|
||||
|
||||
export enum StartCommunityAuthenticationResult {
|
||||
ALREADY_AUTHENTICATED = 'already authenticated',
|
||||
ALREADY_IN_PROGRESS = 'already in progress',
|
||||
SUCCESSFULLY_STARTED = 'successfully started',
|
||||
}
|
||||
|
||||
export async function startCommunityAuthentication(
|
||||
fedComB: DbFederatedCommunity,
|
||||
): Promise<void> {
|
||||
const methodLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.authenticateCommunities.startCommunityAuthentication`)
|
||||
): Promise<StartCommunityAuthenticationResult> {
|
||||
const methodLogger = createLogger('startCommunityAuthentication')
|
||||
const handshakeID = randombytes_random().toString()
|
||||
const fedComBPublicKey = new Ed25519PublicKey(fedComB.publicKey)
|
||||
methodLogger.addContext('handshakeID', handshakeID)
|
||||
methodLogger.debug(`startCommunityAuthentication()...`, {
|
||||
fedComB: new FederatedCommunityLoggingView(fedComB),
|
||||
})
|
||||
const homeComA = await getHomeCommunity()
|
||||
methodLogger.debug('homeComA', new CommunityLoggingView(homeComA!))
|
||||
const homeFedComA = await DbFederatedCommunity.findOneByOrFail({
|
||||
foreign: false,
|
||||
apiVersion: CONFIG_CORE.FEDERATION_BACKEND_SEND_ON_API,
|
||||
})
|
||||
methodLogger.debug('homeFedComA', new FederatedCommunityLoggingView(homeFedComA))
|
||||
const comB = await DbCommunity.findOneByOrFail({ publicKey: fedComB.publicKey })
|
||||
methodLogger.debug('started with comB:', new CommunityLoggingView(comB))
|
||||
methodLogger.debug(`start with public key ${fedComBPublicKey.asHex()}`)
|
||||
const homeComA = await getHomeCommunityWithFederatedCommunityOrFail(fedComB.apiVersion)
|
||||
// methodLogger.debug('homeComA', new CommunityLoggingView(homeComA))
|
||||
const homeFedComA = getFederatedCommunityWithApiOrFail(homeComA, fedComB.apiVersion)
|
||||
|
||||
const comB = await getCommunityByPublicKeyOrFail(fedComBPublicKey)
|
||||
// methodLogger.debug('started with comB:', new CommunityLoggingView(comB))
|
||||
// check if communityUuid is not a valid v4Uuid
|
||||
try {
|
||||
if (
|
||||
comB &&
|
||||
((comB.communityUuid === null && comB.authenticatedAt === null) ||
|
||||
(comB.communityUuid !== null &&
|
||||
(!validateUUID(comB.communityUuid) ||
|
||||
versionUUID(comB.communityUuid!) !== 4)))
|
||||
) {
|
||||
methodLogger.debug('comB.uuid is null or is a not valid v4Uuid...', comB.communityUuid || 'null', comB.authenticatedAt || 'null')
|
||||
const client = AuthenticationClientFactory.getInstance(fedComB)
|
||||
|
||||
if (client instanceof V1_0_AuthenticationClient) {
|
||||
if (!comB.publicJwtKey) {
|
||||
throw new Error('Public JWT key still not exist for comB ' + comB.name)
|
||||
}
|
||||
//create JWT with url in payload encrypted by foreignCom.publicJwtKey and signed with homeCom.privateJwtKey
|
||||
const payload = new OpenConnectionJwtPayloadType(handshakeID,
|
||||
ensureUrlEndsWithSlash(homeFedComA.endPoint).concat(homeFedComA.apiVersion),
|
||||
)
|
||||
methodLogger.debug('payload', payload)
|
||||
const jws = await encryptAndSign(payload, homeComA!.privateJwtKey!, comB.publicJwtKey!)
|
||||
methodLogger.debug('jws', jws)
|
||||
// prepare the args for the client invocation
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = homeComA!.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = handshakeID
|
||||
methodLogger.debug('before client.openConnection() args:', args)
|
||||
const result = await client.openConnection(args)
|
||||
if (result) {
|
||||
methodLogger.debug(`successful initiated at community:`, fedComB.endPoint)
|
||||
} else {
|
||||
methodLogger.error(`can't initiate at community:`, fedComB.endPoint)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
methodLogger.debug(`comB.communityUuid is already a valid v4Uuid ${ comB.communityUuid || 'null' } and was authenticated at ${ comB.authenticatedAt || 'null'}`)
|
||||
}
|
||||
} catch (err) {
|
||||
methodLogger.error(`Error:`, err)
|
||||
|
||||
// communityAuthenticatedSchema.safeParse return true
|
||||
// - if communityUuid is a valid v4Uuid and
|
||||
// - if authenticatedAt is a valid date
|
||||
if (communityAuthenticatedSchema.safeParse(comB).success) {
|
||||
methodLogger.debug(`comB.communityUuid is already a valid v4Uuid ${ comB.communityUuid || 'null' } and was authenticated at ${ comB.authenticatedAt || 'null'}`)
|
||||
return StartCommunityAuthenticationResult.ALREADY_AUTHENTICATED
|
||||
}
|
||||
methodLogger.removeContext('handshakeID')
|
||||
/*methodLogger.debug('comB.uuid is null or is a not valid v4Uuid...',
|
||||
comB.communityUuid || 'null', comB.authenticatedAt || 'null'
|
||||
)*/
|
||||
|
||||
// check if a authentication is already in progress
|
||||
const existingState = await findPendingCommunityHandshake(fedComBPublicKey, fedComB.apiVersion)
|
||||
if (existingState) {
|
||||
const stateLogic = new CommunityHandshakeStateLogic(existingState)
|
||||
// retry on timeout or failure
|
||||
if (!(await stateLogic.isTimeoutUpdate())) {
|
||||
// authentication with community and api version is still in progress and it is not timeout yet
|
||||
methodLogger.debug('existingState, so we exit here', new CommunityHandshakeStateLoggingView(existingState))
|
||||
return StartCommunityAuthenticationResult.ALREADY_IN_PROGRESS
|
||||
}
|
||||
}
|
||||
|
||||
const client = AuthenticationClientFactory.getInstance(fedComB)
|
||||
|
||||
if (client instanceof V1_0_AuthenticationClient) {
|
||||
if (!comB.publicJwtKey) {
|
||||
throw new Error(`Public JWT key still not exist for comB ${comB.name}`)
|
||||
}
|
||||
const state = new DbCommunityHandshakeState()
|
||||
state.publicKey = fedComBPublicKey.asBuffer()
|
||||
state.apiVersion = fedComB.apiVersion
|
||||
state.status = CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION
|
||||
state.handshakeId = parseInt(handshakeID)
|
||||
await state.save()
|
||||
methodLogger.debug('[START_COMMUNITY_AUTHENTICATION] community handshake state created')
|
||||
|
||||
//create JWT with url in payload encrypted by foreignCom.publicJwtKey and signed with homeCom.privateJwtKey
|
||||
const payload = new OpenConnectionJwtPayloadType(handshakeID,
|
||||
ensureUrlEndsWithSlash(homeFedComA.endPoint).concat(homeFedComA.apiVersion),
|
||||
)
|
||||
// methodLogger.debug('payload', payload)
|
||||
const jws = await encryptAndSign(payload, homeComA!.privateJwtKey!, comB.publicJwtKey!)
|
||||
// methodLogger.debug('jws', jws)
|
||||
// prepare the args for the client invocation
|
||||
const args = new EncryptedTransferArgs()
|
||||
const homeComAPublicKey = new Ed25519PublicKey(homeComA!.publicKey)
|
||||
args.publicKey = homeComAPublicKey.asHex()
|
||||
args.jwt = jws
|
||||
args.handshakeID = handshakeID
|
||||
// methodLogger.debug('before client.openConnection() args:', args)
|
||||
const result = await client.openConnection(args)
|
||||
if (result) {
|
||||
methodLogger.debug(`successful initiated at community:`, fedComB.endPoint)
|
||||
} else {
|
||||
const errorMsg = `can't initiate at community: ${fedComB.endPoint}`
|
||||
methodLogger.error(errorMsg)
|
||||
state.status = CommunityHandshakeStateType.FAILED
|
||||
state.lastError = errorMsg
|
||||
}
|
||||
await state.save()
|
||||
}
|
||||
return StartCommunityAuthenticationResult.SUCCESSFULLY_STARTED
|
||||
}
|
||||
|
||||
@ -103,7 +103,7 @@ describe('validate Communities', () => {
|
||||
return {
|
||||
data: {
|
||||
getPublicKey: {
|
||||
publicKey: 'somePubKey',
|
||||
publicKey: '2222222222222222222222222222222222222222222222222222222222222222',
|
||||
},
|
||||
},
|
||||
} as Response<unknown>
|
||||
@ -170,8 +170,8 @@ describe('validate Communities', () => {
|
||||
it('logs not matching publicKeys', () => {
|
||||
expect(logger.debug).toBeCalledWith(
|
||||
'received not matching publicKey:',
|
||||
'somePubKey',
|
||||
expect.stringMatching('11111111111111111111111111111111'),
|
||||
'2222222222222222222222222222222222222222222222222222222222222222',
|
||||
expect.stringMatching('1111111111111111111111111111111100000000000000000000000000000000'),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import {
|
||||
Community as DbCommunity,
|
||||
FederatedCommunity as DbFederatedCommunity,
|
||||
FederatedCommunityLoggingView,
|
||||
getHomeCommunity,
|
||||
} from 'database'
|
||||
import { IsNull } from 'typeorm'
|
||||
@ -11,7 +10,7 @@ import { FederationClient as V1_0_FederationClient } from '@/federation/client/1
|
||||
import { PublicCommunityInfo } from '@/federation/client/1_0/model/PublicCommunityInfo'
|
||||
import { FederationClientFactory } from '@/federation/client/FederationClientFactory'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { createKeyPair } from 'shared'
|
||||
import { createKeyPair, Ed25519PublicKey } from 'shared'
|
||||
import { getLogger } from 'log4js'
|
||||
import { startCommunityAuthentication } from './authenticateCommunities'
|
||||
import { PublicCommunityInfoLoggingView } from './client/1_0/logging/PublicCommunityInfoLogging.view'
|
||||
@ -45,27 +44,31 @@ export async function validateCommunities(): Promise<void> {
|
||||
|
||||
logger.debug(`found ${dbFederatedCommunities.length} dbCommunities`)
|
||||
for (const dbFedComB of dbFederatedCommunities) {
|
||||
logger.debug('dbFedComB', new FederatedCommunityLoggingView(dbFedComB))
|
||||
logger.debug(`verify federation community: ${dbFedComB.endPoint}${dbFedComB.apiVersion}`)
|
||||
const apiValueStrings: string[] = Object.values(ApiVersionType)
|
||||
logger.debug(`suppported ApiVersions=`, apiValueStrings)
|
||||
if (!apiValueStrings.includes(dbFedComB.apiVersion)) {
|
||||
logger.debug('dbFedComB with unsupported apiVersion', dbFedComB.endPoint, dbFedComB.apiVersion)
|
||||
logger.debug(`supported ApiVersions=`, apiValueStrings)
|
||||
continue
|
||||
}
|
||||
try {
|
||||
const client = FederationClientFactory.getInstance(dbFedComB)
|
||||
|
||||
if (client instanceof V1_0_FederationClient) {
|
||||
const pubKey = await client.getPublicKey()
|
||||
if (pubKey && pubKey === dbFedComB.publicKey.toString('hex')) {
|
||||
// throw if key isn't valid hex with length 64
|
||||
const clientPublicKey = new Ed25519PublicKey(await client.getPublicKey())
|
||||
// throw if key isn't valid hex with length 64
|
||||
const fedComBPublicKey = new Ed25519PublicKey(dbFedComB.publicKey)
|
||||
if (clientPublicKey.isSame(fedComBPublicKey)) {
|
||||
await DbFederatedCommunity.update({ id: dbFedComB.id }, { verifiedAt: new Date() })
|
||||
logger.debug(`verified dbFedComB with:`, dbFedComB.endPoint)
|
||||
// logger.debug(`verified dbFedComB with:`, dbFedComB.endPoint)
|
||||
const pubComInfo = await client.getPublicCommunityInfo()
|
||||
if (pubComInfo) {
|
||||
await writeForeignCommunity(dbFedComB, pubComInfo)
|
||||
logger.debug(`wrote response of getPublicCommunityInfo in dbFedComB ${dbFedComB.endPoint}`)
|
||||
try {
|
||||
await startCommunityAuthentication(dbFedComB)
|
||||
const result = await startCommunityAuthentication(dbFedComB)
|
||||
logger.info(`${dbFedComB.endPoint}${dbFedComB.apiVersion} verified, authentication state: ${result}`)
|
||||
} catch (err) {
|
||||
logger.warn(`Warning: Authentication of community ${dbFedComB.endPoint} still ongoing:`, err)
|
||||
}
|
||||
@ -73,7 +76,7 @@ export async function validateCommunities(): Promise<void> {
|
||||
logger.debug('missing result of getPublicCommunityInfo')
|
||||
}
|
||||
} else {
|
||||
logger.debug('received not matching publicKey:', pubKey, dbFedComB.publicKey.toString('hex'))
|
||||
logger.debug('received not matching publicKey:', clientPublicKey.asHex(), fedComBPublicKey.asHex())
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@ -48,6 +48,9 @@ beforeAll(async () => {
|
||||
query = testEnv.query
|
||||
con = testEnv.con
|
||||
await cleanDB()
|
||||
// reset id auto increment
|
||||
await DbCommunity.clear()
|
||||
await DbFederatedCommunity.clear()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
2
bunfig.toml
Normal file
2
bunfig.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[install]
|
||||
linker = "hoisted"
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "config-schema",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.0",
|
||||
"description": "Gradido Config for validate config",
|
||||
"main": "./build/index.js",
|
||||
"types": "./src/index.ts",
|
||||
@ -32,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.2",
|
||||
"joi": "^17.13.3",
|
||||
"joi": "17.13.3",
|
||||
"log4js": "^6.9.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"yoctocolors-cjs": "^2.1.2",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "core",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.0",
|
||||
"description": "Gradido Core Code, High-Level Shared Code, with dependencies on other modules",
|
||||
"main": "./build/index.js",
|
||||
"types": "./src/index.ts",
|
||||
@ -28,6 +28,7 @@
|
||||
"database": "*",
|
||||
"esbuild": "^0.25.2",
|
||||
"i18n": "^0.15.1",
|
||||
"joi": "^17.13.3",
|
||||
"jose": "^4.14.4",
|
||||
"log4js": "^6.9.1",
|
||||
"shared": "*",
|
||||
@ -37,10 +38,12 @@
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.0.0",
|
||||
"@types/i18n": "^0.13.4",
|
||||
"@types/minimatch": "6.0.0",
|
||||
"@types/node": "^17.0.21",
|
||||
"@types/sodium-native": "^2.3.5",
|
||||
"config-schema": "*",
|
||||
"decimal.js-light": "^2.5.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"graphql-request": "5.0.0",
|
||||
"jest": "27.2.4",
|
||||
"type-graphql": "^1.1.1",
|
||||
|
||||
@ -1,42 +1,41 @@
|
||||
import { EncryptedTransferArgs } from '../model/EncryptedTransferArgs'
|
||||
import { JwtPayloadType } from 'shared'
|
||||
import { Ed25519PublicKey, JwtPayloadType } from 'shared'
|
||||
import { Community as DbCommunity } from 'database'
|
||||
import { getLogger } from 'log4js'
|
||||
import { CommunityLoggingView, getHomeCommunity } from 'database'
|
||||
import { verifyAndDecrypt } from 'shared'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.logic.interpretEncryptedTransferArgs`)
|
||||
const createLogger = (functionName: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.logic.interpretEncryptedTransferArgs.${functionName}`)
|
||||
|
||||
export const interpretEncryptedTransferArgs = async (args: EncryptedTransferArgs): Promise<JwtPayloadType | null> => {
|
||||
const methodLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.logic.interpretEncryptedTransferArgs-method`)
|
||||
const methodLogger = createLogger('interpretEncryptedTransferArgs')
|
||||
methodLogger.addContext('handshakeID', args.handshakeID)
|
||||
methodLogger.debug('interpretEncryptedTransferArgs()... args:', args)
|
||||
const argsPublicKey = new Ed25519PublicKey(args.publicKey)
|
||||
// first find with args.publicKey the community 'requestingCom', which starts the request
|
||||
const requestingCom = await DbCommunity.findOneBy({ publicKey: Buffer.from(args.publicKey, 'hex') })
|
||||
// TODO: maybe use community from caller instead of loading it separately
|
||||
const requestingCom = await DbCommunity.findOneBy({ publicKey: argsPublicKey.asBuffer() })
|
||||
if (!requestingCom) {
|
||||
const errmsg = `unknown requesting community with publicKey ${Buffer.from(args.publicKey, 'hex')}`
|
||||
const errmsg = `unknown requesting community with publicKey ${argsPublicKey.asHex()}`
|
||||
methodLogger.error(errmsg)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
if (!requestingCom.publicJwtKey) {
|
||||
const errmsg = `missing publicJwtKey of requesting community with publicKey ${Buffer.from(args.publicKey, 'hex')}`
|
||||
const errmsg = `missing publicJwtKey of requesting community with publicKey ${argsPublicKey.asHex()}`
|
||||
methodLogger.error(errmsg)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
methodLogger.debug(`found requestingCom:`, new CommunityLoggingView(requestingCom))
|
||||
// verify the signing of args.jwt with homeCom.privateJwtKey and decrypt args.jwt with requestingCom.publicJwtKey
|
||||
// TODO: maybe use community from caller instead of loading it separately
|
||||
const homeCom = await getHomeCommunity()
|
||||
const jwtPayload = await verifyAndDecrypt(args.handshakeID, args.jwt, homeCom!.privateJwtKey!, requestingCom.publicJwtKey) as JwtPayloadType
|
||||
if (!jwtPayload) {
|
||||
const errmsg = `invalid payload of community with publicKey ${Buffer.from(args.publicKey, 'hex')}`
|
||||
const errmsg = `invalid payload of community with publicKey ${argsPublicKey.asHex()}`
|
||||
methodLogger.error(errmsg)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
methodLogger.debug('jwtPayload', jwtPayload)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
return jwtPayload
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ import { Decimal } from 'decimal.js-light'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const'
|
||||
import { PendingTransactionState } from 'shared'
|
||||
// import { LogError } from '@/server/LogError'
|
||||
import { calculateSenderBalance } from 'core'
|
||||
import { calculateSenderBalance } from '../../util/calculateSenderBalance'
|
||||
import { TRANSACTIONS_LOCK, getLastTransaction } from 'database'
|
||||
import { getLogger } from 'log4js'
|
||||
|
||||
|
||||
@ -22,4 +22,5 @@ export * from './util/calculateSenderBalance'
|
||||
export * from './util/utilities'
|
||||
export * from './validation/user'
|
||||
export * from './config/index'
|
||||
export * from './logic'
|
||||
|
||||
|
||||
33
core/src/logic/CommunityHandshakeState.logic.ts
Normal file
33
core/src/logic/CommunityHandshakeState.logic.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { CommunityHandshakeState, CommunityHandshakeStateType } from 'database'
|
||||
import { FEDERATION_AUTHENTICATION_TIMEOUT_MS } from 'shared'
|
||||
|
||||
export class CommunityHandshakeStateLogic {
|
||||
public constructor(private self: CommunityHandshakeState) {}
|
||||
|
||||
/**
|
||||
* Check for expired state and if not, check timeout and update (write into db) to expired state
|
||||
* @returns true if the community handshake state is expired
|
||||
*/
|
||||
public async isTimeoutUpdate(): Promise<boolean> {
|
||||
const timeout = this.isTimeout()
|
||||
if (timeout && this.self.status !== CommunityHandshakeStateType.EXPIRED) {
|
||||
this.self.status = CommunityHandshakeStateType.EXPIRED
|
||||
await this.self.save()
|
||||
}
|
||||
return timeout
|
||||
}
|
||||
|
||||
public isTimeout(): boolean {
|
||||
if (this.self.status === CommunityHandshakeStateType.EXPIRED) {
|
||||
return true
|
||||
}
|
||||
if ((Date.now() - this.self.updatedAt.getTime()) > FEDERATION_AUTHENTICATION_TIMEOUT_MS) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public isFailed(): boolean {
|
||||
return this.self.status === CommunityHandshakeStateType.FAILED
|
||||
}
|
||||
}
|
||||
22
core/src/logic/CommunityHandshakeStateLogic.test.ts
Normal file
22
core/src/logic/CommunityHandshakeStateLogic.test.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { CommunityHandshakeState } from 'database'
|
||||
import { CommunityHandshakeStateLogic } from './CommunityHandshakeState.logic'
|
||||
import { CommunityHandshakeStateType } from 'database'
|
||||
import { FEDERATION_AUTHENTICATION_TIMEOUT_MS } from 'shared'
|
||||
|
||||
describe('CommunityHandshakeStateLogic', () => {
|
||||
it('isTimeout', () => {
|
||||
const state = new CommunityHandshakeState()
|
||||
state.updatedAt = new Date(Date.now() - FEDERATION_AUTHENTICATION_TIMEOUT_MS * 2)
|
||||
state.status = CommunityHandshakeStateType.START_AUTHENTICATION
|
||||
const logic = new CommunityHandshakeStateLogic(state)
|
||||
expect(logic.isTimeout()).toEqual(true)
|
||||
})
|
||||
|
||||
it('isTimeout return false', () => {
|
||||
const state = new CommunityHandshakeState()
|
||||
state.updatedAt = new Date(Date.now())
|
||||
state.status = CommunityHandshakeStateType.START_AUTHENTICATION
|
||||
const logic = new CommunityHandshakeStateLogic(state)
|
||||
expect(logic.isTimeout()).toEqual(false)
|
||||
})
|
||||
})
|
||||
12
core/src/logic/community.logic.ts
Normal file
12
core/src/logic/community.logic.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity } from 'database'
|
||||
|
||||
export function getFederatedCommunityWithApiOrFail(
|
||||
community: DbCommunity,
|
||||
apiVersion: string
|
||||
): DbFederatedCommunity {
|
||||
const fedCom = community.federatedCommunities?.find((fedCom) => fedCom.apiVersion === apiVersion)
|
||||
if (!fedCom) {
|
||||
throw new Error(`Missing federated community with api version ${apiVersion}`)
|
||||
}
|
||||
return fedCom
|
||||
}
|
||||
2
core/src/logic/index.ts
Normal file
2
core/src/logic/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './CommunityHandshakeState.logic'
|
||||
export * from './community.logic'
|
||||
@ -36,6 +36,11 @@ export const delay = promisify(setTimeout)
|
||||
export const ensureUrlEndsWithSlash = (url: string): string => {
|
||||
return url.endsWith('/') ? url : url.concat('/')
|
||||
}
|
||||
export function splitUrlInEndPointAndApiVersion(url: string): { endPoint: string, apiVersion: string } {
|
||||
const endPoint = url.slice(0, url.lastIndexOf('/') + 1)
|
||||
const apiVersion = url.slice(url.lastIndexOf('/') + 1, url.length)
|
||||
return { endPoint, apiVersion }
|
||||
}
|
||||
/**
|
||||
* Calculates the date representing the first day of the month, a specified number of months prior to a given date.
|
||||
*
|
||||
|
||||
@ -47,7 +47,8 @@
|
||||
// "baseUrl": ".", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": { }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [".", "../database"], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
/* List of folders to include type definitions from. */
|
||||
"typeRoots": ["./node_modules/@types", "../node_modules/@types"],
|
||||
// "types": ["bun-types"], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
|
||||
@ -46,7 +46,9 @@ FROM base as bun-base
|
||||
|
||||
#RUN apt update && apt install -y --no-install-recommends ca-certificates curl bash unzip
|
||||
RUN apk update && apk add --no-cache curl tar bash
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
COPY .bun-version .bun-version
|
||||
RUN BUN_VERSION=$(cat .bun-version) && \
|
||||
curl -fsSL https://bun.com/install | bash -s "bun-v${BUN_VERSION}"
|
||||
# Add bun's global bin directory to PATH
|
||||
ENV PATH="/root/.bun/bin:${PATH}"
|
||||
|
||||
@ -63,7 +65,7 @@ COPY --chown=app:app . .
|
||||
FROM installer as build-shared
|
||||
|
||||
RUN bun install --filter shared --no-cache --frozen-lockfile \
|
||||
&& cd shared && yarn typecheck && yarn build
|
||||
&& cd shared && bun run typecheck && bun run build
|
||||
|
||||
##################################################################################
|
||||
# Build ##########################################################################
|
||||
@ -75,7 +77,7 @@ RUN bun install --filter database --production --no-cache --frozen-lockfile
|
||||
##################################################################################
|
||||
# PRODUCTION IMAGE ###############################################################
|
||||
##################################################################################
|
||||
FROM base as production
|
||||
FROM bun-base as production
|
||||
|
||||
COPY --chown=app:app --from=build-shared ${DOCKER_WORKDIR}/shared/build ./shared/build
|
||||
COPY --chown=app:app --from=build-shared ${DOCKER_WORKDIR}/shared/package.json ./shared/package.json
|
||||
@ -89,7 +91,7 @@ COPY --chown=app:app --from=build ${DOCKER_WORKDIR}/package.json ./package.json
|
||||
FROM production as up
|
||||
|
||||
# Run command
|
||||
CMD /bin/sh -c "cd database && yarn up"
|
||||
CMD /bin/sh -c "cd database && bun run up"
|
||||
|
||||
##################################################################################
|
||||
# TEST RESET #####################################################################
|
||||
@ -97,7 +99,7 @@ CMD /bin/sh -c "cd database && yarn up"
|
||||
FROM production as reset
|
||||
|
||||
# Run command
|
||||
CMD /bin/sh -c "cd database && yarn reset"
|
||||
CMD /bin/sh -c "cd database && bun run reset"
|
||||
|
||||
##################################################################################
|
||||
# TEST DOWN ######################################################################
|
||||
@ -105,4 +107,4 @@ CMD /bin/sh -c "cd database && yarn reset"
|
||||
FROM production as down
|
||||
|
||||
# Run command
|
||||
CMD /bin/sh -c "cd database && yarn down"
|
||||
CMD /bin/sh -c "cd database && bun run down"
|
||||
|
||||
@ -23,20 +23,20 @@ TypeError: undefined is not an object (evaluating 'module.parent.parent.require'
|
||||
## Upgrade migrations
|
||||
|
||||
```bash
|
||||
yarn up
|
||||
turbo up
|
||||
```
|
||||
|
||||
## Downgrade migrations
|
||||
|
||||
```bash
|
||||
yarn down
|
||||
turbo down
|
||||
```
|
||||
|
||||
|
||||
## Reset database
|
||||
|
||||
```bash
|
||||
yarn reset
|
||||
turbo reset
|
||||
```
|
||||
|
||||
Runs all down migrations and after this all up migrations.
|
||||
@ -45,7 +45,7 @@ Runs all down migrations and after this all up migrations.
|
||||
call truncate for all tables
|
||||
|
||||
```bash
|
||||
yarn clear
|
||||
turbo clearDB
|
||||
```
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(`
|
||||
CREATE TABLE community_handshake_states (
|
||||
id int unsigned NOT NULL AUTO_INCREMENT,
|
||||
handshake_id int unsigned NOT NULL,
|
||||
one_time_code int unsigned NULL DEFAULT NULL,
|
||||
public_key binary(32) NOT NULL,
|
||||
api_version varchar(255) NOT NULL,
|
||||
status varchar(255) NOT NULL DEFAULT 'OPEN_CONNECTION',
|
||||
last_error text,
|
||||
created_at datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_public_key (public_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`)
|
||||
}
|
||||
|
||||
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||
await queryFn(`DROP TABLE community_handshake_states;`)
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "database",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.0",
|
||||
"description": "Gradido Database Tool to execute database migrations",
|
||||
"main": "./build/index.js",
|
||||
"types": "./src/index.ts",
|
||||
@ -40,6 +40,7 @@
|
||||
"@types/faker": "^5.5.9",
|
||||
"@types/geojson": "^7946.0.13",
|
||||
"@types/jest": "27.0.2",
|
||||
"@types/mysql": "^2.15.27",
|
||||
"@types/node": "^18.7.14",
|
||||
"await-semaphore": "^0.1.3",
|
||||
"crypto-random-bigint": "^2.1.1",
|
||||
@ -56,8 +57,8 @@
|
||||
"dotenv": "^10.0.0",
|
||||
"esbuild": "^0.25.2",
|
||||
"geojson": "^0.5.0",
|
||||
"joi-extract-type": "^15.0.8",
|
||||
"log4js": "^6.9.1",
|
||||
"mysql": "^2.18.1",
|
||||
"mysql2": "^2.3.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"shared": "*",
|
||||
|
||||
37
database/src/entity/CommunityHandshakeState.ts
Normal file
37
database/src/entity/CommunityHandshakeState.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { CommunityHandshakeStateType } from '../enum'
|
||||
import { BaseEntity, Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
|
||||
|
||||
@Entity('community_handshake_states')
|
||||
export class CommunityHandshakeState extends BaseEntity {
|
||||
@PrimaryGeneratedColumn({ unsigned: true })
|
||||
id: number
|
||||
|
||||
@Column({ name: 'handshake_id', type: 'int', unsigned: true })
|
||||
handshakeId: number
|
||||
|
||||
@Column({ name: 'one_time_code', type: 'int', unsigned: true, default: null, nullable: true })
|
||||
oneTimeCode?: number
|
||||
|
||||
@Column({ name: 'public_key', type: 'binary', length: 32 })
|
||||
publicKey: Buffer
|
||||
|
||||
@Column({ name: 'api_version', type: 'varchar', length: 255 })
|
||||
apiVersion: string
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
default: CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION,
|
||||
nullable: false,
|
||||
})
|
||||
status: CommunityHandshakeStateType
|
||||
|
||||
@Column({ name: 'last_error', type: 'text', nullable: true })
|
||||
lastError?: string
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'datetime', precision: 3 })
|
||||
createdAt: Date
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'datetime', precision: 3 })
|
||||
updatedAt: Date
|
||||
}
|
||||
@ -7,6 +7,7 @@ import { Event } from './Event'
|
||||
import { FederatedCommunity } from './FederatedCommunity'
|
||||
import { LoginElopageBuys } from './LoginElopageBuys'
|
||||
import { Migration } from './Migration'
|
||||
import { CommunityHandshakeState } from './CommunityHandshakeState'
|
||||
import { OpenaiThreads } from './OpenaiThreads'
|
||||
import { PendingTransaction } from './PendingTransaction'
|
||||
import { ProjectBranding } from './ProjectBranding'
|
||||
@ -18,6 +19,7 @@ import { UserRole } from './UserRole'
|
||||
|
||||
export {
|
||||
Community,
|
||||
CommunityHandshakeState,
|
||||
Contribution,
|
||||
ContributionLink,
|
||||
ContributionMessage,
|
||||
@ -25,7 +27,7 @@ export {
|
||||
Event,
|
||||
FederatedCommunity,
|
||||
LoginElopageBuys,
|
||||
Migration,
|
||||
Migration,
|
||||
ProjectBranding,
|
||||
OpenaiThreads,
|
||||
PendingTransaction,
|
||||
@ -38,6 +40,7 @@ export {
|
||||
|
||||
export const entities = [
|
||||
Community,
|
||||
CommunityHandshakeState,
|
||||
Contribution,
|
||||
ContributionLink,
|
||||
ContributionMessage,
|
||||
|
||||
9
database/src/enum/CommunityHandshakeStateType.ts
Normal file
9
database/src/enum/CommunityHandshakeStateType.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export enum CommunityHandshakeStateType {
|
||||
START_COMMUNITY_AUTHENTICATION = 'START_COMMUNITY_AUTHENTICATION',
|
||||
START_OPEN_CONNECTION_CALLBACK = 'START_OPEN_CONNECTION_CALLBACK',
|
||||
START_AUTHENTICATION = 'START_AUTHENTICATION',
|
||||
|
||||
SUCCESS = 'SUCCESS',
|
||||
FAILED = 'FAILED',
|
||||
EXPIRED = 'EXPIRED'
|
||||
}
|
||||
1
database/src/enum/index.ts
Normal file
1
database/src/enum/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './CommunityHandshakeStateType'
|
||||
@ -1,64 +1,9 @@
|
||||
import { latestDbVersion } from './detectLastDBVersion'
|
||||
import { Community } from './entity/Community'
|
||||
import { Contribution } from './entity/Contribution'
|
||||
import { ContributionLink } from './entity/ContributionLink'
|
||||
import { ContributionMessage } from './entity/ContributionMessage'
|
||||
import { DltTransaction } from './entity/DltTransaction'
|
||||
import { Event } from './entity/Event'
|
||||
import { FederatedCommunity } from './entity/FederatedCommunity'
|
||||
import { LoginElopageBuys } from './entity/LoginElopageBuys'
|
||||
import { Migration } from './entity/Migration'
|
||||
import { OpenaiThreads } from './entity/OpenaiThreads'
|
||||
import { PendingTransaction } from './entity/PendingTransaction'
|
||||
import { ProjectBranding } from './entity/ProjectBranding'
|
||||
import { Transaction } from './entity/Transaction'
|
||||
import { TransactionLink } from './entity/TransactionLink'
|
||||
import { User } from './entity/User'
|
||||
import { UserContact } from './entity/UserContact'
|
||||
import { UserRole } from './entity/UserRole'
|
||||
|
||||
export {
|
||||
Community,
|
||||
Contribution,
|
||||
ContributionLink,
|
||||
ContributionMessage,
|
||||
DltTransaction,
|
||||
Event,
|
||||
FederatedCommunity,
|
||||
LoginElopageBuys,
|
||||
Migration,
|
||||
ProjectBranding,
|
||||
OpenaiThreads,
|
||||
PendingTransaction,
|
||||
Transaction,
|
||||
TransactionLink,
|
||||
User,
|
||||
UserContact,
|
||||
UserRole,
|
||||
}
|
||||
|
||||
export const entities = [
|
||||
Community,
|
||||
Contribution,
|
||||
ContributionLink,
|
||||
ContributionMessage,
|
||||
DltTransaction,
|
||||
Event,
|
||||
FederatedCommunity,
|
||||
LoginElopageBuys,
|
||||
Migration,
|
||||
ProjectBranding,
|
||||
OpenaiThreads,
|
||||
PendingTransaction,
|
||||
Transaction,
|
||||
TransactionLink,
|
||||
User,
|
||||
UserContact,
|
||||
UserRole,
|
||||
]
|
||||
|
||||
export { latestDbVersion }
|
||||
|
||||
export * from './entity'
|
||||
export * from './logging'
|
||||
export * from './queries'
|
||||
export * from './util'
|
||||
export * from './util'
|
||||
export * from './enum'
|
||||
export { AppDatabase } from './AppDatabase'
|
||||
|
||||
21
database/src/logging/CommunityHandshakeStateLogging.view.ts
Normal file
21
database/src/logging/CommunityHandshakeStateLogging.view.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { CommunityHandshakeState } from '..'
|
||||
import { AbstractLoggingView } from './AbstractLogging.view'
|
||||
|
||||
export class CommunityHandshakeStateLoggingView extends AbstractLoggingView {
|
||||
public constructor(private self: CommunityHandshakeState) {
|
||||
super()
|
||||
}
|
||||
|
||||
public toJSON(): any {
|
||||
return {
|
||||
id: this.self.id,
|
||||
handshakeId: this.self.handshakeId,
|
||||
oneTimeCode: this.self.oneTimeCode,
|
||||
publicKey: this.self.publicKey.toString(this.bufferStringFormat),
|
||||
status: this.self.status,
|
||||
lastError: this.self.lastError,
|
||||
createdAt: this.dateToString(this.self.createdAt),
|
||||
updatedAt: this.dateToString(this.self.updatedAt),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { Community } from '../entity'
|
||||
|
||||
import { FederatedCommunityLoggingView } from './FederatedCommunityLogging.view'
|
||||
import { AbstractLoggingView } from './AbstractLogging.view'
|
||||
|
||||
export class CommunityLoggingView extends AbstractLoggingView {
|
||||
@ -21,6 +21,9 @@ export class CommunityLoggingView extends AbstractLoggingView {
|
||||
creationDate: this.dateToString(this.self.creationDate),
|
||||
createdAt: this.dateToString(this.self.createdAt),
|
||||
updatedAt: this.dateToString(this.self.updatedAt),
|
||||
federatedCommunities: this.self.federatedCommunities?.map(
|
||||
(federatedCommunity) => new FederatedCommunityLoggingView(federatedCommunity)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import { TransactionLoggingView } from './TransactionLogging.view'
|
||||
import { UserContactLoggingView } from './UserContactLogging.view'
|
||||
import { UserLoggingView } from './UserLogging.view'
|
||||
import { UserRoleLoggingView } from './UserRoleLogging.view'
|
||||
import { CommunityHandshakeStateLoggingView } from './CommunityHandshakeStateLogging.view'
|
||||
|
||||
export {
|
||||
AbstractLoggingView,
|
||||
@ -24,6 +25,7 @@ export {
|
||||
UserContactLoggingView,
|
||||
UserLoggingView,
|
||||
UserRoleLoggingView,
|
||||
CommunityHandshakeStateLoggingView,
|
||||
}
|
||||
|
||||
export const logger = getLogger(LOG4JS_BASE_CATEGORY_NAME)
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity } from '..'
|
||||
import { AppDatabase } from '../AppDatabase'
|
||||
import { getHomeCommunity, getReachableCommunities } from './communities'
|
||||
import { getCommunityByPublicKeyOrFail, getHomeCommunity, getHomeCommunityWithFederatedCommunityOrFail, getReachableCommunities } from './communities'
|
||||
import { describe, expect, it, beforeEach, beforeAll, afterAll } from 'vitest'
|
||||
import { createCommunity, createVerifiedFederatedCommunity } from '../seeds/community'
|
||||
import { Ed25519PublicKey } from 'shared'
|
||||
|
||||
const db = AppDatabase.getInstance()
|
||||
|
||||
@ -39,6 +40,36 @@ describe('community.queries', () => {
|
||||
expect(community?.privateKey).toStrictEqual(homeCom.privateKey)
|
||||
})
|
||||
})
|
||||
describe('getHomeCommunityWithFederatedCommunityOrFail', () => {
|
||||
it('should return the home community with federated communities', async () => {
|
||||
const homeCom = await createCommunity(false)
|
||||
await createVerifiedFederatedCommunity('1_0', 100, homeCom)
|
||||
const community = await getHomeCommunityWithFederatedCommunityOrFail('1_0')
|
||||
expect(community).toBeDefined()
|
||||
expect(community?.federatedCommunities).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should throw if no home community exists', async () => {
|
||||
expect(() => getHomeCommunityWithFederatedCommunityOrFail('1_0')).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('should throw if no federated community exists', async () => {
|
||||
await createCommunity(false)
|
||||
expect(() => getHomeCommunityWithFederatedCommunityOrFail('1_0')).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('load community by public key returned from getHomeCommunityWithFederatedCommunityOrFail', async () => {
|
||||
const homeCom = await createCommunity(false)
|
||||
await createVerifiedFederatedCommunity('1_0', 100, homeCom)
|
||||
const community = await getHomeCommunityWithFederatedCommunityOrFail('1_0')
|
||||
expect(community).toBeDefined()
|
||||
expect(community?.federatedCommunities).toHaveLength(1)
|
||||
const ed25519PublicKey = new Ed25519PublicKey(community.federatedCommunities![0].publicKey)
|
||||
const communityByPublicKey = await getCommunityByPublicKeyOrFail(ed25519PublicKey)
|
||||
expect(communityByPublicKey).toBeDefined()
|
||||
expect(communityByPublicKey?.communityUuid).toBe(homeCom.communityUuid)
|
||||
})
|
||||
})
|
||||
describe('getReachableCommunities', () => {
|
||||
it('home community counts also to reachable communities', async () => {
|
||||
await createCommunity(false)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { FindOptionsOrder, FindOptionsWhere, IsNull, MoreThanOrEqual, Not } from 'typeorm'
|
||||
import { Community as DbCommunity } from '../entity'
|
||||
import { urlSchema, uuidv4Schema } from 'shared'
|
||||
import { Ed25519PublicKey, urlSchema, uuidv4Schema } from 'shared'
|
||||
|
||||
/**
|
||||
* Retrieves the home community, i.e., a community that is not foreign.
|
||||
@ -10,7 +10,14 @@ export async function getHomeCommunity(): Promise<DbCommunity | null> {
|
||||
// TODO: Put in Cache, it is needed nearly always
|
||||
// TODO: return only DbCommunity or throw to reduce unnecessary checks, because there should be always a home community
|
||||
return await DbCommunity.findOne({
|
||||
where: { foreign: false },
|
||||
where: { foreign: false }
|
||||
})
|
||||
}
|
||||
|
||||
export async function getHomeCommunityWithFederatedCommunityOrFail(apiVersion: string): Promise<DbCommunity> {
|
||||
return await DbCommunity.findOneOrFail({
|
||||
where: { foreign: false, federatedCommunities: { apiVersion } },
|
||||
relations: { federatedCommunities: true },
|
||||
})
|
||||
}
|
||||
|
||||
@ -42,6 +49,22 @@ export async function getCommunityWithFederatedCommunityByIdentifier(
|
||||
})
|
||||
}
|
||||
|
||||
export async function getCommunityWithFederatedCommunityWithApiOrFail(
|
||||
publicKey: Ed25519PublicKey,
|
||||
apiVersion: string
|
||||
): Promise<DbCommunity> {
|
||||
return await DbCommunity.findOneOrFail({
|
||||
where: { foreign: true, publicKey: publicKey.asBuffer(), federatedCommunities: { apiVersion } },
|
||||
relations: { federatedCommunities: true },
|
||||
})
|
||||
}
|
||||
|
||||
export async function getCommunityByPublicKeyOrFail(publicKey: Ed25519PublicKey): Promise<DbCommunity> {
|
||||
return await DbCommunity.findOneOrFail({
|
||||
where: { publicKey: publicKey.asBuffer() },
|
||||
})
|
||||
}
|
||||
|
||||
// returns all reachable communities
|
||||
// home community and all federated communities which have been verified within the last authenticationTimeoutMs
|
||||
export async function getReachableCommunities(
|
||||
@ -60,4 +83,13 @@ export async function getReachableCommunities(
|
||||
],
|
||||
order,
|
||||
})
|
||||
}
|
||||
|
||||
export async function getNotReachableCommunities(
|
||||
order?: FindOptionsOrder<DbCommunity>
|
||||
): Promise<DbCommunity[]> {
|
||||
return await DbCommunity.find({
|
||||
where: { authenticatedAt: IsNull(), foreign: true },
|
||||
order,
|
||||
})
|
||||
}
|
||||
71
database/src/queries/communityHandshakes.test.ts
Normal file
71
database/src/queries/communityHandshakes.test.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { AppDatabase } from '../AppDatabase'
|
||||
import {
|
||||
CommunityHandshakeState as DbCommunityHandshakeState,
|
||||
Community as DbCommunity,
|
||||
FederatedCommunity as DbFederatedCommunity,
|
||||
findPendingCommunityHandshake,
|
||||
CommunityHandshakeStateType
|
||||
} from '..'
|
||||
import { describe, expect, it, beforeEach, beforeAll, afterAll } from 'vitest'
|
||||
import { createCommunity, createVerifiedFederatedCommunity } from '../seeds/community'
|
||||
import { Ed25519PublicKey } from 'shared'
|
||||
import { randomBytes } from 'node:crypto'
|
||||
|
||||
const db = AppDatabase.getInstance()
|
||||
|
||||
beforeAll(async () => {
|
||||
await db.init()
|
||||
})
|
||||
afterAll(async () => {
|
||||
await db.destroy()
|
||||
})
|
||||
|
||||
async function createCommunityHandshakeState(publicKey: Buffer) {
|
||||
const state = new DbCommunityHandshakeState()
|
||||
state.publicKey = publicKey
|
||||
state.apiVersion = '1_0'
|
||||
state.status = CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION
|
||||
state.handshakeId = 1
|
||||
await state.save()
|
||||
}
|
||||
|
||||
describe('communityHandshakes', () => {
|
||||
// clean db for every test case
|
||||
beforeEach(async () => {
|
||||
await DbCommunity.clear()
|
||||
await DbFederatedCommunity.clear()
|
||||
await DbCommunityHandshakeState.clear()
|
||||
})
|
||||
|
||||
it('should find pending community handshake by public key', async () => {
|
||||
const com1 = await createCommunity(false)
|
||||
await createVerifiedFederatedCommunity('1_0', 100, com1)
|
||||
await createCommunityHandshakeState(com1.publicKey)
|
||||
const communityHandshakeState = await findPendingCommunityHandshake(new Ed25519PublicKey(com1.publicKey), '1_0')
|
||||
expect(communityHandshakeState).toBeDefined()
|
||||
expect(communityHandshakeState).toMatchObject({
|
||||
publicKey: com1.publicKey,
|
||||
apiVersion: '1_0',
|
||||
status: CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION,
|
||||
handshakeId: 1
|
||||
})
|
||||
})
|
||||
|
||||
it('update state', async () => {
|
||||
const publicKey = new Ed25519PublicKey(randomBytes(32))
|
||||
await createCommunityHandshakeState(publicKey.asBuffer())
|
||||
const communityHandshakeState = await findPendingCommunityHandshake(publicKey, '1_0')
|
||||
expect(communityHandshakeState).toBeDefined()
|
||||
communityHandshakeState!.status = CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK
|
||||
await communityHandshakeState!.save()
|
||||
const communityHandshakeState2 = await findPendingCommunityHandshake(publicKey, '1_0')
|
||||
const states = await DbCommunityHandshakeState.find()
|
||||
expect(communityHandshakeState2).toBeDefined()
|
||||
expect(communityHandshakeState2).toMatchObject({
|
||||
publicKey: publicKey.asBuffer(),
|
||||
apiVersion: '1_0',
|
||||
status: CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK,
|
||||
handshakeId: 1
|
||||
})
|
||||
})
|
||||
})
|
||||
35
database/src/queries/communityHandshakes.ts
Normal file
35
database/src/queries/communityHandshakes.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { Not, In } from 'typeorm'
|
||||
import { CommunityHandshakeState, CommunityHandshakeStateType} from '..'
|
||||
import { Ed25519PublicKey } from 'shared'
|
||||
|
||||
/**
|
||||
* Find a pending community handshake by public key.
|
||||
* @param publicKey The public key of the community.
|
||||
* @param apiVersion The API version of the community.
|
||||
* @param status The status of the community handshake. Optional, if not set, it will find a pending community handshake.
|
||||
* @returns The CommunityHandshakeState with associated federated community and community.
|
||||
*/
|
||||
export function findPendingCommunityHandshake(
|
||||
publicKey: Ed25519PublicKey, apiVersion: string, status?: CommunityHandshakeStateType
|
||||
): Promise<CommunityHandshakeState | null> {
|
||||
return CommunityHandshakeState.findOne({
|
||||
where: {
|
||||
publicKey: publicKey.asBuffer(),
|
||||
apiVersion,
|
||||
status: status || Not(In([
|
||||
CommunityHandshakeStateType.EXPIRED,
|
||||
CommunityHandshakeStateType.FAILED,
|
||||
CommunityHandshakeStateType.SUCCESS
|
||||
]))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function findPendingCommunityHandshakeOrFailByOneTimeCode(
|
||||
oneTimeCode: number
|
||||
): Promise<CommunityHandshakeState> {
|
||||
return CommunityHandshakeState.findOneOrFail({
|
||||
where: { oneTimeCode },
|
||||
})
|
||||
}
|
||||
|
||||
@ -5,5 +5,6 @@ export * from './communities'
|
||||
export * from './pendingTransactions'
|
||||
export * from './transactions'
|
||||
export * from './transactionLinks'
|
||||
export * from './communityHandshakes'
|
||||
|
||||
export const LOG4JS_QUERIES_CATEGORY_NAME = `${LOG4JS_BASE_CATEGORY_NAME}.queries`
|
||||
|
||||
@ -2,7 +2,13 @@ import { Community, FederatedCommunity } from '../entity'
|
||||
import { randomBytes } from 'node:crypto'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export async function createCommunity(foreign: boolean, save: boolean = true): Promise<Community> {
|
||||
/**
|
||||
* Creates a community.
|
||||
* @param foreign
|
||||
* @param store if true, write to db, default: true
|
||||
* @returns
|
||||
*/
|
||||
export async function createCommunity(foreign: boolean, store: boolean = true): Promise<Community> {
|
||||
const community = new Community()
|
||||
community.publicKey = randomBytes(32)
|
||||
community.communityUuid = uuidv4()
|
||||
@ -23,14 +29,22 @@ export async function createCommunity(foreign: boolean, save: boolean = true): P
|
||||
community.description = 'HomeCommunity-description'
|
||||
community.url = 'http://localhost/api'
|
||||
}
|
||||
return save ? await community.save() : community
|
||||
return store ? await community.save() : community
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a verified federated community.
|
||||
* @param apiVersion
|
||||
* @param verifiedBeforeMs time in ms before the current time
|
||||
* @param community
|
||||
* @param store if true, write to db, default: true
|
||||
* @returns
|
||||
*/
|
||||
export async function createVerifiedFederatedCommunity(
|
||||
apiVersion: string,
|
||||
verifiedBeforeMs: number,
|
||||
community: Community,
|
||||
save: boolean = true
|
||||
store: boolean = true
|
||||
): Promise<FederatedCommunity> {
|
||||
const federatedCommunity = new FederatedCommunity()
|
||||
federatedCommunity.apiVersion = apiVersion
|
||||
@ -38,5 +52,5 @@ export async function createVerifiedFederatedCommunity(
|
||||
federatedCommunity.publicKey = community.publicKey
|
||||
federatedCommunity.community = community
|
||||
federatedCommunity.verifiedAt = new Date(Date.now() - verifiedBeforeMs)
|
||||
return save ? await federatedCommunity.save() : federatedCommunity
|
||||
return store ? await federatedCommunity.save() : federatedCommunity
|
||||
}
|
||||
|
||||
@ -4,6 +4,9 @@
|
||||
"clear": {
|
||||
"cache": false
|
||||
},
|
||||
"clearDB": {
|
||||
"cache": false
|
||||
},
|
||||
"up:backend_test": {
|
||||
"cache": false
|
||||
},
|
||||
|
||||
@ -19,19 +19,34 @@ install_nvm() {
|
||||
}
|
||||
nvm use || install_nvm
|
||||
|
||||
# check for some tools and install them, when missing
|
||||
# bun https://bun.sh/install, faster packet-manager as yarn
|
||||
if ! command -v bun &> /dev/null
|
||||
# unzip needed for bun install script
|
||||
if ! command -v unzip &> /dev/null
|
||||
then
|
||||
if ! command -v unzip &> /dev/null
|
||||
then
|
||||
echo "'unzip' is missing, will be installed now!"
|
||||
sudo apt-get install -y unzip
|
||||
fi
|
||||
echo "'bun' is missing, will be installed now!"
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
echo "'unzip' is missing, will be installed now!"
|
||||
sudo apt-get install -y unzip
|
||||
fi
|
||||
|
||||
# check for some tools and install them, when missing
|
||||
# bun https://bun.com/install, faster packet-manager as yarn
|
||||
BUN_VERSION_FILE="$PROJECT_ROOT/.bun-version"
|
||||
if [ ! -f "$BUN_VERSION_FILE" ]; then
|
||||
echo ".bun-version file not found at: $BUN_VERSION_FILE"
|
||||
exit 1
|
||||
fi
|
||||
BUN_VERSION="$(cat "$BUN_VERSION_FILE" | tr -d '[:space:]')"
|
||||
if ! command -v bun &> /dev/null
|
||||
then
|
||||
echo "'bun' is missing, v$BUN_VERSION will be installed now!"
|
||||
curl -fsSL https://bun.com/install | bash -s "bun-v${BUN_VERSION}"
|
||||
export BUN_INSTALL="$HOME/.bun"
|
||||
export PATH="$BUN_INSTALL/bin:$PATH"
|
||||
else
|
||||
CURRENT_VERSION="$(bun --version | tr -d '[:space:]')"
|
||||
if [ "$CURRENT_VERSION" != "$BUN_VERSION" ]
|
||||
then
|
||||
echo "'bun' is outdated, v$BUN_VERSION will be installed now!"
|
||||
curl -fsSL https://bun.com/install | bash -s "bun-v${BUN_VERSION}"
|
||||
fi
|
||||
fi
|
||||
# turbo https://turborepo.com/docs/getting-started
|
||||
if ! command -v turbo &> /dev/null
|
||||
|
||||
@ -41,7 +41,7 @@ mysql -u ${DB_USER} -p${DB_PASSWORD} <<EOFMYSQL
|
||||
EOFMYSQL
|
||||
|
||||
# Update database if needed (use dev_up for seeding setups)
|
||||
yarn --cwd $PROJECT_ROOT/database up
|
||||
turbo up
|
||||
|
||||
# Start gradido-backend service
|
||||
pm2 start gradido-backend
|
||||
@ -57,8 +57,9 @@ WORKDIR ${DOCKER_WORKDIR}
|
||||
FROM base as bun-base
|
||||
|
||||
RUN apt update && apt install -y --no-install-recommends ca-certificates curl bash unzip
|
||||
#RUN apk update && apk add --no-cache curl tar bash
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
COPY .bun-version .bun-version
|
||||
RUN BUN_VERSION=$(cat .bun-version) && \
|
||||
curl -fsSL https://bun.com/install | bash -s "bun-v${BUN_VERSION}"
|
||||
# Add bun's global bin directory to PATH
|
||||
ENV PATH="/root/.bun/bin:${PATH}"
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dht-node",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.0",
|
||||
"description": "Gradido dht-node module",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/",
|
||||
@ -34,15 +34,14 @@
|
||||
"@swc/jest": "^0.2.38",
|
||||
"@types/dotenv": "^8.2.3",
|
||||
"@types/jest": "27.5.1",
|
||||
"@types/joi": "^17.2.3",
|
||||
"@types/node": "^17.0.45",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"config-schema": "*",
|
||||
"database": "*",
|
||||
"dotenv": "10.0.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"esbuild": "^0.25.3",
|
||||
"jest": "27.5.1",
|
||||
"joi": "^17.13.3",
|
||||
"joi": "17.13.3",
|
||||
"log4js": "^6.9.1",
|
||||
"nodemon": "^2.0.7",
|
||||
"prettier": "^2.8.8",
|
||||
|
||||
@ -55,8 +55,9 @@ WORKDIR ${DOCKER_WORKDIR}
|
||||
FROM base as bun-base
|
||||
|
||||
RUN apt update && apt install -y --no-install-recommends ca-certificates curl bash unzip
|
||||
#RUN apk update && apk add --no-cache curl tar bash
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
COPY .bun-version .bun-version
|
||||
RUN BUN_VERSION=$(cat .bun-version) && \
|
||||
curl -fsSL https://bun.com/install | bash -s "bun-v${BUN_VERSION}"
|
||||
# Add bun's global bin directory to PATH
|
||||
ENV PATH="/root/.bun/bin:${PATH}"
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "federation",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.0",
|
||||
"description": "Gradido federation module providing Gradido-Hub-Federation and versioned API for inter community communication",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/federation",
|
||||
@ -31,6 +31,7 @@
|
||||
"@swc/cli": "^0.7.3",
|
||||
"@swc/core": "^1.11.24",
|
||||
"@swc/helpers": "^0.5.17",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "4.17.21",
|
||||
"@types/jest": "27.0.2",
|
||||
"@types/lodash.clonedeep": "^4.5.6",
|
||||
@ -46,7 +47,8 @@
|
||||
"cors": "2.8.5",
|
||||
"database": "*",
|
||||
"decimal.js-light": "^2.5.1",
|
||||
"dotenv": "10.0.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"esbuild": "^0.25.3",
|
||||
"express": "^4.17.21",
|
||||
"express-slow-down": "^2.0.1",
|
||||
"graphql": "15.10.1",
|
||||
@ -55,14 +57,16 @@
|
||||
"graphql-tag": "^2.12.6",
|
||||
"helmet": "^7.1.0",
|
||||
"jest": "27.2.4",
|
||||
"joi": "^17.13.3",
|
||||
"joi": "17.13.3",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"log4js": "^6.7.1",
|
||||
"nodemon": "^2.0.7",
|
||||
"prettier": "^3.5.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"shared": "*",
|
||||
"source-map-support": "^0.5.21",
|
||||
"ts-jest": "27.0.5",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.1.1",
|
||||
"type-graphql": "^1.1.1",
|
||||
"typeorm": "^0.3.25",
|
||||
|
||||
@ -1,19 +1,31 @@
|
||||
import { CONFIG } from '@/config'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { EncryptedTransferArgs, interpretEncryptedTransferArgs } from 'core'
|
||||
import { CommunityHandshakeStateLogic, EncryptedTransferArgs, interpretEncryptedTransferArgs, splitUrlInEndPointAndApiVersion } from 'core'
|
||||
import {
|
||||
CommunityLoggingView,
|
||||
Community as DbCommunity,
|
||||
CommunityHandshakeStateLoggingView,
|
||||
CommunityHandshakeState as DbCommunityHandshakeState,
|
||||
CommunityHandshakeStateType,
|
||||
FederatedCommunity as DbFedCommunity,
|
||||
FederatedCommunityLoggingView,
|
||||
getHomeCommunity,
|
||||
findPendingCommunityHandshakeOrFailByOneTimeCode,
|
||||
getCommunityByPublicKeyOrFail,
|
||||
} from 'database'
|
||||
import { getLogger } from 'log4js'
|
||||
import { AuthenticationJwtPayloadType, AuthenticationResponseJwtPayloadType, encryptAndSign, OpenConnectionCallbackJwtPayloadType, OpenConnectionJwtPayloadType, uint32Schema, uuidv4Schema } from 'shared'
|
||||
import {
|
||||
AuthenticationJwtPayloadType,
|
||||
AuthenticationResponseJwtPayloadType,
|
||||
Ed25519PublicKey,
|
||||
encryptAndSign,
|
||||
OpenConnectionCallbackJwtPayloadType,
|
||||
OpenConnectionJwtPayloadType,
|
||||
uint32Schema,
|
||||
uuidv4Schema
|
||||
} from 'shared'
|
||||
import { Arg, Mutation, Resolver } from 'type-graphql'
|
||||
import { startAuthentication, startOpenConnectionCallback } from '../util/authenticateCommunity'
|
||||
|
||||
const createLogger = (method: string ) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.api.1_0.resolver.AuthenticationResolver.${method}`)
|
||||
// TODO: think about the case, when we have a higher api version, which still use this resolver
|
||||
const apiVersion = '1_0'
|
||||
const createLogger = (method: string ) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.api.${apiVersion}.resolver.AuthenticationResolver.${method}`)
|
||||
|
||||
@Resolver()
|
||||
export class AuthenticationResolver {
|
||||
@ -24,45 +36,38 @@ export class AuthenticationResolver {
|
||||
): Promise<boolean> {
|
||||
const methodLogger = createLogger('openConnection')
|
||||
methodLogger.addContext('handshakeID', args.handshakeID)
|
||||
methodLogger.debug(`openConnection() via apiVersion=1_0:`, args)
|
||||
const argsPublicKey = new Ed25519PublicKey(args.publicKey)
|
||||
methodLogger.debug(`start via apiVersion=${apiVersion}, public key: ${argsPublicKey.asHex()}`)
|
||||
try {
|
||||
const openConnectionJwtPayload = await interpretEncryptedTransferArgs(args) as OpenConnectionJwtPayloadType
|
||||
methodLogger.debug('openConnectionJwtPayload', openConnectionJwtPayload)
|
||||
methodLogger.debug(`openConnectionJwtPayload url: ${openConnectionJwtPayload.url}`)
|
||||
if (!openConnectionJwtPayload) {
|
||||
const errmsg = `invalid OpenConnection payload of requesting community with publicKey` + args.publicKey
|
||||
methodLogger.error(errmsg)
|
||||
// no infos to the caller
|
||||
return true
|
||||
throw new Error(`invalid OpenConnection payload of requesting community with publicKey ${argsPublicKey.asHex()}`)
|
||||
}
|
||||
if (openConnectionJwtPayload.tokentype !== OpenConnectionJwtPayloadType.OPEN_CONNECTION_TYPE) {
|
||||
const errmsg = `invalid tokentype of community with publicKey` + args.publicKey
|
||||
methodLogger.error(errmsg)
|
||||
// no infos to the caller
|
||||
return true
|
||||
throw new Error(`invalid tokentype: ${openConnectionJwtPayload.tokentype} of community with publicKey ${argsPublicKey.asHex()}`)
|
||||
}
|
||||
if (!openConnectionJwtPayload.url) {
|
||||
const errmsg = `invalid url of community with publicKey` + args.publicKey
|
||||
methodLogger.error(errmsg)
|
||||
// no infos to the caller
|
||||
return true
|
||||
throw new Error(`invalid url of community with publicKey ${argsPublicKey.asHex()}`)
|
||||
}
|
||||
methodLogger.debug(`vor DbFedCommunity.findOneByOrFail()...`, { publicKey: args.publicKey })
|
||||
const fedComA = await DbFedCommunity.findOneByOrFail({ publicKey: Buffer.from(args.publicKey, 'hex') })
|
||||
methodLogger.debug(`nach DbFedCommunity.findOneByOrFail()...`, fedComA)
|
||||
methodLogger.debug('fedComA', new FederatedCommunityLoggingView(fedComA))
|
||||
// methodLogger.debug(`before DbFedCommunity.findOneByOrFail()...`, { publicKey: argsPublicKey.asHex() })
|
||||
const fedComA = await DbFedCommunity.findOneByOrFail({ publicKey: argsPublicKey.asBuffer() })
|
||||
// methodLogger.debug(`after DbFedCommunity.findOneByOrFail()...`, new FederatedCommunityLoggingView(fedComA))
|
||||
if (!openConnectionJwtPayload.url.startsWith(fedComA.endPoint)) {
|
||||
const errmsg = `invalid url of community with publicKey` + args.publicKey
|
||||
methodLogger.error(errmsg)
|
||||
// no infos to the caller
|
||||
return true
|
||||
throw new Error(`invalid url of community with publicKey ${argsPublicKey.asHex()}`)
|
||||
}
|
||||
if (fedComA.apiVersion !== apiVersion) {
|
||||
throw new Error(`invalid apiVersion: ${fedComA.apiVersion} of community with publicKey ${argsPublicKey.asHex()}`)
|
||||
}
|
||||
|
||||
// no await to respond immediately and invoke callback-request asynchronously
|
||||
void startOpenConnectionCallback(args.handshakeID, args.publicKey, CONFIG.FEDERATION_API)
|
||||
// important: startOpenConnectionCallback must catch all exceptions them self, or server will crash!
|
||||
void startOpenConnectionCallback(args.handshakeID, argsPublicKey, fedComA)
|
||||
methodLogger.debug('openConnection() successfully initiated callback and returns true immediately...')
|
||||
return true
|
||||
} catch (err) {
|
||||
methodLogger.error('invalid jwt token:', err)
|
||||
// no infos to the caller
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -74,37 +79,29 @@ export class AuthenticationResolver {
|
||||
): Promise<boolean> {
|
||||
const methodLogger = createLogger('openConnectionCallback')
|
||||
methodLogger.addContext('handshakeID', args.handshakeID)
|
||||
methodLogger.debug(`openConnectionCallback() via apiVersion=1_0 ...`, args)
|
||||
methodLogger.debug(`start via apiVersion=${apiVersion}, public key: ${args.publicKey}`)
|
||||
try {
|
||||
// decrypt args.url with homeCom.privateJwtKey and verify signing with callbackFedCom.publicKey
|
||||
const openConnectionCallbackJwtPayload = await interpretEncryptedTransferArgs(args) as OpenConnectionCallbackJwtPayloadType
|
||||
if (!openConnectionCallbackJwtPayload) {
|
||||
const errmsg = `invalid OpenConnectionCallback payload of requesting community with publicKey` + args.publicKey
|
||||
methodLogger.error(errmsg)
|
||||
// no infos to the caller
|
||||
return true
|
||||
throw new Error(`invalid OpenConnectionCallback payload of requesting community with publicKey ${args.publicKey}`)
|
||||
}
|
||||
|
||||
const endPoint = openConnectionCallbackJwtPayload.url.slice(0, openConnectionCallbackJwtPayload.url.lastIndexOf('/') + 1)
|
||||
const apiVersion = openConnectionCallbackJwtPayload.url.slice(openConnectionCallbackJwtPayload.url.lastIndexOf('/') + 1, openConnectionCallbackJwtPayload.url.length)
|
||||
methodLogger.debug(`search fedComB per:`, endPoint, apiVersion)
|
||||
const { endPoint, apiVersion } = splitUrlInEndPointAndApiVersion(openConnectionCallbackJwtPayload.url)
|
||||
// methodLogger.debug(`search fedComB per:`, endPoint, apiVersion)
|
||||
const fedComB = await DbFedCommunity.findOneBy({ endPoint, apiVersion })
|
||||
if (!fedComB) {
|
||||
const errmsg = `unknown callback community with url` + openConnectionCallbackJwtPayload.url
|
||||
methodLogger.error(errmsg)
|
||||
// no infos to the caller
|
||||
return true
|
||||
throw new Error(`unknown callback community for ${endPoint}${apiVersion}`)
|
||||
}
|
||||
methodLogger.debug(
|
||||
`found fedComB and start authentication:`,
|
||||
new FederatedCommunityLoggingView(fedComB),
|
||||
`found fedComB and start authentication: ${fedComB.endPoint}${fedComB.apiVersion}`,
|
||||
)
|
||||
// no await to respond immediately and invoke authenticate-request asynchronously
|
||||
void startAuthentication(args.handshakeID, openConnectionCallbackJwtPayload.oneTimeCode, fedComB)
|
||||
methodLogger.debug('openConnectionCallback() successfully initiated authentication and returns true immediately...')
|
||||
// methodLogger.debug('openConnectionCallback() successfully initiated authentication and returns true immediately...')
|
||||
return true
|
||||
} catch (err) {
|
||||
methodLogger.error('invalid jwt token:', err)
|
||||
// no infos to the caller
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -116,51 +113,80 @@ export class AuthenticationResolver {
|
||||
): Promise<string | null> {
|
||||
const methodLogger = createLogger('authenticate')
|
||||
methodLogger.addContext('handshakeID', args.handshakeID)
|
||||
methodLogger.debug(`authenticate() via apiVersion=1_0 ...`, args)
|
||||
methodLogger.debug(`start via apiVersion=${apiVersion}, public key: ${args.publicKey}`)
|
||||
let state: DbCommunityHandshakeState | null = null
|
||||
const argsPublicKey = new Ed25519PublicKey(args.publicKey)
|
||||
try {
|
||||
const authArgs = await interpretEncryptedTransferArgs(args) as AuthenticationJwtPayloadType
|
||||
// methodLogger.debug(`interpreted authentication payload...authArgs:`, authArgs)
|
||||
if (!authArgs) {
|
||||
const errmsg = `invalid authentication payload of requesting community with publicKey` + args.publicKey
|
||||
methodLogger.error(errmsg)
|
||||
// no infos to the caller
|
||||
return null
|
||||
methodLogger.debug(`interpretEncryptedTransferArgs was called with`, args)
|
||||
throw new Error(`invalid authentication payload of requesting community with publicKey ${argsPublicKey.asHex()}`)
|
||||
}
|
||||
if (!uint32Schema.safeParse(Number(authArgs.oneTimeCode)).success) {
|
||||
const errmsg = `invalid oneTimeCode: ${authArgs.oneTimeCode} for community with publicKey ${authArgs.publicKey}, expect uint32`
|
||||
methodLogger.error(errmsg)
|
||||
// no infos to the caller
|
||||
return null
|
||||
const validOneTimeCode = uint32Schema.safeParse(Number(authArgs.oneTimeCode))
|
||||
if (!validOneTimeCode.success) {
|
||||
throw new Error(
|
||||
`invalid oneTimeCode: ${authArgs.oneTimeCode} for community with publicKey ${argsPublicKey.asHex()}, expect uint32`
|
||||
)
|
||||
}
|
||||
const authCom = await DbCommunity.findOneByOrFail({ communityUuid: authArgs.oneTimeCode })
|
||||
|
||||
state = await findPendingCommunityHandshakeOrFailByOneTimeCode(validOneTimeCode.data)
|
||||
const stateLogic = new CommunityHandshakeStateLogic(state)
|
||||
if (
|
||||
(await stateLogic.isTimeoutUpdate()) ||
|
||||
state.status !== CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK
|
||||
) {
|
||||
throw new Error('No valid pending community handshake found')
|
||||
}
|
||||
state.status = CommunityHandshakeStateType.SUCCESS
|
||||
await state.save()
|
||||
methodLogger.debug('[SUCCESS] community handshake state updated')
|
||||
|
||||
// methodLogger.debug(`search community per oneTimeCode:`, authArgs.oneTimeCode)
|
||||
const authCom = await getCommunityByPublicKeyOrFail(argsPublicKey)
|
||||
if (authCom) {
|
||||
methodLogger.debug('found authCom:', new CommunityLoggingView(authCom))
|
||||
if (authCom.publicKey !== authArgs.publicKey) {
|
||||
const errmsg = `corrupt authentication call detected, oneTimeCode: ${authArgs.oneTimeCode} doesn't belong to caller: ${authArgs.publicKey}`
|
||||
methodLogger.error(errmsg)
|
||||
// no infos to the caller
|
||||
return null
|
||||
methodLogger.debug(`found authCom ${authCom.name}`)
|
||||
const authComPublicKey = new Ed25519PublicKey(authCom.publicKey)
|
||||
// methodLogger.debug('authCom.publicKey', authComPublicKey.asHex())
|
||||
// methodLogger.debug('args.publicKey', argsPublicKey.asHex())
|
||||
if (!authComPublicKey.isSame(argsPublicKey)) {
|
||||
throw new Error(
|
||||
`corrupt authentication call detected, oneTimeCode: ${authArgs.oneTimeCode} doesn't belong to caller: ${argsPublicKey.asHex()}`
|
||||
)
|
||||
}
|
||||
const communityUuid = uuidv4Schema.safeParse(authArgs.uuid)
|
||||
if (!communityUuid.success) {
|
||||
const errmsg = `invalid uuid: ${authArgs.uuid} for community with publicKey ${authArgs.publicKey}`
|
||||
methodLogger.error(errmsg)
|
||||
// no infos to the caller
|
||||
return null
|
||||
throw new Error(
|
||||
`invalid uuid: ${authArgs.uuid} for community with publicKey ${authComPublicKey.asHex()}`
|
||||
)
|
||||
}
|
||||
authCom.communityUuid = communityUuid.data
|
||||
authCom.authenticatedAt = new Date()
|
||||
await DbCommunity.save(authCom)
|
||||
methodLogger.debug('store authCom.uuid successfully:', new CommunityLoggingView(authCom))
|
||||
await authCom.save()
|
||||
methodLogger.debug(`update authCom.uuid successfully with ${authCom.communityUuid} at ${authCom.authenticatedAt}`)
|
||||
|
||||
const homeComB = await getHomeCommunity()
|
||||
if (homeComB?.communityUuid) {
|
||||
const responseArgs = new AuthenticationResponseJwtPayloadType(args.handshakeID,homeComB.communityUuid)
|
||||
const responseJwt = await encryptAndSign(responseArgs, homeComB.privateJwtKey!, authCom.publicJwtKey!)
|
||||
return responseJwt
|
||||
}
|
||||
} else {
|
||||
throw new Error(`community with publicKey ${argsPublicKey.asHex()} not found`)
|
||||
}
|
||||
return null
|
||||
} catch (err) {
|
||||
methodLogger.error('invalid jwt token:', err)
|
||||
if (state) {
|
||||
try {
|
||||
state.status = CommunityHandshakeStateType.FAILED
|
||||
state.lastError = String(err)
|
||||
await state.save()
|
||||
} catch (err) {
|
||||
methodLogger.error(`failed to save state`, new CommunityHandshakeStateLoggingView(state), err)
|
||||
}
|
||||
}
|
||||
methodLogger.error(`failed`, err)
|
||||
// no infos to the caller
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,85 +1,118 @@
|
||||
import { EncryptedTransferArgs } from 'core'
|
||||
import { CommunityHandshakeStateLogic, EncryptedTransferArgs, ensureUrlEndsWithSlash } from 'core'
|
||||
import {
|
||||
CommunityLoggingView,
|
||||
CommunityHandshakeStateLoggingView,
|
||||
Community as DbCommunity,
|
||||
FederatedCommunity as DbFedCommunity,
|
||||
FederatedCommunityLoggingView,
|
||||
findPendingCommunityHandshake,
|
||||
getCommunityByPublicKeyOrFail,
|
||||
getHomeCommunity,
|
||||
getHomeCommunityWithFederatedCommunityOrFail,
|
||||
} from 'database'
|
||||
import { getLogger } from 'log4js'
|
||||
import { validate as validateUUID, version as versionUUID } from 'uuid'
|
||||
|
||||
import { AuthenticationClientFactory } from '@/client/AuthenticationClientFactory'
|
||||
import { randombytes_random } from 'sodium-native'
|
||||
|
||||
import { AuthenticationClient as V1_0_AuthenticationClient } from '@/client/1_0/AuthenticationClient'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { AuthenticationJwtPayloadType, AuthenticationResponseJwtPayloadType, encryptAndSign, OpenConnectionCallbackJwtPayloadType, uuidv4Schema, verifyAndDecrypt } from 'shared'
|
||||
import {
|
||||
AuthenticationJwtPayloadType,
|
||||
AuthenticationResponseJwtPayloadType,
|
||||
Ed25519PublicKey,
|
||||
encryptAndSign,
|
||||
OpenConnectionCallbackJwtPayloadType,
|
||||
uuidv4Schema,
|
||||
verifyAndDecrypt
|
||||
} from 'shared'
|
||||
import { CommunityHandshakeState as DbCommunityHandshakeState, CommunityHandshakeStateType } from 'database'
|
||||
import { getFederatedCommunityWithApiOrFail } from 'core'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.api.1_0.util.authenticateCommunity`)
|
||||
const createLogger = (method: string ) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.api.1_0.util.authenticateCommunity.${method}`)
|
||||
|
||||
export async function startOpenConnectionCallback(
|
||||
handshakeID: string,
|
||||
publicKey: string,
|
||||
api: string,
|
||||
publicKey: Ed25519PublicKey,
|
||||
fedComA: DbFedCommunity,
|
||||
): Promise<void> {
|
||||
const methodLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.api.1_0.util.authenticateCommunity.startOpenConnectionCallback`)
|
||||
const methodLogger = createLogger('startOpenConnectionCallback')
|
||||
methodLogger.addContext('handshakeID', handshakeID)
|
||||
methodLogger.debug(`Authentication: startOpenConnectionCallback() with:`, {
|
||||
publicKey,
|
||||
})
|
||||
methodLogger.debug(`start`)
|
||||
const api = fedComA.apiVersion
|
||||
|
||||
let state: DbCommunityHandshakeState | null = null
|
||||
try {
|
||||
const homeComB = await getHomeCommunity()
|
||||
const homeFedComB = await DbFedCommunity.findOneByOrFail({
|
||||
foreign: false,
|
||||
apiVersion: api,
|
||||
})
|
||||
const comA = await DbCommunity.findOneByOrFail({ publicKey: Buffer.from(publicKey, 'hex') })
|
||||
const fedComA = await DbFedCommunity.findOneByOrFail({
|
||||
foreign: true,
|
||||
apiVersion: api,
|
||||
publicKey: comA.publicKey,
|
||||
})
|
||||
// store oneTimeCode in requestedCom.community_uuid as authenticate-request-identifier
|
||||
// prevent overwriting valid UUID with oneTimeCode, because this request could be initiated at any time from federated community
|
||||
if (uuidv4Schema.safeParse(comA.communityUuid).success) {
|
||||
throw new Error('Community UUID is already a valid UUID')
|
||||
const pendingState = await findPendingCommunityHandshake(publicKey, api, CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK)
|
||||
if (pendingState) {
|
||||
const stateLogic = new CommunityHandshakeStateLogic(pendingState)
|
||||
// retry on timeout or failure
|
||||
if (!(await stateLogic.isTimeoutUpdate())) {
|
||||
// authentication with community and api version is still in progress and it is not timeout yet
|
||||
methodLogger.debug('existingState, so we exit here', new CommunityHandshakeStateLoggingView(pendingState))
|
||||
return
|
||||
}
|
||||
}
|
||||
// load comA and comB parallel
|
||||
// load with joined federated community of given api version
|
||||
const [homeComB, comA] = await Promise.all([
|
||||
getHomeCommunityWithFederatedCommunityOrFail(api),
|
||||
getCommunityByPublicKeyOrFail(publicKey),
|
||||
])
|
||||
// get federated communities with correct api version
|
||||
// simply check and extract federated community from community of given api version or throw error if not found
|
||||
const homeFedComB = getFederatedCommunityWithApiOrFail(homeComB, api)
|
||||
|
||||
// TODO: make sure it is unique
|
||||
const oneTimeCode = randombytes_random().toString()
|
||||
comA.communityUuid = oneTimeCode
|
||||
await DbCommunity.save(comA)
|
||||
methodLogger.debug(
|
||||
`Authentication: stored oneTimeCode in requestedCom:`,
|
||||
new CommunityLoggingView(comA),
|
||||
)
|
||||
const oneTimeCode = randombytes_random()
|
||||
const oneTimeCodeString = oneTimeCode.toString()
|
||||
|
||||
// Create new community handshake state
|
||||
state = new DbCommunityHandshakeState()
|
||||
state.publicKey = publicKey.asBuffer()
|
||||
state.apiVersion = api
|
||||
state.status = CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK
|
||||
state.handshakeId = parseInt(handshakeID)
|
||||
state.oneTimeCode = oneTimeCode
|
||||
state = await state.save()
|
||||
methodLogger.debug('[START_OPEN_CONNECTION_CALLBACK] community handshake state created')
|
||||
|
||||
const client = AuthenticationClientFactory.getInstance(fedComA)
|
||||
|
||||
if (client instanceof V1_0_AuthenticationClient) {
|
||||
const url = homeFedComB.endPoint.endsWith('/')
|
||||
? homeFedComB.endPoint + homeFedComB.apiVersion
|
||||
: homeFedComB.endPoint + '/' + homeFedComB.apiVersion
|
||||
const url = ensureUrlEndsWithSlash(homeFedComB.endPoint) + homeFedComB.apiVersion
|
||||
|
||||
const callbackArgs = new OpenConnectionCallbackJwtPayloadType(handshakeID, oneTimeCode, url)
|
||||
methodLogger.debug(`Authentication: start openConnectionCallback with args:`, callbackArgs)
|
||||
const callbackArgs = new OpenConnectionCallbackJwtPayloadType(handshakeID, oneTimeCodeString, url)
|
||||
// methodLogger.debug(`Authentication: start openConnectionCallback with args:`, callbackArgs)
|
||||
// encrypt callbackArgs with requestedCom.publicJwtKey and sign it with homeCom.privateJwtKey
|
||||
const jwt = await encryptAndSign(callbackArgs, homeComB!.privateJwtKey!, comA.publicJwtKey!)
|
||||
const jwt = await encryptAndSign(callbackArgs, homeComB.privateJwtKey!, comA.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = homeComB!.publicKey.toString('hex')
|
||||
args.publicKey = new Ed25519PublicKey(homeComB.publicKey).asHex()
|
||||
args.jwt = jwt
|
||||
args.handshakeID = handshakeID
|
||||
methodLogger.debug(`invoke openConnectionCallback(), oneTimeCode: ${oneTimeCodeString}`)
|
||||
const result = await client.openConnectionCallback(args)
|
||||
if (result) {
|
||||
methodLogger.debug('startOpenConnectionCallback() successful:', jwt)
|
||||
methodLogger.debug(`startOpenConnectionCallback() successful`)
|
||||
} else {
|
||||
methodLogger.error('startOpenConnectionCallback() failed:', jwt)
|
||||
methodLogger.debug(`jwt: ${jwt}`)
|
||||
const errorString = 'startOpenConnectionCallback() failed'
|
||||
methodLogger.error(errorString)
|
||||
state.status = CommunityHandshakeStateType.FAILED
|
||||
state.lastError = errorString
|
||||
state = await state.save()
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
methodLogger.error('error in startOpenConnectionCallback:', err)
|
||||
methodLogger.error('error in startOpenConnectionCallback', err)
|
||||
if (state) {
|
||||
try {
|
||||
state.status = CommunityHandshakeStateType.FAILED
|
||||
state.lastError = String(err)
|
||||
state = await state.save()
|
||||
} catch(e) {
|
||||
methodLogger.error('error on saving CommunityHandshakeState', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
methodLogger.removeContext('handshakeID')
|
||||
}
|
||||
|
||||
export async function startAuthentication(
|
||||
@ -87,21 +120,31 @@ export async function startAuthentication(
|
||||
oneTimeCode: string,
|
||||
fedComB: DbFedCommunity,
|
||||
): Promise<void> {
|
||||
const methodLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.api.1_0.util.authenticateCommunity.startAuthentication`)
|
||||
const methodLogger = createLogger('startAuthentication')
|
||||
methodLogger.addContext('handshakeID', handshakeID)
|
||||
methodLogger.debug(`startAuthentication()...`, {
|
||||
oneTimeCode,
|
||||
fedComB: new FederatedCommunityLoggingView(fedComB),
|
||||
})
|
||||
methodLogger.debug(`startAuthentication()... oneTimeCode: ${oneTimeCode}`)
|
||||
let state: DbCommunityHandshakeState | null = null
|
||||
try {
|
||||
const fedComBPublicKey = new Ed25519PublicKey(fedComB.publicKey)
|
||||
const homeComA = await getHomeCommunity()
|
||||
const comB = await DbCommunity.findOneByOrFail({
|
||||
foreign: true,
|
||||
publicKey: fedComB.publicKey,
|
||||
publicKey: fedComBPublicKey.asBuffer(),
|
||||
})
|
||||
if (!comB.publicJwtKey) {
|
||||
throw new Error('Public JWT key still not exist for foreign community')
|
||||
}
|
||||
state = await findPendingCommunityHandshake(fedComBPublicKey, fedComB.apiVersion, CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION)
|
||||
if (!state) {
|
||||
throw new Error('No pending community handshake found')
|
||||
}
|
||||
const stateLogic = new CommunityHandshakeStateLogic(state)
|
||||
if ((await stateLogic.isTimeoutUpdate())) {
|
||||
methodLogger.debug('invalid state', new CommunityHandshakeStateLoggingView(state))
|
||||
throw new Error('No valid pending community handshake found')
|
||||
}
|
||||
state.status = CommunityHandshakeStateType.START_AUTHENTICATION
|
||||
await state.save()
|
||||
|
||||
const client = AuthenticationClientFactory.getInstance(fedComB)
|
||||
|
||||
@ -110,41 +153,55 @@ export async function startAuthentication(
|
||||
// encrypt authenticationArgs.uuid with fedComB.publicJwtKey and sign it with homeCom.privateJwtKey
|
||||
const jwt = await encryptAndSign(authenticationArgs, homeComA!.privateJwtKey!, comB.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = homeComA!.publicKey.toString('hex')
|
||||
args.publicKey = new Ed25519PublicKey(homeComA!.publicKey).asHex()
|
||||
args.jwt = jwt
|
||||
args.handshakeID = handshakeID
|
||||
methodLogger.debug(`invoke authenticate() with:`, args)
|
||||
methodLogger.debug(`invoke authenticate(), publicKey: ${args.publicKey}`)
|
||||
const responseJwt = await client.authenticate(args)
|
||||
methodLogger.debug(`response of authenticate():`, responseJwt)
|
||||
// methodLogger.debug(`response of authenticate():`, responseJwt)
|
||||
|
||||
if (responseJwt !== null) {
|
||||
const payload = await verifyAndDecrypt(handshakeID, responseJwt, homeComA!.privateJwtKey!, comB.publicJwtKey!) as AuthenticationResponseJwtPayloadType
|
||||
methodLogger.debug(
|
||||
/*methodLogger.debug(
|
||||
`received payload from authenticate ComB:`,
|
||||
payload,
|
||||
new FederatedCommunityLoggingView(fedComB),
|
||||
)
|
||||
)*/
|
||||
if (payload.tokentype !== AuthenticationResponseJwtPayloadType.AUTHENTICATION_RESPONSE_TYPE) {
|
||||
const errmsg = `Invalid tokentype in authenticate-response of community with publicKey` + comB.publicKey
|
||||
methodLogger.error(errmsg)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
throw new Error(errmsg)
|
||||
throw new Error(`Invalid tokentype in authenticate-response of community with publicKey ${fedComBPublicKey.asHex()}`)
|
||||
}
|
||||
if (!payload.uuid || !validateUUID(payload.uuid) || versionUUID(payload.uuid) !== 4) {
|
||||
const errmsg = `Invalid uuid in authenticate-response of community with publicKey` + comB.publicKey
|
||||
methodLogger.error(errmsg)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
throw new Error(errmsg)
|
||||
const parsedUuidv4 = uuidv4Schema.safeParse(payload.uuid)
|
||||
if (!parsedUuidv4.success) {
|
||||
throw new Error(`Invalid uuid in authenticate-response of community with publicKey ${fedComBPublicKey.asHex()}`)
|
||||
}
|
||||
comB.communityUuid = payload.uuid
|
||||
methodLogger.debug('received uuid from authenticate ComB:', parsedUuidv4.data)
|
||||
comB.communityUuid = parsedUuidv4.data
|
||||
comB.authenticatedAt = new Date()
|
||||
await DbCommunity.save(comB)
|
||||
methodLogger.debug('Community Authentication successful:', new CommunityLoggingView(comB))
|
||||
await DbCommunity.save(comB)
|
||||
state.status = CommunityHandshakeStateType.SUCCESS
|
||||
await state.save()
|
||||
methodLogger.debug('[SUCCESS] community handshake state updated')
|
||||
const endTime = new Date()
|
||||
const duration = endTime.getTime() - state.createdAt.getTime()
|
||||
methodLogger.debug(`Community Authentication successful in ${duration} ms`)
|
||||
} else {
|
||||
state.status = CommunityHandshakeStateType.FAILED
|
||||
state.lastError = 'Community Authentication failed, empty response'
|
||||
await state.save()
|
||||
methodLogger.error('Community Authentication failed:', authenticationArgs)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
methodLogger.error('error in startAuthentication:', err)
|
||||
if (state) {
|
||||
try {
|
||||
state.status = CommunityHandshakeStateType.FAILED
|
||||
state.lastError = String(err)
|
||||
await state.save()
|
||||
} catch(e) {
|
||||
methodLogger.error('error on saving CommunityHandshakeState', e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
methodLogger.removeContext('handshakeID')
|
||||
}
|
||||
|
||||
@ -6,8 +6,10 @@ import { getLogger } from 'log4js'
|
||||
// config
|
||||
import { CONFIG } from './config'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from './config/const'
|
||||
import { onShutdown, printServerCrashAsciiArt, ShutdownReason } from 'shared'
|
||||
|
||||
async function main() {
|
||||
const startTime = new Date()
|
||||
// init logger
|
||||
const log4jsConfigFileName = CONFIG.LOG4JS_CONFIG_PLACEHOLDER.replace('%v', CONFIG.FEDERATION_API)
|
||||
initLogger(
|
||||
@ -27,6 +29,16 @@ async function main() {
|
||||
`GraphIQL available at ${CONFIG.FEDERATION_COMMUNITY_URL}/api/${CONFIG.FEDERATION_API}`,
|
||||
)
|
||||
}
|
||||
onShutdown(async (reason, error) => {
|
||||
if (ShutdownReason.SIGINT === reason || ShutdownReason.SIGTERM === reason) {
|
||||
logger.info(`graceful shutdown: ${reason}`)
|
||||
} else {
|
||||
const endTime = new Date()
|
||||
const duration = endTime.getTime() - startTime.getTime()
|
||||
printServerCrashAsciiArt('Server Crash', `reason: ${reason}`, `server was ${duration}ms online`)
|
||||
logger.error(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -57,7 +57,9 @@ WORKDIR ${DOCKER_WORKDIR}
|
||||
FROM base as bun-base
|
||||
|
||||
RUN apk update && apk add --no-cache curl tar bash
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
COPY .bun-version .bun-version
|
||||
RUN BUN_VERSION=$(cat .bun-version) && \
|
||||
curl -fsSL https://bun.com/install | bash -s "bun-v${BUN_VERSION}"
|
||||
# Add bun's global bin directory to PATH
|
||||
ENV PATH="/root/.bun/bin:${PATH}"
|
||||
|
||||
|
||||
@ -7,26 +7,54 @@ Then install grass, it need some time, because it will be compiled.
|
||||
```bash
|
||||
cargo install grass
|
||||
```
|
||||
Now with using yarn compile-sass or turbo compile-sass grass will be used.
|
||||
Now with using bun compile-sass or turbo compile-sass grass will be used.
|
||||
|
||||
## install mit yarn
|
||||
```bash
|
||||
cd frontend
|
||||
yarn install
|
||||
yarn run serve
|
||||
|
||||
# build
|
||||
yarn run build
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
turbo dev
|
||||
```
|
||||
or from root folder:
|
||||
|
||||
```
|
||||
turbo frontend#dev
|
||||
```
|
||||
|
||||
## install mit docker
|
||||
|
||||
```bash
|
||||
# build
|
||||
docker build -t gradido-frontend .
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
turbo build
|
||||
```
|
||||
or from root folder:
|
||||
|
||||
# run
|
||||
docker run -it -p 80:80 --rm gradido-frontend
|
||||
```
|
||||
turbo frontend#build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
turbo lint
|
||||
```
|
||||
or from root folder:
|
||||
|
||||
```
|
||||
turbo frontend#lint
|
||||
```
|
||||
|
||||
### Unit tests
|
||||
```
|
||||
turbo test
|
||||
```
|
||||
For filtering out single tests:
|
||||
```
|
||||
turbo test -- <test_name>
|
||||
```
|
||||
Everything after -- will be passed to vitest.
|
||||
|
||||
or from root folder:
|
||||
|
||||
```
|
||||
turbo frontend#test
|
||||
```
|
||||
|
||||
**Fully Coded Components**
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "concurrently \"yarn watch-scss\" \"vite\"",
|
||||
@ -80,10 +80,11 @@
|
||||
"@vitest/coverage-v8": "^2.0.5",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"chokidar-cli": "^3.0.0",
|
||||
"chokidar": "^4.0.3",
|
||||
"concurrently": "^9.1.2",
|
||||
"config-schema": "*",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^10.0.0",
|
||||
"dotenv-webpack": "^7.0.3",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
@ -96,7 +97,7 @@
|
||||
"eslint-plugin-vitest": "^0.5.4",
|
||||
"eslint-plugin-vue": "8.7.1",
|
||||
"eslint-webpack-plugin": "^5.0.0",
|
||||
"joi": "^17.13.3",
|
||||
"joi": "17.13.3",
|
||||
"jsdom": "^25.0.0",
|
||||
"lightningcss": "^1.30.1",
|
||||
"mock-apollo-client": "^1.2.1",
|
||||
|
||||
13
package.json
13
package.json
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "gradido",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.0",
|
||||
"description": "Gradido",
|
||||
"main": "index.js",
|
||||
"repository": "git@github.com:gradido/gradido.git",
|
||||
"author": "Gradido Academy - https://www.gradido.net",
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"packageManager": "yarn@1.22.22",
|
||||
"packageManager": "bun@1.2.0",
|
||||
"workspaces": [
|
||||
"admin",
|
||||
"backend",
|
||||
@ -20,8 +20,9 @@
|
||||
"shared"
|
||||
],
|
||||
"scripts": {
|
||||
"release": "scripts/release.sh",
|
||||
"installAll": "yarn install",
|
||||
"release": "bumpp --no-commit --no-push -r",
|
||||
"version": "auto-changelog -p --commit-limit 0 && git add CHANGELOG.md",
|
||||
"installAll": "bun run install",
|
||||
"docker": "cross-env BUILD_COMMIT=$(git rev-parse HEAD) docker compose -f docker-compose.yml up",
|
||||
"docker:rebuild": "cross-env BUILD_COMMIT=$(git rev-parse HEAD) docker compose -f docker-compose.yml build",
|
||||
"docker_dev": "cross-env BUILD_COMMIT=$(git rev-parse HEAD) docker compose up",
|
||||
@ -36,7 +37,9 @@
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.0.0"
|
||||
"@biomejs/biome": "2.0.0",
|
||||
"@types/minimatch": "6.0.0",
|
||||
"bbump": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# find directories
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
PROJECT_DIR="${SCRIPT_DIR}/../"
|
||||
FRONTEND_DIR="${PROJECT_DIR}/frontend/"
|
||||
BACKEND_DIR="${PROJECT_DIR}/backend/"
|
||||
DATABASE_DIR="${PROJECT_DIR}/database/"
|
||||
SHARED_DIR="${PROJECT_DIR}/shared/"
|
||||
CONFIG_SCHEMA_DIR="${PROJECT_DIR}/config-schema/"
|
||||
CORE_DIR="${PROJECT_DIR}/core/"
|
||||
ADMIN_DIR="${PROJECT_DIR}/admin/"
|
||||
DHTNODE_DIR="${PROJECT_DIR}/dht-node/"
|
||||
FEDERATION_DIR="${PROJECT_DIR}/federation/"
|
||||
DLTCONNECTOR_DIR="${PROJECT_DIR}/dlt-connector/"
|
||||
|
||||
# navigate to project directory
|
||||
cd ${PROJECT_DIR}
|
||||
|
||||
# ask for new version
|
||||
yarn version --no-git-tag-version --no-commit-hooks --no-commit
|
||||
|
||||
# find new version
|
||||
VERSION="$(node -p -e "require('./package.json').version")"
|
||||
|
||||
# update version in sub projects
|
||||
cd ${FRONTEND_DIR}
|
||||
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
|
||||
cd ${BACKEND_DIR}
|
||||
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
|
||||
cd ${DATABASE_DIR}
|
||||
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
|
||||
cd ${SHARED_DIR}
|
||||
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
|
||||
cd ${CONFIG_SCHEMA_DIR}
|
||||
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
|
||||
cd ${CORE_DIR}
|
||||
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
|
||||
cd ${ADMIN_DIR}
|
||||
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
|
||||
cd ${DHTNODE_DIR}
|
||||
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
|
||||
cd ${FEDERATION_DIR}
|
||||
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
|
||||
cd ${DLTCONNECTOR_DIR}
|
||||
yarn version --no-git-tag-version --no-commit-hooks --no-commit --new-version ${VERSION}
|
||||
|
||||
# generate changelog
|
||||
cd ${PROJECT_DIR}
|
||||
./node_modules/.bin/auto-changelog --commit-limit 0 --latest-version ${VERSION}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "shared",
|
||||
"version": "2.6.1",
|
||||
"version": "2.7.0",
|
||||
"description": "Gradido Shared Code, Low-Level Shared Code, without dependencies on other modules",
|
||||
"main": "./build/index.js",
|
||||
"types": "./src/index.ts",
|
||||
@ -26,6 +26,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.0.0",
|
||||
"@types/minimatch": "6.0.0",
|
||||
"@types/node": "^17.0.21",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"typescript": "^4.9.5",
|
||||
@ -36,6 +37,7 @@
|
||||
"esbuild": "^0.25.2",
|
||||
"jose": "^4.14.4",
|
||||
"log4js": "^6.9.1",
|
||||
"yoctocolors-cjs": "^2.1.2",
|
||||
"zod": "^3.25.61"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export const DECAY_START_TIME = new Date('2021-05-13T17:46:31Z')
|
||||
export const SECONDS_PER_YEAR_GREGORIAN_CALENDER = 31556952.0
|
||||
export const LOG4JS_BASE_CATEGORY_NAME = 'shared'
|
||||
export const REDEEM_JWT_TOKEN_EXPIRATION = '10m'
|
||||
export const REDEEM_JWT_TOKEN_EXPIRATION = '10m'
|
||||
// 10 minutes
|
||||
export const FEDERATION_AUTHENTICATION_TIMEOUT_MS = 60 * 1000 * 10
|
||||
51
shared/src/helper/BinaryData.ts
Normal file
51
shared/src/helper/BinaryData.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { getLogger } from 'log4js'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '../const'
|
||||
|
||||
const logging = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.helper.BinaryData`)
|
||||
|
||||
/**
|
||||
* Class mainly for handling ed25519 public keys,
|
||||
* to make sure we have always the correct Format (Buffer or Hex String)
|
||||
*/
|
||||
export class BinaryData {
|
||||
private buf: Buffer
|
||||
private hex: string
|
||||
|
||||
constructor(input: Buffer | string | undefined) {
|
||||
if (typeof input === 'string') {
|
||||
this.buf = Buffer.from(input, 'hex')
|
||||
this.hex = input
|
||||
} else if (Buffer.isBuffer(input)) {
|
||||
this.buf = input
|
||||
this.hex = input.toString('hex')
|
||||
} else {
|
||||
this.buf = Buffer.from('')
|
||||
this.hex = ''
|
||||
}
|
||||
}
|
||||
|
||||
asBuffer(): Buffer {
|
||||
return this.buf
|
||||
}
|
||||
|
||||
asHex(): string {
|
||||
return this.hex
|
||||
}
|
||||
|
||||
isSame(other: BinaryData): boolean {
|
||||
if (other === undefined || !(other instanceof BinaryData)) {
|
||||
logging.error('other is invalid', other)
|
||||
return false
|
||||
}
|
||||
return this.buf.compare(other.asBuffer()) === 0
|
||||
}
|
||||
}
|
||||
|
||||
export class Ed25519PublicKey extends BinaryData {
|
||||
constructor(input: Buffer | string | undefined) {
|
||||
super(input)
|
||||
if (this.asBuffer().length !== 32) {
|
||||
throw new Error('Invalid ed25519 public key length')
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +1,3 @@
|
||||
export * from './updateField'
|
||||
export * from './updateField'
|
||||
export * from './BinaryData'
|
||||
export * from './onShutdown'
|
||||
51
shared/src/helper/onShutdown.ts
Normal file
51
shared/src/helper/onShutdown.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Logger } from 'log4js'
|
||||
import colors from 'yoctocolors-cjs'
|
||||
|
||||
export enum ShutdownReason {
|
||||
SIGINT = 'SIGINT',
|
||||
SIGTERM = 'SIGTERM',
|
||||
UNCAUGHT_EXCEPTION = 'UNCAUGHT_EXCEPTION',
|
||||
UNCAUGHT_REJECTION = 'UNCAUGHT_REJECTION',
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setup graceful shutdown for the process
|
||||
* @param gracefulShutdown will be called if process is terminated
|
||||
*/
|
||||
export function onShutdown(shutdownHandler: (reason: ShutdownReason, error?: Error | any) => Promise<void>) {
|
||||
const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM']
|
||||
signals.forEach(sig => {
|
||||
process.on(sig, async () => {
|
||||
await shutdownHandler(sig as ShutdownReason)
|
||||
process.exit(0)
|
||||
})
|
||||
})
|
||||
|
||||
process.on('uncaughtException', async (err) => {
|
||||
await shutdownHandler(ShutdownReason.UNCAUGHT_EXCEPTION, err)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
process.on('unhandledRejection', async (err) => {
|
||||
await shutdownHandler(ShutdownReason.UNCAUGHT_REJECTION, err)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
if (process.platform === "win32") {
|
||||
const rl = require("readline").createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
})
|
||||
rl.on("SIGINT", () => {
|
||||
process.emit("SIGINT" as any)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function printServerCrashAsciiArt(msg1: string, msg2: string, msg3: string) {
|
||||
console.error(colors.redBright(` /\\_/\\ ${msg1}`))
|
||||
console.error(colors.redBright(`( x.x ) ${msg2}`))
|
||||
console.error(colors.redBright(` > < ${msg3}`))
|
||||
console.error(colors.redBright(''))
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
export * from './schema'
|
||||
export * from './enum'
|
||||
export * from './const'
|
||||
export * from './helper'
|
||||
export * from './logic/decay'
|
||||
export * from './jwt/JWT'
|
||||
|
||||
@ -43,11 +43,9 @@ export const verify = async (handshakeID: string, token: string, publicKey: stri
|
||||
})
|
||||
payload.handshakeID = handshakeID
|
||||
methodLogger.debug('verify after jwtVerify... payload=', payload)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
return payload as JwtPayloadType
|
||||
} catch (err) {
|
||||
methodLogger.error('verify after jwtVerify... error=', err)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -74,11 +72,9 @@ export const encode = async (payload: JwtPayloadType, privatekey: string): Promi
|
||||
.setExpirationTime(payload.expiration)
|
||||
.sign(secret)
|
||||
methodLogger.debug('encode... token=', token)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
return token
|
||||
} catch (e) {
|
||||
methodLogger.error('Failed to sign JWT:', e)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
throw e
|
||||
}
|
||||
}
|
||||
@ -111,11 +107,9 @@ export const encrypt = async (payload: JwtPayloadType, publicKey: string): Promi
|
||||
.setProtectedHeader({ alg: 'RSA-OAEP-256', enc: 'A256GCM' })
|
||||
.encrypt(recipientKey)
|
||||
methodLogger.debug('encrypt... jwe=', jwe)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
return jwe.toString()
|
||||
} catch (e) {
|
||||
methodLogger.error('Failed to encrypt JWT:', e)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
throw e
|
||||
}
|
||||
}
|
||||
@ -131,11 +125,9 @@ export const decrypt = async(handshakeID: string, jwe: string, privateKey: strin
|
||||
await compactDecrypt(jwe, decryptKey)
|
||||
methodLogger.debug('decrypt... plaintext=', plaintext)
|
||||
methodLogger.debug('decrypt... protectedHeader=', protectedHeader)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
return new TextDecoder().decode(plaintext)
|
||||
} catch (e) {
|
||||
methodLogger.error('Failed to decrypt JWT:', e)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
throw e
|
||||
}
|
||||
}
|
||||
@ -147,7 +139,6 @@ export const encryptAndSign = async (payload: JwtPayloadType, privateKey: string
|
||||
methodLogger.debug('encryptAndSign... jwe=', jwe)
|
||||
const jws = await encode(new EncryptedJWEJwtPayloadType(payload.handshakeID, jwe), privateKey)
|
||||
methodLogger.debug('encryptAndSign... jws=', jws)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
return jws
|
||||
}
|
||||
|
||||
@ -171,6 +162,5 @@ export const verifyAndDecrypt = async (handshakeID: string, token: string, priva
|
||||
methodLogger.debug('verifyAndDecrypt... jwe=', jwe)
|
||||
const payload = await decrypt(handshakeID, jwe as string, privateKey)
|
||||
methodLogger.debug('verifyAndDecrypt... payload=', payload)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
return JSON.parse(payload) as JwtPayloadType
|
||||
}
|
||||
|
||||
@ -4,4 +4,4 @@ import { validate, version } from 'uuid'
|
||||
export const uuidv4Schema = string().refine((val: string) => validate(val) && version(val) === 4, 'Invalid uuid')
|
||||
export const emailSchema = string().email()
|
||||
export const urlSchema = string().url()
|
||||
export const uint32Schema = number().positive().lte(4294967295)
|
||||
export const uint32Schema = number().positive().lte(4294967295)
|
||||
|
||||
35
shared/src/schema/community.schema.test.ts
Normal file
35
shared/src/schema/community.schema.test.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { communityAuthenticatedSchema } from './community.schema'
|
||||
import { describe, it, expect } from 'bun:test'
|
||||
|
||||
|
||||
describe('communityAuthenticatedSchema', () => {
|
||||
it('should return an error if communityUuid is not a uuidv4', () => {
|
||||
const data = communityAuthenticatedSchema.safeParse({
|
||||
communityUuid: '1234567890',
|
||||
authenticatedAt: new Date(),
|
||||
})
|
||||
|
||||
expect(data.success).toBe(false)
|
||||
expect(data.error?.issues[0].path).toEqual(['communityUuid'])
|
||||
})
|
||||
|
||||
it('should return an error if authenticatedAt is not a date', () => {
|
||||
const data = communityAuthenticatedSchema.safeParse({
|
||||
communityUuid: uuidv4(),
|
||||
authenticatedAt: '2022-01-01',
|
||||
})
|
||||
|
||||
expect(data.success).toBe(false)
|
||||
expect(data.error?.issues[0].path).toEqual(['authenticatedAt'])
|
||||
})
|
||||
|
||||
it('should return no error for valid data and valid uuid4', () => {
|
||||
const data = communityAuthenticatedSchema.safeParse({
|
||||
communityUuid: uuidv4(),
|
||||
authenticatedAt: new Date(),
|
||||
})
|
||||
|
||||
expect(data.success).toBe(true)
|
||||
})
|
||||
})
|
||||
7
shared/src/schema/community.schema.ts
Normal file
7
shared/src/schema/community.schema.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { object, date, array, string } from 'zod'
|
||||
import { uuidv4Schema } from './base.schema'
|
||||
|
||||
export const communityAuthenticatedSchema = object({
|
||||
communityUuid: uuidv4Schema,
|
||||
authenticatedAt: date(),
|
||||
})
|
||||
@ -1,2 +1,3 @@
|
||||
export * from './user.schema'
|
||||
export * from './base.schema'
|
||||
export * from './base.schema'
|
||||
export * from './community.schema'
|
||||
@ -47,7 +47,8 @@
|
||||
// "baseUrl": ".", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [".", "../database"], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
/* List of folders to include type definitions from. */
|
||||
"typeRoots": ["./node_modules/@types", "../node_modules/@types"],
|
||||
// "types": ["bun-types"], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user