Skip to content

Commit

Permalink
CSRF protection
Browse files Browse the repository at this point in the history
Issue: cujojs#39
  • Loading branch information
scothis committed Aug 23, 2013
1 parent 90c3138 commit d21fb09
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ Change Log

.next
- JSON HAL mime serializer for application/hal+json
- CSRF protection interceptor
- HATEOAS interceptor defaults to indexing relationships directly on the host entity instead of the '_links' child object. A child object may still be configured.
- HATEOAS interceptor returns same promise on multiple relationship property accesses
- support bower 0.10+, older versions of bower will no longer work
Expand Down
56 changes: 56 additions & 0 deletions docs/interceptors.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- [Authentication Interceptors](#interceptor-provided-auth)
- [Basic Auth Interceptor](#module-rest/interceptor/basicAuth)
- [OAuth Interceptor](#module-rest/interceptor/oAuth)
- [CSRF Interceptor](#module-rest/interceptor/csrf)
- [Error Detection and Recovery Interceptors](#interceptor-provided-error)
- [Error Code Interceptor](#module-rest/interceptor/errorCode)
- [Retry Interceptor](#module-rest/interceptor/retry)
Expand Down Expand Up @@ -577,6 +578,61 @@ client({ path: 'http://resourceserver.example.com' }).then(function (response) {
```
<a name="module-rest/interceptor/csrf"></a>
#### CSRF Interceptor
`rest/interceptor/csrf` ([src](../interceptor/csrf.js))
Applies a Cross-Site Request Forgery protection header to a request
CSRF protection helps a server verify that a request came from a trusted client and not another client that was able to masquerade as an authorized client. Sites that use cookie based authentication are particularly vulnerable to request forgeries without extra protection.
**Phases**
- request
**Configuration**
<table>
<tr>
<th>Property</th>
<th>Required?</th>
<th>Default</th>
<th>Description</th>
</tr>
<tr>
<td>name</td>
<td>optional</td>
<td>'X-Csrf-Token'</td>
<td>name of the request header, may be overridden by `request.csrfTokenName`</td>
</tr>
<tr>
<td>token</td>
<td>optional</td>
<td><em>none</em></td>
<td>CSRF token, may be overridden by `request.csrfToken`</td>
</tr>
</table>
**Example**
```javascript
client = rest.chain(csrf, { token: 'abc123xyz789' });
// interceptor config
client({}).then(function (response) {
assert.same('abc123xyz789', response.request.headers['X-Csrf-Token']);
});
```
```javascript
client = rest.chain(csrf);
// request config
client({ csrfToken: 'abc123xyz789' }).then(function (reponse) {
assert.same('abc123xyz789', response.request.headers['X-Csrf-Token']);
});
```
<a name="interceptor-provided-error"></a>
### Error Detection and Recovery Interceptors
Expand Down
61 changes: 61 additions & 0 deletions interceptor/csrf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2013 the original author or authors
* @license MIT, see LICENSE.txt for details
*
* @author Scott Andrews
*/

(function (define) {
'use strict';

define(function (require) {

var interceptor;

interceptor = require('../interceptor');

/**
* Applies a Cross-Site Request Forgery protection header to a request
*
* CSRF protection helps a server verify that a request came from a
* trusted client and not another client that was able to masquerade
* as an authorized client. Sites that use cookie based authentication
* are particularly vulnerable to request forgeries without extra
* protection.
*
* @see http://en.wikipedia.org/wiki/Cross-site_request_forgery
*
* @param {Client} [client] client to wrap
* @param {string} [config.name='X-Csrf-Token'] name of the request
* header, may be overridden by `request.csrfTokenName`
* @param {string} [config.token] CSRF token, may be overridden by
* `request.csrfToken`
*
* @returns {Client}
*/
return interceptor({
init: function (config) {
config.name = config.name || 'X-Csrf-Token';
return config;
},
request: function handleRequest(request, config) {
var headers, name, token;

headers = request.headers || (request.headers = {});
name = request.csrfTokenName || config.name;
token = request.csrfToken || config.token;

if (token) {
headers[name] = token;
}

return request;
}
});

});

}(
typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }
// Boilerplate for AMD and Node
));
87 changes: 87 additions & 0 deletions test/interceptor/csrf-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2013 the original author or authors
* @license MIT, see LICENSE.txt for details
*
* @author Scott Andrews
*/

(function (buster, define) {
'use strict';

var assert, refute, fail;

assert = buster.assertions.assert;
refute = buster.assertions.refute;
fail = buster.assertions.fail;

define('rest/interceptor/csrf-test', function (require) {

var csrf, rest;

csrf = require('rest/interceptor/csrf');
rest = require('rest');

buster.testCase('rest/interceptor/csrf', {
'should protect the requst from the config': function () {
var client = csrf(
function (request) { return { request: request }; },
{ token: 'abc123xyz789'}
);
return client({}).then(function (response) {
assert.equals('abc123xyz789', response.request.headers['X-Csrf-Token']);
}).otherwise(fail);
},
'should protect the requst from the request': function () {
var client = csrf(
function (request) { return { request: request }; }
);
return client({ csrfToken: 'abc123xyz789' }).then(function (response) {
assert.equals('abc123xyz789', response.request.headers['X-Csrf-Token']);
}).otherwise(fail);
},
'should protect the requst from the config using a custom header': function () {
var client = csrf(
function (request) { return { request: request }; },
{ token: 'abc123xyz789', name: 'Csrf-Token' }
);
return client({}).then(function (response) {
assert.equals('abc123xyz789', response.request.headers['Csrf-Token']);
}).otherwise(fail);
},
'should protect the requst from the request using a custom header': function () {
var client = csrf(
function (request) { return { request: request }; }
);
return client({ csrfToken: 'abc123xyz789', csrfTokenName: 'Csrf-Token' }).then(function (response) {
assert.equals('abc123xyz789', response.request.headers['Csrf-Token']);
}).otherwise(fail);
},
'should not protect without a token': function () {
var client = csrf(
function (request) { return { request: request }; }
);
return client({}).then(function (response) {
refute.defined(response.request.headers['X-Csrf-Token']);
}).otherwise(fail);
},
'should have the default client as the parent by default': function () {
assert.same(rest, csrf().skip());
},
'should support interceptor chaining': function () {
assert(typeof csrf().chain === 'function');
}
});

});

}(
this.buster || require('buster'),
typeof define === 'function' && define.amd ? define : function (id, factory) {
var packageName = id.split(/[\/\-]/)[0], pathToRoot = id.replace(/[^\/]+/g, '..');
pathToRoot = pathToRoot.length > 2 ? pathToRoot.substr(3) : pathToRoot;
factory(function (moduleId) {
return require(moduleId.indexOf(packageName) === 0 ? pathToRoot + moduleId.substr(packageName.length) : moduleId);
});
}
// Boilerplate for AMD and Node
));

0 comments on commit d21fb09

Please sign in to comment.