Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions __tests__/transfers-second-wallet.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
require('dotenv').config();
const request = require('supertest');
const { expect } = require('chai');
const server = require('../server/app');
const seed = require('./seed');

describe('Transfers sent to a second wallet of the account', () => {
let bearerTokenA;
let bearerTokenB;
let secondWalletId;

beforeEach(async () => {
await seed.clear();
await seed.seed();
bearerTokenA = seed.wallet.keycloak_account_id;
bearerTokenB = seed.walletB.keycloak_account_id;

const created = await request(server)
.post('/wallets')
.set('content-type', 'application/json')
.set('Authorization', `Bearer ${bearerTokenA}`)
.send({ wallet: 'walletA-second' })
.expect(201);
secondWalletId = created.body.id;

await request(server)
.post('/transfers')
.set('content-type', 'application/json')
.set('Authorization', `Bearer ${bearerTokenB}`)
.send({
tokens: [seed.tokenB.id],
sender_wallet: seed.walletC.name,
receiver_wallet: 'walletA-second',
})
.expect(202);
});

it('are visible to the account that owns the second wallet', async () => {
const res = await request(server)
.get('/transfers')
.set('Authorization', `Bearer ${bearerTokenA}`)
.expect(200);

const mine = res.body.transfers.filter(
(t) => t.destination_wallet === 'walletA-second',
);
expect(mine).lengthOf(1);
expect(mine[0]).to.have.property('state', 'pending');
});

it('can be accepted by that account', async () => {
const listed = await request(server)
.get('/transfers')
.set('Authorization', `Bearer ${bearerTokenA}`)
.expect(200);

const pending = listed.body.transfers.find(
(t) => t.destination_wallet === 'walletA-second',
);

const accepted = await request(server)
.post(`/transfers/${pending.id}/accept`)
.set('content-type', 'application/json')
.set('Authorization', `Bearer ${bearerTokenA}`);

expect(accepted).to.have.property('statusCode', 200);
expect(accepted.body).to.have.property('state', 'completed');
});

it('does not leak transfers between unrelated accounts', async () => {
const res = await request(server)
.get('/transfers')
.set('Authorization', `Bearer ${seed.walletC.keycloak_account_id}`)
.expect(200);

const walletIds = [seed.wallet.id, secondWalletId];
const leaked = res.body.transfers.filter(
(t) =>
walletIds.includes(t.source_wallet_id) ||
walletIds.includes(t.destination_wallet_id),
);
expect(leaked).lengthOf(0);
});
});
29 changes: 22 additions & 7 deletions server/models/Transfer.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,28 @@ class Transfer {
const filter = {
and: [],
};
filter.and.push({
or: [
{ source_wallet_id: walletLoginId },
{ destination_wallet_id: walletLoginId },
{ originator_wallet_id: walletLoginId },
],
});
const { wallets } = await this._wallet.getAllWallets(
walletLoginId,
undefined,
undefined,
'created_at',
'desc',
);
// Never let this list go empty: knex drops an empty `or` and every
// transfer in the table comes back.
const orConditions = [
{ source_wallet_id: walletLoginId },
{ destination_wallet_id: walletLoginId },
{ originator_wallet_id: walletLoginId },
];
wallets
.filter((wallet) => wallet.id !== walletLoginId)
.forEach((wallet) => {
orConditions.push({ source_wallet_id: wallet.id });
orConditions.push({ destination_wallet_id: wallet.id });
orConditions.push({ originator_wallet_id: wallet.id });
});
filter.and.push({ or: orConditions });
if (state) {
filter.and.push({ state });
}
Expand Down
Loading