You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-28 11:02:02 +03:00
Split the core crate
This commit is contained in:
95
Cargo.lock
generated
95
Cargo.lock
generated
@ -1512,9 +1512,11 @@ dependencies = [
|
|||||||
"hyper",
|
"hyper",
|
||||||
"indoc",
|
"indoc",
|
||||||
"mas-config",
|
"mas-config",
|
||||||
"mas-core",
|
"mas-handlers",
|
||||||
"mas-storage",
|
"mas-storage",
|
||||||
|
"mas-tasks",
|
||||||
"mas-templates",
|
"mas-templates",
|
||||||
|
"mas-warp-utils",
|
||||||
"opentelemetry",
|
"opentelemetry",
|
||||||
"opentelemetry-http",
|
"opentelemetry-http",
|
||||||
"opentelemetry-jaeger",
|
"opentelemetry-jaeger",
|
||||||
@ -1562,39 +1564,38 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mas-core"
|
name = "mas-data-model"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"crc",
|
||||||
|
"oauth2-types",
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
"thiserror",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mas-handlers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
"async-trait",
|
|
||||||
"bincode",
|
|
||||||
"chacha20poly1305",
|
|
||||||
"chrono",
|
"chrono",
|
||||||
"cookie",
|
|
||||||
"crc",
|
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"elliptic-curve",
|
|
||||||
"futures-util",
|
|
||||||
"headers",
|
"headers",
|
||||||
"hyper",
|
"hyper",
|
||||||
"indoc",
|
|
||||||
"itertools",
|
|
||||||
"jwt-compact",
|
"jwt-compact",
|
||||||
"k256",
|
|
||||||
"mas-config",
|
"mas-config",
|
||||||
"mas-data-model",
|
"mas-data-model",
|
||||||
"mas-static-files",
|
"mas-static-files",
|
||||||
"mas-storage",
|
"mas-storage",
|
||||||
"mas-templates",
|
"mas-templates",
|
||||||
|
"mas-warp-utils",
|
||||||
"mime",
|
"mime",
|
||||||
"oauth2-types",
|
"oauth2-types",
|
||||||
"once_cell",
|
|
||||||
"opentelemetry",
|
|
||||||
"password-hash",
|
|
||||||
"pkcs8",
|
|
||||||
"rand",
|
"rand",
|
||||||
"rsa",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
@ -1603,23 +1604,11 @@ dependencies = [
|
|||||||
"sqlx",
|
"sqlx",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
"warp",
|
"warp",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mas-data-model"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"chrono",
|
|
||||||
"oauth2-types",
|
|
||||||
"serde",
|
|
||||||
"thiserror",
|
|
||||||
"url",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mas-static-files"
|
name = "mas-static-files"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -1650,6 +1639,19 @@ dependencies = [
|
|||||||
"warp",
|
"warp",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mas-tasks"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"futures-util",
|
||||||
|
"mas-storage",
|
||||||
|
"sqlx",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mas-templates"
|
name = "mas-templates"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -1669,6 +1671,40 @@ dependencies = [
|
|||||||
"warp",
|
"warp",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mas-warp-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bincode",
|
||||||
|
"chacha20poly1305",
|
||||||
|
"chrono",
|
||||||
|
"cookie",
|
||||||
|
"crc",
|
||||||
|
"data-encoding",
|
||||||
|
"headers",
|
||||||
|
"hyper",
|
||||||
|
"jwt-compact",
|
||||||
|
"mas-config",
|
||||||
|
"mas-data-model",
|
||||||
|
"mas-storage",
|
||||||
|
"mas-templates",
|
||||||
|
"mime",
|
||||||
|
"oauth2-types",
|
||||||
|
"once_cell",
|
||||||
|
"opentelemetry",
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"serde_with",
|
||||||
|
"sqlx",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"warp",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "matchers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -1895,7 +1931,6 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"sha2 0.10.0",
|
"sha2 0.10.0",
|
||||||
"sqlx",
|
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
@ -20,6 +20,7 @@ warp = "0.3.2"
|
|||||||
url = "2.2.2"
|
url = "2.2.2"
|
||||||
argon2 = { version = "0.3.2", features = ["password-hash"] }
|
argon2 = { version = "0.3.2", features = ["password-hash"] }
|
||||||
reqwest = { version = "0.11.7", features = ["rustls-tls"], default-features = false, optional = true }
|
reqwest = { version = "0.11.7", features = ["rustls-tls"], default-features = false, optional = true }
|
||||||
|
watchman_client = "0.7.1"
|
||||||
|
|
||||||
tracing = "0.1.29"
|
tracing = "0.1.29"
|
||||||
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
|
||||||
@ -32,17 +33,18 @@ opentelemetry-otlp = { version = "0.9.0", features = ["trace", "metrics"], optio
|
|||||||
opentelemetry-zipkin = { version = "0.14.0", features = ["reqwest-client", "reqwest-rustls"], default-features = false, optional = true }
|
opentelemetry-zipkin = { version = "0.14.0", features = ["reqwest-client", "reqwest-rustls"], default-features = false, optional = true }
|
||||||
|
|
||||||
mas-config = { path = "../config" }
|
mas-config = { path = "../config" }
|
||||||
mas-core = { path = "../core" }
|
mas-handlers = { path = "../handlers" }
|
||||||
mas-templates = { path = "../templates" }
|
mas-templates = { path = "../templates" }
|
||||||
mas-storage = { path = "../storage" }
|
mas-storage = { path = "../storage" }
|
||||||
watchman_client = "0.7.1"
|
mas-tasks = { path = "../tasks" }
|
||||||
|
mas-warp-utils = { path = "../warp-utils" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
indoc = "1.0.3"
|
indoc = "1.0.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["otlp", "jaeger", "zipkin"]
|
default = ["otlp", "jaeger", "zipkin"]
|
||||||
dev = ["mas-templates/dev", "mas-core/dev"]
|
dev = ["mas-templates/dev", "mas-handlers/dev"]
|
||||||
# Enable OpenTelemetry OTLP exporter. Requires "protoc"
|
# Enable OpenTelemetry OTLP exporter. Requires "protoc"
|
||||||
otlp = ["opentelemetry-otlp"]
|
otlp = ["opentelemetry-otlp"]
|
||||||
# Enable OpenTelemetry Jaeger exporter and propagator.
|
# Enable OpenTelemetry Jaeger exporter and propagator.
|
||||||
|
@ -22,8 +22,8 @@ use clap::Parser;
|
|||||||
use futures::{future::TryFutureExt, stream::TryStreamExt};
|
use futures::{future::TryFutureExt, stream::TryStreamExt};
|
||||||
use hyper::{header, Server, Version};
|
use hyper::{header, Server, Version};
|
||||||
use mas_config::RootConfig;
|
use mas_config::RootConfig;
|
||||||
use mas_core::tasks::{self, TaskQueue};
|
|
||||||
use mas_storage::MIGRATOR;
|
use mas_storage::MIGRATOR;
|
||||||
|
use mas_tasks::TaskQueue;
|
||||||
use mas_templates::Templates;
|
use mas_templates::Templates;
|
||||||
use opentelemetry_http::HeaderExtractor;
|
use opentelemetry_http::HeaderExtractor;
|
||||||
use tower::{make::Shared, ServiceBuilder};
|
use tower::{make::Shared, ServiceBuilder};
|
||||||
@ -233,7 +233,7 @@ impl ServerCommand {
|
|||||||
|
|
||||||
info!("Starting task scheduler");
|
info!("Starting task scheduler");
|
||||||
let queue = TaskQueue::default();
|
let queue = TaskQueue::default();
|
||||||
queue.recuring(Duration::from_secs(15), tasks::cleanup_expired(&pool));
|
queue.recuring(Duration::from_secs(15), mas_tasks::cleanup_expired(&pool));
|
||||||
queue.start();
|
queue.start();
|
||||||
|
|
||||||
// Load and compile the templates
|
// Load and compile the templates
|
||||||
@ -254,7 +254,7 @@ impl ServerCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start the server
|
// Start the server
|
||||||
let root = mas_core::handlers::root(&pool, &templates, &config);
|
let root = mas_handlers::root(&pool, &templates, &config);
|
||||||
|
|
||||||
let warp_service = warp::service(root);
|
let warp_service = warp::service(root);
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ pub fn setup(config: &TelemetryConfig) -> anyhow::Result<Option<Tracer>> {
|
|||||||
|
|
||||||
// The CORS filter needs to know what headers it should whitelist for
|
// The CORS filter needs to know what headers it should whitelist for
|
||||||
// CORS-protected requests.
|
// CORS-protected requests.
|
||||||
mas_core::set_propagator(&propagator);
|
mas_warp_utils::filters::cors::set_propagator(&propagator);
|
||||||
global::set_text_map_propagator(propagator);
|
global::set_text_map_propagator(propagator);
|
||||||
|
|
||||||
let tracer = tracer(&config.tracing.exporter)?;
|
let tracer = tracer(&config.tracing.exporter)?;
|
||||||
|
@ -1,242 +0,0 @@
|
|||||||
// Copyright 2021 The Matrix.org Foundation C.I.C.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
//! Access token and refresh token generation and validation
|
|
||||||
//!
|
|
||||||
//! # Example
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! extern crate rand;
|
|
||||||
//!
|
|
||||||
//! use rand::thread_rng;
|
|
||||||
//! use mas_core::tokens::{TokenType, AccessToken, RefreshToken};
|
|
||||||
//!
|
|
||||||
//! let mut rng = thread_rng();
|
|
||||||
//!
|
|
||||||
//! // Generate an access token
|
|
||||||
//! let token = AccessToken.generate(&mut rng);
|
|
||||||
//!
|
|
||||||
//! // Check it and verify its type is right
|
|
||||||
//! assert_eq!(TokenType::check(&token).unwrap(), AccessToken);
|
|
||||||
//!
|
|
||||||
//! // Same, but with a refresh token
|
|
||||||
//! let token = RefreshToken.generate(&mut rng);
|
|
||||||
//! assert_eq!(TokenType::check(&token).unwrap(), RefreshToken);
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
#![deny(missing_docs)]
|
|
||||||
|
|
||||||
use crc::{Crc, CRC_32_ISO_HDLC};
|
|
||||||
use oauth2_types::requests::TokenTypeHint;
|
|
||||||
use rand::{distributions::Alphanumeric, Rng};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
/// Type of token to generate or validate
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum TokenType {
|
|
||||||
/// An access token, used by Relying Parties to authenticate requests
|
|
||||||
AccessToken,
|
|
||||||
/// A refresh token, used by the refresh token grant
|
|
||||||
RefreshToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use TokenType::{AccessToken, RefreshToken};
|
|
||||||
|
|
||||||
impl TokenType {
|
|
||||||
fn prefix(self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
TokenType::AccessToken => "mat",
|
|
||||||
TokenType::RefreshToken => "mar",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_prefix(prefix: &str) -> Option<Self> {
|
|
||||||
match prefix {
|
|
||||||
"mat" => Some(TokenType::AccessToken),
|
|
||||||
"mar" => Some(TokenType::RefreshToken),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a token for the given type
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// extern crate rand;
|
|
||||||
///
|
|
||||||
/// use rand::thread_rng;
|
|
||||||
/// use mas_core::tokens::{AccessToken, RefreshToken};
|
|
||||||
///
|
|
||||||
/// AccessToken.generate(thread_rng());
|
|
||||||
/// RefreshToken.generate(thread_rng());
|
|
||||||
/// ```
|
|
||||||
pub fn generate(self, rng: impl Rng) -> String {
|
|
||||||
let random_part: String = rng
|
|
||||||
.sample_iter(&Alphanumeric)
|
|
||||||
.take(30)
|
|
||||||
.map(char::from)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let base = format!("{}_{}", self.prefix(), random_part);
|
|
||||||
let crc = CRC.checksum(base.as_bytes());
|
|
||||||
let crc = base62_encode(crc);
|
|
||||||
format!("{}_{}", base, crc)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check the format of a token and determine its type
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use mas_core::tokens::TokenType;
|
|
||||||
///
|
|
||||||
/// assert_eq!(
|
|
||||||
/// TokenType::check("mat_kkLSacJDpek22jKWw4AcXG68b7U3W6_0Lg9yb"),
|
|
||||||
/// Ok(TokenType::AccessToken)
|
|
||||||
/// );
|
|
||||||
///
|
|
||||||
/// assert_eq!(
|
|
||||||
/// TokenType::check("mar_PkpplxPkfjsqvtdfUlYR1Afg2TpaHF_GaTQd2"),
|
|
||||||
/// Ok(TokenType::RefreshToken)
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
pub fn check(token: &str) -> Result<TokenType, TokenFormatError> {
|
|
||||||
let split: Vec<&str> = token.split('_').collect();
|
|
||||||
let [prefix, random_part, crc]: [&str; 3] = split
|
|
||||||
.try_into()
|
|
||||||
.map_err(|_| TokenFormatError::InvalidFormat)?;
|
|
||||||
|
|
||||||
if prefix.len() != 3 || random_part.len() != 30 || crc.len() != 6 {
|
|
||||||
return Err(TokenFormatError::InvalidFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
let token_type =
|
|
||||||
TokenType::match_prefix(prefix).ok_or_else(|| TokenFormatError::UnknownPrefix {
|
|
||||||
prefix: prefix.to_string(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let base = format!("{}_{}", token_type.prefix(), random_part);
|
|
||||||
let expected_crc = CRC.checksum(base.as_bytes());
|
|
||||||
let expected_crc = base62_encode(expected_crc);
|
|
||||||
if crc != expected_crc {
|
|
||||||
return Err(TokenFormatError::InvalidCrc {
|
|
||||||
expected: expected_crc,
|
|
||||||
got: crc.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(token_type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq<TokenTypeHint> for TokenType {
|
|
||||||
fn eq(&self, other: &TokenTypeHint) -> bool {
|
|
||||||
matches!(
|
|
||||||
(self, other),
|
|
||||||
(TokenType::AccessToken, TokenTypeHint::AccessToken)
|
|
||||||
| (TokenType::RefreshToken, TokenTypeHint::RefreshToken)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const NUM: [u8; 62] = *b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
||||||
|
|
||||||
fn base62_encode(mut num: u32) -> String {
|
|
||||||
let mut res = String::with_capacity(6);
|
|
||||||
while num > 0 {
|
|
||||||
res.push(NUM[(num % 62) as usize] as char);
|
|
||||||
num /= 62;
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("{:0>6}", res)
|
|
||||||
}
|
|
||||||
|
|
||||||
const CRC: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
|
|
||||||
|
|
||||||
/// Invalid token
|
|
||||||
#[derive(Debug, Error, PartialEq)]
|
|
||||||
pub enum TokenFormatError {
|
|
||||||
/// Overall token format is invalid
|
|
||||||
#[error("invalid token format")]
|
|
||||||
InvalidFormat,
|
|
||||||
|
|
||||||
/// Token used an unknown prefix
|
|
||||||
#[error("unknown token prefix {prefix:?}")]
|
|
||||||
UnknownPrefix {
|
|
||||||
/// The prefix found in the token
|
|
||||||
prefix: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The CRC checksum in the token is invalid
|
|
||||||
#[error("invalid crc {got:?}, expected {expected:?}")]
|
|
||||||
InvalidCrc {
|
|
||||||
/// The CRC hash expected to be found in the token
|
|
||||||
expected: String,
|
|
||||||
/// The CRC found in the token
|
|
||||||
got: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use rand::thread_rng;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_prefix_match() {
|
|
||||||
use TokenType::{AccessToken, RefreshToken};
|
|
||||||
assert_eq!(TokenType::match_prefix("mat"), Some(AccessToken));
|
|
||||||
assert_eq!(TokenType::match_prefix("mar"), Some(RefreshToken));
|
|
||||||
assert_eq!(TokenType::match_prefix("matt"), None);
|
|
||||||
assert_eq!(TokenType::match_prefix("marr"), None);
|
|
||||||
assert_eq!(TokenType::match_prefix("ma"), None);
|
|
||||||
assert_eq!(
|
|
||||||
TokenType::match_prefix(TokenType::AccessToken.prefix()),
|
|
||||||
Some(TokenType::AccessToken)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
TokenType::match_prefix(TokenType::RefreshToken.prefix()),
|
|
||||||
Some(TokenType::RefreshToken)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_generate_and_check() {
|
|
||||||
const COUNT: usize = 500; // Generate 500 of each token type
|
|
||||||
let mut rng = thread_rng();
|
|
||||||
// Generate many access tokens
|
|
||||||
let tokens: HashSet<String> = (0..COUNT)
|
|
||||||
.map(|_| TokenType::AccessToken.generate(&mut rng))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Check that they are all different
|
|
||||||
assert_eq!(tokens.len(), COUNT, "All tokens are unique");
|
|
||||||
|
|
||||||
// Check that they are all valid and detected as access tokens
|
|
||||||
for token in tokens {
|
|
||||||
assert_eq!(TokenType::check(&token).unwrap(), TokenType::AccessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same, but for refresh tokens
|
|
||||||
let tokens: HashSet<String> = (0..COUNT)
|
|
||||||
.map(|_| TokenType::RefreshToken.generate(&mut rng))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
assert_eq!(tokens.len(), COUNT, "All tokens are unique");
|
|
||||||
|
|
||||||
for token in tokens {
|
|
||||||
assert_eq!(TokenType::check(&token).unwrap(), TokenType::RefreshToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,5 +10,7 @@ chrono = "0.4.19"
|
|||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
serde = "1.0.131"
|
serde = "1.0.131"
|
||||||
url = { version = "2.2.2", features = ["serde"] }
|
url = { version = "2.2.2", features = ["serde"] }
|
||||||
|
crc = "2.1.0"
|
||||||
|
rand = "0.8.4"
|
||||||
|
|
||||||
oauth2-types = { path = "../oauth2-types" }
|
oauth2-types = { path = "../oauth2-types" }
|
||||||
|
@ -31,7 +31,7 @@ pub use self::{
|
|||||||
oauth2::{
|
oauth2::{
|
||||||
AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, Client, Pkce, Session,
|
AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, Client, Pkce, Session,
|
||||||
},
|
},
|
||||||
tokens::{AccessToken, RefreshToken},
|
tokens::{AccessToken, RefreshToken, TokenFormatError, TokenType},
|
||||||
traits::{StorageBackend, StorageBackendMarker},
|
traits::{StorageBackend, StorageBackendMarker},
|
||||||
users::{Authentication, BrowserSession, User},
|
users::{Authentication, BrowserSession, User},
|
||||||
};
|
};
|
||||||
|
@ -13,6 +13,10 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use chrono::{DateTime, Duration, Utc};
|
use chrono::{DateTime, Duration, Utc};
|
||||||
|
use crc::{Crc, CRC_32_ISO_HDLC};
|
||||||
|
use oauth2_types::requests::TokenTypeHint;
|
||||||
|
use rand::{distributions::Alphanumeric, Rng};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::traits::{StorageBackend, StorageBackendMarker};
|
use crate::traits::{StorageBackend, StorageBackendMarker};
|
||||||
|
|
||||||
@ -61,3 +65,200 @@ impl<S: StorageBackendMarker> From<RefreshToken<S>> for RefreshToken<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Type of token to generate or validate
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum TokenType {
|
||||||
|
/// An access token, used by Relying Parties to authenticate requests
|
||||||
|
AccessToken,
|
||||||
|
/// A refresh token, used by the refresh token grant
|
||||||
|
RefreshToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TokenType {
|
||||||
|
fn prefix(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
TokenType::AccessToken => "mat",
|
||||||
|
TokenType::RefreshToken => "mar",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_prefix(prefix: &str) -> Option<Self> {
|
||||||
|
match prefix {
|
||||||
|
"mat" => Some(TokenType::AccessToken),
|
||||||
|
"mar" => Some(TokenType::RefreshToken),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a token for the given type
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// extern crate rand;
|
||||||
|
///
|
||||||
|
/// use rand::thread_rng;
|
||||||
|
/// use mas_data_model::TokenType::{AccessToken, RefreshToken};
|
||||||
|
///
|
||||||
|
/// AccessToken.generate(thread_rng());
|
||||||
|
/// RefreshToken.generate(thread_rng());
|
||||||
|
/// ```
|
||||||
|
pub fn generate(self, rng: impl Rng) -> String {
|
||||||
|
let random_part: String = rng
|
||||||
|
.sample_iter(&Alphanumeric)
|
||||||
|
.take(30)
|
||||||
|
.map(char::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let base = format!("{}_{}", self.prefix(), random_part);
|
||||||
|
let crc = CRC.checksum(base.as_bytes());
|
||||||
|
let crc = base62_encode(crc);
|
||||||
|
format!("{}_{}", base, crc)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check the format of a token and determine its type
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use mas_data_model::TokenType;
|
||||||
|
///
|
||||||
|
/// assert_eq!(
|
||||||
|
/// TokenType::check("mat_kkLSacJDpek22jKWw4AcXG68b7U3W6_0Lg9yb"),
|
||||||
|
/// Ok(TokenType::AccessToken)
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// assert_eq!(
|
||||||
|
/// TokenType::check("mar_PkpplxPkfjsqvtdfUlYR1Afg2TpaHF_GaTQd2"),
|
||||||
|
/// Ok(TokenType::RefreshToken)
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
pub fn check(token: &str) -> Result<TokenType, TokenFormatError> {
|
||||||
|
let split: Vec<&str> = token.split('_').collect();
|
||||||
|
let [prefix, random_part, crc]: [&str; 3] = split
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| TokenFormatError::InvalidFormat)?;
|
||||||
|
|
||||||
|
if prefix.len() != 3 || random_part.len() != 30 || crc.len() != 6 {
|
||||||
|
return Err(TokenFormatError::InvalidFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
let token_type =
|
||||||
|
TokenType::match_prefix(prefix).ok_or_else(|| TokenFormatError::UnknownPrefix {
|
||||||
|
prefix: prefix.to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let base = format!("{}_{}", token_type.prefix(), random_part);
|
||||||
|
let expected_crc = CRC.checksum(base.as_bytes());
|
||||||
|
let expected_crc = base62_encode(expected_crc);
|
||||||
|
if crc != expected_crc {
|
||||||
|
return Err(TokenFormatError::InvalidCrc {
|
||||||
|
expected: expected_crc,
|
||||||
|
got: crc.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(token_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<TokenTypeHint> for TokenType {
|
||||||
|
fn eq(&self, other: &TokenTypeHint) -> bool {
|
||||||
|
matches!(
|
||||||
|
(self, other),
|
||||||
|
(TokenType::AccessToken, TokenTypeHint::AccessToken)
|
||||||
|
| (TokenType::RefreshToken, TokenTypeHint::RefreshToken)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const NUM: [u8; 62] = *b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||||
|
|
||||||
|
fn base62_encode(mut num: u32) -> String {
|
||||||
|
let mut res = String::with_capacity(6);
|
||||||
|
while num > 0 {
|
||||||
|
res.push(NUM[(num % 62) as usize] as char);
|
||||||
|
num /= 62;
|
||||||
|
}
|
||||||
|
|
||||||
|
format!("{:0>6}", res)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CRC: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
|
||||||
|
|
||||||
|
/// Invalid token
|
||||||
|
#[derive(Debug, Error, PartialEq)]
|
||||||
|
pub enum TokenFormatError {
|
||||||
|
/// Overall token format is invalid
|
||||||
|
#[error("invalid token format")]
|
||||||
|
InvalidFormat,
|
||||||
|
|
||||||
|
/// Token used an unknown prefix
|
||||||
|
#[error("unknown token prefix {prefix:?}")]
|
||||||
|
UnknownPrefix {
|
||||||
|
/// The prefix found in the token
|
||||||
|
prefix: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// The CRC checksum in the token is invalid
|
||||||
|
#[error("invalid crc {got:?}, expected {expected:?}")]
|
||||||
|
InvalidCrc {
|
||||||
|
/// The CRC hash expected to be found in the token
|
||||||
|
expected: String,
|
||||||
|
/// The CRC found in the token
|
||||||
|
got: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use rand::thread_rng;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_prefix_match() {
|
||||||
|
use TokenType::{AccessToken, RefreshToken};
|
||||||
|
assert_eq!(TokenType::match_prefix("mat"), Some(AccessToken));
|
||||||
|
assert_eq!(TokenType::match_prefix("mar"), Some(RefreshToken));
|
||||||
|
assert_eq!(TokenType::match_prefix("matt"), None);
|
||||||
|
assert_eq!(TokenType::match_prefix("marr"), None);
|
||||||
|
assert_eq!(TokenType::match_prefix("ma"), None);
|
||||||
|
assert_eq!(
|
||||||
|
TokenType::match_prefix(TokenType::AccessToken.prefix()),
|
||||||
|
Some(TokenType::AccessToken)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
TokenType::match_prefix(TokenType::RefreshToken.prefix()),
|
||||||
|
Some(TokenType::RefreshToken)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generate_and_check() {
|
||||||
|
const COUNT: usize = 500; // Generate 500 of each token type
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
// Generate many access tokens
|
||||||
|
let tokens: HashSet<String> = (0..COUNT)
|
||||||
|
.map(|_| TokenType::AccessToken.generate(&mut rng))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Check that they are all different
|
||||||
|
assert_eq!(tokens.len(), COUNT, "All tokens are unique");
|
||||||
|
|
||||||
|
// Check that they are all valid and detected as access tokens
|
||||||
|
for token in tokens {
|
||||||
|
assert_eq!(TokenType::check(&token).unwrap(), TokenType::AccessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same, but for refresh tokens
|
||||||
|
let tokens: HashSet<String> = (0..COUNT)
|
||||||
|
.map(|_| TokenType::RefreshToken.generate(&mut rng))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
assert_eq!(tokens.len(), COUNT, "All tokens are unique");
|
||||||
|
|
||||||
|
for token in tokens {
|
||||||
|
assert_eq!(TokenType::check(&token).unwrap(), TokenType::RefreshToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mas-core"
|
name = "mas-handlers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Quentin Gliech <quenting@element.io>"]
|
authors = ["Quentin Gliech <quenting@element.io>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
@ -10,14 +10,10 @@ dev = ["mas-static-files/dev", "mas-templates/dev"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Async runtime
|
# Async runtime
|
||||||
tokio = { version = "1.14.0", features = ["full"] }
|
tokio = { version = "1.14.0", features = ["macros"] }
|
||||||
async-trait = "0.1.52"
|
|
||||||
tokio-stream = "0.1.8"
|
|
||||||
futures-util = "0.3.18"
|
|
||||||
|
|
||||||
# Logging and tracing
|
# Logging and tracing
|
||||||
tracing = "0.1.29"
|
tracing = "0.1.29"
|
||||||
opentelemetry = "0.16.0"
|
|
||||||
|
|
||||||
# Error management
|
# Error management
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
@ -28,7 +24,7 @@ warp = "0.3.2"
|
|||||||
hyper = { version = "0.14.16", features = ["full"] }
|
hyper = { version = "0.14.16", features = ["full"] }
|
||||||
|
|
||||||
# Database access
|
# Database access
|
||||||
sqlx = { version = "0.5.9", features = ["runtime-tokio-rustls", "postgres", "migrate", "chrono", "offline"] }
|
sqlx = { version = "0.5.9", features = ["runtime-tokio-rustls", "postgres"] }
|
||||||
|
|
||||||
# Various structure (de)serialization
|
# Various structure (de)serialization
|
||||||
serde = { version = "1.0.131", features = ["derive"] }
|
serde = { version = "1.0.131", features = ["derive"] }
|
||||||
@ -38,36 +34,23 @@ serde_urlencoded = "0.7.0"
|
|||||||
|
|
||||||
# Password hashing
|
# Password hashing
|
||||||
argon2 = { version = "0.3.2", features = ["password-hash"] }
|
argon2 = { version = "0.3.2", features = ["password-hash"] }
|
||||||
password-hash = { version = "0.3.2", features = ["std"] }
|
|
||||||
|
|
||||||
# Crypto, hashing and signing stuff
|
# Crypto, hashing and signing stuff
|
||||||
rsa = "0.5.0"
|
|
||||||
k256 = "0.9.6"
|
|
||||||
pkcs8 = { version = "0.7.6", features = ["pem"] }
|
|
||||||
elliptic-curve = { version = "0.10.6", features = ["pem"] }
|
|
||||||
chacha20poly1305 = { version = "0.9.0", features = ["std"] }
|
|
||||||
sha2 = "0.10.0"
|
sha2 = "0.10.0"
|
||||||
crc = "2.1.0"
|
|
||||||
jwt-compact = { version = "0.5.0-beta.1", features = ["with_rsa", "k256"] }
|
jwt-compact = { version = "0.5.0-beta.1", features = ["with_rsa", "k256"] }
|
||||||
|
|
||||||
# Various data types and utilities
|
# Various data types and utilities
|
||||||
data-encoding = "2.3.2"
|
data-encoding = "2.3.2"
|
||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
url = { version = "2.2.2", features = ["serde"] }
|
url = { version = "2.2.2", features = ["serde"] }
|
||||||
itertools = "0.10.3"
|
|
||||||
mime = "0.3.16"
|
mime = "0.3.16"
|
||||||
rand = "0.8.4"
|
rand = "0.8.4"
|
||||||
bincode = "1.3.3"
|
|
||||||
headers = "0.3.5"
|
headers = "0.3.5"
|
||||||
cookie = "0.15.1"
|
|
||||||
once_cell = "1.8.0"
|
|
||||||
|
|
||||||
oauth2-types = { path = "../oauth2-types", features = ["sqlx_type"] }
|
oauth2-types = { path = "../oauth2-types" }
|
||||||
mas-config = { path = "../config" }
|
mas-config = { path = "../config" }
|
||||||
mas-data-model = { path = "../data-model" }
|
mas-data-model = { path = "../data-model" }
|
||||||
mas-templates = { path = "../templates" }
|
mas-templates = { path = "../templates" }
|
||||||
mas-static-files = { path = "../static-files" }
|
mas-static-files = { path = "../static-files" }
|
||||||
mas-storage = { path = "../storage" }
|
mas-storage = { path = "../storage" }
|
||||||
|
mas-warp-utils = { path = "../warp-utils" }
|
||||||
[dev-dependencies]
|
|
||||||
indoc = "1.0.3"
|
|
@ -13,13 +13,12 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use hyper::header::CONTENT_TYPE;
|
use hyper::header::CONTENT_TYPE;
|
||||||
|
use mas_warp_utils::{errors::WrapError, filters::database::connection};
|
||||||
use mime::TEXT_PLAIN;
|
use mime::TEXT_PLAIN;
|
||||||
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
||||||
use tracing::{info_span, Instrument};
|
use tracing::{info_span, Instrument};
|
||||||
use warp::{filters::BoxedFilter, reply::with_header, Filter, Rejection, Reply};
|
use warp::{filters::BoxedFilter, reply::with_header, Filter, Rejection, Reply};
|
||||||
|
|
||||||
use crate::{errors::WrapError, filters::database::connection};
|
|
||||||
|
|
||||||
pub fn filter(pool: &PgPool) -> BoxedFilter<(impl Reply,)> {
|
pub fn filter(pool: &PgPool) -> BoxedFilter<(impl Reply,)> {
|
||||||
warp::path!("health")
|
warp::path!("health")
|
||||||
.and(warp::get())
|
.and(warp::get())
|
@ -12,6 +12,14 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
#![deny(clippy::all)]
|
||||||
|
#![deny(rustdoc::broken_intra_doc_links)]
|
||||||
|
#![warn(clippy::pedantic)]
|
||||||
|
#![allow(clippy::module_name_repetitions)]
|
||||||
|
#![allow(clippy::missing_panics_doc)]
|
||||||
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
#![allow(clippy::implicit_hasher)]
|
||||||
#![allow(clippy::unused_async)] // Some warp filters need that
|
#![allow(clippy::unused_async)] // Some warp filters need that
|
||||||
|
|
||||||
use mas_config::RootConfig;
|
use mas_config::RootConfig;
|
||||||
@ -32,8 +40,7 @@ pub fn root(
|
|||||||
templates: &Templates,
|
templates: &Templates,
|
||||||
config: &RootConfig,
|
config: &RootConfig,
|
||||||
) -> BoxedFilter<(impl Reply,)> {
|
) -> BoxedFilter<(impl Reply,)> {
|
||||||
static_files(config.http.web_root.clone())
|
health(pool)
|
||||||
.or(health(pool))
|
|
||||||
.or(oauth2(pool, templates, &config.oauth2, &config.cookies))
|
.or(oauth2(pool, templates, &config.oauth2, &config.cookies))
|
||||||
.or(views(
|
.or(views(
|
||||||
pool,
|
pool,
|
||||||
@ -42,6 +49,7 @@ pub fn root(
|
|||||||
&config.csrf,
|
&config.csrf,
|
||||||
&config.cookies,
|
&config.cookies,
|
||||||
))
|
))
|
||||||
|
.or(static_files(config.http.web_root.clone()))
|
||||||
.with(warp::log(module_path!()))
|
.with(warp::log(module_path!()))
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
@ -23,7 +23,7 @@ use hyper::{
|
|||||||
use mas_config::{CookiesConfig, OAuth2ClientConfig, OAuth2Config};
|
use mas_config::{CookiesConfig, OAuth2ClientConfig, OAuth2Config};
|
||||||
use mas_data_model::{
|
use mas_data_model::{
|
||||||
Authentication, AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, BrowserSession,
|
Authentication, AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, BrowserSession,
|
||||||
Pkce, StorageBackend,
|
Pkce, StorageBackend, TokenType,
|
||||||
};
|
};
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
oauth2::{
|
oauth2::{
|
||||||
@ -36,6 +36,14 @@ use mas_storage::{
|
|||||||
PostgresqlBackend,
|
PostgresqlBackend,
|
||||||
};
|
};
|
||||||
use mas_templates::{FormPostContext, Templates};
|
use mas_templates::{FormPostContext, Templates};
|
||||||
|
use mas_warp_utils::{
|
||||||
|
errors::WrapError,
|
||||||
|
filters::{
|
||||||
|
database::transaction,
|
||||||
|
session::{optional_session, session},
|
||||||
|
with_templates,
|
||||||
|
},
|
||||||
|
};
|
||||||
use oauth2_types::{
|
use oauth2_types::{
|
||||||
errors::{
|
errors::{
|
||||||
ErrorResponse, InvalidGrant, InvalidRequest, LoginRequired, OAuth2Error,
|
ErrorResponse, InvalidGrant, InvalidRequest, LoginRequired, OAuth2Error,
|
||||||
@ -60,16 +68,7 @@ use warp::{
|
|||||||
Filter, Rejection, Reply,
|
Filter, Rejection, Reply,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::views::{LoginRequest, PostAuthAction, ReauthRequest};
|
||||||
errors::WrapError,
|
|
||||||
filters::{
|
|
||||||
database::transaction,
|
|
||||||
session::{optional_session, session},
|
|
||||||
with_templates,
|
|
||||||
},
|
|
||||||
handlers::views::{LoginRequest, PostAuthAction, ReauthRequest},
|
|
||||||
tokens::{AccessToken, RefreshToken},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct PartialParams {
|
struct PartialParams {
|
||||||
@ -523,8 +522,8 @@ async fn step(
|
|||||||
let (access_token_str, refresh_token_str) = {
|
let (access_token_str, refresh_token_str) = {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
(
|
(
|
||||||
AccessToken.generate(&mut rng),
|
TokenType::AccessToken.generate(&mut rng),
|
||||||
RefreshToken.generate(&mut rng),
|
TokenType::RefreshToken.generate(&mut rng),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
@ -16,6 +16,7 @@ use std::collections::HashSet;
|
|||||||
|
|
||||||
use hyper::Method;
|
use hyper::Method;
|
||||||
use mas_config::OAuth2Config;
|
use mas_config::OAuth2Config;
|
||||||
|
use mas_warp_utils::filters::cors::cors;
|
||||||
use oauth2_types::{
|
use oauth2_types::{
|
||||||
oidc::{Metadata, SigningAlgorithm},
|
oidc::{Metadata, SigningAlgorithm},
|
||||||
pkce::CodeChallengeMethod,
|
pkce::CodeChallengeMethod,
|
||||||
@ -23,8 +24,6 @@ use oauth2_types::{
|
|||||||
};
|
};
|
||||||
use warp::{Filter, Rejection, Reply};
|
use warp::{Filter, Rejection, Reply};
|
||||||
|
|
||||||
use crate::filters::cors::cors;
|
|
||||||
|
|
||||||
pub(super) fn filter(
|
pub(super) fn filter(
|
||||||
config: &OAuth2Config,
|
config: &OAuth2Config,
|
||||||
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone + Send + Sync + 'static {
|
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone + Send + Sync + 'static {
|
@ -14,9 +14,14 @@
|
|||||||
|
|
||||||
use hyper::Method;
|
use hyper::Method;
|
||||||
use mas_config::{OAuth2ClientConfig, OAuth2Config};
|
use mas_config::{OAuth2ClientConfig, OAuth2Config};
|
||||||
|
use mas_data_model::TokenType;
|
||||||
use mas_storage::oauth2::{
|
use mas_storage::oauth2::{
|
||||||
access_token::lookup_active_access_token, refresh_token::lookup_active_refresh_token,
|
access_token::lookup_active_access_token, refresh_token::lookup_active_refresh_token,
|
||||||
};
|
};
|
||||||
|
use mas_warp_utils::{
|
||||||
|
errors::WrapError,
|
||||||
|
filters::{client::client_authentication, cors::cors, database::connection},
|
||||||
|
};
|
||||||
use oauth2_types::requests::{
|
use oauth2_types::requests::{
|
||||||
ClientAuthenticationMethod, IntrospectionRequest, IntrospectionResponse, TokenTypeHint,
|
ClientAuthenticationMethod, IntrospectionRequest, IntrospectionResponse, TokenTypeHint,
|
||||||
};
|
};
|
||||||
@ -24,12 +29,6 @@ use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
|||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
use warp::{Filter, Rejection, Reply};
|
use warp::{Filter, Rejection, Reply};
|
||||||
|
|
||||||
use crate::{
|
|
||||||
errors::WrapError,
|
|
||||||
filters::{client::client_authentication, cors::cors, database::connection},
|
|
||||||
tokens::{self, TokenType},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn filter(
|
pub fn filter(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
oauth2_config: &OAuth2Config,
|
oauth2_config: &OAuth2Config,
|
||||||
@ -88,7 +87,7 @@ async fn introspect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let reply = match token_type {
|
let reply = match token_type {
|
||||||
tokens::TokenType::AccessToken => {
|
TokenType::AccessToken => {
|
||||||
let (token, session) = lookup_active_access_token(&mut conn, token)
|
let (token, session) = lookup_active_access_token(&mut conn, token)
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.wrap_error()?;
|
||||||
@ -109,7 +108,7 @@ async fn introspect(
|
|||||||
jti: None,
|
jti: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tokens::TokenType::RefreshToken => {
|
TokenType::RefreshToken => {
|
||||||
let (token, session) = lookup_active_refresh_token(&mut conn, token)
|
let (token, session) = lookup_active_refresh_token(&mut conn, token)
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.wrap_error()?;
|
@ -14,10 +14,9 @@
|
|||||||
|
|
||||||
use hyper::Method;
|
use hyper::Method;
|
||||||
use mas_config::OAuth2Config;
|
use mas_config::OAuth2Config;
|
||||||
|
use mas_warp_utils::filters::cors::cors;
|
||||||
use warp::{Filter, Rejection, Reply};
|
use warp::{Filter, Rejection, Reply};
|
||||||
|
|
||||||
use crate::filters::cors::cors;
|
|
||||||
|
|
||||||
pub(super) fn filter(
|
pub(super) fn filter(
|
||||||
config: &OAuth2Config,
|
config: &OAuth2Config,
|
||||||
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone + Send + Sync + 'static {
|
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone + Send + Sync + 'static {
|
@ -19,7 +19,7 @@ use headers::{CacheControl, Pragma};
|
|||||||
use hyper::{Method, StatusCode};
|
use hyper::{Method, StatusCode};
|
||||||
use jwt_compact::{Claims, Header, TimeOptions};
|
use jwt_compact::{Claims, Header, TimeOptions};
|
||||||
use mas_config::{KeySet, OAuth2ClientConfig, OAuth2Config};
|
use mas_config::{KeySet, OAuth2ClientConfig, OAuth2Config};
|
||||||
use mas_data_model::AuthorizationGrantStage;
|
use mas_data_model::{AuthorizationGrantStage, TokenType};
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
oauth2::{
|
oauth2::{
|
||||||
access_token::{add_access_token, revoke_access_token},
|
access_token::{add_access_token, revoke_access_token},
|
||||||
@ -28,6 +28,11 @@ use mas_storage::{
|
|||||||
},
|
},
|
||||||
DatabaseInconsistencyError,
|
DatabaseInconsistencyError,
|
||||||
};
|
};
|
||||||
|
use mas_warp_utils::{
|
||||||
|
errors::WrapError,
|
||||||
|
filters::{client::client_authentication, cors::cors, database::connection, with_keys},
|
||||||
|
reply::with_typed_header,
|
||||||
|
};
|
||||||
use oauth2_types::{
|
use oauth2_types::{
|
||||||
errors::{InvalidGrant, InvalidRequest, OAuth2Error, OAuth2ErrorCode, UnauthorizedClient},
|
errors::{InvalidGrant, InvalidRequest, OAuth2Error, OAuth2ErrorCode, UnauthorizedClient},
|
||||||
requests::{
|
requests::{
|
||||||
@ -49,13 +54,6 @@ use warp::{
|
|||||||
Filter, Rejection, Reply,
|
Filter, Rejection, Reply,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
|
||||||
errors::WrapError,
|
|
||||||
filters::{client::client_authentication, cors::cors, database::connection, with_keys},
|
|
||||||
reply::with_typed_header,
|
|
||||||
tokens::{AccessToken, RefreshToken},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
@ -233,8 +231,8 @@ async fn authorization_code_grant(
|
|||||||
let (access_token_str, refresh_token_str) = {
|
let (access_token_str, refresh_token_str) = {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
(
|
(
|
||||||
AccessToken.generate(&mut rng),
|
TokenType::AccessToken.generate(&mut rng),
|
||||||
RefreshToken.generate(&mut rng),
|
TokenType::RefreshToken.generate(&mut rng),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -308,8 +306,8 @@ async fn refresh_token_grant(
|
|||||||
let (access_token_str, refresh_token_str) = {
|
let (access_token_str, refresh_token_str) = {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
(
|
(
|
||||||
AccessToken.generate(&mut rng),
|
TokenType::AccessToken.generate(&mut rng),
|
||||||
RefreshToken.generate(&mut rng),
|
TokenType::RefreshToken.generate(&mut rng),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
@ -16,14 +16,13 @@ use hyper::Method;
|
|||||||
use mas_config::OAuth2Config;
|
use mas_config::OAuth2Config;
|
||||||
use mas_data_model::{AccessToken, Session};
|
use mas_data_model::{AccessToken, Session};
|
||||||
use mas_storage::PostgresqlBackend;
|
use mas_storage::PostgresqlBackend;
|
||||||
use serde::Serialize;
|
use mas_warp_utils::filters::{
|
||||||
use sqlx::PgPool;
|
|
||||||
use warp::{Filter, Rejection, Reply};
|
|
||||||
|
|
||||||
use crate::filters::{
|
|
||||||
authenticate::{authentication, recover_unauthorized},
|
authenticate::{authentication, recover_unauthorized},
|
||||||
cors::cors,
|
cors::cors,
|
||||||
};
|
};
|
||||||
|
use serde::Serialize;
|
||||||
|
use sqlx::PgPool;
|
||||||
|
use warp::{Filter, Rejection, Reply};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct UserInfo {
|
struct UserInfo {
|
@ -20,11 +20,7 @@ use mas_storage::{
|
|||||||
PostgresqlBackend,
|
PostgresqlBackend,
|
||||||
};
|
};
|
||||||
use mas_templates::{AccountContext, TemplateContext, Templates};
|
use mas_templates::{AccountContext, TemplateContext, Templates};
|
||||||
use serde::Deserialize;
|
use mas_warp_utils::{
|
||||||
use sqlx::{pool::PoolConnection, PgExecutor, PgPool, Postgres, Transaction};
|
|
||||||
use warp::{reply::html, Filter, Rejection, Reply};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
errors::WrapError,
|
errors::WrapError,
|
||||||
filters::{
|
filters::{
|
||||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
||||||
@ -34,6 +30,9 @@ use crate::{
|
|||||||
with_templates, CsrfToken,
|
with_templates, CsrfToken,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use sqlx::{pool::PoolConnection, PgExecutor, PgPool, Postgres, Transaction};
|
||||||
|
use warp::{reply::html, Filter, Rejection, Reply};
|
||||||
|
|
||||||
pub(super) fn filter(
|
pub(super) fn filter(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
@ -16,16 +16,15 @@ use mas_config::{CookiesConfig, CsrfConfig, OAuth2Config};
|
|||||||
use mas_data_model::BrowserSession;
|
use mas_data_model::BrowserSession;
|
||||||
use mas_storage::PostgresqlBackend;
|
use mas_storage::PostgresqlBackend;
|
||||||
use mas_templates::{IndexContext, TemplateContext, Templates};
|
use mas_templates::{IndexContext, TemplateContext, Templates};
|
||||||
use sqlx::PgPool;
|
use mas_warp_utils::filters::{
|
||||||
use url::Url;
|
|
||||||
use warp::{reply::html, Filter, Rejection, Reply};
|
|
||||||
|
|
||||||
use crate::filters::{
|
|
||||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
||||||
csrf::updated_csrf_token,
|
csrf::updated_csrf_token,
|
||||||
session::optional_session,
|
session::optional_session,
|
||||||
with_templates, CsrfToken,
|
with_templates, CsrfToken,
|
||||||
};
|
};
|
||||||
|
use sqlx::PgPool;
|
||||||
|
use url::Url;
|
||||||
|
use warp::{reply::html, Filter, Rejection, Reply};
|
||||||
|
|
||||||
pub(super) fn filter(
|
pub(super) fn filter(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
@ -17,12 +17,7 @@ use mas_config::{CookiesConfig, CsrfConfig};
|
|||||||
use mas_data_model::{errors::WrapFormError, BrowserSession, StorageBackend};
|
use mas_data_model::{errors::WrapFormError, BrowserSession, StorageBackend};
|
||||||
use mas_storage::{user::login, PostgresqlBackend};
|
use mas_storage::{user::login, PostgresqlBackend};
|
||||||
use mas_templates::{LoginContext, LoginFormField, TemplateContext, Templates};
|
use mas_templates::{LoginContext, LoginFormField, TemplateContext, Templates};
|
||||||
use serde::Deserialize;
|
use mas_warp_utils::{
|
||||||
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
|
||||||
use warp::{reply::html, Filter, Rejection, Reply};
|
|
||||||
|
|
||||||
use super::{shared::PostAuthAction, RegisterRequest};
|
|
||||||
use crate::{
|
|
||||||
errors::WrapError,
|
errors::WrapError,
|
||||||
filters::{
|
filters::{
|
||||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
||||||
@ -32,6 +27,11 @@ use crate::{
|
|||||||
with_templates, CsrfToken,
|
with_templates, CsrfToken,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
||||||
|
use warp::{reply::html, Filter, Rejection, Reply};
|
||||||
|
|
||||||
|
use super::{shared::PostAuthAction, RegisterRequest};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(bound(deserialize = "S::AuthorizationGrantData: std::str::FromStr,
|
#[serde(bound(deserialize = "S::AuthorizationGrantData: std::str::FromStr,
|
@ -15,13 +15,12 @@
|
|||||||
use mas_config::CookiesConfig;
|
use mas_config::CookiesConfig;
|
||||||
use mas_data_model::BrowserSession;
|
use mas_data_model::BrowserSession;
|
||||||
use mas_storage::{user::end_session, PostgresqlBackend};
|
use mas_storage::{user::end_session, PostgresqlBackend};
|
||||||
use sqlx::{PgPool, Postgres, Transaction};
|
use mas_warp_utils::{
|
||||||
use warp::{hyper::Uri, Filter, Rejection, Reply};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
errors::WrapError,
|
errors::WrapError,
|
||||||
filters::{csrf::protected_form, database::transaction, session::session},
|
filters::{csrf::protected_form, database::transaction, session::session},
|
||||||
};
|
};
|
||||||
|
use sqlx::{PgPool, Postgres, Transaction};
|
||||||
|
use warp::{hyper::Uri, Filter, Rejection, Reply};
|
||||||
|
|
||||||
pub(super) fn filter(
|
pub(super) fn filter(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
@ -17,12 +17,7 @@ use mas_config::{CookiesConfig, CsrfConfig};
|
|||||||
use mas_data_model::{BrowserSession, StorageBackend};
|
use mas_data_model::{BrowserSession, StorageBackend};
|
||||||
use mas_storage::{user::authenticate_session, PostgresqlBackend};
|
use mas_storage::{user::authenticate_session, PostgresqlBackend};
|
||||||
use mas_templates::{ReauthContext, TemplateContext, Templates};
|
use mas_templates::{ReauthContext, TemplateContext, Templates};
|
||||||
use serde::Deserialize;
|
use mas_warp_utils::{
|
||||||
use sqlx::{pool::PoolConnection, PgPool, Postgres, Transaction};
|
|
||||||
use warp::{hyper::Uri, reply::html, Filter, Rejection, Reply};
|
|
||||||
|
|
||||||
use super::PostAuthAction;
|
|
||||||
use crate::{
|
|
||||||
errors::WrapError,
|
errors::WrapError,
|
||||||
filters::{
|
filters::{
|
||||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
||||||
@ -32,6 +27,11 @@ use crate::{
|
|||||||
with_templates, CsrfToken,
|
with_templates, CsrfToken,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use sqlx::{pool::PoolConnection, PgPool, Postgres, Transaction};
|
||||||
|
use warp::{hyper::Uri, reply::html, Filter, Rejection, Reply};
|
||||||
|
|
||||||
|
use super::PostAuthAction;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(bound(deserialize = "S::AuthorizationGrantData: std::str::FromStr,
|
#[serde(bound(deserialize = "S::AuthorizationGrantData: std::str::FromStr,
|
@ -21,12 +21,7 @@ use mas_storage::{
|
|||||||
PostgresqlBackend,
|
PostgresqlBackend,
|
||||||
};
|
};
|
||||||
use mas_templates::{RegisterContext, TemplateContext, Templates};
|
use mas_templates::{RegisterContext, TemplateContext, Templates};
|
||||||
use serde::Deserialize;
|
use mas_warp_utils::{
|
||||||
use sqlx::{pool::PoolConnection, PgPool, Postgres, Transaction};
|
|
||||||
use warp::{reply::html, Filter, Rejection, Reply};
|
|
||||||
|
|
||||||
use super::{LoginRequest, PostAuthAction};
|
|
||||||
use crate::{
|
|
||||||
errors::WrapError,
|
errors::WrapError,
|
||||||
filters::{
|
filters::{
|
||||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
||||||
@ -36,6 +31,11 @@ use crate::{
|
|||||||
with_templates, CsrfToken,
|
with_templates, CsrfToken,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use sqlx::{pool::PoolConnection, PgPool, Postgres, Transaction};
|
||||||
|
use warp::{reply::html, Filter, Rejection, Reply};
|
||||||
|
|
||||||
|
use super::{LoginRequest, PostAuthAction};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(bound(deserialize = "S::AuthorizationGrantData: std::str::FromStr,
|
#[serde(bound(deserialize = "S::AuthorizationGrantData: std::str::FromStr,
|
@ -14,12 +14,8 @@ url = { version = "2.2.2", features = ["serde"] }
|
|||||||
parse-display = "0.5.3"
|
parse-display = "0.5.3"
|
||||||
indoc = "1.0.3"
|
indoc = "1.0.3"
|
||||||
serde_with = { version = "1.11.0", features = ["chrono"] }
|
serde_with = { version = "1.11.0", features = ["chrono"] }
|
||||||
sqlx = { version = "0.5.9", default-features = false, optional = true }
|
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
sha2 = "0.10.0"
|
sha2 = "0.10.0"
|
||||||
data-encoding = "2.3.2"
|
data-encoding = "2.3.2"
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
itertools = "0.10.3"
|
itertools = "0.10.3"
|
||||||
|
|
||||||
[features]
|
|
||||||
sqlx_type = ["sqlx"]
|
|
||||||
|
@ -21,5 +21,5 @@ password-hash = { version = "0.3.2", features = ["std"] }
|
|||||||
rand = "0.8.4"
|
rand = "0.8.4"
|
||||||
url = { version = "2.2.2", features = ["serde"] }
|
url = { version = "2.2.2", features = ["serde"] }
|
||||||
|
|
||||||
oauth2-types = { path = "../oauth2-types", features = ["sqlx_type"] }
|
oauth2-types = { path = "../oauth2-types" }
|
||||||
mas-data-model = { path = "../data-model" }
|
mas-data-model = { path = "../data-model" }
|
||||||
|
16
crates/tasks/Cargo.toml
Normal file
16
crates/tasks/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "mas-tasks"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Quentin Gliech <quenting@element.io>"]
|
||||||
|
edition = "2021"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1.14.0" }
|
||||||
|
async-trait = "0.1.52"
|
||||||
|
tokio-stream = "0.1.8"
|
||||||
|
futures-util = "0.3.18"
|
||||||
|
tracing = "0.1.29"
|
||||||
|
sqlx = { version = "0.5.9", features = ["runtime-tokio-rustls", "postgres"] }
|
||||||
|
|
||||||
|
mas-storage = { path = "../storage" }
|
37
crates/warp-utils/Cargo.toml
Normal file
37
crates/warp-utils/Cargo.toml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
[package]
|
||||||
|
name = "mas-warp-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Quentin Gliech <quenting@element.io>"]
|
||||||
|
edition = "2021"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1.14.0", features = ["macros"] }
|
||||||
|
headers = "0.3.5"
|
||||||
|
cookie = "0.15.1"
|
||||||
|
warp = "0.3.2"
|
||||||
|
hyper = { version = "0.14.16", features = ["full"] }
|
||||||
|
thiserror = "1.0.30"
|
||||||
|
anyhow = "1.0.51"
|
||||||
|
sqlx = { version = "0.5.9", features = ["runtime-tokio-rustls", "postgres"] }
|
||||||
|
jwt-compact = { version = "0.5.0-beta.1", features = ["with_rsa", "k256"] }
|
||||||
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
|
serde = { version = "1.0.131", features = ["derive"] }
|
||||||
|
serde_with = { version = "1.11.0", features = ["hex", "chrono"] }
|
||||||
|
serde_json = "1.0.72"
|
||||||
|
serde_urlencoded = "0.7.0"
|
||||||
|
data-encoding = "2.3.2"
|
||||||
|
chacha20poly1305 = { version = "0.9.0", features = ["std"] }
|
||||||
|
once_cell = "1.8.0"
|
||||||
|
tracing = "0.1.29"
|
||||||
|
opentelemetry = "0.16.0"
|
||||||
|
rand = "0.8.4"
|
||||||
|
mime = "0.3.16"
|
||||||
|
bincode = "1.3.3"
|
||||||
|
crc = "2.1.0"
|
||||||
|
|
||||||
|
oauth2-types = { path = "../oauth2-types" }
|
||||||
|
mas-config = { path = "../config" }
|
||||||
|
mas-templates = { path = "../templates" }
|
||||||
|
mas-data-model = { path = "../data-model" }
|
||||||
|
mas-storage = { path = "../storage" }
|
@ -19,11 +19,11 @@ pub(crate) struct WrappedError(anyhow::Error);
|
|||||||
|
|
||||||
impl warp::reject::Reject for WrappedError {}
|
impl warp::reject::Reject for WrappedError {}
|
||||||
|
|
||||||
pub(crate) fn wrapped_error<T: Into<anyhow::Error>>(e: T) -> impl Reject {
|
pub fn wrapped_error<T: Into<anyhow::Error>>(e: T) -> impl Reject {
|
||||||
WrappedError(e.into())
|
WrappedError(e.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait WrapError<T> {
|
pub trait WrapError<T> {
|
||||||
fn wrap_error(self) -> Result<T, Rejection>;
|
fn wrap_error(self) -> Result<T, Rejection>;
|
||||||
}
|
}
|
||||||
|
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
use headers::{authorization::Bearer, Authorization};
|
use headers::{authorization::Bearer, Authorization};
|
||||||
use hyper::StatusCode;
|
use hyper::StatusCode;
|
||||||
use mas_data_model::{AccessToken, Session};
|
use mas_data_model::{AccessToken, Session, TokenFormatError, TokenType};
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
oauth2::access_token::{lookup_active_access_token, AccessTokenLookupError},
|
oauth2::access_token::{lookup_active_access_token, AccessTokenLookupError},
|
||||||
PostgresqlBackend,
|
PostgresqlBackend,
|
||||||
@ -33,10 +33,7 @@ use super::{
|
|||||||
database::connection,
|
database::connection,
|
||||||
headers::{typed_header, InvalidTypedHeader},
|
headers::{typed_header, InvalidTypedHeader},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::errors::wrapped_error;
|
||||||
errors::wrapped_error,
|
|
||||||
tokens::{TokenFormatError, TokenType},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Bearer token authentication failed
|
/// Bearer token authentication failed
|
||||||
///
|
///
|
@ -61,7 +61,7 @@ pub fn with_keys(
|
|||||||
///
|
///
|
||||||
/// use warp::{filters::header::header, reject::MissingHeader, Filter};
|
/// use warp::{filters::header::header, reject::MissingHeader, Filter};
|
||||||
///
|
///
|
||||||
/// use mas_core::filters::none_on_error;
|
/// use mas_warp_utils::filters::none_on_error;
|
||||||
///
|
///
|
||||||
/// header("Content-Length")
|
/// header("Content-Length")
|
||||||
/// .map(Some)
|
/// .map(Some)
|
@ -12,20 +12,6 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
#![deny(clippy::all)]
|
|
||||||
#![deny(rustdoc::broken_intra_doc_links)]
|
|
||||||
#![warn(clippy::pedantic)]
|
|
||||||
#![allow(clippy::module_name_repetitions)]
|
|
||||||
#![allow(clippy::missing_panics_doc)]
|
|
||||||
#![allow(clippy::missing_errors_doc)]
|
|
||||||
#![allow(clippy::implicit_hasher)]
|
|
||||||
|
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod filters;
|
pub mod filters;
|
||||||
pub mod handlers;
|
|
||||||
pub mod reply;
|
pub mod reply;
|
||||||
pub mod tasks;
|
|
||||||
pub mod tokens;
|
|
||||||
|
|
||||||
pub use self::filters::cors::set_propagator;
|
|
@ -19,7 +19,7 @@
|
|||||||
//! extern crate warp;
|
//! extern crate warp;
|
||||||
//!
|
//!
|
||||||
//! use warp::Reply;
|
//! use warp::Reply;
|
||||||
//! use mas_core::reply::with_typed_header;
|
//! use mas_warp_utils::reply::with_typed_header;
|
||||||
//!
|
//!
|
||||||
//! let reply = r#"{"hello": "world"}"#;
|
//! let reply = r#"{"hello": "world"}"#;
|
||||||
//! let reply = with_typed_header(headers::ContentType::json(), reply);;
|
//! let reply = with_typed_header(headers::ContentType::json(), reply);;
|
Reference in New Issue
Block a user