Skip to content

Commit

Permalink
feat: implement resendConfirmationCode
Browse files Browse the repository at this point in the history
  • Loading branch information
solufa committed Jun 20, 2024
1 parent d1dc6c5 commit 8d1a9ae
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 24 deletions.
22 changes: 13 additions & 9 deletions server/api/@types/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,20 @@ export type Jwks = { keys: [{ kid: string; alg: string }] };

type TargetBody<Req, Res> = { reqBody: Req; resBody: Res };

export type CodeDeliveryDetails = {
AttributeName: 'email';
DeliveryMedium: 'EMAIL';
Destination: string;
};

export type SignUpTarget = TargetBody<
{
Username: string;
Password: string;
UserAttributes: [{ Name: 'email'; Value: string }];
ClientId: MaybeId['userPoolClient'];
},
{
CodeDeliveryDetails: {
AttributeName: 'email';
DeliveryMedium: 'EMAIL';
Destination: string;
};
UserConfirmed: boolean;
UserSub: EntityId['user'];
}
{ CodeDeliveryDetails: CodeDeliveryDetails; UserConfirmed: boolean; UserSub: EntityId['user'] }
>;

export type ConfirmSignUpTarget = TargetBody<
Expand Down Expand Up @@ -106,11 +104,17 @@ export type RevokeTokenTarget = TargetBody<
Record<string, never>
>;

export type ResendConfirmationCodeTarget = TargetBody<
{ ClientId: MaybeId['userPoolClient']; Username: string },
{ CodeDeliveryDetails: CodeDeliveryDetails }
>;

export type AmzTargets = {
'AWSCognitoIdentityProviderService.SignUp': SignUpTarget;
'AWSCognitoIdentityProviderService.ConfirmSignUp': ConfirmSignUpTarget;
'AWSCognitoIdentityProviderService.InitiateAuth': UserSrpAuthTarget | RefreshTokenAuthTarget;
'AWSCognitoIdentityProviderService.RespondToAuthChallenge': RespondToAuthChallengeTarget;
'AWSCognitoIdentityProviderService.GetUser': GetUserTarget;
'AWSCognitoIdentityProviderService.RevokeToken': RevokeTokenTarget;
'AWSCognitoIdentityProviderService.ResendConfirmationCode': ResendConfirmationCodeTarget;
};
4 changes: 4 additions & 0 deletions server/api/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ const targets: {
validator: z.object({ ClientId: brandedId.userPoolClient.maybe, Token: z.string() }),
useCase: authUseCase.revokeToken,
},
'AWSCognitoIdentityProviderService.ResendConfirmationCode': {
validator: z.object({ ClientId: brandedId.userPoolClient.maybe, Username: z.string() }),
useCase: authUseCase.resendConfirmationCode,
},
};

const main = async <T extends keyof AmzTargets>(target: T, body: AmzTargets[T]['reqBody']) =>
Expand Down
8 changes: 8 additions & 0 deletions server/domain/user/service/genCodeDeliveryDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { CodeDeliveryDetails } from 'api/@types/auth';
import type { UserEntity } from 'api/@types/user';

export const genCodeDeliveryDetails = (user: UserEntity): CodeDeliveryDetails => ({
AttributeName: 'email',
DeliveryMedium: 'EMAIL',
Destination: user.email.replace(/^(.).*@(.).+$/, '$1***@$2***'),
});
9 changes: 9 additions & 0 deletions server/domain/user/service/sendConfirmationCode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { UserEntity } from 'api/@types/user';
import { sendMail } from 'service/sendMail';

export const sendConfirmationCode = (user: UserEntity): Promise<void> =>
sendMail({
to: { name: user.name, address: user.email },
subject: 'Your verification code',
text: `Your confirmation code is ${user.confirmationCode}`,
});
34 changes: 19 additions & 15 deletions server/domain/user/useCase/authUseCase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
ConfirmSignUpTarget,
GetUserTarget,
RefreshTokenAuthTarget,
ResendConfirmationCodeTarget,
RespondToAuthChallengeTarget,
RevokeTokenTarget,
SignUpTarget,
Expand All @@ -17,8 +18,9 @@ import { userPoolQuery } from 'domain/userPool/repository/userPoolQuery';
import { jwtDecode } from 'jwt-decode';
import { cognitoAssert } from 'service/cognitoAssert';
import { transaction } from 'service/prismaClient';
import { sendMail } from 'service/sendMail';
import type { AccessTokenJwt } from 'service/types';
import { genCodeDeliveryDetails } from '../service/genCodeDeliveryDetails';
import { sendConfirmationCode } from '../service/sendConfirmationCode';

export const authUseCase = {
signUp: (req: SignUpTarget['reqBody']): Promise<SignUpTarget['resBody']> =>
Expand All @@ -39,18 +41,10 @@ export const authUseCase = {
userPoolId: poolClient.userPoolId,
});
await userCommand.save(tx, user);
await sendMail({
to: { name: user.name, address: user.email },
subject: 'Your verification code',
text: `Your confirmation code is ${user.confirmationCode}`,
});
await sendConfirmationCode(user);

return {
CodeDeliveryDetails: {
AttributeName: 'email',
DeliveryMedium: 'EMAIL',
Destination: req.UserAttributes[0].Value.replace(/^(.).*@(.).+$/, '$1***@$2***'),
},
CodeDeliveryDetails: genCodeDeliveryDetails(user),
UserConfirmed: false,
UserSub: user.id,
};
Expand All @@ -76,10 +70,7 @@ export const authUseCase = {

await userCommand.save(tx, userWithChallenge);

return {
ChallengeName: 'PASSWORD_VERIFIER',
ChallengeParameters,
};
return { ChallengeName: 'PASSWORD_VERIFIER', ChallengeParameters };
}),
refreshTokenAuth: (
req: RefreshTokenAuthTarget['reqBody'],
Expand Down Expand Up @@ -159,4 +150,17 @@ export const authUseCase = {

return {};
}),
resendConfirmationCode: (
req: ResendConfirmationCodeTarget['reqBody'],
): Promise<ResendConfirmationCodeTarget['resBody']> =>
transaction(async (tx) => {
const poolClient = await userPoolQuery.findClientById(tx, req.ClientId);
const user = await userQuery.findByName(tx, req.Username);

assert(poolClient.userPoolId === user.userPoolId);

await sendConfirmationCode(user);

return { CodeDeliveryDetails: genCodeDeliveryDetails(user) };
}),
};
5 changes: 5 additions & 0 deletions server/tests/api/signUp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ test('signUp', async () => {
},
});

await noCookieClient.post({
headers: { 'x-amz-target': 'AWSCognitoIdentityProviderService.ResendConfirmationCode' },
body: { ClientId: DEFAULT_USER_POOL_CLIENT_ID, Username: 'user' },
});

assert(process.env.INBUCKET_URL);

const inbucketClient = new InbucketAPIClient(process.env.INBUCKET_URL);
Expand Down

0 comments on commit 8d1a9ae

Please sign in to comment.