Skip to content

Commit

Permalink
fixed race condition when refreshing access token. (#4084)
Browse files Browse the repository at this point in the history
  • Loading branch information
Stevenic authored Jan 23, 2018
1 parent 40edcbf commit 3ab2548
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 54 deletions.
61 changes: 34 additions & 27 deletions Node/core/lib/bots/ChatConnector.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ var request = require("request");
var async = require("async");
var jwt = require("jsonwebtoken");
var zlib = require("zlib");
var Promise = require("promise");
var urlJoin = require("url-join");
var pjson = require('../../package.json');
var MAX_DATA_LENGTH = 65000;
Expand Down Expand Up @@ -569,33 +570,39 @@ var ChatConnector = (function () {
};
ChatConnector.prototype.refreshAccessToken = function (cb) {
var _this = this;
var opt = {
method: 'POST',
url: this.settings.endpoint.refreshEndpoint,
form: {
grant_type: 'client_credentials',
client_id: this.settings.appId,
client_secret: this.settings.appPassword,
scope: this.settings.endpoint.refreshScope
}
};
this.addUserAgent(opt);
request(opt, function (err, response, body) {
if (!err) {
if (body && response.statusCode < 300) {
var oauthResponse = JSON.parse(body);
_this.accessToken = oauthResponse.access_token;
_this.accessTokenExpires = new Date().getTime() + ((oauthResponse.expires_in - 300) * 1000);
cb(null, _this.accessToken);
}
else {
cb(new Error('Refresh access token failed with status code: ' + response.statusCode), null);
}
}
else {
cb(err, null);
}
});
if (!this.refreshingToken) {
this.refreshingToken = new Promise(function (resolve, reject) {
var opt = {
method: 'POST',
url: _this.settings.endpoint.refreshEndpoint,
form: {
grant_type: 'client_credentials',
client_id: _this.settings.appId,
client_secret: _this.settings.appPassword,
scope: _this.settings.endpoint.refreshScope
}
};
_this.addUserAgent(opt);
request(opt, function (err, response, body) {
if (!err) {
if (body && response.statusCode < 300) {
var oauthResponse = JSON.parse(body);
_this.accessToken = oauthResponse.access_token;
_this.accessTokenExpires = new Date().getTime() + ((oauthResponse.expires_in - 300) * 1000);
_this.refreshingToken = undefined;
resolve(_this.accessToken);
}
else {
reject(new Error('Refresh access token failed with status code: ' + response.statusCode));
}
}
else {
reject(err);
}
});
});
}
this.refreshingToken.then(function (token) { return cb(null, token); }, function (err) { return cb(err, null); });
};
ChatConnector.prototype.getAccessToken = function (cb) {
var _this = this;
Expand Down
63 changes: 36 additions & 27 deletions Node/core/src/bots/ChatConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import * as url from 'url';
import * as http from 'http';
import * as jwt from 'jsonwebtoken';
import * as zlib from 'zlib';
import * as Promise from 'promise';
import urlJoin = require('url-join');

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

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

private refreshAccessToken(cb: (err: Error, accessToken: string) => void): void {
var opt: request.Options = {
method: 'POST',
url: this.settings.endpoint.refreshEndpoint,
form: {
grant_type: 'client_credentials',
client_id: this.settings.appId,
client_secret: this.settings.appPassword,
scope: this.settings.endpoint.refreshScope
}
};
this.addUserAgent(opt);
request(opt, (err, response, body) => {
if (!err) {
if (body && response.statusCode < 300) {
// Subtract 5 minutes from expires_in so they'll we'll get a
// new token before it expires.
var oauthResponse = JSON.parse(body);
this.accessToken = oauthResponse.access_token;
this.accessTokenExpires = new Date().getTime() + ((oauthResponse.expires_in - 300) * 1000);
cb(null, this.accessToken);
} else {
cb(new Error('Refresh access token failed with status code: ' + response.statusCode), null);
}
} else {
cb(err, null);
}
});
// Get token only on first access. Other callers will block while waiting for token.
if (!this.refreshingToken) {
this.refreshingToken = new Promise<string>((resolve, reject) => {
var opt: request.Options = {
method: 'POST',
url: this.settings.endpoint.refreshEndpoint,
form: {
grant_type: 'client_credentials',
client_id: this.settings.appId,
client_secret: this.settings.appPassword,
scope: this.settings.endpoint.refreshScope
}
};
this.addUserAgent(opt);
request(opt, (err, response, body) => {
if (!err) {
if (body && response.statusCode < 300) {
// Subtract 5 minutes from expires_in so they'll we'll get a
// new token before it expires.
var oauthResponse = JSON.parse(body);
this.accessToken = oauthResponse.access_token;
this.accessTokenExpires = new Date().getTime() + ((oauthResponse.expires_in - 300) * 1000);
this.refreshingToken = undefined;
resolve(this.accessToken);
} else {
reject(new Error('Refresh access token failed with status code: ' + response.statusCode));
}
} else {
reject(err);
}
});
})
}
this.refreshingToken.then((token) => cb(null, token), (err) => cb(err, null));
}

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

0 comments on commit 3ab2548

Please sign in to comment.