-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add mutation and UI to delete an upstream link
Signed-off-by: MTRNord <[email protected]>
- Loading branch information
Showing
13 changed files
with
436 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
152 changes: 152 additions & 0 deletions
152
crates/handlers/src/graphql/mutations/upstream_oauth.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
// Copyright 2024 New Vector Ltd. | ||
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C. | ||
// | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
// Please see LICENSE in the repository root for full details. | ||
|
||
use anyhow::Context as _; | ||
use async_graphql::{Context, Description, Enum, InputObject, Object, ID}; | ||
use mas_storage::{user::UserRepository, RepositoryAccess}; | ||
|
||
use crate::graphql::{ | ||
model::{NodeType, UpstreamOAuth2Link, UpstreamOAuth2Provider, User}, | ||
state::ContextExt, | ||
}; | ||
|
||
#[derive(Default)] | ||
pub struct UpstreamOauthMutations { | ||
_private: (), | ||
} | ||
|
||
/// The input for the `removeEmail` mutation | ||
#[derive(InputObject)] | ||
struct RemoveUpstreamLinkInput { | ||
/// The ID of the upstream link to remove | ||
upstream_link_id: ID, | ||
} | ||
|
||
/// The status of the `removeEmail` mutation | ||
#[derive(Enum, Copy, Clone, Eq, PartialEq)] | ||
enum RemoveUpstreamLinkStatus { | ||
/// The upstream link was removed | ||
Removed, | ||
|
||
/// The upstream link was not found | ||
NotFound, | ||
} | ||
|
||
/// The payload of the `removeEmail` mutation | ||
#[derive(Description)] | ||
enum RemoveUpstreamLinkPayload { | ||
Removed(mas_data_model::UpstreamOAuthLink), | ||
NotFound, | ||
} | ||
|
||
#[Object(use_type_description)] | ||
impl RemoveUpstreamLinkPayload { | ||
/// Status of the operation | ||
async fn status(&self) -> RemoveUpstreamLinkStatus { | ||
match self { | ||
RemoveUpstreamLinkPayload::Removed(_) => RemoveUpstreamLinkStatus::Removed, | ||
RemoveUpstreamLinkPayload::NotFound => RemoveUpstreamLinkStatus::NotFound, | ||
} | ||
} | ||
|
||
/// The upstream link that was removed | ||
async fn upstream_link(&self) -> Option<UpstreamOAuth2Link> { | ||
match self { | ||
RemoveUpstreamLinkPayload::Removed(link) => Some(UpstreamOAuth2Link::new(link.clone())), | ||
RemoveUpstreamLinkPayload::NotFound => None, | ||
} | ||
} | ||
|
||
/// The provider to which the upstream link belonged | ||
async fn provider( | ||
&self, | ||
ctx: &Context<'_>, | ||
) -> Result<Option<UpstreamOAuth2Provider>, async_graphql::Error> { | ||
let state = ctx.state(); | ||
let provider_id = match self { | ||
RemoveUpstreamLinkPayload::Removed(link) => link.provider_id, | ||
RemoveUpstreamLinkPayload::NotFound => return Ok(None), | ||
}; | ||
|
||
let mut repo = state.repository().await?; | ||
let provider = repo | ||
.upstream_oauth_provider() | ||
.lookup(provider_id) | ||
.await? | ||
.context("Upstream OAuth 2.0 provider not found")?; | ||
|
||
Ok(Some(UpstreamOAuth2Provider::new(provider))) | ||
} | ||
|
||
/// The user to whom the upstream link belonged | ||
async fn user(&self, ctx: &Context<'_>) -> Result<Option<User>, async_graphql::Error> { | ||
let state = ctx.state(); | ||
let mut repo = state.repository().await?; | ||
|
||
let user_id = match self { | ||
RemoveUpstreamLinkPayload::Removed(link) => link.user_id, | ||
RemoveUpstreamLinkPayload::NotFound => return Ok(None), | ||
}; | ||
|
||
match user_id { | ||
None => return Ok(None), | ||
Some(user_id) => { | ||
let user = repo | ||
.user() | ||
.lookup(user_id) | ||
.await? | ||
.context("User not found")?; | ||
|
||
Ok(Some(User(user))) | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[Object] | ||
impl UpstreamOauthMutations { | ||
/// Remove an upstream linked account | ||
async fn remove_upstream_link( | ||
&self, | ||
ctx: &Context<'_>, | ||
input: RemoveUpstreamLinkInput, | ||
) -> Result<RemoveUpstreamLinkPayload, async_graphql::Error> { | ||
let state = ctx.state(); | ||
let upstream_link_id = | ||
NodeType::UpstreamOAuth2Link.extract_ulid(&input.upstream_link_id)?; | ||
let requester = ctx.requester(); | ||
|
||
let mut repo = state.repository().await?; | ||
|
||
let upstream_link = repo.upstream_oauth_link().lookup(upstream_link_id).await?; | ||
let Some(upstream_link) = upstream_link else { | ||
return Ok(RemoveUpstreamLinkPayload::NotFound); | ||
}; | ||
|
||
if !requester.is_owner_or_admin(&upstream_link) { | ||
return Ok(RemoveUpstreamLinkPayload::NotFound); | ||
} | ||
|
||
// Allow non-admins to remove their email address if the site config allows it | ||
if !requester.is_admin() && !state.site_config().email_change_allowed { | ||
return Err(async_graphql::Error::new("Unauthorized")); | ||
} | ||
|
||
let upstream_link = repo | ||
.upstream_oauth_link() | ||
.lookup(upstream_link.id) | ||
.await? | ||
.context("Failed to load user")?; | ||
|
||
repo.upstream_oauth_link() | ||
.remove(upstream_link.clone()) | ||
.await?; | ||
|
||
repo.save().await?; | ||
|
||
Ok(RemoveUpstreamLinkPayload::Removed(upstream_link)) | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
...rage-pg/.sqlx/query-5edffe16eb0e038083d423bcd753300d2ba034f0e350ad57ab9aeeddefd88701.json
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
14 changes: 14 additions & 0 deletions
14
...rage-pg/.sqlx/query-cc60ad934d347fb4546205d1fe07e9d2f127cb15b1bb650d1ea3805a4c55b196.json
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,5 +90,6 @@ | |
"vite-plugin-graphql-codegen": "^3.3.8", | ||
"vite-plugin-manifest-sri": "^0.2.0", | ||
"vitest": "^2.0.5" | ||
} | ||
}, | ||
"packageManager": "[email protected]+sha512.d08425c8062f56d43bb8e84315864218af2492eb769e1f1ca40740f44e85bd148969382d651660363942e5909cb7ffcbef7ca0ae963ddc2c57a51243b4da8f56" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.