Register user security keys
This commit is contained in:
@ -1,12 +1,14 @@
|
||||
use actix::Addr;
|
||||
use actix_web::{HttpResponse, Responder, web};
|
||||
use uuid::Uuid;
|
||||
use webauthn_rs::proto::RegisterPublicKeyCredential;
|
||||
|
||||
use crate::actors::users_actor;
|
||||
use crate::actors::users_actor::UsersActor;
|
||||
use crate::data::current_user::CurrentUser;
|
||||
use crate::data::totp_key::TotpKey;
|
||||
use crate::data::user::{FactorID, TwoFactor, TwoFactorType, User};
|
||||
use crate::data::webauthn_manager::WebAuthManagerReq;
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct AddTOTPRequest {
|
||||
@ -45,6 +47,43 @@ pub async fn save_totp_factor(user: CurrentUser, form: web::Json<AddTOTPRequest>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct AddWebauthnRequest {
|
||||
opaque_state: String,
|
||||
factor_name: String,
|
||||
credential: RegisterPublicKeyCredential,
|
||||
}
|
||||
|
||||
pub async fn save_webauthn_factor(user: CurrentUser, form: web::Json<AddWebauthnRequest>,
|
||||
users: web::Data<Addr<UsersActor>>,
|
||||
manager: WebAuthManagerReq) -> impl Responder {
|
||||
let key = match manager.finish_registration(
|
||||
&user,
|
||||
&form.0.opaque_state,
|
||||
form.0.credential,
|
||||
) {
|
||||
Ok(k) => k,
|
||||
Err(e) => {
|
||||
log::error!("Failed to register security key! {:?}", e);
|
||||
return HttpResponse::InternalServerError().body("Failed to register key!");
|
||||
}
|
||||
};
|
||||
|
||||
let mut user = User::from(user);
|
||||
user.add_factor(TwoFactor {
|
||||
id: FactorID(Uuid::new_v4().to_string()),
|
||||
name: form.0.factor_name,
|
||||
kind: TwoFactorType::WEBAUTHN(key),
|
||||
});
|
||||
let res = users.send(users_actor::UpdateUserRequest(user)).await.unwrap().0;
|
||||
|
||||
if !res {
|
||||
HttpResponse::InternalServerError().body("Failed to update user information!")
|
||||
} else {
|
||||
HttpResponse::Ok().body("Added new factor!")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct DeleteFactorRequest {
|
||||
id: FactorID,
|
||||
|
@ -2,6 +2,7 @@ use crate::data::client::ClientID;
|
||||
use crate::data::entity_manager::EntityManager;
|
||||
use crate::data::login_redirect::LoginRedirect;
|
||||
use crate::data::totp_key::TotpKey;
|
||||
use crate::data::webauthn_manager::WebauthnPubKey;
|
||||
use crate::utils::err::Res;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
@ -13,7 +14,7 @@ pub struct FactorID(pub String);
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum TwoFactorType {
|
||||
TOTP(TotpKey),
|
||||
_OTHER,
|
||||
WEBAUTHN(WebauthnPubKey),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
@ -27,7 +28,7 @@ impl TwoFactor {
|
||||
pub fn type_str(&self) -> &'static str {
|
||||
match self.kind {
|
||||
TwoFactorType::TOTP(_) => "Authenticator app",
|
||||
_ => unimplemented!()
|
||||
TwoFactorType::WEBAUTHN(_) => "Security key",
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,7 +36,8 @@ impl TwoFactor {
|
||||
match self.kind {
|
||||
TwoFactorType::TOTP(_) => format!("/2fa_otp?id={}&redirect={}",
|
||||
self.id.0, redirect_uri.get_encoded()),
|
||||
_ => unimplemented!()
|
||||
TwoFactorType::WEBAUTHN(_) => format!("/2fa_webauthn?id={}&redirect={}",
|
||||
self.id.0, redirect_uri.get_encoded()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
use std::io::ErrorKind;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::web;
|
||||
use webauthn_rs::{RegistrationState, Webauthn, WebauthnConfig};
|
||||
use webauthn_rs::proto::CreationChallengeResponse;
|
||||
use webauthn_rs::proto::{CreationChallengeResponse, Credential, RegisterPublicKeyCredential};
|
||||
|
||||
use crate::constants::APP_NAME;
|
||||
use crate::data::app_config::AppConfig;
|
||||
@ -35,6 +36,11 @@ pub struct RegisterKeyRequest {
|
||||
pub creation_challenge: CreationChallengeResponse,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct WebauthnPubKey {
|
||||
creds: Credential,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
struct RegisterKeyOpaqueData {
|
||||
registration_state: RegistrationState,
|
||||
@ -54,7 +60,10 @@ impl WebAuthManager {
|
||||
core: Webauthn::new(WebAuthnAppConfig {
|
||||
origin: url::Url::parse(&conf.website_origin)
|
||||
.expect("Failed to parse configuration origin!"),
|
||||
relying_party_id: conf.domain_name().to_string(),
|
||||
relying_party_id: conf.domain_name().split_once(':')
|
||||
.map(|s| s.0)
|
||||
.unwrap_or(conf.domain_name())
|
||||
.to_string(),
|
||||
}),
|
||||
crypto_wrapper: CryptoWrapper::new_random(),
|
||||
}
|
||||
@ -63,7 +72,7 @@ impl WebAuthManager {
|
||||
pub fn start_register(&self, user: &User) -> Res<RegisterKeyRequest> {
|
||||
let (creation_challenge, registration_state) = self.core.generate_challenge_register(
|
||||
&user.username,
|
||||
true,
|
||||
false,
|
||||
)?;
|
||||
|
||||
Ok(RegisterKeyRequest {
|
||||
@ -74,4 +83,18 @@ impl WebAuthManager {
|
||||
creation_challenge,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn finish_registration(&self, user: &User, opaque_state: &str,
|
||||
pub_cred: RegisterPublicKeyCredential) -> Res<WebauthnPubKey> {
|
||||
let state: RegisterKeyOpaqueData = self.crypto_wrapper.decrypt(opaque_state)?;
|
||||
if state.user_id != user.uid {
|
||||
return Err(Box::new(
|
||||
std::io::Error::new(ErrorKind::Other, "Invalid user for pubkey!")));
|
||||
}
|
||||
|
||||
let res = self.core
|
||||
.register_credential(&pub_cred, &state.registration_state, |_| Ok(false))?;
|
||||
|
||||
Ok(WebauthnPubKey { creds: res.0 })
|
||||
}
|
||||
}
|
@ -133,6 +133,7 @@ async fn main() -> std::io::Result<()> {
|
||||
|
||||
// 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))
|
||||
|
||||
// Admin routes
|
||||
|
Reference in New Issue
Block a user