Refactor Done

This commit is contained in:
mortis-0 2024-03-16 17:33:21 +08:00
parent 409b152fce
commit e494d71dc3
32 changed files with 877 additions and 347 deletions

View File

@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT COUNT(KEY) AS count FROM users WHERE KEY = $1;", "query": "SELECT COUNT(id) AS count FROM users WHERE id = $1;",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -16,5 +16,5 @@
false false
] ]
}, },
"hash": "db45619d7a6d50b845ed8f31f58b525ae6b4de88eba24b0d4126ba54d07b0731" "hash": "02e3c3e3c5ed47a4fbed6d00a85bb2581417b471dc702d84faa06d3a80a73626"
} }

View File

@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "INSERT INTO users(KEY, NAME) VALUES($1, $2);",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "0e032978b712ce5ad586ab15bdc4efe82935f5f681c3cf54cbeaf845b70c7ff6"
}

View File

@ -1,10 +1,10 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT key FROM users WHERE name = $1;", "query": "SELECT id FROM users WHERE name = $1;",
"describe": { "describe": {
"columns": [ "columns": [
{ {
"name": "KEY", "name": "ID",
"ordinal": 0, "ordinal": 0,
"type_info": "Text" "type_info": "Text"
} }
@ -16,5 +16,5 @@
false false
] ]
}, },
"hash": "1bb4a0ea9450533e5d08db156a7440258c22cc6d14848d247a322a1ff2c73810" "hash": "13e243d5dedaaca85414aeba7883df7099dee9ac1243301baa5847cf91029178"
} }

View File

@ -1,20 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT credential FROM CREDENTIALS WHERE user_id = $1;",
"describe": {
"columns": [
{
"name": "CREDENTIAL",
"ordinal": 0,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "390c853d2fe8777bebbbe3ed5dae7f25415da7e916934e3cf983a9f590804a41"
}

View File

@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "UPDATE CREDENTIALS SET credential = $1 WHERE user_id = $2 AND credential = $3;", "query": "UPDATE credentials SET credential = $1 WHERE user_id = $2 AND credential = $3;",
"describe": { "describe": {
"columns": [], "columns": [],
"parameters": { "parameters": {
@ -8,5 +8,5 @@
}, },
"nullable": [] "nullable": []
}, },
"hash": "810f3f513dd59e14557979e50b72b0b40fa6a7ea682a74a806ebf2be30c6e4cc" "hash": "5b96d31e66cc895d1f8835d777c7e7bea86ba430d2e284631214751571b4f71f"
} }

View File

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO users(id, name) VALUES($1, $2);",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "765a6500f280d08e51e9457e7851a3dd8d2d3805e314cb84d589713bc5ebd7df"
}

View File

@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT CREDENTIAL FROM CREDENTIALS WHERE USER_ID = $1;", "query": "SELECT credential FROM credentials WHERE user_id = $1;",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -16,5 +16,5 @@
false false
] ]
}, },
"hash": "1ea5285096550f447c4ec8eed2aaf623fe2887015ea22f1579f477bc1aeb7a21" "hash": "bd837e755c015654011c0827cc73e28064aa04f38e7d6e8c37f574196e0e6b98"
} }

View File

@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT NAME FROM users WHERE KEY = $1;", "query": "SELECT name FROM users WHERE id = $1;",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -16,5 +16,5 @@
false false
] ]
}, },
"hash": "e8b97d3910dbfb9ea1dd5d6d8a105886df0573e55c9ac62ac1cc8ecd15ae9801" "hash": "c2ee6dfd462023f43f562277b198e912f6ed52fd9cd1dbe902eaef02416a04ba"
} }

View File

@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "INSERT INTO CREDENTIALS(user_id, credential) VALUES($1, $2);", "query": "INSERT INTO credentials(user_id, credential) VALUES($1, $2);",
"describe": { "describe": {
"columns": [], "columns": [],
"parameters": { "parameters": {
@ -8,5 +8,5 @@
}, },
"nullable": [] "nullable": []
}, },
"hash": "62612ca2817b12ef492a450be661ad58037c7d5b433cba9855fff21461b9e7fc" "hash": "ceb63ff524b09830ab5978258451d597ffc097990591b3964eab20eb41bb7ff9"
} }

View File

