Skip to content

Commit

Permalink
added google recaptcha v2 validator to faucet service
Browse files Browse the repository at this point in the history
  • Loading branch information
wwwehr committed Jun 6, 2024
1 parent f71cdc3 commit 4abae2c
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 27 deletions.
60 changes: 37 additions & 23 deletions packages/faucet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,29 +44,35 @@ start Starts the faucet
Environment variables
FAUCET_CONCURRENCY Number of distributor accounts. Defaults to 5.
FAUCET_PORT Port of the webserver. Defaults to 8000.
FAUCET_MEMO Memo for send transactions. Defaults to unset.
FAUCET_GAS_PRICE Gas price for transactions as a comma separated list.
Defaults to "0.025ucosm".
FAUCET_GAS_LIMIT Gas limit for send transactions. Defaults to 100000.
FAUCET_MNEMONIC Secret mnemonic that serves as the base secret for the
faucet HD accounts
FAUCET_PATH_PATTERN The pattern of BIP32 paths for the faucet accounts.
Must contain one "a" placeholder that is replaced with
the account index.
Defaults to the Cosmos Hub path "m/44'/118'/0'/0/a".
FAUCET_ADDRESS_PREFIX The bech32 address prefix. Defaults to "cosmos".
FAUCET_TOKENS A comma separated list of token denoms, e.g.
"uatom" or "ucosm, mstake".
FAUCET_CREDIT_AMOUNT_TKN Send this amount of TKN to a user requesting TKN. TKN is
a placeholder for the token's denom. Defaults to 10000000.
FAUCET_REFILL_FACTOR Send factor times credit amount on refilling. Defauls to 8.
FAUCET_REFILL_THRESHOLD Refill when balance gets below factor times credit amount.
Defaults to 20.
FAUCET_COOLDOWN_TIME Time (in seconds) after which an address can request
more tokens. Can be set to "0". Defaults to 24 hours
if unset or an empty string.
FAUCET_CONCURRENCY Number of distributor accounts. Defaults to 5.
FAUCET_PORT Port of the webserver. Defaults to 8000.
FAUCET_MEMO Memo for send transactions. Defaults to unset.
FAUCET_GAS_PRICE Gas price for transactions as a comma separated list.
Defaults to "0.025ucosm".
FAUCET_GAS_LIMIT Gas limit for send transactions. Defaults to 100000.
FAUCET_MNEMONIC Secret mnemonic that serves as the base secret for the
faucet HD accounts
FAUCET_PATH_PATTERN The pattern of BIP32 paths for the faucet accounts.
Must contain one "a" placeholder that is replaced with
the account index.
Defaults to the Cosmos Hub path "m/44'/118'/0'/0/a".
FAUCET_ADDRESS_PREFIX The bech32 address prefix. Defaults to "cosmos".
FAUCET_TOKENS A comma separated list of token denoms, e.g.
"uatom" or "ucosm, mstake".
FAUCET_CREDIT_AMOUNT_TKN Send this amount of TKN to a user requesting TKN. TKN is
a placeholder for the token's denom. Defaults to 10000000.
FAUCET_REFILL_FACTOR Send factor times credit amount on refilling. Defauls to 8.
FAUCET_REFILL_THRESHOLD Refill when balance gets below factor times credit amount.
Defaults to 20.
FAUCET_COOLDOWN_TIME Time (in seconds) after which an address can request
more tokens. Can be set to "0". Defaults to 24 hours
if unset or an empty string.
GOOGLE_RECAPTCHA_SECRET_KEY The secret key for validating input with the recaptcha v2
service. If this value is set, then each call to the `/credit`
endpoint will require a valid recaptcha response string in
the JSON POST data named `recaptcha` in addition to the `denom`
and `address`.
Defaults to unset (disabled)
```

### Faucet HD wallet
Expand Down Expand Up @@ -134,6 +140,14 @@ curl --header "Content-Type: application/json" \
http://localhost:8000/credit
```

### Using the faucet with Recaptcha validation enabled
```
curl --header "Content-Type: application/json" \
--request POST \
--data '{"denom":"ucosm","address":"cosmos1yre6ac7qfgyfgvh58ph0rgw627rhw766y430qq", "recaptcha": "03AFcWeA6KFdGLxDQIx_UZ9Y9IMlAJyen-DkT3k..."}' \
http://localhost:8000/credit
```

### Checking the faucets status

