|
@@ -8,6 +8,8 @@ use crate::{
|
|
|
UIDCError,
|
|
|
};
|
|
|
|
|
|
+use super::ExternalAuthenticator;
|
|
|
+
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
pub struct GithubConfig {
|
|
@@ -35,7 +37,119 @@ pub struct GithubAuthenticator {
|
|
|
config: GithubConfig,
|
|
|
}
|
|
|
|
|
|
-impl super::ExternalAuthenticator for GithubAuthenticator {
|
|
|
+impl GithubAuthenticator {
|
|
|
+ async fn do_extract_login_state(&self, req: UIDCRequest) -> Result<tide::Response, UIDCError> {
|
|
|
+ let state = req.state();
|
|
|
+ let realm = req.param("realm").unwrap();
|
|
|
+
|
|
|
+ #[derive(Deserialize)]
|
|
|
+ struct Query {
|
|
|
+ code: String,
|
|
|
+ redirect: String,
|
|
|
+ mode: CallbackRequestType,
|
|
|
+ }
|
|
|
+ let Ok(query) = req.query::<Query>() else {
|
|
|
+ return Ok(tide::Response::builder(400)
|
|
|
+ .body("Query string invalid.")
|
|
|
+ .build());
|
|
|
+ };
|
|
|
+
|
|
|
+ #[derive(Deserialize)]
|
|
|
+ struct TokenResponse {
|
|
|
+ access_token: String,
|
|
|
+ }
|
|
|
+
|
|
|
+ let auth = surf::http::auth::BasicAuth::new(
|
|
|
+ self.config.client_id.as_str(),
|
|
|
+ self.config.client_secret.as_str(),
|
|
|
+ );
|
|
|
+
|
|
|
+ let resp: TokenResponse = match state
|
|
|
+ .client
|
|
|
+ .post(
|
|
|
+ tide::http::Url::parse_with_params(
|
|
|
+ self.config
|
|
|
+ .token_url
|
|
|
+ .as_deref()
|
|
|
+ .unwrap_or(DEFAULT_TOKEN_URL),
|
|
|
+ &[
|
|
|
+ ("client_id", self.config.client_id.as_str()),
|
|
|
+ ("client_secret", self.config.client_secret.as_str()),
|
|
|
+ ("code", query.code.as_str()),
|
|
|
+ ],
|
|
|
+ )
|
|
|
+ .expect("couldn't generate token url for github"),
|
|
|
+ )
|
|
|
+ .header(auth.name(), auth.value())
|
|
|
+ .content_type(surf::http::mime::FORM)
|
|
|
+ .recv_form()
|
|
|
+ .await
|
|
|
+ {
|
|
|
+ Ok(resp) => resp,
|
|
|
+ Err(err) => {
|
|
|
+ return Err(UIDCError::AbortString(format!(
|
|
|
+ "could not parse Github response for token: {err}"
|
|
|
+ )))
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ let atoken = resp.access_token;
|
|
|
+
|
|
|
+ #[derive(Deserialize)]
|
|
|
+ struct UserInfoResponse {
|
|
|
+ id: i64,
|
|
|
+ }
|
|
|
+
|
|
|
+ let resp: UserInfoResponse = match state
|
|
|
+ .client
|
|
|
+ .get(format!(
|
|
|
+ "{base}/user",
|
|
|
+ base = self.config.api_base.as_deref().unwrap_or(DEFAULT_API_BASE)
|
|
|
+ ))
|
|
|
+ .header("Authorization", format!("Bearer {atoken}"))
|
|
|
+ .content_type(surf::http::mime::JSON)
|
|
|
+ .recv_json()
|
|
|
+ .await
|
|
|
+ {
|
|
|
+ Ok(resp) => resp,
|
|
|
+ Err(err) => {
|
|
|
+ return Err(UIDCError::AbortString(format!(
|
|
|
+ "could not parse Github response for token: {err}"
|
|
|
+ )))
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ let user_id = resp.id.to_string();
|
|
|
+
|
|
|
+ let external_auth_map = {
|
|
|
+ let mut lease = state.lease().await?;
|
|
|
+ let Some(realm) = state.db.realms.keyed(realm).get(&mut lease).ok().flatten() else {
|
|
|
+ return Ok(tide::Response::builder(404).body("no such realm").build());
|
|
|
+ };
|
|
|
+
|
|
|
+ realm
|
|
|
+ .external_auth
|
|
|
+ .keyed((
|
|
|
+ &user_id,
|
|
|
+ schema::ExternalAuthProvider::Github.into_serialized(),
|
|
|
+ ))
|
|
|
+ .get(&mut lease)
|
|
|
+ .ok()
|
|
|
+ .flatten()
|
|
|
+ };
|
|
|
+
|
|
|
+ match (query.mode, external_auth_map) {
|
|
|
+ (CallbackRequestType::Login, Some(map)) => {
|
|
|
+ self.handle_matching_login(req, map.internal_user_id, query.redirect.as_str())
|
|
|
+ .await
|
|
|
+ }
|
|
|
+ (CallbackRequestType::Login, None) => self.handle_no_mapping(req, query.redirect).await,
|
|
|
+ (CallbackRequestType::Register, _) => self.handle_registration(req).await,
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl ExternalAuthenticator for GithubAuthenticator {
|
|
|
fn build(_db: &UIDCDatabase, config: &Config) -> Option<Self> {
|
|
|
config.github.as_ref().map(|ghc| Self {
|
|
|
base_url: config.base_url.clone(),
|
|
@@ -92,117 +206,7 @@ impl super::ExternalAuthenticator for GithubAuthenticator {
|
|
|
fn extract_login_state(
|
|
|
&self,
|
|
|
req: UIDCRequest,
|
|
|
- ) -> impl smol::prelude::Future<Output = Result<tide::Response, UIDCError>> {
|
|
|
- async move {
|
|
|
- let state = req.state();
|
|
|
- let realm = req.param("realm").unwrap();
|
|
|
-
|
|
|
- #[derive(Deserialize)]
|
|
|
- struct Query {
|
|
|
- code: String,
|
|
|
- redirect: String,
|
|
|
- mode: CallbackRequestType,
|
|
|
- }
|
|
|
- let Ok(query) = req.query::<Query>() else {
|
|
|
- return Ok(tide::Response::builder(400)
|
|
|
- .body("Query string invalid.")
|
|
|
- .build());
|
|
|
- };
|
|
|
-
|
|
|
- #[derive(Deserialize)]
|
|
|
- struct TokenResponse {
|
|
|
- access_token: String,
|
|
|
- }
|
|
|
-
|
|
|
- let auth = surf::http::auth::BasicAuth::new(
|
|
|
- self.config.client_id.as_str(),
|
|
|
- self.config.client_secret.as_str(),
|
|
|
- );
|
|
|
-
|
|
|
- let resp: TokenResponse = match state
|
|
|
- .client
|
|
|
- .post(
|
|
|
- tide::http::Url::parse_with_params(
|
|
|
- self.config
|
|
|
- .token_url
|
|
|
- .as_deref()
|
|
|
- .unwrap_or(DEFAULT_TOKEN_URL),
|
|
|
- &[
|
|
|
- ("client_id", self.config.client_id.as_str()),
|
|
|
- ("client_secret", self.config.client_secret.as_str()),
|
|
|
- ("code", query.code.as_str()),
|
|
|
- ],
|
|
|
- )
|
|
|
- .expect("couldn't generate token url for github"),
|
|
|
- )
|
|
|
- .header(auth.name(), auth.value())
|
|
|
- .content_type(surf::http::mime::FORM)
|
|
|
- .recv_form()
|
|
|
- .await
|
|
|
- {
|
|
|
- Ok(resp) => resp,
|
|
|
- Err(err) => {
|
|
|
- return Err(UIDCError::AbortString(format!(
|
|
|
- "could not parse Github response for token: {err}"
|
|
|
- )))
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- let atoken = resp.access_token;
|
|
|
-
|
|
|
- #[derive(Deserialize)]
|
|
|
- struct UserInfoResponse {
|
|
|
- id: i64,
|
|
|
- }
|
|
|
-
|
|
|
- let resp: UserInfoResponse = match state
|
|
|
- .client
|
|
|
- .get(format!(
|
|
|
- "{base}/user",
|
|
|
- base = self.config.api_base.as_deref().unwrap_or(DEFAULT_API_BASE)
|
|
|
- ))
|
|
|
- .header("Authorization", format!("Bearer {atoken}"))
|
|
|
- .content_type(surf::http::mime::JSON)
|
|
|
- .recv_json()
|
|
|
- .await
|
|
|
- {
|
|
|
- Ok(resp) => resp,
|
|
|
- Err(err) => {
|
|
|
- return Err(UIDCError::AbortString(format!(
|
|
|
- "could not parse Github response for token: {err}"
|
|
|
- )))
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- let user_id = resp.id.to_string();
|
|
|
-
|
|
|
- let mut lease = state.lease().await?;
|
|
|
-
|
|
|
- let Some(realm) = state.db.realms.keyed(realm).get(&mut lease).ok().flatten() else {
|
|
|
- return Ok(tide::Response::builder(404).body("no such realm").build());
|
|
|
- };
|
|
|
-
|
|
|
- let external_auth_map = realm
|
|
|
- .external_auth
|
|
|
- .keyed((
|
|
|
- &user_id,
|
|
|
- schema::ExternalAuthProvider::Github.into_serialized(),
|
|
|
- ))
|
|
|
- .get(&mut lease)
|
|
|
- .ok()
|
|
|
- .flatten();
|
|
|
-
|
|
|
- drop(lease);
|
|
|
- match (query.mode, external_auth_map) {
|
|
|
- (CallbackRequestType::Login, Some(map)) => {
|
|
|
- self.handle_matching_login(req, map.internal_user_id, query.redirect.as_str())
|
|
|
- .await
|
|
|
- }
|
|
|
- (CallbackRequestType::Login, None) => {
|
|
|
- self.handle_no_mapping(req, query.redirect).await
|
|
|
- }
|
|
|
- (CallbackRequestType::Register, _) => self.handle_registration(req).await,
|
|
|
- }
|
|
|
- }
|
|
|
+ ) -> impl std::future::Future<Output = Result<tide::Response, UIDCError>> {
|
|
|
+ self.do_extract_login_state(req)
|
|
|
}
|
|
|
}
|