package main import ( "database/sql" "encoding/json" "log" "net/http" _ "github.com/mattn/go-sqlite3" ) // KeyPermissions defines the permission levels for a key type KeyPermissions struct { Read bool `json:"read"` Write bool `json:"write"` Invite bool `json:"invite"` } // KeyRegistrationRequest is the expected format for key registration type KeyRegistrationRequest struct { RoomID string `json:"roomId"` PublicKey string `json:"publicKey"` Perms KeyPermissions `json:"permissions"` } // KeyRegistrationResponse is the response format for key registration type KeyRegistrationResponse struct { Success bool `json:"success"` Message string `json:"message"` } var authDB *sql.DB // InitAuthDB initializes the authentication database func InitAuthDB() error { var err error authDB, err = sql.Open("sqlite3", "./auth.db") if err != nil { return err } // Ensure the database connection works if err = authDB.Ping(); err != nil { log.Printf("Failed to connect to auth database: %v", err) return err } log.Printf("Successfully connected to auth database") // Create tables if they don't exist _, err = authDB.Exec(` CREATE TABLE IF NOT EXISTS rooms ( room_id TEXT PRIMARY KEY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_rooms_room_id ON rooms(room_id); CREATE TABLE IF NOT EXISTS room_keys ( id INTEGER PRIMARY KEY AUTOINCREMENT, room_id TEXT NOT NULL, public_key TEXT NOT NULL, can_read BOOLEAN NOT NULL DEFAULT 1, can_write BOOLEAN NOT NULL DEFAULT 1, can_invite BOOLEAN NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(room_id, public_key) ); CREATE INDEX IF NOT EXISTS idx_room_keys_room_id ON room_keys(room_id); `) if err != nil { log.Printf("Failed to create auth database tables: %v", err) return err } log.Printf("Auth database tables created successfully") return nil } // CloseAuthDB closes the authentication database connection func CloseAuthDB() { if authDB != nil { authDB.Close() } } // HandleKeyRegistration handles the registration of public keys for rooms func HandleKeyRegistration(w http.ResponseWriter, r *http.Request) { // Set CORS headers w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") // Handle preflight OPTIONS request if r.Method == http.MethodOptions { log.Println("Received OPTIONS request for key registration") w.WriteHeader(http.StatusOK) return } // Only allow POST requests if r.Method != http.MethodPost { log.Printf("Received non-POST request for key registration: %s", r.Method) http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } log.Println("Received POST request for key registration") // Parse the request body var req KeyRegistrationRequest decoder := json.NewDecoder(r.Body) if err := decoder.Decode(&req); err != nil { log.Printf("Error decoding key registration request: %v", err) http.Error(w, "Invalid request format", http.StatusBadRequest) return } log.Printf("Request content: Room ID=%s, Public Key length=%d", req.RoomID, len(req.PublicKey)) // Validate the request if req.RoomID == "" || req.PublicKey == "" { http.Error(w, "Room ID and public key are required", http.StatusBadRequest) return } // Set default permissions if not provided if req.Perms == (KeyPermissions{}) { req.Perms = KeyPermissions{ Read: true, Write: true, Invite: false, } } // Store the key in the database log.Printf("Storing key in database - Room: %s, Perms: read=%v, write=%v, invite=%v", req.RoomID, req.Perms.Read, req.Perms.Write, req.Perms.Invite) _, err := authDB.Exec( `INSERT OR REPLACE INTO room_keys (room_id, public_key, can_read, can_write, can_invite) VALUES (?, ?, ?, ?, ?)`, req.RoomID, req.PublicKey, req.Perms.Read, req.Perms.Write, req.Perms.Invite, ) if err != nil { log.Printf("Error storing key: %v", err) http.Error(w, "Failed to store key", http.StatusInternalServerError) return } // Return success response response := KeyRegistrationResponse{ Success: true, Message: "Key registered successfully", } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(response) log.Printf("Key registration successful - Room: %s, PublicKey: %s, Permissions: read=%v, write=%v, invite=%v", req.RoomID, req.PublicKey[:20] + "...", // Only log part of the key for security req.Perms.Read, req.Perms.Write, req.Perms.Invite) } // GetRoomKeys retrieves all keys for a specific room func GetRoomKeys(roomID string) ([]struct { PublicKey string CanRead bool CanWrite bool CanInvite bool }, error) { rows, err := authDB.Query( `SELECT public_key, can_read, can_write, can_invite FROM room_keys WHERE room_id = ?`, roomID, ) if err != nil { return nil, err } defer rows.Close() var keys []struct { PublicKey string CanRead bool CanWrite bool CanInvite bool } for rows.Next() { var key struct { PublicKey string CanRead bool CanWrite bool CanInvite bool } if err := rows.Scan(&key.PublicKey, &key.CanRead, &key.CanWrite, &key.CanInvite); err != nil { return nil, err } keys = append(keys, key) } return keys, nil } // GetOrCreateRoom gets a room from the auth database or creates it if it doesn't exist func GetOrCreateRoom(roomID string) error { // Check if room exists var exists bool err := authDB.QueryRow("SELECT 1 FROM rooms WHERE room_id = ?", roomID).Scan(&exists) if err != nil && err != sql.ErrNoRows { return err } // If room doesn't exist, create it if err == sql.ErrNoRows { _, err = authDB.Exec( "INSERT INTO rooms (room_id) VALUES (?)", roomID, ) if err != nil { return err } log.Printf("Created new room in auth database: %s", roomID) } return nil } // CheckRoomExists checks if a room exists in the database func CheckRoomExists(roomID string) (bool, error) { var exists bool err := authDB.QueryRow("SELECT 1 FROM rooms WHERE room_id = ?", roomID).Scan(&exists) if err == sql.ErrNoRows { return false, nil } if err != nil { return false, err } return true, nil } // CheckKeyPermission checks if a key has a specific permission for a room func CheckKeyPermission(roomID, publicKey string, permType string) (bool, error) { var hasPerm bool var query string switch permType { case "read": query = "SELECT can_read FROM room_keys WHERE room_id = ? AND public_key = ?" case "write": query = "SELECT can_write FROM room_keys WHERE room_id = ? AND public_key = ?" case "invite": query = "SELECT can_invite FROM room_keys WHERE room_id = ? AND public_key = ?" default: return false, nil } err := authDB.QueryRow(query, roomID, publicKey).Scan(&hasPerm) if err == sql.ErrNoRows { return false, nil } if err != nil { return false, err } return hasPerm, nil } // AutoGrantInvitePermissions auto-grants invite permissions (read + write + invite) for development mode func AutoGrantInvitePermissions(roomID, publicKey string) error { logKey := publicKey if len(logKey) > 20 { logKey = publicKey[:20] + "..." } log.Printf("Auto-granting invite permissions for key %s in room %s", logKey, roomID) _, err := authDB.Exec( `INSERT OR REPLACE INTO room_keys (room_id, public_key, can_read, can_write, can_invite) VALUES (?, ?, ?, ?, ?)`, roomID, publicKey, true, true, true, ) if err != nil { log.Printf("Error auto-granting permissions: %v", err) return err } log.Printf("Successfully auto-granted invite permissions") return nil } // VerifySignature verifies that a signature was made by the public key func VerifySignature(publicKey string, data string, signature string) (bool, error) { // This is a placeholder - the actual implementation will depend on how you're handling // Web Crypto signatures on the client side // You'll need to parse the public key, decode the signature, and verify using the appropriate // crypto algorithm (likely ECDSA or RSA) // For now, we'll just return true return true, nil }