create-arktos 1.4.0 → 1.5.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.
@@ -1,533 +0,0 @@
1
- import jwt from 'jsonwebtoken';
2
- import crypto from 'crypto';
3
- import { createSuccessResponse, createErrorResponse } from '../utils/response.js';
4
- import { validation } from '../middleware/index.js';
5
- import {
6
- registerSchema,
7
- loginSchema,
8
- refreshTokenSchema,
9
- updateProfileSchema,
10
- changePasswordSchema
11
- } from '../schemas/index.js';
12
- import DatabaseService from '../services/database.service.js';
13
- import logger from '../config/logger.js';
14
- import { ERROR_CODES } from '../constants/errorCodes.js';
15
-
16
- const dbService = DatabaseService.getInstance();
17
- const prisma = dbService.getClient();
18
-
19
- const generateTokens = (userId) => {
20
- const accessToken = jwt.sign(
21
- { userId },
22
- process.env.JWT_SECRET
23
- );
24
-
25
- const refreshToken = jwt.sign(
26
- { userId },
27
- process.env.JWT_REFRESH_SECRET
28
- );
29
-
30
- return { accessToken, refreshToken };
31
- };
32
-
33
- const logLoginAttempt = async (
34
- userId,
35
- req,
36
- isSuccess,
37
- failReason
38
- ) => {
39
- try {
40
- if (userId === 'unknown') return;
41
-
42
- const clientIP = req.ip || req.socket.remoteAddress || 'unknown';
43
- const userAgent = req.headers['user-agent'] || 'unknown';
44
-
45
- await prisma.loginLog.create({
46
- data: {
47
- userId,
48
- loginType: 'EMAIL',
49
- ipAddress: clientIP,
50
- userAgent,
51
- isSuccess,
52
- failReason,
53
- },
54
- });
55
- } catch (error) {
56
- logger.error('Failed to log login attempt:', error);
57
- }
58
- };
59
-
60
- export const register = async (req, res) => {
61
- try {
62
- const validatedData = validation.request(registerSchema, req.body);
63
- const { email, password, firstName, lastName, username } = validatedData;
64
-
65
- // Check if user exists
66
- const existingUser = await prisma.user.findUnique({
67
- where: { email: email.toLowerCase() }
68
- });
69
-
70
- if (existingUser) {
71
- res.status(409).json(createErrorResponse(
72
- 'User already exists with this email',
73
- ERROR_CODES.AUTH_USER_EXISTS
74
- ));
75
- return;
76
- }
77
-
78
- // Check username if provided
79
- if (username) {
80
- const existingUsername = await prisma.user.findUnique({
81
- where: { username }
82
- });
83
-
84
- if (existingUsername) {
85
- res.status(409).json(createErrorResponse(
86
- 'Username already taken',
87
- ERROR_CODES.AUTH_USERNAME_TAKEN
88
- ));
89
- return;
90
- }
91
- }
92
-
93
- // Hash password
94
- const bcrypt = require('bcryptjs');
95
- const saltRounds = parseInt(process.env.BCRYPT_SALT_ROUNDS || '12');
96
- const hashedPassword = await bcrypt.hash(password, saltRounds);
97
-
98
- // Create user
99
- const user = await prisma.user.create({
100
- data: {
101
- email: email.toLowerCase(),
102
- password: hashedPassword,
103
- firstName,
104
- lastName,
105
- username,
106
- },
107
- select: {
108
- id: true,
109
- email: true,
110
- firstName: true,
111
- lastName: true,
112
- username: true,
113
- isEmailVerified: true,
114
- createdAt: true,
115
- }
116
- });
117
-
118
- // Create email verification
119
- const verificationToken = crypto.randomBytes(32).toString('hex');
120
-
121
- await prisma.emailVerification.create({
122
- data: {
123
- userId: user.id,
124
- token: verificationToken,
125
- email: user.email,
126
- expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours
127
- },
128
- });
129
-
130
- await logLoginAttempt(user.id, req, false, 'REGISTRATION_PENDING_VERIFICATION');
131
-
132
- logger.info(`New user registered: ${user.email}`);
133
-
134
- res.status(201).json(createSuccessResponse(
135
- { user },
136
- 'Registration successful. Please check your email to verify your account.',
137
- 'REGISTRATION_SUCCESS'
138
- ));
139
- } catch (error) {
140
- logger.error('Registration error:', error);
141
- res.status(500).json(createErrorResponse(
142
- 'Internal server error',
143
- ERROR_CODES.INTERNAL_ERROR
144
- ));
145
- }
146
- };
147
-
148
- export const login = async (req, res) => {
149
- try {
150
- const validatedData = validation.request(loginSchema, req.body);
151
- const { email, password } = validatedData;
152
-
153
- // Find user
154
- const user = await prisma.user.findUnique({
155
- where: { email: email.toLowerCase() }
156
- });
157
-
158
- if (!user) {
159
- await logLoginAttempt('unknown', req, false, 'USER_NOT_FOUND');
160
- res.status(401).json(createErrorResponse(
161
- 'Invalid credentials',
162
- ERROR_CODES.AUTH_INVALID_CREDENTIALS
163
- ));
164
- return;
165
- }
166
-
167
- if (!user.isActive) {
168
- await logLoginAttempt(user.id, req, false, 'ACCOUNT_DEACTIVATED');
169
- res.status(403).json(createErrorResponse(
170
- 'Account is deactivated',
171
- ERROR_CODES.AUTH_ACCOUNT_DEACTIVATED
172
- ));
173
- return;
174
- }
175
-
176
- // Verify password
177
- const bcrypt = require('bcryptjs');
178
- const isPasswordValid = await bcrypt.compare(password, user.password);
179
-
180
- if (!isPasswordValid) {
181
- await logLoginAttempt(user.id, req, false, 'INVALID_PASSWORD');
182
- res.status(401).json(createErrorResponse(
183
- 'Invalid credentials',
184
- ERROR_CODES.AUTH_INVALID_CREDENTIALS
185
- ));
186
- return;
187
- }
188
-
189
- // Update last login
190
- await prisma.user.update({
191
- where: { id: user.id },
192
- data: { lastLoginAt: new Date() }
193
- });
194
-
195
- // Generate tokens
196
- const { accessToken, refreshToken } = generateTokens(user.id);
197
-
198
- // Store refresh token
199
- const expiresAt = new Date();
200
- expiresAt.setDate(expiresAt.getDate() + 7); // 7 days
201
-
202
- await prisma.refreshToken.create({
203
- data: {
204
- userId: user.id,
205
- token: refreshToken,
206
- expiresAt,
207
- },
208
- });
209
-
210
- await logLoginAttempt(user.id, req, true);
211
-
212
- logger.info(`User logged in: ${user.email}`);
213
-
214
- const userResponse = {
215
- id: user.id,
216
- email: user.email,
217
- firstName: user.firstName,
218
- lastName: user.lastName,
219
- username: user.username,
220
- avatar: user.avatar,
221
- role: user.role,
222
- isEmailVerified: user.isEmailVerified,
223
- lastLoginAt: user.lastLoginAt,
224
- };
225
-
226
- res.json(createSuccessResponse(
227
- {
228
- user: userResponse,
229
- tokens: { accessToken, refreshToken }
230
- },
231
- 'Login successful',
232
- 'LOGIN_SUCCESS'
233
- ));
234
- } catch (error) {
235
- logger.error('Login error:', error);
236
- res.status(500).json(createErrorResponse(
237
- 'Internal server error',
238
- ERROR_CODES.INTERNAL_ERROR
239
- ));
240
- }
241
- };
242
-
243
- export const refreshToken = async (req, res) => {
244
- try {
245
- const validatedData = validation.request(refreshTokenSchema, req.body);
246
- const { refreshToken: token } = validatedData;
247
-
248
- // Verify refresh token
249
- let decoded;
250
- try {
251
- decoded = jwt.verify(token, process.env.JWT_REFRESH_SECRET);
252
- } catch (error) {
253
- res.status(401).json(createErrorResponse(
254
- 'Invalid refresh token',
255
- ERROR_CODES.AUTH_TOKEN_EXPIRED
256
- ));
257
- return;
258
- }
259
-
260
- // Check if token exists and is not revoked
261
- const tokenRecord = await prisma.refreshToken.findUnique({
262
- where: { token },
263
- include: { user: true },
264
- });
265
-
266
- if (!tokenRecord || tokenRecord.isRevoked || tokenRecord.expiresAt < new Date()) {
267
- res.status(401).json(createErrorResponse(
268
- 'Invalid or expired refresh token',
269
- ERROR_CODES.AUTH_TOKEN_EXPIRED
270
- ));
271
- return;
272
- }
273
-
274
- // Generate new tokens
275
- const { accessToken, refreshToken: newRefreshToken } = generateTokens(decoded.userId);
276
-
277
- // Revoke old token
278
- await prisma.refreshToken.update({
279
- where: { id: tokenRecord.id },
280
- data: { isRevoked: true },
281
- });
282
-
283
- // Create new refresh token
284
- const expiresAt = new Date();
285
- expiresAt.setDate(expiresAt.getDate() + 7); // 7 days
286
-
287
- await prisma.refreshToken.create({
288
- data: {
289
- userId: decoded.userId,
290
- token: newRefreshToken,
291
- expiresAt,
292
- },
293
- });
294
-
295
- res.json(createSuccessResponse(
296
- {
297
- tokens: {
298
- accessToken,
299
- refreshToken: newRefreshToken,
300
- }
301
- },
302
- 'Tokens refreshed successfully',
303
- 'TOKEN_REFRESH_SUCCESS'
304
- ));
305
- } catch (error) {
306
- logger.error('Token refresh error:', error);
307
- res.status(500).json(createErrorResponse(
308
- 'Internal server error',
309
- ERROR_CODES.INTERNAL_ERROR
310
- ));
311
- }
312
- };
313
-
314
- export const logout = async (req, res) => {
315
- try {
316
- const { refreshToken } = req.body;
317
-
318
- if (refreshToken) {
319
- await prisma.refreshToken.updateMany({
320
- where: { token: refreshToken },
321
- data: { isRevoked: true },
322
- });
323
- }
324
-
325
- logger.info(`User logged out: ${req.user?.email || 'Unknown'}`);
326
-
327
- res.json(createSuccessResponse(
328
- null,
329
- 'Logged out successfully',
330
- 'LOGOUT_SUCCESS'
331
- ));
332
- } catch (error) {
333
- logger.error('Logout error:', error);
334
- res.status(500).json(createErrorResponse(
335
- 'Internal server error',
336
- ERROR_CODES.INTERNAL_ERROR
337
- ));
338
- }
339
- };
340
-
341
- export const verifyEmail = async (req, res) => {
342
- try {
343
- const { token } = req.params;
344
-
345
- const verification = await prisma.emailVerification.findUnique({
346
- where: { token },
347
- include: { user: true },
348
- });
349
-
350
- if (!verification) {
351
- res.status(400).json(createErrorResponse(
352
- 'Invalid verification token',
353
- ERROR_CODES.AUTH_INVALID_TOKEN
354
- ));
355
- return;
356
- }
357
-
358
- if (verification.expiresAt < new Date()) {
359
- res.status(400).json(createErrorResponse(
360
- 'Verification token has expired',
361
- ERROR_CODES.AUTH_TOKEN_EXPIRED
362
- ));
363
- return;
364
- }
365
-
366
- if (verification.status === 'VERIFIED') {
367
- res.json(createSuccessResponse(
368
- null,
369
- 'Email already verified',
370
- 'EMAIL_ALREADY_VERIFIED'
371
- ));
372
- return;
373
- }
374
-
375
- // Update user and verification status
376
- await prisma.user.update({
377
- where: { id: verification.userId },
378
- data: { isEmailVerified: true }
379
- });
380
-
381
- await prisma.emailVerification.update({
382
- where: { id: verification.id },
383
- data: { status: 'VERIFIED' },
384
- });
385
-
386
- logger.info(`Email verified for user: ${verification.user.email}`);
387
-
388
- res.json(createSuccessResponse(
389
- null,
390
- 'Email verified successfully',
391
- 'EMAIL_VERIFICATION_SUCCESS'
392
- ));
393
- } catch (error) {
394
- logger.error('Email verification error:', error);
395
- res.status(500).json(createErrorResponse(
396
- 'Internal server error',
397
- ERROR_CODES.INTERNAL_ERROR
398
- ));
399
- }
400
- };
401
-
402
- export const getProfile = async (req, res) => {
403
- try {
404
- res.json(createSuccessResponse(
405
- { user: req.user },
406
- 'Profile retrieved successfully',
407
- 'PROFILE_SUCCESS'
408
- ));
409
- } catch (error) {
410
- logger.error('Get profile error:', error);
411
- res.status(500).json(createErrorResponse(
412
- 'Internal server error',
413
- ERROR_CODES.INTERNAL_ERROR
414
- ));
415
- }
416
- };
417
-
418
- export const updateProfile = async (req, res) => {
419
- try {
420
- const validatedData = validation.request(updateProfileSchema, req.body);
421
- const { firstName, lastName, username, avatar } = validatedData;
422
-
423
- const updateData = {};
424
- if (firstName !== undefined) updateData.firstName = firstName;
425
- if (lastName !== undefined) updateData.lastName = lastName;
426
- if (avatar !== undefined) updateData.avatar = avatar;
427
-
428
- if (username !== undefined && username !== req.user.username) {
429
- const existingUsername = await prisma.user.findUnique({
430
- where: { username }
431
- });
432
-
433
- if (existingUsername && existingUsername.id !== req.user.id) {
434
- res.status(409).json(createErrorResponse(
435
- 'Username already taken',
436
- ERROR_CODES.AUTH_USERNAME_TAKEN
437
- ));
438
- return;
439
- }
440
- updateData.username = username;
441
- }
442
-
443
- const updatedUser = await prisma.user.update({
444
- where: { id: req.user.id },
445
- data: updateData,
446
- select: {
447
- id: true,
448
- email: true,
449
- firstName: true,
450
- lastName: true,
451
- username: true,
452
- avatar: true,
453
- role: true,
454
- isEmailVerified: true,
455
- lastLoginAt: true,
456
- updatedAt: true,
457
- }
458
- });
459
-
460
- logger.info(`Profile updated for user: ${req.user.email}`);
461
-
462
- res.json(createSuccessResponse(
463
- { user: updatedUser },
464
- 'Profile updated successfully',
465
- 'PROFILE_UPDATE_SUCCESS'
466
- ));
467
- } catch (error) {
468
- logger.error('Update profile error:', error);
469
- res.status(500).json(createErrorResponse(
470
- 'Internal server error',
471
- ERROR_CODES.INTERNAL_ERROR
472
- ));
473
- }
474
- };
475
-
476
- export const changePassword = async (req, res) => {
477
- try {
478
- const validatedData = validation.request(changePasswordSchema, req.body);
479
- const { currentPassword, newPassword } = validatedData;
480
-
481
- const user = await prisma.user.findUnique({
482
- where: { id: req.user.id }
483
- });
484
-
485
- if (!user) {
486
- res.status(404).json(createErrorResponse(
487
- 'User not found',
488
- ERROR_CODES.AUTH_USER_NOT_FOUND
489
- ));
490
- return;
491
- }
492
-
493
- const bcrypt = require('bcryptjs');
494
- const isCurrentPasswordValid = await bcrypt.compare(currentPassword, user.password);
495
-
496
- if (!isCurrentPasswordValid) {
497
- res.status(400).json(createErrorResponse(
498
- 'Current password is incorrect',
499
- ERROR_CODES.AUTH_INVALID_PASSWORD
500
- ));
501
- return;
502
- }
503
-
504
- // Hash new password
505
- const saltRounds = parseInt(process.env.BCRYPT_SALT_ROUNDS || '12');
506
- const hashedNewPassword = await bcrypt.hash(newPassword, saltRounds);
507
-
508
- await prisma.user.update({
509
- where: { id: req.user.id },
510
- data: { password: hashedNewPassword }
511
- });
512
-
513
- // Revoke all refresh tokens
514
- await prisma.refreshToken.updateMany({
515
- where: { userId: req.user.id },
516
- data: { isRevoked: true },
517
- });
518
-
519
- logger.info(`Password changed for user: ${req.user.email}`);
520
-
521
- res.json(createSuccessResponse(
522
- null,
523
- 'Password changed successfully. Please log in again.',
524
- 'PASSWORD_CHANGE_SUCCESS'
525
- ));
526
- } catch (error) {
527
- logger.error('Change password error:', error);
528
- res.status(500).json(createErrorResponse(
529
- 'Internal server error',
530
- ERROR_CODES.INTERNAL_ERROR
531
- ));
532
- }
533
- };