feat: add IPTV export functionality with M3U and XMLTV generation, including UI components for export dialog

This commit is contained in:
2026-03-14 02:11:20 +01:00
parent 66ec0c51c0
commit e610c23fea
9 changed files with 462 additions and 32 deletions

View File

@@ -39,6 +39,9 @@ impl FromRequestParts<AppState> for CurrentUser {
}
/// Optional current user — returns None instead of error when auth is missing/invalid.
///
/// Checks `Authorization: Bearer <token>` first; falls back to `?token=<jwt>` query param
/// so IPTV clients and direct stream links work without custom headers.
pub struct OptionalCurrentUser(pub Option<User>);
impl FromRequestParts<AppState> for OptionalCurrentUser {
@@ -50,7 +53,21 @@ impl FromRequestParts<AppState> for OptionalCurrentUser {
) -> Result<Self, Self::Rejection> {
#[cfg(feature = "auth-jwt")]
{
return Ok(OptionalCurrentUser(try_jwt_auth(parts, state).await.ok()));
// Try Authorization header first
if let Ok(user) = try_jwt_auth(parts, state).await {
return Ok(OptionalCurrentUser(Some(user)));
}
// Fall back to ?token= query param
let query_token = parts.uri.query().and_then(|q| {
q.split('&')
.find(|seg| seg.starts_with("token="))
.map(|seg| seg[6..].to_owned())
});
if let Some(token) = query_token {
let user = validate_jwt_token(&token, state).await.ok();
return Ok(OptionalCurrentUser(user));
}
return Ok(OptionalCurrentUser(None));
}
#[cfg(not(feature = "auth-jwt"))]
@@ -61,7 +78,7 @@ impl FromRequestParts<AppState> for OptionalCurrentUser {
}
}
/// Authenticate using JWT Bearer token
/// Authenticate using JWT Bearer token from the `Authorization` header.
#[cfg(feature = "auth-jwt")]
async fn try_jwt_auth(parts: &mut Parts, state: &AppState) -> Result<User, ApiError> {
use axum::http::header::AUTHORIZATION;
@@ -79,6 +96,12 @@ async fn try_jwt_auth(parts: &mut Parts, state: &AppState) -> Result<User, ApiEr
ApiError::Unauthorized("Authorization header must use Bearer scheme".to_string())
})?;
validate_jwt_token(token, state).await
}
/// Validate a raw JWT string and return the corresponding `User`.
#[cfg(feature = "auth-jwt")]
pub(crate) async fn validate_jwt_token(token: &str, state: &AppState) -> Result<User, ApiError> {
let validator = state
.jwt_validator
.as_ref()