diff --git a/crates/ruma-identity-service-api/CHANGELOG.md b/crates/ruma-identity-service-api/CHANGELOG.md index 1283ca12..850b33da 100644 --- a/crates/ruma-identity-service-api/CHANGELOG.md +++ b/crates/ruma-identity-service-api/CHANGELOG.md @@ -15,7 +15,10 @@ Improvements: check_3pid_validity::v2, bind_3pid::v2, }, - invitation::sign_invitation_ed25519::v2 + invitation::{ + sign_invitation_ed25519::v2, + store_invitation::v2, + } ``` # 0.1.0 diff --git a/crates/ruma-identity-service-api/src/invitation.rs b/crates/ruma-identity-service-api/src/invitation.rs index db034d65..9fae2d7e 100644 --- a/crates/ruma-identity-service-api/src/invitation.rs +++ b/crates/ruma-identity-service-api/src/invitation.rs @@ -1,2 +1,4 @@ -//! Endpoints for ephemeral invitation signing. +//! Endpoints to store and sign invitations. + pub mod sign_invitation_ed25519; +pub mod store_invitation; diff --git a/crates/ruma-identity-service-api/src/invitation/store_invitation.rs b/crates/ruma-identity-service-api/src/invitation/store_invitation.rs new file mode 100644 index 00000000..56b71da3 --- /dev/null +++ b/crates/ruma-identity-service-api/src/invitation/store_invitation.rs @@ -0,0 +1,3 @@ +//! Endpoint to store pending invitations to a user's 3PID. + +pub mod v2; diff --git a/crates/ruma-identity-service-api/src/invitation/store_invitation/v2.rs b/crates/ruma-identity-service-api/src/invitation/store_invitation/v2.rs new file mode 100644 index 00000000..35fb68e6 --- /dev/null +++ b/crates/ruma-identity-service-api/src/invitation/store_invitation/v2.rs @@ -0,0 +1,149 @@ +//! [POST /_matrix/identity/v2/store-invite](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-store-invite) + +use ruma_api::ruma_api; +use ruma_common::thirdparty::Medium; +use ruma_identifiers::{MxcUri, RoomAliasId, RoomId, UserId}; +use serde::{ser::SerializeSeq, Deserialize, Serialize}; + +ruma_api! { + metadata: { + description: "Store pending invitations to a user's 3PID.", + method: POST, + name: "store_invitation", + path: "/_matrix/identity/v2/store-invite", + authentication: AccessToken, + rate_limited: false, + } + + request: { + /// The type of the third party identifier for the invited user. + /// + /// Currently, only `Medium::Email` is supported. + pub medium: &'a Medium, + + /// The email address of the invited user. + pub address: &'a str, + + /// The Matrix room ID to which the user is invited. + pub room_id: &'a RoomId, + + /// The Matrix user ID of the inviting user. + pub sender: &'a UserId, + + /// The Matrix room alias for the room to which the user is invited. + /// + /// This should be retrieved from the `m.room.canonical` state event. + #[serde(skip_serializing_if = "Option::is_none")] + pub room_alias: Option<&'a RoomAliasId>, + + /// The Content URI for the room to which the user is invited. + /// + /// This should be retrieved from the `m.room.avatar` state event. + #[serde(skip_serializing_if = "Option::is_none")] + pub room_avatar_url: Option<&'a MxcUri>, + + /// The `join_rule` for the room to which the user is invited. + /// + /// This should be retrieved from the `m.room.join_rules` state event. + #[serde(skip_serializing_if = "Option::is_none")] + pub room_join_rules: Option<&'a str>, + + /// The name of the room to which the user is invited. + /// + /// This should be retrieved from the `m.room.name` state event. + #[serde(skip_serializing_if = "Option::is_none")] + pub room_name: Option<&'a str>, + + /// The display name of the user ID initiating the invite. + #[serde(skip_serializing_if = "Option::is_none")] + pub sender_display_name: Option<&'a str>, + + /// The Content URI for the avater of the user ID initiating the invite. + #[serde(skip_serializing_if = "Option::is_none")] + pub sender_avatar_url: Option<&'a MxcUri>, + } + + response: { + /// The generated token. Must be a string consisting of the characters `[0-9a-zA-Z.=_-]`. + /// + /// Its length must not exceed 255 characters and it must not be empty. + pub token: String, + + /// A list of [server's long-term public key, generated ephemeral public key]. + pub public_keys: PublicKeys, + + /// The generated (redacted) display_name. An example is `f...@b...`. + pub display_name: String, + } +} + +impl<'a> Request<'a> { + /// Creates a new `Request with the given medium, email address, room ID and sender. + pub fn new( + medium: &'a Medium, + address: &'a str, + room_id: &'a RoomId, + sender: &'a UserId, + ) -> Self { + Self { + medium, + address, + room_id, + sender, + room_alias: None, + room_avatar_url: None, + room_join_rules: None, + room_name: None, + sender_display_name: None, + sender_avatar_url: None, + } + } + + /// Creates a new `Request` with the given email address, room ID and sender. + pub fn email(address: &'a str, room_id: &'a RoomId, sender: &'a UserId) -> Self { + Self::new(&Medium::Email, address, room_id, sender) + } +} + +impl Response { + /// Creates a new `Response` with the given token, public keys and display name. + pub fn new(token: String, public_keys: PublicKeys, display_name: String) -> Self { + Self { token, public_keys, display_name } + } +} + +/// The server's long-term public key and generated ephemeral public key. +#[derive(Debug, Clone)] +#[allow(clippy::exhaustive_structs)] +pub struct PublicKeys { + /// The server's long-term public key. + pub server_key: String, + + /// The generated ephemeral public key. + pub ephemeral_key: String, +} + +impl<'de> Deserialize<'de> for PublicKeys { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let [server_key, ephemeral_key] = <[String; 2]>::deserialize(deserializer)?; + + Ok(Self { server_key, ephemeral_key }) + } +} + +impl Serialize for PublicKeys { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut seq = serializer.serialize_seq(Some(2))?; + + seq.serialize_element(&self.server_key)?; + seq.serialize_element(&self.ephemeral_key)?; + + seq.end() + } +}