From f8d745d308f7a101e07a977d945a30dc8f8a55c2 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 22 Nov 2023 18:25:47 +0100 Subject: [PATCH] Add a GraphQL mutation to allow cross-signing reset --- crates/graphql/src/mutations/user.rs | 62 ++++++++++++++++++++++++++++ frontend/schema.graphql | 26 ++++++++++++ frontend/src/gql/graphql.ts | 20 +++++++++ frontend/src/gql/schema.ts | 39 +++++++++++++++++ 4 files changed, 147 insertions(+) diff --git a/crates/graphql/src/mutations/user.rs b/crates/graphql/src/mutations/user.rs index ecd61e80d..b469cad45 100644 --- a/crates/graphql/src/mutations/user.rs +++ b/crates/graphql/src/mutations/user.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use anyhow::Context as _; use async_graphql::{Context, Description, Enum, InputObject, Object, ID}; use mas_storage::{ job::{DeactivateUserJob, JobRepositoryExt, ProvisionUserJob}, @@ -22,6 +23,7 @@ use tracing::info; use crate::{ model::{NodeType, User}, state::ContextExt, + UserId, }; #[derive(Default)] @@ -157,6 +159,34 @@ impl SetCanRequestAdminPayload { } } +/// The input for the `allowUserCrossSigningReset` mutation. +#[derive(InputObject)] +struct AllowUserCrossSigningResetInput { + /// The ID of the user to update. + user_id: ID, +} + +/// The payload for the `allowUserCrossSigningReset` mutation. +#[derive(Description)] +enum AllowUserCrossSigningResetPayload { + /// The user was updated. + Allowed(mas_data_model::User), + + /// The user was not found. + NotFound, +} + +#[Object(use_type_description)] +impl AllowUserCrossSigningResetPayload { + /// The user that was updated. + async fn user(&self) -> Option { + match self { + Self::Allowed(user) => Some(User(user.clone())), + Self::NotFound => None, + } + } +} + fn valid_username_character(c: char) -> bool { c.is_ascii_lowercase() || c.is_ascii_digit() @@ -296,4 +326,36 @@ impl UserMutations { Ok(SetCanRequestAdminPayload::Updated(user)) } + + /// Temporarily allow user to reset their cross-signing keys. + async fn allow_user_cross_signing_reset( + &self, + ctx: &Context<'_>, + input: AllowUserCrossSigningResetInput, + ) -> Result { + let state = ctx.state(); + let user_id = NodeType::User.extract_ulid(&input.user_id)?; + let requester = ctx.requester(); + + if !requester.is_owner_or_admin(&UserId(user_id)) { + return Err(async_graphql::Error::new("Unauthorized")); + } + + let mut repo = state.repository().await?; + let user = repo.user().lookup(user_id).await?; + repo.cancel().await?; + + let Some(user) = user else { + return Ok(AllowUserCrossSigningResetPayload::NotFound); + }; + + let conn = state.homeserver_connection(); + let mxid = conn.mxid(&user.username); + + conn.allow_cross_signing_reset(&mxid) + .await + .context("Failed to allow cross-signing reset")?; + + Ok(AllowUserCrossSigningResetPayload::Allowed(user)) + } } diff --git a/frontend/schema.graphql b/frontend/schema.graphql index 7c3af5479..39c9ab63a 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -106,6 +106,26 @@ enum AddUserStatus { INVALID } +""" +The input for the `allowUserCrossSigningReset` mutation. +""" +input AllowUserCrossSigningResetInput { + """ + The ID of the user to update. + """ + userId: ID! +} + +""" +The payload for the `allowUserCrossSigningReset` mutation. +""" +type AllowUserCrossSigningResetPayload { + """ + The user that was updated. + """ + user: User +} + type Anonymous implements Node { id: ID! } @@ -650,6 +670,12 @@ type Mutation { input: SetCanRequestAdminInput! ): SetCanRequestAdminPayload! """ + Temporarily allow user to reset their cross-signing keys. + """ + allowUserCrossSigningReset( + input: AllowUserCrossSigningResetInput! + ): AllowUserCrossSigningResetPayload! + """ Create a new arbitrary OAuth 2.0 Session. Only available for administrators. diff --git a/frontend/src/gql/graphql.ts b/frontend/src/gql/graphql.ts index 54b380041..c061a8525 100644 --- a/frontend/src/gql/graphql.ts +++ b/frontend/src/gql/graphql.ts @@ -99,6 +99,19 @@ export enum AddUserStatus { Invalid = "INVALID", } +/** The input for the `allowUserCrossSigningReset` mutation. */ +export type AllowUserCrossSigningResetInput = { + /** The ID of the user to update. */ + userId: Scalars["ID"]["input"]; +}; + +/** The payload for the `allowUserCrossSigningReset` mutation. */ +export type AllowUserCrossSigningResetPayload = { + __typename?: "AllowUserCrossSigningResetPayload"; + /** The user that was updated. */ + user?: Maybe; +}; + export type Anonymous = Node & { __typename?: "Anonymous"; id: Scalars["ID"]["output"]; @@ -421,6 +434,8 @@ export type Mutation = { addEmail: AddEmailPayload; /** Add a user. This is only available to administrators. */ addUser: AddUserPayload; + /** Temporarily allow user to reset their cross-signing keys. */ + allowUserCrossSigningReset: AllowUserCrossSigningResetPayload; /** * Create a new arbitrary OAuth 2.0 Session. * @@ -459,6 +474,11 @@ export type MutationAddUserArgs = { input: AddUserInput; }; +/** The mutations root of the GraphQL interface. */ +export type MutationAllowUserCrossSigningResetArgs = { + input: AllowUserCrossSigningResetInput; +}; + /** The mutations root of the GraphQL interface. */ export type MutationCreateOauth2SessionArgs = { input: CreateOAuth2SessionInput; diff --git a/frontend/src/gql/schema.ts b/frontend/src/gql/schema.ts index 697801051..41f805550 100644 --- a/frontend/src/gql/schema.ts +++ b/frontend/src/gql/schema.ts @@ -86,6 +86,22 @@ export default { ], interfaces: [], }, + { + kind: "OBJECT", + name: "AllowUserCrossSigningResetPayload", + fields: [ + { + name: "user", + type: { + kind: "OBJECT", + name: "User", + ofType: null, + }, + args: [], + }, + ], + interfaces: [], + }, { kind: "OBJECT", name: "Anonymous", @@ -1100,6 +1116,29 @@ export default { }, ], }, + { + name: "allowUserCrossSigningReset", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "AllowUserCrossSigningResetPayload", + ofType: null, + }, + }, + args: [ + { + name: "input", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + }, + ], + }, { name: "createOauth2Session", type: {