From a26928e10bc05d6983dfdfc702fb5fe61454fe2e Mon Sep 17 00:00:00 2001 From: Mirfayz Karimoff Date: Wed, 5 Jun 2024 18:33:46 +0500 Subject: [PATCH 01/11] set up basic auth with username & password --- actions/auth.ts | 152 +++++++++ app/_components/SignOutBtn.tsx | 20 ++ app/page.tsx | 12 + app/signin/page.tsx | 37 +++ app/signup/page.tsx | 37 +++ drizzle/schema.ts | 20 ++ next.config.mjs | 6 +- package.json | 3 + pnpm-lock.yaml | 591 ++++++++++++++++++++++++++++++++- utils/auth.ts | 75 +++++ 10 files changed, 943 insertions(+), 10 deletions(-) create mode 100644 actions/auth.ts create mode 100644 app/_components/SignOutBtn.tsx create mode 100644 app/signin/page.tsx create mode 100644 app/signup/page.tsx create mode 100644 utils/auth.ts diff --git a/actions/auth.ts b/actions/auth.ts new file mode 100644 index 00000000..dd2cb8ac --- /dev/null +++ b/actions/auth.ts @@ -0,0 +1,152 @@ +"use server"; + +import { hash, verify } from "@node-rs/argon2"; +import { generateIdFromEntropySize } from "lucia"; +import { db } from "~/drizzle/db"; +import { user } from "~/drizzle/schema"; +import { lucia, validateRequest } from "~/utils/auth"; +import { cookies } from "next/headers"; +import { redirect } from "next/navigation"; +import { eq } from "drizzle-orm"; + +export async function signup(formData: FormData) { + const username = formData.get("username"); + // username must be between 4 ~ 31 characters, and only consists of lowercase letters, 0-9, + + if ( + typeof username !== "string" || + username.length < 3 || + username.length > 31 || + !/^[a-z0-9_-]+$/.test(username) + ) { + return { + error: "Invalid username", + }; + } + + const password = formData.get("password"); + + if ( + typeof password !== "string" || + password.length < 6 || + password.length > 255 + ) { + return { + error: "Invalid password", + }; + } + + const passwordHash = await hash(password, { + // recommended minimum parameters + memoryCost: 19456, + timeCost: 2, + outputLen: 32, + parallelism: 1, + }); + + const userId = generateIdFromEntropySize(10); // 16 characters long + + // TODO: check if username is already used + await db.insert(user).values({ + id: userId, + username, + passwordHash, + }); + + const session = await lucia.createSession(userId, {}); + const sessionCookie = lucia.createSessionCookie(session.id); + cookies().set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes + ); + return redirect("/"); +} + +export async function signin(formData: FormData) { + const username = formData.get("username"); + + if ( + typeof username !== "string" || + username.length < 3 || + username.length > 31 || + !/^[a-z0-9_-]+$/.test(username) + ) { + return { + error: "Invalid username", + }; + } + + const password = formData.get("password"); + if ( + typeof password !== "string" || + password.length < 6 || + password.length > 255 + ) { + return { + error: "Invalid password", + }; + } + + const [existingUser] = await db + .select() + .from(user) + .where(eq(user.username, username.toLowerCase())) + .limit(1); + + if (!existingUser) { + // NOTE: + // Returning immediately allows malicious actors to figure out valid usernames from response times, + // allowing them to only focus on guessing passwords in brute-force attacks. + // As a preventive measure, you may want to hash passwords even for invalid usernames. + // However, valid usernames can be already be revealed with the signup page among other methods. + // It will also be much more resource intensive. + // Since protecting against this is non-trivial, + // it is crucial your implementation is protected against brute-force attacks with login throttling etc. + // If usernames are public, you may outright tell the user that the username is invalid. + console.log("invalid username"); + return { + error: "Incorrect username or password", + }; + } + + const validPassword = await verify(existingUser.passwordHash, password, { + memoryCost: 19456, + timeCost: 2, + outputLen: 32, + parallelism: 1, + }); + if (!validPassword) { + return { + error: "Incorrect username or password", + }; + } + + const session = await lucia.createSession(existingUser.id, {}); + const sessionCookie = lucia.createSessionCookie(session.id); + cookies().set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes + ); + return redirect("/"); +} + +export async function signout() { + const { session } = await validateRequest(); + if (!session) { + return { + error: "Unauthorized", + }; + } + + await lucia.invalidateSession(session.id); + + const sessionCookie = lucia.createBlankSessionCookie(); + cookies().set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes + ); + return redirect("/signin"); +} diff --git a/app/_components/SignOutBtn.tsx b/app/_components/SignOutBtn.tsx new file mode 100644 index 00000000..ae1e71b7 --- /dev/null +++ b/app/_components/SignOutBtn.tsx @@ -0,0 +1,20 @@ +"use client"; + +import React from "react"; +import { signout } from "~/actions/auth"; + +const SignOutBtn = () => { + return ( + + ); +}; + +export default SignOutBtn; diff --git a/app/page.tsx b/app/page.tsx index 10772c8d..1b452291 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,7 +1,17 @@ +import { redirect } from "next/navigation"; +import { signout } from "~/actions/auth"; import { getOrganizations } from "~/actions/organizations"; import CreateOrgForm from "~/components/CreateOrgForm"; +import { validateRequest } from "~/utils/auth"; +import SignOutBtn from "./_components/SignOutBtn"; export default async function Home() { + const { user } = await validateRequest(); + + if (!user) { + return redirect("/signin"); + } + const allOrgs = await getOrganizations(); return (
@@ -13,6 +23,8 @@ export default async function Home() {
{org.name}
))} + +
); } diff --git a/app/signin/page.tsx b/app/signin/page.tsx new file mode 100644 index 00000000..fa6eaa4c --- /dev/null +++ b/app/signin/page.tsx @@ -0,0 +1,37 @@ +import Link from "next/link"; +import { signin } from "~/actions/auth"; + +export default async function Page() { + return ( +
+

Sign in

+
+
+ + +
+
+
+ + +
+
+ +
+ +
+

+ Do not have an account{" "} + + Sign Up + +

+
+
+ ); +} diff --git a/app/signup/page.tsx b/app/signup/page.tsx new file mode 100644 index 00000000..d91cf172 --- /dev/null +++ b/app/signup/page.tsx @@ -0,0 +1,37 @@ +import Link from "next/link"; +import { signup } from "~/actions/auth"; + +export default async function Page() { + return ( +
+

Create an account

+
+
+ + +
+
+
+ + +
+
+ +
+ +
+

+ Already have an account{" "} + + Sign In + +

+
+
+ ); +} diff --git a/drizzle/schema.ts b/drizzle/schema.ts index 14c140fc..d421573e 100644 --- a/drizzle/schema.ts +++ b/drizzle/schema.ts @@ -5,6 +5,7 @@ import { pgTable, serial, text, + timestamp, } from "drizzle-orm/pg-core"; export const organizations = pgTable("organizations", { @@ -77,3 +78,22 @@ export const participants = pgTable("participants", { identifier: text("identifier").notNull().unique(), label: text("label"), }); + +export const user = pgTable("user", { + id: text("id").primaryKey(), + username: text("username").notNull().unique(), + passwordHash: text("password_hash").notNull(), +}); + +export const session = pgTable("session", { + id: text("id").primaryKey(), + userId: text("user_id") + .notNull() + .references(() => user.id), + expiresAt: timestamp("expires_at", { + withTimezone: true, + mode: "date", + }).notNull(), +}); + +export type UserType = typeof user.$inferSelect; diff --git a/next.config.mjs b/next.config.mjs index 4678774e..f6f4e20e 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,8 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + experimental: { + serverComponentsExternalPackages: ["@node-rs/argon2"], + }, +}; export default nextConfig; diff --git a/package.json b/package.json index 31c9e4f9..7f1a233c 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,11 @@ "db:studio": "drizzle-kit studio --config=drizzle.config.ts" }, "dependencies": { + "@lucia-auth/adapter-drizzle": "^1.0.7", + "@node-rs/argon2": "^1.8.3", "@t3-oss/env-nextjs": "^0.10.1", "drizzle-orm": "^0.31.1", + "lucia": "^3.2.0", "next": "14.2.3", "postgres": "^3.4.4", "react": "^18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6128994e..c6380534 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,12 +8,21 @@ importers: .: dependencies: + '@lucia-auth/adapter-drizzle': + specifier: ^1.0.7 + version: 1.0.7(lucia@3.2.0) + '@node-rs/argon2': + specifier: ^1.8.3 + version: 1.8.3 '@t3-oss/env-nextjs': specifier: ^0.10.1 version: 0.10.1(typescript@5.4.5)(zod@3.23.8) drizzle-orm: specifier: ^0.31.1 version: 0.31.1(@types/react@18.3.3)(postgres@3.4.4)(react@18.3.1) + lucia: + specifier: ^3.2.0 + version: 3.2.0 next: specifier: 14.2.3 version: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -68,6 +77,21 @@ packages: resolution: {integrity: sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==} engines: {node: '>=6.9.0'} + '@emnapi/core@0.45.0': + resolution: {integrity: sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==} + + '@emnapi/core@1.2.0': + resolution: {integrity: sha512-E7Vgw78I93we4ZWdYCb4DGAwRROGkMIXk7/y87UmANR+J6qsWusmC3gLt0H+O0KOt5e6O38U8oJamgbudrES/w==} + + '@emnapi/runtime@0.45.0': + resolution: {integrity: sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==} + + '@emnapi/runtime@1.2.0': + resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==} + + '@emnapi/wasi-threads@1.0.1': + resolution: {integrity: sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==} + '@esbuild-kit/core-utils@3.3.2': resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} @@ -395,6 +419,14 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@lucia-auth/adapter-drizzle@1.0.7': + resolution: {integrity: sha512-X/V7fLBca8EC/gPXCntwbQpb0+F9oEuRoHElvsi9rCrdnGhCMNxHgwAvgiQ6pes+rIYpyvx4n3hvjqo/fPo03A==} + peerDependencies: + lucia: 3.x + + '@napi-rs/wasm-runtime@0.2.4': + resolution: {integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==} + '@next/env@14.2.3': resolution: {integrity: sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==} @@ -455,6 +487,267 @@ packages: cpu: [x64] os: [win32] + '@node-rs/argon2-android-arm-eabi@1.7.0': + resolution: {integrity: sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@node-rs/argon2-android-arm-eabi@1.8.3': + resolution: {integrity: sha512-JFZPlNM0A8Og+Tncb8UZsQrhEMlbHBXPsT3hRoKImzVmTmq28Os0ucFWow0AACp2coLHBSydXH3Dh0lZup3rWw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@node-rs/argon2-android-arm64@1.7.0': + resolution: {integrity: sha512-s9j/G30xKUx8WU50WIhF0fIl1EdhBGq0RQ06lEhZ0Gi0ap8lhqbE2Bn5h3/G2D1k0Dx+yjeVVNmt/xOQIRG38A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@node-rs/argon2-android-arm64@1.8.3': + resolution: {integrity: sha512-zaf8P3T92caeW2xnMA7P1QvRA4pIt/04oilYP44XlTCtMye//vwXDMeK53sl7dvYiJKnzAWDRx41k8vZvpZazg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@node-rs/argon2-darwin-arm64@1.7.0': + resolution: {integrity: sha512-ZIz4L6HGOB9U1kW23g+m7anGNuTZ0RuTw0vNp3o+2DWpb8u8rODq6A8tH4JRL79S+Co/Nq608m9uackN2pe0Rw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@node-rs/argon2-darwin-arm64@1.8.3': + resolution: {integrity: sha512-DV/IbmLGdNXBtXb5o2UI5ba6kvqXqPAJgmMOTUCuHeBSp992GlLHdfU4rzGu0dNrxudBnunNZv+crd0YdEQSUA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@node-rs/argon2-darwin-x64@1.7.0': + resolution: {integrity: sha512-5oi/pxqVhODW/pj1+3zElMTn/YukQeywPHHYDbcAW3KsojFjKySfhcJMd1DjKTc+CHQI+4lOxZzSUzK7mI14Hw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@node-rs/argon2-darwin-x64@1.8.3': + resolution: {integrity: sha512-YMjmBGFZhLfYjfQ2gll9A+BZu/zAMV7lWZIbKxb7ZgEofILQwuGmExjDtY3Jplido/6leCEdpmlk2oIsME00LA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@node-rs/argon2-freebsd-x64@1.7.0': + resolution: {integrity: sha512-Ify08683hA4QVXYoIm5SUWOY5DPIT/CMB0CQT+IdxQAg/F+qp342+lUkeAtD5bvStQuCx/dFO3bnnzoe2clMhA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@node-rs/argon2-freebsd-x64@1.8.3': + resolution: {integrity: sha512-Hq3Rj5Yb2RolTG/luRPnv+XiGCbi5nAK25Pc8ou/tVapwX+iktEm/NXbxc5zsMxraYVkCvfdwBjweC5O+KqCGw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@node-rs/argon2-linux-arm-gnueabihf@1.7.0': + resolution: {integrity: sha512-7DjDZ1h5AUHAtRNjD19RnQatbhL+uuxBASuuXIBu4/w6Dx8n7YPxwTP4MXfsvuRgKuMWiOb/Ub/HJ3kXVCXRkg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@node-rs/argon2-linux-arm-gnueabihf@1.8.3': + resolution: {integrity: sha512-x49l8RgzKoG0/V0IXa5rrEl1TcJEc936ctlYFvqcunSOyowZ6kiWtrp1qrbOR8gbaNILl11KTF52vF6+h8UlEQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@node-rs/argon2-linux-arm64-gnu@1.7.0': + resolution: {integrity: sha512-nJDoMP4Y3YcqGswE4DvP080w6O24RmnFEDnL0emdI8Nou17kNYBzP2546Nasx9GCyLzRcYQwZOUjrtUuQ+od2g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@node-rs/argon2-linux-arm64-gnu@1.8.3': + resolution: {integrity: sha512-gJesam/qA63reGkb9qJ2TjFSLBtY41zQh2oei7nfnYsmVQPuHHWItJxEa1Bm21SPW53gZex4jFJbDIgj0+PxIw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@node-rs/argon2-linux-arm64-musl@1.7.0': + resolution: {integrity: sha512-BKWS8iVconhE3jrb9mj6t1J9vwUqQPpzCbUKxfTGJfc+kNL58F1SXHBoe2cDYGnHrFEHTY0YochzXoAfm4Dm/A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@node-rs/argon2-linux-arm64-musl@1.8.3': + resolution: {integrity: sha512-7O6kQdSKzB4Tjx/EBa8zKIxnmLkQE8VdJgPm6Ksrpn+ueo0mx2xf76fIDnbbTCtm3UbB+y+FkTo2wLA7tOqIKg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@node-rs/argon2-linux-x64-gnu@1.7.0': + resolution: {integrity: sha512-EmgqZOlf4Jurk/szW1iTsVISx25bKksVC5uttJDUloTgsAgIGReCpUUO1R24pBhu9ESJa47iv8NSf3yAfGv6jQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@node-rs/argon2-linux-x64-gnu@1.8.3': + resolution: {integrity: sha512-OBH+EFG7BGjFyldaao2H2gSCLmjtrrwf420B1L+lFn7JLW9UAjsIPFKAcWsYwPa/PwYzIge9Y7SGcpqlsSEX0w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@node-rs/argon2-linux-x64-musl@1.7.0': + resolution: {integrity: sha512-/o1efYCYIxjfuoRYyBTi2Iy+1iFfhqHCvvVsnjNSgO1xWiWrX0Rrt/xXW5Zsl7vS2Y+yu8PL8KFWRzZhaVxfKA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@node-rs/argon2-linux-x64-musl@1.8.3': + resolution: {integrity: sha512-bDbMuyekIxZaN7NaX+gHVkOyABB8bcMEJYeRPW1vCXKHj3brJns1wiUFSxqeUXreupifNVJlQfPt1Y5B/vFXgQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@node-rs/argon2-wasm32-wasi@1.7.0': + resolution: {integrity: sha512-Evmk9VcxqnuwQftfAfYEr6YZYSPLzmKUsbFIMep5nTt9PT4XYRFAERj7wNYp+rOcBenF3X4xoB+LhwcOMTNE5w==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@node-rs/argon2-wasm32-wasi@1.8.3': + resolution: {integrity: sha512-NBf2cMCDbNKMzp13Pog8ZPmI0M9U4Ak5b95EUjkp17kdKZFds12dwW67EMnj7Zy+pRqby2QLECaWebDYfNENTg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@node-rs/argon2-win32-arm64-msvc@1.7.0': + resolution: {integrity: sha512-qgsU7T004COWWpSA0tppDqDxbPLgg8FaU09krIJ7FBl71Sz8SFO40h7fDIjfbTT5w7u6mcaINMQ5bSHu75PCaA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@node-rs/argon2-win32-arm64-msvc@1.8.3': + resolution: {integrity: sha512-AHpPo7UbdW5WWjwreVpgFSY0o1RY4A7cUFaqDXZB2OqEuyrhMxBdZct9PX7PQKI18D85pLsODnR+gvVuTwJ6rQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@node-rs/argon2-win32-ia32-msvc@1.7.0': + resolution: {integrity: sha512-JGafwWYQ/HpZ3XSwP4adQ6W41pRvhcdXvpzIWtKvX+17+xEXAe2nmGWM6s27pVkg1iV2ZtoYLRDkOUoGqZkCcg==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@node-rs/argon2-win32-ia32-msvc@1.8.3': + resolution: {integrity: sha512-bqzn2rcQkEwCINefhm69ttBVVkgHJb/V03DdBKsPFtiX6H47axXKz62d1imi26zFXhOEYxhKbu3js03GobJOLw==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@node-rs/argon2-win32-x64-msvc@1.7.0': + resolution: {integrity: sha512-9oq4ShyFakw8AG3mRls0AoCpxBFcimYx7+jvXeAf2OqKNO+mSA6eZ9z7KQeVCi0+SOEUYxMGf5UiGiDb9R6+9Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@node-rs/argon2-win32-x64-msvc@1.8.3': + resolution: {integrity: sha512-ILlrRThdbp5xNR5gwYM2ic1n/vG5rJ8dQZ+YMRqksl+lnTJ/6FDe5BOyIhiPtiDwlCiCtUA+1NxpDB9KlUCAIA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@node-rs/argon2@1.7.0': + resolution: {integrity: sha512-zfULc+/tmcWcxn+nHkbyY8vP3+MpEqKORbszt4UkpqZgBgDAAIYvuDN/zukfTgdmo6tmJKKVfzigZOPk4LlIog==} + engines: {node: '>= 10'} + + '@node-rs/argon2@1.8.3': + resolution: {integrity: sha512-sf/QAEI59hsMEEE2J8vO4hKrXrv4Oplte3KI2N4MhMDYpytH0drkVfErmHBfWFZxxIEK03fX1WsBNswS2nIZKg==} + engines: {node: '>= 10'} + + '@node-rs/bcrypt-android-arm-eabi@1.9.0': + resolution: {integrity: sha512-nOCFISGtnodGHNiLrG0WYLWr81qQzZKYfmwHc7muUeq+KY0sQXyHOwZk9OuNQAWv/lnntmtbwkwT0QNEmOyLvA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@node-rs/bcrypt-android-arm64@1.9.0': + resolution: {integrity: sha512-+ZrIAtigVmjYkqZQTThHVlz0+TG6D+GDHWhVKvR2DifjtqJ0i+mb9gjo++hN+fWEQdWNGxKCiBBjwgT4EcXd6A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@node-rs/bcrypt-darwin-arm64@1.9.0': + resolution: {integrity: sha512-CQiS+F9Pa0XozvkXR1g7uXE9QvBOPOplDg0iCCPRYTN9PqA5qYxhwe48G3o+v2UeQceNRrbnEtWuANm7JRqIhw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@node-rs/bcrypt-darwin-x64@1.9.0': + resolution: {integrity: sha512-4pTKGawYd7sNEjdJ7R/R67uwQH1VvwPZ0SSUMmeNHbxD5QlwAPXdDH11q22uzVXsvNFZ6nGQBg8No5OUGpx6Ug==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@node-rs/bcrypt-freebsd-x64@1.9.0': + resolution: {integrity: sha512-UmWzySX4BJhT/B8xmTru6iFif3h0Rpx3TqxRLCcbgmH43r7k5/9QuhpiyzpvKGpKHJCFNm4F3rC2wghvw5FCIg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@node-rs/bcrypt-linux-arm-gnueabihf@1.9.0': + resolution: {integrity: sha512-8qoX4PgBND2cVwsbajoAWo3NwdfJPEXgpCsZQZURz42oMjbGyhhSYbovBCskGU3EBLoC8RA2B1jFWooeYVn5BA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@node-rs/bcrypt-linux-arm64-gnu@1.9.0': + resolution: {integrity: sha512-TuAC6kx0SbcIA4mSEWPi+OCcDjTQUMl213v5gMNlttF+D4ieIZx6pPDGTaMO6M2PDHTeCG0CBzZl0Lu+9b0c7Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@node-rs/bcrypt-linux-arm64-musl@1.9.0': + resolution: {integrity: sha512-/sIvKDABOI8QOEnLD7hIj02BVaNOuCIWBKvxcJOt8+TuwJ6zmY1UI5kSv9d99WbiHjTp97wtAUbZQwauU4b9ew==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@node-rs/bcrypt-linux-x64-gnu@1.9.0': + resolution: {integrity: sha512-DyyhDHDsLBsCKz1tZ1hLvUZSc1DK0FU0v52jK6IBQxrj24WscSU9zZe7ie/V9kdmA4Ep57BfpWX8Dsa2JxGdgQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@node-rs/bcrypt-linux-x64-musl@1.9.0': + resolution: {integrity: sha512-duIiuqQ+Lew8ASSAYm6ZRqcmfBGWwsi81XLUwz86a2HR7Qv6V4yc3ZAUQovAikhjCsIqe8C11JlAZSK6+PlXYg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@node-rs/bcrypt-wasm32-wasi@1.9.0': + resolution: {integrity: sha512-ylaGmn9Wjwv/D5lxtawttx3H6Uu2WTTR7lWlRHGT6Ga/MB1Vj4OjSGUW8G8zIVnKuXpGbZ92pgHlt4HUpSLctw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@node-rs/bcrypt-win32-arm64-msvc@1.9.0': + resolution: {integrity: sha512-2h86gF7QFyEzODuDFml/Dp1MSJoZjxJ4yyT2Erf4NkwsiA5MqowUhUsorRwZhX6+2CtlGa7orbwi13AKMsYndw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@node-rs/bcrypt-win32-ia32-msvc@1.9.0': + resolution: {integrity: sha512-kqxalCvhs4FkN0+gWWfa4Bdy2NQAkfiqq/CEf6mNXC13RSV673Ev9V8sRlQyNpCHCNkeXfOT9pgoBdJmMs9muA==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@node-rs/bcrypt-win32-x64-msvc@1.9.0': + resolution: {integrity: sha512-2y0Tuo6ZAT2Cz8V7DHulSlv1Bip3zbzeXyeur+uR25IRNYXKvI/P99Zl85Fbuu/zzYAZRLLlGTRe6/9IHofe/w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@node-rs/bcrypt@1.9.0': + resolution: {integrity: sha512-u2OlIxW264bFUfvbFqDz9HZKFjwe8FHFtn7T/U8mYjPZ7DWYpbUB+/dkW/QgYfMSfR0ejkyuWaBBe0coW7/7ig==} + engines: {node: '>= 10'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -498,6 +791,12 @@ packages: typescript: optional: true + '@tybys/wasm-util@0.8.3': + resolution: {integrity: sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==} + + '@tybys/wasm-util@0.9.0': + resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} @@ -1076,6 +1375,9 @@ packages: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} + fs-monkey@1.0.6: + resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -1390,6 +1692,16 @@ packages: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} + lucia@3.2.0: + resolution: {integrity: sha512-eXMxXwk6hqtjRTj4W/x3EnTUtAztLPm0p2N2TEBMDEbakDLXiYnDQ9z/qahjPdPdhPguQc+vwO0/88zIWxlpuw==} + + memfs-browser@3.5.10302: + resolution: {integrity: sha512-JJTc/nh3ig05O0gBBGZjTCPOyydaTxNF0uHYBrcc1gHNnO+KIHIvo0Y1FKCJsaei6FCl8C6xfQomXqu+cuzkIw==} + + memfs@3.5.3: + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} + engines: {node: '>= 4.0.0'} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1501,6 +1813,9 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + oslo@1.2.0: + resolution: {integrity: sha512-OoFX6rDsNcOQVAD2gQD/z03u4vEjWZLzJtwkmgfRF+KpQUXwdgEXErD7zNhyowmHwHefP+PM9Pw13pgpHMRlzw==} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -1937,6 +2252,32 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@emnapi/core@0.45.0': + dependencies: + tslib: 2.6.2 + optional: true + + '@emnapi/core@1.2.0': + dependencies: + '@emnapi/wasi-threads': 1.0.1 + tslib: 2.6.2 + optional: true + + '@emnapi/runtime@0.45.0': + dependencies: + tslib: 2.6.2 + optional: true + + '@emnapi/runtime@1.2.0': + dependencies: + tslib: 2.6.2 + optional: true + + '@emnapi/wasi-threads@1.0.1': + dependencies: + tslib: 2.6.2 + optional: true + '@esbuild-kit/core-utils@3.3.2': dependencies: esbuild: 0.18.20 @@ -2143,6 +2484,17 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 + '@lucia-auth/adapter-drizzle@1.0.7(lucia@3.2.0)': + dependencies: + lucia: 3.2.0 + + '@napi-rs/wasm-runtime@0.2.4': + dependencies: + '@emnapi/core': 1.2.0 + '@emnapi/runtime': 1.2.0 + '@tybys/wasm-util': 0.9.0 + optional: true + '@next/env@14.2.3': {} '@next/eslint-plugin-next@14.2.3': @@ -2176,6 +2528,195 @@ snapshots: '@next/swc-win32-x64-msvc@14.2.3': optional: true + '@node-rs/argon2-android-arm-eabi@1.7.0': + optional: true + + '@node-rs/argon2-android-arm-eabi@1.8.3': + optional: true + + '@node-rs/argon2-android-arm64@1.7.0': + optional: true + + '@node-rs/argon2-android-arm64@1.8.3': + optional: true + + '@node-rs/argon2-darwin-arm64@1.7.0': + optional: true + + '@node-rs/argon2-darwin-arm64@1.8.3': + optional: true + + '@node-rs/argon2-darwin-x64@1.7.0': + optional: true + + '@node-rs/argon2-darwin-x64@1.8.3': + optional: true + + '@node-rs/argon2-freebsd-x64@1.7.0': + optional: true + + '@node-rs/argon2-freebsd-x64@1.8.3': + optional: true + + '@node-rs/argon2-linux-arm-gnueabihf@1.7.0': + optional: true + + '@node-rs/argon2-linux-arm-gnueabihf@1.8.3': + optional: true + + '@node-rs/argon2-linux-arm64-gnu@1.7.0': + optional: true + + '@node-rs/argon2-linux-arm64-gnu@1.8.3': + optional: true + + '@node-rs/argon2-linux-arm64-musl@1.7.0': + optional: true + + '@node-rs/argon2-linux-arm64-musl@1.8.3': + optional: true + + '@node-rs/argon2-linux-x64-gnu@1.7.0': + optional: true + + '@node-rs/argon2-linux-x64-gnu@1.8.3': + optional: true + + '@node-rs/argon2-linux-x64-musl@1.7.0': + optional: true + + '@node-rs/argon2-linux-x64-musl@1.8.3': + optional: true + + '@node-rs/argon2-wasm32-wasi@1.7.0': + dependencies: + '@emnapi/core': 0.45.0 + '@emnapi/runtime': 0.45.0 + '@tybys/wasm-util': 0.8.3 + memfs-browser: 3.5.10302 + optional: true + + '@node-rs/argon2-wasm32-wasi@1.8.3': + dependencies: + '@napi-rs/wasm-runtime': 0.2.4 + optional: true + + '@node-rs/argon2-win32-arm64-msvc@1.7.0': + optional: true + + '@node-rs/argon2-win32-arm64-msvc@1.8.3': + optional: true + + '@node-rs/argon2-win32-ia32-msvc@1.7.0': + optional: true + + '@node-rs/argon2-win32-ia32-msvc@1.8.3': + optional: true + + '@node-rs/argon2-win32-x64-msvc@1.7.0': + optional: true + + '@node-rs/argon2-win32-x64-msvc@1.8.3': + optional: true + + '@node-rs/argon2@1.7.0': + optionalDependencies: + '@node-rs/argon2-android-arm-eabi': 1.7.0 + '@node-rs/argon2-android-arm64': 1.7.0 + '@node-rs/argon2-darwin-arm64': 1.7.0 + '@node-rs/argon2-darwin-x64': 1.7.0 + '@node-rs/argon2-freebsd-x64': 1.7.0 + '@node-rs/argon2-linux-arm-gnueabihf': 1.7.0 + '@node-rs/argon2-linux-arm64-gnu': 1.7.0 + '@node-rs/argon2-linux-arm64-musl': 1.7.0 + '@node-rs/argon2-linux-x64-gnu': 1.7.0 + '@node-rs/argon2-linux-x64-musl': 1.7.0 + '@node-rs/argon2-wasm32-wasi': 1.7.0 + '@node-rs/argon2-win32-arm64-msvc': 1.7.0 + '@node-rs/argon2-win32-ia32-msvc': 1.7.0 + '@node-rs/argon2-win32-x64-msvc': 1.7.0 + + '@node-rs/argon2@1.8.3': + optionalDependencies: + '@node-rs/argon2-android-arm-eabi': 1.8.3 + '@node-rs/argon2-android-arm64': 1.8.3 + '@node-rs/argon2-darwin-arm64': 1.8.3 + '@node-rs/argon2-darwin-x64': 1.8.3 + '@node-rs/argon2-freebsd-x64': 1.8.3 + '@node-rs/argon2-linux-arm-gnueabihf': 1.8.3 + '@node-rs/argon2-linux-arm64-gnu': 1.8.3 + '@node-rs/argon2-linux-arm64-musl': 1.8.3 + '@node-rs/argon2-linux-x64-gnu': 1.8.3 + '@node-rs/argon2-linux-x64-musl': 1.8.3 + '@node-rs/argon2-wasm32-wasi': 1.8.3 + '@node-rs/argon2-win32-arm64-msvc': 1.8.3 + '@node-rs/argon2-win32-ia32-msvc': 1.8.3 + '@node-rs/argon2-win32-x64-msvc': 1.8.3 + + '@node-rs/bcrypt-android-arm-eabi@1.9.0': + optional: true + + '@node-rs/bcrypt-android-arm64@1.9.0': + optional: true + + '@node-rs/bcrypt-darwin-arm64@1.9.0': + optional: true + + '@node-rs/bcrypt-darwin-x64@1.9.0': + optional: true + + '@node-rs/bcrypt-freebsd-x64@1.9.0': + optional: true + + '@node-rs/bcrypt-linux-arm-gnueabihf@1.9.0': + optional: true + + '@node-rs/bcrypt-linux-arm64-gnu@1.9.0': + optional: true + + '@node-rs/bcrypt-linux-arm64-musl@1.9.0': + optional: true + + '@node-rs/bcrypt-linux-x64-gnu@1.9.0': + optional: true + + '@node-rs/bcrypt-linux-x64-musl@1.9.0': + optional: true + + '@node-rs/bcrypt-wasm32-wasi@1.9.0': + dependencies: + '@emnapi/core': 0.45.0 + '@emnapi/runtime': 0.45.0 + '@tybys/wasm-util': 0.8.3 + memfs-browser: 3.5.10302 + optional: true + + '@node-rs/bcrypt-win32-arm64-msvc@1.9.0': + optional: true + + '@node-rs/bcrypt-win32-ia32-msvc@1.9.0': + optional: true + + '@node-rs/bcrypt-win32-x64-msvc@1.9.0': + optional: true + + '@node-rs/bcrypt@1.9.0': + optionalDependencies: + '@node-rs/bcrypt-android-arm-eabi': 1.9.0 + '@node-rs/bcrypt-android-arm64': 1.9.0 + '@node-rs/bcrypt-darwin-arm64': 1.9.0 + '@node-rs/bcrypt-darwin-x64': 1.9.0 + '@node-rs/bcrypt-freebsd-x64': 1.9.0 + '@node-rs/bcrypt-linux-arm-gnueabihf': 1.9.0 + '@node-rs/bcrypt-linux-arm64-gnu': 1.9.0 + '@node-rs/bcrypt-linux-arm64-musl': 1.9.0 + '@node-rs/bcrypt-linux-x64-gnu': 1.9.0 + '@node-rs/bcrypt-linux-x64-musl': 1.9.0 + '@node-rs/bcrypt-wasm32-wasi': 1.9.0 + '@node-rs/bcrypt-win32-arm64-msvc': 1.9.0 + '@node-rs/bcrypt-win32-ia32-msvc': 1.9.0 + '@node-rs/bcrypt-win32-x64-msvc': 1.9.0 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2213,6 +2754,16 @@ snapshots: optionalDependencies: typescript: 5.4.5 + '@tybys/wasm-util@0.8.3': + dependencies: + tslib: 2.6.2 + optional: true + + '@tybys/wasm-util@0.9.0': + dependencies: + tslib: 2.6.2 + optional: true + '@types/json5@0.0.29': {} '@types/node@20.14.1': @@ -2716,8 +3267,8 @@ snapshots: '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.34.2(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) @@ -2735,13 +3286,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 4.3.5 enhanced-resolve: 5.16.1 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-core-module: 2.13.1 @@ -2752,18 +3303,18 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -2773,7 +3324,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -2952,6 +3503,9 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 + fs-monkey@1.0.6: + optional: true + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -3267,6 +3821,20 @@ snapshots: lru-cache@10.2.2: {} + lucia@3.2.0: + dependencies: + oslo: 1.2.0 + + memfs-browser@3.5.10302: + dependencies: + memfs: 3.5.3 + optional: true + + memfs@3.5.3: + dependencies: + fs-monkey: 1.0.6 + optional: true + merge2@1.4.1: {} micromatch@4.0.7: @@ -3390,6 +3958,11 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + oslo@1.2.0: + dependencies: + '@node-rs/argon2': 1.7.0 + '@node-rs/bcrypt': 1.9.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 diff --git a/utils/auth.ts b/utils/auth.ts new file mode 100644 index 00000000..a5b6b799 --- /dev/null +++ b/utils/auth.ts @@ -0,0 +1,75 @@ +import { Lucia } from "lucia"; +import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle"; +import { + session as sessionTable, + user as userTable, + UserType, +} from "~/drizzle/schema"; +import { db } from "~/drizzle/db"; +import type { Session, User } from "lucia"; +import { cache } from "react"; +import { cookies } from "next/headers"; + +const adapter = new DrizzlePostgreSQLAdapter(db, sessionTable, userTable); + +export const lucia = new Lucia(adapter, { + sessionCookie: { + // this sets cookies with super long expiration + // since Next.js doesn't allow Lucia to extend cookie expiration when rendering pages + expires: false, + attributes: { + // set to `true` when using HTTPS + secure: process.env.NODE_ENV === "production", + }, + }, + getUserAttributes: (attributes) => { + return { + // attributes has the type of DatabaseUserAttributes + username: attributes.username, + }; + }, +}); + +// IMPORTANT! +declare module "lucia" { + interface Register { + Lucia: typeof lucia; + DatabaseUserAttributes: UserType; + } +} + +export const validateRequest = cache( + async (): Promise< + { user: User; session: Session } | { user: null; session: null } + > => { + const sessionId = cookies().get(lucia.sessionCookieName)?.value ?? null; + if (!sessionId) { + return { + user: null, + session: null, + }; + } + + const result = await lucia.validateSession(sessionId); + // next.js throws when you attempt to set cookie when rendering page + try { + if (result.session && result.session.fresh) { + const sessionCookie = lucia.createSessionCookie(result.session.id); + cookies().set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes + ); + } + if (!result.session) { + const sessionCookie = lucia.createBlankSessionCookie(); + cookies().set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes + ); + } + } catch {} + return result; + } +); From 2074878b203a16d55abbc85829958957be3e26d7 Mon Sep 17 00:00:00 2001 From: Mirfayz Karimoff Date: Wed, 5 Jun 2024 19:31:47 +0500 Subject: [PATCH 02/11] set up shadcn UI --- app/globals.css | 87 ++++++++++++++++++++++++--------- components.json | 17 +++++++ components/ui/button.tsx | 56 ++++++++++++++++++++++ lib/utils.ts | 6 +++ package.json | 6 +++ pnpm-lock.yaml | 101 +++++++++++++++++++++++++++++++++++++-- tailwind.config.ts | 86 +++++++++++++++++++++++++++++---- 7 files changed, 325 insertions(+), 34 deletions(-) create mode 100644 components.json create mode 100644 components/ui/button.tsx create mode 100644 lib/utils.ts diff --git a/app/globals.css b/app/globals.css index 875c01e8..8abdb15c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -2,32 +2,75 @@ @tailwind components; @tailwind utilities; -:root { - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; -} - -@media (prefers-color-scheme: dark) { +@layer base { :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; + + --radius: 0.5rem; } -} -body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + } } -@layer utilities { - .text-balance { - text-wrap: balance; +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; } } diff --git a/components.json b/components.json new file mode 100644 index 00000000..57e19038 --- /dev/null +++ b/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "~/components", + "utils": "~/lib/utils" + } +} \ No newline at end of file diff --git a/components/ui/button.tsx b/components/ui/button.tsx new file mode 100644 index 00000000..d754ca03 --- /dev/null +++ b/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "~/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 00000000..d084ccad --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/package.json b/package.json index 7f1a233c..9d2358d1 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,19 @@ "dependencies": { "@lucia-auth/adapter-drizzle": "^1.0.7", "@node-rs/argon2": "^1.8.3", + "@radix-ui/react-slot": "^1.0.2", "@t3-oss/env-nextjs": "^0.10.1", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", "drizzle-orm": "^0.31.1", "lucia": "^3.2.0", + "lucide-react": "^0.383.0", "next": "14.2.3", "postgres": "^3.4.4", "react": "^18", "react-dom": "^18", + "tailwind-merge": "^2.3.0", + "tailwindcss-animate": "^1.0.7", "zod": "^3.23.8" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c6380534..4f219a41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,15 +14,27 @@ importers: '@node-rs/argon2': specifier: ^1.8.3 version: 1.8.3 + '@radix-ui/react-slot': + specifier: ^1.0.2 + version: 1.0.2(@types/react@18.3.3)(react@18.3.1) '@t3-oss/env-nextjs': specifier: ^0.10.1 version: 0.10.1(typescript@5.4.5)(zod@3.23.8) + class-variance-authority: + specifier: ^0.7.0 + version: 0.7.0 + clsx: + specifier: ^2.1.1 + version: 2.1.1 drizzle-orm: specifier: ^0.31.1 version: 0.31.1(@types/react@18.3.3)(postgres@3.4.4)(react@18.3.1) lucia: specifier: ^3.2.0 version: 3.2.0 + lucide-react: + specifier: ^0.383.0 + version: 0.383.0(react@18.3.1) next: specifier: 14.2.3 version: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -35,6 +47,12 @@ importers: react-dom: specifier: ^18 version: 18.3.1(react@18.3.1) + tailwind-merge: + specifier: ^2.3.0 + version: 2.3.0 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.3) zod: specifier: ^3.23.8 version: 3.23.8 @@ -764,6 +782,24 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@radix-ui/react-compose-refs@1.0.1': + resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.0.2': + resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@rushstack/eslint-patch@1.10.3': resolution: {integrity: sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==} @@ -991,9 +1027,20 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + class-variance-authority@0.7.0: + resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + clsx@2.0.0: + resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} + engines: {node: '>=6'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1695,6 +1742,11 @@ packages: lucia@3.2.0: resolution: {integrity: sha512-eXMxXwk6hqtjRTj4W/x3EnTUtAztLPm0p2N2TEBMDEbakDLXiYnDQ9z/qahjPdPdhPguQc+vwO0/88zIWxlpuw==} + lucide-react@0.383.0: + resolution: {integrity: sha512-13xlG0CQCJtzjSQYwwJ3WRqMHtRj3EXmLlorrARt7y+IHnxUCp3XyFNL1DfaGySWxHObDvnu1u1dV+0VMKHUSg==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 + memfs-browser@3.5.10302: resolution: {integrity: sha512-JJTc/nh3ig05O0gBBGZjTCPOyydaTxNF0uHYBrcc1gHNnO+KIHIvo0Y1FKCJsaei6FCl8C6xfQomXqu+cuzkIw==} @@ -2118,6 +2170,14 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + tailwind-merge@2.3.0: + resolution: {integrity: sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + tailwindcss@3.4.3: resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==} engines: {node: '>=14.0.0'} @@ -2732,6 +2792,21 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@radix-ui/react-compose-refs@1.0.1(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.6 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-slot@1.0.2(@types/react@18.3.3)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.6 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.3 + '@rushstack/eslint-patch@1.10.3': {} '@swc/counter@0.1.3': {} @@ -3001,8 +3076,16 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + class-variance-authority@0.7.0: + dependencies: + clsx: 2.0.0 + client-only@0.0.1: {} + clsx@2.0.0: {} + + clsx@2.1.1: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -3268,7 +3351,7 @@ snapshots: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.34.2(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) @@ -3292,7 +3375,7 @@ snapshots: enhanced-resolve: 5.16.1 eslint: 8.57.0 eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-core-module: 2.13.1 @@ -3314,7 +3397,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -3825,6 +3908,10 @@ snapshots: dependencies: oslo: 1.2.0 + lucide-react@0.383.0(react@18.3.1): + dependencies: + react: 18.3.1 + memfs-browser@3.5.10302: dependencies: memfs: 3.5.3 @@ -4266,6 +4353,14 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + tailwind-merge@2.3.0: + dependencies: + '@babel/runtime': 7.24.6 + + tailwindcss-animate@1.0.7(tailwindcss@3.4.3): + dependencies: + tailwindcss: 3.4.3 + tailwindcss@3.4.3: dependencies: '@alloc/quick-lru': 5.2.0 diff --git a/tailwind.config.ts b/tailwind.config.ts index 5dbfc1df..84287e82 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,12 +1,80 @@ -import type { Config } from "tailwindcss"; +import type { Config } from "tailwindcss" -const config: Config = { +const config = { + darkMode: ["class"], content: [ - "./pages/**/*.{js,ts,jsx,tsx,mdx}", - "./components/**/*.{js,ts,jsx,tsx,mdx}", - "./app/**/*.{js,ts,jsx,tsx,mdx}", - ], + './pages/**/*.{ts,tsx}', + './components/**/*.{ts,tsx}', + './app/**/*.{ts,tsx}', + './src/**/*.{ts,tsx}', + ], + prefix: "", + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + keyframes: { + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, + }, + plugins: [require("tailwindcss-animate")], +} satisfies Config - plugins: [], -}; -export default config; +export default config \ No newline at end of file From 4cc0e0b927aabac296c6de2ffe3f7c3a1da2f1ed Mon Sep 17 00:00:00 2001 From: Mirfayz Karimoff Date: Wed, 5 Jun 2024 20:06:13 +0500 Subject: [PATCH 03/11] polish up the ui for sign in and sign up pages with shadcn --- app/_components/SignOutBtn.tsx | 6 +-- app/globals.css | 8 +++- app/signin/page.tsx | 71 +++++++++++++++++------------- app/signup/page.tsx | 71 +++++++++++++++++------------- components/ui/card.tsx | 79 ++++++++++++++++++++++++++++++++++ components/ui/input.tsx | 25 +++++++++++ components/ui/label.tsx | 26 +++++++++++ package.json | 1 + pnpm-lock.yaml | 55 +++++++++++++++++++++-- 9 files changed, 277 insertions(+), 65 deletions(-) create mode 100644 components/ui/card.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx diff --git a/app/_components/SignOutBtn.tsx b/app/_components/SignOutBtn.tsx index ae1e71b7..d61a2f81 100644 --- a/app/_components/SignOutBtn.tsx +++ b/app/_components/SignOutBtn.tsx @@ -2,18 +2,18 @@ import React from "react"; import { signout } from "~/actions/auth"; +import { Button } from "~/components/ui/button"; const SignOutBtn = () => { return ( - + ); }; diff --git a/app/globals.css b/app/globals.css index 8abdb15c..46f918d6 100644 --- a/app/globals.css +++ b/app/globals.css @@ -4,7 +4,13 @@ @layer base { :root { - --background: 0 0% 100%; + --platinum-hue: 210; + --platinum-saturation: 43%; + --platinum-lightness: 95%; + --platinum: var(--platinum-hue) var(--platinum-saturation) + var(--platinum-lightness); + + --background: var(--platinum); --foreground: 222.2 84% 4.9%; --card: 0 0% 100%; diff --git a/app/signin/page.tsx b/app/signin/page.tsx index fa6eaa4c..e673d217 100644 --- a/app/signin/page.tsx +++ b/app/signin/page.tsx @@ -1,37 +1,50 @@ import Link from "next/link"; import { signin } from "~/actions/auth"; +import { Button } from "~/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "~/components/ui/card"; +import { Input } from "~/components/ui/input"; +import { Label } from "~/components/ui/label"; export default async function Page() { return ( -
-

Sign in

-
-
- - -
-
-
- - -
-
- -
- -
-

- Do not have an account{" "} - - Sign Up - -

-
+
+ + + Sign in to Studio + + Don't have an account?{" "} + + Sign Up + + + + +
+
+ + +
+
+
+ + +
+
+ +
+
+
); } diff --git a/app/signup/page.tsx b/app/signup/page.tsx index d91cf172..555f0119 100644 --- a/app/signup/page.tsx +++ b/app/signup/page.tsx @@ -1,37 +1,50 @@ import Link from "next/link"; import { signup } from "~/actions/auth"; +import { Button } from "~/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "~/components/ui/card"; +import { Input } from "~/components/ui/input"; +import { Label } from "~/components/ui/label"; export default async function Page() { return ( -
-

Create an account

-
-
- - -
-
-
- - -
-
- -
- -
-

- Already have an account{" "} - - Sign In - -

-
+
+ + + Create an account + + Already have an account?{" "} + + Sign In + + + + +
+
+ + +
+
+
+ + +
+
+ +
+
+
); } diff --git a/components/ui/card.tsx b/components/ui/card.tsx new file mode 100644 index 00000000..901efadf --- /dev/null +++ b/components/ui/card.tsx @@ -0,0 +1,79 @@ +import * as React from "react" + +import { cn } from "~/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/components/ui/input.tsx b/components/ui/input.tsx new file mode 100644 index 00000000..35e8137e --- /dev/null +++ b/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react" + +import { cn } from "~/lib/utils" + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/components/ui/label.tsx b/components/ui/label.tsx new file mode 100644 index 00000000..8f407389 --- /dev/null +++ b/components/ui/label.tsx @@ -0,0 +1,26 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "~/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/package.json b/package.json index 9d2358d1..8e0e29f0 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "@lucia-auth/adapter-drizzle": "^1.0.7", "@node-rs/argon2": "^1.8.3", + "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-slot": "^1.0.2", "@t3-oss/env-nextjs": "^0.10.1", "class-variance-authority": "^0.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4f219a41..349c4896 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@node-rs/argon2': specifier: ^1.8.3 version: 1.8.3 + '@radix-ui/react-label': + specifier: ^2.0.2 + version: 2.0.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': specifier: ^1.0.2 version: 1.0.2(@types/react@18.3.3)(react@18.3.1) @@ -791,6 +794,32 @@ packages: '@types/react': optional: true + '@radix-ui/react-label@2.0.2': + resolution: {integrity: sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@1.0.3': + resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-slot@1.0.2': resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} peerDependencies: @@ -2799,6 +2828,26 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-label@2.0.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.6 + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + + '@radix-ui/react-primitive@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.6 + '@radix-ui/react-slot': 1.0.2(@types/react@18.3.3)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/react-slot@1.0.2(@types/react@18.3.3)(react@18.3.1)': dependencies: '@babel/runtime': 7.24.6 @@ -3351,7 +3400,7 @@ snapshots: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.34.2(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) @@ -3375,7 +3424,7 @@ snapshots: enhanced-resolve: 5.16.1 eslint: 8.57.0 eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-core-module: 2.13.1 @@ -3397,7 +3446,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 From ffafd0968a0b715337fb97ea8f5d4be3a41e8c7f Mon Sep 17 00:00:00 2001 From: Mirfayz Karimoff Date: Wed, 5 Jun 2024 22:15:14 +0500 Subject: [PATCH 04/11] refactor sign in and sign up functionalities to use validate fields and use useFormState --- actions/auth.ts | 227 ++++++++++++++------------ app/signin/_components/SignInForm.tsx | 50 ++++++ app/signin/page.tsx | 24 +-- app/signup/_components/SignUpForm.tsx | 50 ++++++ app/signup/page.tsx | 24 +-- package.json | 5 +- pnpm-lock.yaml | 29 ++++ utils/authSchema.ts | 32 ++++ 8 files changed, 291 insertions(+), 150 deletions(-) create mode 100644 app/signin/_components/SignInForm.tsx create mode 100644 app/signup/_components/SignUpForm.tsx create mode 100644 utils/authSchema.ts diff --git a/actions/auth.ts b/actions/auth.ts index dd2cb8ac..13076bb4 100644 --- a/actions/auth.ts +++ b/actions/auth.ts @@ -1,135 +1,152 @@ "use server"; import { hash, verify } from "@node-rs/argon2"; +import { eq } from "drizzle-orm"; import { generateIdFromEntropySize } from "lucia"; +import { cookies } from "next/headers"; +import { redirect } from "next/navigation"; import { db } from "~/drizzle/db"; import { user } from "~/drizzle/schema"; import { lucia, validateRequest } from "~/utils/auth"; -import { cookies } from "next/headers"; -import { redirect } from "next/navigation"; -import { eq } from "drizzle-orm"; - -export async function signup(formData: FormData) { - const username = formData.get("username"); - // username must be between 4 ~ 31 characters, and only consists of lowercase letters, 0-9, - - if ( - typeof username !== "string" || - username.length < 3 || - username.length > 31 || - !/^[a-z0-9_-]+$/.test(username) - ) { +import { + createUserFormDataSchema, + getUserFormDataSchema, +} from "~/utils/authSchema"; + +export async function signup( + currentState: { + success: boolean; + error: null | string; + }, + formData: FormData +) { + const parsedFormData = createUserFormDataSchema.safeParse(formData); + + if (!parsedFormData.success) { return { - error: "Invalid username", + success: false, + error: parsedFormData.error.message, }; } - const password = formData.get("password"); - - if ( - typeof password !== "string" || - password.length < 6 || - password.length > 255 - ) { + try { + const { username, password } = parsedFormData.data; + + const passwordHash = await hash(password, { + // recommended minimum parameters + memoryCost: 19456, + timeCost: 2, + outputLen: 32, + parallelism: 1, + }); + + const userId = generateIdFromEntropySize(10); + + // check if username is taken + const [existingUser] = await db + .select() + .from(user) + .where(eq(user.username, username)) + .limit(1); + + if (existingUser) throw new Error("Username already taken!"); + + // create user + await db.insert(user).values({ + id: userId, + username, + passwordHash, + }); + + const session = await lucia.createSession(userId, {}); + const sessionCookie = lucia.createSessionCookie(session.id); + cookies().set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes + ); + return { error: null, success: true }; + } catch (error) { return { - error: "Invalid password", + success: false, + error: "Username already taken!", }; } - - const passwordHash = await hash(password, { - // recommended minimum parameters - memoryCost: 19456, - timeCost: 2, - outputLen: 32, - parallelism: 1, - }); - - const userId = generateIdFromEntropySize(10); // 16 characters long - - // TODO: check if username is already used - await db.insert(user).values({ - id: userId, - username, - passwordHash, - }); - - const session = await lucia.createSession(userId, {}); - const sessionCookie = lucia.createSessionCookie(session.id); - cookies().set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes - ); - return redirect("/"); } -export async function signin(formData: FormData) { - const username = formData.get("username"); +export async function signin( + currentState: { + success: boolean; + error: null | string; + }, + formData: FormData +) { + const parsedFormData = getUserFormDataSchema.safeParse(formData); - if ( - typeof username !== "string" || - username.length < 3 || - username.length > 31 || - !/^[a-z0-9_-]+$/.test(username) - ) { + if (!parsedFormData.success) { return { - error: "Invalid username", + success: false, + error: parsedFormData.error.message, }; } - const password = formData.get("password"); - if ( - typeof password !== "string" || - password.length < 6 || - password.length > 255 - ) { - return { - error: "Invalid password", - }; - } + try { + const { username, password } = parsedFormData.data; + + const [existingUser] = await db + .select() + .from(user) + .where(eq(user.username, username)) + .limit(1); + + if (!existingUser) { + // NOTE: + // Returning immediately allows malicious actors to figure out valid usernames from response times, + // allowing them to only focus on guessing passwords in brute-force attacks. + // As a preventive measure, you may want to hash passwords even for invalid usernames. + // However, valid usernames can be already be revealed with the signup page among other methods. + // It will also be much more resource intensive. + // Since protecting against this is non-trivial, + // it is crucial your implementation is protected against brute-force attacks with login throttling etc. + // If usernames are public, you may outright tell the user that the username is invalid. + console.log("invalid username"); + return { + success: false, + error: "Incorrect username or password!", + }; + } + + const validPassword = await verify(existingUser.passwordHash, password, { + memoryCost: 19456, + timeCost: 2, + outputLen: 32, + parallelism: 1, + }); + if (!validPassword) { + return { + success: false, + error: "Incorrect username or password!", + }; + } + + const session = await lucia.createSession(existingUser.id, {}); + const sessionCookie = lucia.createSessionCookie(session.id); + cookies().set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes + ); - const [existingUser] = await db - .select() - .from(user) - .where(eq(user.username, username.toLowerCase())) - .limit(1); - - if (!existingUser) { - // NOTE: - // Returning immediately allows malicious actors to figure out valid usernames from response times, - // allowing them to only focus on guessing passwords in brute-force attacks. - // As a preventive measure, you may want to hash passwords even for invalid usernames. - // However, valid usernames can be already be revealed with the signup page among other methods. - // It will also be much more resource intensive. - // Since protecting against this is non-trivial, - // it is crucial your implementation is protected against brute-force attacks with login throttling etc. - // If usernames are public, you may outright tell the user that the username is invalid. - console.log("invalid username"); return { - error: "Incorrect username or password", + success: true, + error: null, }; - } - - const validPassword = await verify(existingUser.passwordHash, password, { - memoryCost: 19456, - timeCost: 2, - outputLen: 32, - parallelism: 1, - }); - if (!validPassword) { + } catch (error) { return { - error: "Incorrect username or password", + success: false, + error: JSON.stringify(error), }; } - - const session = await lucia.createSession(existingUser.id, {}); - const sessionCookie = lucia.createSessionCookie(session.id); - cookies().set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes - ); - return redirect("/"); } export async function signout() { diff --git a/app/signin/_components/SignInForm.tsx b/app/signin/_components/SignInForm.tsx new file mode 100644 index 00000000..6767fdb0 --- /dev/null +++ b/app/signin/_components/SignInForm.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { signin } from "~/actions/auth"; +import { Button } from "~/components/ui/button"; +import { Input } from "~/components/ui/input"; +import { Label } from "~/components/ui/label"; +import { useFormState } from "react-dom"; +import { useEffect } from "react"; +import { redirect } from "next/navigation"; + +const SignInForm = () => { + const initialState = { error: null, success: false }; + const [formState, formAction] = useFormState(signin, initialState); + + useEffect(() => { + if (formState.success) { + // redirect to home page + redirect("/"); + } + }, [formState]); + + return ( +
+ {formState.error && ( +
+ {formState.error} +
+ )} + +
+ + +
+
+
+ + +
+
+ +
+ ); +}; + +export default SignInForm; diff --git a/app/signin/page.tsx b/app/signin/page.tsx index e673d217..e6562511 100644 --- a/app/signin/page.tsx +++ b/app/signin/page.tsx @@ -1,6 +1,4 @@ import Link from "next/link"; -import { signin } from "~/actions/auth"; -import { Button } from "~/components/ui/button"; import { Card, CardContent, @@ -8,8 +6,7 @@ import { CardHeader, CardTitle, } from "~/components/ui/card"; -import { Input } from "~/components/ui/input"; -import { Label } from "~/components/ui/label"; +import SignInForm from "./_components/SignInForm"; export default async function Page() { return ( @@ -25,24 +22,7 @@ export default async function Page() { -
-
- - -
-
-
- - -
-
- -
+
diff --git a/app/signup/_components/SignUpForm.tsx b/app/signup/_components/SignUpForm.tsx new file mode 100644 index 00000000..7abae5ff --- /dev/null +++ b/app/signup/_components/SignUpForm.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { redirect } from "next/navigation"; +import { useEffect } from "react"; +import { useFormState } from "react-dom"; +import { signup } from "~/actions/auth"; +import { Button } from "~/components/ui/button"; +import { Input } from "~/components/ui/input"; +import { Label } from "~/components/ui/label"; + +const SignUpForm = () => { + const initialState = { error: null, success: false }; + const [formState, formAction] = useFormState(signup, initialState); + + useEffect(() => { + if (formState.success) { + // redirect to sign in page + redirect("/signin"); + } + }, [formState]); + + return ( +
+ {formState.error && ( +
+ {formState.error} +
+ )} + +
+ + +
+
+
+ + +
+
+ +
+ ); +}; + +export default SignUpForm; diff --git a/app/signup/page.tsx b/app/signup/page.tsx index 555f0119..1f1d1f1d 100644 --- a/app/signup/page.tsx +++ b/app/signup/page.tsx @@ -1,6 +1,4 @@ import Link from "next/link"; -import { signup } from "~/actions/auth"; -import { Button } from "~/components/ui/button"; import { Card, CardContent, @@ -8,8 +6,7 @@ import { CardHeader, CardTitle, } from "~/components/ui/card"; -import { Input } from "~/components/ui/input"; -import { Label } from "~/components/ui/label"; +import SignUpForm from "./_components/SignUpForm"; export default async function Page() { return ( @@ -25,24 +22,7 @@ export default async function Page() { -
-
- - -
-
-
- - -
-
- -
+
diff --git a/package.json b/package.json index 8e0e29f0..578d2245 100644 --- a/package.json +++ b/package.json @@ -27,12 +27,15 @@ "react-dom": "^18", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", - "zod": "^3.23.8" + "validator": "^13.12.0", + "zod": "^3.23.8", + "zod-form-data": "^2.0.2" }, "devDependencies": { "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@types/validator": "^13.11.10", "drizzle-kit": "^0.22.2", "eslint": "^8", "eslint-config-next": "14.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 349c4896..711fc41c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,9 +56,15 @@ importers: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.3) + validator: + specifier: ^13.12.0 + version: 13.12.0 zod: specifier: ^3.23.8 version: 3.23.8 + zod-form-data: + specifier: ^2.0.2 + version: 2.0.2(zod@3.23.8) devDependencies: '@types/node': specifier: ^20 @@ -69,6 +75,9 @@ importers: '@types/react-dom': specifier: ^18 version: 18.3.0 + '@types/validator': + specifier: ^13.11.10 + version: 13.11.10 drizzle-kit: specifier: ^0.22.2 version: 0.22.2 @@ -877,6 +886,9 @@ packages: '@types/react@18.3.3': resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + '@types/validator@13.11.10': + resolution: {integrity: sha512-e2PNXoXLr6Z+dbfx5zSh9TRlXJrELycxiaXznp4S5+D2M3b9bqJEitNHA5923jhnB2zzFiZHa2f0SI1HoIahpg==} + '@typescript-eslint/parser@7.2.0': resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==} engines: {node: ^16.0.0 || >=18.0.0} @@ -2286,6 +2298,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + validator@13.12.0: + resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} + engines: {node: '>= 0.10'} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -2330,6 +2346,11 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod-form-data@2.0.2: + resolution: {integrity: sha512-sKTi+k0fvkxdakD0V5rq+9WVJA3cuTQUfEmNqvHrTzPLvjfLmkkBLfR0ed3qOi9MScJXTHIDH/jUNnEJ3CBX4g==} + peerDependencies: + zod: '>= 3.11.0' + zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} @@ -2905,6 +2926,8 @@ snapshots: '@types/prop-types': 15.7.12 csstype: 3.1.3 + '@types/validator@13.11.10': {} + '@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@typescript-eslint/scope-manager': 7.2.0 @@ -4523,6 +4546,8 @@ snapshots: util-deprecate@1.0.2: {} + validator@13.12.0: {} + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 @@ -4585,4 +4610,8 @@ snapshots: yocto-queue@0.1.0: {} + zod-form-data@2.0.2(zod@3.23.8): + dependencies: + zod: 3.23.8 + zod@3.23.8: {} diff --git a/utils/authSchema.ts b/utils/authSchema.ts new file mode 100644 index 00000000..649d0add --- /dev/null +++ b/utils/authSchema.ts @@ -0,0 +1,32 @@ +import { isStrongPassword } from "validator"; +import { z } from "zod"; +import { zfd } from "zod-form-data"; + +export const createUserSchema = z.object({ + username: z + .string() + .min(4, { message: "Username must be at least 4 characters" }) + .refine((s) => !s.includes(" "), "Username cannot contain spaces"), + password: z.string().refine( + (password) => + isStrongPassword(password, { + minLowercase: 1, + minUppercase: 1, + minNumbers: 1, + minSymbols: 1, + }), + { + message: + "Password must contain at least 1 lowercase, 1 uppercase, 1 number, and 1 symbol", + } + ), +}); + +export const loginSchema = z.object({ + username: z.string().min(1, { message: "Username cannot be empty" }), + password: z.string().min(1, { message: "Password cannot be empty" }), +}); + +export const createUserFormDataSchema = zfd.formData(createUserSchema); + +export const getUserFormDataSchema = zfd.formData(loginSchema); From 8753cf4264449a08456ef6a155b105812b6359b7 Mon Sep 17 00:00:00 2001 From: Mirfayz Karimoff Date: Wed, 5 Jun 2024 22:24:54 +0500 Subject: [PATCH 05/11] implement requirePageAuth with validateRequest --- app/page.tsx | 10 ++-------- utils/auth.ts | 25 +++++++++++++++++++------ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 1b452291..8936f946 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,16 +1,10 @@ -import { redirect } from "next/navigation"; -import { signout } from "~/actions/auth"; import { getOrganizations } from "~/actions/organizations"; import CreateOrgForm from "~/components/CreateOrgForm"; -import { validateRequest } from "~/utils/auth"; +import { requirePageAuth } from "~/utils/auth"; import SignOutBtn from "./_components/SignOutBtn"; export default async function Home() { - const { user } = await validateRequest(); - - if (!user) { - return redirect("/signin"); - } + await requirePageAuth(); const allOrgs = await getOrganizations(); return ( diff --git a/utils/auth.ts b/utils/auth.ts index a5b6b799..be1fa88d 100644 --- a/utils/auth.ts +++ b/utils/auth.ts @@ -1,14 +1,15 @@ -import { Lucia } from "lucia"; import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle"; +import type { Session, User } from "lucia"; +import { Lucia } from "lucia"; +import { cookies } from "next/headers"; +import { redirect, RedirectType } from "next/navigation"; +import { cache } from "react"; +import { db } from "~/drizzle/db"; import { session as sessionTable, user as userTable, UserType, } from "~/drizzle/schema"; -import { db } from "~/drizzle/db"; -import type { Session, User } from "lucia"; -import { cache } from "react"; -import { cookies } from "next/headers"; const adapter = new DrizzlePostgreSQLAdapter(db, sessionTable, userTable); @@ -69,7 +70,19 @@ export const validateRequest = cache( sessionCookie.attributes ); } - } catch {} + } catch (error) { + console.error(error); + } return result; } ); + +export async function requirePageAuth() { + const { session, user } = await validateRequest(); + + if (!session || !user) { + redirect("/signin", RedirectType.replace); + } + + return { session, user }; +} From b3093549a6c4e812085a3b438f9462ab4397d5cc Mon Sep 17 00:00:00 2001 From: Mirfayz Karimoff Date: Fri, 7 Jun 2024 15:11:28 +0500 Subject: [PATCH 06/11] handle redirection after sign in and sign up via validateRequest --- app/signin/_components/SignInForm.tsx | 7 ------- app/signin/page.tsx | 9 +++++++++ app/signup/_components/SignUpForm.tsx | 9 --------- app/signup/page.tsx | 9 +++++++++ 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/app/signin/_components/SignInForm.tsx b/app/signin/_components/SignInForm.tsx index 6767fdb0..092b228c 100644 --- a/app/signin/_components/SignInForm.tsx +++ b/app/signin/_components/SignInForm.tsx @@ -12,13 +12,6 @@ const SignInForm = () => { const initialState = { error: null, success: false }; const [formState, formAction] = useFormState(signin, initialState); - useEffect(() => { - if (formState.success) { - // redirect to home page - redirect("/"); - } - }, [formState]); - return (
{formState.error && ( diff --git a/app/signin/page.tsx b/app/signin/page.tsx index e6562511..3343ce13 100644 --- a/app/signin/page.tsx +++ b/app/signin/page.tsx @@ -1,4 +1,5 @@ import Link from "next/link"; +import { redirect } from "next/navigation"; import { Card, CardContent, @@ -6,9 +7,17 @@ import { CardHeader, CardTitle, } from "~/components/ui/card"; +import { validateRequest } from "~/utils/auth"; import SignInForm from "./_components/SignInForm"; export default async function Page() { + const { session, user } = await validateRequest(); + + if (session && user) { + // If the user is already signed in, redirect to the home page + redirect("/"); + } + return (
diff --git a/app/signup/_components/SignUpForm.tsx b/app/signup/_components/SignUpForm.tsx index 7abae5ff..3e9b5a13 100644 --- a/app/signup/_components/SignUpForm.tsx +++ b/app/signup/_components/SignUpForm.tsx @@ -1,7 +1,5 @@ "use client"; -import { redirect } from "next/navigation"; -import { useEffect } from "react"; import { useFormState } from "react-dom"; import { signup } from "~/actions/auth"; import { Button } from "~/components/ui/button"; @@ -12,13 +10,6 @@ const SignUpForm = () => { const initialState = { error: null, success: false }; const [formState, formAction] = useFormState(signup, initialState); - useEffect(() => { - if (formState.success) { - // redirect to sign in page - redirect("/signin"); - } - }, [formState]); - return ( {formState.error && ( diff --git a/app/signup/page.tsx b/app/signup/page.tsx index 1f1d1f1d..cc9e5c40 100644 --- a/app/signup/page.tsx +++ b/app/signup/page.tsx @@ -7,8 +7,17 @@ import { CardTitle, } from "~/components/ui/card"; import SignUpForm from "./_components/SignUpForm"; +import { validateRequest } from "~/utils/auth"; +import { redirect } from "next/navigation"; export default async function Page() { + const { session, user } = await validateRequest(); + + if (session && user) { + // If the user is already signed in, redirect to the home page + redirect("/"); + } + return (
From 9f7eb59bc0bc40ee93900ba04b78cc7f35906b22 Mon Sep 17 00:00:00 2001 From: Mirfayz Karimoff Date: Fri, 7 Jun 2024 15:29:31 +0500 Subject: [PATCH 07/11] refactor sign out functionality --- actions/auth.ts | 13 ++++++++++--- app/_components/SignOutBtn.tsx | 11 +---------- app/signin/_components/SignInForm.tsx | 4 +--- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/actions/auth.ts b/actions/auth.ts index 13076bb4..c197be8a 100644 --- a/actions/auth.ts +++ b/actions/auth.ts @@ -3,8 +3,8 @@ import { hash, verify } from "@node-rs/argon2"; import { eq } from "drizzle-orm"; import { generateIdFromEntropySize } from "lucia"; +import { revalidatePath } from "next/cache"; import { cookies } from "next/headers"; -import { redirect } from "next/navigation"; import { db } from "~/drizzle/db"; import { user } from "~/drizzle/schema"; import { lucia, validateRequest } from "~/utils/auth"; @@ -142,9 +142,10 @@ export async function signin( error: null, }; } catch (error) { + console.error("Error while signing in", error); return { success: false, - error: JSON.stringify(error), + error: "Something went wrong! Please try again.", }; } } @@ -153,6 +154,7 @@ export async function signout() { const { session } = await validateRequest(); if (!session) { return { + success: false, error: "Unauthorized", }; } @@ -165,5 +167,10 @@ export async function signout() { sessionCookie.value, sessionCookie.attributes ); - return redirect("/signin"); + + revalidatePath("/"); + return { + success: true, + error: null, + }; } diff --git a/app/_components/SignOutBtn.tsx b/app/_components/SignOutBtn.tsx index d61a2f81..aa9a9cae 100644 --- a/app/_components/SignOutBtn.tsx +++ b/app/_components/SignOutBtn.tsx @@ -5,16 +5,7 @@ import { signout } from "~/actions/auth"; import { Button } from "~/components/ui/button"; const SignOutBtn = () => { - return ( - - ); + return ; }; export default SignOutBtn; diff --git a/app/signin/_components/SignInForm.tsx b/app/signin/_components/SignInForm.tsx index 092b228c..34a50cf0 100644 --- a/app/signin/_components/SignInForm.tsx +++ b/app/signin/_components/SignInForm.tsx @@ -1,12 +1,10 @@ "use client"; +import { useFormState } from "react-dom"; import { signin } from "~/actions/auth"; import { Button } from "~/components/ui/button"; import { Input } from "~/components/ui/input"; import { Label } from "~/components/ui/label"; -import { useFormState } from "react-dom"; -import { useEffect } from "react"; -import { redirect } from "next/navigation"; const SignInForm = () => { const initialState = { error: null, success: false }; From 70c82d366a7eee763321a4fdd145dbb092dac0dd Mon Sep 17 00:00:00 2001 From: Mirfayz Karimoff Date: Fri, 7 Jun 2024 15:48:55 +0500 Subject: [PATCH 08/11] refactor next.config.mjs to remove experimental serverComponentsExternalPackages --- next.config.mjs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/next.config.mjs b/next.config.mjs index f6f4e20e..4678774e 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,8 +1,4 @@ /** @type {import('next').NextConfig} */ -const nextConfig = { - experimental: { - serverComponentsExternalPackages: ["@node-rs/argon2"], - }, -}; +const nextConfig = {}; export default nextConfig; From 48c0c51ecd2a6b1d5e67e2a8b3ee367ae763b598 Mon Sep 17 00:00:00 2001 From: Mirfayz Karimoff Date: Fri, 7 Jun 2024 16:03:39 +0500 Subject: [PATCH 09/11] update pnpm-lock.yaml file to install packages with pnpm 9.1.1 to resolve conflict with main --- pnpm-lock.yaml | 120 ++++++++++++++++++++++++------------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 711fc41c..c409898b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,7 +55,7 @@ importers: version: 2.3.0 tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.3) + version: 1.0.7(tailwindcss@3.4.4) validator: specifier: ^13.12.0 version: 13.12.0 @@ -68,7 +68,7 @@ importers: devDependencies: '@types/node': specifier: ^20 - version: 20.14.1 + version: 20.14.2 '@types/react': specifier: ^18 version: 18.3.3 @@ -80,7 +80,7 @@ importers: version: 13.11.10 drizzle-kit: specifier: ^0.22.2 - version: 0.22.2 + version: 0.22.4 eslint: specifier: ^8 version: 8.57.0 @@ -92,7 +92,7 @@ importers: version: 8.4.38 tailwindcss: specifier: ^3.4.1 - version: 3.4.3 + version: 3.4.4 typescript: specifier: ^5 version: 5.4.5 @@ -103,8 +103,8 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@babel/runtime@7.24.6': - resolution: {integrity: sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==} + '@babel/runtime@7.24.7': + resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} engines: {node: '>=6.9.0'} '@emnapi/core@0.45.0': @@ -874,8 +874,8 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/node@20.14.1': - resolution: {integrity: sha512-T2MzSGEu+ysB/FkWfqmhV3PLyQlowdptmmgD20C6QxsS8Fmv5SjpZ1ayXaEC0S21/h5UJ9iA6W/5vSNU5l00OA==} + '@types/node@20.14.2': + resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==} '@types/prop-types@15.7.12': resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} @@ -1057,8 +1057,8 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001627: - resolution: {integrity: sha512-4zgNiB8nTyV/tHhwZrFs88ryjls/lHiqFhrxCW4qSTeuRByBVnPYpDInchOIySWknznucaf31Z4KYqjfbrecVw==} + caniuse-lite@1.0.30001629: + resolution: {integrity: sha512-c3dl911slnQhmxUIT4HhYzT7wnBK/XYpGnYLOj4nJBaRiw52Ibe7YxlDaAeRECvA786zCuExhxIUJ2K7nHMrBw==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -1173,8 +1173,8 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} - drizzle-kit@0.22.2: - resolution: {integrity: sha512-dxluXlhT1N9bpj2eZR3N/z3u4H0PbbrBYgyouIobFF25tOt2Buy1abx26Jii96qcYV0JgxjhnuV+FBcgR0Xa0w==} + drizzle-kit@0.22.4: + resolution: {integrity: sha512-jsiYGqHsbsP/GtM26y/bGK7je1ja+1H/RniCt1ovg2E7tMNraw6XdqKcjxHhb8FonCfDDjvwFgouRsZS46vrmA==} hasBin: true drizzle-orm@0.31.1: @@ -1266,8 +1266,8 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - enhanced-resolve@5.16.1: - resolution: {integrity: sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==} + enhanced-resolve@5.17.0: + resolution: {integrity: sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==} engines: {node: '>=10.13.0'} es-abstract@1.23.3: @@ -1708,12 +1708,12 @@ packages: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} engines: {node: '>=14'} - jackspeak@3.2.3: - resolution: {integrity: sha512-htOzIMPbpLid/Gq9/zaz9SfExABxqRe1sSCdxntlO/aMD6u0issZQiY25n2GKQUtJ02j7z5sfptlAOMpWWOmvw==} + jackspeak@3.4.0: + resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==} engines: {node: '>=14'} - jiti@1.21.0: - resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} + jiti@1.21.3: + resolution: {integrity: sha512-uy2bNX5zQ+tESe+TiC7ilGRz8AtRGmnJH55NC5S0nSUjvvvM2hJHmefHErugGXN4pNv4Qx7vLsnNw9qJ9mtIsw==} hasBin: true js-tokens@4.0.0: @@ -2219,8 +2219,8 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders' - tailwindcss@3.4.3: - resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==} + tailwindcss@3.4.4: + resolution: {integrity: sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==} engines: {node: '>=14.0.0'} hasBin: true @@ -2254,8 +2254,8 @@ packages: tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} @@ -2358,34 +2358,34 @@ snapshots: '@alloc/quick-lru@5.2.0': {} - '@babel/runtime@7.24.6': + '@babel/runtime@7.24.7': dependencies: regenerator-runtime: 0.14.1 '@emnapi/core@0.45.0': dependencies: - tslib: 2.6.2 + tslib: 2.6.3 optional: true '@emnapi/core@1.2.0': dependencies: '@emnapi/wasi-threads': 1.0.1 - tslib: 2.6.2 + tslib: 2.6.3 optional: true '@emnapi/runtime@0.45.0': dependencies: - tslib: 2.6.2 + tslib: 2.6.3 optional: true '@emnapi/runtime@1.2.0': dependencies: - tslib: 2.6.2 + tslib: 2.6.3 optional: true '@emnapi/wasi-threads@1.0.1': dependencies: - tslib: 2.6.2 + tslib: 2.6.3 optional: true '@esbuild-kit/core-utils@3.3.2': @@ -2844,14 +2844,14 @@ snapshots: '@radix-ui/react-compose-refs@1.0.1(@types/react@18.3.3)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.6 + '@babel/runtime': 7.24.7 react: 18.3.1 optionalDependencies: '@types/react': 18.3.3 '@radix-ui/react-label@2.0.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.6 + '@babel/runtime': 7.24.7 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -2861,7 +2861,7 @@ snapshots: '@radix-ui/react-primitive@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.6 + '@babel/runtime': 7.24.7 '@radix-ui/react-slot': 1.0.2(@types/react@18.3.3)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -2871,7 +2871,7 @@ snapshots: '@radix-ui/react-slot@1.0.2(@types/react@18.3.3)(react@18.3.1)': dependencies: - '@babel/runtime': 7.24.6 + '@babel/runtime': 7.24.7 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1) react: 18.3.1 optionalDependencies: @@ -2884,7 +2884,7 @@ snapshots: '@swc/helpers@0.5.5': dependencies: '@swc/counter': 0.1.3 - tslib: 2.6.2 + tslib: 2.6.3 '@t3-oss/env-core@0.10.1(typescript@5.4.5)(zod@3.23.8)': dependencies: @@ -2901,17 +2901,17 @@ snapshots: '@tybys/wasm-util@0.8.3': dependencies: - tslib: 2.6.2 + tslib: 2.6.3 optional: true '@tybys/wasm-util@0.9.0': dependencies: - tslib: 2.6.2 + tslib: 2.6.3 optional: true '@types/json5@0.0.29': {} - '@types/node@20.14.1': + '@types/node@20.14.2': dependencies: undici-types: 5.26.5 @@ -3129,7 +3129,7 @@ snapshots: camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001627: {} + caniuse-lite@1.0.30001629: {} chalk@4.1.2: dependencies: @@ -3238,7 +3238,7 @@ snapshots: dependencies: esutils: 2.0.3 - drizzle-kit@0.22.2: + drizzle-kit@0.22.4: dependencies: '@esbuild-kit/esm-loader': 2.6.5 esbuild: 0.19.12 @@ -3258,7 +3258,7 @@ snapshots: emoji-regex@9.2.2: {} - enhanced-resolve@5.16.1: + enhanced-resolve@5.17.0: dependencies: graceful-fs: 4.2.11 tapable: 2.2.1 @@ -3422,8 +3422,8 @@ snapshots: '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.34.2(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) @@ -3441,13 +3441,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0): dependencies: debug: 4.3.5 - enhanced-resolve: 5.16.1 + enhanced-resolve: 5.17.0 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-core-module: 2.13.1 @@ -3458,18 +3458,18 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -3479,7 +3479,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -3498,7 +3498,7 @@ snapshots: eslint-plugin-jsx-a11y@6.8.0(eslint@8.57.0): dependencies: - '@babel/runtime': 7.24.6 + '@babel/runtime': 7.24.7 aria-query: 5.3.0 array-includes: 3.1.8 array.prototype.flatmap: 1.3.2 @@ -3714,7 +3714,7 @@ snapshots: glob@10.4.1: dependencies: foreground-child: 3.1.1 - jackspeak: 3.2.3 + jackspeak: 3.4.0 minimatch: 9.0.4 minipass: 7.1.2 path-scurry: 1.11.1 @@ -3912,13 +3912,13 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jackspeak@3.2.3: + jackspeak@3.4.0: dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jiti@1.21.0: {} + jiti@1.21.3: {} js-tokens@4.0.0: {} @@ -4036,7 +4036,7 @@ snapshots: '@next/env': 14.2.3 '@swc/helpers': 0.5.5 busboy: 1.6.0 - caniuse-lite: 1.0.30001627 + caniuse-lite: 1.0.30001629 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.3.1 @@ -4427,13 +4427,13 @@ snapshots: tailwind-merge@2.3.0: dependencies: - '@babel/runtime': 7.24.6 + '@babel/runtime': 7.24.7 - tailwindcss-animate@1.0.7(tailwindcss@3.4.3): + tailwindcss-animate@1.0.7(tailwindcss@3.4.4): dependencies: - tailwindcss: 3.4.3 + tailwindcss: 3.4.4 - tailwindcss@3.4.3: + tailwindcss@3.4.4: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -4443,7 +4443,7 @@ snapshots: fast-glob: 3.3.2 glob-parent: 6.0.2 is-glob: 4.0.3 - jiti: 1.21.0 + jiti: 1.21.3 lilconfig: 2.1.0 micromatch: 4.0.7 normalize-path: 3.0.0 @@ -4489,7 +4489,7 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 - tslib@2.6.2: {} + tslib@2.6.3: {} type-check@0.4.0: dependencies: From b033169a0b4e10ccc287025ca426ade8eb675edf Mon Sep 17 00:00:00 2001 From: Mirfayz Karimoff Date: Fri, 7 Jun 2024 18:15:25 +0500 Subject: [PATCH 10/11] fix linting errors (some are left for later to figure out) --- actions/auth.ts | 52 ++++++------ components/ui/button.tsx | 59 +++++++------ components/ui/input.tsx | 21 +++-- drizzle/db.ts | 12 +-- env.ts | 9 +- next.config.js | 7 +- pnpm-lock.yaml | 178 +++++++++++++++------------------------ tsconfig.json | 8 +- utils/auth.ts | 36 ++++---- 9 files changed, 179 insertions(+), 203 deletions(-) diff --git a/actions/auth.ts b/actions/auth.ts index c197be8a..9a1aaf27 100644 --- a/actions/auth.ts +++ b/actions/auth.ts @@ -1,24 +1,24 @@ -"use server"; - -import { hash, verify } from "@node-rs/argon2"; -import { eq } from "drizzle-orm"; -import { generateIdFromEntropySize } from "lucia"; -import { revalidatePath } from "next/cache"; -import { cookies } from "next/headers"; -import { db } from "~/drizzle/db"; -import { user } from "~/drizzle/schema"; -import { lucia, validateRequest } from "~/utils/auth"; +'use server'; + +import { hash, verify } from '@node-rs/argon2'; +import { eq } from 'drizzle-orm'; +import { generateIdFromEntropySize } from 'lucia'; +import { revalidatePath } from 'next/cache'; +import { cookies } from 'next/headers'; +import { db } from '~/drizzle/db'; +import { user } from '~/drizzle/schema'; +import { lucia, validateRequest } from '~/utils/auth'; import { createUserFormDataSchema, getUserFormDataSchema, -} from "~/utils/authSchema"; +} from '~/utils/authSchema'; export async function signup( currentState: { success: boolean; error: null | string; }, - formData: FormData + formData: FormData, ) { const parsedFormData = createUserFormDataSchema.safeParse(formData); @@ -49,7 +49,7 @@ export async function signup( .where(eq(user.username, username)) .limit(1); - if (existingUser) throw new Error("Username already taken!"); + if (existingUser) throw new Error('Username already taken!'); // create user await db.insert(user).values({ @@ -63,13 +63,13 @@ export async function signup( cookies().set( sessionCookie.name, sessionCookie.value, - sessionCookie.attributes + sessionCookie.attributes, ); return { error: null, success: true }; } catch (error) { return { success: false, - error: "Username already taken!", + error: 'Username already taken!', }; } } @@ -79,7 +79,7 @@ export async function signin( success: boolean; error: null | string; }, - formData: FormData + formData: FormData, ) { const parsedFormData = getUserFormDataSchema.safeParse(formData); @@ -109,10 +109,11 @@ export async function signin( // Since protecting against this is non-trivial, // it is crucial your implementation is protected against brute-force attacks with login throttling etc. // If usernames are public, you may outright tell the user that the username is invalid. - console.log("invalid username"); + // eslint-disable-next-line no-console + console.log('invalid username'); return { success: false, - error: "Incorrect username or password!", + error: 'Incorrect username or password!', }; } @@ -125,7 +126,7 @@ export async function signin( if (!validPassword) { return { success: false, - error: "Incorrect username or password!", + error: 'Incorrect username or password!', }; } @@ -134,7 +135,7 @@ export async function signin( cookies().set( sessionCookie.name, sessionCookie.value, - sessionCookie.attributes + sessionCookie.attributes, ); return { @@ -142,10 +143,11 @@ export async function signin( error: null, }; } catch (error) { - console.error("Error while signing in", error); + // eslint-disable-next-line no-console + console.error('Error while signing in', error); return { success: false, - error: "Something went wrong! Please try again.", + error: 'Something went wrong! Please try again.', }; } } @@ -155,7 +157,7 @@ export async function signout() { if (!session) { return { success: false, - error: "Unauthorized", + error: 'Unauthorized', }; } @@ -165,10 +167,10 @@ export async function signout() { cookies().set( sessionCookie.name, sessionCookie.value, - sessionCookie.attributes + sessionCookie.attributes, ); - revalidatePath("/"); + revalidatePath('/'); return { success: true, error: null, diff --git a/components/ui/button.tsx b/components/ui/button.tsx index d754ca03..552d54f1 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -1,56 +1,55 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" +import * as React from 'react'; +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; -import { cn } from "~/lib/utils" +import { cn } from '~/lib/utils'; const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', { variants: { variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", + default: 'bg-primary text-primary-foreground hover:bg-primary/90', destructive: - "bg-destructive text-destructive-foreground hover:bg-destructive/90", + 'bg-destructive text-destructive-foreground hover:bg-destructive/90', outline: - "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline", + 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', }, size: { - default: "h-10 px-4 py-2", - sm: "h-9 rounded-md px-3", - lg: "h-11 rounded-md px-8", - icon: "h-10 w-10", + default: 'h-10 px-4 py-2', + sm: 'h-9 rounded-md px-3', + lg: 'h-11 rounded-md px-8', + icon: 'h-10 w-10', }, }, defaultVariants: { - variant: "default", - size: "default", + variant: 'default', + size: 'default', }, - } -) + }, +); -export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean -} +export type ButtonProps = { + asChild?: boolean; +} & React.ButtonHTMLAttributes & + VariantProps; const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" + const Comp = asChild ? Slot : 'button'; return ( - ) - } -) -Button.displayName = "Button" + ); + }, +); +Button.displayName = 'Button'; -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/components/ui/input.tsx b/components/ui/input.tsx index 35e8137e..fdd996c9 100644 --- a/components/ui/input.tsx +++ b/components/ui/input.tsx @@ -1,9 +1,8 @@ -import * as React from "react" +import * as React from 'react'; -import { cn } from "~/lib/utils" +import { cn } from '~/lib/utils'; -export interface InputProps - extends React.InputHTMLAttributes {} +export type InputProps = object & React.InputHTMLAttributes; const Input = React.forwardRef( ({ className, type, ...props }, ref) => { @@ -11,15 +10,15 @@ const Input = React.forwardRef( - ) - } -) -Input.displayName = "Input" + ); + }, +); +Input.displayName = 'Input'; -export { Input } +export { Input }; diff --git a/drizzle/db.ts b/drizzle/db.ts index 880be7e8..7e6a26e6 100644 --- a/drizzle/db.ts +++ b/drizzle/db.ts @@ -1,16 +1,18 @@ -import { env } from "~/env"; -import * as schema from "./schema"; -import { PostgresJsDatabase, drizzle } from "drizzle-orm/postgres-js"; -import postgres from "postgres"; +import { env } from '~/env'; +import * as schema from './schema'; +import { type PostgresJsDatabase, drizzle } from 'drizzle-orm/postgres-js'; +import postgres from 'postgres'; declare global { + // Todo: figure out how to fix linting here + // eslint-disable-next-line no-var var db: PostgresJsDatabase | undefined; } let db: PostgresJsDatabase; let pg: ReturnType; -if (env.NODE_ENV === "production") { +if (env.NODE_ENV === 'production') { pg = postgres(env.DATABASE_URL); db = drizzle(pg, { schema }); } else { diff --git a/env.ts b/env.ts index a01e7d3a..ff9858fb 100644 --- a/env.ts +++ b/env.ts @@ -1,5 +1,6 @@ -import { createEnv } from "@t3-oss/env-nextjs"; -import { z } from "zod"; +/* eslint-disable no-process-env */ +import { createEnv } from '@t3-oss/env-nextjs'; +import { z } from 'zod'; export const env = createEnv({ server: { @@ -8,8 +9,8 @@ export const env = createEnv({ client: {}, shared: { NODE_ENV: z - .enum(["development", "test", "production"]) - .default("development"), + .enum(['development', 'test', 'production']) + .default('development'), }, runtimeEnv: { DATABASE_URL: process.env.DATABASE_URL, diff --git a/next.config.js b/next.config.js index 4678774e..d37307a9 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,9 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + // Todo: remove this config once we upgrade to Next 15 + experimental: { + serverComponentsExternalPackages: ['@node-rs/argon2'], + }, +}; export default nextConfig; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a9fe397..c4f83c2f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,7 +23,12 @@ importers: '@t3-oss/env-nextjs': specifier: ^0.10.1 version: 0.10.1(typescript@5.4.5)(zod@3.23.8) -<<<<<<< HEAD + '@typescript-eslint/eslint-plugin': + specifier: ^7.12.0 + version: 7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': + specifier: ^7.12.0 + version: 7.12.0(eslint@8.57.0)(typescript@5.4.5) class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -33,29 +38,18 @@ importers: drizzle-orm: specifier: ^0.31.1 version: 0.31.1(@types/react@18.3.3)(postgres@3.4.4)(react@18.3.1) + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@8.57.0) + knip: + specifier: ^5.17.4 + version: 5.17.4(@types/node@20.14.2)(typescript@5.4.5) lucia: specifier: ^3.2.0 version: 3.2.0 lucide-react: specifier: ^0.383.0 version: 0.383.0(react@18.3.1) -======= - '@typescript-eslint/eslint-plugin': - specifier: ^7.12.0 - version: 7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/parser': - specifier: ^7.12.0 - version: 7.12.0(eslint@8.57.0)(typescript@5.4.5) - drizzle-orm: - specifier: ^0.31.1 - version: 0.31.1(@types/react@18.3.3)(postgres@3.4.4)(react@18.3.1) - knip: - specifier: ^5.17.4 - version: 5.17.4(@types/node@20.14.1)(typescript@5.4.5) - eslint-config-prettier: - specifier: ^9.1.0 - version: 9.1.0(eslint@8.57.0) ->>>>>>> main next: specifier: 14.2.3 version: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -74,7 +68,9 @@ importers: react-dom: specifier: ^18 version: 18.3.1(react@18.3.1) -<<<<<<< HEAD + sharp: + specifier: ^0.33.4 + version: 0.33.4 tailwind-merge: specifier: ^2.3.0 version: 2.3.0 @@ -84,11 +80,6 @@ importers: validator: specifier: ^13.12.0 version: 13.12.0 -======= - sharp: - specifier: ^0.33.4 - version: 0.33.4 ->>>>>>> main zod: specifier: ^3.23.8 version: 3.23.8 @@ -105,15 +96,12 @@ importers: '@types/react-dom': specifier: ^18 version: 18.3.0 -<<<<<<< HEAD '@types/validator': specifier: ^13.11.10 version: 13.11.10 -======= autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.38) ->>>>>>> main drizzle-kit: specifier: ^0.22.2 version: 0.22.4 @@ -143,7 +131,6 @@ packages: resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} engines: {node: '>=6.9.0'} -<<<<<<< HEAD '@emnapi/core@0.45.0': resolution: {integrity: sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==} @@ -158,14 +145,10 @@ packages: '@emnapi/wasi-threads@1.0.1': resolution: {integrity: sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==} -======= - '@emnapi/runtime@1.2.0': - resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==} '@ericcornelissen/bash-parser@0.5.2': resolution: {integrity: sha512-4pIMTa1nEFfMXitv7oaNEWOdM+zpOZavesa5GaiWTgda6Zk32CFGxjUp/iIaN0PwgUW1yTq/fztSjbpE8SLGZQ==} engines: {node: '>=4'} ->>>>>>> main '@esbuild-kit/core-utils@3.3.2': resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} @@ -1061,10 +1044,9 @@ packages: '@types/react@18.3.3': resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} -<<<<<<< HEAD '@types/validator@13.11.10': resolution: {integrity: sha512-e2PNXoXLr6Z+dbfx5zSh9TRlXJrELycxiaXznp4S5+D2M3b9bqJEitNHA5923jhnB2zzFiZHa2f0SI1HoIahpg==} -======= + '@typescript-eslint/eslint-plugin@7.12.0': resolution: {integrity: sha512-7F91fcbuDf/d3S8o21+r3ZncGIke/+eWk0EpO21LXhDfLahriZF9CGj4fbAetEjlaBdjdSm9a6VeXbpbT6Z40Q==} engines: {node: ^18.18.0 || >=20.0.0} @@ -1085,7 +1067,6 @@ packages: peerDependenciesMeta: typescript: optional: true ->>>>>>> main '@typescript-eslint/parser@7.2.0': resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==} @@ -1330,19 +1311,20 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} -<<<<<<< HEAD class-variance-authority@0.7.0: resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} -======= + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} ->>>>>>> main client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} -<<<<<<< HEAD + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + clsx@2.0.0: resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} engines: {node: '>=6'} @@ -1350,11 +1332,6 @@ packages: clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} -======= - clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} ->>>>>>> main color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -1788,13 +1765,11 @@ packages: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} -<<<<<<< HEAD - fs-monkey@1.0.6: - resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} -======= fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} ->>>>>>> main + + fs-monkey@1.0.6: + resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -2067,6 +2042,10 @@ packages: resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==} engines: {node: '>=14'} + jiti@1.21.0: + resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} + hasBin: true + jiti@1.21.3: resolution: {integrity: sha512-uy2bNX5zQ+tESe+TiC7ilGRz8AtRGmnJH55NC5S0nSUjvvvM2hJHmefHErugGXN4pNv4Qx7vLsnNw9qJ9mtIsw==} hasBin: true @@ -2146,7 +2125,6 @@ packages: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} -<<<<<<< HEAD lucia@3.2.0: resolution: {integrity: sha512-eXMxXwk6hqtjRTj4W/x3EnTUtAztLPm0p2N2TEBMDEbakDLXiYnDQ9z/qahjPdPdhPguQc+vwO0/88zIWxlpuw==} @@ -2155,20 +2133,19 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 - memfs-browser@3.5.10302: - resolution: {integrity: sha512-JJTc/nh3ig05O0gBBGZjTCPOyydaTxNF0uHYBrcc1gHNnO+KIHIvo0Y1FKCJsaei6FCl8C6xfQomXqu+cuzkIw==} - - memfs@3.5.3: - resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} - engines: {node: '>= 4.0.0'} -======= magic-string@0.16.0: resolution: {integrity: sha512-c4BEos3y6G2qO0B9X7K0FVLOPT9uGrjYwYRLFmDqyl5YMboUviyecnXWp94fJTSMwPw2/sf+CEYt5AGpmklkkQ==} map-obj@2.0.0: resolution: {integrity: sha512-TzQSV2DiMYgoF5RycneKVUzIa9bQsj/B3tTgsE3dOGqlzHnGIDaC7XBE7grnA+8kZPnfqSGFe95VHc2oc0VFUQ==} engines: {node: '>=4'} ->>>>>>> main + + memfs-browser@3.5.10302: + resolution: {integrity: sha512-JJTc/nh3ig05O0gBBGZjTCPOyydaTxNF0uHYBrcc1gHNnO+KIHIvo0Y1FKCJsaei6FCl8C6xfQomXqu+cuzkIw==} + + memfs@3.5.3: + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} + engines: {node: '>= 4.0.0'} merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} @@ -2420,9 +2397,6 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - pretty-ms@9.0.0: - resolution: {integrity: sha512-E9e9HJ9R9NasGOgPaPE8VMeiPKAyWR5jcFpNnwIejslIhWqdqOrb2wShBsncMPUb+BcCd2OPYfh7p2W6oemTng==} - engines: {node: '>=18'} prettier-plugin-tailwindcss@0.6.1: resolution: {integrity: sha512-AnbeYZu0WGj+QgKciUgdMnRxrqcxltleZPgdwfA5104BHM3siBLONN/HLW1yS2HvzSNkzpQ/JAj+LN0jcJO+0w==} engines: {node: '>=14.21.3'} @@ -2480,6 +2454,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-ms@9.0.0: + resolution: {integrity: sha512-E9e9HJ9R9NasGOgPaPE8VMeiPKAyWR5jcFpNnwIejslIhWqdqOrb2wShBsncMPUb+BcCd2OPYfh7p2W6oemTng==} + engines: {node: '>=18'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -2807,17 +2785,15 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} -<<<<<<< HEAD validator@13.12.0: resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} engines: {node: '>= 0.10'} -======= + vlq@0.2.3: resolution: {integrity: sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==} wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} ->>>>>>> main which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -2863,18 +2839,16 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} -<<<<<<< HEAD zod-form-data@2.0.2: resolution: {integrity: sha512-sKTi+k0fvkxdakD0V5rq+9WVJA3cuTQUfEmNqvHrTzPLvjfLmkkBLfR0ed3qOi9MScJXTHIDH/jUNnEJ3CBX4g==} peerDependencies: zod: '>= 3.11.0' -======= + zod-validation-error@3.3.0: resolution: {integrity: sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw==} engines: {node: '>=18.0.0'} peerDependencies: zod: ^3.18.0 ->>>>>>> main zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} @@ -2887,7 +2861,6 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 -<<<<<<< HEAD '@emnapi/core@0.45.0': dependencies: tslib: 2.6.3 @@ -2913,11 +2886,6 @@ snapshots: dependencies: tslib: 2.6.3 optional: true -======= - '@emnapi/runtime@1.2.0': - dependencies: - tslib: 2.6.2 - optional: true '@ericcornelissen/bash-parser@0.5.2': dependencies: @@ -2940,7 +2908,6 @@ snapshots: shell-quote-word: 1.0.1 to-pascal-case: 1.0.0 unescape-js: 1.1.4 ->>>>>>> main '@esbuild-kit/core-utils@3.3.2': dependencies: @@ -3573,9 +3540,8 @@ snapshots: '@types/prop-types': 15.7.12 csstype: 3.1.3 -<<<<<<< HEAD '@types/validator@13.11.10': {} -======= + '@typescript-eslint/eslint-plugin@7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@eslint-community/regexpp': 4.10.1 @@ -3606,7 +3572,6 @@ snapshots: typescript: 5.4.5 transitivePeerDependencies: - supports-color ->>>>>>> main '@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: @@ -3828,7 +3793,7 @@ snapshots: autoprefixer@10.4.19(postcss@8.4.38): dependencies: browserslist: 4.23.0 - caniuse-lite: 1.0.30001627 + caniuse-lite: 1.0.30001629 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.1 @@ -3866,7 +3831,7 @@ snapshots: browserslist@4.23.0: dependencies: - caniuse-lite: 1.0.30001627 + caniuse-lite: 1.0.30001629 electron-to-chromium: 1.4.792 node-releases: 2.0.14 update-browserslist-db: 1.0.16(browserslist@4.23.0) @@ -3908,24 +3873,20 @@ snapshots: optionalDependencies: fsevents: 2.3.3 -<<<<<<< HEAD class-variance-authority@0.7.0: dependencies: clsx: 2.0.0 - client-only@0.0.1: {} - - clsx@2.0.0: {} - - clsx@2.1.1: {} -======= clean-stack@2.2.0: {} client-only@0.0.1: {} clone@1.0.4: optional: true ->>>>>>> main + + clsx@2.0.0: {} + + clsx@2.1.1: {} color-convert@2.0.1: dependencies: @@ -4487,12 +4448,10 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 -<<<<<<< HEAD + fraction.js@4.3.7: {} + fs-monkey@1.0.6: optional: true -======= - fraction.js@4.3.7: {} ->>>>>>> main fs.realpath@1.0.0: {} @@ -4765,6 +4724,8 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jiti@1.21.0: {} + jiti@1.21.3: {} js-tokens@4.0.0: {} @@ -4794,12 +4755,12 @@ snapshots: dependencies: json-buffer: 3.0.1 - knip@5.17.4(@types/node@20.14.1)(typescript@5.4.5): + knip@5.17.4(@types/node@20.14.2)(typescript@5.4.5): dependencies: '@ericcornelissen/bash-parser': 0.5.2 '@nodelib/fs.walk': 2.0.0 '@snyk/github-codeowners': 1.1.0 - '@types/node': 20.14.1 + '@types/node': 20.14.2 easy-table: 1.2.0 fast-glob: 3.3.2 file-entry-cache: 8.0.0 @@ -4848,7 +4809,6 @@ snapshots: lru-cache@10.2.2: {} -<<<<<<< HEAD lucia@3.2.0: dependencies: oslo: 1.2.0 @@ -4857,6 +4817,12 @@ snapshots: dependencies: react: 18.3.1 + magic-string@0.16.0: + dependencies: + vlq: 0.2.3 + + map-obj@2.0.0: {} + memfs-browser@3.5.10302: dependencies: memfs: 3.5.3 @@ -4866,13 +4832,6 @@ snapshots: dependencies: fs-monkey: 1.0.6 optional: true -======= - magic-string@0.16.0: - dependencies: - vlq: 0.2.3 - - map-obj@2.0.0: {} ->>>>>>> main merge2@1.4.1: {} @@ -5104,15 +5063,16 @@ snapshots: prelude-ls@1.2.1: {} - pretty-ms@9.0.0: - dependencies: - parse-ms: 4.0.0 prettier-plugin-tailwindcss@0.6.1(prettier@3.3.1): dependencies: prettier: 3.3.1 prettier@3.3.1: {} + pretty-ms@9.0.0: + dependencies: + parse-ms: 4.0.0 + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -5515,16 +5475,14 @@ snapshots: util-deprecate@1.0.2: {} -<<<<<<< HEAD validator@13.12.0: {} -======= + vlq@0.2.3: {} wcwidth@1.0.1: dependencies: defaults: 1.0.4 optional: true ->>>>>>> main which-boxed-primitive@1.0.2: dependencies: @@ -5588,11 +5546,11 @@ snapshots: yocto-queue@0.1.0: {} -<<<<<<< HEAD zod-form-data@2.0.2(zod@3.23.8): -======= + dependencies: + zod: 3.23.8 + zod-validation-error@3.3.0(zod@3.23.8): ->>>>>>> main dependencies: zod: 3.23.8 diff --git a/tsconfig.json b/tsconfig.json index 8606067e..e7d41a20 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,6 +21,12 @@ "~/*": ["./*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": [ + "next-env.d.ts", + "next.config.js", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], "exclude": ["node_modules"] } diff --git a/utils/auth.ts b/utils/auth.ts index be1fa88d..f86c7a7c 100644 --- a/utils/auth.ts +++ b/utils/auth.ts @@ -1,15 +1,16 @@ -import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle"; -import type { Session, User } from "lucia"; -import { Lucia } from "lucia"; -import { cookies } from "next/headers"; -import { redirect, RedirectType } from "next/navigation"; -import { cache } from "react"; -import { db } from "~/drizzle/db"; +import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle'; +import type { Session, User } from 'lucia'; +import { Lucia } from 'lucia'; +import { cookies } from 'next/headers'; +import { redirect, RedirectType } from 'next/navigation'; +import { cache } from 'react'; +import { db } from '~/drizzle/db'; import { session as sessionTable, user as userTable, - UserType, -} from "~/drizzle/schema"; + type UserType, +} from '~/drizzle/schema'; +import { env } from '~/env'; const adapter = new DrizzlePostgreSQLAdapter(db, sessionTable, userTable); @@ -20,7 +21,7 @@ export const lucia = new Lucia(adapter, { expires: false, attributes: { // set to `true` when using HTTPS - secure: process.env.NODE_ENV === "production", + secure: env.NODE_ENV === 'production', }, }, getUserAttributes: (attributes) => { @@ -32,7 +33,9 @@ export const lucia = new Lucia(adapter, { }); // IMPORTANT! -declare module "lucia" { +declare module 'lucia' { + // Todo: figure out how to fix linting here + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions interface Register { Lucia: typeof lucia; DatabaseUserAttributes: UserType; @@ -54,12 +57,12 @@ export const validateRequest = cache( const result = await lucia.validateSession(sessionId); // next.js throws when you attempt to set cookie when rendering page try { - if (result.session && result.session.fresh) { + if (result.session?.fresh) { const sessionCookie = lucia.createSessionCookie(result.session.id); cookies().set( sessionCookie.name, sessionCookie.value, - sessionCookie.attributes + sessionCookie.attributes, ); } if (!result.session) { @@ -67,21 +70,22 @@ export const validateRequest = cache( cookies().set( sessionCookie.name, sessionCookie.value, - sessionCookie.attributes + sessionCookie.attributes, ); } } catch (error) { + // eslint-disable-next-line no-console console.error(error); } return result; - } + }, ); export async function requirePageAuth() { const { session, user } = await validateRequest(); if (!session || !user) { - redirect("/signin", RedirectType.replace); + redirect('/signin', RedirectType.replace); } return { session, user }; From e6128f72f17922cd158332139e9da84500cd1fa2 Mon Sep 17 00:00:00 2001 From: Mirfayz Karimoff Date: Fri, 7 Jun 2024 18:22:36 +0500 Subject: [PATCH 11/11] adjust code based on knip errors --- components/ui/button.tsx | 4 ++-- components/ui/card.tsx | 46 ++++++++++++++++++++-------------------- components/ui/input.tsx | 2 +- knip.json | 2 +- package.json | 5 ++--- pnpm-lock.yaml | 12 ----------- utils/authSchema.ts | 22 +++++++++---------- 7 files changed, 40 insertions(+), 53 deletions(-) diff --git a/components/ui/button.tsx b/components/ui/button.tsx index 552d54f1..61dc9df8 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -33,7 +33,7 @@ const buttonVariants = cva( }, ); -export type ButtonProps = { +type ButtonProps = { asChild?: boolean; } & React.ButtonHTMLAttributes & VariantProps; @@ -52,4 +52,4 @@ const Button = React.forwardRef( ); Button.displayName = 'Button'; -export { Button, buttonVariants }; +export { Button }; diff --git a/components/ui/card.tsx b/components/ui/card.tsx index 901efadf..6267c6b0 100644 --- a/components/ui/card.tsx +++ b/components/ui/card.tsx @@ -1,6 +1,6 @@ -import * as React from "react" +import * as React from 'react'; -import { cn } from "~/lib/utils" +import { cn } from '~/lib/utils'; const Card = React.forwardRef< HTMLDivElement, @@ -9,13 +9,13 @@ const Card = React.forwardRef<
-)) -Card.displayName = "Card" +)); +Card.displayName = 'Card'; const CardHeader = React.forwardRef< HTMLDivElement, @@ -23,11 +23,11 @@ const CardHeader = React.forwardRef< >(({ className, ...props }, ref) => (
-)) -CardHeader.displayName = "CardHeader" +)); +CardHeader.displayName = 'CardHeader'; const CardTitle = React.forwardRef< HTMLParagraphElement, @@ -36,13 +36,13 @@ const CardTitle = React.forwardRef<

