New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

volos-quota-apigee

Package Overview
Dependencies
Maintainers
3
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

volos-quota-apigee - npm Package Compare versions

Comparing version 0.9.3 to 0.10.0

304

lib/apigeequota.js

@@ -33,10 +33,24 @@ /****************************************************************************

var assert = require('assert');
var url = require('url');
var debug = require('debug')('apigee');
var http = require('http');
var https = require('https');
var Quota = require('volos-quota-common');
var querystring = require('querystring');
var semver = require('semver');
var superagent = require('superagent');
var util = require('util');
var Quota = require('volos-quota-common');
var debug = require('debug')('apigee');
var url = require('url');
var apigeeAccess;
var hasApigeeAccess;
try {
// Older versions of Apigee won't have this, so be prepared to work around.
apigeeAccess = require('apigee-access');
apigeeAccess.getQuota();
hasApigeeAccess = true;
} catch (e) {
debug('Operating without access to apigee-access');
}
var create = function(options) {

@@ -48,43 +62,57 @@ return new Quota(ApigeeQuotaSpi, options);

var ApigeeQuotaSpi = function(options) {
if (!options.uri) {
throw new Error('uri parameter must be specified');
// Allow users to override use of apigee-access
if (options.apigeeMode === 'local') {
debug('Using apigee-access no matter what');
this.useApigeeAccess = true;
} else if (options.apigeeMode === 'remote') {
debug('Using remote apigee proxy no matter what');
this.useApigeeAccess = false;
} else {
this.useApigeeAccess = hasApigeeAccess;
}
if (!options.key) {
throw new Error('key parameter must be specified');
}
assert(options.timeUnit);
assert(options.interval);
if (options.startTime) {
throw new Error('Quotas with a fixed starting time are not supported');
this.options = options;
if (this.useApigeeAccess) {
this.apigeeQuota = apigeeAccess.getQuota();
} else {
if (!options.uri) {
throw new Error('uri parameter must be specified');
}
if (!options.key) {
throw new Error('key parameter must be specified');
}
this.uri = options.uri;
this.key = options.key;
}
};
this.uri = options.uri;
this.key = options.key;
this.options = options;
ApigeeQuotaSpi.prototype.getImplementationName = function(cb) {
var self = this;
selectImplementation(self, function(err, impl) {
if (err) {
cb(err);
} else {
cb(undefined, impl.getImplementationName());
}
});
};
ApigeeQuotaSpi.prototype.apply = function(options, cb) {
var allow = options.allow || this.options.allow;
var r = {
identifier: options.identifier,
weight: options.weight,
interval: this.options.interval,
allow: allow,
unit: this.options.timeUnit
};
makeRequest(this, 'POST', '/quotas/distributed', querystring.stringify(r), function(err, resp) {
var self = this;
selectImplementation(self, function(err, impl) {
if (err) {
cb(err);
} else {
var ret = {
allowed: Number(resp.allowed),
used: Number(resp.used) + Number(resp.exceeded), // note: this is exceeded for this req, not overall
isAllowed: !resp.failed,
expiryTime: Number(resp.expiry_time),
timestamp: Number(resp.ts)
};
cb(undefined, ret);
impl.apply(options, function(err, result) {
if (err) {
debug('Quota error: %j', err);
} else {
debug('Quota result: %j', result);
}
cb(err, result);
});
}

@@ -94,82 +122,168 @@ });

function makeRequest(self, verb, uriPath, body, cb) {
var finalUri = self.uri + uriPath;
if (debug.enabled) {
debug(util.format('API call to %s: %s', finalUri, body));
// Given the state of apigee-access, or the "version" of the remote proxy,
// select an implementation. This happens on the first call so that we can
// "start" the module if the proxy is down -- but this doesn't complete
// until we can get one successful HTTP call through
function selectImplementation(self, cb) {
if (self.quotaImpl) {
cb(undefined, self.quotaImpl);
return;
}
var r = url.parse(finalUri);
r.headers = {
'x-DNA-Api-Key': self.key
};
r.method = verb;
if (body) {
r.headers['Content-Type'] = 'application/x-www-form-urlencoded';
}
var impl;
if (self.apigeeQuota) {
self.quotaImpl = new ApigeeAccessQuota(self);
cb(undefined, self.quotaImpl);
var req;
if (r.protocol === 'http:') {
req = http.request(r, function(resp) {
requestComplete(resp, cb);
} else {
superagent.agent().
get(self.options.uri + '/v2/version').
set('x-DNA-Api-Key', self.options.key).
end(function(err, resp) {
if (err) {
cb(err);
} else {
if (resp.notFound || !semver.satisfies(resp.text, '>=1.1.0')) {
if (self.options.startTime) {
cb(new Error('Quotas with a fixed starting time are not supported'));
} else {
self.quotaImpl = new ApigeeOldRemoteQuota(self);
cb(undefined, self.quotaImpl);
}
} else if (resp.ok) {
self.quotaImpl = new ApigeeRemoteQuota(self);
cb(undefined, self.quotaImpl);
} else {
cb(new Error(util.format('HTTP error getting proxy version: %d', resp.statusCode)));
}
}
});
} else if (r.protocol === 'https:') {
req = https.request(r, function(resp) {
requestComplete(resp, cb);
});
} else {
cb(new Error('Unsupported protocol ' + r.protocol));
return;
}
}
req.on('error', function(err) {
cb(err);
});
if (body) {
req.end(body);
function makeNewQuotaRequest(self, opts, allow) {
var r ={
identifier: opts.identifier,
weight: opts.weight,
interval: self.options.interval,
allow: allow,
timeUnit: self.options.timeUnit
};
if (self.options.startTime) {
// This type of quota just computes fixed buckets of 24 hours, 60 minutes,
// etc. from a fixed start time. No fancy calendar math.
r.startTime = self.options.startTime;
r.quotaType = 'fixedStart';
} else {
req.end();
// This is the quota type that works most like others in Volos.
// The window starts when the first message for a given ID arrives.
r.quotaType = 'flexi';
}
return r;
}
function readResponse(resp, data) {
var d;
do {
d = resp.read();
if (d) {
data += d;
}
} while (d);
return data;
function ApigeeAccessQuota(quota) {
debug('Using apigee-access for native quota');
this.quota = quota;
}
function requestComplete(resp, cb) {
resp.on('error', function(err) {
cb(err);
});
ApigeeAccessQuota.prototype.getImplementationName = function() {
return 'Local';
};
var respData = '';
resp.on('readable', function() {
respData = readResponse(resp, respData);
});
ApigeeAccessQuota.prototype.apply = function(opts, cb) {
var allow = opts.allow || this.quota.options.allow;
var r = makeNewQuotaRequest(this.quota, opts, allow);
resp.on('end', function() {
if (debug.enabled) {
debug(util.format('API response %d: %s', resp.statusCode, respData));
}
if (resp.statusCode !== 200) {
var err = new Error('Error on HTTP request');
err.statusCode = resp.statusCode;
err.message = respData;
// Result is almost the same as a volos result
debug('Local quota request: %j', r);
this.quota.apigeeQuota.apply(r, function(err, result) {
result.expiryTime = result.expiryTime - result.timestamp;
if (err) {
cb(err);
} else {
var ret;
try {
ret = querystring.parse(respData);
} catch (e) {
// The response might not be a query string -- not everything returns it
cb(e);
}
cb(undefined, ret);
debug('Quota result: %j', result);
cb(undefined, result);
}
});
}
};
function ApigeeRemoteQuota(quota) {
debug('Using a remote quota');
this.quota = quota;
}
ApigeeRemoteQuota.prototype.getImplementationName = function() {
return 'Remote';
};
ApigeeRemoteQuota.prototype.apply = function(opts, cb) {
var allow = opts.allow || this.quota.options.allow;
var r = makeNewQuotaRequest(this.quota, opts, allow);
debug('Remote quota request: %j', r);
superagent.agent().
post(this.quota.options.uri + '/v2/quotas/apply').
set('x-DNA-Api-Key', this.quota.options.key).
type('json').
send(r).
end(function(err, resp) {
if (err) {
cb(err);
} else if (resp.ok) {
// result from apigee is almost what the module expects
var result = resp.body;
result.expiryTime = result.expiryTime - result.timestamp;
cb(undefined, resp.body);
} else {
cb(new Error(util.format('Error updating remote quota: %d %s',
resp.statusCode, resp.text)));
}
});
};
function ApigeeOldRemoteQuota(quota) {
debug('Using a remote quota with the old protocol');
this.quota = quota;
}
ApigeeOldRemoteQuota.prototype.getImplementationName = function() {
return 'OldRemote';
};
ApigeeOldRemoteQuota.prototype.apply = function(opts, cb) {
var allow = opts.allow || this.quota.options.allow;
var r = {
identifier: opts.identifier,
weight: opts.weight,
interval: this.quota.options.interval,
allow: allow,
unit: this.quota.options.timeUnit
};
debug('Old remote quota request: %j', r);
superagent.agent().
post(this.quota.options.uri + '/quotas/distributed').
set('x-DNA-Api-Key', this.quota.options.key).
type('form').
send(r).
end(function(err, resp) {
if (err) {
cb(err);
} else if (resp.ok) {
debug('result: %s', resp.text);
var result = {
allowed: resp.body.allowed,
used: resp.body.used,
isAllowed: !resp.body.failed,
expiryTime: resp.body.expiry_time - resp.body.ts
};
cb(undefined, result);
} else {
cb(new Error(util.format('Error updating remote quota: %d %s',
resp.statusCode, resp.text)));
}
});
};
{
"name": "volos-quota-apigee",
"version": "0.9.3",
"version": "0.10.0",
"main": "lib/apigeequota.js",

@@ -13,4 +13,7 @@ "license": "MIT",

"dependencies": {
"apigee-access": ">=1.2.x",
"debug": "1.0.x",
"volos-quota-common": "0.9.x"
"semver": "3.x.x",
"superagent": "0.x.x",
"volos-quota-common": "0.10.x"
},

@@ -17,0 +20,0 @@ "devDependencies": {},

@@ -34,3 +34,5 @@ # volos-quota-apigee

* uri: The full URI of the Apigee adapter that you deployed in the last step. For instance, if the organization
name is "foo" then this might be "https://foo-test.apigee.net/adapterproxy".
name is "foo" then this might be "https://foo-test.apigee.net/apigee-remote-proxy". This parameter is
not used when the app is always deployed to Apigee Edge, or when "apigeeMode" is set to "local".
See below for more.
* key: An API consumer key for a valid "application" that is part of the same organization where the adapter

@@ -44,2 +46,20 @@ was installed.

The following parameters are optional:
* apigeeMode (optional): By default, this module will use the OAuth service built in to Apigee Edge when it
is deployed there, and use an HTTP-based API to the "uri" specified above when it is not. This parameter
overrides that default.
"apigeeMode" supports two values:
* remote: When set to "remote," this module will use the "uri" specified in the options to communicate
with Apigee Edge, even if it is deployed to Apigee Edge. This allows you to use the OAuth services of
another organization, for instance.
* local: When set to "local," this module wil use the "apigee-access" module to access Apigee Edge
functionality that is built in to the runtime. If this is set and the module is running outside
Apigee Edge, then all calls will fail.
By default, the module will use Apigee Edge functionality when available, and fall back to API
calls when it is not.
## Example

@@ -46,0 +66,0 @@

@@ -35,4 +35,6 @@ /****************************************************************************

this.timeout(150000);
var implementationName;
this.timeout(120000);
function id(_id) {

@@ -55,3 +57,3 @@ return 'test:' + random + ":" + _id;

before(function() {
before(function(done) {
var options = extend(config, {

@@ -63,2 +65,7 @@ timeUnit: 'minute',

pm = Spi.create(options);
pm.quota.getImplementationName(function(err, implName) {
implementationName = implName;
done(err);
});
});

@@ -68,2 +75,4 @@

// Verify that quota is reset within the expiration time,
// plus a fudge factor for distributed quota sync
it('Minute', function(done) {

@@ -74,7 +83,4 @@ var hit = { identifier: id('TimeOne'), weight: 1 };

checkResult(result, 2, 1, true);
var expiry = Date.now() + 60000;
result.expiryTime.should.be.approximately(expiry, 10000);
result.expiryTime.should.be.approximately(60000, 10000);
var offset = result.expiryTime - expiry;
setTimeout(function() {

@@ -85,3 +91,3 @@ pm.apply(hit, function(err, result) {

// Ensure quota is reset after a minute
// Ensure quota is reset within a minute
setTimeout(function() {

@@ -93,6 +99,6 @@ pm.apply(hit, function(err, result) {

});
}, 120000 + offset);
}, 40001);
});
}, 5000);
}, 40001);
});

@@ -103,2 +109,3 @@ });

describe('Basic', function() {
// Just do some basic counting
it('Minute', function(done) {

@@ -111,26 +118,145 @@ pm.apply({

checkResult(result, 2, 1, true);
result.expiryTime.should.be.approximately(Date.now() + 60000, 10000);
result.expiryTime.should.be.approximately(60000, 10000);
setTimeout(function() {
pm.apply({
identifier: id('One'),
weight: 1
}, function(err, result) {
assert(!err);
checkResult(result, 2, 2, true);
pm.apply({
identifier: id('One'),
identifier: id('Two'),
weight: 1
}, function(err, result) {
assert(!err);
checkResult(result, 2, 2, true);
pm.apply({
identifier: id('Two'),
weight: 1
}, function(err, result) {
assert(!err);
checkResult(result, 2, 1, true);
done();
});
checkResult(result, 2, 1, true);
done();
});
}, 5000);
});
});
});
it('Quota weight', function(done) {
// Ensure that weighting works
// Apigee won't keep counting once the quota is exceeded, so set a
// larger "allow" value for this
pm.apply({
identifier: id('WeightOne'),
weight: 1,
allow: 10
}, function(err, result) {
assert(!err);
checkResult(result, 10, 1, true);
pm.apply({
identifier: id('WeightOne'),
weight: 3,
allow: 10
}, function(err, result) {
assert(!err);
checkResult(result, 10, 4, true);
done();
});
});
});
});
describe('Calendar', function() {
// For each of these, verify that the parameters work and that we can
// calculate the right expiration time. Do not wait for expiration
// as we can't seem to get consistent results there yet.
it('Minute', function(done) {
if (implementationName === 'OldRemote') {
console.log('Skipping calendar tests because old implementation doesn\'t support them');
done();
}
this.timeout(2000);
var startTime = Date.now() - 59000; // start almost a minute ago
var options = extend(config, {
timeUnit: 'minute',
interval: 1,
allow: 1,
startTime: startTime
});
var pm = Spi.create(options);
var hit = { identifier: id('TimeTwo'), weight: 1 };
pm.apply(hit, function(err, result) {
assert(!err);
checkResult(result, 1, 1, true);
result.expiryTime.should.be.approximately(1000, 500);
done();
});
});
it('Hour', function(done) {
if (implementationName === 'OldRemote') {
done();
}
this.timeout(2000);
var startTime = Date.now() - (60000 * 60 - 1000); // start almost an hour ago
var options = extend(config, {
timeUnit: 'hour',
interval: 1,
allow: 1,
startTime: startTime
});
var pm = Spi.create(options);
var hit = { identifier: id('TimeThree'), weight: 1 };
pm.apply(hit, function(err, result) {
assert(!err);
checkResult(result, 1, 1, true);
result.expiryTime.should.be.approximately(1000, 500);
done();
});
});
it('Day', function(done) {
if (implementationName === 'OldRemote') {
done();
}
this.timeout(2000);
var startTime = Date.now() - (60000 * 60 * 24 - 1000); // start almost a day ago
var options = extend(config, {
timeUnit: 'day',
interval: 1,
allow: 1,
startTime: startTime
});
var pm = Spi.create(options);
var hit = { identifier: id('TimeFour'), weight: 1 };
pm.apply(hit, function(err, result) {
assert(!err);
checkResult(result, 1, 1, true);
result.expiryTime.should.be.approximately(1000, 500);
done();
});
});
it('Week', function(done) {
if (implementationName === 'OldRemote') {
done();
}
this.timeout(2000);
var startTime = Date.now() - (60000 * 60 * 24 * 7 - 1000); // start almost a week ago
var options = extend(config, {
timeUnit: 'week',
interval: 1,
allow: 1,
startTime: startTime
});
var pm = Spi.create(options);
var hit = { identifier: id('TimeFive'), weight: 1 };
pm.apply(hit, function(err, result) {
assert(!err);
checkResult(result, 1, 1, true);
result.expiryTime.should.be.approximately(1000, 500);
done();
});
});
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc