feat: implement comprehensive room validation and admin-only command access
- Add RoomExists and GetRoomMapping methods to Bridge interface - Implement XMPP room existence checking using disco#info queries (XEP-0030) - Add room validation in BridgeManager to prevent duplicate mappings and invalid rooms - Enhance XMPP client with CheckRoomExists method and comprehensive logging - Implement admin-only access control for all bridge commands - Add user-friendly error messages with actionable troubleshooting steps - Update doctor command with room existence testing and pre-join validation - Add SimpleLogger implementation for standalone command usage 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
1f45197aa8
commit
a95ca8fb76
8 changed files with 454 additions and 17 deletions
|
@ -245,6 +245,39 @@ func (m *Manager) OnChannelMappingCreated(req model.ChannelMappingRequest) error
|
|||
return fmt.Errorf("bridge '%s' is not connected", req.BridgeName)
|
||||
}
|
||||
|
||||
// NEW: Check if room already mapped to another channel
|
||||
existingChannelID, err := bridge.GetRoomMapping(req.BridgeRoomID)
|
||||
if err != nil {
|
||||
m.logger.LogError("Failed to check room mapping", "bridge_room_id", req.BridgeRoomID, "error", err)
|
||||
return fmt.Errorf("failed to check room mapping: %w", err)
|
||||
}
|
||||
if existingChannelID != "" {
|
||||
m.logger.LogWarn("Room already mapped to another channel",
|
||||
"bridge_room_id", req.BridgeRoomID,
|
||||
"existing_channel_id", existingChannelID,
|
||||
"requested_channel_id", req.ChannelID)
|
||||
return fmt.Errorf("room '%s' is already mapped to channel '%s'", req.BridgeRoomID, existingChannelID)
|
||||
}
|
||||
|
||||
// NEW: Check if room exists on target bridge
|
||||
roomExists, err := bridge.RoomExists(req.BridgeRoomID)
|
||||
if err != nil {
|
||||
m.logger.LogError("Failed to check room existence", "bridge_room_id", req.BridgeRoomID, "error", err)
|
||||
return fmt.Errorf("failed to check room existence: %w", err)
|
||||
}
|
||||
if !roomExists {
|
||||
m.logger.LogWarn("Room does not exist on bridge",
|
||||
"bridge_room_id", req.BridgeRoomID,
|
||||
"bridge_name", req.BridgeName)
|
||||
return fmt.Errorf("room '%s' does not exist on %s bridge", req.BridgeRoomID, req.BridgeName)
|
||||
}
|
||||
|
||||
m.logger.LogDebug("Room validation passed",
|
||||
"bridge_room_id", req.BridgeRoomID,
|
||||
"bridge_name", req.BridgeName,
|
||||
"room_exists", roomExists,
|
||||
"already_mapped", false)
|
||||
|
||||
// Create the channel mapping on the receiving bridge
|
||||
if err = bridge.CreateChannelMapping(req.ChannelID, req.BridgeRoomID); err != nil {
|
||||
m.logger.LogError("Failed to create channel mapping", "channel_id", req.ChannelID, "bridge_name", req.BridgeName, "bridge_room_id", req.BridgeRoomID, "error", err)
|
||||
|
|
|
@ -255,3 +255,53 @@ func (b *mattermostBridge) DeleteChannelMapping(channelID string) error {
|
|||
b.logger.LogInfo("Deleted Mattermost channel room mapping", "channel_id", channelID, "room_id", roomID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RoomExists checks if a Mattermost channel exists on the server
|
||||
func (b *mattermostBridge) RoomExists(roomID string) (bool, error) {
|
||||
if b.api == nil {
|
||||
return false, fmt.Errorf("Mattermost API not initialized")
|
||||
}
|
||||
|
||||
b.logger.LogDebug("Checking if Mattermost channel exists", "channel_id", roomID)
|
||||
|
||||
// Use the Mattermost API to check if the channel exists
|
||||
channel, appErr := b.api.GetChannel(roomID)
|
||||
if appErr != nil {
|
||||
if appErr.StatusCode == 404 {
|
||||
b.logger.LogDebug("Mattermost channel does not exist", "channel_id", roomID)
|
||||
return false, nil
|
||||
}
|
||||
b.logger.LogError("Failed to check channel existence", "channel_id", roomID, "error", appErr)
|
||||
return false, fmt.Errorf("failed to check channel existence: %w", appErr)
|
||||
}
|
||||
|
||||
if channel == nil {
|
||||
b.logger.LogDebug("Mattermost channel does not exist (nil response)", "channel_id", roomID)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
b.logger.LogDebug("Mattermost channel exists", "channel_id", roomID, "channel_name", channel.Name)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// GetRoomMapping retrieves the Mattermost channel ID for a given room ID (reverse lookup)
|
||||
func (b *mattermostBridge) GetRoomMapping(roomID string) (string, error) {
|
||||
if b.kvstore == nil {
|
||||
return "", fmt.Errorf("KV store not initialized")
|
||||
}
|
||||
|
||||
b.logger.LogDebug("Getting channel mapping for Mattermost room", "room_id", roomID)
|
||||
|
||||
// Look up the channel ID using the room ID as the key
|
||||
channelIDBytes, err := b.kvstore.Get(kvstore.BuildChannelMapKey("mattermost", roomID))
|
||||
if err != nil {
|
||||
// No mapping found is not an error, just return empty string
|
||||
b.logger.LogDebug("No channel mapping found for room", "room_id", roomID)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
channelID := string(channelIDBytes)
|
||||
b.logger.LogDebug("Found channel mapping for room", "room_id", roomID, "channel_id", channelID)
|
||||
|
||||
return channelID, nil
|
||||
}
|
||||
|
|
|
@ -73,6 +73,7 @@ func (b *xmppBridge) createXMPPClient(cfg *config.Configuration) *xmppClient.Cli
|
|||
cfg.GetXMPPResource(),
|
||||
"", // remoteID not needed for bridge user
|
||||
tlsConfig,
|
||||
b.logger,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -471,3 +472,48 @@ func (b *xmppBridge) DeleteChannelMapping(channelID string) error {
|
|||
b.logger.LogInfo("Deleted channel room mapping", "channel_id", channelID, "room_jid", roomJID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RoomExists checks if an XMPP room exists on the remote service
|
||||
func (b *xmppBridge) RoomExists(roomID string) (bool, error) {
|
||||
if !b.connected.Load() {
|
||||
return false, fmt.Errorf("not connected to XMPP server")
|
||||
}
|
||||
|
||||
if b.xmppClient == nil {
|
||||
return false, fmt.Errorf("XMPP client not initialized")
|
||||
}
|
||||
|
||||
b.logger.LogDebug("Checking if XMPP room exists", "room_jid", roomID)
|
||||
|
||||
// Use the XMPP client to check room existence
|
||||
exists, err := b.xmppClient.CheckRoomExists(roomID)
|
||||
if err != nil {
|
||||
b.logger.LogError("Failed to check room existence", "room_jid", roomID, "error", err)
|
||||
return false, fmt.Errorf("failed to check room existence: %w", err)
|
||||
}
|
||||
|
||||
b.logger.LogDebug("Room existence check completed", "room_jid", roomID, "exists", exists)
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
// GetRoomMapping retrieves the Mattermost channel ID for a given XMPP room JID (reverse lookup)
|
||||
func (b *xmppBridge) GetRoomMapping(roomID string) (string, error) {
|
||||
if b.kvstore == nil {
|
||||
return "", fmt.Errorf("KV store not initialized")
|
||||
}
|
||||
|
||||
b.logger.LogDebug("Getting channel mapping for XMPP room", "room_jid", roomID)
|
||||
|
||||
// Look up the channel ID using the room JID as the key
|
||||
channelIDBytes, err := b.kvstore.Get(kvstore.BuildChannelMapKey("xmpp", roomID))
|
||||
if err != nil {
|
||||
// No mapping found is not an error, just return empty string
|
||||
b.logger.LogDebug("No channel mapping found for room", "room_jid", roomID)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
channelID := string(channelIDBytes)
|
||||
b.logger.LogDebug("Found channel mapping for room", "room_jid", roomID, "channel_id", channelID)
|
||||
|
||||
return channelID, nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue