webortex-auth 1.0.3 → 1.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/CUSTOM_USER_MODEL.md +303 -0
- package/README.md +25 -0
- package/package.json +1 -1
- package/src/controllers/auth.controller.js +3 -2
- package/src/index.js +1 -1
- package/src/models/User.model.js +47 -28
- package/src/routes/auth.routes.js +3 -2
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# Custom User Model Guide
|
|
2
|
+
|
|
3
|
+
This guide shows you how to extend the default User model with your own custom fields.
|
|
4
|
+
|
|
5
|
+
## Method 1: Using `createCustomUserModel` Helper
|
|
6
|
+
|
|
7
|
+
The easiest way to add custom fields to the User model.
|
|
8
|
+
|
|
9
|
+
### Example: Adding Phone Number and Role
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
import express from 'express';
|
|
13
|
+
import mongoose from 'mongoose';
|
|
14
|
+
import { authRoutes, createCustomUserModel } from 'webortex-auth';
|
|
15
|
+
|
|
16
|
+
const app = express();
|
|
17
|
+
app.use(express.json());
|
|
18
|
+
|
|
19
|
+
// Connect to MongoDB
|
|
20
|
+
await mongoose.connect(process.env.MONGO_URI);
|
|
21
|
+
|
|
22
|
+
// Create custom User model with additional fields
|
|
23
|
+
const CustomUser = createCustomUserModel(mongoose, {
|
|
24
|
+
phone: {
|
|
25
|
+
type: String,
|
|
26
|
+
required: false,
|
|
27
|
+
},
|
|
28
|
+
role: {
|
|
29
|
+
type: String,
|
|
30
|
+
enum: ['user', 'admin', 'moderator'],
|
|
31
|
+
default: 'user',
|
|
32
|
+
},
|
|
33
|
+
isVerified: {
|
|
34
|
+
type: Boolean,
|
|
35
|
+
default: false,
|
|
36
|
+
},
|
|
37
|
+
profilePicture: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: null,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Use auth routes with custom User model
|
|
44
|
+
app.use('/api/auth', authRoutes(mongoose, CustomUser));
|
|
45
|
+
|
|
46
|
+
app.listen(3000);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Signup Request with Custom Fields
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
curl -X POST http://localhost:3000/api/auth/signup \
|
|
53
|
+
-H "Content-Type: application/json" \
|
|
54
|
+
-d '{
|
|
55
|
+
"name": "John Doe",
|
|
56
|
+
"email": "john@example.com",
|
|
57
|
+
"password": "securePass123",
|
|
58
|
+
"phone": "+1234567890",
|
|
59
|
+
"role": "admin"
|
|
60
|
+
}'
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Method 2: Creating Your Own Model from Scratch
|
|
66
|
+
|
|
67
|
+
For complete control, create your own User model.
|
|
68
|
+
|
|
69
|
+
### Example: Fully Custom User Model
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
import mongoose from 'mongoose';
|
|
73
|
+
import bcrypt from 'bcryptjs';
|
|
74
|
+
import { authRoutes } from 'webortex-auth';
|
|
75
|
+
|
|
76
|
+
// Create your own User schema
|
|
77
|
+
const userSchema = new mongoose.Schema({
|
|
78
|
+
// Required fields for authentication
|
|
79
|
+
name: {
|
|
80
|
+
type: String,
|
|
81
|
+
required: true,
|
|
82
|
+
},
|
|
83
|
+
email: {
|
|
84
|
+
type: String,
|
|
85
|
+
required: true,
|
|
86
|
+
unique: true,
|
|
87
|
+
lowercase: true,
|
|
88
|
+
},
|
|
89
|
+
password: {
|
|
90
|
+
type: String,
|
|
91
|
+
required: true,
|
|
92
|
+
select: false,
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
// Your custom fields
|
|
96
|
+
company: {
|
|
97
|
+
type: String,
|
|
98
|
+
required: true,
|
|
99
|
+
},
|
|
100
|
+
department: String,
|
|
101
|
+
employeeId: {
|
|
102
|
+
type: String,
|
|
103
|
+
unique: true,
|
|
104
|
+
},
|
|
105
|
+
permissions: [{
|
|
106
|
+
type: String,
|
|
107
|
+
}],
|
|
108
|
+
}, {
|
|
109
|
+
timestamps: true,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// IMPORTANT: Add password hashing middleware
|
|
113
|
+
userSchema.pre('save', async function () {
|
|
114
|
+
if (!this.isModified('password')) return;
|
|
115
|
+
|
|
116
|
+
const salt = await bcrypt.genSalt(10);
|
|
117
|
+
this.password = await bcrypt.hash(this.password, salt);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// IMPORTANT: Add password comparison method
|
|
121
|
+
userSchema.methods.comparePassword = async function (candidatePassword) {
|
|
122
|
+
return await bcrypt.compare(candidatePassword, this.password);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Create model
|
|
126
|
+
const CustomUser = mongoose.model('User', userSchema);
|
|
127
|
+
|
|
128
|
+
// Use with auth routes
|
|
129
|
+
app.use('/api/auth', authRoutes(mongoose, CustomUser));
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Method 3: Extending with Schema Options
|
|
135
|
+
|
|
136
|
+
Add schema options like virtuals, indexes, etc.
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
import { createCustomUserModel } from 'webortex-auth';
|
|
140
|
+
|
|
141
|
+
const CustomUser = createCustomUserModel(
|
|
142
|
+
mongoose,
|
|
143
|
+
// Additional fields
|
|
144
|
+
{
|
|
145
|
+
firstName: String,
|
|
146
|
+
lastName: String,
|
|
147
|
+
dateOfBirth: Date,
|
|
148
|
+
address: {
|
|
149
|
+
street: String,
|
|
150
|
+
city: String,
|
|
151
|
+
country: String,
|
|
152
|
+
zipCode: String,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
// Schema options
|
|
156
|
+
{
|
|
157
|
+
toJSON: { virtuals: true },
|
|
158
|
+
toObject: { virtuals: true },
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Add virtual field
|
|
163
|
+
CustomUser.schema.virtual('fullName').get(function () {
|
|
164
|
+
return `${this.firstName} ${this.lastName}`;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Add index
|
|
168
|
+
CustomUser.schema.index({ email: 1, dateOfBirth: 1 });
|
|
169
|
+
|
|
170
|
+
app.use('/api/auth', authRoutes(mongoose, CustomUser));
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Important Requirements
|
|
176
|
+
|
|
177
|
+
When creating a custom User model, ensure it has:
|
|
178
|
+
|
|
179
|
+
### Required Fields
|
|
180
|
+
- ✅ `name` (String)
|
|
181
|
+
- ✅ `email` (String, unique)
|
|
182
|
+
- ✅ `password` (String, with `select: false`)
|
|
183
|
+
|
|
184
|
+
### Required Methods
|
|
185
|
+
- ✅ `comparePassword(candidatePassword)` - Returns Promise<boolean>
|
|
186
|
+
|
|
187
|
+
### Required Middleware
|
|
188
|
+
- ✅ Pre-save hook to hash password with bcrypt
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Using Custom User Model in Your App
|
|
193
|
+
|
|
194
|
+
After setting up a custom User model, you can use it anywhere:
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
import { createCustomUserModel } from 'webortex-auth';
|
|
198
|
+
|
|
199
|
+
// Create the model
|
|
200
|
+
const User = createCustomUserModel(mongoose, {
|
|
201
|
+
role: { type: String, default: 'user' },
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Use it in your routes
|
|
205
|
+
app.get('/api/users', async (req, res) => {
|
|
206
|
+
const users = await User.find().select('-password');
|
|
207
|
+
res.json(users);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
app.get('/api/users/:id', async (req, res) => {
|
|
211
|
+
const user = await User.findById(req.params.id);
|
|
212
|
+
res.json(user);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
app.patch('/api/users/:id', async (req, res) => {
|
|
216
|
+
const user = await User.findByIdAndUpdate(
|
|
217
|
+
req.params.id,
|
|
218
|
+
req.body,
|
|
219
|
+
{ new: true }
|
|
220
|
+
);
|
|
221
|
+
res.json(user);
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Complete Example
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
import express from 'express';
|
|
231
|
+
import mongoose from 'mongoose';
|
|
232
|
+
import dotenv from 'dotenv';
|
|
233
|
+
import { authRoutes, createCustomUserModel } from 'webortex-auth';
|
|
234
|
+
|
|
235
|
+
dotenv.config();
|
|
236
|
+
|
|
237
|
+
const app = express();
|
|
238
|
+
app.use(express.json());
|
|
239
|
+
|
|
240
|
+
// Connect to MongoDB
|
|
241
|
+
await mongoose.connect(process.env.MONGO_URI);
|
|
242
|
+
|
|
243
|
+
// Create custom User model
|
|
244
|
+
const User = createCustomUserModel(mongoose, {
|
|
245
|
+
phone: String,
|
|
246
|
+
role: {
|
|
247
|
+
type: String,
|
|
248
|
+
enum: ['user', 'admin'],
|
|
249
|
+
default: 'user',
|
|
250
|
+
},
|
|
251
|
+
avatar: String,
|
|
252
|
+
bio: String,
|
|
253
|
+
socialLinks: {
|
|
254
|
+
twitter: String,
|
|
255
|
+
linkedin: String,
|
|
256
|
+
github: String,
|
|
257
|
+
},
|
|
258
|
+
preferences: {
|
|
259
|
+
newsletter: { type: Boolean, default: true },
|
|
260
|
+
notifications: { type: Boolean, default: true },
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Auth routes
|
|
265
|
+
app.use('/api/auth', authRoutes(mongoose, User));
|
|
266
|
+
|
|
267
|
+
// Custom user routes
|
|
268
|
+
app.get('/api/users/me', async (req, res) => {
|
|
269
|
+
// Assuming you have auth middleware that sets req.userId
|
|
270
|
+
const user = await User.findById(req.userId);
|
|
271
|
+
res.json(user);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
app.patch('/api/users/me', async (req, res) => {
|
|
275
|
+
const user = await User.findByIdAndUpdate(
|
|
276
|
+
req.userId,
|
|
277
|
+
{ $set: req.body },
|
|
278
|
+
{ new: true, runValidators: true }
|
|
279
|
+
);
|
|
280
|
+
res.json(user);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
app.listen(3000, () => {
|
|
284
|
+
console.log('Server running on port 3000');
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Tips
|
|
291
|
+
|
|
292
|
+
1. **Don't modify password field** - The package handles password hashing automatically
|
|
293
|
+
2. **Use select: false for sensitive fields** - Like the password field
|
|
294
|
+
3. **Add validation** - Use Mongoose validators for your custom fields
|
|
295
|
+
4. **Create indexes** - For fields you'll query frequently
|
|
296
|
+
5. **Use virtuals** - For computed properties
|
|
297
|
+
6. **Keep it simple** - Start with basic fields and add more as needed
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Need Help?
|
|
302
|
+
|
|
303
|
+
If you have questions about custom User models, please open an issue on GitHub!
|
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ A production-ready, reusable authentication package for Node.js applications usi
|
|
|
12
12
|
- 🎯 **Type Safe** - Clean API with factory functions
|
|
13
13
|
- 🔄 **Reusable** - Use across multiple projects
|
|
14
14
|
- 🛡️ **Production Ready** - Comprehensive error handling
|
|
15
|
+
- 🎨 **Customizable** - Extend User model with your own fields
|
|
15
16
|
|
|
16
17
|
## 📦 Installation
|
|
17
18
|
|
|
@@ -243,6 +244,30 @@ app.use('/api/auth/login', limiter);
|
|
|
243
244
|
app.use('/api/auth', authRoutes(mongoose));
|
|
244
245
|
```
|
|
245
246
|
|
|
247
|
+
### Custom User Model
|
|
248
|
+
|
|
249
|
+
Extend the default User model with your own fields:
|
|
250
|
+
|
|
251
|
+
```javascript
|
|
252
|
+
import { authRoutes, createCustomUserModel } from 'webortex-auth';
|
|
253
|
+
|
|
254
|
+
// Create custom User model with additional fields
|
|
255
|
+
const CustomUser = createCustomUserModel(mongoose, {
|
|
256
|
+
phone: String,
|
|
257
|
+
role: {
|
|
258
|
+
type: String,
|
|
259
|
+
enum: ['user', 'admin'],
|
|
260
|
+
default: 'user',
|
|
261
|
+
},
|
|
262
|
+
avatar: String,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Use with auth routes
|
|
266
|
+
app.use('/api/auth', authRoutes(mongoose, CustomUser));
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**📖 For detailed examples, see [CUSTOM_USER_MODEL.md](CUSTOM_USER_MODEL.md)**
|
|
270
|
+
|
|
246
271
|
## ⚠️ Common Errors & Fixes
|
|
247
272
|
|
|
248
273
|
### Error: `buffering timed out after 10000ms`
|
package/package.json
CHANGED
|
@@ -4,10 +4,11 @@ import { createUserModel } from '../models/User.model.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Creates authentication controllers with injected mongoose instance
|
|
6
6
|
* @param {Object} mongoose - Connected mongoose instance from consuming app
|
|
7
|
+
* @param {Model} [customUserModel] - Optional custom User model (if not provided, uses default)
|
|
7
8
|
* @returns {Object} Authentication controllers (signup, login)
|
|
8
9
|
*/
|
|
9
|
-
export const createAuthController = (mongoose) => {
|
|
10
|
-
const User = createUserModel(mongoose);
|
|
10
|
+
export const createAuthController = (mongoose, customUserModel = null) => {
|
|
11
|
+
const User = customUserModel || createUserModel(mongoose);
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Signup Controller
|
package/src/index.js
CHANGED
package/src/models/User.model.js
CHANGED
|
@@ -1,43 +1,53 @@
|
|
|
1
1
|
import bcrypt from 'bcryptjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Creates User model
|
|
5
|
-
* @param {Object} mongoose - Connected mongoose instance
|
|
6
|
-
* @
|
|
4
|
+
* Creates a custom User model by extending the base schema
|
|
5
|
+
* @param {Object} mongoose - Connected mongoose instance
|
|
6
|
+
* @param {Object} additionalFields - Additional schema fields to add
|
|
7
|
+
* @param {Object} [schemaOptions] - Additional schema options
|
|
8
|
+
* @returns {Model} Extended User model
|
|
7
9
|
*/
|
|
8
|
-
export const
|
|
10
|
+
export const createCustomUserModel = (mongoose, additionalFields = {}, schemaOptions = {}) => {
|
|
9
11
|
// Prevent model recompilation error
|
|
10
12
|
if (mongoose.models.User) {
|
|
11
13
|
return mongoose.models.User;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
},
|
|
16
|
+
// Base user fields
|
|
17
|
+
const baseFields = {
|
|
18
|
+
name: {
|
|
19
|
+
type: String,
|
|
20
|
+
required: [true, 'Name is required'],
|
|
21
|
+
trim: true,
|
|
22
|
+
},
|
|
23
|
+
email: {
|
|
24
|
+
type: String,
|
|
25
|
+
required: [true, 'Email is required'],
|
|
26
|
+
unique: true,
|
|
27
|
+
lowercase: true,
|
|
28
|
+
trim: true,
|
|
29
|
+
match: [
|
|
30
|
+
/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/,
|
|
31
|
+
'Please provide a valid email',
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
password: {
|
|
35
|
+
type: String,
|
|
36
|
+
required: [true, 'Password is required'],
|
|
37
|
+
minlength: [6, 'Password must be at least 6 characters'],
|
|
38
|
+
select: false, // Don't return password by default
|
|
38
39
|
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Merge base fields with additional fields
|
|
43
|
+
const schemaFields = { ...baseFields, ...additionalFields };
|
|
44
|
+
|
|
45
|
+
// Create schema with merged fields
|
|
46
|
+
const userSchema = new mongoose.Schema(
|
|
47
|
+
schemaFields,
|
|
39
48
|
{
|
|
40
49
|
timestamps: true,
|
|
50
|
+
...schemaOptions,
|
|
41
51
|
}
|
|
42
52
|
);
|
|
43
53
|
|
|
@@ -63,3 +73,12 @@ export const createUserModel = (mongoose) => {
|
|
|
63
73
|
|
|
64
74
|
return mongoose.model('User', userSchema);
|
|
65
75
|
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Creates User model with injected mongoose instance
|
|
79
|
+
* @param {Object} mongoose - Connected mongoose instance from consuming app
|
|
80
|
+
* @returns {Model} User model
|
|
81
|
+
*/
|
|
82
|
+
export const createUserModel = (mongoose) => {
|
|
83
|
+
return createCustomUserModel(mongoose);
|
|
84
|
+
};
|
|
@@ -4,11 +4,12 @@ import { createAuthController } from '../controllers/auth.controller.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Creates authentication routes with injected mongoose instance
|
|
6
6
|
* @param {Object} mongoose - Connected mongoose instance from consuming app
|
|
7
|
+
* @param {Model} [customUserModel] - Optional custom User model (if not provided, uses default)
|
|
7
8
|
* @returns {Router} Express router with auth routes
|
|
8
9
|
*/
|
|
9
|
-
export const createAuthRoutes = (mongoose) => {
|
|
10
|
+
export const createAuthRoutes = (mongoose, customUserModel = null) => {
|
|
10
11
|
const router = express.Router();
|
|
11
|
-
const { signup, login } = createAuthController(mongoose);
|
|
12
|
+
const { signup, login } = createAuthController(mongoose, customUserModel);
|
|
12
13
|
|
|
13
14
|
// POST /signup - Register new user
|
|
14
15
|
router.post('/signup', signup);
|