func (b *Backend) join(ctx scope.Context, rb *RoomBinding, session proto.Session) (string, error) { client := &proto.Client{} if !client.FromContext(ctx) { return "", fmt.Errorf("client data not found in scope") } bannedAgentCols, err := allColumns(b.DbMap, BannedAgent{}, "") if err != nil { return "", err } bannedIPCols, err := allColumns(b.DbMap, BannedIP{}, "") if err != nil { return "", err } t, err := b.DbMap.Begin() if err != nil { return "", err } // Check for agent ID bans. agentBans, err := t.Select( BannedAgent{}, fmt.Sprintf( "SELECT %s FROM banned_agent WHERE agent_id = $1 AND (room IS NULL OR room = $2) AND (expires IS NULL OR expires > NOW())", bannedAgentCols), session.Identity().ID().String(), rb.RoomName) if err != nil { rollback(ctx, t) return "", err } if len(agentBans) > 0 { logging.Logger(ctx).Printf("access denied to %s: %#v", session.Identity().ID(), agentBans) if err := t.Rollback(); err != nil { return "", err } return "", proto.ErrAccessDenied } // Check for IP bans. ipBans, err := t.Select( BannedIP{}, fmt.Sprintf( "SELECT %s FROM banned_ip WHERE ip = $1 AND (room IS NULL OR room = $2) AND (expires IS NULL OR expires > NOW())", bannedIPCols), client.IP, rb.RoomName) if err != nil { rollback(ctx, t) return "", err } if len(ipBans) > 0 { logging.Logger(ctx).Printf("access denied to %s: %#v", client.IP, ipBans) if err := t.Rollback(); err != nil { return "", err } return "", proto.ErrAccessDenied } // Virtualize the session's client address. var row struct { Address string `db:"address"` } if err := t.SelectOne(&row, "SELECT virtualize_address($1, $2::inet) AS address", rb.RoomName, client.IP); err != nil { return "", err } virtualAddress := row.Address // Look up session's nick. nickRow, err := t.Get(Nick{}, string(session.Identity().ID()), rb.RoomName) if err != nil && err != sql.ErrNoRows { return "", err } if nickRow != nil { session.SetName(nickRow.(*Nick).Nick) } // Write to session log. // TODO: do proper upsert simulation entry := &SessionLog{ SessionID: session.ID(), IP: client.IP, Room: rb.RoomName, UserAgent: client.UserAgent, Connected: client.Connected, } if _, err := t.Delete(entry); err != nil { if rerr := t.Rollback(); rerr != nil { logging.Logger(ctx).Printf("rollback error: %s", rerr) } return "", err } if err := t.Insert(entry); err != nil { if rerr := t.Rollback(); rerr != nil { logging.Logger(ctx).Printf("rollback error: %s", rerr) } return "", err } // Broadcast a presence event. // TODO: make this an explicit action via the Room protocol, to support encryption presence := &Presence{ Room: rb.RoomName, ServerID: b.desc.ID, ServerEra: b.desc.Era, SessionID: session.ID(), Updated: time.Now(), } sessionView := session.View(proto.Staff) sessionView.ClientAddress = virtualAddress err = presence.SetFact(&proto.Presence{ SessionView: sessionView, LastInteracted: presence.Updated, }) if err != nil { rollback(ctx, t) return "", fmt.Errorf("presence marshal error: %s", err) } if err := t.Insert(presence); err != nil { return "", fmt.Errorf("presence insert error: %s", err) } b.Lock() // Add session to listeners. lm, ok := b.listeners[rb.RoomName] if !ok { lm = ListenerMap{} b.listeners[rb.RoomName] = lm } lm[session.ID()] = Listener{Session: session, Client: client} b.Unlock() event := proto.PresenceEvent(session.View(proto.Staff)) event.ClientAddress = virtualAddress if err := rb.broadcast(ctx, t, proto.JoinEventType, event, session); err != nil { rollback(ctx, t) return "", err } if err := t.Commit(); err != nil { return "", err } return virtualAddress, nil }