Skip to content
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

feat: Adds support for STS response not returning expires_in field. #1216

Merged
merged 23 commits into from
Aug 4, 2021
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f0895ef
Adds support for stsResponse doesnt return expiry time.
xil222 Jul 21, 2021
b245f0e
Adds support for STS response not returning expires_in field.
xil222 Jul 21, 2021
77ab907
Merge branch 'expireTime' of https://github.com/xil222/google-auth-li…
xil222 Jul 21, 2021
2277ed4
Merge branch 'master' into expireTime
bcoe Jul 22, 2021
bb2a430
meta: add nodejs-auth as codeowner (#1217)
bcoe Jul 23, 2021
b1f1094
samples: update TODO section (#1218)
averikitsch Jul 26, 2021
59f6017
feat(impersonated): add impersonated credentials auth (#1207)
bcoe Jul 29, 2021
ec4de59
chore: release 7.4.0 (#1220)
release-please[bot] Jul 29, 2021
549e518
fix(downscoped-client): bug fixes for downscoped client implementatio…
xil222 Jul 29, 2021
049c8c0
chore: release 7.4.1 (#1222)
release-please[bot] Jul 29, 2021
682a859
sample: update region tag (#1223)
averikitsch Jul 29, 2021
84d434b
Adds support for STS response not returning expires_in field.
xil222 Jul 21, 2021
241be27
Adds support for stsResponse doesnt return expiry time.
xil222 Jul 21, 2021
1c4b296
Remove extra tests in baseExternalClient.
xil222 Jul 29, 2021
22afcec
merge origin/expireTime
xil222 Jul 29, 2021
a7aa1aa
Merge branch 'master' into expireTime
xil222 Jul 29, 2021
91a4927
change parsing expire_in logic in baseexternalclient.
xil222 Jul 30, 2021
c87fa70
Merge branch 'expireTime' of https://github.com/xil222/google-auth-li…
xil222 Jul 30, 2021
cc0e1d6
Merge branch 'master' into expireTime
xil222 Jul 30, 2021
26d194f
Add testAuthClient setter and tweak some tests.
xil222 Aug 3, 2021
5233611
Merge branch 'expireTime' of https://github.com/xil222/google-auth-li…
xil222 Aug 3, 2021
af59d70
add emit events for some tests.
xil222 Aug 4, 2021
d314a21
Merge branch 'master' into expireTime
bcoe Aug 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/auth/baseexternalclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,13 +377,19 @@ export abstract class BaseExternalAccountClient extends AuthClient {
this.cachedAccessToken = await this.getImpersonatedAccessToken(
stsResponse.access_token
);
} else {
} else if (stsResponse.expires_in) {
// Save response in cached access token.
this.cachedAccessToken = {
access_token: stsResponse.access_token,
expiry_date: new Date().getTime() + stsResponse.expires_in * 1000,
res: stsResponse.res,
};
} else {
xil222 marked this conversation as resolved.
Show resolved Hide resolved
// Save response in cached access token.
this.cachedAccessToken = {
access_token: stsResponse.access_token,
res: stsResponse.res,
};
}

// Save credentials.
Expand Down
14 changes: 13 additions & 1 deletion src/auth/downscopedclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,22 @@ export class DownscopedClient extends AuthClient {
this.credentialAccessBoundary
);

/**
* The STS endpoint will only return the expiration time for the downscoped
* access token if the original access token represents a service account.
* The downscoped token's expiration time will always match the source
* credential expiration. When no expires_in is returned, we can copy the
* source credential's expiration time.
*/
const sourceCredExpireDate =
xil222 marked this conversation as resolved.
Show resolved Hide resolved
this.authClient.credentials?.expiry_date || null;
const expiryDate = stsResponse.expires_in
? new Date().getTime() + stsResponse.expires_in * 1000
: sourceCredExpireDate;
// Save response in cached access token.
this.cachedDownscopedAccessToken = {
access_token: stsResponse.access_token,
expiry_date: new Date().getTime() + stsResponse.expires_in * 1000,
expiry_date: expiryDate,
res: stsResponse.res,
};

Expand Down
2 changes: 1 addition & 1 deletion src/auth/stscredentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export interface StsSuccessfulResponse {
access_token: string;
issued_token_type: string;
token_type: string;
expires_in: number;
expires_in?: number;
refresh_token?: string;
scope?: string;
res?: GaxiosResponse | null;
Expand Down
34 changes: 34 additions & 0 deletions test/test.baseexternalclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,40 @@ describe('BaseExternalAccountClient', () => {
scope.done();
});

it('should return credential with no expiry date if STS response does not return one', async () => {
const stsSuccessfulResponse2 = Object.assign({}, stsSuccessfulResponse);
delete stsSuccessfulResponse2.expires_in;

const scope = mockStsTokenExchange([
{
statusCode: 200,
response: stsSuccessfulResponse2,
request: {
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
audience,
scope: 'https://www.googleapis.com/auth/cloud-platform',
requested_token_type:
'urn:ietf:params:oauth:token-type:access_token',
subject_token: 'subject_token_0',
subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
},
},
]);

const client = new TestExternalAccountClient(
externalAccountOptionsWithCreds
);
const actualResponse = await client.getAccessToken();

// Confirm raw GaxiosResponse appended to response.
assertGaxiosResponsePresent(actualResponse);
delete actualResponse.res;
assert.deepStrictEqual(actualResponse, {
xil222 marked this conversation as resolved.
Show resolved Hide resolved
token: stsSuccessfulResponse2.access_token,
});
scope.done();
});

it('should handle underlying token exchange errors', async () => {
const errorResponse: OAuthErrorResponse = {
error: 'invalid_request',
Expand Down
92 changes: 91 additions & 1 deletion test/test.downscopedclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,9 +411,11 @@ describe('DownscopedClient', () => {

clock.tick(1);
const refreshedTokenResponse = await downscopedClient.getAccessToken();

const responseExpiresIn = stsSuccessfulResponse.expires_in as number;
const expectedExpirationTime =
credentials.expiry_date +
stsSuccessfulResponse.expires_in * 1000 -
responseExpiresIn * 1000 -
EXPIRATION_TIME_OFFSET;
assert.deepStrictEqual(
refreshedTokenResponse.token,
Expand Down Expand Up @@ -534,12 +536,100 @@ describe('DownscopedClient', () => {
it('should throw when the source AuthClient rejects on token request', async () => {
const expectedError = new Error('Cannot get subject token.');
client.throwError = true;

const downscopedClient = new DownscopedClient(
client,
testClientAccessBoundary
);
await assert.rejects(downscopedClient.getAccessToken(), expectedError);
});

it('should copy source cred expiry time if STS response does not return expiry time', async () => {
xil222 marked this conversation as resolved.
Show resolved Hide resolved
const now = new Date().getTime();
const credentials = {
access_token: 'ACCESS_TOKEN',
expiry_date: now + ONE_HOUR_IN_SECS * 1000,
};
xil222 marked this conversation as resolved.
Show resolved Hide resolved
const stsSuccessfulResponseWithoutExpireInField = Object.assign(
stsSuccessfulResponse,
{}
);
xil222 marked this conversation as resolved.
Show resolved Hide resolved
delete stsSuccessfulResponseWithoutExpireInField.expires_in;
const scope = mockStsTokenExchange([
{
statusCode: 200,
response: stsSuccessfulResponseWithoutExpireInField,
request: {
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
requested_token_type:
'urn:ietf:params:oauth:token-type:access_token',
subject_token: 'subject_token_0',
subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',
options:
testClientAccessBoundary &&
xil222 marked this conversation as resolved.
Show resolved Hide resolved
JSON.stringify(testClientAccessBoundary),
},
},
]);

client.setCredentials(credentials);
const downscopedClient = new DownscopedClient(
client,
testClientAccessBoundary
);

const tokenResponse = await downscopedClient.getAccessToken();
assert.deepStrictEqual(
tokenResponse.token,
xil222 marked this conversation as resolved.
Show resolved Hide resolved
stsSuccessfulResponseWithoutExpireInField.access_token
);
assert.strictEqual(
downscopedClient.credentials.expiry_date,
credentials.expiry_date
);
scope.done();
});

it('should have no expiry date if source cred has no expiry time and STS response does not return one', async () => {
const credentials = {
access_token: 'ACCESS_TOKEN',
};
xil222 marked this conversation as resolved.
Show resolved Hide resolved
const stsSuccessfulResponseWithoutExpireInField = Object.assign(
stsSuccessfulResponse,
{}
xil222 marked this conversation as resolved.
Show resolved Hide resolved
);
delete stsSuccessfulResponseWithoutExpireInField.expires_in;
const scope = mockStsTokenExchange([
{
statusCode: 200,
response: stsSuccessfulResponseWithoutExpireInField,
request: {
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
requested_token_type:
'urn:ietf:params:oauth:token-type:access_token',
subject_token: 'subject_token_0',
subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',
options:
testClientAccessBoundary &&
xil222 marked this conversation as resolved.
Show resolved Hide resolved
JSON.stringify(testClientAccessBoundary),
},
},
]);

client.setCredentials(credentials);
const downscopedClient = new DownscopedClient(
client,
testClientAccessBoundary
);

const tokenResponse = await downscopedClient.getAccessToken();
assert.deepStrictEqual(
tokenResponse.token,
stsSuccessfulResponseWithoutExpireInField.access_token
);
assert.strictEqual(downscopedClient.credentials.expiry_date, null);
scope.done();
});
});

describe('getRequestHeader()', () => {
Expand Down