1use wasm_bindgen::JsValue;
2use worker::{Fetch, Headers, Method, Request, RequestInit, send::SendFuture};
3
4use crate::{
5 Ctx, Error, Result,
6 oauth::{GITHUB, Profile, Provider, parse_profile, parse_token_response, token_request_body},
7};
8
9const USER_AGENT: &str = "ocre (https://github.com/tgeselle/ocre.rs)";
11
12pub fn exchange_code(
38 ctx: &Ctx,
39 provider: &'static Provider,
40 redirect_uri: &str,
41 code: &str,
42 verifier: &str,
43) -> impl Future<Output = Result<String>> + Send + use<> {
44 let env = ctx.env().clone();
45 let (redirect_uri, code, verifier) = (redirect_uri.to_owned(), code.to_owned(), verifier.to_owned());
46 SendFuture::new(async move {
47 let client_id = super::secrets::require(&env, provider.client_id_secret).await?;
48 let client_secret = super::secrets::require(&env, provider.client_secret_secret).await?;
49 let body = token_request_body(&client_id, &client_secret, &redirect_uri, &code, &verifier);
50 let headers = Headers::new();
51 headers.set("Accept", "application/json")?;
52 headers.set("Content-Type", "application/x-www-form-urlencoded")?;
53 let mut init = RequestInit::new();
54 init.with_method(Method::Post).with_headers(headers).with_body(Some(JsValue::from_str(&body)));
55 let mut response = Fetch::Request(Request::new_with_init(provider.token_url, &init)?).send().await?;
56 let status = response.status_code();
57 parse_token_response(provider, status, &response.text().await?)
58 })
59}
60
61pub fn profile(
82 provider: &'static Provider,
83 access_token: &str,
84) -> impl Future<Output = Result<Profile>> + Send + use<> {
85 let access_token = access_token.to_owned();
86 SendFuture::new(async move {
87 let user = get_json(provider, provider.userinfo_url, &access_token).await?;
88 let emails = if provider == &GITHUB {
89 Some(get_json(provider, "https://api.github.com/user/emails", &access_token).await?)
90 } else {
91 None
92 };
93 parse_profile(provider, &user, emails.as_deref())
94 })
95}
96
97async fn get_json(provider: &Provider, url: &str, access_token: &str) -> Result<String> {
98 let headers = Headers::new();
99 headers.set("Authorization", &format!("Bearer {access_token}"))?;
100 headers.set("Accept", "application/json")?;
101 headers.set("User-Agent", USER_AGENT)?;
102 let mut init = RequestInit::new();
103 init.with_method(Method::Get).with_headers(headers);
104 let mut response = Fetch::Request(Request::new_with_init(url, &init)?).send().await?;
105 let (status, body) = (response.status_code(), response.text().await?);
106 if (200..300).contains(&status) {
107 Ok(body)
108 } else {
109 Err(Error::internal(format!("{} answered {status} for {url}: {body}", provider.name)))
110 }
111}