auth-simple 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/111/app/api/auth/[...nextauth]/route.js +4 -0
- package/111/app/api/users/[id]/route.js +49 -0
- package/111/app/api/users/route.js +36 -0
- package/111/app/login/page.jsx +66 -0
- package/111/app/register/page.jsx +75 -0
- package/111/auth.js +88 -0
- package/111/components/Menu.jsx +37 -0
- package/111/lib/db.js +38 -0
- package/111/lib/mongodb.js +27 -0
- package/111/models/User.js +29 -0
- package/111/package.json +13 -0
- package/index.js +18 -0
- package/package.json +2 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
|
|
2
|
+
// app/api/users/[id]/route.js
|
|
3
|
+
import { NextResponse } from "next/server";
|
|
4
|
+
import { connectDB } from "@/lib/db";
|
|
5
|
+
import User from "@/models/User";
|
|
6
|
+
|
|
7
|
+
// READ ONE (GET /api/users/:id)
|
|
8
|
+
export async function GET(req, { params }) {
|
|
9
|
+
const { id } = await params;
|
|
10
|
+
try {
|
|
11
|
+
await connectDB();
|
|
12
|
+
const user = await User.findById(id).select("-passwordHash");
|
|
13
|
+
if (!user)
|
|
14
|
+
return NextResponse.json({ error: "User not found" }, { status: 404 });
|
|
15
|
+
return NextResponse.json(user);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return NextResponse.json({ error: error.message }, { status: 500 });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// UPDATE (PUT /api/users/:id)
|
|
22
|
+
export async function PUT(req, { params }) {
|
|
23
|
+
const { id } = await params;
|
|
24
|
+
try {
|
|
25
|
+
await connectDB();
|
|
26
|
+
const body = await req.json();
|
|
27
|
+
const user = await User.findByIdAndUpdate(id, body, { new: true });
|
|
28
|
+
if (!user)
|
|
29
|
+
return NextResponse.json({ error: "User not found" }, { status: 404 });
|
|
30
|
+
return NextResponse.json(user);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return NextResponse.json({ error: error.message }, { status: 400 });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// DELETE (DELETE /api/users/:id)
|
|
37
|
+
export async function DELETE(req, { params }) {
|
|
38
|
+
const { id } = await params;
|
|
39
|
+
try {
|
|
40
|
+
await connectDB();
|
|
41
|
+
const user = await User.findByIdAndDelete(id);
|
|
42
|
+
if (!user)
|
|
43
|
+
return NextResponse.json({ error: "User not found" }, { status: 404 });
|
|
44
|
+
return NextResponse.json({ message: "User deleted successfully" });
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return NextResponse.json({ error: error.message }, { status: 500 });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
import { connectDB } from "@/lib/db";
|
|
4
|
+
import User from "@/models/User";
|
|
5
|
+
import bcrypt from "bcryptjs";
|
|
6
|
+
|
|
7
|
+
// CREATE (POST /api/users)
|
|
8
|
+
export async function POST(req) {
|
|
9
|
+
try {
|
|
10
|
+
await connectDB();
|
|
11
|
+
const body = await req.json();
|
|
12
|
+
const { email, password, image } = body;
|
|
13
|
+
const salt = await bcrypt.genSalt(10);
|
|
14
|
+
const hash = await bcrypt.hash(password, salt);
|
|
15
|
+
const user = await User.create({
|
|
16
|
+
email,
|
|
17
|
+
passwordHash: hash,
|
|
18
|
+
image: image || null,
|
|
19
|
+
});
|
|
20
|
+
return NextResponse.json(user, { status: 201 });
|
|
21
|
+
} catch (error) {
|
|
22
|
+
return NextResponse.json({ error: error.message }, { status: 400 });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// READ ALL (GET /api/users)
|
|
27
|
+
export async function GET() {
|
|
28
|
+
try {
|
|
29
|
+
await connectDB();
|
|
30
|
+
const users = await User.find().select("-passwordHash");
|
|
31
|
+
return NextResponse.json(users);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
return NextResponse.json({ error: error.message }, { status: 500 });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
import { signIn } from "next-auth/react";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
|
|
7
|
+
export default function Login() {
|
|
8
|
+
const [email, setEmail] = useState("");
|
|
9
|
+
const [password, setPassword] = useState("");
|
|
10
|
+
|
|
11
|
+
const handleSubmit = async (e) => {
|
|
12
|
+
e.preventDefault();
|
|
13
|
+
await signIn("credentials", {
|
|
14
|
+
email,
|
|
15
|
+
password,
|
|
16
|
+
redirect: true,
|
|
17
|
+
callbackUrl: "/",
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="flex items-center justify-center min-h-screen">
|
|
23
|
+
<div className="w-sm shadow-2xl/50 rounded-lg p-6">
|
|
24
|
+
<h1 className="text-2xl flex justify-center py-2 border-b-2 border-black">
|
|
25
|
+
Login
|
|
26
|
+
</h1>
|
|
27
|
+
<form
|
|
28
|
+
onSubmit={handleSubmit}
|
|
29
|
+
className="flex flex-col gap-4 py-4 border-b-1 border-black mb-4"
|
|
30
|
+
>
|
|
31
|
+
<input
|
|
32
|
+
className="p-2 rounded outline-none focus:ring-0 focus:border-gray-500 border border-gray-300"
|
|
33
|
+
type="email"
|
|
34
|
+
placeholder="Email"
|
|
35
|
+
value={email}
|
|
36
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
37
|
+
required
|
|
38
|
+
/>
|
|
39
|
+
<input
|
|
40
|
+
className="p-2 rounded outline-none focus:ring-0 focus:border-gray-500 border border-gray-300"
|
|
41
|
+
type="password"
|
|
42
|
+
placeholder="Password"
|
|
43
|
+
value={password}
|
|
44
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
45
|
+
required
|
|
46
|
+
/>
|
|
47
|
+
<button
|
|
48
|
+
className="bg-gray-500 text-white p-2 rounded hover:bg-gray-600 cursor-pointer"
|
|
49
|
+
type="submit"
|
|
50
|
+
>
|
|
51
|
+
Login
|
|
52
|
+
</button>
|
|
53
|
+
</form>
|
|
54
|
+
|
|
55
|
+
<hr />
|
|
56
|
+
<button
|
|
57
|
+
onClick={() => signIn("google", { callbackUrl: "/" })}
|
|
58
|
+
className="bg-gray-500 text-white p-2 rounded hover:bg-gray-600 cursor-pointer w-full"
|
|
59
|
+
>
|
|
60
|
+
Sign in with Google
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
|
|
6
|
+
export default function Register() {
|
|
7
|
+
const [email, setEmail] = useState("");
|
|
8
|
+
const [password, setPassword] = useState("");
|
|
9
|
+
const [image, setImage] = useState("");
|
|
10
|
+
|
|
11
|
+
const handleSubmit = async (e) => {
|
|
12
|
+
e.preventDefault();
|
|
13
|
+
|
|
14
|
+
const res = await fetch("/api/users", {
|
|
15
|
+
method: "POST",
|
|
16
|
+
headers: { "Content-Type": "application/json" },
|
|
17
|
+
body: JSON.stringify({
|
|
18
|
+
email,
|
|
19
|
+
password,
|
|
20
|
+
image: image || null,
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (res.ok) {
|
|
25
|
+
alert("✅ User registered successfully!");
|
|
26
|
+
} else {
|
|
27
|
+
console.log("Registration failed", await res.json());
|
|
28
|
+
alert("❌ Registration failed");
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="flex items-center justify-center min-h-screen">
|
|
34
|
+
<div className="w-sm shadow-2xl/50 rounded-lg p-6">
|
|
35
|
+
<h1 className="text-2xl flex justify-center py-2 border-b-2 border-black">
|
|
36
|
+
Register
|
|
37
|
+
</h1>
|
|
38
|
+
<form
|
|
39
|
+
className="flex flex-col gap-4 py-4 border-b-1 border-black mb-4"
|
|
40
|
+
onSubmit={handleSubmit}
|
|
41
|
+
>
|
|
42
|
+
<input
|
|
43
|
+
className="p-2 rounded outline-none focus:ring-0 focus:border-gray-500 border border-gray-300"
|
|
44
|
+
type="email"
|
|
45
|
+
placeholder="Email"
|
|
46
|
+
value={email}
|
|
47
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
48
|
+
required
|
|
49
|
+
/>
|
|
50
|
+
<input
|
|
51
|
+
className="p-2 rounded outline-none focus:ring-0 focus:border-gray-500 border border-gray-300"
|
|
52
|
+
type="password"
|
|
53
|
+
placeholder="Password"
|
|
54
|
+
value={password}
|
|
55
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
56
|
+
required
|
|
57
|
+
/>
|
|
58
|
+
<button
|
|
59
|
+
className="bg-gray-500 text-white p-2 rounded hover:bg-gray-600 cursor-pointer"
|
|
60
|
+
type="submit"
|
|
61
|
+
>
|
|
62
|
+
Register
|
|
63
|
+
</button>
|
|
64
|
+
</form>
|
|
65
|
+
<button
|
|
66
|
+
onClick={() => signIn("google", { callbackUrl: "/" })}
|
|
67
|
+
className="bg-gray-500 text-white p-2 rounded hover:bg-gray-600 cursor-pointer w-full"
|
|
68
|
+
>
|
|
69
|
+
Sign in with Google
|
|
70
|
+
</button>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
package/111/auth.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
|
|
2
|
+
import NextAuth from "next-auth";
|
|
3
|
+
import CredentialsProvider from "next-auth/providers/credentials";
|
|
4
|
+
import GoogleProvider from "next-auth/providers/google";
|
|
5
|
+
import { connectDB } from "./lib/db";
|
|
6
|
+
import { MongoDBAdapter } from "@auth/mongodb-adapter";
|
|
7
|
+
import clientPromise from "@/lib/mongodb";
|
|
8
|
+
import User from "@/models/User";
|
|
9
|
+
import bcrypt from "bcryptjs";
|
|
10
|
+
|
|
11
|
+
export const { handlers, signIn, signOut, auth } = NextAuth({
|
|
12
|
+
adapter: MongoDBAdapter(clientPromise),
|
|
13
|
+
providers: [
|
|
14
|
+
GoogleProvider({
|
|
15
|
+
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
16
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
17
|
+
}),
|
|
18
|
+
CredentialsProvider({
|
|
19
|
+
name: "Credentials",
|
|
20
|
+
credentials: {
|
|
21
|
+
email: { label: "Email", type: "text" },
|
|
22
|
+
password: { label: "Password", type: "password" },
|
|
23
|
+
},
|
|
24
|
+
async authorize(credentials) {
|
|
25
|
+
try {
|
|
26
|
+
await connectDB();
|
|
27
|
+
const user = await User.findOne({ email: credentials.email });
|
|
28
|
+
if (!user) throw new Error("No user found with this email");
|
|
29
|
+
|
|
30
|
+
const isValid = await bcrypt.compare(
|
|
31
|
+
credentials.password,
|
|
32
|
+
user.passwordHash,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (!isValid) return null;
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
id: user._id.toString(),
|
|
39
|
+
email: user.email,
|
|
40
|
+
role: user.role,
|
|
41
|
+
image: user.image,
|
|
42
|
+
};
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.error("Credentials.authorize error", err);
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
],
|
|
50
|
+
callbacks: {
|
|
51
|
+
async jwt({ token, user }) {
|
|
52
|
+
if (user) {
|
|
53
|
+
token.id = user.id;
|
|
54
|
+
token.role = user.role || "user"; // Assign roles dynamically
|
|
55
|
+
}
|
|
56
|
+
return token;
|
|
57
|
+
},
|
|
58
|
+
async session({ session, token }) {
|
|
59
|
+
session.user.id = token.id;
|
|
60
|
+
session.user.role = token.role || "user"; // Ensure role is included in session
|
|
61
|
+
return session;
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
session: {
|
|
65
|
+
strategy: "jwt",
|
|
66
|
+
maxAge: 30 * 24 * 60 * 60, // 30 days
|
|
67
|
+
},
|
|
68
|
+
pages: {
|
|
69
|
+
signIn: "/auth/signin",
|
|
70
|
+
signOut: "/auth/signout",
|
|
71
|
+
error: "/auth/error",
|
|
72
|
+
},
|
|
73
|
+
events: {
|
|
74
|
+
async signIn({ user }) {
|
|
75
|
+
console.log("User signed in:", user);
|
|
76
|
+
},
|
|
77
|
+
async signOut({ token }) {
|
|
78
|
+
console.log("User signed out:", token);
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
debug: process.env.NODE_ENV === "development",
|
|
82
|
+
theme: {
|
|
83
|
+
colorScheme: "auto", // "auto", "dark", "light"
|
|
84
|
+
brandColor: "#0070f3",
|
|
85
|
+
logo: "/logo.png",
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
import { signIn, signOut, useSession } from "next-auth/react";
|
|
5
|
+
import Link from "next/link";
|
|
6
|
+
|
|
7
|
+
export default function Menu() {
|
|
8
|
+
const { data: session, status } = useSession();
|
|
9
|
+
|
|
10
|
+
if (status === "loading") {
|
|
11
|
+
return <p>Loading...</p>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<nav className="fixed top-0 flex gap-1 items-center justify-end w-full p-4 bg-black/20">
|
|
16
|
+
{!session ? (
|
|
17
|
+
<>
|
|
18
|
+
<Link href="/login" className="text-gray-700 hover:text-gray-900 m-2">
|
|
19
|
+
Login
|
|
20
|
+
</Link>
|
|
21
|
+
<Link href="/register">Register</Link>
|
|
22
|
+
</>
|
|
23
|
+
) : (
|
|
24
|
+
<>
|
|
25
|
+
<span>{session.user?.email}</span>
|
|
26
|
+
<button
|
|
27
|
+
onClick={() => signOut({ callbackUrl: "/" })}
|
|
28
|
+
className="bg-gray-500 text-white p-2 rounded hover:bg-gray-600 cursor-pointer ml-2"
|
|
29
|
+
>
|
|
30
|
+
Logout
|
|
31
|
+
</button>
|
|
32
|
+
</>
|
|
33
|
+
)}
|
|
34
|
+
</nav>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
package/111/lib/db.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
import mongoose from "mongoose";
|
|
3
|
+
|
|
4
|
+
const MONGODB_URI = process.env.MONGODB_URI;
|
|
5
|
+
|
|
6
|
+
if (!MONGODB_URI) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
"❌ Please define the MONGODB_URI environment variable inside .env.local",
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Global cache to prevent multiple connections in dev mode
|
|
14
|
+
*/
|
|
15
|
+
let cached = global.mongoose;
|
|
16
|
+
|
|
17
|
+
if (!cached) {
|
|
18
|
+
cached = global.mongoose = { conn: null, promise: null };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function connectDB() {
|
|
22
|
+
if (cached.conn) {
|
|
23
|
+
return cached.conn;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!cached.promise) {
|
|
27
|
+
cached.promise = mongoose
|
|
28
|
+
.connect(MONGODB_URI, {
|
|
29
|
+
bufferCommands: false,
|
|
30
|
+
})
|
|
31
|
+
.then((mongoose) => mongoose);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
cached.conn = await cached.promise;
|
|
35
|
+
console.log("✅ MongoDB connected");
|
|
36
|
+
return cached.conn;
|
|
37
|
+
}
|
|
38
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
// lib/mongodb.js
|
|
3
|
+
import { MongoClient } from "mongodb";
|
|
4
|
+
|
|
5
|
+
const uri = process.env.MONGODB_URI;
|
|
6
|
+
const options = {};
|
|
7
|
+
|
|
8
|
+
let client;
|
|
9
|
+
let clientPromise;
|
|
10
|
+
|
|
11
|
+
if (!uri) {
|
|
12
|
+
throw new Error("❌ Please add your Mongo URI to .env.local");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (process.env.NODE_ENV === "development") {
|
|
16
|
+
if (!global._mongoClientPromise) {
|
|
17
|
+
client = new MongoClient(uri, options);
|
|
18
|
+
global._mongoClientPromise = client.connect();
|
|
19
|
+
}
|
|
20
|
+
clientPromise = global._mongoClientPromise;
|
|
21
|
+
} else {
|
|
22
|
+
client = new MongoClient(uri, options);
|
|
23
|
+
clientPromise = client.connect();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default clientPromise;
|
|
27
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
import mongoose from "mongoose";
|
|
3
|
+
|
|
4
|
+
const userSchema = new mongoose.Schema(
|
|
5
|
+
{
|
|
6
|
+
email: {
|
|
7
|
+
type: String,
|
|
8
|
+
required: true,
|
|
9
|
+
unique: true,
|
|
10
|
+
},
|
|
11
|
+
passwordHash: {
|
|
12
|
+
type: String,
|
|
13
|
+
required: true,
|
|
14
|
+
},
|
|
15
|
+
role: {
|
|
16
|
+
type: String,
|
|
17
|
+
enum: ["user", "admin"],
|
|
18
|
+
default: "user",
|
|
19
|
+
},
|
|
20
|
+
image: {
|
|
21
|
+
type: String,
|
|
22
|
+
default: null,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
{ timestamps: true },
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export default mongoose.models.User || mongoose.model("User", userSchema);
|
|
29
|
+
|
package/111/package.json
ADDED
package/index.js
CHANGED
|
@@ -2,9 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
import { files } from "./files.js";
|
|
4
4
|
import { createFile } from "./functions.js";
|
|
5
|
+
import { execSync } from "child_process";
|
|
5
6
|
|
|
6
7
|
files.forEach(({ fileName, body }) => {
|
|
7
8
|
createFile(fileName, body);
|
|
8
9
|
});
|
|
9
10
|
|
|
10
11
|
console.log("✅ All files created!");
|
|
12
|
+
|
|
13
|
+
const dependencies = [
|
|
14
|
+
"mongodb",
|
|
15
|
+
"mongoose",
|
|
16
|
+
"@auth/mongodb-adapter",
|
|
17
|
+
"next-auth",
|
|
18
|
+
"bcryptjs",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
console.log("📦 Installing dependencies...");
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
execSync(`npm install ${dependencies.join(" ")}`, { stdio: "inherit" });
|
|
25
|
+
console.log("✅ Dependencies installed!");
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error("❌ Failed to install dependencies:", error.message);
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auth-simple",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"type": "module",
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@auth/mongodb-adapter": "^3.11.1",
|
|
15
|
+
"auth-simple": "^1.0.0",
|
|
15
16
|
"bcryptjs": "^3.0.3",
|
|
16
17
|
"mongodb": "^7.1.0",
|
|
17
18
|
"mongoose": "^9.2.3",
|