Compare commits
65 Commits
3d19a0eba2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2268e7cbff | |||
| 4801ed7cf9 | |||
|
|
7391a4f488 | ||
| cc72ff64d2 | |||
| 3482c53acf | |||
| 84c3415ad7 | |||
| cc2c3d7626 | |||
| 965a7dbe8b | |||
| bc6bdcbc7d | |||
| 82ea79126a | |||
| 141ec0ab4a | |||
| 883120e076 | |||
| a64a1d8697 | |||
| df9ee10191 | |||
| cf482d15c2 | |||
| 029e229387 | |||
| 4bd238779b | |||
| 4fdf1bdc6b | |||
| c5ad96692a | |||
| ec72f9350e | |||
| 731f85a33a | |||
| 35967751fe | |||
| 651f512408 | |||
| 8482e7ab1e | |||
| 5dfe6d4e85 | |||
| 0f7a63861d | |||
| 7917c90ece | |||
| 804faa6497 | |||
| fca0e421b5 | |||
| 4636bfe7c1 | |||
| cbf13e02e2 | |||
| 8804af3b3d | |||
| 2bcf9a3cce | |||
| ea5b1e6371 | |||
| e811976b21 | |||
| 320d65e8ff | |||
| 1e61b20249 | |||
| 630a5271cd | |||
| 588a06ccbb | |||
| ab1fa624b0 | |||
| c8f7db0bee | |||
| 294c6a5d3b | |||
| 4b02e84047 | |||
| e646798996 | |||
| 029bb1140f | |||
| d4924efa67 | |||
| 20f7e4e706 | |||
| 4fb7b98d8a | |||
| e9e9dc2a41 | |||
| 62f91a4f34 | |||
| 6191eb1682 | |||
| 2a0b20ad00 | |||
| a07c58955e | |||
| adc9b6bee6 | |||
| 487c2febb7 | |||
| 3856529abc | |||
| 3aaaffd1a8 | |||
| 7564702bd5 | |||
| 98c63056e6 | |||
| f490d314e5 | |||
| 6efd6a5abd | |||
| cc79501f5a | |||
| 9535ab754a | |||
| b72fa90b67 | |||
| 054fc0c4cc |
1089
Cargo.lock
generated
1089
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
37
Cargo.toml
37
Cargo.toml
@@ -8,36 +8,35 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
actix = "0.13.5"
|
actix = "0.13.5"
|
||||||
actix-identity = "0.9.0"
|
actix-identity = "0.9.0"
|
||||||
actix-web = "4.12.1"
|
actix-web = "4.13.0"
|
||||||
actix-session = { version = "0.11.0", features = ["cookie-session", "redis-session"] }
|
actix-session = { version = "0.11.0", features = ["cookie-session", "redis-session"] }
|
||||||
actix-remote-ip = "0.1.0"
|
actix-remote-ip = "0.1.0"
|
||||||
clap = { version = "4.5.53", features = ["derive", "env"] }
|
clap = { version = "4.5.60", features = ["derive", "env"] }
|
||||||
include_dir = "0.7.4"
|
include_dir = "0.7.4"
|
||||||
log = "0.4.29"
|
log = "0.4.29"
|
||||||
serde_json = "1.0.145"
|
serde_json = "1.0.149"
|
||||||
serde_yaml = "0.9.34"
|
serde_yml = "0.0.12"
|
||||||
env_logger = "0.11.8"
|
env_logger = "0.11.9"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
bcrypt = "0.17.1"
|
bcrypt = "0.18.0"
|
||||||
uuid = { version = "1.19.0", features = ["v4"] }
|
uuid = { version = "1.21.0", features = ["v4"] }
|
||||||
mime_guess = "2.0.5"
|
mime_guess = "2.0.5"
|
||||||
askama = "0.14.0"
|
askama = "0.15.4"
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
rand = "0.10.0-rc.5"
|
rand = "0.10.0"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
jwt-simple = { version = "0.12.13", default-features = false, features = ["pure-rust"] }
|
jwt-simple = { version = "0.12.14", default-features = false, features = ["pure-rust"] }
|
||||||
digest = "0.11.0-rc.4"
|
sha2 = "0.11.0-rc.5"
|
||||||
sha2 = "0.11.0-rc.3"
|
lazy-regex = "3.6.0"
|
||||||
lazy-regex = "3.4.2"
|
|
||||||
totp_rfc6238 = "0.6.1"
|
totp_rfc6238 = "0.6.1"
|
||||||
base32 = "0.5.1"
|
base32 = "0.5.1"
|
||||||
qrcode-generator = "5.0.0"
|
qrcode-generator = "5.0.0"
|
||||||
webauthn-rs = { version = "0.5.3", features = ["danger-allow-state-serialisation"] }
|
webauthn-rs = { version = "0.5.4", features = ["danger-allow-state-serialisation"] }
|
||||||
url = "2.5.7"
|
url = "2.5.8"
|
||||||
light-openid = { version = "1.0.4", features = ["crypto-wrapper"] }
|
light-openid = { version = "1.1.0", features = ["crypto-wrapper"] }
|
||||||
bincode = "2.0.1"
|
rkyv = "0.8.15"
|
||||||
chrono = "0.4.42"
|
chrono = "0.4.44"
|
||||||
lazy_static = "1.5.0"
|
|
||||||
mailchecker = "6.0.19"
|
mailchecker = "6.0.19"
|
||||||
httpdate = "1.0.3"
|
httpdate = "1.0.3"
|
||||||
build-time = "0.1.3"
|
build-time = "0.1.3"
|
||||||
|
hex = "0.4.3"
|
||||||
@@ -39,6 +39,11 @@ You can configure a list of clients (Relying Parties) in a `clients.yaml` file w
|
|||||||
claims_id_token:
|
claims_id_token:
|
||||||
groups: ["group_{user}"]
|
groups: ["group_{user}"]
|
||||||
service: "auth"
|
service: "auth"
|
||||||
|
# Optional, claims to be added to the access token payload
|
||||||
|
# The placeholders of `claims_id_token` can also be used here
|
||||||
|
claims_access_token:
|
||||||
|
groups: ["group_{user}"]
|
||||||
|
service: "auth"
|
||||||
# Optional, claims to be added to the user info endpoint response
|
# Optional, claims to be added to the user info endpoint response
|
||||||
# The placeholders of `claims_id_token` can also be used here
|
# The placeholders of `claims_id_token` can also be used here
|
||||||
claims_user_info:
|
claims_user_info:
|
||||||
|
|||||||
2
assets/css/bootstrap.css
vendored
2
assets/css/bootstrap.css
vendored
@@ -11,7 +11,7 @@
|
|||||||
* Copyright 2011-2024 The Bootstrap Authors
|
* Copyright 2011-2024 The Bootstrap Authors
|
||||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
*/
|
*/
|
||||||
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap");
|
@import url("/assets/robotfont/font.css");
|
||||||
:root,
|
:root,
|
||||||
[data-bs-theme=light] {
|
[data-bs-theme=light] {
|
||||||
--bs-blue: #2a9fd6;
|
--bs-blue: #2a9fd6;
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ use actix::{Actor, AsyncContext, Context, Handler};
|
|||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::data::access_token::AccessToken;
|
use crate::data::access_token::AccessToken;
|
||||||
use crate::data::app_config::AppConfig;
|
use crate::data::app_config::AppConfig;
|
||||||
use crate::data::client::ClientID;
|
use crate::data::client::{Client, ClientID};
|
||||||
use crate::data::code_challenge::CodeChallenge;
|
use crate::data::code_challenge::CodeChallenge;
|
||||||
use crate::data::jwt_signer::JWTSigner;
|
use crate::data::jwt_signer::JWTSigner;
|
||||||
use crate::data::user::UserID;
|
use crate::data::user::{User, UserID};
|
||||||
use crate::utils::err::Res;
|
use crate::utils::err::Res;
|
||||||
use crate::utils::string_utils::rand_str;
|
use crate::utils::string_utils::rand_str;
|
||||||
use crate::utils::time_utils::time;
|
use crate::utils::time_utils::time;
|
||||||
@@ -17,17 +17,20 @@ pub struct SessionID(pub String);
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
pub session_id: SessionID,
|
|
||||||
pub client: ClientID,
|
pub client: ClientID,
|
||||||
pub user: UserID,
|
pub user: UserID,
|
||||||
pub auth_time: u64,
|
pub auth_time: u64,
|
||||||
pub redirect_uri: String,
|
pub redirect_uri: String,
|
||||||
|
|
||||||
|
pub session_id: SessionID,
|
||||||
|
pub session_expire_at: u64,
|
||||||
|
|
||||||
pub authorization_code: String,
|
pub authorization_code: String,
|
||||||
pub authorization_code_expire_at: u64,
|
pub authorization_code_expire_at: u64,
|
||||||
|
|
||||||
pub access_token: Option<String>,
|
pub access_token: Option<String>,
|
||||||
pub access_token_expire_at: u64,
|
pub access_token_expire_at: u64,
|
||||||
|
|
||||||
pub refresh_token: String,
|
pub refresh_token: String,
|
||||||
pub refresh_token_expire_at: u64,
|
pub refresh_token_expire_at: u64,
|
||||||
|
|
||||||
@@ -37,19 +40,23 @@ pub struct Session {
|
|||||||
|
|
||||||
impl Session {
|
impl Session {
|
||||||
pub fn is_expired(&self) -> bool {
|
pub fn is_expired(&self) -> bool {
|
||||||
self.authorization_code_expire_at < time()
|
self.session_expire_at < time()
|
||||||
|
|| (self.authorization_code_expire_at < time()
|
||||||
&& self.access_token_expire_at < time()
|
&& self.access_token_expire_at < time()
|
||||||
&& self.refresh_token_expire_at < time()
|
&& self.refresh_token_expire_at < time())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn regenerate_access_and_refresh_tokens(
|
pub fn regenerate_access_and_refresh_tokens(
|
||||||
&mut self,
|
&mut self,
|
||||||
app_config: &AppConfig,
|
app_config: &AppConfig,
|
||||||
jwt_signer: &JWTSigner,
|
jwt_signer: &JWTSigner,
|
||||||
|
user: &User,
|
||||||
|
client: &Client,
|
||||||
) -> Res {
|
) -> Res {
|
||||||
let access_token = AccessToken {
|
let access_token = AccessToken {
|
||||||
issuer: app_config.website_origin.to_string(),
|
issuer: app_config.website_origin.to_string(),
|
||||||
subject_identifier: self.user.clone().0,
|
user,
|
||||||
|
client,
|
||||||
issued_at: time(),
|
issued_at: time(),
|
||||||
exp_time: time() + OPEN_ID_ACCESS_TOKEN_TIMEOUT,
|
exp_time: time() + OPEN_ID_ACCESS_TOKEN_TIMEOUT,
|
||||||
rand_val: rand_str(OPEN_ID_ACCESS_TOKEN_LEN),
|
rand_val: rand_str(OPEN_ID_ACCESS_TOKEN_LEN),
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ pub const USERINFO_URI: &str = "/openid/userinfo";
|
|||||||
|
|
||||||
/// Open ID constants
|
/// Open ID constants
|
||||||
pub const OPEN_ID_SESSION_CLEANUP_INTERVAL: Duration = Duration::from_secs(60);
|
pub const OPEN_ID_SESSION_CLEANUP_INTERVAL: Duration = Duration::from_secs(60);
|
||||||
pub const OPEN_ID_SESSION_LEN: usize = 40;
|
pub const OPEN_ID_SESSION_ID_LEN: usize = 40;
|
||||||
|
pub const OPEN_ID_SESSION_MAX_DURATION: Duration = Duration::from_secs(3600 * 24 * 7);
|
||||||
pub const OPEN_ID_AUTHORIZATION_CODE_LEN: usize = 120;
|
pub const OPEN_ID_AUTHORIZATION_CODE_LEN: usize = 120;
|
||||||
pub const OPEN_ID_AUTHORIZATION_CODE_TIMEOUT: u64 = 300;
|
pub const OPEN_ID_AUTHORIZATION_CODE_TIMEOUT: u64 = 300;
|
||||||
pub const OPEN_ID_ACCESS_TOKEN_LEN: usize = 50;
|
pub const OPEN_ID_ACCESS_TOKEN_LEN: usize = 50;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::utils::time_utils;
|
|||||||
use actix_web::http::header;
|
use actix_web::http::header;
|
||||||
use actix_web::{HttpRequest, HttpResponse, web};
|
use actix_web::{HttpRequest, HttpResponse, web};
|
||||||
use include_dir::{Dir, include_dir};
|
use include_dir::{Dir, include_dir};
|
||||||
|
use std::cmp::max;
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -22,14 +23,15 @@ pub async fn assets_route(req: HttpRequest, path: web::Path<String>) -> HttpResp
|
|||||||
None => HttpResponse::NotFound().body("404 Not found"),
|
None => HttpResponse::NotFound().body("404 Not found"),
|
||||||
Some(file) => {
|
Some(file) => {
|
||||||
let res = mime_guess::from_path(path).first_or_octet_stream();
|
let res = mime_guess::from_path(path).first_or_octet_stream();
|
||||||
let digest = format!("{:x?}", sha256(file.contents()));
|
let digest = hex::encode(sha256(file.contents()));
|
||||||
|
let file_time = max(time_utils::time_start_of_day(), time_utils::build_time());
|
||||||
|
|
||||||
// Check if the browser already knows the file by date
|
// Check if the browser already knows the file by date
|
||||||
if let Some(c) = req.headers().get(header::IF_MODIFIED_SINCE) {
|
if let Some(c) = req.headers().get(header::IF_MODIFIED_SINCE) {
|
||||||
let date_str = c.to_str().unwrap_or("");
|
let date_str = c.to_str().unwrap_or("");
|
||||||
if let Ok(date) = httpdate::parse_http_date(date_str)
|
if let Ok(date) = httpdate::parse_http_date(date_str)
|
||||||
&& date.add(Duration::from_secs(1))
|
&& date.add(Duration::from_secs(1))
|
||||||
>= time_utils::unix_to_system_time(time_utils::build_time())
|
>= time_utils::unix_to_system_time(file_time)
|
||||||
{
|
{
|
||||||
return HttpResponse::NotModified().finish();
|
return HttpResponse::NotModified().finish();
|
||||||
}
|
}
|
||||||
@@ -45,10 +47,7 @@ pub async fn assets_route(req: HttpRequest, path: web::Path<String>) -> HttpResp
|
|||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.content_type(res.to_string())
|
.content_type(res.to_string())
|
||||||
.insert_header(("etag", digest))
|
.insert_header(("etag", digest))
|
||||||
.insert_header((
|
.insert_header(("last-modified", time_utils::unix_to_http_date(file_time)))
|
||||||
"last-modified",
|
|
||||||
time_utils::unix_to_http_date(time_utils::build_time()),
|
|
||||||
))
|
|
||||||
.body(file.contents())
|
.body(file.contents())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,11 +219,12 @@ pub async fn authorize(
|
|||||||
(_, "code") => {
|
(_, "code") => {
|
||||||
// Save all authentication information in memory
|
// Save all authentication information in memory
|
||||||
let session = Session {
|
let session = Session {
|
||||||
session_id: SessionID(rand_str(OPEN_ID_SESSION_LEN)),
|
|
||||||
client: client.id.clone(),
|
client: client.id.clone(),
|
||||||
user: user.uid.clone(),
|
user: user.uid.clone(),
|
||||||
auth_time: SessionIdentity(Some(&id)).auth_time(),
|
auth_time: SessionIdentity(Some(&id)).auth_time(),
|
||||||
redirect_uri,
|
redirect_uri,
|
||||||
|
session_id: SessionID(rand_str(OPEN_ID_SESSION_ID_LEN)),
|
||||||
|
session_expire_at: time() + OPEN_ID_SESSION_MAX_DURATION.as_secs(),
|
||||||
authorization_code: rand_str(OPEN_ID_AUTHORIZATION_CODE_LEN),
|
authorization_code: rand_str(OPEN_ID_AUTHORIZATION_CODE_LEN),
|
||||||
authorization_code_expire_at: time() + OPEN_ID_AUTHORIZATION_CODE_TIMEOUT,
|
authorization_code_expire_at: time() + OPEN_ID_AUTHORIZATION_CODE_TIMEOUT,
|
||||||
access_token: None,
|
access_token: None,
|
||||||
@@ -500,13 +501,7 @@ pub async fn token(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
session.regenerate_access_and_refresh_tokens(AppConfig::get(), &jwt_signer)?;
|
// Get user information
|
||||||
|
|
||||||
sessions
|
|
||||||
.send(openid_sessions_actor::UpdateSession(session.clone()))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let user: Option<User> = users
|
let user: Option<User> = users
|
||||||
.send(users_actor::GetUserRequest(session.user.clone()))
|
.send(users_actor::GetUserRequest(session.user.clone()))
|
||||||
.await
|
.await
|
||||||
@@ -517,6 +512,18 @@ pub async fn token(
|
|||||||
Some(u) => u,
|
Some(u) => u,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Refresh access and refresh tokens
|
||||||
|
session.regenerate_access_and_refresh_tokens(
|
||||||
|
AppConfig::get(),
|
||||||
|
&jwt_signer,
|
||||||
|
&user,
|
||||||
|
&client,
|
||||||
|
)?;
|
||||||
|
sessions
|
||||||
|
.send(openid_sessions_actor::UpdateSession(session.clone()))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Generate id token
|
// Generate id token
|
||||||
let id_token = IdToken {
|
let id_token = IdToken {
|
||||||
issuer: AppConfig::get().website_origin.to_string(),
|
issuer: AppConfig::get().website_origin.to_string(),
|
||||||
@@ -573,8 +580,24 @@ pub async fn token(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
session.regenerate_access_and_refresh_tokens(AppConfig::get(), &jwt_signer)?;
|
// Get user information
|
||||||
|
let user: Option<User> = users
|
||||||
|
.send(users_actor::GetUserRequest(session.user.clone()))
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.0;
|
||||||
|
let user = match user {
|
||||||
|
None => return Ok(error_response(&query, "invalid_request", "User not found!")),
|
||||||
|
Some(u) => u,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Regenerate user session
|
||||||
|
session.regenerate_access_and_refresh_tokens(
|
||||||
|
AppConfig::get(),
|
||||||
|
&jwt_signer,
|
||||||
|
&user,
|
||||||
|
&client,
|
||||||
|
)?;
|
||||||
sessions
|
sessions
|
||||||
.send(openid_sessions_actor::UpdateSession(session.clone()))
|
.send(openid_sessions_actor::UpdateSession(session.clone()))
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
use crate::data::client::{AdditionalClaims, Client};
|
||||||
|
use crate::data::user::User;
|
||||||
use jwt_simple::claims::JWTClaims;
|
use jwt_simple::claims::JWTClaims;
|
||||||
use jwt_simple::prelude::Duration;
|
use jwt_simple::prelude::Duration;
|
||||||
|
|
||||||
pub struct AccessToken {
|
pub struct AccessToken<'a> {
|
||||||
pub issuer: String,
|
pub issuer: String,
|
||||||
pub subject_identifier: String,
|
pub user: &'a User,
|
||||||
|
pub client: &'a Client,
|
||||||
pub issued_at: u64,
|
pub issued_at: u64,
|
||||||
pub exp_time: u64,
|
pub exp_time: u64,
|
||||||
pub rand_val: String,
|
pub rand_val: String,
|
||||||
@@ -13,21 +16,26 @@ pub struct AccessToken {
|
|||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
pub struct CustomAccessTokenClaims {
|
pub struct CustomAccessTokenClaims {
|
||||||
rand_val: String,
|
rand_val: String,
|
||||||
|
/// Additional claims
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub additional_claims: Option<AdditionalClaims>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccessToken {
|
impl<'a> AccessToken<'a> {
|
||||||
pub fn to_jwt_claims(self) -> JWTClaims<CustomAccessTokenClaims> {
|
pub fn to_jwt_claims(self) -> JWTClaims<CustomAccessTokenClaims> {
|
||||||
JWTClaims {
|
JWTClaims {
|
||||||
issued_at: Some(Duration::from_secs(self.issued_at)),
|
issued_at: Some(Duration::from_secs(self.issued_at)),
|
||||||
expires_at: Some(Duration::from_secs(self.exp_time)),
|
expires_at: Some(Duration::from_secs(self.exp_time)),
|
||||||
invalid_before: None,
|
invalid_before: None,
|
||||||
issuer: Some(self.issuer),
|
issuer: Some(self.issuer),
|
||||||
subject: Some(self.subject_identifier),
|
subject: Some(self.user.uid.0.to_string()),
|
||||||
audiences: None,
|
audiences: None,
|
||||||
jwt_id: None,
|
jwt_id: None,
|
||||||
nonce: self.nonce,
|
nonce: self.nonce,
|
||||||
custom: CustomAccessTokenClaims {
|
custom: CustomAccessTokenClaims {
|
||||||
rand_val: self.rand_val,
|
rand_val: self.rand_val,
|
||||||
|
additional_claims: self.client.claims_access_token(self.user),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use crate::constants::{
|
use crate::constants::{
|
||||||
APP_NAME, CLIENTS_LIST_FILE, OIDC_PROVIDER_CB_URI, PROVIDERS_LIST_FILE, USERS_LIST_FILE,
|
APP_NAME, CLIENTS_LIST_FILE, OIDC_PROVIDER_CB_URI, PROVIDERS_LIST_FILE, USERS_LIST_FILE,
|
||||||
@@ -93,8 +93,12 @@ pub struct AppConfig {
|
|||||||
redis_password: String,
|
redis_password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
static ARGS: OnceLock<AppConfig> = OnceLock::new();
|
||||||
static ref ARGS: AppConfig = {
|
|
||||||
|
impl AppConfig {
|
||||||
|
/// Get parsed command line arguments
|
||||||
|
pub fn get() -> &'static AppConfig {
|
||||||
|
ARGS.get_or_init(|| {
|
||||||
let mut config = AppConfig::parse();
|
let mut config = AppConfig::parse();
|
||||||
|
|
||||||
// In debug mode only, use dummy token
|
// In debug mode only, use dummy token
|
||||||
@@ -103,13 +107,7 @@ lazy_static::lazy_static! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config
|
config
|
||||||
};
|
})
|
||||||
}
|
|
||||||
|
|
||||||
impl AppConfig {
|
|
||||||
/// Get parsed command line arguments
|
|
||||||
pub fn get() -> &'static AppConfig {
|
|
||||||
&ARGS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn secure_cookie(&self) -> bool {
|
pub fn secure_cookie(&self) -> bool {
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ pub struct Client {
|
|||||||
/// Additional claims to return with the id token
|
/// Additional claims to return with the id token
|
||||||
claims_id_token: Option<AdditionalClaims>,
|
claims_id_token: Option<AdditionalClaims>,
|
||||||
|
|
||||||
|
/// Additional claims to return with the access token
|
||||||
|
claims_access_token: Option<AdditionalClaims>,
|
||||||
|
|
||||||
/// Additional claims to return through the user info endpoint
|
/// Additional claims to return through the user info endpoint
|
||||||
claims_user_info: Option<AdditionalClaims>,
|
claims_user_info: Option<AdditionalClaims>,
|
||||||
}
|
}
|
||||||
@@ -117,6 +120,11 @@ impl Client {
|
|||||||
self.process_additional_claims(user, &self.claims_id_token)
|
self.process_additional_claims(user, &self.claims_id_token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get additional claims for access_token for a successful authentication
|
||||||
|
pub fn claims_access_token(&self, user: &User) -> Option<AdditionalClaims> {
|
||||||
|
self.process_additional_claims(user, &self.claims_access_token)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get additional claims for user info for a successful authentication
|
/// Get additional claims for user info for a successful authentication
|
||||||
pub fn claims_user_info(&self, user: &User) -> Option<AdditionalClaims> {
|
pub fn claims_user_info(&self, user: &User) -> Option<AdditionalClaims> {
|
||||||
self.process_additional_claims(user, &self.claims_user_info)
|
self.process_additional_claims(user, &self.claims_user_info)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ where
|
|||||||
file_path: path.as_ref().to_path_buf(),
|
file_path: path.as_ref().to_path_buf(),
|
||||||
list: match Self::file_format(path.as_ref()) {
|
list: match Self::file_format(path.as_ref()) {
|
||||||
FileFormat::Json => serde_json::from_str(&file_content)?,
|
FileFormat::Json => serde_json::from_str(&file_content)?,
|
||||||
FileFormat::Yaml => serde_yaml::from_str(&file_content)?,
|
FileFormat::Yaml => serde_yml::from_str(&file_content)?,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ where
|
|||||||
&self.file_path,
|
&self.file_path,
|
||||||
match Self::file_format(self.file_path.as_ref()) {
|
match Self::file_format(self.file_path.as_ref()) {
|
||||||
FileFormat::Json => serde_json::to_string(&self.list)?,
|
FileFormat::Json => serde_json::to_string(&self.list)?,
|
||||||
FileFormat::Yaml => serde_yaml::to_string(&self.list)?,
|
FileFormat::Yaml => serde_yml::to_string(&self.list)?,
|
||||||
},
|
},
|
||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use base32::Alphabet;
|
use base32::Alphabet;
|
||||||
use rand::Rng;
|
use rand::RngExt;
|
||||||
use totp_rfc6238::{HashAlgorithm, TotpGenerator};
|
use totp_rfc6238::{HashAlgorithm, TotpGenerator};
|
||||||
|
|
||||||
use crate::data::app_config::AppConfig;
|
use crate::data::app_config::AppConfig;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use bincode::{Decode, Encode};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
@@ -11,7 +10,17 @@ use crate::data::totp_key::TotpKey;
|
|||||||
use crate::data::webauthn_manager::WebauthnPubKey;
|
use crate::data::webauthn_manager::WebauthnPubKey;
|
||||||
use crate::utils::time_utils::{fmt_time, time};
|
use crate::utils::time_utils::{fmt_time, time};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Encode, Decode)]
|
#[derive(
|
||||||
|
Clone,
|
||||||
|
Debug,
|
||||||
|
Eq,
|
||||||
|
PartialEq,
|
||||||
|
serde::Serialize,
|
||||||
|
serde::Deserialize,
|
||||||
|
rkyv::Archive,
|
||||||
|
rkyv::Serialize,
|
||||||
|
rkyv::Deserialize,
|
||||||
|
)]
|
||||||
pub struct UserID(pub String);
|
pub struct UserID(pub String);
|
||||||
|
|
||||||
impl UserID {
|
impl UserID {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use actix_web::web;
|
use actix_web::web;
|
||||||
use bincode::{Decode, Encode};
|
|
||||||
use light_openid::crypto_wrapper::CryptoWrapper;
|
use light_openid::crypto_wrapper::CryptoWrapper;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use webauthn_rs::prelude::{
|
use webauthn_rs::prelude::{
|
||||||
@@ -28,7 +27,9 @@ pub struct RegisterKeyRequest {
|
|||||||
pub creation_challenge: CreationChallengeResponse,
|
pub creation_challenge: CreationChallengeResponse,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Encode, Decode)]
|
#[derive(
|
||||||
|
Debug, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize,
|
||||||
|
)]
|
||||||
struct RegisterKeyOpaqueData {
|
struct RegisterKeyOpaqueData {
|
||||||
registration_state: String,
|
registration_state: String,
|
||||||
user_id: UserID,
|
user_id: UserID,
|
||||||
@@ -40,7 +41,7 @@ pub struct AuthRequest {
|
|||||||
pub login_challenge: RequestChallengeResponse,
|
pub login_challenge: RequestChallengeResponse,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Encode, Decode)]
|
#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
|
||||||
struct AuthStateOpaqueData {
|
struct AuthStateOpaqueData {
|
||||||
authentication_state: String,
|
authentication_state: String,
|
||||||
user_id: UserID,
|
user_id: UserID,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use digest::Digest;
|
use sha2::Digest;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn sha256(input: &[u8]) -> Vec<u8> {
|
pub fn sha256(input: &[u8]) -> Vec<u8> {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use chrono::DateTime;
|
use chrono::{DateTime, Local, NaiveTime};
|
||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
/// Get the current time since epoch
|
/// Get the current time since epoch
|
||||||
@@ -37,6 +37,14 @@ pub fn build_time() -> u64 {
|
|||||||
date.timestamp() as u64
|
date.timestamp() as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the first second of the day (local time)
|
||||||
|
pub fn time_start_of_day() -> u64 {
|
||||||
|
let local: DateTime<Local> = Local::now()
|
||||||
|
.with_time(NaiveTime::from_hms_opt(0, 0, 0).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
local.timestamp() as u64
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::utils::time_utils::{fmt_time, time};
|
use crate::utils::time_utils::{fmt_time, time};
|
||||||
|
|||||||
Reference in New Issue
Block a user