@organizasyon/meeting-nanaman-app-backend 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.
Files changed (101) hide show
  1. package/dist/controllers/auth/index.d.ts +65 -0
  2. package/dist/controllers/auth/index.js +525 -0
  3. package/dist/controllers/employees/index.d.ts +38 -0
  4. package/dist/controllers/employees/index.js +185 -0
  5. package/dist/controllers/health/index.d.ts +9 -0
  6. package/dist/controllers/health/index.js +42 -0
  7. package/dist/controllers/index.d.ts +16 -0
  8. package/dist/controllers/index.js +19 -0
  9. package/dist/controllers/meetings/index.d.ts +23 -0
  10. package/dist/controllers/meetings/index.js +233 -0
  11. package/dist/controllers/modules/index.d.ts +5 -0
  12. package/dist/controllers/modules/index.js +104 -0
  13. package/dist/controllers/users/index.d.ts +103 -0
  14. package/dist/controllers/users/index.js +841 -0
  15. package/dist/data/modules.json +94 -0
  16. package/dist/database/config/index.d.ts +2 -0
  17. package/dist/database/config/index.js +32 -0
  18. package/dist/database/index.d.ts +9 -0
  19. package/dist/database/index.js +9 -0
  20. package/dist/database/seeder/employees/index.d.ts +1 -0
  21. package/dist/database/seeder/employees/index.js +40 -0
  22. package/dist/database/seeder/index.d.ts +4 -0
  23. package/dist/database/seeder/index.js +20 -0
  24. package/dist/database/seeder/users/index.d.ts +1 -0
  25. package/dist/database/seeder/users/index.js +46 -0
  26. package/dist/index.d.ts +9 -0
  27. package/dist/index.js +23 -0
  28. package/dist/jobs/index.d.ts +1 -0
  29. package/dist/jobs/index.js +18 -0
  30. package/dist/jobs/mailer/index.d.ts +4 -0
  31. package/dist/jobs/mailer/index.js +186 -0
  32. package/dist/jobs/mailer/templates/auth.d.ts +11 -0
  33. package/dist/jobs/mailer/templates/auth.js +117 -0
  34. package/dist/jobs/mailer/templates/index.d.ts +1 -0
  35. package/dist/jobs/mailer/templates/index.js +17 -0
  36. package/dist/jobs/queues/index.d.ts +3 -0
  37. package/dist/jobs/queues/index.js +115 -0
  38. package/dist/middlewares/audit/index.d.ts +0 -0
  39. package/dist/middlewares/audit/index.js +1 -0
  40. package/dist/middlewares/guard/index.d.ts +11 -0
  41. package/dist/middlewares/guard/index.js +53 -0
  42. package/dist/middlewares/index.d.ts +2 -0
  43. package/dist/middlewares/index.js +7 -0
  44. package/dist/middlewares/meeting.d.ts +9 -0
  45. package/dist/middlewares/meeting.js +34 -0
  46. package/dist/models/employees/index.d.ts +83 -0
  47. package/dist/models/employees/index.js +70 -0
  48. package/dist/models/index.d.ts +570 -0
  49. package/dist/models/index.js +17 -0
  50. package/dist/models/meetings/index.d.ts +227 -0
  51. package/dist/models/meetings/index.js +112 -0
  52. package/dist/models/passkeys/index.d.ts +77 -0
  53. package/dist/models/passkeys/index.js +55 -0
  54. package/dist/models/queues/index.d.ts +77 -0
  55. package/dist/models/queues/index.js +57 -0
  56. package/dist/models/users/index.d.ts +107 -0
  57. package/dist/models/users/index.js +92 -0
  58. package/dist/queues/index.d.ts +1 -0
  59. package/dist/queues/index.js +17 -0
  60. package/dist/queues/mailer/index.d.ts +4 -0
  61. package/dist/queues/mailer/index.js +74 -0
  62. package/dist/types/index.d.ts +33 -0
  63. package/dist/types/index.js +2 -0
  64. package/dist/utils/notifications.d.ts +2 -0
  65. package/dist/utils/notifications.js +51 -0
  66. package/package.json +39 -0
  67. package/public/health.html +215 -0
  68. package/src/controllers/auth/index.ts +609 -0
  69. package/src/controllers/employees/index.ts +210 -0
  70. package/src/controllers/health/index.ts +41 -0
  71. package/src/controllers/index.ts +9 -0
  72. package/src/controllers/meetings/index.ts +251 -0
  73. package/src/controllers/modules/index.ts +74 -0
  74. package/src/controllers/users/index.ts +981 -0
  75. package/src/data/modules.json +94 -0
  76. package/src/database/config/index.ts +26 -0
  77. package/src/database/index.ts +5 -0
  78. package/src/database/seeder/employees/index.ts +35 -0
  79. package/src/database/seeder/index.ts +18 -0
  80. package/src/database/seeder/users/index.ts +44 -0
  81. package/src/index.ts +10 -0
  82. package/src/jobs/index.ts +2 -0
  83. package/src/jobs/mailer/index.ts +154 -0
  84. package/src/jobs/mailer/templates/auth.ts +113 -0
  85. package/src/jobs/mailer/templates/index.ts +1 -0
  86. package/src/jobs/queues/index.ts +125 -0
  87. package/src/middlewares/audit/index.ts +0 -0
  88. package/src/middlewares/guard/index.ts +64 -0
  89. package/src/middlewares/index.ts +5 -0
  90. package/src/middlewares/meeting.ts +45 -0
  91. package/src/models/employees/index.ts +70 -0
  92. package/src/models/index.ts +8 -0
  93. package/src/models/meetings/index.ts +112 -0
  94. package/src/models/passkeys/index.ts +53 -0
  95. package/src/models/queues/index.ts +55 -0
  96. package/src/models/users/index.ts +92 -0
  97. package/src/queues/index.ts +1 -0
  98. package/src/queues/mailer/index.ts +80 -0
  99. package/src/types/index.ts +38 -0
  100. package/src/utils/notifications.ts +66 -0
  101. package/tsconfig.json +18 -0
