Skip to content

Commit b3d494b

Browse files
feat(oauth): add user endpoint request (#424)
1 parent 2539c83 commit b3d494b

6 files changed

Lines changed: 196 additions & 5 deletions

File tree

packages/methods/shield-oauth/src/actions/sign_in_callback.rs

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use oauth2::{
77
url::form_urlencoded::parse,
88
};
99
use secrecy::SecretString;
10+
use serde_json::Value;
1011
use shield::{
1112
ConfigurationError, CreateEmailAddress, CreateUser, Form, MethodAction, MethodSession, Request,
1213
RequestMethod, Response, ResponseType, SessionAction, ShieldError, SignInCallbackAction,
@@ -221,14 +222,46 @@ impl<U: User + 'static> MethodAction<OauthProvider, OauthSession> for OauthSignI
221222
.await
222223
.map_err(|err| ShieldError::Request(err.to_string()))?;
223224

224-
// TODO: user info
225-
let identifier = "";
226-
let email = Some("");
227-
let name = Some("");
225+
let user_response = async_http_client
226+
.get(&provider.user_url)
227+
.bearer_auth(token_response.access_token().secret())
228+
.send()
229+
.await
230+
.map_err(|err| ShieldError::Request(err.to_string()))?;
231+
232+
let user = user_response
233+
.json::<Value>()
234+
.await
235+
.map_err(|err| ShieldError::Request(err.to_string()))?;
236+
237+
let user = if let Some(user_path) = &provider.user_path {
238+
value_by_path(&user, user_path)?
239+
} else {
240+
&user
241+
};
242+
243+
let identifier = value_by_path(user, &provider.user_id_path)?;
244+
let identifier = identifier
245+
.as_str()
246+
.map(ToOwned::to_owned)
247+
.or_else(|| identifier.as_number().map(|number| number.to_string()))
248+
.ok_or_else(|| ShieldError::Request("Missing or invalid user ID.".to_owned()))?;
249+
250+
let email = if let Ok(email) = value_by_path(user, &provider.user_email_path) {
251+
email.as_str()
252+
} else {
253+
None
254+
};
255+
256+
let name = if let Ok(name) = value_by_path(user, &provider.user_name_path) {
257+
name.as_str()
258+
} else {
259+
None
260+
};
228261

229262
let (connection, user) = match self
230263
.storage
231-
.oauth_connection_by_identifier(&provider.id, identifier)
264+
.oauth_connection_by_identifier(&provider.id, &identifier)
232265
.await?
233266
{
234267
Some(connection) => {
@@ -310,3 +343,19 @@ fn parse_token_response(
310343
.map(|scopes| scopes.iter().map(|scope| scope.to_string()).collect()),
311344
))
312345
}
346+
347+
fn value_by_path<'a>(data: &'a Value, path: &str) -> Result<&'a Value, ShieldError> {
348+
let mut data = data;
349+
350+
for key in path.split(".") {
351+
if let Some(value) = data.get(key) {
352+
data = value;
353+
} else {
354+
return Err(ShieldError::Request(format!(
355+
"Path `{path}` not found in JSON response."
356+
)));
357+
}
358+
}
359+
360+
Ok(data)
361+
}

packages/methods/shield-oauth/src/provider.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ pub struct OauthProvider {
6666
#[builder(default = OauthProviderPkceCodeChallenge::S256)]
6767
pub pkce_code_challenge: OauthProviderPkceCodeChallenge,
6868
pub icon_url: Option<String>,
69+
pub user_url: String,
70+
pub user_path: Option<String>,
71+
#[builder(default = "id")]
72+
pub user_id_path: String,
73+
#[builder(default = "email")]
74+
pub user_email_path: String,
75+
#[builder(default = "name")]
76+
pub user_name_path: String,
6977
}
7078

7179
impl OauthProvider {

packages/storage/shield-sea-orm/src/entities/oauth_provider.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,16 @@ pub struct Model {
8484
pub pkce_code_challenge: OauthProviderPkceCodeChallenge,
8585
#[sea_orm(column_type = "Text", nullable)]
8686
pub icon_url: Option<String>,
87+
#[sea_orm(column_type = "Text")]
88+
pub user_url: String,
89+
#[sea_orm(column_type = "Text", nullable)]
90+
pub user_path: Option<String>,
91+
#[sea_orm(column_type = "Text", nullable)]
92+
pub user_id_path: Option<String>,
93+
#[sea_orm(column_type = "Text", nullable)]
94+
pub user_email_path: Option<String>,
95+
#[sea_orm(column_type = "Text", nullable)]
96+
pub user_name_path: Option<String>,
8797
}
8898

8999
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

packages/storage/shield-sea-orm/src/methods/oauth.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,11 @@ impl TryFrom<oauth_provider::Model> for OauthProvider {
194194
revocation_url: value.revocation_url,
195195
revocation_url_params: value.revocation_url_params,
196196
pkce_code_challenge: value.pkce_code_challenge.into(),
197+
user_url: value.user_url,
198+
user_path: value.user_path,
199+
user_id_path: value.user_id_path.unwrap_or("id".to_owned()),
200+
user_email_path: value.user_email_path.unwrap_or("email".to_owned()),
201+
user_name_path: value.user_name_path.unwrap_or("name".to_owned()),
197202
})
198203
}
199204
}

