Skip to content

Commit

Permalink
Enhancements to privileged seamails (jocosocial#350)
Browse files Browse the repository at this point in the history
* highlight today in trunk for mods and tt

* dont allow privileged users to try and remove the privileged account from chats

* fixes jocosocial#318, doesnt allow muting of privileged chats

* checkpoint

* Revert "checkpoint"

This reverts commit f88af49.

* Revert "Revert "checkpoint""

This reverts commit 3cfebd5.

* fixes jocosocial#349, at least as best we can
  • Loading branch information
cohoe authored Jan 4, 2025
1 parent b14cab4 commit 80ff078
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 8 deletions.
14 changes: 13 additions & 1 deletion Sources/swiftarr/Controllers/FezController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,12 @@ struct FezController: APIRouteCollection {
guard fez.fezType != .closed else {
throw Abort(.badRequest, reason: "Cannot remove members to a closed chat")
}
// Don't allow privileged users looking at a privileged mailbox to attempt to remove
// the privileged user from a Chat. Without this check their removal action will silently
// be a no-op.
if cacheUser.accessLevel.hasAccess(.moderator), !fez.participantArray.contains(cacheUser.userID) {
throw Abort(.badRequest, reason: "Privileged users cannot leave a chat they are not part of themselves")
}
// Save a FezEditRecord containing the participant list before removal
let fezEdit = try FriendlyFezEdit(fez: fez, editorID: cacheUser.userID)
try await fezEdit.save(on: req.db)
Expand Down Expand Up @@ -488,7 +494,7 @@ struct FezController: APIRouteCollection {
if fez.fezType != .closed {
infoStr.append(" in \(fez.fezType.lfgLabel) \"\(fez.title)\".")
}
try await addNotifications(users: participantNotifyList, type: .chatUnreadMsg(fez.requireID(), fez.fezType), info: infoStr, on: req)
try await addNotifications(users: participantNotifyList, type: .chatUnreadMsg(fez.requireID(), fez.fezType), info: infoStr, creatorID: cacheUser.userID, on: req)
try await forwardPostToSockets(fez, post, on: req)
// A user posting is assumed to have read all prev posts. (even if this proves untrue, we should increment
// readCount as they've read the post they just wrote!)
Expand Down Expand Up @@ -906,6 +912,12 @@ struct FezController: APIRouteCollection {
guard !cacheUser.getBlocks().contains(fez.$owner.id) else {
throw Abort(.notFound, reason: "this \(fez.fezType.lfgLabel) is not available")
}
// Without this check Moderator A could mute a chat for all Moderators which
// doesn't feel super good. It's also a confusing UX and would require Help
// signage to work around. So we're just going to the option to do that.
guard effectiveUser.userID == cacheUser.userID else {
throw Abort(.badRequest, reason: "Privileged mailbox chats cannot be muted")
}
guard let fezParticipant = try await fez.$participants.$pivots.query(on: req.db)
.filter(\.$user.$id == effectiveUser.userID).first() else {
throw Abort(.forbidden, reason: "user is not a member of this fez")
Expand Down
20 changes: 14 additions & 6 deletions Sources/swiftarr/Protocols/APIRouteCollection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,13 @@ extension APIRouteCollection {
//
// The users array should be pre-filtered to include only those who can actually see the new content (that is, not
// blocking or muting it, or not in the group that receives the content).
// The only exception to this should be when determining whether to addNotifications for privileged
// users. Example: the list of all mods/twitarrteam are calculated deeper within this function.
// There are situations where it is inappropriate to generate a notification for the user that
// did something. In that situation, the creatorID can be used to avoid notifying one person.
//
// Adding a new notification will also send out an update to all relevant users who are listening on notification sockets.
func addNotifications(users: [UUID], type: NotificationType, info: String, on req: Request) async throws {
func addNotifications(users: [UUID], type: NotificationType, info: String, creatorID: UUID? = nil, on req: Request) async throws {
try await withThrowingTaskGroup(of: Void.self) { group -> Void in
var forwardToSockets = true
// Members of `users` get push notifications
Expand All @@ -172,7 +176,7 @@ extension APIRouteCollection {
stateChangeUsers = bookkeepUserAddedToChat(req: req, msgID: msgID, chatType: chatType, users: users, group: &group)

case .chatUnreadMsg(let msgID, let chatType):
stateChangeUsers = bookkeepNewChatMessage(req: req, msgID: msgID, chatType: chatType, users: users, group: &group)
stateChangeUsers = bookkeepNewChatMessage(req: req, msgID: msgID, chatType: chatType, users: users, group: &group, creatorID: creatorID)

case .nextFollowedEventTime(let date, let id):
for userID in users {
Expand Down Expand Up @@ -261,12 +265,15 @@ extension APIRouteCollection {
}

func bookkeepNewChatMessage(req: Request, msgID: UUID, chatType: FezType, users: [UUID],
group: inout ThrowingTaskGroup<Void, Error>) -> [UUID] {
group: inout ThrowingTaskGroup<Void, Error>, creatorID: UUID? = nil) -> [UUID] {
var updateCountsUsers = users
// For seamail msgs with "moderator" or "TwitarrTeam" in the memberlist, add all team members to the
// update counts list. This is so all team members have individual read counts.
//
// To avoid generating notifications for the user who created the new chat message,
// pass their ID in via the creatorID to omit them from generating the messages.
if let mod = req.userCache.getUser(username: "moderator"), users.contains(mod.userID) {
let modList = req.userCache.allUsersWithAccessLevel(.moderator).map { $0.userID }
let modList = req.userCache.allUsersWithAccessLevel(.moderator).filter({ $0.userID != creatorID }).map { $0.userID }
updateCountsUsers.append(contentsOf: modList)
for modUserID in modList {
group.addTask {
Expand All @@ -275,7 +282,7 @@ extension APIRouteCollection {
}
}
if let ttUser = req.userCache.getUser(username: "TwitarrTeam"), users.contains(ttUser.userID) {
let ttList = req.userCache.allUsersWithAccessLevel(.twitarrteam).map { $0.userID }
let ttList = req.userCache.allUsersWithAccessLevel(.twitarrteam).filter({ $0.userID != creatorID }).map { $0.userID }
updateCountsUsers.append(contentsOf: ttList)
for ttUserID in ttList {
group.addTask {
Expand All @@ -285,7 +292,8 @@ extension APIRouteCollection {
}
// Users who aren't "moderator" and are in the thread see it as a normal thread.
let inbox = Request.Redis.MailInbox.mailboxForChatType(type: chatType)
for userID in users {
let actualUsers = users.filter({ $0 != creatorID })
for userID in actualUsers {
group.addTask { try await req.redis.newUnreadMessage(chatID: msgID, userID: userID, inbox: inbox) }
}
return updateCountsUsers
Expand Down
4 changes: 4 additions & 0 deletions Sources/swiftarr/Resources/Views/Fez/seamailThread.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ <h6>Participants</h6>
</label>
</div>
</div>
<div class="row">
<div class="col text-end text-danger d-none" id="mute_errorDisplay">
Error muting chat: <span class="errortext"></span>
</div>
<div class="row mb-2 mx-0">
#for(participant in fez.members.participants):
<div class="col col-auto border">
Expand Down
2 changes: 1 addition & 1 deletion Sources/swiftarr/Resources/Views/trunk.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<div class="col-auto px-0">
<a class="nav-item nav-link link-light px-1
#if(trunk.tab == "home"): active#endif
#if(trunk.alertCounts.newAnnouncementCount > 0 || trunk.alertCounts.newPrivateEventMessageCount):
#if(trunk.alertCounts.newAnnouncementCount > 0 || trunk.alertCounts.newPrivateEventMessageCount || trunk.alertCounts.moderatorData.newModeratorSeamailMessageCount > 0 || trunk.alertCounts.moderatorData.newTTSeamailMessageCount > 0 || alertCounts.moderatorData.newModeratorForumMentionCount > 0 || alertCounts.moderatorData.newTTForumMentionCount > 0):
border-bottom border-danger
#endif"
href="/">
Expand Down

0 comments on commit 80ff078

Please sign in to comment.