Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 48 additions & 23 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 9 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ homepage = "https://github.com/joshuaclayton/loopauth"
documentation = "https://docs.rs/loopauth"
readme = "README.md"
keywords = ["oauth2", "pkce", "cli", "authentication", "oidc"]
categories = ["authentication", "web-programming"]
categories = ["authentication", "command-line-interface"]

[dependencies]
async-trait = "0.1"
Expand All @@ -24,7 +24,7 @@ rsa = { version = "0.9", features = ["pem"] }
rustls = "0.23"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha2 = "0.10"
sha2 = "0.11"
subtle = "2.6.1"
thiserror = "2"
tokio = { version = "1", features = ["full"] }
Expand All @@ -50,6 +50,7 @@ exhaustive_structs = "deny"
panic = "deny"
panic_in_result_fn = "deny"
unwrap_in_result = "deny"
unwrap_used = "deny"
get_unwrap = "deny"
expect_used = "deny"
todo = "deny"
Expand All @@ -64,11 +65,16 @@ exit = "deny"
# Error handling discipline
map_err_ignore = "deny"
indexing_slicing = "deny"
string_slice = "deny" # can panic on non-ASCII boundaries
string_slice = "deny" # can panic on non-ASCII boundaries
wildcard_enum_match_arm = "deny"
# Meta: suppressions must explain themselves
allow_attributes_without_reason = "deny"
allow_attributes = "deny"
# Public API documentation completeness
missing_errors_doc = "deny"
missing_panics_doc = "deny"
# Style consistency worth enforcing
partial_pub_fields = "deny"
unseparated_literal_suffix = "deny"
missing_assert_message = "deny"
redundant_else = "deny"
1 change: 1 addition & 0 deletions examples/jwks_demo.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![expect(
clippy::string_slice,
clippy::expect_used,
clippy::unwrap_used,
reason = "CLI examples can be more lax"
)]
// # Usage
Expand Down
1 change: 1 addition & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1665,6 +1665,7 @@ mod tests {
#![expect(
clippy::indexing_slicing,
clippy::expect_used,
clippy::unwrap_used,
reason = "tests do not need to meet production lint standards"
)]

