debugging auth

This commit is contained in:
andrzej 2024-09-10 12:29:26 +02:00
parent 67af7fac8c
commit 2614f1a3ee
9 changed files with 318 additions and 8 deletions

View File

@ -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
View File

@ -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');

Binary file not shown.

Binary file not shown.

View File

@ -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">

25
src/app/lib/actions.ts Normal file
View File

@ -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;
}
}

View File

@ -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 />

View File

@ -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]) {

85
src/app/ui/login-form.tsx Normal file
View File

@ -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>
);
}