full code of encrypted authentication process, tests open

This commit is contained in:
clauspeterhuebner 2025-07-04 18:22:02 +02:00
parent ee16e540cf
commit 4ca94b189c
4 changed files with 94 additions and 52 deletions

View File

@ -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<void> {
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`)
}
}

View File

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

View File

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

View File

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