diff --git a/register.html b/register.html
index a68f8d6..24c10c0 100644
--- a/register.html
+++ b/register.html
@@ -110,10 +110,16 @@
},
}),
})
- .then((response) => {
+ .then(async (response) => {
if (response.ok){
console.log("Logged In!");
- window.location.replace("/");
+ var redir = await response.json();
+ if(redir && redir.status === "ok"){
+ console.log("Redirecting");
+ window.location.replace(redir.redirect);
+ } else {
+ window.location.replace("/");
+ }
} else {
console.log("Error");
}
diff --git a/src/controllers/mod.rs b/src/controllers/mod.rs
index 44d00a1..6e4c380 100644
--- a/src/controllers/mod.rs
+++ b/src/controllers/mod.rs
@@ -1,2 +1,3 @@
pub mod otp_controllers;
pub mod passkey_login_controllers;
+pub mod passkey_register_controllers;
\ No newline at end of file
diff --git a/src/controllers/passkey_login_controllers.rs b/src/controllers/passkey_login_controllers.rs
index 19160e9..da0c3a2 100644
--- a/src/controllers/passkey_login_controllers.rs
+++ b/src/controllers/passkey_login_controllers.rs
@@ -130,7 +130,7 @@ pub async fn finish_authentication(
cookies.add(new_cookie);
// 从Cookie中恢复重定向信息
match original_uri {
- Some(redirect) => return Ok(Redirect::to(redirect.value())),
+ Some(redirect) => return Ok(format!("{{\"status\": \"ok\", \"redirect\":\"{}\"}}",redirect.value().to_string())),
_ => (),
};
// 处理完成重定向后,清除Cookie
diff --git a/src/controllers/passkey_register_controller.rs b/src/controllers/passkey_register_controller.rs
deleted file mode 100644
index e69de29..0000000
diff --git a/src/controllers/passkey_register_controllers.rs b/src/controllers/passkey_register_controllers.rs
new file mode 100644
index 0000000..92e29c8
--- /dev/null
+++ b/src/controllers/passkey_register_controllers.rs
@@ -0,0 +1,169 @@
+
+use axum::http::StatusCode;
+use uuid::Uuid;
+use webauthn_rs::prelude::{CreationChallengeResponse, CredentialID, PasskeyAuthentication, PasskeyRegistration, RegisterPublicKeyCredential, WebauthnError};
+use crate::dto::credential_mapper::add_credential_by_id;
+
+use crate::dto::user_mapper::{create_user_if_non_existent, get_user_count_by_uid};
+
+use crate::SESSION_ACTIVE_TIME;
+
+use axum::response::IntoResponse;
+
+use crate::dto::{credential_mapper::get_credential_from_uid, user_mapper::get_uid_by_name};
+
+
+use crate::config::COOKIE_NAME;
+
+use tracing::{info,debug,error};
+
+use axum::extract::{Json, Path};
+
+
+use tower_sessions::Session;
+
+use tower_cookies::Cookies;
+
+use crate::entities::ServerState;
+
+use std::collections::HashMap;
+use std::sync::Arc;
+use std::time::{Duration, Instant};
+
+use axum::extract::State;
+
+
+// TODO - Improve error handling and messages
+fn auth_user(cookie_content: String, session_table: &mut HashMap) -> bool {
+ let Ok(uuid) = Uuid::parse_str(&cookie_content) else {
+ info!("此用户Session不在表中");
+ return false;
+ };
+ let Some(expire) = session_table.get(&uuid) else {
+ info!("此用户Session已过期");
+ return false;
+ };
+ if *expire <= Instant::now() {
+ info!("此用户Session已过期");
+ return false;
+ }
+ session_table.insert(
+ uuid,
+ Instant::now() + Duration::from_secs(*SESSION_ACTIVE_TIME),
+ );
+ tracing::info!("valid cookie {}", uuid);
+ true
+}
+
+pub async fn start_register(
+ State(state): State>,
+ cookies: Cookies,
+ session: Session,
+ Path(username): Path,
+) -> Result, String> {
+ tracing::info!("开始注册");
+ // todo!("Auth User");
+ let Some(cookie_content) = cookies.get(&COOKIE_NAME) else {
+ tracing::info!("用户没有Cookie");
+ return Err(WebauthnError::AuthenticationFailure.to_string());
+ };
+ let mut session_table = state.session.lock().await;
+ if !auth_user(cookie_content.value().to_string(), &mut session_table) {
+ tracing::info!("用户Cookie无效,不在Session表中");
+ return Err(WebauthnError::AuthenticationFailure.to_string());
+ }
+ // Remove any previous registrations that may have occurred from the session.
+ let _ = session
+ .remove::<(String, Uuid, PasskeyRegistration)>("reg_state")
+ .await
+ .map_err(|_| WebauthnError::AuthenticationFailure.to_string())?;
+ let pool = &state.db;
+ let username_tmp = username.clone();
+ let (user_id, exclude_credentials): (Uuid, Option>) =
+ match get_uid_by_name(username_tmp, pool).await {
+ Ok(uid) => {
+ let exclude_passkeys = get_credential_from_uid(uid, pool).await?;
+ (
+ uid,
+ Some(
+ exclude_passkeys
+ .into_iter()
+ .map(|k| k.cred_id().clone())
+ .collect(),
+ ),
+ )
+ }
+ Err(_) => (Uuid::new_v4(), None),
+ };
+
+ let res = match state.webauthn.start_passkey_registration(
+ user_id,
+ &username,
+ &username,
+ exclude_credentials,
+ ) {
+ Ok((ccr, reg_state)) => {
+ // Note that due to the session store in use being a server side memory store, this is
+ // safe to store the reg_state into the session since it is not client controlled and
+ // not open to replay attacks. If this was a cookie store, this would be UNSAFE.
+ session
+ .insert("reg_state", (username, user_id, reg_state))
+ .await
+ .map_err(|_| WebauthnError::ChallengePersistenceError.to_string())?;
+ Json(ccr)
+ }
+ Err(e) => {
+ debug!("challenge_register -> {:?}", e);
+ return Err(WebauthnError::ChallengePersistenceError.to_string());
+ }
+ };
+ Ok(res)
+}
+
+pub async fn finish_register(
+ State(state): State>,
+ session: Session,
+ Json(reg): Json,
+) -> Result {
+ info!("完成注册...");
+ let pool = &state.db;
+ let Ok(Some((user_name, user_id, reg_state))) = session
+ .get::<(String, Uuid, PasskeyRegistration)>("reg_state")
+ .await
+ else {
+ return Err(WebauthnError::AuthenticationFailure.to_string()); //Corrupt Session
+ };
+
+ let _ = session
+ .remove::<(Uuid, PasskeyAuthentication)>("reg_state")
+ .await;
+
+ let res = match state.webauthn.finish_passkey_registration(®, ®_state) {
+ Ok(key) => {
+ info!("Passkey 正常");
+ info!("检查用户是否存在");
+ // Check if the user_id already exists
+ let user_count = get_user_count_by_uid(user_id, pool).await?;
+ // If the user doesn't exist, insert them into the users table
+ if user_count == 0
+ && create_user_if_non_existent(user_id, user_name.to_string(), pool).await? != 1
+ {
+ return Err(WebauthnError::AuthenticationFailure.to_string());
+ }
+
+ // Insert the key into the auth table
+ if add_credential_by_id(user_id, &key, pool).await? != 1 {
+ error!("将用户凭据持久化时失败,rows_affected!=1");
+ return Err(WebauthnError::AuthenticationFailure.to_string());
+ }
+
+ StatusCode::OK
+ }
+ Err(e) => {
+ error!("challenge_register -> {:?}", e);
+ StatusCode::BAD_REQUEST
+ }
+ };
+
+ Ok(res)
+}
diff --git a/src/main.rs b/src/main.rs
index f0a06bb..35fb76f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6,7 +6,6 @@ use entities::*;
use controllers::passkey_login_controllers::{start_authentication, finish_authentication};
use services::{
- auth::start_register,
gc_services::gc_task,
login, login_page, register_page,
};
@@ -22,7 +21,8 @@ pub mod controllers;
pub mod dto;
pub mod entities;
pub mod services;
-use crate::{config::*, services::auth::finish_register};
+use config::*;
+use controllers::passkey_register_controllers::{finish_register,start_register};
#[tokio::main]
async fn main() {
// 初始化日志记录器
diff --git a/src/services/auth.rs b/src/services/auth.rs
deleted file mode 100644
index 427edf1..0000000
--- a/src/services/auth.rs
+++ /dev/null
@@ -1,231 +0,0 @@
-use std::collections::HashMap;
-use std::sync::Arc;
-use std::time::Duration;
-
-use axum::extract::State;
-use axum::{
- extract::{Json, Path},
- http::StatusCode,
- response::IntoResponse,
-};
-use std::time::Instant;
-use tower_cookies::Cookies;
-use tower_sessions::Session;
-use tracing::*;
-
-/*
- * Webauthn RS auth handlers.
- * These files use webauthn to process the data received from each route, and are closely tied to axum
- */
-
-// 1. Import the prelude - this contains everything needed for the server to function.
-use webauthn_rs::prelude::*;
-
-use crate::config::{COOKIE_NAME, SESSION_ACTIVE_TIME};
-use crate::dto::credential_mapper::{add_credential_by_id, get_credential_from_uid};
-use crate::dto::user_mapper::{
- create_user_if_non_existent, get_uid_by_name, get_user_count_by_uid,
-};
-use crate::ServerState;
-
-// 2. The first step a client (user) will carry out is requesting a credential to be
-// registered. We need to provide a challenge for this. The work flow will be:
-//
-// ┌───────────────┐ ┌───────────────┐ ┌───────────────┐
-// │ Authenticator │ │ Browser │ │ Site │
-// └───────────────┘ └───────────────┘ └───────────────┘
-// │ │ │
-// │ │ 1. Start Reg │
-// │ │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶│
-// │ │ │
-// │ │ 2. Challenge │
-// │ │◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
-// │ │ │
-// │ 3. Select Token │ │
-// ─ ─ ─│◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│ │
-// 4. Verify │ │ │ │
-// │ 4. Yield PubKey │ │
-// └ ─ ─▶│─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶ │
-// │ │ │
-// │ │ 5. Send Reg Opts │
-// │ │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶│─ ─ ─
-// │ │ │ │ 5. Verify
-// │ │ │ PubKey
-// │ │ │◀─ ─ ┘
-// │ │ │─ ─ ─
-// │ │ │ │ 6. Persist
-// │ │ │ Credential
-// │ │ │◀─ ─ ┘
-// │ │ │
-// │ │ │
-//
-// In this step, we are responding to the start reg(istration) request, and providing
-// the challenge to the browser.
-
-// TODO - Improve error handling and messages
-fn auth_user(cookie_content: String, session_table: &mut HashMap) -> bool {
- let Ok(uuid) = Uuid::parse_str(&cookie_content) else {
- info!("此用户Session不在表中");
- return false;
- };
- let Some(expire) = session_table.get(&uuid) else {
- info!("此用户Session已过期");
- return false;
- };
- if *expire <= Instant::now() {
- info!("此用户Session已过期");
- return false;
- }
- session_table.insert(
- uuid,
- Instant::now() + Duration::from_secs(*SESSION_ACTIVE_TIME),
- );
- tracing::info!("valid cookie {}", uuid);
- true
-}
-
-pub async fn start_register(
- State(state): State>,
- cookies: Cookies,
- session: Session,
- Path(username): Path,
-) -> Result, String> {
- tracing::info!("开始注册");
- // todo!("Auth User");
- let Some(cookie_content) = cookies.get(&COOKIE_NAME) else {
- tracing::info!("用户没有Cookie");
- return Err(WebauthnError::AuthenticationFailure.to_string());
- };
- let mut session_table = state.session.lock().await;
- if !auth_user(cookie_content.value().to_string(), &mut session_table) {
- tracing::info!("用户Cookie无效,不在Session表中");
- return Err(WebauthnError::AuthenticationFailure.to_string());
- }
- // Remove any previous registrations that may have occurred from the session.
- let _ = session
- .remove::<(String, Uuid, PasskeyRegistration)>("reg_state")
- .await
- .map_err(|_| WebauthnError::AuthenticationFailure.to_string())?;
- let pool = &state.db;
- let username_tmp = username.clone();
- let (user_id, exclude_credentials): (Uuid, Option>) =
- match get_uid_by_name(username_tmp, pool).await {
- Ok(uid) => {
- let exclude_passkeys = get_credential_from_uid(uid, pool).await?;
- (
- uid,
- Some(
- exclude_passkeys
- .into_iter()
- .map(|k| k.cred_id().clone())
- .collect(),
- ),
- )
- }
- Err(_) => (Uuid::new_v4(), None),
- };
-
- let res = match state.webauthn.start_passkey_registration(
- user_id,
- &username,
- &username,
- exclude_credentials,
- ) {
- Ok((ccr, reg_state)) => {
- // Note that due to the session store in use being a server side memory store, this is
- // safe to store the reg_state into the session since it is not client controlled and
- // not open to replay attacks. If this was a cookie store, this would be UNSAFE.
- session
- .insert("reg_state", (username, user_id, reg_state))
- .await
- .map_err(|_| WebauthnError::ChallengePersistenceError.to_string())?;
- Json(ccr)
- }
- Err(e) => {
- debug!("challenge_register -> {:?}", e);
- return Err(WebauthnError::ChallengePersistenceError.to_string());
- }
- };
- Ok(res)
-}
-
-// 3. The browser has completed it's steps and the user has created a public key
-// on their device. Now we have the registration options sent to us, and we need
-// to verify these and persist them.
-
-pub async fn finish_register(
- State(state): State>,
- session: Session,
- Json(reg): Json,
-) -> Result {
- info!("完成注册...");
- let pool = &state.db;
- let Ok(Some((user_name, user_id, reg_state))) = session
- .get::<(String, Uuid, PasskeyRegistration)>("reg_state")
- .await
- else {
- return Err(WebauthnError::AuthenticationFailure.to_string()); //Corrupt Session
- };
-
- let _ = session
- .remove::<(Uuid, PasskeyAuthentication)>("reg_state")
- .await;
-
- let res = match state.webauthn.finish_passkey_registration(®, ®_state) {
- Ok(key) => {
- info!("Passkey 正常");
- info!("检查用户是否存在");
- // Check if the user_id already exists
- let user_count = get_user_count_by_uid(user_id, pool).await?;
- // If the user doesn't exist, insert them into the users table
- if user_count == 0
- && create_user_if_non_existent(user_id, user_name.to_string(), pool).await? != 1
- {
- return Err(WebauthnError::AuthenticationFailure.to_string());
- }
-
- // Insert the key into the auth table
- if add_credential_by_id(user_id, &key, pool).await? != 1 {
- error!("将用户凭据持久化时失败,rows_affected!=1");
- return Err(WebauthnError::AuthenticationFailure.to_string());
- }
-
- StatusCode::OK
- }
- Err(e) => {
- error!("challenge_register -> {:?}", e);
- StatusCode::BAD_REQUEST
- }
- };
-
- Ok(res)
-}
-
-// 4. Now that our public key has been registered, we can authenticate a user and verify
-// that they are the holder of that security token. The work flow is similar to registration.
-//
-// ┌───────────────┐ ┌───────────────┐ ┌───────────────┐
-// │ Authenticator │ │ Browser │ │ Site │
-// └───────────────┘ └───────────────┘ └───────────────┘
-// │ │ │
-// │ │ 1. Start Auth │
-// │ │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶│
-// │ │ │
-// │ │ 2. Challenge │
-// │ │◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
-// │ │ │
-// │ 3. Select Token │ │
-// ─ ─ ─│◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│ │
-// 4. Verify │ │ │ │
-// │ 4. Yield Sig │ │
-// └ ─ ─▶│─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶ │
-// │ │ 5. Send Auth │
-// │ │ Opts │
-// │ │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶│─ ─ ─
-// │ │ │ │ 5. Verify
-// │ │ │ Sig
-// │ │ │◀─ ─ ┘
-// │ │ │
-// │ │ │
-//
-// The user indicates the wish to start authentication and we need to provide a challenge.
diff --git a/src/services/mod.rs b/src/services/mod.rs
index 35aa2b3..e4ddb1f 100644
--- a/src/services/mod.rs
+++ b/src/services/mod.rs
@@ -133,6 +133,5 @@ pub fn check_otp(key_from_db: String, user_input_otp: String) -> bool {
false
}
-pub mod auth;
pub mod cookie_services;
pub mod gc_services;