Skip to content

Commit

Permalink
Merge pull request #8 from astroboy-lab/astroboy_security_0703
Browse files Browse the repository at this point in the history
[new feature] add astroboy-security-csrf middleware
  • Loading branch information
kk0829 authored Jul 4, 2018
2 parents e5c0db8 + eb2b1c3 commit a49e399
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 9 deletions.
2 changes: 1 addition & 1 deletion plugins/astroboy-body/config/middleware.default.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const path = require('path');

module.exports = {
'astroboy-body': {
priority: 3,
priority: 15,
enable: true,
options: {
formidable: {
Expand Down
4 changes: 2 additions & 2 deletions plugins/astroboy-meta/config/middleware.default.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ module.exports = {

'astroboy-meta-node-engine': {
enable: true,
priority: 2
priority: 10
},

'astroboy-meta-rt': {
enable: true,
priority: 1
priority: 5
},

}
20 changes: 20 additions & 0 deletions plugins/astroboy-security/app/lib/randomString.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
var assert = require('assert');

const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const length = Buffer.byteLength(chars)

/**
* 生成一个长度为 len 的字符串
* @param {Number} len 字符串长度
*/
function randomString(len = 10) {
assert(typeof len === 'number' && len >= 0, 'the length of the random string must be a number!')

var str = ''
for (var i = 0; i < len; i++) {
str += chars[Math.floor(length * Math.random())];
}
return str
}

module.exports = randomString;
56 changes: 56 additions & 0 deletions plugins/astroboy-security/app/lib/token.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const crypto = require('crypto');
const randomString = require('./randomString');

class Token {

get defaultOptions() {
return {
saltLength: 10,
secretLength: 18
};
}

constructor(options = {}) {
this.options = Object.assign({}, this.defaultOptions, options);
this.saltLength = this.options.saltLength;
this.secretLength = this.options.secretLength;
}

secretSync() {
return randomString(this.secretLength);
}

create(secret) {
const salt = randomString(this.saltLength);
return this.tokenize(secret, salt);
}

tokenize(secret, salt) {
const hash = crypto
.createHash('sha1')
.update(secret, 'utf-8')
.digest('base64');

return salt + '-' + hash;
}

verify(secret, token) {
if (!secret || !token ||
typeof secret !== 'string' ||
typeof token !== 'string') {
return false;
}

const index = token.indexOf('-');
if (index === -1) {
return false;
}
const salt = token.substr(0, index);
const expected = this.tokenize(secret, salt);

return expected === token;
}

}

module.exports = Token;
Original file line number Diff line number Diff line change
@@ -1,3 +1,56 @@
/**
* CSRF
*/
*/
const Token = require('../lib/token');

class CsrfError extends Error {
constructor(code, msg) {
super(`code: ${code}, msg: ${msg}`);
this.errorContent = {
code,
msg
};
this.errorType = 'CsrfError';
}
}

module.exports = function (options = {}, app) {
let token = new Token({
saltLength: options.saltLength,
secretLength: options.secretLength
});

return async function csrf(ctx, next) {
if (options.excluded.indexOf(ctx.method) === -1 &&
options.env.indexOf(process.env.NODE_ENV) > -1) {
const csrfSecret = ctx.cookies.get(options.csrfSecretName);
const csrfToken = ctx.header[options.csrfTokenName];

// token 或 secret 不存在
if (!csrfSecret || !csrfToken) {
throw new CsrfError(1000, 'CSRF Token Not Found!');
}
// token 校验失败
if (!token.verify(csrfSecret, csrfToken)) {
throw new CsrfError(1001, 'CSRF token Invalid!');
}
}

await next();

// 如果返回 HTML 格式数据,则生成
if (ctx.response.is('text/html')) {
const secret = token.secretSync();
const newToken = token.create(secret);

ctx.cookies.set(options.csrfSecretName, secret, {
maxAge: options.maxAge
});
ctx.cookies.set(options.csrfTokenName, newToken, {
maxAge: options.maxAge,
httpOnly: false
});
}

};
};
24 changes: 19 additions & 5 deletions plugins/astroboy-security/config/middleware.default.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
module.exports = {

'astroboy-security-cto': {
'astroboy-security-csrf': {
priority: 2,
enable: false,
options: {
env: ['prod'],
excluded: ['GET', 'HEAD', 'OPTIONS'],
csrfSecretName: 'csrf_secret',
csrfTokenName: 'csrf_token',
saltLength: 10,
secretLength: 18,
maxAge: 3 * 3600 * 1000
}
},

'astroboy-security-cto': {
priority: 10,
enable: true,
options: 'nosniff'
},

'astroboy-security-frameOptions': {
priority: 2,
priority: 10,
enable: true,
options: 'SAMEORIGIN'
},

'astroboy-security-hsts': {
priority: 2,
priority: 10,
enable: true,
options: {
maxAge: 365 * 24 * 3600
Expand All @@ -22,11 +36,11 @@ module.exports = {

'astroboy-security-xss': {
enable: true,
priority: 10
priority: 20
},

'astroboy-security-xssProtection': {
priority: 2,
priority: 10,
enable: true,
options: '1; mode=block'
},
Expand Down

0 comments on commit a49e399

Please sign in to comment.