@@ -0,0 +1,609 @@
1
+ import { Response } from 'express';
2
+ import type { MulterRequest } from '../../types';
3
+ import UsersModel from '../../models/users';
4
+ import PasskeysModel from '../../models/passkeys';
5
+ import Users from './../../controllers/users';
6
+ import { getEmailQueue } from '../../queues';
7
+ import bcrypt from 'bcrypt';
8
+ import crypto from 'crypto';
9
+ import jwt from 'jsonwebtoken';
10
+ import type { Document } from 'mongoose';
11
+
12
+ export class AuthController extends Users {
13
+
14
+ /**
15
+ * Get user profile
16
+ */
17
+ public async getProfile(req: MulterRequest, res: Response): Promise<void> {
18
+ console.log('=== Backend AuthController.getProfile called ===');
19
+ console.log('req.user:', (req as any).user);
20
+ try {
21
+ // Get user ID from JWT token
22
+ const userId = (req as any).user?.userId;
23
+ console.log('Get profile for userId:', userId);
24
+
25
+ if (!userId) {
26
+ console.log('No userId in request');
27
+ res.status(401).json({ message: 'Unauthorized' });
28
+ return;
29
+ }
30
+
31
+ const user = await UsersModel.findOne({
32
+ _id: userId,
33
+ deleted_at: null
34
+ })
35
+ .select('-password -__v')
36
+ .populate('employee_id', 'first_name last_name middle_name email position department contact_number address')
37
+ .lean()
38
+ .exec();
39
+
40
+ console.log('User found:', !!user);
41
+ if (!user) {
42
+ console.log('User not found for id:', userId);
43
+ res.status(404).json({ message: 'User not found' });
44
+ return;
45
+ }
46
+
47
+ // Format user data same as login response
48
+ const userData = {
49
+ id: user._id,
50
+ email: user.email,
51
+ first_name: user.first_name,
52
+ last_name: user.last_name,
53
+ middle_name: user.middle_name,
54
+ full_name: `${user.first_name} ${user.last_name}`,
55
+ role: user.role,
56
+ employee_id: user.employee_id,
57
+ is_active: user.is_active,
58
+ created_at: user.created_at
59
+ };
60
+
61
+ const response = {
62
+ message: 'Profile retrieved successfully',
63
+ user: userData
64
+ };
65
+ console.log('Sending response:', response);
66
+ console.log('=== Backend AuthController.getProfile completed ===');
67
+ res.json(response);
68
+ } catch (err) {
69
+ console.error('Get profile error', err);
70
+ console.log('=== Backend AuthController.getProfile failed ===');
71
+ res.status(500).json({ message: 'Failed to retrieve profile', error: String(err) });
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Forgot password - send reset email
77
+ */
78
+ public async forgotPassword(req: MulterRequest & { body?: Record<string, any> }, res: Response): Promise<void> {
79
+ try {
80
+ const body = req.body || {};
81
+ const { email } = body;
82
+
83
+ if (!email) {
84
+ res.status(400).json({ message: 'Email is required' });
85
+ return;
86
+ }
87
+
88
+ // Find user by email
89
+ const user = await UsersModel.findOne({
90
+ email: email.trim().toLowerCase(),
91
+ deleted_at: null,
92
+ is_active: true
93
+ }).lean().exec() as any;
94
+
95
+ if (!user) {
96
+ res.status(404).json({ message: 'User with this email not found' });
97
+ return;
98
+ }
99
+
100
+ // Generate reset token
101
+ const resetToken = crypto.randomBytes(32).toString('hex');
102
+ const resetTokenExpiry = Date.now() + (60 * 60 * 1000); // 1 hour
103
+
104
+ // Update user with reset token
105
+ await UsersModel.updateOne(
106
+ { _id: user._id },
107
+ {
108
+ reset_token: resetToken,
109
+ reset_token_expires: resetTokenExpiry,
110
+ updated_at: Date.now()
111
+ }
112
+ );
113
+
114
+ // Add to email queue
115
+ await getEmailQueue().add('send-email', {
116
+ type: 'password_reset',
117
+ data: {
118
+ user_id: user._id,
119
+ email: user.email,
120
+ reset_token: resetToken,
121
+ name: user.full_name || `${user.first_name} ${user.last_name}`
122
+ }
123
+ });
124
+
125
+ res.json({
126
+ message: 'Password reset email sent successfully',
127
+ success: true
128
+ });
129
+ } catch (err) {
130
+ console.error('Forgot password error', err);
131
+ res.status(500).json({ message: 'Failed to send password reset email', error: String(err) });
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Change password
137
+ */
138
+ public async changePassword(req: MulterRequest & { body?: Record<string, any> }, res: Response): Promise<void> {
139
+ try {
140
+ const body = req.body || {};
141
+ const { current_password, new_password } = body;
142
+
143
+ if (!current_password || !new_password) {
144
+ res.status(400).json({ message: 'Current password and new password are required' });
145
+ return;
146
+ }
147
+
148
+ // Get user ID from JWT token
149
+ const userId = (req as any).user?.userId;
150
+ if (!userId) {
151
+ res.status(401).json({ message: 'Unauthorized' });
152
+ return;
153
+ }
154
+
155
+ // Find user
156
+ const user = await UsersModel.findOne({
157
+ _id: userId,
158
+ deleted_at: null,
159
+ is_active: true
160
+ }).exec();
161
+
162
+ if (!user) {
163
+ res.status(404).json({ message: 'User not found' });
164
+ return;
165
+ }
166
+
167
+ // Verify current password
168
+ const isCurrentPasswordValid = await bcrypt.compare(current_password, user.password as string);
169
+ if (!isCurrentPasswordValid) {
170
+ res.status(400).json({ message: 'Current password is incorrect' });
171
+ return;
172
+ }
173
+
174
+ // Hash new password
175
+ const hashedNewPassword = await bcrypt.hash(new_password, 10);
176
+
177
+ // Update password
178
+ await UsersModel.updateOne(
179
+ { _id: userId },
180
+ {
181
+ password: hashedNewPassword,
182
+ updated_at: Date.now()
183
+ }
184
+ );
185
+
186
+ // Add to email queue
187
+ await getEmailQueue().add('send-email', {
188
+ type: 'password_changed',
189
+ data: {
190
+ user_id: user._id,
191
+ email: user.email,
192
+ name: `${user.first_name} ${user.middle_name ? user.middle_name + ' ' : ''}${user.last_name}`
193
+ }
194
+ });
195
+
196
+ res.json({
197
+ message: 'Password changed successfully',
198
+ success: true
199
+ });
200
+ } catch (err) {
201
+ console.error('Change password error', err);
202
+ res.status(500).json({ message: 'Failed to change password', error: String(err) });
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Start passkey registration challenge
208
+ */
209
+ public async registerPasskey(req: MulterRequest & { body?: Record<string, any> }, res: Response): Promise<void> {
210
+ try {
211
+ const body = req.body || {};
212
+ const { user_id, email } = body;
213
+
214
+ if (!user_id || !email) {
215
+ res.status(400).json({ message: 'User ID and email are required' });
216
+ return;
217
+ }
218
+
219
+ // Find user
220
+ const user = await UsersModel.findOne({
221
+ _id: user_id,
222
+ email: email.trim().toLowerCase(),
223
+ deleted_at: null,
224
+ is_active: true
225
+ }).lean().exec();
226
+
227
+ if (!user) {
228
+ res.status(404).json({ message: 'User not found' });
229
+ return;
230
+ }
231
+
232
+ // Generate WebAuthn challenge
233
+ const challenge = crypto.randomBytes(32);
234
+ const deviceId = crypto.randomBytes(16).toString('hex');
235
+
236
+ res.json({
237
+ challenge: challenge.toString('base64'),
238
+ device_id: deviceId,
239
+ device_name: `Device ${new Date().toLocaleDateString()}`
240
+ });
241
+ } catch (err) {
242
+ console.error('Register passkey challenge error', err);
243
+ res.status(500).json({ message: 'Failed to start registration', error: String(err) });
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Complete passkey registration
249
+ */
250
+ public async completePasskeyRegistration(req: MulterRequest & { body?: Record<string, any> }, res: Response): Promise<void> {
251
+ try {
252
+ const body = req.body || {};
253
+ const { user_id, credential_id, device_id, name, public_key } = body;
254
+
255
+ if (!user_id || !credential_id || !device_id || !name || !public_key) {
256
+ res.status(400).json({ message: 'All fields are required' });
257
+ return;
258
+ }
259
+
260
+ // Check if passkey already exists
261
+ const existingPasskey = await PasskeysModel.findOne({
262
+ credential_id,
263
+ deleted_at: null
264
+ }).lean().exec();
265
+
266
+ if (existingPasskey) {
267
+ res.status(409).json({ message: 'Passkey already registered' });
268
+ return;
269
+ }
270
+
271
+ // Get user info for email
272
+ const user = await UsersModel.findOne({ _id: user_id }).lean().exec();
273
+ if (!user) {
274
+ res.status(404).json({ message: 'User not found' });
275
+ return;
276
+ }
277
+
278
+ // Create new passkey - public_key is COSE format CBOR from WebAuthn
279
+ await PasskeysModel.create({
280
+ user_id,
281
+ credential_id,
282
+ device_id,
283
+ name,
284
+ public_key: public_key, // Store COSE public key
285
+ created_at: Date.now(),
286
+ updated_at: Date.now()
287
+ });
288
+
289
+ // Add to email queue
290
+ await getEmailQueue().add('send-email', {
291
+ type: 'passkey',
292
+ data: {
293
+ user_id: user._id,
294
+ email: user.email,
295
+ name: `${user.first_name} ${user.middle_name ? user.middle_name + ' ' : ''}${user.last_name}`,
296
+ device_name: name
297
+ }
298
+ });
299
+
300
+ res.json({
301
+ message: 'Passkey registered successfully',
302
+ success: true,
303
+ device_id,
304
+ device_name: name
305
+ });
306
+ } catch (err) {
307
+ console.error('Complete passkey registration error', err);
308
+ res.status(500).json({ message: 'Failed to register passkey', error: String(err) });
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Get user passkeys
314
+ */
315
+ public async getPasskeys(req: MulterRequest, res: Response): Promise<void> {
316
+ try {
317
+ const { user_id } = req.params;
318
+
319
+ if (!user_id) {
320
+ res.status(400).json({ message: 'User ID is required' });
321
+ return;
322
+ }
323
+
324
+ const passkeys = await PasskeysModel.find({
325
+ user_id,
326
+ deleted_at: null,
327
+ is_active: true
328
+ })
329
+ .select('-__v')
330
+ .sort({ created_at: -1 })
331
+ .lean()
332
+ .exec();
333
+
334
+ res.json({
335
+ message: 'Passkeys retrieved successfully',
336
+ passkeys
337
+ });
338
+ } catch (err) {
339
+ console.error('Get passkeys error', err);
340
+ res.status(500).json({ message: 'Failed to retrieve passkeys', error: String(err) });
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Delete passkey
346
+ */
347
+ public async deletePasskey(req: MulterRequest, res: Response): Promise<void> {
348
+ try {
349
+ const { passkeyId } = req.params;
350
+
351
+ if (!passkeyId) {
352
+ res.status(400).json({ message: 'Passkey ID is required' });
353
+ return;
354
+ }
355
+
356
+ // Soft delete the passkey
357
+ await PasskeysModel.updateOne(
358
+ { _id: passkeyId },
359
+ { deleted_at: Date.now(), is_active: false }
360
+ );
361
+
362
+ res.json({
363
+ message: 'Passkey deleted successfully',
364
+ success: true
365
+ });
366
+ } catch (err) {
367
+ console.error('Delete passkey error', err);
368
+ res.status(500).json({ message: 'Failed to delete passkey', error: String(err) });
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Start passkey authentication challenge
374
+ */
375
+ public async authenticatePasskey(req: MulterRequest & { body?: Record<string, any> }, res: Response): Promise<void> {
376
+ try {
377
+ const body = req.body || {};
378
+ const { email, device_id } = body;
379
+
380
+ if (!email || !device_id) {
381
+ res.status(400).json({ message: 'Email and device ID are required' });
382
+ return;
383
+ }
384
+
385
+ // Find user by email
386
+ const user = await UsersModel.findOne({
387
+ email: email.trim().toLowerCase(),
388
+ deleted_at: null,
389
+ is_active: true
390
+ }).lean().exec();
391
+
392
+ if (!user) {
393
+ res.status(401).json({ message: 'Invalid credentials' });
394
+ return;
395
+ }
396
+
397
+ // Find the latest active passkey for this user and device
398
+ const passkey = await PasskeysModel.findOne({
399
+ user_id: user._id,
400
+ device_id,
401
+ is_active: true,
402
+ deleted_at: null
403
+ })
404
+ .sort({ created_at: -1 })
405
+ .lean()
406
+ .exec();
407
+
408
+ if (!passkey) {
409
+ res.status(401).json({ message: 'No passkey found for this device' });
410
+ return;
411
+ }
412
+
413
+ // Generate WebAuthn challenge
414
+ const challenge = crypto.randomBytes(32);
415
+
416
+ res.json({
417
+ challenge: challenge.toString('base64'),
418
+ user_id: user._id,
419
+ credential_id: passkey.credential_id
420
+ });
421
+ } catch (err) {
422
+ console.error('Passkey authentication challenge error', err);
423
+ res.status(500).json({ message: 'Failed to start authentication', error: String(err) });
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Validate reset token
429
+ */
430
+ public async validateResetToken(req: MulterRequest & { body?: Record<string, any> }, res: Response): Promise<void> {
431
+ try {
432
+ const body = req.body || {};
433
+ const { token } = body;
434
+
435
+ if (!token) {
436
+ res.status(400).json({ message: 'Token is required' });
437
+ return;
438
+ }
439
+
440
+ // Find user with valid reset token
441
+ const user = await UsersModel.findOne({
442
+ reset_token: token,
443
+ reset_token_expires: { $gt: Date.now() },
444
+ deleted_at: null,
445
+ is_active: true
446
+ }).lean().exec();
447
+
448
+ if (!user) {
449
+ res.status(400).json({ message: 'Invalid or expired token' });
450
+ return;
451
+ }
452
+
453
+ res.json({
454
+ message: 'Token is valid',
455
+ valid: true
456
+ });
457
+ } catch (err) {
458
+ console.error('Validate reset token error', err);
459
+ res.status(500).json({ message: 'Failed to validate token', error: String(err) });
460
+ }
461
+ }
462
+
463
+ /**
464
+ * Reset password with token
465
+ */
466
+ public async resetPassword(req: MulterRequest & { body?: Record<string, any> }, res: Response): Promise<void> {
467
+ try {
468
+ const body = req.body || {};
469
+ const { token, new_password } = body;
470
+
471
+ if (!token || !new_password) {
472
+ res.status(400).json({ message: 'Token and new password are required' });
473
+ return;
474
+ }
475
+
476
+ // Find user with valid reset token
477
+ const user = await UsersModel.findOne({
478
+ reset_token: token,
479
+ reset_token_expires: { $gt: Date.now() },
480
+ deleted_at: null,
481
+ is_active: true
482
+ }).exec();
483
+
484
+ if (!user) {
485
+ res.status(400).json({ message: 'Invalid or expired token' });
486
+ return;
487
+ }
488
+
489
+ // Hash new password
490
+ const hashedPassword = await bcrypt.hash(new_password, 10);
491
+
492
+ // Update password and clear reset token
493
+ await UsersModel.updateOne(
494
+ { _id: user._id },
495
+ {
496
+ password: hashedPassword,
497
+ reset_token: null,
498
+ reset_token_expires: null,
499
+ updated_at: Date.now()
500
+ }
501
+ );
502
+
503
+ // Add to email queue for password changed notification
504
+ await getEmailQueue().add('send-email', {
505
+ type: 'password_changed',
506
+ data: {
507
+ user_id: user._id,
508
+ email: user.email,
509
+ name: `${user.first_name} ${user.middle_name ? user.middle_name + ' ' : ''}${user.last_name}`
510
+ }
511
+ });
512
+
513
+ res.json({
514
+ message: 'Password reset successfully',
515
+ success: true
516
+ });
517
+ } catch (err) {
518
+ console.error('Reset password error', err);
519
+ res.status(500).json({ message: 'Failed to reset password', error: String(err) });
520
+ }
521
+ }
522
+
523
+ /**
524
+ * Complete passkey authentication
525
+ */
526
+ public async verifyPasskey(req: MulterRequest & { body?: Record<string, any> }, res: Response): Promise<void> {
527
+ try {
528
+ const body = req.body || {};
529
+ const { email, device_id, credential_id, authenticator_data } = body;
530
+
531
+ if (!email || !device_id || !credential_id || !authenticator_data) {
532
+ res.status(400).json({ message: 'All authentication data is required' });
533
+ return;
534
+ }
535
+
536
+ // Find user by email
537
+ const user = await UsersModel.findOne({
538
+ email: email.trim().toLowerCase(),
539
+ deleted_at: null,
540
+ is_active: true
541
+ }).lean().exec();
542
+
543
+ if (!user) {
544
+ res.status(401).json({ message: 'Invalid credentials' });
545
+ return;
546
+ }
547
+
548
+ // Find the passkey
549
+ const passkey = await PasskeysModel.findOne({
550
+ user_id: user._id,
551
+ credential_id,
552
+ device_id,
553
+ is_active: true,
554
+ deleted_at: null
555
+ }).lean().exec();
556
+
557
+ if (!passkey) {
558
+ res.status(401).json({ message: 'Invalid passkey' });
559
+ return;
560
+ }
561
+
562
+ // For now, trust the client-side WebAuthn verification
563
+ // The authenticator already verified the user locally with biometrics
564
+ // We'll implement full cryptographic signature verification in a future update
565
+
566
+ // Update last used timestamp
567
+ await PasskeysModel.updateOne(
568
+ { _id: passkey._id },
569
+ { last_used_at: Date.now(), updated_at: Date.now() }
570
+ );
571
+
572
+ // Generate JWT token
573
+ const jwtSecret = process.env.JWT_SECRET;
574
+ if (!jwtSecret) {
575
+ throw new Error('JWT_SECRET environment variable is required');
576
+ }
577
+
578
+ const jwt = require('jsonwebtoken');
579
+ const token = jwt.sign(
580
+ {
581
+ userId: user._id,
582
+ email: user.email,
583
+ role: user.role
584
+ },
585
+ jwtSecret,
586
+ { expiresIn: '24h' }
587
+ );
588
+
589
+ res.json({
590
+ message: 'Passkey authentication successful',
591
+ token,
592
+ user: {
593
+ id: user._id,
594
+ email: user.email,
595
+ first_name: user.first_name,
596
+ middle_name: user.middle_name,
597
+ last_name: user.last_name,
598
+ full_name: `${user.first_name} ${user.last_name}`,
599
+ role: user.role,
600
+ employee_id: user.employee_id,
601
+ is_active: user.is_active
602
+ }
603
+ });
604
+ } catch (err) {
605
+ console.error('Passkey verification error', err);
606
+ res.status(500).json({ message: 'Passkey authentication failed', error: String(err) });
607
+ }
608
+ }
609
+ }