remove join from communityHandshakeState, await save direct, to prevent drag away errors

This commit is contained in:
einhornimmond 2025-10-14 12:32:57 +02:00
parent d11a794dd5
commit 1c347b7ff7
8 changed files with 49 additions and 104 deletions

View File

@ -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) {

View File

@ -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
}

View File

@ -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[]
}

View File

@ -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),
}
}
}

View File

@ -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
})
})
})

View File

@ -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<CommunityHandshakeState | null> {
export function findPendingCommunityHandshake(publicKey: Ed25519PublicKey, apiVersion: string): Promise<CommunityHandshakeState | null> {
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<CommunityHandshakeState> {
return CommunityHandshakeState.findOneOrFail({
where: { oneTimeCode },
relations: { federatedCommunity: { community: true } },
})
}

View File

@ -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<DbCommunityHandshakeState> | 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
}
}
}
}

View File

@ -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<DbCommunityHandshakeState> | 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<DbCommunityHandshakeState> | 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
}
}
}