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

[SM] - KOGO-209/should reject if violates password policy #6

Merged
merged 2 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion cdk/src/lambda/handlers/passwordReset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { APIGatewayProxyHandler } from 'aws-lambda';
import { doesUserExistByEmail, resetUserPassword } from '../../service/cognito';
import { successResponse, errorResponse, wrapHandler } from '../handlerUtil';
import { getEmailVerifiedToken, deleteEmailVerifiedToken } from '../../service/email/emailVerifiedToken';
import { CustomPasswordError, checkPasswordPolicy } from '../../service/passwordPolicy';

const passwordReset: APIGatewayProxyHandler = async event => {
const email = event.queryStringParameters?.email;
Expand Down Expand Up @@ -32,13 +33,18 @@ const passwordReset: APIGatewayProxyHandler = async event => {
return errorResponse('New password is required', 400);
}

await checkPasswordPolicy(newPassword);

// Reset the user password and remove the email verified token after successful password reset
await resetUserPassword(email, newPassword);
await deleteEmailVerifiedToken(email);

return successResponse({ message: 'Password reset successfully' });
} catch (error) {
return errorResponse(error instanceof Error ? error.message : 'Password reset failed', 500);
if (error instanceof CustomPasswordError) {
return errorResponse(error.message, 400);
}
return errorResponse('Password reset failed', 500);
}
};

Expand Down
9 changes: 9 additions & 0 deletions cdk/src/lambda/handlers/userRegistration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { APIGatewayProxyHandler } from 'aws-lambda';
import { successResponse, errorResponse, wrapHandler } from '../handlerUtil';
import { getEmailVerifiedToken, deleteEmailVerifiedToken } from '../../service/email/emailVerifiedToken';
import { createUserInCognito } from '../../service/cognito';
import { CustomPasswordError, checkPasswordPolicy } from '../../service/passwordPolicy';

export const handler: APIGatewayProxyHandler = wrapHandler(async event => {
const emailVerifiedToken = event.queryStringParameters?.emailVerifiedToken;
Expand All @@ -28,6 +29,14 @@ export const handler: APIGatewayProxyHandler = wrapHandler(async event => {
return errorResponse('Invalid email verified token', 401);
}

try {
await checkPasswordPolicy(password);
} catch (error) {
if (error instanceof CustomPasswordError) {
return errorResponse(error.message, 400);
}
}

// Proceed with user registration in Cognito
const { AccessToken, IdToken, RefreshToken } = await createUserInCognito(email, password);

Expand Down
32 changes: 32 additions & 0 deletions cdk/src/service/passwordPolicy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export class CustomPasswordError extends Error {
constructor(message: string) {
super(message);
this.name = 'CustomPasswordError';
}
}

export async function checkPasswordPolicy(password: string): Promise<void> {
if (password.length < 8) {
throw new CustomPasswordError('Password must be at least 8 characters');
}

if (password.length > 20) {
throw new CustomPasswordError('Password cannot exceed 20 characters');
}

if (!/[A-Z]/.test(password)) {
throw new CustomPasswordError('Password must contain at least one uppercase letter');
}

if (!/[a-z]/.test(password)) {
throw new CustomPasswordError('Password must contain at least one lowercase letter');
}

if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
throw new CustomPasswordError('Password must contain at least one special character');
}

if (!/[0-9]/.test(password)) {
throw new CustomPasswordError('Password must contain at least one number');
}
}
13 changes: 12 additions & 1 deletion cdk/test/lambda/handlers/passwordReset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ jest.mock('../../../src/service/email/emailVerifiedToken');
describe('passwordReset', () => {
const mockStoredEmailVerifiedToken = 'stored-email-verified-token';
const mockEmail = '[email protected]';
const mockNewPassword = 'newPassword123';
const mockNewPassword = 'newPassword@!#123';
const mockInvalidPassword = 'newPassword';

const invokeHandler = async (event: Partial<APIGatewayProxyEvent>) => {
const context = {} as Context;
Expand Down Expand Up @@ -67,6 +68,16 @@ describe('passwordReset', () => {
expect(handlerUtil.errorResponse).toHaveBeenCalledWith('New password is required', 400);
});

it('should call errorResponse when password is shorter than 8 characters', async () => {
await invokeHandler({
queryStringParameters: { email: mockEmail, emailVerifiedToken: mockStoredEmailVerifiedToken },
body: JSON.stringify({ newPassword: mockInvalidPassword }),
});
expect(handlerUtil.errorResponse).toHaveBeenCalledWith(expect.stringMatching(/Password/), 400);
expect(resetUserPassword).not.toHaveBeenCalled();
expect(deleteEmailVerifiedToken).not.toHaveBeenCalled();
});

it('should reset password successfully with valid inputs', async () => {
await invokeHandler({
queryStringParameters: { email: mockEmail, emailVerifiedToken: mockStoredEmailVerifiedToken },
Expand Down
Loading