cors.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. (function () {
  2. /*global describe, it*/
  3. 'use strict';
  4. var should = require('should'),
  5. cors = require('../lib');
  6. var fakeRequest = function (headers) {
  7. return {
  8. headers: headers || {
  9. 'origin': 'request.com',
  10. 'access-control-request-headers': 'requestedHeader1,requestedHeader2'
  11. },
  12. pause: function () {
  13. // do nothing
  14. return;
  15. },
  16. resume: function () {
  17. // do nothing
  18. return;
  19. }
  20. };
  21. },
  22. fakeResponse = function () {
  23. var headers = {};
  24. return {
  25. allHeaders: function () {
  26. return headers;
  27. },
  28. getHeader: function (key) {
  29. return headers[key];
  30. },
  31. setHeader: function (key, value) {
  32. headers[key] = value;
  33. return;
  34. },
  35. get: function (key) {
  36. return headers[key];
  37. }
  38. };
  39. };
  40. describe('cors', function () {
  41. it('does not alter `options` configuration object', function () {
  42. var options = Object.freeze({
  43. origin: 'custom-origin'
  44. });
  45. (function () {
  46. cors(options);
  47. }).should.not.throw();
  48. });
  49. it('passes control to next middleware', function (done) {
  50. // arrange
  51. var req, res, next;
  52. req = fakeRequest();
  53. res = fakeResponse();
  54. next = function () {
  55. done();
  56. };
  57. // act
  58. cors()(req, res, next);
  59. });
  60. it('shortcircuits preflight requests', function (done) {
  61. // arrange
  62. var req, res, next;
  63. req = fakeRequest();
  64. req.method = 'OPTIONS';
  65. res = fakeResponse();
  66. res.end = function () {
  67. // assert
  68. res.statusCode.should.equal(204);
  69. done();
  70. };
  71. next = function () {
  72. // assert
  73. done('should not be called');
  74. };
  75. // act
  76. cors()(req, res, next);
  77. });
  78. it('can configure preflight success response status code', function (done) {
  79. // arrange
  80. var req, res, next;
  81. req = fakeRequest();
  82. req.method = 'OPTIONS';
  83. res = fakeResponse();
  84. res.end = function () {
  85. // assert
  86. res.statusCode.should.equal(200);
  87. done();
  88. };
  89. next = function () {
  90. // assert
  91. done('should not be called');
  92. };
  93. // act
  94. cors({optionsSuccessStatus: 200})(req, res, next);
  95. });
  96. it('doesn\'t shortcircuit preflight requests with preflightContinue option', function (done) {
  97. // arrange
  98. var req, res, next;
  99. req = fakeRequest();
  100. req.method = 'OPTIONS';
  101. res = fakeResponse();
  102. res.end = function () {
  103. // assert
  104. done('should not be called');
  105. };
  106. next = function () {
  107. // assert
  108. done();
  109. };
  110. // act
  111. cors({preflightContinue: true})(req, res, next);
  112. });
  113. it('normalizes method names', function (done) {
  114. // arrange
  115. var req, res, next;
  116. req = fakeRequest();
  117. req.method = 'options';
  118. res = fakeResponse();
  119. res.end = function () {
  120. // assert
  121. res.statusCode.should.equal(204);
  122. done();
  123. };
  124. next = function () {
  125. // assert
  126. done('should not be called');
  127. };
  128. // act
  129. cors()(req, res, next);
  130. });
  131. it('includes Content-Length response header', function (done) {
  132. // arrange
  133. var req, res, next;
  134. req = fakeRequest();
  135. req.method = 'options';
  136. res = fakeResponse();
  137. res.end = function () {
  138. // assert
  139. res.getHeader('Content-Length').should.equal('0');
  140. done();
  141. };
  142. next = function () {
  143. // assert
  144. done('should not be called');
  145. };
  146. // act
  147. cors()(req, res, next);
  148. });
  149. it('no options enables default CORS to all origins', function (done) {
  150. // arrange
  151. var req, res, next;
  152. req = fakeRequest();
  153. res = fakeResponse();
  154. next = function () {
  155. // assert
  156. res.getHeader('Access-Control-Allow-Origin').should.equal('*');
  157. should.not.exist(res.getHeader('Access-Control-Allow-Methods'));
  158. done();
  159. };
  160. // act
  161. cors()(req, res, next);
  162. });
  163. it('OPTION call with no options enables default CORS to all origins and methods', function (done) {
  164. // arrange
  165. var req, res, next;
  166. req = fakeRequest();
  167. req.method = 'OPTIONS';
  168. res = fakeResponse();
  169. res.end = function () {
  170. // assert
  171. res.statusCode.should.equal(204);
  172. done();
  173. };
  174. next = function () {
  175. // assert
  176. res.getHeader('Access-Control-Allow-Origin').should.equal('*');
  177. res.getHeader('Access-Control-Allow-Methods').should.equal('GET,PUT,PATCH,POST,DELETE');
  178. done();
  179. };
  180. // act
  181. cors()(req, res, next);
  182. });
  183. describe('passing static options', function () {
  184. it('overrides defaults', function (done) {
  185. // arrange
  186. var req, res, next, options;
  187. options = {
  188. origin: 'example.com',
  189. methods: ['FOO', 'bar'],
  190. headers: ['FIZZ', 'buzz'],
  191. credentials: true,
  192. maxAge: 123
  193. };
  194. req = fakeRequest();
  195. req.method = 'OPTIONS';
  196. res = fakeResponse();
  197. res.end = function () {
  198. // assert
  199. res.statusCode.should.equal(204);
  200. done();
  201. };
  202. next = function () {
  203. // assert
  204. res.getHeader('Access-Control-Allow-Origin').should.equal('example.com');
  205. res.getHeader('Access-Control-Allow-Methods').should.equal('FOO,bar');
  206. res.getHeader('Access-Control-Allow-Headers').should.equal('FIZZ,buzz');
  207. res.getHeader('Access-Control-Allow-Credentials').should.equal('true');
  208. res.getHeader('Access-Control-Max-Age').should.equal('123');
  209. done();
  210. };
  211. // act
  212. cors(options)(req, res, next);
  213. });
  214. it('matches request origin against regexp', function(done) {
  215. var req = fakeRequest();
  216. var res = fakeResponse();
  217. var options = { origin: /^(.+\.)?request.com$/ };
  218. cors(options)(req, res, function(err) {
  219. should.not.exist(err);
  220. res.getHeader('Access-Control-Allow-Origin').should.equal(req.headers.origin);
  221. should.exist(res.getHeader('Vary'));
  222. res.getHeader('Vary').should.equal('Origin');
  223. return done();
  224. });
  225. });
  226. it('matches request origin against array of origin checks', function(done) {
  227. var req = fakeRequest();
  228. var res = fakeResponse();
  229. var options = { origin: [ /foo\.com$/, 'request.com' ] };
  230. cors(options)(req, res, function(err) {
  231. should.not.exist(err);
  232. res.getHeader('Access-Control-Allow-Origin').should.equal(req.headers.origin);
  233. should.exist(res.getHeader('Vary'));
  234. res.getHeader('Vary').should.equal('Origin');
  235. return done();
  236. });
  237. });
  238. it('doesn\'t match request origin against array of invalid origin checks', function(done) {
  239. var req = fakeRequest();
  240. var res = fakeResponse();
  241. var options = { origin: [ /foo\.com$/, 'bar.com' ] };
  242. cors(options)(req, res, function(err) {
  243. should.not.exist(err);
  244. should.not.exist(res.getHeader('Access-Control-Allow-Origin'));
  245. should.exist(res.getHeader('Vary'));
  246. res.getHeader('Vary').should.equal('Origin');
  247. return done();
  248. });
  249. });
  250. it('origin of false disables cors', function (done) {
  251. // arrange
  252. var req, res, next, options;
  253. options = {
  254. origin: false,
  255. methods: ['FOO', 'bar'],
  256. headers: ['FIZZ', 'buzz'],
  257. credentials: true,
  258. maxAge: 123
  259. };
  260. req = fakeRequest();
  261. res = fakeResponse();
  262. next = function () {
  263. // assert
  264. should.not.exist(res.getHeader('Access-Control-Allow-Origin'));
  265. should.not.exist(res.getHeader('Access-Control-Allow-Methods'));
  266. should.not.exist(res.getHeader('Access-Control-Allow-Headers'));
  267. should.not.exist(res.getHeader('Access-Control-Allow-Credentials'));
  268. should.not.exist(res.getHeader('Access-Control-Max-Age'));
  269. done();
  270. };
  271. // act
  272. cors(options)(req, res, next);
  273. });
  274. it('can override origin', function (done) {
  275. // arrange
  276. var req, res, next, options;
  277. options = {
  278. origin: 'example.com'
  279. };
  280. req = fakeRequest();
  281. res = fakeResponse();
  282. next = function () {
  283. // assert
  284. res.getHeader('Access-Control-Allow-Origin').should.equal('example.com');
  285. done();
  286. };
  287. // act
  288. cors(options)(req, res, next);
  289. });
  290. it('includes Vary header for specific origins', function (done) {
  291. // arrange
  292. var req, res, next, options;
  293. options = {
  294. origin: 'example.com'
  295. };
  296. req = fakeRequest();
  297. res = fakeResponse();
  298. next = function () {
  299. // assert
  300. should.exist(res.getHeader('Vary'));
  301. res.getHeader('Vary').should.equal('Origin');
  302. done();
  303. };
  304. // act
  305. cors(options)(req, res, next);
  306. });
  307. it('appends to an existing Vary header', function (done) {
  308. // arrange
  309. var req, res, next, options;
  310. options = {
  311. origin: 'example.com'
  312. };
  313. req = fakeRequest();
  314. res = fakeResponse();
  315. res.setHeader('Vary', 'Foo');
  316. next = function () {
  317. // assert
  318. res.getHeader('Vary').should.equal('Foo, Origin');
  319. done();
  320. };
  321. // act
  322. cors(options)(req, res, next);
  323. });
  324. it('origin defaults to *', function (done) {
  325. // arrange
  326. var req, res, next, options;
  327. options = {
  328. };
  329. req = fakeRequest();
  330. res = fakeResponse();
  331. next = function () {
  332. // assert
  333. res.getHeader('Access-Control-Allow-Origin').should.equal('*');
  334. done();
  335. };
  336. // act
  337. cors(options)(req, res, next);
  338. });
  339. it('specifying true for origin reflects requesting origin', function (done) {
  340. // arrange
  341. var req, res, next, options;
  342. options = {
  343. origin: true
  344. };
  345. req = fakeRequest();
  346. res = fakeResponse();
  347. next = function () {
  348. // assert
  349. res.getHeader('Access-Control-Allow-Origin').should.equal('request.com');
  350. done();
  351. };
  352. // act
  353. cors(options)(req, res, next);
  354. });
  355. it('should allow origin when callback returns true', function (done) {
  356. var req, res, next, options;
  357. options = {
  358. origin: function (sentOrigin, cb) {
  359. sentOrigin.should.equal('request.com');
  360. cb(null, true);
  361. }
  362. };
  363. req = fakeRequest();
  364. res = fakeResponse();
  365. next = function () {
  366. res.getHeader('Access-Control-Allow-Origin').should.equal('request.com');
  367. done();
  368. };
  369. cors(options)(req, res, next);
  370. });
  371. it('should not allow origin when callback returns false', function (done) {
  372. var req, res, next, options;
  373. options = {
  374. origin: function (sentOrigin, cb) {
  375. sentOrigin.should.equal('request.com');
  376. cb(null, false);
  377. }
  378. };
  379. req = fakeRequest();
  380. res = fakeResponse();
  381. next = function () {
  382. should.not.exist(res.getHeader('Access-Control-Allow-Origin'));
  383. should.not.exist(res.getHeader('Access-Control-Allow-Methods'));
  384. should.not.exist(res.getHeader('Access-Control-Allow-Headers'));
  385. should.not.exist(res.getHeader('Access-Control-Allow-Credentials'));
  386. should.not.exist(res.getHeader('Access-Control-Max-Age'));
  387. done();
  388. };
  389. cors(options)(req, res, next);
  390. });
  391. it('should not override options.origin callback', function (done) {
  392. var req, res, next, options;
  393. options = {
  394. origin: function (sentOrigin, cb) {
  395. var isValid = sentOrigin === 'request.com';
  396. cb(null, isValid);
  397. }
  398. };
  399. req = fakeRequest();
  400. res = fakeResponse();
  401. next = function () {
  402. res.getHeader('Access-Control-Allow-Origin').should.equal('request.com');
  403. };
  404. cors(options)(req, res, next);
  405. req = fakeRequest({
  406. 'origin': 'invalid-request.com'
  407. });
  408. res = fakeResponse();
  409. next = function () {
  410. should.not.exist(res.getHeader('Access-Control-Allow-Origin'));
  411. should.not.exist(res.getHeader('Access-Control-Allow-Methods'));
  412. should.not.exist(res.getHeader('Access-Control-Allow-Headers'));
  413. should.not.exist(res.getHeader('Access-Control-Allow-Credentials'));
  414. should.not.exist(res.getHeader('Access-Control-Max-Age'));
  415. done();
  416. };
  417. cors(options)(req, res, next);
  418. });
  419. it('can override methods', function (done) {
  420. // arrange
  421. var req, res, next, options;
  422. options = {
  423. methods: ['method1', 'method2']
  424. };
  425. req = fakeRequest();
  426. req.method = 'OPTIONS';
  427. res = fakeResponse();
  428. res.end = function () {
  429. // assert
  430. res.statusCode.should.equal(204);
  431. done();
  432. };
  433. next = function () {
  434. // assert
  435. res.getHeader('Access-Control-Allow-Methods').should.equal('method1,method2');
  436. done();
  437. };
  438. // act
  439. cors(options)(req, res, next);
  440. });
  441. it('methods defaults to GET, PUT, PATCH, POST, DELETE', function (done) {
  442. // arrange
  443. var req, res, next, options;
  444. options = {
  445. };
  446. req = fakeRequest();
  447. req.method = 'OPTIONS';
  448. res = fakeResponse();
  449. res.end = function () {
  450. // assert
  451. res.statusCode.should.equal(204);
  452. done();
  453. };
  454. next = function () {
  455. // assert
  456. res.getHeader('Access-Control-Allow-Methods').should.equal('GET,PUT,PATCH,POST,DELETE');
  457. done();
  458. };
  459. // act
  460. cors(options)(req, res, next);
  461. });
  462. it('can specify allowed headers', function (done) {
  463. // arrange
  464. var req, res, options;
  465. options = {
  466. allowedHeaders: ['header1', 'header2']
  467. };
  468. req = fakeRequest();
  469. req.method = 'OPTIONS';
  470. res = fakeResponse();
  471. res.end = function () {
  472. // assert
  473. res.getHeader('Access-Control-Allow-Headers').should.equal('header1,header2');
  474. should.not.exist(res.getHeader('Vary'));
  475. done();
  476. };
  477. // act
  478. cors(options)(req, res, null);
  479. });
  480. it('specifying an empty list or string of allowed headers will result in no response header for allowed headers', function (done) {
  481. // arrange
  482. var req, res, next, options;
  483. options = {
  484. allowedHeaders: []
  485. };
  486. req = fakeRequest();
  487. res = fakeResponse();
  488. next = function () {
  489. // assert
  490. should.not.exist(res.getHeader('Access-Control-Allow-Headers'));
  491. should.not.exist(res.getHeader('Vary'));
  492. done();
  493. };
  494. // act
  495. cors(options)(req, res, next);
  496. });
  497. it('if no allowed headers are specified, defaults to requested allowed headers', function (done) {
  498. // arrange
  499. var req, res, options;
  500. options = {
  501. };
  502. req = fakeRequest();
  503. req.method = 'OPTIONS';
  504. res = fakeResponse();
  505. res.end = function () {
  506. // assert
  507. res.getHeader('Access-Control-Allow-Headers').should.equal('requestedHeader1,requestedHeader2');
  508. should.exist(res.getHeader('Vary'));
  509. res.getHeader('Vary').should.equal('Access-Control-Request-Headers');
  510. done();
  511. };
  512. // act
  513. cors(options)(req, res, null);
  514. });
  515. it('can specify exposed headers', function (done) {
  516. // arrange
  517. var req, res, options, next;
  518. options = {
  519. exposedHeaders: ['custom-header1', 'custom-header2']
  520. };
  521. req = fakeRequest();
  522. res = fakeResponse();
  523. next = function () {
  524. // assert
  525. res.getHeader('Access-Control-Expose-Headers').should.equal('custom-header1,custom-header2');
  526. done();
  527. };
  528. // act
  529. cors(options)(req, res, next);
  530. });
  531. it('specifying an empty list or string of exposed headers will result in no response header for exposed headers', function (done) {
  532. // arrange
  533. var req, res, next, options;
  534. options = {
  535. exposedHeaders: []
  536. };
  537. req = fakeRequest();
  538. res = fakeResponse();
  539. next = function () {
  540. // assert
  541. should.not.exist(res.getHeader('Access-Control-Expose-Headers'));
  542. done();
  543. };
  544. // act
  545. cors(options)(req, res, next);
  546. });
  547. it('includes credentials if explicitly enabled', function (done) {
  548. // arrange
  549. var req, res, options;
  550. options = {
  551. credentials: true
  552. };
  553. req = fakeRequest();
  554. req.method = 'OPTIONS';
  555. res = fakeResponse();
  556. res.end = function () {
  557. // assert
  558. res.getHeader('Access-Control-Allow-Credentials').should.equal('true');
  559. done();
  560. };
  561. // act
  562. cors(options)(req, res, null);
  563. });
  564. it('does not includes credentials unless explicitly enabled', function (done) {
  565. // arrange
  566. var req, res, next, options;
  567. options = {
  568. };
  569. req = fakeRequest();
  570. res = fakeResponse();
  571. next = function () {
  572. // assert
  573. should.not.exist(res.getHeader('Access-Control-Allow-Credentials'));
  574. done();
  575. };
  576. // act
  577. cors(options)(req, res, next);
  578. });
  579. it('includes maxAge when specified', function (done) {
  580. // arrange
  581. var req, res, options;
  582. options = {
  583. maxAge: 456
  584. };
  585. req = fakeRequest();
  586. req.method = 'OPTIONS';
  587. res = fakeResponse();
  588. res.end = function () {
  589. // assert
  590. res.getHeader('Access-Control-Max-Age').should.equal('456');
  591. done();
  592. };
  593. // act
  594. cors(options)(req, res, null);
  595. });
  596. it('does not includes maxAge unless specified', function (done) {
  597. // arrange
  598. var req, res, next, options;
  599. options = {
  600. };
  601. req = fakeRequest();
  602. res = fakeResponse();
  603. next = function () {
  604. // assert
  605. should.not.exist(res.getHeader('Access-Control-Max-Age'));
  606. done();
  607. };
  608. // act
  609. cors(options)(req, res, next);
  610. });
  611. });
  612. describe('passing a function to build options', function () {
  613. it('handles options specified via callback', function (done) {
  614. // arrange
  615. var req, res, next, delegate;
  616. delegate = function (req2, cb) {
  617. cb(null, {
  618. origin: 'delegate.com'
  619. });
  620. };
  621. req = fakeRequest();
  622. res = fakeResponse();
  623. next = function () {
  624. // assert
  625. res.getHeader('Access-Control-Allow-Origin').should.equal('delegate.com');
  626. done();
  627. };
  628. // act
  629. cors(delegate)(req, res, next);
  630. });
  631. it('handles options specified via callback for preflight', function (done) {
  632. // arrange
  633. var req, res, delegate;
  634. delegate = function (req2, cb) {
  635. cb(null, {
  636. origin: 'delegate.com',
  637. maxAge: 1000
  638. });
  639. };
  640. req = fakeRequest();
  641. req.method = 'OPTIONS';
  642. res = fakeResponse();
  643. res.end = function () {
  644. // assert
  645. res.getHeader('Access-Control-Allow-Origin').should.equal('delegate.com');
  646. res.getHeader('Access-Control-Max-Age').should.equal('1000');
  647. done();
  648. };
  649. // act
  650. cors(delegate)(req, res, null);
  651. });
  652. it('handles error specified via callback', function (done) {
  653. // arrange
  654. var req, res, next, delegate;
  655. delegate = function (req2, cb) {
  656. cb('some error');
  657. };
  658. req = fakeRequest();
  659. res = fakeResponse();
  660. next = function (err) {
  661. // assert
  662. err.should.equal('some error');
  663. done();
  664. };
  665. // act
  666. cors(delegate)(req, res, next);
  667. });
  668. });
  669. });
  670. }());