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 54 additions and 4 deletions
Showing only changes of commit a0325fefbf - Show all commits

View File

@ -2,6 +2,7 @@ use actix::Addr;
use actix_identity::Identity; use actix_identity::Identity;
use actix_web::{web, HttpRequest, HttpResponse, Responder}; use actix_web::{web, HttpRequest, HttpResponse, Responder};
use askama::Template; use askama::Template;
use std::sync::Arc;
use crate::actors::bruteforce_actor::BruteForceActor; use crate::actors::bruteforce_actor::BruteForceActor;
use crate::actors::users_actor::{LoginResult, UsersActor}; use crate::actors::users_actor::{LoginResult, UsersActor};
@ -12,6 +13,7 @@ use crate::controllers::base_controller::{
}; };
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::{Provider, ProvidersManager};
use crate::data::remote_ip::RemoteIP; use crate::data::remote_ip::RemoteIP;
use crate::data::session_identity::{SessionIdentity, SessionStatus}; use crate::data::session_identity::{SessionIdentity, SessionStatus};
use crate::data::user::User; use crate::data::user::User;
@ -30,6 +32,7 @@ struct BaseLoginPage<'a> {
struct LoginTemplate<'a> { struct LoginTemplate<'a> {
_p: BaseLoginPage<'a>, _p: BaseLoginPage<'a>,
login: String, login: String,
providers: Vec<Provider>,
} }
#[derive(Template)] #[derive(Template)]
@ -77,6 +80,7 @@ pub struct LoginRequestQuery {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub async fn login_route( pub async fn login_route(
remote_ip: RemoteIP, remote_ip: RemoteIP,
providers: web::Data<Arc<ProvidersManager>>,
users: web::Data<Addr<UsersActor>>, users: web::Data<Addr<UsersActor>>,
bruteforce: web::Data<Addr<BruteForceActor>>, bruteforce: web::Data<Addr<BruteForceActor>>,
query: web::Query<LoginRequestQuery>, query: web::Query<LoginRequestQuery>,
@ -121,7 +125,7 @@ pub async fn login_route(
query.redirect.get_encoded() query.redirect.get_encoded()
)); ));
} }
// Check if the user has to valide a second factor // Check if the user has to validate a second factor
else if SessionIdentity(id.as_ref()).need_2fa_auth() { else if SessionIdentity(id.as_ref()).need_2fa_auth() {
return redirect_user(&format!( return redirect_user(&format!(
"/2fa_auth?redirect={}", "/2fa_auth?redirect={}",
@ -203,6 +207,7 @@ pub async fn login_route(
redirect_uri: &query.redirect, redirect_uri: &query.redirect,
}, },
login, login,
providers: providers.cloned(),
} }
.render() .render()
.unwrap(), .unwrap(),

View File

@ -1,4 +1,5 @@
use crate::data::entity_manager::EntityManager; use crate::data::entity_manager::EntityManager;
use crate::data::login_redirect::LoginRedirect;
use crate::utils::string_utils::apply_env_vars; use crate::utils::string_utils::apply_env_vars;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)] #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
@ -28,6 +29,11 @@ pub struct Provider {
} }
impl Provider { impl Provider {
/// Get URL-encoded provider id
pub fn id_encoded(&self) -> String {
urlencoding::encode(&self.id.0).to_string()
}
/// Get the URL where the logo can be located /// Get the URL where the logo can be located
pub fn logo_url(&self) -> &str { pub fn logo_url(&self) -> &str {
match self.logo.as_str() { match self.logo.as_str() {
@ -39,6 +45,15 @@ impl Provider {
s => s, s => s,
} }
} }
/// Get the URL to use to login with the provider
pub fn login_url(&self, redirect_url: &LoginRedirect) -> String {
format!(
"/login_with_prov?id={}&redirect_url={}",
self.id_encoded(),
redirect_url.get_encoded()
)
}
} }
impl PartialEq for Provider { impl PartialEq for Provider {

View File

@ -30,8 +30,6 @@
font-size: 3.5rem; font-size: 3.5rem;
} }
} }
</style> </style>
@ -45,7 +43,7 @@
<main class="form-signin"> <main class="form-signin">
<h1 class="h3 mb-3 fw-normal">{{ _p.page_title }}</h1> <h1 class="h3 mb-3 fw-normal" style="margin-bottom: 2rem !important;">{{ _p.page_title }}</h1>
{% if let Some(danger) = _p.danger %} {% if let Some(danger) = _p.danger %}
<div class="alert alert-danger" role="alert"> <div class="alert alert-danger" role="alert">

View File

@ -1,5 +1,23 @@
{% extends "base_login_page.html" %} {% extends "base_login_page.html" %}
{% block content %} {% block content %}
<style>
#providers {
margin-top: 40px;
}
.provider-button {
width: 100%;
display: flex;
margin-top: 10px;
}
.provider-button img {
margin-right: 1em;
width: 1em;
}
</style>
<form action="/login?redirect={{ _p.redirect_uri.get_encoded() }}" method="post"> <form action="/login?redirect={{ _p.redirect_uri.get_encoded() }}" method="post">
<div> <div>
<div class="form-floating"> <div class="form-floating">
@ -19,4 +37,18 @@
</form> </form>
<!-- Upstream providers -->
{% if !providers.is_empty() %}
<div id="providers">
{% for prov in providers %}
<a class="btn btn-secondary btn-lg provider-button" href="{{ prov.login_url(_p.redirect_uri) }}">
<img src="{{ prov.logo_url() }}" alt="Provider icon"/>
<div style="text-align: left;">
Login using {{ prov.name }} <br/>
</div>
</a>
{% endfor %}
</div>
{% endif %}
{% endblock content %} {% endblock content %}