Mohit0199 commited on
Commit
590d81d
·
verified ·
1 Parent(s): 6f90744

now i want to add proper authentication where user can login only with their google account and Users should be able to sign up, log in, and log out securely Once logged in, users should automatically be taken to the chat interface. Each user should have their own chat history — so when they log in again later, they can see their previous conversations in the sidebar

Browse files
Files changed (3) hide show
  1. .env +16 -0
  2. auth.js +93 -0
  3. index.html +128 -168
.env ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```env
2
+ # Google OAuth
3
+ GOOGLE_CLIENT_ID=your_google_client_id.apps.googleusercontent.com
4
+ GOOGLE_CLIENT_SECRET=your_google_client_secret
5
+ GOOGLE_REDIRECT_URI=http://localhost:3000/auth/google/callback
6
+
7
+ # Session
8
+ SESSION_SECRET=your_session_secret_here
9
+
10
+ # OpenRouter
11
+ OPENROUTER_API_KEY=your_openrouter_api_key
12
+
13
+ # Server
14
+ PORT=3000
15
+ NODE_ENV=development
16
+ ```
auth.js ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```javascript
2
+ const { OAuth2Client } = require('google-auth-library');
3
+ const express = require('express');
4
+ const session = require('express-session');
5
+ const dotenv = require('dotenv');
6
+ const path = require('path');
7
+
8
+ dotenv.config();
9
+
10
+ const app = express();
11
+ const client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID);
12
+
13
+ // Middleware
14
+ app.use(express.json());
15
+ app.use(express.static(path.join(__dirname, 'public')));
16
+ app.use(session({
17
+ secret: process.env.SESSION_SECRET,
18
+ resave: false,
19
+ saveUninitialized: false,
20
+ cookie: { secure: process.env.NODE_ENV === 'production', maxAge: 24 * 60 * 60 * 1000 }
21
+ }));
22
+
23
+ // Google OAuth routes
24
+ app.get('/auth/google', (req, res) => {
25
+ const url = client.generateAuthUrl({
26
+ access_type: 'online',
27
+ scope: ['profile', 'email'],
28
+ redirect_uri: process.env.GOOGLE_REDIRECT_URI
29
+ });
30
+ res.redirect(url);
31
+ });
32
+
33
+ app.get('/auth/google/callback', async (req, res) => {
34
+ const { code } = req.query;
35
+
36
+ try {
37
+ const { tokens } = await client.getToken({
38
+ code,
39
+ redirect_uri: process.env.GOOGLE_REDIRECT_URI
40
+ });
41
+
42
+ const ticket = await client.verifyIdToken({
43
+ idToken: tokens.id_token,
44
+ audience: process.env.GOOGLE_CLIENT_ID
45
+ });
46
+
47
+ const payload = ticket.getPayload();
48
+ req.session.user = {
49
+ id: payload.sub,
50
+ email: payload.email,
51
+ name: payload.name,
52
+ picture: payload.picture
53
+ };
54
+
55
+ res.redirect('/');
56
+ } catch (error) {
57
+ console.error('Auth error:', error);
58
+ res.redirect('/?auth_error=1');
59
+ }
60
+ });
61
+
62
+ app.get('/auth/logout', (req, res) => {
63
+ req.session.destroy();
64
+ res.redirect('/');
65
+ });
66
+
67
+ app.get('/auth/status', (req, res) => {
68
+ res.json({ isAuthenticated: !!req.session.user, user: req.session.user });
69
+ });
70
+
71
+ // Chat history routes
72
+ app.get('/api/chats', async (req, res) => {
73
+ if (!req.session.user) return res.sendStatus(401);
74
+
75
+ // In a real app, you'd fetch from a database
76
+ const chats = JSON.parse(localStorage.getItem(`chats_${req.session.user.id}`) || '[]');
77
+ res.json(chats);
78
+ });
79
+
80
+ app.post('/api/chats', async (req, res) => {
81
+ if (!req.session.user) return res.sendStatus(401);
82
+
83
+ // In a real app, you'd save to a database
84
+ const chats = JSON.parse(localStorage.getItem(`chats_${req.session.user.id}`) || '[]');
85
+ chats.push(req.body);
86
+ localStorage.setItem(`chats_${req.session.user.id}`, JSON.stringify(chats));
87
+
88
+ res.sendStatus(201);
89
+ });
90
+
91
+ const PORT = process.env.PORT || 3000;
92
+ app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
93
+ ```
index.html CHANGED
@@ -205,7 +205,7 @@
205
  <p class="mt-1">Your conversations stay private and secure</p>