@ -1,20 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT KEY FROM users WHERE NAME = $1;",
"describe": {
"columns": [
{
"name": "KEY",
"ordinal": 0,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "f1948f31683e327633ad9ae6b56764a0965331c825faf0a86ca18331e4d8ebdf"
}

BIN
auth.db

Binary file not shown.

View File

@ -0,0 +1,19 @@
[Unit]
Description=Rust Auth Service for Port %I
After=network.target
[Service]
ExecStart=/usr/local/bin/rust-auth
Environment="RUST_LOG=debug"
# Cookie 所允许的域名
# Environment="DOMAIN=.aaronhu.cn"
# 部署在哪个子路由上?
Environment="HOME_URL=/aaron"
Environment="RP_ORIGIN=https://sso.aaronhu.cn"
Environment="RP_NAME=Huyunfan Auth"
Environment="RP_ID=aaronhu.cn"
Environment="DATABASE_URL=/var/auth/auth.db"
Environment="PORT=%I"
[Install]
WantedBy=default.target

View File

@ -0,0 +1,14 @@
server {
listen 80;
server_name _; # 通配符,表示匹配所有服务器名
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /var/www/letsencrypt; # 确保这里的路径指向您存放ACME挑战文件的目录
}
# Redirect all other requests to HTTPS
location / {
return 301 https://$host$request_uri;
}
# 保留现有的其它配置 ...
}

View File

@ -0,0 +1,93 @@
##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# https://www.nginx.com/resources/wiki/start/
# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
# https://wiki.debian.org/Nginx/DirectoryStructure
#
# In most cases, administrators will remove this file from sites-enabled/ and
# leave it as reference inside of sites-available where it will continue to be
# updated by the nginx packaging team.
#
# This file will automatically load configuration files provided by other
# applications, such as Drupal or Wordpress. These applications will be made
# available underneath a path with that package name, such as /drupal8.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##
# Default server configuration
#
map $http_upgrade $connection_upgrade {
default keep-alive;
'websocket' upgrade;
'' close;
}
server {
listen 80;
server_name derp.aaronhu.cn;
location / {
return 301 https://$host$request_uri;
}
}
server {
# HEADSCALE
# SSL configuration
#
listen 443 ssl http2;
server_name derp.aaronhu.cn;
ssl_certificate /root/.acme.sh/derp.aaronhu.cn_ecc/fullchain.cer;
#请填写私钥文件的相对路径或绝对路径
ssl_certificate_key /root/.acme.sh/derp.aaronhu.cn_ecc/derp.aaronhu.cn.key;
ssl_session_timeout 5m;
#请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
#请按照以下协议配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /var/www/derp.aaronhu.cn;
}
# [Mon Nov 13 11:52:44 PM CST 2023] Your cert is in: /root/.acme.sh/derp.aaronhu.cn_ecc/derp.aaronhu.cn.cer
# [Mon Nov 13 11:52:44 PM CST 2023] Your cert key is in: /root/.acme.sh/derp.aaronhu.cn_ecc/derp.aaronhu.cn.key
# [Mon Nov 13 11:52:44 PM CST 2023] The intermediate CA cert is in: /root/.acme.sh/derp.aaronhu.cn_ecc/ca.cer
# [Mon Nov 13 11:52:44 PM CST 2023] And the full chain certs is there: /root/.acme.sh/derp.aaronhu.cn_ecc/fullchain.cer
location / {
proxy_pass http://127.0.0.1:18080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $server_name;
proxy_redirect http:// https://;
proxy_buffering off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
}
}
# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#
#server {
# listen 80;
# listen [::]:80;
#
# server_name example.com;
#
# root /var/www/example.com;
# index index.html;
#
# location / {
# try_files $uri $uri/ =404;
# }
#}

View File

@ -0,0 +1,91 @@
##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# https://www.nginx.com/resources/wiki/start/
# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
# https://wiki.debian.org/Nginx/DirectoryStructure
#
# In most cases, administrators will remove this file from sites-enabled/ and
# leave it as reference inside of sites-available where it will continue to be
# updated by the nginx packaging team.
#
# This file will automatically load configuration files provided by other
# applications, such as Drupal or Wordpress. These applications will be made
# available underneath a path with that package name, such as /drupal8.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##
# Default server configuration
#
server {
listen 8899 default_server;
listen [::]:8899 default_server;
root /var/www/machine_status;
}
server{
set $RUST_AUTH_HOME "/aaron";
listen 443 ssl;
listen [::]:443 ssl;
server_name alive.aaronhu.cn;
if ($host != "alive.aaronhu.cn") {
return 404;
}
ssl_certificate /root/.acme.sh/alive.aaronhu.cn_ecc/fullchain.cer;
ssl_certificate_key /root/.acme.sh/alive.aaronhu.cn_ecc/alive.aaronhu.cn.key;
ssl_session_timeout 5m;
#请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
#请按照以下协议配置
ssl_protocols TLSv1.2 TLSv1.3;
location / {
auth_request /auth;
set $original_full_url $scheme://$host$request_uri;
error_page 401 = @error401;
proxy_set_header X-Original-URI $scheme://$host$request_uri;
proxy_pass http://localhost:8083;
}
location /getall/alive/{
proxy_pass http://11.11.11.1:5412/alive;
}
location /pic/{
proxy_pass http://11.11.11.1:8899/;
}
location @error401 {
add_header Set-Cookie "OriginalURL=$scheme://$host$request_uri; Domain=.aaronhu.cn; Path=/aaron; Secure; HttpOnly; Max-Age=120";
return 302 https://sso.aaronhu.cn/aaron/login;
}
location = /auth {
internal;
proxy_pass http://localhost:3000/aaron/auth;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri; #可用来控制权限
proxy_set_header X-Original-Remote-Addr $remote_addr;
proxy_set_header X-Original-Host $host;
}
}
# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#
#server {
# listen 80;
# listen [::]:80;
#
# server_name example.com;
#
# root /var/www/example.com;
# index index.html;
#
# location / {
# try_files $uri $uri/ =404;
# }
#}

View File

@ -0,0 +1,86 @@
##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# https://www.nginx.com/resources/wiki/start/
# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
# https://wiki.debian.org/Nginx/DirectoryStructure
#
# In most cases, administrators will remove this file from sites-enabled/ and
# leave it as reference inside of sites-available where it will continue to be
# updated by the nginx packaging team.
#
# This file will automatically load configuration files provided by other
# applications, such as Drupal or Wordpress. These applications will be made
# available underneath a path with that package name, such as /drupal8.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##
# Default server configuration
#
#server {
# listen 8899 default_server;
# listen [::]:8899 default_server;
#
# root /var/www/machine_status;
#}
server{
listen 443 ssl;
listen [::]:443 ssl;
server_name share.aaronhu.cn;
if ($host != "share.aaronhu.cn") {
return 404;
}
client_max_body_size 3072M; # put the size that is enough
ssl_certificate /root/.acme.sh/share.aaronhu.cn_ecc/share.aaronhu.cn.cer;
ssl_certificate_key /root/.acme.sh/share.aaronhu.cn_ecc/share.aaronhu.cn.key;
ssl_session_timeout 5m;
#请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
#请按照以下协议配置
ssl_protocols TLSv1.2 TLSv1.3;
location / {
client_max_body_size 3072M; # put the size that is enough
auth_request /auth;
set $original_full_url $scheme://$host$request_uri;
error_page 401 = @error401;
proxy_set_header X-Original-URI $scheme://$host$request_uri;
proxy_pass http://localhost:8080;
}
location = /auth {
internal;
proxy_pass http://localhost:3000/aaron/auth;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri; #可用来控制权限
proxy_set_header X-Original-Remote-Addr $remote_addr;
proxy_set_header X-Original-Host $host;
}
location @error401 {
add_header Set-Cookie "OriginalURL=$scheme://$host$request_uri; Domain=.aaronhu.cn; Path=/aaron; Secure; HttpOnly; Max-Age=120";
return 302 https://sso.aaronhu.cn/aaron/login;
}
}
# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#
#server {
# listen 80;
# listen [::]:80;
#
# server_name example.com;
#
# root /var/www/example.com;
# index index.html;
#
# location / {
# try_files $uri $uri/ =404;
# }
#}

