Skip to content

Commit

Permalink
fix: more fine grained error handling (#39)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Due to the fact that we split up the error classes into more fine grain classes
  • Loading branch information
sahinvardar authored Dec 11, 2023
1 parent a70b84c commit 986322f
Show file tree
Hide file tree
Showing 3 changed files with 286 additions and 93 deletions.
124 changes: 83 additions & 41 deletions src/cognito-client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
import hashJs from 'hash.js';
import { BigInteger } from 'jsbn';
import { Buffer } from 'buffer';
import { CognitoCommonException, CognitoError, CognitoException } from './error.js';
import {
ChangePasswordException,
ChangePasswordError,
ConfirmForgotPasswordError,
ConfirmSignUpError,
ForgotPasswordError,
GlobalSignOutError,
InitAuthError,
ResendConfirmationCodeError,
RespondToAuthChallengeError,
RevokeTokenError,
SignUpError,
UpdateUserAttributesError,
VerifyUserAttributeError,
ConfirmForgotPasswordException,
ConfirmSignUpException,
ForgotPasswordException,
GlobalSignOutException,
InitiateAuthException,
ResendConfirmationException,
RespondToAuthChallengeException,
RevokeTokenException,
SignUpException,
UpdateUserAttributesException,
VerifyUserAttributeException
} from './error.js';

import {
calculateSecretHash,
Expand Down Expand Up @@ -330,7 +355,7 @@ export interface DecodedTokens {
* List of used and supported Cognito API calls.
* @see https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_Operations.html for more details
*/
export enum CognitoServiceTarget {
export enum ServiceTarget {
InitiateAuth = 'InitiateAuth',
RespondToAuthChallenge = 'RespondToAuthChallenge',
SignUp = 'SignUp',
Expand All @@ -349,7 +374,7 @@ export enum CognitoServiceTarget {
* Cognito supported federated identities public providers.
* @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-identity.html for more information.
*/
export enum CognitoIdentityProvider {
export enum IdentityProvider {
Cognito = 'COGNITO',
Google = 'Google',
Facebook = 'Facebook',
Expand Down Expand Up @@ -388,7 +413,7 @@ export function authResultToSession(authenticationResult: AuthenticationResult):
};
}

export async function cognitoRequest(body: object, serviceTarget: CognitoServiceTarget, cognitoEndpoint: string) {
export async function cognitoRequest(body: object, serviceTarget: ServiceTarget, cognitoEndpoint: string) {
const cognitoResponse = await fetch(cognitoEndpoint, {
headers: {
'x-amz-target': `AWSCognitoIdentityProviderService.${serviceTarget}`,
Expand Down Expand Up @@ -427,10 +452,35 @@ export async function cognitoRequest(body: object, serviceTarget: CognitoService
cognitoResponse.headers.get('X-Amzn-ErrorType') ??
cognitoResponseBody.code ??
cognitoResponseBody.__type ??
CognitoCommonException.Unknown
'Unknown'
);

throw new CognitoError(errorMessage, cognitoException as CognitoException);
switch (serviceTarget) {
case ServiceTarget.InitiateAuth:
throw new InitAuthError(errorMessage, cognitoException as InitiateAuthException);
case ServiceTarget.RespondToAuthChallenge:
throw new RespondToAuthChallengeError(errorMessage, cognitoException as RespondToAuthChallengeException);
case ServiceTarget.SignUp:
throw new SignUpError(errorMessage, cognitoException as SignUpException);
case ServiceTarget.ConfirmSignUp:
throw new ConfirmSignUpError(errorMessage, cognitoException as ConfirmSignUpException);
case ServiceTarget.ChangePassword:
throw new ChangePasswordError(errorMessage, cognitoException as ChangePasswordException);
case ServiceTarget.RevokeToken:
throw new RevokeTokenError(errorMessage, cognitoException as RevokeTokenException);
case ServiceTarget.ForgotPassword:
throw new ForgotPasswordError(errorMessage, cognitoException as ForgotPasswordException);
case ServiceTarget.ConfirmForgotPassword:
throw new ConfirmForgotPasswordError(errorMessage, cognitoException as ConfirmForgotPasswordException);
case ServiceTarget.ResendConfirmationCode:
throw new ResendConfirmationCodeError(errorMessage, cognitoException as ResendConfirmationException);
case ServiceTarget.UpdateUserAttributes:
throw new UpdateUserAttributesError(errorMessage, cognitoException as UpdateUserAttributesException);
case ServiceTarget.VerifyUserAttribute:
throw new VerifyUserAttributeError(errorMessage, cognitoException as VerifyUserAttributeException);
case ServiceTarget.GlobalSignOut:
throw new GlobalSignOutError(errorMessage, cognitoException as GlobalSignOutException);
}
}

/**
Expand Down Expand Up @@ -469,7 +519,7 @@ export class CognitoClient {
* @param username Username
* @param password Password
*
* @throws {InitiateAuthException}
* @throws {InitAuthError, CognitoRespondToAuthChallengeError}
*/
async authenticateUserSrp(username: string, password: string): Promise<Session> {
const smallA = await generateSmallA();
Expand All @@ -488,7 +538,7 @@ export class CognitoClient {

const challenge = (await cognitoRequest(
initiateAuthPayload,
CognitoServiceTarget.InitiateAuth,
ServiceTarget.InitiateAuth,
this.cognitoEndpoint
)) as ChallengeResponse;

Expand Down Expand Up @@ -530,7 +580,7 @@ export class CognitoClient {

const { AuthenticationResult } = await cognitoRequest(
respondToAuthChallengeRequest,
CognitoServiceTarget.RespondToAuthChallenge,
ServiceTarget.RespondToAuthChallenge,
this.cognitoEndpoint
);

Expand All @@ -544,7 +594,7 @@ export class CognitoClient {
*
* @param username Username
* @param password Password
* @throws {InitiateAuthException}
* @throws {InitAuthError}
*/
async authenticateUser(username: string, password: string): Promise<Session> {
const initiateAuthPayload: AuthIntiRequest = {
Expand All @@ -560,7 +610,7 @@ export class CognitoClient {

const { AuthenticationResult } = (await cognitoRequest(
initiateAuthPayload,
CognitoServiceTarget.InitiateAuth,
ServiceTarget.InitiateAuth,
this.cognitoEndpoint
)) as AuthenticationResponse;

Expand All @@ -574,7 +624,7 @@ export class CognitoClient {
* @param refreshToken Refresh token from a previous session.
* @param username Username is required when using a client secret and needs to be the cognito user id.
* @returns @see Session
* @throws {InitiateAuthException}
* @throws {InitAuthError}
*/
public async refreshSession(refreshToken: string, username?: string): Promise<Session> {
const refreshTokenPayload: AuthIntiRequest = {
Expand All @@ -590,7 +640,7 @@ export class CognitoClient {

const { AuthenticationResult } = (await cognitoRequest(
refreshTokenPayload,
CognitoServiceTarget.InitiateAuth,
ServiceTarget.InitiateAuth,
this.cognitoEndpoint
)) as AuthenticationResponse;

Expand All @@ -606,7 +656,7 @@ export class CognitoClient {
* @param username Username
* @param password Password
*
* @throws {SignUpException}
* @throws {SignUpError}
*/
async signUp(username: string, password: string, userAttributes?: UserAttribute[]) {
const signUpRequest: SignUpRequest = {
Expand All @@ -617,7 +667,7 @@ export class CognitoClient {
SecretHash: this.clientSecret && calculateSecretHash(this.clientSecret, this.userPoolClientId, username)
};

const data = await cognitoRequest(signUpRequest, CognitoServiceTarget.SignUp, this.cognitoEndpoint);
const data = await cognitoRequest(signUpRequest, ServiceTarget.SignUp, this.cognitoEndpoint);

return {
id: data.UserSub as string,
Expand All @@ -631,7 +681,7 @@ export class CognitoClient {
* @param username Username
* @param code Confirmation code the user gets through the registration E-Mail
*
* @throws {ConfirmSignUpException}
* @throws {ConfirmSignUpError}
*/
async confirmSignUp(username: string, code: string) {
const confirmSignUpRequest: ConfirmSignUpRequest = {
Expand All @@ -641,15 +691,15 @@ export class CognitoClient {
SecretHash: this.clientSecret && calculateSecretHash(this.clientSecret, this.userPoolClientId, username)
};

await cognitoRequest(confirmSignUpRequest, CognitoServiceTarget.ConfirmSignUp, this.cognitoEndpoint);
await cognitoRequest(confirmSignUpRequest, ServiceTarget.ConfirmSignUp, this.cognitoEndpoint);
}

/**
*
* @param currentPassword Current user password.
* @param newPassword New user password.
*
* @throws {ChangePasswordException}
* @throws {ChangePasswordError}
*/
async changePassword(currentPassword: string, newPassword: string, accessToken: string) {
const changePasswordPayload = {
Expand All @@ -658,7 +708,7 @@ export class CognitoClient {
AccessToken: accessToken
};

await cognitoRequest(changePasswordPayload, CognitoServiceTarget.ChangePassword, this.cognitoEndpoint);
await cognitoRequest(changePasswordPayload, ServiceTarget.ChangePassword, this.cognitoEndpoint);
}

/**
Expand All @@ -667,15 +717,15 @@ export class CognitoClient {
* @param userAttributes List of user attributes to update.
* @param accessToken Access token of the current user.
*
* @throws {UpdateUserAttributesException}
* @throws {UpdateUserAttributesError}
*/
async updateUserAttributes(userAttributes: UserAttribute[], accessToken: string) {
const updateUserAttributesPayload = {
UserAttributes: userAttributes,
AccessToken: accessToken
};

await cognitoRequest(updateUserAttributesPayload, CognitoServiceTarget.UpdateUserAttributes, this.cognitoEndpoint);
await cognitoRequest(updateUserAttributesPayload, ServiceTarget.UpdateUserAttributes, this.cognitoEndpoint);
}

/**
Expand All @@ -685,7 +735,7 @@ export class CognitoClient {
* @param code Verification code
* @param accessToken Access token of the current user.
*
* @throws {VerifyUserAttributeException}
* @throws {VerifyUserAttributeError}
*/
async verifyUserAttribute(attributeName: string, code: string, accessToken: string) {
const verifyUserAttributePayload = {
Expand All @@ -694,15 +744,15 @@ export class CognitoClient {
AccessToken: accessToken
};

await cognitoRequest(verifyUserAttributePayload, CognitoServiceTarget.VerifyUserAttribute, this.cognitoEndpoint);
await cognitoRequest(verifyUserAttributePayload, ServiceTarget.VerifyUserAttribute, this.cognitoEndpoint);
}

/**
* Revokes all of the access tokens generated by, and at the same time as, the specified refresh token. After a token is revoked, you can't use the revoked token to access Amazon Cognito user APIs, or to authorize access to your resource server.
*
* @param refreshToken Refresh token from a previous session.
* @param username Username is required when using a client secret and needs to be the cognito user id.
* @throws {RevokeTokenException}
* @throws {RevokeTokenError}
*/
async revokeToken(refreshToken: string) {
const revokeTokenPayload = {
Expand All @@ -711,14 +761,14 @@ export class CognitoClient {
ClientSecret: this.clientSecret
};

await cognitoRequest(revokeTokenPayload, CognitoServiceTarget.RevokeToken, this.cognitoEndpoint);
await cognitoRequest(revokeTokenPayload, ServiceTarget.RevokeToken, this.cognitoEndpoint);
}

/**
* Request forgot password.
* @param username Username
*
* @throws {ForgotPasswordException}
* @throws {ForgotPasswordError}
*/
async forgotPassword(username: string) {
const forgotPasswordRequest: ForgotPasswordRequest = {
Expand All @@ -727,7 +777,7 @@ export class CognitoClient {
SecretHash: this.clientSecret && calculateSecretHash(this.clientSecret, this.userPoolClientId, username)
};

await cognitoRequest(forgotPasswordRequest, CognitoServiceTarget.ForgotPassword, this.cognitoEndpoint);
await cognitoRequest(forgotPasswordRequest, ServiceTarget.ForgotPassword, this.cognitoEndpoint);
}

/**
Expand All @@ -737,7 +787,7 @@ export class CognitoClient {
* @param newPassword New password
* @param confirmationCode Confirmation code which the user got through E-mail
*
* @throws {ConfirmForgotPasswordException}
* @throws {ConfirmForgotPasswordError}
*/
async confirmForgotPassword(username: string, newPassword: string, confirmationCode: string) {
const confirmForgotPasswordRequest: ConfirmForgotPasswordRequest = {
Expand All @@ -748,18 +798,14 @@ export class CognitoClient {
SecretHash: this.clientSecret && calculateSecretHash(this.clientSecret, this.userPoolClientId, username)
};

await cognitoRequest(
confirmForgotPasswordRequest,
CognitoServiceTarget.ConfirmForgotPassword,
this.cognitoEndpoint
);
await cognitoRequest(confirmForgotPasswordRequest, ServiceTarget.ConfirmForgotPassword, this.cognitoEndpoint);
}

/**
* Triggers cognito to resend the confirmation code
* @param username Username
*
* @throws {ResendConfirmationCodeException}
* @throws {ResendConfirmationCodeError}
*/
async resendConfirmationCode(username: string) {
const resendConfirmationCodeRequest: ResendConfirmationCodeRequest = {
Expand All @@ -768,11 +814,7 @@ export class CognitoClient {
SecretHash: this.clientSecret && calculateSecretHash(this.clientSecret, this.userPoolClientId, username)
};

await cognitoRequest(
resendConfirmationCodeRequest,
CognitoServiceTarget.ResendConfirmationCode,
this.cognitoEndpoint
);
await cognitoRequest(resendConfirmationCodeRequest, ServiceTarget.ResendConfirmationCode, this.cognitoEndpoint);
}

/**
Expand All @@ -784,7 +826,7 @@ export class CognitoClient {
*
* @throws {Error}
*/
async generateOAuthSignInUrl(identityProvider?: CognitoIdentityProvider) {
async generateOAuthSignInUrl(identityProvider?: IdentityProvider) {
if (this.oAuth === undefined) {
throw Error('You have to define oAuth options to use generateFederatedSignUrl');
}
Expand Down Expand Up @@ -883,6 +925,6 @@ export class CognitoClient {
AccessToken: accessToken
};

await cognitoRequest(globalSignOutPayload, CognitoServiceTarget.GlobalSignOut, this.cognitoEndpoint);
await cognitoRequest(globalSignOutPayload, ServiceTarget.GlobalSignOut, this.cognitoEndpoint);
}
}
Loading

0 comments on commit 986322f

Please sign in to comment.