From 4ca94b189caf93d3b73a61386c87a4edef5c8c6c Mon Sep 17 00:00:00 2001
From: clauspeterhuebner
Date: Fri, 4 Jul 2025 18:22:02 +0200
Subject: [PATCH] full code of encrypted authentication process, tests open
---
.../src/federation/authenticateCommunities.ts | 32 +++++------
.../AuthenticationResponseJwtPayloadType.ts | 17 ++++++
.../1_0/resolver/AuthenticationResolver.ts | 41 ++++++++++----
.../api/1_0/util/authenticateCommunity.ts | 56 +++++++++++--------
4 files changed, 94 insertions(+), 52 deletions(-)
create mode 100644 core/src/auth/jwt/payloadtypes/AuthenticationResponseJwtPayloadType.ts
diff --git a/backend/src/federation/authenticateCommunities.ts b/backend/src/federation/authenticateCommunities.ts
index d52de578b..3b505af0f 100644
--- a/backend/src/federation/authenticateCommunities.ts
+++ b/backend/src/federation/authenticateCommunities.ts
@@ -1,4 +1,4 @@
-import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity } from 'database'
+import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity, getHomeCommunity } from 'database'
import { validate as validateUUID, version as versionUUID } from 'uuid'
import { CONFIG } from '@/config'
@@ -18,45 +18,45 @@ const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.authenticateCo
export async function startCommunityAuthentication(
foreignFedCom: DbFederatedCommunity,
): Promise {
- const homeCom = await DbCommunity.findOneByOrFail({ foreign: false })
- const homeFedCom = await DbFederatedCommunity.findOneByOrFail({
+ const homeComA = await getHomeCommunity()
+ const homeFedComA = await DbFederatedCommunity.findOneByOrFail({
foreign: false,
apiVersion: CONFIG.FEDERATION_BACKEND_SEND_ON_API,
})
- const foreignCom = await DbCommunity.findOneByOrFail({ publicKey: foreignFedCom.publicKey })
+ const foreignComB = await DbCommunity.findOneByOrFail({ publicKey: foreignFedCom.publicKey })
logger.debug(
'started with foreignFedCom:',
foreignFedCom.endPoint,
foreignFedCom.publicKey.toString('hex'),
- foreignCom.publicJwtKey,
+ foreignComB.publicJwtKey,
)
// check if communityUuid is a valid v4Uuid and not still a temporary onetimecode
if (
- foreignCom &&
- ((foreignCom.communityUuid === null && foreignCom.authenticatedAt === null) ||
- (foreignCom.communityUuid !== null &&
- !validateUUID(foreignCom.communityUuid) &&
- versionUUID(foreignCom.communityUuid) !== 4))
+ foreignComB &&
+ ((foreignComB.communityUuid === null && foreignComB.authenticatedAt === null) ||
+ (foreignComB.communityUuid !== null &&
+ !validateUUID(foreignComB.communityUuid) &&
+ versionUUID(foreignComB.communityUuid) !== 4))
) {
try {
const client = AuthenticationClientFactory.getInstance(foreignFedCom)
if (client instanceof V1_0_AuthenticationClient) {
- if (!foreignCom.publicJwtKey) {
+ if (!foreignComB.publicJwtKey) {
throw new Error('Public JWT key still not exist for foreign community')
}
//create JWT with url in payload encrypted by foreignCom.publicJwtKey and signed with homeCom.privateJwtKey
const payload = new OpenConnectionJwtPayloadType(
- ensureUrlEndsWithSlash(homeFedCom.endPoint).concat(homeFedCom.apiVersion),
+ ensureUrlEndsWithSlash(homeFedComA.endPoint).concat(homeFedComA.apiVersion),
)
- const jws = await encryptAndSign(payload, homeCom.privateJwtKey!, foreignCom.publicJwtKey)
+ const jws = await encryptAndSign(payload, homeComA!.privateJwtKey!, foreignComB.publicJwtKey)
// prepare the args for the client invocation
const args = new EncryptedTransferArgs()
- args.publicKey = homeCom.publicKey.toString('hex')
+ args.publicKey = homeComA!.publicKey.toString('hex')
args.jwt = jws
logger.debug(
'before client.openConnection() args:',
- homeCom.publicKey.toString('hex'),
+ homeComA!.publicKey.toString('hex'),
args.jwt,
)
if (await client.openConnection(args)) {
@@ -69,6 +69,6 @@ export async function startCommunityAuthentication(
logger.error(`Error:`, err)
}
} else {
- logger.debug(`foreignCom.communityUuid is not a valid v4Uuid or still a temporary onetimecode`)
+ logger.debug(`foreignComB.communityUuid is not a valid v4Uuid or still a temporary onetimecode`)
}
}
diff --git a/core/src/auth/jwt/payloadtypes/AuthenticationResponseJwtPayloadType.ts b/core/src/auth/jwt/payloadtypes/AuthenticationResponseJwtPayloadType.ts
new file mode 100644
index 000000000..5a3342b39
--- /dev/null
+++ b/core/src/auth/jwt/payloadtypes/AuthenticationResponseJwtPayloadType.ts
@@ -0,0 +1,17 @@
+import { JwtPayloadType } from './JwtPayloadType'
+
+export class AuthenticationResponseJwtPayloadType extends JwtPayloadType {
+ static AUTHENTICATION_RESPONSE_TYPE = 'authenticationResponse'
+
+ uuid: string
+
+ constructor(
+ uuid: string,
+ ) {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
+ super()
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ this.tokentype = AuthenticationResponseJwtPayloadType.AUTHENTICATION_RESPONSE_TYPE
+ this.uuid = uuid
+ }
+}
diff --git a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts
index cdf3dc9de..cc3ed8c50 100644
--- a/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts
+++ b/federation/src/graphql/api/1_0/resolver/AuthenticationResolver.ts
@@ -1,5 +1,4 @@
import { CONFIG } from '@/config'
-import { LogError } from '@/server/LogError'
import {
CommunityLoggingView,
Community as DbCommunity,
@@ -16,6 +15,8 @@ import { OpenConnectionJwtPayloadType } from 'core/src/auth/jwt/payloadtypes/Ope
import { interpretEncryptedTransferArgs } from 'core/src/graphql/logic/interpretEncryptedTransferArgs'
import { OpenConnectionCallbackJwtPayloadType } from 'core/src/auth/jwt/payloadtypes/OpenConnectionCallbackJwtPayloadType'
import { AuthenticationJwtPayloadType } from 'core/src/auth/jwt/payloadtypes/AuthenticationJwtPayloadType'
+import { AuthenticationResponseJwtPayloadType } from 'core/src/auth/jwt/payloadtypes/AuthenticationResponseJwtPayloadType'
+import { encryptAndSign } from 'core/src/auth/jwt/JWT'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.api.1_0.resolver.AuthenticationResolver`)
@@ -29,17 +30,25 @@ export class AuthenticationResolver {
logger.debug(`openConnection() via apiVersion=1_0:`, args)
const openConnectionJwtPayload = await interpretEncryptedTransferArgs(args) as OpenConnectionJwtPayloadType
if (!openConnectionJwtPayload) {
- throw new LogError(`invalid OpenConnection payload of requesting community with publicKey`, args.publicKey)
+ const errmsg = `invalid OpenConnection payload of requesting community with publicKey` + args.publicKey
+ logger.error(errmsg)
+ throw new Error(errmsg)
}
if (openConnectionJwtPayload.tokentype !== OpenConnectionJwtPayloadType.OPEN_CONNECTION_TYPE) {
- throw new LogError(`invalid tokentype of community with publicKey`, args.publicKey)
+ const errmsg = `invalid tokentype of community with publicKey` + args.publicKey
+ logger.error(errmsg)
+ throw new Error(errmsg)
}
if (!openConnectionJwtPayload.url) {
- throw new LogError(`invalid url of community with publicKey`, args.publicKey)
+ const errmsg = `invalid url of community with publicKey` + args.publicKey
+ logger.error(errmsg)
+ throw new Error(errmsg)
}
const fedComA = await DbFedCommunity.findOneByOrFail({ publicKey: Buffer.from(args.publicKey, 'hex') })
if (!openConnectionJwtPayload.url.startsWith(fedComA.endPoint)) {
- throw new LogError(`invalid url of community with publicKey`, args.publicKey)
+ const errmsg = `invalid url of community with publicKey` + args.publicKey
+ logger.error(errmsg)
+ throw new Error(errmsg)
}
// biome-ignore lint/complexity/noVoid: no await to respond immediately and invoke callback-request asynchronously
@@ -53,18 +62,22 @@ export class AuthenticationResolver {
args: EncryptedTransferArgs,
): Promise {
logger.debug(`openConnectionCallback() via apiVersion=1_0 ...`, args)
+ // decrypt args.url with homeCom.privateJwtKey and verify signing with callbackFedCom.publicKey
const openConnectionCallbackJwtPayload = await interpretEncryptedTransferArgs(args) as OpenConnectionCallbackJwtPayloadType
if (!openConnectionCallbackJwtPayload) {
- throw new LogError(`invalid OpenConnectionCallback payload of requesting community with publicKey`, args.publicKey)
+ const errmsg = `invalid OpenConnectionCallback payload of requesting community with publicKey` + args.publicKey
+ logger.error(errmsg)
+ throw new Error(errmsg)
}
- // TODO decrypt args.url with homeCom.privateJwtKey and verify signing with callbackFedCom.publicKey
const endPoint = openConnectionCallbackJwtPayload.url.slice(0, openConnectionCallbackJwtPayload.url.lastIndexOf('/') + 1)
const apiVersion = openConnectionCallbackJwtPayload.url.slice(openConnectionCallbackJwtPayload.url.lastIndexOf('/') + 1, openConnectionCallbackJwtPayload.url.length)
logger.debug(`search fedComB per:`, endPoint, apiVersion)
const fedComB = await DbFedCommunity.findOneBy({ endPoint, apiVersion })
if (!fedComB) {
- throw new LogError(`unknown callback community with url`, openConnectionCallbackJwtPayload.url)
+ const errmsg = `unknown callback community with url` + openConnectionCallbackJwtPayload.url
+ logger.error(errmsg)
+ throw new Error(errmsg)
}
logger.debug(
`found fedComB and start authentication:`,
@@ -83,7 +96,9 @@ export class AuthenticationResolver {
logger.debug(`authenticate() via apiVersion=1_0 ...`, args)
const authArgs = await interpretEncryptedTransferArgs(args) as AuthenticationJwtPayloadType
if (!authArgs) {
- throw new LogError(`invalid authentication payload of requesting community with publicKey`, args.publicKey)
+ const errmsg = `invalid authentication payload of requesting community with publicKey` + args.publicKey
+ logger.error(errmsg)
+ throw new Error(errmsg)
}
const authCom = await DbCommunity.findOneByOrFail({ communityUuid: authArgs.oneTimeCode })
logger.debug('found authCom:', new CommunityLoggingView(authCom))
@@ -92,9 +107,11 @@ export class AuthenticationResolver {
authCom.authenticatedAt = new Date()
await DbCommunity.save(authCom)
logger.debug('store authCom.uuid successfully:', new CommunityLoggingView(authCom))
- const homeCom = await getHomeCommunity()
- if (homeCom?.communityUuid) {
- return homeCom.communityUuid
+ const homeComB = await getHomeCommunity()
+ if (homeComB?.communityUuid) {
+ const responseArgs = new AuthenticationResponseJwtPayloadType(homeComB.communityUuid)
+ const responseJwt = await encryptAndSign(responseArgs, homeComB.privateJwtKey!, authCom.publicJwtKey!)
+ return responseJwt
}
}
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 2167655af..39645db33 100644
--- a/federation/src/graphql/api/1_0/util/authenticateCommunity.ts
+++ b/federation/src/graphql/api/1_0/util/authenticateCommunity.ts
@@ -6,6 +6,7 @@ import {
getHomeCommunity,
} from 'database'
import { getLogger } from 'log4js'
+import { validate as validateUUID, version as versionUUID } from 'uuid'
import { EncryptedTransferArgs } from 'core/src/graphql/model/EncryptedTransferArgs'
import { AuthenticationClientFactory } from '@/client/AuthenticationClientFactory'
@@ -13,9 +14,10 @@ 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 { encryptAndSign } from 'core/src/auth/jwt/JWT'
+import { encryptAndSign, verifyAndDecrypt } from 'core/src/auth/jwt/JWT'
import { OpenConnectionCallbackJwtPayloadType } from 'core/src/auth/jwt/payloadtypes/OpenConnectionCallbackJwtPayloadType'
import { AuthenticationJwtPayloadType } from 'core/src/auth/jwt/payloadtypes/AuthenticationJwtPayloadType'
+import { AuthenticationResponseJwtPayloadType } from 'core/src/auth/jwt/payloadtypes/AuthenticationResponseJwtPayloadType'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.api.1_0.util.authenticateCommunity`)
@@ -27,8 +29,8 @@ export async function startOpenConnectionCallback(
publicKey,
})
try {
- const homeCom = await getHomeCommunity()
- const homeFedCom = await DbFedCommunity.findOneByOrFail({
+ const homeComB = await getHomeCommunity()
+ const homeFedComB = await DbFedCommunity.findOneByOrFail({
foreign: false,
apiVersion: api,
})
@@ -50,14 +52,14 @@ export async function startOpenConnectionCallback(
const client = AuthenticationClientFactory.getInstance(fedComA)
if (client instanceof V1_0_AuthenticationClient) {
- const url = homeFedCom.endPoint.endsWith('/')
- ? homeFedCom.endPoint + homeFedCom.apiVersion
- : homeFedCom.endPoint + '/' + homeFedCom.apiVersion
+ const url = homeFedComB.endPoint.endsWith('/')
+ ? homeFedComB.endPoint + homeFedComB.apiVersion
+ : homeFedComB.endPoint + '/' + homeFedComB.apiVersion
const callbackArgs = new OpenConnectionCallbackJwtPayloadType(oneTimeCode, url)
logger.debug(`Authentication: start openConnectionCallback with args:`, callbackArgs)
// encrypt callbackArgs with requestedCom.publicJwtKey and sign it with homeCom.privateJwtKey
- const jwt = await encryptAndSign(callbackArgs, homeCom!.privateJwtKey!, comA.publicJwtKey!)
+ const jwt = await encryptAndSign(callbackArgs, homeComB!.privateJwtKey!, comA.publicJwtKey!)
const args = new EncryptedTransferArgs()
args.publicKey = comA.publicKey.toString('hex')
args.jwt = jwt
@@ -81,7 +83,7 @@ export async function startAuthentication(
fedComB: new FederatedCommunityLoggingView(fedComB),
})
try {
- const homeCom = await getHomeCommunity()
+ const homeComA = await getHomeCommunity()
const comB = await DbCommunity.findOneByOrFail({
foreign: true,
publicKey: fedComB.publicKey,
@@ -93,30 +95,36 @@ export async function startAuthentication(
const client = AuthenticationClientFactory.getInstance(fedComB)
if (client instanceof V1_0_AuthenticationClient) {
- const authenticationArgs = new AuthenticationJwtPayloadType(oneTimeCode, homeCom!.communityUuid!)
+ const authenticationArgs = new AuthenticationJwtPayloadType(oneTimeCode, homeComA!.communityUuid!)
// encrypt authenticationArgs.uuid with fedComB.publicJwtKey and sign it with homeCom.privateJwtKey
- const jwt = await encryptAndSign(authenticationArgs, homeCom!.privateJwtKey!, comB.publicJwtKey!)
+ const jwt = await encryptAndSign(authenticationArgs, homeComA!.privateJwtKey!, comB.publicJwtKey!)
const args = new EncryptedTransferArgs()
args.publicKey = comB.publicKey.toString('hex')
args.jwt = jwt
logger.debug(`invoke authenticate() with:`, args)
- const fedComUuid = await client.authenticate(args)
- logger.debug(`response of authenticate():`, fedComUuid)
- if (fedComUuid !== null) {
+ const responseJwt = await client.authenticate(args)
+ logger.debug(`response of authenticate():`, responseJwt)
+ if (responseJwt !== null) {
+ const payload = await verifyAndDecrypt(responseJwt, homeComA!.privateJwtKey!, comB.publicJwtKey!) as AuthenticationResponseJwtPayloadType
logger.debug(
- `received communityUUid for callbackFedCom:`,
- fedComUuid,
+ `received payload from authenticate ComB:`,
+ payload,
new FederatedCommunityLoggingView(fedComB),
)
- const callbackCom = await DbCommunity.findOneByOrFail({
- foreign: true,
- publicKey: fedComB.publicKey,
- })
- // TODO decrypt fedComUuid with callbackFedCom.publicKey
- callbackCom.communityUuid = fedComUuid
- callbackCom.authenticatedAt = new Date()
- await DbCommunity.save(callbackCom)
- logger.debug('Community Authentication successful:', new CommunityLoggingView(callbackCom))
+ if (payload.tokentype !== AuthenticationResponseJwtPayloadType.AUTHENTICATION_RESPONSE_TYPE) {
+ const errmsg = `Invalid tokentype in authenticate-response of community with publicKey` + comB.publicKey
+ logger.error(errmsg)
+ throw new Error(errmsg)
+ }
+ if (!payload.uuid || !validateUUID(payload.uuid) || versionUUID(payload.uuid) !== 4) {
+ const errmsg = `Invalid uuid in authenticate-response of community with publicKey` + comB.publicKey
+ logger.error(errmsg)
+ throw new Error(errmsg)
+ }
+ comB.communityUuid = payload.uuid
+ comB.authenticatedAt = new Date()
+ await DbCommunity.save(comB)
+ logger.debug('Community Authentication successful:', new CommunityLoggingView(comB))
} else {
logger.error('Community Authentication failed:', authenticationArgs)
}