The faucet provides a simple status check in the form of an http GET request. As
Expand Down
3 changes: 2 additions & 1 deletion packages/faucet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
"@cosmjs/utils": "workspace:^",
"@koa/cors": "^3.3",
"koa": "^2.13",
"koa-bodyparser": "^4.3"
"koa-bodyparser": "^4.3",
"undici": "^6.18.2"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
Expand Down
6 changes: 5 additions & 1 deletion packages/faucet/src/api/requestparser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { RequestParser } from "./requestparser";
describe("RequestParser", () => {
it("can process valid credit request with denom", () => {
const body = { address: "abc", denom: "utkn" };
expect(RequestParser.parseCreditBody(body)).toEqual({ address: "abc", denom: "utkn" });
expect(RequestParser.parseCreditBody(body)).toEqual({
address: "abc",
denom: "utkn",
recaptcha: undefined,
});
});

it("throws helpful error message when ticker is found", () => {
Expand Down
5 changes: 4 additions & 1 deletion packages/faucet/src/api/requestparser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export interface CreditRequestBodyData {
readonly denom: string;
/** The recipient address */
readonly address: string;
/** The recaptcha v2 response */
readonly recaptcha: string | undefined;
}

export interface CreditRequestBodyDataWithTicker {
Expand All @@ -22,7 +24,7 @@ export class RequestParser {
throw new HttpError(400, "Request body must be a dictionary.");
}

const { address, denom, ticker } = body as any;
const { address, denom, ticker, recaptcha } = body as any;

if (typeof ticker !== "undefined") {
throw new HttpError(400, "The 'ticker' field was removed in CosmJS 0.23. Please use 'denom' instead.");
Expand All @@ -47,6 +49,7 @@ export class RequestParser {
return {
address: address,
denom: denom,
recaptcha: recaptcha,
};
}
}
23 changes: 22 additions & 1 deletion packages/faucet/src/api/webserver.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Koa from "koa";
import cors = require("@koa/cors");
import bodyParser from "koa-bodyparser";
import { request } from "undici";
import qs from "node:querystring";

import { isValidAddress } from "../addresses";
import * as constants from "../constants";
Expand Down Expand Up @@ -59,7 +61,7 @@ export class Webserver {
// context.request.body is set by the bodyParser() plugin
const requestBody = (context.request as any).body;
const creditBody = RequestParser.parseCreditBody(requestBody);
const { address, denom } = creditBody;
const { address, denom, recaptcha } = creditBody;

if (!isValidAddress(address, constants.addressPrefix)) {
throw new HttpError(400, "Address is not in the expected format for this chain.");
Expand All @@ -82,6 +84,25 @@ export class Webserver {
throw new HttpError(422, `Token is not available. Available tokens are: ${availableTokens}`);
}

// if enabled, require recaptcha validation
if (process.env.GOOGLE_RECAPTCHA_SECRET_KEY !== undefined) {
const { body } = await request("https://www.google.com/recaptcha/api/siteverify", {
method: "POST",
headers: {
"content-type": "application/x-www-form-urlencoded",
},
body: qs.stringify({
secret: process.env.GOOGLE_RECAPTCHA_SECRET_KEY,
response: recaptcha,
}),
});
const verify_data = (await body.json()) as { success: boolean };
if (!verify_data.success) {
console.error(`recaptcha validation FAILED ${JSON.stringify(verify_data, null, 4)}`);
throw new HttpError(423, `Recaptcha failed to verify`);
}
}

try {
// Count addresses to prevent draining
this.addressCounter.set(address, new Date());
Expand Down
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,7 @@ __metadata:
source-map-support: ^0.5.19
ts-node: ^8
typescript: ~4.9
undici: ^6.18.2
webpack: ^5.76.0
webpack-cli: ^4.6.0
bin:
Expand Down Expand Up @@ -7697,6 +7698,13 @@ __metadata:
languageName: node
linkType: hard

"undici@npm:^6.18.2":
version: 6.18.2
resolution: "undici@npm:6.18.2"
checksum: c20e47bd4f959c00d24516756b178190f0a9ae000007e875f1f68c8e7f3f9a68b0a7faa03f3d030ddd71a9e3feb558fbce661b5229a0aa8380cfbe1cea4281e4
languageName: node
linkType: hard

"unique-filename@npm:^3.0.0":
version: 3.0.0
resolution: "unique-filename@npm:3.0.0"
Expand Down

0 comments on commit 4abae2c

Please sign in to comment.