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