@friendlycaptcha/server-sdk 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +3 -0
- package/CHANGELOG.md +8 -0
- package/README.md +76 -3
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/client/client.js +1 -1
- package/dist/client/client.js.map +1 -1
- package/dist/client/result.d.ts +1 -1
- package/dist/client/result.js +2 -2
- package/dist/client/result.js.map +1 -1
- package/dist/client/version.gen.d.ts +1 -1
- package/dist/client/version.gen.js +1 -1
- package/example/README.md +29 -0
- package/example/package-lock.json +1404 -0
- package/example/package.json +24 -0
- package/example/src/index.ts +84 -0
- package/example/tsconfig.json +13 -0
- package/example/views/index.ejs +86 -0
- package/package.json +3 -2
- package/src/api/index.ts +1 -1
- package/src/client/client.ts +5 -2
- package/src/client/errors.ts +7 -7
- package/src/client/index.ts +1 -1
- package/src/client/result.ts +8 -7
- package/src/client/version.gen.ts +1 -1
- package/test/client/client.test.ts +11 -12
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "friendly-captcha-example",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Friendly Captcha V2 Javascript SDK integration example",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"private": true,
|
|
8
|
+
"scripts": {
|
|
9
|
+
"start": "node --import tsx src/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"author": "Friendly Captcha GmbH",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@friendlycaptcha/server-sdk": "^0.1.0",
|
|
15
|
+
"ejs": "^3.1.9",
|
|
16
|
+
"express": "^4.18.2"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/express": "^4.17.21",
|
|
20
|
+
"@types/node": "^20.11.5",
|
|
21
|
+
"tsx": "^4.7.0",
|
|
22
|
+
"typescript": "^5.3.3"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import express, { Request, Response } from "express";
|
|
2
|
+
import { FriendlyCaptchaClient } from "@friendlycaptcha/server-sdk";
|
|
3
|
+
|
|
4
|
+
type FormMessage = {
|
|
5
|
+
subject: string;
|
|
6
|
+
message: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const app = express();
|
|
10
|
+
const port = process.env.PORT || 3000;
|
|
11
|
+
|
|
12
|
+
const FRC_SITEKEY = process.env.FRC_SITEKEY;
|
|
13
|
+
const FRC_APIKEY = process.env.FRC_APIKEY;
|
|
14
|
+
|
|
15
|
+
// Optionally we can pass in custom endpoints to be used, such as "eu".
|
|
16
|
+
const FRC_SITEVERIFY_ENDPOINT = process.env.FRC_SITEVERIFY_ENDPOINT;
|
|
17
|
+
const FRC_WIDGET_ENDPOINT = process.env.FRC_WIDGET_ENDPOINT;
|
|
18
|
+
|
|
19
|
+
if (!FRC_SITEKEY || !FRC_APIKEY) {
|
|
20
|
+
console.error(
|
|
21
|
+
"Please set the FRC_SITEKEY and FRC_APIKEY environment values before running this example to your Friendly Captcha sitekey and API key respectively.",
|
|
22
|
+
);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const frcClient = new FriendlyCaptchaClient({
|
|
27
|
+
apiKey: FRC_APIKEY,
|
|
28
|
+
sitekey: FRC_SITEKEY,
|
|
29
|
+
siteverifyEndpoint: FRC_SITEVERIFY_ENDPOINT, // optional, defaults to "global"
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
app.set("view engine", "ejs");
|
|
33
|
+
app.use(express.urlencoded({ extended: true }));
|
|
34
|
+
|
|
35
|
+
app.get("/", (req: Request, res: Response) => {
|
|
36
|
+
res.render("index", {
|
|
37
|
+
message: "",
|
|
38
|
+
sitekey: FRC_SITEKEY,
|
|
39
|
+
widgetEndpoint: FRC_WIDGET_ENDPOINT,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
app.post("/", async (req: Request, res: Response) => {
|
|
44
|
+
const formData = req.body;
|
|
45
|
+
const formMessage = formData as FormMessage;
|
|
46
|
+
|
|
47
|
+
const frcCaptchaResponse = formData["frc-captcha-response"];
|
|
48
|
+
const result = await frcClient.verifyCaptchaResponse(frcCaptchaResponse);
|
|
49
|
+
if (!result.wasAbleToVerify()) {
|
|
50
|
+
// In this case we were not actually able to verify the response embedded in the form, but we may still want to accept it.
|
|
51
|
+
// It could mean there is a network issue or that the service is down. In those cases you generally want to accept submissions anyhow.
|
|
52
|
+
// That's why we use `shouldAccept()` below to actually accept or reject the form submission. It will return true in these cases.
|
|
53
|
+
|
|
54
|
+
if (result.isClientError()) {
|
|
55
|
+
// Something is wrong with our configuration, check your API key!
|
|
56
|
+
// Send yourself an alert to fix this! Your site is unprotected until you fix this.
|
|
57
|
+
console.error("CAPTCHA CONFIG ERROR: ", result.getErrorCode(), result.getResponseError());
|
|
58
|
+
} else {
|
|
59
|
+
console.error("Failed to verify captcha response: ", result.getErrorCode(), result.getResponseError());
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!result.shouldAccept()) {
|
|
64
|
+
res.render("index", {
|
|
65
|
+
message: "❌ Anti-robot check failed, please try again.",
|
|
66
|
+
sitekey: FRC_SITEKEY,
|
|
67
|
+
widgetEndpoint: FRC_WIDGET_ENDPOINT,
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// The captcha was OK, process the form.
|
|
73
|
+
formMessage; // Normally we would use the form data in `formMessage` here and submit it to our database.
|
|
74
|
+
|
|
75
|
+
res.render("index", {
|
|
76
|
+
message: "✅ Your message has been submitted successfully.",
|
|
77
|
+
sitekey: FRC_SITEKEY,
|
|
78
|
+
widgetEndpoint: FRC_WIDGET_ENDPOINT,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
app.listen(port, () => {
|
|
83
|
+
console.log(`Server running at http://localhost:${port}`);
|
|
84
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es6",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"outDir": "./dist",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true
|
|
10
|
+
},
|
|
11
|
+
"include": ["src/**/*.ts"],
|
|
12
|
+
"exclude": ["node_modules"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
+
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
|
8
|
+
<title>Friendly Captcha Javascript SDK example</title>
|
|
9
|
+
<style>
|
|
10
|
+
* {
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans",
|
|
16
|
+
"Helvetica Neue", sans-serif;
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
height: 100vh;
|
|
21
|
+
width: 100vw;
|
|
22
|
+
background-color: #f2f2f2;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
h1 {
|
|
26
|
+
font-size: 1.4em;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
main {
|
|
30
|
+
width: 100%;
|
|
31
|
+
max-width: 680px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.message {
|
|
35
|
+
width: 100%;
|
|
36
|
+
padding: 0.5em 2em;
|
|
37
|
+
margin: 1.5em 0;
|
|
38
|
+
border-radius: 8px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.message h2 {
|
|
42
|
+
margin-bottom: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
label {
|
|
46
|
+
display: block;
|
|
47
|
+
}
|
|
48
|
+
</style>
|
|
49
|
+
|
|
50
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk@0.1.8/site.min.js" async defer></script>
|
|
51
|
+
<script nomodule src="https://cdn.jsdelivr.net/npm/@friendlycaptcha/sdk@0.1.8/site.compat.min.js" async
|
|
52
|
+
defer></script>
|
|
53
|
+
|
|
54
|
+
<!-- You can change the data-api-endpoint via this tag. More info here https://developer.friendlycaptcha.com/docs/sdk/configuration -->
|
|
55
|
+
<!-- <meta name="frc-api-endpoint" content="."> -->
|
|
56
|
+
</head>
|
|
57
|
+
|
|
58
|
+
<body>
|
|
59
|
+
<main>
|
|
60
|
+
<h1>Friendly Captcha JavaScript SDK form</h1>
|
|
61
|
+
<% if (message) { %>
|
|
62
|
+
<p>
|
|
63
|
+
<%= message %>
|
|
64
|
+
</p>
|
|
65
|
+
<% } %>
|
|
66
|
+
<form method="POST">
|
|
67
|
+
<div class="form-group">
|
|
68
|
+
<label>Subject:</label><br />
|
|
69
|
+
<input type="text" name="subject" /><br />
|
|
70
|
+
<label>Message:</label><br />
|
|
71
|
+
<textarea name="message"></textarea><br />
|
|
72
|
+
<div class="frc-captcha" data-sitekey="<%= sitekey %>" <% if (widgetEndpoint) { %> data-api-endpoint="<%=
|
|
73
|
+
widgetEndpoint %>" <% } %>></div>
|
|
74
|
+
<input style="margin-top: 1em" type="submit" value="Submit" />
|
|
75
|
+
</div>
|
|
76
|
+
</form>
|
|
77
|
+
</main>
|
|
78
|
+
|
|
79
|
+
<script>
|
|
80
|
+
if (window.history.replaceState) {
|
|
81
|
+
window.history.replaceState(null, null, window.location.href);
|
|
82
|
+
}
|
|
83
|
+
</script>
|
|
84
|
+
</body>
|
|
85
|
+
|
|
86
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friendlycaptcha/server-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Serverside client SDK for the Friendly Captcha V2 API",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"build:docs": "rm -rf build/docs && api-documenter markdown --output-folder docs --input temp/",
|
|
13
13
|
"test": "ava test/**/*.ts --timeout=1m",
|
|
14
14
|
"api-extractor": "api-extractor",
|
|
15
|
-
"fmt": "prettier src/**/*.ts test/**/*.ts package.json --write"
|
|
15
|
+
"fmt": "prettier src/**/*.ts test/**/*.ts example/**/*.ts package.json --write",
|
|
16
|
+
"fmt:ci": "prettier src/**/*.ts test/**/*.ts example/**/*.ts package.json --check"
|
|
16
17
|
},
|
|
17
18
|
"author": "Friendly Captcha GmbH",
|
|
18
19
|
"license": "MIT",
|
package/src/api/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./types";
|
|
1
|
+
export * from "./types.js";
|
package/src/client/client.ts
CHANGED
|
@@ -84,7 +84,10 @@ export class FriendlyCaptchaClient {
|
|
|
84
84
|
* * `sitekey`: The sitekey to use for this request. Defaults to the sitekey passed to the constructor (if any).
|
|
85
85
|
* @returns A promise that always resolves to a `VerifyResult` object, which contains the fields `shouldAccept()` and `wasAbleToVerify()`. This promise never rejects.
|
|
86
86
|
*/
|
|
87
|
-
public verifyCaptchaResponse(
|
|
87
|
+
public verifyCaptchaResponse(
|
|
88
|
+
response: string,
|
|
89
|
+
opts: { timeout?: number; sitekey?: string } = {},
|
|
90
|
+
): Promise<VerifyResult> {
|
|
88
91
|
const siteverifyRequest: SiteverifyRequest = {
|
|
89
92
|
response,
|
|
90
93
|
};
|
|
@@ -105,7 +108,7 @@ export class FriendlyCaptchaClient {
|
|
|
105
108
|
const headers = {
|
|
106
109
|
"Content-Type": "application/json",
|
|
107
110
|
Accept: "application/json",
|
|
108
|
-
"
|
|
111
|
+
"Frc-Sdk": "friendly-captcha-javascript-sdk@" + SDK_VERSION,
|
|
109
112
|
"X-Api-Key": this.apiKey,
|
|
110
113
|
};
|
|
111
114
|
|
package/src/client/errors.ts
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Failed to encode the captcha response. This means the captcha response was invalid and should never be accepted.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* @public
|
|
5
5
|
*/
|
|
6
6
|
export const FAILED_TO_ENCODE_ERROR_CODE = "failed_to_encode_request";
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
9
|
* The request couldn't be made, perhaps there is a network outage, DNS issue, or the API is unreachable.
|
|
10
|
-
*
|
|
10
|
+
*
|
|
11
11
|
* @public
|
|
12
12
|
*/
|
|
13
13
|
export const REQUEST_FAILED_ERROR_CODE = "request_failed";
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
*
|
|
16
16
|
* The request couldn't be made, perhaps there is a network outage, DNS issue, or the API is unreachable.
|
|
17
|
-
*
|
|
17
|
+
*
|
|
18
18
|
* @public
|
|
19
19
|
*/
|
|
20
20
|
export const REQUEST_FAILED_TIMEOUT_ERROR_CODE = "request_failed_due_to_timeout";
|
|
21
21
|
/**
|
|
22
22
|
* An error occured on the client side that could've been prevented. This generally means your configuration is wrong.
|
|
23
|
-
*
|
|
23
|
+
*
|
|
24
24
|
* Check your API key and sitekey.
|
|
25
25
|
* @public
|
|
26
26
|
*/
|
|
27
27
|
export const FAILED_DUE_TO_CLIENT_ERROR_CODE = "request_failed_due_to_client_error";
|
|
28
28
|
/**
|
|
29
29
|
* The response from the Friendly Captcha API could not be decoded.
|
|
30
|
-
*
|
|
30
|
+
*
|
|
31
31
|
* @public
|
|
32
32
|
*/
|
|
33
33
|
export const FAILED_TO_DECODE_RESPONSE_ERROR_CODE = "verification_response_could_not_be_decoded";
|
package/src/client/index.ts
CHANGED
package/src/client/result.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SiteverifyErrorResponseErrorData, SiteverifyResponse } from "../api";
|
|
1
|
+
import type { SiteverifyErrorResponseErrorData, SiteverifyResponse } from "../api/index.js";
|
|
2
2
|
import {
|
|
3
3
|
FAILED_DUE_TO_CLIENT_ERROR_CODE,
|
|
4
4
|
FAILED_TO_DECODE_RESPONSE_ERROR_CODE,
|
|
@@ -10,14 +10,13 @@ import {
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* The result of a captcha siteverify request.
|
|
13
|
-
*
|
|
13
|
+
*
|
|
14
14
|
* @public
|
|
15
15
|
*/
|
|
16
16
|
export class VerifyResult {
|
|
17
|
-
|
|
18
17
|
private strict: boolean;
|
|
19
18
|
/**
|
|
20
|
-
* The HTTP status code of the response.
|
|
19
|
+
* The HTTP status code of the response.
|
|
21
20
|
* `-1` if there was no response.
|
|
22
21
|
*/
|
|
23
22
|
public status: number = -1;
|
|
@@ -40,7 +39,7 @@ export class VerifyResult {
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
/**
|
|
43
|
-
* @returns Whether the captcha should be accepted.
|
|
42
|
+
* @returns Whether the captcha should be accepted.
|
|
44
43
|
* Note that this does not necessarily mean it was verified.
|
|
45
44
|
*/
|
|
46
45
|
public shouldAccept(): boolean {
|
|
@@ -96,7 +95,9 @@ export class VerifyResult {
|
|
|
96
95
|
* Something went wrong making the request to the Friendly Captcha API, perhaps there is a network connection issue?
|
|
97
96
|
*/
|
|
98
97
|
public isRequestOrTimeoutError(): boolean {
|
|
99
|
-
return
|
|
98
|
+
return (
|
|
99
|
+
this.clientErrorType === REQUEST_FAILED_ERROR_CODE || this.clientErrorType === REQUEST_FAILED_TIMEOUT_ERROR_CODE
|
|
100
|
+
);
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
/**
|
|
@@ -139,7 +140,7 @@ export class VerifyResult {
|
|
|
139
140
|
* If this is false, you should notify yourself and use `getErrorCode()` and `getResponseError()` to see what is wrong.
|
|
140
141
|
*/
|
|
141
142
|
public wasAbleToVerify(): boolean {
|
|
142
|
-
// If we failed to encode, we actually consider `wasAbleToVerify` to be true. This is because we don't want to
|
|
143
|
+
// If we failed to encode, we actually consider `wasAbleToVerify` to be true. This is because we don't want to
|
|
143
144
|
// alert on failed encoding: an attacker could send such malformed data that it fails to encode.
|
|
144
145
|
if (this.isEncodeError()) {
|
|
145
146
|
return true;
|
|
@@ -34,15 +34,14 @@ test("unencodable response gets rejected", async (t) => {
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
test("request failure gets accepted", async (t) => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
const client = new FriendlyCaptchaClient({ apiKey: "my-api-key", siteverifyEndpoint: "http://localhost:9999" }); // Assumes nothing is listening on port 9999
|
|
38
|
+
const result = await client.verifyCaptchaResponse("something");
|
|
39
|
+
|
|
40
|
+
t.false(result.isEncodeError());
|
|
41
|
+
t.false(result.isClientError());
|
|
42
|
+
t.true(result.isRequestOrTimeoutError());
|
|
43
|
+
t.false(result.isDecodeError());
|
|
44
|
+
|
|
45
|
+
t.true(result.shouldAccept());
|
|
46
|
+
t.false(result.shouldReject());
|
|
47
|
+
});
|