JavaScript client: Add authentications support

Closes #1952
This commit is contained in:
xhh 2016-02-06 16:24:59 +08:00
parent c80df9ee4d
commit d52ebdf684
10 changed files with 357 additions and 29 deletions

View File

@ -20,7 +20,13 @@
* The base path to put in front of every API call's (relative) path.
*/
this.basePath = '{{basePath}}'.replace(/\/+$/, '');
{{=< >=}}
this.authentications = {<#authMethods><#isBasic>
'<name>': {type: 'basic'}</isBasic><#isApiKey>
'<name>': {type: 'apiKey', in: <#isKeyInHeader>'header'</isKeyInHeader><^isKeyInHeader>'query'</isKeyInHeader>, name: '<keyParamName>'}</isApiKey><#isOAuth>
'<name>': {type: 'oauth2'}</isOAuth><#hasMore>,</hasMore></authMethods>
};
<={{ }}=>
/**
* The default HTTP headers to be included for all API calls.
*/
@ -158,6 +164,42 @@
}
};
ApiClient.prototype.applyAuthToRequest = function applyAuthToRequest(request, authNames) {
var _this = this;
authNames.forEach(function(authName) {
var auth = _this.authentications[authName];
switch (auth.type) {
case 'basic':
if (auth.username || auth.password) {
request.auth(auth.username || '', auth.password || '');
}
break;
case 'apiKey':
if (auth.apiKey) {
var data = {};
if (auth.apiKeyPrefix) {
data[auth.name] = auth.apiKeyPrefix + ' ' + auth.apiKey;
} else {
data[auth.name] = auth.apiKey;
}
if (auth.in === 'header') {
request.set(data);
} else {
request.query(data);
}
}
break;
case 'oauth2':
if (auth.accessToken) {
request.set({'Authorization': 'Bearer ' + auth.accessToken});
}
break;
default:
throw new Error('Unknown authentication type: ' + auth.type);
}
});
};
ApiClient.prototype.deserialize = function deserialize(response, returnType) {
if (response == null || returnType == null) {
return null;
@ -172,12 +214,15 @@
};
ApiClient.prototype.callApi = function callApi(path, httpMethod, pathParams,
queryParams, headerParams, formParams, bodyParam, contentTypes, accepts,
returnType, callback) {
queryParams, headerParams, formParams, bodyParam, authNames, contentTypes,
accepts, returnType, callback) {
var _this = this;
var url = this.buildUrl(path, pathParams);
var request = superagent(httpMethod, url);
// apply authentications
this.applyAuthToRequest(request, authNames);
// set query parameters
request.query(this.normalizeParams(queryParams));

View File

@ -51,6 +51,7 @@
'<baseName>': <#collectionFormat>this.buildCollectionParam(<paramName>, '<collectionFormat>')</collectionFormat><^collectionFormat><paramName></collectionFormat><#hasMore>,</hasMore></formParams>
};
var authNames = [<#authMethods>'<name>'<#hasMore>, </hasMore></authMethods>];
var contentTypes = [<#consumes>'<mediaType>'<#hasMore>, </hasMore></consumes>];
var accepts = [<#produces>'<mediaType>'<#hasMore>, </hasMore></produces>];
var returnType = <#returnType><&returnType></returnType><^returnType>null</returnType>;
@ -58,7 +59,7 @@
return this.apiClient.callApi(
'<&path>', '<httpMethod>',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
<={{ }}=>
}

View File

@ -12,6 +12,7 @@
},
"devDependencies": {
"mocha": "~2.3.4",
"sinon": "1.17.3",
"expect.js": "~0.3.1"
}
}

View File

@ -12,6 +12,7 @@
},
"devDependencies": {
"mocha": "~2.3.4",
"sinon": "1.17.3",
"expect.js": "~0.3.1"
}
}

View File

