Add authentication from upstream providers (#107)
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			Let BasicOIDC delegate authentication to upstream providers (Google, GitHub, GitLab, Keycloak...) Reviewed-on: #107
This commit is contained in:
		@@ -30,8 +30,6 @@
 | 
			
		||||
                font-size: 3.5rem;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -45,7 +43,7 @@
 | 
			
		||||
 | 
			
		||||
<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 %}
 | 
			
		||||
    <div class="alert alert-danger" role="alert">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,23 @@
 | 
			
		||||
{% extends "base_login_page.html" %}
 | 
			
		||||
{% 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">
 | 
			
		||||
    <div>
 | 
			
		||||
        <div class="form-floating">
 | 
			
		||||
@@ -19,4 +37,18 @@
 | 
			
		||||
 | 
			
		||||
</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 %}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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 %}
 | 
			
		||||
@@ -5,27 +5,27 @@
 | 
			
		||||
    <tbody>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <th scope="row">User ID</th>
 | 
			
		||||
        <td>{{ u.uid.0 }}</td>
 | 
			
		||||
        <td>{{ _p.user.uid.0 }}</td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <th scope="row">First name</th>
 | 
			
		||||
        <td>{{ u.first_name }}</td>
 | 
			
		||||
        <td>{{ _p.user.first_name }}</td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <th scope="row">Last name</th>
 | 
			
		||||
        <td>{{ u.last_name }}</td>
 | 
			
		||||
        <td>{{ _p.user.last_name }}</td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <th scope="row">Username</th>
 | 
			
		||||
        <td>{{ u.username }}</td>
 | 
			
		||||
        <td>{{ _p.user.username }}</td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <th scope="row">Email</th>
 | 
			
		||||
        <td>{{ u.email }}</td>
 | 
			
		||||
        <td>{{ _p.user.email }}</td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <th scope="row">Account type</th>
 | 
			
		||||
        <td>{% if u.admin %}Admin{% else %}Regular user{% endif %}</td>
 | 
			
		||||
        <td>{% if _p.user.admin %}Admin{% else %}Regular user{% endif %}</td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    </tbody>
 | 
			
		||||
</table>
 | 
			
		||||
 
 | 
			
		||||
@@ -14,8 +14,8 @@
 | 
			
		||||
    <a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto link-dark text-decoration-none">
 | 
			
		||||
        <span class="fs-4">{{ _p.app_name }}</span>
 | 
			
		||||
    </a>
 | 
			
		||||
    {% if _p.is_admin %}
 | 
			
		||||
        <span>Version {{ _p.version }}</span>
 | 
			
		||||
    {% if _p.user.admin %}
 | 
			
		||||
    <span>Version {{ _p.version }}</span>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    <hr>
 | 
			
		||||
    <ul class="nav nav-pills flex-column mb-auto">
 | 
			
		||||
@@ -24,24 +24,31 @@
 | 
			
		||||
                Account details
 | 
			
		||||
            </a>
 | 
			
		||||
        </li>
 | 
			
		||||
        {% if _p.user.allow_local_login %}
 | 
			
		||||
        <li>
 | 
			
		||||
            <a href="/settings/change_password" class="nav-link link-dark">
 | 
			
		||||
                Change password
 | 
			
		||||
            </a>
 | 
			
		||||
        </li>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        <li>
 | 
			
		||||
            <a href="/settings/two_factors" class="nav-link link-dark">
 | 
			
		||||
                Two-factor authentication
 | 
			
		||||
            </a>
 | 
			
		||||
        </li>
 | 
			
		||||
 | 
			
		||||
        {% if _p.is_admin %}
 | 
			
		||||
        {% if _p.user.admin %}
 | 
			
		||||
        <hr/>
 | 
			
		||||
        <li>
 | 
			
		||||
            <a href="/admin/clients" class="nav-link link-dark">
 | 
			
		||||
                Clients
 | 
			
		||||
            </a>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
            <a href="/admin/providers" class="nav-link link-dark">
 | 
			
		||||
                Providers
 | 
			
		||||
            </a>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
            <a href="/admin/users" class="nav-link link-dark">
 | 
			
		||||
                Users
 | 
			
		||||
