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