mirror of
https://github.com/lldap/lldap.git
synced 2024-11-25 09:06:03 +00:00
app: make it possible to serve lldap behind a sub-path
This commit is contained in:
parent
ec0737c58a
commit
70d85524db
6 changed files with 88 additions and 35 deletions
|
@ -4,7 +4,8 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>LLDAP Administration</title>
|
||||
<script src="/static/main.js" type="module" defer></script>
|
||||
<base href="/">
|
||||
<script src="static/main.js" type="module" defer></script>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-dark-5@1.1.3/dist/css/bootstrap-nightshade.min.css"
|
||||
rel="preload stylesheet"
|
||||
|
@ -33,7 +34,7 @@
|
|||
href="https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/static/style.css" />
|
||||
href="static/style.css" />
|
||||
<script>
|
||||
function inDarkMode(){
|
||||
return darkmode.inDarkMode;
|
||||
|
|
|
@ -268,7 +268,7 @@ impl App {
|
|||
<header class="p-2 mb-3 border-bottom">
|
||||
<div class="container">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
|
||||
<a href="/" class="d-flex align-items-center mt-2 mb-lg-0 me-md-5 text-decoration-none">
|
||||
<a href={yew_router::utils::base_url().unwrap_or("/".to_string())} class="d-flex align-items-center mt-2 mb-lg-0 me-md-5 text-decoration-none">
|
||||
<h2>{"LLDAP"}</h2>
|
||||
</a>
|
||||
|
||||
|
|
|
@ -18,6 +18,10 @@ fn get_claims_from_jwt(jwt: &str) -> Result<JWTClaims> {
|
|||
|
||||
const NO_BODY: Option<()> = None;
|
||||
|
||||
fn base_url() -> String {
|
||||
yew_router::utils::base_url().unwrap_or_default()
|
||||
}
|
||||
|
||||
async fn call_server(
|
||||
url: &str,
|
||||
body: Option<impl Serialize>,
|
||||
|
@ -97,7 +101,7 @@ impl HostService {
|
|||
};
|
||||
let request_body = QueryType::build_query(variables);
|
||||
call_server_json_with_error_message::<graphql_client::Response<_>, _>(
|
||||
"/api/graphql",
|
||||
&(base_url() + "/api/graphql"),
|
||||
Some(request_body),
|
||||
error_message,
|
||||
)
|
||||
|
@ -109,7 +113,7 @@ impl HostService {
|
|||
request: login::ClientLoginStartRequest,
|
||||
) -> Result<Box<login::ServerLoginStartResponse>> {
|
||||
call_server_json_with_error_message(
|
||||
"/auth/opaque/login/start",
|
||||
&(base_url() + "/auth/opaque/login/start"),
|
||||
Some(request),
|
||||
"Could not start authentication: ",
|
||||
)
|
||||
|
@ -118,7 +122,7 @@ impl HostService {
|
|||
|
||||
pub async fn login_finish(request: login::ClientLoginFinishRequest) -> Result<(String, bool)> {
|
||||
call_server_json_with_error_message::<login::ServerLoginResponse, _>(
|
||||
"/auth/opaque/login/finish",
|
||||
&(base_url() + "/auth/opaque/login/finish"),
|
||||
Some(request),
|
||||
"Could not finish authentication",
|
||||
)
|
||||
|
@ -130,7 +134,7 @@ impl HostService {
|
|||
request: registration::ClientRegistrationStartRequest,
|
||||
) -> Result<Box<registration::ServerRegistrationStartResponse>> {
|
||||
call_server_json_with_error_message(
|
||||
"/auth/opaque/register/start",
|
||||
&(base_url() + "/auth/opaque/register/start"),
|
||||
Some(request),
|
||||
"Could not start registration: ",
|
||||
)
|
||||
|
@ -141,7 +145,7 @@ impl HostService {
|
|||
request: registration::ClientRegistrationFinishRequest,
|
||||
) -> Result<()> {
|
||||
call_server_empty_response_with_error_message(
|
||||
"/auth/opaque/register/finish",
|
||||
&(base_url() + "/auth/opaque/register/finish"),
|
||||
Some(request),
|
||||
"Could not finish registration",
|
||||
)
|
||||
|
@ -150,7 +154,7 @@ impl HostService {
|
|||
|
||||
pub async fn refresh() -> Result<(String, bool)> {
|
||||
call_server_json_with_error_message::<login::ServerLoginResponse, _>(
|
||||
"/auth/refresh",
|
||||
&(base_url() + "/auth/refresh"),
|
||||
NO_BODY,
|
||||
"Could not start authentication: ",
|
||||
)
|
||||
|
@ -160,13 +164,21 @@ impl HostService {
|
|||
|
||||
// The `_request` parameter is to make it the same shape as the other functions.
|
||||
pub async fn logout() -> Result<()> {
|
||||
call_server_empty_response_with_error_message("/auth/logout", NO_BODY, "Could not logout")
|
||||
.await
|
||||
call_server_empty_response_with_error_message(
|
||||
&(base_url() + "/auth/logout"),
|
||||
NO_BODY,
|
||||
"Could not logout",
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn reset_password_step1(username: String) -> Result<()> {
|
||||
call_server_empty_response_with_error_message(
|
||||
&format!("/auth/reset/step1/{}", url_escape::encode_query(&username)),
|
||||
&format!(
|
||||
"{}/auth/reset/step1/{}",
|
||||
base_url(),
|
||||
url_escape::encode_query(&username)
|
||||
),
|
||||
NO_BODY,
|
||||
"Could not initiate password reset",
|
||||
)
|
||||
|
@ -177,7 +189,7 @@ impl HostService {
|
|||
token: String,
|
||||
) -> Result<lldap_auth::password_reset::ServerPasswordResetResponse> {
|
||||
call_server_json_with_error_message(
|
||||
&format!("/auth/reset/step2/{}", token),
|
||||
&format!("{}/auth/reset/step2/{}", base_url(), token),
|
||||
NO_BODY,
|
||||
"Could not validate token",
|
||||
)
|
||||
|
@ -185,13 +197,13 @@ impl HostService {
|
|||
}
|
||||
|
||||
pub async fn probe_password_reset() -> Result<bool> {
|
||||
Ok(
|
||||
gloo_net::http::Request::get("/auth/reset/step1/lldap_unlikely_very_long_user_name")
|
||||
.header("Content-Type", "application/json")
|
||||
.send()
|
||||
.await?
|
||||
.status()
|
||||
!= http::StatusCode::NOT_FOUND,
|
||||
Ok(gloo_net::http::Request::get(
|
||||
&(base_url() + "/auth/reset/step1/lldap_unlikely_very_long_user_name"),
|
||||
)
|
||||
.header("Content-Type", "application/json")
|
||||
.send()
|
||||
.await?
|
||||
.status()
|
||||
!= http::StatusCode::NOT_FOUND)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,10 +22,11 @@ pub fn set_cookie(cookie_name: &str, value: &str, expiration: &DateTime<Utc>) ->
|
|||
.map_err(|_| anyhow!("Document is not an HTMLDocument"))
|
||||
})?;
|
||||
let cookie_string = format!(
|
||||
"{}={}; expires={}; sameSite=Strict; path=/",
|
||||
"{}={}; expires={}; sameSite=Strict; path={}/",
|
||||
cookie_name,
|
||||
value,
|
||||
expiration.to_rfc2822()
|
||||
expiration.to_rfc2822(),
|
||||
yew_router::utils::base_url().unwrap_or_default()
|
||||
);
|
||||
doc.set_cookie(&cookie_string)
|
||||
.map_err(|_| anyhow!("Could not set cookie"))
|
||||
|
|
|
@ -112,13 +112,17 @@ where
|
|||
"Invalid refresh token".to_string(),
|
||||
)));
|
||||
}
|
||||
let mut path = data.server_url.path().to_string();
|
||||
if !path.ends_with('/') {
|
||||
path.push('/');
|
||||
};
|
||||
let groups = data.get_readonly_handler().get_user_groups(&user).await?;
|
||||
let token = create_jwt(data.get_tcp_handler(), jwt_key, &user, groups).await;
|
||||
Ok(HttpResponse::Ok()
|
||||
.cookie(
|
||||
Cookie::build("token", token.as_str())
|
||||
.max_age(1.days())
|
||||
.path("/")
|
||||
.path(&path)
|
||||
.http_only(true)
|
||||
.same_site(SameSite::Strict)
|
||||
.finish(),
|
||||
|
@ -239,12 +243,16 @@ where
|
|||
.await;
|
||||
let groups = HashSet::new();
|
||||
let token = create_jwt(data.get_tcp_handler(), &data.jwt_key, &user_id, groups).await;
|
||||
let mut path = data.server_url.path().to_string();
|
||||
if !path.ends_with('/') {
|
||||
path.push('/');
|
||||
};
|
||||
Ok(HttpResponse::Ok()
|
||||
.cookie(
|
||||
Cookie::build("token", token.as_str())
|
||||
.max_age(5.minutes())
|
||||
// Cookie is only valid to reset the password.
|
||||
.path("/auth")
|
||||
.path(format!("{}auth", path))
|
||||
.http_only(true)
|
||||
.same_site(SameSite::Strict)
|
||||
.finish(),
|
||||
|
@ -284,11 +292,15 @@ where
|
|||
for jwt_hash in new_blacklisted_jwt_hashes {
|
||||
jwt_blacklist.insert(jwt_hash);
|
||||
}
|
||||
let mut path = data.server_url.path().to_string();
|
||||
if !path.ends_with('/') {
|
||||
path.push('/');
|
||||
};
|
||||
Ok(HttpResponse::Ok()
|
||||
.cookie(
|
||||
Cookie::build("token", "")
|
||||
.max_age(0.days())
|
||||
.path("/")
|
||||
.path(&path)
|
||||
.http_only(true)
|
||||
.same_site(SameSite::Strict)
|
||||
.finish(),
|
||||
|
@ -296,7 +308,7 @@ where
|
|||
.cookie(
|
||||
Cookie::build("refresh_token", "")
|
||||
.max_age(0.days())
|
||||
.path("/auth")
|
||||
.path(format!("{}auth", path))
|
||||
.http_only(true)
|
||||
.same_site(SameSite::Strict)
|
||||
.finish(),
|
||||
|
@ -351,12 +363,15 @@ where
|
|||
let (refresh_token, max_age) = data.get_tcp_handler().create_refresh_token(name).await?;
|
||||
let token = create_jwt(data.get_tcp_handler(), &data.jwt_key, name, groups).await;
|
||||
let refresh_token_plus_name = refresh_token + "+" + name.as_str();
|
||||
|
||||
let mut path = data.server_url.path().to_string();
|
||||
if !path.ends_with('/') {
|
||||
path.push('/');
|
||||
};
|
||||
Ok(HttpResponse::Ok()
|
||||
.cookie(
|
||||
Cookie::build("token", token.as_str())
|
||||
.max_age(1.days())
|
||||
.path("/")
|
||||
.path(&path)
|
||||
.http_only(true)
|
||||
.same_site(SameSite::Strict)
|
||||
.finish(),
|
||||
|
@ -364,7 +379,7 @@ where
|
|||
.cookie(
|
||||
Cookie::build("refresh_token", refresh_token_plus_name.clone())
|
||||
.max_age(max_age.num_days().days())
|
||||
.path("/auth")
|
||||
.path(format!("{}auth", path))
|
||||
.http_only(true)
|
||||
.same_site(SameSite::Strict)
|
||||
.finish(),
|
||||
|
|
|
@ -12,7 +12,7 @@ use crate::{
|
|||
tcp_backend_handler::*,
|
||||
},
|
||||
};
|
||||
use actix_files::{Files, NamedFile};
|
||||
use actix_files::Files;
|
||||
use actix_http::{header, HttpServiceBuilder};
|
||||
use actix_server::ServerBuilder;
|
||||
use actix_service::map_config;
|
||||
|
@ -21,13 +21,22 @@ use anyhow::{Context, Result};
|
|||
use hmac::Hmac;
|
||||
use sha2::Sha512;
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::RwLock;
|
||||
use tracing::info;
|
||||
|
||||
async fn index() -> actix_web::Result<NamedFile> {
|
||||
let path = PathBuf::from(r"app/index.html");
|
||||
Ok(NamedFile::open(path)?)
|
||||
async fn index<Backend>(data: web::Data<AppState<Backend>>) -> actix_web::Result<impl Responder> {
|
||||
let mut file = std::fs::read_to_string(r"./app/index.html")?;
|
||||
|
||||
if data.server_url.path() != "/" {
|
||||
file = file.replace(
|
||||
"<base href=\"/\">",
|
||||
format!("<base href=\"{}/\">", data.server_url.path()).as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(file
|
||||
.customize()
|
||||
.insert_header((header::CONTENT_TYPE, "text/html; charset=utf-8")))
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
|
@ -68,6 +77,20 @@ pub(crate) fn error_to_http_response(error: TcpError) -> HttpResponse {
|
|||
.body(error.to_string())
|
||||
}
|
||||
|
||||
async fn main_js_handler<Backend>(
|
||||
data: web::Data<AppState<Backend>>,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
let mut file = std::fs::read_to_string(r"./app/static/main.js")?;
|
||||
|
||||
if data.server_url.path() != "/" {
|
||||
file = file.replace("/pkg/", format!("{}/pkg/", data.server_url.path()).as_str());
|
||||
}
|
||||
|
||||
Ok(file
|
||||
.customize()
|
||||
.insert_header((header::CONTENT_TYPE, "text/javascript")))
|
||||
}
|
||||
|
||||
async fn wasm_handler() -> actix_web::Result<impl Responder> {
|
||||
Ok(actix_files::NamedFile::open_async("./app/pkg/lldap_app_bg.wasm").await?)
|
||||
}
|
||||
|
@ -118,6 +141,7 @@ fn http_config<Backend>(
|
|||
web::resource("/pkg/lldap_app_bg.wasm.gz").route(web::route().to(wasm_handler_compressed)),
|
||||
)
|
||||
.service(web::resource("/pkg/lldap_app_bg.wasm").route(web::route().to(wasm_handler)))
|
||||
.service(web::resource("/static/main.js").route(web::route().to(main_js_handler::<Backend>)))
|
||||
// Serve the /pkg path with the compiled WASM app.
|
||||
.service(Files::new("/pkg", "./app/pkg"))
|
||||
// Serve static files
|
||||
|
@ -125,7 +149,7 @@ fn http_config<Backend>(
|
|||
// Serve static fonts
|
||||
.service(Files::new("/static/fonts", "./app/static/fonts"))
|
||||
// Default to serve index.html for unknown routes, to support routing.
|
||||
.default_service(web::route().guard(guard::Get()).to(index));
|
||||
.default_service(web::route().guard(guard::Get()).to(index::<Backend>));
|
||||
}
|
||||
|
||||
pub(crate) struct AppState<Backend> {
|
||||
|
|
Loading…
Reference in a new issue