auth-simple 1.0.0

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.
Files changed (4) hide show
  1. package/files.js +481 -0
  2. package/functions.js +19 -0
  3. package/index.js +10 -0
  4. package/package.json +20 -0
package/files.js ADDED
@@ -0,0 +1,481 @@
1
+ export const files = [
2
+ {
3
+ fileName: "/app/api/auth/[...nextauth]/route.js",
4
+ body: `
5
+ import { handlers } from "@/auth";
6
+ export const { GET, POST } = handlers;
7
+ `,
8
+ },
9
+ {
10
+ fileName: "/app/api/users/route.js",
11
+ body: `
12
+ import { NextResponse } from "next/server";
13
+ import { connectDB } from "@/lib/db";
14
+ import User from "@/models/User";
15
+ import bcrypt from "bcryptjs";
16
+
17
+ // CREATE (POST /api/users)
18
+ export async function POST(req) {
19
+ try {
20
+ await connectDB();
21
+ const body = await req.json();
22
+ const { email, password, image } = body;
23
+ const salt = await bcrypt.genSalt(10);
24
+ const hash = await bcrypt.hash(password, salt);
25
+ const user = await User.create({
26
+ email,
27
+ passwordHash: hash,
28
+ image: image || null,
29
+ });
30
+ return NextResponse.json(user, { status: 201 });
31
+ } catch (error) {
32
+ return NextResponse.json({ error: error.message }, { status: 400 });
33
+ }
34
+ }
35
+
36
+ // READ ALL (GET /api/users)
37
+ export async function GET() {
38
+ try {
39
+ await connectDB();
40
+ const users = await User.find().select("-passwordHash");
41
+ return NextResponse.json(users);
42
+ } catch (error) {
43
+ return NextResponse.json({ error: error.message }, { status: 500 });
44
+ }
45
+ }
46
+ `,
47
+ },
48
+ {
49
+ fileName: "/app/api/users/[id]/route.js",
50
+ body: `
51
+ // app/api/users/[id]/route.js
52
+ import { NextResponse } from "next/server";
53
+ import { connectDB } from "@/lib/db";
54
+ import User from "@/models/User";
55
+
56
+ // READ ONE (GET /api/users/:id)
57
+ export async function GET(req, { params }) {
58
+ const { id } = await params;
59
+ try {
60
+ await connectDB();
61
+ const user = await User.findById(id).select("-passwordHash");
62
+ if (!user)
63
+ return NextResponse.json({ error: "User not found" }, { status: 404 });
64
+ return NextResponse.json(user);
65
+ } catch (error) {
66
+ return NextResponse.json({ error: error.message }, { status: 500 });
67
+ }
68
+ }
69
+
70
+ // UPDATE (PUT /api/users/:id)
71
+ export async function PUT(req, { params }) {
72
+ const { id } = await params;
73
+ try {
74
+ await connectDB();
75
+ const body = await req.json();
76
+ const user = await User.findByIdAndUpdate(id, body, { new: true });
77
+ if (!user)
78
+ return NextResponse.json({ error: "User not found" }, { status: 404 });
79
+ return NextResponse.json(user);
80
+ } catch (error) {
81
+ return NextResponse.json({ error: error.message }, { status: 400 });
82
+ }
83
+ }
84
+
85
+ // DELETE (DELETE /api/users/:id)
86
+ export async function DELETE(req, { params }) {
87
+ const { id } = await params;
88
+ try {
89
+ await connectDB();
90
+ const user = await User.findByIdAndDelete(id);
91
+ if (!user)
92
+ return NextResponse.json({ error: "User not found" }, { status: 404 });
93
+ return NextResponse.json({ message: "User deleted successfully" });
94
+ } catch (error) {
95
+ return NextResponse.json({ error: error.message }, { status: 500 });
96
+ }
97
+ }
98
+ `,
99
+ },
100
+ {
101
+ fileName: "/app/login/page.jsx",
102
+ body: `
103
+ "use client";
104
+
105
+ import { signIn } from "next-auth/react";
106
+ import { useState } from "react";
107
+
108
+ export default function Login() {
109
+ const [email, setEmail] = useState("");
110
+ const [password, setPassword] = useState("");
111
+
112
+ const handleSubmit = async (e) => {
113
+ e.preventDefault();
114
+ await signIn("credentials", {
115
+ email,
116
+ password,
117
+ redirect: true,
118
+ callbackUrl: "/",
119
+ });
120
+ };
121
+
122
+ return (
123
+ <div className="flex items-center justify-center min-h-screen">
124
+ <div className="w-sm shadow-2xl/50 rounded-lg p-6">
125
+ <h1 className="text-2xl flex justify-center py-2 border-b-2 border-black">
126
+ Login
127
+ </h1>
128
+ <form
129
+ onSubmit={handleSubmit}
130
+ className="flex flex-col gap-4 py-4 border-b-1 border-black mb-4"
131
+ >
132
+ <input
133
+ className="p-2 rounded outline-none focus:ring-0 focus:border-gray-500 border border-gray-300"
134
+ type="email"
135
+ placeholder="Email"
136
+ value={email}
137
+ onChange={(e) => setEmail(e.target.value)}
138
+ required
139
+ />
140
+ <input
141
+ className="p-2 rounded outline-none focus:ring-0 focus:border-gray-500 border border-gray-300"
142
+ type="password"
143
+ placeholder="Password"
144
+ value={password}
145
+ onChange={(e) => setPassword(e.target.value)}
146
+ required
147
+ />
148
+ <button
149
+ className="bg-gray-500 text-white p-2 rounded hover:bg-gray-600 cursor-pointer"
150
+ type="submit"
151
+ >
152
+ Login
153
+ </button>
154
+ </form>
155
+
156
+ <hr />
157
+ <button
158
+ onClick={() => signIn("google", { callbackUrl: "/" })}
159
+ className="bg-gray-500 text-white p-2 rounded hover:bg-gray-600 cursor-pointer w-full"
160
+ >
161
+ Sign in with Google
162
+ </button>
163
+ </div>
164
+ </div>
165
+ );
166
+ }
167
+ `,
168
+ },
169
+ {
170
+ fileName: "/app/register/page.jsx",
171
+ body: `
172
+ "use client";
173
+
174
+ import { useState } from "react";
175
+
176
+ export default function Register() {
177
+ const [email, setEmail] = useState("");
178
+ const [password, setPassword] = useState("");
179
+ const [image, setImage] = useState("");
180
+
181
+ const handleSubmit = async (e) => {
182
+ e.preventDefault();
183
+
184
+ const res = await fetch("/api/users", {
185
+ method: "POST",
186
+ headers: { "Content-Type": "application/json" },
187
+ body: JSON.stringify({
188
+ email,
189
+ password,
190
+ image: image || null,
191
+ }),
192
+ });
193
+
194
+ if (res.ok) {
195
+ alert("✅ User registered successfully!");
196
+ } else {
197
+ console.log("Registration failed", await res.json());
198
+ alert("❌ Registration failed");
199
+ }
200
+ };
201
+
202
+ return (
203
+ <div className="flex items-center justify-center min-h-screen">
204
+ <div className="w-sm shadow-2xl/50 rounded-lg p-6">
205
+ <h1 className="text-2xl flex justify-center py-2 border-b-2 border-black">
206
+ Register
207
+ </h1>
208
+ <form
209
+ className="flex flex-col gap-4 py-4 border-b-1 border-black mb-4"
210
+ onSubmit={handleSubmit}
211
+ >
212
+ <input
213
+ className="p-2 rounded outline-none focus:ring-0 focus:border-gray-500 border border-gray-300"
214
+ type="email"
215
+ placeholder="Email"
216
+ value={email}
217
+ onChange={(e) => setEmail(e.target.value)}
218
+ required
219
+ />
220
+ <input
221
+ className="p-2 rounded outline-none focus:ring-0 focus:border-gray-500 border border-gray-300"
222
+ type="password"
223
+ placeholder="Password"
224
+ value={password}
225
+ onChange={(e) => setPassword(e.target.value)}
226
+ required
227
+ />
228
+ <button
229
+ className="bg-gray-500 text-white p-2 rounded hover:bg-gray-600 cursor-pointer"
230
+ type="submit"
231
+ >
232
+ Register
233
+ </button>
234
+ </form>
235
+ <button
236
+ onClick={() => signIn("google", { callbackUrl: "/" })}
237
+ className="bg-gray-500 text-white p-2 rounded hover:bg-gray-600 cursor-pointer w-full"
238
+ >
239
+ Sign in with Google
240
+ </button>
241
+ </div>
242
+ </div>
243
+ );
244
+ }
245
+ `,
246
+ },
247
+ {
248
+ fileName: "/components/Menu.jsx",
249
+ body: `
250
+ "use client";
251
+
252
+ import { signIn, signOut, useSession } from "next-auth/react";
253
+ import Link from "next/link";
254
+
255
+ export default function Menu() {
256
+ const { data: session, status } = useSession();
257
+
258
+ if (status === "loading") {
259
+ return <p>Loading...</p>;
260
+ }
261
+
262
+ return (
263
+ <nav className="fixed top-0 flex gap-1 items-center justify-end w-full p-4 bg-black/20">
264
+ {!session ? (
265
+ <>
266
+ <Link href="/login" className="text-gray-700 hover:text-gray-900 m-2">
267
+ Login
268
+ </Link>
269
+ <Link href="/register">Register</Link>
270
+ </>
271
+ ) : (
272
+ <>
273
+ <span>{session.user?.email}</span>
274
+ <button
275
+ onClick={() => signOut({ callbackUrl: "/" })}
276
+ className="bg-gray-500 text-white p-2 rounded hover:bg-gray-600 cursor-pointer ml-2"
277
+ >
278
+ Logout
279
+ </button>
280
+ </>
281
+ )}
282
+ </nav>
283
+ );
284
+ }
285
+ `,
286
+ },
287
+ {
288
+ fileName: "/lib/db.js",
289
+ body: `
290
+ import mongoose from "mongoose";
291
+
292
+ const MONGODB_URI = process.env.MONGODB_URI;
293
+
294
+ if (!MONGODB_URI) {
295
+ throw new Error(
296
+ "❌ Please define the MONGODB_URI environment variable inside .env.local",
297
+ );
298
+ }
299
+
300
+ /**
301
+ * Global cache to prevent multiple connections in dev mode
302
+ */
303
+ let cached = global.mongoose;
304
+
305
+ if (!cached) {
306
+ cached = global.mongoose = { conn: null, promise: null };
307
+ }
308
+
309
+ export async function connectDB() {
310
+ if (cached.conn) {
311
+ return cached.conn;
312
+ }
313
+
314
+ if (!cached.promise) {
315
+ cached.promise = mongoose
316
+ .connect(MONGODB_URI, {
317
+ bufferCommands: false,
318
+ })
319
+ .then((mongoose) => mongoose);
320
+ }
321
+
322
+ cached.conn = await cached.promise;
323
+ console.log("✅ MongoDB connected");
324
+ return cached.conn;
325
+ }
326
+ `,
327
+ },
328
+ {
329
+ fileName: "/lib/mongodb.js",
330
+ body: `
331
+ // lib/mongodb.js
332
+ import { MongoClient } from "mongodb";
333
+
334
+ const uri = process.env.MONGODB_URI;
335
+ const options = {};
336
+
337
+ let client;
338
+ let clientPromise;
339
+
340
+ if (!uri) {
341
+ throw new Error("❌ Please add your Mongo URI to .env.local");
342
+ }
343
+
344
+ if (process.env.NODE_ENV === "development") {
345
+ if (!global._mongoClientPromise) {
346
+ client = new MongoClient(uri, options);
347
+ global._mongoClientPromise = client.connect();
348
+ }
349
+ clientPromise = global._mongoClientPromise;
350
+ } else {
351
+ client = new MongoClient(uri, options);
352
+ clientPromise = client.connect();
353
+ }
354
+
355
+ export default clientPromise;
356
+ `,
357
+ },
358
+ {
359
+ fileName: "/models/User.js",
360
+ body: `
361
+ import mongoose from "mongoose";
362
+
363
+ const userSchema = new mongoose.Schema(
364
+ {
365
+ email: {
366
+ type: String,
367
+ required: true,
368
+ unique: true,
369
+ },
370
+ passwordHash: {
371
+ type: String,
372
+ required: true,
373
+ },
374
+ role: {
375
+ type: String,
376
+ enum: ["user", "admin"],
377
+ default: "user",
378
+ },
379
+ image: {
380
+ type: String,
381
+ default: null,
382
+ },
383
+ },
384
+ { timestamps: true },
385
+ );
386
+
387
+ export default mongoose.models.User || mongoose.model("User", userSchema);
388
+ `,
389
+ },
390
+ {
391
+ fileName: "./auth.js",
392
+ body: `
393
+ import NextAuth from "next-auth";
394
+ import CredentialsProvider from "next-auth/providers/credentials";
395
+ import GoogleProvider from "next-auth/providers/google";
396
+ import { connectDB } from "./lib/db";
397
+ import { MongoDBAdapter } from "@auth/mongodb-adapter";
398
+ import clientPromise from "@/lib/mongodb";
399
+ import User from "@/models/User";
400
+ import bcrypt from "bcryptjs";
401
+
402
+ export const { handlers, signIn, signOut, auth } = NextAuth({
403
+ adapter: MongoDBAdapter(clientPromise),
404
+ providers: [
405
+ GoogleProvider({
406
+ clientId: process.env.GOOGLE_CLIENT_ID,
407
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET,
408
+ }),
409
+ CredentialsProvider({
410
+ name: "Credentials",
411
+ credentials: {
412
+ email: { label: "Email", type: "text" },
413
+ password: { label: "Password", type: "password" },
414
+ },
415
+ async authorize(credentials) {
416
+ try {
417
+ await connectDB();
418
+ const user = await User.findOne({ email: credentials.email });
419
+ if (!user) throw new Error("No user found with this email");
420
+
421
+ const isValid = await bcrypt.compare(
422
+ credentials.password,
423
+ user.passwordHash,
424
+ );
425
+
426
+ if (!isValid) return null;
427
+
428
+ return {
429
+ id: user._id.toString(),
430
+ email: user.email,
431
+ role: user.role,
432
+ image: user.image,
433
+ };
434
+ } catch (err) {
435
+ console.error("Credentials.authorize error", err);
436
+ throw err;
437
+ }
438
+ },
439
+ }),
440
+ ],
441
+ callbacks: {
442
+ async jwt({ token, user }) {
443
+ if (user) {
444
+ token.id = user.id;
445
+ token.role = user.role || "user"; // Assign roles dynamically
446
+ }
447
+ return token;
448
+ },
449
+ async session({ session, token }) {
450
+ session.user.id = token.id;
451
+ session.user.role = token.role || "user"; // Ensure role is included in session
452
+ return session;
453
+ },
454
+ },
455
+ session: {
456
+ strategy: "jwt",
457
+ maxAge: 30 * 24 * 60 * 60, // 30 days
458
+ },
459
+ pages: {
460
+ signIn: "/auth/signin",
461
+ signOut: "/auth/signout",
462
+ error: "/auth/error",
463
+ },
464
+ events: {
465
+ async signIn({ user }) {
466
+ console.log("User signed in:", user);
467
+ },
468
+ async signOut({ token }) {
469
+ console.log("User signed out:", token);
470
+ },
471
+ },
472
+ debug: process.env.NODE_ENV === "development",
473
+ theme: {
474
+ colorScheme: "auto", // "auto", "dark", "light"
475
+ brandColor: "#0070f3",
476
+ logo: "/logo.png",
477
+ },
478
+ });
479
+ `,
480
+ },
481
+ ];
package/functions.js ADDED
@@ -0,0 +1,19 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ export const createFile = (fileName, body) => {
5
+ const filePath = path.join(process.cwd(), fileName);
6
+ const dirPath = path.dirname(filePath);
7
+
8
+ // створюємо директорії, якщо їх нема
9
+ if (!fs.existsSync(dirPath)) {
10
+ fs.mkdirSync(dirPath, { recursive: true });
11
+ }
12
+
13
+ if (fs.existsSync(filePath)) {
14
+ console.log(`❌ ${fileName} already exists`);
15
+ } else {
16
+ fs.writeFileSync(filePath, body);
17
+ console.log(`✅ ${fileName} created`);
18
+ }
19
+ };
package/index.js ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { files } from "./files.js";
4
+ import { createFile } from "./functions.js";
5
+
6
+ files.forEach(({ fileName, body }) => {
7
+ createFile(fileName, body);
8
+ });
9
+
10
+ console.log("✅ All files created!");
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "auth-simple",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "auth-simple": "index.js"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "type": "module",
13
+ "dependencies": {
14
+ "@auth/mongodb-adapter": "^3.11.1",
15
+ "bcryptjs": "^3.0.3",
16
+ "mongodb": "^7.1.0",
17
+ "mongoose": "^9.2.3",
18
+ "next-auth": "^4.24.13"
19
+ }
20
+ }