mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
714 lines
28 KiB
TypeScript
714 lines
28 KiB
TypeScript
import { randomBytes } from 'crypto'
|
|
|
|
import { Paginated } from '@arg/Paginated'
|
|
import { TransactionLinkArgs } from '@arg/TransactionLinkArgs'
|
|
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 { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
|
|
import { User } from '@model/User'
|
|
import { QueryLinkResult } from '@union/QueryLinkResult'
|
|
import { Decay, interpretEncryptedTransferArgs, TransactionTypeId } from 'core'
|
|
import {
|
|
AppDatabase, Community as DbCommunity, Contribution as DbContribution,
|
|
ContributionLink as DbContributionLink, FederatedCommunity as DbFederatedCommunity, Transaction as DbTransaction,
|
|
TransactionLink as DbTransactionLink,
|
|
User as DbUser,
|
|
findTransactionLinkByCode,
|
|
getHomeCommunity
|
|
} from 'database'
|
|
import { Decimal } from 'decimal.js-light'
|
|
import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql'
|
|
|
|
import { RIGHTS } from '@/auth/RIGHTS'
|
|
import {
|
|
EVENT_CONTRIBUTION_LINK_REDEEM,
|
|
EVENT_TRANSACTION_LINK_CREATE,
|
|
EVENT_TRANSACTION_LINK_DELETE,
|
|
EVENT_TRANSACTION_LINK_REDEEM,
|
|
} from '@/event/Events'
|
|
import { LogError } from '@/server/LogError'
|
|
import { Context, getClientTimezoneOffset, getUser } from '@/server/context'
|
|
import { calculateBalance } from '@/util/validate'
|
|
import { fullName } from 'core'
|
|
import { TRANSACTION_LINK_LOCK, TRANSACTIONS_LOCK } from 'database'
|
|
import { calculateDecay, decode, DisburseJwtPayloadType, encode, encryptAndSign, EncryptedJWEJwtPayloadType, RedeemJwtPayloadType, verify } from 'shared'
|
|
|
|
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
|
import { DisbursementClient as V1_0_DisbursementClient } from '@/federation/client/1_0/DisbursementClient'
|
|
import { DisbursementClientFactory } from '@/federation/client/DisbursementClientFactory'
|
|
import { EncryptedTransferArgs } from 'core'
|
|
import { getLastTransaction } from 'database'
|
|
import { getLogger, Logger } from 'log4js'
|
|
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}`)
|
|
|
|
// TODO: do not export, test it inside the resolver
|
|
export const transactionLinkCode = (date: Date): string => {
|
|
const time = date.getTime().toString(16)
|
|
return (
|
|
randomBytes(12)
|
|
.toString('hex')
|
|
.substring(0, 24 - time.length) + time
|
|
)
|
|
}
|
|
|
|
const CODE_VALID_DAYS_DURATION = 14
|
|
const db = AppDatabase.getInstance()
|
|
|
|
export const transactionLinkExpireDate = (date: Date): Date => {
|
|
const validUntil = new Date(date)
|
|
return new Date(validUntil.setDate(date.getDate() + CODE_VALID_DAYS_DURATION))
|
|
}
|
|
|
|
@Resolver()
|
|
export class TransactionLinkResolver {
|
|
@Authorized([RIGHTS.CREATE_TRANSACTION_LINK])
|
|
@Mutation(() => TransactionLink)
|
|
async createTransactionLink(
|
|
@Args() { amount, memo }: TransactionLinkArgs,
|
|
@Ctx() context: Context,
|
|
): Promise<TransactionLink> {
|
|
const user = getUser(context)
|
|
|
|
const createdDate = new Date()
|
|
const validUntil = transactionLinkExpireDate(createdDate)
|
|
|
|
const holdAvailableAmount = amount.minus(calculateDecay(amount, createdDate, validUntil).decay)
|
|
|
|
// validate amount
|
|
const sendBalance = await calculateBalance(user.id, holdAvailableAmount.mul(-1), createdDate)
|
|
|
|
if (!sendBalance) {
|
|
throw new LogError('User has not enough GDD', user.id)
|
|
}
|
|
|
|
const transactionLink = DbTransactionLink.create()
|
|
transactionLink.userId = user.id
|
|
transactionLink.amount = amount
|
|
transactionLink.memo = memo
|
|
transactionLink.holdAvailableAmount = holdAvailableAmount
|
|
transactionLink.code = transactionLinkCode(createdDate)
|
|
transactionLink.createdAt = createdDate
|
|
transactionLink.validUntil = validUntil
|
|
await DbTransactionLink.save(transactionLink).catch((e) => {
|
|
throw new LogError('Unable to save transaction link', e)
|
|
})
|
|
await EVENT_TRANSACTION_LINK_CREATE(user, transactionLink, amount)
|
|
|
|
return new TransactionLink(transactionLink, new User(user))
|
|
}
|
|
|
|
@Authorized([RIGHTS.DELETE_TRANSACTION_LINK])
|
|
@Mutation(() => Boolean)
|
|
async deleteTransactionLink(
|
|
@Arg('id', () => Int) id: number,
|
|
@Ctx() context: Context,
|
|
): Promise<boolean> {
|
|
const user = getUser(context)
|
|
|
|
const transactionLink = await DbTransactionLink.findOne({ where: { id } })
|
|
if (!transactionLink) {
|
|
throw new LogError('Transaction link not found', id)
|
|
}
|
|
|
|
if (transactionLink.userId !== user.id) {
|
|
throw new LogError(
|
|
'Transaction link cannot be deleted by another user',
|
|
transactionLink.userId,
|
|
user.id,
|
|
)
|
|
}
|
|
|
|
if (transactionLink.redeemedBy) {
|
|
throw new LogError('Transaction link already redeemed', transactionLink.redeemedBy)
|
|
}
|
|
|
|
await transactionLink.softRemove().catch((e) => {
|
|
throw new LogError('Transaction link could not be deleted', e)
|
|
})
|
|
|
|
await EVENT_TRANSACTION_LINK_DELETE(user, transactionLink)
|
|
|
|
return true
|
|
}
|
|
|
|
@Authorized([RIGHTS.QUERY_TRANSACTION_LINK])
|
|
@Query(() => QueryLinkResult)
|
|
async queryTransactionLink(@Arg('code') code: string): Promise<typeof QueryLinkResult> {
|
|
const methodLogger = createLogger('queryTransactionLink')
|
|
methodLogger.addContext('handshakeID', randombytes_random().toString())
|
|
methodLogger.debug('queryTransactionLink...')
|
|
if (code.match(/^CL-/)) {
|
|
const contributionLink = await DbContributionLink.findOneOrFail({
|
|
where: { code: code.replace('CL-', '') },
|
|
withDeleted: true,
|
|
})
|
|
return new ContributionLink(contributionLink)
|
|
} else {
|
|
let txLinkFound = false
|
|
let dbTransactionLink!: DbTransactionLink
|
|
try {
|
|
dbTransactionLink = await findTransactionLinkByCode(code)
|
|
txLinkFound = true
|
|
} catch (_err) {
|
|
txLinkFound = false
|
|
}
|
|
// normal redeem code
|
|
if (txLinkFound) {
|
|
methodLogger.debug(
|
|
'TransactionLinkResolver.queryTransactionLink... normal redeem code found=',
|
|
txLinkFound,
|
|
)
|
|
const user = await DbUser.findOneOrFail({ where: { id: dbTransactionLink.userId } })
|
|
let redeemedBy
|
|
if (dbTransactionLink.redeemedBy) {
|
|
redeemedBy = new User(
|
|
await DbUser.findOneOrFail({ where: { id: dbTransactionLink.redeemedBy } }),
|
|
)
|
|
}
|
|
const communities = await getAuthenticatedCommunities()
|
|
return new TransactionLink(dbTransactionLink, new User(user), redeemedBy, communities)
|
|
} else {
|
|
// redeem jwt-token
|
|
return await this.queryRedeemJwtLink(code, methodLogger)
|
|
}
|
|
}
|
|
}
|
|
|
|
@Authorized([RIGHTS.REDEEM_TRANSACTION_LINK])
|
|
@Mutation(() => Boolean)
|
|
async redeemTransactionLink(
|
|
@Arg('code', () => String) code: string,
|
|
@Ctx() context: Context,
|
|
): Promise<boolean> {
|
|
const methodLogger = createLogger('redeemTransactionLink')
|
|
methodLogger.addContext('code', code.substring(0, 6))
|
|
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
|
// const homeCom = await DbCommunity.findOneOrFail({ where: { foreign: false } })
|
|
const user = getUser(context)
|
|
if (code.match(/^CL-/)) {
|
|
// acquire lock
|
|
const releaseLock = await TRANSACTIONS_LOCK.acquire()
|
|
try {
|
|
methodLogger.info('redeem contribution link...')
|
|
const now = new Date()
|
|
const queryRunner = db.getDataSource().createQueryRunner()
|
|
await queryRunner.connect()
|
|
await queryRunner.startTransaction('REPEATABLE READ')
|
|
try {
|
|
const contributionLink = await queryRunner.manager
|
|
.createQueryBuilder()
|
|
.select('contributionLink')
|
|
.from(DbContributionLink, 'contributionLink')
|
|
.where('contributionLink.code = :code', { code: code.replace('CL-', '') })
|
|
.getOne()
|
|
if (!contributionLink) {
|
|
throw new LogError('No contribution link found to given code', code)
|
|
}
|
|
methodLogger.info('...contribution link found with id', contributionLink.id)
|
|
if (new Date(contributionLink.validFrom).getTime() > now.getTime()) {
|
|
throw new LogError('Contribution link is not valid yet', contributionLink.validFrom)
|
|
}
|
|
if (contributionLink.validTo) {
|
|
if (new Date(contributionLink.validTo).setHours(23, 59, 59) < now.getTime()) {
|
|
throw new LogError('Contribution link is no longer valid', contributionLink.validTo)
|
|
}
|
|
}
|
|
let alreadyRedeemed: DbContribution | null
|
|
switch (contributionLink.cycle) {
|
|
case ContributionCycleType.ONCE: {
|
|
alreadyRedeemed = await queryRunner.manager
|
|
.createQueryBuilder()
|
|
.select('contribution')
|
|
.from(DbContribution, 'contribution')
|
|
.where('contribution.contributionLinkId = :linkId AND contribution.userId = :id', {
|
|
linkId: contributionLink.id,
|
|
id: user.id,
|
|
})
|
|
.getOne()
|
|
if (alreadyRedeemed) {
|
|
throw new LogError('Contribution link already redeemed', user.id)
|
|
}
|
|
break
|
|
}
|
|
case ContributionCycleType.DAILY: {
|
|
const start = new Date()
|
|
start.setHours(0, 0, 0, 0)
|
|
const end = new Date()
|
|
end.setHours(23, 59, 59, 999)
|
|
alreadyRedeemed = await queryRunner.manager
|
|
.createQueryBuilder()
|
|
.select('contribution')
|
|
.from(DbContribution, 'contribution')
|
|
.where(
|
|
`contribution.contributionLinkId = :linkId AND contribution.userId = :id
|
|
AND Date(contribution.confirmedAt) BETWEEN :start AND :end`,
|
|
{
|
|
linkId: contributionLink.id,
|
|
id: user.id,
|
|
start,
|
|
end,
|
|
},
|
|
)
|
|
.getOne()
|
|
if (alreadyRedeemed) {
|
|
throw new LogError('Contribution link already redeemed today', user.id)
|
|
}
|
|
break
|
|
}
|
|
default: {
|
|
throw new LogError('Contribution link has unknown cycle', contributionLink.cycle)
|
|
}
|
|
}
|
|
|
|
const creations = await getUserCreation(user.id, clientTimezoneOffset)
|
|
methodLogger.info('open creations', creations)
|
|
validateContribution(creations, contributionLink.amount, now, clientTimezoneOffset)
|
|
const contribution = new DbContribution()
|
|
contribution.userId = user.id
|
|
contribution.createdAt = now
|
|
contribution.contributionDate = now
|
|
contribution.memo = contributionLink.memo
|
|
contribution.amount = contributionLink.amount
|
|
contribution.contributionLinkId = contributionLink.id
|
|
contribution.contributionType = ContributionType.LINK
|
|
contribution.contributionStatus = ContributionStatus.CONFIRMED
|
|
|
|
await queryRunner.manager.insert(DbContribution, contribution)
|
|
|
|
const lastTransaction = await getLastTransaction(user.id)
|
|
let newBalance = new Decimal(0)
|
|
|
|
let decay: Decay | null = null
|
|
if (lastTransaction) {
|
|
decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now)
|
|
newBalance = decay.balance
|
|
}
|
|
newBalance = newBalance.add(contributionLink.amount.toString())
|
|
|
|
const transaction = new DbTransaction()
|
|
transaction.typeId = TransactionTypeId.CREATION
|
|
transaction.memo = contribution.memo
|
|
transaction.userId = contribution.userId
|
|
/* local transaction will not carry homeComUuid for local users
|
|
if (homeCom.communityUuid) {
|
|
transaction.userCommunityUuid = homeCom.communityUuid
|
|
}
|
|
*/
|
|
transaction.userGradidoID = user.gradidoID
|
|
transaction.userName = fullName(user.firstName, user.lastName)
|
|
transaction.previous = lastTransaction ? lastTransaction.id : null
|
|
transaction.amount = contribution.amount
|
|
transaction.creationDate = contribution.contributionDate
|
|
transaction.balance = newBalance
|
|
transaction.balanceDate = now
|
|
transaction.decay = decay ? decay.decay : new Decimal(0)
|
|
transaction.decayStart = decay ? decay.start : null
|
|
await queryRunner.manager.insert(DbTransaction, transaction)
|
|
|
|
contribution.confirmedAt = now
|
|
contribution.transactionId = transaction.id
|
|
await queryRunner.manager.update(DbContribution, { id: contribution.id }, contribution)
|
|
|
|
await queryRunner.commitTransaction()
|
|
|
|
await EVENT_CONTRIBUTION_LINK_REDEEM(
|
|
user,
|
|
transaction,
|
|
contribution,
|
|
contributionLink,
|
|
contributionLink.amount,
|
|
)
|
|
} catch (e) {
|
|
await queryRunner.rollbackTransaction()
|
|
throw new LogError('Creation from contribution link was not successful', e)
|
|
} finally {
|
|
await queryRunner.release()
|
|
}
|
|
} finally {
|
|
releaseLock()
|
|
}
|
|
// trigger to send transaction via dlt-connector
|
|
await sendTransactionsToDltConnector()
|
|
return true
|
|
} else {
|
|
const now = new Date()
|
|
const releaseLinkLock = await TRANSACTION_LINK_LOCK.acquire()
|
|
try {
|
|
const transactionLink = await DbTransactionLink.findOne({ where: { code } })
|
|
if (!transactionLink) {
|
|
throw new LogError('Transaction link not found', code)
|
|
}
|
|
|
|
const linkedUser = await DbUser.findOne({
|
|
where: {
|
|
id: transactionLink.userId,
|
|
},
|
|
relations: ['emailContact'],
|
|
})
|
|
|
|
if (!linkedUser) {
|
|
throw new LogError('Linked user not found for given link', transactionLink.userId)
|
|
}
|
|
|
|
if (user.id === linkedUser.id) {
|
|
throw new LogError('Cannot redeem own transaction link', user.id)
|
|
}
|
|
|
|
if (transactionLink.validUntil.getTime() < now.getTime()) {
|
|
throw new LogError('Transaction link is not valid anymore', transactionLink.validUntil)
|
|
}
|
|
|
|
if (transactionLink.redeemedBy) {
|
|
throw new LogError('Transaction link already redeemed', transactionLink.redeemedBy)
|
|
}
|
|
await executeTransaction(
|
|
transactionLink.amount,
|
|
transactionLink.memo,
|
|
linkedUser,
|
|
user,
|
|
methodLogger,
|
|
transactionLink,
|
|
)
|
|
await EVENT_TRANSACTION_LINK_REDEEM(
|
|
user,
|
|
{ id: transactionLink.userId } as DbUser,
|
|
transactionLink,
|
|
transactionLink.amount,
|
|
)
|
|
} finally {
|
|
releaseLinkLock()
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
@Authorized([RIGHTS.QUERY_REDEEM_JWT])
|
|
@Mutation(() => String)
|
|
async createRedeemJwt(
|
|
@Arg('gradidoId') gradidoId: string,
|
|
@Arg('senderCommunityUuid') senderCommunityUuid: string,
|
|
@Arg('senderCommunityName') senderCommunityName: string,
|
|
@Arg('recipientCommunityUuid') recipientCommunityUuid: string,
|
|
@Arg('code') code: string,
|
|
@Arg('amount') amount: string,
|
|
@Arg('memo') memo: string,
|
|
@Arg('firstName', { nullable: true }) firstName?: string,
|
|
@Arg('alias', { nullable: true }) alias?: string,
|
|
@Arg('validUntil', { nullable: true }) validUntil?: string,
|
|
): Promise<string> {
|
|
const methodLogger = createLogger('createRedeemJwt')
|
|
methodLogger.addContext('code', code.substring(0, 6))
|
|
methodLogger.debug('args=', {
|
|
gradidoId,
|
|
senderCommunityUuid,
|
|
senderCommunityName,
|
|
recipientCommunityUuid,
|
|
code,
|
|
amount,
|
|
memo,
|
|
firstName,
|
|
alias,
|
|
validUntil,
|
|
})
|
|
try {
|
|
const redeemJwtPayloadType = new RedeemJwtPayloadType(
|
|
senderCommunityUuid,
|
|
gradidoId,
|
|
alias ?? firstName ?? '',
|
|
code,
|
|
amount,
|
|
memo,
|
|
validUntil ?? '',
|
|
)
|
|
// encode/sign the jwt with the private key of the sender/home community
|
|
const senderCom = await getCommunityByUuid(senderCommunityUuid)
|
|
if (!senderCom) {
|
|
throw new LogError('Sender community not found')
|
|
}
|
|
if (!senderCom.privateJwtKey) {
|
|
throw new LogError('Sender community privateJwtKey is not set')
|
|
}
|
|
const recipientCom = await getCommunityByUuid(recipientCommunityUuid)
|
|
if (!recipientCom) {
|
|
throw new LogError('Recipient community not found')
|
|
}
|
|
if (!recipientCom.publicJwtKey) {
|
|
throw new LogError('Recipient community publicJwtKey is not set')
|
|
}
|
|
const redeemJwt = await encryptAndSign(redeemJwtPayloadType, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
|
if (!redeemJwt) {
|
|
throw new LogError('Redeem JWT was not created successfully')
|
|
}
|
|
// 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)
|
|
throw new LogError(errmsg)
|
|
}
|
|
}
|
|
|
|
@Authorized([RIGHTS.DISBURSE_TRANSACTION_LINK])
|
|
@Mutation(() => Boolean)
|
|
async disburseTransactionLink(
|
|
@Ctx() _context: Context,
|
|
@Arg('senderCommunityUuid') senderCommunityUuid: string,
|
|
@Arg('senderGradidoId') senderGradidoId: string,
|
|
@Arg('recipientCommunityUuid') recipientCommunityUuid: string,
|
|
@Arg('recipientCommunityName') recipientCommunityName: string,
|
|
@Arg('recipientGradidoId') recipientGradidoId: string,
|
|
@Arg('recipientFirstName') recipientFirstName: string,
|
|
@Arg('code') code: string,
|
|
@Arg('amount') amount: string,
|
|
@Arg('memo') memo: string,
|
|
@Arg('validUntil', { nullable: true }) validUntil?: string,
|
|
@Arg('recipientAlias', { nullable: true }) recipientAlias?: string,
|
|
): Promise<boolean> {
|
|
const handshakeID = randombytes_random().toString()
|
|
const methodLogger = createLogger(`disburseTransactionLink`)
|
|
methodLogger.addContext('handshakeID', handshakeID)
|
|
if(methodLogger.isDebugEnabled()) {
|
|
methodLogger.debug('args=', {
|
|
senderGradidoId,
|
|
senderCommunityUuid,
|
|
recipientCommunityUuid,
|
|
recipientCommunityName,
|
|
recipientGradidoId,
|
|
recipientFirstName,
|
|
code,
|
|
amount,
|
|
memo,
|
|
validUntil,
|
|
recipientAlias,
|
|
})
|
|
}
|
|
const senderCom = await getCommunityByUuid(senderCommunityUuid)
|
|
if (!senderCom) {
|
|
const errmsg = `Sender community not found with uuid=${senderCommunityUuid}`
|
|
methodLogger.error(errmsg)
|
|
throw new LogError(errmsg)
|
|
}
|
|
const senderFedCom = await DbFederatedCommunity.findOneBy({ publicKey: senderCom.publicKey })
|
|
if (!senderFedCom) {
|
|
const errmsg = `Sender federated community not found with publicKey=${senderCom.publicKey}`
|
|
methodLogger.error(errmsg)
|
|
throw new LogError(errmsg)
|
|
}
|
|
const recipientCom = await getCommunityByUuid(recipientCommunityUuid)
|
|
if (!recipientCom) {
|
|
const errmsg = `Recipient community not found with uuid=${recipientCommunityUuid}`
|
|
methodLogger.error(errmsg)
|
|
throw new LogError(errmsg)
|
|
}
|
|
const client = DisbursementClientFactory.getInstance(senderFedCom)
|
|
if (client instanceof V1_0_DisbursementClient) {
|
|
const disburseJwtPayload = new DisburseJwtPayloadType(
|
|
handshakeID,
|
|
senderCommunityUuid,
|
|
senderGradidoId,
|
|
recipientCommunityUuid,
|
|
recipientCommunityName,
|
|
recipientGradidoId,
|
|
recipientFirstName,
|
|
code,
|
|
amount,
|
|
memo,
|
|
validUntil!,
|
|
recipientAlias!,
|
|
)
|
|
if(methodLogger.isDebugEnabled()) {
|
|
methodLogger.debug('disburseJwtPayload=', disburseJwtPayload)
|
|
}
|
|
const jws = await encryptAndSign(disburseJwtPayload, recipientCom.privateJwtKey!, senderCom.publicJwtKey!)
|
|
if(methodLogger.isDebugEnabled()) {
|
|
methodLogger.debug('jws=', jws)
|
|
}
|
|
const args = new EncryptedTransferArgs()
|
|
args.publicKey = recipientCom.publicKey.toString('hex')
|
|
args.jwt = jws
|
|
args.handshakeID = handshakeID
|
|
try {
|
|
// now send the disburseJwt to the sender community to invoke a x-community-tx to disbures the redeemLink
|
|
const result = await client.sendDisburseJwtToSenderCommunity(args)
|
|
if(methodLogger.isDebugEnabled()) {
|
|
methodLogger.debug('Disburse JWT was sent successfully with result=', result)
|
|
}
|
|
} catch (e) {
|
|
const errmsg = `Disburse JWT was not sent successfully with error=${e}`
|
|
methodLogger.error(errmsg)
|
|
throw new Error(errmsg)
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
@Authorized([RIGHTS.LIST_TRANSACTION_LINKS])
|
|
@Query(() => TransactionLinkResult)
|
|
async listTransactionLinks(
|
|
@Args()
|
|
paginated: Paginated,
|
|
@Ctx() context: Context,
|
|
): Promise<TransactionLinkResult> {
|
|
return transactionLinkList(
|
|
paginated,
|
|
{
|
|
withDeleted: false,
|
|
withExpired: true,
|
|
withRedeemed: false,
|
|
},
|
|
getUser(context),
|
|
)
|
|
}
|
|
|
|
@Authorized([RIGHTS.LIST_TRANSACTION_LINKS_ADMIN])
|
|
@Query(() => TransactionLinkResult)
|
|
async listTransactionLinksAdmin(
|
|
@Args()
|
|
paginated: Paginated,
|
|
@Arg('filters', () => TransactionLinkFilters, { nullable: true })
|
|
filters: TransactionLinkFilters,
|
|
@Arg('userId', () => Int)
|
|
userId: number,
|
|
): Promise<TransactionLinkResult> {
|
|
const user = await DbUser.findOne({ where: { id: userId } })
|
|
if (!user) {
|
|
throw new LogError('Could not find requested User', userId)
|
|
}
|
|
return transactionLinkList(paginated, filters, user)
|
|
}
|
|
|
|
async queryRedeemJwtLink(code: string, logger: Logger): Promise<RedeemJwtLink> {
|
|
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)
|
|
logger.debug('switch logger-context to received token-handshakeID:' + decodedPayload.handshakeID)
|
|
logger.addContext('handshakeID', decodedPayload.handshakeID)
|
|
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, 'hex'))
|
|
if (!senderCom) {
|
|
const errmsg = `Sender community not found with publicKey=${signedTransferPayload.publicKey}`
|
|
logger.error(errmsg)
|
|
throw new Error(errmsg)
|
|
}
|
|
logger.debug('queryRedeemJwtLink... senderCom=', senderCom)
|
|
const jweVerifyResult = await verify(signedTransferPayload.handshakeID, signedTransferPayload.jwt, senderCom.publicJwtKey!)
|
|
logger.debug('queryRedeemJwtLink... jweVerifyResult=', jweVerifyResult)
|
|
let verifiedRedeemJwtPayload: RedeemJwtPayloadType | null = null
|
|
if (jweVerifyResult === 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 = signedTransferPayload.publicKey
|
|
encryptedTransferArgs.jwt = signedTransferPayload.jwt
|
|
encryptedTransferArgs.handshakeID = signedTransferPayload.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` + signedTransferPayload.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(
|
|
'queryRedeemJwtLink... expDate, exp =',
|
|
expDate,
|
|
verifiedRedeemJwtPayload.exp,
|
|
)
|
|
if (expDate < new Date()) {
|
|
const errmsg = `Redeem JWT-Token expired! jwtPayload.exp=${expDate}`
|
|
logger.error(errmsg)
|
|
throw new Error(errmsg)
|
|
}
|
|
}
|
|
}
|
|
const homeCommunity = await getHomeCommunity()
|
|
if (!homeCommunity) {
|
|
const errmsg = `Home community not found`
|
|
logger.error(errmsg)
|
|
throw new Error(errmsg)
|
|
}
|
|
const recipientCommunity = new Community(homeCommunity)
|
|
const senderCommunity = new Community(senderCom)
|
|
const senderUser = new User(null)
|
|
senderUser.gradidoID = verifiedRedeemJwtPayload.sendergradidoid
|
|
senderUser.firstName = verifiedRedeemJwtPayload.sendername
|
|
const redeemJwtLink = new RedeemJwtLink(
|
|
verifiedRedeemJwtPayload,
|
|
senderCommunity,
|
|
senderUser,
|
|
recipientCommunity,
|
|
)
|
|
logger.debug('TransactionLinkResolver.queryRedeemJwtLink... redeemJwtLink=', redeemJwtLink)
|
|
return redeemJwtLink
|
|
} else {
|
|
const errmsg = `transfer of redeem JWT with wrong envelope! code=${code}`
|
|
logger.error(errmsg)
|
|
throw new Error(errmsg)
|
|
}
|
|
}
|
|
}
|