Compare commits
No commits in common. "a7f6c28e0d6c729e92197abaea95bc08d4ecd552" and "f9907080528c17f65a04a8d5ff61336b7c71de55" have entirely different histories.
a7f6c28e0d
...
f990708052
12 changed files with 8 additions and 87 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1406,7 +1406,6 @@ dependencies = [
|
||||||
"redis",
|
"redis",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
|
|
|
@ -58,7 +58,6 @@ rand = "0.8.5"
|
||||||
rand_core = { version = "0.6.4", features = ["std"] }
|
rand_core = { version = "0.6.4", features = ["std"] }
|
||||||
url = "2.5.3"
|
url = "2.5.3"
|
||||||
strum = "0.26.3"
|
strum = "0.26.3"
|
||||||
serde_urlencoded = "0.7.1"
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
minijinja-embed = "2.3.1"
|
minijinja-embed = "2.3.1"
|
||||||
|
|
8
TODO.md
8
TODO.md
|
@ -10,11 +10,3 @@
|
||||||
- [x] Get access token
|
- [x] Get access token
|
||||||
|
|
||||||
- [ ] Support error responses by https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
|
- [ ] Support error responses by https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
|
||||||
|
|
||||||
- [ ] Redirect to login when JWT expire
|
|
||||||
- [ ] Add admin panel via API
|
|
||||||
- [ ] Add admin CLI
|
|
||||||
|
|
||||||
- [ ] Support OpenID to use with demo client [oauth2c](https://github.com/cloudentity/oauth2c)
|
|
||||||
|
|
||||||
- .well-known/openid-configuration
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
[instance]
|
[instance]
|
||||||
base_uri = "http://localhost:8085"
|
|
||||||
name = "Example org"
|
name = "Example org"
|
||||||
logo_uri = "https://example.org/logo.png"
|
logo_uri = "https://example.org/logo.png"
|
||||||
|
|
||||||
|
@ -10,8 +9,7 @@ description = "A super application where you can do everything you want."
|
||||||
client_id = "a1785786-8be1-443c-9a6f-35feed703609"
|
client_id = "a1785786-8be1-443c-9a6f-35feed703609"
|
||||||
client_secret = "49c6c16a-0a8a-4981-a60d-5cb96582cc1a"
|
client_secret = "49c6c16a-0a8a-4981-a60d-5cb96582cc1a"
|
||||||
allowed_redirect_uris = [
|
allowed_redirect_uris = [
|
||||||
"http://localhost:9090/authorize",
|
"http://localhost:9090/authorize"
|
||||||
"http://localhost:9876/callback"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[roles]]
|
[[roles]]
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
#!/usr/bin/bash
|
|
||||||
|
|
||||||
oauth2c http://localhost:8085 \
|
|
||||||
--client-id "a1785786-8be1-443c-9a6f-35feed703609" \
|
|
||||||
--client-secret "49c6c16a-0a8a-4981-a60d-5cb96582cc1a" \
|
|
||||||
--response-types code \
|
|
||||||
--response-mode query \
|
|
||||||
--grant-type authorization_code \
|
|
||||||
--auth-method client_secret_basic \
|
|
||||||
--scopes "read_user_basic"
|
|
|
@ -1,3 +1,2 @@
|
||||||
pub mod oauth2;
|
pub mod oauth2;
|
||||||
pub mod read_user;
|
pub mod read_user;
|
||||||
pub mod openid;
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
pub mod well_known;
|
|
|
@ -1,32 +0,0 @@
|
||||||
use axum::{extract::State, response::IntoResponse, Json};
|
|
||||||
use fully_pub::fully_pub;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use crate::server::AppState;
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[fully_pub]
|
|
||||||
struct WellKnownOpenIdConfiguration {
|
|
||||||
issuer: String,
|
|
||||||
authorization_endpoint: String,
|
|
||||||
token_endpoint: String,
|
|
||||||
userinfo_endpoint: String,
|
|
||||||
scopes_supported: Vec<String>,
|
|
||||||
response_types_supported: Vec<String>,
|
|
||||||
token_endpoint_auth_methods_supported: Vec<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_well_known_openid_configuration(
|
|
||||||
State(app_state): State<AppState>,
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
let base_url = app_state.config.instance.base_uri;
|
|
||||||
Json(WellKnownOpenIdConfiguration {
|
|
||||||
issuer: base_url.clone(),
|
|
||||||
authorization_endpoint: format!("{}/authorize", base_url),
|
|
||||||
token_endpoint: format!("{}/api/token", base_url),
|
|
||||||
userinfo_endpoint: format!("{}/api/user", base_url),
|
|
||||||
scopes_supported: vec!["read_user_basic".into()],
|
|
||||||
response_types_supported: vec!["code".into()],
|
|
||||||
token_endpoint_auth_methods_supported: vec!["client_secret_basic".into()],
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
use chrono::{Duration, SecondsFormat, Utc};
|
use chrono::{Duration, SecondsFormat, Utc};
|
||||||
use log::info;
|
use log::info;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use axum::{extract::{Query, State}, http::{HeaderMap, HeaderValue, StatusCode}, response::{Html, IntoResponse}, Extension, Form};
|
use axum::{extract::State, http::{HeaderMap, HeaderValue, StatusCode}, response::{Html, IntoResponse}, Extension, Form};
|
||||||
use fully_pub::fully_pub;
|
use fully_pub::fully_pub;
|
||||||
use minijinja::context;
|
use minijinja::context;
|
||||||
|
|
||||||
|
@ -28,16 +28,9 @@ struct LoginForm {
|
||||||
|
|
||||||
const DUMMY_PASSWORD_HASH: &str = "$argon2id$v=19$m=19456,t=2,p=1$+06ud2g4uVTI7kUIXjWM4g$6XqwuHt/+xl0d5J4BYKuIbg2acBp6udxMCnmJ6QfceY";
|
const DUMMY_PASSWORD_HASH: &str = "$argon2id$v=19$m=19456,t=2,p=1$+06ud2g4uVTI7kUIXjWM4g$6XqwuHt/+xl0d5J4BYKuIbg2acBp6udxMCnmJ6QfceY";
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[fully_pub]
|
|
||||||
struct LoginQueryParams {
|
|
||||||
redirect_to: Option<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn perform_login(
|
pub async fn perform_login(
|
||||||
State(app_state): State<AppState>,
|
State(app_state): State<AppState>,
|
||||||
Extension(renderer): Extension<TemplateRenderer>,
|
Extension(renderer): Extension<TemplateRenderer>,
|
||||||
Query(query_params): Query<LoginQueryParams>,
|
|
||||||
Form(login): Form<LoginForm>
|
Form(login): Form<LoginForm>
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// get user from db
|
// get user from db
|
||||||
|
@ -96,15 +89,12 @@ pub async fn perform_login(
|
||||||
let jwt_cookie = format!("minauth_jwt={jwt}; SameSite=Lax; Max-Age={cookie_max_age}");
|
let jwt_cookie = format!("minauth_jwt={jwt}; SameSite=Lax; Max-Age={cookie_max_age}");
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
headers.insert("Set-Cookie", HeaderValue::from_str(&jwt_cookie).unwrap());
|
headers.insert("Set-Cookie", HeaderValue::from_str(&jwt_cookie).unwrap());
|
||||||
// TODO: check redirection for arbitrary URL, enforce relative path
|
headers.insert("Location", HeaderValue::from_str(&format!("/me")).unwrap());
|
||||||
headers.insert("Location", HeaderValue::from_str(
|
|
||||||
&query_params.redirect_to.unwrap_or(format!("/me"))
|
|
||||||
).unwrap());
|
|
||||||
|
|
||||||
(
|
(
|
||||||
StatusCode::SEE_OTHER,
|
StatusCode::FOUND,
|
||||||
headers,
|
headers,
|
||||||
Html("Logged in. Redirecting you.")
|
Html("")
|
||||||
).into_response()
|
).into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use std::collections::HashMap;
|
use axum::{extract::{Request, State}, http::StatusCode, middleware::Next, response::{Html, IntoResponse, Response}, Extension};
|
||||||
|
|
||||||
use axum::{extract::{OriginalUri, Query, Request, State}, http::{HeaderMap, HeaderValue, StatusCode}, middleware::Next, response::{Html, IntoResponse, Redirect, Response}, Extension};
|
|
||||||
use axum_extra::extract::CookieJar;
|
use axum_extra::extract::CookieJar;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -38,7 +36,6 @@ pub async fn auth_middleware(
|
||||||
|
|
||||||
/// require auth
|
/// require auth
|
||||||
pub async fn enforce_auth_middleware(
|
pub async fn enforce_auth_middleware(
|
||||||
OriginalUri(original_uri): OriginalUri,
|
|
||||||
token_claims_ext: Option<Extension<UserTokenClaims>>,
|
token_claims_ext: Option<Extension<UserTokenClaims>>,
|
||||||
req: Request,
|
req: Request,
|
||||||
next: Next,
|
next: Next,
|
||||||
|
@ -47,14 +44,9 @@ pub async fn enforce_auth_middleware(
|
||||||
Some(_val) => (),
|
Some(_val) => (),
|
||||||
None => {
|
None => {
|
||||||
// auth is required
|
// auth is required
|
||||||
// redirect to login UI
|
return Err(
|
||||||
let target_url = format!(
|
(StatusCode::UNAUTHORIZED, Html("Unauthorized: auth is required on this page."))
|
||||||
"/login?{}",
|
|
||||||
serde_urlencoded::to_string(&[
|
|
||||||
("redirect_to", original_uri.to_string())
|
|
||||||
]).expect("To encode URI")
|
|
||||||
);
|
);
|
||||||
return Err(Redirect::to(&target_url));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(next.run(req).await)
|
Ok(next.run(req).await)
|
||||||
|
|
|
@ -7,7 +7,6 @@ const fn _default_true() -> bool { true }
|
||||||
#[fully_pub]
|
#[fully_pub]
|
||||||
/// Instance branding/customization config
|
/// Instance branding/customization config
|
||||||
struct InstanceConfig {
|
struct InstanceConfig {
|
||||||
base_uri: String,
|
|
||||||
name: String,
|
name: String,
|
||||||
logo_uri: Option<String>
|
logo_uri: Option<String>
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,15 +40,11 @@ pub fn build_router(server_config: &ServerConfig, app_state: AppState) -> Router
|
||||||
.route("/api/user", get(api::read_user::read_user_basic))
|
.route("/api/user", get(api::read_user::read_user_basic))
|
||||||
.layer(middleware::from_fn_with_state(app_state.clone(), app_auth::enforce_jwt_auth_middleware));
|
.layer(middleware::from_fn_with_state(app_state.clone(), app_auth::enforce_jwt_auth_middleware));
|
||||||
|
|
||||||
let well_known_routes = Router::new()
|
|
||||||
.route("/.well-known/openid-configuration", get(api::openid::well_known::get_well_known_openid_configuration));
|
|
||||||
|
|
||||||
Router::new()
|
Router::new()
|
||||||
.merge(public_routes)
|
.merge(public_routes)
|
||||||
.merge(user_routes)
|
.merge(user_routes)
|
||||||
.merge(app_routes)
|
.merge(app_routes)
|
||||||
.merge(app_user_routes)
|
.merge(app_user_routes)
|
||||||
.merge(well_known_routes)
|
|
||||||
.layer(middleware::from_fn_with_state(app_state.clone(), renderer_middleware))
|
.layer(middleware::from_fn_with_state(app_state.clone(), renderer_middleware))
|
||||||
.nest_service(
|
.nest_service(
|
||||||
"/assets",
|
"/assets",
|
||||||
|
|
Loading…
Reference in a new issue