diff --git a/examples/swagger/config/default.json b/examples/swagger/config/default.json index e8ab5d7..03ffa5b 100644 --- a/examples/swagger/config/default.json +++ b/examples/swagger/config/default.json @@ -2,8 +2,8 @@ { "express": { "port": "3000", - "middleware": ["cors", "body-parser"], - "middleware$": ["errors"] + "middleware": ["cors", "body-parser", "session", "addHmac", "bos-authentication", "custom-auth"], + "middleware$": [] }, "cors": { @@ -18,6 +18,10 @@ "maxWorkers": 1 }, + "session": { + "keys": ["sessionKey"] + }, + "swagger": { "refCompiler": { "petstore": { @@ -35,4 +39,5 @@ } } + } \ No newline at end of file diff --git a/examples/swagger/handlers/api-v1.js b/examples/swagger/handlers/api-v1.js index 6f51b3c..da0d8a8 100644 --- a/examples/swagger/handlers/api-v1.js +++ b/examples/swagger/handlers/api-v1.js @@ -1,3 +1,4 @@ + exports.init = function() { }; @@ -7,10 +8,11 @@ exports.getFunTimeById = function(req, res, next) { 'curiousPeople': [ { 'kind': 'OtherPerson', - 'curiousPersonReqField': 'hey!', + 'curiousPersonReqField': 'hey?', 'enthusiasticPersonReqField': 'hola!' } ] }); }; + diff --git a/examples/swagger/middleware/addHmac.js b/examples/swagger/middleware/addHmac.js new file mode 100644 index 0000000..823844d --- /dev/null +++ b/examples/swagger/middleware/addHmac.js @@ -0,0 +1,30 @@ +var sjcl = require('sjcl'), + urlParse = require('url-parse'); + +exports.init = function (app) { + app.use(function (req, res, next) { + if (req.path.indexOf('superfuntime') !== -1) { + var apiId = '285cd308-1564-4090-b3b4-ce5cfa697c4c'; + var apiKey = 'JfOJ15SI7EGjDLX1h8zPB19Zr88ONMPKbBQJozMI0Ag'; + var contentMd5 = req.headers['Content-MD5'] || ''; + var contentType = req.headers['Content-Type'] || ''; + var dateString = new Date().toString(); + + var urlPath; + if (urlParse) { + urlPath = urlParse(req.url).pathname; + } + var stringToSign = req.method.toUpperCase() + '\n' + + contentMd5 + '\n' + + contentType + '\n' + + dateString + '\n' + + urlPath; + + var key = sjcl.codec.utf8String.toBits(apiKey); + var out = (new sjcl.misc.hmac(key, sjcl.hash.sha256)).mac(stringToSign); + var hmac = sjcl.codec.base64.fromBits(out); + req.headers.Authorization = 'SFI ' + apiId + ':' + hmac + ':' + dateString; + } + next(); + }); +}; diff --git a/examples/swagger/middleware/custom-auth.js b/examples/swagger/middleware/custom-auth.js new file mode 100644 index 0000000..e4152db --- /dev/null +++ b/examples/swagger/middleware/custom-auth.js @@ -0,0 +1,43 @@ +var _ = require('lodash'); + +exports.init = function(app, logger) { + app.use(function (req, res, next) { + if (!req.bosAuthenticationData) { + return next(); + } + _.forEach(req.bosAuthenticationData, function (authData) { + switch (authData.type) { + + case 'basic': + if (!(authData.username && authData.password)) { + res.setHeader('WWW-Authenticate', 'Basic realm="' + authData.securityReq + '"'); + res.status(401).send(); + return false; + } + break; + case 'apiKey': + if (!authData.password) { + res.status(401).send(); + return false; + } + break; + case 'oauth2': + if (authData.securityDefn.flow === 'implicit') { + if (!(authData.password)) { + res.sendStatus(401); + return false; + } + } else { + if (!(authData.tokenData)) { + res.sendStatus(401); + return false; + } + } + break; + } + }); + if (!res.headersSent) { + next(); + } + }); +}; diff --git a/examples/swagger/services/hmacService.js b/examples/swagger/services/hmacService.js new file mode 100644 index 0000000..e9d8bea --- /dev/null +++ b/examples/swagger/services/hmacService.js @@ -0,0 +1,10 @@ + +exports.init = function () { + +}; + +exports.getApiUser = function (apiId, done) { + done(null, {name: 'joe', getApiKey: function () { + return 'JfOJ15SI7EGjDLX1h8zPB19Zr88ONMPKbBQJozMI0Ag'; + }}); +}; \ No newline at end of file diff --git a/examples/swagger/swagger/api-v1.yaml b/examples/swagger/swagger/api-v1.yaml index 60b69e2..6468a4b 100644 --- a/examples/swagger/swagger/api-v1.yaml +++ b/examples/swagger/swagger/api-v1.yaml @@ -8,6 +8,10 @@ produces: - application/json paths: $ref: 'public/paths.yaml' +securityDefinitions: + $ref: 'public/security-definitions.yaml' +x-bos-securityDefinitions: + $ref: 'public/x-bos-security-definitions.yaml' ### ref-compiler: BEGIN definitions: diff --git a/examples/swagger/swagger/public/paths/superfuntime-{id}.yaml b/examples/swagger/swagger/public/paths/superfuntime-{id}.yaml index 2f163f1..ffc0f9f 100644 --- a/examples/swagger/swagger/public/paths/superfuntime-{id}.yaml +++ b/examples/swagger/swagger/public/paths/superfuntime-{id}.yaml @@ -5,6 +5,17 @@ get: - Fun Times parameters: - $ref: '../parameters/PathId.yaml' + #security: + #- oauthImplicitEx: [] + #security: + #- oauthEx: [] + #security: + #- basicEx: [] + #- apiKeyEx: [] + #security: + #- apiKeyEx: [] + #x-bos-security: + #- hmac: [] responses: 200: schema: diff --git a/examples/swagger/swagger/public/security-definitions.yaml b/examples/swagger/swagger/public/security-definitions.yaml new file mode 100644 index 0000000..e1ada60 --- /dev/null +++ b/examples/swagger/swagger/public/security-definitions.yaml @@ -0,0 +1,17 @@ +basicEx: + type: basic +apiKeyEx: + type: apiKey + in: header + name: x-key +oauthEx: + type: oauth2 + flow: accessCode + authorizationUrl: http://localhost:3000/auth-code + tokenUrl: http://localhost:3000/access-token + scopes: {} +oauthImplicitEx: + type: oauth2 + flow: implicit + authorizationUrl: http://localhost:3000/access-token + scopes: {} \ No newline at end of file diff --git a/examples/swagger/swagger/public/x-bos-security-definitions.yaml b/examples/swagger/swagger/public/x-bos-security-definitions.yaml new file mode 100644 index 0000000..9f915ac --- /dev/null +++ b/examples/swagger/swagger/public/x-bos-security-definitions.yaml @@ -0,0 +1,11 @@ +hmac: + verify: + service: hmacService + method: + name: getApiUser + execute: false + args: [] + routeOptions: + session: false + module: passport-hmac-strategy + x-bos-middleware: bos-passport diff --git a/index.js b/index.js index 7d31303..006a67d 100644 --- a/index.js +++ b/index.js @@ -363,3 +363,5 @@ module.exports.testUtility = function () { return require('./testlib/util'); }; +module.exports.subRequire = require('./lib/subRequire'); + diff --git a/middleware/bos-authentication.js b/middleware/bos-authentication.js new file mode 100644 index 0000000..0a5a753 --- /dev/null +++ b/middleware/bos-authentication.js @@ -0,0 +1,208 @@ +var _ = require('lodash'); +var base64URL = require('base64url'); + +var log; +var loader; +var httpMethods = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch']; +//map of middleware ids to 'true' meaning that they have been initialized +var middlewareInitMap = {}; + +module.exports = { + init : init +}; + +function init(app, logger, serviceLoader, swagger) { + log = logger; + loader = serviceLoader; + _.forEach(swagger.getSimpleSpecs(), function (api, name) { + var basePath = api.basePath || ''; + /* apply security requirements to each route path*/ + _.forEach(_.keys(api.paths), function (path) { + var pathObj = api.paths[path]; + var routePath = basePath + _convertPathToExpress(path); + + //loop for http method keys, like get and post + _.forEach(_.keys(pathObj), function (method) { + if (_.contains(httpMethods, method)) { + var operation = pathObj[method]; + _.forEach(operation['security'], function (securityReq) { + _.forOwn(securityReq, function (scopes, securityDefn) { + _applySecurityRequirement(app, method, routePath, securityDefn, + api.securityDefinitions[securityDefn], + scopes); + }); + }); + _.forEach(operation['x-bos-security'], function (securityReq) { + _.forOwn(securityReq, function (scopes, securityDefn) { + _applyCustomSecurityRequirement(app, method, routePath, securityDefn, + api['x-bos-securityDefinitions'][securityDefn], + scopes); + }); + }); + } + }); + }); + }); +} + +function _applyCustomSecurityRequirement(app, method, route, securityReq, + securityDefn, requiredScopes) { + //load security def middleware + if (securityDefn['x-bos-middleware']) { + var customAuthMiddleware = loader.getConsumer('middleware', securityDefn['x-bos-middleware']); + if (!customAuthMiddleware) { + loader.loadConsumerModules('middleware', + [securityDefn['x-bos-middleware']]); + customAuthMiddleware = loader.getConsumer('middleware', securityDefn['x-bos-middleware']); + } + if (!middlewareInitMap[securityDefn['x-bos-middleware']]) { + loader.initConsumers('middleware', [securityDefn['x-bos-middleware']], function (err) { + if (!err) { + wireAuthenticateToRoute(app, method, route, securityReq, + securityDefn, requiredScopes, customAuthMiddleware); + } + else { + log.warn('Unable to initialize custom middleware %s for security defn %s', + securityDefn['x-bos-middleware'], securityReq); + } + }); + } else { + wireAuthenticateToRoute(app, method, route, securityReq, + securityDefn, requiredScopes, customAuthMiddleware); + } + } else { + log.info('No custom middleware defined for security defn %s. ' + + 'Attempting to use built in middleware...', securityReq); + _applySecurityRequirement(app, method, route, securityReq, + securityDefn, requiredScopes); + } +} + +function wireAuthenticateToRoute(app, method, route, securityReq, securityDefn, requiredScopes, customAuthMiddleware) { + if (customAuthMiddleware.authenticate) { + app[method].call(app, route, customAuthMiddleware.authenticate(securityReq, securityDefn, requiredScopes)); + } + else { + log.warn('custom auth middleware %s missing authenticate method'); + } +} + +function _applySecurityRequirement(app, method, route, securityReq, + securityDefn, requiredScopes) { + //allow use of custom middleware even if a custom security definition was not used + if (securityDefn['x-bos-middleware']) { + _applyCustomSecurityRequirement(app, method, route, securityReq, + securityDefn, requiredScopes); + } else { + switch (securityDefn.type) { + case 'basic': + app[method].call(app, route, basicAuthentication(securityReq)); + break; + case 'apiKey': //may also need a user provided 'verify' function here + app[method].call(app, route, apiKeyAuthentication(securityReq, securityDefn)); + break; + case 'oauth2': + /*if (!oAuthService) { + oAuthService = loader.get('oauth2'); + } + app[method].call(app, route, oauth2(securityReq, securityDefn, requiredScopes));*/ + log.warn('No out of the box oauth2 implementation exists in BOS. ' + + 'You must define your own and reference it in the ' + + '"x-bos-middleware" property of the security definition %s', securityReq); + break; + default: + return log.warn('unrecognized security type %s for security definition %s' + + 'You can provide a custom security definition in "x-bos-securityDefinitions" of your base spec', + securityDefn.type, securityReq); + } + } +} + +function basicAuthentication(securityReq) { + return function (req, res, next) { + if (!req.bosAuthenticationData) { + req.bosAuthenticationData = []; + } + var authenticationData = {type: 'basic', securityReq: securityReq}; + req.bosAuthenticationData.push(authenticationData); + var authHeader = req.get('authorization') ? req.get('authorization') : ''; + if (authHeader !== '') { //header should be of the form "Basic " + user:password as a base64 encoded string + var credentialsBase64 = authHeader.split('Basic ')[1]; + var credentialsDecoded = base64URL.decode(credentialsBase64); + var credentials = credentialsDecoded.split(':'); + authenticationData.username = credentials[0]; + authenticationData.password = credentials[1]; + } + next(); + }; +} + +function apiKeyAuthentication(securityReq, securityDefn) { + return function (req, res, next) { + if (!req.bosAuthenticationData) { + req.bosAuthenticationData = []; + } + var authenticationData = {type: securityDefn.type, securityReq: securityReq, securityDefn: securityDefn}; + req.bosAuthenticationData.push(authenticationData); + if (securityDefn.in === 'query') { + authenticationData.password = req.query[securityDefn.name]; + } + else if (securityDefn.in === 'header') { + authenticationData.password = req.get(securityDefn.name); + } + else { + log.warn('unknown location %s for apiKey. ' + + 'looks like open api specs may have changed on us', securityDefn.in); + } + next(); + }; +} + +/*function oauth2(securityReq, securityDefn, scopes) { + return function (req, res, next) { + if (!req.bosAuthenticationData) { + req.bosAuthenticationData = []; + } + if (securityDefn.flow === 'accessCode') { + if (!req.session) { + log.error('oauth2 accessCode flow requires that session be enabled'); + return res.sendStatus(401); + } + else if (req.session.bosAuthenticationData) { //already authenticated + req.bosAuthenticationData.push(req.session.bosAuthenticationData); + return next(); + } else { + req.session.bosAuthenticationData = { + type: securityDefn.type, + securityReq: securityReq, + securityDefn: securityDefn + }; + req.bosAuthenticationData.push(req.session.bosAuthenticationData); + } + } else if (req.get('authorization')) { //implicit + var authenticationData = {type: securityDefn.type, securityReq: securityReq, securityDefn: securityDefn}; + req.bosAuthenticationData.push(authenticationData); + authenticationData.password = + req.get('authorization').split('Bearer ')[1]; //we assume bearer token type which is the most common + if (authenticationData.password) { //already authenticated + //user defined code will be responsible for validating this token + //which they absolutely should do because it did not come directly from oauth provider + return next(); + } + } + var oAuthInstance = oAuthService.getOAuthInstance(securityReq); + if (!oAuthInstance) { + oAuthInstance = new oAuthService.OAuth2(securityDefn.authorizationUrl, + securityDefn.flow, securityDefn.tokenUrl); + oAuthService.addOAuthInstance(securityReq, oAuthInstance); + } + oAuthInstance.startOAuth(securityReq, scopes, req, res); + }; +}*/ + +//swagger paths use {blah} while express uses :blah +function _convertPathToExpress(swaggerPath) { + var reg = /\{([^\}]+)\}/g; //match all {...} + swaggerPath = swaggerPath.replace(reg, ':$1'); + return swaggerPath; +} \ No newline at end of file diff --git a/test/integration/fixtures/serverAuth/config/default.json b/test/integration/fixtures/serverAuth/config/default.json new file mode 100644 index 0000000..26b6d90 --- /dev/null +++ b/test/integration/fixtures/serverAuth/config/default.json @@ -0,0 +1,31 @@ +//Default config for my app +{ + "express": { + "port": "5000", + "middleware": ["cors", "body-parser", "session", "bos-authentication", "custom-auth"], + "middleware$": [] + }, + "cors": { + "origin": "*" + }, + + "body-parser": { + "json": {} + }, + + "cluster": { + "maxWorkers": 1 + }, + + "session": { + "keys": ["sessionKey"] + }, + + "oauth": { + "clientId": "826839015121-rs7t7ick1ib0uvui9iihbb82gcqg64r9.apps.googleusercontent.com", + "clientSecret": "secret", + "redirectURI": "/oauth-redirect" + } + + +} \ No newline at end of file diff --git a/test/integration/fixtures/serverAuth/handlers/api-v1.js b/test/integration/fixtures/serverAuth/handlers/api-v1.js new file mode 100644 index 0000000..e253f80 --- /dev/null +++ b/test/integration/fixtures/serverAuth/handlers/api-v1.js @@ -0,0 +1,26 @@ + +exports.init = function() { + +}; + +exports.getFunTimeById = function(req, res, next) { + res.status(200).json({ + 'curiousPeople': [ + { + 'kind': 'OtherPerson', + 'curiousPersonReqField': 'hey?', + 'enthusiasticPersonReqField': 'hola!' + } + ] + }); +}; + +exports.addFunTime = function(req, res, next) { + res.status(204).send(); +}; + +exports.deleteFunTimeById = function(req, res, next) { + res.status(204).send(); +}; + + diff --git a/test/integration/fixtures/serverAuth/middleware/custom-auth.js b/test/integration/fixtures/serverAuth/middleware/custom-auth.js new file mode 100644 index 0000000..e4152db --- /dev/null +++ b/test/integration/fixtures/serverAuth/middleware/custom-auth.js @@ -0,0 +1,43 @@ +var _ = require('lodash'); + +exports.init = function(app, logger) { + app.use(function (req, res, next) { + if (!req.bosAuthenticationData) { + return next(); + } + _.forEach(req.bosAuthenticationData, function (authData) { + switch (authData.type) { + + case 'basic': + if (!(authData.username && authData.password)) { + res.setHeader('WWW-Authenticate', 'Basic realm="' + authData.securityReq + '"'); + res.status(401).send(); + return false; + } + break; + case 'apiKey': + if (!authData.password) { + res.status(401).send(); + return false; + } + break; + case 'oauth2': + if (authData.securityDefn.flow === 'implicit') { + if (!(authData.password)) { + res.sendStatus(401); + return false; + } + } else { + if (!(authData.tokenData)) { + res.sendStatus(401); + return false; + } + } + break; + } + }); + if (!res.headersSent) { + next(); + } + }); +}; diff --git a/test/integration/fixtures/serverAuth/swagger/api-v1.yaml b/test/integration/fixtures/serverAuth/swagger/api-v1.yaml new file mode 100644 index 0000000..de1008c --- /dev/null +++ b/test/integration/fixtures/serverAuth/swagger/api-v1.yaml @@ -0,0 +1,28 @@ +swagger: '2.0' +info: + version: 2.0.0-rc.1 + title: Test API + +basePath: /api/v1 +produces: + - application/json +paths: + $ref: 'public/paths.yaml' +securityDefinitions: + $ref: 'public/security-definitions.yaml' +x-bos-securityDefinitions: + $ref: 'public/x-bos-security-definitions.yaml' +definitions: + Contact: + $ref: 'public\definitions\Contact.yaml' + CuriousPerson: + $ref: 'public\definitions\CuriousPerson.yaml' + EnthusiasticPerson: + $ref: 'public\definitions\EnthusiasticPerson.yaml' + OtherPerson: + $ref: 'public\definitions\OtherPerson.yaml' + Person: + $ref: 'public\definitions\Person.yaml' + SuperFunTime: + $ref: 'public\definitions\SuperFunTime.yaml' + diff --git a/test/integration/fixtures/serverAuth/swagger/public/definitions/Contact.yaml b/test/integration/fixtures/serverAuth/swagger/public/definitions/Contact.yaml new file mode 100644 index 0000000..6c64e61 --- /dev/null +++ b/test/integration/fixtures/serverAuth/swagger/public/definitions/Contact.yaml @@ -0,0 +1,13 @@ +properties: + firstName: + type: string + lastName: + type: string + email: + type: string + format: email + gender: + type: string + enum: + - Female + - Male \ No newline at end of file diff --git a/test/integration/fixtures/serverAuth/swagger/public/definitions/CuriousPerson.yaml b/test/integration/fixtures/serverAuth/swagger/public/definitions/CuriousPerson.yaml new file mode 100644 index 0000000..7c30f36 --- /dev/null +++ b/test/integration/fixtures/serverAuth/swagger/public/definitions/CuriousPerson.yaml @@ -0,0 +1,13 @@ +allOf: + - $ref: 'EnthusiasticPerson.yaml' + - type: object + required: + - kind + - curiousPersonReqField + properties: + kind: + type: string + enum: + - CuriousPerson + curiousPersonReqField: + type: string diff --git a/test/integration/fixtures/serverAuth/swagger/public/definitions/EnthusiasticPerson.yaml b/test/integration/fixtures/serverAuth/swagger/public/definitions/EnthusiasticPerson.yaml new file mode 100644 index 0000000..01351c5 --- /dev/null +++ b/test/integration/fixtures/serverAuth/swagger/public/definitions/EnthusiasticPerson.yaml @@ -0,0 +1,14 @@ +allOf: + - $ref: 'Person.yaml' + - type: object + required: + - kind + - enthusiasticPersonReqField + properties: + kind: + type: string + enum: + - EnthusiasticPerson + enthusiasticPersonReqField: + type: string + diff --git a/test/integration/fixtures/serverAuth/swagger/public/definitions/OtherPerson.yaml b/test/integration/fixtures/serverAuth/swagger/public/definitions/OtherPerson.yaml new file mode 100644 index 0000000..e7d44d6 --- /dev/null +++ b/test/integration/fixtures/serverAuth/swagger/public/definitions/OtherPerson.yaml @@ -0,0 +1,14 @@ +allOf: + - $ref: 'CuriousPerson.yaml' + - type: object + required: + - kind + - notes + properties: + kind: + type: string + enum: + - OtherPerson + notes: + type: string + diff --git a/test/integration/fixtures/serverAuth/swagger/public/definitions/Person.yaml b/test/integration/fixtures/serverAuth/swagger/public/definitions/Person.yaml new file mode 100644 index 0000000..d6add6a --- /dev/null +++ b/test/integration/fixtures/serverAuth/swagger/public/definitions/Person.yaml @@ -0,0 +1,10 @@ +allOf: + - $ref: 'Contact.yaml' + - type: object + discriminator: kind + required: + - kind + properties: + kind: + type: string + diff --git a/test/integration/fixtures/serverAuth/swagger/public/definitions/SuperFunTime.yaml b/test/integration/fixtures/serverAuth/swagger/public/definitions/SuperFunTime.yaml new file mode 100644 index 0000000..6f44781 --- /dev/null +++ b/test/integration/fixtures/serverAuth/swagger/public/definitions/SuperFunTime.yaml @@ -0,0 +1,8 @@ +type: object +properties: + curiousPeople: + type: array + items: + $ref: 'CuriousPerson.yaml' + + diff --git a/test/integration/fixtures/serverAuth/swagger/public/parameters/PathId.yaml b/test/integration/fixtures/serverAuth/swagger/public/parameters/PathId.yaml new file mode 100644 index 0000000..73c5b26 --- /dev/null +++ b/test/integration/fixtures/serverAuth/swagger/public/parameters/PathId.yaml @@ -0,0 +1,6 @@ +name: id +in: path +required: true +type: string +description: | + The ID of the resource to which the call applies. diff --git a/test/integration/fixtures/serverAuth/swagger/public/paths.yaml b/test/integration/fixtures/serverAuth/swagger/public/paths.yaml new file mode 100644 index 0000000..ff6ef4f --- /dev/null +++ b/test/integration/fixtures/serverAuth/swagger/public/paths.yaml @@ -0,0 +1,3 @@ +/superfuntime/{id}: + $ref: 'paths/superfuntime-{id}.yaml' + diff --git a/test/integration/fixtures/serverAuth/swagger/public/paths/superfuntime-{id}.yaml b/test/integration/fixtures/serverAuth/swagger/public/paths/superfuntime-{id}.yaml new file mode 100644 index 0000000..8f20aec --- /dev/null +++ b/test/integration/fixtures/serverAuth/swagger/public/paths/superfuntime-{id}.yaml @@ -0,0 +1,63 @@ +get: + summary: Get super fun time + operationId: getFunTimeById + tags: + - Fun Times + parameters: + - $ref: '../parameters/PathId.yaml' + #security: + #- oauthImplicitEx: [] + #security: + #- oauthEx: [] + security: + - basicEx: [] + #security: + #- apiKeyEx: [] + #x-bos-security: + #- hmac: [] + responses: + 200: + schema: + $ref: '../definitions/SuperFunTime.yaml' + description: I want a super fun time +post: + summary: Add fun time + operationId: addFunTime + tags: + - Fun Times + parameters: + - $ref: '../parameters/PathId.yaml' + #security: + #- oauthImplicitEx: [] + #security: + #- oauthEx: [] + #security: + #- basicEx: [] + security: + - apiKeyEx: [] + #x-bos-security: + #- hmac: [] + responses: + default: + description: you did it! +delete: + summary: delete super fun time :( + operationId: deleteFunTimeById + tags: + - Fun Times + parameters: + - $ref: '../parameters/PathId.yaml' + #security: + #- oauthImplicitEx: [] + #security: + #- oauthEx: [] + security: + - basicEx: [] + - apiKeyEx: [] + #security: + #- apiKeyEx: [] + #x-bos-security: + #- hmac: [] + responses: + default: + description: you did it! diff --git a/test/integration/fixtures/serverAuth/swagger/public/security-definitions.yaml b/test/integration/fixtures/serverAuth/swagger/public/security-definitions.yaml new file mode 100644 index 0000000..e1ada60 --- /dev/null +++ b/test/integration/fixtures/serverAuth/swagger/public/security-definitions.yaml @@ -0,0 +1,17 @@ +basicEx: + type: basic +apiKeyEx: + type: apiKey + in: header + name: x-key +oauthEx: + type: oauth2 + flow: accessCode + authorizationUrl: http://localhost:3000/auth-code + tokenUrl: http://localhost:3000/access-token + scopes: {} +oauthImplicitEx: + type: oauth2 + flow: implicit + authorizationUrl: http://localhost:3000/access-token + scopes: {} \ No newline at end of file diff --git a/test/integration/fixtures/serverAuth/swagger/public/x-bos-security-definitions.yaml b/test/integration/fixtures/serverAuth/swagger/public/x-bos-security-definitions.yaml new file mode 100644 index 0000000..9f915ac --- /dev/null +++ b/test/integration/fixtures/serverAuth/swagger/public/x-bos-security-definitions.yaml @@ -0,0 +1,11 @@ +hmac: + verify: + service: hmacService + method: + name: getApiUser + execute: false + args: [] + routeOptions: + session: false + module: passport-hmac-strategy + x-bos-middleware: bos-passport diff --git a/test/integration/testAuth.js b/test/integration/testAuth.js new file mode 100644 index 0000000..e2d604c --- /dev/null +++ b/test/integration/testAuth.js @@ -0,0 +1,144 @@ +var request = require('request'), + assert = require('assert'), + util = require('./launchUtil'); + +describe('test basic auth', function () { + this.timeout(5000); + before(function (done) { + util.launch('serverAuth', done); + }); + + after(function (done) { + util.finish(done); + }); + + it('should fail without credentials', function (done) { + request('http://localhost:' + (process.env.PORT || 5000) + '/api/v1/superfuntime/2', + function (err, resp, body) { + assert.equal(resp.statusCode, 401); + assert.ok(resp.headers['www-authenticate'].indexOf('Basic') >= 0); + done(); + }); + }); + + it('should succeed with credentials', function (done) { + request.get('http://localhost:' + (process.env.PORT || 5000) + '/api/v1/superfuntime/2', + { + auth: { + user: 'username', pass: 'password', sendImmediately: false + } + }, + function (err, resp, body) { + assert.equal(resp.statusCode, 200); + done(); + }); + }); +}); + +describe('test apiKey auth', function () { + this.timeout(5000); + before(function (done) { + util.launch('serverAuth', done); + }); + + after(function (done) { + util.finish(done); + }); + + it('should fail without credentials', function (done) { + request.post('http://localhost:' + (process.env.PORT || 5000) + '/api/v1/superfuntime/2', + { + body: { + 'kind': 'OtherPerson', + 'curiousPersonReqField': 'hey?', + 'enthusiasticPersonReqField': 'hola!' + }, + json: true + }, + function (err, resp, body) { + assert.equal(resp.statusCode, 401); + done(); + }); + }); + + it('should succeed with credentials', function (done) { + request.post('http://localhost:' + (process.env.PORT || 5000) + '/api/v1/superfuntime/2', + { + body: { + 'kind': 'OtherPerson', + 'curiousPersonReqField': 'hey?', + 'enthusiasticPersonReqField': 'hola!' + }, + json: true, + headers: { + 'x-key': 'api secret key' + } + }, + function (err, resp, body) { + assert.equal(resp.statusCode, 204); + done(); + }); + }); +}); + +describe('test basic and apiKey auth together on same operation', function () { + this.timeout(5000); + before(function (done) { + util.launch('serverAuth', done); + }); + + after(function (done) { + util.finish(done); + }); + + it('should fail without any credentials', function (done) { + request.delete('http://localhost:' + (process.env.PORT || 5000) + '/api/v1/superfuntime/2', + function (err, resp, body) { + assert.equal(resp.statusCode, 401); + done(); + }); + }); + + it('should fail without basic credentials', function (done) { + request.delete('http://localhost:' + (process.env.PORT || 5000) + '/api/v1/superfuntime/2', + { + headers: { + 'x-key': 'api secret key' + } + }, + function (err, resp, body) { + assert.equal(resp.statusCode, 401); + done(); + }); + }); + + it('should fail without apiKey credentials', function (done) { + request.delete('http://localhost:' + (process.env.PORT || 5000) + '/api/v1/superfuntime/2', + { + auth: { + user: 'username', pass: 'password', sendImmediately: false + } + }, + function (err, resp, body) { + assert.equal(resp.statusCode, 401); + done(); + }); + }); + + it('should succeed with credentials', function (done) { + request.delete('http://localhost:' + (process.env.PORT || 5000) + '/api/v1/superfuntime/2', + { + auth: { + user: 'username', pass: 'password', sendImmediately: false + }, + headers: { + 'x-key': 'api secret key' + } + }, + function (err, resp, body) { + assert.equal(resp.statusCode, 204); + done(); + }); + }); +}); +