From c325aeaf0b5dc7d909ab7ceab4d736a565f9c8ce Mon Sep 17 00:00:00 2001 From: Adrien Zaganelli Date: Thu, 3 Oct 2024 11:05:23 +0200 Subject: [PATCH] fix(email): fix email verification --- README.md | 7 ++++++- playground/server/routes/auth/login.post.ts | 2 ++ playground/server/routes/auth/register.post.ts | 5 ++++- src/runtime/core/core.ts | 13 ++++++++++++- .../EmailVerificationCodesRepository.ts | 9 +++++++++ src/runtime/core/repositories/UsersRepository.ts | 10 ++++++++++ tests/core-email-password.test.ts | 3 +++ 7 files changed, 46 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e72f75b..625f84f 100644 --- a/README.md +++ b/README.md @@ -151,8 +151,9 @@ Registers a new user in the database if they don’t already exist, email + pass ##### `login(values: ILoginUserParams): Promise<[ string, SlipAuthPublicSession]>` -Email + password login. Creates a session in database +##### `askEmailVerificationCode(user: SlipAuthUser): Promise` +Ask the email verification code for a user. ##### `verifyEmailVerificationCode(user: SlipAuthUser, code: string): Promise` Checks the email verification code. Returns a boolean. @@ -163,6 +164,10 @@ Don't forget to re-login after verifying the email verification code. Registers a new user in the database if they don’t already exist. It handles OAuth authentication by registering the OAuth account, creating a session, and linking the user’s details. - **Returns**: A tuple containing the user ID and the created session details. +##### `getUser(userId: string)` + +Fetches a user by its user ID. + ##### `getSession(sessionId: string)` Fetches a session by its session ID. diff --git a/playground/server/routes/auth/login.post.ts b/playground/server/routes/auth/login.post.ts index aa47731..d1664b1 100644 --- a/playground/server/routes/auth/login.post.ts +++ b/playground/server/routes/auth/login.post.ts @@ -6,12 +6,14 @@ export default defineEventHandler(async (event) => { ...body, ua: getHeader(event, "User-Agent"), }); + const user = await auth.getUser(userId); await setUserSession(event, { expires_at: session.expires_at, id: session.id, user: { id: userId, + email_verified: user?.email_verified ?? false, }, }); return sendRedirect(event, "/"); diff --git a/playground/server/routes/auth/register.post.ts b/playground/server/routes/auth/register.post.ts index 13d7929..5b933d6 100644 --- a/playground/server/routes/auth/register.post.ts +++ b/playground/server/routes/auth/register.post.ts @@ -7,12 +7,15 @@ export default defineEventHandler(async (event) => { ua: getHeader(event, "User-Agent"), }); + const user = await auth.getUser(userId); + await setUserSession(event, { expires_at: session.expires_at, id: session.id, user: { id: userId, + email_verified: user?.email_verified ?? false, }, }); - return sendRedirect(event, "/"); + return sendRedirect(event, "/profile"); }); diff --git a/src/runtime/core/core.ts b/src/runtime/core/core.ts index 1ae9758..19aabf6 100644 --- a/src/runtime/core/core.ts +++ b/src/runtime/core/core.ts @@ -148,7 +148,7 @@ export class SlipAuthCore { try { const user = await this.#repos.users.insert(userId, email, passwordHash); - await this.#repos.emailVerificationCodes.insert(user.id, user.email, this.#createRandomEmailVerificationCode()); + this.askEmailVerificationCode(user); const sessionToLoginId = this.#createRandomSessionId(); const sessionToLogin = await this.#repos.sessions.insert(sessionToLoginId, { userId: user.id, @@ -231,6 +231,11 @@ export class SlipAuthCore { throw new Error("could not find oauth user"); } + public async askEmailVerificationCode(user: SlipAuthUser): Promise { + await this.#repos.emailVerificationCodes.deleteAllByUserId(user.id); + await this.#repos.emailVerificationCodes.insert(user.id, user.email, this.#createRandomEmailVerificationCode()); + } + // TODO: use transactions // should recreate session if true public async verifyEmailVerificationCode(user: SlipAuthUser, code: string): Promise { @@ -252,6 +257,8 @@ export class SlipAuthCore { return false; } + await this.#repos.users.updateEmailVerifiedByUserId(databaseCode.user_id, true); + return true; } @@ -348,6 +355,10 @@ export class SlipAuthCore { this.#passwordHashingMethods = methods; } + public getUser(userId: string) { + return this.#repos.users.findById(userId); + } + public getSession(sessionId: string) { return this.#repos.sessions.findById(sessionId); } diff --git a/src/runtime/core/repositories/EmailVerificationCodesRepository.ts b/src/runtime/core/repositories/EmailVerificationCodesRepository.ts index d23a46c..b42095c 100644 --- a/src/runtime/core/repositories/EmailVerificationCodesRepository.ts +++ b/src/runtime/core/repositories/EmailVerificationCodesRepository.ts @@ -62,6 +62,15 @@ export class EmailVerificationCodesRepository extends TableRepository<"emailVeri return code; } + async deleteAllByUserId(userId: string): Promise { + await this._orm + .delete(this.table) + .where( + eq(this.table.user_id, userId), + ) + .run(); + } + async deleteById(codeId: number) { const codeToDelete = await this.findById(codeId); diff --git a/src/runtime/core/repositories/UsersRepository.ts b/src/runtime/core/repositories/UsersRepository.ts index 9e230ca..d5464d5 100644 --- a/src/runtime/core/repositories/UsersRepository.ts +++ b/src/runtime/core/repositories/UsersRepository.ts @@ -54,4 +54,14 @@ export class UsersRepository extends TableRepository<"users"> { .where(eq(this.table.id, userId)) .run(); }; + + updateEmailVerifiedByUserId = async (userId: string, value: boolean): Promise => { + return await this._orm + .update(this.table) + .set({ + email_verified: value, + }) + .where(eq(this.table.id, userId)) + .run(); + }; } diff --git a/tests/core-email-password.test.ts b/tests/core-email-password.test.ts index 67247fd..b147fd4 100644 --- a/tests/core-email-password.test.ts +++ b/tests/core-email-password.test.ts @@ -257,6 +257,9 @@ describe("SlipAuthCore", () => { const verification = await auth.verifyEmailVerificationCode(fakeUserData as SlipAuthUser, codes[0].code); expect(verification).toBe(true); + + const dbUser = await auth.getUser(userId); + expect(dbUser?.email_verified).toBe(1); }); }); });