206
  </footer>
207
  </div>
208
-
209
  <script>
210
  feather.replace();
211
  // DOM Elements - New Sidebar Elements
@@ -215,17 +215,8 @@
215
  const chatList = document.getElementById('chatList');
216
 
217
  // DOM Elements - Original
218
- const authBtn = document.getElementById('authBtn');
219
- const authModal = document.getElementById('authModal');
220
- const authModalTitle = document.getElementById('authModalTitle');
221
- const closeAuth = document.getElementById('closeAuth');
222
- const regUsername = document.getElementById('regUsername');
223
- const authPassword = document.getElementById('authPassword');
224
- const authActionBtn = document.getElementById('authActionBtn');
225
- const authToggleBtn = document.getElementById('authToggleBtn');
226
- const authToggleText = document.getElementById('authToggleText');
227
- const registerFields = document.getElementById('registerFields');
228
- const newChatBtn = document.getElementById('newChatBtn');
229
  const settingsBtn = document.getElementById('settingsBtn');
230
  const settingsModal = document.getElementById('settingsModal');
231
  const closeSettings = document.getElementById('closeSettings');
@@ -238,36 +229,49 @@ const settingsBtn = document.getElementById('settingsBtn');
238
  const typingIndicator = document.getElementById('typingIndicator');
239
  const modelIndicator = document.getElementById('modelIndicator');
240
  const themeBtns = document.querySelectorAll('.theme-btn');
241
- // User management
242
- let isRegistering = false;
243
 
244
- function updateAuthUI() {
245
- const loggedInUser = localStorage.getItem('chatRouterUser');
246
- if (loggedInUser) {
247
- authBtn.innerHTML = `<i data-feather="log-out" class="w-5 h-5"></i><span>Logout</span>`;
248
- authBtn.dataset.state = 'logout';
249
- document.querySelector('#newChatBtn').disabled = false;
250
- document.querySelector('#settingsBtn').disabled = false;
251
- sendBtn.disabled = false;
252
- } else {
253
- authBtn.innerHTML = `<i data-feather="user" class="w-5 h-5"></i><span>Login</span>`;
254
- authBtn.dataset.state = 'login';
255
- document.querySelector('#newChatBtn').disabled = true;
256
- document.querySelector('#settingsBtn').disabled = true;
257
- sendBtn.disabled = true;
258
- showAuthModal();
 
 
 
 
 
 
 
259
  }
260
- feather.replace();
261
  }
262
 
263
- function showAuthModal(register = false) {
264
- isRegistering = register;
265
- authModalTitle.textContent = register ? 'Register' : 'Login';
266
- authActionBtn.textContent = register ? 'Register' : 'Login';
267
- authToggleText.textContent = register ? 'Already have an account? ' : 'Don\'t have an account? ';
268
- authToggleBtn.textContent = register ? 'Login' : 'Register';
269
- registerFields.classList.toggle('hidden', !register);
270
- authModal.classList.remove('hidden');
 
 
 
 
 
 
 
271
  }
272
  // Chat management
273
  let currentChatId = null;
@@ -276,30 +280,23 @@ const settingsBtn = document.getElementById('settingsBtn');
276
  return 'chat_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
277
  }
278
 
