From a9f809e8c261569e985dd9e2576f14c8b3be3939 Mon Sep 17 00:00:00 2001 From: Yu Le Date: Thu, 28 Sep 2023 18:08:39 +0800 Subject: [PATCH] feat: implement login & logout logic (#23) --- .github/workflows/ci.yml | 6 ++++-- commitlint.config.js | 4 ++-- composables/useSessionUser.ts | 7 +++++++ pages/dashboard.vue | 6 +++++- pages/login.vue | 13 +++++++++++-- prisma/seed.ts | 4 ++-- server/trpc/routers/protected.ts | 4 ++++ server/trpc/routers/public.ts | 6 ++++++ server/utils/password.ts | 20 ++++++++++++++++++++ 9 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 server/utils/password.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40831e2..5981c37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,14 +15,16 @@ jobs: - name: Use Node.js from `.nvmrc` uses: actions/setup-node@v3 with: - node-version-file: '.nvmrc' + node-version-file: .nvmrc # cache: 'pnpm' - name: Install dependencies & Build run: | corepack enable pnpm install - pnpm run --if-present build + + - name: Run ESLint + run: pnpm run lint - name: Run tests run: pnpm run --if-present test diff --git a/commitlint.config.js b/commitlint.config.js index e62538f..d179c69 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1,3 +1,3 @@ export default { - extends: ['@commitlint/config-conventional'] -} \ No newline at end of file + extends: ['@commitlint/config-conventional'], +} diff --git a/composables/useSessionUser.ts b/composables/useSessionUser.ts index 68886d1..9d8caf9 100644 --- a/composables/useSessionUser.ts +++ b/composables/useSessionUser.ts @@ -23,8 +23,15 @@ export function useSessionUser() { return user } + async function logout(path = '/') { + await $client.protected.logout.mutate() + user.value = null + await navigateTo(path) + } + return { init, user, + logout, } } diff --git a/pages/dashboard.vue b/pages/dashboard.vue index 0b2ea62..a963bf6 100644 --- a/pages/dashboard.vue +++ b/pages/dashboard.vue @@ -1,5 +1,5 @@ diff --git a/pages/login.vue b/pages/login.vue index d9ef7e0..367462b 100644 --- a/pages/login.vue +++ b/pages/login.vue @@ -11,17 +11,26 @@ const { $client } = useNuxtApp() const route = useRoute() const toast = useToast() +// Check if user is logged in +onMounted(() => { + const { user } = useSessionUser() + if (user.value) { + // TODO: Wrap a utils function + const r = route.query.r as string + navigateTo(decodeURIComponent(r || '/dashboard')) + } +}) + +// Form const schema = z.object({ email: z.string(), password: z.string(), }) type Schema = z.output - const state = ref({ email: 'hi@apibeer.com', password: 'apibeer', }) - const form = ref>() const submiting = ref(false) async function onSubmit(event: FormSubmitEvent) { diff --git a/prisma/seed.ts b/prisma/seed.ts index 30b27bc..9e055be 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ import process from 'node:process' -import bcrypt from 'bcryptjs' +import { hash } from '~/server/utils/password' import { prisma } from '~/server/utils/prisma' async function seed() { @@ -20,7 +20,7 @@ async function seedUsers() { email: 'hi@apibeer.com', username: 'apibeer', password: { - create: { hash: await bcrypt.hash('123456', 10) }, + create: { hash: await hash('123456') }, }, }, }) diff --git a/server/trpc/routers/protected.ts b/server/trpc/routers/protected.ts index d0e90a6..453daca 100644 --- a/server/trpc/routers/protected.ts +++ b/server/trpc/routers/protected.ts @@ -6,4 +6,8 @@ export const protectedRouter = router({ const user = event.ctx.session.data.user return user }), + logout: protectedProcedure + .mutation(async (event) => { + await event.ctx.session.clear() + }), }) diff --git a/server/trpc/routers/public.ts b/server/trpc/routers/public.ts index 2b346c8..04f1eab 100644 --- a/server/trpc/routers/public.ts +++ b/server/trpc/routers/public.ts @@ -1,5 +1,6 @@ import { z } from 'zod' import { publicProcedure, router } from '../trpc' +import { verify } from '~/server/utils/password' export const publicRouter = router({ login: publicProcedure @@ -20,6 +21,7 @@ export const publicRouter = router({ id: true, email: true, username: true, + password: { select: { hash: true } }, }, }) @@ -27,6 +29,10 @@ export const publicRouter = router({ if (!user) throw new Error('invalid credentials') + // TODO: Skip some env like: (DEMO, Development) + if (!await verify(input.password, user.password!.hash)) + throw new Error('invalid credentials') + // Storge session await ctx.session.update({ user }) diff --git a/server/utils/password.ts b/server/utils/password.ts new file mode 100644 index 0000000..a9c37e6 --- /dev/null +++ b/server/utils/password.ts @@ -0,0 +1,20 @@ +import bcrypt from 'bcryptjs' + +/** + * Hashes a raw password using bcrypt. + * @param raw The raw password to hash. + * @returns A Promise that resolves to the hashed password. + */ +export async function hash(raw: string) { + return await bcrypt.hash(raw, 10) +} + +/** + * Verifies a raw password against a hashed password using bcrypt. + * @param raw The raw password to verify. + * @param hash The hashed password to compare against. + * @returns A Promise that resolves to a boolean indicating whether the passwords match. + */ +export async function verify(raw: string, hash: string) { + return await bcrypt.compare(raw, hash) +}