Skip to content

feat: support credentials providers #100

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 6, 2024
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
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
Loading