Skip to content

Commit 3ab2548

Browse files
authored
fixed race condition when refreshing access token. (#4084)
1 parent 40edcbf commit 3ab2548

File tree

2 files changed

+70
-54
lines changed

2 files changed

+70
-54
lines changed

Node/core/lib/bots/ChatConnector.js

+34-27
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ var request = require("request");
88
var async = require("async");
99
var jwt = require("jsonwebtoken");
1010
var zlib = require("zlib");
11+
var Promise = require("promise");
1112
var urlJoin = require("url-join");
1213
var pjson = require('../../package.json');
1314
var MAX_DATA_LENGTH = 65000;
@@ -569,33 +570,39 @@ var ChatConnector = (function () {
569570
};
570571
ChatConnector.prototype.refreshAccessToken = function (cb) {
571572
var _this = this;
572-
var opt = {
573-
method: 'POST',
574-
url: this.settings.endpoint.refreshEndpoint,
575-
form: {
576-
grant_type: 'client_credentials',
577-
client_id: this.settings.appId,
578-
client_secret: this.settings.appPassword,
579-
scope: this.settings.endpoint.refreshScope
580-
}
581-
};
582-
this.addUserAgent(opt);
583-
request(opt, function (err, response, body) {
584-
if (!err) {
585-
if (body && response.statusCode < 300) {
586-
var oauthResponse = JSON.parse(body);
587-
_this.accessToken = oauthResponse.access_token;
588-
_this.accessTokenExpires = new Date().getTime() + ((oauthResponse.expires_in - 300) * 1000);
589-
cb(null, _this.accessToken);
590-
}
591-
else {
592-
cb(new Error('Refresh access token failed with status code: ' + response.statusCode), null);
593-
}
594-
}
595-
else {
596-
cb(err, null);
597-
}
598-
});
573+
if (!this.refreshingToken) {
574+
this.refreshingToken = new Promise(function (resolve, reject) {
575+
var opt = {
576+
method: 'POST',
577+
url: _this.settings.endpoint.refreshEndpoint,
578+
form: {
579+
grant_type: 'client_credentials',
580+
client_id: _this.settings.appId,
581+
client_secret: _this.settings.appPassword,
582+
scope: _this.settings.endpoint.refreshScope
583+
}
584+
};
585+
_this.addUserAgent(opt);
586+
request(opt, function (err, response, body) {
587+
if (!err) {
588+
if (body && response.statusCode < 300) {
589+
var oauthResponse = JSON.parse(body);
590+
_this.accessToken = oauthResponse.access_token;
591+
_this.accessTokenExpires = new Date().getTime() + ((oauthResponse.expires_in - 300) * 1000);
592+
_this.refreshingToken = undefined;
593+
resolve(_this.accessToken);
594+
}
595+
else {
596+
reject(new Error('Refresh access token failed with status code: ' + response.statusCode));
597+
}
598+
}
599+
else {
600+
reject(err);
601+
}
602+
});
603+
});
604+
}
605+
this.refreshingToken.then(function (token) { return cb(null, token); }, function (err) { return cb(err, null); });
599606
};
600607
ChatConnector.prototype.getAccessToken = function (cb) {
601608
var _this = this;

Node/core/src/bots/ChatConnector.ts

+36-27
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import * as url from 'url';
4444
import * as http from 'http';
4545
import * as jwt from 'jsonwebtoken';
4646
import * as zlib from 'zlib';
47+
import * as Promise from 'promise';
4748
import urlJoin = require('url-join');
4849

4950
var pjson = require('../../package.json');
@@ -98,6 +99,7 @@ export class ChatConnector implements IConnector, IBotStorage {
9899
private accessTokenExpires: number;
99100
private botConnectorOpenIdMetadata: OpenIdMetadata;
100101
private emulatorOpenIdMetadata: OpenIdMetadata;
102+
private refreshingToken: Promise.IThenable<string>;
101103

102104
constructor(protected settings: IChatConnectorSettings = {}) {
103105
if (!this.settings.endpoint) {
@@ -693,33 +695,40 @@ export class ChatConnector implements IConnector, IBotStorage {
693695
}
694696

695697
private refreshAccessToken(cb: (err: Error, accessToken: string) => void): void {
696-
var opt: request.Options = {
697-
method: 'POST',
698-
url: this.settings.endpoint.refreshEndpoint,
699-
form: {
700-
grant_type: 'client_credentials',
701-
client_id: this.settings.appId,
702-
client_secret: this.settings.appPassword,
703-
scope: this.settings.endpoint.refreshScope
704-
}
705-
};
706-
this.addUserAgent(opt);
707-
request(opt, (err, response, body) => {
708-
if (!err) {
709-
if (body && response.statusCode < 300) {
710-
// Subtract 5 minutes from expires_in so they'll we'll get a
711-
// new token before it expires.
712-
var oauthResponse = JSON.parse(body);
713-
this.accessToken = oauthResponse.access_token;
714-
this.accessTokenExpires = new Date().getTime() + ((oauthResponse.expires_in - 300) * 1000);
715-
cb(null, this.accessToken);
716-
} else {
717-
cb(new Error('Refresh access token failed with status code: ' + response.statusCode), null);
718-
}
719-
} else {
720-
cb(err, null);
721-
}
722-
});
698+
// Get token only on first access. Other callers will block while waiting for token.
699+
if (!this.refreshingToken) {
700+
this.refreshingToken = new Promise<string>((resolve, reject) => {
701+
var opt: request.Options = {
702+
method: 'POST',
703+
url: this.settings.endpoint.refreshEndpoint,
704+
form: {
705+
grant_type: 'client_credentials',
706+
client_id: this.settings.appId,
707+
client_secret: this.settings.appPassword,
708+
scope: this.settings.endpoint.refreshScope
709+
}
710+
};
711+
this.addUserAgent(opt);
712+
request(opt, (err, response, body) => {
713+
if (!err) {
714+
if (body && response.statusCode < 300) {
715+
// Subtract 5 minutes from expires_in so they'll we'll get a
716+
// new token before it expires.
717+
var oauthResponse = JSON.parse(body);
718+
this.accessToken = oauthResponse.access_token;
719+
this.accessTokenExpires = new Date().getTime() + ((oauthResponse.expires_in - 300) * 1000);
720+
this.refreshingToken = undefined;
721+
resolve(this.accessToken);
722+
} else {
723+
reject(new Error('Refresh access token failed with status code: ' + response.statusCode));
724+
}
725+
} else {
726+
reject(err);
727+
}
728+
});
729+
})
730+
}
731+
this.refreshingToken.then((token) => cb(null, token), (err) => cb(err, null));
723732
}
724733

725734
public getAccessToken(cb: (err: Error, accessToken: string) => void): void {

0 commit comments

Comments
 (0)