Moved register and login to user folder. Setup basic login, register and JWT

This commit is contained in:
2024-10-08 20:35:38 +00:00
parent c634b8e73f
commit eddd1d8ef7
242 changed files with 3628 additions and 7542 deletions

View File

@ -0,0 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { login } from '../../../../../src/controllers/authController'; // Import the login function from authController
export async function POST(req: NextRequest) {
// Delegate the request to the login function in the controller
return login(req);
}

View File

@ -0,0 +1,63 @@
import { NextRequest, NextResponse } from 'next/server';
import bcrypt from 'bcrypt';
import prisma from '../../../models/prismaClient';
export async function POST(req: NextRequest) {
try {
const body = await req.json();
console.log('Request Body:', body); // Log the request body
const { name, username, email, password, role, company, address, phone, privatePhone, cvr } = body;
// Check if the username already exists
const existingUserByUsername = await prisma.user.findUnique({
where: { username },
});
if (existingUserByUsername) {
console.log('Username already exists');
return NextResponse.json({ error: 'Username already exists' }, { status: 400 });
}
// Check if the email already exists
const existingUserByEmail = await prisma.user.findUnique({
where: { email },
});
if (existingUserByEmail) {
console.log('Email already exists');
return NextResponse.json({ error: 'Email already exists' }, { status: 400 });
}
// Hash the password
const hashedPassword = await bcrypt.hash(password, 10);
// Create the user
const newUser = await prisma.user.create({
data: {
name,
username,
email,
password: hashedPassword,
role,
address,
phone,
company: role === 'BUSINESS' ? company : null,
privatePhone: role === 'BUSINESS' ? privatePhone : null,
cvr: role === 'BUSINESS' ? cvr : null,
},
});
console.log('User created successfully:', newUser);
return NextResponse.json({ user: newUser }, { status: 201 });
} catch (error) {
// Check if the error is an instance of Error and log the details
if (error instanceof Error) {
console.error('Error message:', error.message);
console.error('Full error stack:', error.stack);
} else {
console.error('Unknown error:', error); // Log if the error is not of type `Error`
}
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}

View File

@ -26,6 +26,11 @@ const Header: React.FC = () => {
Contact
</Link>
</li>
<li>
<Link href="/account" className='hover:text-gray-400'>
Account
</Link>
</li>
</ul>
</nav>
</div>

View File

@ -0,0 +1,5 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default prisma;

View File

@ -0,0 +1,91 @@
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
const LoginPage = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const router = useRouter();
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password }),
});
// Check if the response has a body to parse
let data = null;
try {
data = await res.json();
} catch (error) {
// If the response body is empty or not valid JSON, handle it gracefully
console.error('Error parsing response JSON:', error);
}
// Handle successful login
if (res.ok && data?.token) {
localStorage.setItem('token', data.token); // Store JWT token in localStorage
router.push('/'); // Redirect to a protected page (e.g., dashboard)
} else {
// Handle login errors, ensuring data is valid
setError(data?.error || 'Failed to log in');
}
};
return (
<div className="flex justify-center items-center h-screen bg-gray-100">
<div className="bg-white p-6 rounded-lg shadow-md w-96">
<h2 className="text-2xl font-bold mb-4">Login</h2>
{error && <p className="text-red-500 mb-4">{error}</p>}
<form onSubmit={handleLogin}>
<div className="mb-4">
<label htmlFor="username" className="block text-sm font-medium text-gray-700">
Username
</label>
<input
type="text"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
required
/>
</div>
<div className="mb-4">
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
Password
</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
required
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
>
Login
</button>
</form>
</div>
</div>
);
};
export default LoginPage;

View File

