Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 54 additions & 5 deletions packages/methods/shield-oauth/src/actions/sign_in_callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -221,14 +222,46 @@ impl<U: User + 'static> MethodAction<OauthProvider, OauthSession> 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::<Value>()
.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) => {
Expand Down Expand Up @@ -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)
}
8 changes: 8 additions & 0 deletions packages/methods/shield-oauth/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ pub struct OauthProvider {
#[builder(default = OauthProviderPkceCodeChallenge::S256)]
pub pkce_code_challenge: OauthProviderPkceCodeChallenge,
pub icon_url: Option<String>,
pub user_url: String,
pub user_path: Option<String>,
#[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 {
Expand Down
10 changes: 10 additions & 0 deletions packages/storage/shield-sea-orm/src/entities/oauth_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ pub struct Model {
pub pkce_code_challenge: OauthProviderPkceCodeChallenge,
#[sea_orm(column_type = "Text", nullable)]
pub icon_url: Option<String>,
#[sea_orm(column_type = "Text")]
pub user_url: String,
#[sea_orm(column_type = "Text", nullable)]
pub user_path: Option<String>,
#[sea_orm(column_type = "Text", nullable)]
pub user_id_path: Option<String>,
#[sea_orm(column_type = "Text", nullable)]
pub user_email_path: Option<String>,
#[sea_orm(column_type = "Text", nullable)]
pub user_name_path: Option<String>,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
Expand Down
5 changes: 5 additions & 0 deletions packages/storage/shield-sea-orm/src/methods/oauth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ impl TryFrom<oauth_provider::Model> 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()),
})
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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),
]
}
}
Original file line number Diff line number Diff line change
@@ -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,
}