Skip to content

Commit

Permalink
Merge pull request #27 from movementlabsxyz/imola-baku
Browse files Browse the repository at this point in the history
Add Rate Limit and Captcha verification fo Aptos
  • Loading branch information
Primata authored Aug 6, 2024
2 parents 9b5f41a + 4a617e6 commit 837c56f
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 20,608 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,7 @@ yarn-error.log*
.netlify
movement-explorer.txt

yarn.lock
yarn.lock
.vercel

GOOGLE_APPLICATION_CREDENTIALS.json
150 changes: 150 additions & 0 deletions api/rate-limit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import {Ratelimit} from "@upstash/ratelimit";
import {kv} from "@vercel/kv";
import {IncomingMessage} from "http";
import {RecaptchaEnterpriseServiceClient} from "@google-cloud/recaptcha-enterprise";
import {Aptos, AptosConfig, Network} from "@aptos-labs/ts-sdk";

const ratelimit = new Ratelimit({
redis: kv,
// 5 requests from the same IP in 10 seconds
limiter: Ratelimit.slidingWindow(2, "60 s"),
});

type ExtendedIncomingMessage = IncomingMessage & {
headers: {
[key: string]: string | string[] | undefined;
"x-forwarded-for"?: string;
};
};

function getXForwardedFor(req: ExtendedIncomingMessage): string | undefined {
return req.headers["x-forwarded-for"];
}

function ips(req: any) {
return getXForwardedFor(req)?.split(/\s*,\s*/);
}

// TODO: Use createAssessment instead.
async function createAssessment(
// TODO: Replace the token and reCAPTCHA action variables before running the sample.
projectID: string,
recaptchaKey: string,
recaptchaAction: string,
token: string,
) {
// Create the reCAPTCHA client.
// TODO: Cache the client generation code (recommended) or call client.close() before exiting the method.
const client = new RecaptchaEnterpriseServiceClient();
const projectPath = client.projectPath(projectID);

// Build the assessment request.
const request = {
assessment: {
event: {
token: token,
siteKey: recaptchaKey,
},
},
parent: projectPath,
};
console.log("creating assessment");
const [response] = await client.createAssessment(request);
console.log("response", response);
if (!response.tokenProperties) return null;
// Check if the token is valid.
if (!response.tokenProperties.valid) {
console.log(
`The CreateAssessment call failed because the token was: ${response.tokenProperties.invalidReason}`,
);
return null;
}

// Check if the expected action was executed.
// The `action` property is set by user client in the grecaptcha.enterprise.execute() method.
if (response.tokenProperties.action === recaptchaAction) {
// Get the risk score and the reason(s).
// For more information on interpreting the assessment, see:
// https://cloud.google.com/recaptcha-enterprise/docs/interpret-assessment
if (!response.riskAnalysis) return null;
console.log(`The reCAPTCHA score is: ${response.riskAnalysis.score}`);
response.riskAnalysis.reasons?.forEach((reason) => {
console.log(reason);
});

return response.riskAnalysis.score;
} else {
console.log(
"The action attribute in your reCAPTCHA tag does not match the action you are expecting to score",
);
return null;
}
}