@ -0,0 +1,224 @@
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
const RegisterPage = () => {
const [role, setRole] = useState<'PRIVATE' | 'BUSINESS' | null>(null); // Initially, no role is selected
const [name, setName] = useState('');
const [company, setCompany] = useState('');
const [address, setAddress] = useState('');
const [phone, setPhone] = useState('');
const [privatePhone, setPrivatePhone] = useState('');
const [email, setEmail] = useState('');
const [cvr, setCvr] = useState('');
const [password, setPassword] = useState('');
const [username, setUsername] = useState('');
const router = useRouter();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const userData: any = {
name,
address,
phone,
email,
password,
username,
role,
};
if (role === 'BUSINESS') {
userData.company = company;
userData.privatePhone = privatePhone;
userData.cvr = cvr;
}
console.log('Submitting user data:', userData); // Log form data before submission
const res = await fetch('/api/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
if (res.ok) {
router.push('/user/login');
} else {
const errorData = await res.json();
console.error('Failed to register:', errorData); // Log the error response from the server
}
};
return (
<div className="flex flex-col justify-center items-center h-screen bg-gray-100">
{/* Registration Header */}
<div className="mb-8 text-center">
<h2 className="text-3xl font-bold mb-4">Register Here</h2>
<p className="text-gray-600">Please select whether you're a Private or Business user to register.</p>
</div>
{/* Role Selection */}
{role === null && (
<div className="flex space-x-4 mb-6">
<button
className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-6 rounded focus:outline-none focus:shadow-outline"
onClick={() => setRole('PRIVATE')}
>
Private
</button>
<button
className="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-6 rounded focus:outline-none focus:shadow-outline"
onClick={() => setRole('BUSINESS')}
>
Business
</button>
</div>
)}
{/* Registration Form */}
{role && (
<div className="bg-white p-8 rounded-lg shadow-md w-96">
<h3 className="text-2xl font-bold mb-4">{role === 'PRIVATE' ? 'Private Registration' : 'Business Registration'}</h3>
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label htmlFor="name" className="block text-sm font-medium text-gray-700">Name</label>
<input
type="text"
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
required
/>
</div>
{role === 'BUSINESS' && (
<>
<div className="mb-4">
<label htmlFor="company" className="block text-sm font-medium text-gray-700">Company</label>
<input
type="text"
id="company"
value={company}
onChange={(e) => setCompany(e.target.value)}
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
required={role === 'BUSINESS'}
/>
</div>
<div className="mb-4">
<label htmlFor="privatePhone" className="block text-sm font-medium text-gray-700">Private Phone</label>
<input
type="text"
id="privatePhone"
value={privatePhone}
onChange={(e) => setPrivatePhone(e.target.value)}
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
required={role === 'BUSINESS'}
/>
</div>
<div className="mb-4">
<label htmlFor="cvr" className="block text-sm font-medium text-gray-700">CVR</label>
<input
type="text"
id="cvr"
value={cvr}
onChange={(e) => setCvr(e.target.value)}
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
required={role === 'BUSINESS'}
/>
</div>
</>
)}
<div className="mb-4">
<label htmlFor="address" className="block text-sm font-medium text-gray-700">Address</label>
<input
type="text"
id="address"
value={address}
onChange={(e) => setAddress(e.target.value)}
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
required
/>
</div>
<div className="mb-4">
<label htmlFor="phone" className="block text-sm font-medium text-gray-700">Phone</label>
<input
type="text"
id="phone"
value={phone}
onChange={(e) => setPhone(e.target.value)}
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
required
/>
</div>
<div className="mb-4">
<label htmlFor="email" className="block text-sm font-medium text-gray-700">Email</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
required
/>
</div>
<div className="mb-4">
<label htmlFor="username" className="block text-sm font-medium text-gray-700">Username</label>
<input
type="text"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
required
/>
</div>
<div className="mb-4">
<label htmlFor="password" className="block text-sm font-medium text-gray-700">Password</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="mt-1 p-2 block w-full border border-gray-300 rounded-md shadow-sm"
required
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
>
Register
</button>
</form>
</div>
)}
{/* Already Registered */}
<div className="mt-6">
<p className="text-gray-600">
Already registered?{' '}
<Link href="/user/login" className="text-blue-500 hover:underline">
Login here
</Link>
</p>
</div>
</div>
);
};
export default RegisterPage;

1
src/config/config.ts Normal file
View File

@ -0,0 +1 @@
export const SECRET_KEY = process.env.SECRET_KEY || 'f850bce14191c4e9d1d2c9b06701e5ad';

View File

View File

@ -0,0 +1,36 @@
import { NextRequest, NextResponse } from 'next/server';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import prisma from '../app/models/prismaClient';
import { SECRET_KEY } from '../config/config'; // Load from config.ts or directly from process.env
export async function login(req: NextRequest) {
try {
// Parse the request body
const body = await req.json();
const { username, password } = body;
// Find the user by username
const user = await prisma.user.findUnique({ where: { username } });
// If user not found or password is incorrect, return an error
if (!user || !(await bcrypt.compare(password, user.password))) {
return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 });
}
// Ensure SECRET_KEY is defined
if (!SECRET_KEY) {
console.error('SECRET_KEY is missing');
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
// Generate JWT token with user ID
const token = jwt.sign({ userId: user.id }, SECRET_KEY, { expiresIn: '1h' });
// Return the token in the response
return NextResponse.json({ token }, { status: 200 });
} catch (error) {
console.error('Login error:', error);
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}

View File

View File

@ -0,0 +1,23 @@
import { NextApiRequest, NextApiResponse, NextApiHandler } from 'next';
import jwt from 'jsonwebtoken';
import { SECRET_KEY } from '../config/config';
const authMiddleware = (handler: NextApiHandler) => {
return async (req: NextApiRequest, res: NextApiResponse) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const decoded = jwt.verify(token, SECRET_KEY);
(req as any).user = decoded;
return handler(req, res);
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
};
};
export default authMiddleware;

View File

@ -0,0 +1,31 @@
import axios from 'axios';
// Axios Interceptor Instance
const AxiosInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_BASE_URL || 'http://10.0.0.123:3000', // Default to localhost if NEXT_PUBLIC_BASE_URL is not set
timeout: 1000,
headers: { 'Content-Type': 'application/json' },
});
// You can add interceptors here if needed
AxiosInstance.interceptors.request.use(
(config) => {
// Modify request config here if needed
return config;
},
(error) => {
return Promise.reject(error);
}
);
AxiosInstance.interceptors.response.use(
(response) => {
// Modify response data here if needed
return response;
},
(error) => {
return Promise.reject(error);
}
);
export default AxiosInstance;

6
src/utils/jwtUtils.ts Normal file
View File

@ -0,0 +1,6 @@
import jwt from 'jsonwebtoken';
import { SECRET_KEY } from '../config/config';
export const generateToken = (userId: number) => {
return jwt.sign({ userId }, SECRET_KEY, { expiresIn: '1h' });
};