-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathverify.php
More file actions
143 lines (127 loc) · 4.75 KB
/
Copy pathverify.php
File metadata and controls
143 lines (127 loc) · 4.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
<?php
/**
* CaptchaLa server-side verification example
* MIT License — copy / adapt / ship
*
* Usage from your frontend:
* POST /verify.php
* Content-Type: application/json
* { "token": "<token from onSuccess>", "action": "login" }
*
* Returns:
* { "ok": true, "risk_score": 12 } (allow the user)
* { "ok": false, "error": "..." } (block / re-challenge)
*
* The CaptchaLa API key + secret are loaded from env vars.
* Set them on your server:
* export CAPTCHALA_APP_KEY="app_xxx"
* export CAPTCHALA_APP_SECRET="sec_xxx"
*
* Or replace the getenv() calls below with your config loader.
*/
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');
// ---- Config ---------------------------------------------------------------
// In production:
// 1. Generate your own at https://dash.captcha.la
// 2. Keep app_secret out of webroot — load from env vars or a config file
// stored outside the document root (we use /etc/captchala/demo-secrets.php here).
// 3. Never commit secrets to git or expose them as part of view-source mirrors.
$secrets = @include '/tmp/captchala-demo-secrets.php';
if (!is_array($secrets)) {
$secrets = ['APP_KEY' => getenv('CAPTCHALA_APP_KEY'), 'APP_SECRET' => getenv('CAPTCHALA_APP_SECRET')];
}
$APP_KEY = (string)($secrets['APP_KEY'] ?? '');
$APP_SECRET = (string)($secrets['APP_SECRET'] ?? '');
$VERIFY_URL = 'https://apiv1.captcha.la/v1/validate';
if ($APP_KEY === '' || $APP_SECRET === '') {
http_response_code(500);
echo json_encode(['ok' => false, 'error' => 'server_not_configured']);
exit;
}
// ---- Read request ---------------------------------------------------------
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['ok' => false, 'error' => 'method_not_allowed']);
exit;
}
$body = json_decode((string)file_get_contents('php://input'), true);
$token = isset($body['token']) ? trim((string)$body['token']) : '';
$action = isset($body['action']) ? trim((string)$body['action']) : 'default';
if ($token === '') {
echo json_encode(['ok' => false, 'error' => 'missing_token']);
exit;
}
// Real client IP — handles common proxy headers.
// In production, only trust X-Forwarded-For if you control the upstream proxy.
function clientIp(): string
{
foreach (['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR'] as $h) {
if (!empty($_SERVER[$h])) {
$ip = trim(explode(',', (string)$_SERVER[$h])[0]);
if (filter_var($ip, FILTER_VALIDATE_IP)) return $ip;
}
}
return '0.0.0.0';
}
// ---- Call CaptchaLa verify endpoint --------------------------------------
// Real validate API expects `pass_token` (not `token`).
// Frontend SDK's onSuccess(res) returns res.token — that IS the pass_token.
$payload = json_encode([
'pass_token' => $token,
'client_ip' => clientIp(),
], JSON_UNESCAPED_SLASHES);
$ch = curl_init($VERIFY_URL);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-App-Key: ' . $APP_KEY,
'X-App-Secret: ' . $APP_SECRET,
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_CONNECTTIMEOUT => 3,
]);
$resp = curl_exec($ch);
$http = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http !== 200) {
// The verify endpoint was unreachable or returned non-2xx.
// Decide: fail closed for high-value flows, fail open for low-value.
// Default here is fail closed.
http_response_code(502);
echo json_encode(['ok' => false, 'error' => 'upstream_unavailable', 'http' => $http]);
exit;
}
$data = json_decode((string)$resp, true);
if (!is_array($data) || !isset($data['code']) || $data['code'] !== 0) {
echo json_encode(['ok' => false, 'error' => 'invalid_response']);
exit;
}
$inner = $data['data'] ?? [];
$valid = !empty($inner['valid']);
$risk = isset($inner['risk_score']) ? (int)$inner['risk_score'] : null;
if (!$valid) {
echo json_encode([
'ok' => false,
'error' => $inner['error'] ?? 'verification_failed',
]);
exit;
}
// Optional: enforce action binding.
// If you initialized Captchala with action="login" but token came back action="register",
// reject — it's a token from a different flow.
$tokenAction = (string)($inner['action'] ?? '');
if ($tokenAction !== '' && $tokenAction !== $action) {
echo json_encode(['ok' => false, 'error' => 'action_mismatch']);
exit;
}
// All good — token is valid, single-use, action-bound.
echo json_encode([
'ok' => true,
'risk_score' => $risk,
'offline' => !empty($inner['offline']),
]);