Compare commits
2 Commits
2fc6caad1c
...
87c4e424bc
Author | SHA1 | Date | |
---|---|---|---|
87c4e424bc | |||
b49a4724a8 |
@ -1,17 +1,25 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use axum::response::IntoResponse;
|
||||
use openidconnect::AccessToken;
|
||||
use axum::response::{
|
||||
IntoResponse,
|
||||
Redirect,
|
||||
};
|
||||
use reqwest::StatusCode;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AuthError {
|
||||
OpenIdConfig(openidconnect::ConfigurationError),
|
||||
OpenIdRequest(String),
|
||||
TokenNotActive(AccessToken),
|
||||
}
|
||||
|
||||
impl Display for AuthError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
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())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -24,11 +32,27 @@ impl From<openidconnect::ConfigurationError> for AuthError {
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for AuthError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Authentication error: {}", self),
|
||||
).into_response()
|
||||
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 {
|
||||
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),
|
||||
).into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,29 +8,45 @@ use axum::response::{
|
||||
};
|
||||
|
||||
use crate::AppState;
|
||||
use crate::util::openid::ValidAccessToken;
|
||||
use crate::model::tsigkey::{
|
||||
TsigKey,
|
||||
};
|
||||
|
||||
pub async fn list(State(app_state): State<AppState>) -> impl IntoResponse {
|
||||
pub async fn list(
|
||||
State(app_state): State<AppState>,
|
||||
ValidAccessToken(token): ValidAccessToken,
|
||||
) -> impl IntoResponse {
|
||||
app_state.pdns_client.list_tsig_keys()
|
||||
.await
|
||||
.and_then(|keys| Ok(Json(keys)))
|
||||
}
|
||||
|
||||
pub async fn get(State(app_state): State<AppState>, Path(tsig_key_name): Path<String>) -> impl IntoResponse {
|
||||
pub async fn get(
|
||||
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)
|
||||
.await
|
||||
.and_then(|key| Ok(Json(key)))
|
||||
}
|
||||
|
||||
pub async fn add(State(app_state): State<AppState>, Json(tsig_key): Json<TsigKey>) -> impl IntoResponse {
|
||||
pub async fn add(
|
||||
State(app_state): State<AppState>,
|
||||
ValidAccessToken(token): ValidAccessToken,
|
||||
Json(tsig_key): Json<TsigKey>,
|
||||
) -> impl IntoResponse {
|
||||
app_state.pdns_client.add_tsig_key(tsig_key)
|
||||
.await
|
||||
.and_then(|key| Ok(Json(key)))
|
||||
}
|
||||
|
||||
pub async fn delete(State(app_state): State<AppState>, Path(tsig_key_name): Path<String>) -> impl IntoResponse {
|
||||
pub async fn delete(
|
||||
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)
|
||||
.await
|
||||
.and_then(|_| Ok(Json(())))
|
||||
|
@ -4,12 +4,12 @@ use std::time::{
|
||||
};
|
||||
|
||||
use askama::Template;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::response::{
|
||||
IntoResponse,
|
||||
};
|
||||
use axum::extract::State;
|
||||
use axum_extra::extract::cookie::CookieJar;
|
||||
|
||||
use openidconnect::{
|
||||
AccessToken,
|
||||
reqwest::async_http_client,
|
||||
TokenIntrospectionResponse,
|
||||
};
|
||||
@ -17,32 +17,35 @@ use openidconnect::{
|
||||
use crate::util::askama::HtmlTemplate;
|
||||
use crate::AppState;
|
||||
use crate::error::openid::AuthError;
|
||||
use crate::util::openid::AccessToken;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "user_home.html")]
|
||||
struct UserHomeTemplate {
|
||||
is_active: bool,
|
||||
active: bool,
|
||||
username: String,
|
||||
duration: Duration,
|
||||
}
|
||||
|
||||
pub async fn home(
|
||||
State(app_state): State<AppState>,
|
||||
cookies: CookieJar
|
||||
AccessToken(token): AccessToken,
|
||||
) -> Result<impl IntoResponse, AuthError> {
|
||||
let now = Instant::now();
|
||||
let token_serialized: Option<String> = cookies.get("access_token")
|
||||
.map(|cookie| cookie.value().to_owned());
|
||||
let (is_active, username);
|
||||
(is_active, username) = match token_serialized {
|
||||
Some(token) => {
|
||||
let introspection_response = app_state.oidc_client
|
||||
.introspect(&AccessToken::new(token))?
|
||||
.request_async(async_http_client).await.unwrap();
|
||||
println!("Token introspected, answer is {:?}", introspection_response);
|
||||
(introspection_response.active(), introspection_response.username().unwrap_or("").to_string())
|
||||
let response = app_state.oidc_client
|
||||
.introspect(&token)?
|
||||
.request_async(async_http_client)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
match response.active() {
|
||||
true => {
|
||||
let username = response.username().unwrap().to_string();
|
||||
Ok(HtmlTemplate(UserHomeTemplate {
|
||||
active: true,
|
||||
username,
|
||||
duration: now.elapsed(),
|
||||
}))
|
||||
},
|
||||
None => (false, "".to_string())
|
||||
};
|
||||
Ok(HtmlTemplate(UserHomeTemplate { is_active, username, duration: now.elapsed() }))
|
||||
false => Err(AuthError::TokenNotActive(token)),
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ use std::sync::{Arc};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use axum::Router;
|
||||
use axum::extract::FromRef;
|
||||
use axum::routing::{get, post};
|
||||
use openidconnect::{
|
||||
IntrospectionUrl,
|
||||
@ -18,7 +19,7 @@ mod util;
|
||||
|
||||
use crate::util::powerdns::PowerDnsApi;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, FromRef)]
|
||||
pub struct AppState {
|
||||
oidc_client: CoreClient,
|
||||
pdns_client: PowerDnsApi,
|
||||
|
@ -9,7 +9,84 @@ use openidconnect::{
|
||||
IssuerUrl,
|
||||
RedirectUrl,
|
||||
};
|
||||
use openidconnect::TokenIntrospectionResponse;
|
||||
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 {
|
||||
let issuer_url = IssuerUrl::from_url(issuer);
|
||||
|
@ -7,7 +7,7 @@
|
||||
<h1>User Home</h1>
|
||||
{% endif %}
|
||||
|
||||
<p>Your user session is <b>{% if is_active %}active{% else %}inactive{% endif %}</b></p>
|
||||
<p>Your user session is <b>{% if active %}active{% else %}inactive{% endif %}</b></p>
|
||||
|
||||
<p>Request took <b>{{ duration.as_millis() }}</b>ms</p>
|
||||
{% endblock content %}
|
||||
|
Loading…
Reference in New Issue
Block a user