mirror of
https://github.com/readur/readur.git
synced 2026-02-17 20:39:28 -06:00
Merge pull request #422 from readur/fix/update-rust-versions-take1
feat(dev): update rust deps major versions
This commit is contained in:
600
Cargo.lock
generated
600
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
16
Cargo.toml
@@ -50,7 +50,7 @@ raw-cpuid = { version = "11", optional = true }
|
||||
reqwest = { version = "0.12", features = ["json", "multipart"] }
|
||||
quick-xml = { version = "0.37", features = ["serialize"] }
|
||||
urlencoding = "2.1"
|
||||
oauth2 = "4.4"
|
||||
oauth2 = "5"
|
||||
url = "2.4"
|
||||
dotenvy = "0.15"
|
||||
hostname = "0.4"
|
||||
@@ -65,11 +65,11 @@ aws-credential-types = { version = "1.2", optional = true }
|
||||
aws-types = { version = "1.3", optional = true }
|
||||
sha2 = "0.10"
|
||||
utoipa-swagger-ui = { version = "9", features = ["axum"] }
|
||||
testcontainers = { version = "0.24", optional = true }
|
||||
testcontainers-modules = { version = "0.12", features = ["postgres"], optional = true }
|
||||
testcontainers = { version = "0.26", optional = true }
|
||||
testcontainers-modules = { version = "0.14", features = ["postgres"], optional = true }
|
||||
# Office document support - now using XML extraction only
|
||||
zip = "0.6" # Still needed for other archive handling
|
||||
rand = "0.8"
|
||||
zip = "7" # Still needed for other archive handling
|
||||
rand = "0.9"
|
||||
|
||||
[features]
|
||||
default = ["ocr", "s3"]
|
||||
@@ -83,10 +83,10 @@ tempfile = "3"
|
||||
wiremock = "0.6"
|
||||
tokio-test = "0.4"
|
||||
futures = "0.3"
|
||||
rand = "0.8"
|
||||
rand = "0.9"
|
||||
# Database testing dependencies
|
||||
testcontainers = "0.24"
|
||||
testcontainers-modules = { version = "0.12", features = ["postgres"] }
|
||||
testcontainers = "0.26"
|
||||
testcontainers-modules = { version = "0.14", features = ["postgres"] }
|
||||
# Dependencies for creating proper test Office documents
|
||||
rust_xlsxwriter = "0.92" # For creating proper XLSX test files
|
||||
# WebDAV server for realistic stress testing
|
||||
|
||||
79
src/oidc.rs
79
src/oidc.rs
@@ -1,7 +1,8 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use oauth2::{
|
||||
basic::BasicClient, reqwest::async_http_client, AuthUrl, AuthorizationCode, ClientId,
|
||||
ClientSecret, CsrfToken, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, Scope, TokenResponse, TokenUrl,
|
||||
basic::BasicClient, AuthUrl, AuthorizationCode, ClientId,
|
||||
ClientSecret, CsrfToken, PkceCodeChallenge, PkceCodeVerifier,
|
||||
RedirectUrl, Scope, TokenResponse, TokenUrl,
|
||||
};
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -33,7 +34,14 @@ type PkceStore = Mutex<HashMap<String, (PkceCodeVerifier, Instant)>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OidcClient {
|
||||
oauth_client: BasicClient,
|
||||
// Store configuration instead of the oauth2 client to avoid complex typestate types.
|
||||
// The oauth2 5.0 crate uses a typestate pattern that makes storing a configured client
|
||||
// in a struct unwieldy. Instead, we store the config and build the client on-demand.
|
||||
// This is cheap: just struct construction with pre-validated URLs (no network calls).
|
||||
// The expensive OIDC discovery happens once in `new()`.
|
||||
client_id: String,
|
||||
client_secret: Option<String>,
|
||||
redirect_uri: String,
|
||||
discovery: OidcDiscovery,
|
||||
http_client: Client,
|
||||
is_public_client: bool,
|
||||
@@ -49,11 +57,12 @@ impl OidcClient {
|
||||
let client_id = config
|
||||
.oidc_client_id
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("OIDC client ID not configured"))?;
|
||||
.ok_or_else(|| anyhow!("OIDC client ID not configured"))?
|
||||
.clone();
|
||||
|
||||
// Client secret is optional - if not provided, this is a public client
|
||||
let client_secret_opt = config.oidc_client_secret.as_ref();
|
||||
let is_public_client = client_secret_opt.is_none();
|
||||
let client_secret = config.oidc_client_secret.clone();
|
||||
let is_public_client = client_secret.is_none();
|
||||
|
||||
let issuer_url = config
|
||||
.oidc_issuer_url
|
||||
@@ -62,24 +71,18 @@ impl OidcClient {
|
||||
let redirect_uri = config
|
||||
.oidc_redirect_uri
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("OIDC redirect URI not configured"))?;
|
||||
.ok_or_else(|| anyhow!("OIDC redirect URI not configured"))?
|
||||
.clone();
|
||||
|
||||
let http_client = Client::new();
|
||||
|
||||
// Discover OIDC endpoints
|
||||
let discovery = Self::discover_endpoints(&http_client, issuer_url).await?;
|
||||
|
||||
// Create OAuth2 client
|
||||
let oauth_client = BasicClient::new(
|
||||
ClientId::new(client_id.clone()),
|
||||
client_secret_opt.map(|s| ClientSecret::new(s.clone())),
|
||||
AuthUrl::new(discovery.authorization_endpoint.clone())?,
|
||||
Some(TokenUrl::new(discovery.token_endpoint.clone())?),
|
||||
)
|
||||
.set_redirect_uri(RedirectUrl::new(redirect_uri.clone())?);
|
||||
|
||||
Ok(Self {
|
||||
oauth_client,
|
||||
client_id,
|
||||
client_secret,
|
||||
redirect_uri,
|
||||
discovery,
|
||||
http_client,
|
||||
is_public_client,
|
||||
@@ -89,7 +92,7 @@ impl OidcClient {
|
||||
|
||||
async fn discover_endpoints(client: &Client, issuer_url: &str) -> Result<OidcDiscovery> {
|
||||
let discovery_url = format!("{}/.well-known/openid-configuration", issuer_url.trim_end_matches('/'));
|
||||
|
||||
|
||||
let response = client
|
||||
.get(&discovery_url)
|
||||
.send()
|
||||
@@ -111,11 +114,21 @@ impl OidcClient {
|
||||
Ok(discovery)
|
||||
}
|
||||
|
||||
pub fn get_authorization_url(&self) -> (Url, CsrfToken) {
|
||||
pub fn get_authorization_url(&self) -> Result<(Url, CsrfToken)> {
|
||||
// Clean up expired PKCE verifiers (older than 10 minutes)
|
||||
self.cleanup_expired_verifiers();
|
||||
|
||||
let mut auth_request = self.oauth_client
|
||||
// Build OAuth2 client on-demand - this is cheap (just struct construction, no I/O)
|
||||
let mut oauth_client = BasicClient::new(ClientId::new(self.client_id.clone()))
|
||||
.set_auth_uri(AuthUrl::new(self.discovery.authorization_endpoint.clone())?)
|
||||
.set_token_uri(TokenUrl::new(self.discovery.token_endpoint.clone())?)
|
||||
.set_redirect_uri(RedirectUrl::new(self.redirect_uri.clone())?);
|
||||
|
||||
if let Some(secret) = &self.client_secret {
|
||||
oauth_client = oauth_client.set_client_secret(ClientSecret::new(secret.clone()));
|
||||
}
|
||||
|
||||
let mut auth_request = oauth_client
|
||||
.authorize_url(CsrfToken::new_random)
|
||||
.add_scope(Scope::new("openid".to_string()))
|
||||
.add_scope(Scope::new("email".to_string()))
|
||||
@@ -134,10 +147,10 @@ impl OidcClient {
|
||||
csrf_token.secret().clone(),
|
||||
(pkce_verifier, Instant::now() + Duration::from_secs(600)), // 10 minute expiry
|
||||
);
|
||||
(url, csrf_token)
|
||||
Ok((url, csrf_token))
|
||||
} else {
|
||||
// Confidential client - no PKCE needed
|
||||
auth_request.url()
|
||||
Ok(auth_request.url())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,8 +161,17 @@ impl OidcClient {
|
||||
}
|
||||
|
||||
pub async fn exchange_code(&self, code: &str, state: Option<&str>) -> Result<String> {
|
||||
let mut token_request = self
|
||||
.oauth_client
|
||||
// Build OAuth2 client on-demand - this is cheap (just struct construction, no I/O)
|
||||
let mut oauth_client = BasicClient::new(ClientId::new(self.client_id.clone()))
|
||||
.set_auth_uri(AuthUrl::new(self.discovery.authorization_endpoint.clone())?)
|
||||
.set_token_uri(TokenUrl::new(self.discovery.token_endpoint.clone())?)
|
||||
.set_redirect_uri(RedirectUrl::new(self.redirect_uri.clone())?);
|
||||
|
||||
if let Some(secret) = &self.client_secret {
|
||||
oauth_client = oauth_client.set_client_secret(ClientSecret::new(secret.clone()));
|
||||
}
|
||||
|
||||
let mut token_request = oauth_client
|
||||
.exchange_code(AuthorizationCode::new(code.to_string()));
|
||||
|
||||
// For public clients, retrieve and use the PKCE verifier
|
||||
@@ -166,8 +188,14 @@ impl OidcClient {
|
||||
}
|
||||
}
|
||||
|
||||
// Create HTTP client for token exchange with redirect disabled for SSRF protection
|
||||
let oauth_http_client = Client::builder()
|
||||
.redirect(reqwest::redirect::Policy::none())
|
||||
.build()
|
||||
.map_err(|e| anyhow!("Failed to build HTTP client: {}", e))?;
|
||||
|
||||
let token_result = token_request
|
||||
.request_async(async_http_client)
|
||||
.request_async(&oauth_http_client)
|
||||
.await
|
||||
.map_err(|e| anyhow!("Failed to exchange authorization code: {}", e))?;
|
||||
|
||||
@@ -206,4 +234,3 @@ pub struct OidcAuthResponse {
|
||||
pub email: Option<String>,
|
||||
pub is_new_user: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -197,8 +197,10 @@ async fn oidc_login(State(state): State<Arc<AppState>>) -> Result<Redirect, Stat
|
||||
.as_ref()
|
||||
.ok_or(StatusCode::BAD_REQUEST)?;
|
||||
|
||||
let (auth_url, _csrf_token) = oidc_client.get_authorization_url();
|
||||
|
||||
let (auth_url, _csrf_token) = oidc_client
|
||||
.get_authorization_url()
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
Ok(Redirect::to(auth_url.as_str()))
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use readur::services::file_service::FileService;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use tempfile::TempDir;
|
||||
use zip::write::FileOptions;
|
||||
use zip::write::SimpleFileOptions;
|
||||
use zip::{ZipWriter, CompressionMethod};
|
||||
|
||||
/// Helper function to create a proper DOCX file for testing
|
||||
@@ -13,7 +13,7 @@ fn create_test_docx(content: &str) -> Vec<u8> {
|
||||
let mut buffer = Vec::new();
|
||||
{
|
||||
let mut zip = ZipWriter::new(std::io::Cursor::new(&mut buffer));
|
||||
let options = FileOptions::default().compression_method(CompressionMethod::Deflated);
|
||||
let options = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
|
||||
|
||||
// Add [Content_Types].xml - More comprehensive structure
|
||||
zip.start_file("[Content_Types].xml", options).unwrap();
|
||||
|
||||
@@ -30,7 +30,7 @@ impl OfficeTestDocuments {
|
||||
let mut zip = zip::ZipWriter::new(file);
|
||||
|
||||
// Add [Content_Types].xml
|
||||
zip.start_file("[Content_Types].xml", zip::write::FileOptions::default())?;
|
||||
zip.start_file("[Content_Types].xml", zip::write::SimpleFileOptions::default())?;
|
||||
zip.write_all(br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
||||
@@ -39,14 +39,14 @@ impl OfficeTestDocuments {
|
||||
</Types>"#)?;
|
||||
|
||||
// Add _rels/.rels
|
||||
zip.start_file("_rels/.rels", zip::write::FileOptions::default())?;
|
||||
zip.start_file("_rels/.rels", zip::write::SimpleFileOptions::default())?;
|
||||
zip.write_all(br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
|
||||
</Relationships>"#)?;
|
||||
|
||||
// Add word/document.xml with the actual content
|
||||
zip.start_file("word/document.xml", zip::write::FileOptions::default())?;
|
||||
zip.start_file("word/document.xml", zip::write::SimpleFileOptions::default())?;
|
||||
let document_xml = format!(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||
<w:body>
|
||||
@@ -72,7 +72,7 @@ impl OfficeTestDocuments {
|
||||
let mut zip = zip::ZipWriter::new(file);
|
||||
|
||||
// Add [Content_Types].xml with shared strings support
|
||||
zip.start_file("[Content_Types].xml", zip::write::FileOptions::default())?;
|
||||
zip.start_file("[Content_Types].xml", zip::write::SimpleFileOptions::default())?;
|
||||
zip.write_all(br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
||||
@@ -83,14 +83,14 @@ impl OfficeTestDocuments {
|
||||
</Types>"#)?;
|
||||
|
||||
// Add _rels/.rels
|
||||
zip.start_file("_rels/.rels", zip::write::FileOptions::default())?;
|
||||
zip.start_file("_rels/.rels", zip::write::SimpleFileOptions::default())?;
|
||||
zip.write_all(br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
|
||||
</Relationships>"#)?;
|
||||
|
||||
// Add xl/workbook.xml
|
||||
zip.start_file("xl/workbook.xml", zip::write::FileOptions::default())?;
|
||||
zip.start_file("xl/workbook.xml", zip::write::SimpleFileOptions::default())?;
|
||||
zip.write_all(br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
||||
<sheets>
|
||||
@@ -99,7 +99,7 @@ impl OfficeTestDocuments {
|
||||
</workbook>"#)?;
|
||||
|
||||
// Add xl/_rels/workbook.xml.rels with shared strings relationship
|
||||
zip.start_file("xl/_rels/workbook.xml.rels", zip::write::FileOptions::default())?;
|
||||
zip.start_file("xl/_rels/workbook.xml.rels", zip::write::SimpleFileOptions::default())?;
|
||||
zip.write_all(br#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
|
||||
@@ -107,7 +107,7 @@ impl OfficeTestDocuments {
|
||||
</Relationships>"#)?;
|
||||
|
||||
// Add xl/sharedStrings.xml with the text content
|
||||
zip.start_file("xl/sharedStrings.xml", zip::write::FileOptions::default())?;
|
||||
zip.start_file("xl/sharedStrings.xml", zip::write::SimpleFileOptions::default())?;
|
||||
let mut shared_strings_xml = String::from(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="{count}" uniqueCount="{count}">"#);
|
||||
shared_strings_xml = shared_strings_xml.replace("{count}", &content.len().to_string());
|
||||
@@ -122,7 +122,7 @@ impl OfficeTestDocuments {
|
||||
zip.write_all(shared_strings_xml.as_bytes())?;
|
||||
|
||||
// Add xl/worksheets/sheet1.xml with references to shared strings
|
||||
zip.start_file("xl/worksheets/sheet1.xml", zip::write::FileOptions::default())?;
|
||||
zip.start_file("xl/worksheets/sheet1.xml", zip::write::SimpleFileOptions::default())?;
|
||||
let mut worksheet_xml = String::from(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
||||
<sheetData>"#);
|
||||
|
||||
@@ -81,7 +81,7 @@ async fn test_get_authorization_url() {
|
||||
let config = create_test_config_with_oidc(&mock_server.uri());
|
||||
let oidc_client = OidcClient::new(&config).await.unwrap();
|
||||
|
||||
let (auth_url, csrf_token) = oidc_client.get_authorization_url();
|
||||
let (auth_url, csrf_token) = oidc_client.get_authorization_url().unwrap();
|
||||
|
||||
assert!(auth_url.to_string().contains("/auth"));
|
||||
assert!(auth_url.to_string().contains("client_id=test-client-id"));
|
||||
|
||||
Reference in New Issue
Block a user