├── .gitignore ├── CHANGELOG.md ├── README.md ├── demo ├── app.js ├── gt.js └── login.html ├── gt-sdk.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | npm-debug.log 3 | node_modules 4 | .DS_Store -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 4.1.2 2 | 3 | 1. 将gt.js转为本地存储,利用多CDN,增加静态文件加载的可靠性。 4 | 5 | # 4.1.0 6 | 7 | 1. 重命名配置参数 `apiServer` 为 `api_server` 8 | 2. 完善 README 9 | 10 | # 4.0.0 11 | 12 | 1. 完善 `register` 和 `validate` 接口提供的 Promise 风格的异步处理形式 13 | 2. 对于使用回调(handler)形式,符合了 NodeJS 中对错误的通知(即回调的第一个参数为err) 14 | 3. 修改demo,使用了 Promise 风格和回调风格,同时强调了 failback 流程 15 | 16 | # 3.0.0 17 | 18 | 1. 鉴于遇到很多用户对 `publicKey` 和 `privateKey` 容易搞混淆,先将其改名为 `geetest_id` 和 `geetest_key` 。因此,和以前的版本无法兼容,所以修改主版本号。 19 | 20 | # 2.2.1 21 | 22 | 1. 优化移动端自实现弹出式验证demo 23 | 24 | 25 | # 2.2.0 26 | 27 | 1. 新增移动端自实现弹出式验证demo 28 | 29 | # 2.1.1 30 | 31 | 1. 修复register协议名称未添加bug 32 | 33 | # 2.1.0 34 | 35 | 1. 添加promise形式回调 36 | 37 | # 2.0.0 38 | 39 | 1. 增加failback模式 40 | 2. 修改了接口register的回调参数 41 | 3. 实例化传参改为传入一个配置对象 42 | 43 | 具体请参照用法说明:[README](README.md) 44 | 45 | # Update in 1.0.0 更新内容 46 | 47 | 1.添加多实例验证码创建方法 48 | 2.更新demo 49 | 50 | # Update in 0.5.4 更新内容 51 | 对challenge进行md5操作,增加安全性。 52 | 53 | # Update in 0.5.1 更新内容 54 | 将sdk里面的bodyParser函数去掉,简化了各接口的内部实现,接口使用不变。 55 | 添加了示例,并将带有failback功能的脚本统一放到了gt.js文件中,提供一个initGeetest的接口,用法参考示例 56 | 57 | # Update in 0.4.0 更新内容 58 | Use recommended error handler method: callback(err, result). 59 | 规范了错误处理,所有的回调函数现在均遵循node规范,以callback(err, result)的形式,因此您需要按照新的方法修改您的代码以正常运行。 60 | 61 | # Update in 0.3.3 更新内容 62 | You can modify the api server address 63 | 64 | 你现在可以修改APIServer的地址了 65 | 66 | # Update in 0.3.1 更新内容 67 | Use register api to check the server status, if it return false, switch to local captcha manually 68 | 69 | 现在可以用register接口作为检查服务器是否正常的方法,如果register返回false则切换为本地验证码 70 | 71 | # What's New in 0.2.0 更新内容 72 | Pass public key to use `register` API, optional right now, if you don't use it now, the captcha might broke some time in the future 73 | 74 | 将你的Public Key作为第二个参数传入,此参数现在为可选参数,只有传入才能够使用`register`接口。Register接口可能在将来成为必须的接口 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **注意:3.x.x之后修改了所传入的字段名称,将 `publicKey` 和 `privateKey` 修改为 `geetest_id` 和 `geetest_key` ,升级会造成不兼容问题,请各位注意在升级前修改相应的字段名称,新用户无须关注此修改** 2 | 3 | **我们现在将 https://static.geetest.com/static/tools/gt.js 文件放在了本地,目的在于利用多CDN,尽可能保证静态文件的加载。** 4 | 5 | # Install 安装 6 | 7 | ``` 8 | npm install geetest --save 9 | ``` 10 | 11 | # 运行DEMO 12 | 13 | ```shell 14 | cd Project 15 | git clone https://github.com/GeeTeam/gt-node-sdk.git 16 | cd gt-node-sdk 17 | npm install 18 | npm start 19 | // 最后请打开浏览器访问localhost:8080 20 | // 了解sdk的使用方式请查阅demo目录下的app.js文件 21 | ``` 22 | 23 | # 使用说明 24 | 25 | sdk 提供 `Geetest` 构造函数,实例化时需要传入一个配置对象。 26 | 27 | 配置对象的字段如下: 28 | 29 | - `geetest_id`:验证公钥,**必须** 30 | - `geetest_key`:验证私钥,**必须** 31 | - `protocol`:与极验服务器交互时使用的协议,默认为 `http://`,**可选** 32 | - `api_server`:针对私有化用户提供对默认的 `api.geetest.com` 进行修改,普通用户无需关注此选项,**可选** 33 | 34 | `geetest_id` 和 `geetest_key` 申请地址:[account.geetest.com](http://account.geetest.com/) 35 | 36 | 申请后,初始化 `Geetest`: 37 | 38 | ```js 39 | var Geetest = require('geetest'); 40 | 41 | var captcha = new Geetest({ 42 | geetest_id: 'xxx', // 将xxx替换为您申请的id 43 | geetest_key: 'xxx', // 将xxx替换为您申请的key 44 | }); 45 | ``` 46 | 47 | 上述 `Geetest` 的实例 `captcha` 提供两个方法: 48 | 49 | ## `register(callback)` 50 | 51 | ```js 52 | // 回调形式 53 | captcha.register(function (err, data) { 54 | 55 | // err 表示发生错误 56 | if (err) { 57 | console.error(err); 58 | return; 59 | } 60 | 61 | // data 为一个对象,包含 gt, challenge, success, new_captcha 字段 62 | // success 为 1 表示正常模式,为 0 表示宕机模式(failback, fallback) 63 | var body = { 64 | gt: data.geetest_id, 65 | challenge: data.challenge, 66 | success: data.success 67 | }; 68 | 69 | // 将 body 发送给前端... 70 | }); 71 | 72 | // Promise 形式 73 | captcha.register().then(function (data) { 74 | 75 | // data 为一个对象,包含 gt, challenge, success, new_captcha 字段 76 | // success 为 1 表示正常模式,为 0 表示宕机模式(failback, fallback) 77 | var body = { 78 | gt: data.geetest_id, 79 | challenge: data.challenge, 80 | success: data.success 81 | }; 82 | 83 | // 将 body 发送给前端... 84 | 85 | }, function (err) { 86 | console.error(err); 87 | }); 88 | ``` 89 | 90 | ## `validate(result, callback)` 91 | 92 | ```js 93 | // 回调形式 94 | captcha.validate({ 95 | challenge: 'xxx', 96 | validate: 'xxx', 97 | seccode: 'xxx' 98 | }, function (err, success) { 99 | 100 | if (err) { 101 | console.error(err); 102 | return; 103 | } 104 | 105 | if (success) { 106 | 107 | // 二次验证成功,运行用户的操作 108 | 109 | } else { 110 | 111 | // 二次验证失败,不允许用户的操作 112 | 113 | } 114 | 115 | }); 116 | 117 | // Promise 形式 118 | captcha.validate({ 119 | challenge: 'xxx', 120 | validate: 'xxx', 121 | seccode: 'xxx' 122 | }).then(function (success) { 123 | 124 | if (success) { 125 | 126 | // 二次验证成功,运行用户的操作 127 | 128 | } else { 129 | 130 | // 二次验证失败,不允许用户的操作 131 | 132 | } 133 | }, function (err) { 134 | console.error(err); 135 | }) 136 | ``` 137 | 138 | ### 使用前,强烈建议您阅读我们的[入门文档](http://www.geetest.com/install/sections/idx-main-frame.html) 139 | 140 | ### 更新历史:[CHANGELOG](CHANGELOG.md) 141 | 142 | -------------------------------------------------------------------------------- /demo/app.js: -------------------------------------------------------------------------------- 1 | var express = require("express"); 2 | var bodyParser = require("body-parser"); 3 | 4 | var Geetest = require('../gt-sdk'); 5 | 6 | var app = express(); 7 | app.use(bodyParser.json()); 8 | app.use(bodyParser.urlencoded({extended: true})); 9 | app.use(express.static('./')); 10 | app.get("/", function (req, res) { 11 | res.sendFile(__dirname + "/login.html"); 12 | }); 13 | 14 | // pc 端接口 15 | 16 | var pcGeetest = new Geetest({ 17 | geetest_id: 'b46d1900d0a894591916ea94ea91bd2c', 18 | geetest_key: '36fc3fe98530eea08dfc6ce76e3d24c4' 19 | }); 20 | app.get("/pc-geetest/register", function (req, res) { 21 | 22 | // 向极验申请每次验证所需的challenge 23 | pcGeetest.register(function (err, data) { 24 | if (err) { 25 | console.error(err); 26 | return; 27 | } 28 | 29 | if (!data.success) { 30 | // 进入 failback,如果一直进入此模式,请检查服务器到极验服务器是否可访问 31 | // 可以通过修改 hosts 把极验服务器 api.geetest.com 指到不可访问的地址 32 | 33 | // 为以防万一,你可以选择以下两种方式之一: 34 | 35 | // 1. 继续使用极验提供的failback备用方案 36 | res.send(data); 37 | 38 | // 2. 使用自己提供的备用方案 39 | // todo 40 | 41 | } else { 42 | // 正常模式 43 | res.send(data); 44 | } 45 | }); 46 | }); 47 | app.post("/pc-geetest/validate", function (req, res) { 48 | 49 | // 对ajax提供的验证凭证进行二次验证 50 | pcGeetest.validate({ 51 | challenge: req.body.geetest_challenge, 52 | validate: req.body.geetest_validate, 53 | seccode: req.body.geetest_seccode 54 | }, function (err, success) { 55 | 56 | if (err) { 57 | // 网络错误 58 | res.send({ 59 | status: "error", 60 | info: err 61 | }); 62 | } else if (!success) { 63 | 64 | // 二次验证失败 65 | res.send({ 66 | status: "fail", 67 | info: '登录失败' 68 | }); 69 | } else { 70 | 71 | res.send({ 72 | status: "success", 73 | info: '登录成功' 74 | }); 75 | } 76 | }); 77 | }); 78 | app.post("/pc-geetest/form-validate", function (req, res) { 79 | 80 | // 对form表单提供的验证凭证进行验证 81 | pcGeetest.validate({ 82 | 83 | challenge: req.body.geetest_challenge, 84 | validate: req.body.geetest_validate, 85 | seccode: req.body.geetest_seccode 86 | 87 | }, function (err, success) { 88 | 89 | if (err) { 90 | // 网络错误 91 | res.send(err); 92 | 93 | } else if (!success) { 94 | 95 | // 二次验证失败 96 | res.send("

