diff --git a/apps/web/src/app/error.tsx b/apps/web/src/app/error.tsx index efb0260..4d84b93 100644 --- a/apps/web/src/app/error.tsx +++ b/apps/web/src/app/error.tsx @@ -2,7 +2,7 @@ // Error components must be Client Components import * as React from "react"; -import { signOut } from "next-auth/react"; +import { signOut } from "@/lib/actions/auth"; import { RiAlarmWarningFill } from "react-icons/ri"; import { Button } from "@tanya.in/ui/button"; diff --git a/apps/web/src/components/signin-form.tsx b/apps/web/src/components/signin-form.tsx index d5600c3..2071b83 100644 --- a/apps/web/src/components/signin-form.tsx +++ b/apps/web/src/components/signin-form.tsx @@ -3,7 +3,7 @@ import * as React from "react"; import Link from "next/link"; import { signIn } from "@/lib/actions/auth"; -import { signInFormSchema } from "@/lib/validation/sign-in"; +import { authSchema } from "@/server/api/routers/auth/auth.input"; import { FaEye, FaEyeSlash } from "react-icons/fa"; import { Button } from "@tanya.in/ui/button"; @@ -22,7 +22,7 @@ export function SignInForm() { const [isPending, setIsPending] = React.useState(false); const methods = useForm({ - schema: signInFormSchema, + schema: authSchema, mode: "onTouched", }); const { handleSubmit, control } = methods; diff --git a/apps/web/src/components/user.tsx b/apps/web/src/components/user.tsx index 7890099..88907da 100644 --- a/apps/web/src/components/user.tsx +++ b/apps/web/src/components/user.tsx @@ -1,7 +1,7 @@ "use client"; import type { Session } from "next-auth"; -import { signOut } from "next-auth/react"; +import { signOut } from "@/lib/actions/auth"; import { Avatar } from "@tanya.in/ui/avatar"; import { @@ -29,7 +29,7 @@ export function UserButton(props: { user?: Session["user"] }) { aria-label="Profile Actions" variant="flat" disabledKeys={["profile"]} - onAction={async () => signOut()} + onAction={async () => await signOut()} >

Signed in as

