From cf9f2013375df8f9f687c6c2e6d868816f73f458 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 30 Jul 2024 17:37:36 +0200 Subject: [PATCH] admin: get OAuth 2.0 session API --- crates/handlers/src/admin/v1/mod.rs | 4 + .../src/admin/v1/oauth2_sessions/get.rs | 85 +++++++++++++ .../src/admin/v1/oauth2_sessions/mod.rs | 6 +- docs/api/spec.json | 113 ++++++++++++++++-- 4 files changed, 195 insertions(+), 13 deletions(-) create mode 100644 crates/handlers/src/admin/v1/oauth2_sessions/get.rs diff --git a/crates/handlers/src/admin/v1/mod.rs b/crates/handlers/src/admin/v1/mod.rs index 6c44e492..38f9059f 100644 --- a/crates/handlers/src/admin/v1/mod.rs +++ b/crates/handlers/src/admin/v1/mod.rs @@ -39,6 +39,10 @@ where "/oauth2-sessions", get_with(self::oauth2_sessions::list, self::oauth2_sessions::list_doc), ) + .api_route( + "/oauth2-sessions/:id", + get_with(self::oauth2_sessions::get, self::oauth2_sessions::get_doc), + ) .api_route( "/users", get_with(self::users::list, self::users::list_doc) diff --git a/crates/handlers/src/admin/v1/oauth2_sessions/get.rs b/crates/handlers/src/admin/v1/oauth2_sessions/get.rs new file mode 100644 index 00000000..078c92a1 --- /dev/null +++ b/crates/handlers/src/admin/v1/oauth2_sessions/get.rs @@ -0,0 +1,85 @@ +// Copyright 2024 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use aide::{transform::TransformOperation, OperationIo}; +use axum::{response::IntoResponse, Json}; +use hyper::StatusCode; +use ulid::Ulid; + +use crate::{ + admin::{ + call_context::CallContext, + model::OAuth2Session, + params::UlidPathParam, + response::{ErrorResponse, SingleResponse}, + }, + impl_from_error_for_route, +}; + +#[derive(Debug, thiserror::Error, OperationIo)] +#[aide(output_with = "Json")] +pub enum RouteError { + #[error(transparent)] + Internal(Box), + + #[error("OAuth 2.0 session ID {0} not found")] + NotFound(Ulid), +} + +impl_from_error_for_route!(mas_storage::RepositoryError); + +impl IntoResponse for RouteError { + fn into_response(self) -> axum::response::Response { + let error = ErrorResponse::from_error(&self); + let status = match self { + Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, + Self::NotFound(_) => StatusCode::NOT_FOUND, + }; + (status, Json(error)).into_response() + } +} + +pub fn doc(operation: TransformOperation) -> TransformOperation { + operation + .id("getOAuth2Session") + .summary("Get an OAuth 2.0 session") + .tag("oauth2-session") + .response_with::<200, Json>, _>(|t| { + let [sample, ..] = OAuth2Session::samples(); + let response = SingleResponse::new_canonical(sample); + t.description("OAuth 2.0 session was found") + .example(response) + }) + .response_with::<404, RouteError, _>(|t| { + let response = ErrorResponse::from_error(&RouteError::NotFound(Ulid::nil())); + t.description("OAuth 2.0 session was not found") + .example(response) + }) +} + +#[tracing::instrument(name = "handler.admin.v1.oauth2_session.get", skip_all, err)] +pub async fn handler( + CallContext { mut repo, .. }: CallContext, + id: UlidPathParam, +) -> Result>, RouteError> { + let session = repo + .oauth2_session() + .lookup(*id) + .await? + .ok_or(RouteError::NotFound(*id))?; + + Ok(Json(SingleResponse::new_canonical(OAuth2Session::from( + session, + )))) +} diff --git a/crates/handlers/src/admin/v1/oauth2_sessions/mod.rs b/crates/handlers/src/admin/v1/oauth2_sessions/mod.rs index 7882063e..0cbad506 100644 --- a/crates/handlers/src/admin/v1/oauth2_sessions/mod.rs +++ b/crates/handlers/src/admin/v1/oauth2_sessions/mod.rs @@ -12,6 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod get; mod list; -pub use self::list::{doc as list_doc, handler as list}; +pub use self::{ + get::{doc as get_doc, handler as get}, + list::{doc as list_doc, handler as list}, +}; diff --git a/docs/api/spec.json b/docs/api/spec.json index 9458ab6c..6f720492 100644 --- a/docs/api/spec.json +++ b/docs/api/spec.json @@ -247,6 +247,79 @@ } } }, + "/api/admin/v1/oauth2-sessions/{id}": { + "get": { + "tags": [ + "oauth2-session" + ], + "summary": "Get an OAuth 2.0 session", + "operationId": "getOAuth2Session", + "parameters": [ + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "title": "The ID of the resource", + "$ref": "#/components/schemas/ULID" + }, + "style": "simple" + } + ], + "responses": { + "200": { + "description": "OAuth 2.0 session was found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SingleResponse_for_OAuth2Session" + }, + "example": { + "data": { + "type": "oauth2-session", + "id": "01040G2081040G2081040G2081", + "attributes": { + "created_at": "1970-01-01T00:00:00Z", + "finished_at": null, + "user_id": "02081040G2081040G2081040G2", + "user_session_id": "030C1G60R30C1G60R30C1G60R3", + "client_id": "040G2081040G2081040G208104", + "scope": "openid", + "user_agent": "Mozilla/5.0", + "last_active_at": "1970-01-01T00:00:00Z", + "last_active_ip": "127.0.0.1" + }, + "links": { + "self": "/api/admin/v1/oauth2-sessions/01040G2081040G2081040G2081" + } + }, + "links": { + "self": "/api/admin/v1/oauth2-sessions/01040G2081040G2081040G2081" + } + } + } + } + }, + "404": { + "description": "OAuth 2.0 session was not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "example": { + "errors": [ + { + "title": "OAuth 2.0 session ID 00000000000000000000000000 not found" + } + ] + } + } + } + } + } + } + }, "/api/admin/v1/users": { "get": { "tags": [ @@ -1214,6 +1287,34 @@ } } }, + "UlidInPath": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "title": "The ID of the resource", + "$ref": "#/components/schemas/ULID" + } + } + }, + "SingleResponse_for_OAuth2Session": { + "description": "A top-level response with a single resource", + "type": "object", + "required": [ + "data", + "links" + ], + "properties": { + "data": { + "$ref": "#/components/schemas/SingleResource_for_OAuth2Session" + }, + "links": { + "$ref": "#/components/schemas/SelfLinks" + } + } + }, "UserFilter": { "type": "object", "properties": { @@ -1354,18 +1455,6 @@ } } }, - "UlidInPath": { - "type": "object", - "required": [ - "id" - ], - "properties": { - "id": { - "title": "The ID of the resource", - "$ref": "#/components/schemas/ULID" - } - } - }, "SetUserPasswordRequest": { "title": "JSON payload for the `POST /api/admin/v1/users/:id/set-password` endpoint", "type": "object",