Skip to content

Commit 044ccfc

Browse files
committed
endpoint for silo admin to expire all user's tokens and sessions
1 parent 55779df commit 044ccfc

File tree

6 files changed

+94
-0
lines changed

6 files changed

+94
-0
lines changed

nexus/db-queries/src/db/datastore/console_session.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use crate::db::model::ConsoleSession;
1212
use async_bb8_diesel::AsyncRunQueryDsl;
1313
use chrono::Utc;
1414
use diesel::prelude::*;
15+
use nexus_db_errors::ErrorHandler;
16+
use nexus_db_errors::public_error_from_diesel;
1517
use nexus_db_lookup::LookupPath;
1618
use nexus_db_schema::schema::console_session;
1719
use omicron_common::api::external::CreateResult;
@@ -154,4 +156,23 @@ impl DataStore {
154156
))
155157
})
156158
}
159+
160+
/// Delete all session for the user
161+
pub async fn silo_user_sessions_delete(
162+
&self,
163+
opctx: &OpContext,
164+
user: &authz::SiloUser,
165+
) -> Result<(), Error> {
166+
// TODO: check for silo admin on opctx
167+
// TODO: ensure this can only be used in current silo
168+
// TODO: think about dueling admins problem
169+
170+
use nexus_db_schema::schema::console_session;
171+
diesel::delete(console_session::table)
172+
.filter(console_session::silo_user_id.eq(user.id()))
173+
.execute_async(&*self.pool_connection_authorized(opctx).await?)
174+
.await
175+
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))
176+
.map(|_x| ())
177+
}
157178
}

nexus/db-queries/src/db/datastore/device_auth.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,4 +241,23 @@ impl DataStore {
241241

242242
Ok(())
243243
}
244+
245+
/// Delete all tokens for the user
246+
pub async fn silo_user_tokens_delete(
247+
&self,
248+
opctx: &OpContext,
249+
user: &authz::SiloUser,
250+
) -> Result<(), Error> {
251+
// TODO: check for silo admin on opctx
252+
// TODO: ensure this can only be used in current silo
253+
// TODO: think about dueling admins problem
254+
255+
use nexus_db_schema::schema::device_access_token;
256+
diesel::delete(device_access_token::table)
257+
.filter(device_access_token::silo_user_id.eq(user.id()))
258+
.execute_async(&*self.pool_connection_authorized(opctx).await?)
259+
.await
260+
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))
261+
.map(|_x| ())
262+
}
244263
}

nexus/external-api/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3008,6 +3008,17 @@ pub trait NexusExternalApi {
30083008
query_params: Query<PaginatedById<params::OptionalGroupSelector>>,
30093009
) -> Result<HttpResponseOk<ResultsPage<views::User>>, HttpError>;
30103010

3011+
/// Expire all user's tokens and sessions
3012+
#[endpoint {
3013+
method = POST,
3014+
path = "/v1/users/{user_id}/logout",
3015+
tags = ["silos"],
3016+
}]
3017+
async fn user_logout(
3018+
rqctx: RequestContext<Self::Context>,
3019+
path_params: Path<params::UserPath>,
3020+
) -> Result<HttpResponseUpdatedNoContent, HttpError>;
3021+
30113022
// Silo groups
30123023

30133024
/// List groups

nexus/src/app/silo.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,28 @@ impl super::Nexus {
313313
Ok(db_silo_user)
314314
}
315315

316+
/// Fetch a user in a Silo
317+
pub(crate) async fn current_silo_user_logout(
318+
&self,
319+
opctx: &OpContext,
320+
silo_user_id: Uuid,
321+
) -> UpdateResult<()> {
322+
let (_, authz_silo_user, _) = LookupPath::new(opctx, self.datastore())
323+
.silo_user_id(silo_user_id)
324+
.fetch()
325+
.await?;
326+
327+
self.datastore()
328+
.silo_user_tokens_delete(opctx, &authz_silo_user)
329+
.await?;
330+
331+
self.datastore()
332+
.silo_user_sessions_delete(opctx, &authz_silo_user)
333+
.await?;
334+
335+
Ok(())
336+
}
337+
316338
// The "local" identity provider (available only in `LocalOnly` Silos)
317339

318340
/// Helper function for looking up a LocalOnly Silo by name

nexus/src/external_api/http_entrypoints.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6705,6 +6705,26 @@ impl NexusExternalApi for NexusExternalApiImpl {
67056705
.await
67066706
}
67076707

6708+
async fn user_logout(
6709+
rqctx: RequestContext<Self::Context>,
6710+
path_params: Path<params::UserPath>,
6711+
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
6712+
let apictx = rqctx.context();
6713+
let handler = async {
6714+
let nexus = &apictx.context.nexus;
6715+
let path = path_params.into_inner();
6716+
let opctx =
6717+
crate::context::op_context_for_external_api(&rqctx).await?;
6718+
nexus.current_silo_user_logout(&opctx, path.user_id).await?;
6719+
Ok(HttpResponseUpdatedNoContent())
6720+
};
6721+
apictx
6722+
.context
6723+
.external_latencies
6724+
.instrument_dropshot_handler(&rqctx, handler)
6725+
.await
6726+
}
6727+
67086728
// Silo groups
67096729

67106730
async fn group_list(

nexus/types/src/external_api/params.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ path_param!(CertificatePath, certificate, "certificate");
9696

9797
id_path_param!(SupportBundlePath, bundle_id, "support bundle");
9898
id_path_param!(GroupPath, group_id, "group");
99+
id_path_param!(UserPath, user_id, "user");
99100
id_path_param!(TokenPath, token_id, "token");
100101

101102
// TODO: The hardware resources should be represented by its UUID or a hardware

0 commit comments

Comments
 (0)