Compare commits
No commits in common. "87c4e424bc3638ec1f3a3da409d1ba06a3be5db8" and "2fc6caad1c557c8f6e8461bf2261fee9e6498d01" have entirely different histories.
87c4e424bc
...
2fc6caad1c
@ -1,25 +1,17 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use openidconnect::AccessToken;
|
use axum::response::IntoResponse;
|
||||||
use axum::response::{
|
|
||||||
IntoResponse,
|
|
||||||
Redirect,
|
|
||||||
};
|
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AuthError {
|
pub enum AuthError {
|
||||||
OpenIdConfig(openidconnect::ConfigurationError),
|
OpenIdConfig(openidconnect::ConfigurationError),
|
||||||
OpenIdRequest(String),
|
|
||||||
TokenNotActive(AccessToken),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for AuthError {
|
impl Display for AuthError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
AuthError::OpenIdConfig(e) => f.write_fmt(format_args!("OpenID Connect configuration error: {:?}", e)),
|
AuthError::OpenIdConfig(e) => f.write_fmt(format_args!("OpenID Connect configuration error: {:?}", e)),
|
||||||
AuthError::OpenIdRequest(s) => f.write_fmt(format_args!("{}", s)),
|
|
||||||
AuthError::TokenNotActive(t) => f.write_fmt(format_args!("Token {} is not active", t.secret())),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -32,27 +24,11 @@ impl From<openidconnect::ConfigurationError> for AuthError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for AuthError {
|
|
||||||
fn from(s: String) -> Self {
|
|
||||||
Self::OpenIdRequest(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<AccessToken> for AuthError {
|
|
||||||
fn from(t: AccessToken) -> Self {
|
|
||||||
Self::TokenNotActive(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl IntoResponse for AuthError {
|
impl IntoResponse for AuthError {
|
||||||
fn into_response(self) -> axum::response::Response {
|
fn into_response(self) -> axum::response::Response {
|
||||||
match self {
|
(
|
||||||
AuthError::TokenNotActive(_) => Redirect::to("/openid/login").into_response(),
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
_ => (
|
format!("Authentication error: {}", self),
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
).into_response()
|
||||||
format!("Authentication error: {}", self),
|
|
||||||
).into_response(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,45 +8,29 @@ use axum::response::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use crate::util::openid::ValidAccessToken;
|
|
||||||
use crate::model::tsigkey::{
|
use crate::model::tsigkey::{
|
||||||
TsigKey,
|
TsigKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn list(
|
pub async fn list(State(app_state): State<AppState>) -> impl IntoResponse {
|
||||||
State(app_state): State<AppState>,
|
|
||||||
ValidAccessToken(token): ValidAccessToken,
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
app_state.pdns_client.list_tsig_keys()
|
app_state.pdns_client.list_tsig_keys()
|
||||||
.await
|
.await
|
||||||
.and_then(|keys| Ok(Json(keys)))
|
.and_then(|keys| Ok(Json(keys)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(State(app_state): State<AppState>, Path(tsig_key_name): Path<String>) -> impl IntoResponse {
|
||||||
State(app_state): State<AppState>,
|
|
||||||
ValidAccessToken(token): ValidAccessToken,
|
|
||||||
Path(tsig_key_name): Path<String>
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
app_state.pdns_client.get_tsig_key(tsig_key_name)
|
app_state.pdns_client.get_tsig_key(tsig_key_name)
|
||||||
.await
|
.await
|
||||||
.and_then(|key| Ok(Json(key)))
|
.and_then(|key| Ok(Json(key)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add(
|
pub async fn add(State(app_state): State<AppState>, Json(tsig_key): Json<TsigKey>) -> impl IntoResponse {
|
||||||
State(app_state): State<AppState>,
|
|
||||||
ValidAccessToken(token): ValidAccessToken,
|
|
||||||
Json(tsig_key): Json<TsigKey>,
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
app_state.pdns_client.add_tsig_key(tsig_key)
|
app_state.pdns_client.add_tsig_key(tsig_key)
|
||||||
.await
|
.await
|
||||||
.and_then(|key| Ok(Json(key)))
|
.and_then(|key| Ok(Json(key)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(
|
pub async fn delete(State(app_state): State<AppState>, Path(tsig_key_name): Path<String>) -> impl IntoResponse {
|
||||||
State(app_state): State<AppState>,
|
|
||||||
ValidAccessToken(token): ValidAccessToken,
|
|
||||||
Path(tsig_key_name): Path<String>,
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
app_state.pdns_client.delete_tsig_key(tsig_key_name)
|
app_state.pdns_client.delete_tsig_key(tsig_key_name)
|
||||||
.await
|
.await
|
||||||
.and_then(|_| Ok(Json(())))
|
.and_then(|_| Ok(Json(())))
|
||||||
|
@ -4,12 +4,12 @@ use std::time::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use axum::response::{
|
use axum::response::IntoResponse;
|
||||||
IntoResponse,
|
|
||||||
};
|
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
|
use axum_extra::extract::cookie::CookieJar;
|
||||||
|
|
||||||
use openidconnect::{
|
use openidconnect::{
|
||||||
|
AccessToken,
|
||||||
reqwest::async_http_client,
|
reqwest::async_http_client,
|
||||||
TokenIntrospectionResponse,
|
TokenIntrospectionResponse,
|
||||||
};
|
};
|
||||||
@ -17,35 +17,32 @@ use openidconnect::{
|
|||||||
use crate::util::askama::HtmlTemplate;
|
use crate::util::askama::HtmlTemplate;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use crate::error::openid::AuthError;
|
use crate::error::openid::AuthError;
|
||||||
use crate::util::openid::AccessToken;
|
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "user_home.html")]
|
#[template(path = "user_home.html")]
|
||||||
struct UserHomeTemplate {
|
struct UserHomeTemplate {
|
||||||
active: bool,
|
is_active: bool,
|
||||||
username: String,
|
username: String,
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn home(
|
pub async fn home(
|
||||||
State(app_state): State<AppState>,
|
State(app_state): State<AppState>,
|
||||||
AccessToken(token): AccessToken,
|
cookies: CookieJar
|
||||||
) -> Result<impl IntoResponse, AuthError> {
|
) -> Result<impl IntoResponse, AuthError> {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let response = app_state.oidc_client
|
let token_serialized: Option<String> = cookies.get("access_token")
|
||||||
.introspect(&token)?
|
.map(|cookie| cookie.value().to_owned());
|
||||||
.request_async(async_http_client)
|
let (is_active, username);
|
||||||
.await
|
(is_active, username) = match token_serialized {
|
||||||
.map_err(|e| e.to_string())?;
|
Some(token) => {
|
||||||
match response.active() {
|
let introspection_response = app_state.oidc_client
|
||||||
true => {
|
.introspect(&AccessToken::new(token))?
|
||||||
let username = response.username().unwrap().to_string();
|
.request_async(async_http_client).await.unwrap();
|
||||||
Ok(HtmlTemplate(UserHomeTemplate {
|
println!("Token introspected, answer is {:?}", introspection_response);
|
||||||
active: true,
|
(introspection_response.active(), introspection_response.username().unwrap_or("").to_string())
|
||||||
username,
|
|
||||||
duration: now.elapsed(),
|
|
||||||
}))
|
|
||||||
},
|
},
|
||||||
false => Err(AuthError::TokenNotActive(token)),
|
None => (false, "".to_string())
|
||||||
}
|
};
|
||||||
|
Ok(HtmlTemplate(UserHomeTemplate { is_active, username, duration: now.elapsed() }))
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ use std::sync::{Arc};
|
|||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use axum::extract::FromRef;
|
|
||||||
use axum::routing::{get, post};
|
use axum::routing::{get, post};
|
||||||
use openidconnect::{
|
use openidconnect::{
|
||||||
IntrospectionUrl,
|
IntrospectionUrl,
|
||||||
@ -19,7 +18,7 @@ mod util;
|
|||||||
|
|
||||||
use crate::util::powerdns::PowerDnsApi;
|
use crate::util::powerdns::PowerDnsApi;
|
||||||
|
|
||||||
#[derive(Clone, FromRef)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
oidc_client: CoreClient,
|
oidc_client: CoreClient,
|
||||||
pdns_client: PowerDnsApi,
|
pdns_client: PowerDnsApi,
|
||||||
|
@ -9,84 +9,7 @@ use openidconnect::{
|
|||||||
IssuerUrl,
|
IssuerUrl,
|
||||||
RedirectUrl,
|
RedirectUrl,
|
||||||
};
|
};
|
||||||
use openidconnect::TokenIntrospectionResponse;
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use axum::{
|
|
||||||
async_trait,
|
|
||||||
extract::{
|
|
||||||
FromRef,
|
|
||||||
FromRequestParts,
|
|
||||||
State,
|
|
||||||
},
|
|
||||||
http::{
|
|
||||||
StatusCode,
|
|
||||||
request::Parts,
|
|
||||||
},
|
|
||||||
response::Redirect,
|
|
||||||
};
|
|
||||||
use axum_extra::extract::cookie::CookieJar;
|
|
||||||
|
|
||||||
use crate::AppState;
|
|
||||||
|
|
||||||
pub(crate) struct AccessToken(pub(crate) openidconnect::AccessToken);
|
|
||||||
pub(crate) struct ValidAccessToken(pub(crate) openidconnect::AccessToken);
|
|
||||||
pub(crate) type AccessTokenRejection = (StatusCode, String);
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<P> FromRequestParts<P> for AccessToken
|
|
||||||
where
|
|
||||||
P: Send + Sync
|
|
||||||
{
|
|
||||||
type Rejection = AccessTokenRejection;
|
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, state: &P) -> Result<Self, AccessTokenRejection> {
|
|
||||||
let cookie_name = "access_token";
|
|
||||||
let cookies = CookieJar::from_request_parts(parts, state).await.unwrap();
|
|
||||||
let token: Option<String> = cookies
|
|
||||||
.get(cookie_name)
|
|
||||||
.map(|cookie| cookie.value().to_owned());
|
|
||||||
match token {
|
|
||||||
Some(token) => Ok(Self(openidconnect::AccessToken::new(token))),
|
|
||||||
None => Err((
|
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
format!(
|
|
||||||
"Request is missing the '{}' cookie",
|
|
||||||
cookie_name.to_string(),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<P> FromRequestParts<P> for ValidAccessToken
|
|
||||||
where
|
|
||||||
AppState: FromRef<P>,
|
|
||||||
P: Send + Sync,
|
|
||||||
{
|
|
||||||
type Rejection = Redirect;
|
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, state: &P) -> Result<Self, Self::Rejection> {
|
|
||||||
match AccessToken::from_request_parts(parts, state).await {
|
|
||||||
Err(_) => Err(Redirect::to("/openid/login")),
|
|
||||||
Ok(AccessToken(token)) => {
|
|
||||||
match AppState::from_ref(state)
|
|
||||||
.oidc_client
|
|
||||||
.introspect(&token)
|
|
||||||
.map_err(|_| Redirect::to("/openid/login"))?
|
|
||||||
.request_async(async_http_client)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Redirect::to("/openid/login")) {
|
|
||||||
Ok(t) => match t.active() {
|
|
||||||
true => Ok(ValidAccessToken(token)),
|
|
||||||
false => Err(Redirect::to("/openid/login")),
|
|
||||||
},
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create_client(issuer: Url, id: String, secret: String, redirect_url: Url) -> CoreClient {
|
pub async fn create_client(issuer: Url, id: String, secret: String, redirect_url: Url) -> CoreClient {
|
||||||
let issuer_url = IssuerUrl::from_url(issuer);
|
let issuer_url = IssuerUrl::from_url(issuer);
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<h1>User Home</h1>
|
<h1>User Home</h1>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<p>Your user session is <b>{% if active %}active{% else %}inactive{% endif %}</b></p>
|
<p>Your user session is <b>{% if is_active %}active{% else %}inactive{% endif %}</b></p>
|
||||||
|
|
||||||
<p>Request took <b>{{ duration.as_millis() }}</b>ms</p>
|
<p>Request took <b>{{ duration.as_millis() }}</b>ms</p>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user