From e34362c1073b75b7a001ab7a55f7ed360412f76c Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 13 Jan 2024 21:12:27 +0200 Subject: [PATCH] TW-1240: Magic Square campaign. + Msg nonce --- src/index.ts | 8 +++----- src/magic-square.ts | 17 +++++++++++++---- src/utils/signing-nonce.ts | 31 ++++++++++++++++++++----------- 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/index.ts b/src/index.ts index 8d86be7..623101d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,7 +34,7 @@ import { coinGeckoTokens } from './utils/gecko-tokens'; import { getExternalApiErrorPayload, isDefined, isNonEmptyString } from './utils/helpers'; import logger from './utils/logger'; import { getSignedMoonPayUrl } from './utils/moonpay/get-signed-moonpay-url'; -import { getSigningNonce, SIGNING_NONCE_TTL } from './utils/signing-nonce'; +import { getSigningNonce } from './utils/signing-nonce'; import SingleQueryDataProvider from './utils/SingleQueryDataProvider'; import { tezExchangeRateProvider } from './utils/tezos'; import { getExchangeRatesFromDB } from './utils/tokens'; @@ -360,14 +360,12 @@ app.get('/api/magic-square-quest/participants', basicAuth, async (req, res) => { } }); -app.get('/api/auth-nonce', async (req, res) => { +app.get('/api/signing-nonce', async (req, res) => { try { const pkh = req.query.pkh; if (!pkh || typeof pkh !== 'string') throw new Error('PKH is not a string'); - const nonce = getSigningNonce(pkh); - - res.status(200).send({ nonce, ttl: SIGNING_NONCE_TTL }); + res.status(200).send(getSigningNonce(pkh)); } catch (error: any) { console.error(error); diff --git a/src/magic-square.ts b/src/magic-square.ts index 0d880ee..969924b 100644 --- a/src/magic-square.ts +++ b/src/magic-square.ts @@ -8,7 +8,7 @@ import { validateAddress, ValidationResult, verifySignature, getPkhfromPk } from import { redisClient } from './redis'; import { CodedError } from './utils/errors'; import { safeCheck } from './utils/helpers'; -import { getSigningNonce } from './utils/signing-nonce'; +import { getSigningNonce, removeSigningNonce } from './utils/signing-nonce'; const REDIS_DB_KEY = 'magic_square_quest'; @@ -43,6 +43,16 @@ export async function startMagicSquareQuest({ pkh, publicKey, messageBytes, sign throw new CodedError(STATUS_CODE.BAD_REQUEST, 'Invalid EVM public key hash'); } + // Nonce + const { value: nonce } = getSigningNonce(pkh); + const nonceBytes = Buffer.from(nonce, 'utf-8').toString('hex'); + + if (!messageBytes.includes(nonceBytes)) + throw new CodedError(STATUS_CODE.UNAUTHORIZED, 'Invalid Tezos message nonce', 'INVALID_NONCE_TEZ'); + + if (!evm.messageBytes.includes(nonceBytes)) + throw new CodedError(STATUS_CODE.UNAUTHORIZED, 'Invalid EVM message nonce', 'INVALID_NONCE_EVM'); + // Signatures if (!safeCheck(() => verifySignature(messageBytes, publicKey, signature))) @@ -64,11 +74,10 @@ export async function startMagicSquareQuest({ pkh, publicKey, messageBytes, sign .lrange(REDIS_DB_KEY, 0, -1) .then(items => items.some(item => item.includes(pkh) && item.includes(evmPkh))); - if (exists) - throw new CodedError(STATUS_CODE.CONFLICT, 'Quest already started for the given credentials', 'QUEST_STARTED'); + if (exists) throw new CodedError(STATUS_CODE.CONFLICT, 'Your quest was already started before', 'QUEST_IS_STARTED'); // Auth nonce - getSigningNonce.delete(pkh); + removeSigningNonce(pkh); // Registering diff --git a/src/utils/signing-nonce.ts b/src/utils/signing-nonce.ts index edeb0bf..3933b7d 100644 --- a/src/utils/signing-nonce.ts +++ b/src/utils/signing-nonce.ts @@ -4,20 +4,29 @@ import memoizee from 'memoizee'; import { CodedError } from './errors'; -export const SIGNING_NONCE_TTL = 5 * 60_000; +const SIGNING_NONCE_TTL = 5 * 60_000; -const MEMOIZE_OPTIONS = { - max: 500, - maxAge: SIGNING_NONCE_TTL -}; +export const getSigningNonce = memoizee( + (pkh: string) => { + if (validateAddress(pkh) !== ValidationResult.VALID) throw new CodedError(400, 'Invalid address'); -export const getSigningNonce = memoizee((pkh: string) => { - if (validateAddress(pkh) !== ValidationResult.VALID) throw new CodedError(400, 'Invalid address'); + return buildNonce(); + }, + { + max: 500, + maxAge: SIGNING_NONCE_TTL + } +); - return buildNonce(); -}, MEMOIZE_OPTIONS); +export function removeSigningNonce(pkh: string) { + getSigningNonce.delete(pkh); +} function buildNonce() { - // The way it is done in SIWE.generateNonce() - return randomStringForEntropy(96); + // Same as in in SIWE.generateNonce() + const value = randomStringForEntropy(96); + + const expiresAt = new Date(Date.now() + SIGNING_NONCE_TTL).toISOString(); + + return { value, expiresAt }; }