mirror of
https://github.com/SrIzan10/puntos.git
synced 2026-06-06 01:06:59 +00:00
feat: move away from clerk
This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
const nextConfig = {
|
||||
webpack: (config) => {
|
||||
config.externals.push("@node-rs/argon2", "@node-rs/bcrypt");
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
13
package.json
13
package.json
@@ -6,10 +6,15 @@
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint",
|
||||
"db:generate": "prisma generate",
|
||||
"db:migrate": "prisma migrate dev --name"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clerk/nextjs": "^4.29.11",
|
||||
"@lucia-auth/adapter-prisma": "^4.0.1",
|
||||
"@node-rs/argon2": "^1.8.0",
|
||||
"@node-rs/bcrypt": "^1.10.1",
|
||||
"@prisma/adapter-pg": "^5.12.1",
|
||||
"@prisma/client": "^5.12.1",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
@@ -19,7 +24,10 @@
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"lucia": "^3.1.1",
|
||||
"next": "14.1.4",
|
||||
"oslo": "^1.2.0",
|
||||
"pg": "^8.11.5",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"tailwind-merge": "^2.2.2",
|
||||
@@ -27,6 +35,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/pg": "^8.11.4",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"autoprefixer": "^10.0.1",
|
||||
|
||||
18
prisma/migrations/20240406132011_add_lucia/migration.sql
Normal file
18
prisma/migrations/20240406132011_add_lucia/migration.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Session" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[username]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
||||
- Added the required column `hashed_password` to the `User` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `username` to the `User` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "hashed_password" TEXT NOT NULL,
|
||||
ADD COLUMN "username" TEXT NOT NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
previewFeatures = ["driverAdapters"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
@@ -13,6 +14,20 @@ datasource db {
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id
|
||||
sessions Session[]
|
||||
username String @unique
|
||||
hashed_password String
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id
|
||||
userId String
|
||||
expiresAt DateTime
|
||||
user User @relation(references: [id], fields: [userId], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model Point {
|
||||
id Int @id @default(autoincrement())
|
||||
userId String
|
||||
|
||||
8
src/app/api/verifyAuth/route.ts
Normal file
8
src/app/api/verifyAuth/route.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { validateRequest } from "@/lib/auth";
|
||||
|
||||
export default async function GET(request: Request) {
|
||||
const { user } = await validateRequest();
|
||||
if (!user) {
|
||||
return Response.redirect("/auth/login");
|
||||
}
|
||||
}
|
||||
101
src/app/auth/signIn/page.tsx
Normal file
101
src/app/auth/signIn/page.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @see https://v0.dev/t/ZOQ6u9Lf2bO
|
||||
*/
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import Link from "next/link"
|
||||
import { Argon2id } from "oslo/password";
|
||||
import { cookies } from "next/headers";
|
||||
import { lucia } from "@/lib/auth";
|
||||
import { redirect } from "next/navigation";
|
||||
import prisma from "@/lib/db";
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<div className="flex items-center p-4 lg:p-8">
|
||||
<div className="w-full max-w-md m-auto space-y-8">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold pb-1">Inicia sesión</h1>
|
||||
<p className="text-gray-500 dark:text-gray-400">haha yes</p>
|
||||
</div>
|
||||
<form action={login}>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Nombre de usuario</Label>
|
||||
<Input name="username" id="username" placeholder="srizan" required type="text" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input name="password" id="password" placeholder="osakafromazumangadaioh123" required type="password" />
|
||||
</div>
|
||||
<Button className="w-full" type="submit">Iniciar sesión</Button>
|
||||
<div className="text-center text-sm">
|
||||
¿No tienes una cuenta?
|
||||
<Link className="underline pl-1" href="/auth/signUp">
|
||||
Crear una cuenta
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function login(formData: FormData): Promise<ActionResult> {
|
||||
"use server";
|
||||
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 prisma.user.findUnique({
|
||||
where: {
|
||||
username: username
|
||||
}
|
||||
})
|
||||
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.
|
||||
return {
|
||||
error: "Incorrect username or password"
|
||||
};
|
||||
}
|
||||
|
||||
const validPassword = await new Argon2id().verify(existingUser.hashed_password, password);
|
||||
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("/");
|
||||
}
|
||||
|
||||
interface ActionResult {
|
||||
error: string;
|
||||
}
|
||||
90
src/app/auth/signUp/page.tsx
Normal file
90
src/app/auth/signUp/page.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* @see https://v0.dev/t/5ENQEFtiZm9
|
||||
*/
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import Link from "next/link"
|
||||
import prisma from "@/lib/db";
|
||||
import { Argon2id } from "oslo/password";
|
||||
import { cookies } from "next/headers";
|
||||
import { lucia } from "@/lib/auth";
|
||||
import { redirect } from "next/navigation";
|
||||
import { generateId } from "lucia";
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<div className="flex items-center p-6 lg:p-8">
|
||||
<div className="w-full max-w-md m-auto space-y-8">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold pb-1">Crea una cuenta</h1>
|
||||
<p className="text-gray-500 dark:text-gray-400">venga ya tio</p>
|
||||
</div>
|
||||
<form action={signup}>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="username">Nombre de usuario</Label>
|
||||
<Input name="username" placeholder="srizan" required type="text" id="username" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Contraseña</Label>
|
||||
<Input name="password" placeholder="osakafromazumangadaioh123" required type="password" id="password" />
|
||||
</div>
|
||||
<Button className="w-full" type="submit">Registrarse</Button>
|
||||
<div className="text-center text-sm">
|
||||
¿Ya tienes una cuenta?
|
||||
<Link className="underline pl-1" href="/auth/signIn">
|
||||
Login
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function signup(formData: FormData): Promise<ActionResult> {
|
||||
"use server";
|
||||
const username = formData.get("username");
|
||||
console.log(username)
|
||||
// username must be between 4 ~ 31 characters, and only consists of lowercase letters, 0-9, -, and _
|
||||
// keep in mind some database (e.g. mysql) are case insensitive
|
||||
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 hashedPassword = await new Argon2id().hash(password);
|
||||
const userId = generateId(15);
|
||||
|
||||
// TODO: check if username is already used
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
id: userId,
|
||||
username: username,
|
||||
hashed_password: hashedPassword
|
||||
}
|
||||
});
|
||||
|
||||
const session = await lucia.createSession(userId, {});
|
||||
const sessionCookie = lucia.createSessionCookie(session.id);
|
||||
cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
|
||||
return redirect("/");
|
||||
}
|
||||
|
||||
interface ActionResult {
|
||||
error: string;
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import History from '@/components/app/History/History'
|
||||
import prisma from '@/lib/db'
|
||||
import { currentUser } from '@clerk/nextjs'
|
||||
import { validateRequest } from '@/lib/auth';
|
||||
|
||||
export default async function Page() {
|
||||
const { user } = await validateRequest();
|
||||
const pointHistory = (await prisma.point.findMany({
|
||||
where: {
|
||||
userId: (await currentUser())!.id
|
||||
userId: user!.id
|
||||
}
|
||||
})).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()).map(p => {
|
||||
return {
|
||||
|
||||
@@ -3,6 +3,7 @@ import localFont from 'next/font/local'
|
||||
import "./globals.css";
|
||||
import { ClerkProvider } from "@clerk/nextjs";
|
||||
import Navbar from "@/components/app/Navbar/Navbar";
|
||||
import Protected from "@/components/app/Protected/Protected";
|
||||
|
||||
const satoshi = localFont({ src: './fonts/Satoshi-Medium.woff2' })
|
||||
|
||||
@@ -22,15 +23,15 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<ClerkProvider>
|
||||
<html lang="es">
|
||||
<body className={`${satoshi.className}`}>
|
||||
<Navbar />
|
||||
<div className="p-2">
|
||||
{children}
|
||||
</div>
|
||||
<Protected>
|
||||
<div className="p-2">
|
||||
{children}
|
||||
</div>
|
||||
</Protected>
|
||||
</body>
|
||||
</html>
|
||||
</ClerkProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import DesktopPoints from "@/components/app/Points/Desktop/Desktop";
|
||||
import Points from "@/components/app/Points/Points";
|
||||
import prisma from "@/lib/db";
|
||||
import { currentUser } from "@clerk/nextjs";
|
||||
import { validateRequest } from "@/lib/auth";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default async function Home() {
|
||||
const { user } = await validateRequest();
|
||||
|
||||
const pointCount = (await prisma.pointCount.findFirst({
|
||||
where: {
|
||||
userId: (await currentUser())!.id,
|
||||
userId: user!.id,
|
||||
}
|
||||
}))!.balance
|
||||
}) || { balance: 0 }).balance
|
||||
return (
|
||||
<>
|
||||
<h1 className="text-3xl text-center mb-6">tienes {pointCount} puntos</h1>
|
||||
<div className="flex items-center justify-center">
|
||||
<DesktopPoints />
|
||||
<Points />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import RemovePoints from "@/components/app/RemovePoints/RemovePoints"
|
||||
import { validateRequest } from "@/lib/auth";
|
||||
import prisma from "@/lib/db"
|
||||
import { currentUser } from "@clerk/nextjs"
|
||||
|
||||
export default async function Page() {
|
||||
const { user } = await validateRequest();
|
||||
const pointCount = (await prisma.pointCount.findFirst({
|
||||
where: {
|
||||
userId: (await currentUser())!.id,
|
||||
userId: user!.id,
|
||||
}
|
||||
}))!.balance
|
||||
return (
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
* Documentation: https://v0.dev/docs#integrating-generated-code-into-your-nextjs-app
|
||||
*/
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { DropdownMenuShortcut } from "@/components/ui/dropdown-menu"
|
||||
import { UserButton } from "@clerk/nextjs"
|
||||
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuGroup, DropdownMenuItem } from "@/components/ui/dropdown-menu"
|
||||
import { lucia, validateRequest } from "@/lib/auth"
|
||||
import { cookies } from "next/headers"
|
||||
import Link from "next/link"
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
export default function Navbar() {
|
||||
export default async function Navbar() {
|
||||
const { user } = await validateRequest();
|
||||
return (
|
||||
<nav className="flex items-center h-16 px-4 border-b shrink-0">
|
||||
<DropdownMenu>
|
||||
@@ -41,11 +43,34 @@ export default function Navbar() {
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
</DropdownMenuGroup>
|
||||
{user && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<form action={logout}>
|
||||
<DropdownMenuItem>
|
||||
<Button variant={"ghost"} size={'text'}>Log out</Button>
|
||||
</DropdownMenuItem>
|
||||
</form>
|
||||
</DropdownMenuGroup>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<div className="flex-1" />
|
||||
<UserButton />
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
async function logout(): Promise<ActionResult> {
|
||||
"use server";
|
||||
const { session } = await validateRequest();
|
||||
await lucia.invalidateSession(session!.id);
|
||||
const sessionCookie = lucia.createBlankSessionCookie();
|
||||
cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
|
||||
return redirect("/auth/signUp");
|
||||
}
|
||||
|
||||
interface ActionResult {
|
||||
error: string;
|
||||
}
|
||||
@@ -11,9 +11,11 @@ import {
|
||||
import { Input } from "@/components/ui/input"
|
||||
import prisma from "@/lib/db"
|
||||
import { redirect } from "next/navigation"
|
||||
import { currentUser } from "@clerk/nextjs"
|
||||
import { validateRequest } from "@/lib/auth"
|
||||
|
||||
export default function DesktopPoints() {
|
||||
export default async function Points() {
|
||||
const { user } = await validateRequest();
|
||||
|
||||
async function createPoints(formData: FormData) {
|
||||
'use server'
|
||||
|
||||
@@ -24,14 +26,14 @@ export default function DesktopPoints() {
|
||||
|
||||
await prisma.point.create({
|
||||
data: {
|
||||
userId: (await currentUser())!.id,
|
||||
userId: user!.id,
|
||||
number: Number(rawFormData.points),
|
||||
reason: rawFormData.reason as string,
|
||||
}
|
||||
})
|
||||
await prisma.pointCount.upsert({
|
||||
where: {
|
||||
userId: (await currentUser())!.id,
|
||||
userId: user!.id,
|
||||
},
|
||||
update: {
|
||||
balance: {
|
||||
@@ -39,7 +41,7 @@ export default function DesktopPoints() {
|
||||
}
|
||||
},
|
||||
create: {
|
||||
userId: (await currentUser())!.id,
|
||||
userId: user!.id,
|
||||
balance: Number(rawFormData.points),
|
||||
}
|
||||
})
|
||||
14
src/components/app/Protected/Protected.tsx
Normal file
14
src/components/app/Protected/Protected.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { validateRequest } from "@/lib/auth";
|
||||
import { headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default async function Protected({ children }: { children: React.ReactNode }) {
|
||||
const publicRoutes = ['/auth/signIn', '/auth/signUp'];
|
||||
|
||||
if (publicRoutes.includes(headers().get('x-url')!)) return <>{children}</>;
|
||||
|
||||
const { user } = await validateRequest();
|
||||
if (!user) redirect('/auth/signIn');
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -11,9 +11,11 @@ import {
|
||||
import { Input } from "@/components/ui/input"
|
||||
import prisma from "@/lib/db"
|
||||
import { redirect } from "next/navigation"
|
||||
import { currentUser } from "@clerk/nextjs"
|
||||
import { validateRequest } from "@/lib/auth"
|
||||
|
||||
export default async function RemovePoints() {
|
||||
const { user } = await validateRequest();
|
||||
|
||||
export default function RemovePoints() {
|
||||
async function createPoints(formData: FormData) {
|
||||
'use server'
|
||||
|
||||
@@ -24,14 +26,14 @@ export default function RemovePoints() {
|
||||
|
||||
await prisma.point.create({
|
||||
data: {
|
||||
userId: (await currentUser())!.id,
|
||||
userId: user!.id,
|
||||
number: Number(rawFormData.points),
|
||||
reason: rawFormData.reason as string,
|
||||
}
|
||||
})
|
||||
await prisma.pointCount.upsert({
|
||||
where: {
|
||||
userId: (await currentUser())!.id,
|
||||
userId: user!.id,
|
||||
},
|
||||
update: {
|
||||
balance: {
|
||||
@@ -39,7 +41,7 @@ export default function RemovePoints() {
|
||||
}
|
||||
},
|
||||
create: {
|
||||
userId: (await currentUser())!.id,
|
||||
userId: user!.id,
|
||||
balance: Number(rawFormData.points),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -25,6 +25,7 @@ const buttonVariants = cva(
|
||||
sm: "h-8 rounded-md px-3 text-xs",
|
||||
lg: "h-10 rounded-md px-8",
|
||||
icon: "h-9 w-9",
|
||||
text: "h-9"
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
62
src/lib/auth/index.ts
Normal file
62
src/lib/auth/index.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
// src/auth.ts
|
||||
import { Lucia } from "lucia";
|
||||
import { adapter } from "../db";
|
||||
import { cache } from "react";
|
||||
import { cookies } from "next/headers";
|
||||
import type { Session, User } from "lucia";
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
);
|
||||
|
||||
// IMPORTANT!
|
||||
declare module "lucia" {
|
||||
interface Register {
|
||||
Lucia: typeof lucia;
|
||||
DatabaseUserAttributes: DatabaseUserAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
interface DatabaseUserAttributes {
|
||||
username: string;
|
||||
}
|
||||
@@ -1,15 +1,22 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import { PrismaAdapter } from '@lucia-auth/adapter-prisma'
|
||||
import { Pool } from 'pg'
|
||||
import { PrismaPg } from '@prisma/adapter-pg'
|
||||
|
||||
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
|
||||
const pgAdapter = new PrismaPg(pool)
|
||||
const prismaClientSingleton = () => {
|
||||
return new PrismaClient()
|
||||
return new PrismaClient({ adapter: pgAdapter })
|
||||
}
|
||||
|
||||
declare global {
|
||||
var prismaGlobal: undefined | ReturnType<typeof prismaClientSingleton>
|
||||
}
|
||||
|
||||
|
||||
const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()
|
||||
export const adapter = new PrismaAdapter(prisma.session, prisma.user);
|
||||
|
||||
export default prisma
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma
|
||||
if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma
|
||||
@@ -1,7 +1,12 @@
|
||||
import { authMiddleware } from "@clerk/nextjs";
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export default authMiddleware({});
|
||||
export function middleware(request: Request) {
|
||||
const requestHeaders = new Headers(request.headers);
|
||||
requestHeaders.set('x-url', new URL(request.url).pathname);
|
||||
|
||||
export const config = {
|
||||
matcher: ["/((?!.+.[w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
|
||||
};
|
||||
return NextResponse.next({
|
||||
request: {
|
||||
headers: requestHeaders
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user