diff --git a/src/middlewares/openapi.security.ts b/src/middlewares/openapi.security.ts index f6a15af8..1201c063 100644 --- a/src/middlewares/openapi.security.ts +++ b/src/middlewares/openapi.security.ts @@ -120,7 +120,7 @@ class SecuritySchemes { ? defaultSecurityHandler : null; - const promises = this.securities.map(async s => { + const promises = this.securities.map(async (s) => { try { if (Util.isEmptyObject(s)) { // anonumous security @@ -242,8 +242,11 @@ class AuthValidator { if (!req.query[scheme.name]) { throw Error(`query parameter '${scheme.name}' required`); } + } else if (scheme.in === 'cookie') { + if (!req.cookies[scheme.name]) { + throw Error(`cookie '${scheme.name}' required`); + } } - // TODO scheme in cookie this.dissallowScopes(); } diff --git a/test/resources/security.yaml b/test/resources/security.yaml index 1482e63e..858771fa 100644 --- a/test/resources/security.yaml +++ b/test/resources/security.yaml @@ -1,4 +1,4 @@ -openapi: '3.0.2' +openapi: "3.0.2" info: version: 1.0.0 title: requestBodies $ref @@ -8,10 +8,10 @@ servers: - url: /v1/ paths: - /no_security: + /no_security: get: responses: - '200': + "200": description: OK /api_key: @@ -19,9 +19,9 @@ paths: security: - ApiKeyAuth: [] responses: - '200': + "200": description: OK - '401': + "401": description: unauthorized /api_key_or_anonymous: @@ -31,9 +31,9 @@ paths: - {} - ApiKeyAuth: [] responses: - '200': + "200": description: OK - '401': + "401": description: unauthorized # This api key with scopes should fail validation and return 500 @@ -41,11 +41,11 @@ paths: /api_key_with_scopes: get: security: - - ApiKeyAuth: ['read', 'write'] + - ApiKeyAuth: ["read", "write"] responses: - '200': + "200": description: OK - '401': + "401": description: unauthorized /bearer: @@ -53,9 +53,9 @@ paths: security: - BearerAuth: [] responses: - '200': + "200": description: OK - '401': + "401": description: unauthorized /basic: @@ -63,9 +63,19 @@ paths: security: - BasicAuth: [] responses: - '200': + "200": description: OK - '401': + "401": + description: unauthorized + + /cookie_auth: + get: + security: + - CookieAuth: [] + responses: + "200": + description: OK + "401": description: unauthorized /oauth2: @@ -75,9 +85,9 @@ paths: - scope1 - scope2 responses: - '200': + "200": description: OK - '401': + "401": description: unauthorized /openid: @@ -87,9 +97,9 @@ paths: - scope1 - scope2 responses: - '200': + "200": description: OK - '401': + "401": description: unauthorized components: @@ -104,6 +114,10 @@ components: type: apiKey in: header name: X-API-Key + CookieAuth: + type: apiKey + in: cookie + name: JSESSIONID # cookie name OpenID: type: openIdConnect openIdConnectUrl: https://example.com/.well-known/openid-configuration diff --git a/test/security.defaults.spec.ts b/test/security.defaults.spec.ts index d31b44fb..adf9e273 100644 --- a/test/security.defaults.spec.ts +++ b/test/security.defaults.spec.ts @@ -18,6 +18,7 @@ describe('security.defaults', () => { express .Router() .get(`/api_key`, (req, res) => res.json({ logged_in: true })) + .get(`/cookie_auth`, (req, res) => res.json({ logged_in: true })) .get(`/bearer`, (req, res) => res.json({ logged_in: true })) .get(`/basic`, (req, res) => res.json({ logged_in: true })) .get('/no_security', (req, res) => res.json({ logged_in: true })), @@ -29,15 +30,14 @@ describe('security.defaults', () => { }); it('should return 200 if no security', async () => - request(app) - .get(`${basePath}/no_security`) - .expect(200)); + request(app).get(`${basePath}/no_security`).expect(200)); it('should skip validation, even if auth header is missing for basic auth', async () => { return request(app) .get(`${basePath}/basic`) .expect(401) - .then(r => { + .then((r) => { + console.log(r.body); expect(r.body) .to.have.property('message') .that.equals('Authorization header required'); @@ -47,10 +47,22 @@ describe('security.defaults', () => { it('should skip security validation, even if auth header is missing for bearer auth', async () => { return request(app) .get(`${basePath}/bearer`) - .expect(401).then(r => { + .expect(401) + .then((r) => { expect(r.body) .to.have.property('message') .that.equals('Authorization header required'); - }) + }); + }); + + it('should return 401 if cookie auth property is missing', async () => { + return request(app) + .get(`${basePath}/cookie_auth`) + .expect(401) + .then((r) => { + expect(r.body) + .to.have.property('message') + .that.equals('cookie \'JSESSIONID\' required'); + }); }); }); diff --git a/test/security.handlers.spec.ts b/test/security.handlers.spec.ts index 96fa2e91..4a57a616 100644 --- a/test/security.handlers.spec.ts +++ b/test/security.handlers.spec.ts @@ -36,6 +36,7 @@ describe('security.handlers', () => { .get(`/api_key`, (req, res) => res.json({ logged_in: true })) .get(`/bearer`, (req, res) => res.json({ logged_in: true })) .get(`/basic`, (req, res) => res.json({ logged_in: true })) + .get(`/cookie_auth`, (req, res) => res.json({ logged_in: true })) .get(`/oauth2`, (req, res) => res.json({ logged_in: true })) .get(`/openid`, (req, res) => res.json({ logged_in: true })) .get(`/api_key_or_anonymous`, (req, res) => @@ -50,16 +51,14 @@ describe('security.handlers', () => { }); it('should return 200 if no security', async () => - request(app) - .get(`${basePath}/no_security`) - .expect(200)); + request(app).get(`${basePath}/no_security`).expect(200)); it('should return 401 if apikey handler throws exception', async () => request(app) .get(`${basePath}/api_key`) .set('X-API-Key', 'test') .expect(401) - .then(r => { + .then((r) => { const body = r.body; expect(body.errors).to.be.an('array'); expect(body.errors).to.have.length(1); @@ -70,17 +69,15 @@ describe('security.handlers', () => { it('should return 401 if apikey handler returns false', async () => { const validateSecurity = eovConf.validateSecurity; - validateSecurity.handlers.ApiKeyAuth = function(req, scopes, schema) { - expect(scopes) - .to.be.an('array') - .with.length(0); + validateSecurity.handlers.ApiKeyAuth = function (req, scopes, schema) { + expect(scopes).to.be.an('array').with.length(0); return false; }; return request(app) .get(`${basePath}/api_key`) .set('X-API-Key', 'test') .expect(401) - .then(r => { + .then((r) => { const body = r.body; expect(body.errors).to.be.an('array'); expect(body.errors).to.have.length(1); @@ -90,18 +87,35 @@ describe('security.handlers', () => { it('should return 401 if apikey handler returns Promise with false', async () => { const validateSecurity = eovConf.validateSecurity; - validateSecurity.handlers.ApiKeyAuth = function(req, scopes, schema) { - expect(scopes) - .to.be.an('array') - .with.length(0); + validateSecurity.handlers.ApiKeyAuth = function (req, scopes, schema) { + expect(scopes).to.be.an('array').with.length(0); return Promise.resolve(false); }; return request(app) .get(`${basePath}/api_key`) .set('X-API-Key', 'test') .expect(401) - .then(r => { + .then((r) => { + const body = r.body; + expect(body.errors).to.be.an('array'); + expect(body.errors).to.have.length(1); + expect(body.errors[0].message).to.equals('unauthorized'); + }); + }); + + it('should return 401 if cookie auth handler returns Promise with false', async () => { + const validateSecurity = eovConf.validateSecurity; + validateSecurity.handlers.CookieAuth = function (req, scopes, schema) { + expect(scopes).to.be.an('array').with.length(0); + return Promise.resolve(false); + }; + return request(app) + .get(`${basePath}/cookie_auth`) + .set('Cookie', ['JSESSIONID=12345667', 'myApp-other=blah']) + .expect(401) + .then((r) => { const body = r.body; + console.log(body); expect(body.errors).to.be.an('array'); expect(body.errors).to.have.length(1); expect(body.errors[0].message).to.equals('unauthorized'); @@ -111,16 +125,14 @@ describe('security.handlers', () => { it('should return 401 if apikey handler returns Promise reject with custom message', async () => { const validateSecurity = eovConf.validateSecurity; validateSecurity.handlers.ApiKeyAuth = (req, scopes, schema) => { - expect(scopes) - .to.be.an('array') - .with.length(0); + expect(scopes).to.be.an('array').with.length(0); return Promise.reject(new Error('rejected promise')); }; return request(app) .get(`${basePath}/api_key`) .set('X-API-Key', 'test') .expect(401) - .then(r => { + .then((r) => { const body = r.body; expect(body.errors).to.be.an('array'); expect(body.errors).to.have.length(1); @@ -134,7 +146,7 @@ describe('security.handlers', () => { return request(app) .get(`${basePath}/api_key`) .expect(401) - .then(r => { + .then((r) => { const body = r.body; expect(body.errors).to.be.an('array'); expect(body.errors).to.have.length(1); @@ -144,7 +156,7 @@ describe('security.handlers', () => { it('should return 200 if apikey header exists and handler returns true', async () => { const validateSecurity = eovConf.validateSecurity; - validateSecurity.handlers.ApiKeyAuth = function( + validateSecurity.handlers.ApiKeyAuth = function ( req, scopes, schema: OpenAPIV3.ApiKeySecurityScheme, @@ -152,9 +164,7 @@ describe('security.handlers', () => { expect(schema.type).to.equal('apiKey'); expect(schema.in).to.equal('header'); expect(schema.name).to.equal('X-API-Key'); - expect(scopes) - .to.be.an('array') - .with.length(0); + expect(scopes).to.be.an('array').with.length(0); return true; }; return request(app) @@ -173,9 +183,7 @@ describe('security.handlers', () => { expect(schema.type).to.equal('apiKey'); expect(schema.in).to.equal('header'); expect(schema.name).to.equal('X-API-Key'); - expect(scopes) - .to.be.an('array') - .with.length(0); + expect(scopes).to.be.an('array').with.length(0); return true; }; return request(app) @@ -190,7 +198,7 @@ describe('security.handlers', () => { return request(app) .get(`${basePath}/basic`) .expect(401) - .then(r => { + .then((r) => { const body = r.body; expect(body.errors).to.be.an('array'); expect(body.errors).to.have.length(1); @@ -205,7 +213,7 @@ describe('security.handlers', () => { .get(`${basePath}/basic`) .set('Authorization', 'XXXX') .expect(401) - .then(r => { + .then((r) => { const body = r.body; expect(body.errors).to.be.an('array'); expect(body.errors).to.have.length(1); @@ -221,7 +229,7 @@ describe('security.handlers', () => { return request(app) .get(`${basePath}/bearer`) .expect(401) - .then(r => { + .then((r) => { const body = r.body; expect(body.errors).to.be.an('array'); expect(body.errors).to.have.length(1); @@ -236,7 +244,7 @@ describe('security.handlers', () => { .get(`${basePath}/bearer`) .set('Authorization', 'XXXX') .expect(401) - .then(r => { + .then((r) => { const body = r.body; expect(body.errors).to.be.an('array'); expect(body.errors).to.have.length(1); @@ -255,9 +263,7 @@ describe('security.handlers', () => { ) => { expect(schema.type).to.equal('http'); expect(schema.scheme).to.equal('bearer'); - expect(scopes) - .to.be.an('array') - .with.length(0); + expect(scopes).to.be.an('array').with.length(0); return true; }; return request(app) @@ -268,43 +274,37 @@ describe('security.handlers', () => { it('should return 200 if oauth2 auth succeeds', async () => { const validateSecurity = eovConf.validateSecurity; - validateSecurity.handlers.OAuth2 = function( + validateSecurity.handlers.OAuth2 = function ( req, scopes, schema: OpenAPIV3.OAuth2SecurityScheme, ) { expect(schema.type).to.equal('oauth2'); expect(schema).to.have.property('flows'); - expect(scopes) - .to.be.an('array') - .with.length(2); + expect(scopes).to.be.an('array').with.length(2); return true; }; - return request(app) - .get(`${basePath}/oauth2`) - .expect(200); + return request(app).get(`${basePath}/oauth2`).expect(200); }); it('should return 403 if oauth2 handler throws 403', async () => { const validateSecurity = eovConf.validateSecurity; - validateSecurity.handlers.OAuth2 = function( + validateSecurity.handlers.OAuth2 = function ( req, scopes: string[], schema: OpenAPIV3.OAuth2SecurityScheme, ) { expect(schema.type).to.equal('oauth2'); expect(schema).to.have.property('flows'); - expect(scopes) - .to.be.an('array') - .with.length(2); + expect(scopes).to.be.an('array').with.length(2); throw { status: 403, message: 'forbidden' }; }; return request(app) .get(`${basePath}/oauth2`) .expect(403) - .then(r => { + .then((r) => { const body = r.body; expect(r.body.message).to.equal('forbidden'); }); @@ -319,15 +319,11 @@ describe('security.handlers', () => { ) => { expect(schema.type).to.equal('openIdConnect'); expect(schema).to.have.property('openIdConnectUrl'); - expect(scopes) - .to.be.an('array') - .with.length(2); + expect(scopes).to.be.an('array').with.length(2); return true; }; - return request(app) - .get(`${basePath}/openid`) - .expect(200); + return request(app).get(`${basePath}/openid`).expect(200); }); it('should return 500 if security handlers are defined, but not for all securities', async () => { @@ -340,16 +336,14 @@ describe('security.handlers', () => { ) => { expect(schema.type).to.equal('openIdConnect'); expect(schema).to.have.property('openIdConnectUrl'); - expect(scopes) - .to.be.an('array') - .with.length(2); + expect(scopes).to.be.an('array').with.length(2); return true; }; return request(app) .get(`${basePath}/openid`) .expect(500) - .then(r => { + .then((r) => { const body = r.body; const msg = "a security handler for 'OpenID' does not exist"; expect(body.message).to.equal(msg); @@ -363,7 +357,7 @@ describe('security.handlers', () => { .get(`${basePath}/api_key_with_scopes`) .set('X-Api-Key', 'XXX') .expect(500) - .then(r => { + .then((r) => { const body = r.body; expect(body.message).to.equal( "scopes array must be empty for security type 'http'", @@ -373,9 +367,7 @@ describe('security.handlers', () => { it('should return 200 if api_key or anonymous and no api key is supplied', async () => { const validateSecurity = eovConf.validateSecurity; validateSecurity.handlers.ApiKeyAuth = ((req, scopes, schema) => true); - return request(app) - .get(`${basePath}/api_key_or_anonymous`) - .expect(200); + return request(app).get(`${basePath}/api_key_or_anonymous`).expect(200); }); it('should return 200 if api_key or anonymous and api key is supplied', async () => {