279
- function loginUser(username, password) {
280
- // Simple auth - store user in localStorage
281
- localStorage.setItem('chatRouterUser', username);
282
- localStorage.setItem(`chatRouterPass_${username}`, password); // Not secure for production!
283
- updateAuthUI();
284
 
285
- // Create a new chat session
286
- createNewChat();
287
-
288
- // Load user's chat list
289
- loadChatList(username);
290
- }
291
- function logoutUser() {
292
- const username = localStorage.getItem('chatRouterUser');
293
- localStorage.removeItem('chatRouterUser');
294
- updateAuthUI();
295
-
296
- // Clear current chat and sidebar
297
- chatContainer.innerHTML = '';
298
- chatList.innerHTML = '';
299
- currentChatId = null;
300
- addMessage('system', 'Please log in to start chatting.');
301
  }
302
- function createNewChat() {
 
303
  const username = localStorage.getItem('chatRouterUser');
304
  if (!username) return;
305
 
@@ -309,54 +306,44 @@ const settingsBtn = document.getElementById('settingsBtn');
309
  saveChatHistory(username, currentChatId);
310
  addChatToList(username, currentChatId, 'New Chat');
311
  }
312
-
313
- function loadChatHistory(username, chatId) {
314
- const history = localStorage.getItem(`chatHistory_${username}_${chatId}`);
315
- if (history) {
316
- chatContainer.innerHTML = history;
 
 
 
317
  chatContainer.scrollTop = chatContainer.scrollHeight;
318
  currentChatId = chatId;
 
 
319
  }
320
  }
321
 
322
- function saveChatHistory(username, chatId) {
323
  if (!chatId) chatId = currentChatId;
324
- if (!username || !chatId) return;
325
-
326
- localStorage.setItem(`chatHistory_${username}_${chatId}`, chatContainer.innerHTML);
327
-
328
- // Update the chat title if it's the first message
329
- const messages = chatContainer.querySelectorAll('.message-bubble');
330
- if (messages.length === 1) {
331
- updateChatTitle(username, chatId, messages[0].textContent.substring(0, 30));
332
- }
333
- }
334
-
335
- function loadChatList(username) {
336
- chatList.innerHTML = '';
337
- const chats = [];
338
 
339
- // Find all chats for this user
340
- for (let i = 0; i < localStorage.length; i++) {
341
- const key = localStorage.key(i);
342
- if (key.startsWith(`chatHistory_${username}_`)) {
343
- const chatId = key.split('_')[2];
344
- const title = localStorage.getItem(`chatTitle_${username}_${chatId}`) || 'New Chat';
345
- chats.push({ id: chatId, title });
346
- }
 
 
 
 
 
 
 
347
  }
348
-
349
- // Sort by most recent first
350
- chats.sort((a, b) => b.id.localeCompare(a.id));
351
-
352
- // Add to sidebar
353
- chats.forEach(chat => {
354
- addChatToList(username, chat.id, chat.title);
355
- });
356
  }
357
-
358
- function addChatToList(username, chatId, title) {
359
- const chatItem = document.createElement('div');
360
  chatItem.className = 'flex justify-between items-center p-2 hover:bg-gray-700 rounded-lg cursor-pointer';
361
  chatItem.dataset.chatId = chatId;
362
 
@@ -395,37 +382,58 @@ const settingsBtn = document.getElementById('settingsBtn');
395
  chatList.prepend(chatItem);
396
  feather.replace();
397
  }
398
-
399
- function updateChatTitle(username, chatId, title) {
400
- localStorage.setItem(`chatTitle_${username}_${chatId}`, title);
401
- const chatItem = chatList.querySelector(`[data-chat-id="${chatId}"]`);
402
- if (chatItem) {
403
- chatItem.querySelector('span').textContent = title;
 
 
 
 
 
 
 
 
 
 
404
  }
405
  }
406
 
407
- function renameChat(username, chatId, titleElement) {
408
- const newTitle = prompt('Enter new chat title:', titleElement.textContent);
409
  if (newTitle && newTitle.trim()) {
410
  localStorage.setItem(`chatTitle_${username}_${chatId}`, newTitle.trim());
411
  titleElement.textContent = newTitle.trim();
412
  }
413
  }
