diff --git a/src/api/v3/auth/token/mod.rs b/src/api/v3/auth/token/mod.rs index ebb97104..cc0acf6c 100644 --- a/src/api/v3/auth/token/mod.rs +++ b/src/api/v3/auth/token/mod.rs @@ -17,9 +17,10 @@ use utoipa_axum::{router::OpenApiRouter, routes}; use crate::api::auth::Auth; use crate::api::error::KeystoneApiError; +use crate::identity::IdentityApi; use crate::keystone::ServiceState; use crate::token::TokenApi; -use types::{TokenBuilder, TokenResponse}; +use types::{TokenBuilder, TokenResponse, User}; pub mod types; @@ -62,6 +63,21 @@ async fn validate( response.audit_ids(token.audit_ids().clone()); response.methods(token.methods().clone()); response.expires_at(*token.expires_at()); + + let user = state + .provider + .get_identity_provider() + .get_user(&state.db, token.user_id().clone()) + .await + .map_err(KeystoneApiError::identity)? + .ok_or_else(|| KeystoneApiError::NotFound { + resource: "user".into(), + identifier: token.user_id().clone(), + })?; + + let user_response: User = user.into(); + response.user(user_response); + Ok(TokenResponse { token: response.build()?, }) @@ -74,17 +90,28 @@ mod tests { http::{Request, StatusCode}, }; use http_body_util::BodyExt; // for `collect` + use sea_orm::DatabaseConnection; use tower::ServiceExt; // for `call`, `oneshot`, and `ready` use tower_http::trace::TraceLayer; use super::openapi_router; use crate::api::v3::auth::token::types::TokenResponse; - use crate::identity::MockIdentityProvider; + use crate::identity::{MockIdentityProvider, types::User}; use crate::tests::api::{get_mocked_state, get_mocked_state_unauthed}; #[tokio::test] async fn test_get() { - let identity_mock = MockIdentityProvider::default(); + let mut identity_mock = MockIdentityProvider::default(); + identity_mock + .expect_get_user() + .withf(|_: &DatabaseConnection, id: &String| *id == "bar") + .returning(|_, _| { + Ok(Some(User { + id: "bar".into(), + ..Default::default() + })) + }); + let state = get_mocked_state(identity_mock); let mut api = openapi_router() diff --git a/src/api/v3/auth/token/types.rs b/src/api/v3/auth/token/types.rs index de5ecd70..d57d7023 100644 --- a/src/api/v3/auth/token/types.rs +++ b/src/api/v3/auth/token/types.rs @@ -22,6 +22,8 @@ use derive_builder::Builder; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; +use crate::identity::types as provider_types; + /// Authorization token #[derive(Builder, Clone, Debug, Default, Deserialize, PartialEq, Serialize, ToSchema)] #[builder(setter(strip_option, into))] @@ -53,6 +55,10 @@ pub struct Token { #[serde(skip_serializing_if = "Option::is_none")] #[builder(default)] pub project: Option, + + /// A user object. + #[builder(default)] + pub user: User, } #[derive(Builder, Clone, Debug, Default, Deserialize, PartialEq, Serialize, ToSchema)] @@ -76,3 +82,26 @@ pub struct Project { /// Project Name name: String, } + +/// User information +#[derive(Builder, Clone, Debug, Default, Deserialize, PartialEq, Serialize, ToSchema)] +#[builder(setter(into))] +pub struct User { + /// User ID + id: String, + /// User Name + name: String, + /// User password expiry date + #[serde(skip_serializing_if = "Option::is_none")] + password_expires_at: Option>, +} + +impl From for User { + fn from(value: provider_types::User) -> Self { + Self { + id: value.id.clone(), + name: value.name.clone(), + password_expires_at: value.password_expires_at.clone(), + } + } +} diff --git a/src/tests/api.rs b/src/tests/api.rs index 0aaed7a7..4d506538 100644 --- a/src/tests/api.rs +++ b/src/tests/api.rs @@ -44,9 +44,12 @@ pub(crate) fn get_mocked_state(identity_mock: MockIdentityProvider) -> ServiceSt let db = DatabaseConnection::Disconnected; let config = Config::default(); let mut token_mock = MockTokenProvider::default(); - token_mock - .expect_validate_token() - .returning(|_, _| Ok(Token::Unscoped(UnscopedToken::default()))); + token_mock.expect_validate_token().returning(|_, _| { + Ok(Token::Unscoped(UnscopedToken { + user_id: "bar".into(), + ..Default::default() + })) + }); let provider = ProviderBuilder::default() .config(config.clone())