owncast/core/chat/events.go
Gabe Kangas b835de2dc4
IndieAuth support (#1811)
* Able to authenticate user against IndieAuth. For #1273

* WIP server indieauth endpoint. For https://github.com/owncast/owncast/issues/1272

* Add migration to remove access tokens from user

* Add authenticated bool to user for display purposes

* Add indieauth modal and auth flair to display names. For #1273

* Validate URLs and display errors

* Renames, cleanups

* Handle relative auth endpoint paths. Add error handling for missing redirects.

* Disallow using display names in use by registered users. Closes #1810

* Verify code verifier via code challenge on callback

* Use relative path to authorization_endpoint

* Post-rebase fixes

* Use a timestamp instead of a bool for authenticated

* Propertly handle and display error in modal

* Use auth'ed timestamp to derive authenticated flag to display in chat

* don't redirect unless a URL is present

avoids redirecting to `undefined` if there was an error

* improve error message if owncast server URL isn't set

* fix IndieAuth PKCE implementation

use SHA256 instead of SHA1, generates a longer code verifier (must be 43-128 chars long), fixes URL-safe SHA256 encoding

* return real profile data for IndieAuth response

* check the code verifier in the IndieAuth server

* Linting

* Add new chat settings modal anad split up indieauth ui

* Remove logging error

* Update the IndieAuth modal UI. For #1273

* Add IndieAuth repsonse error checking

* Disable IndieAuth client if server URL is not set.

* Add explicit error messages for specific error types

* Fix bad logic

* Return OAuth-keyed error responses for indieauth server

* Display IndieAuth error in plain text with link to return to main page

* Remove redundant check

* Add additional detail to error

* Hide IndieAuth details behind disclosure details

* Break out migration into two steps because some people have been runing dev in production

* Add auth option to user dropdown

Co-authored-by: Aaron Parecki <aaron@parecki.com>
2022-04-21 14:55:26 -07:00

135 lines
3.8 KiB
Go

package chat
import (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/core/user"
"github.com/owncast/owncast/core/webhooks"
log "github.com/sirupsen/logrus"
)
func (s *Server) userNameChanged(eventData chatClientEvent) {
var receivedEvent events.NameChangeEvent
if err := json.Unmarshal(eventData.data, &receivedEvent); err != nil {
log.Errorln("error unmarshalling to NameChangeEvent", err)
return
}
proposedUsername := receivedEvent.NewName
// Check if name is on the blocklist
blocklist := data.GetForbiddenUsernameList()
for _, blockedName := range blocklist {
normalizedName := strings.TrimSpace(blockedName)
normalizedName = strings.ToLower(normalizedName)
if strings.Contains(normalizedName, proposedUsername) {
// Denied.
log.Debugln(eventData.client.User.DisplayName, "blocked from changing name to", proposedUsername, "due to blocked name", normalizedName)
message := fmt.Sprintf("You cannot change your name to **%s**.", proposedUsername)
s.sendActionToClient(eventData.client, message)
// Resend the client's user so their username is in sync.
eventData.client.sendConnectedClientInfo()
return
}
}
// Check if the name is not already assigned to a registered user.
if available, err := user.IsDisplayNameAvailable(proposedUsername); err != nil {
log.Errorln("error checking if name is available", err)
return
} else if !available {
message := fmt.Sprintf("You cannot change your name to **%s**, it is already in use.", proposedUsername)
s.sendActionToClient(eventData.client, message)
// Resend the client's user so their username is in sync.
eventData.client.sendConnectedClientInfo()
return
}
savedUser := user.GetUserByToken(eventData.client.accessToken)
oldName := savedUser.DisplayName
// Save the new name
if err := user.ChangeUsername(eventData.client.User.ID, receivedEvent.NewName); err != nil {
log.Errorln("error changing username", err)
}
// Update the connected clients associated user with the new name
now := time.Now()
eventData.client.User = savedUser
eventData.client.User.NameChangedAt = &now
// Send chat event letting everyone about about the name change
savedUser.DisplayName = receivedEvent.NewName
broadcastEvent := events.NameChangeBroadcast{
Oldname: oldName,
}
broadcastEvent.User = savedUser
broadcastEvent.SetDefaults()
payload := broadcastEvent.GetBroadcastPayload()
if err := s.Broadcast(payload); err != nil {
log.Errorln("error broadcasting NameChangeEvent", err)
return
}
// Send chat user name changed webhook
receivedEvent.User = savedUser
receivedEvent.ClientID = eventData.client.id
webhooks.SendChatEventUsernameChanged(receivedEvent)
}
func (s *Server) userMessageSent(eventData chatClientEvent) {
var event events.UserMessageEvent
if err := json.Unmarshal(eventData.data, &event); err != nil {
log.Errorln("error unmarshalling to UserMessageEvent", err)
return
}
event.SetDefaults()
event.ClientID = eventData.client.id
// Ignore empty messages
if event.Empty() {
return
}
// Ignore if the stream has been offline
if !getStatus().Online && getStatus().LastDisconnectTime != nil {
disconnectedTime := getStatus().LastDisconnectTime.Time
if time.Since(disconnectedTime) > 5*time.Minute {
return
}
}
event.User = user.GetUserByToken(eventData.client.accessToken)
// Guard against nil users
if event.User == nil {
return
}
payload := event.GetBroadcastPayload()
if err := s.Broadcast(payload); err != nil {
log.Errorln("error broadcasting UserMessageEvent payload", err)
return
}
// Send chat message sent webhook
webhooks.SendChatEvent(&event)
chatMessagesSentCounter.Inc()
SaveUserMessage(event)
eventData.client.MessageCount++
_lastSeenCache[event.User.ID] = time.Now()
}