From 8886c716db37467a0a8f5dfe24408c4e66021751 Mon Sep 17 00:00:00 2001 From: scott0929 Date: Sun, 22 Dec 2024 17:21:07 -0800 Subject: [PATCH] feat: verify email for password reset handler --- cdk/lib/lambdaStack.ts | 16 +++++++ cdk/src/docs/emailVerification.yaml | 2 + cdk/src/docs/emailVerificationComplete.yaml | 8 ++-- .../docs/emailVerificationForPassword.yaml | 21 +++++++++ cdk/src/docs/passwordReset.yaml | 6 +-- cdk/src/docs/userRegistration.yaml | 18 ++------ cdk/src/lambda/handlers/authenticateUser.ts | 2 - cdk/src/lambda/handlers/emailVerification.ts | 7 +++ .../handlers/emailVerificationComplete.ts | 10 ++--- .../handlers/emailVerificationForPassword.ts | 43 +++++++++++++++++++ cdk/src/lambda/handlers/passwordReset.ts | 24 +++++------ cdk/src/lambda/handlers/userRegistration.ts | 34 ++++++--------- cdk/src/service/email/authToken.ts | 40 ----------------- cdk/src/service/email/emailVerifiedToken.ts | 40 +++++++++++++++++ .../lambda/handlers/authenticateUser.test.ts | 2 - .../emailVerificationComplete.test.ts | 12 +++--- .../lambda/handlers/passwordReset.test.ts | 34 +++++++-------- 17 files changed, 194 insertions(+), 125 deletions(-) create mode 100644 cdk/src/docs/emailVerificationForPassword.yaml create mode 100644 cdk/src/lambda/handlers/emailVerificationForPassword.ts delete mode 100644 cdk/src/service/email/authToken.ts create mode 100644 cdk/src/service/email/emailVerifiedToken.ts diff --git a/cdk/lib/lambdaStack.ts b/cdk/lib/lambdaStack.ts index e322718..48aaa9c 100644 --- a/cdk/lib/lambdaStack.ts +++ b/cdk/lib/lambdaStack.ts @@ -101,12 +101,28 @@ export class LambdaStack extends cdk.Stack { bundling, }); emailVerificationLambda.addToRolePolicy(policies.ses.sendEmail); + emailVerificationLambda.addToRolePolicy(policies.cognito.userManagement); // path: /student/verify-email const emailVerificationIntegration = new apigateway.LambdaIntegration(emailVerificationLambda); const verifyEmailResource = studentResource.addResource('verify-email'); verifyEmailResource.addMethod('POST', emailVerificationIntegration); + // ================================================================= + // Verify Email For Password Lambda + // ================================================================= + const verifyEmailForPasswordLambda = new NodejsFunction(this, 'VerifyEmailForPasswordHandler', { + ...nodeJsFunctionProps, + entry: path.join(__dirname, '../src/lambda/handlers/emailVerificationForPassword.ts'), + bundling, + }); + verifyEmailForPasswordLambda.addToRolePolicy(policies.ses.sendEmail); + verifyEmailForPasswordLambda.addToRolePolicy(policies.cognito.userManagement); + + // path: /student/verify-email/password + const verifyEmailForPasswordIntegration = new apigateway.LambdaIntegration(verifyEmailForPasswordLambda); + verifyEmailResource.addResource('password').addMethod('POST', verifyEmailForPasswordIntegration); + // ================================================================= // Verify Email Complete Lambda // ================================================================= diff --git a/cdk/src/docs/emailVerification.yaml b/cdk/src/docs/emailVerification.yaml index 0a197af..a9b3e41 100644 --- a/cdk/src/docs/emailVerification.yaml +++ b/cdk/src/docs/emailVerification.yaml @@ -15,5 +15,7 @@ paths: description: Verification email sent '400': description: Bad request - missing or invalid parameters + '409': + description: Conflict - User already exists with the provided email '500': description: Internal server error \ No newline at end of file diff --git a/cdk/src/docs/emailVerificationComplete.yaml b/cdk/src/docs/emailVerificationComplete.yaml index 2bfe25f..afc6894 100644 --- a/cdk/src/docs/emailVerificationComplete.yaml +++ b/cdk/src/docs/emailVerificationComplete.yaml @@ -2,7 +2,7 @@ paths: /student/verify-email/complete: post: summary: Complete email verification - description: Validates the verification code sent to the student's email and returns an authorization token if successful. + description: Validates the verification code sent to the student's email and returns an email verified token if successful. parameters: - name: email in: query @@ -18,7 +18,7 @@ paths: type: string responses: '200': - description: Email verified successfully and an authorization token returned + description: Email verified successfully and an email verified returned content: application/json: schema: @@ -27,9 +27,9 @@ paths: message: type: string example: "Email verified successfully." - authToken: + emailVerifiedToken: type: string - description: Authorization token for further authentication + description: Email verified token for further authentication example: "eyJhbGciOiJIUzI1NiIsInR5..." '400': description: Bad request - missing or invalid parameters diff --git a/cdk/src/docs/emailVerificationForPassword.yaml b/cdk/src/docs/emailVerificationForPassword.yaml new file mode 100644 index 0000000..7c9fe3d --- /dev/null +++ b/cdk/src/docs/emailVerificationForPassword.yaml @@ -0,0 +1,21 @@ +paths: + /student/verify-email/password: + post: + summary: Verify a student's email while resetting passwords + description: Generates a verification code and sends it to the email provided. + parameters: + - name: email + in: query + required: true + description: The email address of the student to verify. + schema: + type: string + responses: + '200': + description: Verification email sent + '400': + description: Bad request - missing or invalid parameters + '404': + description: Not found - No user exists with the provided email + '500': + description: Internal server error \ No newline at end of file diff --git a/cdk/src/docs/passwordReset.yaml b/cdk/src/docs/passwordReset.yaml index 614c117..393f5ed 100644 --- a/cdk/src/docs/passwordReset.yaml +++ b/cdk/src/docs/passwordReset.yaml @@ -2,7 +2,7 @@ paths: /student/password-reset: post: summary: Reset password for a student - description: Resets the password for a student after the authorization token validation. + description: Resets the password for a student after the email verified token validation. requestBody: description: The new password for the student required: true @@ -21,7 +21,7 @@ paths: description: The email address of the student. schema: type: string - - name: authToken + - name: emailVerifiedToken in: query required: true description: The authorization token provided after email verification. @@ -41,7 +41,7 @@ paths: '400': description: Bad request - missing or invalid parameters '401': - description: Unauthorized - Invalid or expired authorization token + description: Unauthorized - Invalid or expired email verified token '404': description: Not found - user does not exist '500': diff --git a/cdk/src/docs/userRegistration.yaml b/cdk/src/docs/userRegistration.yaml index 94b5f09..225055c 100644 --- a/cdk/src/docs/userRegistration.yaml +++ b/cdk/src/docs/userRegistration.yaml @@ -10,7 +10,7 @@ paths: description: The email address of the student. schema: type: string - - name: authToken + - name: emailVerifiedToken in: query required: true description: The authorization token received after email verification. @@ -58,9 +58,9 @@ paths: properties: message: type: string - example: "Email and authorization token are required" + example: "Email and email verified token are required" '401': - description: Unauthorized - Invalid or expired authorization token. + description: Unauthorized - Invalid or expired email verified token. content: application/json: schema: @@ -68,16 +68,6 @@ paths: properties: message: type: string - example: "Invalid or expired authorization token" - '409': - description: Conflict - User already exists. - content: - application/json: - schema: - type: object - properties: - message: - type: string - example: "User already exists with the provided email" + example: "Invalid or expired email verified token" '500': description: Internal server error. \ No newline at end of file diff --git a/cdk/src/lambda/handlers/authenticateUser.ts b/cdk/src/lambda/handlers/authenticateUser.ts index 2ac1bc8..5bfafc5 100644 --- a/cdk/src/lambda/handlers/authenticateUser.ts +++ b/cdk/src/lambda/handlers/authenticateUser.ts @@ -27,7 +27,6 @@ const authenticateUser: APIGatewayProxyHandler = async event => { return successResponse({ userdata: { - username: userDetails.username, email: userDetails.email, schoolKey, schoolData, @@ -41,7 +40,6 @@ const authenticateUser: APIGatewayProxyHandler = async event => { return successResponse({ userdata: { - username: userDetails.username, email: userDetails.email, schoolKey, schoolData, diff --git a/cdk/src/lambda/handlers/emailVerification.ts b/cdk/src/lambda/handlers/emailVerification.ts index 2b98d83..15e97a8 100644 --- a/cdk/src/lambda/handlers/emailVerification.ts +++ b/cdk/src/lambda/handlers/emailVerification.ts @@ -5,6 +5,7 @@ import { buildEmailParams } from '../../service/email'; import { RedisClient } from '../../service/redis'; import { isDesignatedSchoolEmail } from '../../service/school'; import { settings } from '../../settings'; +import { doesUserExistByEmail } from '../../service/cognito'; // Constants const EXPIRATION_TIME = 900; // Set expiration to 15 minutes (900 seconds) @@ -23,6 +24,12 @@ const emailVerification: APIGatewayProxyHandler = async event => { return errorResponse('Email is not from a designated school domain', 400); } + // Check if a user with the same email already exists + const doesUserExist = await doesUserExistByEmail(email); + if (doesUserExist) { + return errorResponse('User already exists with the provided email', 409); + } + try { const verificationCode = Math.floor(100000 + Math.random() * 900000).toString(); const redis = RedisClient.getInstance(); diff --git a/cdk/src/lambda/handlers/emailVerificationComplete.ts b/cdk/src/lambda/handlers/emailVerificationComplete.ts index c4ba794..7a732b7 100644 --- a/cdk/src/lambda/handlers/emailVerificationComplete.ts +++ b/cdk/src/lambda/handlers/emailVerificationComplete.ts @@ -1,7 +1,7 @@ import { APIGatewayProxyHandler } from 'aws-lambda'; import { successResponse, errorResponse, wrapHandler } from '../handlerUtil'; import { RedisClient } from '../../service/redis'; -import { generateAuthToken, storeAuthToken } from '../../service/email/authToken'; +import { generateEmailVerifiedToken, storeEmailVerifiedToken } from '../../service/email/emailVerifiedToken'; const emailVerificationComplete: APIGatewayProxyHandler = async event => { const email = event.queryStringParameters?.email; @@ -25,13 +25,13 @@ const emailVerificationComplete: APIGatewayProxyHandler = async event => { // Verification successful, delete the code from Redis await redis.delete(email); - // Generate a new auth token and store it in Redis - const authToken = generateAuthToken(); - await storeAuthToken(email, authToken); + // Generate a new email verified token and store it in Redis + const emailVerifiedToken = generateEmailVerifiedToken(); + await storeEmailVerifiedToken(email, emailVerifiedToken); return successResponse({ message: 'Email verified successfully.', - authToken, + emailVerifiedToken, }); } catch (error) { return errorResponse(error instanceof Error ? error.message : 'Verification failed', 500); diff --git a/cdk/src/lambda/handlers/emailVerificationForPassword.ts b/cdk/src/lambda/handlers/emailVerificationForPassword.ts new file mode 100644 index 0000000..65a3520 --- /dev/null +++ b/cdk/src/lambda/handlers/emailVerificationForPassword.ts @@ -0,0 +1,43 @@ +import { APIGatewayProxyHandler } from 'aws-lambda'; +import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses'; +import { successResponse, errorResponse, wrapHandler } from '../handlerUtil'; +import { buildEmailParams } from '../../service/email'; +import { RedisClient } from '../../service/redis'; +import { settings } from '../../settings'; +import { doesUserExistByEmail } from '../../service/cognito'; + +// Constants +const EXPIRATION_TIME = 900; // Set expiration to 15 minutes (900 seconds) + +// SES Client +const SES = new SESClient({ region: settings.ses.sesIdentityRegion }); + +const emailVerificationForPassword: APIGatewayProxyHandler = async event => { + const email = event.queryStringParameters?.email; + + if (!email) { + return errorResponse('Email query parameter is required', 400); + } + + // Check if a user with the same email already exists + const doesUserExist = await doesUserExistByEmail(email); + if (!doesUserExist) { + return errorResponse('No user exists with the provided email', 404); + } + + try { + const verificationCode = Math.floor(100000 + Math.random() * 900000).toString(); + const redis = RedisClient.getInstance(); + await redis.setWithExpiry(email, verificationCode, EXPIRATION_TIME); + + const emailParams = buildEmailParams(email, 'verification', { verificationCode }, 'welcome@kogocampus.com'); + const command = new SendEmailCommand(emailParams); + await SES.send(command); + + return successResponse({ message: 'Verification email sent' }); + } catch (error) { + return errorResponse(error instanceof Error ? error.message : 'Failed to send verification email', 500); + } +}; + +export const handler = wrapHandler(emailVerificationForPassword); diff --git a/cdk/src/lambda/handlers/passwordReset.ts b/cdk/src/lambda/handlers/passwordReset.ts index 755d6e4..39b0680 100644 --- a/cdk/src/lambda/handlers/passwordReset.ts +++ b/cdk/src/lambda/handlers/passwordReset.ts @@ -1,24 +1,24 @@ import { APIGatewayProxyHandler } from 'aws-lambda'; import { doesUserExistByEmail, resetUserPassword } from '../../service/cognito'; import { successResponse, errorResponse, wrapHandler } from '../handlerUtil'; -import { getAuthToken, deleteAuthToken } from '../../service/email/authToken'; +import { getEmailVerifiedToken, deleteEmailVerifiedToken } from '../../service/email/emailVerifiedToken'; const passwordReset: APIGatewayProxyHandler = async event => { const email = event.queryStringParameters?.email; - const authToken = event.queryStringParameters?.authToken; + const emailVerifiedToken = event.queryStringParameters?.emailVerifiedToken; - if (!email || !authToken) { - return errorResponse('Email and authorization token are required', 400); + if (!email || !emailVerifiedToken) { + return errorResponse('Email and email verified token are required', 400); } try { - // Verify the auth token - const storedAuthToken = await getAuthToken(email); - if (!storedAuthToken) { - return errorResponse('Authorization token has expired or does not exist', 401); + // Verify the email verified token + const storedEmailVerifiedToken = await getEmailVerifiedToken(email); + if (!storedEmailVerifiedToken) { + return errorResponse('Email verified token has expired or does not exist', 401); } - if (storedAuthToken !== authToken) { - return errorResponse('Invalid authorization token', 401); + if (storedEmailVerifiedToken !== emailVerifiedToken) { + return errorResponse('Invalid email verified token', 401); } const userExists = await doesUserExistByEmail(email); @@ -32,9 +32,9 @@ const passwordReset: APIGatewayProxyHandler = async event => { return errorResponse('New password is required', 400); } - // Reset the user password and remove the auth token after successful password reset + // Reset the user password and remove the email verified token after successful password reset await resetUserPassword(email, newPassword); - await deleteAuthToken(email); + await deleteEmailVerifiedToken(email); return successResponse({ message: 'Password reset successfully' }); } catch (error) { diff --git a/cdk/src/lambda/handlers/userRegistration.ts b/cdk/src/lambda/handlers/userRegistration.ts index e50be94..6e3174b 100644 --- a/cdk/src/lambda/handlers/userRegistration.ts +++ b/cdk/src/lambda/handlers/userRegistration.ts @@ -1,17 +1,17 @@ import { APIGatewayProxyHandler } from 'aws-lambda'; import { successResponse, errorResponse, wrapHandler } from '../handlerUtil'; -import { getAuthToken, deleteAuthToken } from '../../service/email/authToken'; -import { createUserInCognito, doesUserExistByEmail } from '../../service/cognito'; +import { getEmailVerifiedToken, deleteEmailVerifiedToken } from '../../service/email/emailVerifiedToken'; +import { createUserInCognito } from '../../service/cognito'; export const handler: APIGatewayProxyHandler = wrapHandler(async event => { const email = event.queryStringParameters?.email; - const authToken = event.queryStringParameters?.authToken; + const emailVerifiedToken = event.queryStringParameters?.emailVerifiedToken; - if (!email || !authToken) { - return errorResponse('Email and authorization token are required', 400); + if (!email || !emailVerifiedToken) { + return errorResponse('Email and email verified token are required', 400); } - // Get username and password from the request body + // Get password from the request body const requestBody = JSON.parse(event.body || '{}'); const { password } = requestBody; // Check if password is provided @@ -19,26 +19,20 @@ export const handler: APIGatewayProxyHandler = wrapHandler(async event => { return errorResponse('Password is required in the request body', 400); } - // Verify the auth token after checking username and password - const storedAuthToken = await getAuthToken(email); - if (!storedAuthToken) { - return errorResponse('No authorization token found or it has expired', 401); + // Verify the email verified token after checking email and password + const storedEmailVerifiedToken = await getEmailVerifiedToken(email); + if (!storedEmailVerifiedToken) { + return errorResponse('No email verified token found or it has expired', 401); } - if (authToken !== storedAuthToken) { - return errorResponse('Invalid authorization token', 401); - } - - // Check if a user with the same email already exists - const doesUserExist = await doesUserExistByEmail(email); - if (doesUserExist) { - return errorResponse('User already exists with the provided email', 409); + if (emailVerifiedToken !== storedEmailVerifiedToken) { + return errorResponse('Invalid email verified token', 401); } // Proceed with user registration in Cognito const { AccessToken, IdToken, RefreshToken } = await createUserInCognito(email, password); - // Upon successful registration, delete the auth token from Redis - await deleteAuthToken(email); + // Upon successful registration, delete the email verified token from Redis + await deleteEmailVerifiedToken(email); return successResponse({ message: 'User successfully created', diff --git a/cdk/src/service/email/authToken.ts b/cdk/src/service/email/authToken.ts deleted file mode 100644 index 68dbdc7..0000000 --- a/cdk/src/service/email/authToken.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { RedisClient } from '../redis'; - -const AUTH_TOKEN_EXPIRATION_TIME = 3600; // 1 hour expiration for the auth token - -/** - * Generate a new auth token - * @returns {string} A newly generated auth token - */ -export function generateAuthToken(): string { - return `${Date.now()}-${Math.floor(Math.random() * 1000000)}`; -} - -/** - * Store the auth token in Redis with an expiration time - * @param {string} email The user's email - * @param {string} authToken The auth token to store - */ -export async function storeAuthToken(email: string, authToken: string): Promise { - const redis = RedisClient.getInstance(); - await redis.setWithExpiry(`${email}-authToken`, authToken, AUTH_TOKEN_EXPIRATION_TIME); -} - -/** - * Retrieve the auth token from Redis for the given email - * @param {string} email The user's email - * @returns {Promise} The auth token if it exists, null otherwise - */ -export async function getAuthToken(email: string): Promise { - const redis = RedisClient.getInstance(); - return await redis.get(`${email}-authToken`); -} - -/** - * Delete the auth token from Redis for the given email - * @param {string} email The user's email - */ -export async function deleteAuthToken(email: string): Promise { - const redis = RedisClient.getInstance(); - await redis.delete(`${email}-authToken`); -} diff --git a/cdk/src/service/email/emailVerifiedToken.ts b/cdk/src/service/email/emailVerifiedToken.ts new file mode 100644 index 0000000..66c2b3b --- /dev/null +++ b/cdk/src/service/email/emailVerifiedToken.ts @@ -0,0 +1,40 @@ +import { RedisClient } from '../redis'; + +const EMAIL_VERIFIED_TOKEN_EXPIRATION_TIME = 3600; // 1 hour expiration for the email verified token + +/** + * Generate a new email verified token + * @returns {string} A newly generated email verified token + */ +export function generateEmailVerifiedToken(): string { + return `${Date.now()}-${Math.floor(Math.random() * 1000000)}`; +} + +/** + * Store the email verified token in Redis with an expiration time + * @param {string} email The user's email + * @param {string} emailVerifiedToken The email verified token to store + */ +export async function storeEmailVerifiedToken(email: string, emailVerifiedToken: string): Promise { + const redis = RedisClient.getInstance(); + await redis.setWithExpiry(`${email}-emailVerifiedToken`, emailVerifiedToken, EMAIL_VERIFIED_TOKEN_EXPIRATION_TIME); +} + +/** + * Retrieve the email verified token from Redis for the given email + * @param {string} email The user's email + * @returns {Promise} The email verified token if it exists, null otherwise + */ +export async function getEmailVerifiedToken(email: string): Promise { + const redis = RedisClient.getInstance(); + return await redis.get(`${email}-emailVerifiedToken`); +} + +/** + * Delete the email verified token from Redis for the given email + * @param {string} email The user's email + */ +export async function deleteEmailVerifiedToken(email: string): Promise { + const redis = RedisClient.getInstance(); + await redis.delete(`${email}-emailVerifiedToken`); +} diff --git a/cdk/test/lambda/handlers/authenticateUser.test.ts b/cdk/test/lambda/handlers/authenticateUser.test.ts index a8891bb..2f2b080 100644 --- a/cdk/test/lambda/handlers/authenticateUser.test.ts +++ b/cdk/test/lambda/handlers/authenticateUser.test.ts @@ -71,7 +71,6 @@ describe('authenticateUser', () => { }); expect(handlerUtil.successResponse).toHaveBeenCalledWith({ userdata: { - username: mockUserDetails.username, email: mockUserDetails.email, schoolKey: mockSchoolInfo.key, schoolData: mockSchoolInfo.data, @@ -86,7 +85,6 @@ describe('authenticateUser', () => { }); expect(handlerUtil.successResponse).toHaveBeenCalledWith({ userdata: { - username: mockUserDetails.username, email: mockUserDetails.email, schoolKey: mockSchoolInfo.key, schoolData: mockSchoolInfo.data, diff --git a/cdk/test/lambda/handlers/emailVerificationComplete.test.ts b/cdk/test/lambda/handlers/emailVerificationComplete.test.ts index fcec1d3..1a04467 100644 --- a/cdk/test/lambda/handlers/emailVerificationComplete.test.ts +++ b/cdk/test/lambda/handlers/emailVerificationComplete.test.ts @@ -1,11 +1,11 @@ import { APIGatewayProxyEvent, Context, Callback } from 'aws-lambda'; import { handler } from '../../../src/lambda/handlers/emailVerificationComplete'; import { RedisClient } from '../../../src/service/redis'; -import * as authToken from '../../../src/service/email/authToken'; +import * as emailVerifiedToken from '../../../src/service/email/emailVerifiedToken'; import * as handlerUtil from '../../../src/lambda/handlerUtil'; jest.mock('../../../src/service/redis'); -jest.mock('../../../src/service/email/authToken'); +jest.mock('../../../src/service/email/emailVerifiedToken'); describe('emailVerificationComplete', () => { const mockRedis = { @@ -13,7 +13,7 @@ describe('emailVerificationComplete', () => { delete: jest.fn(), }; - const mockAuthToken = 'mock-auth-token'; + const mockEmailVerifiedToken = 'mock-email-verified-token'; const invokeHandler = async (event: Partial) => { const context = {} as Context; @@ -28,7 +28,7 @@ describe('emailVerificationComplete', () => { beforeEach(() => { jest.resetAllMocks(); (RedisClient.getInstance as jest.Mock).mockReturnValue(mockRedis); - (authToken.generateAuthToken as jest.Mock).mockReturnValue(mockAuthToken); + (emailVerifiedToken.generateEmailVerifiedToken as jest.Mock).mockReturnValue(mockEmailVerifiedToken); jest.spyOn(handlerUtil, 'successResponse'); jest.spyOn(handlerUtil, 'errorResponse'); }); @@ -64,10 +64,10 @@ describe('emailVerificationComplete', () => { }); expect(mockRedis.delete).toHaveBeenCalledWith(email); - expect(authToken.storeAuthToken).toHaveBeenCalledWith(email, mockAuthToken); + expect(emailVerifiedToken.storeEmailVerifiedToken).toHaveBeenCalledWith(email, mockEmailVerifiedToken); expect(handlerUtil.successResponse).toHaveBeenCalledWith({ message: 'Email verified successfully.', - authToken: mockAuthToken, + emailVerifiedToken: mockEmailVerifiedToken, }); }); }); diff --git a/cdk/test/lambda/handlers/passwordReset.test.ts b/cdk/test/lambda/handlers/passwordReset.test.ts index 1d6c775..c994cae 100644 --- a/cdk/test/lambda/handlers/passwordReset.test.ts +++ b/cdk/test/lambda/handlers/passwordReset.test.ts @@ -1,14 +1,14 @@ import { APIGatewayProxyEvent, Context, Callback } from 'aws-lambda'; import { handler } from '../../../src/lambda/handlers/passwordReset'; import { doesUserExistByEmail, resetUserPassword } from '../../../src/service/cognito'; -import { getAuthToken, deleteAuthToken } from '../../../src/service/email/authToken'; +import { getEmailVerifiedToken, deleteEmailVerifiedToken } from '../../../src/service/email/emailVerifiedToken'; import * as handlerUtil from '../../../src/lambda/handlerUtil'; jest.mock('../../../src/service/cognito'); -jest.mock('../../../src/service/email/authToken'); +jest.mock('../../../src/service/email/emailVerifiedToken'); describe('passwordReset', () => { - const mockStoredAuthToken = 'stored-auth-token'; + const mockStoredEmailVerifiedToken = 'stored-email-verified-token'; const mockEmail = 'test@sfu.ca'; const mockNewPassword = 'newPassword123'; @@ -25,43 +25,43 @@ describe('passwordReset', () => { beforeEach(() => { jest.resetAllMocks(); - (getAuthToken as jest.Mock).mockResolvedValue(mockStoredAuthToken); + (getEmailVerifiedToken as jest.Mock).mockResolvedValue(mockStoredEmailVerifiedToken); (doesUserExistByEmail as jest.Mock).mockResolvedValue(true); jest.spyOn(handlerUtil, 'successResponse'); jest.spyOn(handlerUtil, 'errorResponse'); }); - it('should call errorResponse when email or auth token is missing', async () => { + it('should call errorResponse when email or email verified token is missing', async () => { await invokeHandler({}); - expect(handlerUtil.errorResponse).toHaveBeenCalledWith('Email and authorization token are required', 400); + expect(handlerUtil.errorResponse).toHaveBeenCalledWith('Email and email verified token are required', 400); }); - it('should call errorResponse when auth token is expired', async () => { - (getAuthToken as jest.Mock).mockResolvedValue(null); + it('should call errorResponse when email verified token is expired', async () => { + (getEmailVerifiedToken as jest.Mock).mockResolvedValue(null); await invokeHandler({ - queryStringParameters: { email: mockEmail, authToken: 'some-token' }, + queryStringParameters: { email: mockEmail, emailVerifiedToken: 'some-token' }, }); - expect(handlerUtil.errorResponse).toHaveBeenCalledWith('Authorization token has expired or does not exist', 401); + expect(handlerUtil.errorResponse).toHaveBeenCalledWith('Email verified token has expired or does not exist', 401); }); - it('should call errorResponse when auth token is invalid', async () => { + it('should call errorResponse when email verified token is invalid', async () => { await invokeHandler({ - queryStringParameters: { email: mockEmail, authToken: 'wrong-token' }, + queryStringParameters: { email: mockEmail, emailVerifiedToken: 'wrong-token' }, }); - expect(handlerUtil.errorResponse).toHaveBeenCalledWith('Invalid authorization token', 401); + expect(handlerUtil.errorResponse).toHaveBeenCalledWith('Invalid email verified token', 401); }); it('should call errorResponse when user does not exist', async () => { (doesUserExistByEmail as jest.Mock).mockResolvedValue(false); await invokeHandler({ - queryStringParameters: { email: mockEmail, authToken: mockStoredAuthToken }, + queryStringParameters: { email: mockEmail, emailVerifiedToken: mockStoredEmailVerifiedToken }, }); expect(handlerUtil.errorResponse).toHaveBeenCalledWith('User does not exist with the provided email', 404); }); it('should call errorResponse when new password is missing', async () => { await invokeHandler({ - queryStringParameters: { email: mockEmail, authToken: mockStoredAuthToken }, + queryStringParameters: { email: mockEmail, emailVerifiedToken: mockStoredEmailVerifiedToken }, body: JSON.stringify({}), }); expect(handlerUtil.errorResponse).toHaveBeenCalledWith('New password is required', 400); @@ -69,12 +69,12 @@ describe('passwordReset', () => { it('should reset password successfully with valid inputs', async () => { await invokeHandler({ - queryStringParameters: { email: mockEmail, authToken: mockStoredAuthToken }, + queryStringParameters: { email: mockEmail, emailVerifiedToken: mockStoredEmailVerifiedToken }, body: JSON.stringify({ newPassword: mockNewPassword }), }); expect(resetUserPassword).toHaveBeenCalledWith(mockEmail, mockNewPassword); - expect(deleteAuthToken).toHaveBeenCalledWith(mockEmail); + expect(deleteEmailVerifiedToken).toHaveBeenCalledWith(mockEmail); expect(handlerUtil.successResponse).toHaveBeenCalledWith({ message: 'Password reset successfully' }); }); });