Add authentication from upstream providers #107
@ -19,12 +19,12 @@ use crate::data::session_identity::{SessionIdentity, SessionStatus};
|
||||
use crate::data::user::User;
|
||||
use crate::data::webauthn_manager::WebAuthManagerReq;
|
||||
|
||||
struct BaseLoginPage<'a> {
|
||||
danger: Option<String>,
|
||||
success: Option<String>,
|
||||
page_title: &'static str,
|
||||
app_name: &'static str,
|
||||
redirect_uri: &'a LoginRedirect,
|
||||
pub struct BaseLoginPage<'a> {
|
||||
pub danger: Option<String>,
|
||||
pub success: Option<String>,
|
||||
pub page_title: &'static str,
|
||||
pub app_name: &'static str,
|
||||
pub redirect_uri: &'a LoginRedirect,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
|
@ -2,16 +2,43 @@ use std::sync::Arc;
|
||||
|
||||
use actix::Addr;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use askama::Template;
|
||||
|
||||
use crate::actors::providers_states_actor;
|
||||
use crate::actors::providers_states_actor::{ProviderLoginState, ProvidersStatesActor};
|
||||
use crate::constants::APP_NAME;
|
||||
use crate::controllers::base_controller::{build_fatal_error_page, redirect_user};
|
||||
use crate::controllers::login_controller::BaseLoginPage;
|
||||
use crate::data::action_logger::{Action, ActionLogger};
|
||||
use crate::data::login_redirect::LoginRedirect;
|
||||
use crate::data::provider::{ProviderID, ProvidersManager};
|
||||
use crate::data::provider_configuration::ProviderConfigurationHelper;
|
||||
use crate::data::remote_ip::RemoteIP;
|
||||
|
||||
#[derive(askama::Template)]
|
||||
#[template(path = "login/prov_login_error.html")]
|
||||
struct ProviderLoginError<'a> {
|
||||
_p: BaseLoginPage<'a>,
|
||||
message: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> ProviderLoginError<'a> {
|
||||
pub fn get(message: &'a str, redirect_uri: &'a LoginRedirect) -> String {
|
||||
Self {
|
||||
_p: BaseLoginPage {
|
||||
danger: None,
|
||||
success: None,
|
||||
page_title: "Upstream login",
|
||||
app_name: APP_NAME,
|
||||
redirect_uri,
|
||||
},
|
||||
message,
|
||||
}
|
||||
.render()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct StartLoginQuery {
|
||||
#[serde(default)]
|
||||
@ -20,7 +47,6 @@ pub struct StartLoginQuery {
|
||||
}
|
||||
|
||||
/// Start user authentication using a provider
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn start_login(
|
||||
remote_ip: RemoteIP,
|
||||
providers: web::Data<Arc<ProvidersManager>>,
|
||||
@ -71,3 +97,66 @@ pub async fn start_login(
|
||||
// Redirect user
|
||||
redirect_user(&url)
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct FinishLoginSuccess {
|
||||
code: String,
|
||||
state: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct FinishLoginError {
|
||||
error: String,
|
||||
error_description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct FinishLoginQuery {
|
||||
#[serde(flatten)]
|
||||
success: Option<FinishLoginSuccess>,
|
||||
#[serde(flatten)]
|
||||
error: Option<FinishLoginError>,
|
||||
}
|
||||
|
||||
/// Finish user authentication using a provider
|
||||
pub async fn finish_login(
|
||||
remote_ip: RemoteIP,
|
||||
providers: web::Data<Arc<ProvidersManager>>,
|
||||
states: web::Data<Addr<ProvidersStatesActor>>,
|
||||
query: web::Query<FinishLoginQuery>,
|
||||
logger: ActionLogger,
|
||||
) -> impl Responder {
|
||||
let query = match query.0.success {
|
||||
Some(q) => q,
|
||||
None => {
|
||||
let error_message = query
|
||||
.0
|
||||
.error
|
||||
.map(|e| e.error_description.unwrap_or(e.error))
|
||||
.unwrap_or("Authentication failed (unspecified error)!".to_string());
|
||||
|
||||
return HttpResponse::Unauthorized().body(ProviderLoginError::get(
|
||||
&error_message,
|
||||
&LoginRedirect::default(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Get & consume state
|
||||
let state = states
|
||||
.send(providers_states_actor::ConsumeState {
|
||||
ip: remote_ip.0,
|
||||
state_id: query.state.clone(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// TODO : rate limiting
|
||||
// TODO : finish login, get user information
|
||||
// TODO : check token signature
|
||||
// TODO : check if user is authorized to access application
|
||||
// TODO : check if 2FA is enabled
|
||||
// TODO : redirect user to login route
|
||||
// TODO : add proper logging
|
||||
HttpResponse::Ok().body("continue")
|
||||
}
|
||||
|
@ -166,6 +166,10 @@ async fn main() -> std::io::Result<()> {
|
||||
"/login_with_prov",
|
||||
web::get().to(providers_controller::start_login),
|
||||
)
|
||||
.route(
|
||||
OIDC_PROVIDER_CB_URI,
|
||||
web::get().to(providers_controller::finish_login),
|
||||
)
|
||||
// Settings routes
|
||||
.route(
|
||||
"/settings",
|
||||
|
13
templates/login/prov_login_error.html
Normal file
13
templates/login/prov_login_error.html
Normal file
@ -0,0 +1,13 @@
|
||||
{% extends "base_login_page.html" %}
|
||||
{% block content %}
|
||||
|
||||
<div class="alert alert-danger" style="margin-bottom: 10px;">
|
||||
<strong>Authentication failed!</strong>
|
||||
|
||||
<p style="margin-top: 10px; text-align: justify;">{{ message }}</p>
|
||||
</div>
|
||||
|
||||
<a href="/login?redirect={{ _p.redirect_uri.get_encoded() }}">Go back to login</a>
|
||||
|
||||
|
||||
{% endblock content %}
|
Loading…
Reference in New Issue
Block a user