414
-
415
- function deleteChat(username, chatId, element) {
416
- if (confirm('Are you sure you want to delete this chat?')) {
417
- localStorage.removeItem(`chatHistory_${username}_${chatId}`);
418
- localStorage.removeItem(`chatTitle_${username}_${chatId}`);
419
  element.remove();
420
 
421
  if (currentChatId === chatId) {
422
  createNewChat();
423
  }
 
 
424
  }
425
  }
426
- // Load saved settings
427
- async function loadSettings() {
428
- const savedApiKey = localStorage.getItem('chatRouterApiKey');
 
 
 
 
 
 
 
 
 
429
  const savedModel = localStorage.getItem('chatRouterModel');
430
  const savedTheme = localStorage.getItem('chatRouterTheme') || 'indigo';
431
 
@@ -655,54 +663,7 @@ async function sendMessage() {
655
  typingIndicator.classList.add('hidden');
656
  }
657
  }
658
- // Event listeners
659
- authBtn.addEventListener('click', () => {
660
- if (authBtn.dataset.state === 'logout') {
661
- logoutUser();
662
- } else {
663
- showAuthModal();
664
- }
665
- });
666
-
667
- closeAuth.addEventListener('click', () => {
668
- authModal.classList.add('hidden');
669
- });
670
-
671
- authToggleBtn.addEventListener('click', () => {
672
- showAuthModal(!isRegistering);
673
- });
674
-
675
- authActionBtn.addEventListener('click', () => {
676
- if (isRegistering) {
677
- const username = regUsername.value.trim();
678
- const password = authPassword.value.trim();
679
-
680
- if (!username || !password) {
681
- alert('Please enter both username and password');
682
- return;
683
- }
684
-
685
- if (localStorage.getItem(`chatRouterPass_${username}`)) {
686
- alert('Username already exists');
687
- return;
688
- }
689
-
690
- loginUser(username, password);
691
- } else {
692
- const username = regUsername.value.trim();
693
- const password = authPassword.value.trim();
694
- const storedPass = localStorage.getItem(`chatRouterPass_${username}`);
695
-
696
- if (!storedPass || storedPass !== password) {
697
- alert('Invalid username or password');
698
- return;
699
- }
700
-
701
- loginUser(username, password);
702
- }
703
- authModal.classList.add('hidden');
704
- });
705
- newChatBtn.addEventListener('click', createNewChat);
706
  newSidebarChat.addEventListener('click', createNewChat);
707
 
708
  // Sidebar toggle for mobile
@@ -724,20 +685,19 @@ messageInput.addEventListener('keydown', (e) => {
724
  });
725
  // Initialize
726
  loadSettings();
727
- updateAuthUI();
728
 
729
  // Welcome message
730
  setTimeout(() => {
731
- if (!localStorage.getItem('chatRouterUser')) {
732
- addMessage('system', 'Welcome to ChatRouter! Please log in to start chatting.');
733
  } else if (!localStorage.getItem('chatRouterModel')) {
734
  addMessage('system', 'Please select a free model from settings to begin.');
735
  } else if (!currentChatId) {
736
  createNewChat();
737
  }
738
  }, 1000);
