csrf.js
3.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/*!
* Connect - csrf
* Copyright(c) 2011 Sencha Inc.
* MIT Licensed
*/
/**
* Module dependencies.
*/
var utils = require('../utils');
var uid = require('uid2');
var crypto = require('crypto');
/**
* Anti CSRF:
*
* CSRF protection middleware.
*
* This middleware adds a `req.csrfToken()` function to make a token
* which should be added to requests which mutate
* state, within a hidden form field, query-string etc. This
* token is validated against the visitor's session.
*
* The default `value` function checks `req.body` generated
* by the `bodyParser()` middleware, `req.query` generated
* by `query()`, and the "X-CSRF-Token" header field.
*
* This middleware requires session support, thus should be added
* somewhere _below_ `session()` and `cookieParser()`.
*
* Options:
*
* - `value` a function accepting the request, returning the token
*
* @param {Object} options
* @api public
*/
module.exports = function csrf(options) {
options = options || {};
var value = options.value || defaultValue;
return function(req, res, next){
// already have one
var secret = req.session._csrfSecret;
if (secret) return createToken(secret);
// generate secret
uid(24, function(err, secret){
if (err) return next(err);
req.session._csrfSecret = secret;
createToken(secret);
});
// generate the token
function createToken(secret) {
var token;
// lazy-load token
req.csrfToken = function csrfToken() {
return token || (token = saltedToken(secret));
};
// compatibility with old middleware
Object.defineProperty(req.session, '_csrf', {
configurable: true,
get: function() {
console.warn('req.session._csrf is deprecated, use req.csrfToken([callback]) instead');
return req.csrfToken();
}
});
// ignore these methods
if ('GET' == req.method || 'HEAD' == req.method || 'OPTIONS' == req.method) return next();
// determine user-submitted value
var val = value(req);
// check
if (!checkToken(val, secret)) return next(utils.error(403));
next();
}
}
};
/**
* Default value function, checking the `req.body`
* and `req.query` for the CSRF token.
*
* @param {IncomingMessage} req
* @return {String}
* @api private
*/
function defaultValue(req) {
return (req.body && req.body._csrf)
|| (req.query && req.query._csrf)
|| (req.headers['x-csrf-token'])
|| (req.headers['x-xsrf-token']);
}
/**
* Return salted token.
*
* @param {String} secret
* @return {String}
* @api private
*/
function saltedToken(secret) {
return createToken(generateSalt(10), secret);
}
/**
* Creates a CSRF token from a given salt and secret.
*
* @param {String} salt (should be 10 characters)
* @param {String} secret
* @return {String}
* @api private
*/
function createToken(salt, secret) {
return salt + crypto
.createHash('sha1')
.update(salt + secret)
.digest('base64');
}
/**
* Checks if a given CSRF token matches the given secret.
*
* @param {String} token
* @param {String} secret
* @return {Boolean}
* @api private
*/
function checkToken(token, secret) {
if ('string' != typeof token) return false;
return token === createToken(token.slice(0, 10), secret);
}
/**
* Generates a random salt, using a fast non-blocking PRNG (Math.random()).
*
* @param {Number} length
* @return {String}
* @api private
*/
function generateSalt(length) {
var i, r = [];
for (i = 0; i < length; ++i) {
r.push(SALTCHARS[Math.floor(Math.random() * SALTCHARS.length)]);
}
return r.join('');
}
var SALTCHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';