@bhandari88/express-auth 1.1.0 → 1.2.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/README.md +196 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -1
- package/dist/middleware/validation.middleware.d.ts +114 -0
- package/dist/middleware/validation.middleware.js +290 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ npm install @bhandari88/express-auth
|
|
|
34
34
|
- ✅ **Easy Integration**
|
|
35
35
|
- Plug-and-play API
|
|
36
36
|
- Express middleware for protected routes
|
|
37
|
+
- Request data validation middleware with custom messages
|
|
37
38
|
- Role-based access control
|
|
38
39
|
- Automatic route setup
|
|
39
40
|
- TypeScript support
|
|
@@ -170,6 +171,201 @@ app.get('/api/admin',
|
|
|
170
171
|
);
|
|
171
172
|
```
|
|
172
173
|
|
|
174
|
+
### 5. Use Validation Middleware
|
|
175
|
+
|
|
176
|
+
The package includes a powerful validation middleware for request data validation with custom error messages. No third-party dependencies required!
|
|
177
|
+
|
|
178
|
+
#### Basic Usage
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { validate, validateBody, validateQuery, validateParams } from '@bhandari88/express-auth';
|
|
182
|
+
|
|
183
|
+
// Validate request body
|
|
184
|
+
app.post('/api/users', validateBody({
|
|
185
|
+
email: {
|
|
186
|
+
rules: [
|
|
187
|
+
{ type: 'required', message: 'Email is required' },
|
|
188
|
+
{ type: 'email', message: 'Invalid email format' }
|
|
189
|
+
]
|
|
190
|
+
},
|
|
191
|
+
password: {
|
|
192
|
+
rules: [
|
|
193
|
+
{ type: 'required', message: 'Password is required' },
|
|
194
|
+
{ type: 'minLength', value: 8, message: 'Password must be at least 8 characters' }
|
|
195
|
+
]
|
|
196
|
+
},
|
|
197
|
+
age: {
|
|
198
|
+
rules: [
|
|
199
|
+
{ type: 'number', message: 'Age must be a number' },
|
|
200
|
+
{ type: 'min', value: 18, message: 'Age must be at least 18' },
|
|
201
|
+
{ type: 'max', value: 120, message: 'Age must be at most 120' }
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
}), (req, res) => {
|
|
205
|
+
// req.body is validated
|
|
206
|
+
res.json({ message: 'User created', data: req.body });
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### Shorthand Syntax
|
|
211
|
+
|
|
212
|
+
You can also use a simpler array syntax:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
app.post('/api/register', validateBody({
|
|
216
|
+
username: [
|
|
217
|
+
{ type: 'required', message: 'Username is required' },
|
|
218
|
+
{ type: 'minLength', value: 3, message: 'Username must be at least 3 characters' },
|
|
219
|
+
{ type: 'maxLength', value: 30, message: 'Username must be at most 30 characters' },
|
|
220
|
+
{ type: 'pattern', value: /^[a-zA-Z0-9_-]+$/, message: 'Username can only contain letters, numbers, underscores, and hyphens' }
|
|
221
|
+
],
|
|
222
|
+
email: [
|
|
223
|
+
{ type: 'required' },
|
|
224
|
+
{ type: 'email', message: 'Invalid email address' }
|
|
225
|
+
]
|
|
226
|
+
}), handler);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### Validate Query Parameters
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
app.get('/api/users', validateQuery({
|
|
233
|
+
page: [
|
|
234
|
+
{ type: 'number', message: 'Page must be a number' },
|
|
235
|
+
{ type: 'min', value: 1, message: 'Page must be at least 1' }
|
|
236
|
+
],
|
|
237
|
+
limit: [
|
|
238
|
+
{ type: 'number' },
|
|
239
|
+
{ type: 'min', value: 1 },
|
|
240
|
+
{ type: 'max', value: 100, message: 'Limit cannot exceed 100' }
|
|
241
|
+
],
|
|
242
|
+
status: [
|
|
243
|
+
{ type: 'enum', values: ['active', 'inactive', 'pending'], message: 'Status must be active, inactive, or pending' }
|
|
244
|
+
]
|
|
245
|
+
}), (req, res) => {
|
|
246
|
+
// req.query is validated
|
|
247
|
+
res.json({ users: [] });
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### Validate Route Parameters
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
app.get('/api/users/:id', validateParams({
|
|
255
|
+
id: [
|
|
256
|
+
{ type: 'required', message: 'User ID is required' },
|
|
257
|
+
{ type: 'pattern', value: /^[a-f\d]{24}$/i, message: 'Invalid user ID format' }
|
|
258
|
+
]
|
|
259
|
+
}), (req, res) => {
|
|
260
|
+
// req.params is validated
|
|
261
|
+
res.json({ user: {} });
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### Validate Multiple Sources
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
app.put('/api/users/:id', validate({
|
|
269
|
+
id: [
|
|
270
|
+
{ type: 'required' },
|
|
271
|
+
{ type: 'pattern', value: /^[a-f\d]{24}$/i }
|
|
272
|
+
],
|
|
273
|
+
email: [
|
|
274
|
+
{ type: 'email' }
|
|
275
|
+
],
|
|
276
|
+
status: [
|
|
277
|
+
{ type: 'enum', values: ['active', 'inactive'] }
|
|
278
|
+
]
|
|
279
|
+
}, {
|
|
280
|
+
source: ['params', 'body'] // Validate both params and body
|
|
281
|
+
}), handler);
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
#### Custom Validation
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
app.post('/api/custom', validateBody({
|
|
288
|
+
password: [
|
|
289
|
+
{ type: 'required' },
|
|
290
|
+
{ type: 'minLength', value: 8 },
|
|
291
|
+
{
|
|
292
|
+
type: 'custom',
|
|
293
|
+
validator: (value) => {
|
|
294
|
+
// Custom validation logic
|
|
295
|
+
return /[A-Z]/.test(value) && /[a-z]/.test(value) && /[0-9]/.test(value);
|
|
296
|
+
},
|
|
297
|
+
message: 'Password must contain uppercase, lowercase, and number'
|
|
298
|
+
}
|
|
299
|
+
],
|
|
300
|
+
confirmPassword: [
|
|
301
|
+
{ type: 'required' },
|
|
302
|
+
{
|
|
303
|
+
type: 'custom',
|
|
304
|
+
validator: (value, data) => {
|
|
305
|
+
// Access other fields from the request
|
|
306
|
+
return value === data.password;
|
|
307
|
+
},
|
|
308
|
+
message: 'Passwords do not match'
|
|
309
|
+
}
|
|
310
|
+
]
|
|
311
|
+
}), handler);
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### Available Validation Rules
|
|
315
|
+
|
|
316
|
+
| Rule | Description | Example |
|
|
317
|
+
|------|-------------|---------|
|
|
318
|
+
| `required` | Field must be present and not empty | `{ type: 'required', message: 'Field is required' }` |
|
|
319
|
+
| `string` | Value must be a string | `{ type: 'string' }` |
|
|
320
|
+
| `number` | Value must be a number | `{ type: 'number' }` |
|
|
321
|
+
| `boolean` | Value must be a boolean | `{ type: 'boolean' }` |
|
|
322
|
+
| `email` | Value must be a valid email | `{ type: 'email', message: 'Invalid email' }` |
|
|
323
|
+
| `min` | Number must be >= value | `{ type: 'min', value: 1 }` |
|
|
324
|
+
| `max` | Number must be <= value | `{ type: 'max', value: 100 }` |
|
|
325
|
+
| `minLength` | String length must be >= value | `{ type: 'minLength', value: 8 }` |
|
|
326
|
+
| `maxLength` | String length must be <= value | `{ type: 'maxLength', value: 255 }` |
|
|
327
|
+
| `pattern` | String must match regex | `{ type: 'pattern', value: /^[a-z]+$/ }` |
|
|
328
|
+
| `enum` | Value must be in array | `{ type: 'enum', values: ['a', 'b', 'c'] }` |
|
|
329
|
+
| `array` | Value must be an array | `{ type: 'array' }` |
|
|
330
|
+
| `object` | Value must be an object | `{ type: 'object' }` |
|
|
331
|
+
| `custom` | Custom validation function | `{ type: 'custom', validator: (val) => boolean }` |
|
|
332
|
+
|
|
333
|
+
#### Validation Options
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
validate(schema, {
|
|
337
|
+
source: 'body' | 'query' | 'params' | ['body', 'query'], // What to validate (default: 'body')
|
|
338
|
+
abortEarly: boolean // Stop on first error or collect all errors (default: false)
|
|
339
|
+
})
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
#### Error Response Format
|
|
343
|
+
|
|
344
|
+
When validation fails, the middleware returns a 400 status with:
|
|
345
|
+
|
|
346
|
+
```json
|
|
347
|
+
{
|
|
348
|
+
"success": false,
|
|
349
|
+
"error": "Validation failed",
|
|
350
|
+
"errors": {
|
|
351
|
+
"email": "Invalid email format",
|
|
352
|
+
"password": "Password must be at least 8 characters"
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Or with `abortEarly: true`, only the first error:
|
|
358
|
+
|
|
359
|
+
```json
|
|
360
|
+
{
|
|
361
|
+
"success": false,
|
|
362
|
+
"error": "Validation failed",
|
|
363
|
+
"errors": {
|
|
364
|
+
"email": "Email is required"
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
173
369
|
## API Endpoints
|
|
174
370
|
|
|
175
371
|
### Register User
|
package/dist/index.d.ts
CHANGED
|
@@ -57,5 +57,6 @@ export { JwtService } from './utils/jwt';
|
|
|
57
57
|
export { PasswordService } from './utils/password';
|
|
58
58
|
export { ValidatorService } from './utils/validator';
|
|
59
59
|
export { AuthMiddleware } from './middleware/auth.middleware';
|
|
60
|
+
export { validate, validateBody, validateQuery, validateParams, ValidationOptions, ValidationSource, ValidationSchema, ValidationRule, FieldValidation } from './middleware/validation.middleware';
|
|
60
61
|
export { LocalAuthHandler } from './handlers/local-auth';
|
|
61
62
|
export { SocialAuthHandler } from './handlers/social-auth';
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.SocialAuthHandler = exports.LocalAuthHandler = exports.AuthMiddleware = exports.ValidatorService = exports.PasswordService = exports.JwtService = exports.Auth = void 0;
|
|
17
|
+
exports.SocialAuthHandler = exports.LocalAuthHandler = exports.validateParams = exports.validateQuery = exports.validateBody = exports.validate = exports.AuthMiddleware = exports.ValidatorService = exports.PasswordService = exports.JwtService = exports.Auth = void 0;
|
|
18
18
|
const jwt_1 = require("./utils/jwt");
|
|
19
19
|
const password_1 = require("./utils/password");
|
|
20
20
|
const local_auth_1 = require("./handlers/local-auth");
|
|
@@ -226,6 +226,11 @@ var validator_1 = require("./utils/validator");
|
|
|
226
226
|
Object.defineProperty(exports, "ValidatorService", { enumerable: true, get: function () { return validator_1.ValidatorService; } });
|
|
227
227
|
var auth_middleware_2 = require("./middleware/auth.middleware");
|
|
228
228
|
Object.defineProperty(exports, "AuthMiddleware", { enumerable: true, get: function () { return auth_middleware_2.AuthMiddleware; } });
|
|
229
|
+
var validation_middleware_1 = require("./middleware/validation.middleware");
|
|
230
|
+
Object.defineProperty(exports, "validate", { enumerable: true, get: function () { return validation_middleware_1.validate; } });
|
|
231
|
+
Object.defineProperty(exports, "validateBody", { enumerable: true, get: function () { return validation_middleware_1.validateBody; } });
|
|
232
|
+
Object.defineProperty(exports, "validateQuery", { enumerable: true, get: function () { return validation_middleware_1.validateQuery; } });
|
|
233
|
+
Object.defineProperty(exports, "validateParams", { enumerable: true, get: function () { return validation_middleware_1.validateParams; } });
|
|
229
234
|
var local_auth_2 = require("./handlers/local-auth");
|
|
230
235
|
Object.defineProperty(exports, "LocalAuthHandler", { enumerable: true, get: function () { return local_auth_2.LocalAuthHandler; } });
|
|
231
236
|
var social_auth_2 = require("./handlers/social-auth");
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { RequestHandler } from 'express';
|
|
2
|
+
export type ValidationSource = 'body' | 'query' | 'params';
|
|
3
|
+
export type ValidationRule = {
|
|
4
|
+
type: 'required';
|
|
5
|
+
message?: string;
|
|
6
|
+
} | {
|
|
7
|
+
type: 'string';
|
|
8
|
+
message?: string;
|
|
9
|
+
} | {
|
|
10
|
+
type: 'number';
|
|
11
|
+
message?: string;
|
|
12
|
+
} | {
|
|
13
|
+
type: 'boolean';
|
|
14
|
+
message?: string;
|
|
15
|
+
} | {
|
|
16
|
+
type: 'email';
|
|
17
|
+
message?: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'min';
|
|
20
|
+
value: number;
|
|
21
|
+
message?: string;
|
|
22
|
+
} | {
|
|
23
|
+
type: 'max';
|
|
24
|
+
value: number;
|
|
25
|
+
message?: string;
|
|
26
|
+
} | {
|
|
27
|
+
type: 'minLength';
|
|
28
|
+
value: number;
|
|
29
|
+
message?: string;
|
|
30
|
+
} | {
|
|
31
|
+
type: 'maxLength';
|
|
32
|
+
value: number;
|
|
33
|
+
message?: string;
|
|
34
|
+
} | {
|
|
35
|
+
type: 'pattern';
|
|
36
|
+
value: RegExp;
|
|
37
|
+
message?: string;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'enum';
|
|
40
|
+
values: any[];
|
|
41
|
+
message?: string;
|
|
42
|
+
} | {
|
|
43
|
+
type: 'custom';
|
|
44
|
+
validator: (value: any) => boolean | Promise<boolean>;
|
|
45
|
+
message?: string;
|
|
46
|
+
} | {
|
|
47
|
+
type: 'array';
|
|
48
|
+
message?: string;
|
|
49
|
+
} | {
|
|
50
|
+
type: 'object';
|
|
51
|
+
message?: string;
|
|
52
|
+
};
|
|
53
|
+
export interface FieldValidation {
|
|
54
|
+
rules: ValidationRule[];
|
|
55
|
+
message?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface ValidationSchema {
|
|
58
|
+
[field: string]: FieldValidation | ValidationRule[];
|
|
59
|
+
}
|
|
60
|
+
export interface ValidationOptions {
|
|
61
|
+
source?: ValidationSource | ValidationSource[];
|
|
62
|
+
abortEarly?: boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Validation middleware factory for request data validation
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* import { validate } from '@bhandari88/express-auth';
|
|
70
|
+
*
|
|
71
|
+
* // Simple validation
|
|
72
|
+
* app.post('/login', validate({
|
|
73
|
+
* email: {
|
|
74
|
+
* rules: [
|
|
75
|
+
* { type: 'required', message: 'Email is required' },
|
|
76
|
+
* { type: 'email', message: 'Invalid email format' }
|
|
77
|
+
* ]
|
|
78
|
+
* },
|
|
79
|
+
* password: {
|
|
80
|
+
* rules: [
|
|
81
|
+
* { type: 'required', message: 'Password is required' },
|
|
82
|
+
* { type: 'minLength', value: 8, message: 'Password must be at least 8 characters' }
|
|
83
|
+
* ]
|
|
84
|
+
* }
|
|
85
|
+
* }), (req, res) => {
|
|
86
|
+
* // req.body is validated
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* // Validate query parameters
|
|
90
|
+
* app.get('/users', validate({
|
|
91
|
+
* page: {
|
|
92
|
+
* rules: [
|
|
93
|
+
* { type: 'number', message: 'Page must be a number' },
|
|
94
|
+
* { type: 'min', value: 1, message: 'Page must be at least 1' }
|
|
95
|
+
* ]
|
|
96
|
+
* }
|
|
97
|
+
* }, { source: 'query' }), (req, res) => {
|
|
98
|
+
* // req.query is validated
|
|
99
|
+
* });
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export declare function validate(schema: ValidationSchema, options?: ValidationOptions): RequestHandler;
|
|
103
|
+
/**
|
|
104
|
+
* Validate request body
|
|
105
|
+
*/
|
|
106
|
+
export declare function validateBody(schema: ValidationSchema, options?: Omit<ValidationOptions, 'source'>): RequestHandler;
|
|
107
|
+
/**
|
|
108
|
+
* Validate request query parameters
|
|
109
|
+
*/
|
|
110
|
+
export declare function validateQuery(schema: ValidationSchema, options?: Omit<ValidationOptions, 'source'>): RequestHandler;
|
|
111
|
+
/**
|
|
112
|
+
* Validate request route parameters
|
|
113
|
+
*/
|
|
114
|
+
export declare function validateParams(schema: ValidationSchema, options?: Omit<ValidationOptions, 'source'>): RequestHandler;
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validate = validate;
|
|
4
|
+
exports.validateBody = validateBody;
|
|
5
|
+
exports.validateQuery = validateQuery;
|
|
6
|
+
exports.validateParams = validateParams;
|
|
7
|
+
/**
|
|
8
|
+
* Validation middleware factory for request data validation
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { validate } from '@bhandari88/express-auth';
|
|
13
|
+
*
|
|
14
|
+
* // Simple validation
|
|
15
|
+
* app.post('/login', validate({
|
|
16
|
+
* email: {
|
|
17
|
+
* rules: [
|
|
18
|
+
* { type: 'required', message: 'Email is required' },
|
|
19
|
+
* { type: 'email', message: 'Invalid email format' }
|
|
20
|
+
* ]
|
|
21
|
+
* },
|
|
22
|
+
* password: {
|
|
23
|
+
* rules: [
|
|
24
|
+
* { type: 'required', message: 'Password is required' },
|
|
25
|
+
* { type: 'minLength', value: 8, message: 'Password must be at least 8 characters' }
|
|
26
|
+
* ]
|
|
27
|
+
* }
|
|
28
|
+
* }), (req, res) => {
|
|
29
|
+
* // req.body is validated
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* // Validate query parameters
|
|
33
|
+
* app.get('/users', validate({
|
|
34
|
+
* page: {
|
|
35
|
+
* rules: [
|
|
36
|
+
* { type: 'number', message: 'Page must be a number' },
|
|
37
|
+
* { type: 'min', value: 1, message: 'Page must be at least 1' }
|
|
38
|
+
* ]
|
|
39
|
+
* }
|
|
40
|
+
* }, { source: 'query' }), (req, res) => {
|
|
41
|
+
* // req.query is validated
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
function validate(schema, options = {}) {
|
|
46
|
+
const { source = 'body', abortEarly = false, } = options;
|
|
47
|
+
const sources = Array.isArray(source) ? source : [source];
|
|
48
|
+
return async (req, res, next) => {
|
|
49
|
+
try {
|
|
50
|
+
const errors = [];
|
|
51
|
+
// Collect data from all specified sources
|
|
52
|
+
const dataToValidate = {};
|
|
53
|
+
for (const src of sources) {
|
|
54
|
+
if (src === 'body') {
|
|
55
|
+
Object.assign(dataToValidate, req.body);
|
|
56
|
+
}
|
|
57
|
+
else if (src === 'query') {
|
|
58
|
+
Object.assign(dataToValidate, req.query);
|
|
59
|
+
}
|
|
60
|
+
else if (src === 'params') {
|
|
61
|
+
Object.assign(dataToValidate, req.params);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Validate each field in the schema
|
|
65
|
+
for (const [field, fieldValidation] of Object.entries(schema)) {
|
|
66
|
+
const value = dataToValidate[field];
|
|
67
|
+
// Get rules array
|
|
68
|
+
let rules;
|
|
69
|
+
let fieldMessage;
|
|
70
|
+
if (Array.isArray(fieldValidation)) {
|
|
71
|
+
rules = fieldValidation;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
rules = fieldValidation.rules;
|
|
75
|
+
fieldMessage = fieldValidation.message;
|
|
76
|
+
}
|
|
77
|
+
// Validate each rule
|
|
78
|
+
for (const rule of rules) {
|
|
79
|
+
const error = await validateRule(field, value, rule, fieldMessage);
|
|
80
|
+
if (error) {
|
|
81
|
+
errors.push(error);
|
|
82
|
+
if (abortEarly) {
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (abortEarly && errors.length > 0) {
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (errors.length > 0) {
|
|
92
|
+
res.status(400).json({
|
|
93
|
+
success: false,
|
|
94
|
+
error: 'Validation failed',
|
|
95
|
+
errors: errors.length === 1
|
|
96
|
+
? { [errors[0].field]: errors[0].message }
|
|
97
|
+
: errors.reduce((acc, err) => {
|
|
98
|
+
acc[err.field] = err.message;
|
|
99
|
+
return acc;
|
|
100
|
+
}, {}),
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
next();
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
res.status(500).json({
|
|
108
|
+
success: false,
|
|
109
|
+
error: 'Validation error',
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Validate a single rule
|
|
116
|
+
*/
|
|
117
|
+
async function validateRule(field, value, rule, fieldMessage) {
|
|
118
|
+
const getMessage = (defaultMsg) => {
|
|
119
|
+
return rule.message || fieldMessage || defaultMsg;
|
|
120
|
+
};
|
|
121
|
+
switch (rule.type) {
|
|
122
|
+
case 'required':
|
|
123
|
+
if (value === undefined || value === null || value === '') {
|
|
124
|
+
return {
|
|
125
|
+
field,
|
|
126
|
+
message: getMessage(`${field} is required`),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
case 'string':
|
|
131
|
+
if (value !== undefined && typeof value !== 'string') {
|
|
132
|
+
return {
|
|
133
|
+
field,
|
|
134
|
+
message: getMessage(`${field} must be a string`),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
case 'number':
|
|
139
|
+
if (value !== undefined && (typeof value !== 'number' || isNaN(value))) {
|
|
140
|
+
// Try to parse string numbers
|
|
141
|
+
if (typeof value === 'string' && !isNaN(Number(value))) {
|
|
142
|
+
return null; // Valid number string
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
field,
|
|
146
|
+
message: getMessage(`${field} must be a number`),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
case 'boolean':
|
|
151
|
+
if (value !== undefined && typeof value !== 'boolean') {
|
|
152
|
+
// Accept string booleans
|
|
153
|
+
if (typeof value === 'string' && (value === 'true' || value === 'false')) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
field,
|
|
158
|
+
message: getMessage(`${field} must be a boolean`),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
case 'email':
|
|
163
|
+
if (value !== undefined && value !== '') {
|
|
164
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
165
|
+
if (!emailRegex.test(String(value))) {
|
|
166
|
+
return {
|
|
167
|
+
field,
|
|
168
|
+
message: getMessage(`${field} must be a valid email`),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
case 'min':
|
|
174
|
+
if (value !== undefined && value !== '') {
|
|
175
|
+
const numValue = typeof value === 'string' ? Number(value) : value;
|
|
176
|
+
if (typeof numValue === 'number' && !isNaN(numValue) && numValue < rule.value) {
|
|
177
|
+
return {
|
|
178
|
+
field,
|
|
179
|
+
message: getMessage(`${field} must be at least ${rule.value}`),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
case 'max':
|
|
185
|
+
if (value !== undefined && value !== '') {
|
|
186
|
+
const numValue = typeof value === 'string' ? Number(value) : value;
|
|
187
|
+
if (typeof numValue === 'number' && !isNaN(numValue) && numValue > rule.value) {
|
|
188
|
+
return {
|
|
189
|
+
field,
|
|
190
|
+
message: getMessage(`${field} must be at most ${rule.value}`),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
break;
|
|
195
|
+
case 'minLength':
|
|
196
|
+
if (value !== undefined && value !== '') {
|
|
197
|
+
const strValue = String(value);
|
|
198
|
+
if (strValue.length < rule.value) {
|
|
199
|
+
return {
|
|
200
|
+
field,
|
|
201
|
+
message: getMessage(`${field} must be at least ${rule.value} characters`),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
case 'maxLength':
|
|
207
|
+
if (value !== undefined && value !== '') {
|
|
208
|
+
const strValue = String(value);
|
|
209
|
+
if (strValue.length > rule.value) {
|
|
210
|
+
return {
|
|
211
|
+
field,
|
|
212
|
+
message: getMessage(`${field} must be at most ${rule.value} characters`),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
case 'pattern':
|
|
218
|
+
if (value !== undefined && value !== '') {
|
|
219
|
+
if (!rule.value.test(String(value))) {
|
|
220
|
+
return {
|
|
221
|
+
field,
|
|
222
|
+
message: getMessage(`${field} format is invalid`),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
case 'enum':
|
|
228
|
+
if (value !== undefined && value !== '') {
|
|
229
|
+
if (!rule.values.includes(value)) {
|
|
230
|
+
return {
|
|
231
|
+
field,
|
|
232
|
+
message: getMessage(`${field} must be one of: ${rule.values.join(', ')}`),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
case 'array':
|
|
238
|
+
if (value !== undefined && !Array.isArray(value)) {
|
|
239
|
+
return {
|
|
240
|
+
field,
|
|
241
|
+
message: getMessage(`${field} must be an array`),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
break;
|
|
245
|
+
case 'object':
|
|
246
|
+
if (value !== undefined && (typeof value !== 'object' || Array.isArray(value) || value === null)) {
|
|
247
|
+
return {
|
|
248
|
+
field,
|
|
249
|
+
message: getMessage(`${field} must be an object`),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
253
|
+
case 'custom':
|
|
254
|
+
try {
|
|
255
|
+
const isValid = await rule.validator(value);
|
|
256
|
+
if (!isValid) {
|
|
257
|
+
return {
|
|
258
|
+
field,
|
|
259
|
+
message: getMessage(`${field} is invalid`),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
return {
|
|
265
|
+
field,
|
|
266
|
+
message: getMessage(`${field} validation failed`),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Validate request body
|
|
275
|
+
*/
|
|
276
|
+
function validateBody(schema, options = {}) {
|
|
277
|
+
return validate(schema, { ...options, source: 'body' });
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Validate request query parameters
|
|
281
|
+
*/
|
|
282
|
+
function validateQuery(schema, options = {}) {
|
|
283
|
+
return validate(schema, { ...options, source: 'query' });
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Validate request route parameters
|
|
287
|
+
*/
|
|
288
|
+
function validateParams(schema, options = {}) {
|
|
289
|
+
return validate(schema, { ...options, source: 'params' });
|
|
290
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bhandari88/express-auth",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Plug-and-play authentication handler for Express.js with TypeScript supporting email, username, phone, and social login",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -23,19 +23,19 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"jsonwebtoken": "^9.0.2",
|
|
25
25
|
"passport": "^0.7.0",
|
|
26
|
-
"passport-facebook": "^3.0.0",
|
|
27
26
|
"passport-google-oauth20": "^2.0.0",
|
|
27
|
+
"passport-facebook": "^3.0.0",
|
|
28
28
|
"validator": "^13.11.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/express": "^4.17.21",
|
|
32
32
|
"@types/jsonwebtoken": "^9.0.5",
|
|
33
|
-
"@types/node": "^20.10.5",
|
|
34
33
|
"@types/passport": "^1.0.16",
|
|
35
|
-
"@types/passport-facebook": "^3.0.3",
|
|
36
34
|
"@types/passport-google-oauth20": "^2.0.14",
|
|
35
|
+
"@types/passport-facebook": "^3.0.3",
|
|
37
36
|
"@types/validator": "^13.11.7",
|
|
38
|
-
"
|
|
37
|
+
"@types/node": "^20.10.5",
|
|
38
|
+
"typescript": "^5.3.3"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"express": "^4.18.0"
|