From a6e2efef19ea1357f9dad063b22a3b8218e20ded Mon Sep 17 00:00:00 2001
From: clauspeterhuebner
Date: Tue, 15 Apr 2025 22:06:43 +0200
Subject: [PATCH] sign jwt with senderCommunityUuid, search senderCom per
decoded payload and verify jwt with senderComUUid selected from db ensured
against referrer
---
backend/src/auth/jwt/JWT.ts | 28 +++----
.../resolver/TransactionLinkResolver.ts | 84 +++++++++++++------
.../RedeemCommunitySelection.vue | 2 +
.../RedeemSelectCommunity.vue | 2 +
frontend/src/graphql/queries.js | 4 +-
frontend/src/pages/TransactionLink.vue | 6 +-
frontend/src/routes/routes.js | 13 +++
7 files changed, 96 insertions(+), 43 deletions(-)
diff --git a/backend/src/auth/jwt/JWT.ts b/backend/src/auth/jwt/JWT.ts
index d4f954987..1f42cb198 100644
--- a/backend/src/auth/jwt/JWT.ts
+++ b/backend/src/auth/jwt/JWT.ts
@@ -1,20 +1,15 @@
import { createPrivateKey, sign } from 'node:crypto'
-import { SignJWT, jwtVerify } from 'jose'
+import { SignJWT, jwtVerify, decodeJwt, JWTPayload } from 'jose'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { JwtPayloadType } from './payloadtypes/JwtPayloadType'
-export const verify = async (token: string, signkey: Buffer): Promise => {
+export const verify = async (token: string, signkey: string): Promise => {
if (!token) throw new LogError('401 Unauthorized')
- logger.info(
- 'JWT.verify... token, signkey, signkey.toString(hex)',
- token,
- signkey,
- signkey.toString('hex'),
- )
+ logger.info('JWT.verify... token, signkey=', token, signkey)
try {
/*
@@ -28,7 +23,8 @@ export const verify = async (token: string, signkey: Buffer): Promise => {
+export const encode = async (payload: JwtPayloadType, signkey: string): Promise => {
logger.info('JWT.encode... payload=', payload)
logger.info('JWT.encode... signkey=', signkey)
- logger.info('JWT.encode... signkey length=', signkey.length)
- logger.info('JWT.encode... signkey.toString(hex)=', signkey.toString('hex'))
try {
+ const secret = new TextEncoder().encode(signkey)
const token = await new SignJWT({ payload, 'urn:gradido:claim': true })
.setProtectedHeader({
alg: 'HS256',
@@ -54,7 +49,7 @@ export const encode = async (payload: JwtPayloadType, signkey: Buffer): Promise<
.setIssuer('urn:gradido:issuer')
.setAudience('urn:gradido:audience')
.setExpirationTime(payload.expiration)
- .sign(signkey)
+ .sign(secret)
return token
} catch (e) {
logger.error('Failed to sign JWT:', e)
@@ -62,7 +57,12 @@ export const encode = async (payload: JwtPayloadType, signkey: Buffer): Promise<
}
}
-export const verifyJwtType = async (token: string, signkey: Buffer): Promise => {
+export const verifyJwtType = async (token: string, signkey: string): Promise => {
const payload = await verify(token, signkey)
return payload ? payload.tokentype : 'unknown token type'
}
+
+export const decode = (token: string): JwtPayloadType => {
+ const payload = decodeJwt(token)
+ return payload as unknown as JwtPayloadType
+}
diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts
index 429c7ba9b..a228df32a 100644
--- a/backend/src/graphql/resolver/TransactionLinkResolver.ts
+++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts
@@ -23,7 +23,7 @@ import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
import { User } from '@model/User'
import { QueryLinkResult } from '@union/QueryLinkResult'
-import { verify, encode } from '@/auth/jwt/JWT'
+import { verify, encode, decode } from '@/auth/jwt/JWT'
import { DisbursementJwtPayloadType } from '@/auth/jwt/payloadtypes/DisbursementJwtPayloadType'
import { RIGHTS } from '@/auth/RIGHTS'
import {
@@ -42,7 +42,11 @@ import { fullName } from '@/util/utilities'
import { calculateBalance } from '@/util/validate'
import { executeTransaction } from './TransactionResolver'
-import { getAuthenticatedCommunities, getHomeCommunity } from './util/communities'
+import {
+ getAuthenticatedCommunities,
+ getCommunityByUuid,
+ getHomeCommunity,
+} from './util/communities'
import { getUserCreation, validateContribution } from './util/creations'
import { getLastTransaction } from './util/getLastTransaction'
import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector'
@@ -139,7 +143,10 @@ export class TransactionLinkResolver {
@Authorized([RIGHTS.QUERY_TRANSACTION_LINK])
@Query(() => QueryLinkResult)
- async queryTransactionLink(@Arg('code') code: string): Promise {
+ async queryTransactionLink(
+ @Arg('code') code: string,
+ @Arg('referrer') referrer: string,
+ ): Promise {
logger.debug('TransactionLinkResolver.queryTransactionLink... code=', code)
const transactionLink = new TransactionLink()
if (code.match(/^CL-/)) {
@@ -178,29 +185,54 @@ export class TransactionLinkResolver {
} else {
// disbursement jwt-token
logger.debug('TransactionLinkResolver.queryTransactionLink... disbursement jwt-token found')
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment
- const homeCom = await getHomeCommunity()
- const jwtPayload = await verify(code, homeCom.publicKey)
- logger.debug('TransactionLinkResolver.queryTransactionLink... jwtPayload=', jwtPayload)
- if (jwtPayload !== null && jwtPayload instanceof DisbursementJwtPayloadType) {
- const disburseJwtPayload: DisbursementJwtPayloadType = jwtPayload
+ // first check sender of payload against referrer
+ const payload = decode(code)
+ if (!payload) {
+ throw new LogError('Invalid JWT payload', payload)
+ }
+ if (payload instanceof DisbursementJwtPayloadType) {
+ const disburseJwtPayload: DisbursementJwtPayloadType = payload
logger.debug(
'TransactionLinkResolver.queryTransactionLink... disburseJwtPayload=',
- jwtPayload,
+ disburseJwtPayload,
)
- transactionLink.communityName = homeCom.name !== null ? homeCom.name : 'unknown'
- // transactionLink.user = new User()
- transactionLink.user.alias = disburseJwtPayload.sendername
- transactionLink.amount = new Decimal(disburseJwtPayload.amount)
- transactionLink.memo = disburseJwtPayload.memo
- transactionLink.code = disburseJwtPayload.redeemcode
- logger.debug(
- 'TransactionLinkResolver.queryTransactionLink... transactionLink=',
- transactionLink,
- )
- return transactionLink
+ const senderCom = await getCommunityByUuid(disburseJwtPayload.sendercommunityuuid)
+ if (!senderCom) {
+ throw new LogError(
+ 'Sender community not found:',
+ disburseJwtPayload.sendercommunityuuid,
+ )
+ }
+ const senderUrl = senderCom.url.replace(/\/api\/?$/, '')
+ if (!senderUrl.startsWith(referrer)) {
+ throw new LogError('Sender community does not match referrer', senderCom.name, referrer)
+ }
+ 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 jwtPayload = await verify(code, senderCom.communityUuid)
+ logger.debug('TransactionLinkResolver.queryTransactionLink... jwtPayload=', jwtPayload)
+ if (jwtPayload !== null && jwtPayload instanceof DisbursementJwtPayloadType) {
+ const disburseJwtPayload: DisbursementJwtPayloadType = jwtPayload
+ logger.debug(
+ 'TransactionLinkResolver.queryTransactionLink... disburseJwtPayload=',
+ disburseJwtPayload,
+ )
+ transactionLink.communityName = senderCom.name !== null ? senderCom.name : 'unknown'
+ transactionLink.user = new User(null)
+ transactionLink.user.alias = disburseJwtPayload.sendername
+ transactionLink.amount = new Decimal(disburseJwtPayload.amount)
+ transactionLink.memo = disburseJwtPayload.memo
+ transactionLink.code = disburseJwtPayload.redeemcode
+ logger.debug(
+ 'TransactionLinkResolver.queryTransactionLink... transactionLink=',
+ transactionLink,
+ )
+ return transactionLink
+ }
} else {
- throw new LogError('Redeem with wrong type of JWT-Token! jwtType=', jwtPayload)
+ throw new LogError('Redeem with wrong type of JWT-Token! payload=', payload)
}
}
}
@@ -446,12 +478,12 @@ export class TransactionLinkResolver {
amount,
memo,
)
- // encode/sign the jwt with the private key of the sender/home community
+ // TODO:encode/sign the jwt normally with the private key of the sender/home community, but interims with uuid
const homeCom = await getHomeCommunity()
- if (!homeCom.privateKey) {
- throw new LogError('Home community private key is not set')
+ if (!homeCom.communityUuid) {
+ throw new LogError('Home community UUID is not set')
}
- const redeemJwt = await encode(disbursementJwtPayloadType, homeCom.privateKey)
+ const redeemJwt = await encode(disbursementJwtPayloadType, homeCom.communityUuid)
// TODO: encrypt the payload with the public key of the target community
return redeemJwt
}
diff --git a/frontend/src/components/LinkInformations/RedeemCommunitySelection.vue b/frontend/src/components/LinkInformations/RedeemCommunitySelection.vue
index 5ae6b69fe..dcc83b3cd 100644
--- a/frontend/src/components/LinkInformations/RedeemCommunitySelection.vue
+++ b/frontend/src/components/LinkInformations/RedeemCommunitySelection.vue
@@ -2,6 +2,7 @@