View File

@ -0,0 +1,38 @@
server{
listen 443 ssl;
listen [::]:443 ssl;
server_name sso.aaronhu.cn;
if ($host != "sso.aaronhu.cn") {
return 404;
}
ssl_certificate /root/.acme.sh/sso.aaronhu.cn_ecc/sso.aaronhu.cn.cer;
ssl_certificate_key /root/.acme.sh/sso.aaronhu.cn_ecc/sso.aaronhu.cn.key;
ssl_session_timeout 5m;
#请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
#请按照以下协议配置
ssl_protocols TLSv1.2 TLSv1.3;
location = /aaron/auth {
internal;
proxy_pass http://localhost:3000/aaron/auth;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri; #可用来控制权限
proxy_set_header X-Original-Remote-Addr $remote_addr;
proxy_set_header X-Original-Host $host;
}
location /aaron {
proxy_pass http://localhost:3000/aaron;
proxy_set_header X-Original-Remote-Addr $remote_addr;
proxy_set_header X-Original-Host $host;
proxy_set_header X-Original-URI $original_full_url;
}
location / {
proxy_pass http://localhost:3000;
proxy_set_header X-Original-Remote-Addr $remote_addr;
proxy_set_header X-Original-Host $host;
proxy_set_header X-Original-URI $original_full_url;
}
}

View File

@ -0,0 +1,114 @@
##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# https://www.nginx.com/resources/wiki/start/
# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
# https://wiki.debian.org/Nginx/DirectoryStructure
#
# In most cases, administrators will remove this file from sites-enabled/ and
# leave it as reference inside of sites-available where it will continue to be
# updated by the nginx packaging team.
#
# This file will automatically load configuration files provided by other
# applications, such as Drupal or Wordpress. These applications will be made
# available underneath a path with that package name, such as /drupal8.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##
# Default server configuration
#
#server {
# listen 8899 default_server;
# listen [::]:8899 default_server;
#
# root /var/www/machine_status;
#}
map $http_upgrade $connection_upgrade {
default keep-alive;
'websocket' upgrade;
'' close;
}
server{
listen 443 ssl;
listen [::]:443 ssl;
server_name vm.aaronhu.cn;
if ($host != "vm.aaronhu.cn") {
return 404;
}
ssl_certificate /root/.acme.sh/vm.aaronhu.cn_ecc/vm.aaronhu.cn.cer;
ssl_certificate_key /root/.acme.sh/vm.aaronhu.cn_ecc/vm.aaronhu.cn.key;
ssl_session_timeout 5m;
#请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
#请按照以下协议配置
ssl_protocols TLSv1.2 TLSv1.3;
location / {
auth_request /auth;
set $original_full_url $scheme://$host$request_uri;
error_page 401 = @error401;
proxy_set_header X-Original-URI $scheme://$host$request_uri;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $server_name;
proxy_redirect http:// https://;
proxy_buffering off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_pass https://11.11.11.50:9090;
proxy_ssl_verify off;
proxy_ssl_verify_depth 0;
}
location @error401 {
add_header Set-Cookie "OriginalURL=$scheme://$host$request_uri; Domain=.aaronhu.cn; Path=/aaron; Secure; HttpOnly; Max-Age=120";
return 302 https://sso.aaronhu.cn/aaron/login;
}
location = /auth {
internal;
proxy_pass http://localhost:3000/aaron/auth;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri; #可用来控制权限
proxy_set_header X-Original-Remote-Addr $remote_addr;
proxy_set_header X-Original-Host $host;
}
# location /shellinabox/ {
# auth_request /aaron/auth;
# error_page 401 =200 /login;
# proxy_pass http://localhost:4200/;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# # 可选:如果你的 Shell In A Box 服务中使用了 WebSocket请添加以下配置
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
# }
}
# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#
#server {
# listen 80;
# listen [::]:80;
#
# server_name example.com;
#
# root /var/www/example.com;
# index index.html;
#
# location / {
# try_files $uri $uri/ =404;
# }
#}

View File

@ -18,8 +18,7 @@ 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 static 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 static 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 static 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 static RP_NAME: Lazy<String> = pub static RP_NAME: Lazy<String> =

View File

