-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat[AUTH]: Setup usersessions and login, signup page
User sessions and Lucia auth, user creation (old code from zephyr-old)
- Loading branch information
1 parent
122588c
commit b875873
Showing
34 changed files
with
1,252 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
"use server"; | ||
|
||
import { lucia, validateRequest } from "@zephyr/auth/auth"; | ||
import { cookies } from "next/headers"; | ||
import { redirect } from "next/navigation"; | ||
|
||
export async function logout() { | ||
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"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { redirect } from "next/navigation"; | ||
|
||
import { validateRequest } from "@zephyr/auth/auth"; | ||
|
||
export default async function Layout({ | ||
children | ||
}: { children: React.ReactNode }) { | ||
const { user } = await validateRequest(); | ||
|
||
if (user) redirect("/"); | ||
|
||
return <>{children}</>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
"use server"; | ||
|
||
import { verify } from "@node-rs/argon2"; | ||
import { lucia } from "@zephyr/auth/auth"; | ||
import { type LoginValues, loginSchema } from "@zephyr/auth/validation"; | ||
import prisma from "@zephyr/db/prisma"; | ||
import { isRedirectError } from "next/dist/client/components/redirect"; | ||
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." }; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import type { Metadata } from "next"; | ||
import Image from "next/image"; | ||
import Link from "next/link"; | ||
|
||
import loginImage from "@zephyr-assets/login-image.jpg"; | ||
import LoginForm from "@zephyr-ui/Auth/LoginForm"; | ||
|
||
export const metadata: Metadata = { | ||
title: "Login" | ||
}; | ||
|
||
export default function LoginPage() { | ||
return ( | ||
<div className="relative flex min-h-screen bg-background"> | ||
<div className="-translate-y-1/2 absolute top-1/2 left-4 hidden lg:block"> | ||
<h1 className="-rotate-90 transform whitespace-nowrap font-bold text-7xl text-primary opacity-25 lg:text-9xl"> | ||
LOGIN | ||
</h1> | ||
</div> | ||
|
||
<div className="flex flex-1 items-center justify-center p-4 sm:p-8"> | ||
<div className="relative z-10 flex min-h-[600px] w-full max-w-5xl flex-col overflow-hidden rounded-2xl bg-card shadow-xl lg:flex-row"> | ||
<div className="relative hidden w-1/2 bg-primary lg:block"> | ||
<div className="relative h-full w-full"> | ||
<Image | ||
src={loginImage} | ||
alt="Login illustration" | ||
fill | ||
style={{ objectFit: "cover" }} | ||
/> | ||
</div> | ||
</div> | ||
<div className="flex w-full flex-col justify-center p-6 sm:p-8 lg:w-1/2"> | ||
<h2 className="mb-2 text-center font-bold text-3xl text-primary sm:mb-4 sm:text-4xl"> | ||
Welcome Back | ||
</h2> | ||
<LoginForm /> | ||
|
||
<div className="flex flex-col items-center justify-center"> | ||
<Link | ||
href="/signup" | ||
className="text-primary text-sm hover:underline" | ||
> | ||
Don't have an account? Sign Up | ||
</Link> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="pointer-events-none absolute right-2 bottom-2 font-bold text-8xl text-primary opacity-50 sm:right-4 sm:bottom-4 sm:text-6xl"> | ||
ZEPHYR. | ||
</div> | ||
<div | ||
className="absolute top-0 right-0 hidden h-full w-1/2 bg-center bg-cover opacity-10 lg:block" | ||
style={{ backgroundImage: `url(${loginImage.src})` }} | ||
/> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
"use server"; | ||
|
||
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"; | ||
|
||
import { lucia } from "@zephyr/auth/auth"; | ||
import { type SignUpValues, signUpSchema } from "@zephyr/auth/validation"; | ||
import prisma from "@zephyr/db/prisma"; | ||
|
||
export async function signUp( | ||
credentials: SignUpValues | ||
): Promise<{ error: string }> { | ||
try { | ||
const { username, email, password } = signUpSchema.parse(credentials); | ||
|
||
const passwordHash = await hash(password, { | ||
memoryCost: 19456, | ||
timeCost: 2, | ||
outputLen: 32, | ||
parallelism: 1 | ||
}); | ||
|
||
const userId = generateIdFromEntropySize(10); | ||
|
||
const existingUsername = await prisma.user.findFirst({ | ||
where: { | ||
username: { | ||
equals: username, | ||
mode: "insensitive" | ||
} | ||
} | ||
}); | ||
|
||
if (existingUsername) { | ||
return { | ||
error: "Username already taken" | ||
}; | ||
} | ||
|
||
const existingEmail = await prisma.user.findFirst({ | ||
where: { | ||
email: { | ||
equals: email, | ||
mode: "insensitive" | ||
} | ||
} | ||
}); | ||
|
||
if (existingEmail) { | ||
return { | ||
error: "Email already taken" | ||
}; | ||
} | ||
|
||
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 | ||
); | ||
|
||
return redirect("/"); | ||
} catch (error) { | ||
if (isRedirectError(error)) throw error; | ||
console.error(error); | ||
return { | ||
error: "Something went wrong. Please try again." | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import type { Metadata } from "next"; | ||
import Image from "next/image"; | ||
import Link from "next/link"; | ||
|
||
import signupImage from "@zephyr-assets/signup-image.jpg"; | ||
import SignUpForm from "@zephyr-ui/Auth/SignUpForm"; | ||
|
||
export const metadata: Metadata = { | ||
title: "Sign Up" | ||
}; | ||
|
||
export default function SignupPage() { | ||
return ( | ||
<div className="relative flex min-h-screen bg-background"> | ||
<div className="-translate-y-1/2 absolute top-1/2 right-4 hidden lg:block"> | ||
<h1 className="rotate-90 transform whitespace-nowrap font-bold text-7xl text-primary opacity-25 lg:text-9xl"> | ||
SIGN UP | ||
</h1> | ||
</div> | ||
|
||
<div className="flex flex-1 items-center justify-center p-4 sm:p-8"> | ||
<div className="relative z-10 flex min-h-[600px] w-full max-w-5xl flex-col overflow-hidden rounded-2xl bg-card shadow-xl lg:flex-row"> | ||
<div className="flex w-full flex-col justify-center p-6 sm:p-8 lg:w-1/2"> | ||
<h2 className="mb-2 text-center font-bold text-3xl text-primary sm:mb-4 sm:text-4xl"> | ||
Launch Your Journey | ||
</h2> | ||
<SignUpForm /> | ||
|
||
<div className="flex flex-col items-center justify-center"> | ||
<Link | ||
href="/login" | ||
className="text-primary text-sm hover:underline" | ||
> | ||
Already have an account? Login | ||
</Link> | ||
</div> | ||
</div> | ||
<div className="relative hidden w-1/2 bg-primary lg:block"> | ||
<div className="relative h-full w-full"> | ||
<Image | ||
src={signupImage} | ||
alt="Signup illustration" | ||
fill | ||
style={{ objectFit: "cover" }} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="pointer-events-none absolute bottom-2 left-2 font-bold text-8xl text-primary opacity-50 sm:bottom-4 sm:left-4 sm:text-6xl"> | ||
ZEPHYR. | ||
</div> | ||
<div | ||
className="absolute top-0 left-0 hidden h-full w-1/2 bg-center bg-cover opacity-10 lg:block" | ||
style={{ backgroundImage: `url(${signupImage.src})` }} | ||
/> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
"use client"; | ||
|
||
import type { Session, User } from "lucia"; | ||
import type React from "react"; | ||
import { createContext, useContext } from "react"; | ||
|
||
interface SessionContext { | ||
user: User; | ||
session: Session; | ||
} | ||
|
||
const SessionContext = createContext<SessionContext | null>(null); | ||
|
||
export default function SessionProvider({ | ||
children, | ||
value | ||
}: React.PropsWithChildren<{ value: SessionContext }>) { | ||
return ( | ||
<SessionContext.Provider value={value}>{children}</SessionContext.Provider> | ||
); | ||
} | ||
|
||
export function useSession() { | ||
const context = useContext(SessionContext); | ||
if (!context) { | ||
throw new Error("useSession must be used within a SessionProvider"); | ||
} | ||
return context; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { redirect } from "next/navigation"; | ||
|
||
import { validateRequest } from "@zephyr/auth/auth"; | ||
import SessionProvider from "./SessionProvider"; | ||
|
||
export default async function Layout({ | ||
children | ||
}: { children: React.ReactNode }) { | ||
const session = await validateRequest(); | ||
|
||
if (!session.user) redirect("/login"); | ||
|
||
return ( | ||
<SessionProvider value={session}> | ||
<div className="flex h-screen flex-col font-custom">{children}</div> | ||
</SessionProvider> | ||
); | ||
} |
File renamed without changes.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.