Skip to content

Commit

Permalink
Basic auth now works!
Browse files Browse the repository at this point in the history
  • Loading branch information
avsomers25 committed Feb 1, 2024
1 parent b4fe663 commit 81a0fec
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 145 deletions.
156 changes: 49 additions & 107 deletions app/lib/actions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
'use server';

import { z } from 'zod';
import { sql } from '@vercel/postgres';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { signIn } from '@/auth';
import { AuthError } from 'next-auth';

import { auth } from "../../auth"

const FormSchema = z.object({
id: z.string(),
customerId: z.string({
Expand All @@ -21,120 +22,61 @@ const FormSchema = z.object({
date: z.string(),
});

const CreateInvoice = FormSchema.omit({ id: true, date: true });
const UpdateInvoice = FormSchema.omit({ date: true, id: true });

// This is temporary
export type State = {
errors?: {
customerId?: string[];
amount?: string[];
status?: string[];
};
message?: string | null;
};

export async function createInvoice(prevState: State, formData: FormData) {
// Validate form fields using Zod
const validatedFields = CreateInvoice.safeParse({
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
});

// If form validation fails, return errors early. Otherwise, continue.
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
message: 'Missing Fields. Failed to Create Invoice.',
};
}

// Prepare data for insertion into the database
const { customerId, amount, status } = validatedFields.data;
const amountInCents = amount * 100;
const date = new Date().toISOString().split('T')[0];

// Insert data into the database
try {
await sql`
INSERT INTO invoices (customer_id, amount, status, date)
VALUES (${customerId}, ${amountInCents}, ${status}, ${date})
`;
} catch (error) {
// If a database error occurs, return a more specific error.
return {
message: 'Database Error: Failed to Create Invoice.',
};
}

// Revalidate the cache for the invoices page and redirect the user.
revalidatePath('/dashboard/invoices');
redirect('/dashboard/invoices');
}

export async function updateInvoice(
id: string,
prevState: State,
formData: FormData,
) {
const validatedFields = UpdateInvoice.safeParse({
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
});

if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
message: 'Missing Fields. Failed to Update Invoice.',
};
}

const { customerId, amount, status } = validatedFields.data;
const amountInCents = amount * 100;

try {
await sql`
UPDATE invoices
SET customer_id = ${customerId}, amount = ${amountInCents}, status = ${status}
WHERE id = ${id}
`;
} catch (error) {
return { message: 'Database Error: Failed to Update Invoice.' };
}

revalidatePath('/dashboard/invoices');
redirect('/dashboard/invoices');
}

export async function deleteInvoice(id: string) {
// throw new Error('Failed to Delete Invoice');

try {
await sql`DELETE FROM invoices WHERE id = ${id}`;
revalidatePath('/dashboard/invoices');
return { message: 'Deleted Invoice' };
} catch (error) {
return { message: 'Database Error: Failed to Delete Invoice.' };
}
}

export async function authenticate(
prevState: string | undefined,
formData: FormData,
) {
try {
console.log("FORM");
const session = await auth();
console.log(session);
await signIn('credentials', formData);
} catch (error) {
if (error instanceof AuthError) {
switch (error.type) {
case 'CredentialsSignin':
return 'Invalid credentials.';
default:
return 'Something went wrong.';
if (error instanceof AuthError) {
switch (error.type) {
case 'CredentialsSignin':
return 'Invalid credentials.';
default:
return 'Something went wrong.';
}
}
}
throw error;
}
}

export async function authUser(email: string, password:string){
console.log(email);
console.log(password);
let resp = {
error: "",
response: "",
};

await fetch("https://api.hackru.org/dev/authorize", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
password: password,
}),
}).then(async (res) => {
let resJSON = await res.json();
if (resJSON.statusCode === 403) {
resp.error = "Invalid email or password";
} else if (resJSON.statusCode === 200) {
resp.response = resJSON.body.token;
} else{
if (resJSON.body) {
resp.error = resJSON.body;
} else {
resp.error = "Unexpected Error";
}
}
})

return resp;
}


59 changes: 51 additions & 8 deletions app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,60 @@
import AcmeLogo from '@/app/ui/acme-logo';
import LoginForm from '@/app/ui/login-form';
"use client"

import { Button } from '@/app/ui/button';

import { useFormState } from 'react-dom';

import { authenticate, authUser } from '../lib/actions';


export default function LoginPage() {

const [errorMessage, dispatch] = useFormState(authenticate, undefined);


return (
<main className="flex items-center justify-center md:h-screen">
<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 />
<form action={dispatch} >
<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
/>
</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
/>
</div>
</div>
</div>
<LoginForm />
</div>
<Button type = "submit">Log in</Button>
</form>
</main>
);
}
28 changes: 17 additions & 11 deletions auth.config.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import type { NextAuthConfig } from 'next-auth';

export const authConfig = {
// pages: {
// signIn: '/login',
// },
pages: {
signIn: '/login',
},
providers: [
// added later in auth.ts since it requires bcrypt which is only compatible with Node.js
// while this file is also used in non-Node.js environments
],
session:{
maxAge: 259200,
},
callbacks: {
authorized({ auth, request: { nextUrl } }) {
// const isLoggedIn = !!auth?.user;
// const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
// if (isOnDashboard) {
// if (isLoggedIn) return true;
// return false; // Redirect unauthenticated users to login page
// } else if (isLoggedIn) {
// return Response.redirect(new URL('/dashboard', nextUrl));
// }
console.log("AUTH")
const isLoggedIn = !!auth?.user;
const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
if (isOnDashboard) {
if (isLoggedIn) return true;
return false; // Redirect unauthenticated users to login page
}
const isOnLogin = nextUrl.pathname.startsWith('/login');
if (isOnLogin && isLoggedIn){
//return Response.redirect(new URL('/dashboard', nextUrl));
}
return true;
},
},
Expand Down
34 changes: 16 additions & 18 deletions auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,34 @@ import Credentials from 'next-auth/providers/credentials';
import bcrypt from 'bcrypt';
import { sql } from '@vercel/postgres';
import { z } from 'zod';
import type { User } from '@/app/lib/definitions';
import { authConfig } from './auth.config';

async function getUser(email: string): Promise<User | undefined> {
try {
const user = await sql<User>`SELECT * FROM users WHERE email=${email}`;
return user.rows[0];
} catch (error) {
console.error('Failed to fetch user:', error);
throw new Error('Failed to fetch user.');
}
}
import { authConfig} from './auth.config';

import { authUser } from './app/lib/actions';

//https://api.hackru.org/dev/authorize



export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [
Credentials({
async authorize(credentials) {
const parsedCredentials = z
.object({ email: z.string().email(), password: z.string().min(6) })
.object({ email: z.string().email(), password: z.string() })
.safeParse(credentials);

if (parsedCredentials.success) {
const { email, password } = parsedCredentials.data;

const user = await getUser(email);
if (!user) return null;
const resp = await authUser(email, password);
let user = null
if(resp.response != "")
user = {email:email, id:email, name:resp.response}

console.log("USER");
console.log(user)

const passwordsMatch = await bcrypt.compare(password, user.password);
if (passwordsMatch) return user;
return user;
}

console.log('Invalid credentials');
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"next": "^14.0.2",
"next-auth": "^5.0.0-beta.4",
"next-auth": "5.0.0-beta.4",
"postcss": "8.4.31",
"react": "18.2.0",
"react-dom": "18.2.0",
Expand Down

0 comments on commit 81a0fec

Please sign in to comment.