Expand Down
19 changes: 16 additions & 3 deletions src/jwks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,11 @@ fn select_key_with_kid<'a>(keys: &'a [JwkKey], kid: &str) -> (Option<&'a JwkKey>
found = Some(key);
break;
}
_ => {
JwkKey::RsaWithKid { .. }
| JwkKey::Rsa { .. }
| JwkKey::Ec { .. }
| JwkKey::EcWithKid { .. }
| JwkKey::Unsupported { .. } => {
skipped.push(jwk_key_description(key));
}
}
Expand All @@ -284,7 +288,9 @@ fn build_decoding_key_and_validation(
alg: jsonwebtoken::Algorithm,
client_id: &str,
) -> Result<(jsonwebtoken::DecodingKey, jsonwebtoken::Validation), JwksValidationError> {
use jsonwebtoken::Algorithm::{ES256, ES384, PS256, PS384, PS512, RS256, RS384, RS512};
use jsonwebtoken::Algorithm::{
ES256, ES384, EdDSA, HS256, HS384, HS512, PS256, PS384, PS512, RS256, RS384, RS512,
};

let mut validation = jsonwebtoken::Validation::new(alg);
validation.leeway = CLOCK_SKEW_LEEWAY_SECONDS;
Expand All @@ -305,7 +311,10 @@ fn build_decoding_key_and_validation(
let expected_crv = match alg {
ES256 => "P-256",
ES384 => "P-384",
other => {
// The outer match arm constrains `alg` to ES256|ES384,
// so this branch is unreachable; kept as a defensive guard.
other @ (HS256 | HS384 | HS512 | RS256 | RS384 | RS512 | PS256 | PS384 | PS512
| EdDSA) => {
return Err(JwksValidationError::new(format!(
"unexpected EC algorithm: {other:?}"
)));
Expand Down Expand Up @@ -416,6 +425,10 @@ impl JwksValidator for RemoteJwksValidator {

#[cfg(test)]
mod tests {
#![expect(
clippy::unwrap_used,
reason = "tests do not need to meet production lint standards"
)]
use super::{JwksValidationError, RemoteJwksValidator};
use crate::oidc::OpenIdConfiguration;
use url::Url;
Expand Down
4 changes: 4 additions & 0 deletions src/oidc/claims/email.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ impl<'de> Deserialize<'de> for Email {

#[cfg(test)]
mod tests {
#![expect(
clippy::unwrap_used,
reason = "tests do not need to meet production lint standards"
)]
use super::Email;

#[test]
Expand Down
4 changes: 4 additions & 0 deletions src/oidc/claims/issuer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ impl<'de> Deserialize<'de> for Issuer {

#[cfg(test)]
mod tests {
#![expect(
clippy::unwrap_used,
reason = "tests do not need to meet production lint standards"
)]
use super::Issuer;
use url::Url;

Expand Down
1 change: 1 addition & 0 deletions src/oidc/claims/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ mod tests {
#![expect(
clippy::indexing_slicing,
clippy::expect_used,
clippy::unwrap_used,
reason = "tests do not need to meet production lint standards"
)]
use super::Claims;
Expand Down
4 changes: 4 additions & 0 deletions src/oidc/claims/picture_url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ impl<'de> Deserialize<'de> for PictureUrl {

#[cfg(test)]
mod tests {
#![expect(
clippy::unwrap_used,
reason = "tests do not need to meet production lint standards"
)]
use super::PictureUrl;

#[test]
Expand Down
47 changes: 23 additions & 24 deletions src/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,25 +296,23 @@ impl ErrorPageRenderer for DefaultErrorPageRenderer {
async fn render_error(&self, ctx: &ErrorPageContext<'_>) -> String {
let mut content = String::new();

match ctx.error() {
crate::error::AuthError::Callback(crate::error::CallbackError::ProviderError {
error,
description,
}) => {
let _ = write!(
content,
"<p class=\"text-base text-red-700 dark:text-red-300\">{}</p><p class=\"mt-1 font-mono text-sm text-red-400 dark:text-red-100\">{}</p>",
html_escape(description),
html_escape(error)
);
}
other => {
let _ = write!(
content,
"<p class=\"text-base text-red-700 dark:text-red-300\">{}</p>",
html_escape(&other.to_string())
);
}
if let crate::error::AuthError::Callback(crate::error::CallbackError::ProviderError {
error,
description,
}) = ctx.error()
{
let _ = write!(
content,
"<p class=\"text-base text-red-700 dark:text-red-300\">{}</p><p class=\"mt-1 font-mono text-sm text-red-400 dark:text-red-100\">{}</p>",
html_escape(description),
html_escape(error)
);
} else {
let _ = write!(
content,
"<p class=\"text-base text-red-700 dark:text-red-300\">{}</p>",
html_escape(&ctx.error().to_string())
);
}

if !ctx.scopes().is_empty() {
Expand Down Expand Up @@ -370,7 +368,7 @@ fn html_escape(s: &str) -> String {
#[cfg(test)]
mod tests {
#![expect(
clippy::panic,
clippy::unwrap_used,
reason = "tests do not need to meet production lint standards"
)]
use super::{
Expand Down Expand Up @@ -451,10 +449,11 @@ mod tests {
ctx.redirect_uri().as_str(),
"http://127.0.0.1:8080/callback"
);
match ctx.error() {
AuthError::Timeout => {}
other => panic!("expected Timeout, got {other:?}"),
}
assert!(
matches!(ctx.error(), AuthError::Timeout),
"expected Timeout, got {:?}",
ctx.error()
);
}

#[tokio::test]
Expand Down
4 changes: 4 additions & 0 deletions src/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ impl From<&str> for RequestScope {

#[cfg(test)]
mod tests {
#![expect(
clippy::unwrap_used,
reason = "tests do not need to meet production lint standards"
)]
use super::{OAuth2Scope, RequestScope};

#[test]
Expand Down
1 change: 1 addition & 0 deletions src/test_support.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Test support utilities for doctests and unit tests.
#![expect(
clippy::pedantic,
clippy::unwrap_used,
reason = "test support code does not need to meet production lint standards"
)]

Expand Down
1 change: 1 addition & 0 deletions src/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ mod tests {
#![expect(
clippy::indexing_slicing,
clippy::expect_used,
clippy::unwrap_used,
reason = "tests do not need to meet production lint standards"
)]
use super::{AccessToken, RefreshToken, TokenSet, Unvalidated, Validated};
Expand Down
Loading
Loading