nodejs-quickstart-structure 2.0.1 → 2.1.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.
- package/CHANGELOG.md +14 -0
- package/README.md +43 -39
- package/bin/index.js +5 -2
- package/lib/generator.js +10 -4
- package/lib/modules/app-setup.js +76 -6
- package/lib/modules/auth-setup.js +143 -0
- package/lib/modules/caching-setup.js +8 -1
- package/lib/modules/database-setup.js +2 -1
- package/lib/modules/project-setup.js +1 -0
- package/lib/prompts.js +39 -0
- package/package.json +5 -4
- package/templates/clean-architecture/js/src/domain/models/User.js +3 -1
- package/templates/clean-architecture/js/src/index.js.ejs +2 -0
- package/templates/clean-architecture/js/src/infrastructure/config/env.js.ejs +12 -3
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +25 -2
- package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +27 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.js.ejs +3 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/server.spec.js.ejs +49 -0
- package/templates/clean-architecture/js/src/infrastructure/webserver/swagger.spec.js.ejs +14 -0
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +41 -4
- package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +69 -4
- package/templates/clean-architecture/js/src/interfaces/graphql/context.js.ejs +13 -6
- package/templates/clean-architecture/js/src/interfaces/graphql/context.spec.js.ejs +38 -21
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.js.ejs +10 -5
- package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs +32 -10
- package/templates/clean-architecture/js/src/interfaces/graphql/typeDefs/user.types.js.ejs +1 -1
- package/templates/clean-architecture/js/src/interfaces/routes/api.js.ejs +15 -0
- package/templates/clean-architecture/js/src/interfaces/routes/api.spec.js.ejs +4 -0
- package/templates/clean-architecture/js/src/usecases/CreateUser.js.ejs +34 -0
- package/templates/clean-architecture/js/src/usecases/CreateUser.spec.js.ejs +3 -2
- package/templates/clean-architecture/js/src/usecases/DeleteUser.js.ejs +27 -0
- package/templates/clean-architecture/js/src/usecases/GetAllUsers.js.ejs +36 -0
- package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +14 -0
- package/templates/clean-architecture/js/src/usecases/GetUserById.js.ejs +36 -0
- package/templates/clean-architecture/js/src/usecases/GetUserById.spec.js.ejs +48 -0
- package/templates/clean-architecture/js/src/usecases/UpdateUser.js.ejs +28 -0
- package/templates/clean-architecture/js/src/utils/errorMessages.js +1 -0
- package/templates/clean-architecture/js/src/utils/httpCodes.js +2 -0
- package/templates/clean-architecture/ts/src/config/env.ts.ejs +12 -3
- package/templates/clean-architecture/ts/src/domain/user.ts +3 -1
- package/templates/clean-architecture/ts/src/index.ts.ejs +4 -0
- package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +55 -9
- package/templates/clean-architecture/ts/src/infrastructure/repositories/userRepository.ts.ejs +32 -3
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +26 -6
- package/templates/clean-architecture/ts/src/interfaces/controllers/userController.ts.ejs +57 -15
- package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +38 -23
- package/templates/clean-architecture/ts/src/interfaces/graphql/context.ts.ejs +14 -8
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +33 -10
- package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.ts.ejs +15 -5
- package/templates/clean-architecture/ts/src/interfaces/graphql/typeDefs/user.types.ts.ejs +1 -1
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +9 -1
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts.ejs +16 -0
- package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +3 -2
- package/templates/clean-architecture/ts/src/usecases/createUser.ts.ejs +35 -0
- package/templates/clean-architecture/ts/src/usecases/deleteUser.spec.ts.ejs +1 -0
- package/templates/clean-architecture/ts/src/usecases/deleteUser.ts.ejs +24 -0
- package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts.ejs +21 -0
- package/templates/clean-architecture/ts/src/usecases/getUserById.spec.ts.ejs +47 -0
- package/templates/clean-architecture/ts/src/usecases/getUserById.ts.ejs +23 -0
- package/templates/clean-architecture/ts/src/usecases/updateUser.spec.ts.ejs +1 -0
- package/templates/clean-architecture/ts/src/usecases/updateUser.ts.ejs +25 -0
- package/templates/clean-architecture/ts/src/utils/errorMessages.ts +1 -0
- package/templates/clean-architecture/ts/src/utils/httpCodes.ts +1 -0
- package/templates/common/.cursorrules.ejs +9 -0
- package/templates/common/.env.example.ejs +17 -10
- package/templates/common/README.md.ejs +63 -18
- package/templates/common/auth/js/controllers/authController.js.ejs +168 -0
- package/templates/common/auth/js/controllers/authController.spec.js.ejs +148 -0
- package/templates/common/auth/js/middleware/authMiddleware.js.ejs +58 -0
- package/templates/common/auth/js/middleware/authMiddleware.spec.js.ejs +108 -0
- package/templates/common/auth/js/routes/authRoutes.js.ejs +16 -0
- package/templates/common/auth/js/services/jwtService.js.ejs +54 -0
- package/templates/common/auth/js/services/jwtService.spec.js.ejs +84 -0
- package/templates/common/auth/ts/controllers/authController.spec.ts.ejs +161 -0
- package/templates/common/auth/ts/controllers/authController.ts.ejs +165 -0
- package/templates/common/auth/ts/middleware/authMiddleware.spec.ts.ejs +128 -0
- package/templates/common/auth/ts/middleware/authMiddleware.ts.ejs +59 -0
- package/templates/common/auth/ts/routes/authRoutes.ts.ejs +20 -0
- package/templates/common/auth/ts/services/jwtService.spec.ts.ejs +89 -0
- package/templates/common/auth/ts/services/jwtService.ts.ejs +60 -0
- package/templates/common/caching/clean/js/CreateUser.js.ejs +14 -5
- package/templates/common/caching/clean/js/DeleteUser.js.ejs +2 -1
- package/templates/common/caching/clean/js/GetUserById.js.ejs +39 -0
- package/templates/common/caching/clean/js/UpdateUser.js.ejs +2 -1
- package/templates/common/caching/clean/ts/createUser.ts.ejs +14 -6
- package/templates/common/caching/clean/ts/deleteUser.ts.ejs +2 -1
- package/templates/common/caching/clean/ts/getUserById.ts.ejs +32 -0
- package/templates/common/caching/clean/ts/updateUser.ts.ejs +2 -1
- package/templates/common/database/js/models/User.js.ejs +14 -1
- package/templates/common/database/js/models/User.js.mongoose.ejs +7 -0
- package/templates/common/database/js/models/User.spec.js.ejs +12 -0
- package/templates/common/database/ts/models/User.spec.ts.ejs +10 -0
- package/templates/common/database/ts/models/User.ts.ejs +17 -0
- package/templates/common/database/ts/models/User.ts.mongoose.ejs +8 -0
- package/templates/common/docker-compose.yml.ejs +12 -0
- package/templates/common/ecosystem.config.js.ejs +9 -3
- package/templates/common/eslint.config.mjs.ejs +3 -0
- package/templates/common/jest.config.js.ejs +11 -9
- package/templates/common/kafka/js/messaging/baseConsumer.js.ejs +1 -1
- package/templates/common/kafka/js/services/kafkaService.js.ejs +1 -1
- package/templates/common/migrations/init.js.ejs +5 -4
- package/templates/common/package.json.ejs +8 -1
- package/templates/common/prompts/project-context.md.ejs +8 -1
- package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +149 -107
- package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +88 -47
- package/templates/common/swagger.yml.ejs +148 -0
- package/templates/common/tsconfig.eslint.json +15 -0
- package/templates/common/tsconfig.json +2 -1
- package/templates/common/views/ejs/index.ejs +264 -30
- package/templates/common/views/ejs/login.ejs.ejs +244 -0
- package/templates/common/views/ejs/signup.ejs.ejs +282 -0
- package/templates/common/views/pug/index.pug +269 -38
- package/templates/common/views/pug/login.pug.ejs +195 -0
- package/templates/common/views/pug/signup.pug.ejs +241 -0
- package/templates/db/mysql/V1__Initial_Setup.sql.ejs +6 -0
- package/templates/db/postgres/V1__Initial_Setup.sql.ejs +6 -0
- package/templates/mvc/js/src/config/env.js.ejs +12 -3
- package/templates/mvc/js/src/controllers/userController.js.ejs +29 -5
- package/templates/mvc/js/src/controllers/userController.spec.js.ejs +27 -12
- package/templates/mvc/js/src/graphql/context.js.ejs +14 -3
- package/templates/mvc/js/src/graphql/context.spec.js.ejs +36 -21
- package/templates/mvc/js/src/graphql/resolvers/user.resolvers.js.ejs +10 -5
- package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +32 -10
- package/templates/mvc/js/src/graphql/typeDefs/user.types.js.ejs +1 -1
- package/templates/mvc/js/src/index.js.ejs +16 -3
- package/templates/mvc/js/src/routes/api.js.ejs +14 -0
- package/templates/mvc/js/src/routes/api.spec.js.ejs +3 -0
- package/templates/mvc/js/src/utils/errorMessages.js +1 -0
- package/templates/mvc/js/src/utils/httpCodes.js +1 -0
- package/templates/mvc/ts/src/config/env.ts.ejs +12 -3
- package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +95 -7
- package/templates/mvc/ts/src/controllers/userController.ts.ejs +68 -11
- package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +36 -23
- package/templates/mvc/ts/src/graphql/context.ts.ejs +15 -6
- package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +32 -10
- package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.ts.ejs +15 -5
- package/templates/mvc/ts/src/graphql/typeDefs/user.types.ts.ejs +1 -1
- package/templates/mvc/ts/src/index.ts.ejs +15 -3
- package/templates/mvc/ts/src/routes/api.spec.ts.ejs +6 -0
- package/templates/mvc/ts/src/routes/api.ts.ejs +15 -0
- package/templates/mvc/ts/src/utils/errorMessages.ts +1 -0
- package/templates/mvc/ts/src/utils/httpCodes.ts +1 -0
- package/templates/clean-architecture/js/src/interfaces/routes/api.js +0 -12
- package/templates/clean-architecture/js/src/usecases/CreateUser.js +0 -14
- package/templates/clean-architecture/js/src/usecases/DeleteUser.js +0 -11
- package/templates/clean-architecture/js/src/usecases/GetAllUsers.js +0 -12
- package/templates/clean-architecture/js/src/usecases/UpdateUser.js +0 -11
- package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.ts +0 -13
- package/templates/clean-architecture/ts/src/usecases/createUser.ts +0 -13
- package/templates/clean-architecture/ts/src/usecases/deleteUser.ts +0 -9
- package/templates/clean-architecture/ts/src/usecases/getAllUsers.ts +0 -10
- package/templates/clean-architecture/ts/src/usecases/updateUser.ts +0 -9
- package/templates/mvc/js/src/routes/api.js +0 -10
- package/templates/mvc/ts/src/routes/api.ts +0 -12
|
@@ -58,3 +58,12 @@ When indexing or searching the workspace, ignore the following paths to prevent
|
|
|
58
58
|
- `controllers`: Request handlers and business logic.
|
|
59
59
|
- `routes`: Define endpoints routing to controllers.
|
|
60
60
|
<% } -%>
|
|
61
|
+
|
|
62
|
+
### 5. Security & Authentication
|
|
63
|
+
<% if (auth.includes('JWT')) { -%>
|
|
64
|
+
- **Protected Routes**: When adding new routes, consider if they need `authMiddleware`.
|
|
65
|
+
- **User Identity**: Extract user info from `req.user` (populated by middleware).
|
|
66
|
+
- **Sensitive Data**: Never return passwords in API responses (ensure `.toJSON()` or DTOs handle this).
|
|
67
|
+
<% } -%>
|
|
68
|
+
- **Input Validation**: Always validate user input using schemas or controller-level checks.
|
|
69
|
+
- **Sanitization**: Ensure inputs are sanitized before being used in DB queries (though our ORMs like Sequelize/Mongoose handle much of this).
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Application
|
|
2
2
|
PORT=3000
|
|
3
3
|
NODE_ENV=development
|
|
4
|
-
|
|
5
4
|
<%_ if (database !== 'None') { -%>
|
|
5
|
+
|
|
6
6
|
# Database
|
|
7
7
|
<%_ if (database === 'MySQL') { -%>
|
|
8
8
|
DB_HOST=localhost
|
|
@@ -10,32 +10,39 @@ DB_PORT=3306
|
|
|
10
10
|
DB_USER=root
|
|
11
11
|
DB_PASSWORD=root
|
|
12
12
|
DB_NAME=<%= dbName %>
|
|
13
|
-
<%
|
|
13
|
+
<% } -%>
|
|
14
14
|
<%_ if (database === 'PostgreSQL') { -%>
|
|
15
15
|
DB_HOST=localhost
|
|
16
16
|
DB_PORT=5432
|
|
17
17
|
DB_USER=postgres
|
|
18
18
|
DB_PASSWORD=root
|
|
19
19
|
DB_NAME=<%= dbName %>
|
|
20
|
-
<%
|
|
20
|
+
<% } -%>
|
|
21
21
|
<%_ if (database === 'MongoDB') { -%>
|
|
22
22
|
DB_HOST=localhost
|
|
23
23
|
DB_PORT=27017
|
|
24
24
|
DB_NAME=<%= dbName %>
|
|
25
|
-
<%
|
|
26
|
-
<%
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
<% } -%>
|
|
26
|
+
<% } -%>
|
|
29
27
|
<%_ if (communication === 'Kafka') { -%>
|
|
28
|
+
|
|
30
29
|
# Communication
|
|
31
30
|
KAFKA_BROKER=localhost:9093
|
|
32
31
|
KAFKA_CLIENT_ID=<%= projectName %>
|
|
33
32
|
KAFKA_GROUP_ID=<%= projectName %>-group
|
|
34
|
-
<%
|
|
35
|
-
|
|
33
|
+
<% } -%>
|
|
36
34
|
<%_ if (caching === 'Redis') { -%>
|
|
35
|
+
|
|
37
36
|
# Caching
|
|
38
37
|
REDIS_HOST=localhost
|
|
39
38
|
REDIS_PORT=6379
|
|
40
39
|
REDIS_PASSWORD=
|
|
41
|
-
<%
|
|
40
|
+
<% } -%>
|
|
41
|
+
<%_ if (auth.includes('JWT')) { -%>
|
|
42
|
+
|
|
43
|
+
# Authentication
|
|
44
|
+
JWT_SECRET=your_jwt_secret_key_here_change_it
|
|
45
|
+
JWT_REFRESH_SECRET=your_jwt_refresh_secret_key_here_change_it
|
|
46
|
+
JWT_EXPIRES_IN=15m
|
|
47
|
+
JWT_REFRESH_EXPIRES_IN=7d
|
|
48
|
+
<% } -%>
|
|
@@ -29,6 +29,7 @@ This project follows a strict **7-Step Production-Ready Process** to ensure qual
|
|
|
29
29
|
|
|
30
30
|
- **Architecture**: <%= architecture %> (<% if (architecture === 'Clean Architecture') { %>Domain, UseCases, Infrastructure<% } else { %>MVC Pattern<% } %>).
|
|
31
31
|
- **Database**: <%= database %> <% if (database !== 'None') { %>(via <%= database === 'MongoDB' ? 'Mongoose' : 'Sequelize' %>)<% } %>.
|
|
32
|
+
<% if (auth.includes('JWT')) { %>- **Authentication**: JWT-based Auth (Sign Up, Login, Protected Routes).<% } %>
|
|
32
33
|
- **Security**: Helmet, CORS, Rate Limiting, HPP, Snyk SCA.
|
|
33
34
|
- **Quality**: 80%+ Test Coverage, Eslint, Prettier, Husky.
|
|
34
35
|
- **DevOps**: Multi-stage Docker, CI/CD ready (GitHub/GitLab/Jenkins/Bitbucket/CircleCI).
|
|
@@ -95,6 +96,21 @@ Microservices communication handled via **Kafka**.
|
|
|
95
96
|
API is exposed via **GraphQL**.
|
|
96
97
|
The Apollo Sandbox UI for API exploration and documentation is available natively, fully embedded for offline development:
|
|
97
98
|
- **URL**: `http://localhost:3000/graphql` (Dynamic based on PORT)
|
|
99
|
+
|
|
100
|
+
<%_ if (auth.includes('JWT')) { _%>
|
|
101
|
+
### 🔐 Authorization
|
|
102
|
+
To access protected resolvers (Query: `getAllUsers`, Mutations: `updateUser`, `deleteUser`), you must provide a valid JWT token in the headers.
|
|
103
|
+
|
|
104
|
+
**How to authenticate:**
|
|
105
|
+
1. Use the REST login endpoint: `POST /api/auth/login` (with your email and password).
|
|
106
|
+
2. Copy the `accessToken` from the response.
|
|
107
|
+
3. In the Apollo Sandbox "HTTP Headers" tab (bottom section), add:
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"Authorization": "Bearer YOUR_ACCESS_TOKEN_HERE"
|
|
111
|
+
}
|
|
112
|
+
```<%_ } _%>
|
|
113
|
+
|
|
98
114
|
If you are opening `http://localhost:3000/graphql` in your browser, you can directly run the following in the Apollo Sandbox UI:
|
|
99
115
|
|
|
100
116
|
**Query to get all users:**
|
|
@@ -111,7 +127,7 @@ query GetAllUsers {
|
|
|
111
127
|
**Mutation to create a user:**
|
|
112
128
|
```graphql
|
|
113
129
|
mutation CreateUser {
|
|
114
|
-
createUser(name: "John Doe", email: "john@example.com") {
|
|
130
|
+
createUser(name: "John Doe", email: "john@example.com"<%_ if (auth.includes('JWT')) { _%>, password: "yourpassword123"<%_ } _%>) {
|
|
115
131
|
id
|
|
116
132
|
name
|
|
117
133
|
email
|
|
@@ -119,7 +135,7 @@ mutation CreateUser {
|
|
|
119
135
|
}
|
|
120
136
|
```
|
|
121
137
|
|
|
122
|
-
**Mutation to update a user:**
|
|
138
|
+
**Mutation to update a user (Protected):**
|
|
123
139
|
```graphql
|
|
124
140
|
mutation UpdateUser {
|
|
125
141
|
updateUser(id: "1", name: "John Updated") {
|
|
@@ -130,7 +146,7 @@ mutation UpdateUser {
|
|
|
130
146
|
}
|
|
131
147
|
```
|
|
132
148
|
|
|
133
|
-
**Mutation to delete a user:**
|
|
149
|
+
**Mutation to delete a user (Protected):**
|
|
134
150
|
```graphql
|
|
135
151
|
mutation DeleteUser {
|
|
136
152
|
deleteUser(id: "1")
|
|
@@ -143,32 +159,61 @@ A Swagger UI for API documentation is available at:
|
|
|
143
159
|
|
|
144
160
|
### 🛣️ User Endpoints:
|
|
145
161
|
- `GET /api/users`: List all users.
|
|
162
|
+
- `GET /api/users/:id`: Get a user by ID.
|
|
146
163
|
- `POST /api/users`: Create a new user.
|
|
147
164
|
- `PATCH /api/users/:id`: Partially update a user.
|
|
148
165
|
- `DELETE /api/users/:id`: Delete a user (Soft Delete).
|
|
149
|
-
<%_
|
|
150
|
-
|
|
166
|
+
<%_ if (auth.includes('JWT')) { %>
|
|
167
|
+
### 🔐 Auth Endpoints:
|
|
168
|
+
- `POST /api/auth/login`: Exchange credentials for a short-lived `accessToken` and a long-lived `refreshToken`.
|
|
169
|
+
- `POST /api/auth/refresh`: Submit a `refreshToken` to receive a new pair of tokens. (Includes theft-detection logic).
|
|
170
|
+
- `POST /api/auth/logout`: Revoke (blacklist) the active `accessToken` and delete the `refreshToken`.
|
|
171
|
+
- `POST /api/users`: Acts as Sign Up when password is provided.
|
|
172
|
+
*Note: To access protected user endpoints (GET/PATCH/DELETE), include `Authorization: Bearer <your_accessToken>` in the headers.*
|
|
173
|
+
<% } -%>
|
|
174
|
+
<% } -%>
|
|
151
175
|
<% if (communication === 'Kafka') { -%>
|
|
152
176
|
## 📡 Testing Kafka Asynchronous Flow
|
|
153
177
|
This project demonstrates a production-ready Kafka flow:
|
|
154
|
-
1. **Producer**: When a user is created via the API, a `USER_CREATED`
|
|
155
|
-
2. **Consumer**: `WelcomeEmailConsumer` listens to `user-topic` and simulates sending an email.
|
|
178
|
+
1. **Producer**: When a user is created, updated, or deleted via the API, a corresponding event (`USER_CREATED`, `USER_UPDATED`, `USER_DELETED`) is sent to `user-topic`.
|
|
179
|
+
2. **Consumer**: `WelcomeEmailConsumer` listens to `user-topic` and simulates processing (e.g., sending an email on creation).
|
|
156
180
|
|
|
157
181
|
### How to verify:
|
|
158
182
|
1. Ensure infrastructure is running: `docker-compose up -d<% if (database !== 'None') { %> db<% } %><% if (caching === 'Redis') { %> redis<% } %><% if (communication === 'Kafka') { %> kafka<% } %>`
|
|
159
183
|
2. Start the app: `npm run dev`
|
|
160
|
-
3. Trigger
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
184
|
+
3. Trigger internal events:
|
|
185
|
+
|
|
186
|
+
**Create User (Sign Up - Public):**
|
|
187
|
+
```bash
|
|
188
|
+
curl -X POST http://localhost:3000/api/users \
|
|
189
|
+
-H "Content-Type: application/json" \
|
|
190
|
+
-d '{"name": "Kafka Tester", "email": "kafka@example.com"<%_ if (auth.includes('JWT')) { _%>, "password": "password123"<%_ } _%>}'
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
<%_ if (auth.includes('JWT')) { _%>
|
|
194
|
+
**Update User (Protected - Requires Auth):**
|
|
195
|
+
1. Login to get token: `POST /api/auth/login`
|
|
196
|
+
2. Update user with token:
|
|
197
|
+
```bash
|
|
198
|
+
curl -X PATCH http://localhost:3000/api/users/1 \
|
|
199
|
+
-H "Authorization: Bearer <YOUR_TOKEN>" \
|
|
200
|
+
-H "Content-Type: application/json" \
|
|
201
|
+
-d '{"name": "Updated Name"}'
|
|
202
|
+
```
|
|
203
|
+
<%_ } else { _%>
|
|
204
|
+
**Update User:**
|
|
205
|
+
```bash
|
|
206
|
+
curl -X PATCH http://localhost:3000/api/users/1 \
|
|
207
|
+
-H "Content-Type: application/json" \
|
|
208
|
+
-d '{"name": "Updated Name"}'
|
|
209
|
+
```
|
|
210
|
+
<%_ } _%>
|
|
166
211
|
4. Observe the logs:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
212
|
+
```text
|
|
213
|
+
[Kafka] Producer: Sent USER_CREATED event for 'kafka@example.com'
|
|
214
|
+
[Kafka] Consumer: Received USER_CREATED.
|
|
215
|
+
[Kafka] Consumer: 📧 Sending welcome email to 'kafka@example.com'... Done!
|
|
216
|
+
```
|
|
172
217
|
|
|
173
218
|
### 🛠️ Kafka Troubleshooting
|
|
174
219
|
If the connection or events are failing:
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
const bcrypt = require('bcryptjs');
|
|
2
|
+
<% if (architecture === 'MVC') { -%>
|
|
3
|
+
const User = require('../models/User');
|
|
4
|
+
const JwtService = require('../services/jwtService');
|
|
5
|
+
<% if (caching !== 'None') { -%>
|
|
6
|
+
const cacheService = require('<% if (caching === "Redis") { %>../config/redisClient<% } else { %>../config/memoryCache<% } %>');
|
|
7
|
+
<% } -%>
|
|
8
|
+
const logger = require('../utils/logger');
|
|
9
|
+
<% } else { -%>
|
|
10
|
+
const User = require('../../../infrastructure/database/models/User');
|
|
11
|
+
const JwtService = require('../../../infrastructure/auth/jwtService');
|
|
12
|
+
<% if (caching !== 'None') { -%>
|
|
13
|
+
const cacheService = require('<% if (caching === "Redis") { %>../../../infrastructure/caching/redisClient<% } else { %>../../../infrastructure/caching/memoryCache<% } %>');
|
|
14
|
+
<% } -%>
|
|
15
|
+
const logger = require('../../../infrastructure/log/logger');
|
|
16
|
+
<% } -%>
|
|
17
|
+
const HTTP_STATUS = require('<% if (architecture === "MVC") { %>../utils/httpCodes<% } else { %>../../../utils/httpCodes<% } %>');
|
|
18
|
+
|
|
19
|
+
class AuthController {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.login = this.login.bind(this);
|
|
22
|
+
this.refresh = this.refresh.bind(this);
|
|
23
|
+
this.logout = this.logout.bind(this);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async login(req, res, next) {
|
|
27
|
+
try {
|
|
28
|
+
const { email, password } = req.body;
|
|
29
|
+
<% if (database === 'MongoDB' || database === 'None') { %>
|
|
30
|
+
const user = await User.findOne({ email });
|
|
31
|
+
<% } else { %>
|
|
32
|
+
const user = await User.findOne({ where: { email } });
|
|
33
|
+
<% } %>
|
|
34
|
+
if (!user) {
|
|
35
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Invalid credentials' });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const isPasswordValid = await bcrypt.compare(password, user.password);
|
|
39
|
+
if (!isPasswordValid) {
|
|
40
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Invalid credentials' });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const userId = String(user.id || user._id);
|
|
44
|
+
const refreshToken = JwtService.generateRefreshToken({ id: userId, email: user.email });
|
|
45
|
+
const refreshJti = JwtService.decodeToken(refreshToken)?.jti;
|
|
46
|
+
const accessToken = JwtService.generateToken({ id: userId, email: user.email, sid: refreshJti });
|
|
47
|
+
|
|
48
|
+
<% if (caching !== 'None') { %>
|
|
49
|
+
const cacheKey = `refresh_tokens:${userId}`;
|
|
50
|
+
const activeTokens = await cacheService.get(cacheKey) || [];
|
|
51
|
+
activeTokens.push(refreshJti);
|
|
52
|
+
await cacheService.set(cacheKey, activeTokens, 7 * 24 * 60 * 60);
|
|
53
|
+
<% } else { -%>
|
|
54
|
+
const activeTokens = JwtService.activeRefreshTokens.get(userId) || [];
|
|
55
|
+
activeTokens.push(refreshJti);
|
|
56
|
+
JwtService.activeRefreshTokens.set(userId, activeTokens);
|
|
57
|
+
<% } %>
|
|
58
|
+
|
|
59
|
+
res.json({ token: accessToken, accessToken, refreshToken });
|
|
60
|
+
} catch (error) {
|
|
61
|
+
logger.error('Login error:', error);
|
|
62
|
+
next(error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async refresh(req, res, next) {
|
|
67
|
+
try {
|
|
68
|
+
const { refreshToken } = req.body;
|
|
69
|
+
if (!refreshToken) {
|
|
70
|
+
return res.status(HTTP_STATUS.BAD_REQUEST).json({ message: 'Refresh token is required' });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const decoded = JwtService.verifyRefreshToken(refreshToken);
|
|
74
|
+
if (!decoded || !decoded.id || !decoded.jti) {
|
|
75
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Invalid refresh token' });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const userId = String(decoded.id);
|
|
79
|
+
const incomingJti = decoded.jti;
|
|
80
|
+
|
|
81
|
+
<% if (caching !== 'None') { %>
|
|
82
|
+
const cacheKey = `refresh_tokens:${userId}`;
|
|
83
|
+
let activeTokens = await cacheService.get(cacheKey) || [];
|
|
84
|
+
|
|
85
|
+
if (!activeTokens.includes(incomingJti)) {
|
|
86
|
+
logger.warn(`Token theft detected for user ${userId}. Revoking all sessions.`);
|
|
87
|
+
await cacheService.del(cacheKey);
|
|
88
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Invalid session' });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
activeTokens = activeTokens.filter(t => t !== incomingJti);
|
|
92
|
+
const newRefreshToken = JwtService.generateRefreshToken({ id: userId, email: decoded.email });
|
|
93
|
+
const newRefreshJti = JwtService.decodeToken(newRefreshToken)?.jti;
|
|
94
|
+
const newAccessToken = JwtService.generateToken({ id: userId, email: decoded.email, sid: newRefreshJti });
|
|
95
|
+
|
|
96
|
+
activeTokens.push(newRefreshJti);
|
|
97
|
+
await cacheService.set(cacheKey, activeTokens, 7 * 24 * 60 * 60);
|
|
98
|
+
<% } else { -%>
|
|
99
|
+
let activeTokens = JwtService.activeRefreshTokens.get(userId) || [];
|
|
100
|
+
|
|
101
|
+
if (!activeTokens.includes(incomingJti)) {
|
|
102
|
+
logger.warn(`Token theft detected for user ${userId}. Revoking all sessions.`);
|
|
103
|
+
JwtService.activeRefreshTokens.delete(userId);
|
|
104
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Invalid session' });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
activeTokens = activeTokens.filter(t => t !== incomingJti);
|
|
108
|
+
const newRefreshToken = JwtService.generateRefreshToken({ id: userId, email: decoded.email });
|
|
109
|
+
const newRefreshJti = JwtService.decodeToken(newRefreshToken)?.jti;
|
|
110
|
+
const newAccessToken = JwtService.generateToken({ id: userId, email: decoded.email, sid: newRefreshJti });
|
|
111
|
+
|
|
112
|
+
activeTokens.push(newRefreshJti);
|
|
113
|
+
JwtService.activeRefreshTokens.set(userId, activeTokens);
|
|
114
|
+
<% } %>
|
|
115
|
+
res.json({ accessToken: newAccessToken, refreshToken: newRefreshToken });
|
|
116
|
+
} catch (error) {
|
|
117
|
+
logger.error('Refresh token error:', error);
|
|
118
|
+
next(error);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async logout(req, res, next) {
|
|
123
|
+
try {
|
|
124
|
+
const authHeader = req.headers.authorization;
|
|
125
|
+
if (!authHeader) {
|
|
126
|
+
return res.status(HTTP_STATUS.BAD_REQUEST).json({ message: 'No token provided' });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const accessTokenStr = authHeader.split(' ')[1];
|
|
130
|
+
const decodedAccess = JwtService.decodeToken(accessTokenStr);
|
|
131
|
+
|
|
132
|
+
if (decodedAccess && decodedAccess.jti && decodedAccess.exp) {
|
|
133
|
+
const remainingTime = Math.max(0, decodedAccess.exp - Math.floor(Date.now() / 1000));
|
|
134
|
+
<%_ if (caching !== 'None') { -%>
|
|
135
|
+
if (remainingTime > 0) {
|
|
136
|
+
await cacheService.set(`blacklist:${decodedAccess.jti}`, true, remainingTime);
|
|
137
|
+
}<%_ } else { -%>
|
|
138
|
+
if (remainingTime > 0) {
|
|
139
|
+
JwtService.blacklistedTokens.set(decodedAccess.jti, Date.now() + remainingTime * 1000);
|
|
140
|
+
}<%_ } -%>
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const { refreshToken } = req.body;
|
|
144
|
+
if (refreshToken) {
|
|
145
|
+
const decodedRefresh = JwtService.decodeToken(refreshToken);
|
|
146
|
+
if (decodedRefresh && decodedRefresh.id && decodedRefresh.jti) {
|
|
147
|
+
const userId = String(decodedRefresh.id);
|
|
148
|
+
<%_ if (caching !== 'None') { -%>
|
|
149
|
+
const cacheKey = `refresh_tokens:${userId}`;
|
|
150
|
+
let activeTokens = await cacheService.get(cacheKey) || [];
|
|
151
|
+
activeTokens = activeTokens.filter(t => t !== decodedRefresh.jti);
|
|
152
|
+
await cacheService.set(cacheKey, activeTokens, 7 * 24 * 60 * 60);
|
|
153
|
+
<%_ } else { -%>
|
|
154
|
+
let activeTokens = JwtService.activeRefreshTokens.get(userId) || [];
|
|
155
|
+
activeTokens = activeTokens.filter(t => t !== decodedRefresh.jti);
|
|
156
|
+
JwtService.activeRefreshTokens.set(userId, activeTokens);<% } %>
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
res.json({ message: 'Logged out successfully' });
|
|
161
|
+
} catch (error) {
|
|
162
|
+
logger.error('Logout error:', error);
|
|
163
|
+
next(error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = AuthController;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<%_ if (architecture === 'Clean Architecture') { _%>
|
|
2
|
+
const AuthController = require('@/interfaces/controllers/auth/authController');
|
|
3
|
+
const JwtService = require('@/infrastructure/auth/jwtService');
|
|
4
|
+
<%_ } else { _%>
|
|
5
|
+
const AuthController = require('@/controllers/authController');
|
|
6
|
+
const JwtService = require('@/services/jwtService');
|
|
7
|
+
<%_ } _%>
|
|
8
|
+
const HTTP_STATUS = require('@/utils/httpCodes');
|
|
9
|
+
const bcrypt = require('bcryptjs');
|
|
10
|
+
|
|
11
|
+
jest.mock('bcryptjs');
|
|
12
|
+
<%_ if (architecture === 'Clean Architecture') { _%>
|
|
13
|
+
jest.mock('@/infrastructure/auth/jwtService');
|
|
14
|
+
jest.mock('@/infrastructure/database/models/User', () => ({
|
|
15
|
+
findOne: jest.fn()
|
|
16
|
+
}));
|
|
17
|
+
const User = require('@/infrastructure/database/models/User');
|
|
18
|
+
<%_ } else { _%>
|
|
19
|
+
jest.mock('@/services/jwtService');
|
|
20
|
+
jest.mock('@/models/User', () => ({
|
|
21
|
+
findOne: jest.fn()
|
|
22
|
+
}));
|
|
23
|
+
const User = require('@/models/User');
|
|
24
|
+
<%_ } _%>
|
|
25
|
+
<%_ if (caching !== 'None') { _%>
|
|
26
|
+
<%_ const cachePath = (architecture === "MVC") ? (caching === "Redis" ? "@/config/redisClient" : "@/config/memoryCache") : (caching === "Redis" ? "@/infrastructure/caching/redisClient" : "@/infrastructure/caching/memoryCache"); _%>
|
|
27
|
+
jest.mock('<%= cachePath %>', () => ({
|
|
28
|
+
get: jest.fn(),
|
|
29
|
+
set: jest.fn(),
|
|
30
|
+
del: jest.fn()
|
|
31
|
+
}), { virtual: true });
|
|
32
|
+
const cacheService = require('<%= cachePath %>');
|
|
33
|
+
<%_ } _%>
|
|
34
|
+
|
|
35
|
+
describe('AuthController', () => {
|
|
36
|
+
let controller;
|
|
37
|
+
let mockReq;
|
|
38
|
+
let mockRes;
|
|
39
|
+
let next;
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
controller = new AuthController();
|
|
43
|
+
mockReq = {
|
|
44
|
+
body: {},
|
|
45
|
+
headers: {}
|
|
46
|
+
};
|
|
47
|
+
mockRes = {
|
|
48
|
+
status: jest.fn().mockReturnThis(),
|
|
49
|
+
json: jest.fn()
|
|
50
|
+
};
|
|
51
|
+
next = jest.fn();
|
|
52
|
+
jest.clearAllMocks();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('login', () => {
|
|
56
|
+
it('should return tokens on success', async () => {
|
|
57
|
+
const user = { id: 1, email: 'test@test.com', password: 'hashedpassword' };
|
|
58
|
+
mockReq.body = { email: 'test@test.com', password: 'password123' };
|
|
59
|
+
User.findOne.mockResolvedValue(user);
|
|
60
|
+
bcrypt.compare.mockResolvedValue(true);
|
|
61
|
+
JwtService.generateToken.mockReturnValue('mock-access-token');
|
|
62
|
+
JwtService.generateRefreshToken.mockReturnValue('mock-refresh-token');
|
|
63
|
+
JwtService.decodeToken.mockReturnValue({ jti: 'test-jti' });
|
|
64
|
+
|
|
65
|
+
// Mock cacheService
|
|
66
|
+
<% if (caching !== 'None') { %>
|
|
67
|
+
cacheService.get.mockResolvedValue([]);
|
|
68
|
+
cacheService.set.mockResolvedValue();<% } %>
|
|
69
|
+
|
|
70
|
+
await controller.login(mockReq, mockRes, next);
|
|
71
|
+
|
|
72
|
+
expect(JwtService.generateToken).toHaveBeenCalledWith(expect.objectContaining({ sid: 'test-jti' }));
|
|
73
|
+
expect(mockRes.json).toHaveBeenCalledWith({ token: 'mock-access-token', accessToken: 'mock-access-token', refreshToken: 'mock-refresh-token' });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should return 401 on invalid email', async () => {
|
|
77
|
+
mockReq.body = { email: 'wrong@test.com', password: 'password123' };
|
|
78
|
+
User.findOne.mockResolvedValue(null);
|
|
79
|
+
|
|
80
|
+
await controller.login(mockReq, mockRes, next);
|
|
81
|
+
|
|
82
|
+
expect(mockRes.status).toHaveBeenCalledWith(HTTP_STATUS.UNAUTHORIZED);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should return 401 on invalid password', async () => {
|
|
86
|
+
const user = { id: 1, email: 'test@test.com', password: 'hashedpassword' };
|
|
87
|
+
mockReq.body = { email: 'test@test.com', password: 'wrongpassword' };
|
|
88
|
+
User.findOne.mockResolvedValue(user);
|
|
89
|
+
bcrypt.compare.mockResolvedValue(false);
|
|
90
|
+
|
|
91
|
+
await controller.login(mockReq, mockRes, next);
|
|
92
|
+
|
|
93
|
+
expect(mockRes.status).toHaveBeenCalledWith(HTTP_STATUS.UNAUTHORIZED);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('refresh', () => {
|
|
98
|
+
it('should return new tokens for a valid refresh token', async () => {
|
|
99
|
+
mockReq.body = { refreshToken: 'valid-refresh' };
|
|
100
|
+
const decoded = { id: '1', email: 'test@test.com', jti: 'old-jti' };
|
|
101
|
+
JwtService.verifyRefreshToken.mockReturnValue(decoded);
|
|
102
|
+
JwtService.generateToken.mockReturnValue('new-access');
|
|
103
|
+
JwtService.generateRefreshToken.mockReturnValue('new-refresh');
|
|
104
|
+
JwtService.decodeToken.mockReturnValue({ jti: 'new-jti' });
|
|
105
|
+
|
|
106
|
+
<% if (caching !== 'None') { %>
|
|
107
|
+
cacheService.get.mockResolvedValue(['old-jti']);
|
|
108
|
+
cacheService.set.mockResolvedValue();
|
|
109
|
+
<% } else { %>
|
|
110
|
+
JwtService.activeRefreshTokens.set('1', ['old-jti']);<% } %>
|
|
111
|
+
|
|
112
|
+
await controller.refresh(mockReq, mockRes, next);
|
|
113
|
+
|
|
114
|
+
expect(JwtService.generateToken).toHaveBeenCalledWith(expect.objectContaining({ sid: 'new-jti' }));
|
|
115
|
+
expect(mockRes.json).toHaveBeenCalledWith({ accessToken: 'new-access', refreshToken: 'new-refresh' });
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should detect theft and revoke if jti is not active', async () => {
|
|
119
|
+
mockReq.body = { refreshToken: 'stolen-refresh' };
|
|
120
|
+
const decoded = { id: '1', email: 'test@test.com', jti: 'stolen-jti' };
|
|
121
|
+
JwtService.verifyRefreshToken.mockReturnValue(decoded);
|
|
122
|
+
|
|
123
|
+
<% if (caching !== 'None') { %>
|
|
124
|
+
cacheService.get.mockResolvedValue(['different-jti']);
|
|
125
|
+
<% } else { %>
|
|
126
|
+
JwtService.activeRefreshTokens.set('1', ['different-jti']);<% } %>
|
|
127
|
+
|
|
128
|
+
await controller.refresh(mockReq, mockRes, next);
|
|
129
|
+
|
|
130
|
+
expect(mockRes.status).toHaveBeenCalledWith(HTTP_STATUS.UNAUTHORIZED);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('logout', () => {
|
|
135
|
+
it('should blacklist access jti and remove refresh jti', async () => {
|
|
136
|
+
mockReq.headers.authorization = 'Bearer access-token';
|
|
137
|
+
mockReq.body = { refreshToken: 'refresh-token' };
|
|
138
|
+
|
|
139
|
+
JwtService.decodeToken
|
|
140
|
+
.mockReturnValueOnce({ jti: 'access-jti', exp: Math.floor(Date.now() / 1000) + 3600 })
|
|
141
|
+
.mockReturnValueOnce({ id: '1', jti: 'refresh-jti' });
|
|
142
|
+
|
|
143
|
+
await controller.logout(mockReq, mockRes, next);
|
|
144
|
+
|
|
145
|
+
expect(mockRes.json).toHaveBeenCalledWith({ message: 'Logged out successfully' });
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<% if (architecture === 'MVC') { -%>
|
|
2
|
+
const JwtService = require('../services/jwtService');
|
|
3
|
+
const HTTP_STATUS = require('../utils/httpCodes');
|
|
4
|
+
<% if (caching !== 'None') { -%>
|
|
5
|
+
const cacheService = require('<% if (caching === "Redis") { %>../config/redisClient<% } else { %>../config/memoryCache<% } %>');
|
|
6
|
+
<% } -%>
|
|
7
|
+
<% } else { -%>
|
|
8
|
+
const JwtService = require('../../../infrastructure/auth/jwtService');
|
|
9
|
+
const HTTP_STATUS = require('../../../utils/httpCodes');
|
|
10
|
+
<% if (caching !== 'None') { -%>
|
|
11
|
+
const cacheService = require('<% if (caching === "Redis") { %>../../../infrastructure/caching/redisClient<% } else { %>../../../infrastructure/caching/memoryCache<% } %>');
|
|
12
|
+
<% } -%>
|
|
13
|
+
<% } -%>
|
|
14
|
+
|
|
15
|
+
const authMiddleware = async (req, res, next) => {
|
|
16
|
+
const authHeader = req.headers.authorization;
|
|
17
|
+
|
|
18
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
19
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'No token provided' });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const token = authHeader.split(' ')[1];
|
|
23
|
+
const decoded = JwtService.verifyToken(token);
|
|
24
|
+
|
|
25
|
+
if (!decoded) {
|
|
26
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Invalid or expired token' });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (decoded.jti) {
|
|
30
|
+
<%_ if (caching !== 'None') { -%>
|
|
31
|
+
const isBlacklisted = await cacheService.get(`blacklist:${decoded.jti}`);
|
|
32
|
+
if (isBlacklisted) {
|
|
33
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Token revoked' });
|
|
34
|
+
}<%_ } else { -%>
|
|
35
|
+
const expiryDate = JwtService.blacklistedTokens.get(decoded.jti);
|
|
36
|
+
if (expiryDate && Date.now() < expiryDate) {
|
|
37
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Token revoked' });
|
|
38
|
+
}<% } %>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (decoded.sid) {
|
|
42
|
+
<%_ if (caching !== 'None') { %>
|
|
43
|
+
const cacheKey = `refresh_tokens:${decoded.id}`;
|
|
44
|
+
const activeTokens = await cacheService.get(cacheKey) || [];
|
|
45
|
+
if (!activeTokens.includes(decoded.sid)) {
|
|
46
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Session expired' });
|
|
47
|
+
}<%_ } else { -%>
|
|
48
|
+
const activeTokens = JwtService.activeRefreshTokens.get(String(decoded.id)) || [];
|
|
49
|
+
if (!activeTokens.includes(decoded.sid)) {
|
|
50
|
+
return res.status(HTTP_STATUS.UNAUTHORIZED).json({ message: 'Session expired' });
|
|
51
|
+
}<%_ } -%>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
req.user = decoded;
|
|
55
|
+
next();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
module.exports = authMiddleware;
|