169 lines
5.0 KiB
Rust
169 lines
5.0 KiB
Rust
use crate::app_config::AppConfig;
|
|
use crate::constants::{STATE_KEY, USER_SESSION_KEY};
|
|
use crate::server::{HttpFailure, HttpResult};
|
|
use crate::user::{User, UserConfig, UserID};
|
|
use crate::utils;
|
|
use actix_session::Session;
|
|
use actix_web::{web, HttpResponse};
|
|
use askama::Template;
|
|
use light_openid::primitives::OpenIDConfig;
|
|
|
|
/// Static assets
|
|
#[derive(rust_embed::Embed)]
|
|
#[folder = "assets/"]
|
|
struct Assets;
|
|
|
|
/// Serve static file
|
|
pub async fn static_file(path: web::Path<String>) -> HttpResult {
|
|
match Assets::get(path.as_ref()) {
|
|
Some(content) => Ok(HttpResponse::Ok()
|
|
.content_type(
|
|
mime_guess::from_path(path.as_str())
|
|
.first_or_octet_stream()
|
|
.as_ref(),
|
|
)
|
|
.body(content.data.into_owned())),
|
|
None => Ok(HttpResponse::NotFound().body("404 Not Found")),
|
|
}
|
|
}
|
|
|
|
#[derive(askama::Template)]
|
|
#[template(path = "index.html")]
|
|
struct HomeTemplate {
|
|
name: String,
|
|
matrix_token: String,
|
|
success_message: Option<String>,
|
|
error_message: Option<String>,
|
|
}
|
|
|
|
/// Update matrix token request
|
|
#[derive(serde::Deserialize)]
|
|
pub struct UpdateMatrixToken {
|
|
new_matrix_token: Option<String>,
|
|
}
|
|
|
|
/// Main route
|
|
pub async fn home(
|
|
session: Session,
|
|
update_matrix_token: Option<web::Form<UpdateMatrixToken>>,
|
|
) -> HttpResult {
|
|
// Get user information, requesting authentication if information is missing
|
|
let Some(user): Option<User> = session.get(USER_SESSION_KEY)? else {
|
|
// Generate auth state
|
|
let state = utils::rand_str(50);
|
|
session.insert(STATE_KEY, &state)?;
|
|
|
|
let oidc = AppConfig::get().openid_provider();
|
|
let config = OpenIDConfig::load_from_url(oidc.configuration_url)
|
|
.await
|
|
.map_err(HttpFailure::OpenID)?;
|
|
|
|
let auth_url = config.gen_authorization_url(oidc.client_id, &state, &oidc.redirect_url);
|
|
|
|
return Ok(HttpResponse::Found()
|
|
.append_header(("location", auth_url))
|
|
.finish());
|
|
};
|
|
|
|
let mut success_message = None;
|
|
let mut error_message = None;
|
|
|
|
// Retrieve user configuration
|
|
let mut config = UserConfig::load(&user.id)
|
|
.await
|
|
.map_err(HttpFailure::FetchUserConfig)?;
|
|
|
|
// Update matrix token, if requested
|
|
if let Some(update_matrix_token) = update_matrix_token {
|
|
if let Some(t) = update_matrix_token.0.new_matrix_token {
|
|
if t.len() < 3 {
|
|
error_message = Some("Specified Matrix token is too short!".to_string());
|
|
} else {
|
|
// TODO : invalidate all existing connections
|
|
config.matrix_token = t;
|
|
config.save().await?;
|
|
success_message = Some("Matrix token was successfully updated!".to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Render page
|
|
Ok(HttpResponse::Ok()
|
|
.insert_header(("content-type", "text/html"))
|
|
.body(
|
|
HomeTemplate {
|
|
name: user.name,
|
|
matrix_token: config.obfuscated_matrix_token(),
|
|
success_message,
|
|
error_message,
|
|
}
|
|
.render()
|
|
.unwrap(),
|
|
))
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct AuthCallbackQuery {
|
|
code: String,
|
|
state: String,
|
|
}
|
|
|
|
/// Authenticate user callback
|
|
pub async fn oidc_cb(session: Session, query: web::Query<AuthCallbackQuery>) -> HttpResult {
|
|
if session.get(STATE_KEY)? != Some(query.state.to_string()) {
|
|
return Ok(HttpResponse::BadRequest()
|
|
.append_header(("content-type", "text/html"))
|
|
.body("State mismatch! <a href='/'>Try again</a>"));
|
|
}
|
|
|
|
let oidc = AppConfig::get().openid_provider();
|
|
let config = OpenIDConfig::load_from_url(oidc.configuration_url)
|
|
.await
|
|
.map_err(HttpFailure::OpenID)?;
|
|
|
|
let (token, _) = match config
|
|
.request_token(
|
|
oidc.client_id,
|
|
oidc.client_secret,
|
|
&query.code,
|
|
&oidc.redirect_url,
|
|
)
|
|
.await
|
|
{
|
|
Ok(t) => t,
|
|
Err(e) => {
|
|
log::error!("Failed to request user token! {e}");
|
|
|
|
return Ok(HttpResponse::BadRequest()
|
|
.append_header(("content-type", "text/html"))
|
|
.body("Authentication failed! <a href='/'>Try again</a>"));
|
|
}
|
|
};
|
|
|
|
let (user, _) = config
|
|
.request_user_info(&token)
|
|
.await
|
|
.map_err(HttpFailure::OpenID)?;
|
|
|
|
let user = User {
|
|
id: UserID(user.sub),
|
|
name: user.name.unwrap_or("no_name".to_string()),
|
|
email: user.email.unwrap_or("no@mail.com".to_string()),
|
|
};
|
|
log::info!("Successful authentication as {:?}", user);
|
|
session.insert(USER_SESSION_KEY, user)?;
|
|
|
|
Ok(HttpResponse::Found()
|
|
.insert_header(("location", "/"))
|
|
.finish())
|
|
}
|
|
|
|
/// De-authenticate user
|
|
pub async fn sign_out(session: Session) -> HttpResult {
|
|
session.remove(USER_SESSION_KEY);
|
|
|
|
Ok(HttpResponse::Found()
|
|
.insert_header(("location", "/"))
|
|
.finish())
|
|
}
|