Merge factors type for authentication
This commit is contained in:
@ -27,8 +27,8 @@ impl AccessToken {
|
||||
jwt_id: None,
|
||||
nonce: self.nonce,
|
||||
custom: CustomAccessTokenClaims {
|
||||
rand_val: self.rand_val
|
||||
rand_val: self.rand_val,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,4 +42,4 @@ impl EntityManager<Client> {
|
||||
c.redirect_uri = apply_env_vars(&c.redirect_uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,10 +16,8 @@ impl CodeChallenge {
|
||||
match self.code_challenge_method.as_str() {
|
||||
"plain" => code_verifer.eq(&self.code_challenge),
|
||||
"S256" => {
|
||||
let encoded = base64::encode_config(
|
||||
sha256(code_verifer.as_bytes()),
|
||||
URL_SAFE_NO_PAD,
|
||||
);
|
||||
let encoded =
|
||||
base64::encode_config(sha256(code_verifer.as_bytes()), URL_SAFE_NO_PAD);
|
||||
|
||||
encoded.eq(&self.code_challenge)
|
||||
}
|
||||
@ -64,7 +62,10 @@ mod test {
|
||||
code_challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM".to_string(),
|
||||
};
|
||||
|
||||
assert_eq!(true, chal.verify_code("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"));
|
||||
assert_eq!(
|
||||
true,
|
||||
chal.verify_code("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk")
|
||||
);
|
||||
assert_eq!(false, chal.verify_code("text1"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::io::ErrorKind;
|
||||
|
||||
use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce};
|
||||
use aes_gcm::aead::{Aead, OsRng};
|
||||
use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce};
|
||||
use rand::Rng;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
@ -17,7 +17,9 @@ pub struct CryptoWrapper {
|
||||
impl CryptoWrapper {
|
||||
/// Generate a new memory wrapper
|
||||
pub fn new_random() -> Self {
|
||||
Self { key: Aes256Gcm::generate_key(&mut OsRng) }
|
||||
Self {
|
||||
key: Aes256Gcm::generate_key(&mut OsRng),
|
||||
}
|
||||
}
|
||||
|
||||
/// Encrypt some data
|
||||
@ -27,11 +29,11 @@ impl CryptoWrapper {
|
||||
|
||||
let serialized_data = bincode::serialize(data)?;
|
||||
|
||||
let mut enc = aes_key.encrypt(Nonce::from_slice(&nonce_bytes),
|
||||
serialized_data.as_slice()).unwrap();
|
||||
let mut enc = aes_key
|
||||
.encrypt(Nonce::from_slice(&nonce_bytes), serialized_data.as_slice())
|
||||
.unwrap();
|
||||
enc.extend_from_slice(&nonce_bytes);
|
||||
|
||||
|
||||
Ok(base64::encode(enc))
|
||||
}
|
||||
|
||||
@ -40,8 +42,10 @@ impl CryptoWrapper {
|
||||
let bytes = base64::decode(input)?;
|
||||
|
||||
if bytes.len() < NONCE_LEN {
|
||||
return Err(Box::new(std::io::Error::new(ErrorKind::Other,
|
||||
"Input string is smaller than nonce!")));
|
||||
return Err(Box::new(std::io::Error::new(
|
||||
ErrorKind::Other,
|
||||
"Input string is smaller than nonce!",
|
||||
)));
|
||||
}
|
||||
|
||||
let (enc, nonce) = bytes.split_at(bytes.len() - NONCE_LEN);
|
||||
@ -53,8 +57,10 @@ impl CryptoWrapper {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
log::error!("Failed to decrypt wrapped data! {:#?}", e);
|
||||
return Err(Box::new(std::io::Error::new(ErrorKind::Other,
|
||||
"Failed to decrypt wrapped data!")));
|
||||
return Err(Box::new(std::io::Error::new(
|
||||
ErrorKind::Other,
|
||||
"Failed to decrypt wrapped data!",
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
@ -87,4 +93,4 @@ mod test {
|
||||
let enc = wrapper_1.encrypt(&msg).unwrap();
|
||||
wrapper_2.decrypt::<Message>(&enc).unwrap_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ use std::pin::Pin;
|
||||
|
||||
use actix::Addr;
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{Error, FromRequest, HttpRequest, web};
|
||||
use actix_web::dev::Payload;
|
||||
use actix_web::error::ErrorInternalServerError;
|
||||
use actix_web::{web, Error, FromRequest, HttpRequest};
|
||||
|
||||
use crate::actors::users_actor;
|
||||
use crate::actors::users_actor::UsersActor;
|
||||
@ -31,27 +31,33 @@ impl Deref for CurrentUser {
|
||||
|
||||
impl FromRequest for CurrentUser {
|
||||
type Error = Error;
|
||||
type Future = Pin<Box<dyn Future<Output=Result<Self, Self::Error>>>>;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
|
||||
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
let user_actor: &web::Data<Addr<UsersActor>> = req.app_data().expect("UserActor undefined!");
|
||||
let user_actor: &web::Data<Addr<UsersActor>> =
|
||||
req.app_data().expect("UserActor undefined!");
|
||||
let user_actor: Addr<UsersActor> = user_actor.as_ref().clone();
|
||||
let identity: Identity = Identity::from_request(req, payload).into_inner()
|
||||
let identity: Identity = Identity::from_request(req, payload)
|
||||
.into_inner()
|
||||
.expect("Failed to get identity!");
|
||||
let user_id = SessionIdentity(Some(&identity)).user_id();
|
||||
|
||||
|
||||
Box::pin(async move {
|
||||
let user = match user_actor.send(
|
||||
users_actor::GetUserRequest(user_id)
|
||||
).await.unwrap().0 {
|
||||
let user = match user_actor
|
||||
.send(users_actor::GetUserRequest(user_id))
|
||||
.await
|
||||
.unwrap()
|
||||
.0
|
||||
{
|
||||
Some(u) => u,
|
||||
None => {
|
||||
return Err(ErrorInternalServerError("Could not extract user information!"));
|
||||
return Err(ErrorInternalServerError(
|
||||
"Could not extract user information!",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(CurrentUser(user))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,10 @@ use std::slice::{Iter, IterMut};
|
||||
|
||||
use crate::utils::err::Res;
|
||||
|
||||
enum FileFormat { Json, Yaml }
|
||||
enum FileFormat {
|
||||
Json,
|
||||
Yaml,
|
||||
}
|
||||
|
||||
pub struct EntityManager<E> {
|
||||
file_path: PathBuf,
|
||||
@ -11,8 +14,8 @@ pub struct EntityManager<E> {
|
||||
}
|
||||
|
||||
impl<E> EntityManager<E>
|
||||
where
|
||||
E: serde::Serialize + serde::de::DeserializeOwned + Eq + Clone,
|
||||
where
|
||||
E: serde::Serialize + serde::de::DeserializeOwned + Eq + Clone,
|
||||
{
|
||||
/// Open entity
|
||||
pub fn open_or_create<A: AsRef<Path>>(path: A) -> Res<Self> {
|
||||
@ -30,7 +33,7 @@ impl<E> EntityManager<E>
|
||||
file_path: path.as_ref().to_path_buf(),
|
||||
list: match Self::file_format(path.as_ref()) {
|
||||
FileFormat::Json => serde_json::from_str(&file_content)?,
|
||||
FileFormat::Yaml => serde_yaml::from_str(&file_content)?
|
||||
FileFormat::Yaml => serde_yaml::from_str(&file_content)?,
|
||||
},
|
||||
})
|
||||
}
|
||||
@ -49,7 +52,7 @@ impl<E> EntityManager<E>
|
||||
fn file_format(p: &Path) -> FileFormat {
|
||||
match p.to_string_lossy().ends_with(".json") {
|
||||
true => FileFormat::Json,
|
||||
false => FileFormat::Yaml
|
||||
false => FileFormat::Yaml,
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,8 +73,8 @@ impl<E> EntityManager<E>
|
||||
|
||||
/// Replace entries in the list that matches a criteria
|
||||
pub fn replace_entries<F>(&mut self, filter: F, el: &E) -> Res
|
||||
where
|
||||
F: Fn(&E) -> bool,
|
||||
where
|
||||
F: Fn(&E) -> bool,
|
||||
{
|
||||
for i in 0..self.list.len() {
|
||||
if filter(&self.list[i]) {
|
||||
|
@ -49,4 +49,4 @@ impl IdToken {
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,9 @@ pub struct JWTSigner(RS256KeyPair);
|
||||
|
||||
impl JWTSigner {
|
||||
pub fn gen_from_memory() -> Res<Self> {
|
||||
Ok(Self(RS256KeyPair::generate(2048)?
|
||||
.with_key_id(&format!("key-{}", rand_str(15)))))
|
||||
Ok(Self(
|
||||
RS256KeyPair::generate(2048)?.with_key_id(&format!("key-{}", rand_str(15))),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_json_web_key(&self) -> JsonWebKey {
|
||||
@ -45,4 +46,4 @@ impl JWTSigner {
|
||||
pub fn sign_token<E: Serialize + DeserializeOwned>(&self, c: JWTClaims<E>) -> Res<String> {
|
||||
Ok(self.0.sign(c)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,4 +18,4 @@ impl Default for LoginRedirect {
|
||||
fn default() -> Self {
|
||||
Self("/".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
pub mod app_config;
|
||||
pub mod entity_manager;
|
||||
pub mod session_identity;
|
||||
pub mod user;
|
||||
pub mod client;
|
||||
pub mod remote_ip;
|
||||
pub mod current_user;
|
||||
pub mod openid_config;
|
||||
pub mod jwt_signer;
|
||||
pub mod id_token;
|
||||
pub mod code_challenge;
|
||||
pub mod open_id_user_info;
|
||||
pub mod access_token;
|
||||
pub mod totp_key;
|
||||
pub mod app_config;
|
||||
pub mod client;
|
||||
pub mod code_challenge;
|
||||
pub mod crypto_wrapper;
|
||||
pub mod current_user;
|
||||
pub mod entity_manager;
|
||||
pub mod id_token;
|
||||
pub mod jwt_signer;
|
||||
pub mod login_redirect;
|
||||
pub mod open_id_user_info;
|
||||
pub mod openid_config;
|
||||
pub mod remote_ip;
|
||||
pub mod session_identity;
|
||||
pub mod totp_key;
|
||||
pub mod user;
|
||||
pub mod webauthn_manager;
|
||||
pub mod crypto_wrapper;
|
@ -21,4 +21,4 @@ pub struct OpenIDUserInfo {
|
||||
|
||||
/// True if the End-User's e-mail address has been verified; otherwise false. When this Claim Value is true, this means that the OP took affirmative steps to ensure that this e-mail address was controlled by the End-User at the time the verification was performed. The means by which an e-mail address is verified is context-specific, and dependent upon the trust framework or contractual agreements within which the parties are operating.
|
||||
pub email_verified: bool,
|
||||
}
|
||||
}
|
||||
|
@ -34,4 +34,4 @@ pub struct OpenIDConfig {
|
||||
pub claims_supported: Vec<&'static str>,
|
||||
|
||||
pub code_challenge_methods_supported: Vec<&'static str>,
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
use std::net::IpAddr;
|
||||
|
||||
use actix_web::{Error, FromRequest, HttpRequest, web};
|
||||
use actix_web::dev::Payload;
|
||||
use futures_util::future::{Ready, ready};
|
||||
use actix_web::{web, Error, FromRequest, HttpRequest};
|
||||
use futures_util::future::{ready, Ready};
|
||||
|
||||
use crate::data::app_config::AppConfig;
|
||||
use crate::utils::network_utils::get_remote_ip;
|
||||
@ -25,4 +25,4 @@ impl FromRequest for RemoteIP {
|
||||
let config: &web::Data<AppConfig> = req.app_data().expect("AppData undefined!");
|
||||
ready(Ok(RemoteIP(get_remote_ip(req, config.proxy_ip.as_deref()))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,8 +33,7 @@ impl<'a> SessionIdentity<'a> {
|
||||
fn get_session_data(&self) -> Option<SessionIdentityData> {
|
||||
if let Some(id) = self.0 {
|
||||
Self::deserialize_session_data(id.id().ok())
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@ -71,12 +70,15 @@ impl<'a> SessionIdentity<'a> {
|
||||
}
|
||||
|
||||
pub fn set_user(&self, req: &HttpRequest, user: &User, status: SessionStatus) {
|
||||
self.set_session_data(req, &SessionIdentityData {
|
||||
id: Some(user.uid.clone()),
|
||||
is_admin: user.admin,
|
||||
auth_time: time(),
|
||||
status,
|
||||
});
|
||||
self.set_session_data(
|
||||
req,
|
||||
&SessionIdentityData {
|
||||
id: Some(user.uid.clone()),
|
||||
is_admin: user.admin,
|
||||
auth_time: time(),
|
||||
status,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn set_status(&self, req: &HttpRequest, status: SessionStatus) {
|
||||
@ -108,7 +110,9 @@ impl<'a> SessionIdentity<'a> {
|
||||
}
|
||||
|
||||
pub fn user_id(&self) -> UserID {
|
||||
self.get_session_data().unwrap_or_default().id
|
||||
self.get_session_data()
|
||||
.unwrap_or_default()
|
||||
.id
|
||||
.expect("UserID should never be null here!")
|
||||
}
|
||||
|
||||
|
@ -23,13 +23,15 @@ impl TotpKey {
|
||||
pub fn new_random() -> Self {
|
||||
let random_bytes = rand::thread_rng().gen::<[u8; 10]>();
|
||||
Self {
|
||||
encoded: base32::encode(BASE32_ALPHABET, &random_bytes)
|
||||
encoded: base32::encode(BASE32_ALPHABET, &random_bytes),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a key from an encoded secret
|
||||
pub fn from_encoded_secret(s: &str) -> Self {
|
||||
Self { encoded: s.to_string() }
|
||||
Self {
|
||||
encoded: s.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get QrCode URL for user
|
||||
@ -74,15 +76,19 @@ impl TotpKey {
|
||||
/// Get the code at a specific time
|
||||
fn get_code_at<F: Fn() -> u64>(&self, get_time: F) -> Res<String> {
|
||||
let gen = TotpGenerator::new()
|
||||
.set_digit(NUM_DIGITS).unwrap()
|
||||
.set_step(PERIOD).unwrap()
|
||||
.set_digit(NUM_DIGITS)
|
||||
.unwrap()
|
||||
.set_step(PERIOD)
|
||||
.unwrap()
|
||||
.set_hash_algorithm(HashAlgorithm::SHA1)
|
||||
.build();
|
||||
|
||||
let key = match base32::decode(BASE32_ALPHABET, &self.encoded) {
|
||||
None => {
|
||||
return Err(Box::new(
|
||||
std::io::Error::new(ErrorKind::Other, "Failed to decode base32 secret!")));
|
||||
return Err(Box::new(std::io::Error::new(
|
||||
ErrorKind::Other,
|
||||
"Failed to decode base32 secret!",
|
||||
)));
|
||||
}
|
||||
Some(k) => k,
|
||||
};
|
||||
@ -113,4 +119,4 @@ mod test {
|
||||
let key = TotpKey::from_encoded_secret("JBSWY3DPEHPK3PXP");
|
||||
assert_eq!("124851", key.get_code_at(|| 1650470683).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,14 +32,32 @@ impl TwoFactor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn description_str(&self) -> &'static str {
|
||||
match self.kind {
|
||||
TwoFactorType::TOTP(_) => "Login by entering an OTP code",
|
||||
TwoFactorType::WEBAUTHN(_) => "Login using a security key",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_image(&self) -> &'static str {
|
||||
match self.kind {
|
||||
TwoFactorType::TOTP(_) => "/assets/img/pin.svg",
|
||||
TwoFactorType::WEBAUTHN(_) => "/assets/img/key.svg",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn login_url(&self, redirect_uri: &LoginRedirect) -> String {
|
||||
match self.kind {
|
||||
TwoFactorType::TOTP(_) => format!("/2fa_otp?id={}&redirect={}",
|
||||
self.id.0, redirect_uri.get_encoded()),
|
||||
TwoFactorType::WEBAUTHN(_) => format!("/2fa_webauthn?id={}&redirect={}",
|
||||
self.id.0, redirect_uri.get_encoded()),
|
||||
TwoFactorType::TOTP(_) => format!("/2fa_otp?redirect={}", redirect_uri.get_encoded()),
|
||||
TwoFactorType::WEBAUTHN(_) => {
|
||||
format!("/2fa_webauthn?redirect={}", redirect_uri.get_encoded())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_webauthn(&self) -> bool {
|
||||
matches!(self.kind, TwoFactorType::WEBAUTHN(_))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
@ -71,7 +89,7 @@ impl User {
|
||||
pub fn can_access_app(&self, id: &ClientID) -> bool {
|
||||
match &self.authorized_clients {
|
||||
None => true,
|
||||
Some(c) => c.contains(id)
|
||||
Some(c) => c.contains(id),
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,6 +112,49 @@ impl User {
|
||||
pub fn find_factor(&self, factor_id: &FactorID) -> Option<&TwoFactor> {
|
||||
self.two_factor.iter().find(|f| f.id.eq(factor_id))
|
||||
}
|
||||
|
||||
pub fn has_webauthn_factor(&self) -> bool {
|
||||
self.two_factor.iter().any(TwoFactor::is_webauthn)
|
||||
}
|
||||
|
||||
/// Get all registered OTP registered passwords
|
||||
pub fn get_otp_factors(&self) -> Vec<TotpKey> {
|
||||
self.two_factor.iter().fold(vec![], |mut acc, factor| {
|
||||
if let TwoFactorType::TOTP(key) = &factor.kind {
|
||||
acc.push(key.clone())
|
||||
}
|
||||
|
||||
acc
|
||||
})
|
||||
}
|
||||
|
||||
/// Get all registered 2FA webauthn public keys
|
||||
pub fn get_webauthn_pub_keys(&self) -> Vec<WebauthnPubKey> {
|
||||
self.two_factor.iter().fold(vec![], |mut acc, factor| {
|
||||
if let TwoFactorType::WEBAUTHN(key) = &factor.kind {
|
||||
acc.push(*key.clone())
|
||||
}
|
||||
|
||||
acc
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the first factor of each kind of factors
|
||||
pub fn get_distinct_factors_types(&self) -> Vec<&TwoFactor> {
|
||||
let mut urls = vec![];
|
||||
|
||||
self.two_factor
|
||||
.iter()
|
||||
.filter(|f| {
|
||||
if urls.contains(&f.type_str()) {
|
||||
false
|
||||
} else {
|
||||
urls.push(f.type_str());
|
||||
true
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for User {
|
||||
@ -157,8 +218,8 @@ impl EntityManager<User> {
|
||||
|
||||
/// Update user information
|
||||
fn update_user<F>(&mut self, id: &UserID, update: F) -> bool
|
||||
where
|
||||
F: FnOnce(User) -> User,
|
||||
where
|
||||
F: FnOnce(User) -> User,
|
||||
{
|
||||
let user = match self.find_by_user_id(id) {
|
||||
None => return false,
|
||||
|
@ -3,10 +3,15 @@ use std::sync::Arc;
|
||||
|
||||
use actix_web::web;
|
||||
use uuid::Uuid;
|
||||
use webauthn_rs::prelude::{
|
||||
CreationChallengeResponse, Passkey, PublicKeyCredential, RegisterPublicKeyCredential,
|
||||
RequestChallengeResponse,
|
||||
};
|
||||
use webauthn_rs::{Webauthn, WebauthnBuilder};
|
||||
use webauthn_rs::prelude::{CreationChallengeResponse, Passkey, PublicKeyCredential, RegisterPublicKeyCredential, RequestChallengeResponse};
|
||||
|
||||
use crate::constants::{APP_NAME, WEBAUTHN_LOGIN_CHALLENGE_EXPIRE, WEBAUTHN_REGISTER_CHALLENGE_EXPIRE};
|
||||
use crate::constants::{
|
||||
APP_NAME, WEBAUTHN_LOGIN_CHALLENGE_EXPIRE, WEBAUTHN_REGISTER_CHALLENGE_EXPIRE,
|
||||
};
|
||||
use crate::data::app_config::AppConfig;
|
||||
use crate::data::crypto_wrapper::CryptoWrapper;
|
||||
use crate::data::user::{User, UserID};
|
||||
@ -42,7 +47,6 @@ struct AuthStateOpaqueData {
|
||||
expire: u64,
|
||||
}
|
||||
|
||||
|
||||
pub type WebAuthManagerReq = web::Data<Arc<WebAuthManager>>;
|
||||
|
||||
pub struct WebAuthManager {
|
||||
@ -54,24 +58,23 @@ impl WebAuthManager {
|
||||
pub fn init(conf: &AppConfig) -> Self {
|
||||
Self {
|
||||
core: WebauthnBuilder::new(
|
||||
conf.domain_name().split_once(':')
|
||||
conf.domain_name()
|
||||
.split_once(':')
|
||||
.map(|s| s.0)
|
||||
.unwrap_or_else(|| conf.domain_name()),
|
||||
&url::Url::parse(&conf.website_origin)
|
||||
.expect("Failed to parse configuration origin!"))
|
||||
.expect("Invalid Webauthn configuration")
|
||||
.rp_name(APP_NAME)
|
||||
.build()
|
||||
.expect("Failed to build webauthn")
|
||||
|
||||
,
|
||||
.expect("Failed to parse configuration origin!"),
|
||||
)
|
||||
.expect("Invalid Webauthn configuration")
|
||||
.rp_name(APP_NAME)
|
||||
.build()
|
||||
.expect("Failed to build webauthn"),
|
||||
crypto_wrapper: CryptoWrapper::new_random(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_register(&self, user: &User) -> Res<RegisterKeyRequest> {
|
||||
let (creation_challenge, registration_state)
|
||||
= self.core.start_passkey_registration(
|
||||
let (creation_challenge, registration_state) = self.core.start_passkey_registration(
|
||||
Uuid::parse_str(&user.uid.0).expect("Failed to parse user id"),
|
||||
&user.username,
|
||||
&user.full_name(),
|
||||
@ -88,29 +91,43 @@ impl WebAuthManager {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn finish_registration(&self, user: &User, opaque_state: &str,
|
||||
pub_cred: RegisterPublicKeyCredential) -> Res<WebauthnPubKey> {
|
||||
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!")));
|
||||
return Err(Box::new(std::io::Error::new(
|
||||
ErrorKind::Other,
|
||||
"Invalid user for pubkey!",
|
||||
)));
|
||||
}
|
||||
|
||||
if state.expire < time() {
|
||||
return Err(Box::new(
|
||||
std::io::Error::new(ErrorKind::Other, "Challenge has expired!")));
|
||||
return Err(Box::new(std::io::Error::new(
|
||||
ErrorKind::Other,
|
||||
"Challenge has expired!",
|
||||
)));
|
||||
}
|
||||
|
||||
let res = self.core
|
||||
.finish_passkey_registration(&pub_cred, &serde_json::from_str(&state.registration_state)?)?;
|
||||
let res = self.core.finish_passkey_registration(
|
||||
&pub_cred,
|
||||
&serde_json::from_str(&state.registration_state)?,
|
||||
)?;
|
||||
|
||||
Ok(WebauthnPubKey { creds: res })
|
||||
}
|
||||
|
||||
pub fn start_authentication(&self, user_id: &UserID, key: &WebauthnPubKey) -> Res<AuthRequest> {
|
||||
let (login_challenge, authentication_state) = self.core.start_passkey_authentication(&vec![
|
||||
key.creds.clone()
|
||||
])?;
|
||||
pub fn start_authentication(
|
||||
&self,
|
||||
user_id: &UserID,
|
||||
keys: &[WebauthnPubKey],
|
||||
) -> Res<AuthRequest> {
|
||||
let (login_challenge, authentication_state) = self.core.start_passkey_authentication(
|
||||
&keys.iter().map(|k| k.creds.clone()).collect::<Vec<_>>(),
|
||||
)?;
|
||||
|
||||
Ok(AuthRequest {
|
||||
opaque_state: self.crypto_wrapper.encrypt(&AuthStateOpaqueData {
|
||||
@ -122,22 +139,32 @@ impl WebAuthManager {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn finish_authentication(&self, user_id: &UserID, opaque_state: &str,
|
||||
pub_cred: &PublicKeyCredential) -> Res {
|
||||
pub fn finish_authentication(
|
||||
&self,
|
||||
user_id: &UserID,
|
||||
opaque_state: &str,
|
||||
pub_cred: &PublicKeyCredential,
|
||||
) -> Res {
|
||||
let state: AuthStateOpaqueData = self.crypto_wrapper.decrypt(opaque_state)?;
|
||||
if &state.user_id != user_id {
|
||||
return Err(Box::new(
|
||||
std::io::Error::new(ErrorKind::Other, "Invalid user for pubkey!")));
|
||||
return Err(Box::new(std::io::Error::new(
|
||||
ErrorKind::Other,
|
||||
"Invalid user for pubkey!",
|
||||
)));
|
||||
}
|
||||
|
||||
if state.expire < time() {
|
||||
return Err(Box::new(
|
||||
std::io::Error::new(ErrorKind::Other, "Challenge has expired!")));
|
||||
return Err(Box::new(std::io::Error::new(
|
||||
ErrorKind::Other,
|
||||
"Challenge has expired!",
|
||||
)));
|
||||
}
|
||||
|
||||
self.core.finish_passkey_authentication(pub_cred,
|
||||
&serde_json::from_str(&state.authentication_state)?)?;
|
||||
self.core.finish_passkey_authentication(
|
||||
pub_cred,
|
||||
&serde_json::from_str(&state.authentication_state)?,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user