Add authentication from upstream providers #107

Merged
pierre merged 25 commits from feat-upstream-providers into master 2023-04-27 10:10:29 +00:00
4 changed files with 113 additions and 7 deletions
Showing only changes of commit 2cca64f9b8 - Show all commits

View File

@ -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)]

View File

@ -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")
}

View File

@ -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",

View 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 %}