Skip to content

Commit 176c059

Browse files
authored
New admin API endpoint to reactivate users & align what lock/unlock/deactivate does in the admin API (#4768)
2 parents 52c3a36 + 8ac2770 commit 176c059

File tree

15 files changed

+438
-63
lines changed

15 files changed

+438
-63
lines changed

crates/cli/src/commands/manage.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ enum Subcommand {
149149
UnlockUser {
150150
/// User to unlock
151151
username: String,
152+
153+
/// Whether to reactivate the user if it had been deactivated
154+
#[arg(long)]
155+
reactivate: bool,
152156
},
153157

154158
/// Register a user
@@ -527,8 +531,12 @@ impl Options {
527531
Ok(ExitCode::SUCCESS)
528532
}
529533

530-
SC::UnlockUser { username } => {
531-
let _span = info_span!("cli.manage.lock_user", user.username = username).entered();
534+
SC::UnlockUser {
535+
username,
536+
reactivate,
537+
} => {
538+
let _span =
539+
info_span!("cli.manage.unlock_user", user.username = username).entered();
532540
let config = DatabaseConfig::extract_or_default(figment)?;
533541
let mut conn = database_connection_from_config(&config).await?;
534542
let txn = conn.begin().await?;
@@ -540,10 +548,14 @@ impl Options {
540548
.await?
541549
.context("User not found")?;
542550

543-
warn!(%user.id, "User scheduling user reactivation");
544-
repo.queue_job()
545-
.schedule_job(&mut rng, &clock, ReactivateUserJob::new(&user))
546-
.await?;
551+
if reactivate {
552+
warn!(%user.id, "Scheduling user reactivation");
553+
repo.queue_job()
554+
.schedule_job(&mut rng, &clock, ReactivateUserJob::new(&user))
555+
.await?;
556+
} else {
557+
repo.user().unlock(user).await?;
558+
}
547559

548560
repo.into_inner().commit().await?;
549561

crates/handlers/src/admin/v1/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ where
9494
"/users/{id}/deactivate",
9595
post_with(self::users::deactivate, self::users::deactivate_doc),
9696
)
97+
.api_route(
98+
"/users/{id}/reactivate",
99+
post_with(self::users::reactivate, self::users::reactivate_doc),
100+
)
97101
.api_route(
98102
"/users/{id}/lock",
99103
post_with(self::users::lock, self::users::lock_doc),

crates/handlers/src/admin/v1/users/deactivate.rs

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,17 @@ pub fn doc(operation: TransformOperation) -> TransformOperation {
5353
operation
5454
.id("deactivateUser")
5555
.summary("Deactivate a user")
56-
.description("Calling this endpoint will lock and deactivate the user, preventing them from doing any action.
57-
This invalidates any existing session, and will ask the homeserver to make them leave all rooms.")
56+
.description(
57+
"Calling this endpoint will deactivate the user, preventing them from doing any action.
58+
This invalidates any existing session, and will ask the homeserver to make them leave all rooms.",
59+
)
5860
.tag("user")
5961
.response_with::<200, Json<SingleResponse<User>>, _>(|t| {
6062
// In the samples, the third user is the one locked
6163
let [_alice, _bob, charlie, ..] = User::samples();
6264
let id = charlie.id();
63-
let response = SingleResponse::new(charlie, format!("/api/admin/v1/users/{id}/deactivate"));
65+
let response =
66+
SingleResponse::new(charlie, format!("/api/admin/v1/users/{id}/deactivate"));
6467
t.description("User was deactivated").example(response)
6568
})
6669
.response_with::<404, RouteError, _>(|t| {
@@ -78,15 +81,13 @@ pub async fn handler(
7881
id: UlidPathParam,
7982
) -> Result<Json<SingleResponse<User>>, RouteError> {
8083
let id = *id;
81-
let mut user = repo
84+
let user = repo
8285
.user()
8386
.lookup(id)
8487
.await?
8588
.ok_or(RouteError::NotFound(id))?;
8689

87-
if user.locked_at.is_none() {
88-
user = repo.user().lock(&clock, user).await?;
89-
}
90+
let user = repo.user().deactivate(&clock, user).await?;
9091

9192
info!(%user.id, "Scheduling deactivation of user");
9293
repo.queue_job()
@@ -132,12 +133,18 @@ mod tests {
132133
response.assert_status(StatusCode::OK);
133134
let body: serde_json::Value = response.json();
134135

135-
// The locked_at timestamp should be the same as the current time
136+
// The deactivated_at timestamp should be the same as the current time
136137
assert_eq!(
137-
body["data"]["attributes"]["locked_at"],
138+
body["data"]["attributes"]["deactivated_at"],
138139
serde_json::json!(state.clock.now())
139140
);
140141

142+
// Deactivating the user should not lock it
143+
assert_eq!(
144+
body["data"]["attributes"]["locked_at"],
145+
serde_json::Value::Null
146+
);
147+
141148
// Make sure to run the jobs in the queue
142149
state.run_jobs_in_queue().await;
143150

@@ -156,7 +163,7 @@ mod tests {
156163
"attributes": {
157164
"username": "alice",
158165
"created_at": "2022-01-16T14:40:00Z",
159-
"locked_at": "2022-01-16T14:40:00Z",
166+
"locked_at": null,
160167
"deactivated_at": "2022-01-16T14:40:00Z",
161168
"admin": false
162169
},
@@ -196,10 +203,16 @@ mod tests {
196203
response.assert_status(StatusCode::OK);
197204
let body: serde_json::Value = response.json();
198205

199-
// The locked_at timestamp should be different from the current time
206+
// The deactivated_at timestamp should be the same as the current time
207+
assert_eq!(
208+
body["data"]["attributes"]["deactivated_at"],
209+
serde_json::json!(state.clock.now())
210+
);
211+
212+
// The deactivated_at timestamp should be different from the locked_at timestamp
200213
assert_ne!(
214+
body["data"]["attributes"]["deactivated_at"],
201215
body["data"]["attributes"]["locked_at"],
202-
serde_json::json!(state.clock.now())
203216
);
204217

205218
// Make sure to run the jobs in the queue

crates/handlers/src/admin/v1/users/lock.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,13 @@ pub async fn handler(
7272
id: UlidPathParam,
7373
) -> Result<Json<SingleResponse<User>>, RouteError> {
7474
let id = *id;
75-
let mut user = repo
75+
let user = repo
7676
.user()
7777
.lookup(id)
7878
.await?
7979
.ok_or(RouteError::NotFound(id))?;
8080

81-
if user.locked_at.is_none() {
82-
user = repo.user().lock(&clock, user).await?;
83-
}
81+
let user = repo.user().lock(&clock, user).await?;
8482

8583
repo.save().await?;
8684

@@ -157,6 +155,10 @@ mod tests {
157155
body["data"]["attributes"]["locked_at"],
158156
serde_json::json!(state.clock.now())
159157
);
158+
assert_ne!(
159+
body["data"]["attributes"]["locked_at"],
160+
serde_json::Value::Null
161+
);
160162
}
161163

162164
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]

crates/handlers/src/admin/v1/users/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod deactivate;
1010
mod get;
1111
mod list;
1212
mod lock;
13+
mod reactivate;
1314
mod set_admin;
1415
mod set_password;
1516
mod unlock;
@@ -21,6 +22,7 @@ pub use self::{
2122
get::{doc as get_doc, handler as get},
2223
list::{doc as list_doc, handler as list},
2324
lock::{doc as lock_doc, handler as lock},
25+
reactivate::{doc as reactivate_doc, handler as reactivate},
2426
set_admin::{doc as set_admin_doc, handler as set_admin},
2527
set_password::{doc as set_password_doc, handler as set_password},
2628
unlock::{doc as unlock_doc, handler as unlock},

0 commit comments

Comments
 (0)