Configure rustfmt & clippy lints
This commit is contained in:
6
.rustfmt.toml
Normal file
6
.rustfmt.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
max_width = 100
|
||||
comment_width = 80
|
||||
wrap_comments = true
|
||||
imports_granularity = "Crate"
|
||||
use_small_heuristics = "Default"
|
||||
group_imports = "StdExternalCrate"
|
||||
@@ -12,12 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use time::Duration;
|
||||
|
||||
use csrf::AesGcmCsrfProtection;
|
||||
use serde::Deserialize;
|
||||
use serde_with::serde_as;
|
||||
use tide::Middleware;
|
||||
use time::Duration;
|
||||
|
||||
use crate::middlewares::CsrfMiddleware;
|
||||
|
||||
|
||||
@@ -29,10 +29,12 @@ fn default_connect_timeout() -> Duration {
|
||||
Duration::from_secs(30)
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn default_idle_timeout() -> Option<Duration> {
|
||||
Some(Duration::from_secs(10 * 60))
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn default_max_lifetime() -> Option<Duration> {
|
||||
Some(Duration::from_secs(30 * 60))
|
||||
}
|
||||
|
||||
@@ -24,10 +24,12 @@ mod database;
|
||||
mod http;
|
||||
mod oauth2;
|
||||
|
||||
pub use self::csrf::Config as CsrfConfig;
|
||||
pub use self::database::Config as DatabaseConfig;
|
||||
pub use self::http::Config as HttpConfig;
|
||||
pub use self::oauth2::{ClientConfig as OAuth2ClientConfig, Config as OAuth2Config};
|
||||
pub use self::{
|
||||
csrf::Config as CsrfConfig,
|
||||
database::Config as DatabaseConfig,
|
||||
http::Config as HttpConfig,
|
||||
oauth2::{ClientConfig as OAuth2ClientConfig, Config as OAuth2Config},
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RootConfig {
|
||||
|
||||
@@ -40,7 +40,7 @@ impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
issuer: default_oauth2_issuer(),
|
||||
clients: Default::default(),
|
||||
clients: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ async fn redirect_uri_from_params<T>(
|
||||
params: QueryParams,
|
||||
storage: &Storage<T>,
|
||||
) -> Result<Url, RedirectUriLookupError> {
|
||||
use RedirectUriLookupError::*;
|
||||
use RedirectUriLookupError::MissingClientId;
|
||||
let client_id = params.client_id.ok_or(MissingClientId)?;
|
||||
let client = storage.lookup_client(&client_id).await?;
|
||||
let redirect_uri: Option<Url> = if let Some(uri) = params.redirect_uri {
|
||||
|
||||
@@ -12,9 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use tide::{Body, Request, Response};
|
||||
|
||||
use oauth2_types::requests::AuthorizationRequest;
|
||||
use tide::{Body, Request, Response};
|
||||
|
||||
use crate::state::State;
|
||||
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use tide::{Body, Request, Response};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use oauth2_types::oidc::Metadata;
|
||||
use tide::{Body, Request, Response};
|
||||
|
||||
use crate::state::State;
|
||||
|
||||
@@ -26,10 +27,10 @@ pub async fn get(req: Request<State>) -> tide::Result {
|
||||
token_endpoint: state.token_endpoint(),
|
||||
jwks_uri: state.jwks_uri(),
|
||||
registration_endpoint: None,
|
||||
scopes_supported: Default::default(),
|
||||
response_types_supported: Default::default(),
|
||||
response_modes_supported: Default::default(),
|
||||
grant_types_supported: Default::default(),
|
||||
scopes_supported: HashSet::default(),
|
||||
response_types_supported: HashSet::default(),
|
||||
response_modes_supported: HashSet::default(),
|
||||
grant_types_supported: HashSet::default(),
|
||||
};
|
||||
|
||||
let body = Body::from_json(&m)?;
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
|
||||
use tide::{Request, Response};
|
||||
|
||||
use crate::state::State;
|
||||
use crate::templates::common_context;
|
||||
use crate::{state::State, templates::common_context};
|
||||
|
||||
pub async fn get(req: Request<State>) -> tide::Result {
|
||||
let state = req.state();
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
use serde::Deserialize;
|
||||
use tide::{Redirect, Request, Response};
|
||||
|
||||
use crate::csrf::CsrfForm;
|
||||
use crate::state::State;
|
||||
use crate::templates::common_context;
|
||||
use crate::{csrf::CsrfForm, state::State, templates::common_context};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LoginForm {
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::{csrf::CsrfForm, state::State};
|
||||
|
||||
pub async fn post(mut req: Request<State>) -> tide::Result {
|
||||
let form: CsrfForm<()> = req.body_form().await?;
|
||||
let _ = form.verify_csrf(&req)?;
|
||||
form.verify_csrf(&req)?;
|
||||
|
||||
let session = req.session_mut();
|
||||
session.remove("current_user");
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
// limitations under the License.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(clippy::all)]
|
||||
#![warn(clippy::pedantic)]
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
use anyhow::Context;
|
||||
use tracing::{info_span, Instrument};
|
||||
@@ -26,9 +29,7 @@ mod state;
|
||||
mod storage;
|
||||
mod templates;
|
||||
|
||||
use self::config::RootConfig;
|
||||
use self::state::State;
|
||||
use self::storage::MIGRATOR;
|
||||
use self::{config::RootConfig, state::State, storage::MIGRATOR};
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() -> tide::Result<()> {
|
||||
|
||||
@@ -12,9 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::cmp::Reverse;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::{cmp::Reverse, future::Future, pin::Pin};
|
||||
|
||||
use mime::{Mime, STAR};
|
||||
use serde::Serialize;
|
||||
@@ -25,19 +23,20 @@ use tide::{
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::state::State;
|
||||
use crate::templates::common_context;
|
||||
use crate::{state::State, templates::common_context};
|
||||
|
||||
/// Get the weight parameter for a mime type from 0 to 1000
|
||||
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
|
||||
fn get_weight(mime: &Mime) -> usize {
|
||||
let q = mime
|
||||
.get_param("q")
|
||||
.map(|q| q.as_str().parse().unwrap_or(0.0))
|
||||
.unwrap_or(1.0_f64)
|
||||
.map_or(1.0_f64, |q| q.as_str().parse().unwrap_or(0.0))
|
||||
.min(1.0)
|
||||
.max(0.0);
|
||||
|
||||
// Weight have a 3 digit precision so we can multiply by 1000 and cast to int
|
||||
// Weight have a 3 digit precision so we can multiply by 1000 and cast to
|
||||
// int. Sign loss should not happen here because of the min/max up there and
|
||||
// truncation does not matter here.
|
||||
(q * 1000.0) as _
|
||||
}
|
||||
|
||||
@@ -47,12 +46,12 @@ fn preferred_mime_type<'a>(
|
||||
supported_types: &'a [Mime],
|
||||
) -> Option<&'a Mime> {
|
||||
let accept = request.header(ACCEPT)?;
|
||||
// Parse the Accept header as a list of mime types with their associated weight
|
||||
// Parse the Accept header as a list of mime types with their associated
|
||||
// weight
|
||||
let accepted_types: Vec<(Mime, usize)> = {
|
||||
let v: Option<Vec<_>> = accept
|
||||
.into_iter()
|
||||
.map(|value| value.as_str().split(','))
|
||||
.flatten()
|
||||
.flat_map(|value| value.as_str().split(','))
|
||||
.map(|mime| {
|
||||
mime.trim().parse().ok().map(|mime| {
|
||||
let q = get_weight(&mime);
|
||||
@@ -65,7 +64,8 @@ fn preferred_mime_type<'a>(
|
||||
v
|
||||
};
|
||||
|
||||
// For each supported content type, find out if it is accepted with what weight and specificity
|
||||
// For each supported content type, find out if it is accepted with what
|
||||
// weight and specificity
|
||||
let mut types: Vec<_> = supported_types
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -133,7 +133,8 @@ pub fn middleware<'a>(
|
||||
|
||||
let mut response = next.run(request).await;
|
||||
|
||||
// Find out what message should be displayed from the response status code
|
||||
// Find out what message should be displayed from the response status
|
||||
// code
|
||||
let (code, description) = match response.status() {
|
||||
StatusCode::NotFound => (Some("Not found".to_string()), None),
|
||||
StatusCode::MethodNotAllowed => (Some("Method not allowed".to_string()), None),
|
||||
@@ -148,8 +149,8 @@ pub fn middleware<'a>(
|
||||
_ => (None, None),
|
||||
};
|
||||
|
||||
// If there is an error associated to the response, format it in a nice way with
|
||||
// a backtrace if we have one
|
||||
// If there is an error associated to the response, format it in a nice
|
||||
// way with a backtrace if we have one
|
||||
let details = response.take_error().map(|err| {
|
||||
format!(
|
||||
"{}{}",
|
||||
@@ -166,7 +167,8 @@ pub fn middleware<'a>(
|
||||
details,
|
||||
};
|
||||
|
||||
// This is the case if one of the code, description or details is not None
|
||||
// This is the case if one of the code, description or details is not
|
||||
// None
|
||||
if error_context.should_render() {
|
||||
match content_type {
|
||||
Some(c) if c == &mime::APPLICATION_JSON => {
|
||||
|
||||
@@ -15,5 +15,4 @@
|
||||
mod csrf;
|
||||
mod errors;
|
||||
|
||||
pub use self::csrf::Middleware as CsrfMiddleware;
|
||||
pub use self::errors::middleware as errors;
|
||||
pub use self::{csrf::Middleware as CsrfMiddleware, errors::middleware as errors};
|
||||
|
||||
@@ -20,8 +20,10 @@ use sqlx::migrate::Migrator;
|
||||
mod client;
|
||||
mod user;
|
||||
|
||||
pub use self::client::{Client, ClientLookupError, InvalidRedirectUriError};
|
||||
pub use self::user::User;
|
||||
pub use self::{
|
||||
client::{Client, ClientLookupError, InvalidRedirectUriError},
|
||||
user::User,
|
||||
};
|
||||
|
||||
pub static MIGRATOR: Migrator = sqlx::migrate!();
|
||||
|
||||
@@ -36,8 +38,8 @@ impl<Pool> Storage<Pool> {
|
||||
pub fn new(pool: Pool) -> Self {
|
||||
Self {
|
||||
pool,
|
||||
clients: Default::default(),
|
||||
users: Default::default(),
|
||||
clients: RwLock::default(),
|
||||
users: RwLock::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,17 +29,17 @@ impl User {
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Invalid credentials")]
|
||||
pub struct UserLoginError;
|
||||
pub struct LoginError;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Could not find user")]
|
||||
pub struct UserLookupError;
|
||||
pub struct LookupError;
|
||||
|
||||
impl<T> super::Storage<T> {
|
||||
pub async fn login(&self, name: &str, password: &str) -> Result<User, UserLoginError> {
|
||||
pub async fn login(&self, name: &str, password: &str) -> Result<User, LoginError> {
|
||||
// Hardcoded bad password to test login failures
|
||||
if password == "bad" {
|
||||
Err(UserLoginError)
|
||||
Err(LoginError)
|
||||
} else {
|
||||
// First lookup for an existing user
|
||||
let users = self.users.upgradable_read().await;
|
||||
@@ -57,8 +57,8 @@ impl<T> super::Storage<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn lookup_user(&self, name: &str) -> Result<User, UserLookupError> {
|
||||
pub async fn lookup_user(&self, name: &str) -> Result<User, LookupError> {
|
||||
let users = self.users.read().await;
|
||||
users.get(name).cloned().ok_or(UserLookupError)
|
||||
users.get(name).cloned().ok_or(LookupError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,23 +22,25 @@ pub trait OAuth2Error: std::fmt::Debug {
|
||||
/// Maps to the required "error" field.
|
||||
fn error(&self) -> &'static str;
|
||||
|
||||
/// Human-readable ASCII text providing additional information, used to assist the client
|
||||
/// developer in understanding the error that occurred.
|
||||
/// Human-readable ASCII text providing additional information, used to
|
||||
/// assist the client developer in understanding the error that
|
||||
/// occurred.
|
||||
///
|
||||
/// Maps to the optional "error_description" field.
|
||||
/// Maps to the optional `error_description` field.
|
||||
fn description(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
/// A URI identifying a human-readable web page with information about the error, used to
|
||||
/// provide the client developer with additional information about the error.
|
||||
/// A URI identifying a human-readable web page with information about the
|
||||
/// error, used to provide the client developer with additional
|
||||
/// information about the error.
|
||||
///
|
||||
/// Maps to the optional "error_uri" field.
|
||||
/// Maps to the optional `error_uri` field.
|
||||
fn uri(&self) -> Option<Url> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Wraps the error with an ErrorResponse to help serializing.
|
||||
/// Wraps the error with an `ErrorResponse` to help serializing.
|
||||
fn into_response(self) -> ErrorResponse<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(clippy::all)]
|
||||
#![warn(clippy::pedantic)]
|
||||
|
||||
pub mod errors;
|
||||
pub mod oidc;
|
||||
pub mod requests;
|
||||
|
||||
@@ -22,8 +22,8 @@ use crate::requests::{GrantType, ResponseMode, ResponseType};
|
||||
// TODO: https://datatracker.ietf.org/doc/html/rfc8414#section-2
|
||||
#[derive(Serialize)]
|
||||
pub struct Metadata {
|
||||
/// The authorization server's issuer identifier, which is a URL that uses the "https" scheme
|
||||
/// and has no query or fragment components.
|
||||
/// The authorization server's issuer identifier, which is a URL that uses
|
||||
/// the "https" scheme and has no query or fragment components.
|
||||
pub issuer: Url,
|
||||
|
||||
/// URL of the authorization server's authorization endpoint.
|
||||
@@ -38,28 +38,29 @@ pub struct Metadata {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub jwks_uri: Option<Url>,
|
||||
|
||||
/// URL of the authorization server's OAuth 2.0 Dynamic Client Registration endpoint.
|
||||
/// URL of the authorization server's OAuth 2.0 Dynamic Client Registration
|
||||
/// endpoint.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub registration_endpoint: Option<Url>,
|
||||
|
||||
/// JSON array containing a list of the OAuth 2.0 "scope" values that this authorization server
|
||||
/// supports.
|
||||
/// JSON array containing a list of the OAuth 2.0 "scope" values that this
|
||||
/// authorization server supports.
|
||||
#[serde(skip_serializing_if = "HashSet::is_empty")]
|
||||
pub scopes_supported: HashSet<String>,
|
||||
|
||||
/// JSON array containing a list of the OAuth 2.0 "response_type" values that this
|
||||
/// authorization server supports.
|
||||
/// JSON array containing a list of the OAuth 2.0 "response_type" values
|
||||
/// that this authorization server supports.
|
||||
#[serde(skip_serializing_if = "HashSet::is_empty")]
|
||||
pub response_types_supported: HashSet<ResponseType>,
|
||||
|
||||
/// JSON array containing a list of the OAuth 2.0 "response_mode" values that this
|
||||
/// authorization server supports, as specified in "OAuth 2.0 Multiple Response Type Encoding
|
||||
/// Practices".
|
||||
/// JSON array containing a list of the OAuth 2.0 "response_mode" values
|
||||
/// that this authorization server supports, as specified in "OAuth 2.0
|
||||
/// Multiple Response Type Encoding Practices".
|
||||
#[serde(skip_serializing_if = "HashSet::is_empty")]
|
||||
pub response_modes_supported: HashSet<ResponseMode>,
|
||||
|
||||
/// JSON array containing a list of the OAuth 2.0 grant type values that this authorization
|
||||
/// server supports.
|
||||
/// JSON array containing a list of the OAuth 2.0 grant type values that
|
||||
/// this authorization server supports.
|
||||
#[serde(skip_serializing_if = "HashSet::is_empty")]
|
||||
pub grant_types_supported: HashSet<GrantType>,
|
||||
}
|
||||
|
||||
@@ -14,10 +14,16 @@
|
||||
|
||||
//! Utilitary types for serde
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashSet, hash::Hash, time::Duration};
|
||||
use std::{
|
||||
collections::{hash_map::RandomState, HashSet},
|
||||
hash::Hash,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
/// A HashSet that serializes to a space-separated string in alphanumerical order
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A `HashSet` that serializes to a space-separated string in alphanumerical
|
||||
/// order
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct StringHashSet<T: Eq + Hash>(HashSet<T>);
|
||||
|
||||
@@ -27,7 +33,7 @@ impl<T: Eq + Hash> From<HashSet<T>> for StringHashSet<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq + Hash> From<StringHashSet<T>> for HashSet<T> {
|
||||
impl<T: Eq + Hash> From<StringHashSet<T>> for HashSet<T, RandomState> {
|
||||
fn from(set: StringHashSet<T>) -> Self {
|
||||
set.0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user