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
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# Kybernus CLI 🚀
|
|
1
|
+
# Kybernus CLI - 3.0.0 🚀
|
|
2
2
|
|
|
3
|
+
**Make sure you always have the most updated version**
|
|
3
4
|
**The Ultimate Backend Scaffolding Tool for Modern Developers.**
|
|
4
5
|
|
|
5
6
|
Build production-ready applications in minutes. Kybernus generates robust, scalable backend projects with industry best practices built-in.
|
|
6
7
|
|
|
7
8
|
[](https://opensource.org/licenses/MIT)
|
|
8
|
-
[](https://discord.gg/u5ANEpAAhT)
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -37,14 +37,19 @@ All stacks and architectures are **100% FREE** and Open Source.
|
|
|
37
37
|
| **Java Spring Boot** | MVC, Clean, Hexagonal |
|
|
38
38
|
| **NestJS** | MVC, Clean, Hexagonal |
|
|
39
39
|
| **Python FastAPI** | MVC, Clean, Hexagonal |
|
|
40
|
+
| **n8n Automation Engine** | Default, AI Assistant, CRM Tracker, System Monitor |
|
|
40
41
|
|
|
41
42
|
---
|
|
42
43
|
|
|
43
44
|
## Commands
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
| Command | Description | Standalone Support |
|
|
47
|
+
|:---|:---|:---:|
|
|
48
|
+
| `kybernus init` | Generate a new backend project from scratch | - |
|
|
49
|
+
| `kybernus add` | Add features (Redis, Swagger, Websocket, Husky - stack dependent) | ✅ |
|
|
50
|
+
| `kybernus auth` | Add complete JWT Authentication logic | ✅ |
|
|
51
|
+
| `kybernus deploy` | Generate deployment configs (Vercel, Railway, etc.) | ✅ |
|
|
52
|
+
| `kybernus doctor` | Check your environment for required tools | ✅ |
|
|
48
53
|
|
|
49
54
|
---
|
|
50
55
|
|
|
@@ -99,8 +104,12 @@ kybernus init
|
|
|
99
104
|
## What's Included
|
|
100
105
|
|
|
101
106
|
### 100% Free & Open Source
|
|
102
|
-
- **All stacks** including NestJS and
|
|
107
|
+
- **All backend stacks** including NestJS, FastAPI and Spring Boot
|
|
108
|
+
- **n8n Enterprise Automation Engine** with templates
|
|
103
109
|
- **Clean & Hexagonal Architecture** patterns
|
|
110
|
+
- **Kybernus Add**: Inject features like Redis, Websocket and Swagger into any project
|
|
111
|
+
- **Kybernus Auth**: Complete Authentication boilerplate (MVC/Clean)
|
|
112
|
+
- **Kybernus Deploy**: Configs for Vercel, Railway, Fly.io and Render
|
|
104
113
|
- **Docker & docker-compose** configurations
|
|
105
114
|
- **CI/CD pipelines** (GitHub Actions)
|
|
106
115
|
- **Terraform** infrastructure configs
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
package com.example.auth;
|
|
2
|
+
|
|
3
|
+
import org.springframework.http.HttpStatus;
|
|
4
|
+
import org.springframework.http.ResponseEntity;
|
|
5
|
+
import org.springframework.security.core.Authentication;
|
|
6
|
+
import org.springframework.web.bind.annotation.*;
|
|
7
|
+
|
|
8
|
+
import java.util.Map;
|
|
9
|
+
|
|
10
|
+
@RestController
|
|
11
|
+
@RequestMapping("/auth")
|
|
12
|
+
public class AuthController {
|
|
13
|
+
|
|
14
|
+
private final AuthService authService;
|
|
15
|
+
|
|
16
|
+
public AuthController(AuthService authService) {
|
|
17
|
+
this.authService = authService;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@PostMapping("/register")
|
|
21
|
+
public ResponseEntity<?> register(@RequestBody RegisterRequest request) {
|
|
22
|
+
try {
|
|
23
|
+
Map<String, Object> response = authService.register(request.getEmail(), request.getPassword());
|
|
24
|
+
return ResponseEntity.status(HttpStatus.CREATED).body(response);
|
|
25
|
+
} catch (IllegalArgumentException e) {
|
|
26
|
+
return ResponseEntity.status(HttpStatus.CONFLICT).body(Map.of("error", e.getMessage()));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@PostMapping("/login")
|
|
31
|
+
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
|
|
32
|
+
try {
|
|
33
|
+
Map<String, String> response = authService.login(request.getEmail(), request.getPassword());
|
|
34
|
+
return ResponseEntity.ok(response);
|
|
35
|
+
} catch (IllegalArgumentException e) {
|
|
36
|
+
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("error", e.getMessage()));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@GetMapping("/me")
|
|
41
|
+
public ResponseEntity<?> getProfile(Authentication authentication) {
|
|
42
|
+
if (authentication == null) {
|
|
43
|
+
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("error", "Not authenticated"));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
String userId = (String) authentication.getPrincipal();
|
|
47
|
+
try {
|
|
48
|
+
Map<String, Object> profile = authService.getUserProfile(userId);
|
|
49
|
+
return ResponseEntity.ok(profile);
|
|
50
|
+
} catch (IllegalArgumentException e) {
|
|
51
|
+
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("error", e.getMessage()));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
package com.example.auth;
|
|
2
|
+
|
|
3
|
+
import com.example.security.JwtUtil;
|
|
4
|
+
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
5
|
+
import org.springframework.stereotype.Service;
|
|
6
|
+
|
|
7
|
+
import java.util.*;
|
|
8
|
+
|
|
9
|
+
// ==========================================
|
|
10
|
+
// 🚨 TODO: DATABASE INTEGRATION REQUIRED 🚨
|
|
11
|
+
// ==========================================
|
|
12
|
+
// This service currently uses an IN-MEMORY list to store users.
|
|
13
|
+
// You MUST replace the "mockDb" logic below with your Spring Data JPA Repository.
|
|
14
|
+
//
|
|
15
|
+
// EXAMPLE WITH JPA:
|
|
16
|
+
// 1. Inject UserRepository:
|
|
17
|
+
// public AuthService(UserRepository userRepository, PasswordEncoder passwordEncoder, JwtUtil jwtUtil) { ... }
|
|
18
|
+
// 2. Register: userRepository.save(new User(email, passwordEncoder.encode(password)));
|
|
19
|
+
// 3. Login: userRepository.findByEmail(email).orElseThrow(...);
|
|
20
|
+
// ==========================================
|
|
21
|
+
|
|
22
|
+
@Service
|
|
23
|
+
public class AuthService {
|
|
24
|
+
|
|
25
|
+
private final PasswordEncoder passwordEncoder;
|
|
26
|
+
private final JwtUtil jwtUtil;
|
|
27
|
+
|
|
28
|
+
// 🚨 REPLACE THIS WITH REAL DB CALLS (e.g., UserRepository) 🚨
|
|
29
|
+
private final List<Map<String, String>> mockDb = new ArrayList<>();
|
|
30
|
+
|
|
31
|
+
public AuthService(PasswordEncoder passwordEncoder, JwtUtil jwtUtil) {
|
|
32
|
+
this.passwordEncoder = passwordEncoder;
|
|
33
|
+
this.jwtUtil = jwtUtil;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public Map<String, Object> register(String email, String password) {
|
|
37
|
+
// 🚨 TODO: Check real DB
|
|
38
|
+
boolean exists = mockDb.stream().anyMatch(u -> u.get("email").equals(email));
|
|
39
|
+
if (exists) {
|
|
40
|
+
throw new IllegalArgumentException("User already exists");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
String hashedPassword = passwordEncoder.encode(password);
|
|
44
|
+
String userId = UUID.randomUUID().toString().substring(0, 8);
|
|
45
|
+
|
|
46
|
+
// 🚨 TODO: Insert into real DB
|
|
47
|
+
Map<String, String> newUser = new HashMap<>();
|
|
48
|
+
newUser.put("id", userId);
|
|
49
|
+
newUser.put("email", email);
|
|
50
|
+
newUser.put("password", hashedPassword);
|
|
51
|
+
mockDb.add(newUser);
|
|
52
|
+
|
|
53
|
+
String token = jwtUtil.generateToken(userId, email);
|
|
54
|
+
|
|
55
|
+
Map<String, Object> response = new HashMap<>();
|
|
56
|
+
response.put("user", Map.of("id", userId, "email", email));
|
|
57
|
+
response.put("accessToken", token);
|
|
58
|
+
return response;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public Map<String, String> login(String email, String password) {
|
|
62
|
+
// 🚨 TODO: Fetch from real DB
|
|
63
|
+
Map<String, String> user = mockDb.stream()
|
|
64
|
+
.filter(u -> u.get("email").equals(email))
|
|
65
|
+
.findFirst()
|
|
66
|
+
.orElseThrow(() -> new IllegalArgumentException("Invalid credentials"));
|
|
67
|
+
|
|
68
|
+
if (!passwordEncoder.matches(password, user.get("password"))) {
|
|
69
|
+
throw new IllegalArgumentException("Invalid credentials");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
String token = jwtUtil.generateToken(user.get("id"), user.get("email"));
|
|
73
|
+
return Map.of("accessToken", token);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public Map<String, Object> getUserProfile(String userId) {
|
|
77
|
+
// 🚨 TODO: Fetch from real DB
|
|
78
|
+
Map<String, String> user = mockDb.stream()
|
|
79
|
+
.filter(u -> u.get("id").equals(userId))
|
|
80
|
+
.findFirst()
|
|
81
|
+
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
|
82
|
+
|
|
83
|
+
return Map.of("id", user.get("id"), "email", user.get("email"));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# 🔐 JWT Authentication Module — Java Spring Boot
|
|
2
|
+
|
|
3
|
+
A complete, modular authentication flow was added to your project inside the `com.example.auth` and `com.example.security` packages.
|
|
4
|
+
|
|
5
|
+
## 📁 Files generated:
|
|
6
|
+
|
|
7
|
+
- `AuthController.java` — REST Controller exposing `/auth/login`, `/auth/register`, and `/auth/me`.
|
|
8
|
+
- `AuthService.java` — Business logic (token generation, password validation). **🚨 ACTION REQUIRED HERE**
|
|
9
|
+
- `dto/LoginRequest.java` and `dto/RegisterRequest.java` — Input validation objects.
|
|
10
|
+
- `security/SecurityConfig.java` — Configures the `SecurityFilterChain` to protect routes.
|
|
11
|
+
- `security/JwtUtil.java` and `security/JwtRequestFilter.java` — Token utility and filter.
|
|
12
|
+
|
|
13
|
+
## 📦 1. Add Dependencies (`pom.xml` / `build.gradle`)
|
|
14
|
+
|
|
15
|
+
You need the `jjwt` library and `spring-boot-starter-security`:
|
|
16
|
+
|
|
17
|
+
```xml
|
|
18
|
+
<dependency>
|
|
19
|
+
<groupId>io.jsonwebtoken</groupId>
|
|
20
|
+
<artifactId>jjwt-api</artifactId>
|
|
21
|
+
<version>0.12.6</version>
|
|
22
|
+
</dependency>
|
|
23
|
+
<dependency>
|
|
24
|
+
<groupId>io.jsonwebtoken</groupId>
|
|
25
|
+
<artifactId>jjwt-impl</artifactId>
|
|
26
|
+
<version>0.12.6</version>
|
|
27
|
+
<scope>runtime</scope>
|
|
28
|
+
</dependency>
|
|
29
|
+
<dependency>
|
|
30
|
+
<groupId>io.jsonwebtoken</groupId>
|
|
31
|
+
<artifactId>jjwt-jackson</artifactId>
|
|
32
|
+
<version>0.12.6</version>
|
|
33
|
+
<scope>runtime</scope>
|
|
34
|
+
</dependency>
|
|
35
|
+
<dependency>
|
|
36
|
+
<groupId>org.springframework.boot</groupId>
|
|
37
|
+
<artifactId>spring-boot-starter-security</artifactId>
|
|
38
|
+
</dependency>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## ⚙️ 2. Environment Variables
|
|
42
|
+
|
|
43
|
+
Add these to your `application.properties` or `application.yml`:
|
|
44
|
+
|
|
45
|
+
```properties
|
|
46
|
+
jwt.secret=your-super-secret-key-change-me-must-be-at-least-32-chars
|
|
47
|
+
jwt.expiration=604800000
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 🚨 3. MANDATORY ACTION: Connect to your Database
|
|
53
|
+
|
|
54
|
+
The generated `AuthService.java` uses an **IN-MEMORY MOCK DATABASE** by default so that the API compiles and runs immediately.
|
|
55
|
+
You **MUST** replace this with calls to your actual Database (e.g., Spring Data JPA `UserRepository`).
|
|
56
|
+
|
|
57
|
+
**Open `AuthService.java` and look for the `🚨 TODO` blocks.**
|
|
58
|
+
|
|
59
|
+
### Example: How to connect it to JPA:
|
|
60
|
+
|
|
61
|
+
```java
|
|
62
|
+
// Inside AuthService.java
|
|
63
|
+
|
|
64
|
+
@Service
|
|
65
|
+
public class AuthService {
|
|
66
|
+
private final UserRepository userRepository; // <-- Inject your real DB Repo
|
|
67
|
+
private final PasswordEncoder passwordEncoder;
|
|
68
|
+
private final JwtUtil jwtUtil;
|
|
69
|
+
|
|
70
|
+
// Remove the mockDb completely!
|
|
71
|
+
|
|
72
|
+
public AuthService(UserRepository userRepository, PasswordEncoder passwordEncoder, JwtUtil jwtUtil) {
|
|
73
|
+
this.userRepository = userRepository;
|
|
74
|
+
this.passwordEncoder = passwordEncoder;
|
|
75
|
+
this.jwtUtil = jwtUtil;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public Map<String, Object> register(String email, String password) {
|
|
79
|
+
if (userRepository.existsByEmail(email)) {
|
|
80
|
+
throw new IllegalArgumentException("User already exists");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
User newUser = new User();
|
|
84
|
+
newUser.setEmail(email);
|
|
85
|
+
newUser.setPassword(passwordEncoder.encode(password));
|
|
86
|
+
userRepository.save(newUser);
|
|
87
|
+
|
|
88
|
+
String token = jwtUtil.generateToken(newUser.getId(), newUser.getEmail());
|
|
89
|
+
// Return response...
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 🔒 4. How to protect other controllers
|
|
97
|
+
|
|
98
|
+
The `SecurityConfig.java` is already checking the JWT token.
|
|
99
|
+
Any new controller you create is **automatically protected**, except for the permitAll routes (`/auth/**` and `/swagger-ui/**`).
|
|
100
|
+
|
|
101
|
+
To access the logged-in user inside any Controller:
|
|
102
|
+
|
|
103
|
+
```java
|
|
104
|
+
import org.springframework.security.core.Authentication;
|
|
105
|
+
import org.springframework.web.bind.annotation.GetMapping;
|
|
106
|
+
import org.springframework.web.bind.annotation.RestController;
|
|
107
|
+
|
|
108
|
+
@RestController
|
|
109
|
+
public class DashboardController {
|
|
110
|
+
|
|
111
|
+
@GetMapping("/api/dashboard")
|
|
112
|
+
public String dashboard(Authentication authentication) {
|
|
113
|
+
// authentication is non-null because the route is protected
|
|
114
|
+
String userId = (String) authentication.getPrincipal();
|
|
115
|
+
|
|
116
|
+
return "Welcome, user ID: " + userId;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package com.example.auth;
|
|
2
|
+
|
|
3
|
+
public class LoginRequest {
|
|
4
|
+
private String email;
|
|
5
|
+
private String password;
|
|
6
|
+
|
|
7
|
+
public String getEmail() {
|
|
8
|
+
return email;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public void setEmail(String email) {
|
|
12
|
+
this.email = email;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public String getPassword() {
|
|
16
|
+
return password;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public void setPassword(String password) {
|
|
20
|
+
this.password = password;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package com.example.auth;
|
|
2
|
+
|
|
3
|
+
public class RegisterRequest {
|
|
4
|
+
private String email;
|
|
5
|
+
private String password;
|
|
6
|
+
|
|
7
|
+
public String getEmail() {
|
|
8
|
+
return email;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public void setEmail(String email) {
|
|
12
|
+
this.email = email;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public String getPassword() {
|
|
16
|
+
return password;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public void setPassword(String password) {
|
|
20
|
+
this.password = password;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
package com.example.security;
|
|
2
|
+
|
|
3
|
+
import jakarta.servlet.FilterChain;
|
|
4
|
+
import jakarta.servlet.ServletException;
|
|
5
|
+
import jakarta.servlet.http.HttpServletRequest;
|
|
6
|
+
import jakarta.servlet.http.HttpServletResponse;
|
|
7
|
+
import org.springframework.beans.factory.annotation.Autowired;
|
|
8
|
+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
9
|
+
import org.springframework.security.core.context.SecurityContextHolder;
|
|
10
|
+
import org.springframework.stereotype.Component;
|
|
11
|
+
import org.springframework.web.filter.OncePerRequestFilter;
|
|
12
|
+
|
|
13
|
+
import java.io.IOException;
|
|
14
|
+
import java.util.Collections;
|
|
15
|
+
|
|
16
|
+
@Component
|
|
17
|
+
public class JwtRequestFilter extends OncePerRequestFilter {
|
|
18
|
+
|
|
19
|
+
@Autowired
|
|
20
|
+
private JwtUtil jwtUtil;
|
|
21
|
+
|
|
22
|
+
@Override
|
|
23
|
+
protected void doFilterInternal(
|
|
24
|
+
HttpServletRequest request,
|
|
25
|
+
HttpServletResponse response,
|
|
26
|
+
FilterChain filterChain) throws ServletException, IOException {
|
|
27
|
+
final String header = request.getHeader("Authorization");
|
|
28
|
+
|
|
29
|
+
if (header != null && header.startsWith("Bearer ")) {
|
|
30
|
+
String token = header.substring(7);
|
|
31
|
+
try {
|
|
32
|
+
String userId = jwtUtil.extractUserId(token);
|
|
33
|
+
if (userId != null && !jwtUtil.isTokenExpired(token)) {
|
|
34
|
+
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userId, null,
|
|
35
|
+
Collections.emptyList());
|
|
36
|
+
SecurityContextHolder.getContext().setAuthentication(auth);
|
|
37
|
+
}
|
|
38
|
+
} catch (Exception ignored) {
|
|
39
|
+
// Invalid token — continue without authentication (handled by SecurityConfig)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
filterChain.doFilter(request, response);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
package com.example.security;
|
|
2
|
+
|
|
3
|
+
import io.jsonwebtoken.Claims;
|
|
4
|
+
import io.jsonwebtoken.Jwts;
|
|
5
|
+
import io.jsonwebtoken.SignatureAlgorithm;
|
|
6
|
+
import io.jsonwebtoken.security.Keys;
|
|
7
|
+
import org.springframework.beans.factory.annotation.Value;
|
|
8
|
+
import org.springframework.stereotype.Component;
|
|
9
|
+
|
|
10
|
+
import javax.crypto.SecretKey;
|
|
11
|
+
import java.nio.charset.StandardCharsets;
|
|
12
|
+
import java.util.Date;
|
|
13
|
+
import java.util.HashMap;
|
|
14
|
+
import java.util.Map;
|
|
15
|
+
|
|
16
|
+
@Component
|
|
17
|
+
public class JwtUtil {
|
|
18
|
+
|
|
19
|
+
@Value("${jwt.secret:change-me-in-production}")
|
|
20
|
+
private String secret;
|
|
21
|
+
|
|
22
|
+
@Value("${jwt.expiration:604800000}") // 7 days in ms
|
|
23
|
+
private long expirationMs;
|
|
24
|
+
|
|
25
|
+
private SecretKey getSigningKey() {
|
|
26
|
+
// Ensure secret is long enough for HS256
|
|
27
|
+
String paddedSecret = secret.length() < 32 ? String.format("%-32s", secret).replace(' ', '0') : secret;
|
|
28
|
+
return Keys.hmacShaKeyFor(paddedSecret.getBytes(StandardCharsets.UTF_8));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public String generateToken(String userId, String email) {
|
|
32
|
+
Map<String, Object> claims = new HashMap<>();
|
|
33
|
+
claims.put("email", email);
|
|
34
|
+
|
|
35
|
+
return Jwts.builder()
|
|
36
|
+
.setClaims(claims)
|
|
37
|
+
.setSubject(userId)
|
|
38
|
+
.setIssuedAt(new Date())
|
|
39
|
+
.setExpiration(new Date(System.currentTimeMillis() + expirationMs))
|
|
40
|
+
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
|
|
41
|
+
.compact();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public Claims extractClaims(String token) {
|
|
45
|
+
return Jwts.parserBuilder()
|
|
46
|
+
.setSigningKey(getSigningKey())
|
|
47
|
+
.build()
|
|
48
|
+
.parseClaimsJws(token)
|
|
49
|
+
.getBody();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public String extractUserId(String token) {
|
|
53
|
+
return extractClaims(token).getSubject();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public boolean isTokenExpired(String token) {
|
|
57
|
+
return extractClaims(token).getExpiration().before(new Date());
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
package com.example.security;
|
|
2
|
+
|
|
3
|
+
import org.springframework.context.annotation.Bean;
|
|
4
|
+
import org.springframework.context.annotation.Configuration;
|
|
5
|
+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
6
|
+
import org.springframework.security.config.http.SessionCreationPolicy;
|
|
7
|
+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
8
|
+
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
9
|
+
import org.springframework.security.web.SecurityFilterChain;
|
|
10
|
+
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
11
|
+
|
|
12
|
+
@Configuration
|
|
13
|
+
public class SecurityConfig {
|
|
14
|
+
|
|
15
|
+
private final JwtRequestFilter jwtRequestFilter;
|
|
16
|
+
|
|
17
|
+
public SecurityConfig(JwtRequestFilter jwtRequestFilter) {
|
|
18
|
+
this.jwtRequestFilter = jwtRequestFilter;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@Bean
|
|
22
|
+
public PasswordEncoder passwordEncoder() {
|
|
23
|
+
return new BCryptPasswordEncoder();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@Bean
|
|
27
|
+
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|
28
|
+
http
|
|
29
|
+
.csrf(csrf -> csrf.disable())
|
|
30
|
+
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
|
31
|
+
.authorizeHttpRequests(auth -> auth
|
|
32
|
+
.requestMatchers("/auth/login", "/auth/register").permitAll()
|
|
33
|
+
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
|
34
|
+
.anyRequest().authenticated())
|
|
35
|
+
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
|
|
36
|
+
|
|
37
|
+
return http.build();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# 🔐 JWT Authentication Module — NestJS
|
|
2
|
+
|
|
3
|
+
A complete, modular authentication flow was added to your project inside the `auth/` directory.
|
|
4
|
+
|
|
5
|
+
## 📁 Files generated:
|
|
6
|
+
|
|
7
|
+
- `auth.module.ts` — The NestJS Module combining Controllers, Services, and JWT configurations.
|
|
8
|
+
- `auth.controller.ts` — Exposes the `/auth/login`, `/auth/register`, and `/auth/me` endpoints.
|
|
9
|
+
- `auth.service.ts` — Business logic (token generation, password validation). **🚨 ACTION REQUIRED HERE**
|
|
10
|
+
- `jwt.strategy.ts` — Connects Passport to handle `Bearer Token` parsing automatically.
|
|
11
|
+
- `jwt-auth.guard.ts` — The `@UseGuards(JwtAuthGuard)` decorator mapped directly to JWT.
|
|
12
|
+
- `dto/*.dto.ts` — Inputs for validation.
|
|
13
|
+
|
|
14
|
+
## 📦 1. Install Dependencies
|
|
15
|
+
|
|
16
|
+
You need to install Passport, JWT, and bcrypt:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @nestjs/passport @nestjs/jwt passport passport-jwt bcryptjs
|
|
20
|
+
npm install -D @types/passport-jwt @types/bcryptjs
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## ⚙️ 2. Environment Variables
|
|
24
|
+
|
|
25
|
+
Add these to your `.env` file at the root of your project:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
JWT_SECRET=your-super-secret-key-change-me
|
|
29
|
+
JWT_EXPIRES_IN=7d
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
> 💡 **Tip:** Generate a strong random secret by running this in your terminal:
|
|
33
|
+
> `node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"`
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 🚨 3. MANDATORY ACTION: Connect to your Database
|
|
38
|
+
|
|
39
|
+
The generated `auth.service.ts` uses an **IN-MEMORY MOCK DATABASE** by default so that it compiles and runs immediately.
|
|
40
|
+
You **MUST** replace this with calls to your actual Database (TypeORM, Prisma, Mongoose, etc).
|
|
41
|
+
|
|
42
|
+
**Open `auth/auth.service.ts` and look for the `🚨 TODO` blocks.**
|
|
43
|
+
|
|
44
|
+
### Example: How to connect it to TypeORM:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// Inside auth.service.ts
|
|
48
|
+
|
|
49
|
+
@Injectable()
|
|
50
|
+
export class AuthService {
|
|
51
|
+
constructor(
|
|
52
|
+
@InjectRepository(User)
|
|
53
|
+
private usersRepository: Repository<User>, // <-- Inject real DB
|
|
54
|
+
private jwtService: JwtService
|
|
55
|
+
) {}
|
|
56
|
+
|
|
57
|
+
async register(email: string, pass: string) {
|
|
58
|
+
const existingUser = await this.usersRepository.findOne({ where: { email } });
|
|
59
|
+
if (existingUser) throw new ConflictException('User already exists');
|
|
60
|
+
|
|
61
|
+
const hashedPassword = await bcrypt.hash(pass, 12);
|
|
62
|
+
|
|
63
|
+
const newUser = this.usersRepository.create({ email, password: hashedPassword });
|
|
64
|
+
await this.usersRepository.save(newUser);
|
|
65
|
+
|
|
66
|
+
const payload = { userId: newUser.id, email: newUser.email };
|
|
67
|
+
return { access_token: this.jwtService.sign(payload) };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## ⚡ 4. Plug the Module into your App
|
|
75
|
+
|
|
76
|
+
Currently, the `AuthModule` exists, but your root App Module doesn't know about it.
|
|
77
|
+
You must register it in your `app.module.ts`.
|
|
78
|
+
|
|
79
|
+
**Open `src/app.module.ts` and add:**
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { Module } from '@nestjs/common';
|
|
83
|
+
import { AuthModule } from './auth/auth.module'; // <-- IMPORT HERE
|
|
84
|
+
|
|
85
|
+
@Module({
|
|
86
|
+
imports: [
|
|
87
|
+
AuthModule, // <-- ADD HERE
|
|
88
|
+
// ...other modules
|
|
89
|
+
],
|
|
90
|
+
})
|
|
91
|
+
export class AppModule {}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 🔒 5. How to protect other controllers
|
|
95
|
+
|
|
96
|
+
You can secure any other route by decorating it or its controller with `@UseGuards(JwtAuthGuard)`:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { Controller, Get, UseGuards, Request } from '@nestjs/common';
|
|
100
|
+
import { JwtAuthGuard } from './auth/jwt-auth.guard';
|
|
101
|
+
|
|
102
|
+
@Controller('admin')
|
|
103
|
+
@UseGuards(JwtAuthGuard) // Protects all routes inside this controller
|
|
104
|
+
export class AdminController {
|
|
105
|
+
|
|
106
|
+
@Get('dashboard')
|
|
107
|
+
getDashboard(@Request() req) {
|
|
108
|
+
// You now have access to the logged-in user's payload!
|
|
109
|
+
return { secretData: `Welcome, user ${req.user.userId}` };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Controller, Post, Get, Body, UseGuards, Request } from '@nestjs/common';
|
|
2
|
+
import { AuthService } from './auth.service';
|
|
3
|
+
import { JwtAuthGuard } from './jwt-auth.guard';
|
|
4
|
+
import { LoginDto } from './dto/login.dto';
|
|
5
|
+
import { RegisterDto } from './dto/register.dto';
|
|
6
|
+
|
|
7
|
+
@Controller('auth')
|
|
8
|
+
export class AuthController {
|
|
9
|
+
constructor(private readonly authService: AuthService) { }
|
|
10
|
+
|
|
11
|
+
@Post('register')
|
|
12
|
+
async register(@Body() registerDto: RegisterDto) {
|
|
13
|
+
return this.authService.register(registerDto.email, registerDto.password);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Post('login')
|
|
17
|
+
async login(@Body() loginDto: LoginDto) {
|
|
18
|
+
return this.authService.login(loginDto.email, loginDto.password);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@UseGuards(JwtAuthGuard)
|
|
22
|
+
@Get('me')
|
|
23
|
+
getProfile(@Request() req) {
|
|
24
|
+
// req.user is extracted by the JwtStrategy from the Bearer token
|
|
25
|
+
return this.authService.getUserProfile(req.user.userId);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { JwtModule } from '@nestjs/jwt';
|
|
3
|
+
import { PassportModule } from '@nestjs/passport';
|
|
4
|
+
import { AuthService } from './auth.service';
|
|
5
|
+
import { AuthController } from './auth.controller';
|
|
6
|
+
import { JwtStrategy } from './jwt.strategy';
|
|
7
|
+
|
|
8
|
+
@Module({
|
|
9
|
+
imports: [
|
|
10
|
+
PassportModule,
|
|
11
|
+
JwtModule.register({
|
|
12
|
+
secret: process.env.JWT_SECRET || 'change-me-in-production',
|
|
13
|
+
signOptions: { expiresIn: process.env.JWT_EXPIRES_IN || '7d' },
|
|
14
|
+
}),
|
|
15
|
+
],
|
|
16
|
+
controllers: [AuthController],
|
|
17
|
+
providers: [AuthService, JwtStrategy],
|
|
18
|
+
exports: [AuthService],
|
|
19
|
+
})
|
|
20
|
+
export class AuthModule { }
|