Skip to content

Commit

Permalink
feat(refresh): add forceRefreshOnFailure flag for refreshing token on…
Browse files Browse the repository at this point in the history
… error (#790)
  • Loading branch information
skeggse authored and SurferJeffAtGoogle committed Oct 14, 2019
1 parent d0b7f8b commit 54cf477
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 7 deletions.
5 changes: 4 additions & 1 deletion src/auth/jwtclient.ts
Expand Up @@ -83,7 +83,10 @@ export class JWT extends OAuth2Client {
optionsOrEmail && typeof optionsOrEmail === 'object'
? optionsOrEmail
: {email: optionsOrEmail, keyFile, key, keyId, scopes, subject};
super({eagerRefreshThresholdMillis: opts.eagerRefreshThresholdMillis});
super({
eagerRefreshThresholdMillis: opts.eagerRefreshThresholdMillis,
forceRefreshOnFailure: opts.forceRefreshOnFailure,
});
this.email = opts.email;
this.keyFile = opts.keyFile;
this.key = opts.key;
Expand Down
22 changes: 17 additions & 5 deletions src/auth/oauth2client.ts
Expand Up @@ -354,6 +354,12 @@ export interface RefreshOptions {
// milliseconds from expiring".
// Defaults to a value of 300000 (5 minutes).
eagerRefreshThresholdMillis?: number;

// Whether to attempt to lazily refresh tokens on 401/403 responses
// even if an attempt is made to refresh the token preemptively based
// on the expiry_date.
// Defaults to false.
forceRefreshOnFailure?: boolean;
}

export class OAuth2Client extends AuthClient {
Expand All @@ -375,6 +381,8 @@ export class OAuth2Client extends AuthClient {

eagerRefreshThresholdMillis: number;

forceRefreshOnFailure: boolean;

/**
* Handles OAuth2 flow for Google APIs.
*
Expand Down Expand Up @@ -402,6 +410,7 @@ export class OAuth2Client extends AuthClient {
this.redirectUri = opts.redirectUri;
this.eagerRefreshThresholdMillis =
opts.eagerRefreshThresholdMillis || 5 * 60 * 1000;
this.forceRefreshOnFailure = !!opts.forceRefreshOnFailure;
}

protected static readonly GOOGLE_TOKEN_INFO_URL =
Expand Down Expand Up @@ -896,15 +905,18 @@ export class OAuth2Client extends AuthClient {
// - We haven't already retried. It only makes sense to retry once.
// - The response was a 401 or a 403
// - The request didn't send a readableStream
// - An access_token and refresh_token were available, but no
// expiry_date was availabe. This can happen when developers stash
// the access_token and refresh_token for later use, but the
// access_token fails on the first try because it's expired.
// - An access_token and refresh_token were available, but either no
// expiry_date was available or the forceRefreshOnFailure flag is set.
// The absent expiry_date case can happen when developers stash the
// access_token and refresh_token for later use, but the access_token
// fails on the first try because it's expired. Some developers may
// choose to enable forceRefreshOnFailure to mitigate time-related
// errors.
const mayRequireRefresh =
this.credentials &&
this.credentials.access_token &&
this.credentials.refresh_token &&
!this.credentials.expiry_date;
(!this.credentials.expiry_date || this.forceRefreshOnFailure);
const isReadableStream = res.config.data instanceof stream.Readable;
const isAuthErr = statusCode === 401 || statusCode === 403;
if (!retry && isAuthErr && !isReadableStream && mayRequireRefresh) {
Expand Down
5 changes: 4 additions & 1 deletion src/auth/refreshclient.ts
Expand Up @@ -44,7 +44,8 @@ export class UserRefreshClient extends OAuth2Client {
optionsOrClientId?: string | UserRefreshClientOptions,
clientSecret?: string,
refreshToken?: string,
eagerRefreshThresholdMillis?: number
eagerRefreshThresholdMillis?: number,
forceRefreshOnFailure?: boolean
) {
const opts =
optionsOrClientId && typeof optionsOrClientId === 'object'
Expand All @@ -54,11 +55,13 @@ export class UserRefreshClient extends OAuth2Client {
clientSecret,
refreshToken,
eagerRefreshThresholdMillis,
forceRefreshOnFailure,
};
super({
clientId: opts.clientId,
clientSecret: opts.clientSecret,
eagerRefreshThresholdMillis: opts.eagerRefreshThresholdMillis,
forceRefreshOnFailure: opts.forceRefreshOnFailure,
});
this._refreshToken = opts.refreshToken;
}
Expand Down
26 changes: 26 additions & 0 deletions test/test.oauth2.ts
Expand Up @@ -1094,6 +1094,32 @@ describe(__filename, () => {
done();
});
});

it(`should refresh token if the server returns ${code} with forceRefreshOnFailure`, done => {
const client = new OAuth2Client({
clientId: CLIENT_ID,
clientSecret: CLIENT_SECRET,
redirectUri: REDIRECT_URI,
forceRefreshOnFailure: true,
});
const scope = nock('http://example.com')
.get('/access')
.reply(code, {
error: {code, message: 'Invalid Credentials'},
});
const scopes = mockExample();
client.credentials = {
access_token: 'initial-access-token',
refresh_token: 'refresh-token-placeholder',
expiry_date: new Date().getTime() + 500000,
};
client.request({url: 'http://example.com/access'}, err => {
scope.done();
scopes[0].done();
assert.strictEqual('abc123', client.credentials.access_token);
done();
});
});
});

it('should not retry requests with streaming data', done => {
Expand Down

0 comments on commit 54cf477

Please sign in to comment.