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

nip29: support for unmanaged groups, top-level relay-local groups and invite codes #1496

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
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
148 changes: 111 additions & 37 deletions 29.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ Relays are supposed to generate the events that describe group metadata and grou

A group may be identified by a string in the format `<host>'<group-id>`. For example, a group with _id_ `abcdef` hosted at the relay `wss://groups.nostr.com` would be identified by the string `groups.nostr.com'abcdef`.

Group identifiers must be strings restricted to the characters `a-z0-9-_`.

When encountering just the `<host>` without the `'<group-id>`, clients can choose to connect to the group with id `_`, which is a special top-level group dedicated to relay-local discussions.

Copy link
Contributor

Choose a reason for hiding this comment

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

here we can add UUID recommendations and why relays may reject some id.

Also, above we defined the id's. here we defined a way to find a group using id and host, combined. which I believe MUST be replaced with names in future.

Copy link
Contributor

Choose a reason for hiding this comment

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

Then we can close #1493.

Copy link
Member Author

Choose a reason for hiding this comment

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

UUID is excessively big and unnecessary, I don't think we should recommend it although some people may want to use it.

Copy link
Contributor

Choose a reason for hiding this comment

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

We do not plan to use id's for users at all. also on the other hand for example 0xchat is generating very big id's larger than UUIDs.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think that is a bug in 0xchat then, and also relays should block giant ids.

Group identifiers in most cases should be random or pseudo-random, as that mitigates message replay confusiong and ensures they can be migrated or forked to other relays easily without risking conflicting with other groups using the same id in these new relays. This isn't a hard rule, as, for example, in `unmanaged` and/or ephemeral relays groups might not want to migrate ever, so they might not care about this. Notably, the `_` relay-local group isn't expected to be migrated ever.

## The `h` tag

Events sent by users to groups (chat messages, text notes, moderation events etc) must have an `h` tag with the value set to the group _id_.
Expand All @@ -36,8 +42,30 @@ This is a hack to prevent messages from being broadcasted to external relays tha

Relays should prevent late publication (messages published now with a timestamp from days or even hours ago) unless they are open to receive a group forked or moved from another relay.

## Group management

Groups can have any number of users with elevated access. These users are identified by role labels which are arbitrarily defined by the relays (see also the description of `kind:39003`). What each role is capable of not defined in this NIP either, it's a relay policy that can vary. Roles can be assigned by other users (as long as they have the capability to add roles) by publishing a `kind:9000` event with that user's pubkey in a `p` tag and the roles afterwards (even if the user is already a group member a `kind:9000` can be issued and the user roles must just be updated).

The roles supported by the group as to having some special privilege assigned to them should be accessible on the event `kind:39003`, but the relay may also accept other role names, arbitrarily defined by clients, and just not do anything with them.

Users with any roles that have any privilege can be considered _admins_ in a broad sense and be returned in the `kind:39001` event for a group.

## Unmanaged groups

Unmanaged groups are impromptu groups that can be used in any public relay unaware of NIP-29 specifics. They piggyback on relays' natural white/blacklists (or lack of) but aside from that are not actively managed and won't have any admins, group state or metadata events.

In `unmanaged` groups, everybody is considered to be a member.

Unmanaged groups can transition to managed groups, in that case the relay master key just has to publish moderation events setting the state of all groups and start enforcing the rules they choose to.

## Event definitions

These are the events expected to be found in NIP-29 groups.

### Normal user-created events

These events generally can be sent by all members of a group and they require the `h` tag to be present so they're attached to a specific group.

- *text root note* (`kind:11`)

This is the basic unit of a "microblog" root text note sent to a group.
Expand Down Expand Up @@ -79,6 +107,14 @@ Similar to `kind:12`, this is the basic unit of a chat message sent to a group.

`kind:10` SHOULD use NIP-10 markers, just like `kind:12`.

- other events:

Groups may also accept other events, like long-form articles, calendar, livestream, market announcements and so on. These should be as defined in their respective NIPs, with the addition of the `h` tag.

### User-related group management events

These are events that can be sent my user to manage their situation in a group, they also require the `h` tag.

Choose a reason for hiding this comment

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

small typo 'sent my user' => 'sent by user'


- *join request* (`kind:9021`)

Any user can send one of these events to the relay in order to be automatically or manually added to the group. If the group is `open` the relay will automatically issue a `kind:9000` in response adding this user. Otherwise group admins may choose to query for these requests and act upon them.
Expand All @@ -88,11 +124,14 @@ Any user can send one of these events to the relay in order to be automatically
"kind": 9021,
"content": "optional reason",
"tags": [
["h", "<group-id>"]
["h", "<group-id>"],
["code", "<optional-invite-code>"]
]
}
```

The optional `code` tag may be used by the relay to preauthorize acceptances in `closed` groups, together with the `kind:9009` `create-invite` moderation event.

- *leave request* (`kind:9022`)

Any user can send one of these events to the relay in order to be automatically removed from the group. The relay will automatically issue a `kind:9001` in response removing this user.
Expand All @@ -107,9 +146,13 @@ Any user can send one of these events to the relay in order to be automatically
}
```