diff --git a/apps/web/src/lib/actions/auth.ts b/apps/web/src/lib/actions/auth.ts index 6354a5f..26b7506 100644 --- a/apps/web/src/lib/actions/auth.ts +++ b/apps/web/src/lib/actions/auth.ts @@ -1,11 +1,11 @@ "use server"; -import { signIn as attempt } from "@/server/auth"; +import { signInServer, signOutServer } from "@/server/auth"; import { AuthError } from "next-auth"; export async function signIn(email: string, password: string) { try { - await attempt("credentials", { + await signInServer("credentials", { email: email, password: password, redirectTo: "/", @@ -19,3 +19,7 @@ export async function signIn(email: string, password: string) { throw error; } } + +export async function signOut() { + await signOutServer(); +} diff --git a/apps/web/src/lib/validation/sign-in.ts b/apps/web/src/lib/validation/sign-in.ts deleted file mode 100644 index a619a51..0000000 --- a/apps/web/src/lib/validation/sign-in.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { z } from "zod"; - -export const signInFormSchema = z.object({ - email: z.string().email(), - password: z - .string() - .min(8, { message: "Password must be at least 8 characters long" }), -}); - -export type SignInFormValues = z.infer; diff --git a/apps/web/src/server/api/routers/auth/auth.input.ts b/apps/web/src/server/api/routers/auth/auth.input.ts index 42be702..985eb05 100644 --- a/apps/web/src/server/api/routers/auth/auth.input.ts +++ b/apps/web/src/server/api/routers/auth/auth.input.ts @@ -1,7 +1,13 @@ import { users } from "@/server/db/schema"; import { createInsertSchema } from "drizzle-zod"; +import { z } from "zod"; -export const authSchema = createInsertSchema(users).pick({ +export const authSchema = createInsertSchema(users, { + email: z.string().email(), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters long" }), +}).pick({ email: true, password: true, }); diff --git a/apps/web/src/server/api/routers/auth/auth.procedure.ts b/apps/web/src/server/api/routers/auth/auth.procedure.ts index bd82935..0c83456 100644 --- a/apps/web/src/server/api/routers/auth/auth.procedure.ts +++ b/apps/web/src/server/api/routers/auth/auth.procedure.ts @@ -1,24 +1,5 @@ -import { createTRPCRouter, publicProcedure } from "@/server/api/trpc"; -import { signIn } from "@/server/auth"; -import { TRPCError } from "@trpc/server"; -import { AuthError } from "next-auth"; - -import { authSchema } from "./auth.input"; +import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc"; export const authRouter = createTRPCRouter({ - login: publicProcedure.input(authSchema).mutation(async ({ input }) => { - try { - await signIn("credentials", { - email: input.email, - password: input.password, - redirectTo: "/", - }); - } catch (error) { - if (error instanceof AuthError) - throw new TRPCError({ - code: "BAD_REQUEST", - message: error.cause?.err?.message ?? "Oops! Something went wrong!", - }); - } - }), + me: protectedProcedure.query(({ ctx }) => ctx.session.user.id), }); diff --git a/apps/web/src/server/auth.config.ts b/apps/web/src/server/auth.config.ts index 851b58f..a2ce4a4 100644 --- a/apps/web/src/server/auth.config.ts +++ b/apps/web/src/server/auth.config.ts @@ -1,5 +1,5 @@ import type { NextAuthConfig } from "next-auth"; -import { signInFormSchema } from "@/lib/validation/sign-in"; +import { authSchema } from "@/server/api/routers/auth/auth.input"; import { db } from "@/server/db"; import bcrypt from "bcryptjs"; import CredentialsProvider from "next-auth/providers/credentials"; @@ -19,8 +19,7 @@ export default { }, }, async authorize(credentials) { - const { email, password } = - await signInFormSchema.parseAsync(credentials); + const { email, password } = await authSchema.parseAsync(credentials); const user = await db.query.users.findFirst({ where: (user, { eq }) => eq(user.email, email), }); @@ -30,7 +29,11 @@ export default { if (!(await bcrypt.compare(password, user.password))) { throw new Error("Invalid credentials"); } - return user; + + return { + ...user, + password: undefined, + }; }, }), ], diff --git a/apps/web/src/server/auth.ts b/apps/web/src/server/auth.ts index ef20124..60f4389 100644 --- a/apps/web/src/server/auth.ts +++ b/apps/web/src/server/auth.ts @@ -1,13 +1,10 @@ import type { DefaultSession } from "next-auth"; -import authConfig from "@/server/auth.config"; -import NextAuth from "next-auth"; - -import "next-auth/jwt"; - import type { z } from "zod"; import { env } from "@/env"; +import authConfig from "@/server/auth.config"; import { users } from "@/server/db/schema"; import { createSelectSchema } from "drizzle-zod"; +import NextAuth from "next-auth"; const user = createSelectSchema(users); type UserData = Omit, "password">; @@ -18,28 +15,15 @@ declare module "next-auth" { /** * Returned by `auth`, `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context */ - interface Session { - user: UserData & - /** - * By default, TypeScript merges new interface properties and overwrites existing ones. - * In this case, the default session user properties will be overwritten, - * with the new ones defined above. To keep the default session user properties, - * you need to add them back into the newly declared interface. - */ - DefaultSession["user"]; - } -} - -declare module "next-auth/jwt" { - interface JWT { - role: UserData["role"]; + interface Session extends DefaultSession { + user: User; } } export const { handlers: { GET, POST }, - signIn, - signOut, + signIn: signInServer, + signOut: signOutServer, auth, } = NextAuth({ ...authConfig, @@ -49,11 +33,12 @@ export const { callbacks: { jwt({ token, user }) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (user) token.role = user.role; + if (user) token.user = user; return token; }, session({ session, token }) { - session.user.role = token.role; + //@ts-expect-error - next-auth type is pain in the ass + session.user = token.user; return session; }, }, diff --git a/bun.lockb b/bun.lockb index da208c3..73ddd7d 100755 Binary files a/bun.lockb and b/bun.lockb differ