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::user::User;
|
||||||
use crate::data::webauthn_manager::WebAuthManagerReq;
|
use crate::data::webauthn_manager::WebAuthManagerReq;
|
||||||
|
|
||||||
struct BaseLoginPage<'a> {
|
pub struct BaseLoginPage<'a> {
|
||||||
danger: Option<String>,
|
pub danger: Option<String>,
|
||||||
success: Option<String>,
|
pub success: Option<String>,
|
||||||
page_title: &'static str,
|
pub page_title: &'static str,
|
||||||
app_name: &'static str,
|
pub app_name: &'static str,
|
||||||
redirect_uri: &'a LoginRedirect,
|
pub redirect_uri: &'a LoginRedirect,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
|
@ -2,16 +2,43 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
use actix_web::{web, HttpResponse, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
|
use askama::Template;
|
||||||
|
|
||||||
use crate::actors::providers_states_actor;
|
use crate::actors::providers_states_actor;
|
||||||
use crate::actors::providers_states_actor::{ProviderLoginState, ProvidersStatesActor};
|
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::base_controller::{build_fatal_error_page, redirect_user};
|
||||||
|
use crate::controllers::login_controller::BaseLoginPage;
|
||||||
use crate::data::action_logger::{Action, ActionLogger};
|
use crate::data::action_logger::{Action, ActionLogger};
|
||||||
use crate::data::login_redirect::LoginRedirect;
|
use crate::data::login_redirect::LoginRedirect;
|
||||||
use crate::data::provider::{ProviderID, ProvidersManager};
|
use crate::data::provider::{ProviderID, ProvidersManager};
|
||||||
use crate::data::provider_configuration::ProviderConfigurationHelper;
|
use crate::data::provider_configuration::ProviderConfigurationHelper;
|
||||||
use crate::data::remote_ip::RemoteIP;
|
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)]
|
#[derive(serde::Deserialize)]
|
||||||
pub struct StartLoginQuery {
|
pub struct StartLoginQuery {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@ -20,7 +47,6 @@ pub struct StartLoginQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Start user authentication using a provider
|
/// Start user authentication using a provider
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub async fn start_login(
|
pub async fn start_login(
|
||||||
remote_ip: RemoteIP,
|
remote_ip: RemoteIP,
|
||||||
providers: web::Data<Arc<ProvidersManager>>,
|
providers: web::Data<Arc<ProvidersManager>>,
|
||||||
@ -71,3 +97,66 @@ pub async fn start_login(
|
|||||||
// Redirect user
|
// Redirect user
|
||||||
redirect_user(&url)
|
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",
|
"/login_with_prov",
|
||||||
web::get().to(providers_controller::start_login),
|
web::get().to(providers_controller::start_login),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
OIDC_PROVIDER_CB_URI,
|
||||||
|
web::get().to(providers_controller::finish_login),
|
||||||
|
)
|
||||||
// Settings routes
|
// Settings routes
|
||||||
.route(
|
.route(
|
||||||
"/settings",
|
"/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