Skip to content

Commit

Permalink
fix: Add cause to Cognito and STS error messages (#272)
Browse files Browse the repository at this point in the history
  • Loading branch information
qhanam committed Oct 25, 2022
1 parent 74e6436 commit 00563f5
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 146 deletions.
10 changes: 6 additions & 4 deletions src/dispatch/Authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class Authentication {
*/
private AnonymousStorageCredentialsProvider = async (): Promise<Credentials> => {
return new Promise<Credentials>((resolve, reject) => {
let credentials;
let credentials: Credentials;
try {
credentials = JSON.parse(localStorage.getItem(CRED_KEY)!);
} catch (e) {
Expand All @@ -91,8 +91,10 @@ export class Authentication {
// The expiration property of Credentials has a date type. Because the date was serialized as a string,
// we need to convert it back into a date, otherwise the AWS SDK signing middleware
// (@aws-sdk/middleware-signing) will throw an exception and no credentials will be returned.
credentials.expiration = new Date(credentials.expiration);
this.credentials = credentials;
this.credentials = {
...credentials,
expiration: new Date(credentials.expiration as Date)
};
if (this.renewCredentials()) {
// The credentials have expired.
return reject();
Expand Down Expand Up @@ -125,7 +127,7 @@ export class Authentication {
WebIdentityToken: getOpenIdTokenResponse.Token
})
)
.then((credentials) => {
.then((credentials: Credentials) => {
this.credentials = credentials;
try {
localStorage.setItem(CRED_KEY, JSON.stringify(credentials));
Expand Down
152 changes: 79 additions & 73 deletions src/dispatch/CognitoIdentityClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { HttpHandler, HttpRequest } from '@aws-sdk/protocol-http';
import { Credentials } from '@aws-sdk/types';
import { responseToJson } from './utils';

const METHOD = 'POST';
const CONTENT_TYPE = 'application/x-amz-json-1.1';
Expand All @@ -24,6 +25,27 @@ interface CognitoProviderParameters {
client: CognitoIdentityClient;
}

interface CognitoCredentials {
AccessKeyId: string;
Expiration: number;
SecretAccessKey: string;
SessionToken: string;
}

interface OpenIdTokenResponse {
IdentityId: string;
Token: string;
}

interface CredentialsResponse {
IdentityId: string;
Credentials: CognitoCredentials;
}

interface GetIdResponse {
IdentityId: string;
}

export const fromCognitoIdentityPool = (
params: CognitoProviderParameters
): (() => Promise<Credentials>) => {
Expand All @@ -45,87 +67,71 @@ export class CognitoIdentityClient {
}

public getId = async (request: { IdentityPoolId: string }) => {
const requestPayload = JSON.stringify(request);

const idRequest = this.getHttpRequest(GET_ID_TARGET, requestPayload);
return this.fetchRequestHandler
.handle(idRequest)
.then(({ response }) =>
response.body
.getReader()
.read()
.then(({ value }: { value: number[] }) =>
JSON.parse(String.fromCharCode.apply(null, value))
)
)
.catch(() => {
throw new Error('CWR: Failed to retrieve Cognito identity');
});
try {
const requestPayload = JSON.stringify(request);
const idRequest = this.getHttpRequest(
GET_ID_TARGET,
requestPayload
);
const { response } = await this.fetchRequestHandler.handle(
idRequest
);
return (await responseToJson(response)) as GetIdResponse;
} catch (e) {
throw new Error(`CWR: Failed to retrieve Cognito identity: ${e}`);
}
};

public getOpenIdToken = async (request: { IdentityId: string }) => {
const requestPayload = JSON.stringify(request);
const tokenRequest = this.getHttpRequest(
GET_TOKEN_TARGET,
requestPayload
);

return this.fetchRequestHandler
.handle(tokenRequest)
.then(({ response }) =>
response.body
.getReader()
.read()
.then(({ value }: { value: number[] }) =>
JSON.parse(String.fromCharCode.apply(null, value))
)
)
.catch(() => {
throw new Error('CWR: Failed to retrieve Cognito OpenId token');
});
try {
const requestPayload = JSON.stringify(request);
const tokenRequest = this.getHttpRequest(
GET_TOKEN_TARGET,
requestPayload
);
const { response } = await this.fetchRequestHandler.handle(
tokenRequest
);
return (await responseToJson(response)) as OpenIdTokenResponse;
} catch (e) {
throw new Error(
`CWR: Failed to retrieve Cognito OpenId token: ${e}`
);
}
};

public getCredentialsForIdentity = async (
identityId: string
): Promise<Credentials> => {
const requestPayload = JSON.stringify({ IdentityId: identityId });
const credentialRequest = this.getHttpRequest(
GET_CREDENTIALS_TARGET,
requestPayload
);

return this.fetchRequestHandler
.handle(credentialRequest)
.then(({ response }) => {
return response.body
.getReader()
.read()
.then(({ value }: { value: number[] }) => {
const { IdentityId, Credentials } = JSON.parse(
String.fromCharCode.apply(null, value)
);

const {
AccessKeyId,
Expiration,
SecretAccessKey,
SessionToken
} = Credentials;

return {
identityId: IdentityId as string,
accessKeyId: AccessKeyId as string,
secretAccessKey: SecretAccessKey as string,
sessionToken: SessionToken as string,
expiration: new Date(Expiration * 1000)
};
});
})
.catch(() => {
throw new Error(
'CWR: Failed to retrieve credentials for Cognito identity'
);
});
try {
const requestPayload = JSON.stringify({ IdentityId: identityId });
const credentialRequest = this.getHttpRequest(
GET_CREDENTIALS_TARGET,
requestPayload
);
const { response } = await this.fetchRequestHandler.handle(
credentialRequest
);
const { Credentials } = (await responseToJson(
response
)) as CredentialsResponse;
const {
AccessKeyId,
Expiration,
SecretAccessKey,
SessionToken
} = Credentials;
return {
accessKeyId: AccessKeyId as string,
secretAccessKey: SecretAccessKey as string,
sessionToken: SessionToken as string,
expiration: new Date(Expiration * 1000)
};
} catch (e) {
throw new Error(
`CWR: Failed to retrieve credentials for Cognito identity: ${e}`
);
}
};

private getHttpRequest = (target: string, payload: string) =>
Expand Down
101 changes: 48 additions & 53 deletions src/dispatch/StsClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { HttpHandler, HttpRequest } from '@aws-sdk/protocol-http';
import { CognitoIdentityClientConfig } from './CognitoIdentityClient';
import { Credentials } from '@aws-sdk/types';
import { responseToString } from './utils';

const METHOD = 'POST';
const CONTENT_TYPE = 'application/x-www-form-urlencoded';
Expand All @@ -22,59 +24,52 @@ export class StsClient {
this.fetchRequestHandler = config.fetchRequestHandler;
}

public assumeRoleWithWebIdentity = async (request: STSSendRequest) => {
const requestObject = {
...request,
Action: ACTION,
Version: VERSION
};
const encodedBody = new URLSearchParams(
Object.entries(requestObject)
).toString();

const STSRequest = new HttpRequest({
method: METHOD,
headers: {
'content-type': CONTENT_TYPE,
host: this.hostname
},
protocol: PROTOCOL,
hostname: this.hostname,
body: encodedBody
});

return this.fetchRequestHandler
.handle(STSRequest)
.then(({ response }) =>
response.body
.getReader()
.read()
.then(({ value }: { value: number[] }) => {
const xmlResponse = String.fromCharCode.apply(
null,
value
);

return {
accessKeyId: xmlResponse
.split('<AccessKeyId>')[1]
.split('</AccessKeyId>')[0],
secretAccessKey: xmlResponse
.split('<SecretAccessKey>')[1]
.split('</SecretAccessKey>')[0],
sessionToken: xmlResponse
.split('<SessionToken>')[1]
.split('</SessionToken>')[0],
expiration: new Date(
xmlResponse
.split('<Expiration>')[1]
.split('</Expiration>')[0]
)
};
})
)
.catch(() => {
throw new Error('CWR: Failed to retrieve credentials from STS');
public assumeRoleWithWebIdentity = async (
request: STSSendRequest
): Promise<Credentials> => {
try {
const requestObject = {
...request,
Action: ACTION,
Version: VERSION
};
const encodedBody = new URLSearchParams(
Object.entries(requestObject)
).toString();
const STSRequest = new HttpRequest({
method: METHOD,
headers: {
'content-type': CONTENT_TYPE,
host: this.hostname
},
protocol: PROTOCOL,
hostname: this.hostname,
body: encodedBody
});
const { response } = await this.fetchRequestHandler.handle(
STSRequest
);
const xmlResponse = await responseToString(response);
return {
accessKeyId: xmlResponse
.split('<AccessKeyId>')[1]
.split('</AccessKeyId>')[0],
secretAccessKey: xmlResponse
.split('<SecretAccessKey>')[1]
.split('</SecretAccessKey>')[0],
sessionToken: xmlResponse
.split('<SessionToken>')[1]
.split('</SessionToken>')[0],
expiration: new Date(
xmlResponse
.split('<Expiration>')[1]
.split('</Expiration>')[0]
)
} as Credentials;
} catch (e) {
throw new Error(
`CWR: Failed to retrieve credentials from STS: ${e}`
);
}
};
}
27 changes: 18 additions & 9 deletions src/dispatch/__tests__/CognitoIdentityClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,13 @@ describe('CognitoIdentityClient tests', () => {
});

test('when getCredentialsForIdentity error, then an error is thrown', async () => {
const e: Error = new Error('There are no credentials');
const e: Error = new Error('Something went wrong');
fetchHandler.mockImplementation(() => {
throw new Error('There are no credentials');
throw e;
});
const expected: Error = new Error(
`CWR: Failed to retrieve credentials for Cognito identity: ${e}`
);

// Init
const client: CognitoIdentityClient = new CognitoIdentityClient({
Expand All @@ -73,7 +76,7 @@ describe('CognitoIdentityClient tests', () => {
// Assert
return expect(
client.getCredentialsForIdentity('my-fake-identity-id')
).rejects.toEqual(e);
).rejects.toEqual(expected);
});

test('when getOpenIdToken is called, then token command is returned', async () => {
Expand Down Expand Up @@ -103,10 +106,13 @@ describe('CognitoIdentityClient tests', () => {
});

test('when getOpenIdToken error, then an error is thrown', async () => {
const e: Error = new Error('There are no credentials');
const e: Error = new Error('Something went wrong');
fetchHandler.mockImplementation(() => {
throw new Error('There are no credentials');
throw e;
});
const expected: Error = new Error(
`CWR: Failed to retrieve Cognito OpenId token: ${e}`
);

// Init
const client: CognitoIdentityClient = new CognitoIdentityClient({
Expand All @@ -119,7 +125,7 @@ describe('CognitoIdentityClient tests', () => {
client.getOpenIdToken({
IdentityId: 'my-fake-identity-id'
})
).rejects.toEqual(e);
).rejects.toEqual(expected);
});

test('when getId is called, then token command is returned', async () => {
Expand Down Expand Up @@ -148,10 +154,13 @@ describe('CognitoIdentityClient tests', () => {
});

test('when getId error, then an error is thrown', async () => {
const e: Error = new Error('There are no credentials');
const e = new Error('Something went wrong');
fetchHandler.mockImplementation(() => {
throw new Error('There are no credentials');
throw e;
});
const expected: Error = new Error(
`CWR: Failed to retrieve Cognito identity: ${e}`
);

// Init
const client: CognitoIdentityClient = new CognitoIdentityClient({
Expand All @@ -164,6 +173,6 @@ describe('CognitoIdentityClient tests', () => {
client.getId({
IdentityPoolId: 'my-fake-identity-pool-id'
})
).rejects.toEqual(e);
).rejects.toEqual(expected);
});
});

0 comments on commit 00563f5

Please sign in to comment.