739
-
740
- // Close sidebar when clicking outside on mobile
741
  document.addEventListener('click', (e) => {
742
  if (window.innerWidth < 768 && !sidebar.contains(e.target) && e.target !== sidebarToggle) {
743
  sidebar.classList.add('hidden');
 
205
  <p class="mt-1">Your conversations stay private and secure</p>
206
  </footer>
207
  </div>
208
+ <script src="https://accounts.google.com/gsi/client" async defer></script>
209
  <script>
210
  feather.replace();
211
  // DOM Elements - New Sidebar Elements
 
215
  const chatList = document.getElementById('chatList');
216
 
217
  // DOM Elements - Original
218
+ const authBtn = document.getElementById('authBtn');
219
+ const newChatBtn = document.getElementById('newChatBtn');
 
 
 
 
 
 
 
 
 
220
  const settingsBtn = document.getElementById('settingsBtn');
221
  const settingsModal = document.getElementById('settingsModal');
222
  const closeSettings = document.getElementById('closeSettings');
 
229
  const typingIndicator = document.getElementById('typingIndicator');
230
  const modelIndicator = document.getElementById('modelIndicator');
231
  const themeBtns = document.querySelectorAll('.theme-btn');
232
+ // Auth state management
233
+ let currentUser = null;
234
 
235
+ async function checkAuth() {
236
+ try {
237
+ const response = await fetch('/auth/status');
238
+ const data = await response.json();
239
+
240
+ if (data.isAuthenticated) {
241
+ currentUser = data.user;
242
+ authBtn.innerHTML = `<img src="${data.user.picture}" class="w-6 h-6 rounded-full mr-2"><span>Logout</span>`;
243
+ document.querySelector('#newChatBtn').disabled = false;
244
+ document.querySelector('#settingsBtn').disabled = false;
245
+ sendBtn.disabled = false;
246
+ loadChatList();
247
+ } else {
248
+ currentUser = null;
249
+ authBtn.innerHTML = `<i data-feather="user" class="w-5 h-5"></i><span>Login with Google</span>`;
250
+ document.querySelector('#newChatBtn').disabled = true;
251
+ document.querySelector('#settingsBtn').disabled = true;
252
+ sendBtn.disabled = true;
253
+ }
254
+ feather.replace();
255
+ } catch (error) {
256
+ console.error('Auth check failed:', error);
257
  }
 
258
  }
259
 
260
+ function handleGoogleSignIn() {
261
+ window.location.href = '/auth/google';
262
+ }
263
+
264
+ async function handleLogout() {
265
+ try {
266
+ await fetch('/auth/logout');
267
+ currentUser = null;
268
+ chatList.innerHTML = '';
269
+ chatContainer.innerHTML = '';
270
+ checkAuth();
271
+ addMessage('system', 'Please sign in to start chatting.');
272
+ } catch (error) {
273
+ console.error('Logout failed:', error);
274
+ }
275
  }
276
  // Chat management
277
  let currentChatId = null;
 
280
  return 'chat_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
281
  }
282
 
283
+ async function loadChatList() {
284
+ if (!currentUser) return;
 
 
 
285
 
286
+ try {
287
+ const response = await fetch('/api/chats');
288
+ const chats = await response.json();
289
+
290
+ chatList.innerHTML = '';
291
+ chats.forEach(chat => {
292
+ addChatToList(chat.id, chat.title);
293
+ });
294
+ } catch (error) {
295
+ console.error('Failed to load chats:', error);
296
+ }
 
 
 
 
 
297
  }
298
+
299
+ function createNewChat() {
300
  const username = localStorage.getItem('chatRouterUser');
301
  if (!username) return;
302
 
 
306
  saveChatHistory(username, currentChatId);
307
  addChatToList(username, currentChatId, 'New Chat');
308
  }
309
+ async function loadChatHistory(chatId) {
310
+ if (!currentUser) return;
311
+
312
+ try {
313
+ const response = await fetch(`/api/chats/${chatId}`);
314
+ const chat = await response.json();
315
+
316
+ chatContainer.innerHTML = chat.history;
317
  chatContainer.scrollTop = chatContainer.scrollHeight;
318
  currentChatId = chatId;
319
+ } catch (error) {
320
+ console.error('Failed to load chat history:', error);
321
  }
322
  }
323
 
324
+ async function saveChatHistory(chatId) {
325
  if (!chatId) chatId = currentChatId;
326
+ if (!currentUser || !chatId) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
+ try {
329
+ await fetch('/api/chats', {
330
+ method: 'POST',
331
+ headers: {
332
+ 'Content-Type': 'application/json'
333
+ },
334
+ body: JSON.stringify({
335
+ id: chatId,
336
+ title: chatContainer.querySelector('.message-bubble')?.textContent.substring(0, 30) || 'New Chat',
337
+ history: chatContainer.innerHTML,
338
+ updatedAt: new Date().toISOString()
339
+ })
340
+ });
341
+ } catch (error) {
342
+ console.error('Failed to save chat:', error);
343
  }
 
 
 
 
 
 
 
 
344
  }
345
+ function addChatToList(chatId, title) {
346
+ const chatItem = document.createElement('div');
 
347
  chatItem.className = 'flex justify-between items-center p-2 hover:bg-gray-700 rounded-lg cursor-pointer';
348
  chatItem.dataset.chatId = chatId;
349
 
 
382
  chatList.prepend(chatItem);
383
  feather.replace();
384
  }
385
+ async function updateChatTitle(chatId, title) {
386
+ try {
387
+ await fetch(`/api/chats/${chatId}`, {
388
+ method: 'PUT',
389
+ headers: {
390
+ 'Content-Type': 'application/json'
391
+ },
392
+ body: JSON.stringify({ title })
393
+ });
394
+
395
+ const chatItem = chatList.querySelector(`[data-chat-id="${chatId}"]`);
396
+ if (chatItem) {
397
+ chatItem.querySelector('span').textContent = title;
398
+ }
399
+ } catch (error) {
400
+ console.error('Failed to update chat title:', error);
401
  }
402
  }
403
 
404
+ async function renameChat(chatId, titleElement) {
405
+ const newTitle = prompt('Enter new chat title:', titleElement.textContent);
406
  if (newTitle && newTitle.trim()) {
407
  localStorage.setItem(`chatTitle_${username}_${chatId}`, newTitle.trim());
408
  titleElement.textContent = newTitle.trim();
409
  }
410
  }
411
+ async function deleteChat(chatId, element) {
412
+ if (!confirm('Are you sure you want to delete this chat?')) return;
413
+
414
+ try {
415
+ await fetch(`/api/chats/${chatId}`, { method: 'DELETE' });
416
  element.remove();
417
 
418
  if (currentChatId === chatId) {
419
  createNewChat();
420
  }
421
+ } catch (error) {
422
+ console.error('Failed to delete chat:', error);
423
  }
424
  }
425
+ // Event listeners
426
+ authBtn.addEventListener('click', () => {
427
+ if (currentUser) {
428
+ handleLogout();
429
+ } else {
430
+ handleGoogleSignIn();
431
+ }
432
+ });
433
+
434
+ // Load saved settings
435
+ async function loadSettings() {
436
+ const savedApiKey = localStorage.getItem('chatRouterApiKey');
437
  const savedModel = localStorage.getItem('chatRouterModel');
438
  const savedTheme = localStorage.getItem('chatRouterTheme') || 'indigo';
439
 
 
663
  typingIndicator.classList.add('hidden');
664
  }
665
  }
666
+ newChatBtn.addEventListener('click', createNewChat);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667
  newSidebarChat.addEventListener('click', createNewChat);
668
 
669
  // Sidebar toggle for mobile
 
685
  });
686
  // Initialize
687
  loadSettings();
688
+ checkAuth();
689
 
690
  // Welcome message
691
  setTimeout(() => {
692
+ if (!currentUser) {
693
+ addMessage('system', 'Welcome to ChatRouter! Please sign in with Google to start chatting.');
694
  } else if (!localStorage.getItem('chatRouterModel')) {
695
  addMessage('system', 'Please select a free model from settings to begin.');
696
  } else if (!currentChatId) {
697
  createNewChat();
698
  }
699
  }, 1000);
700
+ // Close sidebar when clicking outside on mobile
 
701
  document.addEventListener('click', (e) => {
702
  if (window.innerWidth < 768 && !sidebar.contains(e.target) && e.target !== sidebarToggle) {
703
  sidebar.classList.add('hidden');