├── .gitignore ├── History.md ├── LICENSE ├── Readme.md ├── captcha.js ├── package.json └── test-server.js /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | .idea/ 3 | .vscode 4 | node_modules/ 5 | client/bower_components/ 6 | npm-debug.log 7 | 8 | # Windows image file caches 9 | Thumbs.db 10 | ehthumbs.db 11 | TESTS.xml 12 | test-results.xml 13 | 14 | # Folder config file 15 | Desktop.ini 16 | 17 | # Recycle Bin used on file shares 18 | $RECYCLE.BIN/ 19 | 20 | # Windows Installer files 21 | *.cab 22 | *.msi 23 | *.msm 24 | *.msp 25 | 26 | # Windows shortcuts 27 | *.lnk 28 | 29 | # ========================= 30 | # Operating System Files 31 | # ========================= 32 | 33 | # OSX 34 | # ========================= 35 | 36 | .DS_Store 37 | .AppleDouble 38 | .LSOverride 39 | 40 | # Thumbnails 41 | ._* 42 | 43 | # Files that might appear on external disk 44 | .Spotlight-V100 45 | .Trashes 46 | 47 | # Directories potentially created on remote AFP share 48 | .AppleDB 49 | .AppleDesktop 50 | Network Trash Folder 51 | Temporary Items 52 | .apdisk 53 | client/components/**/*.css 54 | .env 55 | dist 56 | typings 57 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 0.1.3 / 2017-04-21 2 | ================== 3 | 4 | * session fix 5 | 6 | 0.1.0 / 2017-04-20 7 | ================== 8 | 9 | * New API (WARNING!) 10 | * support of multiple captcha instances per project 11 | * JPEG instead of PNG 12 | 13 | 0.0.5 / 2013-06-23 14 | ================== 15 | 16 | * More adjustable parameters to captcha 17 | 18 | 0.0.4 / 2012-11-24 19 | ================== 20 | 21 | * Usage docs. 22 | 23 | 24 | 0.0.3 / 2012-11-24 25 | ================== 26 | 27 | * Color parameters. 28 | 29 | 30 | 0.0.1 / 2012-01-18 31 | ================== 32 | 33 | * Initial release. 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2009-2013 napa3um 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Captcha 2 | 3 | Simple captcha for Express. 4 | 5 | WARNING! New API (0.0.5 -> 0.1.0) 6 | 7 | ## Installation 8 | 9 | Via npm: 10 | 11 | $ npm install captcha 12 | 13 | ## Usage (for Express 4) 14 | 15 | ```javascript 16 | 'use strict' 17 | 18 | const express = require('express') 19 | const session = require('express-session') 20 | const bodyParser = require('body-parser') 21 | 22 | const captchaUrl = '/captcha.jpg' 23 | const captchaId = 'captcha' 24 | const captchaFieldName = 'captcha' 25 | 26 | const captcha = require('./captcha').create({ cookie: captchaId }) 27 | 28 | const app = express() 29 | app.use(session({ 30 | secret: 'keyboard cat', 31 | resave: false, 32 | saveUninitialized: true, 33 | })) 34 | app.use(bodyParser.urlencoded({ extended: false })) 35 | 36 | app.get(captchaUrl, captcha.image()) 37 | 38 | app.get('/', (req, res) => { 39 | res.type('html') 40 | res.end(` 41 | 42 |
43 | 44 | 45 |
46 | `) 47 | }) 48 | 49 | app.post('/login', (req, res) => { 50 | res.type('html') 51 | res.end(` 52 |

CAPTCHA VALID: ${ captcha.check(req, req.body[captchaFieldName]) }

