login endpoint authentication now working

This commit is contained in:
andrzej 2024-09-16 12:53:11 +02:00
parent c0fe9dcf0f
commit 274ee8e4de
5 changed files with 56 additions and 35 deletions

View File

@ -1,13 +1,16 @@
"use server" "use server"
import prisma from 'app/lib/db'; import prisma from 'app/lib/db';
import { jwtVerify, JWTPayload, decodeJwt } from 'jose'; import { jwtVerify, JWTPayload, decodeJwt, SignJWT } from 'jose';
import { cookies } from 'next/headers'; import { cookies } from 'next/headers';
import { loginSchema, LoginSchema } from 'app/login/schema';
import { NextResponse } from 'next/server';
import { TextEncoder } from 'util';
export async function getJWTSecretKey() { export async function getJWTSecretKey() {
const secret = process.env.JWT_SECRET const secret = process.env.JWT_SECRET
if (!secret) throw new Error("There is no JWT secret key") if (!secret) throw new Error("There is no JWT secret key")
return new TextEncoder().encode(secret) const enc: Uint8Array = new TextEncoder().encode(secret)
return enc
} }
export async function verifyJwt(token: string): Promise<JWTPayload | null> { export async function verifyJwt(token: string): Promise<JWTPayload | null> {
@ -76,25 +79,20 @@ export async function setUserDataCookie(userData) {
}) })
} }
export type UserLogin = {
email: string,
password: string
}
export async function login(userLogin: UserLogin) { export async function login(userLogin: LoginSchema) {
const isSafe = loginSchema.safeParse(userLogin)
try { try {
if (!isSafe.success) throw new Error("parse failed")
const user = await prisma.user.findFirst({ where: { email: userLogin.email } }) const user = await prisma.user.findFirst({ where: { email: userLogin.email } })
if (!user) { throw new Error('user does not exist') } if (!user) throw new Error("user does not exist")
const bcrypt = require("bcrypt"); const bcrypt = require("bcrypt");
console.log(`client user: ${JSON.stringify(userLogin)}
db user: ${JSON.stringify(user)}`)
const passwordIsValid = await bcrypt.compare(userLogin.password, user.password) const passwordIsValid = await bcrypt.compare(userLogin.password, user.password)
if (!passwordIsValid) throw new Error('invalid password') if (!passwordIsValid) throw new Error("password is not valid")
//return the user object without the hashed password return { email: userLogin.email }
return { email: user.email, id: user.id }
} catch (error) { } catch (error) {
console.error(error) throw new Error('login failed')
throw new Error('invalid login or password')
} }
} }

View File

@ -1,4 +1,5 @@
import { NextResponse, NextRequest } from "next/server"; import { NextResponse, NextRequest } from "next/server";
import prisma from "app/lib/db";
import { SignJWT } from "jose"; import { SignJWT } from "jose";
import { getJWTSecretKey, login, setUserDataCookie } from "../actions"; import { getJWTSecretKey, login, setUserDataCookie } from "../actions";
@ -13,9 +14,9 @@ const dynamic = 'force-dynamic'
//POST endpoint //POST endpoint
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
const { body } = await request.json() const body = await request.json()
const { email, password } = body
console.log(`body: ${JSON.stringify(body)}`) console.log(`body: ${JSON.stringify(body)}`)
const { email, password } = body
if (!email || !password) { if (!email || !password) {
const res = { const res = {
@ -37,7 +38,7 @@ export async function POST(request: NextRequest) {
.setProtectedHeader({ alg: 'HS256' }) .setProtectedHeader({ alg: 'HS256' })
.setIssuedAt() .setIssuedAt()
.setExpirationTime('1h') .setExpirationTime('1h')
.sign(getJWTSecretKey()) .sign(await getJWTSecretKey())
//make response //make response
const res = { success: true } const res = { success: true }

View File

@ -6,28 +6,44 @@ import { toast } from "@/components/ui/use-toast";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { login } from "app/api/auth/actions"; import { redirect } from "next/navigation";
import { loginSchema } from "./schema";
import { useRouter } from "next/navigation";
const formSchema = z.object({
email: z.string().email(),
password: z.string().min(6)
})
export default function LoginForm() { export default function LoginForm() {
const form = useForm<z.infer<typeof formSchema>>({ const router = useRouter()
resolver: zodResolver(formSchema), const form = useForm<z.infer<typeof loginSchema>>({
resolver: zodResolver(loginSchema),
}) })
function onErrors(errors) { const onSubmit = form.handleSubmit(async (data) => {
toast({ // const res = await login(data)
title: "WHOOPS", // if (res?.error) {
description: JSON.stringify(errors) // toast({ title: "Whoops!", description: res.error })
// form.reset()
// } else {
// toast({ title: "login successful" })
// }
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify(data),
}) })
} if (res.status === 200) {
toast({ title: "login successful!" })
router.push('/submission')
} else {
toast({ title: "login failed!" })
}
})
return ( return (
<Form {...form}> <Form {...form}>
<form action={login}> <form onSubmit={onSubmit}>
<FormField <FormField
control={form.control} control={form.control}
name="email" name="email"

7
src/app/login/schema.ts Normal file
View File

@ -0,0 +1,7 @@
import { z } from "zod"
export const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(6)
})
export type LoginSchema = z.infer<typeof loginSchema>

View File

@ -17,10 +17,9 @@ export default async function(request: NextRequest) {
// const url = `${process.env.NEXT_PUBLIC_BASE_URL}/login?redirect=${request.nextUrl.pathname + request.nextUrl.search}` // const url = `${process.env.NEXT_PUBLIC_BASE_URL}/login?redirect=${request.nextUrl.pathname + request.nextUrl.search}`
const url = request.nextUrl.clone() const url = request.nextUrl.clone()
url.pathname = "/login" url.pathname = "/login"
if (protectedRoutes.some(pattern => matchesWildcard(request.nextUrl.pathname, pattern))) { if (protectedRoutes.some(pattern => matchesWildcard(request.nextUrl.pathname, pattern))) {
const token = request.cookies.get('token') const token = request.cookies.get('token')
console.log(`token: ${JSON.stringify(token)}`)
//NOTE - may need to add logic to return 401 for api routes //NOTE - may need to add logic to return 401 for api routes
if (!token) { if (!token) {
@ -30,9 +29,9 @@ export default async function(request: NextRequest) {
try { try {
//decode and verify jwt cookie //decode and verify jwt cookie
const jwtIsVerified = await verifyJwt(token.value) const jwtIsVerified = await verifyJwt(token.value)
if (!jwtIsVerified) { if (!jwtIsVerified) {
//delete token //delete token
console.log('could not verify jwt')
request.cookies.delete('token') request.cookies.delete('token')
return NextResponse.redirect(url) return NextResponse.redirect(url)
} }