Showing
6 changed files
with
196 additions
and
0 deletions
index.js
0 → 100644
lib/authenticator.js
0 → 100644
| 1 | +/** | ||
| 2 | + * Created by lintry on 2017/5/19. | ||
| 3 | + */ | ||
| 4 | + | ||
| 5 | +const Authenticator = function (secret, options) { | ||
| 6 | + if (!(this instanceof Authenticator)) { | ||
| 7 | + return new Authenticator(secret, options) | ||
| 8 | + } | ||
| 9 | + | ||
| 10 | + const _ = require('lodash'), | ||
| 11 | + path = require('path'), | ||
| 12 | + crypto_utils = require('kml-crypto-utils'), | ||
| 13 | + speakeasy = require('speakeasy'), | ||
| 14 | + qr = require('qr-image'); | ||
| 15 | + | ||
| 16 | + options = options || {}; | ||
| 17 | + | ||
| 18 | + this.secret = crypto_utils.AESDecode(secret); | ||
| 19 | + | ||
| 20 | + const TOTP_OPTIONS = this.totp_options = { | ||
| 21 | + secret: this.secret, | ||
| 22 | + encoding: options.encoding || 'base32', | ||
| 23 | + step: options.step || 30 | ||
| 24 | + }; | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 验证token有效性 | ||
| 29 | + * @type {Authenticator.verify} | ||
| 30 | + */ | ||
| 31 | + const verify = this.verify = function(token) { | ||
| 32 | + return speakeasy.totp.verify(_.merge({token: token}, TOTP_OPTIONS)); | ||
| 33 | + }; | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * 在options.window的范围内验证token的有效性 | ||
| 37 | + * @type {Authenticator.verifyDelta} | ||
| 38 | + */ | ||
| 39 | + const verifyDelta = this.verifyDelta = function(token) { | ||
| 40 | + return speakeasy.totp.verifyDelta(_.merge({token: token}, TOTP_OPTIONS)); | ||
| 41 | + }; | ||
| 42 | + | ||
| 43 | + /** | ||
| 44 | + * 获取QR显示值 | ||
| 45 | + * @type {Authenticator.getOtpAuth} | ||
| 46 | + */ | ||
| 47 | + const getOtpAuth = this.getOtpAuth = function (title, issuer) { | ||
| 48 | + let _title = title || 'HowAreU'; | ||
| 49 | + let _issuer = encodeURIComponent(issuer || 'TOTP-KEY'); | ||
| 50 | + return `otpauth://totp/${_title}?secret=${this.secret}&issuer=${_issuer}`; | ||
| 51 | + }; | ||
| 52 | + | ||
| 53 | + /** | ||
| 54 | + * 生成svg的QR图片内容 | ||
| 55 | + * @type {Authenticator.getQR} | ||
| 56 | + */ | ||
| 57 | + const getQR = this.getQR = function(title, issuer) { | ||
| 58 | + return qr.imageSync(getOtpAuth(title, issuer), { type: 'svg' }); | ||
| 59 | + }; | ||
| 60 | + | ||
| 61 | + /** | ||
| 62 | + * 生成新的token | ||
| 63 | + * @type {Authenticator.totp} | ||
| 64 | + */ | ||
| 65 | + const totp = this.totp = function () { | ||
| 66 | + return speakeasy.totp(_.merge({}, TOTP_OPTIONS)); | ||
| 67 | + }; | ||
| 68 | + | ||
| 69 | + return this; | ||
| 70 | +}; | ||
| 71 | + | ||
| 72 | +module.exports = Authenticator; | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
lib/totp.js
0 → 100644
| 1 | +/** | ||
| 2 | + * Created by lintry on 2017/5/19. | ||
| 3 | + */ | ||
| 4 | + | ||
| 5 | +const TOTP = function (options) { | ||
| 6 | + if (!(this instanceof TOTP)) { | ||
| 7 | + return new TOTP(options) | ||
| 8 | + } | ||
| 9 | + | ||
| 10 | + const _ = require('lodash'), | ||
| 11 | + crypto_utils = require('kml-crypto-utils'), | ||
| 12 | + speakeasy = require('speakeasy'), | ||
| 13 | + Authenticator = require('./authenticator'); | ||
| 14 | + | ||
| 15 | + options = options || {}; | ||
| 16 | + | ||
| 17 | + const TOTP_OPTIONS = { | ||
| 18 | + encoding: options.encoding || 'base32', | ||
| 19 | + step: options.step || 30 | ||
| 20 | + }; | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * 生成密钥并加密 | ||
| 24 | + * @return {*} | ||
| 25 | + */ | ||
| 26 | + this.createSecret = function() { | ||
| 27 | + return crypto_utils.AESEncode(speakeasy.generateSecret({length: 20}).base32); | ||
| 28 | + }; | ||
| 29 | + | ||
| 30 | + /** | ||
| 31 | + * 解析密钥 | ||
| 32 | + * @param secret | ||
| 33 | + * @return {Authenticator} | ||
| 34 | + */ | ||
| 35 | + this.parse = function (secret) { | ||
| 36 | + return new Authenticator(secret, TOTP_OPTIONS) | ||
| 37 | + }; | ||
| 38 | + | ||
| 39 | + return this; | ||
| 40 | +}; | ||
| 41 | + | ||
| 42 | +module.exports = TOTP; | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -13,7 +13,15 @@ | ... | @@ -13,7 +13,15 @@ |
| 13 | "keywords": [], | 13 | "keywords": [], |
| 14 | "author": "lintry <shenlin00@gmail.com>", | 14 | "author": "lintry <shenlin00@gmail.com>", |
| 15 | "license": "MIT", | 15 | "license": "MIT", |
| 16 | + "files": [ | ||
| 17 | + "/*", | ||
| 18 | + "!/.*", | ||
| 19 | + "!/test" | ||
| 20 | + ], | ||
| 16 | "dependencies": { | 21 | "dependencies": { |
| 22 | + "kml-crypto-utils": "git+ssh://git@gitlab.kmlab.com:comm/crypto-utils.git#1.1.7", | ||
| 23 | + "lodash": "^4.17.4", | ||
| 24 | + "qr-image": "^3.2.0", | ||
| 17 | "speakeasy": "^2.0.0" | 25 | "speakeasy": "^2.0.0" |
| 18 | } | 26 | } |
| 19 | } | 27 | } | ... | ... |
test/authenticator.test.js
0 → 100644
| 1 | +/** | ||
| 2 | + * Created by lintry on 2017/5/19. | ||
| 3 | + */ | ||
| 4 | + | ||
| 5 | +const Authenticator = require('../lib/authenticator'); | ||
| 6 | +const chalk = require('chalk'); | ||
| 7 | +const fs = require('fs-extra'); | ||
| 8 | +const path = require('path'); | ||
| 9 | + | ||
| 10 | +let secret = 'vH6OdbUEjSukTqlDvW3TYdusjiOIkxRnAHNTjJewfZa5yNueG9wx1N9pJMFOmPAV'; | ||
| 11 | +let authenticator = new Authenticator(secret); | ||
| 12 | + | ||
| 13 | +console.log(chalk.cyan('totp的secret')); | ||
| 14 | +console.log(secret); | ||
| 15 | + | ||
| 16 | +let token = process.argv[2]; | ||
| 17 | +if (!token) { | ||
| 18 | + token = authenticator.totp(); | ||
| 19 | + console.error(chalk.red('none token found, we generate a new token instead'), token); | ||
| 20 | + return; | ||
| 21 | +} | ||
| 22 | +console.log(chalk.green('token is'), token); | ||
| 23 | + | ||
| 24 | +let verify = authenticator.verify(token); | ||
| 25 | +console.log(chalk.magenta('verify is '), (verify ? chalk.green : chalk.red)(verify)); | ||
| 26 | +console.log(chalk.blue('verifyDelta is '), authenticator.verifyDelta(token)); | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +let img_path = path.resolve(process.cwd(), 'img'); | ||
| 30 | +let qr = path.resolve(img_path, 'qr.svg'); | ||
| 31 | + | ||
| 32 | +fs.ensureDir(img_path, function (err, added_root) { | ||
| 33 | + if (err) { | ||
| 34 | + return console.error(chalk.red('create folder error'), chalk.red(JSON.stringify(err, null, 4))); | ||
| 35 | + } | ||
| 36 | + added_root && console.log(chalk.green(img_path + ' is created')); | ||
| 37 | + | ||
| 38 | + let fd = fs.openSync(qr, 'w'); | ||
| 39 | + fs.writeSync(fd, authenticator.getQR('totp@gitlab.kmlab.com', '通行密钥')); | ||
| 40 | + fs.closeSync(fd); | ||
| 41 | + | ||
| 42 | + console.log(chalk.yellow(authenticator.getOtpAuth('totp@gitlab.kmlab.com', '通行密钥'))) | ||
| 43 | +}); | ||
| 44 | + | ||
| 45 | +console.log('QR SVG output is', img_path, qr); | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
test/totp.test.js
0 → 100644
| 1 | +/** | ||
| 2 | + * Created by lintry on 2017/5/19. | ||
| 3 | + */ | ||
| 4 | + | ||
| 5 | +const TOTP = require('../lib/totp'); | ||
| 6 | +const chalk = require('chalk'); | ||
| 7 | +const fs = require('fs-extra'); | ||
| 8 | + | ||
| 9 | +let totp = new TOTP(); | ||
| 10 | + | ||
| 11 | +let secret = totp.createSecret(); | ||
| 12 | +console.log(chalk.cyan('创建totp的secret')); | ||
| 13 | +console.log(secret); | ||
| 14 | + | ||
| 15 | +console.log(chalk.magenta('secret'), secret); | ||
| 16 | + | ||
| 17 | +let authenticator = totp.parse(secret); | ||
| 18 | + | ||
| 19 | +console.log(authenticator.totp_options); | ||
| 20 | + | ||
| 21 | +let token = authenticator.totp(); | ||
| 22 | +console.log(chalk.green('token is'), token); | ||
| 23 | + | ||
| 24 | +console.log(chalk.magenta('verify is '), authenticator.verify(token)); | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or login to post a comment