-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from astroboy-lab/astroboy_security_0703
[new feature] add astroboy-security-csrf middleware
- Loading branch information
Showing
6 changed files
with
152 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
55 changes: 54 additions & 1 deletion
55
plugins/astroboy-security/app/middlewares/astroboy-security-csrf.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}); | ||
} | ||
|
||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters