mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
Merge pull request #3520 from gradido/3516-feature-introduce-security-in-x-com-tx-handshake
feat(backend): introduce security in x com tx handshake
This commit is contained in:
commit
1de7e15a16
@ -6,14 +6,11 @@ import { ensureUrlEndsWithSlash } from '@/util/utilities'
|
||||
import { getLogger } from 'log4js'
|
||||
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { SendCoinsArgsLoggingView } from './logging/SendCoinsArgsLogging.view'
|
||||
import { SendCoinsResultLoggingView } from './logging/SendCoinsResultLogging.view'
|
||||
import { SendCoinsArgs } from './model/SendCoinsArgs'
|
||||
import { SendCoinsResult } from './model/SendCoinsResult'
|
||||
import { revertSendCoins as revertSendCoinsQuery } from './query/revertSendCoins'
|
||||
import { revertSettledSendCoins as revertSettledSendCoinsQuery } from './query/revertSettledSendCoins'
|
||||
import { settleSendCoins as settleSendCoinsQuery } from './query/settleSendCoins'
|
||||
import { voteForSendCoins as voteForSendCoinsQuery } from './query/voteForSendCoins'
|
||||
import { EncryptedTransferArgs } from 'core'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.federation.client.1_0.SendCoinsClient`)
|
||||
|
||||
@ -34,33 +31,26 @@ export class SendCoinsClient {
|
||||
})
|
||||
}
|
||||
|
||||
async voteForSendCoins(args: SendCoinsArgs): Promise<SendCoinsResult> {
|
||||
async voteForSendCoins(args: EncryptedTransferArgs): Promise<string | null> {
|
||||
logger.debug('voteForSendCoins against endpoint=', this.endpoint)
|
||||
try {
|
||||
logger.debug(`voteForSendCoins with args=`, new SendCoinsArgsLoggingView(args))
|
||||
const { data } = await this.client.rawRequest<{ voteForSendCoins: SendCoinsResult }>(
|
||||
voteForSendCoinsQuery,
|
||||
{ args },
|
||||
)
|
||||
const result = data.voteForSendCoins
|
||||
if (!data?.voteForSendCoins?.vote) {
|
||||
logger.debug('voteForSendCoins failed with: ', new SendCoinsResultLoggingView(result))
|
||||
return new SendCoinsResult()
|
||||
const { data } = await this.client.rawRequest<{ voteForSendCoins: string }>(voteForSendCoinsQuery, { args })
|
||||
const responseJwt = data?.voteForSendCoins
|
||||
if (responseJwt) {
|
||||
logger.debug('received response jwt', responseJwt)
|
||||
return responseJwt
|
||||
}
|
||||
logger.debug(
|
||||
'voteForSendCoins successful with result=',
|
||||
new SendCoinsResultLoggingView(result),
|
||||
)
|
||||
return result
|
||||
} catch (err) {
|
||||
throw new LogError(`voteForSendCoins failed for endpoint=${this.endpoint}:`, err)
|
||||
const errmsg = `voteForSendCoins failed for endpoint=${this.endpoint}, err=${err}`
|
||||
logger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async revertSendCoins(args: SendCoinsArgs): Promise<boolean> {
|
||||
async revertSendCoins(args: EncryptedTransferArgs): Promise<boolean> {
|
||||
logger.debug('revertSendCoins against endpoint=', this.endpoint)
|
||||
try {
|
||||
logger.debug(`revertSendCoins with args=`, new SendCoinsArgsLoggingView(args))
|
||||
const { data } = await this.client.rawRequest<{ revertSendCoins: boolean }>(
|
||||
revertSendCoinsQuery,
|
||||
{ args },
|
||||
@ -78,10 +68,9 @@ export class SendCoinsClient {
|
||||
}
|
||||
}
|
||||
|
||||
async settleSendCoins(args: SendCoinsArgs): Promise<boolean> {
|
||||
async settleSendCoins(args: EncryptedTransferArgs): Promise<boolean> {
|
||||
logger.debug(`settleSendCoins against endpoint='${this.endpoint}'...`)
|
||||
try {
|
||||
logger.debug(`settleSendCoins with args=`, new SendCoinsArgsLoggingView(args))
|
||||
const { data } = await this.client.rawRequest<{ settleSendCoins: boolean }>(
|
||||
settleSendCoinsQuery,
|
||||
{ args },
|
||||
@ -98,10 +87,9 @@ export class SendCoinsClient {
|
||||
}
|
||||
}
|
||||
|
||||
async revertSettledSendCoins(args: SendCoinsArgs): Promise<boolean> {
|
||||
async revertSettledSendCoins(args: EncryptedTransferArgs): Promise<boolean> {
|
||||
logger.debug(`revertSettledSendCoins against endpoint='${this.endpoint}'...`)
|
||||
try {
|
||||
logger.debug(`revertSettledSendCoins with args=`, new SendCoinsArgsLoggingView(args))
|
||||
const { data } = await this.client.rawRequest<{ revertSettledSendCoins: boolean }>(
|
||||
revertSettledSendCoinsQuery,
|
||||
{ args },
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { gql } from 'graphql-request'
|
||||
|
||||
export const revertSendCoins = gql`
|
||||
mutation ($args: SendCoinsArgs!) {
|
||||
mutation ($args: EncryptedTransferArgs!) {
|
||||
revertSendCoins(data: $args)
|
||||
}
|
||||
`
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { gql } from 'graphql-request'
|
||||
|
||||
export const revertSettledSendCoins = gql`
|
||||
mutation ($args: SendCoinsArgs!) {
|
||||
mutation ($args: EncryptedTransferArgs!) {
|
||||
revertSettledSendCoins(data: $args)
|
||||
}
|
||||
`
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { gql } from 'graphql-request'
|
||||
|
||||
export const settleSendCoins = gql`
|
||||
mutation ($args: SendCoinsArgs!) {
|
||||
mutation ($args: EncryptedTransferArgs!) {
|
||||
settleSendCoins(data: $args)
|
||||
}
|
||||
`
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import { gql } from 'graphql-request'
|
||||
|
||||
export const voteForSendCoins = gql`
|
||||
mutation ($args: SendCoinsArgs!) {
|
||||
voteForSendCoins(data: $args) {
|
||||
mutation ($args: EncryptedTransferArgs!) {
|
||||
voteForSendCoins(data: $args)
|
||||
}
|
||||
`
|
||||
/*
|
||||
{
|
||||
vote
|
||||
recipGradidoID
|
||||
recipFirstName
|
||||
@ -10,4 +14,4 @@ export const voteForSendCoins = gql`
|
||||
recipAlias
|
||||
}
|
||||
}
|
||||
`
|
||||
*/
|
||||
|
||||
@ -479,7 +479,6 @@ describe('send coins', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('send coins via gradido ID', () => {
|
||||
it('sends the coins', async () => {
|
||||
await expect(
|
||||
@ -591,8 +590,8 @@ describe('send coins', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('X-Com send coins via gradido ID', () => {
|
||||
/*
|
||||
describe.skip('X-Com send coins via gradido ID', () => {
|
||||
beforeAll(async () => {
|
||||
CONFIG.FEDERATION_XCOM_SENDCOINS_ENABLED = true
|
||||
fedForeignCom = DbFederatedCommunity.create()
|
||||
@ -653,7 +652,7 @@ describe('send coins', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
*/
|
||||
describe('more transactions to test semaphore', () => {
|
||||
it('sends the coins four times in a row', async () => {
|
||||
await expect(
|
||||
|
||||
@ -15,7 +15,7 @@ import { In, IsNull } from 'typeorm'
|
||||
import { Paginated } from '@arg/Paginated'
|
||||
import { TransactionSendArgs } from '@arg/TransactionSendArgs'
|
||||
import { Order } from '@enum/Order'
|
||||
import { PendingTransactionState } from 'shared'
|
||||
import { PendingTransactionState, SendCoinsResponseJwtPayloadType } from 'shared'
|
||||
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
||||
import { Transaction } from '@model/Transaction'
|
||||
import { TransactionList } from '@model/TransactionList'
|
||||
@ -482,7 +482,7 @@ export class TransactionResolver {
|
||||
if (recipCom !== null && recipCom.authenticatedAt === null) {
|
||||
throw new LogError('recipient community is connected, but still not authenticated yet!')
|
||||
}
|
||||
let pendingResult: SendCoinsResult
|
||||
let pendingResult: SendCoinsResponseJwtPayloadType | null = null
|
||||
let committingResult: SendCoinsResult
|
||||
const creationDate = new Date()
|
||||
|
||||
@ -497,7 +497,7 @@ export class TransactionResolver {
|
||||
recipientIdentifier,
|
||||
)
|
||||
logger.debug('processXComPendingSendCoins result: ', pendingResult)
|
||||
if (pendingResult.vote && pendingResult.recipGradidoID) {
|
||||
if (pendingResult && pendingResult.vote && pendingResult.recipGradidoID) {
|
||||
logger.debug('vor processXComCommittingSendCoins... ')
|
||||
committingResult = await processXComCommittingSendCoins(
|
||||
recipCom,
|
||||
|
||||
@ -16,7 +16,7 @@ import { SendCoinsClient as V1_0_SendCoinsClient } from '@/federation/client/1_0
|
||||
import { SendCoinsArgs } from '@/federation/client/1_0/model/SendCoinsArgs'
|
||||
import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult'
|
||||
import { SendCoinsClientFactory } from '@/federation/client/SendCoinsClientFactory'
|
||||
import { PendingTransactionState } from 'shared'
|
||||
import { encryptAndSign, PendingTransactionState, SendCoinsJwtPayloadType, SendCoinsResponseJwtPayloadType, verifyAndDecrypt } from 'shared'
|
||||
import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { LogError } from '@/server/LogError'
|
||||
@ -27,8 +27,10 @@ import { getLogger } from 'log4js'
|
||||
import { settlePendingSenderTransaction } from './settlePendingSenderTransaction'
|
||||
import { SendCoinsArgsLoggingView } from '@/federation/client/1_0/logging/SendCoinsArgsLogging.view'
|
||||
import { SendCoinsResultLoggingView } from '@/federation/client/1_0/logging/SendCoinsResultLogging.view'
|
||||
import { EncryptedTransferArgs } from 'core'
|
||||
import { randombytes_random } from 'sodium-native'
|
||||
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.util.processXComSendCoins`)
|
||||
const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.util.processXComSendCoins.${method}`)
|
||||
|
||||
export async function processXComPendingSendCoins(
|
||||
receiverCom: DbCommunity,
|
||||
@ -38,12 +40,13 @@ export async function processXComPendingSendCoins(
|
||||
memo: string,
|
||||
sender: dbUser,
|
||||
recipientIdentifier: string,
|
||||
): Promise<SendCoinsResult> {
|
||||
let voteResult: SendCoinsResult
|
||||
): Promise<SendCoinsResponseJwtPayloadType | null> {
|
||||
let voteResult: SendCoinsResponseJwtPayloadType
|
||||
const methodLogger = createLogger(`processXComPendingSendCoins`)
|
||||
try {
|
||||
// even if debug is not enabled, attributes are processed so we skip the entire call for performance reasons
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug(
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(
|
||||
'XCom: processXComPendingSendCoins...', {
|
||||
receiverCom: new CommunityLoggingView(receiverCom),
|
||||
senderCom: new CommunityLoggingView(senderCom),
|
||||
@ -55,17 +58,21 @@ export async function processXComPendingSendCoins(
|
||||
)
|
||||
}
|
||||
if (await countOpenPendingTransactions([sender.gradidoID, recipientIdentifier]) > 0) {
|
||||
throw new LogError(
|
||||
`There exist still ongoing 'Pending-Transactions' for the involved users on sender-side!`,
|
||||
)
|
||||
const errmsg = `There exist still ongoing 'Pending-Transactions' for the involved users on sender-side!`
|
||||
methodLogger.error(errmsg)
|
||||
throw new LogError(errmsg)
|
||||
}
|
||||
const handshakeID = randombytes_random().toString()
|
||||
methodLogger.addContext('handshakeID', handshakeID)
|
||||
// first calculate the sender balance and check if the transaction is allowed
|
||||
const senderBalance = await calculateSenderBalance(sender.id, amount.mul(-1), creationDate)
|
||||
if (!senderBalance) {
|
||||
throw new LogError('User has not enough GDD or amount is < 0', senderBalance)
|
||||
const errmsg = `User has not enough GDD or amount is < 0`
|
||||
methodLogger.error(errmsg)
|
||||
throw new LogError(errmsg)
|
||||
}
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug(`calculated senderBalance = ${JSON.stringify(senderBalance, null, 2)}`)
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`calculated senderBalance = ${JSON.stringify(senderBalance, null, 2)}`)
|
||||
}
|
||||
|
||||
const receiverFCom = await DbFederatedCommunity.findOneOrFail({
|
||||
@ -77,89 +84,115 @@ export async function processXComPendingSendCoins(
|
||||
const client = SendCoinsClientFactory.getInstance(receiverFCom)
|
||||
|
||||
if (client instanceof V1_0_SendCoinsClient) {
|
||||
const args = new SendCoinsArgs()
|
||||
if (receiverCom.communityUuid) {
|
||||
args.recipientCommunityUuid = receiverCom.communityUuid
|
||||
const payload = new SendCoinsJwtPayloadType(handshakeID,
|
||||
receiverCom.communityUuid!,
|
||||
recipientIdentifier,
|
||||
creationDate.toISOString(),
|
||||
amount,
|
||||
memo,
|
||||
senderCom.communityUuid!,
|
||||
sender.gradidoID,
|
||||
fullName(sender.firstName, sender.lastName),
|
||||
sender.alias
|
||||
)
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`ready for voteForSendCoins with payload=${payload}`)
|
||||
}
|
||||
args.recipientUserIdentifier = recipientIdentifier
|
||||
args.creationDate = creationDate.toISOString()
|
||||
args.amount = amount
|
||||
args.memo = memo
|
||||
if (senderCom.communityUuid) {
|
||||
args.senderCommunityUuid = senderCom.communityUuid
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, receiverCom.publicJwtKey!)
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug('jws', jws)
|
||||
}
|
||||
args.senderUserUuid = sender.gradidoID
|
||||
args.senderUserName = fullName(sender.firstName, sender.lastName)
|
||||
args.senderAlias = sender.alias
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug(`ready for voteForSendCoins with args=${new SendCoinsArgsLoggingView(args)}`)
|
||||
// prepare the args for the client invocation
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = handshakeID
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug('before client.voteForSendCoins() args:', args)
|
||||
}
|
||||
voteResult = await client.voteForSendCoins(args)
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug(`returned from voteForSendCoins: ${new SendCoinsResultLoggingView(voteResult)}`)
|
||||
}
|
||||
if (voteResult.vote) {
|
||||
logger.debug('prepare pendingTransaction for sender...')
|
||||
// writing the pending transaction on receiver-side was successfull, so now write the sender side
|
||||
try {
|
||||
const pendingTx = DbPendingTransaction.create()
|
||||
pendingTx.amount = amount.mul(-1)
|
||||
pendingTx.balance = senderBalance.balance
|
||||
pendingTx.balanceDate = creationDate
|
||||
pendingTx.decay = senderBalance ? senderBalance.decay.decay : new Decimal(0)
|
||||
pendingTx.decayStart = senderBalance ? senderBalance.decay.start : null
|
||||
if (receiverCom.communityUuid) {
|
||||
pendingTx.linkedUserCommunityUuid = receiverCom.communityUuid
|
||||
}
|
||||
if (voteResult.recipGradidoID) {
|
||||
pendingTx.linkedUserGradidoID = voteResult.recipGradidoID
|
||||
}
|
||||
if (voteResult.recipFirstName && voteResult.recipLastName) {
|
||||
pendingTx.linkedUserName = fullName(voteResult.recipFirstName, voteResult.recipLastName)
|
||||
}
|
||||
pendingTx.memo = memo
|
||||
pendingTx.previous = senderBalance ? senderBalance.lastTransactionId : null
|
||||
pendingTx.state = PendingTransactionState.NEW
|
||||
pendingTx.typeId = TransactionTypeId.SEND
|
||||
if (senderCom.communityUuid) {
|
||||
pendingTx.userCommunityUuid = senderCom.communityUuid
|
||||
}
|
||||
pendingTx.userId = sender.id
|
||||
pendingTx.userGradidoID = sender.gradidoID
|
||||
pendingTx.userName = fullName(sender.firstName, sender.lastName)
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug(`initialized sender pendingTX=${new PendingTransactionLoggingView(pendingTx)}`)
|
||||
}
|
||||
|
||||
await DbPendingTransaction.insert(pendingTx)
|
||||
logger.debug('sender pendingTx successfully inserted...')
|
||||
} catch (err) {
|
||||
logger.error(`Error in writing sender pending transaction: ${JSON.stringify(err, null, 2)}`)
|
||||
// revert the existing pending transaction on receiver side
|
||||
let revertCount = 0
|
||||
logger.debug('first try to revertSendCoins of receiver')
|
||||
do {
|
||||
if (await client.revertSendCoins(args)) {
|
||||
logger.debug(`revertSendCoins()-1_0... successfull after revertCount=${revertCount}`)
|
||||
// treat revertingSendCoins as an error of the whole sendCoins-process
|
||||
throw new LogError('Error in writing sender pending transaction: ', err)
|
||||
}
|
||||
} while (CONFIG.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS > revertCount++)
|
||||
throw new LogError(
|
||||
`Error in reverting receiver pending transaction even after revertCount=${revertCount}`,
|
||||
err,
|
||||
)
|
||||
}
|
||||
logger.debug('voteForSendCoins()-1_0... successfull')
|
||||
} else {
|
||||
logger.error(`break with error on writing pendingTransaction for recipient... ${new SendCoinsResultLoggingView(voteResult)}`)
|
||||
const responseJwt = await client.voteForSendCoins(args)
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`response of voteForSendCoins():`, responseJwt)
|
||||
}
|
||||
if (responseJwt !== null) {
|
||||
voteResult = await verifyAndDecrypt(handshakeID, responseJwt, senderCom.privateJwtKey!, receiverCom.publicJwtKey!) as SendCoinsResponseJwtPayloadType
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`received payload from voteForSendCoins():`, voteResult)
|
||||
}
|
||||
if (voteResult && voteResult.tokentype !== SendCoinsResponseJwtPayloadType.SEND_COINS_RESPONSE_TYPE) {
|
||||
const errmsg = `Invalid tokentype in voteForSendCoins-response of community with publicKey` + receiverCom.publicKey
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error('Error in X-Com-TX protocol...')
|
||||
}
|
||||
if (voteResult && voteResult.vote) {
|
||||
methodLogger.debug('prepare pendingTransaction for sender...')
|
||||
// writing the pending transaction on receiver-side was successfull, so now write the sender side
|
||||
try {
|
||||
const pendingTx = DbPendingTransaction.create()
|
||||
pendingTx.amount = amount.mul(-1)
|
||||
pendingTx.balance = senderBalance.balance
|
||||
pendingTx.balanceDate = creationDate
|
||||
pendingTx.decay = senderBalance ? senderBalance.decay.decay : new Decimal(0)
|
||||
pendingTx.decayStart = senderBalance ? senderBalance.decay.start : null
|
||||
if (receiverCom.communityUuid) {
|
||||
pendingTx.linkedUserCommunityUuid = receiverCom.communityUuid
|
||||
}
|
||||
if (voteResult.recipGradidoID) {
|
||||
pendingTx.linkedUserGradidoID = voteResult.recipGradidoID
|
||||
}
|
||||
if (voteResult.recipFirstName && voteResult.recipLastName) {
|
||||
pendingTx.linkedUserName = fullName(voteResult.recipFirstName, voteResult.recipLastName)
|
||||
}
|
||||
pendingTx.memo = memo
|
||||
pendingTx.previous = senderBalance ? senderBalance.lastTransactionId : null
|
||||
pendingTx.state = PendingTransactionState.NEW
|
||||
pendingTx.typeId = TransactionTypeId.SEND
|
||||
if (senderCom.communityUuid) {
|
||||
pendingTx.userCommunityUuid = senderCom.communityUuid
|
||||
}
|
||||
pendingTx.userId = sender.id
|
||||
pendingTx.userGradidoID = sender.gradidoID
|
||||
pendingTx.userName = fullName(sender.firstName, sender.lastName)
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`initialized sender pendingTX=${new PendingTransactionLoggingView(pendingTx)}`)
|
||||
}
|
||||
|
||||
await DbPendingTransaction.insert(pendingTx)
|
||||
methodLogger.debug('sender pendingTx successfully inserted...')
|
||||
} catch (err) {
|
||||
methodLogger.error(`Error in writing sender pending transaction: ${JSON.stringify(err, null, 2)}`)
|
||||
// revert the existing pending transaction on receiver side
|
||||
let revertCount = 0
|
||||
methodLogger.debug('first try to revertSendCoins of receiver')
|
||||
do {
|
||||
if (await client.revertSendCoins(args)) {
|
||||
methodLogger.debug(`revertSendCoins()-1_0... successfull after revertCount=${revertCount}`)
|
||||
// treat revertingSendCoins as an error of the whole sendCoins-process
|
||||
const errmsg = `Error in writing sender pending transaction: ${JSON.stringify(err, null, 2)}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
} while (CONFIG.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS > revertCount++)
|
||||
const errmsg = `Error in reverting receiver pending transaction even after revertCount=${revertCount}` + JSON.stringify(err, null, 2)
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
methodLogger.debug('voteForSendCoins()-1_0... successfull')
|
||||
return voteResult
|
||||
} else {
|
||||
methodLogger.error(`break with error on writing pendingTransaction for recipient... ${voteResult}`)
|
||||
}
|
||||
} else {
|
||||
methodLogger.error(`break with no response from voteForSendCoins()-1_0...`)
|
||||
}
|
||||
return voteResult
|
||||
}
|
||||
} catch (err: any) {
|
||||
throw new LogError(`Error: ${err.message}`, err)
|
||||
} catch (err: any) {
|
||||
const errmsg = `Error: ${err.message}` + JSON.stringify(err, null, 2)
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
return new SendCoinsResult()
|
||||
return null
|
||||
}
|
||||
|
||||
export async function processXComCommittingSendCoins(
|
||||
@ -171,10 +204,13 @@ export async function processXComCommittingSendCoins(
|
||||
sender: dbUser,
|
||||
recipient: SendCoinsResult,
|
||||
): Promise<SendCoinsResult> {
|
||||
const methodLogger = createLogger(`processXComCommittingSendCoins`)
|
||||
const handshakeID = randombytes_random().toString()
|
||||
methodLogger.addContext('handshakeID', handshakeID)
|
||||
const sendCoinsResult = new SendCoinsResult()
|
||||
try {
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug(
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(
|
||||
'XCom: processXComCommittingSendCoins...', {
|
||||
receiverCom: new CommunityLoggingView(receiverCom),
|
||||
senderCom: new CommunityLoggingView(senderCom),
|
||||
@ -200,40 +236,49 @@ export async function processXComCommittingSendCoins(
|
||||
memo,
|
||||
})
|
||||
if (pendingTx) {
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug(`find pending Tx for settlement: ${new PendingTransactionLoggingView(pendingTx)}`)
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`find pending Tx for settlement: ${new PendingTransactionLoggingView(pendingTx)}`)
|
||||
}
|
||||
const receiverFCom = await DbFederatedCommunity.findOneOrFail({
|
||||
where: {
|
||||
publicKey: Buffer.from(receiverCom.publicKey),
|
||||
|
||||
apiVersion: CONFIG.FEDERATION_BACKEND_SEND_ON_API,
|
||||
},
|
||||
})
|
||||
const client = SendCoinsClientFactory.getInstance(receiverFCom)
|
||||
|
||||
if (client instanceof V1_0_SendCoinsClient) {
|
||||
const args = new SendCoinsArgs()
|
||||
args.recipientCommunityUuid = pendingTx.linkedUserCommunityUuid
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
handshakeID,
|
||||
pendingTx.linkedUserCommunityUuid
|
||||
? pendingTx.linkedUserCommunityUuid
|
||||
: CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID,
|
||||
pendingTx.linkedUserGradidoID!,
|
||||
pendingTx.balanceDate.toISOString(),
|
||||
pendingTx.amount.mul(-1),
|
||||
pendingTx.memo,
|
||||
pendingTx.userCommunityUuid,
|
||||
pendingTx.userGradidoID!,
|
||||
pendingTx.userName!,
|
||||
sender.alias,
|
||||
)
|
||||
payload.recipientCommunityUuid = pendingTx.linkedUserCommunityUuid
|
||||
? pendingTx.linkedUserCommunityUuid
|
||||
: CONFIG.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID
|
||||
if (pendingTx.linkedUserGradidoID) {
|
||||
args.recipientUserIdentifier = pendingTx.linkedUserGradidoID
|
||||
payload.recipientUserIdentifier = pendingTx.linkedUserGradidoID
|
||||
}
|
||||
args.creationDate = pendingTx.balanceDate.toISOString()
|
||||
args.amount = pendingTx.amount.mul(-1)
|
||||
args.memo = pendingTx.memo
|
||||
args.senderCommunityUuid = pendingTx.userCommunityUuid
|
||||
args.senderUserUuid = pendingTx.userGradidoID
|
||||
if (pendingTx.userName) {
|
||||
args.senderUserName = pendingTx.userName
|
||||
}
|
||||
args.senderAlias = sender.alias
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug(`ready for settleSendCoins with args=${new SendCoinsArgsLoggingView(args)}`)
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`ready for settleSendCoins with payload=${ JSON.stringify(payload)}`)
|
||||
}
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, receiverCom.publicJwtKey!)
|
||||
// prepare the args for the client invocation
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = handshakeID
|
||||
const acknowledge = await client.settleSendCoins(args)
|
||||
logger.debug(`returnd from settleSendCoins: ${acknowledge}`)
|
||||
methodLogger.debug(`return from settleSendCoins: ${acknowledge}`)
|
||||
if (acknowledge) {
|
||||
// settle the pending transaction on receiver-side was successfull, so now settle the sender side
|
||||
try {
|
||||
@ -257,13 +302,13 @@ export async function processXComCommittingSendCoins(
|
||||
sendCoinsResult.recipAlias = recipient.recipAlias
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`Error in writing sender pending transaction: ${JSON.stringify(err, null, 2)}`)
|
||||
methodLogger.error(`Error in writing sender pending transaction: ${JSON.stringify(err, null, 2)}`)
|
||||
// revert the existing pending transaction on receiver side
|
||||
let revertCount = 0
|
||||
logger.debug('first try to revertSetteledSendCoins of receiver')
|
||||
methodLogger.debug('first try to revertSetteledSendCoins of receiver')
|
||||
do {
|
||||
if (await client.revertSettledSendCoins(args)) {
|
||||
logger.debug(
|
||||
methodLogger.debug(
|
||||
`revertSettledSendCoins()-1_0... successfull after revertCount=${revertCount}`,
|
||||
)
|
||||
// treat revertingSettledSendCoins as an error of the whole sendCoins-process
|
||||
@ -279,7 +324,7 @@ export async function processXComCommittingSendCoins(
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`Error: ${JSON.stringify(err, null, 2)}`)
|
||||
methodLogger.error(`Error: ${JSON.stringify(err, null, 2)}`)
|
||||
sendCoinsResult.vote = false
|
||||
}
|
||||
return sendCoinsResult
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import {
|
||||
AppDatabase,
|
||||
CommunityLoggingView,
|
||||
Community as DbCommunity,
|
||||
PendingTransaction as DbPendingTransaction,
|
||||
User as DbUser,
|
||||
PendingTransactionLoggingView,
|
||||
UserLoggingView,
|
||||
Transaction as dbTransaction,
|
||||
} from 'database'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
@ -34,7 +37,7 @@ export async function settlePendingSenderTransaction(
|
||||
logger.debug(`start Transaction for write-access...`)
|
||||
|
||||
try {
|
||||
logger.info('settlePendingSenderTransaction:', homeCom, senderUser, pendingTx)
|
||||
logger.info('settlePendingSenderTransaction:', new CommunityLoggingView(homeCom), new UserLoggingView(senderUser), new PendingTransactionLoggingView(pendingTx))
|
||||
|
||||
// ensure that no other pendingTx with the same sender or recipient exists
|
||||
const openSenderPendingTx = await DbPendingTransaction.count({
|
||||
@ -88,7 +91,7 @@ export async function settlePendingSenderTransaction(
|
||||
transactionSend.previous = pendingTx.previous
|
||||
transactionSend.linkedTransactionId = pendingTx.linkedTransactionId
|
||||
await queryRunner.manager.insert(dbTransaction, transactionSend)
|
||||
logger.debug(`send Transaction inserted: ${dbTransaction}`)
|
||||
logger.debug(`send Transaction inserted: ${transactionSend}`)
|
||||
|
||||
// and mark the pendingTx in the pending_transactions table as settled
|
||||
pendingTx.state = PendingTransactionState.SETTLED
|
||||
|
||||
@ -15,13 +15,13 @@ export const interpretEncryptedTransferArgs = async (args: EncryptedTransferArgs
|
||||
// first find with args.publicKey the community 'requestingCom', which starts the request
|
||||
const requestingCom = await DbCommunity.findOneBy({ publicKey: Buffer.from(args.publicKey, 'hex') })
|
||||
if (!requestingCom) {
|
||||
const errmsg = `unknown requesting community with publicKey ${args.publicKey}`
|
||||
const errmsg = `unknown requesting community with publicKey ${Buffer.from(args.publicKey, 'hex')}`
|
||||
methodLogger.error(errmsg)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
if (!requestingCom.publicJwtKey) {
|
||||
const errmsg = `missing publicJwtKey of requesting community with publicKey ${args.publicKey}`
|
||||
const errmsg = `missing publicJwtKey of requesting community with publicKey ${Buffer.from(args.publicKey, 'hex')}`
|
||||
methodLogger.error(errmsg)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
throw new Error(errmsg)
|
||||
@ -31,7 +31,7 @@ export const interpretEncryptedTransferArgs = async (args: EncryptedTransferArgs
|
||||
const homeCom = await getHomeCommunity()
|
||||
const jwtPayload = await verifyAndDecrypt(args.handshakeID, args.jwt, homeCom!.privateJwtKey!, requestingCom.publicJwtKey) as JwtPayloadType
|
||||
if (!jwtPayload) {
|
||||
const errmsg = `invalid payload of community with publicKey ${args.publicKey}`
|
||||
const errmsg = `invalid payload of community with publicKey ${Buffer.from(args.publicKey, 'hex')}`
|
||||
methodLogger.error(errmsg)
|
||||
methodLogger.removeContext('handshakeID')
|
||||
throw new Error(errmsg)
|
||||
|
||||
@ -48,7 +48,7 @@ export const startDHT = async (topic: string): Promise<void> => {
|
||||
) as KeyPair
|
||||
const pubKeyString = keyPair.publicKey.toString('hex')
|
||||
logger.info(`keyPairDHT: publicKey=${pubKeyString}`)
|
||||
logger.debug(`keyPairDHT: secretKey=${keyPair.secretKey.toString('hex')}`)
|
||||
logger.debug(`keyPairDHT: secretKey=${keyPair.secretKey.toString('hex').slice(0, 6)}`)
|
||||
await writeHomeCommunityEntry(keyPair)
|
||||
|
||||
const ownApiVersions = await writeFederatedHomeCommunityEntries(pubKeyString)
|
||||
|
||||
@ -31,7 +31,7 @@ export class AuthenticationClient {
|
||||
methodLogger.addContext('handshakeID', args.handshakeID)
|
||||
methodLogger.debug('openConnectionCallback with endpoint', this.endpoint, args)
|
||||
try {
|
||||
const { data } = await this.client.rawRequest<any>(openConnectionCallback, { args })
|
||||
const { data } = await this.client.rawRequest<{ openConnectionCallback: boolean }>(openConnectionCallback, { args })
|
||||
methodLogger.debug('after openConnectionCallback: data:', data)
|
||||
|
||||
if (!data || !data.openConnectionCallback) {
|
||||
@ -51,13 +51,13 @@ export class AuthenticationClient {
|
||||
methodLogger.addContext('handshakeID', args.handshakeID)
|
||||
methodLogger.debug('authenticate with endpoint=', this.endpoint)
|
||||
try {
|
||||
const { data } = await this.client.rawRequest<any>(authenticate, { args })
|
||||
const { data } = await this.client.rawRequest<{ authenticate: string }>(authenticate, { args })
|
||||
methodLogger.debug('after authenticate: data:', data)
|
||||
|
||||
const authUuid: string = data?.authenticate
|
||||
if (authUuid) {
|
||||
methodLogger.debug('received authenticated uuid', authUuid)
|
||||
return authUuid
|
||||
const responseJwt = data?.authenticate
|
||||
if (responseJwt) {
|
||||
methodLogger.debug('received authenticated uuid as jwt', responseJwt)
|
||||
return responseJwt
|
||||
}
|
||||
} catch (err) {
|
||||
methodLogger.error('authenticate failed', {
|
||||
|
||||
@ -8,8 +8,8 @@ import Decimal from 'decimal.js-light'
|
||||
import { GraphQLError } from 'graphql'
|
||||
import { getLogger } from 'log4js'
|
||||
import { DataSource } from 'typeorm'
|
||||
import { SendCoinsArgs } from '../model/SendCoinsArgs'
|
||||
|
||||
import { EncryptedTransferArgs } from 'core'
|
||||
import { createKeyPair, encryptAndSign, SendCoinsJwtPayloadType, SendCoinsResponseJwtPayloadType, verifyAndDecrypt } from 'shared'
|
||||
let mutate: ApolloServerTestClient['mutate'] // , con: Connection
|
||||
// let query: ApolloServerTestClient['query']
|
||||
|
||||
@ -21,8 +21,8 @@ let testEnv: {
|
||||
|
||||
CONFIG.FEDERATION_API = '1_0'
|
||||
|
||||
let homeCom: DbCommunity
|
||||
let foreignCom: DbCommunity
|
||||
let recipientCom: DbCommunity
|
||||
let senderCom: DbCommunity
|
||||
let sendUser: DbUser
|
||||
let sendContact: DbUserContact
|
||||
let recipUser: DbUser
|
||||
@ -37,7 +37,7 @@ beforeAll(async () => {
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
// await cleanDB()
|
||||
await cleanDB()
|
||||
if (testEnv.con?.isInitialized) {
|
||||
await testEnv.con.destroy()
|
||||
}
|
||||
@ -45,53 +45,54 @@ afterAll(async () => {
|
||||
|
||||
describe('SendCoinsResolver', () => {
|
||||
const voteForSendCoinsMutation = `
|
||||
mutation ($args: SendCoinsArgs!) {
|
||||
voteForSendCoins(data: $args) {
|
||||
vote
|
||||
recipGradidoID
|
||||
recipFirstName
|
||||
recipLastName
|
||||
recipAlias
|
||||
}
|
||||
mutation ($args: EncryptedTransferArgs!) {
|
||||
voteForSendCoins(data: $args)
|
||||
}`
|
||||
const settleSendCoinsMutation = `
|
||||
mutation ($args: SendCoinsArgs!) {
|
||||
mutation ($args: EncryptedTransferArgs!) {
|
||||
settleSendCoins(data: $args)
|
||||
}`
|
||||
const revertSendCoinsMutation = `
|
||||
mutation ($args: SendCoinsArgs!) {
|
||||
mutation ($args: EncryptedTransferArgs!) {
|
||||
revertSendCoins(data: $args)
|
||||
}`
|
||||
const revertSettledSendCoinsMutation = `
|
||||
mutation ($args: SendCoinsArgs!) {
|
||||
mutation ($args: EncryptedTransferArgs!) {
|
||||
revertSettledSendCoins(data: $args)
|
||||
}`
|
||||
|
||||
beforeEach(async () => {
|
||||
await cleanDB()
|
||||
homeCom = DbCommunity.create()
|
||||
homeCom.foreign = false
|
||||
homeCom.url = 'homeCom-url'
|
||||
homeCom.name = 'homeCom-Name'
|
||||
homeCom.description = 'homeCom-Description'
|
||||
homeCom.creationDate = new Date()
|
||||
homeCom.publicKey = Buffer.from('homeCom-publicKey')
|
||||
homeCom.communityUuid = '56a55482-909e-46a4-bfa2-cd025e894eba'
|
||||
await DbCommunity.insert(homeCom)
|
||||
// Generate key pair using jose library
|
||||
const { publicKey: homePublicKey, privateKey: homePrivateKey } = await createKeyPair();
|
||||
recipientCom = DbCommunity.create()
|
||||
recipientCom.foreign = false
|
||||
recipientCom.url = 'homeCom-url'
|
||||
recipientCom.name = 'homeCom-Name'
|
||||
recipientCom.description = 'homeCom-Description'
|
||||
recipientCom.creationDate = new Date()
|
||||
recipientCom.publicKey = Buffer.alloc(32, '15F92F8EC2EA685D5FD51EE3588F5B4805EBD330EF9EDD16043F3BA9C35C0D91', 'hex') // 'homeCom-publicKey', 'hex')
|
||||
recipientCom.publicJwtKey = homePublicKey;
|
||||
recipientCom.privateJwtKey = homePrivateKey;
|
||||
recipientCom.communityUuid = '56a55482-909e-46a4-bfa2-cd025e894eba'
|
||||
await DbCommunity.insert(recipientCom)
|
||||
|
||||
foreignCom = DbCommunity.create()
|
||||
foreignCom.foreign = true
|
||||
foreignCom.url = 'foreignCom-url'
|
||||
foreignCom.name = 'foreignCom-Name'
|
||||
foreignCom.description = 'foreignCom-Description'
|
||||
foreignCom.creationDate = new Date()
|
||||
foreignCom.publicKey = Buffer.from('foreignCom-publicKey')
|
||||
foreignCom.communityUuid = '56a55482-909e-46a4-bfa2-cd025e894ebb'
|
||||
await DbCommunity.insert(foreignCom)
|
||||
const { publicKey: foreignPublicKey, privateKey: foreignPrivateKey } = await createKeyPair();
|
||||
senderCom = DbCommunity.create()
|
||||
senderCom.foreign = true
|
||||
senderCom.url = 'foreignCom-url'
|
||||
senderCom.name = 'foreignCom-Name'
|
||||
senderCom.description = 'foreignCom-Description'
|
||||
senderCom.creationDate = new Date()
|
||||
senderCom.publicKey = Buffer.alloc(32, '15F92F8EC2EA685D5FD51EE3588F5B4805EBD330EF9EDD16043F3BA9C35C0D92', 'hex') // 'foreignCom-publicKey', 'hex')
|
||||
senderCom.publicJwtKey = foreignPublicKey;
|
||||
senderCom.privateJwtKey = foreignPrivateKey;
|
||||
senderCom.communityUuid = '56a55482-909e-46a4-bfa2-cd025e894ebb'
|
||||
await DbCommunity.insert(senderCom)
|
||||
|
||||
sendUser = DbUser.create()
|
||||
sendUser.alias = 'sendUser-alias'
|
||||
sendUser.communityUuid = '56a55482-909e-46a4-bfa2-cd025e894eba'
|
||||
sendUser.communityUuid = '56a55482-909e-46a4-bfa2-cd025e894ebb'
|
||||
sendUser.firstName = 'sendUser-FirstName'
|
||||
sendUser.gradidoID = '56a55482-909e-46a4-bfa2-cd025e894ebc'
|
||||
sendUser.lastName = 'sendUser-LastName'
|
||||
@ -106,7 +107,7 @@ describe('SendCoinsResolver', () => {
|
||||
|
||||
recipUser = DbUser.create()
|
||||
recipUser.alias = 'recipUser-alias'
|
||||
recipUser.communityUuid = '56a55482-909e-46a4-bfa2-cd025e894ebb'
|
||||
recipUser.communityUuid = '56a55482-909e-46a4-bfa2-cd025e894eba'
|
||||
recipUser.firstName = 'recipUser-FirstName'
|
||||
recipUser.gradidoID = '56a55482-909e-46a4-bfa2-cd025e894ebd'
|
||||
recipUser.lastName = 'recipUser-LastName'
|
||||
@ -120,30 +121,37 @@ describe('SendCoinsResolver', () => {
|
||||
await DbUser.save(recipUser)
|
||||
})
|
||||
|
||||
describe('voteForSendCoins', () => {
|
||||
describe('voteForSendCoins', () => {
|
||||
describe('unknown recipient community', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
args.recipientCommunityUuid = 'invalid foreignCom'
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = new Date().toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
'invalid recipientCom',
|
||||
recipUser.gradidoID,
|
||||
new Date().toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
const graphQLResponse = await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
})
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
}),
|
||||
graphQLResponse,
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('voteForSendCoins with wrong recipientCommunityUuid')],
|
||||
errors: [new GraphQLError('voteForSendCoins with wrong recipientCommunityUuid: invalid recipientCom')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -152,20 +160,25 @@ describe('SendCoinsResolver', () => {
|
||||
describe('unknown recipient user', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = 'invalid recipient'
|
||||
args.creationDate = new Date().toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
recipientCom.communityUuid!,
|
||||
'invalid recipient',
|
||||
new Date().toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
@ -175,7 +188,7 @@ describe('SendCoinsResolver', () => {
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
'voteForSendCoins with unknown recipientUserIdentifier in the community=',
|
||||
'voteForSendCoins with unknown recipientUserIdentifier in the community=homeCom-Name',
|
||||
),
|
||||
],
|
||||
}),
|
||||
@ -186,36 +199,42 @@ describe('SendCoinsResolver', () => {
|
||||
describe('valid X-Com-TX voted per gradidoID', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = new Date().toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
recipientCom.communityUuid!,
|
||||
recipUser.gradidoID,
|
||||
new Date().toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
const responseJwt = await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
})
|
||||
const voteResult = await verifyAndDecrypt('handshakeID', responseJwt.data.voteForSendCoins, senderCom.privateJwtKey!, recipientCom.publicJwtKey!) as SendCoinsResponseJwtPayloadType
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
}),
|
||||
voteResult,
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
voteForSendCoins: {
|
||||
recipGradidoID: '56a55482-909e-46a4-bfa2-cd025e894ebd',
|
||||
recipFirstName: 'recipUser-FirstName',
|
||||
recipLastName: 'recipUser-LastName',
|
||||
recipAlias: 'recipUser-alias',
|
||||
vote: true,
|
||||
},
|
||||
},
|
||||
expiration: '10m',
|
||||
handshakeID: 'handshakeID',
|
||||
recipGradidoID: '56a55482-909e-46a4-bfa2-cd025e894ebd',
|
||||
recipFirstName: 'recipUser-FirstName',
|
||||
recipLastName: 'recipUser-LastName',
|
||||
recipAlias: 'recipUser-alias',
|
||||
tokentype: SendCoinsResponseJwtPayloadType.SEND_COINS_RESPONSE_TYPE,
|
||||
vote: true,
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -224,36 +243,39 @@ describe('SendCoinsResolver', () => {
|
||||
describe('valid X-Com-TX voted per alias', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipUser.alias
|
||||
args.creationDate = new Date().toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
recipientCom.communityUuid!,
|
||||
recipUser.alias,
|
||||
new Date().toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
const responseJwt = await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
})
|
||||
const voteResult = await verifyAndDecrypt('handshakeID', responseJwt.data.voteForSendCoins, senderCom.privateJwtKey!, recipientCom.publicJwtKey!) as SendCoinsResponseJwtPayloadType
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
}),
|
||||
voteResult,
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
voteForSendCoins: {
|
||||
recipGradidoID: '56a55482-909e-46a4-bfa2-cd025e894ebd',
|
||||
recipFirstName: 'recipUser-FirstName',
|
||||
recipLastName: 'recipUser-LastName',
|
||||
recipAlias: 'recipUser-alias',
|
||||
vote: true,
|
||||
},
|
||||
},
|
||||
recipGradidoID: '56a55482-909e-46a4-bfa2-cd025e894ebd',
|
||||
recipFirstName: 'recipUser-FirstName',
|
||||
recipLastName: 'recipUser-LastName',
|
||||
recipAlias: 'recipUser-alias',
|
||||
vote: true,
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -262,36 +284,40 @@ describe('SendCoinsResolver', () => {
|
||||
describe('valid X-Com-TX voted per email', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipContact.email
|
||||
args.creationDate = new Date().toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
|
||||
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
recipientCom.communityUuid!,
|
||||
recipContact.email,
|
||||
new Date().toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
const responseJwt = await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
})
|
||||
const voteResult = await verifyAndDecrypt('handshakeID', responseJwt.data.voteForSendCoins, senderCom.privateJwtKey!, recipientCom.publicJwtKey!) as SendCoinsResponseJwtPayloadType
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
}),
|
||||
voteResult,
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
data: {
|
||||
voteForSendCoins: {
|
||||
recipGradidoID: '56a55482-909e-46a4-bfa2-cd025e894ebd',
|
||||
recipFirstName: 'recipUser-FirstName',
|
||||
recipLastName: 'recipUser-LastName',
|
||||
recipAlias: 'recipUser-alias',
|
||||
vote: true,
|
||||
},
|
||||
},
|
||||
recipGradidoID: '56a55482-909e-46a4-bfa2-cd025e894ebd',
|
||||
recipFirstName: 'recipUser-FirstName',
|
||||
recipLastName: 'recipUser-LastName',
|
||||
recipAlias: 'recipUser-alias',
|
||||
vote: true,
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -302,20 +328,25 @@ describe('SendCoinsResolver', () => {
|
||||
const creationDate = new Date()
|
||||
|
||||
beforeEach(async () => {
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
recipientCom.communityUuid!,
|
||||
recipUser.gradidoID,
|
||||
creationDate.toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
@ -325,18 +356,25 @@ describe('SendCoinsResolver', () => {
|
||||
describe('unknown recipient community', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
args.recipientCommunityUuid = 'invalid foreignCom'
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
'invalid recipientCom',
|
||||
recipUser.gradidoID,
|
||||
creationDate.toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSendCoinsMutation,
|
||||
@ -344,7 +382,7 @@ describe('SendCoinsResolver', () => {
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('revertSendCoins with wrong recipientCommunityUuid')],
|
||||
errors: [new GraphQLError('revertSendCoins with wrong recipientCommunityUuid=invalid recipientCom')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -353,20 +391,25 @@ describe('SendCoinsResolver', () => {
|
||||
describe('unknown recipient user', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = 'invalid recipient'
|
||||
args.creationDate = creationDate.toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
recipientCom.communityUuid!,
|
||||
'invalid recipient',
|
||||
creationDate.toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSendCoinsMutation,
|
||||
@ -376,7 +419,7 @@ describe('SendCoinsResolver', () => {
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
'revertSendCoins with unknown recipientUserIdentifier in the community=',
|
||||
'revertSendCoins with unknown recipientUserIdentifier in the community=homeCom-Name',
|
||||
),
|
||||
],
|
||||
}),
|
||||
@ -387,20 +430,26 @@ describe('SendCoinsResolver', () => {
|
||||
describe('valid X-Com-TX reverted', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
recipientCom.communityUuid!,
|
||||
recipUser.gradidoID,
|
||||
creationDate.toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSendCoinsMutation,
|
||||
@ -421,20 +470,25 @@ describe('SendCoinsResolver', () => {
|
||||
const creationDate = new Date()
|
||||
|
||||
beforeEach(async () => {
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
recipientCom.communityUuid!,
|
||||
recipUser.gradidoID,
|
||||
creationDate.toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
@ -444,18 +498,24 @@ describe('SendCoinsResolver', () => {
|
||||
describe('unknown recipient community', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
args.recipientCommunityUuid = 'invalid foreignCom'
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
'invalid recipientCom',
|
||||
recipUser.gradidoID,
|
||||
creationDate.toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: settleSendCoinsMutation,
|
||||
@ -463,7 +523,7 @@ describe('SendCoinsResolver', () => {
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('settleSendCoins with wrong recipientCommunityUuid')],
|
||||
errors: [new GraphQLError('settleSendCoins with wrong recipientCommunityUuid=invalid recipientCom')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -472,20 +532,24 @@ describe('SendCoinsResolver', () => {
|
||||
describe('unknown recipient user', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = 'invalid recipient'
|
||||
args.creationDate = creationDate.toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
recipientCom.communityUuid!,
|
||||
'invalid recipient',
|
||||
creationDate.toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: settleSendCoinsMutation,
|
||||
@ -495,7 +559,7 @@ describe('SendCoinsResolver', () => {
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
'settleSendCoins with unknown recipientUserIdentifier in the community=',
|
||||
'settleSendCoins with unknown recipientUserIdentifier in the community=' + recipientCom.name,
|
||||
),
|
||||
],
|
||||
}),
|
||||
@ -506,20 +570,24 @@ describe('SendCoinsResolver', () => {
|
||||
describe('valid X-Com-TX settled', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
recipientCom.communityUuid!,
|
||||
recipUser.gradidoID,
|
||||
creationDate.toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: settleSendCoinsMutation,
|
||||
@ -540,20 +608,24 @@ describe('SendCoinsResolver', () => {
|
||||
const creationDate = new Date()
|
||||
|
||||
beforeEach(async () => {
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
recipientCom.communityUuid!,
|
||||
recipUser.gradidoID,
|
||||
creationDate.toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
await mutate({
|
||||
mutation: voteForSendCoinsMutation,
|
||||
variables: { args },
|
||||
@ -567,18 +639,24 @@ describe('SendCoinsResolver', () => {
|
||||
describe('unknown recipient community', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
args.recipientCommunityUuid = 'invalid foreignCom'
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
'invalid recipientCom',
|
||||
recipUser.gradidoID,
|
||||
creationDate.toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSettledSendCoinsMutation,
|
||||
@ -586,7 +664,7 @@ describe('SendCoinsResolver', () => {
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('revertSettledSendCoins with wrong recipientCommunityUuid')],
|
||||
errors: [new GraphQLError('revertSettledSendCoins with wrong recipientCommunityUuid=invalid recipientCom')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
@ -595,20 +673,24 @@ describe('SendCoinsResolver', () => {
|
||||
describe('unknown recipient user', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = 'invalid recipient'
|
||||
args.creationDate = creationDate.toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
recipientCom.communityUuid!,
|
||||
'invalid recipient',
|
||||
creationDate.toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSettledSendCoinsMutation,
|
||||
@ -618,7 +700,7 @@ describe('SendCoinsResolver', () => {
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
'revertSettledSendCoins with unknown recipientUserIdentifier in the community=',
|
||||
'revertSettledSendCoins with unknown recipientUserIdentifier in the community=' + recipientCom.name,
|
||||
),
|
||||
],
|
||||
}),
|
||||
@ -629,20 +711,24 @@ describe('SendCoinsResolver', () => {
|
||||
describe('valid X-Com-TX settled', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
const args = new SendCoinsArgs()
|
||||
if (foreignCom.communityUuid) {
|
||||
args.recipientCommunityUuid = foreignCom.communityUuid
|
||||
}
|
||||
args.recipientUserIdentifier = recipUser.gradidoID
|
||||
args.creationDate = creationDate.toISOString()
|
||||
args.amount = new Decimal(100)
|
||||
args.memo = 'X-Com-TX memo'
|
||||
if (homeCom.communityUuid) {
|
||||
args.senderCommunityUuid = homeCom.communityUuid
|
||||
}
|
||||
args.senderUserUuid = sendUser.gradidoID
|
||||
args.senderUserName = fullName(sendUser.firstName, sendUser.lastName)
|
||||
args.senderAlias = sendUser.alias
|
||||
const payload = new SendCoinsJwtPayloadType(
|
||||
'handshakeID',
|
||||
recipientCom.communityUuid!,
|
||||
recipUser.gradidoID,
|
||||
creationDate.toISOString(),
|
||||
new Decimal(100),
|
||||
'X-Com-TX memo',
|
||||
senderCom.communityUuid!,
|
||||
sendUser.gradidoID,
|
||||
fullName(sendUser.firstName, sendUser.lastName),
|
||||
sendUser.alias
|
||||
)
|
||||
// invoke encryption as beeing on the foreignCom side to find in voteForSendCoins the correct homeCom
|
||||
const jws = await encryptAndSign(payload, senderCom.privateJwtKey!, recipientCom.publicJwtKey!)
|
||||
const args = new EncryptedTransferArgs()
|
||||
args.publicKey = senderCom.publicKey.toString('hex')
|
||||
args.jwt = jws
|
||||
args.handshakeID = 'handshakeID'
|
||||
expect(
|
||||
await mutate({
|
||||
mutation: revertSettledSendCoinsMutation,
|
||||
|
||||
@ -10,294 +10,363 @@ import Decimal from 'decimal.js-light'
|
||||
import { getLogger } from 'log4js'
|
||||
import { Arg, Mutation, Resolver } from 'type-graphql'
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { PendingTransactionState } from 'shared'
|
||||
import { encryptAndSign, PendingTransactionState, verifyAndDecrypt } from 'shared'
|
||||
import { TransactionTypeId } from '../enum/TransactionTypeId'
|
||||
import { SendCoinsArgsLoggingView } from '../logger/SendCoinsArgsLogging.view'
|
||||
import { SendCoinsArgs } from '../model/SendCoinsArgs'
|
||||
import { SendCoinsResult } from '../model/SendCoinsResult'
|
||||
import { SendCoinsResponseJwtPayloadType } from 'shared'
|
||||
import { calculateRecipientBalance } from '../util/calculateRecipientBalance'
|
||||
// import { checkTradingLevel } from '@/graphql/util/checkTradingLevel'
|
||||
import { revertSettledReceiveTransaction } from '../util/revertSettledReceiveTransaction'
|
||||
import { settlePendingReceiveTransaction } from '../util/settlePendingReceiveTransaction'
|
||||
import { storeForeignUser } from '../util/storeForeignUser'
|
||||
import { countOpenPendingTransactions } from 'database'
|
||||
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.api.1_0.resolver.SendCoinsResolver`)
|
||||
import { EncryptedTransferArgs } from 'core'
|
||||
import { interpretEncryptedTransferArgs } from 'core'
|
||||
import { SendCoinsJwtPayloadType } from 'shared'
|
||||
const createLogger = (method: string) => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.api.1_0.resolver.SendCoinsResolver.${method}`)
|
||||
|
||||
@Resolver()
|
||||
export class SendCoinsResolver {
|
||||
@Mutation(() => SendCoinsResult)
|
||||
@Mutation(() => String)
|
||||
async voteForSendCoins(
|
||||
@Arg('data')
|
||||
args: SendCoinsArgs,
|
||||
): Promise<SendCoinsResult> {
|
||||
logger.debug(`voteForSendCoins() via apiVersion=1_0 ...`, new SendCoinsArgsLoggingView(args))
|
||||
const result = new SendCoinsResult()
|
||||
args: EncryptedTransferArgs,
|
||||
): Promise<string> {
|
||||
const methodLogger = createLogger(`voteForSendCoins`)
|
||||
methodLogger.addContext('handshakeID', args.handshakeID)
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`voteForSendCoins() via apiVersion=1_0 ...`, args)
|
||||
}
|
||||
const authArgs = await interpretEncryptedTransferArgs(args) as SendCoinsJwtPayloadType
|
||||
if (!authArgs) {
|
||||
const errmsg = `invalid authentication payload of requesting community with publicKey` + args.publicKey
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`voteForSendCoins() via apiVersion=1_0 ...`, authArgs)
|
||||
}
|
||||
// first check if receiver community is correct
|
||||
const homeCom = await DbCommunity.findOneBy({
|
||||
communityUuid: args.recipientCommunityUuid,
|
||||
const recipientCom = await DbCommunity.findOneBy({
|
||||
communityUuid: authArgs.recipientCommunityUuid,
|
||||
})
|
||||
if (!homeCom) {
|
||||
throw new LogError(
|
||||
`voteForSendCoins with wrong recipientCommunityUuid`,
|
||||
args.recipientCommunityUuid,
|
||||
)
|
||||
if (!recipientCom) {
|
||||
const errmsg = `voteForSendCoins with wrong recipientCommunityUuid: ${authArgs.recipientCommunityUuid}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
const senderCom = await DbCommunity.findOneBy({
|
||||
communityUuid: authArgs.senderCommunityUuid,
|
||||
})
|
||||
if (!senderCom) {
|
||||
const errmsg = `voteForSendCoins with wrong senderCommunityUuid: ${authArgs.senderCommunityUuid}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
let receiverUser
|
||||
|
||||
// second check if receiver user exists in this community
|
||||
receiverUser = await findUserByIdentifier(
|
||||
args.recipientUserIdentifier,
|
||||
args.recipientCommunityUuid,
|
||||
authArgs.recipientUserIdentifier,
|
||||
authArgs.recipientCommunityUuid,
|
||||
)
|
||||
if (!receiverUser) {
|
||||
logger.error('Error in findUserByIdentifier:')
|
||||
throw new LogError(
|
||||
`voteForSendCoins with unknown recipientUserIdentifier in the community=`,
|
||||
homeCom.name,
|
||||
)
|
||||
const errmsg = `voteForSendCoins with unknown recipientUserIdentifier in the community=` + recipientCom.name
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
|
||||
if (await countOpenPendingTransactions([args.senderUserUuid, receiverUser.gradidoID]) > 0) {
|
||||
throw new LogError(
|
||||
`There exist still ongoing 'Pending-Transactions' for the involved users on receiver-side!`,
|
||||
)
|
||||
if (await countOpenPendingTransactions([authArgs.senderUserUuid, receiverUser.gradidoID]) > 0) {
|
||||
const errmsg = `There exist still ongoing 'Pending-Transactions' for the involved users on receiver-side!`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
|
||||
try {
|
||||
const txDate = new Date(args.creationDate)
|
||||
const receiveBalance = await calculateRecipientBalance(receiverUser.id, args.amount, txDate)
|
||||
const txDate = new Date(authArgs.creationDate)
|
||||
const receiveBalance = await calculateRecipientBalance(receiverUser.id, authArgs.amount, txDate)
|
||||
const pendingTx = DbPendingTransaction.create()
|
||||
pendingTx.amount = args.amount
|
||||
pendingTx.balance = receiveBalance ? receiveBalance.balance : args.amount
|
||||
pendingTx.amount = authArgs.amount
|
||||
pendingTx.balance = receiveBalance ? receiveBalance.balance : authArgs.amount
|
||||
pendingTx.balanceDate = txDate
|
||||
pendingTx.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0)
|
||||
pendingTx.decayStart = receiveBalance ? receiveBalance.decay.start : null
|
||||
pendingTx.creationDate = new Date()
|
||||
pendingTx.linkedUserCommunityUuid = args.senderCommunityUuid
|
||||
pendingTx.linkedUserGradidoID = args.senderUserUuid
|
||||
pendingTx.linkedUserName = args.senderUserName
|
||||
pendingTx.memo = args.memo
|
||||
pendingTx.linkedUserCommunityUuid = authArgs.senderCommunityUuid
|
||||
pendingTx.linkedUserGradidoID = authArgs.senderUserUuid
|
||||
pendingTx.linkedUserName = authArgs.senderUserName
|
||||
pendingTx.memo = authArgs.memo
|
||||
pendingTx.previous = receiveBalance ? receiveBalance.lastTransactionId : null
|
||||
pendingTx.state = PendingTransactionState.NEW
|
||||
pendingTx.typeId = TransactionTypeId.RECEIVE
|
||||
pendingTx.userId = receiverUser.id
|
||||
pendingTx.userCommunityUuid = args.recipientCommunityUuid
|
||||
pendingTx.userCommunityUuid = authArgs.recipientCommunityUuid
|
||||
pendingTx.userGradidoID = receiverUser.gradidoID
|
||||
pendingTx.userName = fullName(receiverUser.firstName, receiverUser.lastName)
|
||||
|
||||
await DbPendingTransaction.insert(pendingTx)
|
||||
result.vote = true
|
||||
result.recipFirstName = receiverUser.firstName
|
||||
result.recipLastName = receiverUser.lastName
|
||||
result.recipAlias = receiverUser.alias
|
||||
result.recipGradidoID = receiverUser.gradidoID
|
||||
logger.debug(`voteForSendCoins()-1_0... successfull`)
|
||||
const responseArgs = new SendCoinsResponseJwtPayloadType(
|
||||
authArgs.handshakeID,
|
||||
true,
|
||||
receiverUser.gradidoID,
|
||||
receiverUser.firstName,
|
||||
receiverUser.lastName,
|
||||
receiverUser.alias,
|
||||
)
|
||||
const responseJwt = await encryptAndSign(responseArgs, recipientCom.privateJwtKey!, senderCom.publicJwtKey!)
|
||||
methodLogger.debug(`voteForSendCoins()-1_0... successfull`)
|
||||
return responseJwt
|
||||
} catch (err) {
|
||||
throw new LogError(`Error in voteForSendCoins: `, err)
|
||||
const errmsg = `Error in voteForSendCoins: ` + err
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async revertSendCoins(
|
||||
@Arg('data')
|
||||
args: SendCoinsArgs,
|
||||
args: EncryptedTransferArgs,
|
||||
): Promise<boolean> {
|
||||
logger.debug(`revertSendCoins() via apiVersion=1_0 ...`)
|
||||
const methodLogger = createLogger(`revertSendCoins`)
|
||||
methodLogger.addContext('handshakeID', args.handshakeID)
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`revertSendCoins() via apiVersion=1_0 ...`)
|
||||
}
|
||||
const authArgs = await interpretEncryptedTransferArgs(args) as SendCoinsJwtPayloadType
|
||||
if (!authArgs) {
|
||||
const errmsg = `invalid revertSendCoins payload of requesting community with publicKey=${args.publicKey}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`revertSendCoins() via apiVersion=1_0 ...`, authArgs)
|
||||
}
|
||||
// first check if receiver community is correct
|
||||
const homeCom = await DbCommunity.findOneBy({
|
||||
communityUuid: args.recipientCommunityUuid,
|
||||
communityUuid: authArgs.recipientCommunityUuid,
|
||||
})
|
||||
if (!homeCom) {
|
||||
throw new LogError(
|
||||
`revertSendCoins with wrong recipientCommunityUuid`,
|
||||
args.recipientCommunityUuid,
|
||||
)
|
||||
const errmsg = `revertSendCoins with wrong recipientCommunityUuid=${authArgs.recipientCommunityUuid}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
let receiverUser
|
||||
|
||||
// second check if receiver user exists in this community
|
||||
receiverUser = await findUserByIdentifier(args.recipientUserIdentifier)
|
||||
receiverUser = await findUserByIdentifier(authArgs.recipientUserIdentifier)
|
||||
if (!receiverUser) {
|
||||
logger.error('Error in findUserByIdentifier')
|
||||
throw new LogError(
|
||||
`revertSendCoins with unknown recipientUserIdentifier in the community=`,
|
||||
homeCom.name,
|
||||
)
|
||||
const errmsg = `revertSendCoins with unknown recipientUserIdentifier in the community=${homeCom.name}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
try {
|
||||
const pendingTx = await DbPendingTransaction.findOneBy({
|
||||
userCommunityUuid: args.recipientCommunityUuid,
|
||||
userCommunityUuid: authArgs.recipientCommunityUuid,
|
||||
userGradidoID: receiverUser.gradidoID,
|
||||
state: PendingTransactionState.NEW,
|
||||
typeId: TransactionTypeId.RECEIVE,
|
||||
balanceDate: new Date(args.creationDate),
|
||||
linkedUserCommunityUuid: args.senderCommunityUuid,
|
||||
linkedUserGradidoID: args.senderUserUuid,
|
||||
balanceDate: new Date(authArgs.creationDate),
|
||||
linkedUserCommunityUuid: authArgs.senderCommunityUuid,
|
||||
linkedUserGradidoID: authArgs.senderUserUuid,
|
||||
})
|
||||
logger.debug(
|
||||
'XCom: revertSendCoins found pendingTX=',
|
||||
pendingTx ? new PendingTransactionLoggingView(pendingTx) : 'null',
|
||||
)
|
||||
if (pendingTx && pendingTx.amount.toString() === args.amount.toString()) {
|
||||
logger.debug('XCom: revertSendCoins matching pendingTX for remove...')
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(
|
||||
'XCom: revertSendCoins found pendingTX=',
|
||||
pendingTx ? new PendingTransactionLoggingView(pendingTx) : 'null',
|
||||
)
|
||||
}
|
||||
if (pendingTx && pendingTx.amount.toString() === authArgs.amount.toString()) {
|
||||
methodLogger.debug('XCom: revertSendCoins matching pendingTX for remove...')
|
||||
try {
|
||||
await pendingTx.remove()
|
||||
logger.debug('XCom: revertSendCoins pendingTX for remove successfully')
|
||||
methodLogger.debug('XCom: revertSendCoins pendingTX for remove successfully')
|
||||
} catch (err) {
|
||||
throw new LogError('Error in revertSendCoins on removing pendingTx of receiver: ', err)
|
||||
const errmsg = `Error in revertSendCoins on removing pendingTx of receiver: ` + err
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
} else {
|
||||
logger.debug(
|
||||
methodLogger.debug(
|
||||
'XCom: revertSendCoins NOT matching pendingTX for remove:',
|
||||
pendingTx?.amount.toString(),
|
||||
args.amount.toString(),
|
||||
authArgs.amount.toString(),
|
||||
)
|
||||
throw new LogError(`Can't find in revertSendCoins the pending receiver TX for `, {
|
||||
args: new SendCoinsArgsLoggingView(args),
|
||||
const errmsg = `Can't find in revertSendCoins the pending receiver TX for ` + {
|
||||
args: new SendCoinsArgsLoggingView(authArgs),
|
||||
pendingTransactionState: PendingTransactionState.NEW,
|
||||
transactionType: TransactionTypeId.RECEIVE,
|
||||
})
|
||||
}
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
logger.debug(`revertSendCoins()-1_0... successfull`)
|
||||
methodLogger.debug(`revertSendCoins()-1_0... successfull`)
|
||||
return true
|
||||
} catch (err) {
|
||||
throw new LogError(`Error in revertSendCoins: `, err)
|
||||
const errmsg = `Error in revertSendCoins: ` + err
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async settleSendCoins(
|
||||
@Arg('data')
|
||||
args: SendCoinsArgs,
|
||||
args: EncryptedTransferArgs,
|
||||
): Promise<boolean> {
|
||||
logger.debug(`settleSendCoins() via apiVersion=1_0 ...`, new SendCoinsArgsLoggingView(args))
|
||||
const methodLogger = createLogger(`settleSendCoins`)
|
||||
methodLogger.addContext('handshakeID', args.handshakeID)
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`settleSendCoins() via apiVersion=1_0 ...`, args)
|
||||
}
|
||||
const authArgs = await interpretEncryptedTransferArgs(args) as SendCoinsJwtPayloadType
|
||||
if (!authArgs) {
|
||||
const errmsg = `invalid settleSendCoins payload of requesting community with publicKey=${args.publicKey}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`settleSendCoins() via apiVersion=1_0 ...`, authArgs)
|
||||
}
|
||||
// first check if receiver community is correct
|
||||
const homeCom = await DbCommunity.findOneBy({
|
||||
communityUuid: args.recipientCommunityUuid,
|
||||
communityUuid: authArgs.recipientCommunityUuid,
|
||||
})
|
||||
if (!homeCom) {
|
||||
throw new LogError(
|
||||
`settleSendCoins with wrong recipientCommunityUuid`,
|
||||
args.recipientCommunityUuid,
|
||||
)
|
||||
const errmsg = `settleSendCoins with wrong recipientCommunityUuid=${authArgs.recipientCommunityUuid}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
|
||||
// second check if receiver user exists in this community
|
||||
const receiverUser = await findUserByIdentifier(args.recipientUserIdentifier)
|
||||
const receiverUser = await findUserByIdentifier(authArgs.recipientUserIdentifier)
|
||||
if (!receiverUser) {
|
||||
logger.error('Error in findUserByIdentifier')
|
||||
throw new LogError(
|
||||
`settleSendCoins with unknown recipientUserIdentifier in the community=`,
|
||||
homeCom.name,
|
||||
)
|
||||
const errmsg = `settleSendCoins with unknown recipientUserIdentifier in the community=${homeCom.name}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
const pendingTx = await DbPendingTransaction.findOneBy({
|
||||
userCommunityUuid: args.recipientCommunityUuid,
|
||||
userCommunityUuid: authArgs.recipientCommunityUuid,
|
||||
userGradidoID: receiverUser.gradidoID,
|
||||
state: PendingTransactionState.NEW,
|
||||
typeId: TransactionTypeId.RECEIVE,
|
||||
balanceDate: new Date(args.creationDate),
|
||||
linkedUserCommunityUuid: args.senderCommunityUuid,
|
||||
linkedUserGradidoID: args.senderUserUuid,
|
||||
balanceDate: new Date(authArgs.creationDate),
|
||||
linkedUserCommunityUuid: authArgs.senderCommunityUuid,
|
||||
linkedUserGradidoID: authArgs.senderUserUuid,
|
||||
})
|
||||
logger.debug(
|
||||
'XCom: settleSendCoins found pendingTX=',
|
||||
pendingTx ? new PendingTransactionLoggingView(pendingTx) : 'null',
|
||||
)
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(
|
||||
'XCom: settleSendCoins found pendingTX=',
|
||||
pendingTx ? new PendingTransactionLoggingView(pendingTx) : 'null',
|
||||
)
|
||||
}
|
||||
if (
|
||||
pendingTx &&
|
||||
pendingTx.amount.toString() === args.amount.toString() &&
|
||||
pendingTx.memo === args.memo
|
||||
pendingTx.amount.toString() === authArgs.amount.toString() &&
|
||||
pendingTx.memo === authArgs.memo
|
||||
) {
|
||||
logger.debug('XCom: settleSendCoins matching pendingTX for settlement...')
|
||||
methodLogger.debug('XCom: settleSendCoins matching pendingTX for settlement...')
|
||||
|
||||
await settlePendingReceiveTransaction(homeCom, receiverUser, pendingTx)
|
||||
// after successful x-com-tx store the recipient as foreign user
|
||||
logger.debug('store recipient as foreign user...')
|
||||
if (await storeForeignUser(args)) {
|
||||
logger.info(
|
||||
methodLogger.debug('store recipient as foreign user...')
|
||||
if (await storeForeignUser(authArgs)) {
|
||||
methodLogger.info(
|
||||
'X-Com: new foreign user inserted successfully...',
|
||||
args.senderCommunityUuid,
|
||||
args.senderUserUuid,
|
||||
authArgs.senderCommunityUuid,
|
||||
authArgs.senderUserUuid,
|
||||
)
|
||||
}
|
||||
|
||||
logger.debug(`XCom: settlePendingReceiveTransaction()-1_0... successful`)
|
||||
methodLogger.debug(`XCom: settlePendingReceiveTransaction()-1_0... successful`)
|
||||
return true
|
||||
} else {
|
||||
logger.debug('XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...')
|
||||
throw new LogError(
|
||||
`Can't find in settlePendingReceiveTransaction the pending receiver TX for `,
|
||||
{
|
||||
args: new SendCoinsArgsLoggingView(args),
|
||||
pendingTransactionState: PendingTransactionState.NEW,
|
||||
methodLogger.debug('XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...')
|
||||
const errmsg = `Can't find in settlePendingReceiveTransaction the pending receiver TX for ` + {
|
||||
args: new SendCoinsArgsLoggingView(authArgs),
|
||||
pendingTransactionState: PendingTransactionState.NEW,
|
||||
transactionTypeId: TransactionTypeId.RECEIVE,
|
||||
},
|
||||
)
|
||||
}
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async revertSettledSendCoins(
|
||||
@Arg('data')
|
||||
args: SendCoinsArgs,
|
||||
args: EncryptedTransferArgs,
|
||||
): Promise<boolean> {
|
||||
logger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`)
|
||||
const methodLogger = createLogger(`revertSettledSendCoins`)
|
||||
methodLogger.addContext('handshakeID', args.handshakeID)
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`)
|
||||
}
|
||||
const authArgs = await interpretEncryptedTransferArgs(args) as SendCoinsJwtPayloadType
|
||||
if (!authArgs) {
|
||||
const errmsg = `invalid revertSettledSendCoins payload of requesting community with publicKey=${args.publicKey}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
if(methodLogger.isDebugEnabled()) {
|
||||
methodLogger.debug(`revertSettledSendCoins() via apiVersion=1_0 ...`, authArgs)
|
||||
}
|
||||
// first check if receiver community is correct
|
||||
const homeCom = await DbCommunity.findOneBy({
|
||||
communityUuid: args.recipientCommunityUuid,
|
||||
communityUuid: authArgs.recipientCommunityUuid,
|
||||
})
|
||||
if (!homeCom) {
|
||||
throw new LogError(
|
||||
`revertSettledSendCoins with wrong recipientCommunityUuid`,
|
||||
args.recipientCommunityUuid,
|
||||
)
|
||||
const errmsg = `revertSettledSendCoins with wrong recipientCommunityUuid=${authArgs.recipientCommunityUuid}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
|
||||
// second check if receiver user exists in this community
|
||||
const receiverUser = await findUserByIdentifier(args.recipientUserIdentifier)
|
||||
const receiverUser = await findUserByIdentifier(authArgs.recipientUserIdentifier)
|
||||
if (!receiverUser) {
|
||||
logger.error('Error in findUserByIdentifier')
|
||||
throw new LogError(
|
||||
`revertSettledSendCoins with unknown recipientUserIdentifier in the community=`,
|
||||
homeCom.name,
|
||||
)
|
||||
const errmsg = `revertSettledSendCoins with unknown recipientUserIdentifier in the community=${homeCom.name}`
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
const pendingTx = await DbPendingTransaction.findOneBy({
|
||||
userCommunityUuid: args.recipientCommunityUuid,
|
||||
userGradidoID: args.recipientUserIdentifier,
|
||||
userCommunityUuid: authArgs.recipientCommunityUuid,
|
||||
userGradidoID: authArgs.recipientUserIdentifier,
|
||||
state: PendingTransactionState.SETTLED,
|
||||
typeId: TransactionTypeId.RECEIVE,
|
||||
balanceDate: new Date(args.creationDate),
|
||||
linkedUserCommunityUuid: args.senderCommunityUuid,
|
||||
linkedUserGradidoID: args.senderUserUuid,
|
||||
balanceDate: new Date(authArgs.creationDate),
|
||||
linkedUserCommunityUuid: authArgs.senderCommunityUuid,
|
||||
linkedUserGradidoID: authArgs.senderUserUuid,
|
||||
})
|
||||
logger.debug(
|
||||
methodLogger.debug(
|
||||
'XCom: revertSettledSendCoins found pendingTX=',
|
||||
pendingTx ? new PendingTransactionLoggingView(pendingTx) : 'null',
|
||||
)
|
||||
if (
|
||||
pendingTx &&
|
||||
pendingTx.amount.toString() === args.amount.toString() &&
|
||||
pendingTx.memo === args.memo
|
||||
pendingTx.amount.toString() === authArgs.amount.toString() &&
|
||||
pendingTx.memo === authArgs.memo
|
||||
) {
|
||||
logger.debug('XCom: revertSettledSendCoins matching pendingTX for remove...')
|
||||
methodLogger.debug('XCom: revertSettledSendCoins matching pendingTX for remove...')
|
||||
try {
|
||||
await revertSettledReceiveTransaction(homeCom, receiverUser, pendingTx)
|
||||
logger.debug('XCom: revertSettledSendCoins pendingTX successfully')
|
||||
methodLogger.debug('XCom: revertSettledSendCoins pendingTX successfully')
|
||||
} catch (err) {
|
||||
throw new LogError('Error in revertSettledSendCoins of receiver: ', err)
|
||||
const errmsg = `Error in revertSettledSendCoins of receiver: ` + err
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
} else {
|
||||
logger.debug('XCom: revertSettledSendCoins NOT matching pendingTX...')
|
||||
throw new LogError(`Can't find in revertSettledSendCoins the pending receiver TX for `, {
|
||||
args: new SendCoinsArgsLoggingView(args),
|
||||
methodLogger.debug('XCom: revertSettledSendCoins NOT matching pendingTX...')
|
||||
const errmsg = `Can't find in revertSettledSendCoins the pending receiver TX for ` + {
|
||||
args: new SendCoinsArgsLoggingView(authArgs),
|
||||
pendingTransactionState: PendingTransactionState.SETTLED,
|
||||
transactionTypeId: TransactionTypeId.RECEIVE,
|
||||
})
|
||||
}
|
||||
methodLogger.error(errmsg)
|
||||
throw new Error(errmsg)
|
||||
}
|
||||
logger.debug(`revertSendCoins()-1_0... successfull`)
|
||||
methodLogger.debug(`revertSettledSendCoins()-1_0... successfull`)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,3 +10,6 @@ export * from './jwt/payloadtypes/JwtPayloadType'
|
||||
export * from './jwt/payloadtypes/OpenConnectionJwtPayloadType'
|
||||
export * from './jwt/payloadtypes/OpenConnectionCallbackJwtPayloadType'
|
||||
export * from './jwt/payloadtypes/RedeemJwtPayloadType'
|
||||
export * from './jwt/payloadtypes/SendCoinsJwtPayloadType'
|
||||
export * from './jwt/payloadtypes/SendCoinsResponseJwtPayloadType'
|
||||
|
||||
|
||||
43
shared/src/jwt/payloadtypes/SendCoinsJwtPayloadType.ts
Normal file
43
shared/src/jwt/payloadtypes/SendCoinsJwtPayloadType.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { JwtPayloadType } from './JwtPayloadType'
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
|
||||
export class SendCoinsJwtPayloadType extends JwtPayloadType {
|
||||
static SEND_COINS_TYPE = 'send-coins'
|
||||
|
||||
recipientCommunityUuid: string
|
||||
recipientUserIdentifier: string
|
||||
creationDate: string
|
||||
amount: Decimal
|
||||
memo: string
|
||||
senderCommunityUuid: string
|
||||
senderUserUuid: string
|
||||
senderUserName: string
|
||||
senderAlias?: string | null
|
||||
|
||||
constructor(
|
||||
handshakeID: string,
|
||||
recipientCommunityUuid: string,
|
||||
recipientUserIdentifier: string,
|
||||
creationDate: string,
|
||||
amount: Decimal,
|
||||
memo: string,
|
||||
senderCommunityUuid: string,
|
||||
senderUserUuid: string,
|
||||
senderUserName: string,
|
||||
senderAlias?: string | null,
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
super(handshakeID)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
this.tokentype = SendCoinsJwtPayloadType.SEND_COINS_TYPE
|
||||
this.recipientCommunityUuid = recipientCommunityUuid
|
||||
this.recipientUserIdentifier = recipientUserIdentifier
|
||||
this.creationDate = creationDate
|
||||
this.amount = amount
|
||||
this.memo = memo
|
||||
this.senderCommunityUuid = senderCommunityUuid
|
||||
this.senderUserUuid = senderUserUuid
|
||||
this.senderUserName = senderUserName
|
||||
this.senderAlias = senderAlias
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
import { JwtPayloadType } from './JwtPayloadType'
|
||||
|
||||
export class SendCoinsResponseJwtPayloadType extends JwtPayloadType {
|
||||
static SEND_COINS_RESPONSE_TYPE = 'send-coins-response'
|
||||
|
||||
vote: boolean
|
||||
recipGradidoID: string | null
|
||||
recipFirstName: string | null
|
||||
recipLastName: string | null
|
||||
recipAlias: string | null
|
||||
|
||||
constructor(
|
||||
handshakeID: string,
|
||||
vote: boolean,
|
||||
recipGradidoID: string | null,
|
||||
recipFirstName: string | null,
|
||||
recipLastName: string | null,
|
||||
recipAlias: string | null,
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
super(handshakeID)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
this.tokentype = SendCoinsResponseJwtPayloadType.SEND_COINS_RESPONSE_TYPE
|
||||
this.vote = vote
|
||||
this.recipGradidoID = recipGradidoID
|
||||
this.recipFirstName = recipFirstName
|
||||
this.recipLastName = recipLastName
|
||||
this.recipAlias = recipAlias
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user