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();