now working login and register
parent
bf940e80fb
commit
f633208d8e
3
bun.lock
3
bun.lock
|
@ -21,6 +21,7 @@
|
|||
"clsx": "^2.1.1",
|
||||
"cmdk": "1.0.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"lucide-react": "^0.475.0",
|
||||
"mongoose": "^8.12.2",
|
||||
"next": "^15.2.0",
|
||||
|
@ -471,6 +472,8 @@
|
|||
|
||||
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
|
||||
|
||||
"dotenv": ["dotenv@16.4.7", "", {}, "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
publish = ".next"
|
||||
|
||||
[build.environment]
|
||||
NETLIFY_NEXT_PLUGIN_SKIP = "true"
|
||||
NEXT_PUBLIC_BASE_URL = "/"
|
||||
NODE_VERSION = "18.17.0"
|
||||
|
||||
[[plugins]]
|
||||
package = "@netlify/plugin-nextjs"
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
"clsx": "^2.1.1",
|
||||
"cmdk": "1.0.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"lucide-react": "^0.475.0",
|
||||
"mongoose": "^8.12.2",
|
||||
"next": "^15.2.0",
|
||||
|
@ -3862,6 +3863,18 @@
|
|||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.4.7",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
||||
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
"clsx": "^2.1.1",
|
||||
"cmdk": "1.0.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"lucide-react": "^0.475.0",
|
||||
"mongoose": "^8.12.2",
|
||||
"next": "^15.2.0",
|
||||
|
|
|
@ -10,7 +10,7 @@ const DEMO_USERS = [
|
|||
id: "1",
|
||||
name: "Admin User",
|
||||
email: "admin@example.com",
|
||||
password: "$2a$12$lJJMfNCHLTw2S6AC.VWGRO0CVPz6jDk6kv9BvDBvJpBCzt6GGvhwu", // password123
|
||||
password: "password123", // Plain text for demo
|
||||
role: "admin",
|
||||
isActive: true
|
||||
},
|
||||
|
@ -18,7 +18,7 @@ const DEMO_USERS = [
|
|||
id: "2",
|
||||
name: "HR User",
|
||||
email: "hr@example.com",
|
||||
password: "$2a$12$lJJMfNCHLTw2S6AC.VWGRO0CVPz6jDk6kv9BvDBvJpBCzt6GGvhwu", // password123
|
||||
password: "password123", // Plain text for demo
|
||||
role: "hr",
|
||||
isActive: true
|
||||
},
|
||||
|
@ -26,7 +26,7 @@ const DEMO_USERS = [
|
|||
id: "3",
|
||||
name: "Inactive HR User",
|
||||
email: "inactive@example.com",
|
||||
password: "$2a$12$lJJMfNCHLTw2S6AC.VWGRO0CVPz6jDk6kv9BvDBvJpBCzt6GGvhwu", // password123
|
||||
password: "password123", // Plain text for demo
|
||||
role: "hr",
|
||||
isActive: false
|
||||
}
|
||||
|
@ -42,71 +42,89 @@ export const authOptions: AuthOptions = {
|
|||
},
|
||||
async authorize(credentials) {
|
||||
if (!credentials?.email || !credentials?.password) {
|
||||
console.log("Missing credentials");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// First try with MongoDB
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const user = await User.findOne({ email: credentials.email });
|
||||
if (user) {
|
||||
// Check if user is active
|
||||
if (!user.isActive) {
|
||||
throw new Error("Account is inactive");
|
||||
}
|
||||
console.log(`Attempting login for: ${credentials.email}`);
|
||||
|
||||
const isPasswordValid = await bcrypt.compare(
|
||||
credentials.password,
|
||||
user.password
|
||||
);
|
||||
|
||||
if (isPasswordValid) {
|
||||
return {
|
||||
id: user._id.toString(),
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
isActive: user.isActive
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("MongoDB connection error, falling back to demo users");
|
||||
}
|
||||
|
||||
// Fallback to demo users if MongoDB fails
|
||||
// First check demo users directly with plain text comparison
|
||||
const demoUser = DEMO_USERS.find(
|
||||
(user) => user.email === credentials.email
|
||||
);
|
||||
|
||||
if (!demoUser) {
|
||||
return null;
|
||||
if (demoUser) {
|
||||
console.log("Found demo user");
|
||||
|
||||
// Check if demo user is active
|
||||
if (!demoUser.isActive) {
|
||||
console.log("Demo user found but inactive");
|
||||
throw new Error("Account is inactive");
|
||||
}
|
||||
|
||||
// Direct comparison for demo users
|
||||
if (credentials.password === demoUser.password) {
|
||||
console.log("Demo user password matched");
|
||||
return {
|
||||
id: demoUser.id,
|
||||
email: demoUser.email,
|
||||
name: demoUser.name,
|
||||
role: demoUser.role,
|
||||
isActive: demoUser.isActive
|
||||
};
|
||||
} else {
|
||||
console.log("Invalid password for demo user");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if user is active
|
||||
if (!demoUser.isActive) {
|
||||
throw new Error("Account is inactive");
|
||||
// If no demo user found, try with MongoDB
|
||||
let dbUser = null;
|
||||
|
||||
try {
|
||||
await connectToDatabase();
|
||||
dbUser = await User.findOne({ email: credentials.email });
|
||||
console.log(`MongoDB connected, user found:`, !!dbUser);
|
||||
} catch (dbError) {
|
||||
console.error("MongoDB connection error:", dbError);
|
||||
return null; // If no demo user and MongoDB fails, authentication fails
|
||||
}
|
||||
|
||||
const isPasswordValid = await bcrypt.compare(
|
||||
credentials.password,
|
||||
demoUser.password
|
||||
);
|
||||
// If MongoDB user exists
|
||||
if (dbUser) {
|
||||
// Check if user is active
|
||||
if (!dbUser.isActive) {
|
||||
console.log("MongoDB user found but inactive");
|
||||
throw new Error("Account is inactive");
|
||||
}
|
||||
|
||||
if (!isPasswordValid) {
|
||||
return null;
|
||||
const isPasswordValid = await bcrypt.compare(
|
||||
credentials.password,
|
||||
dbUser.password
|
||||
);
|
||||
|
||||
console.log(`Password validation for MongoDB user: ${isPasswordValid}`);
|
||||
|
||||
if (isPasswordValid) {
|
||||
return {
|
||||
id: dbUser._id.toString(),
|
||||
email: dbUser.email,
|
||||
name: dbUser.name,
|
||||
role: dbUser.role,
|
||||
isActive: dbUser.isActive
|
||||
};
|
||||
} else {
|
||||
console.log("Invalid password for MongoDB user");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: demoUser.id,
|
||||
email: demoUser.email,
|
||||
name: demoUser.name,
|
||||
role: demoUser.role,
|
||||
isActive: demoUser.isActive
|
||||
};
|
||||
// No user found in demo or database
|
||||
console.log("User not found in demo users or database");
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error("Auth error:", error);
|
||||
console.error("Authentication error:", error);
|
||||
throw error; // Propagate errors like 'Account is inactive'
|
||||
}
|
||||
},
|
||||
|
@ -138,7 +156,8 @@ export const authOptions: AuthOptions = {
|
|||
strategy: "jwt",
|
||||
maxAge: 24 * 60 * 60, // 24 hours
|
||||
},
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
secret: process.env.NEXTAUTH_SECRET || "a-default-secret-for-development-only",
|
||||
debug: process.env.NODE_ENV !== "production",
|
||||
};
|
||||
|
||||
const handler = NextAuth(authOptions);
|
||||
|
|
|
@ -19,13 +19,13 @@ export async function POST(request: NextRequest) {
|
|||
}
|
||||
|
||||
// Hash the password
|
||||
const hashedPassword = await bcrypt.hash(password, 12);
|
||||
// const hashedPassword = await bcrypt.hash(password, 12);
|
||||
|
||||
// Create the user
|
||||
const user = await User.create({
|
||||
name,
|
||||
email,
|
||||
password: hashedPassword,
|
||||
password,
|
||||
role,
|
||||
});
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import { signIn } from "next-auth/react";
|
|||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form";
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { toast } from "sonner";
|
||||
|
@ -25,6 +25,7 @@ export default function LoginPage() {
|
|||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [loginError, setLoginError] = useState<string | null>(null);
|
||||
|
||||
// Form definition
|
||||
const form = useForm<LoginFormValues>({
|
||||
|
@ -38,6 +39,7 @@ export default function LoginPage() {
|
|||
// Handle form submission
|
||||
const onSubmit = async (data: LoginFormValues) => {
|
||||
setIsLoading(true);
|
||||
setLoginError(null);
|
||||
|
||||
try {
|
||||
const result = await signIn("credentials", {
|
||||
|
@ -45,11 +47,13 @@ export default function LoginPage() {
|
|||
password: data.password,
|
||||
redirect: false,
|
||||
});
|
||||
console.log('result', result);
|
||||
|
||||
if (result?.error) {
|
||||
if (result.error === "Account is inactive") {
|
||||
setLoginError("This account has been disabled by an administrator");
|
||||
toast.error("This account has been disabled by an administrator");
|
||||
} else {
|
||||
setLoginError("Invalid email or password");
|
||||
toast.error("Invalid email or password");
|
||||
}
|
||||
return;
|
||||
|
@ -60,6 +64,7 @@ console.log('result', result);
|
|||
router.refresh();
|
||||
} catch (error) {
|
||||
console.error("Login error:", error);
|
||||
setLoginError("An error occurred during login");
|
||||
toast.error("An error occurred during login");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
@ -70,12 +75,13 @@ console.log('result', result);
|
|||
useEffect(() => {
|
||||
const error = searchParams.get("error");
|
||||
if (error) {
|
||||
setLoginError(decodeURIComponent(error));
|
||||
toast.error(decodeURIComponent(error));
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-slate-800 px-4 py-12">
|
||||
<div className="flex min-h-screen items-center justify-center bg-[#1a2840] px-4 py-12">
|
||||
<div className="w-full max-w-md space-y-6">
|
||||
<div className="text-center space-y-2">
|
||||
<h1 className="text-2xl font-bold tracking-tight text-white">
|
||||
|
@ -86,8 +92,8 @@ console.log('result', result);
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-900 rounded-xl p-6 shadow-lg">
|
||||
<Alert variant="info" className="mb-6 bg-slate-800 border-blue-500">
|
||||
<div className="bg-[#192133] rounded-xl p-6 shadow-lg border border-slate-800">
|
||||
<Alert variant="info" className="mb-6 bg-[#192133] border-blue-500/30">
|
||||
<Info className="h-4 w-4 text-blue-500" />
|
||||
<AlertTitle className="text-blue-500">Demo Account</AlertTitle>
|
||||
<AlertDescription className="text-slate-300">
|
||||
|
@ -96,6 +102,16 @@ console.log('result', result);
|
|||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
{loginError && (
|
||||
<Alert variant="destructive" className="mb-4 bg-red-900/30 border-red-500/50">
|
||||
<AlertCircle className="h-4 w-4 text-red-500" />
|
||||
<AlertTitle className="text-red-400">Login Failed</AlertTitle>
|
||||
<AlertDescription className="text-slate-300">
|
||||
{loginError}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<FormField
|
||||
|
@ -125,7 +141,7 @@ console.log('result', result);
|
|||
<FormLabel className="text-slate-300">Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="text"
|
||||
type="password"
|
||||
placeholder="••••••••"
|
||||
autoComplete="current-password"
|
||||
{...field}
|
||||
|
|
|
@ -5,7 +5,7 @@ export interface IUser extends Document {
|
|||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
role: 'admin' | 'hr';
|
||||
role: 'admin' | 'hr' | 'user';
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
|
@ -31,7 +31,7 @@ const UserSchema = new Schema<IUser>({
|
|||
},
|
||||
role: {
|
||||
type: String,
|
||||
enum: ['admin', 'hr'],
|
||||
enum: ['admin', 'hr', 'user'],
|
||||
default: 'hr',
|
||||
},
|
||||
isActive: {
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
// Test database connection
|
||||
require('dotenv').config({ path: '.env.local' });
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
async function testDbConnection() {
|
||||
try {
|
||||
const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/candidate_filter_portal';
|
||||
console.log('Connecting to:', mongoUri);
|
||||
|
||||
await mongoose.connect(mongoUri, {
|
||||
serverSelectionTimeoutMS: 5000
|
||||
});
|
||||
|
||||
console.log('MongoDB connected successfully!');
|
||||
|
||||
// Close the connection
|
||||
await mongoose.disconnect();
|
||||
console.log('MongoDB disconnected');
|
||||
} catch (error) {
|
||||
console.error('MongoDB connection error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
testDbConnection();
|
|
@ -0,0 +1,36 @@
|
|||
// Test login flow script
|
||||
const credentials = {
|
||||
admin: {
|
||||
email: "admin@example.com",
|
||||
password: "password123"
|
||||
},
|
||||
hr: {
|
||||
email: "hr@example.com",
|
||||
password: "password123"
|
||||
}
|
||||
};
|
||||
|
||||
// Simulate login with demo users
|
||||
function testLoginFlow() {
|
||||
console.log("Testing login flow with demo credentials");
|
||||
|
||||
// Admin user test
|
||||
console.log("\nTesting admin login:");
|
||||
console.log("Email:", credentials.admin.email);
|
||||
console.log("Password:", credentials.admin.password);
|
||||
console.log("Expected result: Success - Should authenticate and return admin role");
|
||||
|
||||
// HR user test
|
||||
console.log("\nTesting HR login:");
|
||||
console.log("Email:", credentials.hr.email);
|
||||
console.log("Password:", credentials.hr.password);
|
||||
console.log("Expected result: Success - Should authenticate and return hr role");
|
||||
|
||||
console.log("\nTo test this login:");
|
||||
console.log("1. Go to the login page");
|
||||
console.log("2. Enter the credentials above");
|
||||
console.log("3. Click 'Sign In'");
|
||||
console.log("4. You should be redirected to the dashboard");
|
||||
}
|
||||
|
||||
testLoginFlow();
|
|
@ -0,0 +1,17 @@
|
|||
// Test login script
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
// Test password hashing to verify what's stored in the demo users
|
||||
async function testPassword() {
|
||||
// Hash password
|
||||
const password = 'password123';
|
||||
const hashedPassword = await bcrypt.hash(password, 12);
|
||||
console.log('Hashed password:', hashedPassword);
|
||||
|
||||
// Compare with stored hash
|
||||
const storedHash = '$2b$12$CCIPxtBFUjfJqz8sMvJdj.9MQLE6ghAoIl/amO9uNaRc6VXcWUDiW';
|
||||
const isMatch = await bcrypt.compare(password, storedHash);
|
||||
console.log('Password matches:', isMatch);
|
||||
}
|
||||
|
||||
testPassword();
|
Loading…
Reference in New Issue