Skip to content

Commit 4abae2c

Browse files
committed
added google recaptcha v2 validator to faucet service
1 parent f71cdc3 commit 4abae2c

File tree

6 files changed

+78
-27
lines changed

6 files changed

+78
-27
lines changed

packages/faucet/README.md

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -44,29 +44,35 @@ start Starts the faucet
4444
4545
Environment variables
4646
47-
FAUCET_CONCURRENCY Number of distributor accounts. Defaults to 5.
48-
FAUCET_PORT Port of the webserver. Defaults to 8000.
49-
FAUCET_MEMO Memo for send transactions. Defaults to unset.
50-
FAUCET_GAS_PRICE Gas price for transactions as a comma separated list.
51-
Defaults to "0.025ucosm".
52-
FAUCET_GAS_LIMIT Gas limit for send transactions. Defaults to 100000.
53-
FAUCET_MNEMONIC Secret mnemonic that serves as the base secret for the
54-
faucet HD accounts
55-
FAUCET_PATH_PATTERN The pattern of BIP32 paths for the faucet accounts.
56-
Must contain one "a" placeholder that is replaced with
57-
the account index.
58-
Defaults to the Cosmos Hub path "m/44'/118'/0'/0/a".
59-
FAUCET_ADDRESS_PREFIX The bech32 address prefix. Defaults to "cosmos".
60-
FAUCET_TOKENS A comma separated list of token denoms, e.g.
61-
"uatom" or "ucosm, mstake".
62-
FAUCET_CREDIT_AMOUNT_TKN Send this amount of TKN to a user requesting TKN. TKN is
63-
a placeholder for the token's denom. Defaults to 10000000.
64-
FAUCET_REFILL_FACTOR Send factor times credit amount on refilling. Defauls to 8.
65-
FAUCET_REFILL_THRESHOLD Refill when balance gets below factor times credit amount.
66-
Defaults to 20.
67-
FAUCET_COOLDOWN_TIME Time (in seconds) after which an address can request
68-
more tokens. Can be set to "0". Defaults to 24 hours
69-
if unset or an empty string.
47+
FAUCET_CONCURRENCY Number of distributor accounts. Defaults to 5.
48+
FAUCET_PORT Port of the webserver. Defaults to 8000.
49+
FAUCET_MEMO Memo for send transactions. Defaults to unset.
50+
FAUCET_GAS_PRICE Gas price for transactions as a comma separated list.
51+
Defaults to "0.025ucosm".
52+
FAUCET_GAS_LIMIT Gas limit for send transactions. Defaults to 100000.
53+
FAUCET_MNEMONIC Secret mnemonic that serves as the base secret for the
54+
faucet HD accounts
55+
FAUCET_PATH_PATTERN The pattern of BIP32 paths for the faucet accounts.
56+
Must contain one "a" placeholder that is replaced with
57+
the account index.
58+
Defaults to the Cosmos Hub path "m/44'/118'/0'/0/a".
59+
FAUCET_ADDRESS_PREFIX The bech32 address prefix. Defaults to "cosmos".
60+
FAUCET_TOKENS A comma separated list of token denoms, e.g.
61+
"uatom" or "ucosm, mstake".
62+
FAUCET_CREDIT_AMOUNT_TKN Send this amount of TKN to a user requesting TKN. TKN is
63+
a placeholder for the token's denom. Defaults to 10000000.
64+
FAUCET_REFILL_FACTOR Send factor times credit amount on refilling. Defauls to 8.
65+
FAUCET_REFILL_THRESHOLD Refill when balance gets below factor times credit amount.
66+
Defaults to 20.
67+
FAUCET_COOLDOWN_TIME Time (in seconds) after which an address can request
68+
more tokens. Can be set to "0". Defaults to 24 hours
69+
if unset or an empty string.
70+
GOOGLE_RECAPTCHA_SECRET_KEY The secret key for validating input with the recaptcha v2
71+
service. If this value is set, then each call to the `/credit`
72+
endpoint will require a valid recaptcha response string in
73+
the JSON POST data named `recaptcha` in addition to the `denom`
74+
and `address`.
75+
Defaults to unset (disabled)
7076
```
7177

7278
### Faucet HD wallet
@@ -134,6 +140,14 @@ curl --header "Content-Type: application/json" \
134140
http://localhost:8000/credit
135141
```
136142

