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",
|
||||
"indoc",
|
||||
"mas-config",
|
||||
"mas-core",
|
||||
"mas-handlers",
|
||||
"mas-storage",
|
||||
"mas-tasks",
|
||||
"mas-templates",
|
||||
"mas-warp-utils",
|
||||
"opentelemetry",
|
||||
"opentelemetry-http",
|
||||
"opentelemetry-jaeger",
|
||||
@ -1562,39 +1564,38 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[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"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
"async-trait",
|
||||
"bincode",
|
||||
"chacha20poly1305",
|
||||
"chrono",
|
||||
"cookie",
|
||||
"crc",
|
||||
"data-encoding",
|
||||
"elliptic-curve",
|
||||
"futures-util",
|
||||
"headers",
|
||||
"hyper",
|
||||
"indoc",
|
||||
"itertools",
|
||||
"jwt-compact",
|
||||
"k256",
|
||||
"mas-config",
|
||||
"mas-data-model",
|
||||
"mas-static-files",
|
||||
"mas-storage",
|
||||
"mas-templates",
|
||||
"mas-warp-utils",
|
||||
"mime",
|
||||
"oauth2-types",
|
||||
"once_cell",
|
||||
"opentelemetry",
|
||||
"password-hash",
|
||||
"pkcs8",
|
||||
"rand",
|
||||
"rsa",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
@ -1603,23 +1604,11 @@ dependencies = [
|
||||
"sqlx",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"url",
|
||||
"warp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mas-data-model"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"oauth2-types",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mas-static-files"
|
||||
version = "0.1.0"
|
||||
@ -1650,6 +1639,19 @@ dependencies = [
|
||||
"warp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mas-tasks"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures-util",
|
||||
"mas-storage",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mas-templates"
|
||||
version = "0.1.0"
|
||||
@ -1669,6 +1671,40 @@ dependencies = [
|
||||
"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]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
@ -1895,7 +1931,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"sha2 0.10.0",
|
||||
"sqlx",
|
||||
"thiserror",
|
||||
"url",
|
||||
]
|
||||
|
@ -20,6 +20,7 @@ warp = "0.3.2"
|
||||
url = "2.2.2"
|
||||
argon2 = { version = "0.3.2", features = ["password-hash"] }
|
||||
reqwest = { version = "0.11.7", features = ["rustls-tls"], default-features = false, optional = true }
|
||||
watchman_client = "0.7.1"
|
||||
|
||||
tracing = "0.1.29"
|
||||
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 }
|
||||
|
||||
mas-config = { path = "../config" }
|
||||
mas-core = { path = "../core" }
|
||||
mas-handlers = { path = "../handlers" }
|
||||
mas-templates = { path = "../templates" }
|
||||
mas-storage = { path = "../storage" }
|
||||
watchman_client = "0.7.1"
|
||||
mas-tasks = { path = "../tasks" }
|
||||
mas-warp-utils = { path = "../warp-utils" }
|
||||
|
||||
[dev-dependencies]
|
||||
indoc = "1.0.3"
|
||||
|
||||
[features]
|
||||
default = ["otlp", "jaeger", "zipkin"]
|
||||
dev = ["mas-templates/dev", "mas-core/dev"]
|
||||
dev = ["mas-templates/dev", "mas-handlers/dev"]
|
||||
# Enable OpenTelemetry OTLP exporter. Requires "protoc"
|
||||
otlp = ["opentelemetry-otlp"]
|
||||
# Enable OpenTelemetry Jaeger exporter and propagator.
|
||||
|
@ -22,8 +22,8 @@ use clap::Parser;
|
||||
use futures::{future::TryFutureExt, stream::TryStreamExt};
|
||||
use hyper::{header, Server, Version};
|
||||
use mas_config::RootConfig;
|
||||
use mas_core::tasks::{self, TaskQueue};
|
||||
use mas_storage::MIGRATOR;
|
||||
use mas_tasks::TaskQueue;
|
||||
use mas_templates::Templates;
|
||||
use opentelemetry_http::HeaderExtractor;
|
||||
use tower::{make::Shared, ServiceBuilder};
|
||||
@ -233,7 +233,7 @@ impl ServerCommand {
|
||||
|
||||
info!("Starting task scheduler");
|
||||
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();
|
||||
|
||||
// Load and compile the templates
|
||||
@ -254,7 +254,7 @@ impl ServerCommand {
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
|
@ -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
|
||||
// CORS-protected requests.
|
||||
mas_core::set_propagator(&propagator);
|
||||
mas_warp_utils::filters::cors::set_propagator(&propagator);
|
||||
global::set_text_map_propagator(propagator);
|
||||
|
||||
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"
|
||||
serde = "1.0.131"
|
||||
url = { version = "2.2.2", features = ["serde"] }
|
||||
crc = "2.1.0"
|
||||
rand = "0.8.4"
|
||||
|
||||
oauth2-types = { path = "../oauth2-types" }
|
||||
|
@ -31,7 +31,7 @@ pub use self::{
|
||||
oauth2::{
|
||||
AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, Client, Pkce, Session,
|
||||
},
|
||||
tokens::{AccessToken, RefreshToken},
|
||||
tokens::{AccessToken, RefreshToken, TokenFormatError, TokenType},
|
||||
traits::{StorageBackend, StorageBackendMarker},
|
||||
users::{Authentication, BrowserSession, User},
|
||||
};
|
||||
|
@ -13,6 +13,10 @@
|
||||
// limitations under the License.
|
||||
|
||||
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};
|
||||
|
||||
@ -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]
|
||||
name = "mas-core"
|
||||
name = "mas-handlers"
|
||||
version = "0.1.0"
|
||||
authors = ["Quentin Gliech <quenting@element.io>"]
|
||||
edition = "2021"
|
||||
@ -10,14 +10,10 @@ dev = ["mas-static-files/dev", "mas-templates/dev"]
|
||||
|
||||
[dependencies]
|
||||
# Async runtime
|
||||
tokio = { version = "1.14.0", features = ["full"] }
|
||||
async-trait = "0.1.52"
|
||||
tokio-stream = "0.1.8"
|
||||
futures-util = "0.3.18"
|
||||
tokio = { version = "1.14.0", features = ["macros"] }
|
||||
|
||||
# Logging and tracing
|
||||
tracing = "0.1.29"
|
||||
opentelemetry = "0.16.0"
|
||||
|
||||
# Error management
|
||||
thiserror = "1.0.30"
|
||||
@ -28,7 +24,7 @@ warp = "0.3.2"
|
||||
hyper = { version = "0.14.16", features = ["full"] }
|
||||
|
||||
# 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
|
||||
serde = { version = "1.0.131", features = ["derive"] }
|
||||
@ -38,36 +34,23 @@ serde_urlencoded = "0.7.0"
|
||||
|
||||
# Password hashing
|
||||
argon2 = { version = "0.3.2", features = ["password-hash"] }
|
||||
password-hash = { version = "0.3.2", features = ["std"] }
|
||||
|
||||
# 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"
|
||||
crc = "2.1.0"
|
||||
jwt-compact = { version = "0.5.0-beta.1", features = ["with_rsa", "k256"] }
|
||||
|
||||
# Various data types and utilities
|
||||
data-encoding = "2.3.2"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
url = { version = "2.2.2", features = ["serde"] }
|
||||
itertools = "0.10.3"
|
||||
mime = "0.3.16"
|
||||
rand = "0.8.4"
|
||||
bincode = "1.3.3"
|
||||
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-data-model = { path = "../data-model" }
|
||||
mas-templates = { path = "../templates" }
|
||||
mas-static-files = { path = "../static-files" }
|
||||
mas-storage = { path = "../storage" }
|
||||
|
||||
[dev-dependencies]
|
||||
indoc = "1.0.3"
|
||||
mas-warp-utils = { path = "../warp-utils" }
|
@ -13,13 +13,12 @@
|
||||
// limitations under the License.
|
||||
|
||||
use hyper::header::CONTENT_TYPE;
|
||||
use mas_warp_utils::{errors::WrapError, filters::database::connection};
|
||||
use mime::TEXT_PLAIN;
|
||||
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
||||
use tracing::{info_span, Instrument};
|
||||
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,)> {
|
||||
warp::path!("health")
|
||||
.and(warp::get())
|
@ -12,6 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// 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
|
||||
|
||||
use mas_config::RootConfig;
|
||||
@ -32,8 +40,7 @@ pub fn root(
|
||||
templates: &Templates,
|
||||
config: &RootConfig,
|
||||
) -> BoxedFilter<(impl Reply,)> {
|
||||
static_files(config.http.web_root.clone())
|
||||
.or(health(pool))
|
||||
health(pool)
|
||||
.or(oauth2(pool, templates, &config.oauth2, &config.cookies))
|
||||
.or(views(
|
||||
pool,
|
||||
@ -42,6 +49,7 @@ pub fn root(
|
||||
&config.csrf,
|
||||
&config.cookies,
|
||||
))
|
||||
.or(static_files(config.http.web_root.clone()))
|
||||
.with(warp::log(module_path!()))
|
||||
.boxed()
|
||||
}
|
@ -23,7 +23,7 @@ use hyper::{
|
||||
use mas_config::{CookiesConfig, OAuth2ClientConfig, OAuth2Config};
|
||||
use mas_data_model::{
|
||||
Authentication, AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, BrowserSession,
|
||||
Pkce, StorageBackend,
|
||||
Pkce, StorageBackend, TokenType,
|
||||
};
|
||||
use mas_storage::{
|
||||
oauth2::{
|
||||
@ -36,6 +36,14 @@ use mas_storage::{
|
||||
PostgresqlBackend,
|
||||
};
|
||||
use mas_templates::{FormPostContext, Templates};
|
||||
use mas_warp_utils::{
|
||||
errors::WrapError,
|
||||
filters::{
|
||||
database::transaction,
|
||||
session::{optional_session, session},
|
||||
with_templates,
|
||||
},
|
||||
};
|
||||
use oauth2_types::{
|
||||
errors::{
|
||||
ErrorResponse, InvalidGrant, InvalidRequest, LoginRequired, OAuth2Error,
|
||||
@ -60,16 +68,7 @@ use warp::{
|
||||
Filter, Rejection, Reply,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
errors::WrapError,
|
||||
filters::{
|
||||
database::transaction,
|
||||
session::{optional_session, session},
|
||||
with_templates,
|
||||
},
|
||||
handlers::views::{LoginRequest, PostAuthAction, ReauthRequest},
|
||||
tokens::{AccessToken, RefreshToken},
|
||||
};
|
||||
use crate::views::{LoginRequest, PostAuthAction, ReauthRequest};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PartialParams {
|
||||
@ -523,8 +522,8 @@ async fn step(
|
||||
let (access_token_str, refresh_token_str) = {
|
||||
let mut rng = thread_rng();
|
||||
(
|
||||
AccessToken.generate(&mut rng),
|
||||
RefreshToken.generate(&mut rng),
|
||||
TokenType::AccessToken.generate(&mut rng),
|
||||
TokenType::RefreshToken.generate(&mut rng),
|
||||
)
|
||||
};
|
||||
|
@ -16,6 +16,7 @@ use std::collections::HashSet;
|
||||
|
||||
use hyper::Method;
|
||||
use mas_config::OAuth2Config;
|
||||
use mas_warp_utils::filters::cors::cors;
|
||||
use oauth2_types::{
|
||||
oidc::{Metadata, SigningAlgorithm},
|
||||
pkce::CodeChallengeMethod,
|
||||
@ -23,8 +24,6 @@ use oauth2_types::{
|
||||
};
|
||||
use warp::{Filter, Rejection, Reply};
|
||||
|
||||
use crate::filters::cors::cors;
|
||||
|
||||
pub(super) fn filter(
|
||||
config: &OAuth2Config,
|
||||
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone + Send + Sync + 'static {
|
@ -14,9 +14,14 @@
|
||||
|
||||
use hyper::Method;
|
||||
use mas_config::{OAuth2ClientConfig, OAuth2Config};
|
||||
use mas_data_model::TokenType;
|
||||
use mas_storage::oauth2::{
|
||||
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::{
|
||||
ClientAuthenticationMethod, IntrospectionRequest, IntrospectionResponse, TokenTypeHint,
|
||||
};
|
||||
@ -24,12 +29,6 @@ use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
||||
use tracing::{info, warn};
|
||||
use warp::{Filter, Rejection, Reply};
|
||||
|
||||
use crate::{
|
||||
errors::WrapError,
|
||||
filters::{client::client_authentication, cors::cors, database::connection},
|
||||
tokens::{self, TokenType},
|
||||
};
|
||||
|
||||
pub fn filter(
|
||||
pool: &PgPool,
|
||||
oauth2_config: &OAuth2Config,
|
||||
@ -88,7 +87,7 @@ async fn introspect(
|
||||
}
|
||||
|
||||
let reply = match token_type {
|
||||
tokens::TokenType::AccessToken => {
|
||||
TokenType::AccessToken => {
|
||||
let (token, session) = lookup_active_access_token(&mut conn, token)
|
||||
.await
|
||||
.wrap_error()?;
|
||||
@ -109,7 +108,7 @@ async fn introspect(
|
||||
jti: None,
|
||||
}
|
||||
}
|
||||
tokens::TokenType::RefreshToken => {
|
||||
TokenType::RefreshToken => {
|
||||
let (token, session) = lookup_active_refresh_token(&mut conn, token)
|
||||
.await
|
||||
.wrap_error()?;
|
@ -14,10 +14,9 @@
|
||||
|
||||
use hyper::Method;
|
||||
use mas_config::OAuth2Config;
|
||||
use mas_warp_utils::filters::cors::cors;
|
||||
use warp::{Filter, Rejection, Reply};
|
||||
|
||||
use crate::filters::cors::cors;
|
||||
|
||||
pub(super) fn filter(
|
||||
config: &OAuth2Config,
|
||||
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone + Send + Sync + 'static {
|
@ -19,7 +19,7 @@ use headers::{CacheControl, Pragma};
|
||||
use hyper::{Method, StatusCode};
|
||||
use jwt_compact::{Claims, Header, TimeOptions};
|
||||
use mas_config::{KeySet, OAuth2ClientConfig, OAuth2Config};
|
||||
use mas_data_model::AuthorizationGrantStage;
|
||||
use mas_data_model::{AuthorizationGrantStage, TokenType};
|
||||
use mas_storage::{
|
||||
oauth2::{
|
||||
access_token::{add_access_token, revoke_access_token},
|
||||
@ -28,6 +28,11 @@ use mas_storage::{
|
||||
},
|
||||
DatabaseInconsistencyError,
|
||||
};
|
||||
use mas_warp_utils::{
|
||||
errors::WrapError,
|
||||
filters::{client::client_authentication, cors::cors, database::connection, with_keys},
|
||||
reply::with_typed_header,
|
||||
};
|
||||
use oauth2_types::{
|
||||
errors::{InvalidGrant, InvalidRequest, OAuth2Error, OAuth2ErrorCode, UnauthorizedClient},
|
||||
requests::{
|
||||
@ -49,13 +54,6 @@ use warp::{
|
||||
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]
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Debug)]
|
||||
@ -233,8 +231,8 @@ async fn authorization_code_grant(
|
||||
let (access_token_str, refresh_token_str) = {
|
||||
let mut rng = thread_rng();
|
||||
(
|
||||
AccessToken.generate(&mut rng),
|
||||
RefreshToken.generate(&mut rng),
|
||||
TokenType::AccessToken.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 mut rng = thread_rng();
|
||||
(
|
||||
AccessToken.generate(&mut rng),
|
||||
RefreshToken.generate(&mut rng),
|
||||
TokenType::AccessToken.generate(&mut rng),
|
||||
TokenType::RefreshToken.generate(&mut rng),
|
||||
)
|
||||
};
|
||||
|
@ -16,14 +16,13 @@ use hyper::Method;
|
||||
use mas_config::OAuth2Config;
|
||||
use mas_data_model::{AccessToken, Session};
|
||||
use mas_storage::PostgresqlBackend;
|
||||
use serde::Serialize;
|
||||
use sqlx::PgPool;
|
||||
use warp::{Filter, Rejection, Reply};
|
||||
|
||||
use crate::filters::{
|
||||
use mas_warp_utils::filters::{
|
||||
authenticate::{authentication, recover_unauthorized},
|
||||
cors::cors,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use sqlx::PgPool;
|
||||
use warp::{Filter, Rejection, Reply};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct UserInfo {
|
@ -20,11 +20,7 @@ use mas_storage::{
|
||||
PostgresqlBackend,
|
||||
};
|
||||
use mas_templates::{AccountContext, TemplateContext, Templates};
|
||||
use serde::Deserialize;
|
||||
use sqlx::{pool::PoolConnection, PgExecutor, PgPool, Postgres, Transaction};
|
||||
use warp::{reply::html, Filter, Rejection, Reply};
|
||||
|
||||
use crate::{
|
||||
use mas_warp_utils::{
|
||||
errors::WrapError,
|
||||
filters::{
|
||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
||||
@ -34,6 +30,9 @@ use crate::{
|
||||
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(
|
||||
pool: &PgPool,
|
@ -16,16 +16,15 @@ use mas_config::{CookiesConfig, CsrfConfig, OAuth2Config};
|
||||
use mas_data_model::BrowserSession;
|
||||
use mas_storage::PostgresqlBackend;
|
||||
use mas_templates::{IndexContext, TemplateContext, Templates};
|
||||
use sqlx::PgPool;
|
||||
use url::Url;
|
||||
use warp::{reply::html, Filter, Rejection, Reply};
|
||||
|
||||
use crate::filters::{
|
||||
use mas_warp_utils::filters::{
|
||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
||||
csrf::updated_csrf_token,
|
||||
session::optional_session,
|
||||
with_templates, CsrfToken,
|
||||
};
|
||||
use sqlx::PgPool;
|
||||
use url::Url;
|
||||
use warp::{reply::html, Filter, Rejection, Reply};
|
||||
|
||||
pub(super) fn filter(
|
||||
pool: &PgPool,
|
@ -17,12 +17,7 @@ use mas_config::{CookiesConfig, CsrfConfig};
|
||||
use mas_data_model::{errors::WrapFormError, BrowserSession, StorageBackend};
|
||||
use mas_storage::{user::login, PostgresqlBackend};
|
||||
use mas_templates::{LoginContext, LoginFormField, TemplateContext, Templates};
|
||||
use serde::Deserialize;
|
||||
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
||||
use warp::{reply::html, Filter, Rejection, Reply};
|
||||
|
||||
use super::{shared::PostAuthAction, RegisterRequest};
|
||||
use crate::{
|
||||
use mas_warp_utils::{
|
||||
errors::WrapError,
|
||||
filters::{
|
||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
||||
@ -32,6 +27,11 @@ use crate::{
|
||||
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)]
|
||||
#[serde(bound(deserialize = "S::AuthorizationGrantData: std::str::FromStr,
|
@ -15,13 +15,12 @@
|
||||
use mas_config::CookiesConfig;
|
||||
use mas_data_model::BrowserSession;
|
||||
use mas_storage::{user::end_session, PostgresqlBackend};
|
||||
use sqlx::{PgPool, Postgres, Transaction};
|
||||
use warp::{hyper::Uri, Filter, Rejection, Reply};
|
||||
|
||||
use crate::{
|
||||
use mas_warp_utils::{
|
||||
errors::WrapError,
|
||||
filters::{csrf::protected_form, database::transaction, session::session},
|
||||
};
|
||||
use sqlx::{PgPool, Postgres, Transaction};
|
||||
use warp::{hyper::Uri, Filter, Rejection, Reply};
|
||||
|
||||
pub(super) fn filter(
|
||||
pool: &PgPool,
|
@ -17,12 +17,7 @@ use mas_config::{CookiesConfig, CsrfConfig};
|
||||
use mas_data_model::{BrowserSession, StorageBackend};
|
||||
use mas_storage::{user::authenticate_session, PostgresqlBackend};
|
||||
use mas_templates::{ReauthContext, TemplateContext, Templates};
|
||||
use serde::Deserialize;
|
||||
use sqlx::{pool::PoolConnection, PgPool, Postgres, Transaction};
|
||||
use warp::{hyper::Uri, reply::html, Filter, Rejection, Reply};
|
||||
|
||||
use super::PostAuthAction;
|
||||
use crate::{
|
||||
use mas_warp_utils::{
|
||||
errors::WrapError,
|
||||
filters::{
|
||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
||||
@ -32,6 +27,11 @@ use crate::{
|
||||
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)]
|
||||
#[serde(bound(deserialize = "S::AuthorizationGrantData: std::str::FromStr,
|
@ -21,12 +21,7 @@ use mas_storage::{
|
||||
PostgresqlBackend,
|
||||
};
|
||||
use mas_templates::{RegisterContext, TemplateContext, Templates};
|
||||
use serde::Deserialize;
|
||||
use sqlx::{pool::PoolConnection, PgPool, Postgres, Transaction};
|
||||
use warp::{reply::html, Filter, Rejection, Reply};
|
||||
|
||||
use super::{LoginRequest, PostAuthAction};
|
||||
use crate::{
|
||||
use mas_warp_utils::{
|
||||
errors::WrapError,
|
||||
filters::{
|
||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
||||
@ -36,6 +31,11 @@ use crate::{
|
||||
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)]
|
||||
#[serde(bound(deserialize = "S::AuthorizationGrantData: std::str::FromStr,
|
@ -14,12 +14,8 @@ url = { version = "2.2.2", features = ["serde"] }
|
||||
parse-display = "0.5.3"
|
||||
indoc = "1.0.3"
|
||||
serde_with = { version = "1.11.0", features = ["chrono"] }
|
||||
sqlx = { version = "0.5.9", default-features = false, optional = true }
|
||||
chrono = "0.4.19"
|
||||
sha2 = "0.10.0"
|
||||
data-encoding = "2.3.2"
|
||||
thiserror = "1.0.30"
|
||||
itertools = "0.10.3"
|
||||
|
||||
[features]
|
||||
sqlx_type = ["sqlx"]
|
||||
|
@ -21,5 +21,5 @@ password-hash = { version = "0.3.2", features = ["std"] }
|
||||
rand = "0.8.4"
|
||||
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" }
|
||||
|
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 {}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
pub(crate) trait WrapError<T> {
|
||||
pub trait WrapError<T> {
|
||||
fn wrap_error(self) -> Result<T, Rejection>;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
use headers::{authorization::Bearer, Authorization};
|
||||
use hyper::StatusCode;
|
||||
use mas_data_model::{AccessToken, Session};
|
||||
use mas_data_model::{AccessToken, Session, TokenFormatError, TokenType};
|
||||
use mas_storage::{
|
||||
oauth2::access_token::{lookup_active_access_token, AccessTokenLookupError},
|
||||
PostgresqlBackend,
|
||||
@ -33,10 +33,7 @@ use super::{
|
||||
database::connection,
|
||||
headers::{typed_header, InvalidTypedHeader},
|
||||
};
|
||||
use crate::{
|
||||
errors::wrapped_error,
|
||||
tokens::{TokenFormatError, TokenType},
|
||||
};
|
||||
use crate::errors::wrapped_error;
|
||||
|
||||
/// Bearer token authentication failed
|
||||
///
|
@ -61,7 +61,7 @@ pub fn with_keys(
|
||||
///
|
||||
/// 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")
|
||||
/// .map(Some)
|
@ -12,20 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// 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 filters;
|
||||
pub mod handlers;
|
||||
pub mod reply;
|
||||
pub mod tasks;
|
||||
pub mod tokens;
|
||||
|
||||
pub use self::filters::cors::set_propagator;
|
@ -19,7 +19,7 @@
|
||||
//! extern crate warp;
|
||||
//!
|
||||
//! 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 = with_typed_header(headers::ContentType::json(), reply);;
|
Reference in New Issue
Block a user