diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d863506..511f023 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -16,25 +16,17 @@ datasource db { } model User { - id String @id - username String @unique - displayName String - email String? @unique - passwordHash String? - googleId String? @unique - avatarUrl String? - bio String? - sessions Session[] - posts Post[] - following Follow[] @relation("Following") - followers Follow[] @relation("Followers") - likes Like[] - bookmarks Bookmark[] - comments Comment[] - receivedNotifications Notification[] @relation("Recipient") - issuedNotifications Notification[] @relation("Issuer") - - createdAt DateTime @default(now()) + id String @id + username String @unique + displayName String + email String? @unique + passwordHash String? + googleID String? @unique + avatarUrl String? + bio String? + sessions Session[] + + createAt DateTime @default(now()) @@map("users") } @@ -43,108 +35,7 @@ model Session { id String @id userId String expiresAt DateTime - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - - @@map("sessions") -} - -model Follow { - followerId String - follower User @relation("Following", fields: [followerId], references: [id], onDelete: Cascade) - followingId String - following User @relation("Followers", fields: [followingId], references: [id], onDelete: Cascade) - - @@unique([followerId, followingId]) - @@map("follows") -} - -model Post { - id String @id @default(cuid()) - content String - userId String - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - attachments Media[] - likes Like[] - bookmarks Bookmark[] - comments Comment[] - linkedNotifications Notification[] - - createdAt DateTime @default(now()) - - @@map("posts") -} - -model Media { - id String @id @default(cuid()) - postId String? - post Post? @relation(fields: [postId], references: [id], onDelete: SetNull) - type MediaType - url String - - createdAt DateTime @default(now()) - - @@map("post_media") -} - -enum MediaType { - IMAGE - VIDEO -} - -model Comment { - id String @id @default(cuid()) - content String - userId String - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - postId String - post Post @relation(fields: [postId], references: [id], onDelete: Cascade) - - createdAt DateTime @default(now()) - - @@map("comments") -} - -model Like { - userId String - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - postId String - post Post @relation(fields: [postId], references: [id], onDelete: Cascade) - - @@unique([userId, postId]) - @@map("likes") -} - -model Bookmark { - id String @id @default(cuid()) - userId String - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - postId String - post Post @relation(fields: [postId], references: [id], onDelete: Cascade) - - createdAt DateTime @default(now()) - - @@unique([userId, postId]) - @@map("bookmarks") -} - -model Notification { - id String @id @default(cuid()) - recipientId String - recipient User @relation("Recipient", fields: [recipientId], references: [id], onDelete: Cascade) - issuerId String - issuer User @relation("Issuer", fields: [issuerId], references: [id], onDelete: Cascade) - postId String? - post Post? @relation(fields: [postId], references: [id], onDelete: Cascade) - type NotificationType - read Boolean @default(false) - - createdAt DateTime @default(now()) - - @@map("notifications") -} + User User @relation(fields: [userId], references: [id], onDelete: Cascade) -enum NotificationType { - LIKE - FOLLOW - COMMENT +@@map("sessions") } diff --git a/src/app/(auth)/actions.ts b/src/app/(auth)/actions.ts index c0ffcc9..bf1e64f 100644 --- a/src/app/(auth)/actions.ts +++ b/src/app/(auth)/actions.ts @@ -1,10 +1,16 @@ +<<<<<<< Updated upstream "use server"; import { lucia, validateRequest } from "@/auth"; +======= +"user server" +import { lucia, validateRequest } from "@/autho" +>>>>>>> Stashed changes import { cookies } from "next/headers"; import { redirect } from "next/navigation"; export async function logout() { +<<<<<<< Updated upstream const { session } = await validateRequest(); if (!session) { @@ -23,3 +29,23 @@ export async function logout() { return redirect("/login"); } +======= + const {session} = await validateRequest(); + + if (!session) { + throw new Error("Unauthorized"); + } + + await lucia.invalidateSession(session.id); + + const sessionCookie = lucia.createBlankSessionCookie(); + + cookies().set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes, + ); + + return redirect("/login"); +} +>>>>>>> Stashed changes diff --git a/src/app/(auth)/login/action.ts b/src/app/(auth)/login/action.ts new file mode 100644 index 0000000..9052fdd --- /dev/null +++ b/src/app/(auth)/login/action.ts @@ -0,0 +1,59 @@ +"use server" +import prisma from "@/lib/prisma"; +import { loginSchema, LoginValues } from "@/lib/validation"; +import { isRedirectError } from "next/dist/client/components/redirect"; +import {verify} from "@node-rs/argon2"; +import { lucia } from "@/autho"; +import { cookies } from "next/headers"; +import { redirect } from "next/navigation"; + +export async function login( + credentials: LoginValues, +): Promise<{error: string}> { + try { + const {username, password} = loginSchema.parse(credentials) + const existingUser = await prisma.user.findFirst({ + where: { + username: { + equals: username, + mode: "insensitive" + } + } + }) + if (!existingUser || !existingUser.passwordHash) { + 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("/"); + + } catch (error) { + if (isRedirectError(error)) throw error; + console.error(error); + return { + error: "Something went wrong. Please try again.", + }; + } +} \ No newline at end of file diff --git a/src/app/(auth)/signup/actions.ts b/src/app/(auth)/signup/actions.ts index 6bbedf9..f4a6ef7 100644 --- a/src/app/(auth)/signup/actions.ts +++ b/src/app/(auth)/signup/actions.ts @@ -1,91 +1,84 @@ -"use server"; +"use server" -import { lucia } from "@/auth"; +import { lucia } from "@/autho"; import prisma from "@/lib/prisma"; -import streamServerClient from "@/lib/stream"; import { signUpSchema, SignUpValues } from "@/lib/validation"; -import { hash } from "@node-rs/argon2"; +import { hash } from "@node-rs/argon2" import { generateIdFromEntropySize } from "lucia"; import { isRedirectError } from "next/dist/client/components/redirect"; import { cookies } from "next/headers"; import { redirect } from "next/navigation"; export async function signUp( - credentials: SignUpValues, + credentials: SignUpValues ): Promise<{ error: string }> { - try { - const { username, email, password } = signUpSchema.parse(credentials); + try { + const { username, email, password } = signUpSchema.parse(credentials); - const passwordHash = await hash(password, { - memoryCost: 19456, - timeCost: 2, - outputLen: 32, - parallelism: 1, - }); + const passwordHash = await hash(password, { + memoryCost: 19456, + timeCost: 2, + outputLen: 32, + parallelism: 1, + }); - const userId = generateIdFromEntropySize(10); + const userId = generateIdFromEntropySize(10); - const existingUsername = await prisma.user.findFirst({ - where: { - username: { - equals: username, - mode: "insensitive", - }, - }, - }); + const existingUsername = await prisma.user.findFirst({ + where: { + username: { + equals: username, + mode: "insensitive", + }, + }, + }); - if (existingUsername) { - return { - error: "Username already taken", - }; - } + if (existingUsername) { + return { + error: "Username already taken", + }; + } - const existingEmail = await prisma.user.findFirst({ - where: { - email: { - equals: email, - mode: "insensitive", - }, - }, - }); + const existingEmail = await prisma.user.findFirst({ + where: { + email: { + equals: email, + mode: "insensitive", + }, + }, + }); - if (existingEmail) { - return { - error: "Email already taken", - }; - } + if (existingEmail) { + return { + error: "Email already taken", + }; + } - await prisma.$transaction(async (tx) => { - await tx.user.create({ - data: { - id: userId, - username, - displayName: username, - email, - passwordHash, - }, - }); - await streamServerClient.upsertUser({ - id: userId, - username, - name: username, - }); - }); + await prisma.user.create({ + data: { + id: userId, + username, + displayName: username, + email, + passwordHash, + } + }) - const session = await lucia.createSession(userId, {}); - const sessionCookie = lucia.createSessionCookie(session.id); - cookies().set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes, - ); + const session = await lucia.createSession(userId, {}); + const sessionCookie = lucia.createSessionCookie(session.id); + cookies().set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes, + ); - return redirect("/"); - } catch (error) { - if (isRedirectError(error)) throw error; - console.error(error); - return { - error: "Something went wrong. Please try again.", - }; - } + return redirect("/"); + + } catch (error) { + if (isRedirectError(error)) throw error; + console.error(error); + return { + error: "Something went wrong. Please try agian.", + }; + } } diff --git a/src/app/(auth)/signup/page.tsx b/src/app/(auth)/signup/page.tsx index aea0a33..bb3ab4c 100644 --- a/src/app/(auth)/signup/page.tsx +++ b/src/app/(auth)/signup/page.tsx @@ -1,38 +1,26 @@ -import signupImage from "@/assets/signup-image.jpg"; -import { Metadata } from "next"; -import Image from "next/image"; -import Link from "next/link"; -import SignUpForm from "./SignUpForm"; +import { Metadata } from 'next'; +import Image from 'next/image'; // Import the Image component from next/image +import signupImage from '@/assets/signup-image.jpg'; export const metadata: Metadata = { - title: "Sign Up", + title: 'Sign Up', }; export default function Page() { - return ( - - - - - Sign up to bugbook - - A place where even you can find a - friend. - - - - - - Already have an account? Log in - - - - - - - ); + return ( + + + + login form + + + + + ); } diff --git a/src/autho.ts b/src/autho.ts new file mode 100644 index 0000000..5273baf --- /dev/null +++ b/src/autho.ts @@ -0,0 +1,78 @@ +import { PrismaAdapter } from "@lucia-auth/adapter-prisma"; +import prisma from "./lib/prisma"; +import { Lucia, Session, User } from "lucia"; +import { cache } from "react"; +import { cookies } from "next/headers"; + +const adapter = new PrismaAdapter(prisma.session, prisma.user); + +export const lucia = new Lucia(adapter, { + sessionCookie: { + expires: false, + attributes: { + secure: process.env.NODE_ENV === "production" + } + }, + getUserAttributes(databaseUserAttributes) { + return { + id: databaseUserAttributes.id, + username: databaseUserAttributes.username, + displayName: databaseUserAttributes.displayName, + avatarUrl: databaseUserAttributes.avatarUrl, + googleId: databaseUserAttributes.googleId, + } + }, +}) + +declare module "lucia" { + interface Register { + Lucia: typeof lucia; + DatabaseUserAttributes: DatabaseUserAttributes; + } +} + +interface DatabaseUserAttributes { + id: string, + username: string, + displayName: string, + avatarUrl: string | null, + googleId: string | null, +} + +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); + + 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; + } +) \ No newline at end of file diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index 186965e..ba707eb 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -1,11 +1,11 @@ import { PrismaClient } from "@prisma/client"; const prismaClientSingleton = () => { - return new PrismaClient(); + return new PrismaClient(); }; declare global { - var prismaGlobal: undefined | ReturnType; + var prismaGlobal: undefined | ReturnType; } const prisma = globalThis.prismaGlobal ?? prismaClientSingleton(); diff --git a/src/lib/validation.ts b/src/lib/validation.ts index ae0b554..efe1dbe 100644 --- a/src/lib/validation.ts +++ b/src/lib/validation.ts @@ -3,6 +3,7 @@ import { z } from "zod"; const requiredString = z.string().trim().min(1, "Required"); export const signUpSchema = z.object({ +<<<<<<< Updated upstream email: requiredString.email("Invalid email address"), username: requiredString.regex( /^[a-zA-Z0-9_-]+$/, @@ -35,3 +36,23 @@ export type UpdateUserProfileValues = z.infer; export const createCommentSchema = z.object({ content: requiredString, }); +======= + email: requiredString.email("Invalid email address"), + username: requiredString.regex( + /^[a-zA-Z0-9_-]+$/, + "Only letters, numbers, - and _ allowed" + ), + password: requiredString.min(8, "Must be at least 8 characters") +}); + +export type SignUpValues = z.infer + +export const loginSchema = z.object({ + username: requiredString, + password: requiredString, +}) + +export type LoginValues = z.infer; + + +>>>>>>> Stashed changes
- A place where even you can find a - friend. -