Skip to content

Commit

Permalink
feat: auto set stsConfig when sts expires (#856)
Browse files Browse the repository at this point in the history
* feat: auto set stsConfig when sts expires

* test: add auto set sts test case
  • Loading branch information
weiyie committed Sep 27, 2020
1 parent 7435c56 commit 158d294
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 5 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ options:
- accessKeyId {String} access key you create on aliyun console website
- accessKeySecret {String} access secret you create
- [stsToken] {String} used by temporary authorization, detail [see](https://www.alibabacloud.com/help/doc-detail/32077.htm)
- [refreshSTSToken] {Function} used by auto set `stsToken``accessKeyId``accessKeySecret` when sts info expires. return value must be object contains `stsToken``accessKeyId``accessKeySecret`
- [bucket] {String} the default bucket you want to access
If you don't have any bucket, please use `putBucket()` create one first.
- [endpoint] {String} oss region domain. It takes priority over `region`.
Expand Down
11 changes: 11 additions & 0 deletions lib/browser/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const _initOptions = require('../common/client/initOptions');
const { createRequest } = require('../common/utils/createRequest');
const { encoder } = require('../common/utils/encoder');
const { getReqUrl } = require('../common/client/getReqUrl');
const { setSTSToken } = require('../common/utils/setSTSToken');

const globalHttpAgent = new AgentKeepalive();

Expand Down Expand Up @@ -221,6 +222,16 @@ proto.request = async function request(params) {
}

if (err) {
if (err.status === 403 && err.code === 'InvalidAccessKeyId' &&
this.options.accessKeyId.startsWith('STS.') &&
typeof this.options.refreshSTSToken === 'function') {
// prevent infinite loop, only trigger once within 10 seconds
if (!this._setOptions || Date.now() - this._setOptions > 10000) {
this._setOptions = Date.now();
await setSTSToken.call(this);
return this.request(params);
}
}
throw err;
}

Expand Down
12 changes: 12 additions & 0 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const _initOptions = require('./common/client/initOptions');
const { createRequest } = require('./common/utils/createRequest');
const { encoder } = require('./common/utils/encoder');
const { getReqUrl } = require('./common/client/getReqUrl');
const { setSTSToken } = require('./common/utils/setSTSToken');

const globalHttpAgent = new AgentKeepalive();
const globalHttpsAgent = new HttpsAgentKeepalive();
Expand Down Expand Up @@ -187,6 +188,17 @@ proto.request = async function request(params) {
// consume the response stream
await sendToWormhole(result.res);
}

if (err.status === 403 && err.code === 'InvalidAccessKeyId' &&
this.options.accessKeyId.startsWith('STS.') &&
typeof this.options.refreshSTSToken === 'function') {
// prevent infinite loop, only trigger once within 10 seconds
if (!this._setOptions || Date.now() - this._setOptions > 10000) {
this._setOptions = Date.now();
await setSTSToken.call(this);
return this.request(params);
}
}
throw err;
}

Expand Down
3 changes: 2 additions & 1 deletion lib/common/client/initOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ module.exports = function (options) {
cname: false,
isRequestPay: false,
sldEnable: false,
headerEncoding: 'utf-8'
headerEncoding: 'utf-8',
refreshSTSToken: null
}, options);

opts.accessKeyId = opts.accessKeyId.trim();
Expand Down
1 change: 1 addition & 0 deletions lib/common/utils/setSTSToken.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export declare function setSTSToken(this: any): Promise<void>;
25 changes: 25 additions & 0 deletions lib/common/utils/setSTSToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setSTSToken = void 0;
const formatObjKey_1 = require("./formatObjKey");
async function setSTSToken() {
if (!this.options)
this.options = {};
let credentials = await this.options.refreshSTSToken();
credentials = formatObjKey_1.formatObjKey(credentials, 'firstLowerCase');
if (credentials.securityToken) {
credentials.stsToken = credentials.securityToken;
}
checkCredentials(credentials);
Object.assign(this.options, credentials);
}
exports.setSTSToken = setSTSToken;
function checkCredentials(obj) {
const stsTokenKey = ['accessKeySecret', 'accessKeyId', 'stsToken'];
const objKeys = Object.keys(obj);
stsTokenKey.forEach(_ => {
if (!objKeys.find(key => key === _)) {
throw Error(`refreshSTSToken must return contains ${_}`);
}
});
}
22 changes: 22 additions & 0 deletions lib/common/utils/setSTSToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { formatObjKey } from './formatObjKey';