@@ -54,7 +61,7 @@
 | 
			
		||||
        <a href="#" class="d-flex align-items-center link-dark text-decoration-none dropdown-toggle" id="dropdownUser"
 | 
			
		||||
           data-bs-toggle="dropdown" aria-expanded="false">
 | 
			
		||||
            <img src="/assets/img/account.png" alt="" width="32" height="32" class="rounded-circle me-2">
 | 
			
		||||
            <strong>{{ _p.user_name }}</strong>
 | 
			
		||||
            <strong>{{ _p.user.username }}</strong>
 | 
			
		||||
        </a>
 | 
			
		||||
        <ul class="dropdown-menu text-small shadow" aria-labelledby="dropdownUser">
 | 
			
		||||
            <li><a class="dropdown-item" href="/logout">Sign out</a></li>
 | 
			
		||||
@@ -83,6 +90,7 @@
 | 
			
		||||
    if(el.href === location.href) el.classList.add("active");
 | 
			
		||||
    else el.classList.remove("active")
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
{% if _p.ip_location_api.is_some() %}
 | 
			
		||||
<script>const IP_LOCATION_API = "{{ _p.ip_location_api.unwrap() }}"</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -112,28 +112,61 @@
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <ul>
 | 
			
		||||
        {% for e in u.get_formatted_2fa_successful_logins() %}
 | 
			
		||||
            {% if e.can_bypass_2fa %}<li style="font-weight: bold;">{{ e.ip }} - {{ e.fmt_time() }} - BYPASS 2FA</li>
 | 
			
		||||
            {% else %}<li>{{ e.ip }} - {{ e.fmt_time() }}</li>{% endif %}
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
            {% for e in u.get_formatted_2fa_successful_logins() %}
 | 
			
		||||
            {% if e.can_bypass_2fa %}
 | 
			
		||||
            <li style="font-weight: bold;">{{ e.ip }} - {{ e.fmt_time() }} - BYPASS 2FA</li>
 | 
			
		||||
            {% else %}
 | 
			
		||||
            <li>{{ e.ip }} - {{ e.fmt_time() }}</li>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        </ul>
 | 
			
		||||
    </fieldset>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
    <!-- Authorized authentication sources -->
 | 
			
		||||
    <fieldset class="form-group">
 | 
			
		||||
        <legend class="mt-4">Authorized authentication sources</legend>
 | 
			
		||||
 | 
			
		||||
        <!-- Local login -->
 | 
			
		||||
        <div class="form-check">
 | 
			
		||||
            <input class="form-check-input" type="checkbox" name="allow_local_login" id="allow_local_login"
 | 
			
		||||
                   {% if u.allow_local_login %} checked="" {% endif %}>
 | 
			
		||||
            <label class="form-check-label" for="allow_local_login">
 | 
			
		||||
                Allow local login
 | 
			
		||||
            </label>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- Upstream providers -->
 | 
			
		||||
        <input type="hidden" name="authorized_sources" id="authorized_sources"/>
 | 
			
		||||
        {% for prov in providers %}
 | 
			
		||||
        <div class="form-check">
 | 
			
		||||
            <input class="form-check-input authorized_provider" type="checkbox" name="prov-{{ prov.id.0 }}"
 | 
			
		||||
                   id="prov-{{ prov.id.0 }}"
 | 
			
		||||
                   data-id="{{ prov.id.0 }}"
 | 
			
		||||
                   {% if u.can_login_from_provider(prov) %} checked="" {% endif %}>
 | 
			
		||||
            <label class="form-check-label" for="prov-{{ prov.id.0 }}">
 | 
			
		||||
                Allow login from {{ prov.name }}
 | 
			
		||||
            </label>
 | 
			
		||||
        </div>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    </fieldset>
 | 
			
		||||
 | 
			
		||||
    <!-- Granted clients -->
 | 
			
		||||
    <fieldset class="form-group">
 | 
			
		||||
        <legend class="mt-4">Granted clients</legend>
 | 
			
		||||
        <div class="form-check">
 | 
			
		||||
            <label class="form-check-label">
 | 
			
		||||
                <input type="radio" class="form-check-input" name="grant_type"
 | 
			
		||||
                       value="all_clients" {% if u.granted_clients() == GrantedClients::AllClients %} checked="" {% endif %}>
 | 
			
		||||
                       value="all_clients" {% if u.granted_clients()== GrantedClients::AllClients %} checked="" {% endif
 | 
			
		||||
                       %}>
 | 
			
		||||
                Grant all clients
 | 
			
		||||
            </label>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="form-check">
 | 
			
		||||
            <label class="form-check-label">
 | 
			
		||||
                <input type="radio" class="form-check-input" name="grant_type"
 | 
			
		||||
                       value="custom_clients" {% if matches!(self.u.granted_clients(), GrantedClients::SomeClients(_)) %} checked="checked" {% endif %}>
 | 
			
		||||
                       value="custom_clients" {% if matches!(self.u.granted_clients(), GrantedClients::SomeClients(_))
 | 
			
		||||
                       %} checked="checked" {% endif %}>
 | 
			
		||||
                Manually specify allowed clients
 | 
			
		||||
            </label>
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -155,7 +188,8 @@
 | 
			
		||||
        <div class="form-check">
 | 
			
		||||
            <label class="form-check-label">
 | 
			
		||||
                <input type="radio" class="form-check-input" name="grant_type"
 | 
			
		||||
                       value="no_client" {% if u.granted_clients() == GrantedClients::NoClient %} checked="checked" {% endif %}>
 | 
			
		||||
                       value="no_client" {% if u.granted_clients()== GrantedClients::NoClient %} checked="checked" {%
 | 
			
		||||
                       endif %}>
 | 
			
		||||
                Do not grant any client
 | 
			
		||||
            </label>
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -215,6 +249,13 @@
 | 
			
		||||
    form.addEventListener("submit", (ev) => {
 | 
			
		||||
        ev.preventDefault();
 | 
			
		||||
 | 
			
		||||
        const authorized_sources = [...document.querySelectorAll(".authorized_provider")]
 | 
			
		||||
            .filter(e => e.checked)
 | 
			
		||||
            .map(e => e.getAttribute("data-id")).join(",")
 | 
			
		||||
 | 
			
		||||
        document.querySelector("input[name=authorized_sources]").value = authorized_sources;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        const authorized_clients = [...document.querySelectorAll(".authorize_client_checkbox")]
 | 
			
		||||
            .filter(e => e.checked)
 | 
			
		||||
            .map(e => e.getAttribute("data-id")).join(",")
 | 
			
		||||
@@ -231,6 +272,9 @@
 | 
			
		||||
        form.submit();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
{% endblock content %}
 | 
			
		||||
							
								
								
									
										39
									
								
								templates/settings/providers_list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								templates/settings/providers_list.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
{% extends "base_settings_page.html" %}
 | 
			
		||||
{% block content %}
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
#providers td {
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<table id="providers" class="table table-hover" style="max-width: 800px;" aria-describedby="OpenID providers list">
 | 
			
		||||
    <thead>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <th scope="col"></th>
 | 
			
		||||
        <th scope="col">ID</th>
 | 
			
		||||
        <th scope="col">Name</th>
 | 
			
		||||
        <th scope="col">Configuration URL</th>
 | 
			
		||||
        <th scope="col">Client ID</th>
 | 
			
		||||
    </tr>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tbody>
 | 
			
		||||
    {% for c in providers %}
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td>
 | 
			
		||||
            <img src="{{ c.logo_url() }}" alt="{{ c.name }} logo" width="30px"/>
 | 
			
		||||
        </td>
 | 
			
		||||
        <td>{{ c.id.0 }}</td>
 | 
			
		||||
        <td>{{ c.name }}</td>
 | 
			
		||||
        <td><a href="{{ c.configuration_url }}" target="_blank" rel="noreferrer">
 | 
			
		||||
            {{ c.configuration_url }}
 | 
			
		||||
        </a></td>
 | 
			
		||||
        <td>{{ c.client_id }}</td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
    </tbody>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
<p>Redirect URL for new clients: {{ redirect_url }}</p>
 | 
			
		||||
 | 
			
		||||
{% endblock content %}
 | 
			
		||||
		Reference in New Issue
	
	Block a user