Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bin/vaultclient
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ program
.command('create-account')
.option('--name <NAME>')
.option('--email <EMAIL>')
.option('--quota <Quota>', 'Maximum quota for the account', parseInt)
.option('--quota <Quota>', 'Maximum quota for the account', BigInt)
.option('--accountid <ExternalAccountID>')
.option('--canonicalid <ExternalCanonicalID>')
.action(action.bind(null, 'create-account', (client, args) => {
Expand Down Expand Up @@ -213,7 +213,7 @@ program
program
.command('update-account-quota')
.option('--account-name <NAME>', 'Name of the account')
.option('--quota <QUOTA>', 'Maximum quota for the account', parseInt)
.option('--quota <QUOTA>', 'Maximum quota for the account', BigInt)
.option('--parameter-validation', 'Disable validation of quota')
.action(action.bind(null, 'update-account-quota', (client, args) => {
client.updateAccountQuota(args.accountName, args.quota, handleVaultResponse);
Expand Down
8 changes: 4 additions & 4 deletions lib/IAMClient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ declare class VaultClient {
* @param {string} accountName - account name
* @param {object} options - additional creation params
* @param {string} options.email - account email
* @param {string} [options.quota] - maximum quota for the account
* @param {string|number|bigint} [options.quota] - maximum quota for the account
* @param {VaultClient~requestCallback} callback - callback
* @returns {undefined}
*/
createAccount(accountName: string, options: {
email: string;
quota?: string;
quota?: string | number | bigint;
}, callback: any): undefined;
/**
* Create a password for an account
Expand Down Expand Up @@ -97,11 +97,11 @@ declare class VaultClient {
* Update Quota of an account
*
* @param {string} accountName - account name
* @param {number} quota - maximum quota for the account
* @param {string|number|bigint} quota - maximum quota for the account
* @param {VaultClient~requestCallback} callback - callback
* @returns {undefined}
*/
updateAccountQuota(accountName: string, quota: number, callback: any): undefined;
updateAccountQuota(accountName: string, quota: string | number | bigint, callback: any): undefined;
/**
* Delete Quota of an account
*
Expand Down
2 changes: 1 addition & 1 deletion lib/IAMClient.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 28 additions & 11 deletions lib/IAMClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class VaultClient {
* @param {string} accountName - account name
* @param {object} options - additional creation params
* @param {string} options.email - account email
* @param {string} [options.quota] - maximum quota for the account
* @param {string|number|bigint} [options.quota] - maximum quota for the account
* @param {VaultClient~requestCallback} callback - callback
* @returns {undefined}
*/
Expand All @@ -144,11 +144,13 @@ class VaultClient {
} = options;

if (quota) {
const conv = Number.isNaN(Number.parseInt(quota, 10));
assert(typeof quota === 'number'
&& !conv
&& quota >= 0, 'Quota must be a non-negative number');
data.quotaMax = quota;
try {
const quotaValue = BigInt(quota);
assert(quotaValue >= 0n, 'Quota must be non-negative');
data.quotaMax = quotaValue.toString();
} catch {
throw new Error('Quota must be a non-negative number, bigint, or string');
}
}
if (externalAccountId) {
const conv = Number.isNaN(Number.parseInt(externalAccountId, 10));
Expand Down Expand Up @@ -285,18 +287,23 @@ class VaultClient {
* Update Quota of an account
*
* @param {string} accountName - account name
* @param {number} quota - maximum quota for the account
* @param {string|number|bigint} quota - maximum quota for the account
* @param {VaultClient~requestCallback} callback - callback
* @returns {undefined}
*/
updateAccountQuota(accountName, quota, callback) {
if (this.parameterValidation) {
try {
assert(BigInt(quota) >= 0n, 'Quota must be non-negative');
} catch {
throw new Error('Quota must be a non-negative number, bigint, or string');
}
}
const data = {
Action: 'UpdateAccountQuota',
Version: '2010-05-08',
quotaMax: quota,
quotaMax: quota.toString(),
};
const quotaIsValid = typeof quota === 'number' && !Number.isNaN(Number.parseInt(quota, 10)) && quota >= 0;
assert(!this.parameterValidation || quotaIsValid, 'Quota must be a positive number');
if (accountName) {
assert(!this.parameterValidation || typeof accountName === 'string',
'the account name, if set, should be a string');
Expand Down Expand Up @@ -344,6 +351,7 @@ class VaultClient {
'the account name, if set, should be a string');
data.AccountName = accountName;
}

this.request('POST', '/', true, callback, data);
}

Expand Down Expand Up @@ -590,6 +598,7 @@ class VaultClient {
'filterKeyStartsWith should be a string');
data.filterKeyStartsWith = filterKeyStartsWith;
}

this.request('POST', '/', true, callback, data);
}

Expand Down Expand Up @@ -653,6 +662,7 @@ class VaultClient {
assert(false, 'arn, name, id, emailAddress and canonicalId IDs'
+ ' are exclusive');
}

this.request('POST', '/', true, callback, data);
}

Expand Down Expand Up @@ -1252,7 +1262,14 @@ class VaultClient {
let obj = null;
let err = null;
try {
obj = JSON.parse(ret);
obj = JSON.parse(ret, (key, value) => {
// keep old behavior for backward compatibility
if (key === 'maxQuota' && typeof value === 'string') {
return BigInt(value);
}

return value;
});
} catch (error) {
err = error;
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"engines": {
"node": ">=20"
},
"version": "8.5.4",
"version": "8.5.5",
"description": "Client library and binary for Vault, the user directory and key management service",
"main": "index.js",
"repository": "scality/vaultclient",
Expand All @@ -25,6 +25,7 @@
"eslint": "^9.9.1",
"eslint-config-scality": "scality/Guidelines#8.3.0",
"mocha": "^10.7.3",
"sinon": "^21.0.1",
"typescript": "^5.5.4"
},
"scripts": {
Expand Down
86 changes: 86 additions & 0 deletions tests/unit/clientUtilities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
'use strict';

const assert = require('assert');
const IAMClient = require('../../lib/IAMClient');

describe('client utilities', () => {
let client;

beforeEach(() => {
client = new IAMClient('127.0.0.1', 8500);
});

describe('parseObj', () => {
it('should convert maxQuota string to bigint', () => {
const json = JSON.stringify({ maxQuota: '9007199254740992' }); // > MAX_SAFE_INTEGER
const result = client.parseObj(json);

assert.strictEqual(typeof result.maxQuota, 'bigint');
assert.strictEqual(result.maxQuota, 9007199254740992n);
});

it('should keep maxQuota as number when received as number (backward compatibility)', () => {
const json = JSON.stringify({ maxQuota: 1000000 });
const result = client.parseObj(json);

assert.strictEqual(typeof result.maxQuota, 'number');
assert.strictEqual(result.maxQuota, 1000000);
});

it('should convert maxQuota in nested structures', () => {
const json = JSON.stringify({
Accounts: [
{ id: '1', name: 'account1', maxQuota: '100000' },
{ id: '2', name: 'account2', maxQuota: '200000' },
],
});
const result = client.parseObj(json);

assert.strictEqual(typeof result.Accounts[0].maxQuota, 'bigint');
assert.strictEqual(result.Accounts[0].maxQuota, 100000n);
assert.strictEqual(result.Accounts[1].maxQuota, 200000n);
});

it('should handle maxQuota value of "0"', () => {
const json = JSON.stringify({ maxQuota: '0' });
const result = client.parseObj(json);

assert.strictEqual(typeof result.maxQuota, 'bigint');
assert.strictEqual(result.maxQuota, 0n);
});

it('should return error for invalid JSON', () => {
const invalidJson = '{ invalid json }';
const result = client.parseObj(invalidJson);

assert(result instanceof Error);
});
});

describe('handleResponse', () => {
const res = { statusCode: 200, statusMessage: 'OK' };

it('should convert maxQuota in successful JSON response', done => {
const ret = JSON.stringify({ maxQuota: '9999999999999' });

client.handleResponse(res, ret, {}, (err, obj, code, message) => {
assert.strictEqual(err, null);
assert.strictEqual(typeof obj.maxQuota, 'bigint');
assert.strictEqual(obj.maxQuota, 9999999999999n);
assert.strictEqual(code, 200);
assert.strictEqual(message, 'OK');
done();
});
});

it('should handle empty response', done => {
const ret = '';

client.handleResponse(res, ret, {}, (err, obj) => {
assert.strictEqual(err, null);
assert.deepStrictEqual(obj, {});
done();
});
});
});
});
Loading