Move sign in to service
This commit is contained in:
parent
d0870419f0
commit
ed9a0baa47
|
|
@ -11,9 +11,7 @@ import {
|
|||
Button,
|
||||
Alert,
|
||||
} from "@mantine/core";
|
||||
import { redirect } from "next/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
import { useFormState } from "react-dom";
|
||||
|
||||
const initialState = {
|
||||
|
|
|
|||
|
|
@ -1,68 +1,41 @@
|
|||
"use server";
|
||||
import prisma from "@/core/db";
|
||||
import { User } from "@prisma/client";
|
||||
import { cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import AuthError from "../error/AuthError";
|
||||
import comparePassword from "../utils/comparePassword";
|
||||
import { createJwtToken } from "../utils/createJwtToken";
|
||||
import signIn from "../services/signIn";
|
||||
|
||||
/**
|
||||
* Handles the sign-in process for a user.
|
||||
* Asynchronously handles the sign-in process for a user by validating their credentials against the database.
|
||||
* Upon successful validation, the user is redirected to the dashboard, and a JWT token is set as a cookie.
|
||||
* If validation fails, a custom `AuthError` is thrown, and detailed error information is provided to the caller.
|
||||
*
|
||||
* This function validates a user's credentials (email and password), checks against the database,
|
||||
* and on successful validation, redirects the user to the dashboard and sets a cookie with a JWT token.
|
||||
* If the validation fails at any stage, it throws a custom AuthError.
|
||||
* Note: Future enhancements should include throttling to prevent brute force attacks and a check to prevent
|
||||
* sign-in attempts if the user is already logged in.
|
||||
*
|
||||
* @param prevState - The previous state of the application, not currently used.
|
||||
* @param rawFormData - The raw form data containing the user's email and password.
|
||||
* @returns A promise that resolves to a redirect to the dashboard on successful authentication,
|
||||
* or an object containing error details on failure.
|
||||
* @throws Specific authentication error based on the failure stage.
|
||||
* @param prevState - The previous state of the application. Currently not utilized but may be used for future enhancements.
|
||||
* @param rawFormData - The raw form data obtained from the sign-in form, containing the user's email and password.
|
||||
* @returns A promise that, upon successful authentication, resolves to a redirection to the dashboard. If authentication fails,
|
||||
* it resolves to an object containing error details.
|
||||
* @throws {AuthError} - Throws a custom `AuthError` with specific error codes for different stages of the authentication failure.
|
||||
*/
|
||||
export default async function signIn(prevState: any, rawFormData: FormData) {
|
||||
export default async function signInAction(prevState: any, rawFormData: FormData) {
|
||||
//TODO: Add Throttling
|
||||
//TODO: Add validation check if the user is already logged in
|
||||
try {
|
||||
// Extract email and password from the raw form data.
|
||||
const formData = {
|
||||
email: rawFormData.get("email") as string,
|
||||
password: rawFormData.get("password") as string,
|
||||
};
|
||||
|
||||
// Retrieve user from the database by email
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: formData.email },
|
||||
});
|
||||
// Attempt to sign in with the provided credentials.
|
||||
const result = await signIn(formData)
|
||||
|
||||
// Throw if user not found
|
||||
if (!user)
|
||||
throw new AuthError({
|
||||
errorCode: "EMAIL_NOT_FOUND",
|
||||
message: "Email or Password does not match",
|
||||
});
|
||||
// Set the JWT token in cookies upon successful sign-in.
|
||||
cookies().set("token", result.token);
|
||||
|
||||
// Throw if user has no password hash
|
||||
// TODO: Add check if the user uses another provider
|
||||
if (!user.passwordHash)
|
||||
throw new AuthError({ errorCode: "EMPTY_USER_HASH" });
|
||||
|
||||
// Compare the provided password with the user's stored password hash
|
||||
const isMatch = await comparePassword(
|
||||
formData.password,
|
||||
user.passwordHash
|
||||
);
|
||||
if (!isMatch)
|
||||
throw new AuthError({
|
||||
errorCode: "INVALID_CREDENTIALS",
|
||||
message: "Email or Password does not match",
|
||||
});
|
||||
|
||||
//Set cookie
|
||||
//TODO: Auth: Add expiry
|
||||
const token = createJwtToken({ id: user.id });
|
||||
|
||||
cookies().set("token", token);
|
||||
// Redirect to the dashboard after successful sign-in.
|
||||
redirect("/dashboard");
|
||||
} catch (e: unknown) {
|
||||
// Custom error handling for authentication errors
|
||||
if (e instanceof AuthError) {
|
||||
|
|
@ -94,6 +67,4 @@ export default async function signIn(prevState: any, rawFormData: FormData) {
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
redirect("/dashboard");
|
||||
}
|
||||
|
|
|
|||
54
src/modules/auth/services/signIn.ts
Normal file
54
src/modules/auth/services/signIn.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import "server-only";
|
||||
import SignInFormData from "../types/SignInFormData";
|
||||
import db from "@/core/db";
|
||||
import AuthError from "../error/AuthError";
|
||||
import comparePassword from "../utils/comparePassword";
|
||||
import { createJwtToken } from "../utils/createJwtToken";
|
||||
|
||||
/**
|
||||
* Authenticates a user with email and password credentials.
|
||||
*
|
||||
* This function looks up the user in the database by email. If the user exists and the password matches
|
||||
* the hashed password in the database, a JWT token is created and returned. If any step of this process fails,
|
||||
* an `AuthError` with a specific error code and message is thrown.
|
||||
*
|
||||
* @param rawCredential - Contains the email and password provided by the user.
|
||||
* @returns An object containing a JWT token if authentication is successful.
|
||||
* @throws {AuthError} - Throws an `AuthError` with an appropriate error code and message for various failure scenarios.
|
||||
*/
|
||||
export default async function signIn(rawCredential: SignInFormData) {
|
||||
const user = await db.user.findUnique({
|
||||
where: { email: rawCredential.email },
|
||||
});
|
||||
|
||||
if (!user)
|
||||
throw new AuthError({
|
||||
errorCode: "EMAIL_NOT_FOUND",
|
||||
message: "Email or Password does not match",
|
||||
});
|
||||
|
||||
//TODO: Add handle for empty password hash
|
||||
// Ensure there is a password hash to compare against.
|
||||
if (!user.passwordHash)
|
||||
throw new AuthError({
|
||||
errorCode: "EMPTY_USER_HASH",
|
||||
message: "Something wrong. Please contact your administrator",
|
||||
});
|
||||
|
||||
// Compare the provided password with the stored hash.
|
||||
const isMatch = await comparePassword(
|
||||
rawCredential.password,
|
||||
user.passwordHash
|
||||
);
|
||||
|
||||
// Create a JWT token upon successful authentication.
|
||||
if (!isMatch)
|
||||
throw new AuthError({
|
||||
errorCode: "INVALID_CREDENTIALS",
|
||||
message: "Email or Password does not match",
|
||||
});
|
||||
|
||||
const token = createJwtToken({ id: user.id });
|
||||
|
||||
return { token };
|
||||
}
|
||||
21
src/modules/auth/types/SignInFormData.d.ts
vendored
Normal file
21
src/modules/auth/types/SignInFormData.d.ts
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Defines the structure for sign-in form data.
|
||||
*
|
||||
* This interface is utilized to type-check the data received from the sign-in form, ensuring that it contains
|
||||
* both an `email` and a `password` field of type `string`. The `email` field represents the user's email address,
|
||||
* and the `password` field represents the user's password. Both fields are required for the sign-in process.
|
||||
*/
|
||||
export default interface SignInFormData {
|
||||
/**
|
||||
* The user's email address.
|
||||
* Must be a valid email format.
|
||||
*/
|
||||
email: string;
|
||||
|
||||
/**
|
||||
* The user's password.
|
||||
* There are no specific constraints defined here for the password's format or strength, but it is expected
|
||||
* to comply with the application's password policy.
|
||||
*/
|
||||
password: string;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user