Skip to content

Commit

Permalink
Organize
Browse files Browse the repository at this point in the history
  • Loading branch information
delbaoliveira committed Mar 14, 2024
1 parent 1db0c64 commit 6c61376
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 53 deletions.
2 changes: 1 addition & 1 deletion app/(public)/login/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { login } from '@/app/auth/auth';
import { login } from '@/app/auth/01-auth';
import Link from 'next/link';
import { useFormState, useFormStatus } from 'react-dom';

Expand Down
6 changes: 4 additions & 2 deletions app/(public)/signup/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { signup } from '@/lib/auth';
import { signup } from '@/app/auth/01-auth';
import { useFormState, useFormStatus } from 'react-dom';

export function SignupForm() {
Expand Down Expand Up @@ -35,7 +35,9 @@ export function SignupForm() {
<p>Password must:</p>
<ul>
{state.errors.password.map((error) => (
<li key={error}>- {error}</li>
<li key={error} className="text-red-500">
- {error}
</li>
))}
</ul>
</div>
Expand Down
70 changes: 39 additions & 31 deletions lib/auth.ts → app/auth/01-auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// this file includes the authentication logic for
// signing up, logging in, and logging out.
// see `session.ts` for the session management logic.
// This file includes the authentication logic for
// signing up, logging in, and logging out using Server Actions.
// See `02-session.ts` for the session management logic.

// We're querying the database directly
// but at this point, we should recommend calling an Auth Provider's API.

'use server';

Expand All @@ -10,13 +13,16 @@ import {
FormState,
LoginFormSchema,
SignupFormSchema,
} from '@/lib/definitions';
import { createSession, deleteSession } from '@/lib/session';
} from '@/app/auth/definitions';
import { createSession, deleteSession } from '@/app/auth/02-session';
import bcrypt from 'bcrypt';
import { eq } from 'drizzle-orm';
import { redirect } from 'next/navigation';

export async function signup(state: FormState, formData: FormData) {
export async function signup(
state: FormState,
formData: FormData,
): Promise<FormState> {
// 1. Validate form fields
const validatedFields = SignupFormSchema.safeParse({
name: formData.get('name'),
Expand Down Expand Up @@ -59,51 +65,53 @@ export async function signup(state: FormState, formData: FormData) {
}
}

export async function login(state: FormState, formData: FormData) {
export async function login(
state: FormState,
formData: FormData,
): Promise<FormState> {
// 1. Validate form fields
const validatedFields = LoginFormSchema.safeParse({
email: formData.get('email'),
password: formData.get('password'),
});
const errorMessage = { message: 'Invalid login credentials.' };

// 2. If any form fields are invalid, return early and display errors
if (!validatedFields.success) {
return {
message: validatedFields.error.flatten().fieldErrors,
errors: validatedFields.error.flatten().fieldErrors,
};
}

// 3. Query the database for the user with the given email
const user = await db.query.users.findFirst({
where: eq(users.email, validatedFields.data.email),
});
try {
const user = await db.query.users.findFirst({
where: eq(users.email, validatedFields.data.email),
});

// If user is not found, return an error
if (!user) {
return {
message: 'No user found with that email.',
};
}
// If user is not found, return early and display an error
if (!user) {
return errorMessage;
}
// 4. Compare the user's password with the hashed password in the database
const passwordMatch = await bcrypt.compare(
validatedFields.data.password,
user.password,
);

// 4. Compare the user's password with the hashed password in the database
const passwordMatch = await bcrypt.compare(
validatedFields.data.password,
user.password,
);
// If the password does not match, return early and display an error
if (!passwordMatch) {
return errorMessage;
}

// If the password does not match, return an error
if (!passwordMatch) {
return {
message: 'Invalid login credentials.',
};
// 5. If login successful, create a session for the user
await createSession(user.id);
} catch (error) {
return errorMessage;
}

// 5. If the password is correct, create a session for the user
await createSession(user.id);
}

export async function logout() {
console.log('logging out');
deleteSession();
redirect('/login');
}
39 changes: 23 additions & 16 deletions lib/session.ts → app/auth/02-session.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
// This file includes the session management logic
// It's the 2nd part of the auth process
// See `01-auth.ts` for the authentication logic
// See `middleware.ts` and `03-dal.ts` for the authorization / data access logic

import 'server-only';

import { db } from '@/drizzle/db';
import { sessions } from '@/drizzle/schema';
import { eq } from 'drizzle-orm';
import jwt from 'jsonwebtoken';
import { cookies } from 'next/headers';
import { eq } from 'drizzle-orm';

// TODO: Replace with secret key from environment variables
const secretKey = 'yourSecretKey';

// Option 1: Client-side stateless session with cookies | Optimistic auth check
// Option 2: Server-side sessions with tokens stored in a database | Secure auth check
// Option 2: Server-side sessions with tokens (or session ID) stored in a database | Secure auth check

export async function createSession(id: number) {
const token = jwt.sign({ id }, secretKey, {
Expand All @@ -30,22 +33,30 @@ export async function createSession(id: number) {
});

// Option 2: Store token in database
await db.insert(sessions).values({
userId: id,
token,
expiresAt,
});
const data = await db
.insert(sessions)
.values({
userId: id,
token, // or session ID
expiresAt,
})
.returning({ token: sessions.token });

// Store session ID in a cookie...
}

// Option 1: Optimistically check for token in cookies
export async function verifyClientSession() {
const token = cookies().get('token')?.value;

if (!token) return null;

const { id } = jwt.verify(token, secretKey) as { id: number };

return { isAuth: true, userId: id };
try {
const { id } = jwt.verify(token, secretKey) as { id: number };
if (!id) return null;
return { isAuth: true, userId: id };
} catch (error) {
return null;
}
}

// Option 2: Securely check for token in the database
Expand All @@ -65,17 +76,13 @@ export async function verifyServerSession() {
.where(eq(sessions.token, token));

if (!data || data[0].expiresAt < new Date()) return null;

return { isAuth: true, userId: data[0].userId };
} catch (error) {
return null;
}
}

export function updateSession() {
// Option 1
// Option 2
}
export function updateSession() {}

export function deleteSession() {
// Option 1
Expand Down
2 changes: 2 additions & 0 deletions app/auth/03-dal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// authorization begins with middleware
// see middleware.ts
4 changes: 2 additions & 2 deletions lib/definitions.ts → app/auth/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const SignupFormSchema = z.object({
});

export const LoginFormSchema = z.object({
email: z.string().email({ message: 'Please enter a valid email' }),
password: z.string().min(1, { message: 'Password field must not be empty' }),
email: z.string().email({ message: 'Please enter a valid email.' }),
password: z.string().min(1, { message: 'Password field must not be empty.' }),
});

export type FormState =
Expand Down
2 changes: 1 addition & 1 deletion app/dashboard/logout-button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { logout } from '@/lib/auth';
import { logout } from '@/app/auth/01-auth';
import { LogOutIcon } from '@/components/ui/icons';
export default function LogoutButton() {
return (
Expand Down
Empty file removed lib/dal.ts
Empty file.

0 comments on commit 6c61376

Please sign in to comment.