-)) -CardTitle.displayName = "CardTitle" +)); +CardTitle.displayName = 'CardTitle'; const CardDescription = React.forwardRef< HTMLParagraphElement, @@ -50,19 +50,19 @@ const CardDescription = React.forwardRef< >(({ className, ...props }, ref) => (

-)) -CardDescription.displayName = "CardDescription" +)); +CardDescription.displayName = 'CardDescription'; const CardContent = React.forwardRef< HTMLDivElement, React.HTMLAttributes >(({ className, ...props }, ref) => ( -

-)) -CardContent.displayName = "CardContent" +
+)); +CardContent.displayName = 'CardContent'; const CardFooter = React.forwardRef< HTMLDivElement, @@ -70,10 +70,10 @@ const CardFooter = React.forwardRef< >(({ className, ...props }, ref) => (
-)) -CardFooter.displayName = "CardFooter" +)); +CardFooter.displayName = 'CardFooter'; -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } +export { Card, CardHeader, CardTitle, CardDescription, CardContent }; diff --git a/components/ui/input.tsx b/components/ui/input.tsx index fdd996c9..53b6f7ed 100644 --- a/components/ui/input.tsx +++ b/components/ui/input.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { cn } from '~/lib/utils'; -export type InputProps = object & React.InputHTMLAttributes; +type InputProps = object & React.InputHTMLAttributes; const Input = React.forwardRef( ({ className, type, ...props }, ref) => { diff --git a/knip.json b/knip.json index 26fe4090..10dd0c9e 100644 --- a/knip.json +++ b/knip.json @@ -1,5 +1,5 @@ { "$schema": "https://unpkg.com/knip@5/schema.json", - "ignoreDependencies": ["server-only", "sharp"], + "ignoreDependencies": ["sharp"], "ignoreBinaries": ["docker-compose"] } diff --git a/package.json b/package.json index 439240fb..24a9e4e9 100644 --- a/package.json +++ b/package.json @@ -24,10 +24,9 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "drizzle-orm": "^0.31.1", - "knip": "^5.17.4", "eslint-config-prettier": "^9.1.0", + "knip": "^5.17.4", "lucia": "^3.2.0", - "lucide-react": "^0.383.0", "next": "14.2.3", "postgres": "^3.4.4", "prettier": "^3.3.1", @@ -45,8 +44,8 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", - "autoprefixer": "^10.4.19", "@types/validator": "^13.11.10", + "autoprefixer": "^10.4.19", "drizzle-kit": "^0.22.2", "eslint": "^8.57.0", "eslint-config-next": "14.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4f83c2f..17a2eba6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,9 +47,6 @@ importers: lucia: specifier: ^3.2.0 version: 3.2.0 - lucide-react: - specifier: ^0.383.0 - version: 0.383.0(react@18.3.1) next: specifier: 14.2.3 version: 14.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2128,11 +2125,6 @@ packages: lucia@3.2.0: resolution: {integrity: sha512-eXMxXwk6hqtjRTj4W/x3EnTUtAztLPm0p2N2TEBMDEbakDLXiYnDQ9z/qahjPdPdhPguQc+vwO0/88zIWxlpuw==} - lucide-react@0.383.0: - resolution: {integrity: sha512-13xlG0CQCJtzjSQYwwJ3WRqMHtRj3EXmLlorrARt7y+IHnxUCp3XyFNL1DfaGySWxHObDvnu1u1dV+0VMKHUSg==} - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 - magic-string@0.16.0: resolution: {integrity: sha512-c4BEos3y6G2qO0B9X7K0FVLOPT9uGrjYwYRLFmDqyl5YMboUviyecnXWp94fJTSMwPw2/sf+CEYt5AGpmklkkQ==} @@ -4813,10 +4805,6 @@ snapshots: dependencies: oslo: 1.2.0 - lucide-react@0.383.0(react@18.3.1): - dependencies: - react: 18.3.1 - magic-string@0.16.0: dependencies: vlq: 0.2.3 diff --git a/utils/authSchema.ts b/utils/authSchema.ts index 649d0add..6c1c8ed4 100644 --- a/utils/authSchema.ts +++ b/utils/authSchema.ts @@ -1,12 +1,12 @@ -import { isStrongPassword } from "validator"; -import { z } from "zod"; -import { zfd } from "zod-form-data"; +import { isStrongPassword } from 'validator'; +import { z } from 'zod'; +import { zfd } from 'zod-form-data'; -export const createUserSchema = z.object({ +const createUserSchema = z.object({ username: z .string() - .min(4, { message: "Username must be at least 4 characters" }) - .refine((s) => !s.includes(" "), "Username cannot contain spaces"), + .min(4, { message: 'Username must be at least 4 characters' }) + .refine((s) => !s.includes(' '), 'Username cannot contain spaces'), password: z.string().refine( (password) => isStrongPassword(password, { @@ -17,14 +17,14 @@ export const createUserSchema = z.object({ }), { message: - "Password must contain at least 1 lowercase, 1 uppercase, 1 number, and 1 symbol", - } + 'Password must contain at least 1 lowercase, 1 uppercase, 1 number, and 1 symbol', + }, ), }); -export const loginSchema = z.object({ - username: z.string().min(1, { message: "Username cannot be empty" }), - password: z.string().min(1, { message: "Password cannot be empty" }), +const loginSchema = z.object({ + username: z.string().min(1, { message: 'Username cannot be empty' }), + password: z.string().min(1, { message: 'Password cannot be empty' }), }); export const createUserFormDataSchema = zfd.formData(createUserSchema);