@ -0,0 +1,2 @@
pub mod otp_controllers;
pub mod passkey_login_controllers;

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,156 @@
// 5. The browser and user have completed their part of the processing. Only in the
// case that the webauthn authenticate call returns Ok, is authentication considered
// a success. If the browser does not complete this call, or *any* error occurs,
// this is an authentication failure.
use axum::http::StatusCode;
use tower_cookies::Cookie;
use axum::response::Redirect;
use tracing::{debug, info};
use uuid::Uuid;
use webauthn_rs::prelude::{Passkey, PasskeyAuthentication, PublicKeyCredential, WebauthnError};
use crate::dto::credential_mapper::update_credential_on_success;
use crate::dto::user_mapper::get_username_by_id;
use crate::services::cookie_services::create_cookie_session;
use crate::config::{COOKIE_DOMAIN, SESSION_ACTIVE_TIME};
use std::time::{Duration, Instant};
use axum::response::IntoResponse;
use axum::extract::Json;
use tower_cookies::Cookies;
use tower_sessions::Session;
use crate::ServerState;
use std::sync::Arc;
use axum::extract::State;
use crate::dto::credential_mapper::get_credential_from_uid;
use crate::dto::user_mapper::get_uid_by_name;
use axum::extract::Path;
pub async fn start_authentication(
State(state): State<Arc<ServerState>>,
session: Session,
Path(user_name): Path<String>,
) -> Result<impl IntoResponse, String> {
info!("开始passkey 验证");
let pool = &state.db;
// Remove any previous authentication that may have occurred from the session.
let _ = session
.remove::<(Uuid, PasskeyAuthentication)>("auth_state")
.await;
let user_id = get_uid_by_name(user_name, pool).await?;
let available_creds = get_credential_from_uid(user_id, pool).await?;
if available_creds.is_empty() {
return Err(WebauthnError::CredentialNotFound.to_string());
}
let allow_credentials: Vec<Passkey> = available_creds;
let res = match state
.webauthn
.start_passkey_authentication(&allow_credentials)
{
Ok((rcr, auth_state)) => {
// Note that due to the session store in use being a server side memory store, this is
// safe to store the auth_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("auth_state", (&user_id, auth_state))
.await
.map_err(|_| WebauthnError::AuthenticationFailure.to_string())?;
Json(rcr)
}
Err(e) => {
debug!("challenge_authenticate -> {:?}", e);
return Err(WebauthnError::MismatchedChallenge.to_string());
}
};
Ok(res)
}
pub async fn finish_authentication(
State(state): State<Arc<ServerState>>,
session: Session,
cookies: Cookies,
Json(auth): Json<PublicKeyCredential>,
) -> Result<impl IntoResponse, String> {
let pool = &state.db;
let (user_id, auth_state): (Uuid, PasskeyAuthentication) = session
.get("auth_state")
.await
.unwrap()
.ok_or(WebauthnError::AuthenticationFailure.to_string())?;
info!("已获得认证状态uid:{}", user_id);
let _ = session
.remove::<(Uuid, PasskeyAuthentication)>("auth_state")
.await;
let res = match state
.webauthn
.finish_passkey_authentication(&auth, &auth_state)
{
Ok(auth_result) => {
info!("passkey认证通过uid:{}", user_id);
let passkeys = crate::dto::credential_mapper::get_credential_from_uid(user_id, pool)
.await
.map_err(|_| WebauthnError::AuthenticatorDataMissingExtension.to_string())?;
if passkeys.is_empty() {
return Err(WebauthnError::UserNotPresent.to_string());
}
for passkey in passkeys {
let mut credential = passkey.clone();
if credential.cred_id() == auth_result.cred_id() {
credential.update_credential(&auth_result);
match update_credential_on_success(credential, user_id, passkey, pool).await {
Ok(_) => break,
Err(x) => return Err(x),
}
}
}
let user_name = get_username_by_id(user_id, pool).await?;
// Add our own values to the session
session
.insert("user_id", user_id)
.await
.map_err(|_| WebauthnError::InvalidUserUniqueId.to_string())?;
session
.insert("user_name", user_name)
.await
.map_err(|_| WebauthnError::InvalidUsername.to_string())?;
let mut lock = state.session.lock().await;
let uuid = Uuid::new_v4();
let expires = Instant::now() + Duration::from_secs(*SESSION_ACTIVE_TIME);
lock.insert(uuid, expires);
let original_uri = cookies.get("OriginalURL");
let new_cookie = create_cookie_session(uuid);
cookies.add(new_cookie);
// 从Cookie中恢复重定向信息
match original_uri {
Some(redirect) => return Ok(Redirect::to(redirect.value())),
_ => (),
};
// 处理完成重定向后清除Cookie
cookies.remove(Cookie::new("OriginalURL", ""));
info!(
"Passkey登录成功设置Cookie for {}",
COOKIE_DOMAIN.to_string()
);
info!(
"从passkey登录创建了新Session{},过期时间{}s后",
uuid, *SESSION_ACTIVE_TIME
);
StatusCode::OK
}
Err(e) => {
debug!("challenge_register -> {:?}", e);
StatusCode::BAD_REQUEST
}
};
info!("Authentication Successful!");
Err(res.to_string())
}

View File

@ -0,0 +1,75 @@
#![allow(non_snake_case)]
use uuid::Uuid;
use sqlx::Pool;
use sqlx::Sqlite;
use webauthn_rs::prelude::Passkey;
use webauthn_rs::prelude::WebauthnError;
pub async fn get_credential_from_uid(
uid: Uuid,
pool: &Pool<Sqlite>,
) -> Result<Vec<Passkey>, String> {
let uid = uid.to_string();
let result = sqlx::query!(
"SELECT credential FROM credentials WHERE user_id = $1;",
uid
)
.fetch_all(pool)
.await
.map_err(|e| e.to_string())?;
result
.into_iter()
.map(|e| serde_json::from_str::<Passkey>(&e.CREDENTIAL).map_err(|e| e.to_string()))
.collect()
}
pub async fn update_credential_on_success(
new_cred: Passkey,
uid: Uuid,
old_cred: Passkey,
pool: &sqlx::Pool<sqlx::Sqlite>,
) -> Result<String, String> {
let new_cred_str =
serde_json::to_string(&new_cred).map_err(|_| "Cannot Serialize new passkey")?;
let old_cred_str =
serde_json::to_string(&old_cred).map_err(|_| "Cannot Serialize old passkey")?;
let uid = uid.to_string();
if sqlx::query!(
"UPDATE credentials SET credential = $1 WHERE user_id = $2 AND credential = $3;",
new_cred_str,
uid,
old_cred_str
)
.execute(pool)
.await
.is_ok_and(|e| e.rows_affected() != 1)
{
return Err(WebauthnError::AuthenticationFailure.to_string());
}
Ok("Successful Operation update_credential_on_success".to_owned())
}
/// Return Rows Affected
pub async fn add_credential_by_id(
uid: Uuid,
cred: &Passkey,
pool: &sqlx::Pool<sqlx::Sqlite>,
) -> Result<u64, String> {
let uid = uid.to_string();
// Serialise the key
let serialised_key =
serde_json::ser::to_string(&cred).map_err(|_| "Key Serialisation failed")?;
let res = sqlx::query!(
"INSERT INTO credentials(user_id, credential) VALUES($1, $2);",
uid,
serialised_key
)
.execute(pool)
.await
.map_err(|_| WebauthnError::UserNotPresent.to_string())?
.rows_affected();
Ok(res)
}