### Group state -- or moderation

These are events expected to be sent by the relay master key or by group admins -- and relays should reject them if they don't come from an authorized admin. They also require the `h` tag.

- *moderation events* (`kinds:9000-9020`) (optional)

Clients can send these events to a relay in order to accomplish a moderation action. Relays must check if the pubkey sending the event is capable of performing the given action. The relay may discard the event after taking action or keep it as a moderation log.
Clients can send these events to a relay in order to accomplish a moderation action. Relays must check if the pubkey sending the event is capable of performing the given action based on its role and the relay's internal policy (see also the description of `kind:39003`).

```json
{
Expand All @@ -124,24 +167,29 @@ Clients can send these events to a relay in order to accomplish a moderation act

Each moderation action uses a different kind and requires different arguments, which are given as tags. These are defined in the following table:

| kind | name | tags |
| --- | --- | --- |
| 9000 | `add-user` | `p` (pubkey hex) |
| 9001 | `remove-user` | `p` (pubkey hex) |
| 9002 | `edit-metadata` | `name`, `about`, `picture` (string) |
| 9003 | `add-permission` | `p` (pubkey), `permission` (name) |
| 9004 | `remove-permission` | `p` (pubkey), `permission` (name) |
| 9005 | `delete-event` | `e` (id hex) |
| 9006 | `edit-group-status` | `public` or `private`, `open` or `closed` |
| 9007 | `create-group` | |
| 9008 | `delete-group` | |
| kind | name | tags |
| --- | --- | --- |
| 9000 | `add-user` | `p` with pubkey hex and optional roles |
| 9001 | `remove-user` | `p` with pubkey hex |
| 9002 | `edit-metadata` | fields from `kind:39000` to be modified |
| 9005 | `delete-event` | |
| 9007 | `create-group` | |
| 9008 | `delete-group` | |

It's expected that the group state (of who is an allowed member or not, who is an admin and with which permission or not, what are the group name and picture etc) can be fully reconstructed from the canonical sequence of these events.

### Group metadata events

These events contain the group id in a `d` tag instead of the `h` tag. They MUST be created by the relay master key only and a single instance of each (or none) should exist at all times for each group. They are merely informative but should reflect the latest group state (as it was changed by moderation events over time).

- *group metadata* (`kind:39000`) (optional)

This event defines the metadata for the group -- basically how clients should display it. It must be generated and signed by the relay in which is found. Relays shouldn't accept these events if they're signed by anyone else.

If the group is forked and hosted in multiple relays, there will be multiple versions of this event in each different relay and so on.

When this event is not found, clients may still connect to the group, but treat it as having a different status, `unmanaged`,

```jsonc
{
"kind": 39000,
Expand All @@ -162,41 +210,29 @@ If the group is forked and hosted in multiple relays, there will be multiple ver

- *group admins* (`kind:39001`) (optional)

Similar to the group metadata, this event is supposed to be generated by relays that host the group.

Each admin gets a label that is only used for display purposes, and a list of permissions it has are listed afterwards. These permissions can inform client building UI, but ultimately are evaluated by the relay in order to become effective.
Each admin is listed along with one or more roles. These roles SHOULD have a correspondence with the roles supported by the relay, as advertised by the `kind:39003` event.

The list of capabilities, as defined by this NIP, for now, is the following:

- `add-user`
- `edit-metadata`
- `delete-event`
- `remove-user`
- `add-permission`
- `remove-permission`
- `edit-group-status`
- `delete-group`

```json
```jsonc
{
"kind": 39001,
"content": "list of admins for the pizza lovers group",
"tags": [
["d", "<group-id>"],
["p", "<pubkey1-as-hex>", "ceo", "add-user", "edit-metadata", "delete-event", "remove-user"],
["p", "<pubkey2-as-hex>", "secretary", "add-user", "delete-event"]
]
["p", "<pubkey1-as-hex>", "ceo"],
Copy link
Contributor

Choose a reason for hiding this comment

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

how can we set admin permissions for each role or admin ?

Copy link

@ismyhc ismyhc Oct 21, 2024

Choose a reason for hiding this comment

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

Roles are defined at the relay level in my implementation. For instance there are 3 roles.

owner
admin
moderator

There is a kind 39003 that will describe the roles and what permissions they have. That can be used on the client side to understand what an "admin" can do, etc.

Now right now, my relay only supports 3 roles and every group gets those three roles, but at some point maybe we could have roles that could be chosen by the group creator. I don't want to go down this road now though.

On the relay side those roles are enforced on what they can do. For example.

An admin can add-user with role no "higher" than itself. An admin can remove a user with role no higher than itself, etc.

A 39003 might look something like this.

One thing that's not in this PR that I've added is after the role description, I list out the "permissions" that the role has ability to perform. I may or may keep this.

{
  "content": "",
  "created_at": 1728676272,
  "id": "121489f55f3e59db7d60751604a1ba76d3896ea4285fa4c6873ee3d59ebe4523",
  "kind": 39003,
  "pubkey": "000009056fd123b6a2368f532ec707f9b368c0d1fed62f68be0baeceec776274",
  "sig": "a3088ef308a4a6ac73d7144692828001d769652d112cf13e8e724d161c55cfe680f0a883735c23bb87966bb76c3fe9c3d481a9cf3d98ffc4a52aa0efcf442ad7",
  "tags": [
    [
      "d",
      "01J9YJ56G6RHSTANHPA2WASDSD"
    ],
    [
      "owner",
      "Owner",
      "The owner role is like god mode for the group",
      "add-user",
      "edit-metadata",
      "delete-event",
      "remove-user",
      "delete-group"
    ],
    [
      "admin",
      "Admin",
      "The admin role can do everything except remove the owner, delete the group and change owner role",
      "add-user",
      "edit-metadata",
      "delete-event",
      "remove-user"
    ],
    [
      "moderator",
      "Moderator",
      "The moderator role can do everything except remove the owner and admin, delete the group, edit metadata and change roles",
      "add-user",
      "delete-event",
      "remove-user"
    ]
  ]
}

A client could use this to determine what users could do. Again remember, the roles/permissions are enforced on the relay.

Copy link
Contributor

Choose a reason for hiding this comment

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

One thing that's not in this PR...

true. that was my point. @fiatjaf need to add permissions somewhere.

["p", "<pubkey2-as-hex>", "secretary", "gardener"],
// other pubkeys...
],
// other fields...
}
```

- *group members* (`kind:39002`) (optional)

Similar to *group admins*, this event is supposed to be generated by relays that host the group.
It's a list of pubkeys that are members of the group. Relays might choose to not to publish this information, to restrict what pubkeys can fetch it or to only display a subset of the members in it.

It's a NIP-51-like list of pubkeys that are members of the group. Relays might choose to not to publish this information or to restrict what pubkeys can fetch it.
Clients should not assume this will always be present or that it will contain a full list of members.

```json
```jsonc
{
"kind": 39002,
"content": "list of members for the pizza lovers group",
Expand All @@ -205,10 +241,48 @@ It's a NIP-51-like list of pubkeys that are members of the group. Relays might c
["p", "<admin1>"],
["p", "<member-pubkey1>"],
["p", "<member-pubkey2>"],
]
// other pubkeys...
],
// other fields...
}
```

