@bhandari88/express-auth 1.0.1 → 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 CHANGED
@@ -10,6 +10,11 @@ npm install @bhandari88/express-auth
10
10
 
11
11
  **Package URL**: [https://www.npmjs.com/package/@bhandari88/express-auth](https://www.npmjs.com/package/@bhandari88/express-auth)
12
12
 
13
+
14
+ ## Example
15
+
16
+ ***Example Application**: [https://github.com/manojsinghindiit/auth-boiler-example](https://github.com/manojsinghindiit/auth-boiler-example)
17
+
13
18
  ## Features
14
19
 
15
20
  - ✅ **Multiple Authentication Methods**
@@ -29,6 +34,7 @@ npm install @bhandari88/express-auth
29
34
  - ✅ **Easy Integration**
30
35
  - Plug-and-play API
31
36
  - Express middleware for protected routes
37
+ - Request data validation middleware with custom messages
32
38
  - Role-based access control
33
39
  - Automatic route setup
34
40
  - TypeScript support
@@ -165,6 +171,201 @@ app.get('/api/admin',
165
171
  );
166
172
  ```
167
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
+
168
369
  ## API Endpoints
169
370
 
170
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.0.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",