better-auth-api 1.0.0-alpha.5

Plugin implementations for better-auth
#![allow(
    clippy::expect_used,
    reason = "Fixture setup and response assertions require successful fallible construction."
)]
use super::super::{callback, generic_profile};
use super::*;
use better_auth_core::{
    AuthPlugin, AuthRequest, AuthResponse, Headers, HttpMethod, store::StatelessSchema,
};
use harness::Harness;

fn request(method: HttpMethod, path: &str) -> AuthRequest {
    let mut request = AuthRequest::new(method, path);
    request.headers = HashMap::from([
        ("origin".into(), ORIGIN.into()),
        ("user-agent".into(), "native-profile-contract".into()),
    ]);
    request
}

impl Harness {
    async fn dispatch(&self, request: AuthRequest, state: &State) -> AuthResult<AuthResponse> {
        let is_callback = request.path.starts_with("/callback/");
        let route = AuthPlugin::<StatelessSchema>::routes(&self.plugin)
            .into_iter()
            .find(|route| route.matches(request.method(), request.path()))
            .unwrap();
        self.dispatcher
            .native(
                request,
                route,
                &self.context,
                |request, context| async move {
                    if is_callback {
                        callback::handle_callback(
                            self.plugin.resolved_config().await?,
                            state.scenario.provider_id(),
                            &request,
                            &context,
                        )
                        .await
                    } else {
                        self.plugin
                            .on_request(&request, &context)
                            .await?
                            .ok_or_else(|| {
                                AuthError::internal("Native profile endpoint did not match")
                            })
                    }
                },
            )
            .await
    }

    async fn invoke(&self, state: &State) -> AuthResult<AuthResponse> {
        if state.scenario.operation == "account-info" {
            let mut request =
                AuthRequest::new(HttpMethod::Get, "/account-info").with_optional_headers(None);
            request.query = Some(json!({"accountId": "stored-account", "userId": "owner"}));
            return self.dispatch(request, state).await;
        }
        let mut signin = request(HttpMethod::Post, "/sign-in/social");
        let body = if state.scenario.operation == "direct" {
            json!({"provider": state.scenario.provider_id(), "idToken": {
                "token": ID_TOKEN, "nonce": "native-profile-nonce", "accessToken": ACCESS_TOKEN,
            }})
        } else {
            assert_eq!(state.scenario.operation, "callback");
            json!({"provider": state.scenario.provider_id(), "callbackURL": format!("{ORIGIN}/complete"),
                "errorCallbackURL": format!("{ORIGIN}/failed"), "disableRedirect": true})
        };
        signin.body = Some(serde_json::to_vec(&body)?);
        let started = self.dispatch(signin, state).await?;
        if state.scenario.operation == "direct" {
            return Ok(started);
        }
        let url = started.body.field_value()?.model_property("url")?;
        let url = url::Url::parse(url.as_str().unwrap()).unwrap();
        let state_id = url
            .query_pairs()
            .find(|(key, _)| key == "state")
            .unwrap()
            .1
            .into_owned();
        state.remember(&state_id, "oauth-state");
        let mut callback = request(
            HttpMethod::Get,
            &format!("/callback/{}", state.scenario.provider_id()),
        );
        callback.headers.insert(
            "cookie".into(),
            started
                .headers
                .get_all("set-cookie")
                .map(|cookie| cookie.split(';').next().unwrap())
                .collect::<Vec<_>>()
                .join("; "),
        );
        callback.query = Some(json!({"code": "native-profile-code", "state": state_id}));
        match self.dispatch(callback, state).await {
            Ok(response) => response.into_http_response(),
            Err(error) if error.is_api_error()? => error
                .to_auth_response()
                .expect("fixture response succeeds")
                .into_http_response(),
            Err(error) => Err(error),
        }
    }
}

pub(super) async fn capture(scenario: Scenario) -> AuthResult<Value> {
    let state = State::new(scenario);
    let sample = Harness::new(&state).await?;
    let before = capture::Before::capture(&state, &sample.store)?;
    let observed = state.clone();
    let observer: Arc<dyn Fn(&FieldValue) + Send + Sync> = Arc::new(move |value| {
        observed.tracking.lock().unwrap().info = Some(value.clone());
    });
    let result = generic_profile::PROFILE_OBSERVER
        .scope(observer, sample.invoke(&state))
        .await;
    let (value, outcome, observed_headers) = match result {
        Ok(response) => {
            let headers = headers::observe(Some(&response.headers), &state);
            if state.scenario.operation == "callback" {
                (
                    FieldValue::Undefined,
                    json!({"returned": true, "kind": "http", "status": response.status,
                    "body": String::from_utf8(response.body.bytes()?.into_owned()).unwrap()}),
                    headers,
                )
            } else {
                (
                    response.body.field_value()?,
                    json!({"returned": true, "kind": "native"}),
                    headers,
                )
            }
        }
        Err(error) => {
            let mut outcome = json!({"returned": false});
            outcome.as_object_mut().unwrap().extend(
                observe::error_value(&error, &state)?
                    .as_object()
                    .unwrap()
                    .clone(),
            );
            let explicit = error.headers_value()?;
            let headers = if explicit.is_truthy() {
                Some(Headers::from_field_value(&explicit)?)
            } else {
                None
            };
            (
                FieldValue::Undefined,
                outcome,
                headers::observe(headers.as_ref(), &state),
            )
        }
    };
    before.finish(&state, &sample.store, value, outcome, observed_headers)
}