BasicOIDC/src/main.rs
Pierre Hubert 6cc9f4c54c
All checks were successful
continuous-integration/drone/push Build is passing
Refactor dependencies to reduce code base size (#111)
Use crates to reduce code base size :

* `actix-remote-ip` to safely determine user IP location
* `light-openid` for the OpenID primitives & as client to handle federation

Reviewed-on: #111
2023-04-29 11:11:24 +00:00

280 lines
10 KiB
Rust

use core::time::Duration;
use std::sync::Arc;
use actix::Actor;
use actix_identity::config::LogoutBehaviour;
use actix_identity::IdentityMiddleware;
use actix_remote_ip::RemoteIPConfig;
use actix_session::storage::CookieSessionStore;
use actix_session::SessionMiddleware;
use actix_web::cookie::{Key, SameSite};
use actix_web::middleware::Logger;
use actix_web::{get, middleware, web, App, HttpResponse, HttpServer};
use basic_oidc::actors::bruteforce_actor::BruteForceActor;
use basic_oidc::actors::openid_sessions_actor::OpenIDSessionsActor;
use basic_oidc::actors::providers_states_actor::ProvidersStatesActor;
use basic_oidc::actors::users_actor::{UsersActor, UsersSyncBackend};
use basic_oidc::constants::*;
use basic_oidc::controllers::assets_controller::assets_route;
use basic_oidc::controllers::*;
use basic_oidc::data::app_config::AppConfig;
use basic_oidc::data::client::ClientManager;
use basic_oidc::data::entity_manager::EntityManager;
use basic_oidc::data::jwt_signer::JWTSigner;
use basic_oidc::data::provider::ProvidersManager;
use basic_oidc::data::user::User;
use basic_oidc::data::webauthn_manager::WebAuthManager;
use basic_oidc::middlewares::auth_middleware::AuthMiddleware;
#[get("/health")]
async fn health() -> &'static str {
"Running"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
let config = AppConfig::get();
if !config.storage_path().exists() {
log::error!(
"Specified storage path {:?} does not exists!",
config.storage_path()
);
panic!()
}
let mut users = EntityManager::<User>::open_or_create(config.users_file())
.expect("Failed to load users list!");
// Create initial user if required
if users.is_empty() {
log::info!("Create default {} user", DEFAULT_ADMIN_USERNAME);
let default_admin = User {
username: DEFAULT_ADMIN_USERNAME.to_string(),
authorized_clients: None,
admin: true,
..Default::default()
};
users
.insert(default_admin.clone())
.expect("Failed to create initial user!");
// Set default admin password
users
.change_user_password(&default_admin.uid, DEFAULT_ADMIN_PASSWORD, true)
.expect("Failed to change default admin password!");
}
let users_actor = UsersActor::new(users).start();
let bruteforce_actor = BruteForceActor::default().start();
let providers_states_actor = ProvidersStatesActor::default().start();
let openid_sessions_actor = OpenIDSessionsActor::default().start();
let jwt_signer = JWTSigner::gen_from_memory().expect("Failed to generate JWKS key");
let webauthn_manager = Arc::new(WebAuthManager::init(config));
let mut clients =
ClientManager::open_or_create(config.clients_file()).expect("Failed to load clients list!");
clients.apply_environment_variables();
let clients = Arc::new(clients);
let mut providers = ProvidersManager::open_or_create(config.providers_file())
.expect("Failed to load providers list!");
providers.apply_environment_variables();
let providers = Arc::new(providers);
log::info!("Server will listen on {}", config.listen_address);
let listen_address = config.listen_address.to_string();
HttpServer::new(move || {
let session_mw = SessionMiddleware::builder(
CookieSessionStore::default(),
Key::from(config.token_key.as_bytes()),
)
.cookie_name(SESSION_COOKIE_NAME.to_string())
.cookie_secure(config.secure_cookie())
.cookie_same_site(SameSite::Lax)
.build();
let identity_middleware = IdentityMiddleware::builder()
.logout_behaviour(LogoutBehaviour::PurgeSession)
.visit_deadline(Some(Duration::from_secs(MAX_INACTIVITY_DURATION)))
.login_deadline(Some(Duration::from_secs(MAX_SESSION_DURATION)))
.build();
App::new()
.app_data(web::Data::new(users_actor.clone()))
.app_data(web::Data::new(bruteforce_actor.clone()))
.app_data(web::Data::new(providers_states_actor.clone()))
.app_data(web::Data::new(openid_sessions_actor.clone()))
.app_data(web::Data::new(clients.clone()))
.app_data(web::Data::new(providers.clone()))
.app_data(web::Data::new(jwt_signer.clone()))
.app_data(web::Data::new(webauthn_manager.clone()))
.app_data(web::Data::new(RemoteIPConfig {
proxy: AppConfig::get().proxy_ip.clone(),
}))
.wrap(
middleware::DefaultHeaders::new().add(("Permissions-Policy", "interest-cohort=()")),
)
.wrap(Logger::default())
.wrap(AuthMiddleware {})
.wrap(identity_middleware)
.wrap(session_mw)
// Main route
.route(
"/",
web::get().to(|| async {
HttpResponse::Found()
.append_header(("Location", "/settings"))
.finish()
}),
)
.route("/robots.txt", web::get().to(assets_controller::robots_txt))
// Health route
.service(health)
// Assets serving
.route("/assets/{path:.*}", web::get().to(assets_route))
// Login pages
.route("/logout", web::get().to(login_controller::logout_route))
.route("/login", web::get().to(login_controller::login_route))
.route("/login", web::post().to(login_controller::login_route))
.route(
"/reset_password",
web::get().to(login_controller::reset_password_route),
)
.route(
"/reset_password",
web::post().to(login_controller::reset_password_route),
)
.route(
"/2fa_auth",
web::get().to(login_controller::choose_2fa_method),
)
.route("/2fa_otp", web::get().to(login_controller::login_with_otp))
.route("/2fa_otp", web::post().to(login_controller::login_with_otp))
.route(
"/2fa_webauthn",
web::get().to(login_controller::login_with_webauthn),
)
// Login api
.route(
"/login/api/auth_webauthn",
web::post().to(login_api::auth_webauthn),
)
// Providers controller
.route(
"/login_with_prov",
web::get().to(providers_controller::start_login),
)
.route(
OIDC_PROVIDER_CB_URI,
web::get().to(providers_controller::finish_login),
)
// Settings routes
.route(
"/settings",
web::get().to(settings_controller::account_settings_details_route),
)
.route(
"/settings/change_password",
web::get().to(settings_controller::change_password_route),
)
.route(
"/settings/change_password",
web::post().to(settings_controller::change_password_route),
)
.route(
"/settings/two_factors",
web::get().to(two_factors_controller::two_factors_route),
)
.route(
"/settings/two_factors/add_totp",
web::get().to(two_factors_controller::add_totp_factor_route),
)
.route(
"/settings/two_factors/add_webauthn",
web::get().to(two_factors_controller::add_webauthn_factor_route),
)
// User API
.route(
"/settings/api/two_factor/save_totp_factor",
web::post().to(two_factor_api::save_totp_factor),
)
.route(
"/settings/api/two_factor/save_webauthn_factor",
web::post().to(two_factor_api::save_webauthn_factor),
)
.route(
"/settings/api/two_factor/delete_factor",
web::post().to(two_factor_api::delete_factor),
)
.route(
"/settings/api/two_factor/clear_login_history",
// Use POST to prevent CSRF
web::post().to(two_factor_api::clear_login_history),
)
// Admin routes
.route(
"/admin",
web::get().to(|| async {
HttpResponse::Found()
.append_header(("Location", "/settings"))
.finish()
}),
)
.route(
"/admin/clients",
web::get().to(admin_controller::clients_route),
)
.route(
"/admin/providers",
web::get().to(admin_controller::providers_route),
)
.route("/admin/users", web::get().to(admin_controller::users_route))
.route(
"/admin/users",
web::post().to(admin_controller::users_route),
)
.route(
"/admin/create_user",
web::get().to(admin_controller::create_user),
)
.route(
"/admin/edit_user",
web::get().to(admin_controller::edit_user),
)
// Admin API
.route(
"/admin/api/find_username",
web::post().to(admin_api::find_username),
)
.route(
"/admin/api/delete_user",
web::post().to(admin_api::delete_user),
)
// OpenID routes
.route(
"/.well-known/openid-configuration",
web::get().to(openid_controller::get_configuration),
)
.route(AUTHORIZE_URI, web::get().to(openid_controller::authorize))
.route(TOKEN_URI, web::post().to(openid_controller::token))
.route(CERT_URI, web::get().to(openid_controller::cert_uri))
.route(
USERINFO_URI,
web::post().to(openid_controller::user_info_post),
)
.route(
USERINFO_URI,
web::get().to(openid_controller::user_info_get),
)
})
.bind(listen_address)?
.run()
.await
}