packages/storage/shield-sea-orm/src/migrations/providers/oauth.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod m20241211_095111_create_provider_oauth;
22
mod m20250118_133257_add_icon_url;
3+
mod m20260613_131851_add_user_url_and_paths;
34

45
use async_trait::async_trait;
56
use sea_orm_migration::{MigrationTrait, MigratorTrait};
@@ -12,6 +13,7 @@ impl MigratorTrait for ProviderOauthMigrator {
1213
vec![
1314
Box::new(self::m20241211_095111_create_provider_oauth::Migration),
1415
Box::new(self::m20250118_133257_add_icon_url::Migration),
16+
Box::new(self::m20260613_131851_add_user_url_and_paths::Migration),
1517
]
1618
}
1719
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use async_trait::async_trait;
2+
use sea_orm_migration::prelude::*;
3+
4+
#[derive(DeriveMigrationName)]
5+
pub struct Migration;
6+
7+
#[async_trait]
8+
impl MigrationTrait for Migration {
9+
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
10+
manager
11+
.alter_table(
12+
Table::alter()
13+
.table(OauthProvider::Table)
14+
.add_column(ColumnDef::new(OauthProvider::UserUrl).text().not_null())
15+
.to_owned(),
16+
)
17+
.await?;
18+
19+
manager
20+
.alter_table(
21+
Table::alter()
22+
.table(OauthProvider::Table)
23+
.add_column(ColumnDef::new(OauthProvider::UserPath).text())
24+
.to_owned(),
25+
)
26+
.await?;
27+
28+
manager
29+
.alter_table(
30+
Table::alter()
31+
.table(OauthProvider::Table)
32+
.add_column(ColumnDef::new(OauthProvider::UserIdPath).text())
33+
.to_owned(),
34+
)
35+
.await?;
36+
37+
manager
38+
.alter_table(
39+
Table::alter()
40+
.table(OauthProvider::Table)
41+
.add_column(ColumnDef::new(OauthProvider::UserEmailPath).text())
42+
.to_owned(),
43+
)
44+
.await?;
45+
46+
manager
47+
.alter_table(
48+
Table::alter()
49+
.table(OauthProvider::Table)
50+
.add_column(ColumnDef::new(OauthProvider::UserNamePath).text())
51+
.to_owned(),
52+
)
53+
.await?;
54+
55+
Ok(())
56+
}
57+
58+
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
59+
manager
60+
.alter_table(
61+
Table::alter()
62+
.table(OauthProvider::Table)
63+
.drop_column(OauthProvider::UserNamePath)
64+
.to_owned(),
65+
)
66+
.await?;
67+
68+
manager
69+
.alter_table(
70+
Table::alter()
71+
.table(OauthProvider::Table)
72+
.drop_column(OauthProvider::UserEmailPath)
73+
.to_owned(),
74+
)
75+
.await?;
76+
77+
manager
78+
.alter_table(
79+
Table::alter()
80+
.table(OauthProvider::Table)
81+
.drop_column(OauthProvider::UserIdPath)
82+
.to_owned(),
83+
)
84+
.await?;
85+
86+
manager
87+
.alter_table(
88+
Table::alter()
89+
.table(OauthProvider::Table)
90+
.drop_column(OauthProvider::UserPath)
91+
.to_owned(),
92+
)
93+
.await?;
94+
95+
manager
96+
.alter_table(
97+
Table::alter()
98+
.table(OauthProvider::Table)
99+
.drop_column(OauthProvider::UserUrl)
100+
.to_owned(),
101+
)
102+
.await?;
103+
104+
Ok(())
105+
}
106+
}
107+
108+
#[derive(DeriveIden)]
109+
enum OauthProvider {
110+
Table,
111+
112+
UserUrl,
113+
UserPath,
114+
UserIdPath,
115+
UserEmailPath,
116+
UserNamePath,
117+
}

0 commit comments

Comments
 (0)