- *group roles* (`kind:39003`) (optional)

This is an event that MAY be published by the relay informing users and clients about what are the roles supported by this relay according to its internal logic.

For example, a relay may choose to support the roles `"admin"` and `"moderator"`, in which the `"admin"` will be allowed to edit the group metadata, delete messages and remove users from the group, while the `"moderator"` can only delete messages (or the relay may choose to call these roles `"ceo"` and `"secretary"` instead, the exact role name is not relevant).

The process through which the relay decides what roles to support and how to handle moderation events internally based on them is specific to each relay and not specified here.

```jsonc
{
"kind": 39003,
"content": "list of roles supported by this group",
"tags": [
["d", "<group-id>"],
["role", "<role-name>", "<optional-description>"],
["role", "<role-name>", "<optional-description>"],
// other roles...
],
// other fields...
}
```

## Storing the list of groups a user belongs to
## Implementation quirks

### Checking your own membership in a group

The latest of either `kind:9000` or `kind:9001` events present in a group should tell a user that they are currently members of the group or if they were removed. In case none of these exist the user is assumed to not be a member of the group -- unless the group is `unmanaged`, in which case the user is assumed to be a member.

### Adding yourself to a group

When a group is `open`, anyone can send a `kind:9021` event to it in order to be added, then expect a `kind:9000` event to be emitted confirming that the user was added. The same happens with `closed` groups, except in that case a user may only send a `kind:9021` if it has an invite code.

### Storing your list of groups

A definition for `kind:10009` was included in [NIP-51](51.md) that allows clients to store the list of groups a user wants to remember being in.

### Using `unmanaged` relays

A definition for kind `10009` was included in [NIP-51](51.md) that allows clients to store the list of groups a user wants to remember being in.
To prevent event leakage, replay and confusion, when using `unmanaged` relays, clients should include the [NIP-70](70.md) `-` tag, as just the `previous` tag won't be checked by other `unmanaged` relays.