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 6, 2024
1 parent c514d23 commit 9cd062b
Show file tree
Hide file tree
Showing 6 changed files with 409 additions and 31 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ jobs:
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
node-version: [8.x, 10.x, 12.x, 14.x, 16.x]
node-version: [8.x, 10.x, 12.x, 14.x, 16.x, 18.x, 20.x, 22.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
Expand All @@ -27,3 +28,8 @@ jobs:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm run ci

- name: Upload Coverage Report
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }} # required
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.securityToken) {
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
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"test": "mocha -R spec test/*.test.js",
"test-cov": "nyc -r=html -r=text -r=lcov mocha -t 3000 -R spec test/*.test.js",
"test-integration": "mocha -R spec test/*.integration.js",
"ci": "npm run lint && npm run test-cov && codecov"
"ci": "npm run lint && npm run test-cov"
},
"keywords": [
"Aliyun",
Expand All @@ -33,7 +33,6 @@
"index.js"
],
"devDependencies": {
"codecov": "^3.0.4",
"eslint": "^6.6.0",
"expect.js": "^0.3.1",
"mocha": "^4",
Expand Down
Loading

0 comments on commit 9cd062b

Please sign in to comment.