登录失败

"); 97 | 98 | } else { 99 | res.send("

登录成功

"); 100 | } 101 | 102 | }); 103 | }); 104 | 105 | // 移动端接口 106 | var mobileGeetest = new Geetest({ 107 | geetest_id: '7c25da6fe21944cfe507d2f9876775a9', 108 | geetest_key: 'f5883f4ee3bd4fa8caec67941de1b903' 109 | }); 110 | app.get("/mobile-geetest/register", function (req, res) { 111 | 112 | // 向极验申请每次验证所需的challenge 113 | mobileGeetest.register().then(function (data) { 114 | 115 | if (!data.success) { 116 | // 进入 failback,如果一直进入此模式,请检查服务器到极验服务器是否可访问 117 | // 可以通过修改 hosts 把极验服务器 api.geetest.com 指到不可访问的地址 118 | 119 | // 为以防万一,你可以选择以下两种方式之一: 120 | 121 | // 1. 继续使用极验提供的failback备用方案 122 | res.send(data); 123 | 124 | // 2. 使用自己提供的备用方案 125 | // todo 126 | 127 | } else { 128 | // 正常模式 129 | res.send(data); 130 | } 131 | 132 | }, function (err) { 133 | 134 | console.error(err); 135 | 136 | }); 137 | }); 138 | app.post("/mobile-geetest/validate", function (req, res) { 139 | 140 | // 对ajax提供的验证凭证进行二次验证 141 | mobileGeetest.validate({ 142 | challenge: req.body.geetest_challenge, 143 | validate: req.body.geetest_validate, 144 | seccode: req.body.geetest_seccode 145 | }).then(function (success) { 146 | if (success) { 147 | res.send({ 148 | status: "success", 149 | info: '登录成功' 150 | }); 151 | } else { 152 | res.send({ 153 | status: "fail", 154 | info: '登录失败' 155 | }); 156 | } 157 | }, function (err) { 158 | // 网络错误 159 | res.send({ 160 | status: "error", 161 | info: err 162 | }); 163 | }); 164 | }); 165 | 166 | var port = 9988; 167 | app.listen(port, function () { 168 | console.log('listening at http://localhost:' + port) 169 | }); 170 | -------------------------------------------------------------------------------- /demo/gt.js: -------------------------------------------------------------------------------- 1 | /* initGeetest 1.0.0 2 | * 用于加载id对应的验证码库,并支持宕机模式 3 | * 暴露 initGeetest 进行验证码的初始化 4 | * 一般不需要用户进行修改 5 | */ 6 | (function (global, factory) { 7 | "use strict"; 8 | if (typeof module === "object" && typeof module.exports === "object") { 9 | // CommonJS 10 | module.exports = global.document ? 11 | factory(global, true) : 12 | function (w) { 13 | if (!w.document) { 14 | throw new Error("Geetest requires a window with a document"); 15 | } 16 | return factory(w); 17 | }; 18 | } else { 19 | factory(global); 20 | } 21 | })(typeof window !== "undefined" ? window : this, function (window, noGlobal) { 22 | "use strict"; 23 | if (typeof window === 'undefined') { 24 | throw new Error('Geetest requires browser environment'); 25 | } 26 | var document = window.document; 27 | var Math = window.Math; 28 | var head = document.getElementsByTagName("head")[0]; 29 | 30 | function _Object(obj) { 31 | this._obj = obj; 32 | } 33 | 34 | _Object.prototype = { 35 | _each: function (process) { 36 | var _obj = this._obj; 37 | for (var k in _obj) { 38 | if (_obj.hasOwnProperty(k)) { 39 | process(k, _obj[k]); 40 | } 41 | } 42 | return this; 43 | } 44 | }; 45 | function Config(config) { 46 | var self = this; 47 | new _Object(config)._each(function (key, value) { 48 | self[key] = value; 49 | }); 50 | } 51 | 52 | Config.prototype = { 53 | api_server: 'api.geetest.com', 54 | protocol: 'http://', 55 | type_path: '/gettype.php', 56 | fallback_config: { 57 | slide: { 58 | static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"], 59 | type: 'slide', 60 | slide: '/static/js/geetest.0.0.0.js' 61 | }, 62 | fullpage: { 63 | static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"], 64 | type: 'fullpage', 65 | fullpage: '/static/js/fullpage.0.0.0.js' 66 | } 67 | }, 68 | _get_fallback_config: function () { 69 | var self = this; 70 | if (isString(self.type)) { 71 | return self.fallback_config[self.type]; 72 | } else if (self.new_captcha) { 73 | return self.fallback_config.fullpage; 74 | } else { 75 | return self.fallback_config.slide; 76 | } 77 | }, 78 | _extend: function (obj) { 79 | var self = this; 80 | new _Object(obj)._each(function (key, value) { 81 | self[key] = value; 82 | }) 83 | } 84 | }; 85 | var isNumber = function (value) { 86 | return (typeof value === 'number'); 87 | }; 88 | var isString = function (value) { 89 | return (typeof value === 'string'); 90 | }; 91 | var isBoolean = function (value) { 92 | return (typeof value === 'boolean'); 93 | }; 94 | var isObject = function (value) { 95 | return (typeof value === 'object' && value !== null); 96 | }; 97 | var isFunction = function (value) { 98 | return (typeof value === 'function'); 99 | }; 100 | var callbacks = {}; 101 | var status = {}; 102 | var random = function () { 103 | return parseInt(Math.random() * 10000) + (new Date()).valueOf(); 104 | }; 105 | var loadScript = function (url, cb) { 106 | var script = document.createElement("script"); 107 | script.charset = "UTF-8"; 108 | script.async = true; 109 | script.onerror = function () { 110 | cb(true); 111 | }; 112 | var loaded = false; 113 | script.onload = script.onreadystatechange = function () { 114 | if (!loaded && 115 | (!script.readyState || 116 | "loaded" === script.readyState || 117 | "complete" === script.readyState)) { 118 | 119 | loaded = true; 120 | setTimeout(function () { 121 | cb(false); 122 | }, 0); 123 | } 124 | }; 125 | script.src = url; 126 | head.appendChild(script); 127 | }; 128 | var normalizeDomain = function (domain) { 129 | return domain.replace(/^https?:\/\/|\/$/g, ''); 130 | }; 131 | var normalizePath = function (path) { 132 | path = path.replace(/\/+/g, '/'); 133 | if (path.indexOf('/') !== 0) { 134 | path = '/' + path; 135 | } 136 | return path; 137 | }; 138 | var normalizeQuery = function (query) { 139 | if (!query) { 140 | return ''; 141 | } 142 | var q = '?'; 143 | new _Object(query)._each(function (key, value) { 144 | if (isString(value) || isNumber(value) || isBoolean(value)) { 145 | q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; 146 | } 147 | }); 148 | if (q === '?') { 149 | q = ''; 150 | } 151 | return q.replace(/&$/, ''); 152 | }; 153 | var makeURL = function (protocol, domain, path, query) { 154 | domain = normalizeDomain(domain); 155 | 156 | var url = normalizePath(path) + normalizeQuery(query); 157 | if (domain) { 158 | url = protocol + domain + url; 159 | } 160 | 161 | return url; 162 | }; 163 | var load = function (protocol, domains, path, query, cb) { 164 | var tryRequest = function (at) { 165 | 166 | var url = makeURL(protocol, domains[at], path, query); 167 | loadScript(url, function (err) { 168 | if (err) { 169 | if (at >= domains.length - 1) { 170 | cb(true); 171 | } else { 172 | tryRequest(at + 1); 173 | } 174 | } else { 175 | cb(false); 176 | } 177 | }); 178 | }; 179 | tryRequest(0); 180 | }; 181 | var jsonp = function (domains, path, config, callback) { 182 | if (isObject(config.getLib)) { 183 | config._extend(config.getLib); 184 | callback(config); 185 | return; 186 | } 187 | if (config.offline) { 188 | callback(config._get_fallback_config()); 189 | return; 190 | } 191 | var cb = "geetest_" + random(); 192 | window[cb] = function (data) { 193 | if (data.status === 'success') { 194 | callback(data.data); 195 | } else if (!data.status) { 196 | callback(data); 197 | } else { 198 | callback(config._get_fallback_config()); 199 | } 200 | window[cb] = undefined; 201 | try { 202 | delete window[cb]; 203 | } catch (e) { 204 | } 205 | }; 206 | load(config.protocol, domains, path, { 207 | gt: config.gt, 208 | callback: cb 209 | }, function (err) { 210 | if (err) { 211 | callback(config._get_fallback_config()); 212 | } 213 | }); 214 | }; 215 | var throwError = function (errorType, config) { 216 | var errors = { 217 | networkError: '网络错误' 218 | }; 219 | if (typeof config.onError === 'function') { 220 | config.onError(errors[errorType]); 221 | } else { 222 | throw new Error(errors[errorType]); 223 | } 224 | }; 225 | var detect = function () { 226 | return !!window.Geetest; 227 | }; 228 | if (detect()) { 229 | status.slide = "loaded"; 230 | } 231 | var initGeetest = function (userConfig, callback) { 232 | var config = new Config(userConfig); 233 | if (userConfig.https) { 234 | config.protocol = 'https://'; 235 | } else if (!userConfig.protocol) { 236 | config.protocol = window.location.protocol + '//'; 237 | } 238 | jsonp([config.api_server || config.apiserver], config.type_path, config, function (newConfig) { 239 | var type = newConfig.type; 240 | var init = function () { 241 | config._extend(newConfig); 242 | callback(new window.Geetest(config)); 243 | }; 244 | callbacks[type] = callbacks[type] || []; 245 | var s = status[type] || 'init'; 246 | if (s === 'init') { 247 | status[type] = 'loading'; 248 | callbacks[type].push(init); 249 | load(config.protocol, newConfig.static_servers || newConfig.domains, newConfig[type] || newConfig.path, null, function (err) { 250 | if (err) { 251 | status[type] = 'fail'; 252 | throwError('networkError', config); 253 | } else { 254 | status[type] = 'loaded'; 255 | var cbs = callbacks[type]; 256 | for (var i = 0, len = cbs.length; i < len; i = i + 1) { 257 | var cb = cbs[i]; 258 | if (isFunction(cb)) { 259 | cb(); 260 | } 261 | } 262 | callbacks[type] = []; 263 | } 264 | }); 265 | } else if (s === "loaded") { 266 | init(); 267 | } else if (s === "fail") { 268 | throwError('networkError', config); 269 | } else if (s === "loading") { 270 | callbacks[type].push(init); 271 | } 272 | }); 273 | }; 274 | window.initGeetest = initGeetest; 275 | return initGeetest; 276 | }); 277 | 278 | -------------------------------------------------------------------------------- /demo/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | gt-node-sdk-demo 7 | 74 | 75 | 76 |

极验验证SDKDemo

77 |

78 |
79 |

80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 105 | 106 | 161 |

162 |
163 |

164 | 184 | 185 | 223 |

224 |
225 |

226 | 243 | 244 | 301 | 302 | 303 | -------------------------------------------------------------------------------- /gt-sdk.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var crypto = require('crypto'), 4 | request = require('request'), 5 | pkg = require("./package.json"); 6 | 7 | var md5 = function (str) { 8 | return crypto.createHash('md5').update(String(str)).digest('hex'); 9 | }; 10 | 11 | var randint = function (from, to) { 12 | // range: from ~ to 13 | return Math.floor(Math.random() * (to - from + 1) + from); 14 | }; 15 | 16 | function Geetest(config) { 17 | if (!config.geetest_id) { 18 | throw new Error('Geetest ID Required'); 19 | } 20 | if (!config.geetest_key) { 21 | throw new Error("Geetest KEY Required"); 22 | } 23 | if (config.protocol) { 24 | this.PROTOCOL = config.protocol; 25 | } 26 | if (config.api_server) { 27 | this.API_SERVER = config.api_server; 28 | } 29 | 30 | this.geetest_id = config.geetest_id; 31 | this.geetest_key = config.geetest_key; 32 | } 33 | 34 | Geetest.prototype = { 35 | 36 | PROTOCOL: 'http://', 37 | API_SERVER: 'api.geetest.com', 38 | VALIDATE_PATH: '/validate.php', 39 | REGISTER_PATH: '/register.php', 40 | 41 | validate: function (result, callback) { 42 | var that = this; 43 | 44 | return new Promise(function (resolve, reject) { 45 | 46 | that._validate(result, function (err, data) { 47 | if (typeof callback === 'function') { 48 | callback(err, data); 49 | } 50 | if (err) { 51 | reject(err); 52 | } else { 53 | resolve(data); 54 | } 55 | }); 56 | }) 57 | }, 58 | 59 | _validate: function (result, callback) { 60 | var challenge = result.challenge; 61 | var validate = result.validate; 62 | 63 | if (validate.split('_').length === 3) { 64 | 65 | var validate_strs = validate.split('_'); 66 | var encode_ans = validate_strs[0]; 67 | var encode_fbii = validate_strs[1]; 68 | var encode_igi = validate_strs[2]; 69 | 70 | var decode_ans = this._decode_response(challenge, encode_ans); 71 | var decode_fbii = this._decode_response(challenge, encode_fbii); 72 | var decode_igi = this._decode_response(challenge, encode_igi); 73 | 74 | var validate_result = this._validate_fail_image(decode_ans, decode_fbii, decode_igi); 75 | 76 | if (validate_result === 1) { 77 | callback(null, true); 78 | } else { 79 | callback(null, false); 80 | } 81 | 82 | } else { 83 | 84 | var hash = this.geetest_key + 'geetest' + result.challenge; 85 | if (result.validate === md5(hash)) { 86 | var url = this.PROTOCOL + this.API_SERVER + this.VALIDATE_PATH; 87 | request.post(url, { 88 | form: { 89 | seccode: result.seccode 90 | } 91 | }, function (err, res, body) { 92 | if (err) { 93 | callback(err); 94 | } else { 95 | callback(null, body === md5(result.seccode)); 96 | } 97 | }); 98 | } else { 99 | callback(null, false); 100 | } 101 | } 102 | }, 103 | 104 | _validate_fail_image: function (ans, full_bg_index, img_grp_index) { 105 | 106 | var thread = 3; 107 | var full_bg_name = md5(full_bg_index).slice(0, 10); 108 | var bg_name = md5(img_grp_index).slice(10, 20); 109 | var answer_decode = ''; 110 | var i; 111 | for (i = 0; i < 9; i = i + 1) { 112 | if (i % 2 == 0) { 113 | answer_decode += full_bg_name[i]; 114 | } else { 115 | answer_decode += bg_name[i]; 116 | } 117 | } 118 | var x_decode = answer_decode.slice(4); 119 | var x_int = parseInt(x_decode, 16); 120 | var result = x_int % 200; 121 | if (result < 40) { 122 | result = 40; 123 | } 124 | if (Math.abs(ans - result) < thread) { 125 | return 1; 126 | } else { 127 | return 0; 128 | } 129 | }, 130 | 131 | _decode_response: function (challenge, userresponse) { 132 | if (userresponse.length > 100) { 133 | return 0; 134 | } 135 | var shuzi = [1, 2, 5, 10, 50]; 136 | var chongfu = []; 137 | var key = {}; 138 | var count = 0, i, len; 139 | for (i = 0, len = challenge.length; i < len; i = i + 1) { 140 | var c = challenge[i]; 141 | if (chongfu.indexOf(c) === -1) { 142 | chongfu.push(c); 143 | key[c] = shuzi[count % 5]; 144 | count += 1; 145 | } 146 | } 147 | var res = 0; 148 | for (i = 0, len = userresponse.length; i < len; i = i + 1) { 149 | res += key[userresponse[i]] || 0; 150 | } 151 | res = res - this._decode_rand_base(challenge); 152 | return res; 153 | }, 154 | 155 | _decode_rand_base: function (challenge) { 156 | var str_base = challenge.slice(32); 157 | var i, len, temp_array = []; 158 | for (i = 0, len = str_base.length; i < len; i = i + 1) { 159 | var temp_char = str_base[i]; 160 | var temp_ascii = temp_char.charCodeAt(0); 161 | var result = temp_ascii > 57 ? temp_ascii - 87 : temp_ascii - 48; 162 | temp_array.push(result); 163 | } 164 | var decode_res = temp_array[0] * 36 + temp_array[1]; 165 | return decode_res; 166 | }, 167 | 168 | register: function (callback) { 169 | 170 | var that = this; 171 | return new Promise(function (resolve, reject) { 172 | that._register(function (err, data) { 173 | if (typeof callback === 'function') { 174 | callback(err, data); 175 | } 176 | if (err) { 177 | reject(err); 178 | } else { 179 | resolve(data); 180 | } 181 | }); 182 | }); 183 | }, 184 | 185 | _register: function (callback) { 186 | var url = this.PROTOCOL + this.API_SERVER + this.REGISTER_PATH 187 | + '?gt=' + this.geetest_id + '&sdk=Node_' + pkg.version; 188 | 189 | var that = this; 190 | request.get(url, {timeout: 2000}, function (err, res, challenge) { 191 | 192 | if (err || challenge.length !== 32) { 193 | 194 | // fallback 195 | callback(null, { 196 | success: 0, 197 | challenge: that._make_challenge(), 198 | gt: that.geetest_id 199 | }); 200 | 201 | } else { 202 | 203 | callback(null, { 204 | success: 1, 205 | challenge: md5(challenge + that.geetest_key), 206 | gt: that.geetest_id 207 | }); 208 | } 209 | }); 210 | }, 211 | 212 | _make_challenge: function () { 213 | var rnd1 = randint(0, 90); 214 | var rnd2 = randint(0, 90); 215 | var md5_str1 = md5(rnd1); 216 | var md5_str2 = md5(rnd2); 217 | 218 | return md5_str1 + md5_str2.slice(0, 2); 219 | } 220 | }; 221 | 222 | module.exports = Geetest; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geetest", 3 | "version": "4.1.2", 4 | "description": "Node SDK for Geetest captcha", 5 | "main": "gt-sdk.js", 6 | "keywords": [ 7 | "Geetest", 8 | "Captcha" 9 | ], 10 | "contributors": [ 11 | { 12 | "name": "zhangcx93", 13 | "email": "zhangcx93@gmail.com", 14 | "url": "http://www.geetest.com" 15 | }, 16 | { 17 | "name": "lhtin", 18 | "email": "lehuading@qq.com", 19 | "url": "http://www.geetest.com" 20 | } 21 | ], 22 | "license": "ISC", 23 | "dependencies": { 24 | "request": "^2.54.0" 25 | }, 26 | "devDependencies": { 27 | "express": "^4.13.3", 28 | "body-parser": "^1.14.1" 29 | }, 30 | "repository": "https://github.com/GeeTeam/gt-node-sdk", 31 | "scripts": { 32 | "start": "node demo/app.js" 33 | } 34 | } --------------------------------------------------------------------------------