export async function setSTSToken(this: any) {
if (!this.options) this.options = {};
let credentials = await this.options.refreshSTSToken();
credentials = formatObjKey(credentials, 'firstLowerCase');
if (credentials.securityToken) {
credentials.stsToken = credentials.securityToken;
}
checkCredentials(credentials);
Object.assign(this.options, credentials);
}

function checkCredentials(obj) {
const stsTokenKey = ['accessKeySecret', 'accessKeyId', 'stsToken'];
const objKeys = Object.keys(obj);
stsTokenKey.forEach(_ => {
if (!objKeys.find(key => key === _)) {
throw Error(`refreshSTSToken must return contains ${_}`);
}
});
}
51 changes: 47 additions & 4 deletions test/node/sts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
const assert = require('assert');
const utils = require('./utils');
const sts = require('../..').STS;
const oss = require('../..');
const OSS = require('../..');
const { formatObjKey } = require('../../lib/common/utils/formatObjKey');
const config = require('../config').oss;
const stsConfig = require('../config').sts;
const mm = require('mm');
const { obj2xml } = require('../../lib/common/utils/obj2xml');

describe('test/sts.test.js', () => {
const { prefix } = utils;
Expand Down Expand Up @@ -93,7 +96,7 @@ describe('test/sts.test.js', () => {
let result = await stsClient.assumeRole(stsConfig.roleArn);
assert.equal(result.res.status, 200);

const ossClient = oss({
const ossClient = new OSS({
region: config.region,
accessKeyId: result.credentials.AccessKeyId,
accessKeySecret: result.credentials.AccessKeySecret,
Expand Down Expand Up @@ -131,7 +134,7 @@ describe('test/sts.test.js', () => {
let result = await stsClient.assumeRole(stsConfig.roleArn, policy);
assert.equal(result.res.status, 200);

let ossClient = oss({
let ossClient = new OSS({
region: config.region,
accessKeyId: result.credentials.AccessKeyId,
accessKeySecret: result.credentials.AccessKeySecret,
Expand Down Expand Up @@ -170,7 +173,7 @@ describe('test/sts.test.js', () => {
result = await stsClient.assumeRole(stsConfig.roleArn, policy);
assert.equal(result.res.status, 200);

ossClient = oss({
ossClient = new OSS({
region: config.region,
accessKeyId: result.credentials.AccessKeyId,
accessKeySecret: result.credentials.AccessKeySecret,
Expand All @@ -182,4 +185,44 @@ describe('test/sts.test.js', () => {
assert.equal(result.res.status, 200);
});
});

describe('refreshSTSToken()', () => {
const stsClient = sts(stsConfig);

let store;
before(async () => {
let { credentials } = await stsClient.assumeRole(stsConfig.roleArn);
credentials = formatObjKey(credentials, 'firstLowerCase');
const testRefreshSTSTokenConf = {
...credentials,
};
store = new OSS(testRefreshSTSTokenConf);
});
it('should refresh sts token when token is expired', async () => {
try {
store.options.refreshSTSToken = async () => {
mm.restore();
const { credentials } = await stsClient.assumeRole(stsConfig.roleArn);
return credentials;
};
// 模拟sts过期错误
const errData = {
Test: {
Code: 'InvalidAccessKeyId'
}
};
mm.error(store.urllib, 'request', {
status: 403,
headers: {},
data: obj2xml(errData)
});
const res = await store.listBuckets();
assert.strictEqual(res.res.status, 200);
assert(res.buckets && Array.isArray(res.buckets));
} catch (error) {
assert(false, error);
}
});
});

});
21 changes: 21 additions & 0 deletions test/node/utils/setSTSToken.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const { setSTSToken } = require('../../../lib/common/utils/setSTSToken');
const assert = require('assert');

describe('setSTSToken()', () => {
it('should set sts token', async () => {
const refreshSTSToken = () => {
return {
securityToken: 'test-securityToken',
accessKeyId: 'test-AccessKeyId',
accessKeySecret: 'test-accessKeySecret',
};
};
const test_set_token = { options: { refreshSTSToken } };

await setSTSToken.call(test_set_token);

assert(test_set_token.options.accessKeyId);
assert(test_set_token.options.accessKeySecret);
assert(test_set_token.options.securityToken);
});
});

0 comments on commit 158d294

Please sign in to comment.