@bhandari88/express-auth 1.0.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/INSTALLATION.md +71 -0
- package/README.md +425 -0
- package/dist/handlers/index.d.ts +2 -0
- package/dist/handlers/index.js +7 -0
- package/dist/handlers/local-auth.d.ts +12 -0
- package/dist/handlers/local-auth.js +182 -0
- package/dist/handlers/social-auth.d.ts +15 -0
- package/dist/handlers/social-auth.js +164 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.js +232 -0
- package/dist/middleware/auth.middleware.d.ts +11 -0
- package/dist/middleware/auth.middleware.js +102 -0
- package/dist/types/index.d.ts +84 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/jwt.d.ts +15 -0
- package/dist/utils/jwt.js +120 -0
- package/dist/utils/password.d.ts +14 -0
- package/dist/utils/password.js +103 -0
- package/dist/utils/validator.d.ts +25 -0
- package/dist/utils/validator.js +63 -0
- package/package.json +60 -0
package/INSTALLATION.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Installation Guide
|
|
2
|
+
|
|
3
|
+
## Quick Setup
|
|
4
|
+
|
|
5
|
+
### 1. Install Dependencies
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This will install all required dependencies including:
|
|
12
|
+
- Node.js crypto (built-in, no installation needed - used for password hashing with scrypt)
|
|
13
|
+
- jsonwebtoken (JWT tokens)
|
|
14
|
+
- passport (authentication strategies)
|
|
15
|
+
- passport-google-oauth20 (Google login)
|
|
16
|
+
- passport-facebook (Facebook login)
|
|
17
|
+
- validator (input validation)
|
|
18
|
+
|
|
19
|
+
### 2. Install Peer Dependencies
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install express
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 3. Build the Package
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm run build
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This will compile TypeScript to JavaScript in the `dist/` directory.
|
|
32
|
+
|
|
33
|
+
### 4. Set Environment Variables
|
|
34
|
+
|
|
35
|
+
Create a `.env` file (or use environment variables):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
|
|
39
|
+
REFRESH_TOKEN_SECRET=your-super-secret-refresh-token-key
|
|
40
|
+
GOOGLE_CLIENT_ID=your-google-client-id
|
|
41
|
+
GOOGLE_CLIENT_SECRET=your-google-client-secret
|
|
42
|
+
FACEBOOK_CLIENT_ID=your-facebook-app-id
|
|
43
|
+
FACEBOOK_CLIENT_SECRET=your-facebook-app-secret
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 5. Implement User Repository
|
|
47
|
+
|
|
48
|
+
You need to implement the `UserRepository` interface for your database. See `example/usage.ts` for a MongoDB/Mongoose example.
|
|
49
|
+
|
|
50
|
+
### 6. Use the Package
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { Auth, AuthConfig } from './src';
|
|
54
|
+
// or if installed as package: from '@auth-boiler/express-auth'
|
|
55
|
+
|
|
56
|
+
const auth = new Auth(config, userRepository);
|
|
57
|
+
auth.setupRoutes(app, '/api/auth');
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Development
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm run dev # Watch mode for TypeScript compilation
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Publishing
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm run build # Build first
|
|
70
|
+
npm publish # Publish to npm registry
|
|
71
|
+
```
|
package/README.md
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# @auth-boiler/express-auth
|
|
2
|
+
|
|
3
|
+
A plug-and-play authentication handler for Express.js with TypeScript supporting multiple authentication methods including email, username, phone, and social login (Google, Facebook).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Multiple Authentication Methods**
|
|
8
|
+
- Email/Password
|
|
9
|
+
- Username/Password
|
|
10
|
+
- Phone/Password
|
|
11
|
+
- Social Login (Google, Facebook)
|
|
12
|
+
|
|
13
|
+
- ✅ **Security Best Practices**
|
|
14
|
+
- JWT-based authentication with access and refresh tokens
|
|
15
|
+
- Node.js crypto (scrypt) password hashing (memory-hard, highly secure)
|
|
16
|
+
- Input validation and sanitization
|
|
17
|
+
- Token expiration and refresh mechanism
|
|
18
|
+
- Secure password requirements
|
|
19
|
+
- Timing-safe password comparison to prevent timing attacks
|
|
20
|
+
|
|
21
|
+
- ✅ **Easy Integration**
|
|
22
|
+
- Plug-and-play API
|
|
23
|
+
- Express middleware for protected routes
|
|
24
|
+
- Role-based access control
|
|
25
|
+
- Automatic route setup
|
|
26
|
+
- TypeScript support
|
|
27
|
+
|
|
28
|
+
- ✅ **Flexible**
|
|
29
|
+
- Works with any database/user repository
|
|
30
|
+
- Configurable token expiration
|
|
31
|
+
- Optional refresh tokens
|
|
32
|
+
- Customizable user fields
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install @auth-boiler/express-auth
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Peer Dependencies
|
|
41
|
+
|
|
42
|
+
Make sure you have the following peer dependencies installed:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install express
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
### 1. Install Dependencies
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install @auth-boiler/express-auth express
|
|
54
|
+
npm install --save-dev typescript @types/express @types/node
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. Create User Repository
|
|
58
|
+
|
|
59
|
+
Implement the `UserRepository` interface for your database:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { UserRepository, UserDocument } from '@auth-boiler/express-auth';
|
|
63
|
+
|
|
64
|
+
class MyUserRepository implements UserRepository {
|
|
65
|
+
async findById(id: string): Promise<UserDocument | null> {
|
|
66
|
+
// Your database query logic
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async findByEmail(email: string): Promise<UserDocument | null> {
|
|
70
|
+
// Your database query logic
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async findByUsername(username: string): Promise<UserDocument | null> {
|
|
74
|
+
// Your database query logic
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async findByPhone(phone: string): Promise<UserDocument | null> {
|
|
78
|
+
// Your database query logic
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async findByProvider(provider: string, providerId: string): Promise<UserDocument | null> {
|
|
82
|
+
// Your database query logic
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async create(userData: Partial<UserDocument>): Promise<UserDocument> {
|
|
86
|
+
// Your database create logic
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async update(id: string, updateData: Partial<UserDocument>): Promise<UserDocument | null> {
|
|
90
|
+
// Your database update logic
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async delete(id: string): Promise<boolean> {
|
|
94
|
+
// Your database delete logic
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 3. Configure and Initialize Auth
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import express from 'express';
|
|
103
|
+
import { Auth, AuthConfig } from '@auth-boiler/express-auth';
|
|
104
|
+
import MyUserRepository from './repositories/MyUserRepository';
|
|
105
|
+
|
|
106
|
+
const app = express();
|
|
107
|
+
app.use(express.json());
|
|
108
|
+
|
|
109
|
+
const authConfig: AuthConfig = {
|
|
110
|
+
jwtSecret: process.env.JWT_SECRET || 'your-secret-key',
|
|
111
|
+
jwtExpiresIn: '15m',
|
|
112
|
+
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET || 'your-refresh-secret',
|
|
113
|
+
refreshTokenExpiresIn: '7d',
|
|
114
|
+
bcryptRounds: 16384, // Scrypt cost parameter (2^14, configurable)
|
|
115
|
+
enableRefreshTokens: true,
|
|
116
|
+
socialAuth: {
|
|
117
|
+
google: {
|
|
118
|
+
clientID: process.env.GOOGLE_CLIENT_ID || '',
|
|
119
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
|
|
120
|
+
callbackURL: 'http://localhost:3000/api/auth/google/callback',
|
|
121
|
+
},
|
|
122
|
+
facebook: {
|
|
123
|
+
clientID: process.env.FACEBOOK_CLIENT_ID || '',
|
|
124
|
+
clientSecret: process.env.FACEBOOK_CLIENT_SECRET || '',
|
|
125
|
+
callbackURL: 'http://localhost:3000/api/auth/facebook/callback',
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const userRepository = new MyUserRepository();
|
|
131
|
+
const auth = new Auth(authConfig, userRepository);
|
|
132
|
+
|
|
133
|
+
// Setup all auth routes automatically
|
|
134
|
+
auth.setupRoutes(app, '/api/auth');
|
|
135
|
+
|
|
136
|
+
app.listen(3000, () => {
|
|
137
|
+
console.log('Server running on port 3000');
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 4. Use Protected Routes
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// Protect a route
|
|
145
|
+
app.get('/api/profile', auth.getAuthMiddleware(), (req, res) => {
|
|
146
|
+
const user = (req as any).user;
|
|
147
|
+
res.json({ user });
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Optional authentication (doesn't fail if no token)
|
|
151
|
+
app.get('/api/public', auth.getOptionalAuthMiddleware(), (req, res) => {
|
|
152
|
+
const user = (req as any).user; // May be undefined
|
|
153
|
+
res.json({ user });
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Role-based access control
|
|
157
|
+
app.get('/api/admin',
|
|
158
|
+
auth.getAuthMiddleware(),
|
|
159
|
+
auth.requireRole(['admin', 'superadmin']),
|
|
160
|
+
(req, res) => {
|
|
161
|
+
res.json({ message: 'Admin access granted' });
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## API Endpoints
|
|
167
|
+
|
|
168
|
+
### Register User
|
|
169
|
+
|
|
170
|
+
**POST** `/api/auth/register`
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"email": "user@example.com",
|
|
175
|
+
"password": "securepassword123"
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Or with username:
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"username": "johndoe",
|
|
183
|
+
"password": "securepassword123"
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Or with phone:
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"phone": "+1234567890",
|
|
191
|
+
"password": "securepassword123"
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Response:**
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"success": true,
|
|
199
|
+
"user": {
|
|
200
|
+
"id": "user-id",
|
|
201
|
+
"email": "user@example.com",
|
|
202
|
+
"verified": false
|
|
203
|
+
},
|
|
204
|
+
"tokens": {
|
|
205
|
+
"accessToken": "jwt-token",
|
|
206
|
+
"refreshToken": "refresh-token",
|
|
207
|
+
"expiresIn": 900
|
|
208
|
+
},
|
|
209
|
+
"message": "User registered successfully"
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Login
|
|
214
|
+
|
|
215
|
+
**POST** `/api/auth/login`
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"email": "user@example.com",
|
|
220
|
+
"password": "securepassword123"
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Or with username:
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"username": "johndoe",
|
|
228
|
+
"password": "securepassword123"
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Or with phone:
|
|
233
|
+
```json
|
|
234
|
+
{
|
|
235
|
+
"phone": "+1234567890",
|
|
236
|
+
"password": "securepassword123"
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Response:**
|
|
241
|
+
```json
|
|
242
|
+
{
|
|
243
|
+
"success": true,
|
|
244
|
+
"user": {
|
|
245
|
+
"id": "user-id",
|
|
246
|
+
"email": "user@example.com"
|
|
247
|
+
},
|
|
248
|
+
"tokens": {
|
|
249
|
+
"accessToken": "jwt-token",
|
|
250
|
+
"refreshToken": "refresh-token",
|
|
251
|
+
"expiresIn": 900
|
|
252
|
+
},
|
|
253
|
+
"message": "Login successful"
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Refresh Token
|
|
258
|
+
|
|
259
|
+
**POST** `/api/auth/refresh`
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
{
|
|
263
|
+
"refreshToken": "your-refresh-token"
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Response:**
|
|
268
|
+
```json
|
|
269
|
+
{
|
|
270
|
+
"success": true,
|
|
271
|
+
"tokens": {
|
|
272
|
+
"accessToken": "new-jwt-token",
|
|
273
|
+
"refreshToken": "new-refresh-token",
|
|
274
|
+
"expiresIn": 900
|
|
275
|
+
},
|
|
276
|
+
"message": "Token refreshed successfully"
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Change Password
|
|
281
|
+
|
|
282
|
+
**POST** `/api/auth/change-password`
|
|
283
|
+
|
|
284
|
+
**Headers:**
|
|
285
|
+
```
|
|
286
|
+
Authorization: Bearer <access-token>
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Body:**
|
|
290
|
+
```json
|
|
291
|
+
{
|
|
292
|
+
"oldPassword": "oldpassword123",
|
|
293
|
+
"newPassword": "newpassword456"
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Social Login
|
|
298
|
+
|
|
299
|
+
**Google:**
|
|
300
|
+
- **GET** `/api/auth/google` - Initiate Google login
|
|
301
|
+
- **GET** `/api/auth/google/callback` - Google callback (configured automatically)
|
|
302
|
+
|
|
303
|
+
**Facebook:**
|
|
304
|
+
- **GET** `/api/auth/facebook` - Initiate Facebook login
|
|
305
|
+
- **GET** `/api/auth/facebook/callback` - Facebook callback (configured automatically)
|
|
306
|
+
|
|
307
|
+
## Programmatic Usage
|
|
308
|
+
|
|
309
|
+
You can also use the auth methods programmatically:
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// Register
|
|
313
|
+
const result = await auth.register({
|
|
314
|
+
email: 'user@example.com',
|
|
315
|
+
password: 'password123',
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Login
|
|
319
|
+
const loginResult = await auth.login({
|
|
320
|
+
email: 'user@example.com',
|
|
321
|
+
password: 'password123',
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Change password
|
|
325
|
+
const changeResult = await auth.changePassword(
|
|
326
|
+
userId,
|
|
327
|
+
'oldPassword',
|
|
328
|
+
'newPassword'
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
// Refresh token
|
|
332
|
+
const refreshResult = await auth.refreshToken(refreshToken);
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Configuration Options
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
interface AuthConfig {
|
|
339
|
+
jwtSecret: string; // Required: Secret for JWT signing
|
|
340
|
+
jwtExpiresIn?: string; // Optional: Access token expiration (default: '15m')
|
|
341
|
+
refreshTokenSecret?: string; // Optional: Secret for refresh tokens (defaults to jwtSecret)
|
|
342
|
+
refreshTokenExpiresIn?: string; // Optional: Refresh token expiration (default: '7d')
|
|
343
|
+
bcryptRounds?: number; // Optional: Scrypt cost parameter N (default: 16384 = 2^14)
|
|
344
|
+
enableRefreshTokens?: boolean; // Optional: Enable refresh tokens (default: true)
|
|
345
|
+
socialAuth?: { // Optional: Social auth configuration
|
|
346
|
+
google?: {
|
|
347
|
+
clientID: string;
|
|
348
|
+
clientSecret: string;
|
|
349
|
+
callbackURL: string;
|
|
350
|
+
};
|
|
351
|
+
facebook?: {
|
|
352
|
+
clientID: string;
|
|
353
|
+
clientSecret: string;
|
|
354
|
+
callbackURL: string;
|
|
355
|
+
};
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Security Best Practices
|
|
361
|
+
|
|
362
|
+
1. **Environment Variables**: Always use environment variables for secrets:
|
|
363
|
+
```bash
|
|
364
|
+
JWT_SECRET=your-super-secret-key
|
|
365
|
+
REFRESH_TOKEN_SECRET=your-refresh-secret-key
|
|
366
|
+
GOOGLE_CLIENT_ID=your-google-client-id
|
|
367
|
+
GOOGLE_CLIENT_SECRET=your-google-client-secret
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
2. **Password Requirements**:
|
|
371
|
+
- Minimum 6 characters
|
|
372
|
+
- Maximum 128 characters
|
|
373
|
+
- Hashed with Node.js crypto.scrypt (memory-hard, highly secure)
|
|
374
|
+
- Uses timing-safe comparison to prevent timing attacks
|
|
375
|
+
- Default cost parameter: 16384 (2^14)
|
|
376
|
+
|
|
377
|
+
3. **Token Expiration**:
|
|
378
|
+
- Access tokens: Short-lived (15 minutes default)
|
|
379
|
+
- Refresh tokens: Long-lived (7 days default)
|
|
380
|
+
|
|
381
|
+
4. **HTTPS**: Always use HTTPS in production
|
|
382
|
+
|
|
383
|
+
5. **Input Validation**: All inputs are validated and sanitized automatically
|
|
384
|
+
|
|
385
|
+
## Types
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
interface User {
|
|
389
|
+
id: string;
|
|
390
|
+
email?: string;
|
|
391
|
+
username?: string;
|
|
392
|
+
phone?: string;
|
|
393
|
+
password?: string;
|
|
394
|
+
provider?: 'local' | 'google' | 'facebook';
|
|
395
|
+
providerId?: string;
|
|
396
|
+
verified?: boolean;
|
|
397
|
+
[key: string]: any; // Additional custom fields
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
interface AuthResult {
|
|
401
|
+
success: boolean;
|
|
402
|
+
user?: User;
|
|
403
|
+
tokens?: AuthTokens;
|
|
404
|
+
message?: string;
|
|
405
|
+
error?: string;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
interface AuthTokens {
|
|
409
|
+
accessToken: string;
|
|
410
|
+
refreshToken?: string;
|
|
411
|
+
expiresIn: number;
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
## Example with MongoDB (Mongoose)
|
|
416
|
+
|
|
417
|
+
See `example/usage.ts` for a complete example using MongoDB with Mongoose.
|
|
418
|
+
|
|
419
|
+
## License
|
|
420
|
+
|
|
421
|
+
MIT
|
|
422
|
+
|
|
423
|
+
## Contributing
|
|
424
|
+
|
|
425
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SocialAuthHandler = exports.LocalAuthHandler = void 0;
|
|
4
|
+
var local_auth_1 = require("./local-auth");
|
|
5
|
+
Object.defineProperty(exports, "LocalAuthHandler", { enumerable: true, get: function () { return local_auth_1.LocalAuthHandler; } });
|
|
6
|
+
var social_auth_1 = require("./social-auth");
|
|
7
|
+
Object.defineProperty(exports, "SocialAuthHandler", { enumerable: true, get: function () { return social_auth_1.SocialAuthHandler; } });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { UserRepository, LoginCredentials, RegisterData, AuthResult } from '../types';
|
|
2
|
+
import { JwtService } from '../utils/jwt';
|
|
3
|
+
import { PasswordService } from '../utils/password';
|
|
4
|
+
export declare class LocalAuthHandler {
|
|
5
|
+
private userRepository;
|
|
6
|
+
private jwtService;
|
|
7
|
+
private passwordService;
|
|
8
|
+
constructor(userRepository: UserRepository, jwtService: JwtService, passwordService: PasswordService);
|
|
9
|
+
register(data: RegisterData): Promise<AuthResult>;
|
|
10
|
+
login(credentials: LoginCredentials): Promise<AuthResult>;
|
|
11
|
+
changePassword(userId: string, oldPassword: string, newPassword: string): Promise<AuthResult>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocalAuthHandler = void 0;
|
|
4
|
+
const validator_1 = require("../utils/validator");
|
|
5
|
+
class LocalAuthHandler {
|
|
6
|
+
constructor(userRepository, jwtService, passwordService) {
|
|
7
|
+
this.userRepository = userRepository;
|
|
8
|
+
this.jwtService = jwtService;
|
|
9
|
+
this.passwordService = passwordService;
|
|
10
|
+
}
|
|
11
|
+
async register(data) {
|
|
12
|
+
try {
|
|
13
|
+
// Validate input
|
|
14
|
+
const validation = validator_1.ValidatorService.validateRegisterData(data);
|
|
15
|
+
if (!validation.valid) {
|
|
16
|
+
return { success: false, error: validation.message };
|
|
17
|
+
}
|
|
18
|
+
// Check if user already exists
|
|
19
|
+
let existingUser = null;
|
|
20
|
+
if (data.email) {
|
|
21
|
+
const sanitizedEmail = validator_1.ValidatorService.sanitizeEmail(data.email);
|
|
22
|
+
existingUser = await this.userRepository.findByEmail(sanitizedEmail);
|
|
23
|
+
if (existingUser) {
|
|
24
|
+
return { success: false, error: 'User with this email already exists' };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (data.username) {
|
|
28
|
+
existingUser = await this.userRepository.findByUsername(data.username);
|
|
29
|
+
if (existingUser) {
|
|
30
|
+
return { success: false, error: 'Username already taken' };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (data.phone) {
|
|
34
|
+
const sanitizedPhone = validator_1.ValidatorService.sanitizePhone(data.phone);
|
|
35
|
+
existingUser = await this.userRepository.findByPhone(sanitizedPhone);
|
|
36
|
+
if (existingUser) {
|
|
37
|
+
return { success: false, error: 'User with this phone number already exists' };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Validate password
|
|
41
|
+
const passwordValidation = this.passwordService.validatePassword(data.password);
|
|
42
|
+
if (!passwordValidation.valid) {
|
|
43
|
+
return { success: false, error: passwordValidation.message };
|
|
44
|
+
}
|
|
45
|
+
// Hash password
|
|
46
|
+
const hashedPassword = await this.passwordService.hash(data.password);
|
|
47
|
+
// Create user
|
|
48
|
+
const userData = {
|
|
49
|
+
password: hashedPassword,
|
|
50
|
+
provider: 'local',
|
|
51
|
+
verified: false,
|
|
52
|
+
};
|
|
53
|
+
if (data.email) {
|
|
54
|
+
userData.email = validator_1.ValidatorService.sanitizeEmail(data.email);
|
|
55
|
+
}
|
|
56
|
+
if (data.username) {
|
|
57
|
+
userData.username = data.username.toLowerCase().trim();
|
|
58
|
+
}
|
|
59
|
+
if (data.phone) {
|
|
60
|
+
userData.phone = validator_1.ValidatorService.sanitizePhone(data.phone);
|
|
61
|
+
}
|
|
62
|
+
// Add any additional fields
|
|
63
|
+
const { email, username, phone, password, ...additionalFields } = data;
|
|
64
|
+
Object.assign(userData, additionalFields);
|
|
65
|
+
const user = await this.userRepository.create(userData);
|
|
66
|
+
// Generate tokens
|
|
67
|
+
const tokens = this.jwtService.generateTokens({
|
|
68
|
+
userId: user.id || user._id || '',
|
|
69
|
+
email: user.email,
|
|
70
|
+
username: user.username,
|
|
71
|
+
phone: user.phone,
|
|
72
|
+
});
|
|
73
|
+
// Remove password from response
|
|
74
|
+
const { password: _, ...userWithoutPassword } = user;
|
|
75
|
+
const userResponse = userWithoutPassword;
|
|
76
|
+
return {
|
|
77
|
+
success: true,
|
|
78
|
+
user: userResponse,
|
|
79
|
+
tokens,
|
|
80
|
+
message: 'User registered successfully',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return {
|
|
85
|
+
success: false,
|
|
86
|
+
error: error instanceof Error ? error.message : 'Registration failed',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async login(credentials) {
|
|
91
|
+
try {
|
|
92
|
+
// Validate input
|
|
93
|
+
const validation = validator_1.ValidatorService.validateLoginCredentials(credentials);
|
|
94
|
+
if (!validation.valid) {
|
|
95
|
+
return { success: false, error: validation.message };
|
|
96
|
+
}
|
|
97
|
+
// Find user
|
|
98
|
+
let user = null;
|
|
99
|
+
if (credentials.email) {
|
|
100
|
+
const sanitizedEmail = validator_1.ValidatorService.sanitizeEmail(credentials.email);
|
|
101
|
+
user = await this.userRepository.findByEmail(sanitizedEmail);
|
|
102
|
+
}
|
|
103
|
+
else if (credentials.username) {
|
|
104
|
+
user = await this.userRepository.findByUsername(credentials.username);
|
|
105
|
+
}
|
|
106
|
+
else if (credentials.phone) {
|
|
107
|
+
const sanitizedPhone = validator_1.ValidatorService.sanitizePhone(credentials.phone);
|
|
108
|
+
user = await this.userRepository.findByPhone(sanitizedPhone);
|
|
109
|
+
}
|
|
110
|
+
if (!user) {
|
|
111
|
+
return { success: false, error: 'Invalid credentials' };
|
|
112
|
+
}
|
|
113
|
+
// Check if user has password (not social login only)
|
|
114
|
+
if (!user.password) {
|
|
115
|
+
return { success: false, error: 'Please use social login for this account' };
|
|
116
|
+
}
|
|
117
|
+
// Verify password
|
|
118
|
+
const isPasswordValid = await this.passwordService.compare(credentials.password, user.password);
|
|
119
|
+
if (!isPasswordValid) {
|
|
120
|
+
return { success: false, error: 'Invalid credentials' };
|
|
121
|
+
}
|
|
122
|
+
// Generate tokens
|
|
123
|
+
const tokens = this.jwtService.generateTokens({
|
|
124
|
+
userId: user.id || user._id || '',
|
|
125
|
+
email: user.email,
|
|
126
|
+
username: user.username,
|
|
127
|
+
phone: user.phone,
|
|
128
|
+
});
|
|
129
|
+
// Remove password from response
|
|
130
|
+
const { password: _, ...userWithoutPassword } = user;
|
|
131
|
+
const userResponse = userWithoutPassword;
|
|
132
|
+
return {
|
|
133
|
+
success: true,
|
|
134
|
+
user: userResponse,
|
|
135
|
+
tokens,
|
|
136
|
+
message: 'Login successful',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
return {
|
|
141
|
+
success: false,
|
|
142
|
+
error: error instanceof Error ? error.message : 'Login failed',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async changePassword(userId, oldPassword, newPassword) {
|
|
147
|
+
try {
|
|
148
|
+
const user = await this.userRepository.findById(userId);
|
|
149
|
+
if (!user) {
|
|
150
|
+
return { success: false, error: 'User not found' };
|
|
151
|
+
}
|
|
152
|
+
if (!user.password) {
|
|
153
|
+
return { success: false, error: 'Password change not available for social login accounts' };
|
|
154
|
+
}
|
|
155
|
+
// Verify old password
|
|
156
|
+
const isOldPasswordValid = await this.passwordService.compare(oldPassword, user.password);
|
|
157
|
+
if (!isOldPasswordValid) {
|
|
158
|
+
return { success: false, error: 'Current password is incorrect' };
|
|
159
|
+
}
|
|
160
|
+
// Validate new password
|
|
161
|
+
const passwordValidation = this.passwordService.validatePassword(newPassword);
|
|
162
|
+
if (!passwordValidation.valid) {
|
|
163
|
+
return { success: false, error: passwordValidation.message };
|
|
164
|
+
}
|
|
165
|
+
// Hash new password
|
|
166
|
+
const hashedPassword = await this.passwordService.hash(newPassword);
|
|
167
|
+
// Update user
|
|
168
|
+
await this.userRepository.update(userId, { password: hashedPassword });
|
|
169
|
+
return {
|
|
170
|
+
success: true,
|
|
171
|
+
message: 'Password changed successfully',
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
return {
|
|
176
|
+
success: false,
|
|
177
|
+
error: error instanceof Error ? error.message : 'Password change failed',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.LocalAuthHandler = LocalAuthHandler;
|