Skip to content

Commit

Permalink
Merge pull request #6 from KogoCampus/KOGO-209/passwordPolicy
Browse files Browse the repository at this point in the history
[SM] - KOGO-209/should reject if violates password policy
  • Loading branch information
jiin-kim109 authored Dec 24, 2024
2 parents ed64693 + 38a03cd commit 72b8b32
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 2 deletions.
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

0 comments on commit 72b8b32

Please sign in to comment.