diff --git a/deploy/shagram/compose.yaml b/deploy/shagram/compose.yaml index 03731f5..fa37009 100644 --- a/deploy/shagram/compose.yaml +++ b/deploy/shagram/compose.yaml @@ -5,6 +5,7 @@ services: context: ../.. container_name: shagram-app environment: + - WS_ALLOWED_ORIGINS=https://localhost,http://localhost - DATABASE_PATH=/app/data/shagram.db - JWT_SECRET=${JWT_SECRET} volumes: diff --git a/internal/api/websocket.go b/internal/api/websocket.go index 107ec3d..e5240e2 100644 --- a/internal/api/websocket.go +++ b/internal/api/websocket.go @@ -3,15 +3,30 @@ package api import ( "log" "net/http" + "os" + "shagram/internal/auth" "shagram/internal/db" "shagram/internal/websocket" + "strings" "github.com/gin-gonic/gin" gws "github.com/gorilla/websocket" ) var upgrader = gws.Upgrader{ - CheckOrigin: func(r *http.Request) bool { return true }, + CheckOrigin: func(r *http.Request) bool { + origin := r.Header.Get("Origin") + if origin == "" { + return false + } + allowed := strings.Split(os.Getenv("WS_ALLOWED_ORIGINS"), ",") + for _, a := range allowed { + if strings.TrimSpace(a) == origin { + return true + } + } + return false + }, } func WebSocketHandler(hub *websocket.Hub, database *db.DB) gin.HandlerFunc { @@ -19,6 +34,14 @@ func WebSocketHandler(hub *websocket.Hub, database *db.DB) gin.HandlerFunc { roomID := c.Param("room") room := hub.GetOrCreateRoom(roomID) + tokenString := c.Query("token") + claims, err := auth.ParseAccessToken(tokenString) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"}) + return + } + username := claims.Username + conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { return @@ -35,25 +58,24 @@ func WebSocketHandler(hub *websocket.Hub, database *db.DB) gin.HandlerFunc { for { var msg map[string]string - err := client.Conn.ReadJSON(&msg) - if err != nil { + if err := client.Conn.ReadJSON(&msg); err != nil { break } - user := msg["user"] - if user == "" { - user = "Anonymous" + + text := msg["text"] + if text == "" { + continue } + _, err = database.Exec(` INSERT INTO messages (room_id, user, text) - VALUES (?, ?, ?)`, roomID, user, msg["text"]) + VALUES (?, ?, ?)`, roomID, username, text) if err != nil { log.Printf("Save message error: %v", err) } - formattedMsg := user + ": " + msg["text"] - message := []byte(formattedMsg) - room.Broadcast(message) + + room.Broadcast([]byte(username + ": " + text)) } }() } - } diff --git a/static/index.html b/static/index.html index ce71eca..72a5e78 100644 --- a/static/index.html +++ b/static/index.html @@ -4,18 +4,18 @@ Shagram Chat

📱 Shagram Chat

- +
+ + + +
-
- +
-
diff --git a/static/js/chat.js b/static/js/chat.js index 621cca1..7f41d31 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -1,71 +1,103 @@ let ws = null; +let accessToken = null; + const messagesDiv = document.getElementById('messages'); const messageInput = document.getElementById('messageInput'); const roomSelect = document.getElementById('roomSelect'); +const authStatus = document.getElementById('authStatus'); + +async function login() { + const usernameInput = document.getElementById('userInput'); + const username = usernameInput ? usernameInput.value.trim() : ''; + + if (!username) { + alert('Enter username'); + return; + } + + authStatus.textContent = 'Logging in...'; + + const resp = await fetch('/api/auth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username }), + }); + + if (!resp.ok) { + const text = await resp.text(); + authStatus.textContent = 'Login failed'; + console.error('Login failed:', resp.status, text); + return; + } + + const data = await resp.json(); + accessToken = data.access_token || null; + + if (!accessToken) { + authStatus.textContent = 'Login failed'; + return; + } + + authStatus.textContent = '✅ Logged in'; +} function loadRooms() { fetch('/api/rooms') - .then(response => response.json()) - .then(data => { - roomSelect.innerHTML = ''; - data.rooms.forEach(room => { - const option = document.createElement('option'); - option.value = room; - option.textContent = room; - roomSelect.appendChild(option); + .then(response => response.json()) + .then(data => { + roomSelect.innerHTML = ''; + (data.rooms || []).forEach(room => { + const option = document.createElement('option'); + option.value = room; + option.textContent = room; + roomSelect.appendChild(option); + }); + }) + .catch(error => { + console.error('Error loading rooms:', error); }); - }) - .catch(error => { - console.error('Error loading rooms:', error); - }) } function connectRoom() { const room = roomSelect.value || 'general'; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - const host = window.location.host; + const host = window.location.host; + + if (!accessToken) { + alert('Please login first'); + return; + } + if (ws) { ws.close(); } - const wsUrl = `${protocol}//${host}/ws/${room}`; + + const wsUrl = `${protocol}//${host}/ws/${room}?token=${encodeURIComponent(accessToken)}`; ws = new WebSocket(wsUrl); - - ws.onopen = function() { - console.log('WebSocket connected, loading history for', room); - fetch(`/api/messages/${room}`) - .then(response => { - console.log('API response:', response.status); - return response.json() - }) + .then(response => response.json()) .then(data => { - console.log('History data:', data); messagesDiv.innerHTML = ''; const history = data.messages || []; - console.log('history length:', history.length); if (history.length > 0) { - history.reverse().forEach((msg, index) => { - console.log(`Message ${index}:`, msg) + history.reverse().forEach(msg => { const msgDiv = document.createElement('div'); msgDiv.className = 'message'; msgDiv.textContent = `${msg.user}: ${msg.text}`; messagesDiv.appendChild(msgDiv); - }) + }); } const connectMsg = document.createElement('div'); connectMsg.className = 'message'; connectMsg.textContent = `✅ Connected to ${room}`; messagesDiv.appendChild(connectMsg); messagesDiv.scrollTop = messagesDiv.scrollHeight; - console.log('History loaded!'); }) .catch(error => { - console.error('❌ History error', error); - }) - - + console.error('History error', error); + }); }; ws.onmessage = function(event) { @@ -88,19 +120,14 @@ function connectRoom() { function sendMessage() { const text = messageInput.value.trim(); - const usernameInput = document.getElementById('userInput'); - const username = usernameInput ? usernameInput.value.trim() || 'Anonymous' : 'Anonymous'; if (!text || !ws || ws.readyState !== WebSocket.OPEN) { alert('Not connected or empty message'); return; } - - ws.send(JSON.stringify({ - text: text, - user: username, - })); + + ws.send(JSON.stringify({ text })); messageInput.value = ''; } -loadRooms(); \ No newline at end of file +loadRooms();