Refactor Done
This commit is contained in:
parent
409b152fce
commit
e494d71dc3
@ -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"
|
||||||
}
|
}
|
||||||
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "INSERT INTO users(KEY, NAME) VALUES($1, $2);",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 2
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "0e032978b712ce5ad586ab15bdc4efe82935f5f681c3cf54cbeaf845b70c7ff6"
|
|
||||||
}
|
|
||||||
@ -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"
|
||||||
}
|
}
|
||||||
@ -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"
|
|
||||||
}
|
|
||||||
@ -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"
|
||||||
}
|
}
|
||||||
12
.sqlx/query-765a6500f280d08e51e9457e7851a3dd8d2d3805e314cb84d589713bc5ebd7df.json
generated
Normal file
12
.sqlx/query-765a6500f280d08e51e9457e7851a3dd8d2d3805e314cb84d589713bc5ebd7df.json
generated
Normal 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"
|
||||||
|
}
|
||||||
@ -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"
|
||||||
}
|
}
|
||||||
@ -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"
|
||||||
}
|
}
|
||||||
@ -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"
|
||||||
}
|
}
|
||||||
@ -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"
|
|
||||||
}
|
|
||||||
19
example-config/rust-auth@3000.service
Normal file
19
example-config/rust-auth@3000.service
Normal 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
|
||||||
14
example-config/sites-enabled/acme
Normal file
14
example-config/sites-enabled/acme
Normal 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;
|
||||||
|
}
|
||||||
|
# 保留现有的其它配置 ...
|
||||||
|
}
|
||||||
93
example-config/sites-enabled/headscale
Normal file
93
example-config/sites-enabled/headscale
Normal 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;
|
||||||
|
# }
|
||||||
|
#}
|
||||||
91
example-config/sites-enabled/machine_status
Normal file
91
example-config/sites-enabled/machine_status
Normal 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;
|
||||||
|
# }
|
||||||
|
#}
|
||||||
86
example-config/sites-enabled/share
Normal file
86
example-config/sites-enabled/share
Normal 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;
|
||||||
|
# }
|
||||||
|
#}
|
||||||
38
example-config/sites-enabled/sso
Normal file
38
example-config/sites-enabled/sso
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
114
example-config/sites-enabled/vm
Normal file
114
example-config/sites-enabled/vm
Normal 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;
|
||||||
|
# }
|
||||||
|
#}
|
||||||
@ -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> =
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
pub mod otp_controllers;
|
||||||
|
pub mod passkey_login_controllers;
|
||||||
1
src/controllers/otp_controllers.rs
Normal file
1
src/controllers/otp_controllers.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
156
src/controllers/passkey_login_controllers.rs
Normal file
156
src/controllers/passkey_login_controllers.rs
Normal 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())
|
||||||
|
}
|
||||||
0
src/controllers/passkey_register_controller.rs
Normal file
0
src/controllers/passkey_register_controller.rs
Normal file
75
src/dto/credential_mapper.rs
Normal file
75
src/dto/credential_mapper.rs
Normal 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
2
src/dto/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod credential_mapper;
|
||||||
|
pub mod user_mapper;
|
||||||
68
src/dto/user_mapper.rs
Normal file
68
src/dto/user_mapper.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/main.rs
13
src/main.rs
@ -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),
|
||||||
|
|||||||
@ -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(®, ®_state) {
|
let res = match state.webauthn.finish_passkey_registration(®, ®_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())
|
|
||||||
}
|
|
||||||
|
|||||||
19
src/services/cookie_services.rs
Normal file
19
src/services/cookie_services.rs
Normal 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
|
||||||
|
}
|
||||||
30
src/services/gc_services.rs
Normal file
30
src/services/gc_services.rs
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user