Use light-openid to reduce code base size #4
27
Cargo.lock
generated
27
Cargo.lock
generated
@ -1041,6 +1041,23 @@ version = "0.2.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
|
checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "light-openid"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "608aa1b7148a6eeab631c6267deca33407ff851ab50eea115e52c13a9bb184ee"
|
||||||
|
dependencies = [
|
||||||
|
"aes-gcm",
|
||||||
|
"base64",
|
||||||
|
"bincode",
|
||||||
|
"log",
|
||||||
|
"rand",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"urlencoding",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
@ -1185,20 +1202,16 @@ name = "oidc-test-client"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"aes-gcm",
|
|
||||||
"askama",
|
"askama",
|
||||||
"base64",
|
|
||||||
"bincode",
|
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"light-openid",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"urlencoding",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1413,9 +1426,9 @@ checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.11.16"
|
version = "0.11.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254"
|
checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
light-openid = { version = "1.0.1", features=["crypto-wrapper"] }
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
clap = { version = "4.2.4", features = ["derive", "env"] }
|
clap = { version = "4.2.4", features = ["derive", "env"] }
|
||||||
@ -15,9 +16,4 @@ askama = "0.12.0"
|
|||||||
serde = { version = "1.0.160", features = ["derive"] }
|
serde = { version = "1.0.160", features = ["derive"] }
|
||||||
serde_json = "1.0.96"
|
serde_json = "1.0.96"
|
||||||
reqwest = { version = "0.11.16", features = ["json"] }
|
reqwest = { version = "0.11.16", features = ["json"] }
|
||||||
urlencoding = "2.1.2"
|
futures-util = "0.3.28"
|
||||||
futures-util = "0.3.28"
|
|
||||||
aes-gcm = "0.10.1"
|
|
||||||
base64 = "0.21.0"
|
|
||||||
rand = "0.8.5"
|
|
||||||
bincode = {version="2.0.0-rc.3",features=["serde"]}
|
|
@ -34,6 +34,10 @@ main {
|
|||||||
* Header
|
* Header
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-masthead .nav-link {
|
.nav-masthead .nav-link {
|
||||||
color: rgba(255, 255, 255, .5);
|
color: rgba(255, 255, 255, .5);
|
||||||
border-bottom: .25rem solid transparent;
|
border-bottom: .25rem solid transparent;
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
use std::io::ErrorKind;
|
|
||||||
|
|
||||||
use crate::Res;
|
|
||||||
use aes_gcm::aead::{Aead, OsRng};
|
|
||||||
use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce};
|
|
||||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
|
||||||
use base64::Engine as _;
|
|
||||||
use bincode::{Decode, Encode};
|
|
||||||
use rand::Rng;
|
|
||||||
|
|
||||||
const NONCE_LEN: usize = 12;
|
|
||||||
|
|
||||||
pub struct CryptoWrapper {
|
|
||||||
key: Key<Aes256Gcm>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CryptoWrapper {
|
|
||||||
/// Generate a new memory wrapper
|
|
||||||
pub fn new_random() -> Self {
|
|
||||||
Self {
|
|
||||||
key: Aes256Gcm::generate_key(&mut OsRng),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encrypt some data
|
|
||||||
pub fn encrypt<T: Encode + Decode>(&self, data: &T) -> Res<String> {
|
|
||||||
let aes_key = Aes256Gcm::new(&self.key);
|
|
||||||
let nonce_bytes = rand::thread_rng().gen::<[u8; NONCE_LEN]>();
|
|
||||||
|
|
||||||
let serialized_data = bincode::encode_to_vec(data, bincode::config::standard())?;
|
|
||||||
|
|
||||||
let mut enc = aes_key
|
|
||||||
.encrypt(Nonce::from_slice(&nonce_bytes), serialized_data.as_slice())
|
|
||||||
.unwrap();
|
|
||||||
enc.extend_from_slice(&nonce_bytes);
|
|
||||||
|
|
||||||
Ok(BASE64_STANDARD.encode(enc))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypt some data previously encrypted using the [`CryptoWrapper::encrypt`] method
|
|
||||||
pub fn decrypt<T: Decode>(&self, input: &str) -> Res<T> {
|
|
||||||
let bytes = BASE64_STANDARD.decode(input)?;
|
|
||||||
|
|
||||||
if bytes.len() < NONCE_LEN {
|
|
||||||
return Err(Box::new(std::io::Error::new(
|
|
||||||
ErrorKind::Other,
|
|
||||||
"Input string is smaller than nonce!",
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (enc, nonce) = bytes.split_at(bytes.len() - NONCE_LEN);
|
|
||||||
assert_eq!(nonce.len(), NONCE_LEN);
|
|
||||||
|
|
||||||
let aes_key = Aes256Gcm::new(&self.key);
|
|
||||||
|
|
||||||
let dec = match aes_key.decrypt(Nonce::from_slice(nonce), enc) {
|
|
||||||
Ok(d) => d,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Failed to decrypt wrapped data! {:#?}", e);
|
|
||||||
return Err(Box::new(std::io::Error::new(
|
|
||||||
ErrorKind::Other,
|
|
||||||
"Failed to decrypt wrapped data!",
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(bincode::decode_from_slice(&dec, bincode::config::standard())?.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::crypto_wrapper::CryptoWrapper;
|
|
||||||
use bincode::{Decode, Encode};
|
|
||||||
|
|
||||||
#[derive(Encode, Decode, Eq, PartialEq, Debug)]
|
|
||||||
struct Message(String);
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encrypt_and_decrypt() {
|
|
||||||
let wrapper = CryptoWrapper::new_random();
|
|
||||||
let msg = Message("Pierre was here".to_string());
|
|
||||||
let enc = wrapper.encrypt(&msg).unwrap();
|
|
||||||
let dec: Message = wrapper.decrypt(&enc).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(dec, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encrypt_and_decrypt_invalid() {
|
|
||||||
let wrapper_1 = CryptoWrapper::new_random();
|
|
||||||
let wrapper_2 = CryptoWrapper::new_random();
|
|
||||||
let msg = Message("Pierre was here".to_string());
|
|
||||||
let enc = wrapper_1.encrypt(&msg).unwrap();
|
|
||||||
wrapper_2.decrypt::<Message>(&enc).unwrap_err();
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,8 +3,4 @@ use std::error::Error;
|
|||||||
pub type Res<A = ()> = Result<A, Box<dyn Error>>;
|
pub type Res<A = ()> = Result<A, Box<dyn Error>>;
|
||||||
|
|
||||||
pub mod app_config;
|
pub mod app_config;
|
||||||
pub mod crypto_wrapper;
|
|
||||||
pub mod openid_primitives;
|
|
||||||
pub mod remote_ip;
|
pub mod remote_ip;
|
||||||
pub mod state_manager;
|
|
||||||
pub mod time_utils;
|
|
||||||
|
27
src/main.rs
27
src/main.rs
@ -1,11 +1,11 @@
|
|||||||
use actix_web::middleware::Logger;
|
use actix_web::middleware::Logger;
|
||||||
use actix_web::{get, web, App, HttpResponse, HttpServer};
|
use actix_web::{get, web, App, HttpResponse, HttpServer};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
use light_openid::basic_state_manager::BasicStateManager;
|
||||||
|
use light_openid::primitives::OpenIDConfig;
|
||||||
|
|
||||||
use oidc_test_client::app_config::AppConfig;
|
use oidc_test_client::app_config::AppConfig;
|
||||||
use oidc_test_client::openid_primitives::OpenIDConfig;
|
|
||||||
use oidc_test_client::remote_ip::RemoteIP;
|
use oidc_test_client::remote_ip::RemoteIP;
|
||||||
use oidc_test_client::state_manager::StateManager;
|
|
||||||
|
|
||||||
#[get("/assets/bootstrap.min.css")]
|
#[get("/assets/bootstrap.min.css")]
|
||||||
async fn bootstrap() -> HttpResponse {
|
async fn bootstrap() -> HttpResponse {
|
||||||
@ -60,8 +60,8 @@ async fn home() -> HttpResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/start")]
|
#[get("/start")]
|
||||||
async fn start(remote_ip: RemoteIP) -> HttpResponse {
|
async fn start(remote_ip: RemoteIP, state_manager: web::Data<BasicStateManager>) -> HttpResponse {
|
||||||
let config = match OpenIDConfig::load_from(&AppConfig::get().configuration_url).await {
|
let config = match OpenIDConfig::load_from_url(&AppConfig::get().configuration_url).await {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to load OpenID configuration! {e}");
|
log::error!("Failed to load OpenID configuration! {e}");
|
||||||
@ -69,7 +69,7 @@ async fn start(remote_ip: RemoteIP) -> HttpResponse {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let state = match StateManager::gen_state(&remote_ip) {
|
let state = match state_manager.gen_state(remote_ip.0) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to generate state! {:?}", e);
|
log::error!("Failed to generate state! {:?}", e);
|
||||||
@ -77,7 +77,7 @@ async fn start(remote_ip: RemoteIP) -> HttpResponse {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let authorization_url = config.authorization_url(
|
let authorization_url = config.gen_authorization_url(
|
||||||
&AppConfig::get().client_id,
|
&AppConfig::get().client_id,
|
||||||
&state,
|
&state,
|
||||||
&AppConfig::get().redirect_url(),
|
&AppConfig::get().redirect_url(),
|
||||||
@ -95,15 +95,19 @@ struct RedirectQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/redirect")]
|
#[get("/redirect")]
|
||||||
async fn redirect(remote_ip: RemoteIP, query: web::Query<RedirectQuery>) -> HttpResponse {
|
async fn redirect(
|
||||||
|
remote_ip: RemoteIP,
|
||||||
|
query: web::Query<RedirectQuery>,
|
||||||
|
state_manager: web::Data<BasicStateManager>,
|
||||||
|
) -> HttpResponse {
|
||||||
// First, validate state
|
// First, validate state
|
||||||
if let Err(e) = StateManager::validate_state(&remote_ip, &query.state) {
|
if let Err(e) = state_manager.validate_state(remote_ip.0, &query.state) {
|
||||||
log::error!("Failed to validate state {}: {:?}", query.state, e);
|
log::error!("Failed to validate state {}: {:?}", query.state, e);
|
||||||
return ErrorTemplate::build("State could not be validated!");
|
return ErrorTemplate::build("State could not be validated!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then, load OpenID configuration
|
// Then, load OpenID configuration
|
||||||
let config = match OpenIDConfig::load_from(&AppConfig::get().configuration_url).await {
|
let config = match OpenIDConfig::load_from_url(&AppConfig::get().configuration_url).await {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to load OpenID configuration! {e}");
|
log::error!("Failed to load OpenID configuration! {e}");
|
||||||
@ -158,13 +162,14 @@ async fn main() -> std::io::Result<()> {
|
|||||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
|
|
||||||
log::info!("Init state manager");
|
log::info!("Init state manager");
|
||||||
StateManager::init();
|
let state_manager = web::Data::new(BasicStateManager::new());
|
||||||
|
|
||||||
log::info!("Will listen on {}", AppConfig::get().listen_addr);
|
log::info!("Will listen on {}", AppConfig::get().listen_addr);
|
||||||
|
|
||||||
HttpServer::new(|| {
|
HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(Logger::default())
|
.wrap(Logger::default())
|
||||||
|
.app_data(state_manager.clone())
|
||||||
.service(bootstrap)
|
.service(bootstrap)
|
||||||
.service(cover)
|
.service(cover)
|
||||||
.service(home)
|
.service(home)
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
|
||||||
use base64::Engine;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::Res;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Deserialize)]
|
|
||||||
pub struct OpenIDConfig {
|
|
||||||
pub authorization_endpoint: String,
|
|
||||||
pub token_endpoint: String,
|
|
||||||
pub userinfo_endpoint: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub struct TokenResponse {
|
|
||||||
pub access_token: String,
|
|
||||||
pub token_type: String,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub refresh_token: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub expires_in: Option<u64>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub id_token: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub struct UserInfo {
|
|
||||||
pub sub: String,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub name: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub given_name: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub family_name: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub preferred_username: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub email: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub email_verified: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OpenIDConfig {
|
|
||||||
/// Load OpenID configuration from a given URL
|
|
||||||
pub async fn load_from(url: &str) -> Res<Self> {
|
|
||||||
Ok(reqwest::get(url).await?.json().await?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the authorization URL where a user should be redirect
|
|
||||||
pub fn authorization_url(&self, client_id: &str, state: &str, redirect_uri: &str) -> String {
|
|
||||||
let client_id = urlencoding::encode(client_id);
|
|
||||||
let state = urlencoding::encode(state);
|
|
||||||
let redirect_uri = urlencoding::encode(redirect_uri);
|
|
||||||
|
|
||||||
format!("{}?response_type=code&scope=openid%20profile%20email&client_id={client_id}&state={state}&redirect_uri={redirect_uri}", self.authorization_endpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Query the token endpoint
|
|
||||||
pub async fn request_token(
|
|
||||||
&self,
|
|
||||||
client_id: &str,
|
|
||||||
client_secret: &str,
|
|
||||||
code: &str,
|
|
||||||
redirect_uri: &str,
|
|
||||||
) -> Res<(TokenResponse, String)> {
|
|
||||||
let authorization = BASE64_STANDARD.encode(format!("{}:{}", client_id, client_secret));
|
|
||||||
|
|
||||||
let mut params = HashMap::new();
|
|
||||||
params.insert("grant_type", "authorization_code");
|
|
||||||
params.insert("code", code);
|
|
||||||
params.insert("redirect_uri", redirect_uri);
|
|
||||||
|
|
||||||
let response = reqwest::Client::new()
|
|
||||||
.post(&self.token_endpoint)
|
|
||||||
.header("Authorization", format!("Basic {authorization}"))
|
|
||||||
.form(¶ms)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.text()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok((serde_json::from_str(&response)?, response))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Query the UserInfo endpoint
|
|
||||||
pub async fn request_user_info(&self, token: &TokenResponse) -> Res<(UserInfo, String)> {
|
|
||||||
let response = reqwest::Client::new()
|
|
||||||
.get(&self.userinfo_endpoint)
|
|
||||||
.header("Authorization", format!("Bearer {}", token.access_token))
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.text()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok((serde_json::from_str(&response)?, response))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
use std::error::Error;
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use crate::crypto_wrapper::CryptoWrapper;
|
|
||||||
use crate::remote_ip::RemoteIP;
|
|
||||||
use crate::time_utils::time;
|
|
||||||
use crate::Res;
|
|
||||||
use bincode::{Decode, Encode};
|
|
||||||
use std::net::IpAddr;
|
|
||||||
|
|
||||||
pub struct StateManager;
|
|
||||||
|
|
||||||
static mut WRAPPER: Option<CryptoWrapper> = None;
|
|
||||||
|
|
||||||
#[derive(Encode, Decode, Debug)]
|
|
||||||
struct State {
|
|
||||||
ip: IpAddr,
|
|
||||||
expire: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
pub fn new(ip: IpAddr) -> Self {
|
|
||||||
Self {
|
|
||||||
ip,
|
|
||||||
expire: time() + 15 * 60,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
enum StateError {
|
|
||||||
InvalidIp,
|
|
||||||
Expired,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for StateError {}
|
|
||||||
|
|
||||||
impl fmt::Display for StateError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "StateManager error {:?}", self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StateManager {
|
|
||||||
pub fn init() {
|
|
||||||
unsafe {
|
|
||||||
WRAPPER = Some(CryptoWrapper::new_random());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a new state
|
|
||||||
pub fn gen_state(ip: &RemoteIP) -> Res<String> {
|
|
||||||
let state = State::new(ip.0);
|
|
||||||
|
|
||||||
unsafe { WRAPPER.as_ref().unwrap() }.encrypt(&state)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validate generated state
|
|
||||||
pub fn validate_state(ip: &RemoteIP, state: &str) -> Res {
|
|
||||||
let state: State = unsafe { WRAPPER.as_ref().unwrap() }.decrypt(state)?;
|
|
||||||
|
|
||||||
if state.ip != ip.0 {
|
|
||||||
return Err(Box::new(StateError::InvalidIp));
|
|
||||||
}
|
|
||||||
|
|
||||||
if state.expire < time() {
|
|
||||||
return Err(Box::new(StateError::Expired));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
|
||||||
|
|
||||||
/// Get current time since epoch
|
|
||||||
pub fn time() -> u64 {
|
|
||||||
SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_secs()
|
|
||||||
}
|
|
@ -20,7 +20,7 @@
|
|||||||
<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
|
<div class="cover-container d-flex w-100 h-100 p-3 mx-auto flex-column">
|
||||||
<header class="mb-auto">
|
<header class="mb-auto">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="float-md-start mb-0">OIDC test client</h3>
|
<a href="/"><h3 class="float-md-start mb-0 title">OIDC test client</h3></a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
Loading…
Reference in New Issue
Block a user