export default async function handler(request: any, response: any) {
// You could alternatively limit based on user ID or similar
const {token, address} = request.body;
const secretKey = process.env.RECAPTCHA_SECRET_KEY;
const verificationUrl = `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${token}`;

if (!secretKey || !process.env.FAUCET_AUTH_TOKEN) {
return request.status(500).json({error: "reCAPTCHA secret key not set"});
}
const ip = ips(request) ?? "127.0.0.1";

const {success, pending, limit, reset, remaining} = await ratelimit.limit(
ip[0],
);

// const score = await createAssessment("movement-faucet-1722352143785", "6LdVjR0qAAAAAFSjzYqyRFsnUDn-iRrzQmv0nnp3", "", token);
// console.log('score', score)
// if (score === (null || undefined)) {
// response.status(400).json({ error: 'Invalid reCAPTCHA token' });
// } else if (score != null && score < 0.5) {
// response.status(400).json({ error: 'reCAPTCHA score too low' });
// } else {
// response.status(success ? 200 : 429).json({ success, pending, limit, reset, remaining });
// }

try {
const verification = await fetch(verificationUrl, {
method: "POST",
});
const data = await verification.json();
if (data.success == false) {
return response
.status(400)
.json({success: false, error: "Invalid reCAPTCHA token"});
}
if (!success) {
return response.status(429).json({success: false, error: "Rate limited"});
}

const HEADERS = {
authorization: `Bearer ${process.env.FAUCET_AUTH_TOKEN}`
};
const aptos = new Aptos(
new AptosConfig({
network: Network.TESTNET,
fullnode: "https://aptos.testnet.suzuka.movementlabs.xyz/v1",
faucet: "https://faucet.testnet.suzuka.movementlabs.xyz",
faucetConfig: {HEADERS: HEADERS},
}),
);


const fund = await aptos.fundAccount({
accountAddress: address,
amount: 1000000000,
});
if (!fund.success) {
return response
.status(400)
.json({success: false, error: "Failed to fund account"});
}
return response.status(200).json({success: true, hash: fund.hash, limit: limit});
} catch (error) {
console.log(`error`)
return response.status(500).json({success: false, error: "Server error"});
}
}
24 changes: 0 additions & 24 deletions middleware.ts

This file was deleted.

27 changes: 27 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
console.log(__dirname);
const nextConfig = {
reactStrictMode: false,
transpilePackages: [
"antd",
"rc-util",
"@babel/runtime",
"@ant-design/icons",
"@ant-design/icons-svg",
"rc-pagination",
"rc-picker",
"rc-tree",
"rc-table",
],
images: {
domains: ["faucet.movementlabs.xyz", "localhost:3000", "localhost"],
},
webpack: (config) => {
config.resolve.alias["@"] = path.resolve(__dirname, "src");
return config;
},
};

export default nextConfig;
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@
"name": "movement-faucets",
"version": "1.5.3",
"private": true,
"type": "module",
"dependencies": {
"@apollo/client": "^3.7.10",
"@aptos-labs/aptos-names-connector": "^0.0.2",
"@aptos-labs/ts-sdk": "^1.20.0",
"@aptos-labs/wallet-adapter-mui-design": "^0.3.2",
"@aptos-labs/wallet-adapter-react": "^3.5.2",
"@aptos-labs/wallet-adapter-react": "3.5.6",
"@blocto/aptos-wallet-adapter-plugin": "^0.1.4",
"@download/blockies": "^1.0.3",
"@emotion/react": "latest",
"@emotion/styled": "latest",
"@google-cloud/recaptcha-enterprise": "^5.10.0",
"@martianwallet/aptos-wallet-adapter": "^0.0.4",
"@mui/icons-material": "^5.11.11",
"@mui/material": "^5.11.16",
"@mui/system": "^5.11.16",
"@mui/x-date-pickers": "^5.0.20",
"@mysten/dapp-kit": "^0.14.12",
"@mysten/sui": "^1.3.0",
"@mysten/sui": "1.4.0",
"@nightlylabs/aptos-wallet-adapter-plugin": "^0.2.12",
"@openblockhq/aptos-wallet-adapter": "^0.1.5",
"@pontem/wallet-adapter-plugin": "^0.1.4",
Expand All @@ -35,6 +37,7 @@
"@types/react-simple-maps": "^3.0.0",
"@upstash/ratelimit": "^2.0.1",
"@vercel/edge": "^1.1.2",
"@vercel/functions": "^1.4.0",
"@vercel/kv": "^2.0.0",
"@web3modal/wagmi": "^5.0.7",
"@welldone-studio/aptos-wallet-adapter": "^0.1.2",
Expand Down Expand Up @@ -75,6 +78,7 @@
"statsig-react": "^1.23.2",
"ts-results": "^3.3.0",
"typescript": "^5.0.2",
"vercel": "^35.2.2",
"viem": "^2.17.5",
"wagmi": "^2.11.3"
},
Expand Down
Loading

0 comments on commit 837c56f

Please sign in to comment.