-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #167 from nightly-labs/login-with-passkey
login with passkey
- Loading branch information
Showing
10 changed files
with
296 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
server/src/http/cloud/login/login_with_passkey_finish.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
use crate::{ | ||
http::cloud::utils::{generate_tokens, validate_request}, | ||
structs::{ | ||
cloud::api_cloud_errors::CloudApiErrors, | ||
session_cache::{ApiSessionsCache, SessionCache, SessionsCacheKey}, | ||
}, | ||
}; | ||
use axum::{ | ||
extract::{ConnectInfo, State}, | ||
http::StatusCode, | ||
Json, | ||
}; | ||
use database::db::Db; | ||
use garde::Validate; | ||
use log::{error, warn}; | ||
use serde::{Deserialize, Serialize}; | ||
use std::{net::SocketAddr, sync::Arc}; | ||
use ts_rs::TS; | ||
use webauthn_rs::{prelude::PublicKeyCredential, Webauthn}; | ||
|
||
#[derive(Validate, Debug, Deserialize, Serialize)] | ||
pub struct HttpLoginWithPasskeyFinishRequest { | ||
#[garde(email)] | ||
pub email: String, | ||
#[garde(skip)] | ||
pub passkey_credential: PublicKeyCredential, | ||
#[garde(skip)] | ||
pub enforce_ip: bool, | ||
} | ||
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)] | ||
#[ts(export)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct HttpLoginWithPasskeyFinishResponse { | ||
pub user_id: String, | ||
pub auth_token: String, | ||
pub refresh_token: String, | ||
} | ||
|
||
pub async fn login_with_passkey_finish( | ||
ConnectInfo(ip): ConnectInfo<SocketAddr>, | ||
State(db): State<Arc<Db>>, | ||
State(web_auth): State<Arc<Webauthn>>, | ||
State(sessions_cache): State<Arc<ApiSessionsCache>>, | ||
Json(request): Json<HttpLoginWithPasskeyFinishRequest>, | ||
) -> Result<Json<HttpLoginWithPasskeyFinishResponse>, (StatusCode, String)> { | ||
// Validate request | ||
validate_request(&request, &())?; | ||
|
||
// Get session data | ||
let sessions_key = SessionsCacheKey::PasskeyLogin(request.email.clone()).to_string(); | ||
let session_data = match sessions_cache.get(&sessions_key) { | ||
Some(SessionCache::PasskeyLogin(session)) => session, | ||
_ => { | ||
return Err(( | ||
StatusCode::INTERNAL_SERVER_ERROR, | ||
CloudApiErrors::InternalServerError.to_string(), | ||
)); | ||
} | ||
}; | ||
|
||
// Remove leftover session data | ||
sessions_cache.remove(&sessions_key); | ||
|
||
// Finish passkey authentication | ||
if let Err(err) = web_auth.finish_passkey_authentication( | ||
&request.passkey_credential, | ||
&session_data.passkey_verification_state, | ||
) { | ||
warn!( | ||
"Failed to finish passkey authentication: {:?}, user_email: {}", | ||
err, request.email | ||
); | ||
return Err(( | ||
StatusCode::BAD_REQUEST, | ||
CloudApiErrors::InvalidPasskeyCredential.to_string(), | ||
)); | ||
} | ||
|
||
// Get user data | ||
let user = match db.get_user_by_email(&request.email).await { | ||
Ok(Some(user)) => user, | ||
Ok(None) => { | ||
return Err(( | ||
StatusCode::BAD_REQUEST, | ||
CloudApiErrors::UserDoesNotExist.to_string(), | ||
)); | ||
} | ||
Err(err) => { | ||
error!("Failed to get user by email: {:?}", err); | ||
return Err(( | ||
StatusCode::INTERNAL_SERVER_ERROR, | ||
CloudApiErrors::DatabaseError.to_string(), | ||
)); | ||
} | ||
}; | ||
|
||
// Generate tokens | ||
let (auth_token, refresh_token) = generate_tokens(request.enforce_ip, ip, &user.user_id)?; | ||
|
||
return Ok(Json(HttpLoginWithPasskeyFinishResponse { | ||
auth_token, | ||
refresh_token, | ||
user_id: user.user_id, | ||
})); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
use crate::{ | ||
http::cloud::utils::validate_request, | ||
structs::{ | ||
cloud::api_cloud_errors::CloudApiErrors, | ||
session_cache::{ | ||
ApiSessionsCache, PasskeyLoginVerification, SessionCache, SessionsCacheKey, | ||
}, | ||
}, | ||
utils::get_timestamp_in_milliseconds, | ||
}; | ||
use axum::{extract::State, http::StatusCode, Json}; | ||
use database::db::Db; | ||
use garde::Validate; | ||
use log::error; | ||
use serde::{Deserialize, Serialize}; | ||
use std::sync::Arc; | ||
use ts_rs::TS; | ||
use webauthn_rs::{prelude::RequestChallengeResponse, Webauthn}; | ||
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS, Validate)] | ||
#[ts(export)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct HttpLoginWithPasskeyStartRequest { | ||
#[garde(email)] | ||
pub email: String, | ||
} | ||
|
||
pub type HttpLoginWithPasskeyStartResponse = RequestChallengeResponse; | ||
|
||
pub async fn login_with_passkey_start( | ||
State(db): State<Arc<Db>>, | ||
State(web_auth): State<Arc<Webauthn>>, | ||
State(sessions_cache): State<Arc<ApiSessionsCache>>, | ||
Json(request): Json<HttpLoginWithPasskeyStartRequest>, | ||
) -> Result<Json<HttpLoginWithPasskeyStartResponse>, (StatusCode, String)> { | ||
// Validate request | ||
validate_request(&request, &())?; | ||
|
||
// Check if user exists | ||
let user = match db.get_user_by_email(&request.email).await { | ||
Ok(Some(user)) => user, | ||
Ok(None) => { | ||
return Err(( | ||
StatusCode::BAD_REQUEST, | ||
CloudApiErrors::UserDoesNotExist.to_string(), | ||
)); | ||
} | ||
Err(err) => { | ||
error!("Failed to get user by email: {:?}", err); | ||
return Err(( | ||
StatusCode::INTERNAL_SERVER_ERROR, | ||
CloudApiErrors::DatabaseError.to_string(), | ||
)); | ||
} | ||
}; | ||
|
||
// Check if user has passkey | ||
let passkeys = match user.passkeys { | ||
Some(passkeys) => passkeys, | ||
None => { | ||
return Err(( | ||
StatusCode::BAD_REQUEST, | ||
CloudApiErrors::PasswordNotSet.to_string(), | ||
)); | ||
} | ||
}; | ||
|
||
// Save to cache passkey challenge request | ||
let sessions_key = SessionsCacheKey::PasskeyLogin(request.email.clone()).to_string(); | ||
|
||
// Remove leftover session data | ||
sessions_cache.remove(&sessions_key); | ||
|
||
match web_auth.start_passkey_authentication(&passkeys) { | ||
Ok((rcr, auth_state)) => { | ||
sessions_cache.set( | ||
sessions_key, | ||
SessionCache::PasskeyLogin(PasskeyLoginVerification { | ||
email: request.email.clone(), | ||
passkey_verification_state: auth_state, | ||
created_at: get_timestamp_in_milliseconds(), | ||
}), | ||
None, | ||
); | ||
return Ok(Json(rcr)); | ||
} | ||
Err(_) => { | ||
return Err(( | ||
StatusCode::INTERNAL_SERVER_ERROR, | ||
CloudApiErrors::WebAuthnError.to_string(), | ||
)); | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
pub mod login_with_google; | ||
pub mod login_with_passkey_finish; | ||
pub mod login_with_passkey_start; | ||
pub mod login_with_password; |
Oops, something went wrong.