From 9d9e6a14be7074da4382df6c890bcd5cf0258631 Mon Sep 17 00:00:00 2001
From: clauspeterhuebner
Date: Thu, 28 Aug 2025 19:02:02 +0200
Subject: [PATCH] transfer redeemJwt in an envelopJwt with pubKey
---
.../resolver/TransactionLinkResolver.ts | 177 +++++++++---------
.../src/graphql/resolver/util/communities.ts | 5 +
shared/src/index.ts | 1 +
.../payloadtypes/SignedTransferPayloadType.ts | 21 +++
4 files changed, 117 insertions(+), 87 deletions(-)
create mode 100644 shared/src/jwt/payloadtypes/SignedTransferPayloadType.ts
diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts
index 227f6b4ba..e4d042914 100644
--- a/backend/src/graphql/resolver/TransactionLinkResolver.ts
+++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts
@@ -6,13 +6,13 @@ import { TransactionLinkFilters } from '@arg/TransactionLinkFilters'
import { ContributionCycleType } from '@enum/ContributionCycleType'
import { ContributionStatus } from '@enum/ContributionStatus'
import { ContributionType } from '@enum/ContributionType'
+import { Community } from '@model/Community'
import { ContributionLink } from '@model/ContributionLink'
import { RedeemJwtLink } from '@model/RedeemJwtLink'
-import { Community } from '@model/Community'
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
import { User } from '@model/User'
import { QueryLinkResult } from '@union/QueryLinkResult'
-import { Decay, TransactionTypeId } from 'core'
+import { Decay, interpretEncryptedTransferArgs, TransactionTypeId } from 'core'
import {
AppDatabase, Community as DbCommunity, Contribution as DbContribution,
ContributionLink as DbContributionLink, FederatedCommunity as DbFederatedCommunity, Transaction as DbTransaction,
@@ -48,11 +48,14 @@ import { randombytes_random } from 'sodium-native'
import { executeTransaction } from './TransactionResolver'
import {
getAuthenticatedCommunities,
+ getCommunityByIdentifier,
+ getCommunityByPublicKey,
getCommunityByUuid,
} from './util/communities'
import { getUserCreation, validateContribution } from './util/creations'
import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector'
import { transactionLinkList } from './util/transactionLinkList'
+import { SignedTransferPayloadType } from 'shared'
const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.TransactionLinkResolver.${method}`)
@@ -454,7 +457,31 @@ export class TransactionLinkResolver {
if (!redeemJwt) {
throw new LogError('Redeem JWT was not created successfully')
}
- return redeemJwt
+ // prepare the args for the client invocation
+ const args = new EncryptedTransferArgs()
+ args.publicKey = senderCom.publicKey.toString('hex')
+ args.jwt = redeemJwt
+ args.handshakeID = randombytes_random().toString()
+ if(methodLogger.isDebugEnabled()) {
+ methodLogger.debug('successfully created RedeemJWT-Response with args:', args)
+ }
+ const signedTransferPayload = new SignedTransferPayloadType(
+ args.publicKey,
+ args.jwt,
+ args.handshakeID,
+ )
+ if(methodLogger.isDebugEnabled()) {
+ methodLogger.debug('successfully created RedeemJWT-Response with signedTransferPayload:', signedTransferPayload)
+ }
+ const signedTransferJwt = await encode(signedTransferPayload, senderCom.privateJwtKey!)
+ if (!signedTransferJwt) {
+ throw new LogError('SignedTransfer JWT was not created successfully')
+ }
+ if(methodLogger.isDebugEnabled()) {
+ methodLogger.debug('successfully created RedeemJWT-Response with signedTransferJwt:', signedTransferJwt)
+ }
+
+ return signedTransferJwt
} catch (e) {
const errmsg = `Error on creating Redeem JWT: error=${e}`
methodLogger.error(errmsg)
@@ -592,98 +619,75 @@ export class TransactionLinkResolver {
}
async queryRedeemJwtLink(code: string, logger: Logger): Promise {
- logger.debug('TransactionLinkResolver.queryRedeemJwtLink... redeem jwt-token found')
- // decode token first to get the senderCommunityUuid as input for verify token
- const decodedPayload = decode(code)
- logger.debug('TransactionLinkResolver.queryRedeemJwtLink... decodedPayload=', decodedPayload)
- if (
- decodedPayload != null &&
- decodedPayload.tokentype === RedeemJwtPayloadType.REDEEM_ACTIVATION_TYPE
- ) {
- const redeemJwtPayload = new RedeemJwtPayloadType(
- decodedPayload.sendercommunityuuid as string,
- decodedPayload.sendergradidoid as string,
- decodedPayload.sendername as string,
- decodedPayload.redeemcode as string,
- decodedPayload.amount as string,
- decodedPayload.memo as string,
- decodedPayload.validuntil as string,
- )
- logger.debug(
- 'TransactionLinkResolver.queryRedeemJwtLink... redeemJwtPayload=',
- redeemJwtPayload,
- )
- const senderCom = await getCommunityByUuid(redeemJwtPayload.sendercommunityuuid)
+ logger.debug('queryRedeemJwtLink... redeem jwt-token found')
+
+ // decode token first to get the EncryptedTransferArgs with the senderCommunity.publicKey as input to verify token
+ const decodedPayload = decode(code) as SignedTransferPayloadType
+ logger.debug('queryRedeemJwtLink... decodedPayload=', decodedPayload)
+ if(decodedPayload !== null && decodedPayload.tokentype === SignedTransferPayloadType.SIGNED_TRANSFER_TYPE) {
+ const signedTransferPayload = new SignedTransferPayloadType(
+ decodedPayload.publicKey,
+ decodedPayload.jwt,
+ decodedPayload.handshakeID)
+ logger.debug('queryRedeemJwtLink... signedTransferPayload=', signedTransferPayload)
+ const senderCom = await getCommunityByPublicKey(Buffer.from(signedTransferPayload.publicKey))
if (!senderCom) {
- throw new LogError('Sender community not found:', redeemJwtPayload.sendercommunityuuid)
+ const errmsg = `Sender community not found with publicKey=${signedTransferPayload.publicKey}`
+ logger.error(errmsg)
+ throw new Error(errmsg)
}
- logger.debug('TransactionLinkResolver.queryRedeemJwtLink... senderCom=', senderCom)
- if (!senderCom.communityUuid) {
- throw new LogError('Sender community UUID is not set')
- }
- // now with the sender community UUID the jwt token can be verified
- const verifiedJwtPayload = await verify('handshakeID', code, senderCom.communityUuid)
- logger.debug(
- 'TransactionLinkResolver.queryRedeemJwtLink... nach verify verifiedJwtPayload=',
- verifiedJwtPayload,
- )
+ logger.debug('queryRedeemJwtLink... senderCom=', senderCom)
+ const verifiedJwtPayload = await verify(signedTransferPayload.handshakeID, signedTransferPayload.jwt, signedTransferPayload.publicKey) as SignedTransferPayloadType
+ logger.debug('queryRedeemJwtLink... verifiedJwtPayload=', verifiedJwtPayload)
let verifiedRedeemJwtPayload: RedeemJwtPayloadType | null = null
- if (verifiedJwtPayload !== null) {
- if (verifiedJwtPayload.exp !== undefined) {
- const expDate = new Date(verifiedJwtPayload.exp * 1000)
+ if (verifiedJwtPayload === null) {
+ const errmsg = `Error on verify transferred redeem token with publicKey=${signedTransferPayload.publicKey}`
+ logger.error(errmsg)
+ throw new Error(errmsg)
+ } else {
+ const encryptedTransferArgs = new EncryptedTransferArgs()
+ encryptedTransferArgs.publicKey = verifiedJwtPayload.publicKey
+ encryptedTransferArgs.jwt = verifiedJwtPayload.jwt
+ encryptedTransferArgs.handshakeID = verifiedJwtPayload.handshakeID
+
+ verifiedRedeemJwtPayload = await interpretEncryptedTransferArgs(encryptedTransferArgs) as RedeemJwtPayloadType
+ if(logger.isDebugEnabled()) {
+ logger.debug(`queryRedeemJwtLink() ...`, verifiedRedeemJwtPayload)
+ }
+ if (!verifiedRedeemJwtPayload) {
+ const errmsg = `invalid authentication payload of requesting community with publicKey` + verifiedJwtPayload.publicKey
+ logger.error(errmsg)
+ throw new Error(errmsg)
+ }
+ if (verifiedRedeemJwtPayload.tokentype === RedeemJwtPayloadType.REDEEM_ACTIVATION_TYPE) {
+ const errmsg = `Wrong tokentype in redeem JWT: type=` + verifiedRedeemJwtPayload.tokentype + ' vs expected ' + RedeemJwtPayloadType.REDEEM_ACTIVATION_TYPE
+ logger.error(errmsg)
+ throw new Error(errmsg)
+ }
+ if(senderCom?.communityUuid !== verifiedRedeemJwtPayload.sendercommunityuuid) {
+ const errmsg = `Mismatch of sender community UUID in redeem JWT against transfer JWT: uuid=` + senderCom.communityUuid + ' vs ' + verifiedRedeemJwtPayload.sendercommunityuuid
+ logger.error(errmsg)
+ throw new Error(errmsg)
+ }
+ if (verifiedRedeemJwtPayload.exp !== undefined) {
+ const expDate = new Date(verifiedRedeemJwtPayload.exp * 1000)
logger.debug(
- 'TransactionLinkResolver.queryRedeemJwtLink... expDate, exp =',
+ 'queryRedeemJwtLink... expDate, exp =',
expDate,
- verifiedJwtPayload.exp,
+ verifiedRedeemJwtPayload.exp,
)
if (expDate < new Date()) {
- throw new LogError('Redeem JWT-Token expired! jwtPayload.exp=', expDate)
+ const errmsg = `Redeem JWT-Token expired! jwtPayload.exp=${expDate}`
+ logger.error(errmsg)
+ throw new Error(errmsg)
}
}
- if (verifiedJwtPayload.tokentype === RedeemJwtPayloadType.REDEEM_ACTIVATION_TYPE) {
- logger.debug(
- 'TransactionLinkResolver.queryRedeemJwtLink... verifiedJwtPayload.tokentype=',
- verifiedJwtPayload.tokentype,
- )
- verifiedRedeemJwtPayload = new RedeemJwtPayloadType(
- verifiedJwtPayload.sendercommunityuuid as string,
- verifiedJwtPayload.sendergradidoid as string,
- verifiedJwtPayload.sendername as string,
- verifiedJwtPayload.redeemcode as string,
- verifiedJwtPayload.amount as string,
- verifiedJwtPayload.memo as string,
- verifiedJwtPayload.validuntil as string,
- )
- logger.debug(
- 'TransactionLinkResolver.queryRedeemJwtLink... nach verify verifiedRedeemJwtPayload=',
- verifiedRedeemJwtPayload,
- )
- }
- }
- if (verifiedRedeemJwtPayload === null) {
- logger.debug(
- 'TransactionLinkResolver.queryRedeemJwtLink... verifiedRedeemJwtPayload===null',
- )
- verifiedRedeemJwtPayload = new RedeemJwtPayloadType(
- decodedPayload.sendercommunityuuid as string,
- decodedPayload.sendergradidoid as string,
- decodedPayload.sendername as string,
- decodedPayload.redeemcode as string,
- decodedPayload.amount as string,
- decodedPayload.memo as string,
- decodedPayload.validuntil as string,
- )
- } else {
- // TODO: as long as the verification fails, fallback to simply decoded payload
- verifiedRedeemJwtPayload = redeemJwtPayload
- logger.debug(
- 'TransactionLinkResolver.queryRedeemJwtLink... fallback to decode verifiedRedeemJwtPayload=',
- verifiedRedeemJwtPayload,
- )
}
const homeCommunity = await getHomeCommunity()
if (!homeCommunity) {
- throw new LogError('Home community not found')
+ const errmsg = `Home community not found`
+ logger.error(errmsg)
+ throw new Error(errmsg)
}
const recipientCommunity = new Community(homeCommunity)
const senderCommunity = new Community(senderCom)
@@ -699,10 +703,9 @@ export class TransactionLinkResolver {
logger.debug('TransactionLinkResolver.queryRedeemJwtLink... redeemJwtLink=', redeemJwtLink)
return redeemJwtLink
} else {
- throw new LogError(
- 'Redeem with wrong type of JWT-Token or expired! decodedPayload=',
- decodedPayload,
- )
+ const errmsg = `transfer of redeem JWT with wrong envelope! code=${code}`
+ logger.error(errmsg)
+ throw new Error(errmsg)
}
}
}
diff --git a/backend/src/graphql/resolver/util/communities.ts b/backend/src/graphql/resolver/util/communities.ts
index ee7a0f96d..d1288a1ea 100644
--- a/backend/src/graphql/resolver/util/communities.ts
+++ b/backend/src/graphql/resolver/util/communities.ts
@@ -78,6 +78,11 @@ export async function getCommunityByUuid(communityUuid: string): Promise {
+ return await DbCommunity.findOne({
+ where: [{ publicKey: publicKey }],
+ })
+}
export async function getAuthenticatedCommunities(): Promise {
const dbCommunities: DbCommunity[] = await DbCommunity.find({
diff --git a/shared/src/index.ts b/shared/src/index.ts
index afbe82bce..dd7965fdc 100644
--- a/shared/src/index.ts
+++ b/shared/src/index.ts
@@ -12,4 +12,5 @@ export * from './jwt/payloadtypes/OpenConnectionCallbackJwtPayloadType'
export * from './jwt/payloadtypes/RedeemJwtPayloadType'
export * from './jwt/payloadtypes/SendCoinsJwtPayloadType'
export * from './jwt/payloadtypes/SendCoinsResponseJwtPayloadType'
+export * from './jwt/payloadtypes/SignedTransferPayloadType'
diff --git a/shared/src/jwt/payloadtypes/SignedTransferPayloadType.ts b/shared/src/jwt/payloadtypes/SignedTransferPayloadType.ts
new file mode 100644
index 000000000..26c4235f3
--- /dev/null
+++ b/shared/src/jwt/payloadtypes/SignedTransferPayloadType.ts
@@ -0,0 +1,21 @@
+import { JwtPayloadType } from './JwtPayloadType'
+
+export class SignedTransferPayloadType extends JwtPayloadType {
+ static SIGNED_TRANSFER_TYPE = 'signed-transfer'
+
+ publicKey: string
+ jwt: string
+
+ constructor(
+ publicKey: string,
+ jwt: string,
+ handshakeID: string,
+ ) {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
+ super(handshakeID)
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ this.tokentype = SignedTransferPayloadType.SIGNED_TRANSFER_TYPE
+ this.publicKey = publicKey
+ this.jwt = jwt
+ }
+}