Added sign in action
This commit is contained in:
parent
9ad195ad78
commit
d05fa7e27d
2
.env
2
.env
|
|
@ -1,3 +1,3 @@
|
|||
DATABASE_URL=mysql://root:root@localhost:3306/dashboard_template
|
||||
|
||||
NEXTAUTH_SECRET=
|
||||
JWT_SECRET=
|
||||
|
|
@ -21,9 +21,10 @@
|
|||
"@trpc/react-query": "^10.45.0",
|
||||
"@trpc/server": "^10.45.0",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/jsonwebtoken": "^9.0.5",
|
||||
"bcrypt": "^5.1.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"next": "14.0.4",
|
||||
"next-auth": "beta",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"superjson": "^2.2.1",
|
||||
|
|
|
|||
116
pnpm-lock.yaml
116
pnpm-lock.yaml
|
|
@ -41,15 +41,18 @@ dependencies:
|
|||
'@types/bcrypt':
|
||||
specifier: ^5.0.2
|
||||
version: 5.0.2
|
||||
'@types/jsonwebtoken':
|
||||
specifier: ^9.0.5
|
||||
version: 9.0.5
|
||||
bcrypt:
|
||||
specifier: ^5.1.1
|
||||
version: 5.1.1
|
||||
jsonwebtoken:
|
||||
specifier: ^9.0.2
|
||||
version: 9.0.2
|
||||
next:
|
||||
specifier: 14.0.4
|
||||
version: 14.0.4(react-dom@18.2.0)(react@18.2.0)
|
||||
next-auth:
|
||||
specifier: beta
|
||||
version: 5.0.0-beta.4(next@14.0.4)(react@18.2.0)
|
||||
react:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
|
|
@ -113,22 +116,6 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/@auth/core@0.18.4:
|
||||
resolution: {integrity: sha512-GsNhsP1xE/3FoNS3dVkPjqRljLNJ4iyL2OLv3klQGNvw3bMpROFcK4lqhx7+pPHiamnVaYt2vg1xbB+lsNaevg==}
|
||||
peerDependencies:
|
||||
nodemailer: ^6.8.0
|
||||
peerDependenciesMeta:
|
||||
nodemailer:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@panva/hkdf': 1.1.1
|
||||
cookie: 0.6.0
|
||||
jose: 5.2.0
|
||||
oauth4webapi: 2.4.3
|
||||
preact: 10.11.3
|
||||
preact-render-to-string: 5.2.3(preact@10.11.3)
|
||||
dev: false
|
||||
|
||||
/@auth/core@0.20.0:
|
||||
resolution: {integrity: sha512-04lQH58H5d/9xQ63MOTDTOC7sXWYlr/RhJ97wfFLXzll7nYyCKbkrT3ZMdzdLC5O+qt90sQDK85TAtLlcZ2WBg==}
|
||||
peerDependencies:
|
||||
|
|
@ -646,6 +633,12 @@ packages:
|
|||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||
dev: true
|
||||
|
||||
/@types/jsonwebtoken@9.0.5:
|
||||
resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==}
|
||||
dependencies:
|
||||
'@types/node': 20.10.6
|
||||
dev: false
|
||||
|
||||
/@types/node@20.10.6:
|
||||
resolution: {integrity: sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==}
|
||||
dependencies:
|
||||
|
|
@ -1009,6 +1002,10 @@ packages:
|
|||
update-browserslist-db: 1.0.13(browserslist@4.22.2)
|
||||
dev: true
|
||||
|
||||
/buffer-equal-constant-time@1.0.1:
|
||||
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
|
||||
dev: false
|
||||
|
||||
/busboy@1.6.0:
|
||||
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
||||
engines: {node: '>=10.16.0'}
|
||||
|
|
@ -1231,6 +1228,12 @@ packages:
|
|||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
dev: true
|
||||
|
||||
/ecdsa-sig-formatter@1.0.11:
|
||||
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/electron-to-chromium@1.4.623:
|
||||
resolution: {integrity: sha512-lKoz10iCYlP1WtRYdh5MvocQPWVRoI7ysp6qf18bmeBgR8abE6+I2CsfyNKztRDZvhdWc+krKT6wS7Neg8sw3A==}
|
||||
dev: true
|
||||
|
|
@ -2203,6 +2206,22 @@ packages:
|
|||
minimist: 1.2.8
|
||||
dev: true
|
||||
|
||||
/jsonwebtoken@9.0.2:
|
||||
resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==}
|
||||
engines: {node: '>=12', npm: '>=6'}
|
||||
dependencies:
|
||||
jws: 3.2.2
|
||||
lodash.includes: 4.3.0
|
||||
lodash.isboolean: 3.0.3
|
||||
lodash.isinteger: 4.0.4
|
||||
lodash.isnumber: 3.0.3
|
||||
lodash.isplainobject: 4.0.6
|
||||
lodash.isstring: 4.0.1
|
||||
lodash.once: 4.1.1
|
||||
ms: 2.1.3
|
||||
semver: 7.5.4
|
||||
dev: false
|
||||
|
||||
/jsx-ast-utils@3.3.5:
|
||||
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
|
@ -2213,6 +2232,21 @@ packages:
|
|||
object.values: 1.1.7
|
||||
dev: true
|
||||
|
||||
/jwa@1.4.1:
|
||||
resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==}
|
||||
dependencies:
|
||||
buffer-equal-constant-time: 1.0.1
|
||||
ecdsa-sig-formatter: 1.0.11
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/jws@3.2.2:
|
||||
resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==}
|
||||
dependencies:
|
||||
jwa: 1.4.1
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/keyv@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
dependencies:
|
||||
|
|
@ -2264,10 +2298,38 @@ packages:
|
|||
p-locate: 5.0.0
|
||||
dev: true
|
||||
|
||||
/lodash.includes@4.3.0:
|
||||
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
|
||||
dev: false
|
||||
|
||||
/lodash.isboolean@3.0.3:
|
||||
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
|
||||
dev: false
|
||||
|
||||
/lodash.isinteger@4.0.4:
|
||||
resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
|
||||
dev: false
|
||||
|
||||
/lodash.isnumber@3.0.3:
|
||||
resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==}
|
||||
dev: false
|
||||
|
||||
/lodash.isplainobject@4.0.6:
|
||||
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
|
||||
dev: false
|
||||
|
||||
/lodash.isstring@4.0.1:
|
||||
resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
|
||||
dev: false
|
||||
|
||||
/lodash.merge@4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
dev: true
|
||||
|
||||
/lodash.once@4.1.1:
|
||||
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
|
||||
dev: false
|
||||
|
||||
/loose-envify@1.4.0:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
|
|
@ -2357,7 +2419,6 @@ packages:
|
|||
|
||||
/ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
dev: true
|
||||
|
||||
/mz@2.7.0:
|
||||
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
||||
|
|
@ -2376,21 +2437,6 @@ packages:
|
|||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
dev: true
|
||||
|
||||
/next-auth@5.0.0-beta.4(next@14.0.4)(react@18.2.0):
|
||||
resolution: {integrity: sha512-vgocjvwPA8gxd/zrIP/vr9lJ/HeNe+C56lPP1D3sdyenHt8KncQV6ro7q0xCsDp1fcOKx7WAWVZH5o8aMxDzgw==}
|
||||
peerDependencies:
|
||||
next: ^14
|
||||
nodemailer: ^6.6.5
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
nodemailer:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@auth/core': 0.18.4
|
||||
next: 14.0.4(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/next@14.0.4(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==}
|
||||
engines: {node: '>=18.17.0'}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { signIn } from "next-auth/react";
|
||||
import signIn from "@/features/auth/actions/signIn";
|
||||
import {
|
||||
Paper,
|
||||
PasswordInput,
|
||||
|
|
@ -12,51 +12,17 @@ import {
|
|||
Button,
|
||||
Alert,
|
||||
} from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import { useFormState } from "react-dom";
|
||||
|
||||
/**
|
||||
* Type definition for login form values.
|
||||
*/
|
||||
interface LoginFormType {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* LoginPage component: Renders a login form allowing users to authenticate using their credentials.
|
||||
* Utilizes Mantine for UI components and Next-Auth for authentication handling.
|
||||
*
|
||||
* @returns React functional component representing the login page.
|
||||
*/
|
||||
export default function LoginPage() {
|
||||
const form = useForm<LoginFormType>({
|
||||
initialValues: {
|
||||
email: "",
|
||||
password: "",
|
||||
const initialState = {
|
||||
errors: {
|
||||
message: "",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
|
||||
/**
|
||||
* Handles form submission by calling Next-Auth signIn function with credentials.
|
||||
*
|
||||
* @param values - Object containing email and password entered by the user.
|
||||
*/
|
||||
const handleFormSubmit = async (values: LoginFormType) => {
|
||||
try {
|
||||
await signIn("credentials", {
|
||||
email: values.email,
|
||||
password: values.password,
|
||||
callbackUrl: "/",
|
||||
redirect: false,
|
||||
});
|
||||
} catch (e) {
|
||||
// TODO: Handle proper error message
|
||||
setErrorMessage("Email/Password does not match");
|
||||
}
|
||||
};
|
||||
export default function LoginPage() {
|
||||
const [state, formAction] = useFormState(signIn, initialState);
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen flex items-center justify-center">
|
||||
|
|
@ -64,16 +30,16 @@ export default function LoginPage() {
|
|||
<Text size="lg" fw={500} mb={30}>
|
||||
Welcome
|
||||
</Text>
|
||||
<form onSubmit={form.onSubmit(handleFormSubmit)}>
|
||||
<form action={formAction}>
|
||||
<Stack>
|
||||
{errorMessage ? (
|
||||
{state.errors.message ? (
|
||||
<Alert
|
||||
variant="filled"
|
||||
color="pink"
|
||||
title=""
|
||||
// icon={icon}
|
||||
>
|
||||
{errorMessage}
|
||||
{state.errors.message}
|
||||
</Alert>
|
||||
) : null}
|
||||
<TextInput
|
||||
|
|
@ -81,14 +47,12 @@ export default function LoginPage() {
|
|||
placeholder="Enter your email"
|
||||
name="email"
|
||||
autoComplete="email"
|
||||
{...form.getInputProps("email")}
|
||||
/>
|
||||
<PasswordInput
|
||||
label="Password"
|
||||
placeholder="Your password"
|
||||
name="password"
|
||||
autoComplete="password"
|
||||
{...form.getInputProps("password")}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { auth } from "@/features/auth";
|
||||
import { signIn } from "next-auth/react";
|
||||
import {
|
||||
Paper,
|
||||
PasswordInput,
|
||||
|
|
@ -14,7 +12,6 @@ import {
|
|||
} from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import React, { useEffect } from "react";
|
||||
import { api } from "@/trpc/utils";
|
||||
|
||||
export interface RegisterFormSchema {
|
||||
email: string,
|
||||
|
|
@ -40,30 +37,13 @@ export default function RegisterPage() {
|
|||
}
|
||||
});
|
||||
|
||||
const registerMutation = api.auth.register.useMutation({
|
||||
onSuccess: async () => {
|
||||
console.log("success. signing in")
|
||||
await signIn("credentials", {
|
||||
email: form.values.email,
|
||||
password: form.values.password,
|
||||
callbackUrl: "/dashboard"
|
||||
})
|
||||
console.log("signed in")
|
||||
}
|
||||
})
|
||||
|
||||
const handleFormSubmit = (values: RegisterFormSchema) => {
|
||||
// await
|
||||
registerMutation.mutate(values)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen flex items-center justify-center">
|
||||
<Paper radius="md" p="xl" withBorder w={400}>
|
||||
<Text size="lg" fw={500} mb={30}>
|
||||
Register
|
||||
</Text>
|
||||
<form onSubmit={form.onSubmit(handleFormSubmit)}>
|
||||
<form onSubmit={form.onSubmit(() => {})}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
label="Name"
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export {GET, POST} from "@/features/auth"
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
|
||||
import { appRouter } from '@/trpc/routes/_app';
|
||||
|
||||
const handler = (req: Request) =>
|
||||
fetchRequestHandler({
|
||||
endpoint: '/api/trpc',
|
||||
req,
|
||||
router: appRouter,
|
||||
createContext: () => ({ })
|
||||
});
|
||||
|
||||
export { handler as GET, handler as POST };
|
||||
|
|
@ -5,7 +5,6 @@ import "./globals.css"
|
|||
import '@mantine/core/styles.css';
|
||||
|
||||
import { ColorSchemeScript, MantineProvider } from '@mantine/core';
|
||||
import { TrpcProvider } from '@/trpc/TrpcProvider';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
|
|
@ -26,9 +25,7 @@ export default function RootLayout({
|
|||
</head>
|
||||
<body className={inter.className}>
|
||||
<MantineProvider>
|
||||
<TrpcProvider>
|
||||
{children}
|
||||
</TrpcProvider>
|
||||
</MantineProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ export enum AuthErrorCode {
|
|||
EMAIL_NOT_FOUND = "EMAIL_NOT_FOUND",
|
||||
EMPTY_USER_HASH = "EMPTY_USER_HASH",
|
||||
INVALID_CREDENTIALS = "INVALID_CREDENTIALS",
|
||||
USER_ALREADY_EXISTS = "USER_ALREADY_EXISTS"
|
||||
JWT_SECRET_EMPTY = "JWT_SECRET_NOT_EMPTY",
|
||||
USER_ALREADY_EXISTS = "USER_ALREADY_EXISTS",
|
||||
}
|
||||
|
||||
export default class AuthError extends BaseError {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
"use server"
|
||||
import prisma from "@/db";
|
||||
import { User } from "@prisma/client";
|
||||
import AuthError, { AuthErrorCode } from "../AuthError";
|
||||
import { comparePassword } from "../authUtils";
|
||||
import { comparePassword, createJwtToken } from "../authUtils";
|
||||
import { cookies } from "next/headers";
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
/**
|
||||
* Validates the user by their email and password.
|
||||
|
|
@ -13,10 +16,17 @@ import { comparePassword } from "../authUtils";
|
|||
* @returns The authenticated user object.
|
||||
* @throws {AuthError} - EMAIL_NOT_FOUND if no user is found, INVALID_CREDENTIALS if the password doesn't match, or other auth-related errors.
|
||||
*/
|
||||
export default async function signIn(email: string, password: string): Promise<User> {
|
||||
export default async function signIn(prevState: any, rawFormData: FormData) {
|
||||
//TODO: Add Throttling
|
||||
try {
|
||||
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 }
|
||||
where: { email: formData.email }
|
||||
});
|
||||
|
||||
// Throw if user not found
|
||||
|
|
@ -27,8 +37,34 @@ export default async function signIn(email: string, password: string): Promise<U
|
|||
if (!user.passwordHash) throw new AuthError(AuthErrorCode.EMPTY_USER_HASH, 500);
|
||||
|
||||
// Compare the provided password with the user's stored password hash
|
||||
const isMatch = await comparePassword(password, user.passwordHash);
|
||||
const isMatch = await comparePassword(formData.password, user.passwordHash);
|
||||
if (!isMatch) throw new AuthError(AuthErrorCode.INVALID_CREDENTIALS, 401);
|
||||
|
||||
return user;
|
||||
//Set cookie
|
||||
//TODO: Auth: Add expiry
|
||||
const token = createJwtToken({id: user.id});
|
||||
|
||||
cookies().set("token",token);
|
||||
|
||||
redirect("/dashboard");
|
||||
|
||||
} catch (e: unknown){
|
||||
if (e instanceof AuthError){
|
||||
if ([
|
||||
AuthErrorCode.EMAIL_NOT_FOUND, AuthErrorCode.INVALID_CREDENTIALS
|
||||
]) {
|
||||
return {
|
||||
errors: {
|
||||
message: "Email/Password combination is not match. Please try again"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
errors: {
|
||||
message: "There's something wrong happened on the server. Please try again or contact administrator"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import { User } from "@prisma/client";
|
|||
import * as bcrypt from "bcrypt";
|
||||
import AuthError, { AuthErrorCode } from "./AuthError";
|
||||
import authConfig from "@/config/auth";
|
||||
import jwt from "jsonwebtoken"
|
||||
import UserClaims from "./types/UserClaims";
|
||||
|
||||
/**
|
||||
* Hashes a plain text password using bcrypt.
|
||||
|
|
@ -24,3 +26,10 @@ export async function hashPassword(password: string): Promise<string> {
|
|||
export async function comparePassword(password: string, hash: string): Promise<boolean> {
|
||||
return bcrypt.compare(password, hash);
|
||||
}
|
||||
|
||||
export function createJwtToken(userclaims: UserClaims, options?: jwt.SignOptions){
|
||||
const secret = process.env.JWT_SECRET;
|
||||
if (!secret) throw new AuthError(AuthErrorCode.JWT_SECRET_EMPTY);
|
||||
const token = jwt.sign(userclaims, secret, options);
|
||||
return token;
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
import NextAuth from "next-auth";
|
||||
import emailPasswordProvider from "./providers/emailPasswordProvider";
|
||||
import prisma from "@/db";
|
||||
import { PrismaAdapter } from "@auth/prisma-adapter";
|
||||
|
||||
const nextAuth = NextAuth({
|
||||
adapter: PrismaAdapter(prisma),
|
||||
session: {
|
||||
strategy: "jwt"
|
||||
},
|
||||
providers:[
|
||||
emailPasswordProvider
|
||||
],
|
||||
callbacks: {
|
||||
// signIn: ({ user, account, profile, email, credentials}) => {
|
||||
// console.log("sign in callback")
|
||||
// console.table({user, account, profile, email, credentials})
|
||||
// return true;
|
||||
// },
|
||||
// session: async({session, user, token}) => {
|
||||
// if (session.user){
|
||||
// session.user.id = token.userId as string;
|
||||
// }
|
||||
// return session;
|
||||
// },
|
||||
// jwt: async ({ token, user, account, profile }) => {
|
||||
// if(account && account.type === "credentials") {
|
||||
// token.userId = account.providerAccountId; // this is Id that coming from authorize() callback
|
||||
// }
|
||||
// return token
|
||||
// },
|
||||
// redirect: async ({url, baseUrl}) => {
|
||||
// console.log("redirect callback called")
|
||||
// console.table({url, baseUrl})
|
||||
// return url
|
||||
// }
|
||||
},
|
||||
pages: {
|
||||
signIn: "/login"
|
||||
}
|
||||
});
|
||||
|
||||
export const {
|
||||
signIn,
|
||||
signOut,
|
||||
handlers: { GET, POST},
|
||||
auth
|
||||
} = nextAuth;
|
||||
|
||||
export default nextAuth;
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
import CredentialsProvider from "next-auth/providers/credentials"
|
||||
import AuthError, { AuthErrorCode } from "../AuthError";
|
||||
import BaseError from "@/BaseError";
|
||||
import signIn from "../actions/signIn";
|
||||
|
||||
/**
|
||||
* Factory function to create a credential provider.
|
||||
* It defines the structure of the credentials and includes an authorization function
|
||||
* to validate the user's credentials.
|
||||
*
|
||||
* @returns A CredentialsProvider instance configured for email-password authentication.
|
||||
*/
|
||||
const credential = CredentialsProvider({
|
||||
name: "email-password",
|
||||
credentials: {
|
||||
email: {
|
||||
label: "Email",
|
||||
type: "email",
|
||||
},
|
||||
password: {
|
||||
label: "password",
|
||||
type: "password"
|
||||
}
|
||||
},
|
||||
authorize: async (credentials) => {
|
||||
try {
|
||||
// Ensure credentials are properly formatted strings
|
||||
if (typeof credentials.email !== "string" || typeof credentials.password !== "string"){
|
||||
throw new AuthError(AuthErrorCode.INVALID_CREDENTIALS, 401);
|
||||
}
|
||||
|
||||
// Validate user with provided credentials
|
||||
const user = await signIn(credentials.email, credentials.password);
|
||||
return user;
|
||||
} catch (e: unknown){
|
||||
// Handle specific authentication errors, re-throw others
|
||||
if (e instanceof AuthError){
|
||||
// Auth invalid
|
||||
if ([AuthErrorCode.EMAIL_NOT_FOUND, AuthErrorCode.EMPTY_USER_HASH, AuthErrorCode.INVALID_CREDENTIALS].includes(e.errorCode as AuthErrorCode))
|
||||
return null;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export default credential;
|
||||
7
src/features/auth/types/UserClaims.d.ts
vendored
Normal file
7
src/features/auth/types/UserClaims.d.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { User } from "@prisma/client"
|
||||
|
||||
type UserClaims = {
|
||||
id: User["id"]
|
||||
}
|
||||
|
||||
export default UserClaims
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { httpBatchLink, getFetch, loggerLink } from "@trpc/client";
|
||||
import { useState } from "react";
|
||||
import superjson from "superjson";
|
||||
import { api as trpc } from "@/trpc/utils";
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
|
||||
export const TrpcProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [queryClient] = useState(() => new QueryClient());
|
||||
const [trpcClient] = useState(() =>
|
||||
trpc.createClient({
|
||||
links: [
|
||||
httpBatchLink({
|
||||
url: 'http://localhost:3000/api/trpc',
|
||||
// You can pass any HTTP headers you wish here
|
||||
async headers() {
|
||||
return {
|
||||
// authorization: getAuthCookie(),
|
||||
};
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
return (
|
||||
<trpc.Provider client={trpcClient} queryClient={queryClient}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
<ReactQueryDevtools />
|
||||
</QueryClientProvider>
|
||||
</trpc.Provider>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
import { initTRPC } from '@trpc/server';
|
||||
const t = initTRPC.create();
|
||||
|
||||
// Base router and procedure helpers
|
||||
export const router = t.router;
|
||||
export const procedure = t.procedure;
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import { z } from 'zod';
|
||||
import { procedure, router } from '..';
|
||||
import authRouter from './auth';
|
||||
|
||||
export const appRouter = router({
|
||||
hello: procedure
|
||||
.input(
|
||||
z.object({
|
||||
text: z.string(),
|
||||
}),
|
||||
)
|
||||
.query((opts) => {
|
||||
return {
|
||||
greeting: `hello ${opts.input.text}`,
|
||||
};
|
||||
}),
|
||||
auth: authRouter,
|
||||
});
|
||||
|
||||
// export type definition of API
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
import { z } from "zod";
|
||||
import { procedure, router } from "..";
|
||||
import prisma from "@/db";
|
||||
import createUser from "@/features/auth/actions/createUser";
|
||||
import { AuthError } from "next-auth";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
const authRouter = router({
|
||||
register: procedure
|
||||
.input(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
email: z.string().email(),
|
||||
password: z.string(),
|
||||
passwordConfirmation: z.string(),
|
||||
})
|
||||
.refine(data => data.password === data.passwordConfirmation, {
|
||||
message: "Password don't match",
|
||||
path: ["passwordConfirmation"]
|
||||
})
|
||||
)
|
||||
.mutation(async ({input}) => {
|
||||
try {
|
||||
const user = await createUser({
|
||||
email: input.email,
|
||||
name: input.name,
|
||||
plainPassword: input.password
|
||||
})
|
||||
|
||||
return "ok"
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof AuthError){
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: e.message,
|
||||
cause: e
|
||||
})
|
||||
}
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR"
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
export default authRouter;
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import { httpBatchLink } from '@trpc/client';
|
||||
import { createTRPCNext } from '@trpc/next';
|
||||
import type { AppRouter } from './routes/_app';
|
||||
import { createTRPCReact } from '@trpc/react-query';
|
||||
|
||||
function getBaseUrl() {
|
||||
if (typeof window !== 'undefined')
|
||||
// browser should use relative path
|
||||
return '';
|
||||
|
||||
if (process.env.VERCEL_URL)
|
||||
// reference for vercel.com
|
||||
return `https://${process.env.VERCEL_URL}`;
|
||||
|
||||
if (process.env.RENDER_INTERNAL_HOSTNAME)
|
||||
// reference for render.com
|
||||
return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`;
|
||||
|
||||
// assume localhost
|
||||
return `http://localhost:${process.env.PORT ?? 3000}`;
|
||||
}
|
||||
|
||||
export const api = createTRPCReact<AppRouter>({
|
||||
|
||||
})
|
||||
Loading…
Reference in New Issue
Block a user