Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Private Communities #5

Merged
merged 5 commits into from
Jul 23, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions 0005-private-communities.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
- Feature Name: Private Communities
- Start Date: 2024-02-15
- RFC PR: [LemmyNet/rfcs#0005](https://github.com/LemmyNet/rfcs/pull/5)
- Lemmy Issue: [LemmyNet/lemmy#187](https://github.com/LemmyNet/lemmy/issues/187)

# Summary

Lemmy is currently limited to public communities with public content. This RFC proposes a way to implement private, federated communities where only approved users can read and write.

# Motivation

Private spaces are a fundamental aspect of human communication. They allow discussing sensitive topics without interruption from outsiders. Supporting private communities in Lemmy gives a major new use case for talking among friends or within organizations. This use case is currently not covered by major Fediverse projects.

# Detailed design

The functionality requires only minor changes to the existing community implementation. The community profile with sidebar, icon etc remains public, so that anyone can follow the community. When a follow request is received, it needs to be manually approved by a moderator, using an interface similar to the existing registration applications. Posts and comments inside a private community are only visible to approved followers.

This change is in some ways similar to [Local-only communities](https://github.com/LemmyNet/lemmy/pull/4350). That PR can be used as a reference for parts of code which need to be changed.

## Database

The `CommunityVisibility` enum used in `community.visibility` gets a new variant `Private`. Queries in `post_view.rs` and `comment_view.rs` need to be adjusted so that content in communities set to private is only shown to followers. This also needs to be covered by unit tests.

There needs to be a new table to store follow requests:
```sql
CREATE TABLE community_follow_request (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding this table, we should just add a accepted_application to the community_follower table, or just not add any columns at all, and rely on the SubscribedType::pending for unapproved apps to private communities.

And I'd recommend that the new table is called community_application (this one's my fav), private_community_application, community_registration_application or something like that, and look almost like our existing registration_application table, with these columns:

  • Answer, person_id, moderator_id (optional until acted upon), deny_reason (optional string)

id serial PRIMARY KEY,
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL UNIQUE,
moderator_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE,
published timestamptz NOT NULL DEFAULT now()
);
```

## API

New follow requests for a private community need to be stored in `community_follow_request` table, and not in `community_follower`. This works very similar to the `registration_application` used for site registrations. Moderators can review and approve/reject applications with the following endpoints:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say it is alright for accepted_application to be in community_follower , just like accepted_application is in local_user

- `GET /api/v3/community/follow_request/count`
- `GET /api/v3/community/follow_request/list`
- `POST /api/v3/community/follow_request/approve`

The follow_request list for each item should contain a boolean value `is_new_instance`. This value should be true if the community has no followers from the user's instance yet, and allows showing a warning when content will be federated to a new server.

Once the approve endpoint is called, the follow request can be deleted. If the request was approved, write the follow relation to `community_follower`. If it was rejected, we could notify the user about this (optional).

Users who don't follow the private community need to be prevented from posting or making other actions in it. This can be done by adding a check in [check_community_user_action()](https://github.com/LemmyNet/lemmy/blob/main/crates/api_common/src/utils.rs#L171).

When [processing mentions](https://github.com/LemmyNet/lemmy/blob/main/crates/utils/src/utils/mention.rs#L24), the code needs to check if it is inside a private community, and in that case ignore mentions of users which don't follow it.

The new endpoints should be covered by [Typescript API tests](https://github.com/LemmyNet/lemmy/tree/main/api_tests).

## Federation

As described in the API section, follow requests for private communities are stored in `community_follow_request` instead of `community_follower`. Once a request is approved, send an `Accept` activity back to the user.

The `Group` actor for private communities remains public and can be fetched without restrictions, so that remote users can fetch it and follow it. It needs a new attribute to indicate that the group is not public. [ActivityStreams](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-group) doesn't seem to provide anything suitable, so we can simply add a custom attribute `private: true`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find anything usable either.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The micro-blogging part of the Fediverse uses manuallyApprovesFollowers property (boolean). It is not defined in AS vocabulary, and usually defined as {"manuallyApprovesFollowers": "as:manuallyApprovesFollowers"}. However, it is supported by most projects (including Mastodon).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, Ive changed it. Only thing is that it doesnt give any indication that content in the group is private.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps audience property can be used for that. as:Public would indicate a public or semi-private community.

Feels a bit awkward because audience is already used in FEP-1b12 objects.


With Activitypub federation there are two ways instances can communicate with each other: Fetch data via HTTP GET, or POST an activity to the inbox. Activities are only sent to approved followers, so we don't need to worry about leaking any private data. However the fetching needs adjustments. For this we can use authorized fetch, which is already [implemented behind a setting](https://github.com/LemmyNet/lemmy/blob/main/src/lib.rs#L182). The simplest solution here is to remove the setting and sign all all outgoing requests, so we don't have to track which specific objects require auth or not.

On the server side we need to verify the signature when an object gets fetched in a private community. The code for this is [here](https://github.com/LemmyNet/lemmy/blob/main/crates/apub/src/http/post.rs), [here](https://github.com/LemmyNet/lemmy/blob/main/crates/apub/src/http/comment.rs) and [here](https://github.com/LemmyNet/lemmy/blob/main/crates/apub/src/http/community.rs#L76). The logic for signature verification is [implemented in the activitypub-federation crate](https://github.com/LemmyNet/activitypub-federation-rust/blob/main/src/http_signatures.rs#L146), but not yet part of the public API.

Like in the API code we need to ensure that only followers can perform actions in private communities. Here a check needs to be added to [verify_person_in_community()](https://github.com/LemmyNet/lemmy/blob/main/crates/apub/src/activities/mod.rs#L77).

[Objects](https://github.com/LemmyNet/lemmy/blob/main/crates/apub/src/objects/post.rs#L127) and [activities](https://github.com/LemmyNet/lemmy/blob/main/crates/apub/src/activities/create_or_update/post.rs#L53) are currently marked as public, we need to get rid of this in private communities. Additionally, the method [verify_is_public()](https://github.com/LemmyNet/lemmy/blob/main/crates/apub/src/activities/mod.rs#L127) needs to take community visibility into account. If the community is public, reject any non-public content (just like now). if it is private, reject any public content.

## RSS

The community RSS feed needs to be disabled if the community is marked private. Other feed types need to exclude any content from private communities. This should already be handled by changes to `post_view.rs`
dessalines marked this conversation as resolved.
Show resolved Hide resolved

## Frontends and Apps

Community moderators need to get access to the new `community.visibility` setting in the sidebar. Community visibility should also be indicated to users.

Frontends need to show an interface for moderators to approve new followers. This can likely reuse much of the code from registration applications, and use the endpoints described under "API". When `is_new_instance` is true on a given application, show a warning similar to the following before approving:

> Warning: This is the first follower from example.com. After approval, the admin of example.com is technically able to access all past and future content in this community. It is also possible that the instance at example.com makes community posts publicly available. If the community has sensitive content, make sure to only approve followers from trusted instances.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good idea. Matrix has to deal with the same problem: as soon as 1 malicious instance gets access to the private room, they get all messages. Good to have warnings about that limitation.


# Alternatives

The only alternative would be not to implement this feature in Lemmy, and rely on different platforms for private communities instead. Considering that Lemmy already has a large number of users and many important features, it is better to provide this feature directly.

# Unresolved questions

Images in private communities are publicly available for anyone who knows the url. This is probably fine as image URLs are long and randomized, so they are impossible to guess.

When setting an existing community to private, this setting will generally also federate to other instances. However it is possible that some instances don't refetch the community, and old content remains public. And of course old content can be stored in external archives. This problem could be avoided by preventing existing, public communities to be marked as private.

In a public community, old content missing from your instance can be fetched by visiting the community on its home instance and copying the URL of individual posts and comments into your own instance's search field to fetch them. This is not easily possible with a private community, as it cannot be viewed publicly. However this is a general problem with Lemmy communities and can be handled separately.