mirror of
https://github.com/CloudNebulaProject/barycenter.git
synced 2026-04-10 13:10:42 +00:00
Commit work in progress
Signed-off-by: Till Wegmueller <toasterson@gmail.com>
This commit is contained in:
parent
d7bdd51164
commit
86c88d8aee
5 changed files with 529 additions and 150 deletions
103
src/jobs.rs
103
src/jobs.rs
|
|
@ -22,23 +22,23 @@ pub async fn init_scheduler(db: DatabaseConnection) -> Result<JobScheduler, Crab
|
|||
let db = db_clone.clone();
|
||||
Box::pin(async move {
|
||||
info!("Running cleanup_expired_sessions job");
|
||||
let execution_id = start_job_execution(&db, "cleanup_expired_sessions")
|
||||
let execution_id = start_job_execution(db, "cleanup_expired_sessions")
|
||||
.await
|
||||
.ok();
|
||||
|
||||
match storage::cleanup_expired_sessions(&db).await {
|
||||
match storage::cleanup_expired_sessions(db).await {
|
||||
Ok(count) => {
|
||||
info!("Cleaned up {} expired sessions", count);
|
||||
if let Some(id) = execution_id {
|
||||
let _ =
|
||||
complete_job_execution(&db, id, true, None, Some(count as i64)).await;
|
||||
complete_job_execution(db, id, true, None, Some(count as i64)).await;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to cleanup expired sessions: {}", e);
|
||||
if let Some(id) = execution_id {
|
||||
let _ =
|
||||
complete_job_execution(&db, id, false, Some(e.to_string()), None).await;
|
||||
complete_job_execution(db, id, false, Some(e.to_string()), None).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -58,23 +58,23 @@ pub async fn init_scheduler(db: DatabaseConnection) -> Result<JobScheduler, Crab
|
|||
let db = db_clone.clone();
|
||||
Box::pin(async move {
|
||||
info!("Running cleanup_expired_refresh_tokens job");
|
||||
let execution_id = start_job_execution(&db, "cleanup_expired_refresh_tokens")
|
||||
let execution_id = start_job_execution(db, "cleanup_expired_refresh_tokens")
|
||||
.await
|
||||
.ok();
|
||||
|
||||
match storage::cleanup_expired_refresh_tokens(&db).await {
|
||||
match storage::cleanup_expired_refresh_tokens(db).await {
|
||||
Ok(count) => {
|
||||
info!("Cleaned up {} expired refresh tokens", count);
|
||||
if let Some(id) = execution_id {
|
||||
let _ =
|
||||
complete_job_execution(&db, id, true, None, Some(count as i64)).await;
|
||||
complete_job_execution(db, id, true, None, Some(count as i64)).await;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to cleanup expired refresh tokens: {}", e);
|
||||
if let Some(id) = execution_id {
|
||||
let _ =
|
||||
complete_job_execution(&db, id, false, Some(e.to_string()), None).await;
|
||||
complete_job_execution(db, id, false, Some(e.to_string()), None).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -94,23 +94,23 @@ pub async fn init_scheduler(db: DatabaseConnection) -> Result<JobScheduler, Crab
|
|||
let db = db_clone.clone();
|
||||
Box::pin(async move {
|
||||
info!("Running cleanup_expired_challenges job");
|
||||
let execution_id = start_job_execution(&db, "cleanup_expired_challenges")
|
||||
let execution_id = start_job_execution(db, "cleanup_expired_challenges")
|
||||
.await
|
||||
.ok();
|
||||
|
||||
match storage::cleanup_expired_challenges(&db).await {
|
||||
match storage::cleanup_expired_challenges(db).await {
|
||||
Ok(count) => {
|
||||
info!("Cleaned up {} expired WebAuthn challenges", count);
|
||||
if let Some(id) = execution_id {
|
||||
let _ =
|
||||
complete_job_execution(&db, id, true, None, Some(count as i64)).await;
|
||||
complete_job_execution(db, id, true, None, Some(count as i64)).await;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to cleanup expired challenges: {}", e);
|
||||
if let Some(id) = execution_id {
|
||||
let _ =
|
||||
complete_job_execution(&db, id, false, Some(e.to_string()), None).await;
|
||||
complete_job_execution(db, id, false, Some(e.to_string()), None).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -226,28 +226,43 @@ mod tests {
|
|||
use sea_orm_migration::MigratorTrait;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
/// Helper to create an in-memory test database
|
||||
async fn test_db() -> DatabaseConnection {
|
||||
/// Test database helper that keeps temp file alive
|
||||
struct TestDb {
|
||||
connection: DatabaseConnection,
|
||||
_temp_file: NamedTempFile,
|
||||
}
|
||||
|
||||
impl TestDb {
|
||||
async fn new() -> Self {
|
||||
let temp_file = NamedTempFile::new().expect("Failed to create temp file");
|
||||
let db_path = temp_file.path().to_str().expect("Invalid temp file path");
|
||||
let db_url = format!("sqlite://{}?mode=rwc", db_path);
|
||||
|
||||
let db = Database::connect(&db_url)
|
||||
let connection = Database::connect(&db_url)
|
||||
.await
|
||||
.expect("Failed to connect to test database");
|
||||
|
||||
migration::Migrator::up(&db, None)
|
||||
migration::Migrator::up(&connection, None)
|
||||
.await
|
||||
.expect("Failed to run migrations");
|
||||
|
||||
db
|
||||
Self {
|
||||
connection,
|
||||
_temp_file: temp_file,
|
||||
}
|
||||
}
|
||||
|
||||
fn connection(&self) -> &DatabaseConnection {
|
||||
&self.connection
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_start_job_execution() {
|
||||
let db = test_db().await;
|
||||
let test_db = TestDb::new().await;
|
||||
let db = test_db.connection();
|
||||
|
||||
let execution_id = start_job_execution(&db, "test_job")
|
||||
let execution_id = start_job_execution(db, "test_job")
|
||||
.await
|
||||
.expect("Failed to start job execution");
|
||||
|
||||
|
|
@ -257,7 +272,7 @@ mod tests {
|
|||
use entities::job_execution::{Column, Entity};
|
||||
let execution = Entity::find()
|
||||
.filter(Column::Id.eq(execution_id))
|
||||
.one(&db)
|
||||
.one(db)
|
||||
.await
|
||||
.expect("Failed to query job execution")
|
||||
.expect("Job execution not found");
|
||||
|
|
@ -270,13 +285,14 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_complete_job_execution_success() {
|
||||
let db = test_db().await;
|
||||
let test_db = TestDb::new().await;
|
||||
let db = test_db.connection();
|
||||
|
||||
let execution_id = start_job_execution(&db, "test_job")
|
||||
let execution_id = start_job_execution(db, "test_job")
|
||||
.await
|
||||
.expect("Failed to start job execution");
|
||||
|
||||
complete_job_execution(&db, execution_id, true, None, Some(42))
|
||||
complete_job_execution(db, execution_id, true, None, Some(42))
|
||||
.await
|
||||
.expect("Failed to complete job execution");
|
||||
|
||||
|
|
@ -284,7 +300,7 @@ mod tests {
|
|||
use entities::job_execution::{Column, Entity};
|
||||
let execution = Entity::find()
|
||||
.filter(Column::Id.eq(execution_id))
|
||||
.one(&db)
|
||||
.one(db)
|
||||
.await
|
||||
.expect("Failed to query job execution")
|
||||
.expect("Job execution not found");
|
||||
|
|
@ -297,9 +313,10 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_complete_job_execution_failure() {
|
||||
let db = test_db().await;
|
||||
let test_db = TestDb::new().await;
|
||||
let db = test_db.connection();
|
||||
|
||||
let execution_id = start_job_execution(&db, "test_job")
|
||||
let execution_id = start_job_execution(db, "test_job")
|
||||
.await
|
||||
.expect("Failed to start job execution");
|
||||
|
||||
|
|
@ -317,7 +334,7 @@ mod tests {
|
|||
use entities::job_execution::{Column, Entity};
|
||||
let execution = Entity::find()
|
||||
.filter(Column::Id.eq(execution_id))
|
||||
.one(&db)
|
||||
.one(db)
|
||||
.await
|
||||
.expect("Failed to query job execution")
|
||||
.expect("Job execution not found");
|
||||
|
|
@ -330,20 +347,21 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_trigger_job_manually_cleanup_sessions() {
|
||||
let db = test_db().await;
|
||||
let test_db = TestDb::new().await;
|
||||
let db = test_db.connection();
|
||||
|
||||
// Create an expired session
|
||||
let user = storage::create_user(&db, "testuser", "password123", None)
|
||||
let user = storage::create_user(db, "testuser", "password123", None)
|
||||
.await
|
||||
.expect("Failed to create user");
|
||||
|
||||
let past_auth_time = Utc::now().timestamp() - 7200; // 2 hours ago
|
||||
storage::create_session(&db, &user.subject, past_auth_time, 3600, None, None) // 1 hour TTL
|
||||
storage::create_session(db, &user.subject, past_auth_time, 3600, None, None) // 1 hour TTL
|
||||
.await
|
||||
.expect("Failed to create session");
|
||||
|
||||
// Trigger cleanup job
|
||||
trigger_job_manually(&db, "cleanup_expired_sessions")
|
||||
trigger_job_manually(db, "cleanup_expired_sessions")
|
||||
.await
|
||||
.expect("Failed to trigger job");
|
||||
|
||||
|
|
@ -351,7 +369,7 @@ mod tests {
|
|||
use entities::job_execution::{Column, Entity};
|
||||
let execution = Entity::find()
|
||||
.filter(Column::JobName.eq("cleanup_expired_sessions"))
|
||||
.one(&db)
|
||||
.one(db)
|
||||
.await
|
||||
.expect("Failed to query job execution")
|
||||
.expect("Job execution not found");
|
||||
|
|
@ -362,10 +380,11 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_trigger_job_manually_cleanup_tokens() {
|
||||
let db = test_db().await;
|
||||
let test_db = TestDb::new().await;
|
||||
let db = test_db.connection();
|
||||
|
||||
// Trigger cleanup_expired_refresh_tokens job
|
||||
trigger_job_manually(&db, "cleanup_expired_refresh_tokens")
|
||||
trigger_job_manually(db, "cleanup_expired_refresh_tokens")
|
||||
.await
|
||||
.expect("Failed to trigger job");
|
||||
|
||||
|
|
@ -373,7 +392,7 @@ mod tests {
|
|||
use entities::job_execution::{Column, Entity};
|
||||
let execution = Entity::find()
|
||||
.filter(Column::JobName.eq("cleanup_expired_refresh_tokens"))
|
||||
.one(&db)
|
||||
.one(db)
|
||||
.await
|
||||
.expect("Failed to query job execution")
|
||||
.expect("Job execution not found");
|
||||
|
|
@ -383,9 +402,10 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_trigger_job_manually_invalid_name() {
|
||||
let db = test_db().await;
|
||||
let test_db = TestDb::new().await;
|
||||
let db = test_db.connection();
|
||||
|
||||
let result = trigger_job_manually(&db, "invalid_job_name").await;
|
||||
let result = trigger_job_manually(db, "invalid_job_name").await;
|
||||
|
||||
assert!(result.is_err());
|
||||
match result {
|
||||
|
|
@ -398,14 +418,15 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_job_execution_records_processed() {
|
||||
let db = test_db().await;
|
||||
let test_db = TestDb::new().await;
|
||||
let db = test_db.connection();
|
||||
|
||||
let execution_id = start_job_execution(&db, "test_job")
|
||||
let execution_id = start_job_execution(db, "test_job")
|
||||
.await
|
||||
.expect("Failed to start job execution");
|
||||
|
||||
// Complete with specific record count
|
||||
complete_job_execution(&db, execution_id, true, None, Some(123))
|
||||
complete_job_execution(db, execution_id, true, None, Some(123))
|
||||
.await
|
||||
.expect("Failed to complete job execution");
|
||||
|
||||
|
|
@ -413,7 +434,7 @@ mod tests {
|
|||
use entities::job_execution::{Column, Entity};
|
||||
let execution = Entity::find()
|
||||
.filter(Column::Id.eq(execution_id))
|
||||
.one(&db)
|
||||
.one(db)
|
||||
.await
|
||||
.expect("Failed to query job execution")
|
||||
.expect("Job execution not found");
|
||||
|
|
|
|||
|
|
@ -229,7 +229,8 @@ mod tests {
|
|||
let mut payload = JwtPayload::new();
|
||||
payload.set_issuer("https://example.com");
|
||||
payload.set_subject("user123");
|
||||
payload.set_expires_at(&(chrono::Utc::now() + chrono::Duration::hours(1)));
|
||||
let exp_time: std::time::SystemTime = (chrono::Utc::now() + chrono::Duration::hours(1)).into();
|
||||
payload.set_expires_at(&exp_time);
|
||||
|
||||
// Sign the JWT
|
||||
let token = manager.sign_jwt_rs256(&payload)
|
||||
|
|
|
|||
493
src/storage.rs
493
src/storage.rs
|
|
@ -5,7 +5,7 @@ use base64ct::Encoding;
|
|||
use chrono::Utc;
|
||||
use rand::RngCore;
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, Database, DatabaseConnection, EntityTrait, QueryFilter, Set,
|
||||
ActiveModelTrait, ColumnTrait, Database, DatabaseConnection, EntityTrait, QueryFilter, QueryOrder, Set,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
|
@ -61,6 +61,8 @@ pub struct User {
|
|||
pub email_verified: i64,
|
||||
pub created_at: i64,
|
||||
pub enabled: i64,
|
||||
pub requires_2fa: i64,
|
||||
pub passkey_enrolled_at: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -72,6 +74,9 @@ pub struct Session {
|
|||
pub expires_at: i64,
|
||||
pub user_agent: Option<String>,
|
||||
pub ip_address: Option<String>,
|
||||
pub amr: Option<String>,
|
||||
pub acr: Option<String>,
|
||||
pub mfa_verified: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -414,6 +419,8 @@ pub async fn get_user_by_username(
|
|||
email_verified: model.email_verified,
|
||||
created_at: model.created_at,
|
||||
enabled: model.enabled,
|
||||
requires_2fa: model.requires_2fa,
|
||||
passkey_enrolled_at: model.passkey_enrolled_at,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
|
|
@ -445,26 +452,35 @@ pub async fn verify_user_password(
|
|||
}
|
||||
}
|
||||
|
||||
/// Update user enabled and email_verified flags
|
||||
/// Update user fields (enabled, email, requires_2fa)
|
||||
pub async fn update_user(
|
||||
db: &DatabaseConnection,
|
||||
username: &str,
|
||||
subject: &str,
|
||||
enabled: bool,
|
||||
email_verified: bool,
|
||||
email: Option<String>,
|
||||
requires_2fa: Option<bool>,
|
||||
) -> Result<(), CrabError> {
|
||||
use entities::user::{Column, Entity};
|
||||
|
||||
// Find the user
|
||||
// Find the user by subject
|
||||
let user = Entity::find()
|
||||
.filter(Column::Username.eq(username))
|
||||
.filter(Column::Subject.eq(subject))
|
||||
.one(db)
|
||||
.await?
|
||||
.ok_or_else(|| CrabError::Other(format!("User not found: {}", username)))?;
|
||||
.ok_or_else(|| CrabError::Other(format!("User not found: {}", subject)))?;
|
||||
|
||||
// Update the user
|
||||
let mut active: entities::user::ActiveModel = user.into();
|
||||
active.enabled = Set(if enabled { 1 } else { 0 });
|
||||
active.email_verified = Set(if email_verified { 1 } else { 0 });
|
||||
|
||||
if let Some(email_val) = email {
|
||||
active.email = Set(Some(email_val));
|
||||
}
|
||||
|
||||
if let Some(requires_2fa_val) = requires_2fa {
|
||||
active.requires_2fa = Set(if requires_2fa_val { 1 } else { 0 });
|
||||
}
|
||||
|
||||
active.update(db).await?;
|
||||
|
||||
Ok(())
|
||||
|
|
@ -498,22 +514,26 @@ pub async fn update_user_email(
|
|||
pub async fn create_session(
|
||||
db: &DatabaseConnection,
|
||||
subject: &str,
|
||||
auth_time: i64,
|
||||
ttl_secs: i64,
|
||||
user_agent: Option<String>,
|
||||
ip_address: Option<String>,
|
||||
) -> Result<Session, CrabError> {
|
||||
let session_id = random_id();
|
||||
let now = Utc::now().timestamp();
|
||||
let expires_at = now + ttl_secs;
|
||||
let expires_at = auth_time + ttl_secs;
|
||||
|
||||
let session = entities::session::ActiveModel {
|
||||
session_id: Set(session_id.clone()),
|
||||
subject: Set(subject.to_string()),
|
||||
auth_time: Set(now),
|
||||
auth_time: Set(auth_time),
|
||||
created_at: Set(now),
|
||||
expires_at: Set(expires_at),
|
||||
user_agent: Set(user_agent.clone()),
|
||||
ip_address: Set(ip_address.clone()),
|
||||
amr: Set(None),
|
||||
acr: Set(None),
|
||||
mfa_verified: Set(0),
|
||||
};
|
||||
|
||||
session.insert(db).await?;
|
||||
|
|
@ -521,11 +541,14 @@ pub async fn create_session(
|
|||
Ok(Session {
|
||||
session_id,
|
||||
subject: subject.to_string(),
|
||||
auth_time: now,
|
||||
auth_time,
|
||||
created_at: now,
|
||||
expires_at,
|
||||
user_agent,
|
||||
ip_address,
|
||||
amr: None,
|
||||
acr: None,
|
||||
mfa_verified: 0,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -554,6 +577,9 @@ pub async fn get_session(
|
|||
expires_at: model.expires_at,
|
||||
user_agent: model.user_agent,
|
||||
ip_address: model.ip_address,
|
||||
amr: model.amr,
|
||||
acr: model.acr,
|
||||
mfa_verified: model.mfa_verified,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
|
|
@ -706,6 +732,316 @@ pub async fn cleanup_expired_refresh_tokens(db: &DatabaseConnection) -> Result<u
|
|||
Ok(result.rows_affected)
|
||||
}
|
||||
|
||||
// User helper functions
|
||||
|
||||
pub async fn get_user_by_subject(
|
||||
db: &DatabaseConnection,
|
||||
subject: &str,
|
||||
) -> Result<Option<User>, CrabError> {
|
||||
use entities::user::{Column, Entity};
|
||||
|
||||
if let Some(model) = Entity::find()
|
||||
.filter(Column::Subject.eq(subject))
|
||||
.one(db)
|
||||
.await?
|
||||
{
|
||||
Ok(Some(User {
|
||||
subject: model.subject,
|
||||
username: model.username,
|
||||
password_hash: model.password_hash,
|
||||
email: model.email,
|
||||
email_verified: model.email_verified,
|
||||
created_at: model.created_at,
|
||||
enabled: model.enabled,
|
||||
requires_2fa: model.requires_2fa,
|
||||
passkey_enrolled_at: model.passkey_enrolled_at,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
// Passkey management functions
|
||||
|
||||
pub async fn create_passkey(
|
||||
db: &DatabaseConnection,
|
||||
credential_id: &str,
|
||||
subject: &str,
|
||||
passkey_json: &str,
|
||||
counter: i64,
|
||||
aaguid: Option<String>,
|
||||
backup_eligible: bool,
|
||||
backup_state: bool,
|
||||
transports: Option<String>,
|
||||
name: Option<&str>,
|
||||
) -> Result<entities::passkey::Model, CrabError> {
|
||||
use entities::passkey;
|
||||
|
||||
let now = Utc::now().timestamp();
|
||||
|
||||
let passkey = passkey::ActiveModel {
|
||||
credential_id: Set(credential_id.to_string()),
|
||||
subject: Set(subject.to_string()),
|
||||
public_key_cose: Set(passkey_json.to_string()),
|
||||
counter: Set(counter),
|
||||
aaguid: Set(aaguid),
|
||||
backup_eligible: Set(if backup_eligible { 1 } else { 0 }),
|
||||
backup_state: Set(if backup_state { 1 } else { 0 }),
|
||||
transports: Set(transports),
|
||||
name: Set(name.map(|n| n.to_string())),
|
||||
created_at: Set(now),
|
||||
last_used_at: Set(None),
|
||||
};
|
||||
|
||||
let result = passkey.insert(db).await?;
|
||||
|
||||
// Update user's passkey_enrolled_at if this is their first passkey
|
||||
use entities::user::{Column as UserColumn, Entity as UserEntity};
|
||||
|
||||
if let Some(user) = UserEntity::find()
|
||||
.filter(UserColumn::Subject.eq(subject))
|
||||
.one(db)
|
||||
.await?
|
||||
{
|
||||
if user.passkey_enrolled_at.is_none() {
|
||||
let mut user_active: entities::user::ActiveModel = user.into();
|
||||
user_active.passkey_enrolled_at = Set(Some(now));
|
||||
user_active.update(db).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn get_passkey_by_credential_id(
|
||||
db: &DatabaseConnection,
|
||||
credential_id: &str,
|
||||
) -> Result<Option<entities::passkey::Model>, CrabError> {
|
||||
use entities::passkey::{Column, Entity};
|
||||
|
||||
let passkey = Entity::find()
|
||||
.filter(Column::CredentialId.eq(credential_id))
|
||||
.one(db)
|
||||
.await?;
|
||||
|
||||
Ok(passkey)
|
||||
}
|
||||
|
||||
pub async fn get_passkeys_by_subject(
|
||||
db: &DatabaseConnection,
|
||||
subject: &str,
|
||||
) -> Result<Vec<entities::passkey::Model>, CrabError> {
|
||||
use entities::passkey::{Column, Entity};
|
||||
|
||||
let passkeys = Entity::find()
|
||||
.filter(Column::Subject.eq(subject))
|
||||
.all(db)
|
||||
.await?;
|
||||
|
||||
Ok(passkeys)
|
||||
}
|
||||
|
||||
pub async fn update_passkey_counter(
|
||||
db: &DatabaseConnection,
|
||||
credential_id: &str,
|
||||
new_counter: i64,
|
||||
) -> Result<(), CrabError> {
|
||||
use entities::passkey::{Column, Entity};
|
||||
|
||||
let now = Utc::now().timestamp();
|
||||
|
||||
if let Some(passkey) = Entity::find()
|
||||
.filter(Column::CredentialId.eq(credential_id))
|
||||
.one(db)
|
||||
.await?
|
||||
{
|
||||
let mut active: entities::passkey::ActiveModel = passkey.into();
|
||||
active.counter = Set(new_counter);
|
||||
active.last_used_at = Set(Some(now));
|
||||
active.update(db).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_passkey_name(
|
||||
db: &DatabaseConnection,
|
||||
credential_id: &str,
|
||||
name: Option<String>,
|
||||
) -> Result<(), CrabError> {
|
||||
use entities::passkey::{Column, Entity};
|
||||
|
||||
if let Some(passkey) = Entity::find()
|
||||
.filter(Column::CredentialId.eq(credential_id))
|
||||
.one(db)
|
||||
.await?
|
||||
{
|
||||
let mut active: entities::passkey::ActiveModel = passkey.into();
|
||||
active.name = Set(name);
|
||||
active.update(db).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_passkey(
|
||||
db: &DatabaseConnection,
|
||||
credential_id: &str,
|
||||
) -> Result<(), CrabError> {
|
||||
use entities::passkey::{Column, Entity};
|
||||
|
||||
Entity::delete_many()
|
||||
.filter(Column::CredentialId.eq(credential_id))
|
||||
.exec(db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// WebAuthn challenge management
|
||||
|
||||
pub async fn create_webauthn_challenge(
|
||||
db: &DatabaseConnection,
|
||||
challenge: &str,
|
||||
subject: Option<&str>,
|
||||
session_id: Option<&str>,
|
||||
challenge_type: &str,
|
||||
options_json: &str,
|
||||
) -> Result<entities::webauthn_challenge::Model, CrabError> {
|
||||
use entities::webauthn_challenge;
|
||||
|
||||
let now = Utc::now().timestamp();
|
||||
let expires_at = now + 300; // 5 minutes
|
||||
|
||||
let challenge_model = webauthn_challenge::ActiveModel {
|
||||
challenge: Set(challenge.to_string()),
|
||||
subject: Set(subject.map(|s| s.to_string())),
|
||||
session_id: Set(session_id.map(|s| s.to_string())),
|
||||
challenge_type: Set(challenge_type.to_string()),
|
||||
options_json: Set(options_json.to_string()),
|
||||
created_at: Set(now),
|
||||
expires_at: Set(expires_at),
|
||||
};
|
||||
|
||||
let result = challenge_model.insert(db).await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn get_latest_webauthn_challenge_by_subject(
|
||||
db: &DatabaseConnection,
|
||||
subject: &str,
|
||||
challenge_type: &str,
|
||||
) -> Result<Option<entities::webauthn_challenge::Model>, CrabError> {
|
||||
use entities::webauthn_challenge::{Column, Entity};
|
||||
|
||||
let now = Utc::now().timestamp();
|
||||
|
||||
let challenge = Entity::find()
|
||||
.filter(Column::Subject.eq(subject))
|
||||
.filter(Column::ChallengeType.eq(challenge_type))
|
||||
.filter(Column::ExpiresAt.gt(now))
|
||||
.order_by_desc(Column::CreatedAt)
|
||||
.one(db)
|
||||
.await?;
|
||||
|
||||
Ok(challenge)
|
||||
}
|
||||
|
||||
pub async fn delete_webauthn_challenge(
|
||||
db: &DatabaseConnection,
|
||||
challenge: &str,
|
||||
) -> Result<(), CrabError> {
|
||||
use entities::webauthn_challenge::{Column, Entity};
|
||||
|
||||
Entity::delete_many()
|
||||
.filter(Column::Challenge.eq(challenge))
|
||||
.exec(db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn cleanup_expired_challenges(db: &DatabaseConnection) -> Result<u64, CrabError> {
|
||||
use entities::webauthn_challenge::{Column, Entity};
|
||||
|
||||
let now = Utc::now().timestamp();
|
||||
let result = Entity::delete_many()
|
||||
.filter(Column::ExpiresAt.lt(now))
|
||||
.exec(db)
|
||||
.await?;
|
||||
|
||||
Ok(result.rows_affected)
|
||||
}
|
||||
|
||||
// Session authentication context management
|
||||
|
||||
pub async fn update_session_auth_context(
|
||||
db: &DatabaseConnection,
|
||||
session_id: &str,
|
||||
amr: Option<&str>,
|
||||
acr: Option<&str>,
|
||||
mfa_verified: Option<bool>,
|
||||
) -> Result<(), CrabError> {
|
||||
use entities::session::{Column, Entity};
|
||||
|
||||
if let Some(session) = Entity::find()
|
||||
.filter(Column::SessionId.eq(session_id))
|
||||
.one(db)
|
||||
.await?
|
||||
{
|
||||
let mut active: entities::session::ActiveModel = session.into();
|
||||
|
||||
if let Some(amr_val) = amr {
|
||||
active.amr = Set(Some(amr_val.to_string()));
|
||||
}
|
||||
|
||||
if let Some(acr_val) = acr {
|
||||
active.acr = Set(Some(acr_val.to_string()));
|
||||
}
|
||||
|
||||
if let Some(mfa_val) = mfa_verified {
|
||||
active.mfa_verified = Set(if mfa_val { 1 } else { 0 });
|
||||
}
|
||||
|
||||
active.update(db).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn append_session_amr(
|
||||
db: &DatabaseConnection,
|
||||
session_id: &str,
|
||||
new_amr: &str,
|
||||
) -> Result<(), CrabError> {
|
||||
use entities::session::{Column, Entity};
|
||||
|
||||
if let Some(session) = Entity::find()
|
||||
.filter(Column::SessionId.eq(session_id))
|
||||
.one(db)
|
||||
.await?
|
||||
{
|
||||
let mut amr_array: Vec<String> = if let Some(existing_amr) = &session.amr {
|
||||
serde_json::from_str(existing_amr).unwrap_or_else(|_| vec![])
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
// Only append if not already present
|
||||
if !amr_array.contains(&new_amr.to_string()) {
|
||||
amr_array.push(new_amr.to_string());
|
||||
}
|
||||
|
||||
let amr_json = serde_json::to_string(&amr_array)?;
|
||||
|
||||
let mut active: entities::session::ActiveModel = session.into();
|
||||
active.amr = Set(Some(amr_json));
|
||||
active.update(db).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -811,8 +1147,7 @@ mod tests {
|
|||
.expect("Failed to get client")
|
||||
.expect("Client not found");
|
||||
|
||||
let parsed_uris: Vec<String> = serde_json::from_str(&retrieved.redirect_uris)
|
||||
.expect("Failed to parse redirect_uris");
|
||||
let parsed_uris = retrieved.redirect_uris.clone();
|
||||
|
||||
assert_eq!(parsed_uris, uris);
|
||||
}
|
||||
|
|
@ -827,18 +1162,21 @@ mod tests {
|
|||
|
||||
let code = issue_auth_code(
|
||||
&db,
|
||||
"test_subject",
|
||||
"test_client_id",
|
||||
"openid profile",
|
||||
Some("test_nonce"),
|
||||
"http://localhost:3000/callback",
|
||||
Some("challenge_string"),
|
||||
Some("S256"),
|
||||
"openid profile",
|
||||
"test_subject",
|
||||
Some("test_nonce".to_string()),
|
||||
"challenge_string",
|
||||
"S256",
|
||||
300, // 5 minutes TTL
|
||||
None, // auth_time
|
||||
)
|
||||
.await
|
||||
.expect("Failed to issue auth code");
|
||||
|
||||
assert!(!code.is_empty());
|
||||
assert!(!code.code.is_empty());
|
||||
assert_eq!(code.subject, "test_subject");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -847,18 +1185,20 @@ mod tests {
|
|||
|
||||
let code = issue_auth_code(
|
||||
&db,
|
||||
"test_subject",
|
||||
"test_client_id",
|
||||
"openid profile",
|
||||
Some("test_nonce"),
|
||||
"http://localhost:3000/callback",
|
||||
Some("challenge_string"),
|
||||
Some("S256"),
|
||||
"openid profile",
|
||||
"test_subject",
|
||||
Some("test_nonce".to_string()),
|
||||
"challenge_string",
|
||||
"S256",
|
||||
300,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to issue auth code");
|
||||
|
||||
let auth_code = consume_auth_code(&db, &code)
|
||||
let auth_code = consume_auth_code(&db, &code.code)
|
||||
.await
|
||||
.expect("Failed to consume auth code")
|
||||
.expect("Auth code not found");
|
||||
|
|
@ -874,25 +1214,27 @@ mod tests {
|
|||
|
||||
let code = issue_auth_code(
|
||||
&db,
|
||||
"test_subject",
|
||||
"test_client_id",
|
||||
"openid profile",
|
||||
None,
|
||||
"http://localhost:3000/callback",
|
||||
"openid profile",
|
||||
"test_subject",
|
||||
None,
|
||||
None,
|
||||
"",
|
||||
"",
|
||||
300, // TTL
|
||||
None, // auth_time
|
||||
)
|
||||
.await
|
||||
.expect("Failed to issue auth code");
|
||||
|
||||
// First consumption succeeds
|
||||
consume_auth_code(&db, &code)
|
||||
consume_auth_code(&db, &code.code)
|
||||
.await
|
||||
.expect("Failed to consume auth code")
|
||||
.expect("Auth code not found");
|
||||
|
||||
// Second consumption returns None
|
||||
let result = consume_auth_code(&db, &code)
|
||||
let result = consume_auth_code(&db, &code.code)
|
||||
.await
|
||||
.expect("Query failed");
|
||||
|
||||
|
|
@ -905,13 +1247,15 @@ mod tests {
|
|||
|
||||
let code = issue_auth_code(
|
||||
&db,
|
||||
"test_subject",
|
||||
"test_client_id",
|
||||
"openid profile",
|
||||
None,
|
||||
"http://localhost:3000/callback",
|
||||
"openid profile",
|
||||
"test_subject",
|
||||
None,
|
||||
None,
|
||||
"",
|
||||
"",
|
||||
300, // TTL
|
||||
None, // auth_time
|
||||
)
|
||||
.await
|
||||
.expect("Failed to issue auth code");
|
||||
|
|
@ -925,13 +1269,13 @@ mod tests {
|
|||
|
||||
Entity::update_many()
|
||||
.col_expr(Column::ExpiresAt, sea_orm::sea_query::Expr::value(past_timestamp))
|
||||
.filter(Column::Code.eq(&code))
|
||||
.filter(Column::Code.eq(&code.code))
|
||||
.exec(&db)
|
||||
.await
|
||||
.expect("Failed to update expiry");
|
||||
|
||||
// Consumption should return None for expired code
|
||||
let result = consume_auth_code(&db, &code)
|
||||
let result = consume_auth_code(&db, &code.code)
|
||||
.await
|
||||
.expect("Query failed");
|
||||
|
||||
|
|
@ -944,24 +1288,26 @@ mod tests {
|
|||
|
||||
let code = issue_auth_code(
|
||||
&db,
|
||||
"test_subject",
|
||||
"test_client_id",
|
||||
"openid profile",
|
||||
None,
|
||||
"http://localhost:3000/callback",
|
||||
Some("challenge_string"),
|
||||
Some("S256"),
|
||||
"openid profile",
|
||||
"test_subject",
|
||||
None,
|
||||
"challenge_string",
|
||||
"S256",
|
||||
300,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to issue auth code");
|
||||
|
||||
let auth_code = consume_auth_code(&db, &code)
|
||||
let auth_code = consume_auth_code(&db, &code.code)
|
||||
.await
|
||||
.expect("Failed to consume auth code")
|
||||
.expect("Auth code not found");
|
||||
|
||||
assert_eq!(auth_code.code_challenge, Some("challenge_string".to_string()));
|
||||
assert_eq!(auth_code.code_challenge_method, Some("S256".to_string()));
|
||||
assert_eq!(auth_code.code_challenge, "challenge_string");
|
||||
assert_eq!(auth_code.code_challenge_method, "S256");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -972,22 +1318,26 @@ mod tests {
|
|||
async fn test_issue_access_token() {
|
||||
let db = test_db().await;
|
||||
|
||||
let token = issue_access_token(&db, "test_subject", "test_client_id", "openid profile")
|
||||
let token = issue_access_token(&db, "test_subject", "test_client_id", "openid profile",
|
||||
3600, // TTL
|
||||
)
|
||||
.await
|
||||
.expect("Failed to issue access token");
|
||||
|
||||
assert!(!token.is_empty());
|
||||
assert!(!token.token.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_access_token_valid() {
|
||||
let db = test_db().await;
|
||||
|
||||
let token = issue_access_token(&db, "test_subject", "test_client_id", "openid profile")
|
||||
let token = issue_access_token(&db, "test_subject", "test_client_id", "openid profile",
|
||||
3600, // TTL
|
||||
)
|
||||
.await
|
||||
.expect("Failed to issue access token");
|
||||
|
||||
let access_token = get_access_token(&db, &token)
|
||||
let access_token = get_access_token(&db, &token.token)
|
||||
.await
|
||||
.expect("Failed to get access token")
|
||||
.expect("Access token not found");
|
||||
|
|
@ -1001,7 +1351,9 @@ mod tests {
|
|||
async fn test_get_access_token_expired() {
|
||||
let db = test_db().await;
|
||||
|
||||
let token = issue_access_token(&db, "test_subject", "test_client_id", "openid profile")
|
||||
let token = issue_access_token(&db, "test_subject", "test_client_id", "openid profile",
|
||||
3600, // TTL
|
||||
)
|
||||
.await
|
||||
.expect("Failed to issue access token");
|
||||
|
||||
|
|
@ -1013,13 +1365,13 @@ mod tests {
|
|||
|
||||
Entity::update_many()
|
||||
.col_expr(Column::ExpiresAt, sea_orm::sea_query::Expr::value(past_timestamp))
|
||||
.filter(Column::Token.eq(&token))
|
||||
.filter(Column::Token.eq(&token.token))
|
||||
.exec(&db)
|
||||
.await
|
||||
.expect("Failed to update expiry");
|
||||
|
||||
// Should return None for expired token
|
||||
let result = get_access_token(&db, &token)
|
||||
let result = get_access_token(&db, &token.token)
|
||||
.await
|
||||
.expect("Query failed");
|
||||
|
||||
|
|
@ -1030,7 +1382,9 @@ mod tests {
|
|||
async fn test_get_access_token_revoked() {
|
||||
let db = test_db().await;
|
||||
|
||||
let token = issue_access_token(&db, "test_subject", "test_client_id", "openid profile")
|
||||
let token = issue_access_token(&db, "test_subject", "test_client_id", "openid profile",
|
||||
3600, // TTL
|
||||
)
|
||||
.await
|
||||
.expect("Failed to issue access token");
|
||||
|
||||
|
|
@ -1040,13 +1394,13 @@ mod tests {
|
|||
|
||||
Entity::update_many()
|
||||
.col_expr(Column::Revoked, sea_orm::sea_query::Expr::value(1))
|
||||
.filter(Column::Token.eq(&token))
|
||||
.filter(Column::Token.eq(&token.token))
|
||||
.exec(&db)
|
||||
.await
|
||||
.expect("Failed to revoke token");
|
||||
|
||||
// Should return None for revoked token
|
||||
let result = get_access_token(&db, &token)
|
||||
let result = get_access_token(&db, &token.token)
|
||||
.await
|
||||
.expect("Query failed");
|
||||
|
||||
|
|
@ -1058,38 +1412,44 @@ mod tests {
|
|||
let db = test_db().await;
|
||||
|
||||
// Create initial refresh token
|
||||
let token1 = issue_refresh_token(&db, "test_subject", "test_client_id", "openid profile", None)
|
||||
let token1 = issue_refresh_token(&db, "test_subject", "test_client_id", "openid profile",
|
||||
86400, // TTL
|
||||
None, // parent_token
|
||||
)
|
||||
.await
|
||||
.expect("Failed to issue refresh token");
|
||||
|
||||
// Rotate to new token
|
||||
let token2 = issue_refresh_token(&db, "test_subject", "test_client_id", "openid profile", Some(&token1))
|
||||
let token2 = issue_refresh_token(&db, "test_client_id", "test_subject", "openid profile", 86400, Some(token1.token.clone()))
|
||||
.await
|
||||
.expect("Failed to rotate refresh token");
|
||||
|
||||
// Verify parent chain
|
||||
let rt2 = get_refresh_token(&db, &token2)
|
||||
let rt2 = get_refresh_token(&db, &token2.token)
|
||||
.await
|
||||
.expect("Failed to get token")
|
||||
.expect("Token not found");
|
||||
|
||||
assert_eq!(rt2.parent_refresh_token, Some(token1));
|
||||
assert_eq!(rt2.parent_token, Some(token1.token));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_revoke_refresh_token() {
|
||||
let db = test_db().await;
|
||||
|
||||
let token = issue_refresh_token(&db, "test_subject", "test_client_id", "openid profile", None)
|
||||
let token = issue_refresh_token(&db, "test_subject", "test_client_id", "openid profile",
|
||||
86400, // TTL
|
||||
None, // parent_token
|
||||
)
|
||||
.await
|
||||
.expect("Failed to issue refresh token");
|
||||
|
||||
revoke_refresh_token(&db, &token)
|
||||
revoke_refresh_token(&db, &token.token)
|
||||
.await
|
||||
.expect("Failed to revoke token");
|
||||
|
||||
// Should return None for revoked token
|
||||
let result = get_refresh_token(&db, &token)
|
||||
let result = get_refresh_token(&db, &token.token)
|
||||
.await
|
||||
.expect("Query failed");
|
||||
|
||||
|
|
@ -1140,12 +1500,13 @@ mod tests {
|
|||
.await
|
||||
.expect("Failed to create user");
|
||||
|
||||
let user = verify_user_password(&db, "testuser", "password123")
|
||||
let subject = verify_user_password(&db, "testuser", "password123")
|
||||
.await
|
||||
.expect("Failed to verify password")
|
||||
.expect("Verification failed");
|
||||
|
||||
assert_eq!(user.username, "testuser");
|
||||
// Verify it's a valid subject (not empty)
|
||||
assert!(!subject.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -1192,7 +1553,7 @@ mod tests {
|
|||
.await
|
||||
.expect("Failed to create user");
|
||||
|
||||
update_user(&db, &user.subject, false, Some("test@example.com"), Some(true))
|
||||
update_user(&db, &user.subject, false, Some("test@example.com".to_string()), Some(true))
|
||||
.await
|
||||
.expect("Failed to update user");
|
||||
|
||||
|
|
@ -1214,7 +1575,7 @@ mod tests {
|
|||
.await
|
||||
.expect("Failed to create user");
|
||||
|
||||
update_user(&db, &user.subject, true, Some("new@example.com"), None)
|
||||
update_user(&db, &user.subject, true, Some("new@example.com".to_string()), None)
|
||||
.await
|
||||
.expect("Failed to update email");
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ async fn sync_user(db: &DatabaseConnection, user_def: &UserDefinition) -> Result
|
|||
None => {
|
||||
// Create new user
|
||||
tracing::info!("Creating user: {}", user_def.username);
|
||||
storage::create_user(
|
||||
let user = storage::create_user(
|
||||
db,
|
||||
&user_def.username,
|
||||
&user_def.password,
|
||||
|
|
@ -107,13 +107,14 @@ async fn sync_user(db: &DatabaseConnection, user_def: &UserDefinition) -> Result
|
|||
.await
|
||||
.into_diagnostic()?;
|
||||
|
||||
// Update enabled and email_verified flags if needed
|
||||
if !user_def.enabled || user_def.email_verified {
|
||||
// Update enabled flag if needed (email already set during creation)
|
||||
if !user_def.enabled {
|
||||
storage::update_user(
|
||||
db,
|
||||
&user_def.username,
|
||||
&user.subject,
|
||||
user_def.enabled,
|
||||
user_def.email_verified,
|
||||
None, // email already set
|
||||
None, // requires_2fa not set during sync
|
||||
)
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
|
|
@ -128,24 +129,18 @@ async fn sync_user(db: &DatabaseConnection, user_def: &UserDefinition) -> Result
|
|||
(existing_user.email_verified == 1) == user_def.email_verified;
|
||||
let email_matches = existing_user.email == user_def.email;
|
||||
|
||||
if !enabled_matches || !email_verified_matches || !email_matches {
|
||||
if !enabled_matches || !email_matches {
|
||||
tracing::info!("Updating user: {}", user_def.username);
|
||||
storage::update_user(
|
||||
db,
|
||||
&user_def.username,
|
||||
&existing_user.subject,
|
||||
user_def.enabled,
|
||||
user_def.email_verified,
|
||||
if !email_matches { user_def.email.clone() } else { None },
|
||||
None, // requires_2fa not set during sync
|
||||
)
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
|
||||
// Update email if it changed
|
||||
if !email_matches {
|
||||
storage::update_user_email(db, &user_def.username, user_def.email.clone())
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
}
|
||||
|
||||
SyncResult::Updated
|
||||
} else {
|
||||
SyncResult::Unchanged
|
||||
|
|
|
|||
31
src/web.rs
31
src/web.rs
|
|
@ -1633,7 +1633,8 @@ async fn login_submit(
|
|||
.and_then(|h| h.to_str().ok())
|
||||
.map(String::from);
|
||||
|
||||
let session = match storage::create_session(&state.db, &subject, 3600, user_agent, None).await {
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
let session = match storage::create_session(&state.db, &subject, now, 3600, user_agent, None).await {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
let return_to = urlencoded(&form.return_to.unwrap_or_default());
|
||||
|
|
@ -1918,11 +1919,10 @@ async fn passkey_register_start(
|
|||
storage::create_webauthn_challenge(
|
||||
&state.db,
|
||||
&challenge_b64,
|
||||
Some(session.subject.clone()),
|
||||
Some(&session.subject),
|
||||
None,
|
||||
"registration",
|
||||
&options_json,
|
||||
300, // 5 minutes
|
||||
)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
|
@ -2014,7 +2014,7 @@ async fn passkey_register_finish(
|
|||
false, // backup_eligible - TODO: extract from passkey
|
||||
false, // backup_state - TODO: extract from passkey
|
||||
None, // transports
|
||||
req.name.clone(), // Name from request
|
||||
req.name.as_deref(), // Name from request
|
||||
)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
|
@ -2091,11 +2091,10 @@ async fn passkey_auth_start(
|
|||
storage::create_webauthn_challenge(
|
||||
&state.db,
|
||||
&challenge_b64,
|
||||
subject,
|
||||
subject.as_deref(),
|
||||
None,
|
||||
"authentication",
|
||||
&options_json,
|
||||
300, // 5 minutes
|
||||
)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
|
@ -2161,17 +2160,20 @@ async fn passkey_auth_finish(
|
|||
};
|
||||
|
||||
// Create session
|
||||
let session = storage::create_session(&state.db, &passkey.subject, 3600, None, None)
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
let session = storage::create_session(&state.db, &passkey.subject, now, 3600, None, None)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
// Update session with passkey AMR
|
||||
let amr_json = serde_json::to_string(&amr)
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
storage::update_session_auth_context(
|
||||
&state.db,
|
||||
&session.session_id,
|
||||
Some(amr),
|
||||
Some("aal1".to_string()),
|
||||
false,
|
||||
Some(&amr_json),
|
||||
Some("aal1"),
|
||||
Some(false),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
|
@ -2249,11 +2251,10 @@ async fn passkey_2fa_start(
|
|||
storage::create_webauthn_challenge(
|
||||
&state.db,
|
||||
&challenge_b64,
|
||||
Some(session.subject.clone()),
|
||||
Some(session.session_id.clone()),
|
||||
Some(&session.subject),
|
||||
Some(&session.session_id),
|
||||
"2fa",
|
||||
&options_json,
|
||||
300,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
|
@ -2340,8 +2341,8 @@ async fn passkey_2fa_finish(
|
|||
&state.db,
|
||||
&session.session_id,
|
||||
None,
|
||||
Some("aal2".to_string()),
|
||||
true,
|
||||
Some("aal2"),
|
||||
Some(true),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue