Skip to content
This repository has been archived by the owner on Sep 10, 2024. It is now read-only.

Commit

Permalink
GraphQL API to unlock a user
Browse files Browse the repository at this point in the history
Fixes #2101
  • Loading branch information
sandhose committed Jul 16, 2024
1 parent 3423a64 commit e9a57a4
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 0 deletions.
80 changes: 80 additions & 0 deletions crates/handlers/src/graphql/mutations/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,52 @@ impl LockUserPayload {
}
}

/// The input for the `unlockUser` mutation.
#[derive(InputObject)]
struct UnlockUserInput {
/// The ID of the user to unlock
user_id: ID,
}

/// The status of the `unlockUser` mutation.
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
enum UnlockUserStatus {
/// The user was unlocked.
Unlocked,

/// The user was not found.
NotFound,
}

/// The payload for the `unlockUser` mutation.
#[derive(Description)]
enum UnlockUserPayload {
/// The user was unlocked.
Unlocked(mas_data_model::User),

/// The user was not found.
NotFound,
}

#[Object(use_type_description)]
impl UnlockUserPayload {
/// Status of the operation
async fn status(&self) -> UnlockUserStatus {
match self {
Self::Unlocked(_) => UnlockUserStatus::Unlocked,
Self::NotFound => UnlockUserStatus::NotFound,
}
}

/// The user that was unlocked.
async fn user(&self) -> Option<User> {
match self {
Self::Unlocked(user) => Some(User(user.clone())),
Self::NotFound => None,
}
}
}

/// The input for the `setCanRequestAdmin` mutation.
#[derive(InputObject)]
struct SetCanRequestAdminInput {
Expand Down Expand Up @@ -382,6 +428,40 @@ impl UserMutations {
Ok(LockUserPayload::Locked(user))
}

/// Unlock a user. This is only available to administrators.
async fn unlock_user(
&self,
ctx: &Context<'_>,
input: UnlockUserInput,
) -> Result<UnlockUserPayload, async_graphql::Error> {
let state = ctx.state();
let requester = ctx.requester();
let matrix = state.homeserver_connection();

if !requester.is_admin() {
return Err(async_graphql::Error::new("Unauthorized"));
}

let mut repo = state.repository().await?;
let user_id = NodeType::User.extract_ulid(&input.user_id)?;
let user = repo.user().lookup(user_id).await?;

let Some(user) = user else {
return Ok(UnlockUserPayload::NotFound);
};

// Call the homeserver synchronously to unlock the user
let mxid = matrix.mxid(&user.username);
matrix.reactivate_user(&mxid).await?;

// Now unlock the user in our database
let user = repo.user().unlock(user).await?;

repo.save().await?;

Ok(UnlockUserPayload::Unlocked(user))
}

/// Set whether a user can request admin. This is only available to
/// administrators.
async fn set_can_request_admin(
Expand Down
42 changes: 42 additions & 0 deletions frontend/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,10 @@ type Mutation {
"""
lockUser(input: LockUserInput!): LockUserPayload!
"""
Unlock a user. This is only available to administrators.
"""
unlockUser(input: UnlockUserInput!): UnlockUserPayload!
"""
Set whether a user can request admin. This is only available to
administrators.
"""
Expand Down Expand Up @@ -1399,6 +1403,44 @@ type SiteConfig implements Node {
id: ID!
}

"""
The input for the `unlockUser` mutation.
"""
input UnlockUserInput {
"""
The ID of the user to unlock
"""
userId: ID!
}

"""
The payload for the `unlockUser` mutation.
"""
type UnlockUserPayload {
"""
Status of the operation
"""
status: UnlockUserStatus!
"""
The user that was unlocked.
"""
user: User
}

"""
The status of the `unlockUser` mutation.
"""
enum UnlockUserStatus {
"""
The user was unlocked.
"""
UNLOCKED
"""
The user was not found.
"""
NOT_FOUND
}

type UpstreamOAuth2Link implements Node & CreationEvent {
"""
ID of the object.
Expand Down
31 changes: 31 additions & 0 deletions frontend/src/gql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,8 @@ export type Mutation = {
setPassword: SetPasswordPayload;
/** Set an email address as primary */
setPrimaryEmail: SetPrimaryEmailPayload;
/** Unlock a user. This is only available to administrators. */
unlockUser: UnlockUserPayload;
/** Submit a verification code for an email address */
verifyEmail: VerifyEmailPayload;
};
Expand Down Expand Up @@ -586,6 +588,12 @@ export type MutationSetPrimaryEmailArgs = {
};


/** The mutations root of the GraphQL interface. */
export type MutationUnlockUserArgs = {
input: UnlockUserInput;
};


/** The mutations root of the GraphQL interface. */
export type MutationVerifyEmailArgs = {
input: VerifyEmailInput;
Expand Down Expand Up @@ -1040,6 +1048,29 @@ export type SiteConfig = Node & {
tosUri?: Maybe<Scalars['Url']['output']>;
};

/** The input for the `unlockUser` mutation. */
export type UnlockUserInput = {
/** The ID of the user to unlock */
userId: Scalars['ID']['input'];
};

/** The payload for the `unlockUser` mutation. */
export type UnlockUserPayload = {
__typename?: 'UnlockUserPayload';
/** Status of the operation */
status: UnlockUserStatus;
/** The user that was unlocked. */
user?: Maybe<User>;
};

/** The status of the `unlockUser` mutation. */
export enum UnlockUserStatus {
/** The user was not found. */
NotFound = 'NOT_FOUND',
/** The user was unlocked. */
Unlocked = 'UNLOCKED'
}

export type UpstreamOAuth2Link = CreationEvent & Node & {
__typename?: 'UpstreamOAuth2Link';
/** When the object was created. */
Expand Down
50 changes: 50 additions & 0 deletions frontend/src/gql/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1468,6 +1468,29 @@ export default {
}
]
},
{
"name": "unlockUser",
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "OBJECT",
"name": "UnlockUserPayload",
"ofType": null
}
},
"args": [
{
"name": "input",
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "SCALAR",
"name": "Any"
}
}
}
]
},
{
"name": "verifyEmail",
"type": {
Expand Down Expand Up @@ -2622,6 +2645,33 @@ export default {
}
]
},
{
"kind": "OBJECT",
"name": "UnlockUserPayload",
"fields": [
{
"name": "status",
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "SCALAR",
"name": "Any"
}
},
"args": []
},
{
"name": "user",
"type": {
"kind": "OBJECT",
"name": "User",
"ofType": null
},
"args": []
}
],
"interfaces": []
},
{
"kind": "OBJECT",
"name": "UpstreamOAuth2Link",
Expand Down

0 comments on commit e9a57a4

Please sign in to comment.