kybernus 2.4.0 → 3.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/README.md +15 -6
- package/add-features/auth/java-spring/AuthController.java +54 -0
- package/add-features/auth/java-spring/AuthService.java +85 -0
- package/add-features/auth/java-spring/INSTRUCTIONS.md +119 -0
- package/add-features/auth/java-spring/dto/LoginRequest.java +22 -0
- package/add-features/auth/java-spring/dto/RegisterRequest.java +22 -0
- package/add-features/auth/java-spring/security/JwtRequestFilter.java +45 -0
- package/add-features/auth/java-spring/security/JwtUtil.java +59 -0
- package/add-features/auth/java-spring/security/SecurityConfig.java +39 -0
- package/add-features/auth/nestjs/INSTRUCTIONS.md +112 -0
- package/add-features/auth/nestjs/auth.controller.ts +27 -0
- package/add-features/auth/nestjs/auth.module.ts +20 -0
- package/add-features/auth/nestjs/auth.service.ts +81 -0
- package/add-features/auth/nestjs/dto/login.dto.ts +4 -0
- package/add-features/auth/nestjs/dto/register.dto.ts +4 -0
- package/add-features/auth/nestjs/jwt-auth.guard.ts +17 -0
- package/add-features/auth/nestjs/jwt.strategy.ts +24 -0
- package/add-features/auth/nextjs/INSTRUCTIONS.md +97 -0
- package/add-features/auth/nextjs/jwt.ts +21 -0
- package/add-features/auth/nextjs/middleware.ts +37 -0
- package/add-features/auth/nextjs/routes/login.ts +43 -0
- package/add-features/auth/nextjs/routes/register.ts +50 -0
- package/add-features/auth/nextjs/session.ts +28 -0
- package/add-features/auth/nodejs-express/INSTRUCTIONS.md +109 -0
- package/add-features/auth/nodejs-express/auth.controller.ts +59 -0
- package/add-features/auth/nodejs-express/auth.middleware.ts +38 -0
- package/add-features/auth/nodejs-express/auth.routes.ts +15 -0
- package/add-features/auth/nodejs-express/auth.service.ts +73 -0
- package/add-features/auth/nodejs-express/jwt.config.ts +17 -0
- package/add-features/auth/python-fastapi/INSTRUCTIONS.md +100 -0
- package/add-features/auth/python-fastapi/router.py +26 -0
- package/add-features/auth/python-fastapi/schemas.py +25 -0
- package/add-features/auth/python-fastapi/security.py +37 -0
- package/add-features/auth/python-fastapi/service.py +61 -0
- package/add-features/deploy/dockerfiles/Dockerfile.java +22 -0
- package/add-features/deploy/dockerfiles/Dockerfile.nextjs +32 -0
- package/add-features/deploy/dockerfiles/Dockerfile.nodejs +25 -0
- package/add-features/deploy/dockerfiles/Dockerfile.python +17 -0
- package/add-features/deploy/fly/INSTRUCTIONS.md +39 -0
- package/add-features/deploy/fly/java-spring.toml +21 -0
- package/add-features/deploy/fly/nextjs.toml +16 -0
- package/add-features/deploy/fly/nodejs.toml +21 -0
- package/add-features/deploy/fly/python-fastapi.toml +21 -0
- package/add-features/deploy/railway/INSTRUCTIONS.md +38 -0
- package/add-features/deploy/railway/java-spring.toml +16 -0
- package/add-features/deploy/railway/nextjs.toml +14 -0
- package/add-features/deploy/railway/nodejs.toml +14 -0
- package/add-features/deploy/railway/python-fastapi.toml +13 -0
- package/add-features/deploy/render/INSTRUCTIONS.md +35 -0
- package/add-features/deploy/render/java-spring.yaml +14 -0
- package/add-features/deploy/render/nextjs.yaml +17 -0
- package/add-features/deploy/render/nodejs.yaml +15 -0
- package/add-features/deploy/render/python-fastapi.yaml +13 -0
- package/add-features/deploy/vercel/INSTRUCTIONS.md +40 -0
- package/add-features/deploy/vercel/nextjs.json +16 -0
- package/add-features/deploy/vercel/nodejs-express.json +21 -0
- package/add-features/deploy/vercel/python-fastapi.json +21 -0
- package/add-features/husky/INSTRUCTIONS.md +52 -0
- package/add-features/husky/commit-msg +4 -0
- package/add-features/husky/commitlint.config.js +3 -0
- package/add-features/husky/pre-commit +4 -0
- package/add-features/redis/.env.snippet +1 -0
- package/add-features/redis/INSTRUCTIONS.md +64 -0
- package/add-features/redis/docker-compose.snippet.yml +18 -0
- package/add-features/redis/java-spring.java +27 -0
- package/add-features/redis/nextjs.ts +23 -0
- package/add-features/redis/nodejs.ts +14 -0
- package/add-features/redis/python.py +22 -0
- package/add-features/swagger/INSTRUCTIONS.md +53 -0
- package/add-features/swagger/java-spring.java +34 -0
- package/add-features/swagger/nestjs.ts +16 -0
- package/add-features/swagger/nextjs-route.ts +11 -0
- package/add-features/swagger/nextjs-swagger.ts +17 -0
- package/add-features/swagger/nodejs-express.ts +30 -0
- package/add-features/swagger/python-fastapi.py +21 -0
- package/add-features/websocket/INSTRUCTIONS.md +63 -0
- package/add-features/websocket/java-spring.java +60 -0
- package/add-features/websocket/nestjs.ts +38 -0
- package/add-features/websocket/nodejs-express.ts +38 -0
- package/add-features/websocket/python-fastapi.py +41 -0
- package/dist/cli/commands/add.d.ts +11 -0
- package/dist/cli/commands/add.d.ts.map +1 -0
- package/dist/cli/commands/add.js +102 -0
- package/dist/cli/commands/add.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +11 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +71 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/deploy.d.ts +10 -0
- package/dist/cli/commands/deploy.d.ts.map +1 -0
- package/dist/cli/commands/deploy.js +110 -0
- package/dist/cli/commands/deploy.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +110 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/init.d.ts +1 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +1 -0
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/prompts/wizard.d.ts +1 -0
- package/dist/cli/prompts/wizard.d.ts.map +1 -1
- package/dist/cli/prompts/wizard.js +23 -15
- package/dist/cli/prompts/wizard.js.map +1 -1
- package/dist/cli/utils/cli-helpers.d.ts +43 -0
- package/dist/cli/utils/cli-helpers.d.ts.map +1 -0
- package/dist/cli/utils/cli-helpers.js +107 -0
- package/dist/cli/utils/cli-helpers.js.map +1 -0
- package/dist/core/deploy/deploy-generator.d.ts +18 -0
- package/dist/core/deploy/deploy-generator.d.ts.map +1 -0
- package/dist/core/deploy/deploy-generator.js +155 -0
- package/dist/core/deploy/deploy-generator.js.map +1 -0
- package/dist/core/features/feature-generator.d.ts +26 -0
- package/dist/core/features/feature-generator.d.ts.map +1 -0
- package/dist/core/features/feature-generator.js +376 -0
- package/dist/core/features/feature-generator.js.map +1 -0
- package/dist/core/generator/project.d.ts.map +1 -1
- package/dist/core/generator/project.js +42 -2
- package/dist/core/generator/project.js.map +1 -1
- package/dist/core/templates/engine.d.ts.map +1 -1
- package/dist/core/templates/engine.js +4 -0
- package/dist/core/templates/engine.js.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/models/config.d.ts +1 -0
- package/dist/models/config.d.ts.map +1 -1
- package/package.json +14 -2
- package/templates/nestjs/clean/package.json.hbs +51 -41
- package/templates/nestjs/hexagonal/package.json.hbs +51 -41
- package/templates/nestjs/mvc/package.json.hbs +49 -39
- package/templates/nextjs/mvc/package.json.hbs +46 -36
- package/templates/nodejs-express/clean/package.json.hbs +69 -59
- package/templates/nodejs-express/hexagonal/package.json.hbs +69 -59
- package/templates/nodejs-express/mvc/package.json.hbs +67 -57
- /package/templates/nestjs/hexagonal/prisma/{schema.prisma → schema.prisma.hbs} +0 -0
- /package/templates/nodejs-express/clean/prisma/{schema.prisma → schema.prisma.hbs} +0 -0
- /package/templates/nodejs-express/hexagonal/prisma/{schema.prisma → schema.prisma.hbs} +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Injectable, UnauthorizedException, ConflictException, NotFoundException } from '@nestjs/common';
|
|
2
|
+
import { JwtService } from '@nestjs/jwt';
|
|
3
|
+
import * as bcrypt from 'bcryptjs';
|
|
4
|
+
|
|
5
|
+
// ==========================================
|
|
6
|
+
// 🚨 TODO: DATABASE INTEGRATION REQUIRED 🚨
|
|
7
|
+
// ==========================================
|
|
8
|
+
// This service currently uses an IN-MEMORY array to store users.
|
|
9
|
+
// You MUST replace "this.mockDb" logic with your actual ORM or Repository provider.
|
|
10
|
+
//
|
|
11
|
+
// EXAMPLE WITH PRISMA/TYPEORM:
|
|
12
|
+
// 1. Inject your repository in the constructor:
|
|
13
|
+
// constructor(private usersService: UsersService, private jwtService: JwtService) {}
|
|
14
|
+
// 2. Change register() to: await this.usersService.create(email, hashedPassword);
|
|
15
|
+
// 3. Change login() to: await this.usersService.findByEmail(email);
|
|
16
|
+
// ==========================================
|
|
17
|
+
|
|
18
|
+
@Injectable()
|
|
19
|
+
export class AuthService {
|
|
20
|
+
private mockDb = []; // 🚨 REPLACE THIS WITH REAL DB CALLS 🚨
|
|
21
|
+
|
|
22
|
+
constructor(private jwtService: JwtService) { }
|
|
23
|
+
|
|
24
|
+
async register(email: string, pass: string) {
|
|
25
|
+
// 🚨 TODO: Change this to checking your real database!
|
|
26
|
+
const existingUser = this.mockDb.find((u) => u.email === email);
|
|
27
|
+
if (existingUser) {
|
|
28
|
+
throw new ConflictException('User already exists');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const hashedPassword = await bcrypt.hash(pass, 12);
|
|
32
|
+
|
|
33
|
+
// 🚨 TODO: Change this to inserting into your real database!
|
|
34
|
+
const newUser = {
|
|
35
|
+
id: Math.random().toString(36).substring(7),
|
|
36
|
+
email,
|
|
37
|
+
password: hashedPassword,
|
|
38
|
+
};
|
|
39
|
+
this.mockDb.push(newUser);
|
|
40
|
+
|
|
41
|
+
const payload = { userId: newUser.id, email: newUser.email };
|
|
42
|
+
const { password, ...userResult } = newUser;
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
user: userResult,
|
|
46
|
+
access_token: this.jwtService.sign(payload),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async login(email: string, pass: string) {
|
|
51
|
+
// 🚨 TODO: Change this to fetching from your real database!
|
|
52
|
+
const user = this.mockDb.find((u) => u.email === email);
|
|
53
|
+
|
|
54
|
+
if (!user) {
|
|
55
|
+
throw new UnauthorizedException('Invalid credentials');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const isMatch = await bcrypt.compare(pass, user.password);
|
|
59
|
+
if (!isMatch) {
|
|
60
|
+
throw new UnauthorizedException('Invalid credentials');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const payload = { userId: user.id, email: user.email };
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
access_token: this.jwtService.sign(payload),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async getUserProfile(userId: string) {
|
|
71
|
+
// 🚨 TODO: Change this to fetching from your real database!
|
|
72
|
+
const user = this.mockDb.find((u) => u.id === userId);
|
|
73
|
+
|
|
74
|
+
if (!user) {
|
|
75
|
+
throw new NotFoundException('User not found');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const { password, ...result } = user;
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
2
|
+
import { AuthGuard } from '@nestjs/passport';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Guard that protects routes using JWT authentication.
|
|
6
|
+
*
|
|
7
|
+
* Usage: @UseGuards(JwtAuthGuard) on controller or route handler.
|
|
8
|
+
*/
|
|
9
|
+
@Injectable()
|
|
10
|
+
export class JwtAuthGuard extends AuthGuard('jwt') {
|
|
11
|
+
handleRequest(err: any, user: any) {
|
|
12
|
+
if (err || !user) {
|
|
13
|
+
throw err || new UnauthorizedException('Missing or invalid token');
|
|
14
|
+
}
|
|
15
|
+
return user;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { PassportStrategy } from '@nestjs/passport';
|
|
3
|
+
import { ExtractJwt, Strategy } from 'passport-jwt';
|
|
4
|
+
|
|
5
|
+
export interface JwtPayload {
|
|
6
|
+
userId: string;
|
|
7
|
+
email: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@Injectable()
|
|
11
|
+
export class JwtStrategy extends PassportStrategy(Strategy) {
|
|
12
|
+
constructor() {
|
|
13
|
+
super({
|
|
14
|
+
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
15
|
+
ignoreExpiration: false,
|
|
16
|
+
secretOrKey: process.env.JWT_SECRET || 'change-me-in-production',
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async validate(payload: JwtPayload) {
|
|
21
|
+
// Passport will automatically attach this to the Request object as `req.user`
|
|
22
|
+
return { userId: payload.userId, email: payload.email };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# 🔐 JWT Authentication Module — Next.js (App Router)
|
|
2
|
+
|
|
3
|
+
A complete, modular authentication flow was added to your Next.js project.
|
|
4
|
+
|
|
5
|
+
## 📁 Files generated:
|
|
6
|
+
|
|
7
|
+
- `src/lib/auth/jwt.ts` — Utilities for signing and verifying JSON Web Tokens.
|
|
8
|
+
- `src/lib/auth/session.ts` — Utilities for reading the session cookie from Server Components.
|
|
9
|
+
- `src/app/api/auth/login/route.ts` — Login API Route. **🚨 ACTION REQUIRED HERE**
|
|
10
|
+
- `src/app/api/auth/register/route.ts` — Registration API Route. **🚨 ACTION REQUIRED HERE**
|
|
11
|
+
- `src/middleware.ts` — Edge Middleware that automatically intercepts page requests and redirects unauthenticated users.
|
|
12
|
+
|
|
13
|
+
## 📦 1. Install Dependencies
|
|
14
|
+
|
|
15
|
+
You need to install the JWT and Hash libraries:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install jsonwebtoken bcryptjs
|
|
19
|
+
npm install -D @types/jsonwebtoken @types/bcryptjs
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## ⚙️ 2. Environment Variables
|
|
23
|
+
|
|
24
|
+
Add these to your `.env` or `.env.local` file at the root of your project:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
JWT_SECRET=your-super-secret-key-change-me
|
|
28
|
+
JWT_EXPIRES_IN=7d
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> 💡 **Tip:** Generate a strong random secret by running this in your terminal:
|
|
32
|
+
> `node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"`
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 🚨 3. MANDATORY ACTION: Connect to your Database
|
|
37
|
+
|
|
38
|
+
The generated `login/route.ts` and `register/route.ts` use an **IN-MEMORY MOCK DATABASE** by default so that they compile and run immediately.
|
|
39
|
+
You **MUST** replace this with your actual database queries (Prisma, Drizzle, MongoDB, etc).
|
|
40
|
+
|
|
41
|
+
**Open `src/app/api/auth/login/route.ts` and `src/app/api/auth/register/route.ts` and look for the `🚨 TODO` blocks.**
|
|
42
|
+
|
|
43
|
+
### Example: How to connect `register` to Prisma:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// Inside src/app/api/auth/register/route.ts
|
|
47
|
+
|
|
48
|
+
import prisma from '@/lib/prisma'; // Provide your own PrismaClient
|
|
49
|
+
|
|
50
|
+
// Inside the POST function:
|
|
51
|
+
const existingUser = await prisma.user.findUnique({ where: { email } });
|
|
52
|
+
if (existingUser) return NextResponse.json({ error: 'User exists' }, { status: 409 });
|
|
53
|
+
|
|
54
|
+
const hashedPassword = await bcrypt.hash(password, 12);
|
|
55
|
+
|
|
56
|
+
const newUser = await prisma.user.create({
|
|
57
|
+
data: { email, password: hashedPassword },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// ... continuing with token generation ...
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## ⚡ 4. Consuming the Session in Server Components
|
|
66
|
+
|
|
67
|
+
You can easily know who the current user is from any Server Component, without making an additional network request:
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
// src/app/dashboard/page.tsx
|
|
71
|
+
|
|
72
|
+
import { getSession } from '@/lib/auth/session';
|
|
73
|
+
import { redirect } from 'next/navigation';
|
|
74
|
+
|
|
75
|
+
export default async function DashboardPage() {
|
|
76
|
+
const session = await getSession();
|
|
77
|
+
|
|
78
|
+
// The middleware already protects /dashboard, but this is how you read the data
|
|
79
|
+
if (!session) {
|
|
80
|
+
redirect('/login');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div>
|
|
85
|
+
<h1>Welcome back!</h1>
|
|
86
|
+
<p>Your ID: {session.userId}</p>
|
|
87
|
+
<p>Your Email: {session.email}</p>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 🔒 5. Middleware Protection
|
|
94
|
+
|
|
95
|
+
Open `src/middleware.ts`. At the top, you will see `protectedRoutes`.
|
|
96
|
+
Any route added to that array (e.g. `/dashboard`, `/settings`) cannot be accessed unless a valid JWT cookie is present.
|
|
97
|
+
Unauthenticated users will automatically redirect to `/login`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import jwt from 'jsonwebtoken';
|
|
2
|
+
|
|
3
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'change-me-in-production';
|
|
4
|
+
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d';
|
|
5
|
+
|
|
6
|
+
export interface JwtPayload {
|
|
7
|
+
userId: string;
|
|
8
|
+
email: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function generateToken(payload: JwtPayload): string {
|
|
12
|
+
return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function verifyToken(token: string): JwtPayload | null {
|
|
16
|
+
try {
|
|
17
|
+
return jwt.verify(token, JWT_SECRET) as JwtPayload;
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import type { NextRequest } from 'next/server';
|
|
3
|
+
import { verifyToken } from '@/lib/auth/jwt';
|
|
4
|
+
|
|
5
|
+
// Define the routes that require authentication
|
|
6
|
+
const protectedRoutes = ['/dashboard', '/api/protected'];
|
|
7
|
+
const authRoutes = ['/login', '/register'];
|
|
8
|
+
|
|
9
|
+
export function middleware(request: NextRequest) {
|
|
10
|
+
const { pathname } = request.nextUrl;
|
|
11
|
+
|
|
12
|
+
// Check if it's a protected route
|
|
13
|
+
const isProtectedRoute = protectedRoutes.some(route => pathname.startsWith(route));
|
|
14
|
+
const isAuthRoute = authRoutes.some(route => pathname.startsWith(route));
|
|
15
|
+
|
|
16
|
+
if (isProtectedRoute || isAuthRoute) {
|
|
17
|
+
const token = request.cookies.get('token')?.value;
|
|
18
|
+
const payload = token ? verifyToken(token) : null;
|
|
19
|
+
|
|
20
|
+
if (isProtectedRoute && !payload) {
|
|
21
|
+
// Redirect to login if trying to access a protected route without a valid token
|
|
22
|
+
return NextResponse.redirect(new URL('/login', request.url));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (isAuthRoute && payload) {
|
|
26
|
+
// Redirect to dashboard if logged in and trying to access login/register
|
|
27
|
+
return NextResponse.redirect(new URL('/dashboard', request.url));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return NextResponse.next();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Ensure the middleware runs on relevant paths
|
|
35
|
+
export const config = {
|
|
36
|
+
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
37
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { generateToken } from '@/lib/auth/jwt';
|
|
3
|
+
import { sessionCookieOptions } from '@/lib/auth/session';
|
|
4
|
+
import bcrypt from 'bcryptjs';
|
|
5
|
+
|
|
6
|
+
// ==========================================
|
|
7
|
+
// 🚨 TODO: DATABASE INTEGRATION REQUIRED 🚨
|
|
8
|
+
// ==========================================
|
|
9
|
+
const mockDb: any[] = []; // 🚨 REPLACE THIS WITH REAL DB CALLS (e.g. Prisma) 🚨
|
|
10
|
+
|
|
11
|
+
export async function POST(req: NextRequest) {
|
|
12
|
+
try {
|
|
13
|
+
const { email, password } = await req.json();
|
|
14
|
+
|
|
15
|
+
if (!email || !password) {
|
|
16
|
+
return NextResponse.json({ error: 'Email and password are required' }, { status: 400 });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 🚨 TODO: Replace with real db call (e.g. prisma.user.findUnique)
|
|
20
|
+
const user = mockDb.find((u) => u.email === email);
|
|
21
|
+
|
|
22
|
+
if (!user) {
|
|
23
|
+
return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const isMatch = await bcrypt.compare(password, user.password);
|
|
27
|
+
if (!isMatch) {
|
|
28
|
+
return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const token = generateToken({ userId: user.id, email: user.email });
|
|
32
|
+
|
|
33
|
+
const response = NextResponse.json({ success: true });
|
|
34
|
+
|
|
35
|
+
// Set the cookie
|
|
36
|
+
response.cookies.set('token', token, sessionCookieOptions);
|
|
37
|
+
|
|
38
|
+
return response;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('Login error:', error);
|
|
41
|
+
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { generateToken } from '@/lib/auth/jwt';
|
|
3
|
+
import { sessionCookieOptions } from '@/lib/auth/session';
|
|
4
|
+
import bcrypt from 'bcryptjs';
|
|
5
|
+
|
|
6
|
+
// ==========================================
|
|
7
|
+
// 🚨 TODO: DATABASE INTEGRATION REQUIRED 🚨
|
|
8
|
+
// ==========================================
|
|
9
|
+
const mockDb: any[] = []; // 🚨 REPLACE THIS WITH REAL DB CALLS (e.g. Prisma) 🚨
|
|
10
|
+
|
|
11
|
+
export async function POST(req: NextRequest) {
|
|
12
|
+
try {
|
|
13
|
+
const { email, password } = await req.json();
|
|
14
|
+
|
|
15
|
+
if (!email || !password) {
|
|
16
|
+
return NextResponse.json({ error: 'Email and password are required' }, { status: 400 });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 🚨 TODO: Replace with real db call (e.g. prisma.user.findUnique)
|
|
20
|
+
const existingUser = mockDb.find((u) => u.email === email);
|
|
21
|
+
if (existingUser) {
|
|
22
|
+
return NextResponse.json({ error: 'User already exists' }, { status: 409 });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const hashedPassword = await bcrypt.hash(password, 12);
|
|
26
|
+
|
|
27
|
+
// 🚨 TODO: Replace with real db call (e.g. prisma.user.create)
|
|
28
|
+
const newUser = {
|
|
29
|
+
id: Math.random().toString(36).substring(7),
|
|
30
|
+
email,
|
|
31
|
+
password: hashedPassword,
|
|
32
|
+
};
|
|
33
|
+
mockDb.push(newUser);
|
|
34
|
+
|
|
35
|
+
const token = generateToken({ userId: newUser.id, email: newUser.email });
|
|
36
|
+
|
|
37
|
+
const response = NextResponse.json(
|
|
38
|
+
{ user: { id: newUser.id, email: newUser.email } },
|
|
39
|
+
{ status: 201 }
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Set the cookie
|
|
43
|
+
response.cookies.set('token', token, sessionCookieOptions);
|
|
44
|
+
|
|
45
|
+
return response;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Registration error:', error);
|
|
48
|
+
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { cookies } from 'next/headers';
|
|
2
|
+
import { verifyToken, JwtPayload } from './jwt';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Get the current user from the session cookie.
|
|
6
|
+
* This can only be called from Server Components, Server Actions, or API Routes.
|
|
7
|
+
*/
|
|
8
|
+
export async function getSession(): Promise<JwtPayload | null> {
|
|
9
|
+
const cookieStore = await cookies();
|
|
10
|
+
const token = cookieStore.get('token')?.value;
|
|
11
|
+
|
|
12
|
+
if (!token) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return verifyToken(token);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Define your cookie configuration to be used during login/logout
|
|
21
|
+
*/
|
|
22
|
+
export const sessionCookieOptions = {
|
|
23
|
+
httpOnly: true,
|
|
24
|
+
secure: process.env.NODE_ENV === 'production',
|
|
25
|
+
sameSite: 'lax' as const,
|
|
26
|
+
path: '/',
|
|
27
|
+
maxAge: 60 * 60 * 24 * 7, // 7 days (must match your JWT expiry conceptually)
|
|
28
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# 🔐 JWT Authentication Module — Node.js Express
|
|
2
|
+
|
|
3
|
+
A complete, modular authentication flow was added to your project inside the `auth/` directory.
|
|
4
|
+
|
|
5
|
+
## 📁 Files generated:
|
|
6
|
+
|
|
7
|
+
- `auth.routes.ts` — The Express Router defining `/login`, `/register`, and `/me`.
|
|
8
|
+
- `auth.controller.ts` — Handles HTTP Requests and Responses.
|
|
9
|
+
- `auth.service.ts` — Business logic (token generation, password hashing). **🚨 ACTION REQUIRED HERE**
|
|
10
|
+
- `auth.middleware.ts` — Middleware to protect secure routes.
|
|
11
|
+
- `jwt.config.ts` — JWT sign and verify utilities.
|
|
12
|
+
|
|
13
|
+
## 📦 1. Install Dependencies
|
|
14
|
+
|
|
15
|
+
You need to install the JWT and Cryptography packages:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install jsonwebtoken bcryptjs
|
|
19
|
+
npm install -D @types/jsonwebtoken @types/bcryptjs
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## ⚙️ 2. Environment Variables
|
|
23
|
+
|
|
24
|
+
Add these to your `.env` file at the root of your project:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
JWT_SECRET=your-super-secret-key-change-me
|
|
28
|
+
JWT_EXPIRES_IN=7d
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> 💡 **Tip:** Generate a strong random secret by running this in your terminal:
|
|
32
|
+
> `node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"`
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 🚨 3. MANDATORY ACTION: Connect to your Database
|
|
37
|
+
|
|
38
|
+
The generated `auth.service.ts` uses an **IN-MEMORY MOCK DATABASE** by default so that it compiles and runs immediately.
|
|
39
|
+
You **MUST** replace this with your actual Database / ORM calls.
|
|
40
|
+
|
|
41
|
+
**Open `auth/auth.service.ts` and look for the `🚨 TODO` blocks.**
|
|
42
|
+
|
|
43
|
+
### Example: How to connect it to Prisma:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// Inside auth.service.ts
|
|
47
|
+
|
|
48
|
+
import { PrismaClient } from '@prisma/client';
|
|
49
|
+
import bcrypt from 'bcryptjs';
|
|
50
|
+
import { generateToken } from './jwt.config';
|
|
51
|
+
|
|
52
|
+
const prisma = new PrismaClient();
|
|
53
|
+
|
|
54
|
+
export class AuthService {
|
|
55
|
+
async register(email: string, password: string) {
|
|
56
|
+
const existingUser = await prisma.user.findUnique({ where: { email } });
|
|
57
|
+
if (existingUser) throw new Error('User already exists');
|
|
58
|
+
|
|
59
|
+
const hashedPassword = await bcrypt.hash(password, 12);
|
|
60
|
+
|
|
61
|
+
const newUser = await prisma.user.create({
|
|
62
|
+
data: { email, password: hashedPassword },
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const token = generateToken({ userId: newUser.id, email: newUser.email });
|
|
66
|
+
return { token };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## ⚡ 4. Plug the Router into your App
|
|
74
|
+
|
|
75
|
+
Currently, the routes exist but your Express server doesn't know about them.
|
|
76
|
+
You must register the `auth.routes.ts` file in your main Express app entrypoint.
|
|
77
|
+
|
|
78
|
+
**Open your `src/app.ts` or `src/index.ts` and add:**
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import express from 'express';
|
|
82
|
+
// 1. Import the router
|
|
83
|
+
import authRoutes from './auth/auth.routes';
|
|
84
|
+
|
|
85
|
+
const app = express();
|
|
86
|
+
app.use(express.json());
|
|
87
|
+
|
|
88
|
+
// 2. Register the router
|
|
89
|
+
app.use('/api/auth', authRoutes);
|
|
90
|
+
|
|
91
|
+
app.listen(3000, () => console.log('Server running!'));
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 🔒 5. How to protect other routes
|
|
95
|
+
|
|
96
|
+
You can secure any other route in your API by importing the `authMiddleware`:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { authMiddleware } from './auth/auth.middleware';
|
|
100
|
+
|
|
101
|
+
// Any route using authMiddleware requires a valid 'Authorization: Bearer <token>' header
|
|
102
|
+
app.get('/api/admin/dashboard', authMiddleware, (req, res) => {
|
|
103
|
+
// You now have access to the logged-in user's payload!
|
|
104
|
+
console.log(req.user.userId);
|
|
105
|
+
console.log(req.user.email);
|
|
106
|
+
|
|
107
|
+
res.json({ secretData: "This is locked" });
|
|
108
|
+
});
|
|
109
|
+
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
import { AuthService } from './auth.service';
|
|
3
|
+
|
|
4
|
+
const authService = new AuthService();
|
|
5
|
+
|
|
6
|
+
export class AuthController {
|
|
7
|
+
|
|
8
|
+
async register(req: Request, res: Response) {
|
|
9
|
+
try {
|
|
10
|
+
const { email, password } = req.body;
|
|
11
|
+
|
|
12
|
+
if (!email || !password) {
|
|
13
|
+
return res.status(400).json({ error: 'Email and password are required' });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const result = await authService.register(email, password);
|
|
17
|
+
return res.status(201).json(result);
|
|
18
|
+
} catch (error: any) {
|
|
19
|
+
if (error.message === 'User already exists') {
|
|
20
|
+
return res.status(409).json({ error: 'User already exists' });
|
|
21
|
+
}
|
|
22
|
+
return res.status(500).json({ error: 'Internal server error' });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async login(req: Request, res: Response) {
|
|
27
|
+
try {
|
|
28
|
+
const { email, password } = req.body;
|
|
29
|
+
|
|
30
|
+
if (!email || !password) {
|
|
31
|
+
return res.status(400).json({ error: 'Email and password are required' });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const result = await authService.login(email, password);
|
|
35
|
+
return res.status(200).json(result);
|
|
36
|
+
} catch (error: any) {
|
|
37
|
+
if (error.message === 'Invalid credentials') {
|
|
38
|
+
return res.status(401).json({ error: 'Invalid credentials' });
|
|
39
|
+
}
|
|
40
|
+
return res.status(500).json({ error: 'Internal server error' });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async getProfile(req: Request, res: Response) {
|
|
45
|
+
// req.user is populated by the auth.middleware.ts
|
|
46
|
+
const user = req.user;
|
|
47
|
+
|
|
48
|
+
if (!user) {
|
|
49
|
+
return res.status(401).json({ error: 'Not authenticated' });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const profile = await authService.getUserProfile(user.userId);
|
|
54
|
+
return res.status(200).json(profile);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
return res.status(404).json({ error: 'User not found' });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { verifyToken, JwtPayload } from './jwt.config';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extends Express Request to include the authenticated user payload.
|
|
6
|
+
*/
|
|
7
|
+
declare global {
|
|
8
|
+
namespace Express {
|
|
9
|
+
interface Request {
|
|
10
|
+
user?: JwtPayload;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Middleware that validates the JWT from the Authorization header.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* router.get('/protected-route', authMiddleware, controllerHandler);
|
|
20
|
+
*/
|
|
21
|
+
export function authMiddleware(req: Request, res: Response, next: NextFunction): void {
|
|
22
|
+
const header = req.headers.authorization;
|
|
23
|
+
|
|
24
|
+
if (!header || !header.startsWith('Bearer ')) {
|
|
25
|
+
res.status(401).json({ error: 'Missing or invalid Authorization header' });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const token = header.split(' ')[1];
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const payload = verifyToken(token);
|
|
33
|
+
req.user = payload;
|
|
34
|
+
next();
|
|
35
|
+
} catch {
|
|
36
|
+
res.status(401).json({ error: 'Invalid or expired token' });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { AuthController } from './auth.controller';
|
|
3
|
+
import { authMiddleware } from './auth.middleware';
|
|
4
|
+
|
|
5
|
+
const router = Router();
|
|
6
|
+
const authController = new AuthController();
|
|
7
|
+
|
|
8
|
+
// Public Routes
|
|
9
|
+
router.post('/register', authController.register);
|
|
10
|
+
router.post('/login', authController.login);
|
|
11
|
+
|
|
12
|
+
// Protected Routes (requires valid JWT)
|
|
13
|
+
router.get('/me', authMiddleware, authController.getProfile);
|
|
14
|
+
|
|
15
|
+
export default router;
|