2
src/dto/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod credential_mapper;
pub mod user_mapper;

68
src/dto/user_mapper.rs Normal file
View File

@ -0,0 +1,68 @@
#![allow(non_snake_case)]
use sqlx::Acquire;
use uuid::Uuid;
use sqlx::Pool;
use sqlx::Sqlite;
pub async fn get_username_by_id(id: Uuid, pool: &Pool<Sqlite>) -> Result<String, String> {
let id_str = id.to_string();
let res = sqlx::query!("SELECT name FROM users WHERE id = $1;", id_str)
.fetch_one(pool)
.await
.map_err(|_| format!("Cannot find username from {}", id))?;
Ok(res.NAME)
}
pub async fn get_uid_by_name(name: String, pool: &Pool<Sqlite>) -> Result<Uuid, String> {
let res = sqlx::query!("SELECT id FROM users WHERE name = $1;", name)
.fetch_optional(pool)
.await
.map_err(|_| format!("Cannot find Uid from {}", name))?;
match res {
Some(id) => {
Ok(Uuid::parse_str(&id.ID)
.map_err(|_| "Cannot parse uid string to uuid".to_string())?)
}
None => Err("cannot get uid from name".to_string()),
}
}
pub async fn get_user_count_by_uid(id: Uuid, pool: &Pool<Sqlite>) -> Result<i32, String> {
let id = id.to_string();
let record = sqlx::query!("SELECT COUNT(id) AS count FROM users WHERE id = $1;", id)
.fetch_one(pool)
.await
.map_err(|_| "Error in db while checking usercount")?;
return Ok(record.count);
}
/// Return rows affected and error reason
pub async fn create_user_if_non_existent(
id: Uuid,
name: String,
pool: &Pool<Sqlite>,
) -> Result<u64, String> {
let uid = id.to_string();
let mut tx = pool
.begin()
.await
.map_err(|_| "cannot start DB transaction")?;
let record = sqlx::query!("SELECT COUNT(id) AS count FROM users WHERE id = $1;", uid)
.fetch_one(&mut *tx)
.await
.map_err(|_| "Error in db while checking usercount")?;
let err_msg = "Transaction Failed".to_string();
if record.count == 0 {
let res = sqlx::query!("INSERT INTO users(id, name) VALUES($1, $2);", uid, name)
.execute(&mut *tx)
.await
.map_err(|_| "error from db when adding user".to_string())?
.rows_affected();
let _ = tx.commit().await.map_err(|_| err_msg)?;
Ok(res)
} else {
let _ = tx.rollback().await.map_err(|_| err_msg.to_string())?;
Err(err_msg)
}
}

View File

