mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 22:52:22 +00:00
frontend: Move oidc configuration to dedicated section
This commit is contained in:
@@ -38,8 +38,6 @@ pub struct FrontendConfig {
|
|||||||
pub secret_key: [u8; 64],
|
pub secret_key: [u8; 64],
|
||||||
#[serde(default = "default_true")]
|
#[serde(default = "default_true")]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
#[serde(default)]
|
|
||||||
pub oidc: Option<OidcConfig>,
|
|
||||||
#[serde(default = "default_true")]
|
#[serde(default = "default_true")]
|
||||||
pub allow_password_login: bool,
|
pub allow_password_login: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ pub mod nextcloud_login;
|
|||||||
mod oidc;
|
mod oidc;
|
||||||
mod routes;
|
mod routes;
|
||||||
|
|
||||||
pub use config::FrontendConfig;
|
pub use config::{FrontendConfig, OidcConfig};
|
||||||
|
|
||||||
pub fn generate_app_token() -> String {
|
pub fn generate_app_token() -> String {
|
||||||
rand::thread_rng()
|
rand::thread_rng()
|
||||||
@@ -193,63 +193,68 @@ pub fn configure_frontend<AP: AuthenticationProvider, CS: CalendarStore, AS: Add
|
|||||||
cal_store: Arc<CS>,
|
cal_store: Arc<CS>,
|
||||||
addr_store: Arc<AS>,
|
addr_store: Arc<AS>,
|
||||||
frontend_config: FrontendConfig,
|
frontend_config: FrontendConfig,
|
||||||
|
oidc_config: Option<OidcConfig>,
|
||||||
) {
|
) {
|
||||||
cfg.service(
|
let mut scope = web::scope("")
|
||||||
web::scope("")
|
.wrap(ErrorHandlers::new().handler(StatusCode::UNAUTHORIZED, unauthorized_handler))
|
||||||
.wrap(ErrorHandlers::new().handler(StatusCode::UNAUTHORIZED, unauthorized_handler))
|
.wrap(AuthenticationMiddleware::new(auth_provider.clone()))
|
||||||
.wrap(AuthenticationMiddleware::new(auth_provider.clone()))
|
.wrap(session_middleware(frontend_config.secret_key))
|
||||||
.wrap(session_middleware(frontend_config.secret_key))
|
.app_data(Data::from(auth_provider))
|
||||||
.app_data(Data::from(auth_provider))
|
.app_data(Data::from(cal_store.clone()))
|
||||||
.app_data(Data::from(cal_store.clone()))
|
.app_data(Data::from(addr_store.clone()))
|
||||||
.app_data(Data::from(addr_store.clone()))
|
.app_data(Data::new(frontend_config.clone()))
|
||||||
.app_data(Data::new(frontend_config.clone()))
|
.app_data(Data::new(oidc_config.clone()))
|
||||||
.service(EmbedService::<Assets>::new("/assets".to_owned()))
|
.service(EmbedService::<Assets>::new("/assets".to_owned()))
|
||||||
.service(web::resource("").route(web::method(Method::GET).to(route_root)))
|
.service(web::resource("").route(web::method(Method::GET).to(route_root)))
|
||||||
.service(
|
.service(
|
||||||
web::resource("/user")
|
web::resource("/user")
|
||||||
.route(web::method(Method::GET).to(route_user))
|
.route(web::method(Method::GET).to(route_user))
|
||||||
.name("frontend_user"),
|
.name("frontend_user"),
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/user/{user}")
|
web::resource("/user/{user}")
|
||||||
.route(web::method(Method::GET).to(route_user_named::<CS, AS>))
|
.route(web::method(Method::GET).to(route_user_named::<CS, AS>))
|
||||||
.name("frontend_user_named"),
|
.name("frontend_user_named"),
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/user/{user}/app_token")
|
web::resource("/user/{user}/app_token")
|
||||||
.route(web::method(Method::POST).to(route_post_app_token::<AP>)),
|
.route(web::method(Method::POST).to(route_post_app_token::<AP>)),
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/user/{user}/app_token/{id}/delete")
|
web::resource("/user/{user}/app_token/{id}/delete")
|
||||||
.route(web::method(Method::POST).to(route_delete_app_token::<AP>)),
|
.route(web::method(Method::POST).to(route_delete_app_token::<AP>)),
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/user/{user}/calendar/{calendar}")
|
web::resource("/user/{user}/calendar/{calendar}")
|
||||||
.route(web::method(Method::GET).to(route_calendar::<CS>)),
|
.route(web::method(Method::GET).to(route_calendar::<CS>)),
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/user/{user}/calendar/{calendar}/restore")
|
web::resource("/user/{user}/calendar/{calendar}/restore")
|
||||||
.route(web::method(Method::POST).to(route_calendar_restore::<CS>)),
|
.route(web::method(Method::POST).to(route_calendar_restore::<CS>)),
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/user/{user}/addressbook/{addressbook}")
|
web::resource("/user/{user}/addressbook/{addressbook}")
|
||||||
.route(web::method(Method::GET).to(route_addressbook::<AS>)),
|
.route(web::method(Method::GET).to(route_addressbook::<AS>)),
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/user/{user}/addressbook/{addressbook}/restore")
|
web::resource("/user/{user}/addressbook/{addressbook}/restore")
|
||||||
.route(web::method(Method::POST).to(route_addressbook_restore::<AS>)),
|
.route(web::method(Method::POST).to(route_addressbook_restore::<AS>)),
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/login")
|
web::resource("/login")
|
||||||
.name("frontend_login")
|
.name("frontend_login")
|
||||||
.route(web::method(Method::GET).to(route_get_login))
|
.route(web::method(Method::GET).to(route_get_login))
|
||||||
.route(web::method(Method::POST).to(route_post_login::<AP>)),
|
.route(web::method(Method::POST).to(route_post_login::<AP>)),
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::resource("/logout")
|
web::resource("/logout")
|
||||||
.name("frontend_logout")
|
.name("frontend_logout")
|
||||||
.route(web::method(Method::POST).to(route_post_logout)),
|
.route(web::method(Method::POST).to(route_post_logout)),
|
||||||
)
|
);
|
||||||
|
|
||||||
|
if let Some(oidc_config) = oidc_config {
|
||||||
|
scope = scope
|
||||||
|
.app_data(Data::new(oidc_config))
|
||||||
.service(
|
.service(
|
||||||
web::resource("/login/oidc")
|
web::resource("/login/oidc")
|
||||||
.name("frontend_login_oidc")
|
.name("frontend_login_oidc")
|
||||||
@@ -259,6 +264,8 @@ pub fn configure_frontend<AP: AuthenticationProvider, CS: CalendarStore, AS: Add
|
|||||||
web::resource("/login/oidc/callback")
|
web::resource("/login/oidc/callback")
|
||||||
.name("frontend_oidc_callback")
|
.name("frontend_oidc_callback")
|
||||||
.route(web::method(Method::GET).to(route_get_oidc_callback::<AP>)),
|
.route(web::method(Method::GET).to(route_get_oidc_callback::<AP>)),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
cfg.service(scope);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ pub enum OidcError {
|
|||||||
#[error("Cannot generate redirect url, something's not configured correctly")]
|
#[error("Cannot generate redirect url, something's not configured correctly")]
|
||||||
ActixUrlGenerationError(#[from] UrlGenerationError),
|
ActixUrlGenerationError(#[from] UrlGenerationError),
|
||||||
|
|
||||||
#[error("RustiCal is not configured correctly for OIDC")]
|
|
||||||
IncorrectConfiguration,
|
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
OidcConfigurationError(#[from] ConfigurationError),
|
OidcConfigurationError(#[from] ConfigurationError),
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
use crate::{
|
use crate::config::{OidcConfig, UserIdClaim};
|
||||||
FrontendConfig,
|
|
||||||
config::{OidcConfig, UserIdClaim},
|
|
||||||
};
|
|
||||||
use actix_session::Session;
|
use actix_session::Session;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
HttpRequest, HttpResponse, Responder,
|
HttpRequest, HttpResponse, Responder,
|
||||||
@@ -92,17 +89,12 @@ pub struct GetOidcForm {
|
|||||||
pub async fn route_post_oidc(
|
pub async fn route_post_oidc(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
Form(GetOidcForm { redirect_uri }): Form<GetOidcForm>,
|
Form(GetOidcForm { redirect_uri }): Form<GetOidcForm>,
|
||||||
config: Data<FrontendConfig>,
|
oidc_config: Data<OidcConfig>,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Result<impl Responder, OidcError> {
|
) -> Result<impl Responder, OidcError> {
|
||||||
let oidc_config = config
|
|
||||||
.oidc
|
|
||||||
.clone()
|
|
||||||
.ok_or(OidcError::IncorrectConfiguration)?;
|
|
||||||
|
|
||||||
let http_client = get_http_client();
|
let http_client = get_http_client();
|
||||||
let oidc_client = get_oidc_client(
|
let oidc_client = get_oidc_client(
|
||||||
oidc_config.clone(),
|
oidc_config.as_ref().clone(),
|
||||||
&http_client,
|
&http_client,
|
||||||
RedirectUrl::new(req.url_for_static("frontend_oidc_callback")?.to_string())?,
|
RedirectUrl::new(req.url_for_static("frontend_oidc_callback")?.to_string())?,
|
||||||
)
|
)
|
||||||
@@ -137,21 +129,16 @@ pub async fn route_post_oidc(
|
|||||||
pub struct AuthCallbackQuery {
|
pub struct AuthCallbackQuery {
|
||||||
code: AuthorizationCode,
|
code: AuthorizationCode,
|
||||||
iss: IssuerUrl,
|
iss: IssuerUrl,
|
||||||
// scope: String,
|
|
||||||
// state: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle callback from IdP page
|
||||||
pub async fn route_get_oidc_callback<AP: AuthenticationProvider>(
|
pub async fn route_get_oidc_callback<AP: AuthenticationProvider>(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
config: Data<FrontendConfig>,
|
oidc_config: Data<OidcConfig>,
|
||||||
session: Session,
|
session: Session,
|
||||||
auth_provider: Data<AP>,
|
auth_provider: Data<AP>,
|
||||||
Query(AuthCallbackQuery { code, iss }): Query<AuthCallbackQuery>,
|
Query(AuthCallbackQuery { code, iss }): Query<AuthCallbackQuery>,
|
||||||
) -> Result<impl Responder, OidcError> {
|
) -> Result<impl Responder, OidcError> {
|
||||||
let oidc_config = config
|
|
||||||
.oidc
|
|
||||||
.clone()
|
|
||||||
.ok_or(OidcError::IncorrectConfiguration)?;
|
|
||||||
assert_eq!(iss, oidc_config.issuer);
|
assert_eq!(iss, oidc_config.issuer);
|
||||||
let oidc_state = session
|
let oidc_state = session
|
||||||
.remove_as::<OidcState>(SESSION_KEY_OIDC_STATE)
|
.remove_as::<OidcState>(SESSION_KEY_OIDC_STATE)
|
||||||
@@ -160,7 +147,7 @@ pub async fn route_get_oidc_callback<AP: AuthenticationProvider>(
|
|||||||
|
|
||||||
let http_client = get_http_client();
|
let http_client = get_http_client();
|
||||||
let oidc_client = get_oidc_client(
|
let oidc_client = get_oidc_client(
|
||||||
oidc_config.clone(),
|
oidc_config.get_ref().clone(),
|
||||||
&http_client,
|
&http_client,
|
||||||
RedirectUrl::new(req.url_for_static("frontend_oidc_callback")?.to_string())?,
|
RedirectUrl::new(req.url_for_static("frontend_oidc_callback")?.to_string())?,
|
||||||
)
|
)
|
||||||
@@ -186,11 +173,11 @@ pub async fn route_get_oidc_callback<AP: AuthenticationProvider>(
|
|||||||
.await
|
.await
|
||||||
.map_err(|_| OidcError::Other("Error fetching user info"))?;
|
.map_err(|_| OidcError::Other("Error fetching user info"))?;
|
||||||
|
|
||||||
if let Some(require_group) = oidc_config.require_group {
|
if let Some(require_group) = &oidc_config.require_group {
|
||||||
if !user_info_claims
|
if !user_info_claims
|
||||||
.additional_claims()
|
.additional_claims()
|
||||||
.groups
|
.groups
|
||||||
.contains(&require_group)
|
.contains(require_group)
|
||||||
{
|
{
|
||||||
return Ok(HttpResponse::build(StatusCode::UNAUTHORIZED)
|
return Ok(HttpResponse::build(StatusCode::UNAUTHORIZED)
|
||||||
.body("User is not in an authorized group to use RustiCal"));
|
.body("User is not in an authorized group to use RustiCal"));
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{FrontendConfig, oidc::OidcProviderData};
|
use crate::{FrontendConfig, OidcConfig, oidc::OidcProviderData};
|
||||||
use actix_session::Session;
|
use actix_session::Session;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
HttpRequest, HttpResponse, Responder,
|
HttpRequest, HttpResponse, Responder,
|
||||||
@@ -24,22 +24,27 @@ pub struct GetLoginQuery {
|
|||||||
redirect_uri: Option<String>,
|
redirect_uri: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(req, config))]
|
#[instrument(skip(req, config, oidc_config))]
|
||||||
pub async fn route_get_login(
|
pub async fn route_get_login(
|
||||||
Query(GetLoginQuery { redirect_uri }): Query<GetLoginQuery>,
|
Query(GetLoginQuery { redirect_uri }): Query<GetLoginQuery>,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
config: Data<FrontendConfig>,
|
config: Data<FrontendConfig>,
|
||||||
|
oidc_config: Data<Option<OidcConfig>>,
|
||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
LoginPage {
|
let oidc_data = oidc_config
|
||||||
redirect_uri,
|
.as_ref()
|
||||||
allow_password_login: config.allow_password_login,
|
.as_ref()
|
||||||
oidc_data: config.oidc.as_ref().map(|oidc| OidcProviderData {
|
.map(|oidc_config| OidcProviderData {
|
||||||
name: &oidc.name,
|
name: &oidc_config.name,
|
||||||
redirect_url: req
|
redirect_url: req
|
||||||
.url_for_static("frontend_login_oidc")
|
.url_for_static("frontend_login_oidc")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
}),
|
});
|
||||||
|
LoginPage {
|
||||||
|
redirect_uri,
|
||||||
|
allow_password_login: config.allow_password_login,
|
||||||
|
oidc_data,
|
||||||
}
|
}
|
||||||
.respond_to(&req)
|
.respond_to(&req)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ You can set up RustiCal with an OpenID Connect identity provider
|
|||||||
## Example: Authelia
|
## Example: Authelia
|
||||||
|
|
||||||
```toml title="RustiCal configuration"
|
```toml title="RustiCal configuration"
|
||||||
[frontend.oidc]
|
[oidc]
|
||||||
name = "Authelia"
|
name = "Authelia"
|
||||||
issuer = "https://auth.example.com"
|
issuer = "https://auth.example.com"
|
||||||
client_id = "rustical"
|
client_id = "rustical"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use actix_web::{App, web};
|
|||||||
use rustical_caldav::caldav_service;
|
use rustical_caldav::caldav_service;
|
||||||
use rustical_carddav::carddav_service;
|
use rustical_carddav::carddav_service;
|
||||||
use rustical_frontend::nextcloud_login::{NextcloudFlows, configure_nextcloud_login};
|
use rustical_frontend::nextcloud_login::{NextcloudFlows, configure_nextcloud_login};
|
||||||
use rustical_frontend::{FrontendConfig, configure_frontend};
|
use rustical_frontend::{FrontendConfig, OidcConfig, configure_frontend};
|
||||||
use rustical_store::auth::AuthenticationProvider;
|
use rustical_store::auth::AuthenticationProvider;
|
||||||
use rustical_store::{AddressbookStore, CalendarStore, SubscriptionStore};
|
use rustical_store::{AddressbookStore, CalendarStore, SubscriptionStore};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -13,12 +13,14 @@ use tracing_actix_web::TracingLogger;
|
|||||||
|
|
||||||
use crate::config::NextcloudLoginConfig;
|
use crate::config::NextcloudLoginConfig;
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn make_app<AS: AddressbookStore, CS: CalendarStore, S: SubscriptionStore>(
|
pub fn make_app<AS: AddressbookStore, CS: CalendarStore, S: SubscriptionStore>(
|
||||||
addr_store: Arc<AS>,
|
addr_store: Arc<AS>,
|
||||||
cal_store: Arc<CS>,
|
cal_store: Arc<CS>,
|
||||||
subscription_store: Arc<S>,
|
subscription_store: Arc<S>,
|
||||||
auth_provider: Arc<impl AuthenticationProvider>,
|
auth_provider: Arc<impl AuthenticationProvider>,
|
||||||
frontend_config: FrontendConfig,
|
frontend_config: FrontendConfig,
|
||||||
|
oidc_config: Option<OidcConfig>,
|
||||||
nextcloud_login_config: NextcloudLoginConfig,
|
nextcloud_login_config: NextcloudLoginConfig,
|
||||||
nextcloud_flows_state: Arc<NextcloudFlows>,
|
nextcloud_flows_state: Arc<NextcloudFlows>,
|
||||||
) -> App<
|
) -> App<
|
||||||
@@ -70,6 +72,7 @@ pub fn make_app<AS: AddressbookStore, CS: CalendarStore, S: SubscriptionStore>(
|
|||||||
cal_store.clone(),
|
cal_store.clone(),
|
||||||
addr_store.clone(),
|
addr_store.clone(),
|
||||||
frontend_config,
|
frontend_config,
|
||||||
|
oidc_config,
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
.service(web::redirect("/", "/frontend").see_other());
|
.service(web::redirect("/", "/frontend").see_other());
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ pub fn cmd_gen_config(_args: GenConfigArgs) -> anyhow::Result<()> {
|
|||||||
frontend: FrontendConfig {
|
frontend: FrontendConfig {
|
||||||
secret_key: generate_frontend_secret(),
|
secret_key: generate_frontend_secret(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
oidc: None,
|
|
||||||
allow_password_login: true,
|
allow_password_login: true,
|
||||||
},
|
},
|
||||||
|
oidc: None,
|
||||||
dav_push: DavPushConfig::default(),
|
dav_push: DavPushConfig::default(),
|
||||||
nextcloud_login: Default::default(),
|
nextcloud_login: Default::default(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use rustical_frontend::FrontendConfig;
|
use rustical_frontend::{FrontendConfig, OidcConfig};
|
||||||
use rustical_store::auth::TomlUserStoreConfig;
|
use rustical_store::auth::TomlUserStoreConfig;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -84,6 +84,8 @@ pub struct Config {
|
|||||||
pub http: HttpConfig,
|
pub http: HttpConfig,
|
||||||
pub frontend: FrontendConfig,
|
pub frontend: FrontendConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub oidc: Option<OidcConfig>,
|
||||||
|
#[serde(default)]
|
||||||
pub tracing: TracingConfig,
|
pub tracing: TracingConfig,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub dav_push: DavPushConfig,
|
pub dav_push: DavPushConfig,
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ async fn main() -> Result<()> {
|
|||||||
subscription_store.clone(),
|
subscription_store.clone(),
|
||||||
user_store.clone(),
|
user_store.clone(),
|
||||||
config.frontend.clone(),
|
config.frontend.clone(),
|
||||||
|
config.oidc.clone(),
|
||||||
config.nextcloud_login.clone(),
|
config.nextcloud_login.clone(),
|
||||||
nextcloud_flows.clone(),
|
nextcloud_flows.clone(),
|
||||||
)
|
)
|
||||||
@@ -221,9 +222,9 @@ mod tests {
|
|||||||
FrontendConfig {
|
FrontendConfig {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
secret_key: generate_frontend_secret(),
|
secret_key: generate_frontend_secret(),
|
||||||
oidc: None,
|
|
||||||
allow_password_login: false,
|
allow_password_login: false,
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
NextcloudLoginConfig { enabled: false },
|
NextcloudLoginConfig { enabled: false },
|
||||||
Arc::new(NextcloudFlows::default()),
|
Arc::new(NextcloudFlows::default()),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user