Skip to content

Commit

Permalink
feat: support credentials providers
Browse files Browse the repository at this point in the history
  • Loading branch information
yndu13 committed Dec 4, 2024
1 parent c514d23 commit 71f5753
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 27 deletions.
1 change: 1 addition & 0 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
node-version: [8.x, 10.x, 12.x, 14.x, 16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
Expand Down
61 changes: 51 additions & 10 deletions lib/roa.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,30 @@ class ROAClient {
throw new Error(`"config.endpoint" must starts with 'https://' or 'http://'.`);
}
assert(config.apiVersion, 'must pass "config.apiVersion"');
assert(config.accessKeyId, 'must pass "config.accessKeyId"');
assert(config.accessKeySecret, 'must pass "config.accessKeySecret"');
if (config.credentialsProvider) {
if (typeof config.credentialsProvider.getCredentials !== 'function') {
throw new Error(`must pass "config.credentialsProvider" with function "getCredentials()"`);
}
this.credentialsProvider = config.credentialsProvider;
} else {
assert(config.accessKeyId, 'must pass "config.accessKeyId"');
assert(config.accessKeySecret, 'must pass "config.accessKeySecret"');
this.accessKeyId = config.accessKeyId;
this.accessKeySecret = config.accessKeySecret;
this.securityToken = config.securityToken;
this.credentialsProvider = {
getCredentials: async () => {
return {
accessKeyId: config.accessKeyId,
accessKeySecret: config.accessKeySecret,
securityToken: config.securityToken,
};
}
};
}

this.endpoint = config.endpoint;

this.apiVersion = config.apiVersion;
this.accessKeyId = config.accessKeyId;
this.accessKeySecret = config.accessKeySecret;
this.securityToken = config.securityToken;
this.host = url.parse(this.endpoint).hostname;
this.opts = config.opts;
var httpModule = this.endpoint.startsWith('https://') ? require('https') : require('http');
Expand Down Expand Up @@ -163,9 +178,31 @@ class ROAClient {
}

async request(method, uriPattern, query = {}, body = '', headers = {}, opts = {}) {
const credentials = await this.credentialsProvider.getCredentials();

const now = new Date();
var defaultHeaders = {
accept: 'application/json',
date: now.toGMTString(),
host: this.host,
'x-acs-signature-nonce': kitx.makeNonce(),
'x-acs-version': this.apiVersion,
'user-agent': helper.DEFAULT_UA,
'x-sdk-client': helper.DEFAULT_CLIENT
};
if (credentials && credentials.accessKeyId && credentials.accessKeySecret) {
defaultHeaders['x-acs-signature-method'] = 'HMAC-SHA1';
defaultHeaders['x-acs-signature-version'] = '1.0';
if (credentials.securityToken) {
defaultHeaders['x-acs-accesskey-id'] = credentials.accessKeyId;
defaultHeaders['x-acs-security-token'] = credentials.securityToken;
}
}

var mixHeaders = Object.assign(defaultHeaders, keyLowerify(headers));

var postBody = null;

var mixHeaders = Object.assign(this.buildHeaders(), keyLowerify(headers));
postBody = Buffer.from(body, 'utf8');
mixHeaders['content-md5'] = kitx.md5(postBody, 'base64');
mixHeaders['content-length'] = postBody.length;
Expand All @@ -175,9 +212,13 @@ class ROAClient {
url += `?${querystring.stringify(query)}`;
}

const stringToSign = buildStringToSign(method, uriPattern, mixHeaders, query);
debug('stringToSign: %s', stringToSign);
mixHeaders['authorization'] = this.buildAuthorization(stringToSign);
if (credentials && credentials.accessKeyId && credentials.accessKeySecret) {
const stringToSign = buildStringToSign(method, uriPattern, mixHeaders, query);
debug('stringToSign: %s', stringToSign);
const utf8Buff = Buffer.from(stringToSign, 'utf8');
const signature = kitx.sha1(utf8Buff, credentials.accessKeySecret, 'base64');
mixHeaders['authorization'] = `acs ${credentials.accessKeyId}:${signature}`;
}

const options = Object.assign({
method,
Expand Down
68 changes: 51 additions & 17 deletions lib/rpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,36 @@ class RPCClient {
throw new Error(`"config.endpoint" must starts with 'https://' or 'http://'.`);
}
assert(config.apiVersion, 'must pass "config.apiVersion"');
assert(config.accessKeyId, 'must pass "config.accessKeyId"');
var accessKeySecret = config.secretAccessKey || config.accessKeySecret;
assert(accessKeySecret, 'must pass "config.accessKeySecret"');
if (config.credentialsProvider) {
if (typeof config.credentialsProvider.getCredentials !== 'function') {
throw new Error(`must pass "config.credentialsProvider" with function "getCredentials()"`);
}
this.credentialsProvider = config.credentialsProvider;
} else {
assert(config.accessKeyId, 'must pass "config.accessKeyId"');
var accessKeySecret = config.secretAccessKey || config.accessKeySecret;
assert(accessKeySecret, 'must pass "config.accessKeySecret"');
this.accessKeyId = config.accessKeyId;
this.accessKeySecret = accessKeySecret;
this.securityToken = config.securityToken;
this.credentialsProvider = {
getCredentials: async () => {
return {
accessKeyId: config.accessKeyId,
accessKeySecret: accessKeySecret,
securityToken: config.securityToken,
};
}
};
}


if (config.endpoint.endsWith('/')) {
config.endpoint = config.endpoint.slice(0, -1);
}

this.endpoint = config.endpoint;
this.apiVersion = config.apiVersion;
this.accessKeyId = config.accessKeyId;
this.accessKeySecret = accessKeySecret;
this.securityToken = config.securityToken;
this.verbose = verbose === true;
// 非 codes 里的值,将抛出异常
this.codes = new Set([200, '200', 'OK', 'Success', 'success']);
Expand All @@ -145,6 +162,7 @@ class RPCClient {
}

async request(action, params = {}, opts = {}) {
const credentials = await this.credentialsProvider.getCredentials();
// 1. compose params and opts
opts = Object.assign({
headers: {
Expand All @@ -164,20 +182,36 @@ class RPCClient {
if (opts.formatParams !== false) {
params = formatParams(params);
}
const defaults = this._buildParams();
params = Object.assign({Action: action}, defaults, params);
const defaultParams = {
Format: 'JSON',
Timestamp: timestamp(),
Version: this.apiVersion,
};
if (credentials && credentials.accessKeyId && credentials.accessKeySecret) {
defaultParams.SignatureMethod = 'HMAC-SHA1';
defaultParams.SignatureVersion = '1.0';
defaultParams.SignatureNonce = kitx.makeNonce();
defaultParams.AccessKeyId = credentials.accessKeyId;
if (credentials.accessKeySecret) {
defaultParams.SecurityToken = credentials.securityToken;
}
}
params = Object.assign({ Action: action }, defaultParams, params);

// 2. caculate signature
const method = (opts.method || 'GET').toUpperCase();
const normalized = normalize(params);
const canonicalized = canonicalize(normalized);
// 2.1 get string to sign
const stringToSign = `${method}&${encode('/')}&${encode(canonicalized)}`;
// 2.2 get signature
const key = this.accessKeySecret + '&';
const signature = kitx.sha1(stringToSign, key, 'base64');
// add signature
normalized.push(['Signature', encode(signature)]);
// 2. caculate signature
if (credentials && credentials.accessKeyId && credentials.accessKeySecret) {
const canonicalized = canonicalize(normalized);
// 2.1 get string to sign
const stringToSign = `${method}&${encode('/')}&${encode(canonicalized)}`;
// 2.2 get signature
const key = credentials.accessKeySecret + '&';
const signature = kitx.sha1(stringToSign, key, 'base64');
// add signature
normalized.push(['Signature', encode(signature)]);
}

// 3. generate final url
const url = opts.method === 'POST' ? `${this.endpoint}/` : `${this.endpoint}/?${canonicalize(normalized)}`;
// 4. send request
Expand Down
20 changes: 20 additions & 0 deletions test/roa.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ describe('roa core', function () {
apiVersion: '1.0'
});
}).to.throwException(/must pass "config\.accessKeyId"/);
expect(function () {
new ROAClient({
endpoint: 'http://ecs.aliyuncs.com/',
apiVersion: '1.0',
credentialsProvider: null
});
}).to.throwException(/must pass "config\.accessKeyId"/);
});

it('should pass into "config.accessKeySecret"', function () {
Expand All @@ -56,6 +63,19 @@ describe('roa core', function () {
}).to.throwException(/must pass "config\.accessKeySecret"/);
});

it('should pass into "config.credentialsProvider" with getCredentials()', function () {
expect(function () {
new ROAClient({
endpoint: 'http://ecs.aliyuncs.com/',
apiVersion: '1.0',
credentialsProvider: {
accessKeyId: 'test',
accessKeySecret: 'test',
}
});
}).to.throwException(/must pass "config\.credentialsProvider" with function "getCredentials\(\)"/);
});

it('should ok with http protocol', function () {
const client = new ROAClient({
endpoint: 'http://ecs.aliyuncs.com/',
Expand Down
20 changes: 20 additions & 0 deletions test/rpc.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ describe('rpc core', function () {
apiVersion: '1.0'
});
}).to.throwException(/must pass "config\.accessKeyId"/);
expect(function () {
new RPCClient({
endpoint: 'http://ecs.aliyuncs.com/',
apiVersion: '1.0',
credentialsProvider: null
});
}).to.throwException(/must pass "config\.accessKeyId"/);
});

it('should pass into "config.accessKeySecret"', function () {
Expand All @@ -56,6 +63,19 @@ describe('rpc core', function () {
}).to.throwException(/must pass "config\.accessKeySecret"/);
});

it('should pass into "config.credentialsProvider" with getCredentials()', function () {
expect(function () {
new RPCClient({
endpoint: 'http://ecs.aliyuncs.com/',
apiVersion: '1.0',
credentialsProvider: {
accessKeyId: 'test',
accessKeySecret: 'test',
}
});
}).to.throwException(/must pass "config\.credentialsProvider" with function "getCredentials\(\)"/);
});

it('should ok with http endpoint', function () {
const client = new RPCClient({
endpoint: 'http://ecs.aliyuncs.com',
Expand Down

0 comments on commit 71f5753

Please sign in to comment.