From d17f0eabf41661b2b0f05cea8f9f3050ba79feb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABlle=20Huisman?= Date: Sat, 13 Jun 2026 15:28:24 +0200 Subject: [PATCH] feat(oauth): add user endpoint request --- .../src/actions/sign_in_callback.rs | 59 ++++++++- packages/methods/shield-oauth/src/provider.rs | 8 ++ .../src/entities/oauth_provider.rs | 10 ++ .../shield-sea-orm/src/methods/oauth.rs | 5 + .../src/migrations/providers/oauth.rs | 2 + ...m20260613_131851_add_user_url_and_paths.rs | 117 ++++++++++++++++++ 6 files changed, 196 insertions(+), 5 deletions(-) create mode 100644 packages/storage/shield-sea-orm/src/migrations/providers/oauth/m20260613_131851_add_user_url_and_paths.rs diff --git a/packages/methods/shield-oauth/src/actions/sign_in_callback.rs b/packages/methods/shield-oauth/src/actions/sign_in_callback.rs index 114c299..0e81e0e 100644 --- a/packages/methods/shield-oauth/src/actions/sign_in_callback.rs +++ b/packages/methods/shield-oauth/src/actions/sign_in_callback.rs @@ -7,6 +7,7 @@ use oauth2::{ url::form_urlencoded::parse, }; use secrecy::SecretString; +use serde_json::Value; use shield::{ ConfigurationError, CreateEmailAddress, CreateUser, Form, MethodAction, MethodSession, Request, RequestMethod, Response, ResponseType, SessionAction, ShieldError, SignInCallbackAction, @@ -221,14 +222,46 @@ impl MethodAction for OauthSignI .await .map_err(|err| ShieldError::Request(err.to_string()))?; - // TODO: user info - let identifier = ""; - let email = Some(""); - let name = Some(""); + let user_response = async_http_client + .get(&provider.user_url) + .bearer_auth(token_response.access_token().secret()) + .send() + .await + .map_err(|err| ShieldError::Request(err.to_string()))?; + + let user = user_response + .json::() + .await + .map_err(|err| ShieldError::Request(err.to_string()))?; + + let user = if let Some(user_path) = &provider.user_path { + value_by_path(&user, user_path)? + } else { + &user + }; + + let identifier = value_by_path(user, &provider.user_id_path)?; + let identifier = identifier + .as_str() + .map(ToOwned::to_owned) + .or_else(|| identifier.as_number().map(|number| number.to_string())) + .ok_or_else(|| ShieldError::Request("Missing or invalid user ID.".to_owned()))?; + + let email = if let Ok(email) = value_by_path(user, &provider.user_email_path) { + email.as_str() + } else { + None + }; + + let name = if let Ok(name) = value_by_path(user, &provider.user_name_path) { + name.as_str() + } else { + None + }; let (connection, user) = match self .storage - .oauth_connection_by_identifier(&provider.id, identifier) + .oauth_connection_by_identifier(&provider.id, &identifier) .await? { Some(connection) => { @@ -310,3 +343,19 @@ fn parse_token_response( .map(|scopes| scopes.iter().map(|scope| scope.to_string()).collect()), )) } + +fn value_by_path<'a>(data: &'a Value, path: &str) -> Result<&'a Value, ShieldError> { + let mut data = data; + + for key in path.split(".") { + if let Some(value) = data.get(key) { + data = value; + } else { + return Err(ShieldError::Request(format!( + "Path `{path}` not found in JSON response." + ))); + } + } + + Ok(data) +} diff --git a/packages/methods/shield-oauth/src/provider.rs b/packages/methods/shield-oauth/src/provider.rs index 721270d..3b09922 100644 --- a/packages/methods/shield-oauth/src/provider.rs +++ b/packages/methods/shield-oauth/src/provider.rs @@ -66,6 +66,14 @@ pub struct OauthProvider { #[builder(default = OauthProviderPkceCodeChallenge::S256)] pub pkce_code_challenge: OauthProviderPkceCodeChallenge, pub icon_url: Option, + pub user_url: String, + pub user_path: Option, + #[builder(default = "id")] + pub user_id_path: String, + #[builder(default = "email")] + pub user_email_path: String, + #[builder(default = "name")] + pub user_name_path: String, } impl OauthProvider { diff --git a/packages/storage/shield-sea-orm/src/entities/oauth_provider.rs b/packages/storage/shield-sea-orm/src/entities/oauth_provider.rs index 806dcf4..c32eca6 100644 --- a/packages/storage/shield-sea-orm/src/entities/oauth_provider.rs +++ b/packages/storage/shield-sea-orm/src/entities/oauth_provider.rs @@ -84,6 +84,16 @@ pub struct Model { pub pkce_code_challenge: OauthProviderPkceCodeChallenge, #[sea_orm(column_type = "Text", nullable)] pub icon_url: Option, + #[sea_orm(column_type = "Text")] + pub user_url: String, + #[sea_orm(column_type = "Text", nullable)] + pub user_path: Option, + #[sea_orm(column_type = "Text", nullable)] + pub user_id_path: Option, + #[sea_orm(column_type = "Text", nullable)] + pub user_email_path: Option, + #[sea_orm(column_type = "Text", nullable)] + pub user_name_path: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/packages/storage/shield-sea-orm/src/methods/oauth.rs b/packages/storage/shield-sea-orm/src/methods/oauth.rs index 14309a6..576e163 100644 --- a/packages/storage/shield-sea-orm/src/methods/oauth.rs +++ b/packages/storage/shield-sea-orm/src/methods/oauth.rs @@ -194,6 +194,11 @@ impl TryFrom for OauthProvider { revocation_url: value.revocation_url, revocation_url_params: value.revocation_url_params, pkce_code_challenge: value.pkce_code_challenge.into(), + user_url: value.user_url, + user_path: value.user_path, + user_id_path: value.user_id_path.unwrap_or("id".to_owned()), + user_email_path: value.user_email_path.unwrap_or("email".to_owned()), + user_name_path: value.user_name_path.unwrap_or("name".to_owned()), }) } } diff --git a/packages/storage/shield-sea-orm/src/migrations/providers/oauth.rs b/packages/storage/shield-sea-orm/src/migrations/providers/oauth.rs index 2215e61..80ecaca 100644 --- a/packages/storage/shield-sea-orm/src/migrations/providers/oauth.rs +++ b/packages/storage/shield-sea-orm/src/migrations/providers/oauth.rs @@ -1,5 +1,6 @@ mod m20241211_095111_create_provider_oauth; mod m20250118_133257_add_icon_url; +mod m20260613_131851_add_user_url_and_paths; use async_trait::async_trait; use sea_orm_migration::{MigrationTrait, MigratorTrait}; @@ -12,6 +13,7 @@ impl MigratorTrait for ProviderOauthMigrator { vec![ Box::new(self::m20241211_095111_create_provider_oauth::Migration), Box::new(self::m20250118_133257_add_icon_url::Migration), + Box::new(self::m20260613_131851_add_user_url_and_paths::Migration), ] } } diff --git a/packages/storage/shield-sea-orm/src/migrations/providers/oauth/m20260613_131851_add_user_url_and_paths.rs b/packages/storage/shield-sea-orm/src/migrations/providers/oauth/m20260613_131851_add_user_url_and_paths.rs new file mode 100644 index 0000000..17b0881 --- /dev/null +++ b/packages/storage/shield-sea-orm/src/migrations/providers/oauth/m20260613_131851_add_user_url_and_paths.rs @@ -0,0 +1,117 @@ +use async_trait::async_trait; +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(OauthProvider::Table) + .add_column(ColumnDef::new(OauthProvider::UserUrl).text().not_null()) + .to_owned(), + ) + .await?; + + manager + .alter_table( + Table::alter() + .table(OauthProvider::Table) + .add_column(ColumnDef::new(OauthProvider::UserPath).text()) + .to_owned(), + ) + .await?; + + manager + .alter_table( + Table::alter() + .table(OauthProvider::Table) + .add_column(ColumnDef::new(OauthProvider::UserIdPath).text()) + .to_owned(), + ) + .await?; + + manager + .alter_table( + Table::alter() + .table(OauthProvider::Table) + .add_column(ColumnDef::new(OauthProvider::UserEmailPath).text()) + .to_owned(), + ) + .await?; + + manager + .alter_table( + Table::alter() + .table(OauthProvider::Table) + .add_column(ColumnDef::new(OauthProvider::UserNamePath).text()) + .to_owned(), + ) + .await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(OauthProvider::Table) + .drop_column(OauthProvider::UserNamePath) + .to_owned(), + ) + .await?; + + manager + .alter_table( + Table::alter() + .table(OauthProvider::Table) + .drop_column(OauthProvider::UserEmailPath) + .to_owned(), + ) + .await?; + + manager + .alter_table( + Table::alter() + .table(OauthProvider::Table) + .drop_column(OauthProvider::UserIdPath) + .to_owned(), + ) + .await?; + + manager + .alter_table( + Table::alter() + .table(OauthProvider::Table) + .drop_column(OauthProvider::UserPath) + .to_owned(), + ) + .await?; + + manager + .alter_table( + Table::alter() + .table(OauthProvider::Table) + .drop_column(OauthProvider::UserUrl) + .to_owned(), + ) + .await?; + + Ok(()) + } +} + +#[derive(DeriveIden)] +enum OauthProvider { + Table, + + UserUrl, + UserPath, + UserIdPath, + UserEmailPath, + UserNamePath, +}