143+
### Using the faucet with Recaptcha validation enabled
144+
```
145+
curl --header "Content-Type: application/json" \
146+
--request POST \
147+
--data '{"denom":"ucosm","address":"cosmos1yre6ac7qfgyfgvh58ph0rgw627rhw766y430qq", "recaptcha": "03AFcWeA6KFdGLxDQIx_UZ9Y9IMlAJyen-DkT3k..."}' \
148+
http://localhost:8000/credit
149+
```
150+
137151
### Checking the faucets status
138152

139153
The faucet provides a simple status check in the form of an http GET request. As

packages/faucet/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@
4949
"@cosmjs/utils": "workspace:^",
5050
"@koa/cors": "^3.3",
5151
"koa": "^2.13",
52-
"koa-bodyparser": "^4.3"
52+
"koa-bodyparser": "^4.3",
53+
"undici": "^6.18.2"
5354
},
5455
"devDependencies": {
5556
"@istanbuljs/nyc-config-typescript": "^1.0.1",

packages/faucet/src/api/requestparser.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import { RequestParser } from "./requestparser";
33
describe("RequestParser", () => {
44
it("can process valid credit request with denom", () => {
55
const body = { address: "abc", denom: "utkn" };
6-
expect(RequestParser.parseCreditBody(body)).toEqual({ address: "abc", denom: "utkn" });
6+
expect(RequestParser.parseCreditBody(body)).toEqual({
7+
address: "abc",
8+
denom: "utkn",
9+
recaptcha: undefined,
10+
});
711
});
812

913
it("throws helpful error message when ticker is found", () => {

packages/faucet/src/api/requestparser.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export interface CreditRequestBodyData {
77
readonly denom: string;
88
/** The recipient address */
99
readonly address: string;
10+
/** The recaptcha v2 response */
11+
readonly recaptcha: string | undefined;
1012
}
1113

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

25-
const { address, denom, ticker } = body as any;
27+
const { address, denom, ticker, recaptcha } = body as any;
2628

2729
if (typeof ticker !== "undefined") {
2830
throw new HttpError(400, "The 'ticker' field was removed in CosmJS 0.23. Please use 'denom' instead.");
@@ -47,6 +49,7 @@ export class RequestParser {
4749
return {
4850
address: address,
4951
denom: denom,
52+
recaptcha: recaptcha,
5053
};
5154
}
5255
}

packages/faucet/src/api/webserver.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import Koa from "koa";
22
import cors = require("@koa/cors");
33
import bodyParser from "koa-bodyparser";
4+
import { request } from "undici";
5+
import qs from "node:querystring";
46

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

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

87+
// if enabled, require recaptcha validation
88+
if (process.env.GOOGLE_RECAPTCHA_SECRET_KEY !== undefined) {
89+
const { body } = await request("https://www.google.com/recaptcha/api/siteverify", {
90+
method: "POST",
91+
headers: {
92+
"content-type": "application/x-www-form-urlencoded",
93+
},
94+
body: qs.stringify({
95+
secret: process.env.GOOGLE_RECAPTCHA_SECRET_KEY,
96+
response: recaptcha,
97+
}),
98+
});
99+
const verify_data = (await body.json()) as { success: boolean };
100+
if (!verify_data.success) {
101+
console.error(`recaptcha validation FAILED ${JSON.stringify(verify_data, null, 4)}`);
102+
throw new HttpError(423, `Recaptcha failed to verify`);
103+
}
104+
}
105+
85106
try {
86107
// Count addresses to prevent draining
87108
this.addressCounter.set(address, new Date());

yarn.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,7 @@ __metadata:
606606
source-map-support: ^0.5.19
607607
ts-node: ^8
608608
typescript: ~4.9
609+
undici: ^6.18.2
609610
webpack: ^5.76.0
610611
webpack-cli: ^4.6.0
611612
bin:
@@ -7697,6 +7698,13 @@ __metadata:
76977698
languageName: node
76987699
linkType: hard
76997700

7701+
"undici@npm:^6.18.2":
7702+
version: 6.18.2
7703+
resolution: "undici@npm:6.18.2"
7704+
checksum: c20e47bd4f959c00d24516756b178190f0a9ae000007e875f1f68c8e7f3f9a68b0a7faa03f3d030ddd71a9e3feb558fbce661b5229a0aa8380cfbe1cea4281e4
7705+
languageName: node
7706+
linkType: hard
7707+
77007708
"unique-filename@npm:^3.0.0":
77017709
version: 3.0.0
77027710
resolution: "unique-filename@npm:3.0.0"

0 commit comments

Comments
 (0)