DONE,处理重定向

This commit is contained in:
yly 2024-03-14 21:55:12 +08:00
parent 2b7932dd49
commit d119f958f6
7 changed files with 34 additions and 42 deletions

View File

@ -11,7 +11,6 @@ server {
proxy_set_header X-Original-URI $scheme://$host$request_uri; proxy_set_header X-Original-URI $scheme://$host$request_uri;
proxy_pass http://protected/; proxy_pass http://protected/;
} }
location
location = /aaron/auth { location = /aaron/auth {
internal; internal;
proxy_pass http://localhost:3000/auth; proxy_pass http://localhost:3000/auth;

View File

@ -63,7 +63,7 @@
<body> <body>
<div class="container"> <div class="container">
<h1>Login</h1> <h1>Login</h1>
<form action="{{ home_url|safe }}/login{{ url }}" method="POST"> <form action="{{ home_url|safe }}/login" method="POST">
<label for="username">Username:</label> <label for="username">Username:</label>
<input type="text" id="username" name="username" required> <input type="text" id="username" name="username" required>
<label for="password">Password:</label> <label for="password">Password:</label>

View File

@ -1,26 +1,26 @@
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::env; use std::env;
pub const COOKIE_NAME: Lazy<String> = pub static COOKIE_NAME: Lazy<String> =
Lazy::new(|| env::var("COOKIE_NAME").unwrap_or("aaron_auth".to_string())); Lazy::new(|| env::var("COOKIE_NAME").unwrap_or("aaron_auth".to_string()));
pub const SESSION_ACTIVE_TIME: Lazy<u64> = Lazy::new(|| { pub static SESSION_ACTIVE_TIME: Lazy<u64> = Lazy::new(|| {
env::var("SESSION_ACTIVE_TIME") env::var("SESSION_ACTIVE_TIME")
.ok() .ok()
.and_then(|value| value.parse().ok()) .and_then(|value| value.parse().ok())
.unwrap_or(600) .unwrap_or(600)
}); });
pub const COOKIE_DOMAIN: Lazy<String> = pub static COOKIE_DOMAIN: Lazy<String> =
Lazy::new(|| env::var("DOMAIN").ok().unwrap_or(".aaronhu.cn".to_owned())); Lazy::new(|| env::var("DOMAIN").ok().unwrap_or(".aaronhu.cn".to_owned()));
pub const LOGIN_PAGE_HTML: &str = include_str!("../loginpage.html"); pub static LOGIN_PAGE_HTML: &str = include_str!("../loginpage.html");
pub const REGISTER_PAGE_HTML: &str = include_str!("../register.html"); pub static REGISTER_PAGE_HTML: &str = include_str!("../register.html");
pub const PORT: Lazy<String> = Lazy::new(|| env::var("PORT").unwrap_or("3000".to_string())); pub static PORT: Lazy<String> = Lazy::new(|| env::var("PORT").unwrap_or("3000".to_string()));
// 部署此应用的URL,默认为/aaron // 部署此应用的URL,默认为/aaron
pub const HOME_URL: Lazy<String> = pub static HOME_URL: Lazy<String> =
Lazy::new(|| env::var("HOME_URL").unwrap_or("/aaron".to_owned())); Lazy::new(|| env::var("HOME_URL").unwrap_or("/aaron".to_owned()));
pub const DATABASE_URL: Lazy<String> = pub static DATABASE_URL: Lazy<String> =
Lazy::new(|| env::var("DATABASE_URL").unwrap_or("".to_owned())); Lazy::new(|| env::var("DATABASE_URL").unwrap_or("".to_owned()));
pub const RP_ID: Lazy<String> = pub static RP_ID: Lazy<String> =
Lazy::new(|| env::var("RP_ID").unwrap_or("localhost".to_owned())); Lazy::new(|| env::var("RP_ID").unwrap_or("localhost".to_owned()));
pub const RP_ORIGIN: Lazy<String> = pub static RP_ORIGIN: Lazy<String> =
Lazy::new(|| env::var("RP_ORIGIN").unwrap_or("http://localhost:8080".to_owned())); Lazy::new(|| env::var("RP_ORIGIN").unwrap_or("http://localhost:8080".to_owned()));
pub const RP_NAME: Lazy<String> = pub static RP_NAME: Lazy<String> =
Lazy::new(|| env::var("RP_NAME").unwrap_or("Localhost".to_owned())); Lazy::new(|| env::var("RP_NAME").unwrap_or("Localhost".to_owned()));

View File

@ -20,11 +20,11 @@ pub struct ServerState {
impl ServerState { impl ServerState {
pub async fn new() -> Self { pub async fn new() -> Self {
// Effective domain name. // Effective domain name.
let rp_id = RP_ID; let rp_id = &RP_ID;
// Url containing the effective domain name // Url containing the effective domain name
// MUST include the port number! // MUST include the port number!
let rp_origin = Url::parse(&RP_ORIGIN).expect("Invalid URL"); let rp_origin = Url::parse(&RP_ORIGIN).expect("Invalid URL");
let builder = WebauthnBuilder::new(&rp_id, &rp_origin).expect("Invalid configuration"); let builder = WebauthnBuilder::new(rp_id, &rp_origin).expect("Invalid configuration");
// Now, with the builder you can define other options. // Now, with the builder you can define other options.
// Set a "nice" relying party name. Has no security properties and // Set a "nice" relying party name. Has no security properties and

View File

@ -1,7 +1,7 @@
use axum::{routing::{get, get_service, post, Route}, Router}; use axum::{routing::{get, post}, Router};
use entities::*; use entities::*;
use services::{auth::{self, finish_authentication, start_authentication}, gc_task, login, login_page, register_page}; use services::{auth::{self, finish_authentication, start_authentication}, gc_task, login, login_page, register_page};
@ -10,7 +10,6 @@ use std::sync::Arc;
use tower_cookies::CookieManagerLayer; use tower_cookies::CookieManagerLayer;
use tower_http::trace::{self, TraceLayer}; use tower_http::trace::{self, TraceLayer};
use tower_sessions::{Expiry, MemoryStore, SessionManagerLayer}; use tower_sessions::{Expiry, MemoryStore, SessionManagerLayer};
use tower_http::services::{ServeDir, ServeFile};
use tracing::Level; use tracing::Level;
pub mod config; pub mod config;

View File

@ -3,7 +3,6 @@ use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use axum::debug_handler;
use axum::extract::State; use axum::extract::State;
use axum::{ use axum::{
extract::{Json, Path}, extract::{Json, Path},
@ -63,12 +62,15 @@ use crate::ServerState;
// TODO - Improve error handling and messages // TODO - Improve error handling and messages
fn auth_user(cookie_content: String, session_table: &mut HashMap<Uuid, Instant>) -> bool { fn auth_user(cookie_content: String, session_table: &mut HashMap<Uuid, Instant>) -> bool {
let Ok(uuid) = Uuid::parse_str(&cookie_content) else { let Ok(uuid) = Uuid::parse_str(&cookie_content) else {
info!("此用户Session不在表中");
return false; return false;
}; };
let Some(expire) = session_table.get(&uuid) else { let Some(expire) = session_table.get(&uuid) else {
info!("此用户Session已过期");
return false; return false;
}; };
if *expire <= Instant::now() { if *expire <= Instant::now() {
info!("此用户Session已过期");
return false; return false;
} }
session_table.insert( session_table.insert(
@ -76,7 +78,7 @@ fn auth_user(cookie_content: String, session_table: &mut HashMap<Uuid, Instant>)
Instant::now() + Duration::from_secs(*SESSION_ACTIVE_TIME), Instant::now() + Duration::from_secs(*SESSION_ACTIVE_TIME),
); );
tracing::info!("valid cookie {}", uuid); tracing::info!("valid cookie {}", uuid);
return true; true
} }
pub async fn start_register( pub async fn start_register(
@ -188,6 +190,7 @@ pub async fn finish_register(
info!("Passkey 正常"); info!("Passkey 正常");
let uid = &user_id.to_string(); let uid = &user_id.to_string();
let username = &user_name.to_string(); let username = &user_name.to_string();
info!("检查用户是否存在");
// Check if the user_id already exists // Check if the user_id already exists
let record = sqlx::query!("SELECT COUNT(KEY) AS count FROM users WHERE KEY = $1;", uid) let record = sqlx::query!("SELECT COUNT(KEY) AS count FROM users WHERE KEY = $1;", uid)
.fetch_one(pool) .fetch_one(pool)
@ -226,13 +229,14 @@ pub async fn finish_register(
.rows_affected() .rows_affected()
!= 1 != 1
{ {
error!("将用户凭据持久化时失败");
return Err(WebauthnError::AuthenticationFailure.to_string()); return Err(WebauthnError::AuthenticationFailure.to_string());
} }
StatusCode::OK StatusCode::OK
} }
Err(e) => { Err(e) => {
debug!("challenge_register -> {:?}", e); error!("challenge_register -> {:?}", e);
StatusCode::BAD_REQUEST StatusCode::BAD_REQUEST
} }
}; };
@ -277,7 +281,7 @@ pub async fn start_authentication(
info!("Start Authentication"); info!("Start Authentication");
let pool = &state.db; let pool = &state.db;
// Remove any previous authentication that may have occurred from the session. // Remove any previous authentication that may have occurred from the session.
let _ = session.remove::<(Uuid, PasskeyAuthentication)>("auth_state"); let _ = session.remove::<(Uuid, PasskeyAuthentication)>("auth_state").await;
let user_id = sqlx::query!("SELECT KEY FROM users WHERE NAME = $1;", user_name) let user_id = sqlx::query!("SELECT KEY FROM users WHERE NAME = $1;", user_name)
.fetch_one(pool) .fetch_one(pool)
.await .await
@ -342,7 +346,7 @@ pub async fn finish_authentication(
.unwrap() .unwrap()
.ok_or(WebauthnError::AuthenticationFailure.to_string())?; .ok_or(WebauthnError::AuthenticationFailure.to_string())?;
let _ = session.remove::<(Uuid, PasskeyAuthentication)>("auth_state"); let _ = session.remove::<(Uuid, PasskeyAuthentication)>("auth_state").await;
let res = match state let res = match state
.webauthn .webauthn

View File

@ -62,7 +62,6 @@ pub async fn auth_otp(
pub async fn login( pub async fn login(
State(state): State<Arc<ServerState>>, State(state): State<Arc<ServerState>>,
cookies: Cookies, cookies: Cookies,
Query(params): Query<HashMap<String, String>>,
Form(frm): Form<UserLoginForm>, Form(frm): Form<UserLoginForm>,
) -> Result<Redirect, (StatusCode, &'static str)> { ) -> Result<Redirect, (StatusCode, &'static str)> {
let conn = state.db.acquire().await; let conn = state.db.acquire().await;
@ -88,14 +87,18 @@ pub async fn login(
s, s,
Instant::now() + Duration::from_secs(*SESSION_ACTIVE_TIME), Instant::now() + Duration::from_secs(*SESSION_ACTIVE_TIME),
); );
let original_uri = cookies.get("OriginalURL");
let mut new_cookie = Cookie::new(COOKIE_NAME.to_string(), s.to_string()); let mut new_cookie = Cookie::new(COOKIE_NAME.to_string(), s.to_string());
new_cookie.set_domain(COOKIE_DOMAIN.to_string()); new_cookie.set_domain(COOKIE_DOMAIN.to_string());
cookies.add(new_cookie); cookies.add(new_cookie);
if let Some(original_uri) = params.get("original_url") { // 从Cookie中恢复重定向信息
return Ok(Redirect::to(original_uri)); let res = match original_uri{
} Some(redirect) => Ok(Redirect::to(redirect.value())),
None => Err((StatusCode::ACCEPTED, "ok")),
return Err((StatusCode::ACCEPTED, "ok")); };
// 处理完成重定向后清除Cookie
cookies.remove(Cookie::new("OriginalURL", ""));
return res;
} else { } else {
return Err((StatusCode::UNAUTHORIZED, "wrong password")); return Err((StatusCode::UNAUTHORIZED, "wrong password"));
} }
@ -108,22 +111,9 @@ pub async fn login_page(headers: HeaderMap<HeaderValue>) -> impl IntoResponse {
let mut env = Environment::new(); let mut env = Environment::new();
env.add_template("login.html", LOGIN_PAGE_HTML).unwrap(); env.add_template("login.html", LOGIN_PAGE_HTML).unwrap();
let template = env.get_template("login.html").unwrap(); let template = env.get_template("login.html").unwrap();
if let Some(original_uri) = headers.get("X-Original-URI") {
if let Ok(uri) = original_uri.to_str() {
tracing::info!("redirect to {}", uri);
if !uri.is_empty() {
let uri = "?original_url=".to_owned() + uri;
return Html(
template
.render(context! { url => uri, home_url => HOME_URL.to_string() })
.unwrap_or("Error".to_string()),
);
}
}
}
Html( Html(
template template
.render(context! { url => String::new(), home_url => HOME_URL.to_string() }) .render(context! { home_url => HOME_URL.to_string() })
.unwrap_or("Error".to_string()), .unwrap_or("Error".to_string()),
) )
} }