nom-nom-nix-gc/src/handlers/mod.rs

95 lines
4.2 KiB
Rust

use webauthn_rs::prelude::{RegisterPublicKeyCredential, Uuid};
use actix_web::{error, HttpResponse, http::header::{ContentType, self}, web, cookie::{Cookie, SameSite}, HttpRequest};
use crate::{models::{AppState, User, PendingRegistration}, templates};
pub async fn landing_page (app_state: web::Data<AppState<'_>>) -> HttpResponse {
let content: String = templates::landing_page(app_state.hbs.clone()).unwrap();
HttpResponse::Ok()
.content_type(ContentType::html())
.body(content)
}
/// Page you get after clicking a register-user link.
///
/// The path contains the registration Uuid segment.
pub async fn webauthn_registration(app_state: web::Data<AppState<'_>>, path: web::Path<String>) -> HttpResponse {
let requested_uuid = match Uuid::parse_str(&path.into_inner()) {
Ok(p) => p,
Err(_) => return HttpResponse::from_error(error::ErrorBadRequest("This registration token is invalid: invalid UUID")),
};
let registration_link = app_state.retrieve_registration_link(requested_uuid).await;
let user = match registration_link {
Ok(Some(user)) => user,
Ok(None) => return HttpResponse::from_error(error::ErrorBadRequest("This registration token is invalid: cannot find it in the database")),
Err(e) => return HttpResponse::from_error(error::ErrorInternalServerError(e.to_string())),
};
let response = templates::register_user_start(app_state.hbs.clone(), requested_uuid, user.user_name).unwrap();
HttpResponse::Ok()
.content_type(ContentType::html())
.body(response)
}
/// First phase of the webauthn key enrolling.
///
/// For now, we don't save anything to the DB. We just generate the
/// challenge, the UUID and store everything in the session hashmap
/// server-side, in the response and cookie on the client-side.
pub async fn start_webauthn_registration(app_state: web::Data<AppState<'_>>, user: web::Json<User>) -> HttpResponse {
let uuid = Uuid::new_v4();
let (creation_challenge_response, passkey_registration) = app_state.webauthn.start_passkey_registration(uuid, &user.user_name, &user.user_name, None).unwrap();
let uuid_str = uuid.to_string();
{
let mut user_registrations = app_state.session.user_registrations.write().await;
user_registrations.insert(uuid, PendingRegistration{
user: user.into_inner(),
registration: passkey_registration
});
}
let res = serde_json::to_string(&creation_challenge_response).unwrap();
let cookie = Cookie::build("uuid", &uuid_str)
.secure(true)
.same_site(SameSite::Strict)
.finish();
HttpResponse::Ok()
.content_type(ContentType::json())
.insert_header((header::SET_COOKIE, cookie.encoded().to_string()))
.body(res)
}
/// Final phase of the webauthn key enrolling.
///
/// We verify the key enrolling challenge, then store the new key in the DB.
pub async fn finish_webauthn_registration(req: HttpRequest, app_state: web::Data<AppState<'_>>, register: web::Json<RegisterPublicKeyCredential>) -> impl actix_web::Responder {
let cook = req.cookie("uuid");
let uuid = Uuid::parse_str(cook.unwrap().value()).unwrap();
let pending_registration: Option<PendingRegistration> = {
let user_registrations = app_state.session.user_registrations.read().await;
user_registrations.get(&uuid).cloned()
};
// 1. Check registration.
// 2. Save user.
// 3. Save key.
match pending_registration {
Some(PendingRegistration { user, registration }) => {
let registration_result = app_state.webauthn.finish_passkey_registration(&register, &registration);
match registration_result {
Ok(passkey) => {
let _ = app_state.save_user(&user).await;
let _ = app_state.save_user_key(&uuid, &passkey).await;
HttpResponse::Ok()
.body("ok")
},
Err(_) =>
HttpResponse::from_error(error::ErrorUnauthorized("Webauthn challenge failed"))
}
},
None => {
return HttpResponse::from_error(error::ErrorInternalServerError("Session expired"))
}
}
}