@ -1,14 +1,11 @@
use crate::config::{DATABASE_URL, RP_ID, RP_NAME, RP_ORIGIN};
use serde::Deserialize; use serde::Deserialize;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
use std::{collections::HashMap, sync::Arc};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use uuid::Uuid; use uuid::Uuid;
use webauthn_rs::prelude::Url; use webauthn_rs::{prelude::Url, Webauthn, WebauthnBuilder};
use webauthn_rs::{Webauthn, WebauthnBuilder};
use crate::config::{DATABASE_URL, RP_ID, RP_NAME, RP_ORIGIN};
pub struct ServerState { pub struct ServerState {
pub db: sqlx::Pool<sqlx::Sqlite>, pub db: sqlx::Pool<sqlx::Sqlite>,
@ -51,7 +48,7 @@ impl ServerState {
pub struct UserLoginForm { pub struct UserLoginForm {
#[sqlx(rename = "NAME")] #[sqlx(rename = "NAME")]
pub username: String, pub username: String,
#[sqlx(rename = "KEY")] #[sqlx(rename = "OTP_KEY")]
pub otp: String, pub otp: String,
} }
@ -59,6 +56,9 @@ pub struct UserLoginForm {
pub struct User { pub struct User {
#[sqlx(rename = "NAME")] #[sqlx(rename = "NAME")]
pub username: String, pub username: String,
#[sqlx(rename = "KEY")] #[sqlx(rename = "ID")]
pub uid: String, pub uid: String,
#[sqlx(rename = "OTP_KEY")]
pub otp_key: String,
} }

View File

@ -4,9 +4,11 @@ use axum::{
}; };
use entities::*; use entities::*;
use controllers::passkey_login_controllers::{start_authentication, finish_authentication};
use services::{ use services::{
auth::{self, finish_authentication, start_authentication}, auth::start_register,
gc_task, login, login_page, register_page, gc_services::gc_task,
login, login_page, register_page,
}; };
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::Arc; use std::sync::Arc;
@ -17,9 +19,10 @@ use tracing::Level;
pub mod config; pub mod config;
pub mod controllers; pub mod controllers;
pub mod dto;
pub mod entities; pub mod entities;
pub mod services; pub mod services;
use crate::config::*; use crate::{config::*, services::auth::finish_register};
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
// 初始化日志记录器 // 初始化日志记录器
@ -33,8 +36,8 @@ async fn main() {
.route("/auth", get(crate::services::auth_otp)) // http://127.0.0.1:3000 .route("/auth", get(crate::services::auth_otp)) // http://127.0.0.1:3000
.route("/login", get(login_page).post(login)) .route("/login", get(login_page).post(login))
.route("/register", get(register_page)) .route("/register", get(register_page))
.route("/register_start/:username", post(auth::start_register)) .route("/register_start/:username", post(start_register))
.route("/register_finish", post(auth::finish_register)) .route("/register_finish", post(finish_register))
.route( .route(
"/webauthn_login_start/:username", "/webauthn_login_start/:username",
post(start_authentication), post(start_authentication),

View File

@ -1,18 +1,15 @@
#![allow(non_snake_case)]
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use axum::extract::State; use axum::extract::State;
use axum::response::Redirect;
use axum::{ use axum::{
extract::{Json, Path}, extract::{Json, Path},
http::StatusCode, http::StatusCode,
response::IntoResponse, response::IntoResponse,
}; };
use tower_cookies::cookie::SameSite;
use std::time::Instant; use std::time::Instant;
use tower_cookies::{Cookie, Cookies}; use tower_cookies::Cookies;
use tower_sessions::Session; use tower_sessions::Session;
use tracing::*; use tracing::*;
@ -24,7 +21,11 @@ use tracing::*;
// 1. Import the prelude - this contains everything needed for the server to function. // 1. Import the prelude - this contains everything needed for the server to function.
use webauthn_rs::prelude::*; use webauthn_rs::prelude::*;
use crate::config::{COOKIE_DOMAIN, COOKIE_NAME, SESSION_ACTIVE_TIME}; 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; use crate::ServerState;
// 2. The first step a client (user) will carry out is requesting a credential to be // 2. The first step a client (user) will carry out is requesting a credential to be
@ -106,39 +107,22 @@ pub async fn start_register(
.await .await
.map_err(|_| WebauthnError::AuthenticationFailure.to_string())?; .map_err(|_| WebauthnError::AuthenticationFailure.to_string())?;
let pool = &state.db; let pool = &state.db;
let username = username.clone(); let username_tmp = username.clone();
let (user_id, exclude_credentials): (Uuid, Option<Vec<CredentialID>>) = let (user_id, exclude_credentials): (Uuid, Option<Vec<CredentialID>>) =
match sqlx::query!("SELECT key FROM users WHERE name = $1;", username) match get_uid_by_name(username_tmp, pool).await {
.fetch_optional(pool) Ok(uid) => {
.await let exclude_passkeys = get_credential_from_uid(uid, pool).await?;
.map_err(|_| WebauthnError::AuthenticationFailure.to_string())?
{
Some(record) => {
let uid = record.KEY.clone();
let records = sqlx::query!(
"SELECT credential FROM CREDENTIALS WHERE user_id = $1;",
uid
)
.fetch_all(pool)
.await
.map_err(|_| WebauthnError::AuthenticationFailure.to_string())?;
( (
Uuid::parse_str(&record.KEY) uid,
.map_err(|_| WebauthnError::AuthenticationFailure.to_string())?,
Some( Some(
records exclude_passkeys
.iter() .into_iter()
.map(|record| serde_json::from_str::<Passkey>(&record.CREDENTIAL)) .map(|k| k.cred_id().clone())
.collect::<Result<Vec<Passkey>, _>>()
.map_err(|_| WebauthnError::CredentialPersistenceError.to_string())?
.iter()
.map(|passkey| passkey.cred_id().clone())
.collect(), .collect(),
), ),
) )
} }
None => (Uuid::new_v4(), None), Err(_) => (Uuid::new_v4(), None),
}; };
let res = match state.webauthn.start_passkey_registration( let res = match state.webauthn.start_passkey_registration(
@ -190,48 +174,19 @@ pub async fn finish_register(
let res = match state.webauthn.finish_passkey_registration(&reg, &reg_state) { let res = match state.webauthn.finish_passkey_registration(&reg, &reg_state) {
Ok(key) => { Ok(key) => {
info!("Passkey 正常"); info!("Passkey 正常");
let uid = &user_id.to_string();
let username = &user_name.to_string();
info!("检查用户是否存在"); 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 user_count = get_user_count_by_uid(user_id, pool).await?;
.fetch_one(pool)
.await
.map_err(|_| WebauthnError::AuthenticationFailure.to_string())?;
// If the user doesn't exist, insert them into the users table // If the user doesn't exist, insert them into the users table
if record.count == 0 if user_count == 0
&& sqlx::query!( && create_user_if_non_existent(user_id, user_name.to_string(), pool).await? != 1
"INSERT INTO users(KEY, NAME) VALUES($1, $2);",
uid,
username
)
.execute(pool)
.await
.map_err(|_| WebauthnError::UserNotPresent.to_string())?
.rows_affected()
!= 1
{ {
return Err(WebauthnError::AuthenticationFailure.to_string()); return Err(WebauthnError::AuthenticationFailure.to_string());
} }
// Serialise the key
let serialised_key = serde_json::ser::to_string(&key)
.map_err(|_| WebauthnError::CredentialPersistenceError.to_string())?;
// Insert the key into the auth table // Insert the key into the auth table
if sqlx::query!( if add_credential_by_id(user_id, &key, pool).await? != 1 {
"INSERT INTO CREDENTIALS(user_id, credential) VALUES($1, $2);", error!("将用户凭据持久化时失败,rows_affected!=1");
uid,
serialised_key
)
.execute(pool)
.await
.map_err(|_| WebauthnError::UserNotPresent.to_string())?
.rows_affected()
!= 1
{
error!("将用户凭据持久化时失败");
return Err(WebauthnError::AuthenticationFailure.to_string()); return Err(WebauthnError::AuthenticationFailure.to_string());
} }
@ -274,172 +229,3 @@ pub async fn finish_register(
// │ │ │ // │ │ │
// //
// The user indicates the wish to start authentication and we need to provide a challenge. // The user indicates the wish to start authentication and we need to provide a challenge.
pub async fn start_authentication(
State(state): State<Arc<ServerState>>,
session: Session,
Path(user_name): Path<String>,
) -> Result<impl IntoResponse, String> {
info!("开始passkey 验证");
let pool = &state.db;
// Remove any previous authentication that may have occurred from the session.
let _ = session.remove::<(Uuid, PasskeyAuthentication)>("auth_state").await;
let user_id = sqlx::query!("SELECT KEY FROM users WHERE NAME = $1;", user_name)
.fetch_one(pool)
.await
.map_err(|_| WebauthnError::UserNotPresent.to_string())?
.KEY;
let records = sqlx::query!(
"SELECT credential FROM CREDENTIALS WHERE user_id = $1;",
user_id
)
.fetch_all(pool)
.await
.map_err(|_| WebauthnError::AuthenticationFailure.to_string())?;
if records.is_empty() {
return Err(WebauthnError::CredentialNotFound.to_string());
}
let allow_credentials: Vec<Passkey> = records
.iter()
.map(|record| serde_json::de::from_str::<Passkey>(&record.CREDENTIAL))
.collect::<Result<Vec<Passkey>, _>>()
.map_err(|_| WebauthnError::CredentialPersistenceError.to_string())?;
let res = match state
.webauthn
.start_passkey_authentication(&allow_credentials)
{
Ok((rcr, auth_state)) => {
// Note that due to the session store in use being a server side memory store, this is
// safe to store the auth_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("auth_state", (&user_id, auth_state))
.await
.map_err(|_| WebauthnError::AuthenticationFailure.to_string())?;
Json(rcr)
}
Err(e) => {
debug!("challenge_authenticate -> {:?}", e);
return Err(WebauthnError::MismatchedChallenge.to_string());
}
};
Ok(res)
}
// 5. The browser and user have completed their part of the processing. Only in the
// case that the webauthn authenticate call returns Ok, is authentication considered
// a success. If the browser does not complete this call, or *any* error occurs,
// this is an authentication failure.
pub async fn finish_authentication(
State(state): State<Arc<ServerState>>,
session: Session,
cookies: Cookies,
Json(auth): Json<PublicKeyCredential>,
) -> Result<impl IntoResponse, String> {
let pool = &state.db;
let (user_id, auth_state): (Uuid, PasskeyAuthentication) = session
.get("auth_state")
.await
.unwrap()
.ok_or(WebauthnError::AuthenticationFailure.to_string())?;
info!("已获得认证状态uid:{}",user_id);
let _ = session.remove::<(Uuid, PasskeyAuthentication)>("auth_state").await;
let res = match state
.webauthn
.finish_passkey_authentication(&auth, &auth_state)
{
Ok(auth_result) => {
info!("passkey认证通过uid:{}",user_id);
let uid = user_id.clone().to_string();
let records = sqlx::query!(
"SELECT CREDENTIAL FROM CREDENTIALS WHERE USER_ID = $1;",
uid
)
.fetch_all(pool)
.await
.map_err(|_| WebauthnError::AuthenticatorDataMissingExtension.to_string())?;
if records.is_empty() {
return Err(WebauthnError::UserNotPresent.to_string());
}
for record in records {
let mut credential = serde_json::from_str::<Passkey>(&record.CREDENTIAL)
.map_err(|_| WebauthnError::CredentialExistCheckError.to_string())?;
if credential.cred_id() == auth_result.cred_id() {
credential.update_credential(&auth_result);
let credential = serde_json::to_string(&credential)
.map_err(|_| WebauthnError::CredentialPersistenceError.to_string())?;
if sqlx::query!(
"UPDATE CREDENTIALS SET credential = $1 WHERE user_id = $2 AND credential = $3;",
credential,
uid,
record.CREDENTIAL
)
.execute(pool)
.await.is_ok_and(|e|e.rows_affected() !=1 )
{
return Err(WebauthnError::AuthenticationFailure.to_string());
}
break;
}
}
let user_name = sqlx::query!("SELECT NAME FROM users WHERE KEY = $1;", uid)
.fetch_one(pool)
.await
.map_err(|_| WebauthnError::AuthenticationFailure.to_string())?;
// Add our own values to the session
session
.insert("user_id", user_id)
.await
.map_err(|_| WebauthnError::InvalidUserUniqueId.to_string())?;
session
.insert("user_name", user_name.NAME)
.await
.map_err(|_| WebauthnError::InvalidUsername.to_string())?;
let mut lock = state.session.lock().await;
let uuid = Uuid::new_v4();
let expires = Instant::now() + Duration::from_secs(*SESSION_ACTIVE_TIME);
lock.insert(
uuid,
expires
);
let original_uri = cookies.get("OriginalURL");
let mut new_cookie = Cookie::new(COOKIE_NAME.to_string(), uuid.to_string());
new_cookie.set_domain(COOKIE_DOMAIN.to_string());
new_cookie.set_http_only(true);
new_cookie.set_path("/");
new_cookie.set_same_site(SameSite::None);
new_cookie.set_secure(Some(true));
cookies.add(new_cookie);
// 从Cookie中恢复重定向信息
match original_uri {
Some(redirect) => return Ok(Redirect::to(redirect.value())),
_ => (),
};
// 处理完成重定向后清除Cookie
cookies.remove(Cookie::new("OriginalURL", ""));
tracing::info!("Passkey登录成功设置Cookie for {}", COOKIE_DOMAIN.to_string());
info!("从passkey登录创建了新Session{},过期时间{}s后",uuid,*SESSION_ACTIVE_TIME);
StatusCode::OK
}
Err(e) => {
debug!("challenge_register -> {:?}", e);
StatusCode::BAD_REQUEST
}
};
info!("Authentication Successful!");
Err(res.to_string())
}

View File

@ -0,0 +1,19 @@
use tower_cookies::cookie::SameSite;
use crate::config::COOKIE_DOMAIN;
use crate::config::COOKIE_NAME;
use tower_cookies::Cookie;
use uuid::Uuid;
pub(crate) fn create_cookie_session<'a>(s: Uuid) -> Cookie<'a> {
let mut new_cookie = Cookie::new(COOKIE_NAME.to_string(), s.to_string());
new_cookie.set_domain(COOKIE_DOMAIN.to_string());
new_cookie.set_http_only(true);
new_cookie.set_path("/");
new_cookie.set_same_site(SameSite::None);
new_cookie.set_secure(Some(true));
new_cookie
}

View File

@ -0,0 +1,30 @@
use std::{borrow::BorrowMut, time::Instant};
use crate::ServerState;
use std::sync::Arc;
pub async fn gc(state: Arc<ServerState>) -> Result<(), String> {
let mut locked = state.session.lock().await;
let current_time = Instant::now();
tracing::info!("before gc ,active Sessions {:?}", locked);
locked.borrow_mut().retain(|_, v| *v > current_time);
tracing::info!("gc fired,active Sessions {:?}", locked);
Ok(())
}
use crate::config::SESSION_ACTIVE_TIME;
use std::time::Duration;
pub async fn gc_task(state: Arc<ServerState>) {
let mut interval = tokio::time::interval(Duration::from_secs(*SESSION_ACTIVE_TIME));
loop {
interval.tick().await;
let res = crate::services::gc_services::gc(state.clone()).await;
match res {
Ok(_) => tracing::info!("gc completed"),
Err(s) => tracing::error!("gc failed:{}", s),
}
}
}

View File

@ -2,33 +2,19 @@ use axum::http::{HeaderMap, HeaderValue};
use axum::response::{Html, Redirect}; use axum::response::{Html, Redirect};
use axum::{extract::State, http::StatusCode, response::IntoResponse, Form}; use axum::{extract::State, http::StatusCode, response::IntoResponse, Form};
use minijinja::{context, Environment}; use minijinja::{context, Environment};
use tower_cookies::cookie::SameSite;
use std::borrow::BorrowMut; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use std::str::FromStr;
use tower_cookies::{Cookie, Cookies}; use tower_cookies::{Cookie, Cookies};
use uuid::Uuid; use uuid::Uuid;
use super::config::{COOKIE_DOMAIN, COOKIE_NAME, LOGIN_PAGE_HTML, SESSION_ACTIVE_TIME}; use super::config::{COOKIE_NAME, LOGIN_PAGE_HTML, SESSION_ACTIVE_TIME};
use crate::config::{HOME_URL, REGISTER_PAGE_HTML}; use crate::config::{COOKIE_DOMAIN, HOME_URL, REGISTER_PAGE_HTML};
use crate::{ServerState, UserLoginForm}; use crate::{ServerState, UserLoginForm};
pub async fn gc_task(state: Arc<ServerState>) {
let mut interval = tokio::time::interval(Duration::from_secs(*SESSION_ACTIVE_TIME));
loop {
interval.tick().await;
let res = gc(state.clone()).await;
match res {
Ok(_) => tracing::info!("gc completed"),
Err(s) => tracing::error!("gc failed:{}", s),
}
}
}
// 处理/auth // 处理/auth
pub async fn auth_otp( pub async fn auth_otp(
State(state): State<Arc<ServerState>>, State(state): State<Arc<ServerState>>,
@ -69,7 +55,7 @@ pub async fn login(
tracing::info!("{:?}", &frm); tracing::info!("{:?}", &frm);
let target = sqlx::query_as::<_, UserLoginForm>( let target = sqlx::query_as::<_, UserLoginForm>(
r" r"
SELECT NAME, KEY FROM USERS WHERE NAME = ? SELECT NAME, OTP_KEY FROM USERS WHERE NAME = ?
", ",
) )
.bind(frm.username) .bind(frm.username)
@ -86,12 +72,7 @@ pub async fn login(
Instant::now() + Duration::from_secs(*SESSION_ACTIVE_TIME), Instant::now() + Duration::from_secs(*SESSION_ACTIVE_TIME),
); );
let original_uri = cookies.get("OriginalURL"); let original_uri = cookies.get("OriginalURL");
let mut new_cookie = Cookie::new(COOKIE_NAME.to_string(), s.to_string()); let new_cookie = crate::services::cookie_services::create_cookie_session(s);
new_cookie.set_domain(COOKIE_DOMAIN.to_string());
new_cookie.set_http_only(true);
new_cookie.set_path("/");
new_cookie.set_same_site(SameSite::None);
new_cookie.set_secure(Some(true));
cookies.add(new_cookie); cookies.add(new_cookie);
tracing::info!("登录成功设置Cookie for {}", COOKIE_DOMAIN.to_string()); tracing::info!("登录成功设置Cookie for {}", COOKIE_DOMAIN.to_string());
// 从Cookie中恢复重定向信息 // 从Cookie中恢复重定向信息
@ -152,13 +133,6 @@ pub fn check_otp(key_from_db: String, user_input_otp: String) -> bool {
false false
} }
pub async fn gc(state: Arc<ServerState>) -> Result<(), String> {
let mut locked = state.session.lock().await;
let current_time = Instant::now();
tracing::info!("before gc ,active Sessions {:?}", locked);
locked.borrow_mut().retain(|_, v| *v > current_time);
tracing::info!("gc fired,active Sessions {:?}", locked);
Ok(())
}
pub mod auth; pub mod auth;
pub mod cookie_services;
pub mod gc_services;