@ -21,6 +21,11 @@
*/
this.basePath = 'http://petstore.swagger.io/v2'.replace(/\/+$/, '');
this.authentications = {
'petstore_auth': {type: 'oauth2'},
'api_key': {type: 'apiKey', in: 'header', name: 'api_key'}
};
/**
* The default HTTP headers to be included for all API calls.
*/
@ -158,6 +163,42 @@
}
};
ApiClient.prototype.applyAuthToRequest = function applyAuthToRequest(request, authNames) {
var _this = this;
authNames.forEach(function(authName) {
var auth = _this.authentications[authName];
switch (auth.type) {
case 'basic':
if (auth.username || auth.password) {
request.auth(auth.username || '', auth.password || '');
}
break;
case 'apiKey':
if (auth.apiKey) {
var data = {};
if (auth.apiKeyPrefix) {
data[auth.name] = auth.apiKeyPrefix + ' ' + auth.apiKey;
} else {
data[auth.name] = auth.apiKey;
}
if (auth.in === 'header') {
request.set(data);
} else {
request.query(data);
}
}
break;
case 'oauth2':
if (auth.accessToken) {
request.set({'Authorization': 'Bearer ' + auth.accessToken});
}
break;
default:
throw new Error('Unknown authentication type: ' + auth.type);
}
});
};
ApiClient.prototype.deserialize = function deserialize(response, returnType) {
if (response == null || returnType == null) {
return null;
@ -172,12 +213,15 @@
};
ApiClient.prototype.callApi = function callApi(path, httpMethod, pathParams,
queryParams, headerParams, formParams, bodyParam, contentTypes, accepts,
returnType, callback) {
queryParams, headerParams, formParams, bodyParam, authNames, contentTypes,
accepts, returnType, callback) {
var _this = this;
var url = this.buildUrl(path, pathParams);
var request = superagent(httpMethod, url);
// apply authentications
this.applyAuthToRequest(request, authNames);
// set query parameters
request.query(this.normalizeParams(queryParams));

View File

@ -41,6 +41,7 @@
var formParams = {
};
var authNames = ['petstore_auth'];
var contentTypes = ['application/json', 'application/xml'];
var accepts = ['application/json', 'application/xml'];
var returnType = null;
@ -48,7 +49,7 @@
return this.apiClient.callApi(
'/pet', 'PUT',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -73,6 +74,7 @@
var formParams = {
};
var authNames = ['petstore_auth'];
var contentTypes = ['application/json', 'application/xml'];
var accepts = ['application/json', 'application/xml'];
var returnType = null;
@ -80,7 +82,7 @@
return this.apiClient.callApi(
'/pet', 'POST',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -107,6 +109,7 @@
var formParams = {
};
var authNames = ['petstore_auth'];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = [Pet];
@ -114,7 +117,7 @@
return this.apiClient.callApi(
'/pet/findByStatus', 'GET',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -141,6 +144,7 @@
var formParams = {
};
var authNames = ['petstore_auth'];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = [Pet];
@ -148,7 +152,7 @@
return this.apiClient.callApi(
'/pet/findByTags', 'GET',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -180,6 +184,7 @@
var formParams = {
};
var authNames = ['api_key'];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = Pet;
@ -187,7 +192,7 @@
return this.apiClient.callApi(
'/pet/{petId}', 'GET',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -222,6 +227,7 @@
'status': status
};
var authNames = ['petstore_auth'];
var contentTypes = ['application/x-www-form-urlencoded'];
var accepts = ['application/json', 'application/xml'];
var returnType = null;
@ -229,7 +235,7 @@
return this.apiClient.callApi(
'/pet/{petId}', 'POST',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -262,6 +268,7 @@
var formParams = {
};
var authNames = ['petstore_auth'];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = null;
@ -269,7 +276,7 @@
return this.apiClient.callApi(
'/pet/{petId}', 'DELETE',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -304,6 +311,7 @@
'file': file
};
var authNames = ['petstore_auth'];
var contentTypes = ['multipart/form-data'];
var accepts = ['application/json', 'application/xml'];
var returnType = null;
@ -311,7 +319,7 @@
return this.apiClient.callApi(
'/pet/{petId}/uploadImage', 'POST',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -343,6 +351,7 @@
var formParams = {
};
var authNames = ['api_key'];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = 'String';
@ -350,7 +359,7 @@
return this.apiClient.callApi(
'/pet/{petId}?testing_byte_array=true', 'GET',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -375,6 +384,7 @@
var formParams = {
};
var authNames = ['petstore_auth'];
var contentTypes = ['application/json', 'application/xml'];
var accepts = ['application/json', 'application/xml'];
var returnType = null;
@ -382,7 +392,7 @@
return this.apiClient.callApi(
'/pet?testing_byte_array=true', 'POST',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}

View File

@ -41,6 +41,7 @@
var formParams = {
};
var authNames = ['api_key'];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = {'String': 'Integer'};
@ -48,7 +49,7 @@
return this.apiClient.callApi(
'/store/inventory', 'GET',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -74,6 +75,7 @@
var formParams = {
};
var authNames = [];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = Order;
@ -81,7 +83,7 @@
return this.apiClient.callApi(
'/store/order', 'POST',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -113,6 +115,7 @@
var formParams = {
};
var authNames = [];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = Order;
@ -120,7 +123,7 @@
return this.apiClient.callApi(
'/store/order/{orderId}', 'GET',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -151,6 +154,7 @@
var formParams = {
};
var authNames = [];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = null;
@ -158,7 +162,7 @@
return this.apiClient.callApi(
'/store/order/{orderId}', 'DELETE',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}

View File

@ -41,6 +41,7 @@
var formParams = {
};
var authNames = [];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = null;
@ -48,7 +49,7 @@
return this.apiClient.callApi(
'/user', 'POST',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -73,6 +74,7 @@
var formParams = {
};
var authNames = [];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = null;
@ -80,7 +82,7 @@
return this.apiClient.callApi(
'/user/createWithArray', 'POST',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -105,6 +107,7 @@
var formParams = {
};
var authNames = [];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = null;
@ -112,7 +115,7 @@
return this.apiClient.callApi(
'/user/createWithList', 'POST',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -141,6 +144,7 @@
var formParams = {
};
var authNames = [];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = 'String';
@ -148,7 +152,7 @@
return this.apiClient.callApi(
'/user/login', 'GET',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -172,6 +176,7 @@
var formParams = {
};
var authNames = [];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = null;
@ -179,7 +184,7 @@
return this.apiClient.callApi(
'/user/logout', 'GET',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -211,6 +216,7 @@
var formParams = {
};
var authNames = [];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = User;
@ -218,7 +224,7 @@
return this.apiClient.callApi(
'/user/{username}', 'GET',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -250,6 +256,7 @@
var formParams = {
};
var authNames = [];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = null;
@ -257,7 +264,7 @@
return this.apiClient.callApi(
'/user/{username}', 'PUT',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}
@ -288,6 +295,7 @@
var formParams = {
};
var authNames = [];
var contentTypes = [];
var accepts = ['application/json', 'application/xml'];
var returnType = null;
@ -295,7 +303,7 @@
return this.apiClient.callApi(
'/user/{username}', 'DELETE',
pathParams, queryParams, headerParams, formParams, postBody,
contentTypes, accepts, returnType, callback
authNames, contentTypes, accepts, returnType, callback
);
}

View File

@ -1,6 +1,7 @@
if (typeof module === 'object' && module.exports) {
var expect = require('expect.js');
var SwaggerPetstore = require('../src/index');
var sinon = require('sinon');
}
var apiClient = SwaggerPetstore.ApiClient.default;
@ -10,6 +11,10 @@ describe('ApiClient', function() {
it('should have correct default values with the default API client', function() {
expect(apiClient).to.be.ok();
expect(apiClient.basePath).to.be('http://petstore.swagger.io/v2');
expect(apiClient.authentications).to.eql({
'petstore_auth': {type: 'oauth2'},
'api_key': {type: 'apiKey', in: 'header', name: 'api_key'}
});
});
it('should have correct default values with new API client and can customize it', function() {
@ -99,6 +104,212 @@ describe('ApiClient', function() {
});
});
describe('#applyAuthToRequest', function() {
var req, newClient;
beforeEach(function() {
req = {
auth: function() {},
set: function() {},
query: function() {}
};
sinon.stub(req, 'auth');
sinon.stub(req, 'set');
sinon.stub(req, 'query');
newClient = new SwaggerPetstore.ApiClient();
});
describe('basic', function() {
var authName = 'testBasicAuth';
var authNames = [authName];
var auth;
beforeEach(function() {
newClient.authentications[authName] = {type: 'basic'};
auth = newClient.authentications[authName];
});
it('sets auth header with username and password set', function() {
auth.username = 'user';
auth.password = 'pass';
newClient.applyAuthToRequest(req, authNames);
sinon.assert.calledOnce(req.auth);
// 'dXNlcjpwYXNz' is base64-encoded string of 'user:pass'
sinon.assert.calledWithMatch(req.auth, 'user', 'pass');
sinon.assert.notCalled(req.set);
sinon.assert.notCalled(req.query);
});
it('sets header with only username set', function() {
auth.username = 'user';
newClient.applyAuthToRequest(req, authNames);
sinon.assert.calledOnce(req.auth);
// 'dXNlcjo=' is base64-encoded string of 'user:'
sinon.assert.calledWithMatch(req.auth, 'user', '');
sinon.assert.notCalled(req.set);
sinon.assert.notCalled(req.query);
});
it('sets header with only password set', function() {
auth.password = 'pass';
newClient.applyAuthToRequest(req, authNames);
sinon.assert.calledOnce(req.auth);
// 'OnBhc3M=' is base64-encoded string of ':pass'
sinon.assert.calledWithMatch(req.auth, '', 'pass');
sinon.assert.notCalled(req.set);
sinon.assert.notCalled(req.query);
});
it('does not set header when username and password are not set', function() {
newClient.applyAuthToRequest(req, authNames);
sinon.assert.notCalled(req.auth);
sinon.assert.notCalled(req.set);
sinon.assert.notCalled(req.query);
});
});
describe('apiKey', function() {
var authName = 'testApiKey';
var authNames = [authName];
var auth;
beforeEach(function() {
newClient.authentications[authName] = {type: 'apiKey', name: 'api_key'};
auth = newClient.authentications[authName];
});
it('sets api key in header', function() {
auth.in = 'header';
auth.apiKey = 'my-api-key';
newClient.applyAuthToRequest(req, authNames);
sinon.assert.calledOnce(req.set);
sinon.assert.calledWithMatch(req.set, {'api_key': 'my-api-key'});
sinon.assert.notCalled(req.auth);
sinon.assert.notCalled(req.query);
});
it('sets api key in query', function() {
auth.in = 'query';
auth.apiKey = 'my-api-key';
newClient.applyAuthToRequest(req, authNames);
sinon.assert.calledOnce(req.query);
sinon.assert.calledWithMatch(req.query, {'api_key': 'my-api-key'});
sinon.assert.notCalled(req.auth);
sinon.assert.notCalled(req.set);
});
it('sets api key in header with prefix', function() {
auth.in = 'header';
auth.apiKey = 'my-api-key';
auth.apiKeyPrefix = 'Key';
newClient.applyAuthToRequest(req, authNames);
sinon.assert.calledOnce(req.set);
sinon.assert.calledWithMatch(req.set, {'api_key': 'Key my-api-key'});
sinon.assert.notCalled(req.auth);
sinon.assert.notCalled(req.query);
});
it('works when api key is not set', function() {
auth.in = 'query';
auth.apiKey = null;
newClient.applyAuthToRequest(req, authNames);
sinon.assert.notCalled(req.query);
sinon.assert.notCalled(req.auth);
sinon.assert.notCalled(req.set);
});
});
describe('oauth2', function() {
var authName = 'testOAuth2';
var authNames = [authName];
var auth;
beforeEach(function() {
newClient.authentications[authName] = {type: 'oauth2'};
auth = newClient.authentications[authName];
});
it('sets access token in header', function() {
auth.accessToken = 'my-access-token';
newClient.applyAuthToRequest(req, authNames);
sinon.assert.calledOnce(req.set);
sinon.assert.calledWithMatch(req.set, {'Authorization': 'Bearer my-access-token'});
sinon.assert.notCalled(req.auth);
sinon.assert.notCalled(req.query);
});
it('works when access token is not set', function() {
auth.accessToken = null;
newClient.applyAuthToRequest(req, authNames);
sinon.assert.notCalled(req.query);
sinon.assert.notCalled(req.auth);
sinon.assert.notCalled(req.set);
});
});
describe('apiKey and oauth2', function() {
var apiKeyAuthName = 'testApiKey';
var oauth2Name = 'testOAuth2';
var authNames = [apiKeyAuthName, oauth2Name];
var apiKeyAuth, oauth2;
beforeEach(function() {
newClient.authentications[apiKeyAuthName] = {type: 'apiKey', name: 'api_key', in: 'query'};
newClient.authentications[oauth2Name] = {type: 'oauth2'};
apiKeyAuth = newClient.authentications[apiKeyAuthName];
oauth2 = newClient.authentications[oauth2Name];
});
it('works when setting both api key and access token', function() {
apiKeyAuth.apiKey = 'my-api-key';
oauth2.accessToken = 'my-access-token';
newClient.applyAuthToRequest(req, authNames);
sinon.assert.calledOnce(req.query);
sinon.assert.calledWithMatch(req.query, {'api_key': 'my-api-key'});
sinon.assert.calledOnce(req.set);
sinon.assert.calledWithMatch(req.set, {'Authorization': 'Bearer my-access-token'});
sinon.assert.notCalled(req.auth);
});
it('works when setting only api key', function() {
apiKeyAuth.apiKey = 'my-api-key';
oauth2.accessToken = null;
newClient.applyAuthToRequest(req, authNames);
sinon.assert.calledOnce(req.query);
sinon.assert.calledWithMatch(req.query, {'api_key': 'my-api-key'});
sinon.assert.notCalled(req.set);
sinon.assert.notCalled(req.auth);
});
it('works when neither api key nor access token is set', function() {
apiKeyAuth.apiKey = null;
oauth2.accessToken = null;
newClient.applyAuthToRequest(req, authNames);
sinon.assert.notCalled(req.query);
sinon.assert.notCalled(req.auth);
sinon.assert.notCalled(req.set);
});
});
describe('unknown type', function() {
var authName = 'unknown';
var authNames = [authName];
beforeEach(function() {
newClient.authentications[authName] = {type: 'UNKNOWN'};
});
it('throws error for unknown auth type', function() {
expect(function() {
newClient.applyAuthToRequest(req, authNames);
}).to.throwError();
sinon.assert.notCalled(req.set);
sinon.assert.notCalled(req.auth);
sinon.assert.notCalled(req.query);
});
});
});
describe('#defaultHeaders', function() {
it('should initialize default headers to be an empty object', function() {
expect(apiClient.defaultHeaders).to.eql({});
@ -143,10 +354,12 @@ function makeDumbRequest(apiClient, opts) {
var headerParams = opts.headerParams || {};
var formParams = opts.formParams || {};
var bodyParam = opts.bodyParam;
var authNames = [];
var contentTypes = opts.contentTypes || [];
var accepts = opts.accepts || [];
var callback = opts.callback;
return apiClient.callApi(path, httpMethod, pathParams, queryParams,
headerParams, formParams, bodyParam, contentTypes, accepts, callback
headerParams, formParams, bodyParam, authNames, contentTypes, accepts,
callback
);
}

View File

@ -9,6 +9,7 @@
<script src="https://cdn.rawgit.com/jquery/jquery/2.1.4/dist/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/Automattic/expect.js/0.3.1/index.js"></script>
<script src="http://sinonjs.org/releases/sinon-1.17.3.js"></script>
<script src="https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.js"></script>
<script>
mocha.setup({