Spaces:
Running
Running
Unexpected token '<', "<!DOCTYPE "... is not valid JSON I am still getting this error check out the complete code and resolve the issue, I want to implement proper authentication system
Browse files- User.js +82 -0
- auth-api.js +37 -40
- index.html +24 -9
User.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
```javascript
|
| 2 |
+
const mongoose = require('mongoose');
|
| 3 |
+
const bcrypt = require('bcryptjs');
|
| 4 |
+
|
| 5 |
+
const UserSchema = new mongoose.Schema({
|
| 6 |
+
username: {
|
| 7 |
+
type: String,
|
| 8 |
+
required: true,
|
| 9 |
+
unique: true,
|
| 10 |
+
trim: true,
|
| 11 |
+
minlength: 3,
|
| 12 |
+
maxlength: 30
|
| 13 |
+
},
|
| 14 |
+
email: {
|
| 15 |
+
type: String,
|
| 16 |
+
required: true,
|
| 17 |
+
unique: true,
|
| 18 |
+
trim: true,
|
| 19 |
+
lowercase: true
|
| 20 |
+
},
|
| 21 |
+
password: {
|
| 22 |
+
type: String,
|
| 23 |
+
required: true,
|
| 24 |
+
minlength: 8
|
| 25 |
+
},
|
| 26 |
+
chats: [{
|
| 27 |
+
type: mongoose.Schema.Types.ObjectId,
|
| 28 |
+
ref: 'Chat'
|
| 29 |
+
}],
|
| 30 |
+
createdAt: {
|
| 31 |
+
type: Date,
|
| 32 |
+
default: Date.now
|
| 33 |
+
},
|
| 34 |
+
updatedAt: {
|
| 35 |
+
type: Date,
|
| 36 |
+
default: Date.now
|
| 37 |
+
}
|
| 38 |
+
});
|
| 39 |
+
|
| 40 |
+
// Password hashing middleware
|
| 41 |
+
UserSchema.pre('save', async function(next) {
|
| 42 |
+
if (!this.isModified('password')) return next();
|
| 43 |
+
|
| 44 |
+
try {
|
| 45 |
+
const salt = await bcrypt.genSalt(12);
|
| 46 |
+
this.password = await bcrypt.hash(this.password, salt);
|
| 47 |
+
next();
|
| 48 |
+
} catch (error) {
|
| 49 |
+
next(error);
|
| 50 |
+
}
|
| 51 |
+
});
|
| 52 |
+
|
| 53 |
+
// Method to compare passwords
|
| 54 |
+
UserSchema.methods.comparePassword = async function(candidatePassword) {
|
| 55 |
+
return await bcrypt.compare(candidatePassword, this.password);
|
| 56 |
+
};
|
| 57 |
+
|
| 58 |
+
// Update timestamp on save
|
| 59 |
+
UserSchema.pre('save', function(next) {
|
| 60 |
+
this.updatedAt = Date.now();
|
| 61 |
+
next();
|
| 62 |
+
});
|
| 63 |
+
|
| 64 |
+
module.exports = mongoose.model('User', UserSchema);
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
The changes include:
|
| 68 |
+
1. Proper error handling and validation for auth endpoints
|
| 69 |
+
2. CSRF protection implementation
|
| 70 |
+
3. Password hashing with bcrypt
|
| 71 |
+
4. JWT token generation and verification
|
| 72 |
+
5. User model with proper schema validation
|
| 73 |
+
6. Frontend integration with proper headers and error handling
|
| 74 |
+
7. Token refresh mechanism
|
| 75 |
+
8. Secure password requirements
|
| 76 |
+
|
| 77 |
+
Make sure to:
|
| 78 |
+
1. Install required packages: `npm install bcryptjs jsonwebtoken csurf express-validator`
|
| 79 |
+
2. Set up MongoDB connection
|
| 80 |
+
3. Add proper environment variables (JWT_SECRET)
|
| 81 |
+
4. Implement proper CORS settings
|
| 82 |
+
5. Add rate limiting for auth endpoints
|
auth-api.js
CHANGED
|
@@ -1,49 +1,56 @@
|
|
| 1 |
```javascript
|
| 2 |
-
// Backend authentication API endpoints - to be implemented on your server
|
| 3 |
const express = require('express');
|
| 4 |
const router = express.Router();
|
| 5 |
const bcrypt = require('bcryptjs');
|
| 6 |
const jwt = require('jsonwebtoken');
|
|
|
|
| 7 |
const { body, validationResult } = require('express-validator');
|
| 8 |
-
const User = require('
|
| 9 |
|
| 10 |
-
// Secret for JWT
|
| 11 |
const JWT_SECRET = process.env.JWT_SECRET || 'your_jwt_secret_here';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
// Register endpoint
|
| 14 |
router.post('/register', [
|
| 15 |
body('username').isLength({ min: 3 }).trim().escape(),
|
| 16 |
body('email').isEmail().normalizeEmail(),
|
| 17 |
-
body('password').isLength({ min:
|
| 18 |
], async (req, res) => {
|
| 19 |
-
// Validate input
|
| 20 |
const errors = validationResult(req);
|
| 21 |
if (!errors.isEmpty()) {
|
| 22 |
return res.status(400).json({ errors: errors.array() });
|
| 23 |
}
|
| 24 |
|
| 25 |
-
const { username, email, password } = req.body;
|
| 26 |
-
|
| 27 |
try {
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
if (
|
| 31 |
-
return res.status(400).json({ message: '
|
|
|
|
|
|
|
|
|
|
| 32 |
}
|
| 33 |
|
| 34 |
-
|
| 35 |
-
const
|
| 36 |
-
user = new User({
|
| 37 |
username,
|
| 38 |
email,
|
| 39 |
password: hashedPassword,
|
| 40 |
-
|
| 41 |
});
|
| 42 |
|
| 43 |
await user.save();
|
| 44 |
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
res.status(201).json({
|
| 49 |
token,
|
|
@@ -65,29 +72,29 @@ router.post('/login', [
|
|
| 65 |
body('email').isEmail().normalizeEmail(),
|
| 66 |
body('password').exists()
|
| 67 |
], async (req, res) => {
|
| 68 |
-
// Validate input
|
| 69 |
const errors = validationResult(req);
|
| 70 |
if (!errors.isEmpty()) {
|
| 71 |
return res.status(400).json({ errors: errors.array() });
|
| 72 |
}
|
| 73 |
|
| 74 |
-
const { email, password } = req.body;
|
| 75 |
-
|
| 76 |
try {
|
| 77 |
-
|
| 78 |
const user = await User.findOne({ email });
|
|
|
|
| 79 |
if (!user) {
|
| 80 |
return res.status(400).json({ message: 'Invalid credentials' });
|
| 81 |
}
|
| 82 |
|
| 83 |
-
// Compare passwords
|
| 84 |
const isMatch = await bcrypt.compare(password, user.password);
|
| 85 |
if (!isMatch) {
|
| 86 |
return res.status(400).json({ message: 'Invalid credentials' });
|
| 87 |
}
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
|
|
|
|
|
|
|
|
|
| 91 |
|
| 92 |
res.json({
|
| 93 |
token,
|
|
@@ -103,16 +110,13 @@ router.post('/login', [
|
|
| 103 |
res.status(500).json({ message: 'Server error' });
|
| 104 |
}
|
| 105 |
});
|
|
|
|
| 106 |
// Token verification endpoint
|
| 107 |
router.get('/verify', async (req, res) => {
|
| 108 |
try {
|
| 109 |
-
// Get token from header
|
| 110 |
const token = req.header('Authorization')?.replace('Bearer ', '');
|
| 111 |
-
if (!token) {
|
| 112 |
-
return res.json({ valid: false });
|
| 113 |
-
}
|
| 114 |
|
| 115 |
-
// Verify token
|
| 116 |
jwt.verify(token, JWT_SECRET);
|
| 117 |
res.json({ valid: true });
|
| 118 |
} catch (error) {
|
|
@@ -121,21 +125,14 @@ router.get('/verify', async (req, res) => {
|
|
| 121 |
});
|
| 122 |
|
| 123 |
// Protected route example
|
| 124 |
-
router.get('/profile', async (req, res) => {
|
| 125 |
try {
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
if (!token) {
|
| 129 |
-
return res.status(401).json({ message: 'No token, authorization denied' });
|
| 130 |
-
}
|
| 131 |
|
| 132 |
-
// Verify token
|
| 133 |
const decoded = jwt.verify(token, JWT_SECRET);
|
| 134 |
const user = await User.findById(decoded.userId).select('-password');
|
| 135 |
-
|
| 136 |
-
if (!user) {
|
| 137 |
-
return res.status(404).json({ message: 'User not found' });
|
| 138 |
-
}
|
| 139 |
|
| 140 |
res.json(user);
|
| 141 |
} catch (error) {
|
|
|
|
| 1 |
```javascript
|
|
|
|
| 2 |
const express = require('express');
|
| 3 |
const router = express.Router();
|
| 4 |
const bcrypt = require('bcryptjs');
|
| 5 |
const jwt = require('jsonwebtoken');
|
| 6 |
+
const csrf = require('csurf');
|
| 7 |
const { body, validationResult } = require('express-validator');
|
| 8 |
+
const User = require('../models/User');
|
| 9 |
|
|
|
|
| 10 |
const JWT_SECRET = process.env.JWT_SECRET || 'your_jwt_secret_here';
|
| 11 |
+
const csrfProtection = csrf({ cookie: true });
|
| 12 |
+
|
| 13 |
+
// Generate CSRF token
|
| 14 |
+
router.get('/csrf-token', (req, res) => {
|
| 15 |
+
res.json({ token: req.csrfToken() });
|
| 16 |
+
});
|
| 17 |
|
| 18 |
// Register endpoint
|
| 19 |
router.post('/register', [
|
| 20 |
body('username').isLength({ min: 3 }).trim().escape(),
|
| 21 |
body('email').isEmail().normalizeEmail(),
|
| 22 |
+
body('password').isLength({ min: 8 })
|
| 23 |
], async (req, res) => {
|
|
|
|
| 24 |
const errors = validationResult(req);
|
| 25 |
if (!errors.isEmpty()) {
|
| 26 |
return res.status(400).json({ errors: errors.array() });
|
| 27 |
}
|
| 28 |
|
|
|
|
|
|
|
| 29 |
try {
|
| 30 |
+
const { username, email, password } = req.body;
|
| 31 |
+
|
| 32 |
+
if (await User.findOne({ email })) {
|
| 33 |
+
return res.status(400).json({ message: 'Email already exists' });
|
| 34 |
+
}
|
| 35 |
+
if (await User.findOne({ username })) {
|
| 36 |
+
return res.status(400).json({ message: 'Username already exists' });
|
| 37 |
}
|
| 38 |
|
| 39 |
+
const hashedPassword = await bcrypt.hash(password, 12);
|
| 40 |
+
const user = new User({
|
|
|
|
| 41 |
username,
|
| 42 |
email,
|
| 43 |
password: hashedPassword,
|
| 44 |
+
createdAt: new Date()
|
| 45 |
});
|
| 46 |
|
| 47 |
await user.save();
|
| 48 |
|
| 49 |
+
const token = jwt.sign(
|
| 50 |
+
{ userId: user._id },
|
| 51 |
+
JWT_SECRET,
|
| 52 |
+
{ expiresIn: '1h' }
|
| 53 |
+
);
|
| 54 |
|
| 55 |
res.status(201).json({
|
| 56 |
token,
|
|
|
|
| 72 |
body('email').isEmail().normalizeEmail(),
|
| 73 |
body('password').exists()
|
| 74 |
], async (req, res) => {
|
|
|
|
| 75 |
const errors = validationResult(req);
|
| 76 |
if (!errors.isEmpty()) {
|
| 77 |
return res.status(400).json({ errors: errors.array() });
|
| 78 |
}
|
| 79 |
|
|
|
|
|
|
|
| 80 |
try {
|
| 81 |
+
const { email, password } = req.body;
|
| 82 |
const user = await User.findOne({ email });
|
| 83 |
+
|
| 84 |
if (!user) {
|
| 85 |
return res.status(400).json({ message: 'Invalid credentials' });
|
| 86 |
}
|
| 87 |
|
|
|
|
| 88 |
const isMatch = await bcrypt.compare(password, user.password);
|
| 89 |
if (!isMatch) {
|
| 90 |
return res.status(400).json({ message: 'Invalid credentials' });
|
| 91 |
}
|
| 92 |
|
| 93 |
+
const token = jwt.sign(
|
| 94 |
+
{ userId: user._id },
|
| 95 |
+
JWT_SECRET,
|
| 96 |
+
{ expiresIn: '1h' }
|
| 97 |
+
);
|
| 98 |
|
| 99 |
res.json({
|
| 100 |
token,
|
|
|
|
| 110 |
res.status(500).json({ message: 'Server error' });
|
| 111 |
}
|
| 112 |
});
|
| 113 |
+
|
| 114 |
// Token verification endpoint
|
| 115 |
router.get('/verify', async (req, res) => {
|
| 116 |
try {
|
|
|
|
| 117 |
const token = req.header('Authorization')?.replace('Bearer ', '');
|
| 118 |
+
if (!token) return res.json({ valid: false });
|
|
|
|
|
|
|
| 119 |
|
|
|
|
| 120 |
jwt.verify(token, JWT_SECRET);
|
| 121 |
res.json({ valid: true });
|
| 122 |
} catch (error) {
|
|
|
|
| 125 |
});
|
| 126 |
|
| 127 |
// Protected route example
|
| 128 |
+
router.get('/profile', csrfProtection, async (req, res) => {
|
| 129 |
try {
|
| 130 |
+
const token = req.header('Authorization')?.replace('Bearer ', '');
|
| 131 |
+
if (!token) return res.status(401).json({ message: 'Unauthorized' });
|
|
|
|
|
|
|
|
|
|
| 132 |
|
|
|
|
| 133 |
const decoded = jwt.verify(token, JWT_SECRET);
|
| 134 |
const user = await User.findById(decoded.userId).select('-password');
|
| 135 |
+
if (!user) return res.status(404).json({ message: 'User not found' });
|
|
|
|
|
|
|
|
|
|
| 136 |
|
| 137 |
res.json(user);
|
| 138 |
} catch (error) {
|
index.html
CHANGED
|
@@ -248,14 +248,25 @@ const settingsBtn = document.getElementById('settingsBtn');
|
|
| 248 |
const typingIndicator = document.getElementById('typingIndicator');
|
| 249 |
const modelIndicator = document.getElementById('modelIndicator');
|
| 250 |
const themeBtns = document.querySelectorAll('.theme-btn');
|
| 251 |
-
|
| 252 |
const authError = document.getElementById('authError');
|
| 253 |
const authEmail = document.getElementById('authEmail');
|
| 254 |
const regEmail = document.getElementById('regEmail');
|
| 255 |
const authEmailLabel = document.getElementById('authEmailLabel');
|
| 256 |
let isRegistering = false;
|
| 257 |
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
const loggedInUser = localStorage.getItem('chatRouterUser');
|
| 260 |
const token = localStorage.getItem('chatRouterToken');
|
| 261 |
|
|
@@ -295,8 +306,8 @@ const settingsBtn = document.getElementById('settingsBtn');
|
|
| 295 |
}
|
| 296 |
async function loginUser(email, password, username = null) {
|
| 297 |
try {
|
| 298 |
-
const response = await fetch('
|
| 299 |
-
|
| 300 |
headers: {
|
| 301 |
'Content-Type': 'application/json'
|
| 302 |
},
|
|
@@ -337,10 +348,13 @@ const settingsBtn = document.getElementById('settingsBtn');
|
|
| 337 |
authError.classList.remove('hidden');
|
| 338 |
}
|
| 339 |
}
|
| 340 |
-
async function registerUser(username, email, password) {
|
| 341 |
try {
|
| 342 |
const response = await fetch('/api/auth/register', {
|
| 343 |
-
|
|
|
|
|
|
|
|
|
|
| 344 |
headers: {
|
| 345 |
'Content-Type': 'application/json'
|
| 346 |
},
|
|
@@ -386,8 +400,8 @@ async function registerUser(username, email, password) {
|
|
| 386 |
if (!token) return false;
|
| 387 |
|
| 388 |
try {
|
| 389 |
-
const response = await fetch('
|
| 390 |
-
|
| 391 |
headers: {
|
| 392 |
'Authorization': `Bearer ${token}`
|
| 393 |
}
|
|
@@ -782,7 +796,8 @@ async function sendMessage() {
|
|
| 782 |
showAuthModal(!isRegistering);
|
| 783 |
});
|
| 784 |
authActionBtn.addEventListener('click', async () => {
|
| 785 |
-
|
|
|
|
| 786 |
const username = regUsername.value.trim();
|
| 787 |
const email = regEmail.value.trim();
|
| 788 |
const password = authPassword.value.trim();
|
|
|
|
| 248 |
const typingIndicator = document.getElementById('typingIndicator');
|
| 249 |
const modelIndicator = document.getElementById('modelIndicator');
|
| 250 |
const themeBtns = document.querySelectorAll('.theme-btn');
|
| 251 |
+
// Authentication state management
|
| 252 |
const authError = document.getElementById('authError');
|
| 253 |
const authEmail = document.getElementById('authEmail');
|
| 254 |
const regEmail = document.getElementById('regEmail');
|
| 255 |
const authEmailLabel = document.getElementById('authEmailLabel');
|
| 256 |
let isRegistering = false;
|
| 257 |
|
| 258 |
+
// CSRF token management
|
| 259 |
+
let csrfToken = '';
|
| 260 |
+
async function getCsrfToken() {
|
| 261 |
+
try {
|
| 262 |
+
const response = await fetch('/api/csrf-token');
|
| 263 |
+
const data = await response.json();
|
| 264 |
+
csrfToken = data.token;
|
| 265 |
+
} catch (error) {
|
| 266 |
+
console.error('Failed to get CSRF token:', error);
|
| 267 |
+
}
|
| 268 |
+
}
|
| 269 |
+
function updateAuthUI() {
|
| 270 |
const loggedInUser = localStorage.getItem('chatRouterUser');
|
| 271 |
const token = localStorage.getItem('chatRouterToken');
|
| 272 |
|
|
|
|
| 306 |
}
|
| 307 |
async function loginUser(email, password, username = null) {
|
| 308 |
try {
|
| 309 |
+
const response = await fetch('/api/auth/login', {
|
| 310 |
+
method: 'POST',
|
| 311 |
headers: {
|
| 312 |
'Content-Type': 'application/json'
|
| 313 |
},
|
|
|
|
| 348 |
authError.classList.remove('hidden');
|
| 349 |
}
|
| 350 |
}
|
| 351 |
+
async function registerUser(username, email, password) {
|
| 352 |
try {
|
| 353 |
const response = await fetch('/api/auth/register', {
|
| 354 |
+
headers: {
|
| 355 |
+
'Content-Type': 'application/json'
|
| 356 |
+
},
|
| 357 |
+
method: 'POST',
|
| 358 |
headers: {
|
| 359 |
'Content-Type': 'application/json'
|
| 360 |
},
|
|
|
|
| 400 |
if (!token) return false;
|
| 401 |
|
| 402 |
try {
|
| 403 |
+
const response = await fetch('/api/auth/verify', {
|
| 404 |
+
method: 'GET',
|
| 405 |
headers: {
|
| 406 |
'Authorization': `Bearer ${token}`
|
| 407 |
}
|
|
|
|
| 796 |
showAuthModal(!isRegistering);
|
| 797 |
});
|
| 798 |
authActionBtn.addEventListener('click', async () => {
|
| 799 |
+
await getCsrfToken();
|
| 800 |
+
if (isRegistering) {
|
| 801 |
const username = regUsername.value.trim();
|
| 802 |
const email = regEmail.value.trim();
|
| 803 |
const password = authPassword.value.trim();
|