auth.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import express from 'express';
  2. import bcrypt from 'bcrypt';
  3. import { userDb, db } from '../database/db.js';
  4. import { generateToken, authenticateToken } from '../middleware/auth.js';
  5. import { DISABLE_LOCAL_AUTH } from '../constants/config.js';
  6. const router = express.Router();
  7. // Check auth status and setup requirements
  8. router.get('/status', async (req, res) => {
  9. try {
  10. if (DISABLE_LOCAL_AUTH) {
  11. return res.json({
  12. needsSetup: false,
  13. isAuthenticated: true,
  14. authDisabled: true,
  15. });
  16. }
  17. const hasUsers = await userDb.hasUsers();
  18. res.json({
  19. needsSetup: !hasUsers,
  20. isAuthenticated: false // Will be overridden by frontend if token exists
  21. });
  22. } catch (error) {
  23. console.error('Auth status error:', error);
  24. res.status(500).json({ error: 'Internal server error' });
  25. }
  26. });
  27. // User registration (setup) - only allowed if no users exist
  28. router.post('/register', async (req, res) => {
  29. try {
  30. if (DISABLE_LOCAL_AUTH) {
  31. return res.status(403).json({ error: 'Registration is disabled (PILOTDECK_DISABLE_LOCAL_AUTH)' });
  32. }
  33. const { username, password } = req.body;
  34. // Validate input
  35. if (!username || !password) {
  36. return res.status(400).json({ error: 'Username and password are required' });
  37. }
  38. if (username.length < 3 || password.length < 6) {
  39. return res.status(400).json({ error: 'Username must be at least 3 characters, password at least 6 characters' });
  40. }
  41. // Use a transaction to prevent race conditions
  42. db.prepare('BEGIN').run();
  43. try {
  44. // Check if users already exist (only allow one user)
  45. const hasUsers = userDb.hasUsers();
  46. if (hasUsers) {
  47. db.prepare('ROLLBACK').run();
  48. return res.status(403).json({ error: 'User already exists. This is a single-user system.' });
  49. }
  50. // Hash password
  51. const saltRounds = 12;
  52. const passwordHash = await bcrypt.hash(password, saltRounds);
  53. // Create user
  54. const user = userDb.createUser(username, passwordHash);
  55. // Generate token
  56. const token = generateToken(user);
  57. db.prepare('COMMIT').run();
  58. // Update last login (non-fatal, outside transaction)
  59. userDb.updateLastLogin(user.id);
  60. res.json({
  61. success: true,
  62. user: { id: user.id, username: user.username },
  63. token
  64. });
  65. } catch (error) {
  66. db.prepare('ROLLBACK').run();
  67. throw error;
  68. }
  69. } catch (error) {
  70. console.error('Registration error:', error);
  71. if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
  72. res.status(409).json({ error: 'Username already exists' });
  73. } else {
  74. res.status(500).json({ error: 'Internal server error' });
  75. }
  76. }
  77. });
  78. // User login
  79. router.post('/login', async (req, res) => {
  80. try {
  81. if (DISABLE_LOCAL_AUTH) {
  82. return res.status(403).json({ error: 'Login is disabled (PILOTDECK_DISABLE_LOCAL_AUTH)' });
  83. }
  84. const { username, password } = req.body;
  85. // Validate input
  86. if (!username || !password) {
  87. return res.status(400).json({ error: 'Username and password are required' });
  88. }
  89. // Get user from database
  90. const user = userDb.getUserByUsername(username);
  91. if (!user) {
  92. return res.status(401).json({ error: 'Invalid username or password' });
  93. }
  94. // Verify password
  95. const isValidPassword = await bcrypt.compare(password, user.password_hash);
  96. if (!isValidPassword) {
  97. return res.status(401).json({ error: 'Invalid username or password' });
  98. }
  99. // Generate token
  100. const token = generateToken(user);
  101. // Update last login
  102. userDb.updateLastLogin(user.id);
  103. res.json({
  104. success: true,
  105. user: { id: user.id, username: user.username },
  106. token
  107. });
  108. } catch (error) {
  109. console.error('Login error:', error);
  110. res.status(500).json({ error: 'Internal server error' });
  111. }
  112. });
  113. // Get current user (protected route)
  114. router.get('/user', authenticateToken, (req, res) => {
  115. res.json({
  116. user: req.user
  117. });
  118. });
  119. // Logout (client-side token removal, but this endpoint can be used for logging)
  120. router.post('/logout', authenticateToken, (req, res) => {
  121. // In a simple JWT system, logout is mainly client-side
  122. // This endpoint exists for consistency and potential future logging
  123. res.json({ success: true, message: 'Logged out successfully' });
  124. });
  125. export default router;