mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 01:46:07 +00:00
sign jwt with senderCommunityUuid, search senderCom per decoded payload
and verify jwt with senderComUUid selected from db ensured against referrer
This commit is contained in:
parent
41032e5fc6
commit
a6e2efef19
@ -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<JwtPayloadType | null> => {
|
||||
export const verify = async (token: string, signkey: string): Promise<JwtPayloadType | null> => {
|
||||
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<JwtPayload
|
||||
logger.info('JWT.verify... keyObject.asymmetricKeyType=', keyObject.asymmetricKeyType)
|
||||
logger.info('JWT.verify... keyObject.asymmetricKeySize=', keyObject.asymmetricKeySize)
|
||||
*/
|
||||
const { payload } = await jwtVerify(token, signkey, {
|
||||
const secret = new TextEncoder().encode(signkey)
|
||||
const { payload } = await jwtVerify(token, secret, {
|
||||
issuer: 'urn:gradido:issuer',
|
||||
audience: 'urn:gradido:audience',
|
||||
})
|
||||
@ -40,12 +36,11 @@ export const verify = async (token: string, signkey: Buffer): Promise<JwtPayload
|
||||
}
|
||||
}
|
||||
|
||||
export const encode = async (payload: JwtPayloadType, signkey: Buffer): Promise<string> => {
|
||||
export const encode = async (payload: JwtPayloadType, signkey: string): Promise<string> => {
|
||||
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<string> => {
|
||||
export const verifyJwtType = async (token: string, signkey: string): Promise<string> => {
|
||||
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
|
||||
}
|
||||
|
||||
@ -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<typeof QueryLinkResult> {
|
||||
async queryTransactionLink(
|
||||
@Arg('code') code: string,
|
||||
@Arg('referrer') referrer: string,
|
||||
): Promise<typeof QueryLinkResult> {
|
||||
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
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
<div
|
||||
:link-data="linkData"
|
||||
:redeem-code="redeemCode"
|
||||
:referrer="referrer"
|
||||
:is-contribution-link="isContributionLink"
|
||||
class="redeem-community-selection"
|
||||
>
|
||||
@ -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,
|
||||
|
||||
@ -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 },
|
||||
})
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
<redeem-select-community
|
||||
:link-data="linkData"
|
||||
:redeem-code="redeemCode"
|
||||
:referrer="referrer"
|
||||
:is-contribution-link="isContributionLink"
|
||||
/>
|
||||
</template>
|
||||
@ -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)
|
||||
})
|
||||
|
||||
|
||||
@ -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(.*)',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user