debugging auth
This commit is contained in:
		
							parent
							
								
									67af7fac8c
								
							
						
					
					
						commit
						2614f1a3ee
					
				|  | @ -1,4 +1,5 @@ | |||
| import type { NextAuthConfig } from 'next-auth'; | ||||
| import Credentials from 'next-auth/providers/credentials'; | ||||
| 
 | ||||
| export const authConfig = { | ||||
| 	pages: { | ||||
|  | @ -7,15 +8,16 @@ export const authConfig = { | |||
| 	callbacks: { | ||||
| 		authorized({ auth, request: { nextUrl } }) { | ||||
| 			const isLoggedIn = !!auth?.user; | ||||
| 			const isOnDashboard = nextUrl.pathname.startsWith('/dashboard'); | ||||
| 			console.log(`isLoggedIn: ${isLoggedIn}`) | ||||
| 			const isOnDashboard = nextUrl.pathname.startsWith('/'); | ||||
| 			if (isOnDashboard) { | ||||
| 				if (isLoggedIn) return true; | ||||
| 				return false; // Redirect unauthenticated users to login page
 | ||||
| 			} else if (isLoggedIn) { | ||||
| 				return Response.redirect(new URL('/dashboard', nextUrl)); | ||||
| 				return Response.redirect(new URL('/', nextUrl)); | ||||
| 			} | ||||
| 			return true; | ||||
| 		}, | ||||
| 	}, | ||||
| 	providers: [], // Add providers with an empty array for now
 | ||||
| 	providers: [Credentials({})], // Add providers with an empty array for now
 | ||||
| } satisfies NextAuthConfig; | ||||
|  |  | |||
							
								
								
									
										15
									
								
								auth.ts
								
								
								
								
							
							
						
						
									
										15
									
								
								auth.ts
								
								
								
								
							|  | @ -1,10 +1,15 @@ | |||
| import NextAuth, { User } from 'next-auth'; | ||||
| import NextAuth from 'next-auth'; | ||||
| import { authConfig } from './auth.config'; | ||||
| import Credentials from 'next-auth/providers/credentials'; | ||||
| import { z } from 'zod'; | ||||
| import prisma from 'app/lib/db'; | ||||
| import bcrypt from 'bcrypt'; | ||||
| 
 | ||||
| export type User = { | ||||
| 	id: number, | ||||
| 	email: string, | ||||
| 	password: string | ||||
| } | ||||
| 
 | ||||
| async function getUser(email: string): Promise<User | undefined> { | ||||
| 	try { | ||||
|  | @ -29,14 +34,20 @@ export const { auth, signIn, signOut } = NextAuth({ | |||
| 				.object({ email: z.string().email(), password: z.string().min(6) }) | ||||
| 				.safeParse(credentials); | ||||
| 
 | ||||
| 			console.log(`parsed credentials: ${JSON.stringify(parsedCredentials)}`) | ||||
| 
 | ||||
| 			if (parsedCredentials.success) { | ||||
| 				const { email, password } = parsedCredentials.data; | ||||
| 				const user = await getUser(email); | ||||
| 				console.log(`USER:${user}`) | ||||
| 				if (!user) return null; | ||||
| 				console.log(`checking string "·${password}" against hash "${user.password}"`) | ||||
| 				const passwordsMatch = await bcrypt.compare(password, user.password); | ||||
| 
 | ||||
| 				if (passwordsMatch) return user; | ||||
| 				if (passwordsMatch) { | ||||
| 					console.log("passwords match!") | ||||
| 					return user | ||||
| 				}; | ||||
| 			} | ||||
| 
 | ||||
| 			console.log('Invalid credentials'); | ||||
|  |  | |||
							
								
								
									
										
											BIN
										
									
								
								prisma/dev.db
								
								
								
								
							
							
						
						
									
										
											BIN
										
									
								
								prisma/dev.db
								
								
								
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -6,6 +6,8 @@ import "./globals.css"; | |||
| import Navlinks from "./ui/navLinks"; | ||||
| import { ModeToggle } from "./ui/modeToggle"; | ||||
| import { inter } from "./ui/fonts"; | ||||
| import { signOut } from "../../auth"; | ||||
| import { LogOut } from "lucide-react"; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  | @ -37,6 +39,19 @@ export default function RootLayout({ | |||
|                 <p className="mt-2 mx-1 text-sm antialiased w-40">The self-hosted literary submission tracker.</p> | ||||
|               </header> | ||||
|                 <Navlinks className="mt-6" /> | ||||
| 
 | ||||
|                 <form | ||||
|                   action={async () => { | ||||
|                     'use server'; | ||||
|                     await signOut(); | ||||
|                   }} | ||||
|                 > | ||||
|                   <button className="flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3"> | ||||
|                     <LogOut className="w-6" /> | ||||
|                     <div className="hidden md:block">Sign Out</div> | ||||
|                   </button> | ||||
|                 </form> | ||||
| 
 | ||||
|                 <footer className="mt-auto"><ModeToggle /></footer> | ||||
|               </div> | ||||
|               <div className="flex justify-center w-full"> | ||||
|  |  | |||
|  | @ -0,0 +1,25 @@ | |||
| 'use server'; | ||||
| 
 | ||||
| import { signIn } from "../../../auth"; | ||||
| import { AuthError } from 'next-auth'; | ||||
| 
 | ||||
| // ...
 | ||||
| 
 | ||||
| export async function authenticate( | ||||
| 	prevState: string | undefined, | ||||
| 	formData: FormData, | ||||
| ) { | ||||
| 	try { | ||||
| 		await signIn('credentials', formData); | ||||
| 	} catch (error) { | ||||
| 		if (error instanceof AuthError) { | ||||
| 			switch (error.type) { | ||||
| 				case 'CredentialsSignin': | ||||
| 					return 'Invalid credentials.'; | ||||
| 				default: | ||||
| 					return 'Something went wrong.'; | ||||
| 			} | ||||
| 		} | ||||
| 		throw error; | ||||
| 	} | ||||
| } | ||||
|  | @ -1,5 +1,4 @@ | |||
| import AcmeLogo from '@/app/ui/acme-logo'; | ||||
| import LoginForm from '@/app/ui/login-form'; | ||||
| import LoginForm from 'app/ui/login-form'; | ||||
| 
 | ||||
| export default function LoginPage() { | ||||
|   return ( | ||||
|  | @ -7,7 +6,6 @@ export default function LoginPage() { | |||
|       <div className="relative mx-auto flex w-full max-w-[400px] flex-col space-y-2.5 p-4 md:-mt-32"> | ||||
|         <div className="flex h-20 w-full items-end rounded-lg bg-blue-500 p-3 md:h-36"> | ||||
|           <div className="w-32 text-white md:w-36"> | ||||
|             <AcmeLogo /> | ||||
|           </div> | ||||
|         </div> | ||||
|         <LoginForm /> | ||||
|  |  | |||
|  | @ -634,6 +634,10 @@ body { | |||
|   border-width: 0; | ||||
| } | ||||
| 
 | ||||
| .pointer-events-none { | ||||
|   pointer-events: none; | ||||
| } | ||||
| 
 | ||||
| .pointer-events-auto { | ||||
|   pointer-events: auto; | ||||
| } | ||||
|  | @ -666,6 +670,10 @@ body { | |||
|   left: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .left-3 { | ||||
|   left: 0.75rem; | ||||
| } | ||||
| 
 | ||||
| .left-\[50\%\] { | ||||
|   left: 50%; | ||||
| } | ||||
|  | @ -686,6 +694,10 @@ body { | |||
|   top: 0px; | ||||
| } | ||||
| 
 | ||||
| .top-1\/2 { | ||||
|   top: 50%; | ||||
| } | ||||
| 
 | ||||
| .top-2 { | ||||
|   top: 0.5rem; | ||||
| } | ||||
|  | @ -740,6 +752,10 @@ body { | |||
|   margin-bottom: 1.5rem; | ||||
| } | ||||
| 
 | ||||
| .mb-3 { | ||||
|   margin-bottom: 0.75rem; | ||||
| } | ||||
| 
 | ||||
| .mb-4 { | ||||
|   margin-bottom: 1rem; | ||||
| } | ||||
|  | @ -764,6 +780,10 @@ body { | |||
|   margin-top: 1rem; | ||||
| } | ||||
| 
 | ||||
| .mt-5 { | ||||
|   margin-top: 1.25rem; | ||||
| } | ||||
| 
 | ||||
| .mt-6 { | ||||
|   margin-top: 1.5rem; | ||||
| } | ||||
|  | @ -772,6 +792,10 @@ body { | |||
|   margin-top: auto; | ||||
| } | ||||
| 
 | ||||
| .block { | ||||
|   display: block; | ||||
| } | ||||
| 
 | ||||
| .flex { | ||||
|   display: flex; | ||||
| } | ||||
|  | @ -792,6 +816,10 @@ body { | |||
|   display: grid; | ||||
| } | ||||
| 
 | ||||
| .hidden { | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| .size-full { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|  | @ -857,6 +885,14 @@ body { | |||
|   height: 1.2rem; | ||||
| } | ||||
| 
 | ||||
| .h-\[18px\] { | ||||
|   height: 18px; | ||||
| } | ||||
| 
 | ||||
| .h-\[48px\] { | ||||
|   height: 48px; | ||||
| } | ||||
| 
 | ||||
| .h-\[var\(--radix-select-trigger-height\)\] { | ||||
|   height: var(--radix-select-trigger-height); | ||||
| } | ||||
|  | @ -922,10 +958,18 @@ body { | |||
|   width: 10rem; | ||||
| } | ||||
| 
 | ||||
| .w-5 { | ||||
|   width: 1.25rem; | ||||
| } | ||||
| 
 | ||||
| .w-5\/6 { | ||||
|   width: 83.333333%; | ||||
| } | ||||
| 
 | ||||
| .w-6 { | ||||
|   width: 1.5rem; | ||||
| } | ||||
| 
 | ||||
| .w-7 { | ||||
|   width: 1.75rem; | ||||
| } | ||||
|  | @ -942,6 +986,10 @@ body { | |||
|   width: 1.2rem; | ||||
| } | ||||
| 
 | ||||
| .w-\[18px\] { | ||||
|   width: 18px; | ||||
| } | ||||
| 
 | ||||
| .w-\[240px\] { | ||||
|   width: 240px; | ||||
| } | ||||
|  | @ -1000,10 +1048,18 @@ body { | |||
|   max-width: 20rem; | ||||
| } | ||||
| 
 | ||||
| .flex-1 { | ||||
|   flex: 1 1 0%; | ||||
| } | ||||
| 
 | ||||
| .shrink-0 { | ||||
|   flex-shrink: 0; | ||||
| } | ||||
| 
 | ||||
| .grow { | ||||
|   flex-grow: 1; | ||||
| } | ||||
| 
 | ||||
| .caption-bottom { | ||||
|   caption-side: bottom; | ||||
| } | ||||
|  | @ -1012,6 +1068,11 @@ body { | |||
|   border-collapse: collapse; | ||||
| } | ||||
| 
 | ||||
| .-translate-y-1\/2 { | ||||
|   --tw-translate-y: -50%; | ||||
|   transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); | ||||
| } | ||||
| 
 | ||||
| .translate-x-\[-50\%\] { | ||||
|   --tw-translate-x: -50%; | ||||
|   transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); | ||||
|  | @ -1191,6 +1252,12 @@ body { | |||
|   margin-bottom: calc(0.625rem * var(--tw-space-y-reverse)); | ||||
| } | ||||
| 
 | ||||
| .space-y-3 > :not([hidden]) ~ :not([hidden]) { | ||||
|   --tw-space-y-reverse: 0; | ||||
|   margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse))); | ||||
|   margin-bottom: calc(0.75rem * var(--tw-space-y-reverse)); | ||||
| } | ||||
| 
 | ||||
| .space-y-4 > :not([hidden]) ~ :not([hidden]) { | ||||
|   --tw-space-y-reverse: 0; | ||||
|   margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); | ||||
|  | @ -1269,6 +1336,11 @@ body { | |||
|   border-color: hsl(var(--destructive)); | ||||
| } | ||||
| 
 | ||||
| .border-gray-200 { | ||||
|   --tw-border-opacity: 1; | ||||
|   border-color: rgb(229 231 235 / var(--tw-border-opacity)); | ||||
| } | ||||
| 
 | ||||
| .border-input { | ||||
|   border-color: hsl(var(--input)); | ||||
| } | ||||
|  | @ -1310,6 +1382,11 @@ body { | |||
|   background-color: hsl(var(--destructive)); | ||||
| } | ||||
| 
 | ||||
| .bg-gray-50 { | ||||
|   --tw-bg-opacity: 1; | ||||
|   background-color: rgb(249 250 251 / var(--tw-bg-opacity)); | ||||
| } | ||||
| 
 | ||||
| .bg-input { | ||||
|   background-color: hsl(var(--input)); | ||||
| } | ||||
|  | @ -1395,6 +1472,11 @@ body { | |||
|   padding-right: 1rem; | ||||
| } | ||||
| 
 | ||||
| .px-6 { | ||||
|   padding-left: 1.5rem; | ||||
|   padding-right: 1.5rem; | ||||
| } | ||||
| 
 | ||||
| .px-8 { | ||||
|   padding-left: 2rem; | ||||
|   padding-right: 2rem; | ||||
|  | @ -1430,6 +1512,19 @@ body { | |||
|   padding-bottom: 1rem; | ||||
| } | ||||
| 
 | ||||
| .py-\[9px\] { | ||||
|   padding-top: 9px; | ||||
|   padding-bottom: 9px; | ||||
| } | ||||
| 
 | ||||
| .pb-4 { | ||||
|   padding-bottom: 1rem; | ||||
| } | ||||
| 
 | ||||
| .pl-10 { | ||||
|   padding-left: 2.5rem; | ||||
| } | ||||
| 
 | ||||
| .pl-3 { | ||||
|   padding-left: 0.75rem; | ||||
| } | ||||
|  | @ -1458,6 +1553,10 @@ body { | |||
|   padding-top: 0.25rem; | ||||
| } | ||||
| 
 | ||||
| .pt-8 { | ||||
|   padding-top: 2rem; | ||||
| } | ||||
| 
 | ||||
| .text-left { | ||||
|   text-align: left; | ||||
| } | ||||
|  | @ -1470,6 +1569,11 @@ body { | |||
|   vertical-align: middle; | ||||
| } | ||||
| 
 | ||||
| .text-2xl { | ||||
|   font-size: 1.5rem; | ||||
|   line-height: 2rem; | ||||
| } | ||||
| 
 | ||||
| .text-3xl { | ||||
|   font-size: 1.875rem; | ||||
|   line-height: 2.25rem; | ||||
|  | @ -1569,6 +1673,21 @@ body { | |||
|   color: hsl(var(--foreground) / 0.5); | ||||
| } | ||||
| 
 | ||||
| .text-gray-50 { | ||||
|   --tw-text-opacity: 1; | ||||
|   color: rgb(249 250 251 / var(--tw-text-opacity)); | ||||
| } | ||||
| 
 | ||||
| .text-gray-500 { | ||||
|   --tw-text-opacity: 1; | ||||
|   color: rgb(107 114 128 / var(--tw-text-opacity)); | ||||
| } | ||||
| 
 | ||||
| .text-gray-900 { | ||||
|   --tw-text-opacity: 1; | ||||
|   color: rgb(17 24 39 / var(--tw-text-opacity)); | ||||
| } | ||||
| 
 | ||||
| .text-muted-foreground { | ||||
|   color: hsl(var(--muted-foreground)); | ||||
| } | ||||
|  | @ -1585,6 +1704,11 @@ body { | |||
|   color: hsl(var(--primary-foreground)); | ||||
| } | ||||
| 
 | ||||
| .text-red-500 { | ||||
|   --tw-text-opacity: 1; | ||||
|   color: rgb(239 68 68 / var(--tw-text-opacity)); | ||||
| } | ||||
| 
 | ||||
| .text-secondary-foreground { | ||||
|   color: hsl(var(--secondary-foreground)); | ||||
| } | ||||
|  | @ -1648,6 +1772,10 @@ body { | |||
|   outline-style: solid; | ||||
| } | ||||
| 
 | ||||
| .outline-2 { | ||||
|   outline-width: 2px; | ||||
| } | ||||
| 
 | ||||
| .ring-offset-background { | ||||
|   --tw-ring-offset-color: hsl(var(--background)); | ||||
| } | ||||
|  | @ -1925,6 +2053,16 @@ body { | |||
|   font-weight: 500; | ||||
| } | ||||
| 
 | ||||
| .placeholder\:text-gray-500::-moz-placeholder { | ||||
|   --tw-text-opacity: 1; | ||||
|   color: rgb(107 114 128 / var(--tw-text-opacity)); | ||||
| } | ||||
| 
 | ||||
| .placeholder\:text-gray-500::placeholder { | ||||
|   --tw-text-opacity: 1; | ||||
|   color: rgb(107 114 128 / var(--tw-text-opacity)); | ||||
| } | ||||
| 
 | ||||
| .placeholder\:text-muted-foreground::-moz-placeholder { | ||||
|   color: hsl(var(--muted-foreground)); | ||||
| } | ||||
|  | @ -1973,10 +2111,20 @@ body { | |||
|   background-color: hsl(var(--secondary) / 0.8); | ||||
| } | ||||
| 
 | ||||
| .hover\:bg-sky-100:hover { | ||||
|   --tw-bg-opacity: 1; | ||||
|   background-color: rgb(224 242 254 / var(--tw-bg-opacity)); | ||||
| } | ||||
| 
 | ||||
| .hover\:text-accent-foreground:hover { | ||||
|   color: hsl(var(--accent-foreground)); | ||||
| } | ||||
| 
 | ||||
| .hover\:text-blue-600:hover { | ||||
|   --tw-text-opacity: 1; | ||||
|   color: rgb(37 99 235 / var(--tw-text-opacity)); | ||||
| } | ||||
| 
 | ||||
| .hover\:text-foreground:hover { | ||||
|   color: hsl(var(--foreground)); | ||||
| } | ||||
|  | @ -2106,6 +2254,11 @@ body { | |||
|   --tw-ring-offset-color: #dc2626; | ||||
| } | ||||
| 
 | ||||
| .peer:focus ~ .peer-focus\:text-gray-900 { | ||||
|   --tw-text-opacity: 1; | ||||
|   color: rgb(17 24 39 / var(--tw-text-opacity)); | ||||
| } | ||||
| 
 | ||||
| .peer:disabled ~ .peer-disabled\:cursor-not-allowed { | ||||
|   cursor: not-allowed; | ||||
| } | ||||
|  | @ -2382,6 +2535,10 @@ body { | |||
|     margin-top: -8rem; | ||||
|   } | ||||
| 
 | ||||
|   .md\:block { | ||||
|     display: block; | ||||
|   } | ||||
| 
 | ||||
|   .md\:h-36 { | ||||
|     height: 9rem; | ||||
|   } | ||||
|  | @ -2397,6 +2554,23 @@ body { | |||
|   .md\:max-w-\[420px\] { | ||||
|     max-width: 420px; | ||||
|   } | ||||
| 
 | ||||
|   .md\:flex-none { | ||||
|     flex: none; | ||||
|   } | ||||
| 
 | ||||
|   .md\:justify-start { | ||||
|     justify-content: flex-start; | ||||
|   } | ||||
| 
 | ||||
|   .md\:p-2 { | ||||
|     padding: 0.5rem; | ||||
|   } | ||||
| 
 | ||||
|   .md\:px-3 { | ||||
|     padding-left: 0.75rem; | ||||
|     padding-right: 0.75rem; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .\[\&\:has\(\[aria-selected\]\)\]\:bg-accent:has([aria-selected]) { | ||||
|  |  | |||
|  | @ -0,0 +1,85 @@ | |||
| 'use client'; | ||||
| 
 | ||||
| import { | ||||
|   AtSign, | ||||
|   KeyIcon, | ||||
|   CircleAlert, | ||||
| } from 'lucide-react'; | ||||
| import { Button } from '@/components/ui/button'; | ||||
| // import { useActionState } from 'react';
 | ||||
| import { useFormState } from 'react-dom'; | ||||
| import { authenticate } from 'app/lib/actions'; | ||||
| import { ArrowRightIcon } from 'lucide-react'; | ||||
| 
 | ||||
| export default function LoginForm() { | ||||
|   const [errorMessage, formAction, isPending] = useFormState( | ||||
|     authenticate, | ||||
|     undefined, | ||||
|   ); | ||||
| 
 | ||||
|   return ( | ||||
|     <form action={formAction} className="space-y-3"> | ||||
|       <div className="flex-1 rounded-lg bg-gray-50 px-6 pb-4 pt-8"> | ||||
|         <h1 className={` mb-3 text-2xl`}> | ||||
|           Please log in to continue. | ||||
|         </h1> | ||||
|         <div className="w-full"> | ||||
|           <div> | ||||
|             <label | ||||
|               className="mb-3 mt-5 block text-xs font-medium text-gray-900" | ||||
|               htmlFor="email" | ||||
|             > | ||||
|               Email | ||||
|             </label> | ||||
|             <div className="relative"> | ||||
|               <input | ||||
|                 className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500" | ||||
|                 id="email" | ||||
|                 type="email" | ||||
|                 name="email" | ||||
|                 placeholder="Enter your email address" | ||||
|                 required | ||||
|               /> | ||||
|               <AtSign className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" /> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div className="mt-4"> | ||||
|             <label | ||||
|               className="mb-3 mt-5 block text-xs font-medium text-gray-900" | ||||
|               htmlFor="password" | ||||
|             > | ||||
|               Password | ||||
|             </label> | ||||
|             <div className="relative"> | ||||
|               <input | ||||
|                 className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500" | ||||
|                 id="password" | ||||
|                 type="password" | ||||
|                 name="password" | ||||
|                 placeholder="Enter password" | ||||
|                 required | ||||
|                 minLength={6} | ||||
|               /> | ||||
|               <KeyIcon className="pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" /> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|         <Button className="mt-4 w-full" aria-disabled={isPending}> | ||||
|           Log in <ArrowRightIcon className="ml-auto h-5 w-5 text-gray-50" /> | ||||
|         </Button> | ||||
|         <div | ||||
|           className="flex h-8 items-end space-x-1" | ||||
|           aria-live="polite" | ||||
|           aria-atomic="true" | ||||
|         > | ||||
|           {errorMessage && ( | ||||
|             <> | ||||
|               <CircleAlert className="h-5 w-5 text-red-500" /> | ||||
|               <p className="text-sm text-red-500">{errorMessage}</p> | ||||
|             </> | ||||
|           )} | ||||
|         </div> | ||||
|       </div> | ||||
|     </form> | ||||
|   ); | ||||
| } | ||||
		Loading…
	
		Reference in New Issue