mirror of
https://github.com/nitnelave/lldap.git
synced 2023-04-12 14:25:13 +00:00
290 lines
9.0 KiB
Rust
290 lines
9.0 KiB
Rust
use super::cookies::set_cookie;
|
|
use anyhow::{anyhow, Context, Result};
|
|
use graphql_client::GraphQLQuery;
|
|
use lldap_auth::{login, registration, JWTClaims};
|
|
|
|
use yew::callback::Callback;
|
|
use yew::format::Json;
|
|
use yew::services::fetch::{Credentials, FetchOptions, FetchService, FetchTask, Request, Response};
|
|
|
|
#[derive(Default)]
|
|
pub struct HostService {}
|
|
|
|
fn get_default_options() -> FetchOptions {
|
|
FetchOptions {
|
|
credentials: Some(Credentials::SameOrigin),
|
|
..FetchOptions::default()
|
|
}
|
|
}
|
|
|
|
fn get_claims_from_jwt(jwt: &str) -> Result<JWTClaims> {
|
|
use jwt::*;
|
|
let token = Token::<header::Header, JWTClaims, token::Unverified>::parse_unverified(jwt)?;
|
|
Ok(token.claims().clone())
|
|
}
|
|
|
|
fn create_handler<Resp, CallbackResult, F>(
|
|
callback: Callback<Result<CallbackResult>>,
|
|
handler: F,
|
|
) -> Callback<Response<Result<Resp>>>
|
|
where
|
|
F: Fn(http::StatusCode, Resp) -> Result<CallbackResult> + 'static,
|
|
CallbackResult: 'static,
|
|
{
|
|
Callback::once(move |response: Response<Result<Resp>>| {
|
|
let (meta, maybe_data) = response.into_parts();
|
|
let message = maybe_data
|
|
.context("Could not reach server")
|
|
.and_then(|data| handler(meta.status, data));
|
|
callback.emit(message)
|
|
})
|
|
}
|
|
|
|
struct RequestBody<T>(T);
|
|
|
|
impl<'a, R> From<&'a R> for RequestBody<Json<&'a R>>
|
|
where
|
|
R: serde::ser::Serialize,
|
|
{
|
|
fn from(request: &'a R) -> Self {
|
|
Self(Json(request))
|
|
}
|
|
}
|
|
|
|
impl From<yew::format::Nothing> for RequestBody<yew::format::Nothing> {
|
|
fn from(request: yew::format::Nothing) -> Self {
|
|
Self(request)
|
|
}
|
|
}
|
|
|
|
fn call_server<Req, CallbackResult, F, RB>(
|
|
url: &str,
|
|
request: RB,
|
|
callback: Callback<Result<CallbackResult>>,
|
|
error_message: &'static str,
|
|
parse_response: F,
|
|
) -> Result<FetchTask>
|
|
where
|
|
F: Fn(String) -> Result<CallbackResult> + 'static,
|
|
CallbackResult: 'static,
|
|
RB: Into<RequestBody<Req>>,
|
|
Req: Into<yew::format::Text>,
|
|
{
|
|
let request = {
|
|
// If the request type is empty (if the size is 0), it's a get.
|
|
if std::mem::size_of::<RB>() == 0 {
|
|
Request::get(url)
|
|
} else {
|
|
Request::post(url)
|
|
}
|
|
}
|
|
.header("Content-Type", "application/json")
|
|
.body(request.into().0)?;
|
|
let handler = create_handler(callback, move |status: http::StatusCode, data: String| {
|
|
if status.is_success() {
|
|
parse_response(data)
|
|
} else {
|
|
Err(anyhow!("{}[{}]: {}", error_message, status, data))
|
|
}
|
|
});
|
|
FetchService::fetch_with_options(request, get_default_options(), handler)
|
|
}
|
|
|
|
fn call_server_json_with_error_message<CallbackResult, RB, Req>(
|
|
url: &str,
|
|
request: RB,
|
|
callback: Callback<Result<CallbackResult>>,
|
|
error_message: &'static str,
|
|
) -> Result<FetchTask>
|
|
where
|
|
CallbackResult: serde::de::DeserializeOwned + 'static,
|
|
RB: Into<RequestBody<Req>>,
|
|
Req: Into<yew::format::Text>,
|
|
{
|
|
call_server(url, request, callback, error_message, |data: String| {
|
|
serde_json::from_str(&data).context("Could not parse response")
|
|
})
|
|
}
|
|
|
|
fn call_server_empty_response_with_error_message<RB, Req>(
|
|
url: &str,
|
|
request: RB,
|
|
callback: Callback<Result<()>>,
|
|
error_message: &'static str,
|
|
) -> Result<FetchTask>
|
|
where
|
|
RB: Into<RequestBody<Req>>,
|
|
Req: Into<yew::format::Text>,
|
|
{
|
|
call_server(
|
|
url,
|
|
request,
|
|
callback,
|
|
error_message,
|
|
|_data: String| Ok(()),
|
|
)
|
|
}
|
|
|
|
impl HostService {
|
|
pub fn graphql_query<QueryType>(
|
|
variables: QueryType::Variables,
|
|
callback: Callback<Result<QueryType::ResponseData>>,
|
|
error_message: &'static str,
|
|
) -> Result<FetchTask>
|
|
where
|
|
QueryType: GraphQLQuery + 'static,
|
|
{
|
|
let unwrap_graphql_response = |graphql_client::Response { data, errors }| {
|
|
data.ok_or_else(|| {
|
|
anyhow!(
|
|
"Errors: [{}]",
|
|
errors
|
|
.unwrap_or_default()
|
|
.iter()
|
|
.map(ToString::to_string)
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)
|
|
})
|
|
};
|
|
let parse_graphql_response = move |data: String| {
|
|
serde_json::from_str(&data)
|
|
.context("Could not parse response")
|
|
.and_then(unwrap_graphql_response)
|
|
};
|
|
let request_body = QueryType::build_query(variables);
|
|
call_server(
|
|
"/api/graphql",
|
|
&request_body,
|
|
callback,
|
|
error_message,
|
|
parse_graphql_response,
|
|
)
|
|
}
|
|
|
|
pub fn login_start(
|
|
request: login::ClientLoginStartRequest,
|
|
callback: Callback<Result<Box<login::ServerLoginStartResponse>>>,
|
|
) -> Result<FetchTask> {
|
|
call_server_json_with_error_message(
|
|
"/auth/opaque/login/start",
|
|
&request,
|
|
callback,
|
|
"Could not start authentication: ",
|
|
)
|
|
}
|
|
|
|
pub fn login_finish(
|
|
request: login::ClientLoginFinishRequest,
|
|
callback: Callback<Result<(String, bool)>>,
|
|
) -> Result<FetchTask> {
|
|
let set_cookies = |jwt_claims: JWTClaims| {
|
|
let is_admin = jwt_claims.groups.contains("lldap_admin");
|
|
set_cookie("user_id", &jwt_claims.user, &jwt_claims.exp)
|
|
.map(|_| set_cookie("is_admin", &is_admin.to_string(), &jwt_claims.exp))
|
|
.map(|_| (jwt_claims.user.clone(), is_admin))
|
|
.context("Error clearing cookie")
|
|
};
|
|
let parse_token = move |data: String| {
|
|
serde_json::from_str::<login::ServerLoginResponse>(&data)
|
|
.context("Could not parse response")
|
|
.and_then(|r| {
|
|
get_claims_from_jwt(r.token.as_str())
|
|
.context("Could not parse response")
|
|
.and_then(set_cookies)
|
|
})
|
|
};
|
|
call_server(
|
|
"/auth/opaque/login/finish",
|
|
&request,
|
|
callback,
|
|
"Could not finish authentication",
|
|
parse_token,
|
|
)
|
|
}
|
|
|
|
pub fn register_start(
|
|
request: registration::ClientRegistrationStartRequest,
|
|
callback: Callback<Result<Box<registration::ServerRegistrationStartResponse>>>,
|
|
) -> Result<FetchTask> {
|
|
call_server_json_with_error_message(
|
|
"/auth/opaque/register/start",
|
|
&request,
|
|
callback,
|
|
"Could not start registration: ",
|
|
)
|
|
}
|
|
|
|
pub fn register_finish(
|
|
request: registration::ClientRegistrationFinishRequest,
|
|
callback: Callback<Result<()>>,
|
|
) -> Result<FetchTask> {
|
|
call_server_empty_response_with_error_message(
|
|
"/auth/opaque/register/finish",
|
|
&request,
|
|
callback,
|
|
"Could not finish registration",
|
|
)
|
|
}
|
|
|
|
pub fn refresh(_request: (), callback: Callback<Result<(String, bool)>>) -> Result<FetchTask> {
|
|
let set_cookies = |jwt_claims: JWTClaims| {
|
|
let is_admin = jwt_claims.groups.contains("lldap_admin");
|
|
set_cookie("user_id", &jwt_claims.user, &jwt_claims.exp)
|
|
.map(|_| set_cookie("is_admin", &is_admin.to_string(), &jwt_claims.exp))
|
|
.map(|_| (jwt_claims.user.clone(), is_admin))
|
|
.context("Error clearing cookie")
|
|
};
|
|
let parse_token = move |data: String| {
|
|
serde_json::from_str::<login::ServerLoginResponse>(&data)
|
|
.context("Could not parse response")
|
|
.and_then(|r| {
|
|
get_claims_from_jwt(r.token.as_str())
|
|
.context("Could not parse response")
|
|
.and_then(set_cookies)
|
|
})
|
|
};
|
|
call_server(
|
|
"/auth/refresh",
|
|
yew::format::Nothing,
|
|
callback,
|
|
"Could not start authentication: ",
|
|
parse_token,
|
|
)
|
|
}
|
|
|
|
// The `_request` parameter is to make it the same shape as the other functions.
|
|
pub fn logout(_request: (), callback: Callback<Result<()>>) -> Result<FetchTask> {
|
|
call_server_empty_response_with_error_message(
|
|
"/auth/logout",
|
|
yew::format::Nothing,
|
|
callback,
|
|
"Could not logout",
|
|
)
|
|
}
|
|
|
|
pub fn reset_password_step1(
|
|
username: &str,
|
|
callback: Callback<Result<()>>,
|
|
) -> Result<FetchTask> {
|
|
call_server_empty_response_with_error_message(
|
|
&format!("/auth/reset/step1/{}", url_escape::encode_query(username)),
|
|
yew::format::Nothing,
|
|
callback,
|
|
"Could not initiate password reset",
|
|
)
|
|
}
|
|
|
|
pub fn reset_password_step2(
|
|
token: &str,
|
|
callback: Callback<Result<lldap_auth::password_reset::ServerPasswordResetResponse>>,
|
|
) -> Result<FetchTask> {
|
|
call_server_json_with_error_message(
|
|
&format!("/auth/reset/step2/{}", token),
|
|
yew::format::Nothing,
|
|
callback,
|
|
"Could not validate token",
|
|
)
|
|
}
|
|
}
|