Skip to content

Commit

Permalink
data types validation
Browse files Browse the repository at this point in the history
  • Loading branch information
TheTrunk committed Jan 27, 2025
1 parent ae16fa5 commit a17bd25
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 30 deletions.
122 changes: 112 additions & 10 deletions src/apiServices/actionApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,14 @@ async function getAction(req, res) {
try {
let { id } = req.params;
id = id || req.query.id; // id is wkIdentity
if (!id) {
res.sendStatus(400);
if (
!id ||
typeof id !== 'string' ||
id.length > 200 ||
!/^[a-zA-Z0-9_-]+$/.test(id)
) {
// send status code 400 and message of invalid id
res.status(400).send('Invalid ID');
return;
}
const syncExist = await actionService.getAction(id);
Expand All @@ -51,22 +57,118 @@ function postAction(req, res) {
try {
const processedBody = serviceHelper.ensureObject(body);
log.info(processedBody);
if (!processedBody.chain) {
throw new Error('No Chain specified');
if (
!processedBody.chain ||
typeof processedBody.chain !== 'string' ||
processedBody.chain.length > 200 ||
!/^[a-zA-Z0-9_-]+$/.test(processedBody.chain)
) {
throw new Error('Invalid Chain specified');
}
if (!processedBody.wkIdentity) {
throw new Error('No Wallet-Key Identity specified');
if (
!processedBody.wkIdentity ||
typeof processedBody.wkIdentity !== 'string' ||
processedBody.wkIdentity.length > 200 ||
!/^[a-zA-Z0-9_-]+$/.test(processedBody.wkIdentity)
) {
throw new Error('Invalid Wallet-Key Identity specified');
}
if (!processedBody.action) {
throw new Error('No Action specified');
if (
!processedBody.action ||
typeof processedBody.action !== 'string' ||
processedBody.action.length > 200 ||
!/^[a-zA-Z0-9_-]+$/.test(processedBody.action)
) {
throw new Error('Invalid Action specified');
}
if (!processedBody.payload) {
throw new Error('No Payload specified');
if (
!processedBody.payload ||
typeof processedBody.payload !== 'string' ||
processedBody.payload.length > 1000000
) {
throw new Error('Invalid Payload specified'); // can be large
}
if (processedBody.action === 'tx' && !processedBody.path) {
throw new Error('No Derivation Path specified');
}

if (processedBody.path) {
if (
typeof processedBody.path !== 'string' ||
processedBody.path.length > 200 ||
!/^[a-zA-Z0-9_-]+$/.test(processedBody.path)
) {
throw new Error('Invalid Derivation Path specified');
}
}

// utxo {
// txid: string;
// vout: number;
// scriptPubKey: string;
// satoshis: string;
// confirmations: number;
// coinbase: boolean;
// }
if (processedBody.utxos) {
// utxos must be an array of utxo objects. Not all fields have to be specified there
if (!Array.isArray(processedBody.utxos)) {
throw new Error('Invalid UTXOs specified');
}
// utxos must be an array of utxo objects. Not all fields have to be specified there
processedBody.utxos.forEach((utxo) => {
if (typeof utxo !== 'object' || !utxo.txid || !utxo.vout) {
throw new Error('Invalid UTXO specified');
}
if (typeof utxo.txid !== 'string' || utxo.txid.length > 200) {
// mandatory
throw new Error('Invalid UTXO txid specified');
}
if (Number(utxo.vout) > 100000) {
// mandatory
throw new Error('Invalid UTXO vout specified');
}
if (utxo.scriptPubKey) {
// optional
if (
typeof utxo.scriptPubKey !== 'string' ||
utxo.scriptPubKey.length > 5000
) {
throw new Error('Invalid UTXO scriptPubKey specified');
}
}
if (utxo.satoshis) {
// if its false, 0 its alright
// optional
if (Number(utxo.satoshis) > 1000000000000000000) {
throw new Error('Invalid UTXO satoshis specified');
}
}
if (utxo.confirmations) {
// if its false, 0 its alright
// optional
if (Number(utxo.confirmations) > 100000000000) {
throw new Error('Invalid UTXO confirmations specified');
}
}
if (utxo.coinbase) {
// if its false, 0 its alright
// optional
if (
typeof utxo.coinbase !== 'boolean' &&
utxo.coinbase !== 'true' &&
utxo.coinbase !== 'false' &&
utxo.coinbase !== '0' &&
utxo.coinbase !== '1' &&
utxo.coinbase !== 0 &&
utxo.coinbase !== 1
) {
throw new Error('Invalid UTXO coinbase specified');
}
}
});
}

const data: actionData = {
chain: processedBody.chain,
path: processedBody.path,
Expand Down
113 changes: 99 additions & 14 deletions src/apiServices/syncApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@ async function getSync(req, res) {
try {
let { id } = req.params;
id = id || req.query.id; // id is walletIdentity
if (!id) {
res.sendStatus(400);
if (
!id ||
typeof id !== 'string' ||
id.length > 200 ||
!/^[a-zA-Z0-9_-]+$/.test(id)
) {
// send status code 400 and message of invalid id
res.status(400).send('Invalid ID');
return;
}
const syncExist = await syncService.getSync(id);
Expand All @@ -40,17 +46,87 @@ function postSync(req, res) {
req.on('end', async () => {
try {
const processedBody = serviceHelper.ensureObject(body);
if (!processedBody.chain) {
throw new Error('No Chain specified');
if (
!processedBody.chain ||
typeof processedBody.chain !== 'string' ||
processedBody.chain.length > 200 ||
!/^[a-zA-Z0-9_-]+$/.test(processedBody.chain)
) {
throw new Error('Invalid Chain specified');
}
if (!processedBody.walletIdentity) {
throw new Error('No Wallet identity specified');
if (
!processedBody.walletIdentity ||
typeof processedBody.walletIdentity !== 'string' ||
processedBody.walletIdentity.length > 200 ||
!/^[a-zA-Z0-9_-]+$/.test(processedBody.walletIdentity)
) {
throw new Error('Invalid Wallet identity specified');
}
if (!processedBody.keyXpub) {
throw new Error('No XPUB of Key specified');
if (
!processedBody.keyXpub ||
typeof processedBody.keyXpub !== 'string' ||
processedBody.keyXpub.length > 200 ||
!/^[a-zA-Z0-9_-]+$/.test(processedBody.keyXpub)
) {
throw new Error('Invalid XPUB of Key specified');
}
if (!processedBody.wkIdentity) {
throw new Error('No SSP Identity specified');
if (
!processedBody.wkIdentity ||
typeof processedBody.wkIdentity !== 'string' ||
processedBody.wkIdentity.length > 200 ||
!/^[a-zA-Z0-9_-]+$/.test(processedBody.wkIdentity)
) {
throw new Error('Invalid SSP Identity specified');
}

// validations
// generated address
if (
processedBody.generatedAddress &&
typeof processedBody.generatedAddress !== 'string'
) {
throw new Error('Invalid generated address');
}

if (
processedBody.generatedAddress &&
(processedBody.generatedAddress.length > 200 ||
!/^[a-zA-Z0-9_-]+$/.test(processedBody.generatedAddress))
) {
throw new Error('Generated address is invalid');
}

// public nonces
if (
processedBody.publicNonces &&
!Array.isArray(processedBody.publicNonces)
) {
throw new Error('Invalid public nonces');
}

if (processedBody.publicNonces && processedBody.publicNonces.length > 0) {
processedBody.publicNonces.forEach((nonce) => {
// nonce is object containing kPublic and kTwoPublic
if (
typeof nonce !== 'object' ||
typeof nonce.kPublic !== 'string' ||
typeof nonce.kTwoPublic !== 'string'
) {
throw new Error('Invalid public nonce detected');
}

if (nonce.kPublic.length > 200 || nonce.kTwoPublic.length > 200) {
throw new Error('Public nonce is too long');
}
});
}

// too many nonces
if (
processedBody.publicNonces &&
processedBody.publicNonces.length > 1000
) {
throw new Error('Too many public nonces submitted');
}

const data: syncData = {
Expand Down Expand Up @@ -98,12 +174,21 @@ function postToken(req, res) {
req.on('end', async () => {
try {
const processedBody = serviceHelper.ensureObject(body);
if (!processedBody.wkIdentity) {
throw new Error('No SSP identity specified');
if (
!processedBody.wkIdentity ||
typeof processedBody.wkIdentity !== 'string' ||
processedBody.wkIdentity.length > 200 ||
!/^[a-zA-Z0-9_-]+$/.test(processedBody.wkIdentity)
) {
throw new Error('Invalid SSP identity specified');
}

if (!processedBody.keyToken) {
throw new Error('No SSP Key Token specified');
if (
!processedBody.keyToken ||
typeof processedBody.keyToken !== 'string' ||
processedBody.keyToken.length > 5000
) {
throw new Error('Invalid SSP Key Token specified');
}

const tokenData = {
Expand Down
29 changes: 25 additions & 4 deletions src/apiServices/ticketsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,43 @@ function postTicket(req, res) {
req.on('end', async () => {
try {
const processedBody = serviceHelper.ensureObject(body);
if (!processedBody.description) {
if (
!processedBody.description ||
typeof processedBody.description !== 'string'
) {
throw new Error('No description specified');
}
if (!processedBody.subject) {
if (!processedBody.subject || typeof processedBody.subject !== 'string') {
throw new Error('No subject specified');
}
if (!processedBody.type) {
if (!processedBody.type || typeof processedBody.type !== 'string') {
throw new Error('No type specified');
}
if (!processedBody.email) {
if (!processedBody.email || typeof processedBody.email !== 'string') {
throw new Error('No email specified');
}
// request must contain challenge header
if (!req.headers['x-challenge']) {
throw new Error('Invalid request');
}

// validate data
if (processedBody.description.length > 50000) {
throw new Error('Description is too long');
}
if (processedBody.subject.length > 1000) {
throw new Error('Subject is too long');
}
if (
processedBody.email.length > 500 ||
!processedBody.email.includes('@')
) {
throw new Error('Email is invalid');
}
if (processedBody.type.length > 500) {
throw new Error('Type is too long');
}

// only following IP can make the request
const ip = req.headers['x-forwarded-for'].split(',')[0];
if (alreadySubmittedIps.filter((item) => item === ip).length > 10) {
Expand Down
24 changes: 23 additions & 1 deletion src/apiServices/tokenApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,33 @@ import { getFromAlchemy } from '../services/tokenServices';
async function getTokenInfo(req, res) {
try {
let { network } = req.params;
network = network ?? req.query.contract;
network = network ?? req.query.network ?? 'eth'; // default to eth

let { address } = req.params;
address = address ?? req.query.address;

if (
!address ||
typeof address !== 'string' ||
address.length > 100 ||
!/^[a-zA-Z0-9_-]+$/.test(address)
) {
// send status code 400 and message of invalid address
res.status(400).send('Invalid contract address');
return;
}

if (
!network ||
typeof network !== 'string' ||
network.length > 100 ||
!/^[a-zA-Z0-9_-]+$/.test(network)
) {
// send status code 400 and message of invalid network
res.status(400).send('Invalid network');
return;
}

const value = await getFromAlchemy(address, network);

res.json(value);
Expand Down
5 changes: 5 additions & 0 deletions src/lib/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@ app.use(cors());
app.use(limiter);
routes(app);

// Catch-all route for undefined routes
app.use((req, res) => {
res.status(404).send('Not found.');
});

export default app;
2 changes: 1 addition & 1 deletion src/services/tokenServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export async function getFromAlchemy(contractAddress: string, network: string) {
} else if (network === 'sepolia') {
networkValue = Network.ETH_SEPOLIA;
} else {
networkValue = Network.ETH_SEPOLIA;
throw new Error('Unsupported network');
}

const alchemy = new Alchemy({
Expand Down

0 comments on commit a17bd25

Please sign in to comment.