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 @@
@@ -48,6 +49,7 @@ import { useMutation } from '@vue/apollo-composable' const props = defineProps({ linkData: { type: Object, required: true }, redeemCode: { type: String, required: true }, + referrer: { type: String, required: true }, isContributionLink: { type: Boolean, default: false }, receiverCommunity: { type: Object, diff --git a/frontend/src/components/LinkInformations/RedeemSelectCommunity.vue b/frontend/src/components/LinkInformations/RedeemSelectCommunity.vue index 7910a4032..1515a6e46 100644 --- a/frontend/src/components/LinkInformations/RedeemSelectCommunity.vue +++ b/frontend/src/components/LinkInformations/RedeemSelectCommunity.vue @@ -4,6 +4,7 @@ v-model:receiver-community="receiverCommunity" :link-data="props.linkData" :redeem-code="props.redeemCode" + :referrer="props.referrer" :is-contribution-link="props.isContributionLink" /> @@ -39,6 +40,7 @@ const { login, register } = useAuthLinks() const props = defineProps({ linkData: { type: Object, required: true }, redeemCode: { type: String, required: true }, + referrer: { type: String, required: true }, isContributionLink: { type: Boolean, default: false }, }) diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index bc4755776..29294c561 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -128,8 +128,8 @@ export const checkUsername = gql` ` export const queryTransactionLink = gql` - query ($code: String!) { - queryTransactionLink(code: $code) { + query ($code: String!, $referrer: String!) { + queryTransactionLink(code: $code, referrer: $referrer) { ... on TransactionLink { id amount diff --git a/frontend/src/pages/TransactionLink.vue b/frontend/src/pages/TransactionLink.vue index b5f5c9d46..85b08210d 100644 --- a/frontend/src/pages/TransactionLink.vue +++ b/frontend/src/pages/TransactionLink.vue @@ -6,6 +6,7 @@ @@ -48,7 +49,7 @@ import { useI18n } from 'vue-i18n' const { toastError, toastSuccess } = useAppToast() const router = useRouter() -const { params } = useRoute() +const { params, meta } = useRoute() const store = useStore() const { d, t } = useI18n() @@ -73,6 +74,7 @@ const redeemedBoxText = ref('') const { result, onResult, loading, error, onError } = useQuery(queryTransactionLink, { code: params.code, + referrer: meta.referrer, }) const { @@ -86,6 +88,7 @@ const isContributionLink = computed(() => { }) const redeemCode = computed(() => params.code) +const referrer = computed(() => meta.referrer) const tokenExpiresInSeconds = computed(() => { const remainingSecs = Math.floor( @@ -170,6 +173,7 @@ const emit = defineEmits(['set-mobile-start']) onMounted(() => { console.log('TransactionLink.onMounted... params=', params) + console.log('TransactionLink.onMounted... meta=', meta) emit('set-mobile-start', false) }) diff --git a/frontend/src/routes/routes.js b/frontend/src/routes/routes.js index bef277c9f..4336bfd6b 100755 --- a/frontend/src/routes/routes.js +++ b/frontend/src/routes/routes.js @@ -1,5 +1,11 @@ import NotFound from '@/pages/NotFoundPage' +function setReferrerToMeta(to, from) { + if (Object.keys(from.query).length) { + to.meta.referrer = from.path + } +} + const routes = [ { path: '/authenticate', @@ -156,6 +162,13 @@ const routes = [ { path: '/redeem/:code', component: () => import('@/pages/TransactionLink'), + beforeEnter: (to, from) => { + setReferrerToMeta(to, from) + return true + }, + meta: { + referrer: 'unknown', + }, }, { path: '/:catchAll(.*)',