53 | `) 54 | }) 55 | 56 | app.listen(8080, () => { 57 | console.log('server started') 58 | }) 59 | ``` -------------------------------------------------------------------------------- /captcha.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Canvas = require('canvas') 4 | const crypto = require('crypto') 5 | 6 | class Captcha { 7 | constructor(params) { 8 | params.cookie = params.cookie || 'captcha' 9 | params.cryptoAlg = params.cryptoAlg || 'sha1' 10 | params.cryptoPass = params.cryptoPass || crypto.randomBytes(32) 11 | params.color = params.color || 'rgb(0,100,100)' 12 | params.background = params.background || 'rgb(255,200,150)' 13 | params.lineWidth = params.lineWidth || 8 14 | params.fontSize = params.fontSize || 80 15 | params.codeLength = params.codeLength || 6 16 | params.canvasWidth = params.canvasWidth || 250 17 | params.canvasHeight = params.canvasHeight || 150 18 | 19 | this.params = params 20 | } 21 | 22 | image() { 23 | return (req, res, next) => { 24 | const canvas = new Canvas(this.params.canvasWidth, this.params.canvasHeight) 25 | const ctx = canvas.getContext('2d') 26 | 27 | ctx.antialias = 'gray' 28 | ctx.fillStyle = this.params.background 29 | ctx.fillRect(0, 0, this.params.canvasWidth, this.params.canvasHeight) 30 | ctx.fillStyle = this.params.color 31 | ctx.lineWidth = this.params.lineWidth 32 | ctx.strokeStyle = this.params.color 33 | ctx.font = `${this.params.fontSize }px sans` 34 | 35 | // draw two curve lines: 36 | for (var i = 0; i < 2; i++) { 37 | ctx.moveTo(Math.floor(0.08 * this.params.canvasWidth), Math.random() * this.params.canvasHeight) 38 | ctx.bezierCurveTo(Math.floor(0.32 * this.params.canvasWidth), Math.random() * this.params.canvasHeight, Math.floor(1.07 * this.params.canvasHeight), Math.random() * this.params.canvasHeight, Math.floor(0.92 * this.params.canvasWidth), Math.random() * this.params.canvasHeight) 39 | ctx.stroke() 40 | } 41 | 42 | // draw text: 43 | const text = ('' + crypto.randomBytes(4).readUIntBE(0, 4)).substr(2, this.params.codeLength) 44 | text.split('').forEach((char, i) => { 45 | ctx.setTransform(Math.random() * 0.5 + 1, Math.random() * 0.4, Math.random() * 0.4, Math.random() * 0.5 + 1, Math.floor(0.375 * this.params.fontSize) * i + Math.floor(0.25 * this.params.fontSize), Math.floor(1.25 * this.params.fontSize)) 46 | ctx.fillText(char, 0, 0) 47 | }) 48 | 49 | // save text: 50 | if (req.session === undefined) { 51 | throw Error('node-captcha requires express-session!') 52 | } 53 | req.session[this.params.cookie] = text 54 | 55 | // send image: 56 | res.type('jpg') 57 | res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0') 58 | res.header('Expires', 'Sun, 19 May 1984 02:00:00 GMT') 59 | canvas.jpegStream().pipe(res) 60 | } 61 | } 62 | 63 | check(req, text) { 64 | if (req.session === undefined) { 65 | throw Error('node-captcha requires express-session!') 66 | } 67 | const res = req.session[this.params.cookie] === text 68 | req.session[this.params.cookie] = null 69 | return res 70 | } 71 | } 72 | 73 | module.exports.create = params => new Captcha(params) 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "captcha", 3 | "description": "Simple captcha middleware for Express", 4 | "version": "0.1.3", 5 | "author": "napa3um ", 6 | "dependencies": { 7 | "canvas": "^1.6.5" 8 | }, 9 | "main": "./captcha.js", 10 | "repository": { 11 | "type": "git", 12 | "url": "git@github.com:napa3um/node-captcha.git" 13 | }, 14 | "license": "MIT", 15 | "readmeFilename": "Readme.md", 16 | "devDependencies": { 17 | "body-parser": "^1.17.1", 18 | "express": "^4.15.2", 19 | "express-session": "^1.15.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test-server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const express = require('express') 4 | const session = require('express-session') 5 | const bodyParser = require('body-parser') 6 | 7 | const captchaUrl = '/captcha.jpg' 8 | const captchaId = 'captcha' 9 | const captchaFieldName = 'captcha' 10 | 11 | const captcha = require('./captcha').create({ cookie: captchaId }) 12 | 13 | const app = express() 14 | app.use(session({ 15 | secret: 'keyboard cat', 16 | resave: false, 17 | saveUninitialized: true, 18 | })) 19 | app.use(bodyParser.urlencoded({ extended: false })) 20 | 21 | app.get(captchaUrl, captcha.image()) 22 | 23 | app.get('/', (req, res) => { 24 | res.type('html') 25 | res.end(` 26 | 27 |
28 | 29 | 30 |
31 | `) 32 | }) 33 | 34 | app.post('/login', (req, res) => { 35 | res.type('html') 36 | res.end(` 37 |

CAPTCHA VALID: ${ captcha.check(req, req.body[captchaFieldName]) }

38 | `) 39 | }) 40 | 41 | app.listen(8080, () => { 42 | console.log('server started') 43 | }) 44 | --------------------------------------------------------------------------------