From fed1bfb524e390a24fb4272a4a513cb3c8ab2346 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Sun, 5 Jan 2025 22:27:41 -0800 Subject: [PATCH] Fixing the settings change email flow. --- .run/AdelieStack.xml | 8 ++++ .../dtos/{ => settings}/update-email.dto.ts | 0 .../dtos/{ => settings}/verify-email.dto.ts | 2 +- .../api/common/middleware/auth.middleware.ts | 4 +- src/lib/server/api/iam/iam.controller.ts | 1 + .../server/api/users/dtos/update-email.dto.ts | 6 --- .../server/api/users/dtos/verify-email.dto.ts | 7 ---- .../email-change-requests.service.ts | 11 +++++- src/lib/server/api/users/users.controller.ts | 5 ++- .../settings/account/+page.server.ts | 39 +++++++++++++------ .../(protected)/settings/account/schemas.ts | 9 ----- .../settings/account/update-email-card.svelte | 35 +++++++---------- 12 files changed, 67 insertions(+), 60 deletions(-) create mode 100644 .run/AdelieStack.xml rename src/lib/dtos/{ => settings}/update-email.dto.ts (100%) rename src/lib/dtos/{ => settings}/verify-email.dto.ts (82%) delete mode 100644 src/lib/server/api/users/dtos/update-email.dto.ts delete mode 100644 src/lib/server/api/users/dtos/verify-email.dto.ts delete mode 100644 src/routes/(app)/(protected)/settings/account/schemas.ts diff --git a/.run/AdelieStack.xml b/.run/AdelieStack.xml new file mode 100644 index 0000000..4982401 --- /dev/null +++ b/.run/AdelieStack.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/lib/dtos/update-email.dto.ts b/src/lib/dtos/settings/update-email.dto.ts similarity index 100% rename from src/lib/dtos/update-email.dto.ts rename to src/lib/dtos/settings/update-email.dto.ts diff --git a/src/lib/dtos/verify-email.dto.ts b/src/lib/dtos/settings/verify-email.dto.ts similarity index 82% rename from src/lib/dtos/verify-email.dto.ts rename to src/lib/dtos/settings/verify-email.dto.ts index bd1a395..672372d 100644 --- a/src/lib/dtos/verify-email.dto.ts +++ b/src/lib/dtos/settings/verify-email.dto.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; export const verifyEmailDto = z.object({ - token: z.string() + code: z.string().length(6), }); export type VerifyEmailDto = z.infer; diff --git a/src/lib/server/api/common/middleware/auth.middleware.ts b/src/lib/server/api/common/middleware/auth.middleware.ts index a6ddf74..42c356b 100644 --- a/src/lib/server/api/common/middleware/auth.middleware.ts +++ b/src/lib/server/api/common/middleware/auth.middleware.ts @@ -13,7 +13,9 @@ type UnauthedReturnType = typeof unauthed; export function authState(state: 'session'): AuthedReturnType; export function authState(state: 'none'): UnauthedReturnType; export function authState(state: AuthStates): AuthedReturnType | UnauthedReturnType { - if (state === 'session') return authed; + if (state === 'session') { + return authed; + } return unauthed; } diff --git a/src/lib/server/api/iam/iam.controller.ts b/src/lib/server/api/iam/iam.controller.ts index 5a699d8..c3b58ed 100644 --- a/src/lib/server/api/iam/iam.controller.ts +++ b/src/lib/server/api/iam/iam.controller.ts @@ -30,6 +30,7 @@ export class IamController extends Controller { routes() { return this.controller .post('/login', openApi(signInEmail), authState('none'), zValidator('json', signinDto), rateLimit({ limit: 3, minutes: 1 }), async (c) => { + this.loggerService.log.info(`Login with identifier: ${c.req.valid('json').identifier}`); const session = await this.loginRequestsService.login(c.req.valid('json')); await this.sessionsService.setSessionCookie(session); return c.json({ message: 'welcome' }); diff --git a/src/lib/server/api/users/dtos/update-email.dto.ts b/src/lib/server/api/users/dtos/update-email.dto.ts deleted file mode 100644 index fd8728a..0000000 --- a/src/lib/server/api/users/dtos/update-email.dto.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { z } from 'zod'; -import { userDto } from './user.dto'; - -export const updateEmailDto = userDto.pick({ email: true }); - -export type UpdateEmailDto = z.infer; diff --git a/src/lib/server/api/users/dtos/verify-email.dto.ts b/src/lib/server/api/users/dtos/verify-email.dto.ts deleted file mode 100644 index e062f54..0000000 --- a/src/lib/server/api/users/dtos/verify-email.dto.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { z } from 'zod'; - -export const verifyEmailDto = z.object({ - code: z.string().length(6) -}); - -export type VerifyEmailDto = z.infer; diff --git a/src/lib/server/api/users/email-change-requests/email-change-requests.service.ts b/src/lib/server/api/users/email-change-requests/email-change-requests.service.ts index 3208783..b2e3950 100644 --- a/src/lib/server/api/users/email-change-requests/email-change-requests.service.ts +++ b/src/lib/server/api/users/email-change-requests/email-change-requests.service.ts @@ -6,11 +6,13 @@ import { EmailChangeNoticeEmail } from '../../mail/templates/email-change-notice import { BadRequest } from '../../common/utils/exceptions'; import { UsersRepository } from '../users.repository'; import { VerificationCodesService } from '../../common/services/verification-codes.service'; +import { LoggerService } from '../../common/services/logger.service'; @injectable() export class EmailChangeRequestsService { constructor( private emailChangeRequetsRepository = inject(EmailChangeRequestsRepository), + private loggerService = inject(LoggerService), private verificationCodesService = inject(VerificationCodesService), private mailerService = inject(MailerService), private usersRepository = inject(UsersRepository) @@ -44,19 +46,24 @@ export class EmailChangeRequestsService { to: requestedEmail, template: new EmailChangeRequestEmail(verificationCode) }); + this.loggerService.log.info(`Email change request sent to ${requestedEmail}`); } async verifyEmailChange(userId: string, verificationCode: string) { // Get the email change request const emailChangeRequest = await this.emailChangeRequetsRepository.get(userId); - if (!emailChangeRequest) throw BadRequest('Bad Request'); + if (!emailChangeRequest) { + throw BadRequest('Bad Request'); + } // Verify the verification code const isValid = await this.verificationCodesService.verify({ verificationCode, hashedVerificationCode: emailChangeRequest.hashedCode }); - if (!isValid) throw BadRequest('Bad Request'); + if (!isValid) { + throw BadRequest('Bad Request'); + } // Update the account's email await this.usersRepository.update(userId, { diff --git a/src/lib/server/api/users/users.controller.ts b/src/lib/server/api/users/users.controller.ts index cf47d8e..2bbc3c3 100644 --- a/src/lib/server/api/users/users.controller.ts +++ b/src/lib/server/api/users/users.controller.ts @@ -5,9 +5,9 @@ import { authState } from '../common/middleware/auth.middleware'; import { zValidator } from '@hono/zod-validator'; import { updateUserDto } from './dtos/update-user.dto'; import { EmailChangeRequestsService } from './email-change-requests/email-change-requests.service'; -import { updateEmailDto } from './dtos/update-email.dto'; +import { updateEmailDto } from '$lib/dtos/settings/update-email.dto'; import { UsersRepository } from './users.repository'; -import { verifyEmailDto } from './dtos/verify-email.dto'; +import { verifyEmailDto } from '$lib/dtos/settings/verify-email.dto'; import { rateLimit } from '../common/middleware/rate-limit.middleware'; @injectable() @@ -39,6 +39,7 @@ export class UsersController extends Controller { zValidator('json', updateEmailDto), rateLimit({ limit: 5, minutes: 15 }), async (c) => { + c.var.logger.info(`Request email change: ${c.req.valid('json').email}`); await this.emailChangeRequestsService.requestEmailChange( c.var.session.userId, c.req.valid('json').email diff --git a/src/routes/(app)/(protected)/settings/account/+page.server.ts b/src/routes/(app)/(protected)/settings/account/+page.server.ts index b57c05f..3d7bdc2 100644 --- a/src/routes/(app)/(protected)/settings/account/+page.server.ts +++ b/src/routes/(app)/(protected)/settings/account/+page.server.ts @@ -1,32 +1,47 @@ import { zod } from "sveltekit-superforms/adapters"; import { fail, setError, superValidate } from "sveltekit-superforms"; import { StatusCodes } from "@/constants/status-codes.js"; -import { updateEmailFormSchema, verifyEmailFormSchema } from "./schemas.js"; +import { updateEmailDto } from "$lib/dtos/settings/update-email.dto.js"; +import { verifyEmailDto } from "$lib/dtos/settings/verify-email.dto.js"; +import { redirect } from "sveltekit-flash-message/server"; +import { notSignedInMessage } from "$lib/utils/flashMessages.js"; -export let load = async (event) => { - const authedUser = await event.locals.getAuthedUserOrThrow() +export const load = async (event) => { + const { parent } = event; + const { authedUser } = await parent(); + + if (!authedUser) { + throw redirect('/login', notSignedInMessage, event); + } return { - authedUser, - updateEmailForm: await superValidate(authedUser, zod(updateEmailFormSchema)), - verifyEmailForm: await superValidate(zod(verifyEmailFormSchema)) + updateEmailForm: await superValidate(zod(updateEmailDto), { + defaults: { + email: authedUser.email + } + }), + verifyEmailForm: await superValidate(zod(verifyEmailDto)) }; }; export const actions = { updateEmail: async ({ request, locals }) => { - const updateEmailForm = await superValidate(request, zod(updateEmailFormSchema)); + const updateEmailForm = await superValidate(request, zod(updateEmailDto)); if (!updateEmailForm.valid) return fail(StatusCodes.BAD_REQUEST, { updateEmailForm }) - const { error } = await locals.api.iam.email.$patch({ json: updateEmailForm.data }).then(locals.parseApiResponse); + const { error } = await locals.api.users.me.email.request.$post({ json: updateEmailForm.data }).then(locals.parseApiResponse); if (error) return setError(updateEmailForm, 'email', error); return { updateEmailForm } }, verifyEmail: async ({ request, locals }) => { - const verifyEmailForm = await superValidate(request, zod(verifyEmailFormSchema)); + const verifyEmailForm = await superValidate(request, zod(verifyEmailDto)); console.log(verifyEmailForm) - if (!verifyEmailForm.valid) return fail(StatusCodes.BAD_REQUEST, { verifyEmailForm }) - const { error } = await locals.api.iam.email.verify.$post({ json: verifyEmailForm.data }).then(locals.parseApiResponse); - if (error) return setError(verifyEmailForm, 'token', error); + if (!verifyEmailForm.valid) { + return fail(StatusCodes.BAD_REQUEST, { verifyEmailForm }); + } + const { error } = await locals.api.users.me.email.verify.$post({ json: verifyEmailForm.data }).then(locals.parseApiResponse); + if (error) { + return setError(verifyEmailForm, 'code', error); + } return { verifyEmailForm } } }; diff --git a/src/routes/(app)/(protected)/settings/account/schemas.ts b/src/routes/(app)/(protected)/settings/account/schemas.ts deleted file mode 100644 index 793dabd..0000000 --- a/src/routes/(app)/(protected)/settings/account/schemas.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { z } from "zod"; - -export const verifyEmailFormSchema = z.object({ - token: z.string() -}); - -export const updateEmailFormSchema = z.object({ - email: z.string().email() -}); \ No newline at end of file diff --git a/src/routes/(app)/(protected)/settings/account/update-email-card.svelte b/src/routes/(app)/(protected)/settings/account/update-email-card.svelte index 866d889..c3b65be 100644 --- a/src/routes/(app)/(protected)/settings/account/update-email-card.svelte +++ b/src/routes/(app)/(protected)/settings/account/update-email-card.svelte @@ -1,12 +1,3 @@ - -