/test/integration/hook.cors_csrf.test.js

https://gitlab.com/yutiansut/sails · JavaScript · 1310 lines · 1094 code · 179 blank · 37 comment · 65 complexity · 43c8b83d35864b095ac0017785ddff21 MD5 · raw file

  1. /**
  2. * Module dependencies
  3. */
  4. var assert = require('assert');
  5. var httpHelper = require('./helpers/httpHelper.js');
  6. var appHelper = require('./helpers/appHelper');
  7. var path = require('path');
  8. var fs = require('fs');
  9. describe('CORS and CSRF ::', function() {
  10. var appName = 'testApp';
  11. var sailsApp;
  12. beforeEach(function(done) {
  13. appHelper.lift({
  14. log: {
  15. level: 'silent'
  16. }
  17. }, function(err, sails) {
  18. if (err) {
  19. return done(err);
  20. }
  21. sailsApp = sails;
  22. return done();
  23. });
  24. });
  25. afterEach(function(done) {
  26. sailsApp.lower(function(err) {
  27. if (err) {
  28. return done(err);
  29. }
  30. setTimeout(done, 100);
  31. });
  32. });
  33. // ██████╗ ██████╗ ██████╗ ███████╗ ██████╗ ██████╗ ███╗ ██╗███████╗██╗ ██████╗
  34. // ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██╔════╝██╔═══██╗████╗ ██║██╔════╝██║██╔════╝
  35. // ██║ ██║ ██║██████╔╝███████╗ ██║ ██║ ██║██╔██╗ ██║█████╗ ██║██║ ███╗
  36. // ██║ ██║ ██║██╔══██╗╚════██║ ██║ ██║ ██║██║╚██╗██║██╔══╝ ██║██║ ██║
  37. // ╚██████╗╚██████╔╝██║ ██║███████║ ╚██████╗╚██████╔╝██║ ╚████║██║ ██║╚██████╔╝
  38. // ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝
  39. //
  40. describe('CORS config ::', function() {
  41. before(function(done) {
  42. appHelper.build(done);
  43. });
  44. describe('with "allRoutes: true" and origin "*"', function() {
  45. before(function() {
  46. fs.writeFileSync(path.resolve('../', appName, 'config/cors.js'), 'module.exports.cors = { origin: \'*\', allRoutes: true};');
  47. var routeConfig = {
  48. 'GET /test/find': {
  49. controller: 'TestController',
  50. action: 'find',
  51. cors: false
  52. },
  53. 'GET /test/update': {
  54. controller: 'TestController',
  55. action: 'update',
  56. cors: 'http://www.example.com'
  57. },
  58. 'GET /test2': {
  59. controller: 'TestController',
  60. action: 'find',
  61. cors: {
  62. 'exposeHeaders': 'x-custom-header'
  63. }
  64. },
  65. 'PUT /test': {
  66. controller: 'TestController',
  67. action: 'update',
  68. cors: 'http://www.example.com'
  69. },
  70. 'POST /test': {
  71. controller: 'TestController',
  72. action: 'create',
  73. cors: 'http://www.different.com'
  74. },
  75. 'DELETE /test': {
  76. controller: 'TestController',
  77. action: 'delete',
  78. cors: false
  79. },
  80. 'POST /test2': {
  81. controller: 'TestController',
  82. action: 'create',
  83. cors: true
  84. },
  85. 'OPTIONS /test2': {
  86. controller: 'TestController',
  87. action: 'index'
  88. },
  89. 'PUT /test2': {
  90. controller: 'TestController',
  91. action: 'update'
  92. },
  93. 'GET /test/patch': {
  94. controller: 'TestController',
  95. action: 'update',
  96. cors: 'http://www.example.com:1338'
  97. },
  98. 'GET /test/create': {
  99. controller: 'TestController',
  100. action: 'create',
  101. cors: 'http://www.different.com'
  102. },
  103. 'GET /test/destroy': {
  104. controller: 'TestController',
  105. action: 'destroy',
  106. cors: {
  107. origin: 'http://www.example.com',
  108. credentials: false
  109. }
  110. },
  111. };
  112. fs.writeFileSync(path.resolve('../', appName, 'config/routes.js'), 'module.exports.routes = ' + JSON.stringify(routeConfig));
  113. });
  114. describe('an OPTIONS request with origin "http://www.example.com"', function() {
  115. it('for a PUT route with {cors: http://example.com} and an Access-Control-Request-Method header set to "PUT" should respond with correct Access-Control-Allow-* headers', function(done) {
  116. httpHelper.testRoute('options', {
  117. url: 'test',
  118. headers: {
  119. 'Access-Control-Request-Method': 'PUT',
  120. 'Origin': 'http://www.example.com'
  121. },
  122. }, function(err, response) {
  123. if (err) {
  124. return done(new Error(err));
  125. }
  126. assert.equal(response.statusCode, 200);
  127. assert.equal(response.headers['access-control-allow-origin'], 'http://www.example.com');
  128. assert.equal(response.headers['access-control-allow-methods'], 'put');
  129. assert.equal(response.headers['access-control-allow-headers'], 'content-type');
  130. done();
  131. });
  132. });
  133. it('for a route without a custom OPTIONS handler should use the default Express handler', function(done) {
  134. httpHelper.testRoute('options', {
  135. url: 'test',
  136. }, function(err, response) {
  137. if (err) {
  138. return done(new Error(err));
  139. }
  140. var body = response.body.split(',').sort().join(',');
  141. // Get the expected methods, either from Node herself (if available) or else from
  142. // what we know Express will get from the "methods" dependency
  143. var expected = (function() {
  144. var methods;
  145. if (require('http').METHODS) {
  146. methods = require('http').METHODS.reduce(function(memo, method) {
  147. if (method.toUpperCase() !== 'OPTIONS') {
  148. memo.push(method.toUpperCase());
  149. }
  150. return memo;
  151. }, []).sort().join(',');
  152. } else {
  153. methods = 'CHECKOUT,CONNECT,COPY,DELETE,GET,HEAD,LOCK,M-SEARCH,MERGE,MKACTIVITY,MKCOL,MOVE,NOTIFY,PATCH,POST,PROPFIND,PROPPATCH,PURGE,PUT,REPORT,SEARCH,SUBSCRIBE,TRACE,UNLOCK,UNSUBSCRIBE';
  154. }
  155. return methods;
  156. })();
  157. assert.equal(response.statusCode, 200);
  158. assert.equal(body, expected, require('util').format('\nExpected methods: %s\nActual methods: ', expected, response.body));
  159. done();
  160. });
  161. });
  162. it('for a route with a custom OPTIONS handler should use the custom handler', function(done) {
  163. httpHelper.testRoute('options', {
  164. url: 'test2',
  165. }, function(err, response) {
  166. if (err) {
  167. return done(err);
  168. }
  169. assert.equal(response.statusCode, 200);
  170. assert.equal(response.body, 'index');
  171. done();
  172. });
  173. });
  174. it('for a POST route with {cors: http://www.different.com} with an Access-Control-Request-Method header set to "POST" should respond with blank Access-Control-Allow-Origin and correct Access-Control-Allow-Method headers', function(done) {
  175. httpHelper.testRoute('options', {
  176. url: 'test',
  177. headers: {
  178. 'Access-Control-Request-Method': 'POST',
  179. 'Origin': 'http://www.example.com'
  180. },
  181. }, function(err, response) {
  182. if (err) {
  183. return done(err);
  184. }
  185. assert.equal(response.statusCode, 200);
  186. assert.equal(response.headers['access-control-allow-origin'], '');
  187. assert.equal(response.headers['access-control-allow-methods'], 'post');
  188. done();
  189. });
  190. });
  191. it('for a DELETE route with {cors: false} and an Access-Control-Request-Method header set to "DELETE" should respond with correct Access-Control-Allow-Origin and Access-Control-Allow-Method headers', function(done) {
  192. httpHelper.testRoute('options', {
  193. url: 'test',
  194. headers: {
  195. 'Access-Control-Request-Method': 'DELETE',
  196. 'Origin': 'http://www.example.com'
  197. },
  198. }, function(err, response) {
  199. if (err) {
  200. return done(err);
  201. }
  202. assert.equal(response.statusCode, 200);
  203. assert.equal(response.headers['access-control-allow-origin'], '');
  204. assert.equal(response.headers['access-control-allow-methods'], '');
  205. done();
  206. });
  207. });
  208. it('for a POST route with {cors: true} and an Access-Control-Request-Method header set to "POST" should respond with correct Access-Control-Allow-Origin and Access-Control-Allow-Method headers', function(done) {
  209. httpHelper.testRoute('options', {
  210. url: 'test2',
  211. headers: {
  212. 'Access-Control-Request-Method': 'POST',
  213. 'Origin': 'http://www.example.com'
  214. },
  215. }, function(err, response) {
  216. if (err) {
  217. return done(err);
  218. }
  219. assert.equal(response.statusCode, 200);
  220. assert.equal(response.headers['access-control-allow-origin'], 'http://www.example.com');
  221. assert.equal(response.headers['access-control-allow-methods'].toLowerCase(), 'get, post, put, delete, options, head');
  222. done();
  223. });
  224. });
  225. it('for a PUT route with no CORS settings and an Access-Control-Request-Method header set to "PUT" should respond with correct Access-Control-Allow-Origin and Access-Control-Allow-Method headers', function(done) {
  226. httpHelper.testRoute('options', {
  227. url: 'test2',
  228. headers: {
  229. 'Access-Control-Request-Method': 'PUT',
  230. 'Origin': 'http://www.example.com'
  231. },
  232. }, function(err, response) {
  233. if (err) {
  234. return done(err);
  235. }
  236. assert.equal(response.statusCode, 200);
  237. assert.equal(response.headers['access-control-allow-origin'], 'http://www.example.com');
  238. assert.equal(response.headers['access-control-allow-methods'].toLowerCase(), 'get, post, put, delete, options, head');
  239. done();
  240. });
  241. });
  242. });
  243. describe('a GET request with origin "http://www.example.com"', function() {
  244. it('to a route without a CORS config should result in a 200 response with a correct Access-Control-Allow-Origin and Access-Control-Expose-Headers header', function(done) {
  245. httpHelper.testRoute('get', {
  246. url: 'test',
  247. headers: {
  248. 'Origin': 'http://www.example.com'
  249. },
  250. }, function(err, response) {
  251. if (err) {
  252. return done(err);
  253. }
  254. assert.equal(response.statusCode, 200);
  255. assert.equal(response.headers['access-control-allow-origin'], 'http://www.example.com');
  256. assert.equal(response.headers['access-control-expose-headers'], '');
  257. done();
  258. });
  259. });
  260. it('to a route with "exposeHeaders" configured should result in a 200 response with a correct Access-Control-Allow-Origin and Access-Control-Expose-Headers header', function(done) {
  261. httpHelper.testRoute('get', {
  262. url: 'test2',
  263. headers: {
  264. 'Origin': 'http://www.example.com'
  265. },
  266. }, function(err, response) {
  267. if (err) {
  268. return done(err);
  269. }
  270. assert.equal(response.statusCode, 200);
  271. assert.equal(response.headers['access-control-allow-origin'], 'http://www.example.com');
  272. assert.equal(response.headers['access-control-expose-headers'], 'x-custom-header');
  273. done();
  274. });
  275. });
  276. it('to a route configured with {cors: false} should result in a 200 response with an empty Access-Control-Allow-Origin header', function(done) {
  277. httpHelper.testRoute('get', {
  278. url: 'test/find',
  279. headers: {
  280. 'Origin': 'http://www.example.com'
  281. },
  282. }, function(err, response) {
  283. if (err) {
  284. return done(err);
  285. }
  286. assert.equal(response.statusCode, 200);
  287. assert.equal(response.headers['access-control-allow-origin'], '');
  288. done();
  289. });
  290. });
  291. it('to a route with config {cors: "http://www.example.com"} should result in a 200 response with a correct Access-Control-Allow-Origin header', function(done) {
  292. httpHelper.testRoute('get', {
  293. url: 'test/update',
  294. headers: {
  295. 'Origin': 'http://www.example.com'
  296. },
  297. }, function(err, response) {
  298. if (err) {
  299. return done(err);
  300. }
  301. assert.equal(response.statusCode, 200);
  302. assert.equal(response.headers['access-control-allow-origin'], 'http://www.example.com');
  303. done();
  304. });
  305. });
  306. it('to a route with config {cors: "http://www.example.com:1338"} should result in a 200 response with an empty Access-Control-Allow-Origin header', function(done) {
  307. httpHelper.testRoute('get', {
  308. url: 'test/patch',
  309. headers: {
  310. 'Origin': 'http://www.example.com'
  311. },
  312. }, function(err, response) {
  313. if (err) {
  314. return done(err);
  315. }
  316. assert.equal(response.statusCode, 200);
  317. assert.equal(response.headers['access-control-allow-origin'], '');
  318. done();
  319. });
  320. });
  321. it('to a route with config {cors: {origin: "http://www.example.com"}} should result in a 200 response with a correct Access-Control-Allow-Origin header', function(done) {
  322. httpHelper.testRoute('get', {
  323. url: 'test/destroy',
  324. headers: {
  325. 'Origin': 'http://www.example.com'
  326. },
  327. }, function(err, response) {
  328. if (err) {
  329. return done(err);
  330. }
  331. assert.equal(response.statusCode, 200);
  332. assert.equal(response.headers['access-control-allow-origin'], 'http://www.example.com');
  333. done();
  334. });
  335. });
  336. it('to a route with config {cors: "http://www.different.com"} should result in a 200 response with an empty Access-Control-Allow-Origin header', function(done) {
  337. httpHelper.testRoute('get', {
  338. url: 'test/create',
  339. headers: {
  340. 'Origin': 'http://www.example.com'
  341. },
  342. }, function(err, response) {
  343. if (err) {
  344. return done(err);
  345. }
  346. assert.equal(response.statusCode, 200);
  347. assert.equal(response.headers['access-control-allow-origin'], '');
  348. done();
  349. });
  350. });
  351. });
  352. describe('a request with origin "http://www.example.com:1338"', function() {
  353. it('to a route with config {cors: "http://www.example.com:1338"} should result in a 200 response with a correct Access-Control-Allow-Origin header', function(done) {
  354. httpHelper.testRoute('get', {
  355. url: 'test/patch',
  356. headers: {
  357. 'Origin': 'http://www.example.com:1338'
  358. },
  359. }, function(err, response) {
  360. if (err) {
  361. return done(err);
  362. }
  363. assert.equal(response.statusCode, 200);
  364. assert.equal(response.headers['access-control-allow-origin'], 'http://www.example.com:1338');
  365. done();
  366. });
  367. });
  368. it('to a route with config {cors: "http://www.example.com"} should result in a 200 response with an empty Access-Control-Allow-Origin header', function(done) {
  369. httpHelper.testRoute('get', {
  370. url: 'test/update',
  371. headers: {
  372. 'Origin': 'http://www.example.com:1338'
  373. },
  374. }, function(err, response) {
  375. if (err) {
  376. return done(err);
  377. }
  378. assert.equal(response.statusCode, 200);
  379. assert.equal(response.headers['access-control-allow-origin'], '');
  380. done();
  381. });
  382. });
  383. });
  384. describe('a request with the same origin as the server (http://localhost:1342)"', function() {
  385. it('to a route with config {cors: "http://www.example.com:1338"} should result in a 200 response with an empty Access-Control-Allow-Origin header', function(done) {
  386. httpHelper.testRoute('get', {
  387. url: 'test/patch',
  388. headers: {
  389. 'Origin': 'http://localhost:1342'
  390. },
  391. }, function(err, response) {
  392. if (err) {
  393. return done(err);
  394. }
  395. assert.equal(response.statusCode, 200);
  396. assert.equal(response.headers['access-control-allow-origin'], '');
  397. done();
  398. });
  399. });
  400. it('to a route with config {cors: "http://www.example.com"} should result in a 200 response with an empty Access-Control-Allow-Origin header', function(done) {
  401. httpHelper.testRoute('get', {
  402. url: 'test/update',
  403. headers: {
  404. 'Origin': 'http://localhost:1342'
  405. },
  406. }, function(err, response) {
  407. if (err) {
  408. return done(err);
  409. }
  410. assert.equal(response.statusCode, 200);
  411. assert.equal(response.headers['access-control-allow-origin'], '');
  412. done();
  413. });
  414. });
  415. });
  416. });
  417. describe('with "allRoutes: false" and origin "*"', function() {
  418. before(function() {
  419. fs.writeFileSync(path.resolve('../', appName, 'config/cors.js'), "module.exports.cors = { 'origin': '*', 'allRoutes': false };");
  420. var routeConfig = {
  421. 'GET /test/find': {
  422. controller: 'TestController',
  423. action: 'find',
  424. cors: true
  425. },
  426. 'GET /test/update': {
  427. controller: 'TestController',
  428. action: 'update',
  429. cors: 'http://www.example.com'
  430. },
  431. 'GET /test/create': {
  432. controller: 'TestController',
  433. action: 'create',
  434. cors: 'http://www.different.com'
  435. },
  436. 'GET /test/destroy': {
  437. controller: 'TestController',
  438. action: 'destroy',
  439. cors: {
  440. origin: 'http://www.example.com'
  441. }
  442. }
  443. };
  444. fs.writeFileSync(path.resolve('../', appName, 'config/routes.js'), "module.exports.routes = " + JSON.stringify(routeConfig));
  445. });
  446. describe('a request with origin "http://www.example.com"', function() {
  447. it('to a route with no CORS should result in a 200 response with a blank Access-Control-Allow-Origin header', function(done) {
  448. httpHelper.testRoute('get', {
  449. url: 'test',
  450. headers: {
  451. 'Origin': 'http://www.example.com'
  452. },
  453. }, function(err, response) {
  454. if (err) {
  455. return done(err);
  456. }
  457. assert.equal(response.statusCode, 200);
  458. assert.equal(response.headers['access-control-allow-origin'], '');
  459. done();
  460. });
  461. });
  462. it('to a route with {cors: true} should result in a 200 response and a correct Access-Control-Allow-Origin header', function(done) {
  463. httpHelper.testRoute('get', {
  464. url: 'test/find',
  465. headers: {
  466. 'Origin': 'http://www.example.com'
  467. },
  468. }, function(err, response) {
  469. if (err) {
  470. return done(err);
  471. }
  472. assert.equal(response.statusCode, 200);
  473. assert.equal(response.headers['access-control-allow-origin'], 'http://www.example.com');
  474. done();
  475. });
  476. });
  477. it('to a route with config {cors: "http://www.example.com"} should result in a 200 response with a correct Access-Control-Allow-Origin header', function(done) {
  478. httpHelper.testRoute('get', {
  479. url: 'test/update',
  480. headers: {
  481. 'Origin': 'http://www.example.com'
  482. },
  483. }, function(err, response) {
  484. if (err) {
  485. return done(err);
  486. }
  487. assert.equal(response.statusCode, 200);
  488. assert.equal(response.headers['access-control-allow-origin'], 'http://www.example.com');
  489. done();
  490. });
  491. });
  492. it('to a route with config {cors: {origin: "http://www.example.com"}} should result in a 200 response with a correct Access-Control-Allow-Origin header', function(done) {
  493. httpHelper.testRoute('get', {
  494. url: 'test/destroy',
  495. headers: {
  496. 'Origin': 'http://www.example.com'
  497. },
  498. }, function(err, response) {
  499. if (err) {
  500. return done(err);
  501. }
  502. assert.equal(response.statusCode, 200);
  503. assert.equal(response.headers['access-control-allow-origin'], 'http://www.example.com');
  504. done();
  505. });
  506. });
  507. it('to a route with config {cors: "http://www.different.com"} should result in a 200 response with an empty Access-Control-Allow-Origin header', function(done) {
  508. httpHelper.testRoute('get', {
  509. url: 'test/create',
  510. headers: {
  511. 'Origin': 'http://www.example.com'
  512. },
  513. }, function(err, response) {
  514. if (err) {
  515. return done(err);
  516. }
  517. assert.equal(response.statusCode, 200);
  518. assert.equal(response.headers['access-control-allow-origin'], '');
  519. done();
  520. });
  521. });
  522. });
  523. it('a request with no origin header to a route with no CORS should return successfully', function(done) {
  524. httpHelper.testRoute('get', {
  525. url: 'test',
  526. }, function(err, response) {
  527. if (err) {
  528. return done(err);
  529. }
  530. assert.equal(response.statusCode, 200);
  531. done();
  532. });
  533. });
  534. it('a request with a non-http origin header to a route with no CORS should return successfully', function(done) {
  535. httpHelper.testRoute('get', {
  536. url: 'test',
  537. headers: {
  538. 'Origin': 'chrome-extension://abc123'
  539. },
  540. }, function(err, response) {
  541. if (err) {
  542. return done(err);
  543. }
  544. assert.equal(response.statusCode, 200);
  545. done();
  546. });
  547. });
  548. });
  549. describe('with "allRoutes: true" and origin "http://www.example.com", a request', function() {
  550. before(function() {
  551. fs.writeFileSync(path.resolve('../', appName, 'config/cors.js'), "module.exports.cors = { 'origin': 'http://www.example.com', 'allRoutes': true };");
  552. var routeConfig = {
  553. 'GET /test/find': {
  554. controller: 'TestController',
  555. action: 'find',
  556. cors: false
  557. },
  558. 'GET /test/update': {
  559. controller: 'TestController',
  560. action: 'update',
  561. cors: 'http://www.example.com'
  562. },
  563. 'GET /test/create': {
  564. controller: 'TestController',
  565. action: 'create',
  566. cors: 'http://www.different.com'
  567. },
  568. 'GET /test/destroy': {
  569. controller: 'TestController',
  570. action: 'destroy',
  571. cors: {
  572. origin: 'http://www.example.com'
  573. }
  574. }
  575. };
  576. fs.writeFileSync(path.resolve('../', appName, 'config/routes.js'), "module.exports.routes = " + JSON.stringify(routeConfig));
  577. });
  578. describe('with origin "http://www.example.com"', function() {
  579. it('to a route without a CORS config should result in a 200 response with a correct Access-Control-Allow-Origin header', function(done) {
  580. httpHelper.testRoute('get', {
  581. url: 'test',
  582. headers: {
  583. 'Origin': 'http://www.example.com'
  584. },
  585. }, function(err, response) {
  586. if (err) {
  587. return done(err);
  588. }
  589. assert.equal(response.statusCode, 200);
  590. assert.equal(response.headers['access-control-allow-origin'], 'http://www.example.com');
  591. done();
  592. });
  593. });
  594. it('to a route configured with {cors: false} should result in a 200 response with an empty Access-Control-Allow-Origin header', function(done) {
  595. httpHelper.testRoute('get', {
  596. url: 'test/find',
  597. headers: {
  598. 'Origin': 'http://www.example.com'
  599. },
  600. }, function(err, response) {
  601. if (err) {
  602. return done(err);
  603. }
  604. assert.equal(response.statusCode, 200);
  605. assert.equal(response.headers['access-control-allow-origin'], '');
  606. done();
  607. });
  608. });
  609. it('to a route with config {cors: "http://www.example.com"} should result in a 200 response with a correct Access-Control-Allow-Origin header', function(done) {
  610. httpHelper.testRoute('get', {
  611. url: 'test/update',
  612. headers: {
  613. 'Origin': 'http://www.example.com'
  614. },
  615. }, function(err, response) {
  616. if (err) {
  617. return done(err);
  618. }
  619. assert.equal(response.statusCode, 200);
  620. assert.equal(response.headers['access-control-allow-origin'], 'http://www.example.com');
  621. done();
  622. });
  623. });
  624. it('to a route with config {cors: "http://www.different.com"} should result in a 200 response with an empty Access-Control-Allow-Origin header', function(done) {
  625. httpHelper.testRoute('get', {
  626. url: 'test/create',
  627. headers: {
  628. 'Origin': 'http://www.example.com'
  629. },
  630. }, function(err, response) {
  631. if (err) {
  632. return done(err);
  633. }
  634. assert.equal(response.statusCode, 200);
  635. assert.equal(response.headers['access-control-allow-origin'], '');
  636. done();
  637. });
  638. });
  639. });
  640. describe('with origin "http://www.different.com"', function() {
  641. it('to a route without a CORS config should result in a 200 response with an empty Access-Control-Allow-Origin header', function(done) {
  642. httpHelper.testRoute('get', {
  643. url: 'test',
  644. headers: {
  645. 'Origin': 'http://www.different.com'
  646. },
  647. }, function(err, response) {
  648. if (err) {
  649. return done(err);
  650. }
  651. assert.equal(response.statusCode, 200);
  652. assert.equal(response.headers['access-control-allow-origin'], '');
  653. done();
  654. });
  655. });
  656. it('to a route with config {cors: "http://www.different.com"} should result in a 200 response with a correct Access-Control-Allow-Origin header', function(done) {
  657. httpHelper.testRoute('get', {
  658. url: 'test/create',
  659. headers: {
  660. 'Origin': 'http://www.different.com'
  661. },
  662. }, function(err, response) {
  663. if (err) {
  664. return done(err);
  665. }
  666. assert.equal(response.statusCode, 200);
  667. assert.equal(response.headers['access-control-allow-origin'], 'http://www.different.com');
  668. done();
  669. });
  670. });
  671. it('to a route with config {cors: {origin: "http://www.example.com"}} should result in a 200 response with an empty Access-Control-Allow-Origin header', function(done) {
  672. httpHelper.testRoute('get', {
  673. url: 'test/destroy',
  674. headers: {
  675. 'Origin': 'http://www.different.com'
  676. },
  677. }, function(err, response) {
  678. if (err) {
  679. return done(err);
  680. }
  681. assert.equal(response.statusCode, 200);
  682. assert.equal(response.headers['access-control-allow-origin'], '');
  683. done();
  684. });
  685. });
  686. });
  687. });
  688. describe('with "credentials: true", a request', function() {
  689. before(function() {
  690. fs.writeFileSync(path.resolve('../', appName, 'config/cors.js'), "module.exports.cors = { 'origin': '*', 'allRoutes': true, 'credentials': true};");
  691. var routeConfig = {
  692. 'GET /test/destroy': {
  693. controller: 'TestController',
  694. action: 'destroy',
  695. cors: {
  696. origin: 'http://www.example.com',
  697. credentials: false
  698. }
  699. }
  700. };
  701. fs.writeFileSync(path.resolve('../', appName, 'config/routes.js'), "module.exports.routes = " + JSON.stringify(routeConfig));
  702. });
  703. it('to a route without a CORS config should result in a 200 response with an Access-Control-Allow-Credentials header with value "true"', function(done) {
  704. httpHelper.testRoute('get', {
  705. url: 'test',
  706. headers: {
  707. 'Origin': 'http://www.example.com'
  708. },
  709. }, function(err, response) {
  710. if (err) {
  711. return done(err);
  712. }
  713. assert.equal(response.statusCode, 200);
  714. assert.equal(response.headers['access-control-allow-credentials'], 'true');
  715. done();
  716. });
  717. });
  718. it('to a route with {cors: {credentials: false}} should result in a 200 response with an Access-Control-Allow-Credentials header with value "false"', function(done) {
  719. httpHelper.testRoute('get', {
  720. url: 'test/destroy',
  721. headers: {
  722. 'Origin': 'http://www.example.com'
  723. },
  724. }, function(err, response) {
  725. if (err) {
  726. return done(err);
  727. }
  728. assert.equal(response.statusCode, 200);
  729. assert.equal(response.headers['access-control-allow-credentials'], 'false');
  730. done();
  731. });
  732. });
  733. });
  734. describe('with "credentials: false", a request', function() {
  735. before(function() {
  736. fs.writeFileSync(path.resolve('../', appName, 'config/cors.js'), 'module.exports.cors = { origin: \'*\', allRoutes: true, credentials: false};');
  737. var routeConfig = {
  738. 'GET /test/destroy': {
  739. controller: 'TestController',
  740. action: 'destroy',
  741. cors: {
  742. origin: 'http://www.example.com',
  743. credentials: true
  744. }
  745. }
  746. };
  747. fs.writeFileSync(path.resolve('../', appName, 'config/routes.js'), 'module.exports.routes = ' + JSON.stringify(routeConfig));
  748. });
  749. it('to a route without a CORS config should result in a 200 response with an Access-Control-Allow-Credentials header with value "false"', function(done) {
  750. httpHelper.testRoute('get', {
  751. url: 'test',
  752. headers: {
  753. 'Origin': 'http://www.example.com'
  754. },
  755. }, function(err, response) {
  756. if (err) {
  757. return done(err);
  758. }
  759. assert.equal(response.statusCode, 200);
  760. assert.equal(response.headers['access-control-allow-credentials'], 'false');
  761. done();
  762. });
  763. });
  764. it('to a route with {cors: {credentials: true}} should result in a 200 response with an Access-Control-Allow-Credentials header with value "true"', function(done) {
  765. httpHelper.testRoute('get', {
  766. url: 'test/destroy',
  767. headers: {
  768. 'Origin': 'http://www.example.com'
  769. },
  770. }, function(err, response) {
  771. if (err) {
  772. return done(err);
  773. }
  774. assert.equal(response.statusCode, 200);
  775. assert.equal(response.headers['access-control-allow-credentials'], 'true');
  776. done();
  777. });
  778. });
  779. });
  780. // Delete app directory.
  781. after(function() {
  782. process.chdir('../');
  783. appHelper.teardown();
  784. });
  785. }); //</describe('CORS config ::')>
  786. // ██████╗███████╗██████╗ ███████╗ ██████╗ ██████╗ ███╗ ██╗███████╗██╗ ██████╗
  787. // ██╔════╝██╔════╝██╔══██╗██╔════╝ ██╔════╝██╔═══██╗████╗ ██║██╔════╝██║██╔════╝
  788. // ██║ ███████╗██████╔╝█████╗ ██║ ██║ ██║██╔██╗ ██║█████╗ ██║██║ ███╗
  789. // ██║ ╚════██║██╔══██╗██╔══╝ ██║ ██║ ██║██║╚██╗██║██╔══╝ ██║██║ ██║
  790. // ╚██████╗███████║██║ ██║██║ ╚██████╗╚██████╔╝██║ ╚████║██║ ██║╚██████╔╝
  791. // ╚═════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝
  792. //
  793. describe('CSRF config ::', function() {
  794. before(function(done) {
  795. appHelper.build(done);
  796. });
  797. describe('with CSRF set to `false`', function() {
  798. it('no CSRF token should be present in view locals', function(done) {
  799. httpHelper.testRoute('get', 'viewtest/csrf', function(err, response) {
  800. if (err) {
  801. return done(err);
  802. }
  803. assert(response.body.indexOf('csrf=null') !== -1, response.body);
  804. done();
  805. });
  806. });
  807. it('a request to /csrfToken should result in a 404 error', function(done) {
  808. httpHelper.testRoute('get', '/csrfToken', function(err, response) {
  809. if (err) {
  810. return done(err);
  811. }
  812. assert(response.statusCode === 404);
  813. done();
  814. });
  815. });
  816. });
  817. describe('with CSRF set to `true`', function() {
  818. before(function() {
  819. fs.writeFileSync(path.resolve('../', appName, 'config/csrf.js'), 'module.exports.csrf = true;');
  820. });
  821. it("a CSRF token should be present in view locals", function(done) {
  822. httpHelper.testRoute("get", 'viewtest/csrf', function(err, response) {
  823. if (err) {
  824. return done(err);
  825. }
  826. assert(response.body.match(/csrf=.{36}(?!.)/), response.body);
  827. done();
  828. });
  829. });
  830. it("a request to /csrfToken should respond with a _csrf token", function(done) {
  831. httpHelper.testRoute("get", 'csrftoken', function(err, response) {
  832. if (err) {
  833. return done(err);
  834. }
  835. try {
  836. var body = JSON.parse(response.body);
  837. assert(body._csrf, response.body);
  838. done();
  839. } catch (e) {
  840. done(new Error('Unexpected response: ' + response.body));
  841. }
  842. });
  843. });
  844. it("a POST request without a CSRF token should result in a 403 response", function(done) {
  845. httpHelper.testRoute("post", 'user', function(err, response) {
  846. if (err) {
  847. return done(err);
  848. }
  849. assert.equal(response.statusCode, 403);
  850. done();
  851. });
  852. });
  853. it("a POST request with a valid CSRF token should result in a 201 response", function(done) {
  854. httpHelper.testRoute("get", 'csrftoken', function(err, response) {
  855. if (err) {
  856. return done(err);
  857. }
  858. try {
  859. var body = JSON.parse(response.body);
  860. var sid = response.headers['set-cookie'][0].split(';')[0].substr(10);
  861. httpHelper.testRoute("post", {
  862. url: 'user',
  863. headers: {
  864. 'Content-type': 'application/json',
  865. 'cookie': 'sails.sid=' + sid
  866. },
  867. body: '{"_csrf":"' + body._csrf + '"}'
  868. }, function(err, response) {
  869. if (err) {
  870. return done(err);
  871. }
  872. assert.equal(response.statusCode, 201);
  873. done();
  874. });
  875. } catch (e) {
  876. done(e);
  877. }
  878. });
  879. });
  880. });
  881. describe("with CSRF set to {route: '/anotherCsrf'}", function() {
  882. before(function() {
  883. fs.writeFileSync(path.resolve('../', appName, 'config/csrf.js'), "module.exports.csrf = {route: '/anotherCsrf'};");
  884. });
  885. it("a request to /csrfToken should respond with a 404", function(done) {
  886. httpHelper.testRoute("get", 'csrftoken', function(err, response) {
  887. if (err) {
  888. return done(err);
  889. }
  890. assert.equal(response.statusCode, 404);
  891. done();
  892. });
  893. });
  894. it("a request to /anotherCsrf should respond with a _csrf token", function(done) {
  895. httpHelper.testRoute("get", 'anotherCsrf', function(err, response) {
  896. if (err) {
  897. return done(err);
  898. }
  899. try {
  900. var body = JSON.parse(response.body);
  901. assert(body._csrf, response.body);
  902. done();
  903. } catch (e) {
  904. done(new Error('Unexpected response: ' + response.body));
  905. }
  906. });
  907. });
  908. it("a POST request without a CSRF token should result in a 403 response", function(done) {
  909. httpHelper.testRoute("post", 'user', function(err, response) {
  910. if (err) {
  911. return done(err);
  912. }
  913. assert.equal(response.statusCode, 403);
  914. done();
  915. });
  916. });
  917. it("a POST request with a valid CSRF token should result in a 201 response", function(done) {
  918. httpHelper.testRoute("get", 'anotherCsrf', function(err, response) {
  919. if (err) {
  920. return done(err);
  921. }
  922. try {
  923. var body = JSON.parse(response.body);
  924. var sid = response.headers['set-cookie'][0].split(';')[0].substr(10);
  925. httpHelper.testRoute("post", {
  926. url: 'user',
  927. headers: {
  928. 'Content-type': 'application/json',
  929. 'cookie': 'sails.sid=' + sid
  930. },
  931. body: '{"_csrf":"' + body._csrf + '"}'
  932. }, function(err, response) {
  933. if (err) {
  934. return done(err);
  935. }
  936. assert.equal(response.statusCode, 201);
  937. done();
  938. });
  939. } catch (e) {
  940. done(e);
  941. }
  942. });
  943. });
  944. });
  945. describe("with CSRF set to {protectionEnabled: true, grantTokenViaAjax: false}", function() {
  946. before(function() {
  947. fs.writeFileSync(path.resolve('../', appName, 'config/csrf.js'), "module.exports.csrf = {protectionEnabled: true, grantTokenViaAjax: false};");
  948. });
  949. it("a request to /csrfToken should respond with a 404", function(done) {
  950. httpHelper.testRoute("get", 'csrftoken', function(err, response) {
  951. if (err) {
  952. return done(err);
  953. }
  954. assert.equal(response.statusCode, 404);
  955. done();
  956. });
  957. });
  958. });
  959. describe("with CSRF set to {protectionEnabled: true, routesDisabled: '/foo, /user'}", function() {
  960. before(function() {
  961. fs.writeFileSync(path.resolve('../', appName, 'config/csrf.js'), "module.exports.csrf = {protectionEnabled: true, routesDisabled: '/user'};");
  962. });
  963. it("a POST request on /user without a CSRF token should result in a 201 response", function(done) {
  964. httpHelper.testRoute("post", 'user', function(err, response) {
  965. if (err) {
  966. return done(err);
  967. }
  968. assert.equal(response.statusCode, 201);
  969. done();
  970. });
  971. });
  972. it("a POST request on /test without a CSRF token should result in a 403 response", function(done) {
  973. httpHelper.testRoute("post", 'test', function(err, response) {
  974. if (err) {
  975. return done(err);
  976. }
  977. assert.equal(response.statusCode, 403);
  978. done();
  979. });
  980. });
  981. });
  982. describe("with CSRF set to true and sessions disabled", function() {
  983. before(function() {
  984. fs.writeFileSync(path.resolve('../', appName, 'config/csrf.js'), "module.exports.csrf = true;");
  985. fs.writeFileSync(path.resolve('../', appName, 'config/killsession.js'), "module.exports.http = {middleware: {session: function(req, res, next) {return next();}}};");
  986. });
  987. it("a POST request on /user without a CSRF token should result in a 201 response", function(done) {
  988. httpHelper.testRoute("post", 'user', function(err, response) {
  989. if (err) {
  990. return done(err);
  991. }
  992. assert.equal(response.statusCode, 201);
  993. done();
  994. });
  995. });
  996. it("a POST request on /test without a CSRF token should result in a 200 response", function(done) {
  997. httpHelper.testRoute("post", 'test', function(err, response) {
  998. if (err) {
  999. return done(err);
  1000. }
  1001. assert.equal(response.statusCode, 200);
  1002. done();
  1003. });
  1004. });
  1005. });
  1006. // Remove test app files/directories.
  1007. after(function() {
  1008. process.chdir('../');
  1009. appHelper.teardown();
  1010. });
  1011. }); //</describe('CSRF config ::')>
  1012. // ██████╗ ██████╗ ██████╗ ███████╗ ██╗ ██████╗███████╗██████╗ ███████╗
  1013. // ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██║ ██╔════╝██╔════╝██╔══██╗██╔════╝
  1014. // ██║ ██║ ██║██████╔╝███████╗ ████████╗ ██║ ███████╗██████╔╝█████╗
  1015. // ██║ ██║ ██║██╔══██╗╚════██║ ██╔═██╔═╝ ██║ ╚════██║██╔══██╗██╔══╝
  1016. // ╚██████╗╚██████╔╝██║ ██║███████║ ██████║ ╚██████╗███████║██║ ██║██║
  1017. // ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝╚══════╝╚═╝ ╚═╝╚═╝
  1018. //
  1019. // ██╗████████╗ ██████╗ ██████╗ ███████╗████████╗██╗ ██╗███████╗██████╗ █████╗ ████████╗ ██╗ █████╗ ███████╗████████╗██╗
  1020. // ██╔╝╚══██╔══╝██╔═══██╗██╔════╝ ██╔════╝╚══██╔══╝██║ ██║██╔════╝██╔══██╗ ██╔══██╗╚══██╔══╝ ██║ ██╔══██╗██╔════╝╚══██╔══╝╚██╗
  1021. // ██║ ██║ ██║ ██║██║ ███╗█████╗ ██║ ███████║█████╗ ██████╔╝ ███████║ ██║ ██║ ███████║███████╗ ██║ ██║
  1022. // ██║ ██║ ██║ ██║██║ ██║██╔══╝ ██║ ██╔══██║██╔══╝ ██╔══██╗ ██╔══██║ ██║ ██║ ██╔══██║╚════██║ ██║ ██║
  1023. // ╚██╗ ██║ ╚██████╔╝╚██████╔╝███████╗ ██║ ██║ ██║███████╗██║ ██║ ██║ ██║ ██║ ███████╗██║ ██║███████║ ██║ ██╔╝
  1024. // ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝
  1025. //
  1026. describe('CORS+CSRF ::', function() {
  1027. before(function(done) {
  1028. appHelper.build(function(err) {
  1029. if (err) {
  1030. return done(err);
  1031. }
  1032. // Add a CORS config that should be IGNORED by the CSRF hook, which does its own CORS handling
  1033. // If this isn't being ignored properly, then errors should occur when requesting /csrfToken from a different origin
  1034. fs.writeFileSync(path.resolve('../', appName, 'config/cors.js'), "module.exports.cors = { 'origin': 'http://www.example.com,http://www.someplace.com,http://www.different.com', 'allRoutes': true, 'credentials': false};");
  1035. done();
  1036. });
  1037. });
  1038. describe("with CSRF set to true (no origin set)", function() {
  1039. before(function() {
  1040. fs.writeFileSync(path.resolve('../', appName, 'config/csrf.js'), "module.exports.csrf = true;");
  1041. });
  1042. it("a request to /csrfToken should result in a 200 response and a null token", function(done) {
  1043. httpHelper.testRoute("get", {
  1044. url: 'csrfToken',
  1045. headers: {
  1046. origin: "http://www.example.com"
  1047. }
  1048. }, function(err, response) {
  1049. if (err) {
  1050. return done(err);
  1051. }
  1052. assert(response.statusCode == 200);
  1053. assert(JSON.parse(response.body)._csrf === null, response.body);
  1054. done();
  1055. });
  1056. });
  1057. });
  1058. describe("with CSRF set to {origin: 'http://www.example.com,http://www.someplace.com', credentials: false}", function() {
  1059. before(function() {
  1060. fs.writeFileSync(path.resolve('../', appName, 'config/cors.js'), "module.exports.cors = { 'origin': '*', 'allRoutes': false, 'credentials': false};");
  1061. fs.writeFileSync(path.resolve('../', appName, 'config/csrf.js'), "module.exports.csrf = {origin: ' http://www.example.com, http://www.someplace.com '};");
  1062. fs.writeFileSync(path.resolve('../', appName, 'config/routes.js'), "module.exports.routes = {\"/viewtest/csrf\":{\"cors\":true}}");
  1063. });
  1064. describe("when the request origin header is 'http://www.example.com'", function() {
  1065. it("a CSRF token should be present in view locals", function(done) {
  1066. httpHelper.testRoute("get", {
  1067. url: 'viewtest/csrf',
  1068. headers: {
  1069. origin: "http://www.someplace.com"
  1070. }
  1071. }, function(err, response) {
  1072. if (err) {
  1073. return done(err);
  1074. }
  1075. assert(response.body.match(/csrf=.{36}(?!.)/));
  1076. done();
  1077. });
  1078. });
  1079. it("a request to /csrfToken should respond with a _csrf token", function(done) {
  1080. httpHelper.testRoute("get", {
  1081. url: 'csrfToken',
  1082. headers: {
  1083. origin: "http://www.example.com"
  1084. }
  1085. }, function(err, response) {
  1086. if (err) {
  1087. return done(err);
  1088. }
  1089. assert.equal(response.headers['access-control-allow-origin'], 'http://www.example.com');
  1090. assert.equal(response.headers['access-control-allow-credentials'], 'true');
  1091. try {
  1092. var body = JSON.parse(response.body);
  1093. assert(body._csrf, response.body);
  1094. done();
  1095. } catch (e) {
  1096. done(new Error('Unexpected response: ' + response.body));
  1097. }
  1098. });
  1099. });
  1100. });
  1101. describe("when the request origin header is 'http://www.someplace.com'", function() {
  1102. it("a CSRF token should be present in view locals", function(done) {
  1103. httpHelper.testRoute("get", {
  1104. url: 'viewtest/csrf',
  1105. headers: {
  1106. origin: "http://www.someplace.com"
  1107. }
  1108. }, function(err, response) {
  1109. if (err) {
  1110. return done(err);
  1111. }
  1112. assert(response.body.match(/csrf=.{36}(?!.)/));
  1113. done();
  1114. });
  1115. });
  1116. it("a request to /csrfToken should respond with a _csrf token", function(done) {
  1117. httpHelper.testRoute("get", {
  1118. url: 'csrfToken',
  1119. headers: {
  1120. origin: "http://www.someplace.com"
  1121. }
  1122. }, function(err, response) {
  1123. if (err) {
  1124. return done(err);
  1125. }
  1126. assert.equal(response.headers['access-control-allow-origin'], 'http://www.someplace.com');
  1127. try {
  1128. var body = JSON.parse(response.body);
  1129. assert(body._csrf, response.body);
  1130. done();
  1131. } catch (e) {