From 47b38ac58f99ade8d550c03004b240b409bbd81b Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Thu, 9 Oct 2025 12:16:12 +0200 Subject: [PATCH 01/48] combine fixes for community authentication --- .../api/1_0/resolver/AuthenticationResolver.ts | 16 ++++++++++++---- .../api/1_0/util/authenticateCommunity.ts | 8 ++++++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index 4c14360e9..84d85a0af 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -9,7 +9,15 @@ import { getHomeCommunity, } from 'database' import { getLogger } from 'log4js' -import { AuthenticationJwtPayloadType, AuthenticationResponseJwtPayloadType, encryptAndSign, OpenConnectionCallbackJwtPayloadType, OpenConnectionJwtPayloadType, uint32Schema, uuidv4Schema } from 'shared' +import { + AuthenticationJwtPayloadType, + AuthenticationResponseJwtPayloadType, + encryptAndSign, + OpenConnectionCallbackJwtPayloadType, + OpenConnectionJwtPayloadType, + uint32Schema, + uuidv4Schema +} from 'shared' import { Arg, Mutation, Resolver } from 'type-graphql' import { startAuthentication, startOpenConnectionCallback } from '../util/authenticateCommunity' @@ -134,15 +142,15 @@ export class AuthenticationResolver { const authCom = await DbCommunity.findOneByOrFail({ communityUuid: authArgs.oneTimeCode }) 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}` + if (authCom.publicKey.compare(Buffer.from(args.publicKey, 'hex')) !== 0) { + const errmsg = `corrupt authentication call detected, oneTimeCode: ${authArgs.oneTimeCode} doesn't belong to caller: ${args.publicKey}` methodLogger.error(errmsg) // no infos to the caller return null } const communityUuid = uuidv4Schema.safeParse(authArgs.uuid) if (!communityUuid.success) { - const errmsg = `invalid uuid: ${authArgs.uuid} for community with publicKey ${authArgs.publicKey}` + const errmsg = `invalid uuid: ${authArgs.uuid} for community with publicKey ${authCom.publicKey}` methodLogger.error(errmsg) // no infos to the caller return null diff --git a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts index 33f725737..a5a3f532a 100644 --- a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts +++ b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts @@ -14,7 +14,7 @@ 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, encryptAndSign, OpenConnectionCallbackJwtPayloadType, uint32Schema, uuidv4Schema, verifyAndDecrypt } from 'shared' const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.api.1_0.util.authenticateCommunity`) @@ -43,7 +43,11 @@ export async function startOpenConnectionCallback( // 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') + methodLogger.debug('Community UUID is already a valid UUID') + return + } else if (uint32Schema.safeParse(Number(comA.communityUuid)).success) { + methodLogger.debug('Community UUID is still in authentication...oneTimeCode=', comA.communityUuid) + return } // TODO: make sure it is unique const oneTimeCode = randombytes_random().toString() From e8ef1bc3109a9bcb631a2de0fa90da72c33a05a0 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Sun, 12 Oct 2025 08:48:08 +0200 Subject: [PATCH 02/48] try to make oneTimeCode more robust --- .../src/federation/authenticateCommunities.ts | 13 +++---- backend/src/federation/validateCommunities.ts | 13 ++++++- database/src/queries/communities.ts | 9 +++++ .../1_0/resolver/AuthenticationResolver.ts | 6 ++++ .../api/1_0/util/authenticateCommunity.ts | 8 +++-- shared/src/const/index.ts | 4 ++- shared/src/index.ts | 1 + shared/src/schema/community.schema.test.ts | 35 +++++++++++++++++++ shared/src/schema/community.schema.ts | 7 ++++ shared/src/schema/index.ts | 3 +- 10 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 shared/src/schema/community.schema.test.ts create mode 100644 shared/src/schema/community.schema.ts diff --git a/backend/src/federation/authenticateCommunities.ts b/backend/src/federation/authenticateCommunities.ts index ad2d91469..f186adb35 100644 --- a/backend/src/federation/authenticateCommunities.ts +++ b/backend/src/federation/authenticateCommunities.ts @@ -7,7 +7,7 @@ import { AuthenticationClient as V1_0_AuthenticationClient } from '@/federation/ import { ensureUrlEndsWithSlash } 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' @@ -34,13 +34,10 @@ export async function startCommunityAuthentication( 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))) - ) { + // communityAuthenticatedSchema.safeParse return true + // - if communityUuid is a valid v4Uuid and + // - if authenticatedAt is a valid date + if (comB && !communityAuthenticatedSchema.safeParse(comB).success) { methodLogger.debug('comB.uuid is null or is a not valid v4Uuid...', comB.communityUuid || 'null', comB.authenticatedAt || 'null') const client = AuthenticationClientFactory.getInstance(fedComB) diff --git a/backend/src/federation/validateCommunities.ts b/backend/src/federation/validateCommunities.ts index 36c9985c9..cb56a8c53 100644 --- a/backend/src/federation/validateCommunities.ts +++ b/backend/src/federation/validateCommunities.ts @@ -3,6 +3,7 @@ import { FederatedCommunity as DbFederatedCommunity, FederatedCommunityLoggingView, getHomeCommunity, + getNotReachableCommunities, } from 'database' import { IsNull } from 'typeorm' @@ -11,7 +12,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, uint32Schema } from 'shared' import { getLogger } from 'log4js' import { startCommunityAuthentication } from './authenticateCommunities' import { PublicCommunityInfoLoggingView } from './client/1_0/logging/PublicCommunityInfoLogging.view' @@ -27,6 +28,16 @@ export async function startValidateCommunities(timerInterval: number): Promise see https://javascript.info/settimeout-setinterval setTimeout(async function run() { diff --git a/database/src/queries/communities.ts b/database/src/queries/communities.ts index e216f8af6..cdb7cabb6 100644 --- a/database/src/queries/communities.ts +++ b/database/src/queries/communities.ts @@ -60,4 +60,13 @@ export async function getReachableCommunities( ], order, }) +} + +export async function getNotReachableCommunities( + order?: FindOptionsOrder +): Promise { + return await DbCommunity.find({ + where: { authenticatedAt: IsNull(), foreign: true }, + order, + }) } \ No newline at end of file diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index 84d85a0af..fd948300e 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -127,21 +127,27 @@ export class AuthenticationResolver { methodLogger.debug(`authenticate() via apiVersion=1_0 ...`, args) 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 } + 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 } + + methodLogger.debug(`search community per oneTimeCode:`, authArgs.oneTimeCode) const authCom = await DbCommunity.findOneByOrFail({ communityUuid: authArgs.oneTimeCode }) if (authCom) { methodLogger.debug('found authCom:', new CommunityLoggingView(authCom)) + methodLogger.debug('authCom.publicKey', authCom.publicKey.toString('hex')) + methodLogger.debug('args.publicKey', args.publicKey) if (authCom.publicKey.compare(Buffer.from(args.publicKey, 'hex')) !== 0) { const errmsg = `corrupt authentication call detected, oneTimeCode: ${authArgs.oneTimeCode} doesn't belong to caller: ${args.publicKey}` methodLogger.error(errmsg) diff --git a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts index a5a3f532a..4d8af9187 100644 --- a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts +++ b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts @@ -15,6 +15,7 @@ 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, uint32Schema, uuidv4Schema, verifyAndDecrypt } from 'shared' +import { FEDERATION_AUTHENTICATION_TIMEOUT_MS } from 'shared' const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.api.1_0.util.authenticateCommunity`) @@ -45,9 +46,12 @@ export async function startOpenConnectionCallback( if (uuidv4Schema.safeParse(comA.communityUuid).success) { methodLogger.debug('Community UUID is already a valid UUID') return + // check for still ongoing authentication, but with timeout } else if (uint32Schema.safeParse(Number(comA.communityUuid)).success) { - methodLogger.debug('Community UUID is still in authentication...oneTimeCode=', comA.communityUuid) - return + if (comA.updatedAt && (Date.now() - comA.updatedAt.getTime()) < FEDERATION_AUTHENTICATION_TIMEOUT_MS) { + methodLogger.debug('Community UUID is still in authentication...oneTimeCode=', comA.communityUuid) + return + } } // TODO: make sure it is unique const oneTimeCode = randombytes_random().toString() diff --git a/shared/src/const/index.ts b/shared/src/const/index.ts index e6fb80990..549991c61 100644 --- a/shared/src/const/index.ts +++ b/shared/src/const/index.ts @@ -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' \ No newline at end of file +export const REDEEM_JWT_TOKEN_EXPIRATION = '10m' +// 10 minutes +export const FEDERATION_AUTHENTICATION_TIMEOUT_MS = 60 * 60 * 1000 * 10 \ No newline at end of file diff --git a/shared/src/index.ts b/shared/src/index.ts index a9e070d7f..1e44e2c48 100644 --- a/shared/src/index.ts +++ b/shared/src/index.ts @@ -1,5 +1,6 @@ export * from './schema' export * from './enum' +export * from './const' export * from './helper' export * from './logic/decay' export * from './jwt/JWT' diff --git a/shared/src/schema/community.schema.test.ts b/shared/src/schema/community.schema.test.ts new file mode 100644 index 000000000..a46ea9c94 --- /dev/null +++ b/shared/src/schema/community.schema.test.ts @@ -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) + }) +}) diff --git a/shared/src/schema/community.schema.ts b/shared/src/schema/community.schema.ts new file mode 100644 index 000000000..6067e3538 --- /dev/null +++ b/shared/src/schema/community.schema.ts @@ -0,0 +1,7 @@ +import { object, date } from 'zod' +import { uuidv4Schema } from './base.schema' + +export const communityAuthenticatedSchema = object({ + communityUuid: uuidv4Schema, + authenticatedAt: date(), +}) \ No newline at end of file diff --git a/shared/src/schema/index.ts b/shared/src/schema/index.ts index d8c9f9e4c..83455fb73 100644 --- a/shared/src/schema/index.ts +++ b/shared/src/schema/index.ts @@ -1,2 +1,3 @@ export * from './user.schema' -export * from './base.schema' \ No newline at end of file +export * from './base.schema' +export * from './community.schema' \ No newline at end of file From 2a148793863498d9d203df3f0a3d8ee4d801e57a Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Sun, 12 Oct 2025 11:22:24 +0200 Subject: [PATCH 03/48] make update for bun 1.3 --- admin/package.json | 1 + backend/package.json | 3 +- .../resolver/CommunityResolver.test.ts | 3 + bun.lock | 105 +++++------------- core/package.json | 2 + .../logic/settlePendingSenderTransaction.ts | 2 +- database/package.json | 4 +- dht-node/package.json | 2 +- federation/package.json | 6 +- frontend/package.json | 3 +- 10 files changed, 50 insertions(+), 81 deletions(-) diff --git a/admin/package.json b/admin/package.json index 6301b315a..578b16d05 100644 --- a/admin/package.json +++ b/admin/package.json @@ -61,6 +61,7 @@ "@vue/test-utils": "^2.4.6", "config-schema": "*", "cross-env": "^7.0.3", + "dotenv": "^17.2.3", "dotenv-webpack": "^7.0.3", "eslint": "8.57.1", "eslint-config-prettier": "^10.1.1", diff --git a/backend/package.json b/backend/package.json index 659ac59dc..7413d8c83 100644 --- a/backend/package.json +++ b/backend/package.json @@ -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", @@ -62,7 +63,7 @@ "cors": "^2.8.5", "database": "*", "decimal.js-light": "^2.5.1", - "dotenv": "^10.0.0", + "dotenv": "^17.2.3", "esbuild": "^0.25.2", "express": "^4.17.21", "express-slow-down": "^2.0.1", diff --git a/backend/src/graphql/resolver/CommunityResolver.test.ts b/backend/src/graphql/resolver/CommunityResolver.test.ts index 5e7d929e2..57173c9d1 100644 --- a/backend/src/graphql/resolver/CommunityResolver.test.ts +++ b/backend/src/graphql/resolver/CommunityResolver.test.ts @@ -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 () => { diff --git a/bun.lock b/bun.lock index 5c1e61306..e13c10c4f 100644 --- a/bun.lock +++ b/bun.lock @@ -56,6 +56,7 @@ "@vue/test-utils": "^2.4.6", "config-schema": "*", "cross-env": "^7.0.3", + "dotenv": "^17.2.3", "dotenv-webpack": "^7.0.3", "eslint": "8.57.1", "eslint-config-prettier": "^10.1.1", @@ -97,6 +98,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", @@ -113,12 +115,12 @@ "await-semaphore": "^0.1.3", "axios": "^0.21.1", "class-validator": "^0.13.1", - "config-schema": "workspace:*", - "core": "workspace:*", + "config-schema": "*", + "core": "*", "cors": "^2.8.5", - "database": "workspace:*", + "database": "*", "decimal.js-light": "^2.5.1", - "dotenv": "^10.0.0", + "dotenv": "^17.2.3", "esbuild": "^0.25.2", "express": "^4.17.21", "express-slow-down": "^2.0.1", @@ -146,7 +148,7 @@ "random-bigint": "^0.0.1", "reflect-metadata": "^0.1.13", "regenerator-runtime": "^0.14.1", - "shared": "workspace:*", + "shared": "*", "source-map-support": "^0.5.21", "ts-jest": "29.4.0", "ts-node": "^10.9.2", @@ -185,6 +187,7 @@ "database": "*", "esbuild": "^0.25.2", "i18n": "^0.15.1", + "joi": "^17.13.3", "jose": "^4.14.4", "log4js": "^6.9.1", "shared": "*", @@ -198,6 +201,7 @@ "@types/sodium-native": "^2.3.5", "config-schema": "*", "decimal.js-light": "^2.5.1", + "dotenv": "^17.2.3", "graphql-request": "5.0.0", "jest": "27.2.4", "type-graphql": "^1.1.1", @@ -211,11 +215,12 @@ "@types/uuid": "^8.3.4", "cross-env": "^7.0.3", "decimal.js-light": "^2.5.1", - "dotenv": "^10.0.0", + "dotenv": "^17.2.3", "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": "*", @@ -235,6 +240,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", @@ -267,7 +273,7 @@ "@types/uuid": "^8.3.4", "config-schema": "*", "database": "*", - "dotenv": "10.0.0", + "dotenv": "^17.2.3", "esbuild": "^0.25.3", "jest": "27.5.1", "joi": "^17.13.3", @@ -296,6 +302,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", @@ -311,7 +318,8 @@ "cors": "2.8.5", "database": "*", "decimal.js-light": "^2.5.1", - "dotenv": "10.0.0", + "dotenv": "^17.2.3", + "esbuild": "^0.25.3", "express": "^4.17.21", "express-slow-down": "^2.0.1", "graphql": "15.10.1", @@ -326,8 +334,10 @@ "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", @@ -394,10 +404,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": "^17.2.3", "dotenv-webpack": "^7.0.3", "eslint": "8.57.1", "eslint-config-prettier": "^10.1.1", @@ -1026,7 +1037,7 @@ "@types/cookies": ["@types/cookies@0.9.0", "", { "dependencies": { "@types/connect": "*", "@types/express": "*", "@types/keygrip": "*", "@types/node": "*" } }, "sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q=="], - "@types/cors": ["@types/cors@2.8.10", "", {}, "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ=="], + "@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="], "@types/dotenv": ["@types/dotenv@8.2.3", "", { "dependencies": { "dotenv": "*" } }, "sha512-g2FXjlDX/cYuc5CiQvyU/6kkbP1JtmGzh0obW50zD7OKeILVL0NSpPWLXVfqoAGQjom2/SLLx9zHq0KXvD6mbw=="], @@ -1534,8 +1545,6 @@ "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - "chokidar-cli": ["chokidar-cli@3.0.0", "", { "dependencies": { "chokidar": "^3.5.2", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", "yargs": "^13.3.0" }, "bin": { "chokidar": "index.js" } }, "sha512-xVW+Qeh7z15uZRxHOkP93Ux8A0xbPzwK4GaqD8dQOYc34TlkqUhVSS59fK36DOp5WdJlrRzlYSy02Ht99FjZqQ=="], - "chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="], "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], @@ -1662,8 +1671,6 @@ "debugging-stream": ["debugging-stream@2.0.0", "", { "dependencies": { "streamx": "^2.12.4" } }, "sha512-xwfl6wB/3xc553uwtGnSa94jFxnGOc02C0WU2Nmzwr80gzeqn1FX4VcbvoKIhe8L/lPq4BTQttAbrTN94uN8rA=="], - "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], - "decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="], "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], @@ -1740,7 +1747,7 @@ "dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="], - "dotenv": ["dotenv@10.0.0", "", {}, "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q=="], + "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], "dotenv-defaults": ["dotenv-defaults@2.0.2", "", { "dependencies": { "dotenv": "^8.2.0" } }, "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg=="], @@ -2438,8 +2445,6 @@ "lodash.clonedeep": ["lodash.clonedeep@4.5.0", "", {}, "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="], - "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], - "lodash.get": ["lodash.get@4.4.2", "", {}, "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="], "lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="], @@ -2448,8 +2453,6 @@ "lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="], - "lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="], - "lodash.truncate": ["lodash.truncate@4.4.2", "", {}, "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw=="], "log4js": ["log4js@6.9.1", "", { "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", "flatted": "^3.2.7", "rfdc": "^1.3.0", "streamroller": "^3.1.5" } }, "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g=="], @@ -2860,8 +2863,6 @@ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], - "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="], - "requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="], "resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="], @@ -2938,8 +2939,6 @@ "serve-static": ["serve-static@1.16.2", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.19.0" } }, "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw=="], - "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], - "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], @@ -3004,7 +3003,7 @@ "sql-highlight": ["sql-highlight@6.0.0", "", {}, "sha512-+fLpbAbWkQ+d0JEchJT/NrRRXbYRNbG15gFpANx73EwxQB1PRjj+k/OI0GTU0J63g8ikGkJECQp9z8XEJZvPRw=="], - "sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="], + "sqlstring": ["sqlstring@2.3.1", "", {}, "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ=="], "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], @@ -3368,8 +3367,6 @@ "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="], - "which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="], - "which-runtime": ["which-runtime@1.2.1", "", {}, "sha512-8feIHccQFH/whiA1fD1b4c5+Q7T4ry1g1oHYc2mHnFh81tTQFsCvy3zhS2geUapkFAVBddUT/AM1a3rbqJweFg=="], "which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="], @@ -3556,6 +3553,10 @@ "@types/cookies/@types/node": ["@types/node@18.19.96", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-PzBvgsZ7YdFs/Kng1BSW8IGv68/SPcOxYYhT7luxD7QyzIhFS1xPTpfK3K9eHBa7hVwlW+z8nN0mOd515yaduQ=="], + "@types/cors/@types/node": ["@types/node@18.19.96", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-PzBvgsZ7YdFs/Kng1BSW8IGv68/SPcOxYYhT7luxD7QyzIhFS1xPTpfK3K9eHBa7hVwlW+z8nN0mOd515yaduQ=="], + + "@types/dotenv/dotenv": ["dotenv@10.0.0", "", {}, "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q=="], + "@types/express-serve-static-core/@types/node": ["@types/node@18.19.96", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-PzBvgsZ7YdFs/Kng1BSW8IGv68/SPcOxYYhT7luxD7QyzIhFS1xPTpfK3K9eHBa7hVwlW+z8nN0mOd515yaduQ=="], "@types/fs-capacitor/@types/node": ["@types/node@18.19.96", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-PzBvgsZ7YdFs/Kng1BSW8IGv68/SPcOxYYhT7luxD7QyzIhFS1xPTpfK3K9eHBa7hVwlW+z8nN0mOd515yaduQ=="], @@ -3642,6 +3643,8 @@ "apollo-server-express/@types/body-parser": ["@types/body-parser@1.19.0", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ=="], + "apollo-server-express/@types/cors": ["@types/cors@2.8.10", "", {}, "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ=="], + "apollo-utilities/@wry/equality": ["@wry/equality@0.1.11", "", { "dependencies": { "tslib": "^1.9.3" } }, "sha512-mwEVBDUVODlsQQ5dfuLUS5/Tf7jqUKyhKYHmVi4fPB6bDMOfWvUPJmKgS1Z7Za/sOI3vzWt4+O7yCiL/70MogA=="], "apollo-utilities/ts-invariant": ["ts-invariant@0.4.4", "", { "dependencies": { "tslib": "^1.9.3" } }, "sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA=="], @@ -3680,10 +3683,6 @@ "cheerio-select/domutils": ["domutils@2.8.0", "", { "dependencies": { "dom-serializer": "^1.0.1", "domelementtype": "^2.2.0", "domhandler": "^4.2.0" } }, "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A=="], - "chokidar-cli/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], - - "chokidar-cli/yargs": ["yargs@13.3.2", "", { "dependencies": { "cliui": "^5.0.0", "find-up": "^3.0.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^3.0.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^13.1.2" } }, "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw=="], - "citty/consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], "clean-css/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], @@ -3856,7 +3855,7 @@ "mysql/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], - "mysql/sqlstring": ["sqlstring@2.3.1", "", {}, "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ=="], + "mysql2/sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="], "named-placeholders/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], @@ -4112,20 +4111,6 @@ "cheerio/htmlparser2/entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="], - "chokidar-cli/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "chokidar-cli/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - - "chokidar-cli/yargs/cliui": ["cliui@5.0.0", "", { "dependencies": { "string-width": "^3.1.0", "strip-ansi": "^5.2.0", "wrap-ansi": "^5.1.0" } }, "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA=="], - - "chokidar-cli/yargs/find-up": ["find-up@3.0.0", "", { "dependencies": { "locate-path": "^3.0.0" } }, "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg=="], - - "chokidar-cli/yargs/string-width": ["string-width@3.1.0", "", { "dependencies": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" } }, "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w=="], - - "chokidar-cli/yargs/y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], - - "chokidar-cli/yargs/yargs-parser": ["yargs-parser@13.1.2", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg=="], - "css-select/domutils/dom-serializer": ["dom-serializer@1.4.1", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", "entities": "^2.0.0" } }, "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag=="], "database/ts-jest/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], @@ -4342,20 +4327,6 @@ "cheerio-select/domutils/dom-serializer/entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="], - "chokidar-cli/yargs/cliui/strip-ansi": ["strip-ansi@5.2.0", "", { "dependencies": { "ansi-regex": "^4.1.0" } }, "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA=="], - - "chokidar-cli/yargs/cliui/wrap-ansi": ["wrap-ansi@5.1.0", "", { "dependencies": { "ansi-styles": "^3.2.0", "string-width": "^3.0.0", "strip-ansi": "^5.0.0" } }, "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q=="], - - "chokidar-cli/yargs/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="], - - "chokidar-cli/yargs/string-width/emoji-regex": ["emoji-regex@7.0.3", "", {}, "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="], - - "chokidar-cli/yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@2.0.0", "", {}, "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w=="], - - "chokidar-cli/yargs/string-width/strip-ansi": ["strip-ansi@5.2.0", "", { "dependencies": { "ansi-regex": "^4.1.0" } }, "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA=="], - - "chokidar-cli/yargs/yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], - "css-select/domutils/dom-serializer/entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="], "editorconfig/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], @@ -4410,16 +4381,6 @@ "@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "chokidar-cli/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="], - - "chokidar-cli/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], - - "chokidar-cli/yargs/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="], - - "chokidar-cli/yargs/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="], - - "chokidar-cli/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="], - "js-beautify/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], @@ -4429,11 +4390,5 @@ "typeorm/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "vue-apollo/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], - - "chokidar-cli/yargs/cliui/wrap-ansi/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], - - "chokidar-cli/yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], - - "chokidar-cli/yargs/cliui/wrap-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], } } diff --git a/core/package.json b/core/package.json index 9e745f948..6f7e6a3e5 100644 --- a/core/package.json +++ b/core/package.json @@ -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": "*", @@ -41,6 +42,7 @@ "@types/sodium-native": "^2.3.5", "config-schema": "*", "decimal.js-light": "^2.5.1", + "dotenv": "^17.2.3", "graphql-request": "5.0.0", "jest": "27.2.4", "type-graphql": "^1.1.1", diff --git a/core/src/graphql/logic/settlePendingSenderTransaction.ts b/core/src/graphql/logic/settlePendingSenderTransaction.ts index 7e8aa9055..7f55f8ae2 100644 --- a/core/src/graphql/logic/settlePendingSenderTransaction.ts +++ b/core/src/graphql/logic/settlePendingSenderTransaction.ts @@ -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' diff --git a/database/package.json b/database/package.json index 26aa6b9e1..b208446b4 100644 --- a/database/package.json +++ b/database/package.json @@ -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", @@ -53,11 +54,12 @@ "@types/uuid": "^8.3.4", "cross-env": "^7.0.3", "decimal.js-light": "^2.5.1", - "dotenv": "^10.0.0", + "dotenv": "^17.2.3", "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": "*", diff --git a/dht-node/package.json b/dht-node/package.json index ee4b01bef..4824fec20 100644 --- a/dht-node/package.json +++ b/dht-node/package.json @@ -39,7 +39,7 @@ "@types/uuid": "^8.3.4", "config-schema": "*", "database": "*", - "dotenv": "10.0.0", + "dotenv": "^17.2.3", "esbuild": "^0.25.3", "jest": "27.5.1", "joi": "^17.13.3", diff --git a/federation/package.json b/federation/package.json index ab11016a7..78281b373 100644 --- a/federation/package.json +++ b/federation/package.json @@ -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": "^17.2.3", + "esbuild": "^0.25.3", "express": "^4.17.21", "express-slow-down": "^2.0.1", "graphql": "15.10.1", @@ -61,8 +63,10 @@ "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", diff --git a/frontend/package.json b/frontend/package.json index cc71a8a4d..7ecb40508 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -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": "^17.2.3", "dotenv-webpack": "^7.0.3", "eslint": "8.57.1", "eslint-config-prettier": "^10.1.1", From 3c2dedc07710c44d69a9e77228460c7729123429 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Sun, 12 Oct 2025 11:58:24 +0200 Subject: [PATCH 04/48] add bun config --- bunfig.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 bunfig.toml diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 000000000..931d76919 --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,2 @@ +[install] +linker = "hoisted" \ No newline at end of file From 69002ecde18d654a55a5484151c739338db5a4b1 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Sun, 12 Oct 2025 12:03:00 +0200 Subject: [PATCH 05/48] use exact bun version for ci --- .bun-version | 1 + .github/workflows/test_admin_interface.yml | 2 ++ .github/workflows/test_backend.yml | 4 ++++ .github/workflows/test_config.yml | 2 ++ .github/workflows/test_core.yml | 2 ++ .github/workflows/test_database.yml | 4 ++++ .github/workflows/test_dht_node.yml | 2 ++ .github/workflows/test_e2e.yml | 6 ++++++ .github/workflows/test_federation.yml | 2 ++ .github/workflows/test_frontend.yml | 6 +++++- .github/workflows/test_shared.yml | 2 ++ 11 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 .bun-version diff --git a/.bun-version b/.bun-version new file mode 100644 index 000000000..589268e6f --- /dev/null +++ b/.bun-version @@ -0,0 +1 @@ +1.3.0 \ No newline at end of file diff --git a/.github/workflows/test_admin_interface.yml b/.github/workflows/test_admin_interface.yml index 2026b487a..7c7c43f2a 100644 --- a/.github/workflows/test_admin_interface.yml +++ b/.github/workflows/test_admin_interface.yml @@ -54,6 +54,8 @@ jobs: - name: install bun uses: oven-sh/setup-bun@v2 + with: + bun-version-file: '.bun-version' - name: install dependencies run: | diff --git a/.github/workflows/test_backend.yml b/.github/workflows/test_backend.yml index efb59ba37..bcc805295 100644 --- a/.github/workflows/test_backend.yml +++ b/.github/workflows/test_backend.yml @@ -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: | diff --git a/.github/workflows/test_config.yml b/.github/workflows/test_config.yml index 8643bf959..2aad47e9e 100644 --- a/.github/workflows/test_config.yml +++ b/.github/workflows/test_config.yml @@ -31,6 +31,8 @@ jobs: - 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 diff --git a/.github/workflows/test_core.yml b/.github/workflows/test_core.yml index b6e95f60c..eacf6faff 100644 --- a/.github/workflows/test_core.yml +++ b/.github/workflows/test_core.yml @@ -32,6 +32,8 @@ jobs: - name: install bun uses: oven-sh/setup-bun@v2 + with: + bun-version-file: '.bun-version' - name: install dependencies run: | diff --git a/.github/workflows/test_database.yml b/.github/workflows/test_database.yml index e0b52d149..7c573d208 100644 --- a/.github/workflows/test_database.yml +++ b/.github/workflows/test_database.yml @@ -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: | diff --git a/.github/workflows/test_dht_node.yml b/.github/workflows/test_dht_node.yml index bdde2d22f..cbfbd2041 100644 --- a/.github/workflows/test_dht_node.yml +++ b/.github/workflows/test_dht_node.yml @@ -53,6 +53,8 @@ jobs: - name: install bun uses: oven-sh/setup-bun@v2 + with: + bun-version-file: '.bun-version' - name: install dependencies run: | diff --git a/.github/workflows/test_e2e.yml b/.github/workflows/test_e2e.yml index 9a36f063f..dd61f3d82 100644 --- a/.github/workflows/test_e2e.yml +++ b/.github/workflows/test_e2e.yml @@ -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 @@ -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 diff --git a/.github/workflows/test_federation.yml b/.github/workflows/test_federation.yml index 18cceed89..6b461f215 100644 --- a/.github/workflows/test_federation.yml +++ b/.github/workflows/test_federation.yml @@ -53,6 +53,8 @@ jobs: - name: install bun uses: oven-sh/setup-bun@v2 + with: + bun-version-file: '.bun-version' - name: install dependencies run: | diff --git a/.github/workflows/test_frontend.yml b/.github/workflows/test_frontend.yml index 9691e4694..c7595cfd4 100644 --- a/.github/workflows/test_frontend.yml +++ b/.github/workflows/test_frontend.yml @@ -52,6 +52,8 @@ 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 @@ -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 diff --git a/.github/workflows/test_shared.yml b/.github/workflows/test_shared.yml index 6e377dbbf..16accf3c0 100644 --- a/.github/workflows/test_shared.yml +++ b/.github/workflows/test_shared.yml @@ -30,6 +30,8 @@ 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 From d6d626c848bb2ff222069cc98f7fb6990b3d3715 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 13 Oct 2025 07:40:38 +0200 Subject: [PATCH 06/48] add community handshake states table and enum --- ...95-add_community_handshake_states_table.ts | 19 +++++++++ .../src/entity/CommunityHandshakeState.ts | 39 +++++++++++++++++++ database/src/entity/FederatedCommunity.ts | 6 +++ .../src/enum/CommunityHandshakeStateType.ts | 8 ++++ database/src/enum/index.ts | 1 + 5 files changed, 73 insertions(+) create mode 100644 database/migration/migrations/0095-add_community_handshake_states_table.ts create mode 100644 database/src/entity/CommunityHandshakeState.ts create mode 100644 database/src/enum/CommunityHandshakeStateType.ts create mode 100644 database/src/enum/index.ts diff --git a/database/migration/migrations/0095-add_community_handshake_states_table.ts b/database/migration/migrations/0095-add_community_handshake_states_table.ts new file mode 100644 index 000000000..a00d9f6d7 --- /dev/null +++ b/database/migration/migrations/0095-add_community_handshake_states_table.ts @@ -0,0 +1,19 @@ +export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { + 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 NOT NULL, + public_key binary(32) 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>) { + await queryFn(`DROP TABLE community_handshake_states;`) +} diff --git a/database/src/entity/CommunityHandshakeState.ts b/database/src/entity/CommunityHandshakeState.ts new file mode 100644 index 000000000..25352e2b4 --- /dev/null +++ b/database/src/entity/CommunityHandshakeState.ts @@ -0,0 +1,39 @@ +import { CommunityHandshakeStateType } from '../enum' +import { BaseEntity, Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm' +import { FederatedCommunity } from './FederatedCommunity' + +@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 }) + oneTimeCode: number + + @Column({ name: 'public_key', type: 'binary', length: 32 }) + publicKey: Buffer + + @Column({ + type: 'varchar', + length: 255, + default: CommunityHandshakeStateType.OPEN_CONNECTION, + 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 + + @ManyToOne(() => FederatedCommunity, (federatedCommunity) => federatedCommunity.communityHandshakeStates) + @JoinColumn({ name: 'public_key', referencedColumnName: 'publicKey' }) + federatedCommunity: FederatedCommunity +} \ No newline at end of file diff --git a/database/src/entity/FederatedCommunity.ts b/database/src/entity/FederatedCommunity.ts index a6eaee80f..8993f0663 100644 --- a/database/src/entity/FederatedCommunity.ts +++ b/database/src/entity/FederatedCommunity.ts @@ -5,10 +5,12 @@ import { Entity, JoinColumn, ManyToOne, + OneToMany, PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm' import { Community } from './Community' +import { CommunityHandshakeState } from './CommunityHandshakeState' @Entity('federated_communities') export class FederatedCommunity extends BaseEntity { @@ -60,4 +62,8 @@ export class FederatedCommunity extends BaseEntity { ) @JoinColumn({ name: 'public_key', referencedColumnName: 'publicKey' }) community?: Community + + @OneToMany(() => CommunityHandshakeState, (communityHandshakeState) => communityHandshakeState.federatedCommunity) + @JoinColumn({ name: 'public_key', referencedColumnName: 'publicKey' }) + communityHandshakeStates: CommunityHandshakeState[] } diff --git a/database/src/enum/CommunityHandshakeStateType.ts b/database/src/enum/CommunityHandshakeStateType.ts new file mode 100644 index 000000000..e79c4a04c --- /dev/null +++ b/database/src/enum/CommunityHandshakeStateType.ts @@ -0,0 +1,8 @@ +export enum CommunityHandshakeStateType { + OPEN_CONNECTION = 'OPEN_CONNECTION', + OPEN_CONNECTION_CALLBACK = 'OPEN_CONNECTION_CALLBACK', + + SUCCESS = 'SUCCESS', + FAILED = 'FAILED', + EXPIRED = 'EXPIRED' +} \ No newline at end of file diff --git a/database/src/enum/index.ts b/database/src/enum/index.ts new file mode 100644 index 000000000..c1d445299 --- /dev/null +++ b/database/src/enum/index.ts @@ -0,0 +1 @@ +export * from './CommunityHandshakeStateType' \ No newline at end of file From b5b4e94c2a85c93f46e884602c9e0d2f59115b91 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 13 Oct 2025 07:41:47 +0200 Subject: [PATCH 07/48] add entity to index --- database/src/entity/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/database/src/entity/index.ts b/database/src/entity/index.ts index 01195c37e..4d4b5d803 100644 --- a/database/src/entity/index.ts +++ b/database/src/entity/index.ts @@ -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' @@ -26,6 +27,7 @@ export { FederatedCommunity, LoginElopageBuys, Migration, + CommunityHandshakeState, ProjectBranding, OpenaiThreads, PendingTransaction, @@ -46,6 +48,7 @@ export const entities = [ FederatedCommunity, LoginElopageBuys, Migration, + CommunityHandshakeState, ProjectBranding, OpenaiThreads, PendingTransaction, From ebd43543c62cce18a2ae4d87be67d2e4afd12ba6 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 13 Oct 2025 07:42:14 +0200 Subject: [PATCH 08/48] add enum to index --- database/src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/database/src/index.ts b/database/src/index.ts index 56dec24ee..b694567a2 100644 --- a/database/src/index.ts +++ b/database/src/index.ts @@ -60,5 +60,6 @@ export const entities = [ export { latestDbVersion } export * from './logging' export * from './queries' -export * from './util' +export * from './util' +export * from './enum' export { AppDatabase } from './AppDatabase' From ed422fe2930275b7d793a2c8b73af8dde12c186c Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 13 Oct 2025 09:13:26 +0200 Subject: [PATCH 09/48] startCommunityAuthentication refactored --- .../src/federation/authenticateCommunities.ts | 133 +++++++++++------- core/src/index.ts | 1 + .../logic/CommunityHandshakeState.logic.ts | 22 +++ core/src/logic/index.ts | 1 + ...95-add_community_handshake_states_table.ts | 3 +- .../src/entity/CommunityHandshakeState.ts | 5 +- database/src/entity/index.ts | 6 +- .../src/enum/CommunityHandshakeStateType.ts | 1 + database/src/index.ts | 60 +------- .../CommunityHandshakeStateLogging.view.ts | 25 ++++ database/src/logging/CommunityLogging.view.ts | 5 +- database/src/logging/index.ts | 2 + database/src/queries/communities.test.ts | 20 ++- database/src/queries/communities.ts | 9 +- database/src/queries/communityHandshakes.ts | 15 ++ database/src/queries/index.ts | 1 + database/src/seeds/community.ts | 22 ++- 17 files changed, 212 insertions(+), 119 deletions(-) create mode 100644 core/src/logic/CommunityHandshakeState.logic.ts create mode 100644 core/src/logic/index.ts create mode 100644 database/src/logging/CommunityHandshakeStateLogging.view.ts create mode 100644 database/src/queries/communityHandshakes.ts diff --git a/backend/src/federation/authenticateCommunities.ts b/backend/src/federation/authenticateCommunities.ts index f186adb35..0a94b24c8 100644 --- a/backend/src/federation/authenticateCommunities.ts +++ b/backend/src/federation/authenticateCommunities.ts @@ -1,5 +1,14 @@ -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, + CommunityLoggingView, + Community as DbCommunity, + FederatedCommunity as DbFederatedCommunity, + FederatedCommunityLoggingView, + findPendingCommunityHandshake, + getHomeCommunityWithFederatedCommunityOrFail, + CommunityHandshakeStateType +} from 'database' import { randombytes_random } from 'sodium-native' import { CONFIG as CONFIG_CORE } from 'core' @@ -11,65 +20,91 @@ import { communityAuthenticatedSchema, encryptAndSign, OpenConnectionJwtPayloadT import { getLogger } from 'log4js' import { AuthenticationClientFactory } from './client/AuthenticationClientFactory' import { EncryptedTransferArgs } from 'core' +import { CommunityHandshakeStateLogic } from 'core' -const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.authenticateCommunities`) +const createLogger = (functionName: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.authenticateCommunities.${functionName}`) export async function startCommunityAuthentication( fedComB: DbFederatedCommunity, ): Promise { - const methodLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.authenticateCommunities.startCommunityAuthentication`) + const methodLogger = createLogger('startCommunityAuthentication') const handshakeID = randombytes_random().toString() 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 homeComA = await getHomeCommunityWithFederatedCommunityOrFail(fedComB.apiVersion) + methodLogger.debug('homeComA', new CommunityLoggingView(homeComA)) + // check if result is like expected + // TODO: use zod/valibot + if ( + !homeComA.federatedCommunities || + homeComA.federatedCommunities.length === 0 || + homeComA.federatedCommunities[0].apiVersion !== fedComB.apiVersion + ) { + throw new Error(`Missing home community or federated community with api version ${fedComB.apiVersion}`) + } + const homeFedComA = homeComA.federatedCommunities[0] const comB = await DbCommunity.findOneByOrFail({ publicKey: fedComB.publicKey }) methodLogger.debug('started with comB:', new CommunityLoggingView(comB)) // check if communityUuid is not a valid v4Uuid - try { - // communityAuthenticatedSchema.safeParse return true - // - if communityUuid is a valid v4Uuid and - // - if authenticatedAt is a valid date - if (comB && !communityAuthenticatedSchema.safeParse(comB).success) { - 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 + } + 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(fedComB, false) + if (existingState) { + const logic = new CommunityHandshakeStateLogic(existingState) + if (!await logic.isTimeoutUpdate()) { + // authentication with community and api version is still in progress and it is not timeout yet + methodLogger.debug('existingState', new CommunityHandshakeStateLoggingView(existingState)) + return + } + } + + const state = new DbCommunityHandshakeState() + state.publicKey = fedComB.publicKey + state.apiVersion = fedComB.apiVersion + state.status = CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION + state.handshakeId = parseInt(handshakeID) + + const client = AuthenticationClientFactory.getInstance(fedComB) + + if (client instanceof V1_0_AuthenticationClient) { + if (!comB.publicJwtKey) { + state.lastError = 'Public JWT key still not exist for comB ' + comB.name + await state.save() + throw new Error(state.lastError) + } + const stateSaveResolver = state.save() + //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 + await stateSaveResolver + methodLogger.debug('before client.openConnection() args:', args) + const result = await client.openConnection(args) + if (result) { + methodLogger.info(`successful initiated at community:`, fedComB.endPoint) + } else { + methodLogger.error(`can't initiate at community:`, fedComB.endPoint) + } } - methodLogger.removeContext('handshakeID') } diff --git a/core/src/index.ts b/core/src/index.ts index a355bb9bd..a10eb6caa 100644 --- a/core/src/index.ts +++ b/core/src/index.ts @@ -22,4 +22,5 @@ export * from './util/calculateSenderBalance' export * from './util/utilities' export * from './validation/user' export * from './config/index' +export * from './logic' diff --git a/core/src/logic/CommunityHandshakeState.logic.ts b/core/src/logic/CommunityHandshakeState.logic.ts new file mode 100644 index 000000000..a3e8631eb --- /dev/null +++ b/core/src/logic/CommunityHandshakeState.logic.ts @@ -0,0 +1,22 @@ +import { CommunityHandshakeState, CommunityHandshakeStateType } from 'database' +import { FEDERATION_AUTHENTICATION_TIMEOUT_MS } from 'shared' + +export class CommunityHandshakeStateLogic { + public constructor(private communityHandshakeStateEntity: 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 { + if (this.communityHandshakeStateEntity.status === CommunityHandshakeStateType.EXPIRED) { + return true + } + if (Date.now() - this.communityHandshakeStateEntity.updatedAt.getTime() > FEDERATION_AUTHENTICATION_TIMEOUT_MS) { + this.communityHandshakeStateEntity.status = CommunityHandshakeStateType.EXPIRED + await this.communityHandshakeStateEntity.save() + return true + } + return false + } +} diff --git a/core/src/logic/index.ts b/core/src/logic/index.ts new file mode 100644 index 000000000..3a5c48f58 --- /dev/null +++ b/core/src/logic/index.ts @@ -0,0 +1 @@ +export { CommunityHandshakeStateLogic } from './CommunityHandshakeState.logic' \ No newline at end of file diff --git a/database/migration/migrations/0095-add_community_handshake_states_table.ts b/database/migration/migrations/0095-add_community_handshake_states_table.ts index a00d9f6d7..38553c336 100644 --- a/database/migration/migrations/0095-add_community_handshake_states_table.ts +++ b/database/migration/migrations/0095-add_community_handshake_states_table.ts @@ -5,12 +5,13 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis handshake_id int unsigned NOT NULL, one_time_code int unsigned NOT 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), + KEY idx_public_key (public_key) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`) } diff --git a/database/src/entity/CommunityHandshakeState.ts b/database/src/entity/CommunityHandshakeState.ts index 25352e2b4..4fb62a733 100644 --- a/database/src/entity/CommunityHandshakeState.ts +++ b/database/src/entity/CommunityHandshakeState.ts @@ -16,10 +16,13 @@ export class CommunityHandshakeState extends BaseEntity { @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.OPEN_CONNECTION, + default: CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION, nullable: false, }) status: CommunityHandshakeStateType diff --git a/database/src/entity/index.ts b/database/src/entity/index.ts index 4d4b5d803..32bbe239b 100644 --- a/database/src/entity/index.ts +++ b/database/src/entity/index.ts @@ -19,6 +19,7 @@ import { UserRole } from './UserRole' export { Community, + CommunityHandshakeState, Contribution, ContributionLink, ContributionMessage, @@ -26,8 +27,7 @@ export { Event, FederatedCommunity, LoginElopageBuys, - Migration, - CommunityHandshakeState, + Migration, ProjectBranding, OpenaiThreads, PendingTransaction, @@ -40,6 +40,7 @@ export { export const entities = [ Community, + CommunityHandshakeState, Contribution, ContributionLink, ContributionMessage, @@ -48,7 +49,6 @@ export const entities = [ FederatedCommunity, LoginElopageBuys, Migration, - CommunityHandshakeState, ProjectBranding, OpenaiThreads, PendingTransaction, diff --git a/database/src/enum/CommunityHandshakeStateType.ts b/database/src/enum/CommunityHandshakeStateType.ts index e79c4a04c..a047e740d 100644 --- a/database/src/enum/CommunityHandshakeStateType.ts +++ b/database/src/enum/CommunityHandshakeStateType.ts @@ -1,4 +1,5 @@ export enum CommunityHandshakeStateType { + START_COMMUNITY_AUTHENTICATION = 'START_COMMUNITY_AUTHENTICATION', OPEN_CONNECTION = 'OPEN_CONNECTION', OPEN_CONNECTION_CALLBACK = 'OPEN_CONNECTION_CALLBACK', diff --git a/database/src/index.ts b/database/src/index.ts index b694567a2..45b60530c 100644 --- a/database/src/index.ts +++ b/database/src/index.ts @@ -1,63 +1,7 @@ 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' diff --git a/database/src/logging/CommunityHandshakeStateLogging.view.ts b/database/src/logging/CommunityHandshakeStateLogging.view.ts new file mode 100644 index 000000000..5345474a0 --- /dev/null +++ b/database/src/logging/CommunityHandshakeStateLogging.view.ts @@ -0,0 +1,25 @@ +import { CommunityHandshakeState } from '..' +import { AbstractLoggingView } from './AbstractLogging.view' +import { FederatedCommunityLoggingView } from './FederatedCommunityLogging.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), + federatedCommunity: this.self.federatedCommunity + ? new FederatedCommunityLoggingView(this.self.federatedCommunity) + : undefined, + } + } +} \ No newline at end of file diff --git a/database/src/logging/CommunityLogging.view.ts b/database/src/logging/CommunityLogging.view.ts index c06a4db41..1d675828c 100644 --- a/database/src/logging/CommunityLogging.view.ts +++ b/database/src/logging/CommunityLogging.view.ts @@ -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) + ), } } } diff --git a/database/src/logging/index.ts b/database/src/logging/index.ts index c19bd9a57..522fc3b56 100644 --- a/database/src/logging/index.ts +++ b/database/src/logging/index.ts @@ -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) diff --git a/database/src/queries/communities.test.ts b/database/src/queries/communities.test.ts index 18975256c..2d208a224 100644 --- a/database/src/queries/communities.test.ts +++ b/database/src/queries/communities.test.ts @@ -1,6 +1,6 @@ import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity } from '..' import { AppDatabase } from '../AppDatabase' -import { getHomeCommunity, getReachableCommunities } from './communities' +import { getHomeCommunity, getHomeCommunityWithFederatedCommunityOrFail, getReachableCommunities } from './communities' import { describe, expect, it, beforeEach, beforeAll, afterAll } from 'vitest' import { createCommunity, createVerifiedFederatedCommunity } from '../seeds/community' @@ -39,6 +39,24 @@ 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() + }) + }) describe('getReachableCommunities', () => { it('home community counts also to reachable communities', async () => { await createCommunity(false) diff --git a/database/src/queries/communities.ts b/database/src/queries/communities.ts index cdb7cabb6..265be41d4 100644 --- a/database/src/queries/communities.ts +++ b/database/src/queries/communities.ts @@ -10,7 +10,14 @@ export async function getHomeCommunity(): Promise { // 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 { + return await DbCommunity.findOneOrFail({ + where: { foreign: false, federatedCommunities: { apiVersion } }, + relations: { federatedCommunities: true }, }) } diff --git a/database/src/queries/communityHandshakes.ts b/database/src/queries/communityHandshakes.ts new file mode 100644 index 000000000..991bd9c47 --- /dev/null +++ b/database/src/queries/communityHandshakes.ts @@ -0,0 +1,15 @@ +import { CommunityHandshakeState } from '../entity' +import { FederatedCommunity } from '../entity/FederatedCommunity' + +/** + * Find a pending community handshake by public key. + * @param publicKey The public key of the community. + * @param withRelations Whether to include the federated community and community in the result, default true. + * @returns The CommunityHandshakeState with associated federated community and community. + */ +export function findPendingCommunityHandshake(federatedCommunity: FederatedCommunity, withRelations = true): Promise { + return CommunityHandshakeState.findOne({ + where: { publicKey: federatedCommunity.publicKey, apiVersion: federatedCommunity.apiVersion }, + relations: withRelations ? { federatedCommunity: { community: true } } : undefined, + }) +} \ No newline at end of file diff --git a/database/src/queries/index.ts b/database/src/queries/index.ts index 1fec568bf..73a2cc15b 100644 --- a/database/src/queries/index.ts +++ b/database/src/queries/index.ts @@ -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` diff --git a/database/src/seeds/community.ts b/database/src/seeds/community.ts index 4db872398..12a5bd67f 100644 --- a/database/src/seeds/community.ts +++ b/database/src/seeds/community.ts @@ -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 { +/** + * 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 { 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 { 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 } From ae23aafd8728c31fb81de270e834fe02983fe3fb Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 13 Oct 2025 09:27:19 +0200 Subject: [PATCH 10/48] update error handling for startCommunityAuthentication --- .../src/federation/authenticateCommunities.ts | 29 ++++++++++--------- .../logic/CommunityHandshakeState.logic.ts | 15 ++++++++-- database/src/queries/communityHandshakes.ts | 14 +++++++-- .../1_0/resolver/AuthenticationResolver.ts | 11 ++++++- 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/backend/src/federation/authenticateCommunities.ts b/backend/src/federation/authenticateCommunities.ts index 0a94b24c8..4b2e5b77b 100644 --- a/backend/src/federation/authenticateCommunities.ts +++ b/backend/src/federation/authenticateCommunities.ts @@ -63,29 +63,28 @@ export async function startCommunityAuthentication( // check if a authentication is already in progress const existingState = await findPendingCommunityHandshake(fedComB, false) if (existingState) { - const logic = new CommunityHandshakeStateLogic(existingState) - if (!await logic.isTimeoutUpdate()) { + 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', new CommunityHandshakeStateLoggingView(existingState)) return } } - - const state = new DbCommunityHandshakeState() - state.publicKey = fedComB.publicKey - state.apiVersion = fedComB.apiVersion - state.status = CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION - state.handshakeId = parseInt(handshakeID) - + const client = AuthenticationClientFactory.getInstance(fedComB) if (client instanceof V1_0_AuthenticationClient) { if (!comB.publicJwtKey) { - state.lastError = 'Public JWT key still not exist for comB ' + comB.name - await state.save() - throw new Error(state.lastError) + throw new Error(`Public JWT key still not exist for comB ${comB.name}`) } + const state = new DbCommunityHandshakeState() + state.publicKey = fedComB.publicKey + state.apiVersion = fedComB.apiVersion + state.status = CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION + state.handshakeId = parseInt(handshakeID) const stateSaveResolver = state.save() + //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), @@ -104,7 +103,11 @@ export async function startCommunityAuthentication( if (result) { methodLogger.info(`successful initiated at community:`, fedComB.endPoint) } else { - methodLogger.error(`can't initiate at community:`, fedComB.endPoint) + const errorMsg = `can't initiate at community: ${fedComB.endPoint}` + methodLogger.error(errorMsg) + state.status = CommunityHandshakeStateType.FAILED + state.lastError = errorMsg + await state.save() } } } diff --git a/core/src/logic/CommunityHandshakeState.logic.ts b/core/src/logic/CommunityHandshakeState.logic.ts index a3e8631eb..03bf9a762 100644 --- a/core/src/logic/CommunityHandshakeState.logic.ts +++ b/core/src/logic/CommunityHandshakeState.logic.ts @@ -9,14 +9,25 @@ export class CommunityHandshakeStateLogic { * @returns true if the community handshake state is expired */ public async isTimeoutUpdate(): Promise { + const timeout = this.isTimeout() + if (timeout && this.communityHandshakeStateEntity.status !== CommunityHandshakeStateType.EXPIRED) { + this.communityHandshakeStateEntity.status = CommunityHandshakeStateType.EXPIRED + await this.communityHandshakeStateEntity.save() + } + return timeout + } + + public isTimeout(): boolean { if (this.communityHandshakeStateEntity.status === CommunityHandshakeStateType.EXPIRED) { return true } if (Date.now() - this.communityHandshakeStateEntity.updatedAt.getTime() > FEDERATION_AUTHENTICATION_TIMEOUT_MS) { - this.communityHandshakeStateEntity.status = CommunityHandshakeStateType.EXPIRED - await this.communityHandshakeStateEntity.save() return true } return false } + + public isFailed(): boolean { + return this.communityHandshakeStateEntity.status === CommunityHandshakeStateType.FAILED + } } diff --git a/database/src/queries/communityHandshakes.ts b/database/src/queries/communityHandshakes.ts index 991bd9c47..3248b311e 100644 --- a/database/src/queries/communityHandshakes.ts +++ b/database/src/queries/communityHandshakes.ts @@ -1,5 +1,5 @@ -import { CommunityHandshakeState } from '../entity' -import { FederatedCommunity } from '../entity/FederatedCommunity' +import { Not, In } from 'typeorm' +import { CommunityHandshakeState, CommunityHandshakeStateType, FederatedCommunity} from '..' /** * Find a pending community handshake by public key. @@ -9,7 +9,15 @@ import { FederatedCommunity } from '../entity/FederatedCommunity' */ export function findPendingCommunityHandshake(federatedCommunity: FederatedCommunity, withRelations = true): Promise { return CommunityHandshakeState.findOne({ - where: { publicKey: federatedCommunity.publicKey, apiVersion: federatedCommunity.apiVersion }, + where: { + publicKey: federatedCommunity.publicKey, + apiVersion: federatedCommunity.apiVersion, + status: Not(In([ + CommunityHandshakeStateType.EXPIRED, + CommunityHandshakeStateType.FAILED, + CommunityHandshakeStateType.SUCCESS + ])) + }, relations: withRelations ? { federatedCommunity: { community: true } } : undefined, }) } \ No newline at end of file diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index fd948300e..dc6e1347b 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -6,9 +6,11 @@ import { Community as DbCommunity, FederatedCommunity as DbFedCommunity, FederatedCommunityLoggingView, + CommunityHandshakeStateType, + CommunityHandshakeState as DbCommunityHandshakeState, getHomeCommunity, } from 'database' -import { getLogger } from 'log4js' +import { getLogger, Logger } from 'log4js' import { AuthenticationJwtPayloadType, AuthenticationResponseJwtPayloadType, @@ -25,6 +27,13 @@ const createLogger = (method: string ) => getLogger(`${LOG4JS_BASE_CATEGORY_NAM @Resolver() export class AuthenticationResolver { + + private async errorState(errmsg: string, methodLogger: Logger, state: DbCommunityHandshakeState) { + methodLogger.error(errmsg) + state.lastError = errmsg + await state.save() + } + @Mutation(() => Boolean) async openConnection( @Arg('data') From ced6f42fa0c9214764ca85d3592bc3474bb28544 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 13 Oct 2025 10:11:27 +0200 Subject: [PATCH 11/48] refactor startOpenConnectionCallback --- .../src/federation/authenticateCommunities.ts | 15 +-- core/src/logic/Community.logic.ts | 13 ++ .../logic/CommunityHandshakeState.logic.ts | 14 +- core/src/logic/index.ts | 3 +- .../src/enum/CommunityHandshakeStateType.ts | 2 +- database/src/queries/communities.ts | 10 ++ database/src/queries/communityHandshakes.ts | 10 +- .../1_0/resolver/AuthenticationResolver.ts | 10 +- .../api/1_0/util/authenticateCommunity.ts | 123 ++++++++++++------ shared/src/schema/community.schema.ts | 4 +- 10 files changed, 126 insertions(+), 78 deletions(-) create mode 100644 core/src/logic/Community.logic.ts diff --git a/backend/src/federation/authenticateCommunities.ts b/backend/src/federation/authenticateCommunities.ts index 4b2e5b77b..d71314efa 100644 --- a/backend/src/federation/authenticateCommunities.ts +++ b/backend/src/federation/authenticateCommunities.ts @@ -21,6 +21,7 @@ import { getLogger } from 'log4js' import { AuthenticationClientFactory } from './client/AuthenticationClientFactory' import { EncryptedTransferArgs } from 'core' import { CommunityHandshakeStateLogic } from 'core' +import { CommunityLogic } from 'core' const createLogger = (functionName: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.authenticateCommunities.${functionName}`) @@ -35,16 +36,8 @@ export async function startCommunityAuthentication( }) const homeComA = await getHomeCommunityWithFederatedCommunityOrFail(fedComB.apiVersion) methodLogger.debug('homeComA', new CommunityLoggingView(homeComA)) - // check if result is like expected - // TODO: use zod/valibot - if ( - !homeComA.federatedCommunities || - homeComA.federatedCommunities.length === 0 || - homeComA.federatedCommunities[0].apiVersion !== fedComB.apiVersion - ) { - throw new Error(`Missing home community or federated community with api version ${fedComB.apiVersion}`) - } - const homeFedComA = homeComA.federatedCommunities[0] + const homeComALogic = new CommunityLogic(homeComA) + const homeFedComA = homeComALogic.getFederatedCommunityWithApiOrFail(fedComB.apiVersion) const comB = await DbCommunity.findOneByOrFail({ publicKey: fedComB.publicKey }) methodLogger.debug('started with comB:', new CommunityLoggingView(comB)) // check if communityUuid is not a valid v4Uuid @@ -61,7 +54,7 @@ export async function startCommunityAuthentication( ) // check if a authentication is already in progress - const existingState = await findPendingCommunityHandshake(fedComB, false) + const existingState = await findPendingCommunityHandshake(fedComB.publicKey, fedComB.apiVersion, false) if (existingState) { const stateLogic = new CommunityHandshakeStateLogic(existingState) // retry on timeout or failure diff --git a/core/src/logic/Community.logic.ts b/core/src/logic/Community.logic.ts new file mode 100644 index 000000000..40779c703 --- /dev/null +++ b/core/src/logic/Community.logic.ts @@ -0,0 +1,13 @@ +import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity } from 'database' + +export class CommunityLogic { + public constructor(private self: DbCommunity) {} + + public getFederatedCommunityWithApiOrFail(apiVersion: string): DbFederatedCommunity { + const fedCom = this.self.federatedCommunities?.find((fedCom) => fedCom.apiVersion === apiVersion) + if (!fedCom) { + throw new Error(`Missing federated community with api version ${apiVersion}`) + } + return fedCom + } +} \ No newline at end of file diff --git a/core/src/logic/CommunityHandshakeState.logic.ts b/core/src/logic/CommunityHandshakeState.logic.ts index 03bf9a762..e9cb23d5c 100644 --- a/core/src/logic/CommunityHandshakeState.logic.ts +++ b/core/src/logic/CommunityHandshakeState.logic.ts @@ -2,7 +2,7 @@ import { CommunityHandshakeState, CommunityHandshakeStateType } from 'database' import { FEDERATION_AUTHENTICATION_TIMEOUT_MS } from 'shared' export class CommunityHandshakeStateLogic { - public constructor(private communityHandshakeStateEntity: CommunityHandshakeState) {} + public constructor(private self: CommunityHandshakeState) {} /** * Check for expired state and if not, check timeout and update (write into db) to expired state @@ -10,24 +10,24 @@ export class CommunityHandshakeStateLogic { */ public async isTimeoutUpdate(): Promise { const timeout = this.isTimeout() - if (timeout && this.communityHandshakeStateEntity.status !== CommunityHandshakeStateType.EXPIRED) { - this.communityHandshakeStateEntity.status = CommunityHandshakeStateType.EXPIRED - await this.communityHandshakeStateEntity.save() + if (timeout && this.self.status !== CommunityHandshakeStateType.EXPIRED) { + this.self.status = CommunityHandshakeStateType.EXPIRED + await this.self.save() } return timeout } public isTimeout(): boolean { - if (this.communityHandshakeStateEntity.status === CommunityHandshakeStateType.EXPIRED) { + if (this.self.status === CommunityHandshakeStateType.EXPIRED) { return true } - if (Date.now() - this.communityHandshakeStateEntity.updatedAt.getTime() > FEDERATION_AUTHENTICATION_TIMEOUT_MS) { + if (Date.now() - this.self.updatedAt.getTime() > FEDERATION_AUTHENTICATION_TIMEOUT_MS) { return true } return false } public isFailed(): boolean { - return this.communityHandshakeStateEntity.status === CommunityHandshakeStateType.FAILED + return this.self.status === CommunityHandshakeStateType.FAILED } } diff --git a/core/src/logic/index.ts b/core/src/logic/index.ts index 3a5c48f58..809465850 100644 --- a/core/src/logic/index.ts +++ b/core/src/logic/index.ts @@ -1 +1,2 @@ -export { CommunityHandshakeStateLogic } from './CommunityHandshakeState.logic' \ No newline at end of file +export { CommunityHandshakeStateLogic } from './CommunityHandshakeState.logic' +export { CommunityLogic } from './Community.logic' \ No newline at end of file diff --git a/database/src/enum/CommunityHandshakeStateType.ts b/database/src/enum/CommunityHandshakeStateType.ts index a047e740d..5378974aa 100644 --- a/database/src/enum/CommunityHandshakeStateType.ts +++ b/database/src/enum/CommunityHandshakeStateType.ts @@ -1,6 +1,6 @@ export enum CommunityHandshakeStateType { START_COMMUNITY_AUTHENTICATION = 'START_COMMUNITY_AUTHENTICATION', - OPEN_CONNECTION = 'OPEN_CONNECTION', + START_OPEN_CONNECTION_CALLBACK = 'START_OPEN_CONNECTION_CALLBACK', OPEN_CONNECTION_CALLBACK = 'OPEN_CONNECTION_CALLBACK', SUCCESS = 'SUCCESS', diff --git a/database/src/queries/communities.ts b/database/src/queries/communities.ts index 265be41d4..22bfdb39f 100644 --- a/database/src/queries/communities.ts +++ b/database/src/queries/communities.ts @@ -49,6 +49,16 @@ export async function getCommunityWithFederatedCommunityByIdentifier( }) } +export async function getCommunityWithFederatedCommunityWithApiOrFail( + publicKey: Buffer, + apiVersion: string +): Promise { + return await DbCommunity.findOneOrFail({ + where: { foreign: true, publicKey, federatedCommunities: { apiVersion } }, + relations: { federatedCommunities: true }, + }) +} + // returns all reachable communities // home community and all federated communities which have been verified within the last authenticationTimeoutMs export async function getReachableCommunities( diff --git a/database/src/queries/communityHandshakes.ts b/database/src/queries/communityHandshakes.ts index 3248b311e..3902fb26b 100644 --- a/database/src/queries/communityHandshakes.ts +++ b/database/src/queries/communityHandshakes.ts @@ -1,5 +1,5 @@ import { Not, In } from 'typeorm' -import { CommunityHandshakeState, CommunityHandshakeStateType, FederatedCommunity} from '..' +import { CommunityHandshakeState, CommunityHandshakeStateType} from '..' /** * Find a pending community handshake by public key. @@ -7,11 +7,13 @@ import { CommunityHandshakeState, CommunityHandshakeStateType, FederatedCommunit * @param withRelations Whether to include the federated community and community in the result, default true. * @returns The CommunityHandshakeState with associated federated community and community. */ -export function findPendingCommunityHandshake(federatedCommunity: FederatedCommunity, withRelations = true): Promise { +export function findPendingCommunityHandshake( + publicKey: Buffer, apiVersion: string, withRelations = true +): Promise { return CommunityHandshakeState.findOne({ where: { - publicKey: federatedCommunity.publicKey, - apiVersion: federatedCommunity.apiVersion, + publicKey, + apiVersion, status: Not(In([ CommunityHandshakeStateType.EXPIRED, CommunityHandshakeStateType.FAILED, diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index dc6e1347b..74f75239e 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -6,11 +6,9 @@ import { Community as DbCommunity, FederatedCommunity as DbFedCommunity, FederatedCommunityLoggingView, - CommunityHandshakeStateType, - CommunityHandshakeState as DbCommunityHandshakeState, getHomeCommunity, } from 'database' -import { getLogger, Logger } from 'log4js' +import { getLogger } from 'log4js' import { AuthenticationJwtPayloadType, AuthenticationResponseJwtPayloadType, @@ -28,12 +26,6 @@ const createLogger = (method: string ) => getLogger(`${LOG4JS_BASE_CATEGORY_NAM @Resolver() export class AuthenticationResolver { - private async errorState(errmsg: string, methodLogger: Logger, state: DbCommunityHandshakeState) { - methodLogger.error(errmsg) - state.lastError = errmsg - await state.save() - } - @Mutation(() => Boolean) async openConnection( @Arg('data') diff --git a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts index 4d8af9187..3ffd4b3d5 100644 --- a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts +++ b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts @@ -1,12 +1,16 @@ -import { EncryptedTransferArgs } from 'core' +import { CommunityHandshakeStateLogic, CommunityLogic, EncryptedTransferArgs, ensureUrlEndsWithSlash } from 'core' import { + CommunityHandshakeStateLoggingView, CommunityLoggingView, Community as DbCommunity, FederatedCommunity as DbFedCommunity, FederatedCommunityLoggingView, + findPendingCommunityHandshake, + getCommunityWithFederatedCommunityWithApiOrFail, getHomeCommunity, + getHomeCommunityWithFederatedCommunityOrFail, } from 'database' -import { getLogger } from 'log4js' +import { getLogger, Logger } from 'log4js' import { validate as validateUUID, version as versionUUID } from 'uuid' import { AuthenticationClientFactory } from '@/client/AuthenticationClientFactory' @@ -14,80 +18,113 @@ 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, uint32Schema, uuidv4Schema, verifyAndDecrypt } from 'shared' -import { FEDERATION_AUTHENTICATION_TIMEOUT_MS } from 'shared' +import { + AuthenticationJwtPayloadType, + AuthenticationResponseJwtPayloadType, + encryptAndSign, + OpenConnectionCallbackJwtPayloadType, + verifyAndDecrypt +} from 'shared' +import { CommunityHandshakeState as DbCommunityHandshakeState, CommunityHandshakeStateType } from 'database' -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}`) + +async function errorState( + error: string, + methodLogger: Logger, + state: DbCommunityHandshakeState, +): Promise { + methodLogger.error(error) + state.status = CommunityHandshakeStateType.FAILED + state.lastError = error + return state.save() +} export async function startOpenConnectionCallback( handshakeID: string, publicKey: string, api: string, ): Promise { - 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, }) - 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) { - methodLogger.debug('Community UUID is already a valid UUID') + const publicKeyBuffer = Buffer.from(publicKey, 'hex') + const pendingState = await findPendingCommunityHandshake(publicKeyBuffer, api, false) + 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', new CommunityHandshakeStateLoggingView(pendingState)) return - // check for still ongoing authentication, but with timeout - } else if (uint32Schema.safeParse(Number(comA.communityUuid)).success) { - if (comA.updatedAt && (Date.now() - comA.updatedAt.getTime()) < FEDERATION_AUTHENTICATION_TIMEOUT_MS) { - methodLogger.debug('Community UUID is still in authentication...oneTimeCode=', comA.communityUuid) - return - } } + } + let stateSaveResolver: Promise | undefined = undefined + const state = new DbCommunityHandshakeState() + try { + const [homeComB, comA] = await Promise.all([ + getHomeCommunityWithFederatedCommunityOrFail(api), + getCommunityWithFederatedCommunityWithApiOrFail(publicKeyBuffer, api), + ]) + // load helpers + const homeComBLogic = new CommunityLogic(homeComB) + const comALogic = new CommunityLogic(comA) + // get federated communities with correct api version + const homeFedComB = homeComBLogic.getFederatedCommunityWithApiOrFail(api) + const fedComA = comALogic.getFederatedCommunityWithApiOrFail(api) + // TODO: make sure it is unique - const oneTimeCode = randombytes_random().toString() - comA.communityUuid = oneTimeCode - await DbCommunity.save(comA) + const oneTimeCode = randombytes_random() + const oneTimeCodeString = oneTimeCode.toString() + + state.publicKey = publicKeyBuffer + state.apiVersion = api + state.status = CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK + state.handshakeId = parseInt(handshakeID) + state.oneTimeCode = oneTimeCode + stateSaveResolver = state.save() methodLogger.debug( - `Authentication: stored oneTimeCode in requestedCom:`, - new CommunityLoggingView(comA), + `Authentication: store oneTimeCode in CommunityHandshakeState:`, + new CommunityHandshakeStateLoggingView(state), ) 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) + 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 = homeComB.publicKey.toString('hex') args.jwt = jwt args.handshakeID = handshakeID + await stateSaveResolver const result = await client.openConnectionCallback(args) if (result) { - methodLogger.debug('startOpenConnectionCallback() successful:', jwt) + methodLogger.debug(`startOpenConnectionCallback() successful: ${jwt}`) } else { - methodLogger.error('startOpenConnectionCallback() failed:', jwt) + methodLogger.debug(`jwt: ${jwt}`) + stateSaveResolver = errorState('startOpenConnectionCallback() failed', methodLogger, state) } } } catch (err) { - methodLogger.error('error in startOpenConnectionCallback:', err) + let errorString: string = '' + if (err instanceof Error) { + errorString = err.message + } else { + errorString = String(err) + } + stateSaveResolver = errorState(`error in startOpenConnectionCallback: ${errorString}`, methodLogger, state) + } finally { + if (stateSaveResolver) { + await stateSaveResolver + } } - methodLogger.removeContext('handshakeID') } export async function startAuthentication( diff --git a/shared/src/schema/community.schema.ts b/shared/src/schema/community.schema.ts index 6067e3538..6957b16b0 100644 --- a/shared/src/schema/community.schema.ts +++ b/shared/src/schema/community.schema.ts @@ -1,7 +1,7 @@ -import { object, date } from 'zod' +import { object, date, array, string } from 'zod' import { uuidv4Schema } from './base.schema' export const communityAuthenticatedSchema = object({ communityUuid: uuidv4Schema, authenticatedAt: date(), -}) \ No newline at end of file +}) From 6e13f5d8ab558c6e0379b2583d74a1c1fb4f6624 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 13 Oct 2025 12:53:04 +0200 Subject: [PATCH 12/48] finish refactor authentication with states --- .../src/federation/authenticateCommunities.ts | 2 +- .../logic/interpretEncryptedTransferArgs.ts | 16 ++--- core/src/util/utilities.ts | 5 ++ .../src/enum/CommunityHandshakeStateType.ts | 1 + database/src/queries/communityHandshakes.ts | 12 +++- .../1_0/resolver/AuthenticationResolver.ts | 69 ++++++++++++------- .../api/1_0/util/authenticateCommunity.ts | 51 ++++++++++---- shared/src/jwt/JWT.ts | 10 --- 8 files changed, 110 insertions(+), 56 deletions(-) diff --git a/backend/src/federation/authenticateCommunities.ts b/backend/src/federation/authenticateCommunities.ts index d71314efa..78fea419f 100644 --- a/backend/src/federation/authenticateCommunities.ts +++ b/backend/src/federation/authenticateCommunities.ts @@ -100,7 +100,7 @@ export async function startCommunityAuthentication( methodLogger.error(errorMsg) state.status = CommunityHandshakeStateType.FAILED state.lastError = errorMsg - await state.save() } + await state.save() } } diff --git a/core/src/graphql/logic/interpretEncryptedTransferArgs.ts b/core/src/graphql/logic/interpretEncryptedTransferArgs.ts index 301f6da16..31bf47a56 100644 --- a/core/src/graphql/logic/interpretEncryptedTransferArgs.ts +++ b/core/src/graphql/logic/interpretEncryptedTransferArgs.ts @@ -6,37 +6,35 @@ 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 => { - 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) // first find with args.publicKey the community 'requestingCom', which starts the request + // TODO: maybe use community from caller instead of loading it separately const requestingCom = await DbCommunity.findOneBy({ publicKey: Buffer.from(args.publicKey, 'hex') }) if (!requestingCom) { - const errmsg = `unknown requesting community with publicKey ${Buffer.from(args.publicKey, 'hex')}` + const errmsg = `unknown requesting community with publicKey ${args.publicKey}` 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 ${args.publicKey}` 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 ${args.publicKey}` methodLogger.error(errmsg) - methodLogger.removeContext('handshakeID') throw new Error(errmsg) } methodLogger.debug('jwtPayload', jwtPayload) - methodLogger.removeContext('handshakeID') return jwtPayload } diff --git a/core/src/util/utilities.ts b/core/src/util/utilities.ts index 0e8a8da85..be32736fc 100644 --- a/core/src/util/utilities.ts +++ b/core/src/util/utilities.ts @@ -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. * diff --git a/database/src/enum/CommunityHandshakeStateType.ts b/database/src/enum/CommunityHandshakeStateType.ts index 5378974aa..8b811da61 100644 --- a/database/src/enum/CommunityHandshakeStateType.ts +++ b/database/src/enum/CommunityHandshakeStateType.ts @@ -1,6 +1,7 @@ export enum CommunityHandshakeStateType { START_COMMUNITY_AUTHENTICATION = 'START_COMMUNITY_AUTHENTICATION', START_OPEN_CONNECTION_CALLBACK = 'START_OPEN_CONNECTION_CALLBACK', + START_AUTHENTICATION = 'START_AUTHENTICATION', OPEN_CONNECTION_CALLBACK = 'OPEN_CONNECTION_CALLBACK', SUCCESS = 'SUCCESS', diff --git a/database/src/queries/communityHandshakes.ts b/database/src/queries/communityHandshakes.ts index 3902fb26b..f8fa2ba89 100644 --- a/database/src/queries/communityHandshakes.ts +++ b/database/src/queries/communityHandshakes.ts @@ -22,4 +22,14 @@ export function findPendingCommunityHandshake( }, relations: withRelations ? { federatedCommunity: { community: true } } : undefined, }) -} \ No newline at end of file +} + +export function findPendingCommunityHandshakeOrFailByOneTimeCode( + oneTimeCode: number +): Promise { + return CommunityHandshakeState.findOneOrFail({ + where: { oneTimeCode }, + relations: { federatedCommunity: { community: true } }, + }) +} + \ No newline at end of file diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index 74f75239e..40b5e2c18 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -1,12 +1,17 @@ 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, + CommunityHandshakeStateLoggingView, + CommunityHandshakeState as DbCommunityHandshakeState, + CommunityHandshakeStateType, Community as DbCommunity, FederatedCommunity as DbFedCommunity, FederatedCommunityLoggingView, getHomeCommunity, + findPendingCommunityHandshake, + findPendingCommunityHandshakeOrFailByOneTimeCode, } from 'database' import { getLogger } from 'log4js' import { @@ -93,9 +98,7 @@ export class AuthenticationResolver { // no infos to the caller return true } - - const endPoint = openConnectionCallbackJwtPayload.url.slice(0, openConnectionCallbackJwtPayload.url.lastIndexOf('/') + 1) - const apiVersion = openConnectionCallbackJwtPayload.url.slice(openConnectionCallbackJwtPayload.url.lastIndexOf('/') + 1, openConnectionCallbackJwtPayload.url.length) + const { endPoint, apiVersion } = splitUrlInEndPointAndApiVersion(openConnectionCallbackJwtPayload.url) methodLogger.debug(`search fedComB per:`, endPoint, apiVersion) const fedComB = await DbFedCommunity.findOneBy({ endPoint, apiVersion }) if (!fedComB) { @@ -126,45 +129,47 @@ export class AuthenticationResolver { const methodLogger = createLogger('authenticate') methodLogger.addContext('handshakeID', args.handshakeID) methodLogger.debug(`authenticate() via apiVersion=1_0 ...`, args) + let state: DbCommunityHandshakeState | null = null + let stateSaveResolver: Promise | undefined = undefined 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 + throw new Error(`invalid authentication payload of requesting community with publicKey ${args.publicKey}`) } 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 + throw new Error( + `invalid oneTimeCode: ${authArgs.oneTimeCode} for community with publicKey ${authArgs.publicKey}, expect uint32` + ) } + state = await findPendingCommunityHandshakeOrFailByOneTimeCode(Number(authArgs.oneTimeCode)) + 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 + stateSaveResolver = state.save() + methodLogger.debug(`search community per oneTimeCode:`, authArgs.oneTimeCode) - const authCom = await DbCommunity.findOneByOrFail({ communityUuid: authArgs.oneTimeCode }) + const authCom = state.federatedCommunity.community if (authCom) { methodLogger.debug('found authCom:', new CommunityLoggingView(authCom)) methodLogger.debug('authCom.publicKey', authCom.publicKey.toString('hex')) methodLogger.debug('args.publicKey', args.publicKey) if (authCom.publicKey.compare(Buffer.from(args.publicKey, 'hex')) !== 0) { - const errmsg = `corrupt authentication call detected, oneTimeCode: ${authArgs.oneTimeCode} doesn't belong to caller: ${args.publicKey}` - methodLogger.error(errmsg) - // no infos to the caller - return null + throw new Error( + `corrupt authentication call detected, oneTimeCode: ${authArgs.oneTimeCode} doesn't belong to caller: ${args.publicKey}` + ) } const communityUuid = uuidv4Schema.safeParse(authArgs.uuid) if (!communityUuid.success) { - const errmsg = `invalid uuid: ${authArgs.uuid} for community with publicKey ${authCom.publicKey}` - methodLogger.error(errmsg) - // no infos to the caller - return null + throw new Error(`invalid uuid: ${authArgs.uuid} for community with publicKey ${authCom.publicKey}`) } authCom.communityUuid = communityUuid.data authCom.authenticatedAt = new Date() - await DbCommunity.save(authCom) + await authCom.save() methodLogger.debug('store authCom.uuid successfully:', new CommunityLoggingView(authCom)) const homeComB = await getHomeCommunity() if (homeComB?.communityUuid) { @@ -175,8 +180,26 @@ export class AuthenticationResolver { } return null } catch (err) { - methodLogger.error('invalid jwt token:', err) + let errorString = '' + if (err instanceof Error) { + errorString = err.message + } else { + errorString = String(err) + } + if (state) { + methodLogger.info(`state: ${new CommunityHandshakeStateLoggingView(state)}`) + state.status = CommunityHandshakeStateType.FAILED + state.lastError = errorString + stateSaveResolver = state.save() + } + methodLogger.error(`failed: ${errorString}`) + // no infos to the caller return null + } finally { + if (stateSaveResolver) { + await stateSaveResolver + } } + } } diff --git a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts index 3ffd4b3d5..d901a84c3 100644 --- a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts +++ b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts @@ -132,12 +132,14 @@ export async function startAuthentication( oneTimeCode: string, fedComB: DbFedCommunity, ): Promise { - 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), }) + let state: DbCommunityHandshakeState | null = null + let stateSaveResolver: Promise | undefined = undefined try { const homeComA = await getHomeCommunity() const comB = await DbCommunity.findOneByOrFail({ @@ -147,6 +149,17 @@ export async function startAuthentication( if (!comB.publicJwtKey) { throw new Error('Public JWT key still not exist for foreign community') } + state = await findPendingCommunityHandshake(fedComB.publicKey, fedComB.apiVersion, false) + if (!state) { + throw new Error('No pending community handshake found') + } + const stateLogic = new CommunityHandshakeStateLogic(state) + if (await stateLogic.isTimeoutUpdate() || state.status !== CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION) { + methodLogger.debug('invalid state', new CommunityHandshakeStateLoggingView(state)) + throw new Error('No valid pending community handshake found') + } + state.status = CommunityHandshakeStateType.START_AUTHENTICATION + stateSaveResolver = state.save() const client = AuthenticationClientFactory.getInstance(fedComB) @@ -161,6 +174,7 @@ export async function startAuthentication( methodLogger.debug(`invoke authenticate() with:`, args) const responseJwt = await client.authenticate(args) methodLogger.debug(`response of authenticate():`, responseJwt) + if (responseJwt !== null) { const payload = await verifyAndDecrypt(handshakeID, responseJwt, homeComA!.privateJwtKey!, comB.publicJwtKey!) as AuthenticationResponseJwtPayloadType methodLogger.debug( @@ -169,27 +183,40 @@ export async function startAuthentication( 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 ${comB.publicKey}`) } 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) + throw new Error(`Invalid uuid in authenticate-response of community with publicKey ${comB.publicKey}`) } comB.communityUuid = payload.uuid comB.authenticatedAt = new Date() - await DbCommunity.save(comB) + await DbCommunity.save(comB) + state.status = CommunityHandshakeStateType.SUCCESS + stateSaveResolver = state.save() methodLogger.debug('Community Authentication successful:', new CommunityLoggingView(comB)) } else { + state.status = CommunityHandshakeStateType.FAILED + state.lastError = 'Community Authentication failed, empty response' + stateSaveResolver = state.save() methodLogger.error('Community Authentication failed:', authenticationArgs) } } } catch (err) { - methodLogger.error('error in startAuthentication:', err) + let errorString: string = '' + if (err instanceof Error) { + errorString = err.message + } else { + errorString = String(err) + } + if (state) { + state.status = CommunityHandshakeStateType.FAILED + state.lastError = errorString + stateSaveResolver = state.save() + } + methodLogger.error('error in startAuthentication:', errorString) + } finally { + if (stateSaveResolver) { + await stateSaveResolver + } } - methodLogger.removeContext('handshakeID') } diff --git a/shared/src/jwt/JWT.ts b/shared/src/jwt/JWT.ts index 1af50f5bd..7c8fd799d 100644 --- a/shared/src/jwt/JWT.ts +++ b/shared/src/jwt/JWT.ts @@ -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 } From a31ba3756a38766aa4ed65cf3f6f735c8ee4d8e3 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 13 Oct 2025 13:09:51 +0200 Subject: [PATCH 13/48] one time code default NULL --- .../migrations/0095-add_community_handshake_states_table.ts | 2 +- database/src/entity/CommunityHandshakeState.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/database/migration/migrations/0095-add_community_handshake_states_table.ts b/database/migration/migrations/0095-add_community_handshake_states_table.ts index 38553c336..45e5b29a8 100644 --- a/database/migration/migrations/0095-add_community_handshake_states_table.ts +++ b/database/migration/migrations/0095-add_community_handshake_states_table.ts @@ -3,7 +3,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis CREATE TABLE community_handshake_states ( id int unsigned NOT NULL AUTO_INCREMENT, handshake_id int unsigned NOT NULL, - one_time_code 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', diff --git a/database/src/entity/CommunityHandshakeState.ts b/database/src/entity/CommunityHandshakeState.ts index 4fb62a733..f3a27b834 100644 --- a/database/src/entity/CommunityHandshakeState.ts +++ b/database/src/entity/CommunityHandshakeState.ts @@ -10,8 +10,8 @@ export class CommunityHandshakeState extends BaseEntity { @Column({ name: 'handshake_id', type: 'int', unsigned: true }) handshakeId: number - @Column({ name: 'one_time_code', type: 'int', unsigned: true }) - oneTimeCode: 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 From 95d6b9175f15706d4ae93913d37234b4cb38a3e7 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 13 Oct 2025 17:09:13 +0200 Subject: [PATCH 14/48] change dotenv version --- admin/package.json | 2 +- backend/package.json | 2 +- core/package.json | 2 +- database/package.json | 2 +- dht-node/package.json | 2 +- federation/package.json | 2 +- frontend/package.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/admin/package.json b/admin/package.json index 578b16d05..53f47282d 100644 --- a/admin/package.json +++ b/admin/package.json @@ -61,7 +61,7 @@ "@vue/test-utils": "^2.4.6", "config-schema": "*", "cross-env": "^7.0.3", - "dotenv": "^17.2.3", + "dotenv": "^10.0.0", "dotenv-webpack": "^7.0.3", "eslint": "8.57.1", "eslint-config-prettier": "^10.1.1", diff --git a/backend/package.json b/backend/package.json index 7413d8c83..ae82cea7d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -63,7 +63,7 @@ "cors": "^2.8.5", "database": "*", "decimal.js-light": "^2.5.1", - "dotenv": "^17.2.3", + "dotenv": "^10.0.0", "esbuild": "^0.25.2", "express": "^4.17.21", "express-slow-down": "^2.0.1", diff --git a/core/package.json b/core/package.json index 6f7e6a3e5..7a4d515a9 100644 --- a/core/package.json +++ b/core/package.json @@ -42,7 +42,7 @@ "@types/sodium-native": "^2.3.5", "config-schema": "*", "decimal.js-light": "^2.5.1", - "dotenv": "^17.2.3", + "dotenv": "^10.0.0", "graphql-request": "5.0.0", "jest": "27.2.4", "type-graphql": "^1.1.1", diff --git a/database/package.json b/database/package.json index b208446b4..6b77f99b5 100644 --- a/database/package.json +++ b/database/package.json @@ -54,7 +54,7 @@ "@types/uuid": "^8.3.4", "cross-env": "^7.0.3", "decimal.js-light": "^2.5.1", - "dotenv": "^17.2.3", + "dotenv": "^10.0.0", "esbuild": "^0.25.2", "geojson": "^0.5.0", "joi-extract-type": "^15.0.8", diff --git a/dht-node/package.json b/dht-node/package.json index 4824fec20..887415060 100644 --- a/dht-node/package.json +++ b/dht-node/package.json @@ -39,7 +39,7 @@ "@types/uuid": "^8.3.4", "config-schema": "*", "database": "*", - "dotenv": "^17.2.3", + "dotenv": "^10.0.0", "esbuild": "^0.25.3", "jest": "27.5.1", "joi": "^17.13.3", diff --git a/federation/package.json b/federation/package.json index 78281b373..b4a69cc0d 100644 --- a/federation/package.json +++ b/federation/package.json @@ -47,7 +47,7 @@ "cors": "2.8.5", "database": "*", "decimal.js-light": "^2.5.1", - "dotenv": "^17.2.3", + "dotenv": "^10.0.0", "esbuild": "^0.25.3", "express": "^4.17.21", "express-slow-down": "^2.0.1", diff --git a/frontend/package.json b/frontend/package.json index 7ecb40508..5c18b5e62 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -84,7 +84,7 @@ "concurrently": "^9.1.2", "config-schema": "*", "cross-env": "^7.0.3", - "dotenv": "^17.2.3", + "dotenv": "^10.0.0", "dotenv-webpack": "^7.0.3", "eslint": "8.57.1", "eslint-config-prettier": "^10.1.1", From fd262f57a8094d07c514c173f473393c98abbd07 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 13 Oct 2025 17:10:40 +0200 Subject: [PATCH 15/48] add missing changed bun lockfile --- bun.lock | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/bun.lock b/bun.lock index e13c10c4f..740d736f7 100644 --- a/bun.lock +++ b/bun.lock @@ -56,7 +56,7 @@ "@vue/test-utils": "^2.4.6", "config-schema": "*", "cross-env": "^7.0.3", - "dotenv": "^17.2.3", + "dotenv": "^10.0.0", "dotenv-webpack": "^7.0.3", "eslint": "8.57.1", "eslint-config-prettier": "^10.1.1", @@ -120,7 +120,7 @@ "cors": "^2.8.5", "database": "*", "decimal.js-light": "^2.5.1", - "dotenv": "^17.2.3", + "dotenv": "^10.0.0", "esbuild": "^0.25.2", "express": "^4.17.21", "express-slow-down": "^2.0.1", @@ -201,7 +201,7 @@ "@types/sodium-native": "^2.3.5", "config-schema": "*", "decimal.js-light": "^2.5.1", - "dotenv": "^17.2.3", + "dotenv": "^10.0.0", "graphql-request": "5.0.0", "jest": "27.2.4", "type-graphql": "^1.1.1", @@ -215,7 +215,7 @@ "@types/uuid": "^8.3.4", "cross-env": "^7.0.3", "decimal.js-light": "^2.5.1", - "dotenv": "^17.2.3", + "dotenv": "^10.0.0", "esbuild": "^0.25.2", "geojson": "^0.5.0", "joi-extract-type": "^15.0.8", @@ -273,7 +273,7 @@ "@types/uuid": "^8.3.4", "config-schema": "*", "database": "*", - "dotenv": "^17.2.3", + "dotenv": "^10.0.0", "esbuild": "^0.25.3", "jest": "27.5.1", "joi": "^17.13.3", @@ -318,7 +318,7 @@ "cors": "2.8.5", "database": "*", "decimal.js-light": "^2.5.1", - "dotenv": "^17.2.3", + "dotenv": "^10.0.0", "esbuild": "^0.25.3", "express": "^4.17.21", "express-slow-down": "^2.0.1", @@ -408,7 +408,7 @@ "concurrently": "^9.1.2", "config-schema": "*", "cross-env": "^7.0.3", - "dotenv": "^17.2.3", + "dotenv": "^10.0.0", "dotenv-webpack": "^7.0.3", "eslint": "8.57.1", "eslint-config-prettier": "^10.1.1", @@ -1747,7 +1747,7 @@ "dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="], - "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], + "dotenv": ["dotenv@10.0.0", "", {}, "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q=="], "dotenv-defaults": ["dotenv-defaults@2.0.2", "", { "dependencies": { "dotenv": "^8.2.0" } }, "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg=="], @@ -3555,8 +3555,6 @@ "@types/cors/@types/node": ["@types/node@18.19.96", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-PzBvgsZ7YdFs/Kng1BSW8IGv68/SPcOxYYhT7luxD7QyzIhFS1xPTpfK3K9eHBa7hVwlW+z8nN0mOd515yaduQ=="], - "@types/dotenv/dotenv": ["dotenv@10.0.0", "", {}, "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q=="], - "@types/express-serve-static-core/@types/node": ["@types/node@18.19.96", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-PzBvgsZ7YdFs/Kng1BSW8IGv68/SPcOxYYhT7luxD7QyzIhFS1xPTpfK3K9eHBa7hVwlW+z8nN0mOd515yaduQ=="], "@types/fs-capacitor/@types/node": ["@types/node@18.19.96", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-PzBvgsZ7YdFs/Kng1BSW8IGv68/SPcOxYYhT7luxD7QyzIhFS1xPTpfK3K9eHBa7hVwlW+z8nN0mOd515yaduQ=="], From 49010af54fa1b856c8f4c84717f1f9bde79eff89 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 13 Oct 2025 17:12:03 +0200 Subject: [PATCH 16/48] use own class for buffer/string hex handling --- .../src/federation/authenticateCommunities.ts | 16 ++-- backend/src/federation/validateCommunities.ts | 11 ++- database/src/queries/communities.ts | 12 ++- database/src/queries/communityHandshakes.ts | 5 +- .../1_0/resolver/AuthenticationResolver.ts | 87 ++++++++++--------- .../api/1_0/util/authenticateCommunity.ts | 32 ++++--- shared/src/helper/BinaryData.ts | 41 +++++++++ shared/src/helper/index.ts | 3 +- shared/src/schema/base.schema.test.ts | 17 +++- shared/src/schema/base.schema.ts | 9 +- 10 files changed, 157 insertions(+), 76 deletions(-) create mode 100644 shared/src/helper/BinaryData.ts diff --git a/backend/src/federation/authenticateCommunities.ts b/backend/src/federation/authenticateCommunities.ts index 78fea419f..3f4b769e8 100644 --- a/backend/src/federation/authenticateCommunities.ts +++ b/backend/src/federation/authenticateCommunities.ts @@ -2,15 +2,14 @@ import { CommunityHandshakeState as DbCommunityHandshakeState, CommunityHandshakeStateLoggingView, CommunityLoggingView, - Community as DbCommunity, FederatedCommunity as DbFederatedCommunity, FederatedCommunityLoggingView, findPendingCommunityHandshake, getHomeCommunityWithFederatedCommunityOrFail, - CommunityHandshakeStateType + 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' @@ -22,6 +21,7 @@ import { AuthenticationClientFactory } from './client/AuthenticationClientFactor import { EncryptedTransferArgs } from 'core' import { CommunityHandshakeStateLogic } from 'core' import { CommunityLogic } from 'core' +import { Ed25519PublicKey } from 'shared' const createLogger = (functionName: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.authenticateCommunities.${functionName}`) @@ -38,7 +38,8 @@ export async function startCommunityAuthentication( methodLogger.debug('homeComA', new CommunityLoggingView(homeComA)) const homeComALogic = new CommunityLogic(homeComA) const homeFedComA = homeComALogic.getFederatedCommunityWithApiOrFail(fedComB.apiVersion) - const comB = await DbCommunity.findOneByOrFail({ publicKey: fedComB.publicKey }) + const fedComBPublicKey = new Ed25519PublicKey(fedComB.publicKey) + const comB = await getCommunityByPublicKeyOrFail(fedComBPublicKey) methodLogger.debug('started with comB:', new CommunityLoggingView(comB)) // check if communityUuid is not a valid v4Uuid @@ -54,7 +55,7 @@ export async function startCommunityAuthentication( ) // check if a authentication is already in progress - const existingState = await findPendingCommunityHandshake(fedComB.publicKey, fedComB.apiVersion, false) + const existingState = await findPendingCommunityHandshake(fedComBPublicKey, fedComB.apiVersion, false) if (existingState) { const stateLogic = new CommunityHandshakeStateLogic(existingState) // retry on timeout or failure @@ -72,7 +73,7 @@ export async function startCommunityAuthentication( throw new Error(`Public JWT key still not exist for comB ${comB.name}`) } const state = new DbCommunityHandshakeState() - state.publicKey = fedComB.publicKey + state.publicKey = fedComBPublicKey.asBuffer() state.apiVersion = fedComB.apiVersion state.status = CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION state.handshakeId = parseInt(handshakeID) @@ -87,7 +88,8 @@ export async function startCommunityAuthentication( methodLogger.debug('jws', jws) // prepare the args for the client invocation const args = new EncryptedTransferArgs() - args.publicKey = homeComA!.publicKey.toString('hex') + const homeComAPublicKey = new Ed25519PublicKey(homeComA!.publicKey) + args.publicKey = homeComAPublicKey.asHex() args.jwt = jws args.handshakeID = handshakeID await stateSaveResolver diff --git a/backend/src/federation/validateCommunities.ts b/backend/src/federation/validateCommunities.ts index cb56a8c53..8d8972ed5 100644 --- a/backend/src/federation/validateCommunities.ts +++ b/backend/src/federation/validateCommunities.ts @@ -12,7 +12,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, uint32Schema } from 'shared' +import { buffer32Schema, createKeyPair, Ed25519PublicKey, hex64Schema, uint32Schema } from 'shared' import { getLogger } from 'log4js' import { startCommunityAuthentication } from './authenticateCommunities' import { PublicCommunityInfoLoggingView } from './client/1_0/logging/PublicCommunityInfoLogging.view' @@ -67,8 +67,11 @@ export async function validateCommunities(): Promise { 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) const pubComInfo = await client.getPublicCommunityInfo() @@ -84,7 +87,7 @@ export async function validateCommunities(): Promise { 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) { diff --git a/database/src/queries/communities.ts b/database/src/queries/communities.ts index 22bfdb39f..81cd12765 100644 --- a/database/src/queries/communities.ts +++ b/database/src/queries/communities.ts @@ -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. @@ -50,15 +50,21 @@ export async function getCommunityWithFederatedCommunityByIdentifier( } export async function getCommunityWithFederatedCommunityWithApiOrFail( - publicKey: Buffer, + publicKey: Ed25519PublicKey, apiVersion: string ): Promise { return await DbCommunity.findOneOrFail({ - where: { foreign: true, publicKey, federatedCommunities: { apiVersion } }, + where: { foreign: true, publicKey: publicKey.asBuffer(), federatedCommunities: { apiVersion } }, relations: { federatedCommunities: true }, }) } +export async function getCommunityByPublicKeyOrFail(publicKey: Ed25519PublicKey): Promise { + 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( diff --git a/database/src/queries/communityHandshakes.ts b/database/src/queries/communityHandshakes.ts index f8fa2ba89..9181cfbb3 100644 --- a/database/src/queries/communityHandshakes.ts +++ b/database/src/queries/communityHandshakes.ts @@ -1,5 +1,6 @@ import { Not, In } from 'typeorm' import { CommunityHandshakeState, CommunityHandshakeStateType} from '..' +import { Ed25519PublicKey } from 'shared' /** * Find a pending community handshake by public key. @@ -8,11 +9,11 @@ import { CommunityHandshakeState, CommunityHandshakeStateType} from '..' * @returns The CommunityHandshakeState with associated federated community and community. */ export function findPendingCommunityHandshake( - publicKey: Buffer, apiVersion: string, withRelations = true + publicKey: Ed25519PublicKey, apiVersion: string, withRelations = true ): Promise { return CommunityHandshakeState.findOne({ where: { - publicKey, + publicKey: publicKey.asBuffer(), apiVersion, status: Not(In([ CommunityHandshakeStateType.EXPIRED, diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index 40b5e2c18..50bedf34c 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -6,17 +6,16 @@ import { CommunityHandshakeStateLoggingView, CommunityHandshakeState as DbCommunityHandshakeState, CommunityHandshakeStateType, - Community as DbCommunity, FederatedCommunity as DbFedCommunity, FederatedCommunityLoggingView, getHomeCommunity, - findPendingCommunityHandshake, findPendingCommunityHandshakeOrFailByOneTimeCode, } from 'database' import { getLogger } from 'log4js' import { AuthenticationJwtPayloadType, AuthenticationResponseJwtPayloadType, + Ed25519PublicKey, encryptAndSign, OpenConnectionCallbackJwtPayloadType, OpenConnectionJwtPayloadType, @@ -39,44 +38,40 @@ export class AuthenticationResolver { const methodLogger = createLogger('openConnection') methodLogger.addContext('handshakeID', args.handshakeID) methodLogger.debug(`openConnection() via apiVersion=1_0:`, args) + const argsPublicKey = new Ed25519PublicKey(args.publicKey) try { const openConnectionJwtPayload = await interpretEncryptedTransferArgs(args) as OpenConnectionJwtPayloadType methodLogger.debug('openConnectionJwtPayload', openConnectionJwtPayload) 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 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(`vor DbFedCommunity.findOneByOrFail()...`, { publicKey: argsPublicKey.asHex() }) + const fedComA = await DbFedCommunity.findOneByOrFail({ publicKey: argsPublicKey.asBuffer() }) methodLogger.debug(`nach DbFedCommunity.findOneByOrFail()...`, fedComA) methodLogger.debug('fedComA', 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()}`) } // no await to respond immediately and invoke callback-request asynchronously - void startOpenConnectionCallback(args.handshakeID, args.publicKey, CONFIG.FEDERATION_API) + void startOpenConnectionCallback(args.handshakeID, argsPublicKey, CONFIG.FEDERATION_API) methodLogger.debug('openConnection() successfully initiated callback and returns true immediately...') return true } catch (err) { - methodLogger.error('invalid jwt token:', err) + let errorText = '' + if (err instanceof Error) { + errorText = err.message + } else { + errorText = String(err) + } + methodLogger.error('invalid jwt token:', errorText) + // no infos to the caller return true } } @@ -93,19 +88,13 @@ export class AuthenticationResolver { // 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, 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 with url ${openConnectionCallbackJwtPayload.url}`) } methodLogger.debug( `found fedComB and start authentication:`, @@ -116,7 +105,14 @@ export class AuthenticationResolver { methodLogger.debug('openConnectionCallback() successfully initiated authentication and returns true immediately...') return true } catch (err) { - methodLogger.error('invalid jwt token:', err) + let errorText = '' + if (err instanceof Error) { + errorText = err.message + } else { + errorText = String(err) + } + methodLogger.error('invalid jwt token:', errorText) + // no infos to the caller return true } } @@ -131,22 +127,26 @@ export class AuthenticationResolver { methodLogger.debug(`authenticate() via apiVersion=1_0 ...`, args) let state: DbCommunityHandshakeState | null = null let stateSaveResolver: Promise | undefined = undefined + const argsPublicKey = new Ed25519PublicKey(args.publicKey) try { const authArgs = await interpretEncryptedTransferArgs(args) as AuthenticationJwtPayloadType methodLogger.debug(`interpreted authentication payload...authArgs:`, authArgs) if (!authArgs) { - throw new Error(`invalid authentication payload of requesting community with publicKey ${args.publicKey}`) + throw new Error(`invalid authentication payload of requesting community with publicKey ${argsPublicKey.asHex()}`) } - - if (!uint32Schema.safeParse(Number(authArgs.oneTimeCode)).success) { + const validOneTimeCode = uint32Schema.safeParse(Number(authArgs.oneTimeCode)) + if (!validOneTimeCode.success) { throw new Error( - `invalid oneTimeCode: ${authArgs.oneTimeCode} for community with publicKey ${authArgs.publicKey}, expect uint32` + `invalid oneTimeCode: ${authArgs.oneTimeCode} for community with publicKey ${argsPublicKey.asHex()}, expect uint32` ) } - state = await findPendingCommunityHandshakeOrFailByOneTimeCode(Number(authArgs.oneTimeCode)) + state = await findPendingCommunityHandshakeOrFailByOneTimeCode(validOneTimeCode.data) const stateLogic = new CommunityHandshakeStateLogic(state) - if (await stateLogic.isTimeoutUpdate() || state.status !== CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK) { + if ( + await stateLogic.isTimeoutUpdate() || + state.status !== CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK + ) { throw new Error('No valid pending community handshake found') } state.status = CommunityHandshakeStateType.SUCCESS @@ -156,16 +156,19 @@ export class AuthenticationResolver { const authCom = state.federatedCommunity.community if (authCom) { methodLogger.debug('found authCom:', new CommunityLoggingView(authCom)) - methodLogger.debug('authCom.publicKey', authCom.publicKey.toString('hex')) - methodLogger.debug('args.publicKey', args.publicKey) - if (authCom.publicKey.compare(Buffer.from(args.publicKey, 'hex')) !== 0) { + 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: ${args.publicKey}` + `corrupt authentication call detected, oneTimeCode: ${authArgs.oneTimeCode} doesn't belong to caller: ${argsPublicKey.asHex()}` ) } const communityUuid = uuidv4Schema.safeParse(authArgs.uuid) if (!communityUuid.success) { - throw new Error(`invalid uuid: ${authArgs.uuid} for community with publicKey ${authCom.publicKey}`) + throw new Error( + `invalid uuid: ${authArgs.uuid} for community with publicKey ${authComPublicKey.asHex()}` + ) } authCom.communityUuid = communityUuid.data authCom.authenticatedAt = new Date() diff --git a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts index d901a84c3..0c52dfb63 100644 --- a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts +++ b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts @@ -11,7 +11,6 @@ import { getHomeCommunityWithFederatedCommunityOrFail, } from 'database' import { getLogger, Logger } from 'log4js' -import { validate as validateUUID, version as versionUUID } from 'uuid' import { AuthenticationClientFactory } from '@/client/AuthenticationClientFactory' import { randombytes_random } from 'sodium-native' @@ -21,8 +20,10 @@ import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { AuthenticationJwtPayloadType, AuthenticationResponseJwtPayloadType, + Ed25519PublicKey, encryptAndSign, OpenConnectionCallbackJwtPayloadType, + uuidv4Schema, verifyAndDecrypt } from 'shared' import { CommunityHandshakeState as DbCommunityHandshakeState, CommunityHandshakeStateType } from 'database' @@ -42,7 +43,7 @@ async function errorState( export async function startOpenConnectionCallback( handshakeID: string, - publicKey: string, + publicKey: Ed25519PublicKey, api: string, ): Promise { const methodLogger = createLogger('startOpenConnectionCallback') @@ -50,8 +51,7 @@ export async function startOpenConnectionCallback( methodLogger.debug(`Authentication: startOpenConnectionCallback() with:`, { publicKey, }) - const publicKeyBuffer = Buffer.from(publicKey, 'hex') - const pendingState = await findPendingCommunityHandshake(publicKeyBuffer, api, false) + const pendingState = await findPendingCommunityHandshake(publicKey, api, false) if (pendingState) { const stateLogic = new CommunityHandshakeStateLogic(pendingState) // retry on timeout or failure @@ -66,7 +66,7 @@ export async function startOpenConnectionCallback( try { const [homeComB, comA] = await Promise.all([ getHomeCommunityWithFederatedCommunityOrFail(api), - getCommunityWithFederatedCommunityWithApiOrFail(publicKeyBuffer, api), + getCommunityWithFederatedCommunityWithApiOrFail(publicKey, api), ]) // load helpers const homeComBLogic = new CommunityLogic(homeComB) @@ -79,7 +79,7 @@ export async function startOpenConnectionCallback( const oneTimeCode = randombytes_random() const oneTimeCodeString = oneTimeCode.toString() - state.publicKey = publicKeyBuffer + state.publicKey = publicKey.asBuffer() state.apiVersion = api state.status = CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK state.handshakeId = parseInt(handshakeID) @@ -100,7 +100,7 @@ export async function startOpenConnectionCallback( // encrypt callbackArgs with requestedCom.publicJwtKey and sign it with homeCom.privateJwtKey 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 await stateSaveResolver @@ -140,21 +140,25 @@ export async function startAuthentication( }) let state: DbCommunityHandshakeState | null = null let stateSaveResolver: Promise | undefined = undefined + const fedComBPublicKey = new Ed25519PublicKey(fedComB.publicKey) try { 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(fedComB.publicKey, fedComB.apiVersion, false) + state = await findPendingCommunityHandshake(fedComBPublicKey, fedComB.apiVersion, false) if (!state) { throw new Error('No pending community handshake found') } const stateLogic = new CommunityHandshakeStateLogic(state) - if (await stateLogic.isTimeoutUpdate() || state.status !== CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION) { + if ( + await stateLogic.isTimeoutUpdate() || + state.status !== CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION + ) { methodLogger.debug('invalid state', new CommunityHandshakeStateLoggingView(state)) throw new Error('No valid pending community handshake found') } @@ -168,7 +172,7 @@ 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) @@ -183,10 +187,10 @@ export async function startAuthentication( new FederatedCommunityLoggingView(fedComB), ) if (payload.tokentype !== AuthenticationResponseJwtPayloadType.AUTHENTICATION_RESPONSE_TYPE) { - throw new Error(`Invalid tokentype in authenticate-response of community with publicKey ${comB.publicKey}`) + throw new Error(`Invalid tokentype in authenticate-response of community with publicKey ${fedComBPublicKey.asHex()}`) } - if (!payload.uuid || !validateUUID(payload.uuid) || versionUUID(payload.uuid) !== 4) { - throw new Error(`Invalid uuid in authenticate-response of community with publicKey ${comB.publicKey}`) + if (!uuidv4Schema.safeParse(payload.uuid).success) { + throw new Error(`Invalid uuid in authenticate-response of community with publicKey ${fedComBPublicKey.asHex()}`) } comB.communityUuid = payload.uuid comB.authenticatedAt = new Date() diff --git a/shared/src/helper/BinaryData.ts b/shared/src/helper/BinaryData.ts new file mode 100644 index 000000000..f570ac381 --- /dev/null +++ b/shared/src/helper/BinaryData.ts @@ -0,0 +1,41 @@ +/** + * 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 { + throw new Error('Either valid hex string or Buffer expected') + } + } + + asBuffer(): Buffer { + return this.buf + } + + asHex(): string { + return this.hex + } + + isSame(other: BinaryData): boolean { + return this.buf.compare(other.buf) === 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') + } + } +} \ No newline at end of file diff --git a/shared/src/helper/index.ts b/shared/src/helper/index.ts index abfe2c8dc..39f988ba4 100644 --- a/shared/src/helper/index.ts +++ b/shared/src/helper/index.ts @@ -1 +1,2 @@ -export * from './updateField' \ No newline at end of file +export * from './updateField' +export * from './BinaryData' \ No newline at end of file diff --git a/shared/src/schema/base.schema.test.ts b/shared/src/schema/base.schema.test.ts index d12f2a3b4..db0a07d52 100644 --- a/shared/src/schema/base.schema.test.ts +++ b/shared/src/schema/base.schema.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'bun:test' -import { uuidv4Schema, uint32Schema } from './base.schema' +import { generateKeyPairSync } from 'node:crypto' +import { uuidv4Schema, uint32Schema, buffer32Schema } from './base.schema' import { v4 as uuidv4 } from 'uuid' describe('uuidv4 schema', () => { @@ -22,3 +23,17 @@ describe('uint32 schema', () => { expect(uint32Schema.safeParse(2092352810).success).toBeTruthy() }) }) + +describe('buffer32 schema', () => { + it('should validate buffer', () => { + const { publicKey } = generateKeyPairSync('ed25519') + const buffer = publicKey.export({ type: 'spki', format: 'der' }).slice(-32) + expect(Buffer.isBuffer(buffer)).toBeTruthy() + expect(buffer.length).toBe(32) + expect(buffer32Schema.safeParse(buffer).success).toBeTruthy() + }) + + it("shouldn't validate string", () => { + expect(buffer32Schema.safeParse('3e1a2eecc95c48fedf47a522a8c77b91').success).toBeFalsy() + }) +}) diff --git a/shared/src/schema/base.schema.ts b/shared/src/schema/base.schema.ts index ee9383dd2..0dcdf09a9 100644 --- a/shared/src/schema/base.schema.ts +++ b/shared/src/schema/base.schema.ts @@ -1,7 +1,12 @@ -import { string, number } from 'zod' +import { string, number, custom } from 'zod' 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) \ No newline at end of file +export const uint32Schema = number().positive().lte(4294967295) +export const buffer32Schema = custom( + (val: Buffer) => Buffer.isBuffer(val) && val.length === 32, + 'Invalid buffer' +) +export const hex64Schema = string().length(64).regex(/^[0-9A-Fa-f]$/) \ No newline at end of file From 2491cf2fd61ac9d4b6fcf0963f6e8b4fb735dbf1 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 13 Oct 2025 17:28:57 +0200 Subject: [PATCH 17/48] fix test --- backend/src/federation/validateCommunities.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/federation/validateCommunities.test.ts b/backend/src/federation/validateCommunities.test.ts index 255f8f180..949195652 100644 --- a/backend/src/federation/validateCommunities.test.ts +++ b/backend/src/federation/validateCommunities.test.ts @@ -103,7 +103,7 @@ describe('validate Communities', () => { return { data: { getPublicKey: { - publicKey: 'somePubKey', + publicKey: '2222222222222222222222222222222222222222222222222222222222222222', }, }, } as Response @@ -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'), ) }) }) From 29363a0f093ed5dccf4354b781a3dec380afd4f9 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 13 Oct 2025 17:42:14 +0200 Subject: [PATCH 18/48] fix logic --- core/src/logic/CommunityHandshakeState.logic.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/logic/CommunityHandshakeState.logic.ts b/core/src/logic/CommunityHandshakeState.logic.ts index e9cb23d5c..52422be15 100644 --- a/core/src/logic/CommunityHandshakeState.logic.ts +++ b/core/src/logic/CommunityHandshakeState.logic.ts @@ -21,7 +21,7 @@ export class CommunityHandshakeStateLogic { if (this.self.status === CommunityHandshakeStateType.EXPIRED) { return true } - if (Date.now() - this.self.updatedAt.getTime() > FEDERATION_AUTHENTICATION_TIMEOUT_MS) { + if ((Date.now() - this.self.updatedAt.getTime()) > FEDERATION_AUTHENTICATION_TIMEOUT_MS) { return true } return false From 40c0c2f143c6848a409d00ae4c59616ee42cef93 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 13 Oct 2025 17:45:07 +0200 Subject: [PATCH 19/48] update log --- backend/src/federation/authenticateCommunities.ts | 2 +- federation/src/graphql/api/1_0/util/authenticateCommunity.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/federation/authenticateCommunities.ts b/backend/src/federation/authenticateCommunities.ts index 3f4b769e8..340705437 100644 --- a/backend/src/federation/authenticateCommunities.ts +++ b/backend/src/federation/authenticateCommunities.ts @@ -61,7 +61,7 @@ export async function startCommunityAuthentication( // 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', new CommunityHandshakeStateLoggingView(existingState)) + methodLogger.debug('existingState, so we exit here', new CommunityHandshakeStateLoggingView(existingState)) return } } diff --git a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts index 0c52dfb63..7abda623c 100644 --- a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts +++ b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts @@ -57,7 +57,7 @@ export async function startOpenConnectionCallback( // 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', new CommunityHandshakeStateLoggingView(pendingState)) + methodLogger.debug('existingState, so we exit here', new CommunityHandshakeStateLoggingView(pendingState)) return } } From 5dd337e96c73430296db2bda44b682d28df48c9e Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 13 Oct 2025 18:03:37 +0200 Subject: [PATCH 20/48] fix const 10 minutes not 10 hours (._.);, update logging --- .../src/federation/authenticateCommunities.ts | 2 +- .../CommunityHandshakeStateLogic.test.ts | 22 +++++++++++++++++++ shared/src/const/index.ts | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 core/src/logic/CommunityHandshakeStateLogic.test.ts diff --git a/backend/src/federation/authenticateCommunities.ts b/backend/src/federation/authenticateCommunities.ts index 340705437..c548be8e7 100644 --- a/backend/src/federation/authenticateCommunities.ts +++ b/backend/src/federation/authenticateCommunities.ts @@ -59,7 +59,7 @@ export async function startCommunityAuthentication( if (existingState) { const stateLogic = new CommunityHandshakeStateLogic(existingState) // retry on timeout or failure - if (!await stateLogic.isTimeoutUpdate()) { + 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 diff --git a/core/src/logic/CommunityHandshakeStateLogic.test.ts b/core/src/logic/CommunityHandshakeStateLogic.test.ts new file mode 100644 index 000000000..52d11d8fb --- /dev/null +++ b/core/src/logic/CommunityHandshakeStateLogic.test.ts @@ -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) + }) +}) \ No newline at end of file diff --git a/shared/src/const/index.ts b/shared/src/const/index.ts index 549991c61..97fe8f306 100644 --- a/shared/src/const/index.ts +++ b/shared/src/const/index.ts @@ -3,4 +3,4 @@ export const SECONDS_PER_YEAR_GREGORIAN_CALENDER = 31556952.0 export const LOG4JS_BASE_CATEGORY_NAME = 'shared' export const REDEEM_JWT_TOKEN_EXPIRATION = '10m' // 10 minutes -export const FEDERATION_AUTHENTICATION_TIMEOUT_MS = 60 * 60 * 1000 * 10 \ No newline at end of file +export const FEDERATION_AUTHENTICATION_TIMEOUT_MS = 60 * 1000 * 10 \ No newline at end of file From b995c329ed53f69296f0ae0a95dbeeb1e4076a1e Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Mon, 13 Oct 2025 18:34:24 +0200 Subject: [PATCH 21/48] add brackets --- .../src/graphql/api/1_0/resolver/AuthenticationResolver.ts | 2 +- federation/src/graphql/api/1_0/util/authenticateCommunity.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index 50bedf34c..fba153275 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -144,7 +144,7 @@ export class AuthenticationResolver { state = await findPendingCommunityHandshakeOrFailByOneTimeCode(validOneTimeCode.data) const stateLogic = new CommunityHandshakeStateLogic(state) if ( - await stateLogic.isTimeoutUpdate() || + (await stateLogic.isTimeoutUpdate()) || state.status !== CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK ) { throw new Error('No valid pending community handshake found') diff --git a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts index 7abda623c..d69bfc4c5 100644 --- a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts +++ b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts @@ -55,7 +55,7 @@ export async function startOpenConnectionCallback( if (pendingState) { const stateLogic = new CommunityHandshakeStateLogic(pendingState) // retry on timeout or failure - if (!await stateLogic.isTimeoutUpdate()) { + 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 @@ -156,7 +156,7 @@ export async function startAuthentication( } const stateLogic = new CommunityHandshakeStateLogic(state) if ( - await stateLogic.isTimeoutUpdate() || + (await stateLogic.isTimeoutUpdate()) || state.status !== CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION ) { methodLogger.debug('invalid state', new CommunityHandshakeStateLoggingView(state)) From 76cf268ef4f41913f61323b56c012f890f429f82 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 07:55:30 +0200 Subject: [PATCH 22/48] add detailed debug log for BinaryData --- .../src/graphql/api/1_0/util/authenticateCommunity.ts | 2 +- shared/src/helper/BinaryData.ts | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts index d69bfc4c5..00c140d9c 100644 --- a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts +++ b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts @@ -49,7 +49,7 @@ export async function startOpenConnectionCallback( const methodLogger = createLogger('startOpenConnectionCallback') methodLogger.addContext('handshakeID', handshakeID) methodLogger.debug(`Authentication: startOpenConnectionCallback() with:`, { - publicKey, + publicKey: publicKey.asHex(), }) const pendingState = await findPendingCommunityHandshake(publicKey, api, false) if (pendingState) { diff --git a/shared/src/helper/BinaryData.ts b/shared/src/helper/BinaryData.ts index f570ac381..c21fad9dd 100644 --- a/shared/src/helper/BinaryData.ts +++ b/shared/src/helper/BinaryData.ts @@ -1,3 +1,8 @@ +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) @@ -7,6 +12,12 @@ export class BinaryData { private hex: string constructor(input: Buffer | string | undefined) { + if (!input) { + logging.debug('constructor() with undefined input') + } + logging.debug(`constructor() input type: ${typeof input}`) + logging.debug(`constructor() input isBuffer: ${Buffer.isBuffer(input)}`) + logging.debug(`constructor() input: ${input}`) if (typeof input === 'string') { this.buf = Buffer.from(input, 'hex') this.hex = input From 6e1d12d3b826d6947f0bf36b89fbadaa6081fdec Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 08:28:45 +0200 Subject: [PATCH 23/48] log shutdown reasons --- .../1_0/resolver/AuthenticationResolver.ts | 5 +-- federation/src/index.ts | 8 ++++ shared/src/helper/BinaryData.ts | 10 ++++- shared/src/helper/index.ts | 3 +- shared/src/helper/onShutdown.ts | 43 +++++++++++++++++++ 5 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 shared/src/helper/onShutdown.ts diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index fba153275..6712c47d5 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -51,10 +51,9 @@ export class AuthenticationResolver { if (!openConnectionJwtPayload.url) { throw new Error(`invalid url of community with publicKey ${argsPublicKey.asHex()}`) } - methodLogger.debug(`vor DbFedCommunity.findOneByOrFail()...`, { publicKey: argsPublicKey.asHex() }) + methodLogger.debug(`before DbFedCommunity.findOneByOrFail()...`, { publicKey: argsPublicKey.asHex() }) const fedComA = await DbFedCommunity.findOneByOrFail({ publicKey: argsPublicKey.asBuffer() }) - methodLogger.debug(`nach DbFedCommunity.findOneByOrFail()...`, fedComA) - methodLogger.debug('fedComA', new FederatedCommunityLoggingView(fedComA)) + methodLogger.debug(`after DbFedCommunity.findOneByOrFail()...`, new FederatedCommunityLoggingView(fedComA)) if (!openConnectionJwtPayload.url.startsWith(fedComA.endPoint)) { throw new Error(`invalid url of community with publicKey ${argsPublicKey.asHex()}`) } diff --git a/federation/src/index.ts b/federation/src/index.ts index 4492f24fb..ad0df6b0f 100644 --- a/federation/src/index.ts +++ b/federation/src/index.ts @@ -6,6 +6,7 @@ import { getLogger } from 'log4js' // config import { CONFIG } from './config' import { LOG4JS_BASE_CATEGORY_NAME } from './config/const' +import { onShutdown, ShutdownReason } from 'shared' async function main() { // init logger @@ -27,6 +28,13 @@ async function main() { `GraphIQL available at ${CONFIG.FEDERATION_COMMUNITY_URL}/api/${CONFIG.FEDERATION_API}`, ) } + onShutdown(async (reason, details) => { + if (ShutdownReason.SIGINT === reason || ShutdownReason.SIGTERM === reason) { + logger.info(`graceful shutdown: ${reason}`) + } else { + logger.error(`crash: ${reason} ${details}`) + } + }) }) } diff --git a/shared/src/helper/BinaryData.ts b/shared/src/helper/BinaryData.ts index c21fad9dd..afffd5bfb 100644 --- a/shared/src/helper/BinaryData.ts +++ b/shared/src/helper/BinaryData.ts @@ -17,7 +17,13 @@ export class BinaryData { } logging.debug(`constructor() input type: ${typeof input}`) logging.debug(`constructor() input isBuffer: ${Buffer.isBuffer(input)}`) - logging.debug(`constructor() input: ${input}`) + if (typeof input === 'string') { + logging.debug(`constructor() input: ${input}`) + } else if (Buffer.isBuffer(input)) { + logging.debug(`constructor() input: ${input.toString('hex')}`) + } else { + logging.debug(`constructor() unexpected input type: ${typeof input}`) + } if (typeof input === 'string') { this.buf = Buffer.from(input, 'hex') this.hex = input @@ -38,6 +44,8 @@ export class BinaryData { } isSame(other: BinaryData): boolean { + logging.debug(`isSame() this: ${this.buf}`) + logging.debug(`isSame() other: ${other.buf}`) return this.buf.compare(other.buf) === 0 } } diff --git a/shared/src/helper/index.ts b/shared/src/helper/index.ts index 39f988ba4..9170aad49 100644 --- a/shared/src/helper/index.ts +++ b/shared/src/helper/index.ts @@ -1,2 +1,3 @@ export * from './updateField' -export * from './BinaryData' \ No newline at end of file +export * from './BinaryData' +export * from './onShutdown' \ No newline at end of file diff --git a/shared/src/helper/onShutdown.ts b/shared/src/helper/onShutdown.ts new file mode 100644 index 000000000..ff74699d8 --- /dev/null +++ b/shared/src/helper/onShutdown.ts @@ -0,0 +1,43 @@ +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, details?: string) => Promise) { + 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, origin) => { + await shutdownHandler(ShutdownReason.UNCAUGHT_EXCEPTION, `${origin}: ${err}`) + process.exit(1) + }) + + process.on('unhandledRejection', async (reason, promise) => { + await shutdownHandler(ShutdownReason.UNCAUGHT_REJECTION, `${promise}: ${reason}`) + 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) + }) + } + + +} \ No newline at end of file From f54b4e67d125385dd67a1dddd98ba09418307c3d Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 08:43:39 +0200 Subject: [PATCH 24/48] handle undefined case --- shared/src/helper/BinaryData.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/shared/src/helper/BinaryData.ts b/shared/src/helper/BinaryData.ts index afffd5bfb..d4af1142b 100644 --- a/shared/src/helper/BinaryData.ts +++ b/shared/src/helper/BinaryData.ts @@ -30,8 +30,11 @@ export class BinaryData { } else if (Buffer.isBuffer(input)) { this.buf = input this.hex = input.toString('hex') + } else if (input === undefined) { + this.buf = Buffer.from('') + this.hex = '' } else { - throw new Error('Either valid hex string or Buffer expected') + throw new Error(`Either valid hex string or Buffer expected: ${input}`) } } From 75ed95a54879b943e2bfc473ef8171b3b07f547b Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 08:59:04 +0200 Subject: [PATCH 25/48] more precise error logging --- federation/src/index.ts | 4 ++-- shared/src/helper/BinaryData.ts | 18 ++++-------------- shared/src/helper/onShutdown.ts | 10 +++++----- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/federation/src/index.ts b/federation/src/index.ts index ad0df6b0f..d2249badb 100644 --- a/federation/src/index.ts +++ b/federation/src/index.ts @@ -28,11 +28,11 @@ async function main() { `GraphIQL available at ${CONFIG.FEDERATION_COMMUNITY_URL}/api/${CONFIG.FEDERATION_API}`, ) } - onShutdown(async (reason, details) => { + onShutdown(async (reason, error) => { if (ShutdownReason.SIGINT === reason || ShutdownReason.SIGTERM === reason) { logger.info(`graceful shutdown: ${reason}`) } else { - logger.error(`crash: ${reason} ${details}`) + logger.error(`crash: ${reason}`, error) } }) }) diff --git a/shared/src/helper/BinaryData.ts b/shared/src/helper/BinaryData.ts index d4af1142b..f1d16c4a0 100644 --- a/shared/src/helper/BinaryData.ts +++ b/shared/src/helper/BinaryData.ts @@ -12,18 +12,6 @@ export class BinaryData { private hex: string constructor(input: Buffer | string | undefined) { - if (!input) { - logging.debug('constructor() with undefined input') - } - logging.debug(`constructor() input type: ${typeof input}`) - logging.debug(`constructor() input isBuffer: ${Buffer.isBuffer(input)}`) - if (typeof input === 'string') { - logging.debug(`constructor() input: ${input}`) - } else if (Buffer.isBuffer(input)) { - logging.debug(`constructor() input: ${input.toString('hex')}`) - } else { - logging.debug(`constructor() unexpected input type: ${typeof input}`) - } if (typeof input === 'string') { this.buf = Buffer.from(input, 'hex') this.hex = input @@ -47,8 +35,10 @@ export class BinaryData { } isSame(other: BinaryData): boolean { - logging.debug(`isSame() this: ${this.buf}`) - logging.debug(`isSame() other: ${other.buf}`) + if (other === undefined || !(other instanceof BinaryData) || other.buf === undefined || !Buffer.isBuffer(other.buf)) { + logging.error('other is invalid', other) + return false + } return this.buf.compare(other.buf) === 0 } } diff --git a/shared/src/helper/onShutdown.ts b/shared/src/helper/onShutdown.ts index ff74699d8..c4d046071 100644 --- a/shared/src/helper/onShutdown.ts +++ b/shared/src/helper/onShutdown.ts @@ -10,7 +10,7 @@ export enum ShutdownReason { * Setup graceful shutdown for the process * @param gracefulShutdown will be called if process is terminated */ -export function onShutdown(shutdownHandler: (reason: ShutdownReason, details?: string) => Promise) { +export function onShutdown(shutdownHandler: (reason: ShutdownReason, error?: Error | any) => Promise) { const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM'] signals.forEach(sig => { process.on(sig, async () => { @@ -19,13 +19,13 @@ export function onShutdown(shutdownHandler: (reason: ShutdownReason, details?: s }) }) - process.on('uncaughtException', async (err, origin) => { - await shutdownHandler(ShutdownReason.UNCAUGHT_EXCEPTION, `${origin}: ${err}`) + process.on('uncaughtException', async (err) => { + await shutdownHandler(ShutdownReason.UNCAUGHT_EXCEPTION, err) process.exit(1) }) - process.on('unhandledRejection', async (reason, promise) => { - await shutdownHandler(ShutdownReason.UNCAUGHT_REJECTION, `${promise}: ${reason}`) + process.on('unhandledRejection', async (err) => { + await shutdownHandler(ShutdownReason.UNCAUGHT_REJECTION, err) process.exit(1) }) From e1dd78716699cc5e1734090e75d2a70f021ff4fd Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 09:16:17 +0200 Subject: [PATCH 26/48] try to make even more robust --- shared/src/helper/BinaryData.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/shared/src/helper/BinaryData.ts b/shared/src/helper/BinaryData.ts index f1d16c4a0..70e4954fd 100644 --- a/shared/src/helper/BinaryData.ts +++ b/shared/src/helper/BinaryData.ts @@ -27,6 +27,12 @@ export class BinaryData { } asBuffer(): Buffer { + if (this.buf === undefined || !Buffer.isBuffer(this.buf)) { + if (this.buf) { + logging.error('invalid buf: ', this.buf) + } + throw new Error('buf is invalid') + } return this.buf } @@ -35,11 +41,14 @@ export class BinaryData { } isSame(other: BinaryData): boolean { - if (other === undefined || !(other instanceof BinaryData) || other.buf === undefined || !Buffer.isBuffer(other.buf)) { + if (other === undefined || !(other instanceof BinaryData)) { logging.error('other is invalid', other) return false } - return this.buf.compare(other.buf) === 0 + if (logging.isDebugEnabled()) { + logging.debug('compare hex: ', this.hex, other.asHex(), this.hex === other.asHex()) + } + return this.buf.compare(other.asBuffer()) === 0 } } From 97e503b954ee372a99c4f789562b91495ce49375 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 09:28:37 +0200 Subject: [PATCH 27/48] compare via hex --- shared/src/helper/BinaryData.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/shared/src/helper/BinaryData.ts b/shared/src/helper/BinaryData.ts index 70e4954fd..aeff97029 100644 --- a/shared/src/helper/BinaryData.ts +++ b/shared/src/helper/BinaryData.ts @@ -27,12 +27,6 @@ export class BinaryData { } asBuffer(): Buffer { - if (this.buf === undefined || !Buffer.isBuffer(this.buf)) { - if (this.buf) { - logging.error('invalid buf: ', this.buf) - } - throw new Error('buf is invalid') - } return this.buf } @@ -45,10 +39,10 @@ export class BinaryData { logging.error('other is invalid', other) return false } - if (logging.isDebugEnabled()) { - logging.debug('compare hex: ', this.hex, other.asHex(), this.hex === other.asHex()) - } - return this.buf.compare(other.asBuffer()) === 0 + return this.asHex() === other.asHex() + // don't work reliable, in specific cases fail with: + // The "otherBuffer" argument must be an instance of Buffer or Uint8Array. Received an instance of Object + // return this.buf.compare(other.asBuffer()) === 0 } } From 7f74c03fc5dbb1b85617f7fa5b36d07f206984bd Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 09:38:34 +0200 Subject: [PATCH 28/48] log --- .../src/graphql/api/1_0/resolver/AuthenticationResolver.ts | 1 + shared/src/helper/BinaryData.ts | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index 6712c47d5..2c5853d17 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -171,6 +171,7 @@ export class AuthenticationResolver { } authCom.communityUuid = communityUuid.data authCom.authenticatedAt = new Date() + methodLogger.debug('try to save: ', authCom) await authCom.save() methodLogger.debug('store authCom.uuid successfully:', new CommunityLoggingView(authCom)) const homeComB = await getHomeCommunity() diff --git a/shared/src/helper/BinaryData.ts b/shared/src/helper/BinaryData.ts index aeff97029..756d3ed3c 100644 --- a/shared/src/helper/BinaryData.ts +++ b/shared/src/helper/BinaryData.ts @@ -39,10 +39,7 @@ export class BinaryData { logging.error('other is invalid', other) return false } - return this.asHex() === other.asHex() - // don't work reliable, in specific cases fail with: - // The "otherBuffer" argument must be an instance of Buffer or Uint8Array. Received an instance of Object - // return this.buf.compare(other.asBuffer()) === 0 + return this.buf.compare(other.asBuffer()) === 0 } } From 5876e7db13984ec12140975ed122ad292f914c90 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 09:55:19 +0200 Subject: [PATCH 29/48] try hack --- .../src/graphql/api/1_0/resolver/AuthenticationResolver.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index 2c5853d17..ae916b251 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -169,6 +169,7 @@ export class AuthenticationResolver { `invalid uuid: ${authArgs.uuid} for community with publicKey ${authComPublicKey.asHex()}` ) } + authCom.publicKey = authComPublicKey.asBuffer() authCom.communityUuid = communityUuid.data authCom.authenticatedAt = new Date() methodLogger.debug('try to save: ', authCom) From 51b28edc98ec56a5022818b8841f313f951c9bd0 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 10:01:12 +0200 Subject: [PATCH 30/48] load community entity fresh --- .../api/1_0/resolver/AuthenticationResolver.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index ae916b251..c29ea7391 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -10,6 +10,7 @@ import { FederatedCommunityLoggingView, getHomeCommunity, findPendingCommunityHandshakeOrFailByOneTimeCode, + getCommunityByPublicKeyOrFail, } from 'database' import { getLogger } from 'log4js' import { @@ -169,11 +170,13 @@ export class AuthenticationResolver { `invalid uuid: ${authArgs.uuid} for community with publicKey ${authComPublicKey.asHex()}` ) } - authCom.publicKey = authComPublicKey.asBuffer() - authCom.communityUuid = communityUuid.data - authCom.authenticatedAt = new Date() + const authComFresh = await getCommunityByPublicKeyOrFail(authComPublicKey) + authComFresh.communityUuid = communityUuid.data + authComFresh.authenticatedAt = new Date() methodLogger.debug('try to save: ', authCom) - await authCom.save() + await authComFresh.save().catch((err) => { + methodLogger.fatal('failed to save authCom:', err) + }) methodLogger.debug('store authCom.uuid successfully:', new CommunityLoggingView(authCom)) const homeComB = await getHomeCommunity() if (homeComB?.communityUuid) { From 83f29652997484deb1e601732a5b71e6ff580938 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 10:06:09 +0200 Subject: [PATCH 31/48] update log --- .../src/graphql/api/1_0/resolver/AuthenticationResolver.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index c29ea7391..948a8dc28 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -170,10 +170,12 @@ export class AuthenticationResolver { `invalid uuid: ${authArgs.uuid} for community with publicKey ${authComPublicKey.asHex()}` ) } + methodLogger.debug('before loading auth community again from db') const authComFresh = await getCommunityByPublicKeyOrFail(authComPublicKey) authComFresh.communityUuid = communityUuid.data authComFresh.authenticatedAt = new Date() - methodLogger.debug('try to save: ', authCom) + methodLogger.debug('after loading auth community again from db') + methodLogger.debug('try to save: ', authComFresh) await authComFresh.save().catch((err) => { methodLogger.fatal('failed to save authCom:', err) }) From 412557b76d4fead30517778f83d3c3ea31382592 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 10:11:22 +0200 Subject: [PATCH 32/48] try workaround --- .../src/graphql/api/1_0/resolver/AuthenticationResolver.ts | 2 +- shared/src/helper/BinaryData.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index 948a8dc28..930563e87 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -171,7 +171,7 @@ export class AuthenticationResolver { ) } methodLogger.debug('before loading auth community again from db') - const authComFresh = await getCommunityByPublicKeyOrFail(authComPublicKey) + const authComFresh = await getCommunityByPublicKeyOrFail(argsPublicKey) authComFresh.communityUuid = communityUuid.data authComFresh.authenticatedAt = new Date() methodLogger.debug('after loading auth community again from db') diff --git a/shared/src/helper/BinaryData.ts b/shared/src/helper/BinaryData.ts index 756d3ed3c..583dccd4e 100644 --- a/shared/src/helper/BinaryData.ts +++ b/shared/src/helper/BinaryData.ts @@ -27,6 +27,10 @@ export class BinaryData { } asBuffer(): Buffer { + if (!this.buf || !Buffer.isBuffer(this.buf)) { + logging.warn('BinaryData.buf is invalid, try to create fresh buffer from hex') + this.buf = Buffer.from(this.hex, 'hex') + } return this.buf } From 0ce9eaab3db4516cc7631a2a71f224cbc5b71ab0 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 10:53:07 +0200 Subject: [PATCH 33/48] make server crash more visible in logs, try fixing bug with query builder --- database/src/queries/communities.test.ts | 15 +++- .../src/queries/communityHandshakes.test.ts | 79 +++++++++++++++++++ .../1_0/resolver/AuthenticationResolver.ts | 26 +++--- federation/src/index.ts | 8 +- shared/src/helper/onShutdown.ts | 9 ++- 5 files changed, 122 insertions(+), 15 deletions(-) create mode 100644 database/src/queries/communityHandshakes.test.ts diff --git a/database/src/queries/communities.test.ts b/database/src/queries/communities.test.ts index 2d208a224..b435c3649 100644 --- a/database/src/queries/communities.test.ts +++ b/database/src/queries/communities.test.ts @@ -1,8 +1,9 @@ import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity } from '..' import { AppDatabase } from '../AppDatabase' -import { getHomeCommunity, getHomeCommunityWithFederatedCommunityOrFail, 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() @@ -56,6 +57,18 @@ describe('community.queries', () => { 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 () => { diff --git a/database/src/queries/communityHandshakes.test.ts b/database/src/queries/communityHandshakes.test.ts new file mode 100644 index 000000000..badf5ad96 --- /dev/null +++ b/database/src/queries/communityHandshakes.test.ts @@ -0,0 +1,79 @@ +import { AppDatabase } from '../AppDatabase' +import { + CommunityHandshakeState as DbCommunityHandshakeState, + Community as DbCommunity, + FederatedCommunity as DbFederatedCommunity, + getHomeCommunityWithFederatedCommunityOrFail, + getCommunityByPublicKeyOrFail, + findPendingCommunityHandshake, + CommunityHandshakeStateType +} from '..' +import { describe, expect, it, beforeEach, beforeAll, afterAll } from 'vitest' +import { createCommunity, createVerifiedFederatedCommunity } from '../seeds/community' +import { Ed25519PublicKey } from 'shared' + +const db = AppDatabase.getInstance() + +beforeAll(async () => { + await db.init() +}) +afterAll(async () => { + await db.destroy() +}) + +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) + const state = new DbCommunityHandshakeState() + state.publicKey = com1.publicKey + state.apiVersion = '1_0' + state.status = CommunityHandshakeStateType.OPEN_CONNECTION_CALLBACK + state.handshakeId = 1 + await state.save() + const communityHandshakeState = await findPendingCommunityHandshake(new Ed25519PublicKey(com1.publicKey), '1_0') + expect(communityHandshakeState).toBeDefined() + expect(communityHandshakeState).toMatchObject({ + publicKey: com1.publicKey, + apiVersion: '1_0', + status: CommunityHandshakeStateType.OPEN_CONNECTION_CALLBACK, + handshakeId: 1 + }) + }) + + it('try to use returned public key for loading community', async () => { + // test this explicit case, because in federation.authentication it lead to server crash + const com1 = await createCommunity(false) + await createVerifiedFederatedCommunity('1_0', 100, com1) + const state = new DbCommunityHandshakeState() + state.publicKey = com1.publicKey + state.apiVersion = '1_0' + state.status = CommunityHandshakeStateType.OPEN_CONNECTION_CALLBACK + state.handshakeId = 1 + await state.save() + const communityHandshakeState = await findPendingCommunityHandshake(new Ed25519PublicKey(com1.publicKey), '1_0') + expect(communityHandshakeState).toBeDefined() + expect(communityHandshakeState?.federatedCommunity?.community).toBeDefined() + const ed25519PublicKey = new Ed25519PublicKey(communityHandshakeState?.federatedCommunity?.community?.publicKey) + const community = await DbCommunity.findOneBy({ publicKey: ed25519PublicKey.asBuffer() }) + expect(community).toBeDefined() + expect(community).toMatchObject({ + communityUuid: com1.communityUuid, + name: com1.name, + description: com1.description, + url: com1.url, + creationDate: com1.creationDate, + authenticatedAt: com1.authenticatedAt, + foreign: com1.foreign, + publicKey: com1.publicKey, + privateKey: com1.privateKey + }) + }) +}) \ No newline at end of file diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index 930563e87..4e3c0eaeb 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -10,7 +10,7 @@ import { FederatedCommunityLoggingView, getHomeCommunity, findPendingCommunityHandshakeOrFailByOneTimeCode, - getCommunityByPublicKeyOrFail, + Community as DbCommunity, } from 'database' import { getLogger } from 'log4js' import { @@ -170,16 +170,20 @@ export class AuthenticationResolver { `invalid uuid: ${authArgs.uuid} for community with publicKey ${authComPublicKey.asHex()}` ) } - methodLogger.debug('before loading auth community again from db') - const authComFresh = await getCommunityByPublicKeyOrFail(argsPublicKey) - authComFresh.communityUuid = communityUuid.data - authComFresh.authenticatedAt = new Date() - methodLogger.debug('after loading auth community again from db') - methodLogger.debug('try to save: ', authComFresh) - await authComFresh.save().catch((err) => { - methodLogger.fatal('failed to save authCom:', err) - }) - methodLogger.debug('store authCom.uuid successfully:', new CommunityLoggingView(authCom)) + methodLogger.debug('before updating auth community again from db') + // need to use query builder, loading from db, changing and save lead to server crash with this error: + // TypeError [ERR_INVALID_ARG_TYPE]: The "otherBuffer" argument must be of type Buffer or Uint8Array. Received an instance of Object + // seems to be a typeorm problem with Buffer, even if I give a freshly created Buffer for public_key + await DbCommunity.createQueryBuilder() + .update(DbCommunity) + .set({ + communityUuid: communityUuid.data, + authenticatedAt: new Date(), + }) + .where({ id: authCom.id }) + .execute() + methodLogger.debug('update authCom.uuid successfully') + const homeComB = await getHomeCommunity() if (homeComB?.communityUuid) { const responseArgs = new AuthenticationResponseJwtPayloadType(args.handshakeID,homeComB.communityUuid) diff --git a/federation/src/index.ts b/federation/src/index.ts index d2249badb..a4247a2cc 100644 --- a/federation/src/index.ts +++ b/federation/src/index.ts @@ -6,9 +6,10 @@ import { getLogger } from 'log4js' // config import { CONFIG } from './config' import { LOG4JS_BASE_CATEGORY_NAME } from './config/const' -import { onShutdown, ShutdownReason } from 'shared' +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( @@ -32,7 +33,10 @@ async function main() { if (ShutdownReason.SIGINT === reason || ShutdownReason.SIGTERM === reason) { logger.info(`graceful shutdown: ${reason}`) } else { - logger.error(`crash: ${reason}`, error) + const endTime = new Date() + const duration = endTime.getTime() - startTime.getTime() + printServerCrashAsciiArt(logger, `reason: ${reason}`, `duration: ${duration}ms`, '') + logger.error(error) } }) }) diff --git a/shared/src/helper/onShutdown.ts b/shared/src/helper/onShutdown.ts index c4d046071..c6e166d43 100644 --- a/shared/src/helper/onShutdown.ts +++ b/shared/src/helper/onShutdown.ts @@ -1,3 +1,5 @@ +import { Logger } from "log4js" + export enum ShutdownReason { SIGINT = 'SIGINT', SIGTERM = 'SIGTERM', @@ -38,6 +40,11 @@ export function onShutdown(shutdownHandler: (reason: ShutdownReason, error?: Err process.emit("SIGINT" as any) }) } +} - +export function printServerCrashAsciiArt(logger: Logger, msg1: string, msg2: string, msg3: string) { + logger.error(` /\\_/\\ ${msg1}`) + logger.error(`( x.x ) ${msg2}`) + logger.error(`> < ${msg3}`) + logger.error('') } \ No newline at end of file From c0a60f4ca6bce16988a60ecae29c1e3681c9e17b Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 11:04:12 +0200 Subject: [PATCH 34/48] skip compare altogether --- .../api/1_0/resolver/AuthenticationResolver.ts | 4 ++-- federation/src/index.ts | 2 +- shared/package.json | 3 ++- shared/src/helper/onShutdown.ts | 13 +++++++------ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index 4e3c0eaeb..b6e7ca5c2 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -159,11 +159,11 @@ export class AuthenticationResolver { const authComPublicKey = new Ed25519PublicKey(authCom.publicKey) methodLogger.debug('authCom.publicKey', authComPublicKey.asHex()) methodLogger.debug('args.publicKey', argsPublicKey.asHex()) - if (!authComPublicKey.isSame(argsPublicKey)) { + /*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) { throw new Error( diff --git a/federation/src/index.ts b/federation/src/index.ts index a4247a2cc..9e8738b80 100644 --- a/federation/src/index.ts +++ b/federation/src/index.ts @@ -35,7 +35,7 @@ async function main() { } else { const endTime = new Date() const duration = endTime.getTime() - startTime.getTime() - printServerCrashAsciiArt(logger, `reason: ${reason}`, `duration: ${duration}ms`, '') + printServerCrashAsciiArt(`reason: ${reason}`, `duration: ${duration}ms`, '') logger.error(error) } }) diff --git a/shared/package.json b/shared/package.json index 9f15c67bc..647baa1bb 100644 --- a/shared/package.json +++ b/shared/package.json @@ -34,9 +34,10 @@ }, "dependencies": { "decimal.js-light": "^2.5.1", - "esbuild": "^0.25.2", + "esbuild": "^0.25.2", "jose": "^4.14.4", "log4js": "^6.9.1", + "yoctocolors-cjs": "^2.1.2", "zod": "^3.25.61" }, "engines": { diff --git a/shared/src/helper/onShutdown.ts b/shared/src/helper/onShutdown.ts index c6e166d43..395c4274b 100644 --- a/shared/src/helper/onShutdown.ts +++ b/shared/src/helper/onShutdown.ts @@ -1,4 +1,5 @@ -import { Logger } from "log4js" +import { Logger } from 'log4js' +import colors from 'yoctocolors-cjs' export enum ShutdownReason { SIGINT = 'SIGINT', @@ -42,9 +43,9 @@ export function onShutdown(shutdownHandler: (reason: ShutdownReason, error?: Err } } -export function printServerCrashAsciiArt(logger: Logger, msg1: string, msg2: string, msg3: string) { - logger.error(` /\\_/\\ ${msg1}`) - logger.error(`( x.x ) ${msg2}`) - logger.error(`> < ${msg3}`) - logger.error('') +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('')) } \ No newline at end of file From b7f34ace84d5ad4bbc5e879adb3b8b9f7a76b709 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 11:08:04 +0200 Subject: [PATCH 35/48] fix crash log, uncomment code which may lead to crash --- .../graphql/api/1_0/resolver/AuthenticationResolver.ts | 9 +++++---- federation/src/index.ts | 2 +- shared/src/helper/onShutdown.ts | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index b6e7ca5c2..38089a9d3 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -159,11 +159,11 @@ export class AuthenticationResolver { const authComPublicKey = new Ed25519PublicKey(authCom.publicKey) methodLogger.debug('authCom.publicKey', authComPublicKey.asHex()) methodLogger.debug('args.publicKey', argsPublicKey.asHex()) - /*if (!authComPublicKey.isSame(argsPublicKey)) { + 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) { throw new Error( @@ -174,7 +174,7 @@ export class AuthenticationResolver { // need to use query builder, loading from db, changing and save lead to server crash with this error: // TypeError [ERR_INVALID_ARG_TYPE]: The "otherBuffer" argument must be of type Buffer or Uint8Array. Received an instance of Object // seems to be a typeorm problem with Buffer, even if I give a freshly created Buffer for public_key - await DbCommunity.createQueryBuilder() + /*await DbCommunity.createQueryBuilder() .update(DbCommunity) .set({ communityUuid: communityUuid.data, @@ -183,7 +183,8 @@ export class AuthenticationResolver { .where({ id: authCom.id }) .execute() methodLogger.debug('update authCom.uuid successfully') - + */ + methodLogger.debug('skipped community update') const homeComB = await getHomeCommunity() if (homeComB?.communityUuid) { const responseArgs = new AuthenticationResponseJwtPayloadType(args.handshakeID,homeComB.communityUuid) diff --git a/federation/src/index.ts b/federation/src/index.ts index 9e8738b80..a113ef5a4 100644 --- a/federation/src/index.ts +++ b/federation/src/index.ts @@ -35,7 +35,7 @@ async function main() { } else { const endTime = new Date() const duration = endTime.getTime() - startTime.getTime() - printServerCrashAsciiArt(`reason: ${reason}`, `duration: ${duration}ms`, '') + printServerCrashAsciiArt('Server Crash', `reason: ${reason}`, `duration: ${duration}ms`) logger.error(error) } }) diff --git a/shared/src/helper/onShutdown.ts b/shared/src/helper/onShutdown.ts index 395c4274b..a27c931a6 100644 --- a/shared/src/helper/onShutdown.ts +++ b/shared/src/helper/onShutdown.ts @@ -46,6 +46,6 @@ export function onShutdown(shutdownHandler: (reason: ShutdownReason, error?: Err 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(` > < ${msg3}`)) console.error(colors.redBright('')) } \ No newline at end of file From 8f40342665ba5c6b21663e5c214437666cdc4969 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 11:13:09 +0200 Subject: [PATCH 36/48] update bun lock file --- bun.lock | 17 +++++++++-------- federation/src/index.ts | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/bun.lock b/bun.lock index 5a76238fe..c7810fb83 100644 --- a/bun.lock +++ b/bun.lock @@ -449,6 +449,7 @@ "esbuild": "^0.25.2", "jose": "^4.14.4", "log4js": "^6.9.1", + "yoctocolors-cjs": "^2.1.2", "zod": "^3.25.61", }, "devDependencies": { @@ -1780,7 +1781,7 @@ "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], - "convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], "cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="], @@ -2816,7 +2817,7 @@ "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], - "ohash": ["ohash@1.1.6", "", {}, "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg=="], + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], @@ -3636,8 +3637,6 @@ "@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - "@babel/core/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], @@ -3724,20 +3723,22 @@ "@jest/source-map/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "@jest/transform/convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="], + "@jest/transform/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "@jest/transform/write-file-atomic": ["write-file-atomic@3.0.3", "", { "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", "signal-exit": "^3.0.2", "typedarray-to-buffer": "^3.1.5" } }, "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q=="], "@jest/types/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], + "@morev/utils/ohash": ["ohash@1.1.6", "", {}, "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg=="], + "@morev/utils/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], "@nuxt/kit/consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], "@nuxt/kit/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - "@nuxt/kit/ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], - "@nuxt/kit/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "@nuxt/kit/pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], @@ -3886,8 +3887,6 @@ "c12/dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], - "c12/ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], - "c12/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "c12/pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], @@ -4248,6 +4247,8 @@ "unplugin-vue-components/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "v8-to-istanbul/convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="], + "vee-validate/@vue/devtools-api": ["@vue/devtools-api@7.7.7", "", { "dependencies": { "@vue/devtools-kit": "^7.7.7" } }, "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg=="], "vee-validate/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], diff --git a/federation/src/index.ts b/federation/src/index.ts index a113ef5a4..c9f58b0e5 100644 --- a/federation/src/index.ts +++ b/federation/src/index.ts @@ -35,7 +35,7 @@ async function main() { } else { const endTime = new Date() const duration = endTime.getTime() - startTime.getTime() - printServerCrashAsciiArt('Server Crash', `reason: ${reason}`, `duration: ${duration}ms`) + printServerCrashAsciiArt('Server Crash', `reason: ${reason}`, `server was ${duration}ms online`) logger.error(error) } }) From d11a794dd5fa260f559102f675482640f9427718 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 11:16:45 +0200 Subject: [PATCH 37/48] try another culprint --- .../src/graphql/api/1_0/resolver/AuthenticationResolver.ts | 3 ++- shared/package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index 38089a9d3..0c9e5e2e5 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -150,7 +150,8 @@ export class AuthenticationResolver { throw new Error('No valid pending community handshake found') } state.status = CommunityHandshakeStateType.SUCCESS - stateSaveResolver = state.save() + // stateSaveResolver = state.save() + await state.save() methodLogger.debug(`search community per oneTimeCode:`, authArgs.oneTimeCode) const authCom = state.federatedCommunity.community diff --git a/shared/package.json b/shared/package.json index 647baa1bb..d18fa29cf 100644 --- a/shared/package.json +++ b/shared/package.json @@ -34,7 +34,7 @@ }, "dependencies": { "decimal.js-light": "^2.5.1", - "esbuild": "^0.25.2", + "esbuild": "^0.25.2", "jose": "^4.14.4", "log4js": "^6.9.1", "yoctocolors-cjs": "^2.1.2", From 1c347b7ff71def8943c1899579b0ab00942272d4 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 12:32:57 +0200 Subject: [PATCH 38/48] remove join from communityHandshakeState, await save direct, to prevent drag away errors --- .../src/federation/authenticateCommunities.ts | 5 +- .../src/entity/CommunityHandshakeState.ts | 7 +-- database/src/entity/FederatedCommunity.ts | 6 -- .../CommunityHandshakeStateLogging.view.ts | 6 +- .../src/queries/communityHandshakes.test.ts | 60 ++++++++----------- database/src/queries/communityHandshakes.ts | 8 +-- .../1_0/resolver/AuthenticationResolver.ts | 32 +++------- .../api/1_0/util/authenticateCommunity.ts | 29 +++------ 8 files changed, 49 insertions(+), 104 deletions(-) diff --git a/backend/src/federation/authenticateCommunities.ts b/backend/src/federation/authenticateCommunities.ts index c548be8e7..9226d652c 100644 --- a/backend/src/federation/authenticateCommunities.ts +++ b/backend/src/federation/authenticateCommunities.ts @@ -55,7 +55,7 @@ export async function startCommunityAuthentication( ) // check if a authentication is already in progress - const existingState = await findPendingCommunityHandshake(fedComBPublicKey, fedComB.apiVersion, false) + const existingState = await findPendingCommunityHandshake(fedComBPublicKey, fedComB.apiVersion) if (existingState) { const stateLogic = new CommunityHandshakeStateLogic(existingState) // retry on timeout or failure @@ -77,7 +77,7 @@ export async function startCommunityAuthentication( state.apiVersion = fedComB.apiVersion state.status = CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION state.handshakeId = parseInt(handshakeID) - const stateSaveResolver = state.save() + await state.save() //create JWT with url in payload encrypted by foreignCom.publicJwtKey and signed with homeCom.privateJwtKey const payload = new OpenConnectionJwtPayloadType(handshakeID, @@ -92,7 +92,6 @@ export async function startCommunityAuthentication( args.publicKey = homeComAPublicKey.asHex() args.jwt = jws args.handshakeID = handshakeID - await stateSaveResolver methodLogger.debug('before client.openConnection() args:', args) const result = await client.openConnection(args) if (result) { diff --git a/database/src/entity/CommunityHandshakeState.ts b/database/src/entity/CommunityHandshakeState.ts index f3a27b834..eca9c07e7 100644 --- a/database/src/entity/CommunityHandshakeState.ts +++ b/database/src/entity/CommunityHandshakeState.ts @@ -1,6 +1,5 @@ import { CommunityHandshakeStateType } from '../enum' -import { BaseEntity, Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm' -import { FederatedCommunity } from './FederatedCommunity' +import { BaseEntity, Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm' @Entity('community_handshake_states') export class CommunityHandshakeState extends BaseEntity { @@ -35,8 +34,4 @@ export class CommunityHandshakeState extends BaseEntity { @UpdateDateColumn({ name: 'updated_at', type: 'datetime', precision: 3 }) updatedAt: Date - - @ManyToOne(() => FederatedCommunity, (federatedCommunity) => federatedCommunity.communityHandshakeStates) - @JoinColumn({ name: 'public_key', referencedColumnName: 'publicKey' }) - federatedCommunity: FederatedCommunity } \ No newline at end of file diff --git a/database/src/entity/FederatedCommunity.ts b/database/src/entity/FederatedCommunity.ts index 8993f0663..a6eaee80f 100644 --- a/database/src/entity/FederatedCommunity.ts +++ b/database/src/entity/FederatedCommunity.ts @@ -5,12 +5,10 @@ import { Entity, JoinColumn, ManyToOne, - OneToMany, PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm' import { Community } from './Community' -import { CommunityHandshakeState } from './CommunityHandshakeState' @Entity('federated_communities') export class FederatedCommunity extends BaseEntity { @@ -62,8 +60,4 @@ export class FederatedCommunity extends BaseEntity { ) @JoinColumn({ name: 'public_key', referencedColumnName: 'publicKey' }) community?: Community - - @OneToMany(() => CommunityHandshakeState, (communityHandshakeState) => communityHandshakeState.federatedCommunity) - @JoinColumn({ name: 'public_key', referencedColumnName: 'publicKey' }) - communityHandshakeStates: CommunityHandshakeState[] } diff --git a/database/src/logging/CommunityHandshakeStateLogging.view.ts b/database/src/logging/CommunityHandshakeStateLogging.view.ts index 5345474a0..b7df2452d 100644 --- a/database/src/logging/CommunityHandshakeStateLogging.view.ts +++ b/database/src/logging/CommunityHandshakeStateLogging.view.ts @@ -1,6 +1,5 @@ import { CommunityHandshakeState } from '..' import { AbstractLoggingView } from './AbstractLogging.view' -import { FederatedCommunityLoggingView } from './FederatedCommunityLogging.view' export class CommunityHandshakeStateLoggingView extends AbstractLoggingView { public constructor(private self: CommunityHandshakeState) { @@ -16,10 +15,7 @@ export class CommunityHandshakeStateLoggingView extends AbstractLoggingView { status: this.self.status, lastError: this.self.lastError, createdAt: this.dateToString(this.self.createdAt), - updatedAt: this.dateToString(this.self.updatedAt), - federatedCommunity: this.self.federatedCommunity - ? new FederatedCommunityLoggingView(this.self.federatedCommunity) - : undefined, + updatedAt: this.dateToString(this.self.updatedAt), } } } \ No newline at end of file diff --git a/database/src/queries/communityHandshakes.test.ts b/database/src/queries/communityHandshakes.test.ts index badf5ad96..32eb31c4b 100644 --- a/database/src/queries/communityHandshakes.test.ts +++ b/database/src/queries/communityHandshakes.test.ts @@ -3,14 +3,13 @@ import { CommunityHandshakeState as DbCommunityHandshakeState, Community as DbCommunity, FederatedCommunity as DbFederatedCommunity, - getHomeCommunityWithFederatedCommunityOrFail, - getCommunityByPublicKeyOrFail, 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() @@ -21,6 +20,15 @@ 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 () => { @@ -32,48 +40,32 @@ describe('communityHandshakes', () => { it('should find pending community handshake by public key', async () => { const com1 = await createCommunity(false) await createVerifiedFederatedCommunity('1_0', 100, com1) - const state = new DbCommunityHandshakeState() - state.publicKey = com1.publicKey - state.apiVersion = '1_0' - state.status = CommunityHandshakeStateType.OPEN_CONNECTION_CALLBACK - state.handshakeId = 1 - await state.save() + 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.OPEN_CONNECTION_CALLBACK, + status: CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION, handshakeId: 1 }) }) - it('try to use returned public key for loading community', async () => { - // test this explicit case, because in federation.authentication it lead to server crash - const com1 = await createCommunity(false) - await createVerifiedFederatedCommunity('1_0', 100, com1) - const state = new DbCommunityHandshakeState() - state.publicKey = com1.publicKey - state.apiVersion = '1_0' - state.status = CommunityHandshakeStateType.OPEN_CONNECTION_CALLBACK - state.handshakeId = 1 - await state.save() - const communityHandshakeState = await findPendingCommunityHandshake(new Ed25519PublicKey(com1.publicKey), '1_0') + it('update state', async () => { + const publicKey = new Ed25519PublicKey(randomBytes(32)) + await createCommunityHandshakeState(publicKey.asBuffer()) + const communityHandshakeState = await findPendingCommunityHandshake(publicKey, '1_0') expect(communityHandshakeState).toBeDefined() - expect(communityHandshakeState?.federatedCommunity?.community).toBeDefined() - const ed25519PublicKey = new Ed25519PublicKey(communityHandshakeState?.federatedCommunity?.community?.publicKey) - const community = await DbCommunity.findOneBy({ publicKey: ed25519PublicKey.asBuffer() }) - expect(community).toBeDefined() - expect(community).toMatchObject({ - communityUuid: com1.communityUuid, - name: com1.name, - description: com1.description, - url: com1.url, - creationDate: com1.creationDate, - authenticatedAt: com1.authenticatedAt, - foreign: com1.foreign, - publicKey: com1.publicKey, - privateKey: com1.privateKey + communityHandshakeState!.status = CommunityHandshakeStateType.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.OPEN_CONNECTION_CALLBACK, + handshakeId: 1 }) }) }) \ No newline at end of file diff --git a/database/src/queries/communityHandshakes.ts b/database/src/queries/communityHandshakes.ts index 9181cfbb3..23fe4f0d1 100644 --- a/database/src/queries/communityHandshakes.ts +++ b/database/src/queries/communityHandshakes.ts @@ -5,12 +5,10 @@ import { Ed25519PublicKey } from 'shared' /** * Find a pending community handshake by public key. * @param publicKey The public key of the community. - * @param withRelations Whether to include the federated community and community in the result, default true. + * @param apiVersion The API version of the community. * @returns The CommunityHandshakeState with associated federated community and community. */ -export function findPendingCommunityHandshake( - publicKey: Ed25519PublicKey, apiVersion: string, withRelations = true -): Promise { +export function findPendingCommunityHandshake(publicKey: Ed25519PublicKey, apiVersion: string): Promise { return CommunityHandshakeState.findOne({ where: { publicKey: publicKey.asBuffer(), @@ -21,7 +19,6 @@ export function findPendingCommunityHandshake( CommunityHandshakeStateType.SUCCESS ])) }, - relations: withRelations ? { federatedCommunity: { community: true } } : undefined, }) } @@ -30,7 +27,6 @@ export function findPendingCommunityHandshakeOrFailByOneTimeCode( ): Promise { return CommunityHandshakeState.findOneOrFail({ where: { oneTimeCode }, - relations: { federatedCommunity: { community: true } }, }) } \ No newline at end of file diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index 0c9e5e2e5..0df45f794 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -11,6 +11,7 @@ import { getHomeCommunity, findPendingCommunityHandshakeOrFailByOneTimeCode, Community as DbCommunity, + getCommunityByPublicKeyOrFail, } from 'database' import { getLogger } from 'log4js' import { @@ -126,7 +127,6 @@ export class AuthenticationResolver { methodLogger.addContext('handshakeID', args.handshakeID) methodLogger.debug(`authenticate() via apiVersion=1_0 ...`, args) let state: DbCommunityHandshakeState | null = null - let stateSaveResolver: Promise | undefined = undefined const argsPublicKey = new Ed25519PublicKey(args.publicKey) try { const authArgs = await interpretEncryptedTransferArgs(args) as AuthenticationJwtPayloadType @@ -150,11 +150,10 @@ export class AuthenticationResolver { throw new Error('No valid pending community handshake found') } state.status = CommunityHandshakeStateType.SUCCESS - // stateSaveResolver = state.save() await state.save() - + methodLogger.debug(`search community per oneTimeCode:`, authArgs.oneTimeCode) - const authCom = state.federatedCommunity.community + const authCom = await getCommunityByPublicKeyOrFail(argsPublicKey) if (authCom) { methodLogger.debug('found authCom:', new CommunityLoggingView(authCom)) const authComPublicKey = new Ed25519PublicKey(authCom.publicKey) @@ -171,21 +170,11 @@ export class AuthenticationResolver { `invalid uuid: ${authArgs.uuid} for community with publicKey ${authComPublicKey.asHex()}` ) } - methodLogger.debug('before updating auth community again from db') - // need to use query builder, loading from db, changing and save lead to server crash with this error: - // TypeError [ERR_INVALID_ARG_TYPE]: The "otherBuffer" argument must be of type Buffer or Uint8Array. Received an instance of Object - // seems to be a typeorm problem with Buffer, even if I give a freshly created Buffer for public_key - /*await DbCommunity.createQueryBuilder() - .update(DbCommunity) - .set({ - communityUuid: communityUuid.data, - authenticatedAt: new Date(), - }) - .where({ id: authCom.id }) - .execute() + authCom.communityUuid = communityUuid.data + authCom.authenticatedAt = new Date() + await authCom.save() methodLogger.debug('update authCom.uuid successfully') - */ - methodLogger.debug('skipped community update') + const homeComB = await getHomeCommunity() if (homeComB?.communityUuid) { const responseArgs = new AuthenticationResponseJwtPayloadType(args.handshakeID,homeComB.communityUuid) @@ -205,16 +194,11 @@ export class AuthenticationResolver { methodLogger.info(`state: ${new CommunityHandshakeStateLoggingView(state)}`) state.status = CommunityHandshakeStateType.FAILED state.lastError = errorString - stateSaveResolver = state.save() + await state.save() } methodLogger.error(`failed: ${errorString}`) // no infos to the caller return null - } finally { - if (stateSaveResolver) { - await stateSaveResolver - } } - } } diff --git a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts index 00c140d9c..a2c9136b0 100644 --- a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts +++ b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts @@ -51,7 +51,7 @@ export async function startOpenConnectionCallback( methodLogger.debug(`Authentication: startOpenConnectionCallback() with:`, { publicKey: publicKey.asHex(), }) - const pendingState = await findPendingCommunityHandshake(publicKey, api, false) + const pendingState = await findPendingCommunityHandshake(publicKey, api) if (pendingState) { const stateLogic = new CommunityHandshakeStateLogic(pendingState) // retry on timeout or failure @@ -61,7 +61,6 @@ export async function startOpenConnectionCallback( return } } - let stateSaveResolver: Promise | undefined = undefined const state = new DbCommunityHandshakeState() try { const [homeComB, comA] = await Promise.all([ @@ -84,7 +83,7 @@ export async function startOpenConnectionCallback( state.status = CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK state.handshakeId = parseInt(handshakeID) state.oneTimeCode = oneTimeCode - stateSaveResolver = state.save() + await state.save() methodLogger.debug( `Authentication: store oneTimeCode in CommunityHandshakeState:`, new CommunityHandshakeStateLoggingView(state), @@ -103,13 +102,12 @@ export async function startOpenConnectionCallback( args.publicKey = new Ed25519PublicKey(homeComB.publicKey).asHex() args.jwt = jwt args.handshakeID = handshakeID - await stateSaveResolver const result = await client.openConnectionCallback(args) if (result) { methodLogger.debug(`startOpenConnectionCallback() successful: ${jwt}`) } else { methodLogger.debug(`jwt: ${jwt}`) - stateSaveResolver = errorState('startOpenConnectionCallback() failed', methodLogger, state) + await errorState('startOpenConnectionCallback() failed', methodLogger, state) } } } catch (err) { @@ -119,11 +117,7 @@ export async function startOpenConnectionCallback( } else { errorString = String(err) } - stateSaveResolver = errorState(`error in startOpenConnectionCallback: ${errorString}`, methodLogger, state) - } finally { - if (stateSaveResolver) { - await stateSaveResolver - } + await errorState(`error in startOpenConnectionCallback: ${errorString}`, methodLogger, state) } } @@ -139,7 +133,6 @@ export async function startAuthentication( fedComB: new FederatedCommunityLoggingView(fedComB), }) let state: DbCommunityHandshakeState | null = null - let stateSaveResolver: Promise | undefined = undefined const fedComBPublicKey = new Ed25519PublicKey(fedComB.publicKey) try { const homeComA = await getHomeCommunity() @@ -150,7 +143,7 @@ export async function startAuthentication( if (!comB.publicJwtKey) { throw new Error('Public JWT key still not exist for foreign community') } - state = await findPendingCommunityHandshake(fedComBPublicKey, fedComB.apiVersion, false) + state = await findPendingCommunityHandshake(fedComBPublicKey, fedComB.apiVersion) if (!state) { throw new Error('No pending community handshake found') } @@ -163,7 +156,7 @@ export async function startAuthentication( throw new Error('No valid pending community handshake found') } state.status = CommunityHandshakeStateType.START_AUTHENTICATION - stateSaveResolver = state.save() + await state.save() const client = AuthenticationClientFactory.getInstance(fedComB) @@ -196,12 +189,12 @@ export async function startAuthentication( comB.authenticatedAt = new Date() await DbCommunity.save(comB) state.status = CommunityHandshakeStateType.SUCCESS - stateSaveResolver = state.save() + await state.save() methodLogger.debug('Community Authentication successful:', new CommunityLoggingView(comB)) } else { state.status = CommunityHandshakeStateType.FAILED state.lastError = 'Community Authentication failed, empty response' - stateSaveResolver = state.save() + await state.save() methodLogger.error('Community Authentication failed:', authenticationArgs) } } @@ -215,12 +208,8 @@ export async function startAuthentication( if (state) { state.status = CommunityHandshakeStateType.FAILED state.lastError = errorString - stateSaveResolver = state.save() + await state.save() } methodLogger.error('error in startAuthentication:', errorString) - } finally { - if (stateSaveResolver) { - await stateSaveResolver - } } } From 2a7e97319bf9029933d3a915ad8afcf4f5ac17a2 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 14:32:01 +0200 Subject: [PATCH 39/48] update logging, reduce logging redundant informations --- .../logic/interpretEncryptedTransferArgs.ts | 11 +- core/src/logic/Community.logic.ts | 13 -- core/src/logic/community.logic.ts | 12 ++ core/src/logic/index.ts | 4 +- .../1_0/resolver/AuthenticationResolver.ts | 87 +++++------ .../api/1_0/util/authenticateCommunity.ts | 135 +++++++++--------- shared/src/helper/BinaryData.ts | 8 +- 7 files changed, 126 insertions(+), 144 deletions(-) delete mode 100644 core/src/logic/Community.logic.ts create mode 100644 core/src/logic/community.logic.ts diff --git a/core/src/graphql/logic/interpretEncryptedTransferArgs.ts b/core/src/graphql/logic/interpretEncryptedTransferArgs.ts index 31bf47a56..576a2b3b8 100644 --- a/core/src/graphql/logic/interpretEncryptedTransferArgs.ts +++ b/core/src/graphql/logic/interpretEncryptedTransferArgs.ts @@ -1,5 +1,5 @@ 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' @@ -12,16 +12,17 @@ export const interpretEncryptedTransferArgs = async (args: EncryptedTransferArgs 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 // TODO: maybe use community from caller instead of loading it separately - const requestingCom = await DbCommunity.findOneBy({ publicKey: Buffer.from(args.publicKey, 'hex') }) + const requestingCom = await DbCommunity.findOneBy({ publicKey: argsPublicKey.asBuffer() }) if (!requestingCom) { - const errmsg = `unknown requesting community with publicKey ${args.publicKey}` + const errmsg = `unknown requesting community with publicKey ${argsPublicKey.asHex()}` methodLogger.error(errmsg) throw new Error(errmsg) } if (!requestingCom.publicJwtKey) { - const errmsg = `missing publicJwtKey of requesting community with publicKey ${args.publicKey}` + const errmsg = `missing publicJwtKey of requesting community with publicKey ${argsPublicKey.asHex()}` methodLogger.error(errmsg) throw new Error(errmsg) } @@ -31,7 +32,7 @@ export const interpretEncryptedTransferArgs = async (args: EncryptedTransferArgs 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 ${args.publicKey}` + const errmsg = `invalid payload of community with publicKey ${argsPublicKey.asHex()}` methodLogger.error(errmsg) throw new Error(errmsg) } diff --git a/core/src/logic/Community.logic.ts b/core/src/logic/Community.logic.ts deleted file mode 100644 index 40779c703..000000000 --- a/core/src/logic/Community.logic.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity } from 'database' - -export class CommunityLogic { - public constructor(private self: DbCommunity) {} - - public getFederatedCommunityWithApiOrFail(apiVersion: string): DbFederatedCommunity { - const fedCom = this.self.federatedCommunities?.find((fedCom) => fedCom.apiVersion === apiVersion) - if (!fedCom) { - throw new Error(`Missing federated community with api version ${apiVersion}`) - } - return fedCom - } -} \ No newline at end of file diff --git a/core/src/logic/community.logic.ts b/core/src/logic/community.logic.ts new file mode 100644 index 000000000..8fb9cd59b --- /dev/null +++ b/core/src/logic/community.logic.ts @@ -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 +} diff --git a/core/src/logic/index.ts b/core/src/logic/index.ts index 809465850..7d7a943bf 100644 --- a/core/src/logic/index.ts +++ b/core/src/logic/index.ts @@ -1,2 +1,2 @@ -export { CommunityHandshakeStateLogic } from './CommunityHandshakeState.logic' -export { CommunityLogic } from './Community.logic' \ No newline at end of file +export * from './CommunityHandshakeState.logic' +export * from './community.logic' \ No newline at end of file diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index 0df45f794..9a8008a3c 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -10,7 +10,6 @@ import { FederatedCommunityLoggingView, getHomeCommunity, findPendingCommunityHandshakeOrFailByOneTimeCode, - Community as DbCommunity, getCommunityByPublicKeyOrFail, } from 'database' import { getLogger } from 'log4js' @@ -27,11 +26,12 @@ import { 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 { - @Mutation(() => Boolean) async openConnection( @Arg('data') @@ -39,39 +39,37 @@ export class AuthenticationResolver { ): Promise { 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) { throw new Error(`invalid OpenConnection payload of requesting community with publicKey ${argsPublicKey.asHex()}`) } if (openConnectionJwtPayload.tokentype !== OpenConnectionJwtPayloadType.OPEN_CONNECTION_TYPE) { - throw new Error(`invalid tokentype of community with publicKey ${argsPublicKey.asHex()}`) + throw new Error(`invalid tokentype: ${openConnectionJwtPayload.tokentype} of community with publicKey ${argsPublicKey.asHex()}`) } if (!openConnectionJwtPayload.url) { throw new Error(`invalid url of community with publicKey ${argsPublicKey.asHex()}`) } - methodLogger.debug(`before DbFedCommunity.findOneByOrFail()...`, { publicKey: argsPublicKey.asHex() }) + // methodLogger.debug(`before DbFedCommunity.findOneByOrFail()...`, { publicKey: argsPublicKey.asHex() }) const fedComA = await DbFedCommunity.findOneByOrFail({ publicKey: argsPublicKey.asBuffer() }) - methodLogger.debug(`after DbFedCommunity.findOneByOrFail()...`, new FederatedCommunityLoggingView(fedComA)) + // methodLogger.debug(`after DbFedCommunity.findOneByOrFail()...`, new FederatedCommunityLoggingView(fedComA)) if (!openConnectionJwtPayload.url.startsWith(fedComA.endPoint)) { 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, argsPublicKey, 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) { - let errorText = '' - if (err instanceof Error) { - errorText = err.message - } else { - errorText = String(err) - } - methodLogger.error('invalid jwt token:', errorText) + methodLogger.error('invalid jwt token:', err) // no infos to the caller return true } @@ -84,7 +82,7 @@ export class AuthenticationResolver { ): Promise { 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 @@ -92,27 +90,20 @@ export class AuthenticationResolver { throw new Error(`invalid OpenConnectionCallback payload of requesting community with publicKey ${args.publicKey}`) } const { endPoint, apiVersion } = splitUrlInEndPointAndApiVersion(openConnectionCallbackJwtPayload.url) - methodLogger.debug(`search fedComB per:`, endPoint, apiVersion) + // methodLogger.debug(`search fedComB per:`, endPoint, apiVersion) const fedComB = await DbFedCommunity.findOneBy({ endPoint, apiVersion }) if (!fedComB) { - throw new Error(`unknown callback community with url ${openConnectionCallbackJwtPayload.url}`) + 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) { - let errorText = '' - if (err instanceof Error) { - errorText = err.message - } else { - errorText = String(err) - } - methodLogger.error('invalid jwt token:', errorText) + methodLogger.error('invalid jwt token:', err) // no infos to the caller return true } @@ -125,13 +116,14 @@ export class AuthenticationResolver { ): Promise { 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) + // methodLogger.debug(`interpreted authentication payload...authArgs:`, authArgs) if (!authArgs) { + methodLogger.debug(`interpretEncryptedTransferArgs was called with`, args) throw new Error(`invalid authentication payload of requesting community with publicKey ${argsPublicKey.asHex()}`) } const validOneTimeCode = uint32Schema.safeParse(Number(authArgs.oneTimeCode)) @@ -152,13 +144,13 @@ export class AuthenticationResolver { state.status = CommunityHandshakeStateType.SUCCESS await state.save() - methodLogger.debug(`search community per oneTimeCode:`, authArgs.oneTimeCode) + // methodLogger.debug(`search community per oneTimeCode:`, authArgs.oneTimeCode) const authCom = await getCommunityByPublicKeyOrFail(argsPublicKey) if (authCom) { - methodLogger.debug('found authCom:', new CommunityLoggingView(authCom)) + methodLogger.debug(`found authCom ${authCom.name}`) const authComPublicKey = new Ed25519PublicKey(authCom.publicKey) - methodLogger.debug('authCom.publicKey', authComPublicKey.asHex()) - methodLogger.debug('args.publicKey', argsPublicKey.asHex()) + // 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()}` @@ -173,30 +165,29 @@ export class AuthenticationResolver { authCom.communityUuid = communityUuid.data authCom.authenticatedAt = new Date() await authCom.save() - methodLogger.debug('update authCom.uuid successfully') - + 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) { - let errorString = '' - if (err instanceof Error) { - errorString = err.message - } else { - errorString = String(err) - } if (state) { - methodLogger.info(`state: ${new CommunityHandshakeStateLoggingView(state)}`) - state.status = CommunityHandshakeStateType.FAILED - state.lastError = errorString - await state.save() + 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: ${errorString}`) + methodLogger.error(`failed`, err) // no infos to the caller return null } diff --git a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts index a2c9136b0..efff863fa 100644 --- a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts +++ b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts @@ -1,4 +1,4 @@ -import { CommunityHandshakeStateLogic, CommunityLogic, EncryptedTransferArgs, ensureUrlEndsWithSlash } from 'core' +import { CommunityHandshakeStateLogic, EncryptedTransferArgs, ensureUrlEndsWithSlash } from 'core' import { CommunityHandshakeStateLoggingView, CommunityLoggingView, @@ -6,6 +6,7 @@ import { FederatedCommunity as DbFedCommunity, FederatedCommunityLoggingView, findPendingCommunityHandshake, + getCommunityByPublicKeyOrFail, getCommunityWithFederatedCommunityWithApiOrFail, getHomeCommunity, getHomeCommunityWithFederatedCommunityOrFail, @@ -27,67 +28,55 @@ import { verifyAndDecrypt } from 'shared' import { CommunityHandshakeState as DbCommunityHandshakeState, CommunityHandshakeStateType } from 'database' +import { getFederatedCommunityWithApiOrFail } from 'core' const createLogger = (method: string ) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.api.1_0.util.authenticateCommunity.${method}`) -async function errorState( - error: string, - methodLogger: Logger, - state: DbCommunityHandshakeState, -): Promise { - methodLogger.error(error) - state.status = CommunityHandshakeStateType.FAILED - state.lastError = error - return state.save() -} - export async function startOpenConnectionCallback( handshakeID: string, publicKey: Ed25519PublicKey, - api: string, + fedComA: DbFedCommunity, ): Promise { const methodLogger = createLogger('startOpenConnectionCallback') methodLogger.addContext('handshakeID', handshakeID) - methodLogger.debug(`Authentication: startOpenConnectionCallback() with:`, { - publicKey: publicKey.asHex(), - }) - const pendingState = await findPendingCommunityHandshake(publicKey, api) - 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 - } - } - const state = new DbCommunityHandshakeState() + methodLogger.debug(`start`) + const api = fedComA.apiVersion + + let state: DbCommunityHandshakeState | null = null try { + const pendingState = await findPendingCommunityHandshake(publicKey, api) + 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), - getCommunityWithFederatedCommunityWithApiOrFail(publicKey, api), + getCommunityByPublicKeyOrFail(publicKey), ]) - // load helpers - const homeComBLogic = new CommunityLogic(homeComB) - const comALogic = new CommunityLogic(comA) // get federated communities with correct api version - const homeFedComB = homeComBLogic.getFederatedCommunityWithApiOrFail(api) - const fedComA = comALogic.getFederatedCommunityWithApiOrFail(api) + // 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() 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 - await state.save() - methodLogger.debug( - `Authentication: store oneTimeCode in CommunityHandshakeState:`, - new CommunityHandshakeStateLoggingView(state), - ) + state = await state.save() + methodLogger.debug('[START_OPEN_CONNECTION_CALLBACK] community handshake state created') const client = AuthenticationClientFactory.getInstance(fedComA) @@ -95,29 +84,37 @@ export async function startOpenConnectionCallback( const url = ensureUrlEndsWithSlash(homeFedComB.endPoint) + homeFedComB.apiVersion const callbackArgs = new OpenConnectionCallbackJwtPayloadType(handshakeID, oneTimeCodeString, url) - methodLogger.debug(`Authentication: start openConnectionCallback with args:`, callbackArgs) + // 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 args = new EncryptedTransferArgs() 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.debug(`jwt: ${jwt}`) - await errorState('startOpenConnectionCallback() failed', methodLogger, state) + const errorString = 'startOpenConnectionCallback() failed' + methodLogger.error(errorString) + state.status = CommunityHandshakeStateType.FAILED + state.lastError = errorString + state = await state.save() } } } catch (err) { - let errorString: string = '' - if (err instanceof Error) { - errorString = err.message - } else { - errorString = String(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) + } } - await errorState(`error in startOpenConnectionCallback: ${errorString}`, methodLogger, state) } } @@ -128,13 +125,10 @@ export async function startAuthentication( ): Promise { 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 - const fedComBPublicKey = new Ed25519PublicKey(fedComB.publicKey) try { + const fedComBPublicKey = new Ed25519PublicKey(fedComB.publicKey) const homeComA = await getHomeCommunity() const comB = await DbCommunity.findOneByOrFail({ foreign: true, @@ -168,29 +162,33 @@ export async function startAuthentication( 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) { throw new Error(`Invalid tokentype in authenticate-response of community with publicKey ${fedComBPublicKey.asHex()}`) } - if (!uuidv4Schema.safeParse(payload.uuid).success) { + 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) state.status = CommunityHandshakeStateType.SUCCESS await state.save() - methodLogger.debug('Community Authentication successful:', new CommunityLoggingView(comB)) + 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' @@ -199,17 +197,16 @@ export async function startAuthentication( } } } catch (err) { - let errorString: string = '' - if (err instanceof Error) { - errorString = err.message - } else { - errorString = String(err) - } + methodLogger.error('error in startAuthentication:', err) if (state) { - state.status = CommunityHandshakeStateType.FAILED - state.lastError = errorString - await state.save() + try { + state.status = CommunityHandshakeStateType.FAILED + state.lastError = String(err) + await state.save() + } catch(e) { + methodLogger.error('error on saving CommunityHandshakeState', e) + } } - methodLogger.error('error in startAuthentication:', errorString) + } } diff --git a/shared/src/helper/BinaryData.ts b/shared/src/helper/BinaryData.ts index 583dccd4e..37d63b156 100644 --- a/shared/src/helper/BinaryData.ts +++ b/shared/src/helper/BinaryData.ts @@ -18,19 +18,13 @@ export class BinaryData { } else if (Buffer.isBuffer(input)) { this.buf = input this.hex = input.toString('hex') - } else if (input === undefined) { + } else { this.buf = Buffer.from('') this.hex = '' - } else { - throw new Error(`Either valid hex string or Buffer expected: ${input}`) } } asBuffer(): Buffer { - if (!this.buf || !Buffer.isBuffer(this.buf)) { - logging.warn('BinaryData.buf is invalid, try to create fresh buffer from hex') - this.buf = Buffer.from(this.hex, 'hex') - } return this.buf } From b78a627844624eea90dd2f453f88e032eb740c39 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 14:50:05 +0200 Subject: [PATCH 40/48] more logging optimizations --- .../src/federation/authenticateCommunities.ts | 42 ++++++++++--------- backend/src/federation/validateCommunities.ts | 21 +++------- .../1_0/resolver/AuthenticationResolver.ts | 1 + .../api/1_0/util/authenticateCommunity.ts | 1 + 4 files changed, 31 insertions(+), 34 deletions(-) diff --git a/backend/src/federation/authenticateCommunities.ts b/backend/src/federation/authenticateCommunities.ts index 9226d652c..5c314ab93 100644 --- a/backend/src/federation/authenticateCommunities.ts +++ b/backend/src/federation/authenticateCommunities.ts @@ -7,12 +7,12 @@ import { findPendingCommunityHandshake, getHomeCommunityWithFederatedCommunityOrFail, CommunityHandshakeStateType, - getCommunityByPublicKeyOrFail + getCommunityByPublicKeyOrFail, } from 'database' import { randombytes_random } from 'sodium-native' 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 { communityAuthenticatedSchema, encryptAndSign, OpenConnectionJwtPayloadType } from 'shared' @@ -20,27 +20,29 @@ import { getLogger } from 'log4js' import { AuthenticationClientFactory } from './client/AuthenticationClientFactory' import { EncryptedTransferArgs } from 'core' import { CommunityHandshakeStateLogic } from 'core' -import { CommunityLogic } from 'core' import { Ed25519PublicKey } from 'shared' 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 { +): Promise { const methodLogger = createLogger('startCommunityAuthentication') const handshakeID = randombytes_random().toString() methodLogger.addContext('handshakeID', handshakeID) - methodLogger.debug(`startCommunityAuthentication()...`, { - fedComB: new FederatedCommunityLoggingView(fedComB), - }) + methodLogger.debug(`start with public key ${fedComB.publicKey}`) const homeComA = await getHomeCommunityWithFederatedCommunityOrFail(fedComB.apiVersion) - methodLogger.debug('homeComA', new CommunityLoggingView(homeComA)) - const homeComALogic = new CommunityLogic(homeComA) - const homeFedComA = homeComALogic.getFederatedCommunityWithApiOrFail(fedComB.apiVersion) + // methodLogger.debug('homeComA', new CommunityLoggingView(homeComA)) + const homeFedComA = getFederatedCommunityWithApiOrFail(homeComA, fedComB.apiVersion) const fedComBPublicKey = new Ed25519PublicKey(fedComB.publicKey) const comB = await getCommunityByPublicKeyOrFail(fedComBPublicKey) - methodLogger.debug('started with comB:', new CommunityLoggingView(comB)) + // methodLogger.debug('started with comB:', new CommunityLoggingView(comB)) // check if communityUuid is not a valid v4Uuid // communityAuthenticatedSchema.safeParse return true @@ -48,11 +50,11 @@ export async function startCommunityAuthentication( // - 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 + return StartCommunityAuthenticationResult.ALREADY_AUTHENTICATED } - methodLogger.debug('comB.uuid is null or is a not valid v4Uuid...', + /*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) @@ -62,7 +64,7 @@ export async function startCommunityAuthentication( 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 + return StartCommunityAuthenticationResult.ALREADY_IN_PROGRESS } } @@ -78,24 +80,25 @@ export async function startCommunityAuthentication( 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) + // methodLogger.debug('payload', payload) const jws = await encryptAndSign(payload, homeComA!.privateJwtKey!, comB.publicJwtKey!) - methodLogger.debug('jws', jws) + // 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) + // methodLogger.debug('before client.openConnection() args:', args) const result = await client.openConnection(args) if (result) { - methodLogger.info(`successful initiated at community:`, fedComB.endPoint) + methodLogger.debug(`successful initiated at community:`, fedComB.endPoint) } else { const errorMsg = `can't initiate at community: ${fedComB.endPoint}` methodLogger.error(errorMsg) @@ -104,4 +107,5 @@ export async function startCommunityAuthentication( } await state.save() } + return StartCommunityAuthenticationResult.SUCCESSFULLY_STARTED } diff --git a/backend/src/federation/validateCommunities.ts b/backend/src/federation/validateCommunities.ts index 8d8972ed5..bb5905aa3 100644 --- a/backend/src/federation/validateCommunities.ts +++ b/backend/src/federation/validateCommunities.ts @@ -12,7 +12,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 { buffer32Schema, createKeyPair, Ed25519PublicKey, hex64Schema, uint32Schema } from 'shared' +import { createKeyPair, Ed25519PublicKey, uint32Schema } from 'shared' import { getLogger } from 'log4js' import { startCommunityAuthentication } from './authenticateCommunities' import { PublicCommunityInfoLoggingView } from './client/1_0/logging/PublicCommunityInfoLogging.view' @@ -28,16 +28,6 @@ export async function startValidateCommunities(timerInterval: number): Promise see https://javascript.info/settimeout-setinterval setTimeout(async function run() { @@ -56,11 +46,11 @@ export async function validateCommunities(): Promise { 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 { @@ -73,13 +63,14 @@ export async function validateCommunities(): Promise { 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) } diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index 9a8008a3c..7a709d3f7 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -143,6 +143,7 @@ export class AuthenticationResolver { } 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) diff --git a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts index efff863fa..b6766f73a 100644 --- a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts +++ b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts @@ -186,6 +186,7 @@ export async function startAuthentication( 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`) From 2f69771e720db61f4733a9838db10c58a769699d Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 14:53:58 +0200 Subject: [PATCH 41/48] cleanup headers --- backend/src/federation/authenticateCommunities.ts | 2 -- backend/src/federation/validateCommunities.ts | 4 +--- .../src/graphql/api/1_0/resolver/AuthenticationResolver.ts | 3 --- federation/src/graphql/api/1_0/util/authenticateCommunity.ts | 5 +---- 4 files changed, 2 insertions(+), 12 deletions(-) diff --git a/backend/src/federation/authenticateCommunities.ts b/backend/src/federation/authenticateCommunities.ts index 5c314ab93..980fabf6f 100644 --- a/backend/src/federation/authenticateCommunities.ts +++ b/backend/src/federation/authenticateCommunities.ts @@ -1,9 +1,7 @@ import { CommunityHandshakeState as DbCommunityHandshakeState, CommunityHandshakeStateLoggingView, - CommunityLoggingView, FederatedCommunity as DbFederatedCommunity, - FederatedCommunityLoggingView, findPendingCommunityHandshake, getHomeCommunityWithFederatedCommunityOrFail, CommunityHandshakeStateType, diff --git a/backend/src/federation/validateCommunities.ts b/backend/src/federation/validateCommunities.ts index bb5905aa3..10088cf35 100644 --- a/backend/src/federation/validateCommunities.ts +++ b/backend/src/federation/validateCommunities.ts @@ -1,9 +1,7 @@ import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity, - FederatedCommunityLoggingView, getHomeCommunity, - getNotReachableCommunities, } from 'database' import { IsNull } from 'typeorm' @@ -12,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, Ed25519PublicKey, uint32Schema } from 'shared' +import { createKeyPair, Ed25519PublicKey } from 'shared' import { getLogger } from 'log4js' import { startCommunityAuthentication } from './authenticateCommunities' import { PublicCommunityInfoLoggingView } from './client/1_0/logging/PublicCommunityInfoLogging.view' diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts index 7a709d3f7..d22c816ff 100644 --- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts @@ -1,13 +1,10 @@ -import { CONFIG } from '@/config' import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { CommunityHandshakeStateLogic, EncryptedTransferArgs, interpretEncryptedTransferArgs, splitUrlInEndPointAndApiVersion } from 'core' import { - CommunityLoggingView, CommunityHandshakeStateLoggingView, CommunityHandshakeState as DbCommunityHandshakeState, CommunityHandshakeStateType, FederatedCommunity as DbFedCommunity, - FederatedCommunityLoggingView, getHomeCommunity, findPendingCommunityHandshakeOrFailByOneTimeCode, getCommunityByPublicKeyOrFail, diff --git a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts index b6766f73a..23f336800 100644 --- a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts +++ b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts @@ -1,17 +1,14 @@ import { CommunityHandshakeStateLogic, EncryptedTransferArgs, ensureUrlEndsWithSlash } from 'core' import { CommunityHandshakeStateLoggingView, - CommunityLoggingView, Community as DbCommunity, FederatedCommunity as DbFedCommunity, - FederatedCommunityLoggingView, findPendingCommunityHandshake, getCommunityByPublicKeyOrFail, - getCommunityWithFederatedCommunityWithApiOrFail, getHomeCommunity, getHomeCommunityWithFederatedCommunityOrFail, } from 'database' -import { getLogger, Logger } from 'log4js' +import { getLogger } from 'log4js' import { AuthenticationClientFactory } from '@/client/AuthenticationClientFactory' import { randombytes_random } from 'sodium-native' From 044ff3358c204fcb239e565638f2b0a18a6048c6 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 14:57:17 +0200 Subject: [PATCH 42/48] remove not used enum value --- database/src/enum/CommunityHandshakeStateType.ts | 1 - database/src/queries/communityHandshakes.test.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/database/src/enum/CommunityHandshakeStateType.ts b/database/src/enum/CommunityHandshakeStateType.ts index 8b811da61..e41913cc0 100644 --- a/database/src/enum/CommunityHandshakeStateType.ts +++ b/database/src/enum/CommunityHandshakeStateType.ts @@ -2,7 +2,6 @@ export enum CommunityHandshakeStateType { START_COMMUNITY_AUTHENTICATION = 'START_COMMUNITY_AUTHENTICATION', START_OPEN_CONNECTION_CALLBACK = 'START_OPEN_CONNECTION_CALLBACK', START_AUTHENTICATION = 'START_AUTHENTICATION', - OPEN_CONNECTION_CALLBACK = 'OPEN_CONNECTION_CALLBACK', SUCCESS = 'SUCCESS', FAILED = 'FAILED', diff --git a/database/src/queries/communityHandshakes.test.ts b/database/src/queries/communityHandshakes.test.ts index 32eb31c4b..372fb1293 100644 --- a/database/src/queries/communityHandshakes.test.ts +++ b/database/src/queries/communityHandshakes.test.ts @@ -56,7 +56,7 @@ describe('communityHandshakes', () => { await createCommunityHandshakeState(publicKey.asBuffer()) const communityHandshakeState = await findPendingCommunityHandshake(publicKey, '1_0') expect(communityHandshakeState).toBeDefined() - communityHandshakeState!.status = CommunityHandshakeStateType.OPEN_CONNECTION_CALLBACK + communityHandshakeState!.status = CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK await communityHandshakeState!.save() const communityHandshakeState2 = await findPendingCommunityHandshake(publicKey, '1_0') const states = await DbCommunityHandshakeState.find() @@ -64,7 +64,7 @@ describe('communityHandshakes', () => { expect(communityHandshakeState2).toMatchObject({ publicKey: publicKey.asBuffer(), apiVersion: '1_0', - status: CommunityHandshakeStateType.OPEN_CONNECTION_CALLBACK, + status: CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK, handshakeId: 1 }) }) From dd0ce21a15d157651ffd3813945c00715231d7b4 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 15:00:38 +0200 Subject: [PATCH 43/48] removed not used schemas --- shared/src/schema/base.schema.test.ts | 17 +---------------- shared/src/schema/base.schema.ts | 7 +------ 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/shared/src/schema/base.schema.test.ts b/shared/src/schema/base.schema.test.ts index db0a07d52..d12f2a3b4 100644 --- a/shared/src/schema/base.schema.test.ts +++ b/shared/src/schema/base.schema.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from 'bun:test' -import { generateKeyPairSync } from 'node:crypto' -import { uuidv4Schema, uint32Schema, buffer32Schema } from './base.schema' +import { uuidv4Schema, uint32Schema } from './base.schema' import { v4 as uuidv4 } from 'uuid' describe('uuidv4 schema', () => { @@ -23,17 +22,3 @@ describe('uint32 schema', () => { expect(uint32Schema.safeParse(2092352810).success).toBeTruthy() }) }) - -describe('buffer32 schema', () => { - it('should validate buffer', () => { - const { publicKey } = generateKeyPairSync('ed25519') - const buffer = publicKey.export({ type: 'spki', format: 'der' }).slice(-32) - expect(Buffer.isBuffer(buffer)).toBeTruthy() - expect(buffer.length).toBe(32) - expect(buffer32Schema.safeParse(buffer).success).toBeTruthy() - }) - - it("shouldn't validate string", () => { - expect(buffer32Schema.safeParse('3e1a2eecc95c48fedf47a522a8c77b91').success).toBeFalsy() - }) -}) diff --git a/shared/src/schema/base.schema.ts b/shared/src/schema/base.schema.ts index 0dcdf09a9..2f158b1b0 100644 --- a/shared/src/schema/base.schema.ts +++ b/shared/src/schema/base.schema.ts @@ -1,12 +1,7 @@ -import { string, number, custom } from 'zod' +import { string, number } from 'zod' 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 buffer32Schema = custom( - (val: Buffer) => Buffer.isBuffer(val) && val.length === 32, - 'Invalid buffer' -) -export const hex64Schema = string().length(64).regex(/^[0-9A-Fa-f]$/) \ No newline at end of file From 6293eff1de4e3b3b0d76601c776961bf69ef57b9 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 15:24:39 +0200 Subject: [PATCH 44/48] fix public key logging as hex --- backend/src/federation/authenticateCommunities.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/federation/authenticateCommunities.ts b/backend/src/federation/authenticateCommunities.ts index 980fabf6f..9c87da3c0 100644 --- a/backend/src/federation/authenticateCommunities.ts +++ b/backend/src/federation/authenticateCommunities.ts @@ -33,12 +33,13 @@ export async function startCommunityAuthentication( ): Promise { const methodLogger = createLogger('startCommunityAuthentication') const handshakeID = randombytes_random().toString() + const fedComBPublicKey = new Ed25519PublicKey(fedComB.publicKey) methodLogger.addContext('handshakeID', handshakeID) - methodLogger.debug(`start with public key ${fedComB.publicKey}`) + 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 fedComBPublicKey = new Ed25519PublicKey(fedComB.publicKey) + const comB = await getCommunityByPublicKeyOrFail(fedComBPublicKey) // methodLogger.debug('started with comB:', new CommunityLoggingView(comB)) // check if communityUuid is not a valid v4Uuid From c8eeac2c02e9b762a6eb4355f7614f2589cf13f5 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Oct 2025 15:52:39 +0200 Subject: [PATCH 45/48] react to case, if both communities try to authenticate each other at the same time --- database/src/queries/communityHandshakes.ts | 7 +++++-- .../src/graphql/api/1_0/util/authenticateCommunity.ts | 9 +++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/database/src/queries/communityHandshakes.ts b/database/src/queries/communityHandshakes.ts index 23fe4f0d1..9dc83118b 100644 --- a/database/src/queries/communityHandshakes.ts +++ b/database/src/queries/communityHandshakes.ts @@ -6,14 +6,17 @@ 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): Promise { +export function findPendingCommunityHandshake( + publicKey: Ed25519PublicKey, apiVersion: string, status?: CommunityHandshakeStateType +): Promise { return CommunityHandshakeState.findOne({ where: { publicKey: publicKey.asBuffer(), apiVersion, - status: Not(In([ + status: status || Not(In([ CommunityHandshakeStateType.EXPIRED, CommunityHandshakeStateType.FAILED, CommunityHandshakeStateType.SUCCESS diff --git a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts index 23f336800..4b95898ba 100644 --- a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts +++ b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts @@ -41,7 +41,7 @@ export async function startOpenConnectionCallback( let state: DbCommunityHandshakeState | null = null try { - const pendingState = await findPendingCommunityHandshake(publicKey, api) + const pendingState = await findPendingCommunityHandshake(publicKey, api, CommunityHandshakeStateType.START_OPEN_CONNECTION_CALLBACK) if (pendingState) { const stateLogic = new CommunityHandshakeStateLogic(pendingState) // retry on timeout or failure @@ -134,15 +134,12 @@ export async function startAuthentication( if (!comB.publicJwtKey) { throw new Error('Public JWT key still not exist for foreign community') } - state = await findPendingCommunityHandshake(fedComBPublicKey, fedComB.apiVersion) + 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()) || - state.status !== CommunityHandshakeStateType.START_COMMUNITY_AUTHENTICATION - ) { + if ((await stateLogic.isTimeoutUpdate())) { methodLogger.debug('invalid state', new CommunityHandshakeStateLoggingView(state)) throw new Error('No valid pending community handshake found') } From 414ff8ac5a7477109f80123ccca5c4c8ed4511b2 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Wed, 15 Oct 2025 14:25:39 +0200 Subject: [PATCH 46/48] fixes --- bun.lock | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++- package.json | 5 +++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/bun.lock b/bun.lock index c7810fb83..63a955458 100644 --- a/bun.lock +++ b/bun.lock @@ -13,6 +13,7 @@ "devDependencies": { "@biomejs/biome": "2.0.0", "@types/minimatch": "6.0.0", + "bbump": "^1.0.2", }, }, "admin": { @@ -759,6 +760,10 @@ "@iconify/utils": ["@iconify/utils@2.3.0", "", { "dependencies": { "@antfu/install-pkg": "^1.0.0", "@antfu/utils": "^8.1.0", "@iconify/types": "^2.0.0", "debug": "^4.4.0", "globals": "^15.14.0", "kolorist": "^1.8.0", "local-pkg": "^1.0.0", "mlly": "^1.7.4" } }, "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA=="], + "@inquirer/external-editor": ["@inquirer/external-editor@1.0.2", "", { "dependencies": { "chardet": "^2.1.0", "iconv-lite": "^0.7.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ=="], + + "@inquirer/figures": ["@inquirer/figures@1.0.14", "", {}, "sha512-DbFgdt+9/OZYFM+19dbpXOSeAstPy884FPy1KjDu4anWwymZeOYhMY1mdFri172htv6mvc/uvIAAi7b7tvjJBQ=="], + "@intlify/bundle-utils": ["@intlify/bundle-utils@10.0.1", "", { "dependencies": { "@intlify/message-compiler": "^11.1.2", "@intlify/shared": "^11.1.2", "acorn": "^8.8.2", "escodegen": "^2.1.0", "estree-walker": "^2.0.2", "jsonc-eslint-parser": "^2.3.0", "mlly": "^1.2.0", "source-map-js": "^1.0.1", "yaml-eslint-parser": "^1.2.2" } }, "sha512-WkaXfSevtpgtUR4t8K2M6lbR7g03mtOxFeh+vXp5KExvPqS12ppaRj1QxzwRuRI5VUto54A22BjKoBMLyHILWQ=="], "@intlify/core-base": ["@intlify/core-base@9.14.5", "", { "dependencies": { "@intlify/message-compiler": "9.14.5", "@intlify/shared": "9.14.5" } }, "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA=="], @@ -1631,6 +1636,8 @@ "baseline-browser-mapping": ["baseline-browser-mapping@2.8.16", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw=="], + "bbump": ["bbump@1.0.2", "", { "dependencies": { "inquirer": "^9.0.0" }, "bin": { "bbump": "index.js" } }, "sha512-JGwlqjBF9cvPCjONPlb8R7YH4Uum8E06MJchUxpbjr2Ft7v/hjq+mM7zq93anI0Ww7sg9WAQulzCVDGUnd9YdQ=="], + "bignumber.js": ["bignumber.js@9.0.0", "", {}, "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A=="], "bin-version": ["bin-version@6.0.0", "", { "dependencies": { "execa": "^5.0.0", "find-versions": "^5.0.0" } }, "sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw=="], @@ -1641,6 +1648,8 @@ "birpc": ["birpc@2.6.1", "", {}, "sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ=="], + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + "bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="], "body-parser": ["body-parser@1.20.3", "", { "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" } }, "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g=="], @@ -1715,6 +1724,8 @@ "character-parser": ["character-parser@2.2.0", "", { "dependencies": { "is-regex": "^1.0.3" } }, "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw=="], + "chardet": ["chardet@2.1.0", "", {}, "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA=="], + "check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="], "cheerio": ["cheerio@1.0.0-rc.10", "", { "dependencies": { "cheerio-select": "^1.5.0", "dom-serializer": "^1.3.2", "domhandler": "^4.2.0", "htmlparser2": "^6.1.0", "parse5": "^6.0.1", "parse5-htmlparser2-tree-adapter": "^6.0.1", "tslib": "^2.2.0" } }, "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw=="], @@ -1735,10 +1746,18 @@ "clean-css": ["clean-css@5.3.3", "", { "dependencies": { "source-map": "~0.6.0" } }, "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg=="], + "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], + + "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + + "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], + "clipboard-polyfill": ["clipboard-polyfill@4.1.1", "", {}, "sha512-nbvNLrcX0zviek5QHLFRAaLrx8y/s8+RF2stH43tuS+kP5XlHMrcD0UGBWq43Hwp6WuuK7KefRMP56S45ibZkA=="], "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], + "co": ["co@4.6.0", "", {}, "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ=="], "collect-v8-coverage": ["collect-v8-coverage@1.0.2", "", {}, "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q=="], @@ -2345,6 +2364,8 @@ "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + "inquirer": ["inquirer@9.3.8", "", { "dependencies": { "@inquirer/external-editor": "^1.0.2", "@inquirer/figures": "^1.0.3", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "1.0.0", "ora": "^5.4.1", "run-async": "^3.0.0", "rxjs": "^7.8.1", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.2" } }, "sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w=="], + "inspect-with-kind": ["inspect-with-kind@1.0.5", "", { "dependencies": { "kind-of": "^6.0.2" } }, "sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g=="], "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], @@ -2389,6 +2410,8 @@ "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="], + "is-language-code": ["is-language-code@3.1.0", "", { "dependencies": { "@babel/runtime": "^7.14.0" } }, "sha512-zJdQ3QTeLye+iphMeK3wks+vXSRFKh68/Pnlw7aOfApFSEIOhYa8P9vwwa6QrImNNBMJTiL1PpYF0f4BxDuEgA=="], "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], @@ -2427,6 +2450,8 @@ "is-typedarray": ["is-typedarray@1.0.0", "", {}, "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="], + "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], + "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], @@ -2643,6 +2668,8 @@ "lodash.truncate": ["lodash.truncate@4.4.2", "", {}, "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw=="], + "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], + "log4js": ["log4js@6.9.1", "", { "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", "flatted": "^3.2.7", "rfdc": "^1.3.0", "streamroller": "^3.1.5" } }, "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g=="], "loglevel": ["loglevel@1.9.2", "", {}, "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg=="], @@ -2733,6 +2760,8 @@ "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], + "mute-stream": ["mute-stream@1.0.0", "", {}, "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA=="], + "mysql": ["mysql@2.18.1", "", { "dependencies": { "bignumber.js": "9.0.0", "readable-stream": "2.3.7", "safe-buffer": "5.1.2", "sqlstring": "2.3.1" } }, "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig=="], "mysql2": ["mysql2@2.3.3", "", { "dependencies": { "denque": "^2.0.1", "generate-function": "^2.3.1", "iconv-lite": "^0.6.3", "long": "^4.0.0", "lru-cache": "^6.0.0", "named-placeholders": "^1.1.2", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA=="], @@ -2833,6 +2862,8 @@ "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], + "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], "oxc-resolver": ["oxc-resolver@11.9.0", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.9.0", "@oxc-resolver/binding-android-arm64": "11.9.0", "@oxc-resolver/binding-darwin-arm64": "11.9.0", "@oxc-resolver/binding-darwin-x64": "11.9.0", "@oxc-resolver/binding-freebsd-x64": "11.9.0", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.9.0", "@oxc-resolver/binding-linux-arm-musleabihf": "11.9.0", "@oxc-resolver/binding-linux-arm64-gnu": "11.9.0", "@oxc-resolver/binding-linux-arm64-musl": "11.9.0", "@oxc-resolver/binding-linux-ppc64-gnu": "11.9.0", "@oxc-resolver/binding-linux-riscv64-gnu": "11.9.0", "@oxc-resolver/binding-linux-riscv64-musl": "11.9.0", "@oxc-resolver/binding-linux-s390x-gnu": "11.9.0", "@oxc-resolver/binding-linux-x64-gnu": "11.9.0", "@oxc-resolver/binding-linux-x64-musl": "11.9.0", "@oxc-resolver/binding-wasm32-wasi": "11.9.0", "@oxc-resolver/binding-win32-arm64-msvc": "11.9.0", "@oxc-resolver/binding-win32-ia32-msvc": "11.9.0", "@oxc-resolver/binding-win32-x64-msvc": "11.9.0" } }, "sha512-u714L0DBBXpD0ERErCQlun2XwinuBfIGo2T8bA7xE8WLQ4uaJudO/VOEQCWslOmcDY2nEkS+UVir5PpyvSG23w=="], @@ -3067,6 +3098,8 @@ "responselike": ["responselike@3.0.0", "", { "dependencies": { "lowercase-keys": "^3.0.0" } }, "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg=="], + "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="], + "ret": ["ret@0.1.15", "", {}, "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="], "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], @@ -3083,6 +3116,8 @@ "run-applescript": ["run-applescript@3.2.0", "", { "dependencies": { "execa": "^0.10.0" } }, "sha512-Ep0RsvAjnRcBX1p5vogbaBdAGu/8j/ewpvGqnQYunnLd9SM0vWcPJewPKNnWFggf0hF0pwIgwV5XK7qQ7UZ8Qg=="], + "run-async": ["run-async@3.0.0", "", {}, "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q=="], + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], @@ -3535,6 +3570,8 @@ "watchpack": ["watchpack@2.4.4", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA=="], + "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], + "web-resource-inliner": ["web-resource-inliner@6.0.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "escape-goat": "^3.0.0", "htmlparser2": "^5.0.0", "mime": "^2.4.6", "node-fetch": "^2.6.0", "valid-data-url": "^3.0.0" } }, "sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A=="], "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], @@ -3577,7 +3614,7 @@ "workerpool": ["workerpool@9.3.4", "", {}, "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg=="], - "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -3671,6 +3708,8 @@ "@iconify/utils/local-pkg": ["local-pkg@1.1.2", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A=="], + "@inquirer/external-editor/iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], + "@intlify/bundle-utils/@intlify/message-compiler": ["@intlify/message-compiler@11.1.12", "", { "dependencies": { "@intlify/shared": "11.1.12", "source-map-js": "^1.0.2" } }, "sha512-Fv9iQSJoJaXl4ZGkOCN1LDM3trzze0AS2zRz2EHLiwenwL6t0Ki9KySYlyr27yVOj5aVz0e55JePO+kELIvfdQ=="], "@intlify/bundle-utils/@intlify/shared": ["@intlify/shared@11.1.12", "", {}, "sha512-Om86EjuQtA69hdNj3GQec9ZC0L0vPSAnXzB3gP/gyJ7+mA7t06d9aOAiqMZ+xEOsumGP4eEBlfl8zF2LOTzf2A=="], @@ -3877,6 +3916,10 @@ "backend/regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="], + "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], @@ -3909,6 +3952,8 @@ "clean-css/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "concurrently/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], "css-select/domhandler": ["domhandler@4.3.1", "", { "dependencies": { "domelementtype": "^2.2.0" } }, "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ=="], @@ -4133,6 +4178,8 @@ "readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "run-applescript/execa": ["execa@0.10.0", "", { "dependencies": { "cross-spawn": "^6.0.0", "get-stream": "^3.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", "signal-exit": "^3.0.0", "strip-eof": "^1.0.0" } }, "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw=="], "safe-array-concat/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], @@ -4275,6 +4322,8 @@ "vue-i18n/@intlify/core-base": ["@intlify/core-base@9.13.1", "", { "dependencies": { "@intlify/message-compiler": "9.13.1", "@intlify/shared": "9.13.1" } }, "sha512-+bcQRkJO9pcX8d0gel9ZNfrzU22sZFSA0WVhfXrf5jdJOS24a+Bp8pozuS9sBI9Hk/tGz83pgKfmqcn/Ci7/8w=="], + "wcwidth/defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], + "web-resource-inliner/htmlparser2": ["htmlparser2@5.0.1", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^3.3.0", "domutils": "^2.4.2", "entities": "^2.0.0" } }, "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ=="], "web-resource-inliner/mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="], @@ -4599,6 +4648,8 @@ "html-to-text/htmlparser2/domutils/dom-serializer": ["dom-serializer@1.4.1", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", "entities": "^2.0.0" } }, "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag=="], + "jest-cli/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "jest-environment-jsdom/jsdom/cssstyle/cssom": ["cssom@0.3.8", "", {}, "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="], "jest-environment-jsdom/jsdom/http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], diff --git a/package.json b/package.json index cbfb818ec..4429c94fe 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ ], "scripts": { "release": "bumpp --no-commit --no-push -r", - "version": "auto-changelog -p && git add CHANGELOG.md", + "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", @@ -38,7 +38,8 @@ }, "devDependencies": { "@biomejs/biome": "2.0.0", - "@types/minimatch": "6.0.0" + "@types/minimatch": "6.0.0", + "bbump": "^1.0.2" }, "engines": { "node": ">=18" From 4c55e8ae09955e3dd89a87298d757fb960c1d156 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Wed, 15 Oct 2025 14:25:50 +0200 Subject: [PATCH 47/48] chore: release v2.7.0 --- admin/package.json | 2 +- backend/package.json | 2 +- config-schema/package.json | 2 +- core/package.json | 2 +- database/package.json | 2 +- dht-node/package.json | 2 +- federation/package.json | 2 +- frontend/package.json | 2 +- package.json | 2 +- shared/package.json | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/admin/package.json b/admin/package.json index 78183299a..672da9425 100644 --- a/admin/package.json +++ b/admin/package.json @@ -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", diff --git a/backend/package.json b/backend/package.json index 959897cfb..5921bca92 100644 --- a/backend/package.json +++ b/backend/package.json @@ -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", diff --git a/config-schema/package.json b/config-schema/package.json index 69ce50ab9..e5fd4544b 100644 --- a/config-schema/package.json +++ b/config-schema/package.json @@ -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", diff --git a/core/package.json b/core/package.json index f7d350d25..3771e3a8e 100644 --- a/core/package.json +++ b/core/package.json @@ -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", diff --git a/database/package.json b/database/package.json index af60a522e..84504372c 100644 --- a/database/package.json +++ b/database/package.json @@ -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", diff --git a/dht-node/package.json b/dht-node/package.json index af383364e..a4c578c12 100644 --- a/dht-node/package.json +++ b/dht-node/package.json @@ -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/", diff --git a/federation/package.json b/federation/package.json index a6bc99524..7908ddf29 100644 --- a/federation/package.json +++ b/federation/package.json @@ -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", diff --git a/frontend/package.json b/frontend/package.json index aa46774b2..f8826b0b1 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "2.6.1", + "version": "2.7.0", "private": true, "scripts": { "dev": "concurrently \"yarn watch-scss\" \"vite\"", diff --git a/package.json b/package.json index 4429c94fe..e022f9008 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gradido", - "version": "2.6.1", + "version": "2.7.0", "description": "Gradido", "main": "index.js", "repository": "git@github.com:gradido/gradido.git", diff --git a/shared/package.json b/shared/package.json index d18fa29cf..73e4e6832 100644 --- a/shared/package.json +++ b/shared/package.json @@ -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", From ccd183075357e24d270e27e6f21575ccd470b024 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Wed, 15 Oct 2025 14:27:53 +0200 Subject: [PATCH 48/48] changelog --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b6f58fbb..9fa75018f 100644 --- a/CHANGELOG.md +++ b/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)