mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-09-11 04:36:05 +00:00
Frontend on /app path for easy custom homepage
This commit is contained in:
+25
-2
@@ -130,14 +130,37 @@ pub async fn proxy_handler(
|
||||
Err(e) => {
|
||||
warn!("Token validation failed: {:?}", e);
|
||||
if matches!(e, crate::auth::TokenValidationError::TokenExpired) {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
let auth_header_str = headers
|
||||
.get("Authorization")
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.unwrap_or("");
|
||||
let is_dpop = auth_header_str
|
||||
.trim()
|
||||
.get(..5)
|
||||
.is_some_and(|s| s.eq_ignore_ascii_case("dpop "));
|
||||
let scheme = if is_dpop { "DPoP" } else { "Bearer" };
|
||||
let www_auth = format!(
|
||||
"{} error=\"invalid_token\", error_description=\"Token has expired\"",
|
||||
scheme
|
||||
);
|
||||
let mut response = (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(json!({
|
||||
"error": "ExpiredToken",
|
||||
"message": "Token has expired"
|
||||
})),
|
||||
)
|
||||
.into_response();
|
||||
response
|
||||
.headers_mut()
|
||||
.insert("WWW-Authenticate", www_auth.parse().unwrap());
|
||||
if is_dpop {
|
||||
let nonce = crate::oauth::verify::generate_dpop_nonce();
|
||||
response
|
||||
.headers_mut()
|
||||
.insert("DPoP-Nonce", nonce.parse().unwrap());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,18 @@ pub async fn delete_record(
|
||||
axum::extract::OriginalUri(uri): axum::extract::OriginalUri,
|
||||
Json(input): Json<DeleteRecordInput>,
|
||||
) -> Response {
|
||||
let auth =
|
||||
match prepare_repo_write(&state, &headers, &input.repo, "POST", &uri.to_string()).await {
|
||||
Ok(res) => res,
|
||||
Err(err_res) => return err_res,
|
||||
};
|
||||
let auth = match prepare_repo_write(
|
||||
&state,
|
||||
&headers,
|
||||
&input.repo,
|
||||
"POST",
|
||||
&crate::util::build_full_url(&uri.to_string()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(res) => res,
|
||||
Err(err_res) => return err_res,
|
||||
};
|
||||
|
||||
if let Err(e) = crate::auth::scope_check::check_repo_scope(
|
||||
auth.is_oauth,
|
||||
|
||||
@@ -89,11 +89,28 @@ pub async fn prepare_repo_write(
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
(
|
||||
tracing::warn!(error = ?e, is_dpop = extracted.is_dpop, "Token validation failed in prepare_repo_write");
|
||||
let mut response = (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(json!({"error": e.to_string()})),
|
||||
)
|
||||
.into_response()
|
||||
.into_response();
|
||||
if matches!(e, crate::auth::TokenValidationError::TokenExpired) {
|
||||
let scheme = if extracted.is_dpop { "DPoP" } else { "Bearer" };
|
||||
let www_auth = format!(
|
||||
"{} error=\"invalid_token\", error_description=\"Token has expired\"",
|
||||
scheme
|
||||
);
|
||||
response.headers_mut().insert(
|
||||
"WWW-Authenticate",
|
||||
www_auth.parse().unwrap(),
|
||||
);
|
||||
if extracted.is_dpop {
|
||||
let nonce = crate::oauth::verify::generate_dpop_nonce();
|
||||
response.headers_mut().insert("DPoP-Nonce", nonce.parse().unwrap());
|
||||
}
|
||||
}
|
||||
response
|
||||
})?;
|
||||
if repo_did != auth_user.did {
|
||||
return Err((
|
||||
@@ -219,11 +236,18 @@ pub async fn create_record(
|
||||
axum::extract::OriginalUri(uri): axum::extract::OriginalUri,
|
||||
Json(input): Json<CreateRecordInput>,
|
||||
) -> Response {
|
||||
let auth =
|
||||
match prepare_repo_write(&state, &headers, &input.repo, "POST", &uri.to_string()).await {
|
||||
Ok(res) => res,
|
||||
Err(err_res) => return err_res,
|
||||
};
|
||||
let auth = match prepare_repo_write(
|
||||
&state,
|
||||
&headers,
|
||||
&input.repo,
|
||||
"POST",
|
||||
&crate::util::build_full_url(&uri.to_string()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(res) => res,
|
||||
Err(err_res) => return err_res,
|
||||
};
|
||||
|
||||
if let Err(e) = crate::auth::scope_check::check_repo_scope(
|
||||
auth.is_oauth,
|
||||
@@ -459,11 +483,18 @@ pub async fn put_record(
|
||||
axum::extract::OriginalUri(uri): axum::extract::OriginalUri,
|
||||
Json(input): Json<PutRecordInput>,
|
||||
) -> Response {
|
||||
let auth =
|
||||
match prepare_repo_write(&state, &headers, &input.repo, "POST", &uri.to_string()).await {
|
||||
Ok(res) => res,
|
||||
Err(err_res) => return err_res,
|
||||
};
|
||||
let auth = match prepare_repo_write(
|
||||
&state,
|
||||
&headers,
|
||||
&input.repo,
|
||||
"POST",
|
||||
&crate::util::build_full_url(&uri.to_string()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(res) => res,
|
||||
Err(err_res) => return err_res,
|
||||
};
|
||||
|
||||
if let Err(e) = crate::auth::scope_check::check_repo_scope(
|
||||
auth.is_oauth,
|
||||
|
||||
@@ -1257,7 +1257,7 @@ pub async fn request_passkey_recovery(
|
||||
|
||||
let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
|
||||
let recovery_url = format!(
|
||||
"https://{}/#/recover-passkey?did={}&token={}",
|
||||
"https://{}/app/recover-passkey?did={}&token={}",
|
||||
hostname,
|
||||
urlencoding::encode(&user.did),
|
||||
urlencoding::encode(&recovery_token)
|
||||
|
||||
@@ -396,6 +396,7 @@ pub async fn validate_token_with_dpop(
|
||||
controller_did: None,
|
||||
})
|
||||
}
|
||||
Err(crate::oauth::OAuthError::ExpiredToken(_)) => Err(TokenValidationError::TokenExpired),
|
||||
Err(_) => Err(TokenValidationError::AuthenticationFailed),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,9 +352,9 @@ pub async fn enqueue_email_update(
|
||||
let strings = get_strings(&prefs.locale);
|
||||
let encoded_email = urlencoding::encode(new_email);
|
||||
let encoded_token = urlencoding::encode(code);
|
||||
let verify_page = format!("https://{}/#/verify", hostname);
|
||||
let verify_page = format!("https://{}/app/verify", hostname);
|
||||
let verify_link = format!(
|
||||
"https://{}/#/verify?token={}&identifier={}",
|
||||
"https://{}/app/verify?token={}&identifier={}",
|
||||
hostname, encoded_token, encoded_email
|
||||
);
|
||||
let body = format_message(
|
||||
@@ -389,9 +389,9 @@ pub async fn enqueue_email_update_token(
|
||||
let prefs = get_user_comms_prefs(db, user_id).await?;
|
||||
let strings = get_strings(&prefs.locale);
|
||||
let current_email = prefs.email.clone().unwrap_or_default();
|
||||
let verify_page = format!("https://{}/#/verify?type=email-update", hostname);
|
||||
let verify_page = format!("https://{}/app/verify?type=email-update", hostname);
|
||||
let verify_link = format!(
|
||||
"https://{}/#/verify?type=email-update&token={}",
|
||||
"https://{}/app/verify?type=email-update&token={}",
|
||||
hostname,
|
||||
urlencoding::encode(code)
|
||||
);
|
||||
@@ -556,9 +556,9 @@ pub async fn enqueue_signup_verification(
|
||||
let encoded_email = urlencoding::encode(recipient);
|
||||
let encoded_token = urlencoding::encode(code);
|
||||
(
|
||||
format!("https://{}/#/verify", hostname),
|
||||
format!("https://{}/app/verify", hostname),
|
||||
format!(
|
||||
"https://{}/#/verify?token={}&identifier={}",
|
||||
"https://{}/app/verify?token={}&identifier={}",
|
||||
hostname, encoded_token, encoded_email
|
||||
),
|
||||
)
|
||||
@@ -606,9 +606,9 @@ pub async fn enqueue_migration_verification(
|
||||
let strings = get_strings(&prefs.locale);
|
||||
let encoded_email = urlencoding::encode(email);
|
||||
let encoded_token = urlencoding::encode(token);
|
||||
let verify_page = format!("https://{}/#/verify", hostname);
|
||||
let verify_page = format!("https://{}/app/verify", hostname);
|
||||
let verify_link = format!(
|
||||
"https://{}/#/verify?token={}&identifier={}",
|
||||
"https://{}/app/verify?token={}&identifier={}",
|
||||
hostname, encoded_token, encoded_email
|
||||
);
|
||||
let body = format_message(
|
||||
|
||||
+17
-2
@@ -657,8 +657,23 @@ pub fn app(state: AppState) -> Router {
|
||||
.exists()
|
||||
{
|
||||
let index_path = format!("{}/index.html", frontend_dir);
|
||||
let serve_dir = ServeDir::new(&frontend_dir).not_found_service(ServeFile::new(index_path));
|
||||
router.fallback_service(serve_dir)
|
||||
let homepage_path = format!("{}/homepage.html", frontend_dir);
|
||||
|
||||
let homepage_exists = std::path::Path::new(&homepage_path).exists();
|
||||
let homepage_file = if homepage_exists {
|
||||
homepage_path
|
||||
} else {
|
||||
index_path.clone()
|
||||
};
|
||||
|
||||
let spa_router = Router::new().fallback_service(ServeFile::new(&index_path));
|
||||
|
||||
let serve_dir = ServeDir::new(&frontend_dir).not_found_service(ServeFile::new(&index_path));
|
||||
|
||||
router
|
||||
.route_service("/", ServeFile::new(&homepage_file))
|
||||
.nest("/app", spa_router)
|
||||
.fallback_service(serve_dir)
|
||||
} else {
|
||||
router
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ fn redirect_see_other(uri: &str) -> Response {
|
||||
|
||||
fn redirect_to_frontend_error(error: &str, description: &str) -> Response {
|
||||
redirect_see_other(&format!(
|
||||
"/#/oauth/error?error={}&error_description={}",
|
||||
"/app/oauth/error?error={}&error_description={}",
|
||||
url_encode(error),
|
||||
url_encode(description)
|
||||
))
|
||||
@@ -236,7 +236,7 @@ pub async fn authorize_get(
|
||||
if is_delegated && !has_password {
|
||||
tracing::info!("Redirecting to delegation auth");
|
||||
return redirect_see_other(&format!(
|
||||
"/#/oauth/delegation?request_uri={}&delegated_did={}",
|
||||
"/app/oauth/delegation?request_uri={}&delegated_did={}",
|
||||
url_encode(&request_uri),
|
||||
url_encode(&user.did)
|
||||
));
|
||||
@@ -259,12 +259,12 @@ pub async fn authorize_get(
|
||||
&& !accounts.is_empty()
|
||||
{
|
||||
return redirect_see_other(&format!(
|
||||
"/#/oauth/accounts?request_uri={}",
|
||||
"/app/oauth/accounts?request_uri={}",
|
||||
url_encode(&request_uri)
|
||||
));
|
||||
}
|
||||
redirect_see_other(&format!(
|
||||
"/#/oauth/login?request_uri={}",
|
||||
"/app/oauth/login?request_uri={}",
|
||||
url_encode(&request_uri)
|
||||
))
|
||||
}
|
||||
@@ -466,7 +466,7 @@ pub async fn authorize_post(
|
||||
.into_response();
|
||||
}
|
||||
redirect_see_other(&format!(
|
||||
"/#/oauth/login?request_uri={}&error={}",
|
||||
"/app/oauth/login?request_uri={}&error={}",
|
||||
url_encode(&form.request_uri),
|
||||
url_encode(error_msg)
|
||||
))
|
||||
@@ -539,7 +539,7 @@ pub async fn authorize_post(
|
||||
return show_login_error("An error occurred. Please try again.", json_response);
|
||||
}
|
||||
let redirect_url = format!(
|
||||
"/#/oauth/delegation?request_uri={}&delegated_did={}",
|
||||
"/app/oauth/delegation?request_uri={}&delegated_did={}",
|
||||
url_encode(&form.request_uri),
|
||||
url_encode(&user.did)
|
||||
);
|
||||
@@ -565,7 +565,7 @@ pub async fn authorize_post(
|
||||
return show_login_error("An error occurred. Please try again.", json_response);
|
||||
}
|
||||
let redirect_url = format!(
|
||||
"/#/oauth/passkey?request_uri={}",
|
||||
"/app/oauth/passkey?request_uri={}",
|
||||
url_encode(&form.request_uri)
|
||||
);
|
||||
if json_response {
|
||||
@@ -620,7 +620,7 @@ pub async fn authorize_post(
|
||||
.into_response();
|
||||
}
|
||||
return redirect_see_other(&format!(
|
||||
"/#/oauth/totp?request_uri={}",
|
||||
"/app/oauth/totp?request_uri={}",
|
||||
url_encode(&form.request_uri)
|
||||
));
|
||||
}
|
||||
@@ -649,7 +649,7 @@ pub async fn authorize_post(
|
||||
.into_response();
|
||||
}
|
||||
return redirect_see_other(&format!(
|
||||
"/#/oauth/2fa?request_uri={}&channel={}",
|
||||
"/app/oauth/2fa?request_uri={}&channel={}",
|
||||
url_encode(&form.request_uri),
|
||||
url_encode(channel_name)
|
||||
));
|
||||
@@ -713,7 +713,7 @@ pub async fn authorize_post(
|
||||
.unwrap_or(true);
|
||||
if needs_consent {
|
||||
let consent_url = format!(
|
||||
"/#/oauth/consent?request_uri={}",
|
||||
"/app/oauth/consent?request_uri={}",
|
||||
url_encode(&form.request_uri)
|
||||
);
|
||||
if json_response {
|
||||
@@ -1103,7 +1103,7 @@ pub async fn authorize_2fa_get(
|
||||
};
|
||||
let channel = query.channel.as_deref().unwrap_or("email");
|
||||
redirect_see_other(&format!(
|
||||
"/#/oauth/2fa?request_uri={}&channel={}",
|
||||
"/app/oauth/2fa?request_uri={}&channel={}",
|
||||
url_encode(&query.request_uri),
|
||||
url_encode(channel)
|
||||
))
|
||||
@@ -1464,6 +1464,7 @@ pub async fn consent_post(
|
||||
|| s.starts_with("blob:")
|
||||
|| s.starts_with("rpc:")
|
||||
|| s.starts_with("account:")
|
||||
|| s.starts_with("identity:")
|
||||
|| s.starts_with("include:")
|
||||
});
|
||||
if !has_valid_scope {
|
||||
@@ -1708,7 +1709,7 @@ pub async fn authorize_2fa_post(
|
||||
.unwrap_or(true);
|
||||
if needs_consent {
|
||||
let consent_url = format!(
|
||||
"/#/oauth/consent?request_uri={}",
|
||||
"/app/oauth/consent?request_uri={}",
|
||||
url_encode(&form.request_uri)
|
||||
);
|
||||
return Json(serde_json::json!({"redirect_uri": consent_url})).into_response();
|
||||
@@ -2345,7 +2346,7 @@ pub async fn passkey_finish(
|
||||
|
||||
if needs_consent {
|
||||
let consent_url = format!(
|
||||
"/#/oauth/consent?request_uri={}",
|
||||
"/app/oauth/consent?request_uri={}",
|
||||
url_encode(&form.request_uri)
|
||||
);
|
||||
return Json(serde_json::json!({"redirect_uri": consent_url})).into_response();
|
||||
@@ -2729,7 +2730,7 @@ pub async fn authorize_passkey_finish(
|
||||
}
|
||||
let channel_name = channel_display_name(user.preferred_comms_channel);
|
||||
let redirect_url = format!(
|
||||
"/#/oauth/2fa?request_uri={}&channel={}",
|
||||
"/app/oauth/2fa?request_uri={}&channel={}",
|
||||
url_encode(&form.request_uri),
|
||||
url_encode(channel_name)
|
||||
);
|
||||
@@ -2754,7 +2755,7 @@ pub async fn authorize_passkey_finish(
|
||||
}
|
||||
|
||||
let redirect_url = format!(
|
||||
"/#/oauth/consent?request_uri={}",
|
||||
"/app/oauth/consent?request_uri={}",
|
||||
url_encode(&form.request_uri)
|
||||
);
|
||||
(
|
||||
|
||||
@@ -206,7 +206,7 @@ pub async fn delegation_auth(
|
||||
success: true,
|
||||
needs_totp: Some(true),
|
||||
redirect_uri: Some(format!(
|
||||
"/#/oauth/delegation-totp?request_uri={}",
|
||||
"/app/oauth/delegation-totp?request_uri={}",
|
||||
urlencoding::encode(&form.request_uri)
|
||||
)),
|
||||
error: None,
|
||||
@@ -239,7 +239,7 @@ pub async fn delegation_auth(
|
||||
success: true,
|
||||
needs_totp: None,
|
||||
redirect_uri: Some(format!(
|
||||
"/#/oauth/consent?request_uri={}",
|
||||
"/app/oauth/consent?request_uri={}",
|
||||
urlencoding::encode(&form.request_uri)
|
||||
)),
|
||||
error: None,
|
||||
@@ -374,7 +374,7 @@ pub async fn delegation_totp_verify(
|
||||
success: true,
|
||||
needs_totp: None,
|
||||
redirect_uri: Some(format!(
|
||||
"/#/oauth/consent?request_uri={}",
|
||||
"/app/oauth/consent?request_uri={}",
|
||||
urlencoding::encode(&form.request_uri)
|
||||
)),
|
||||
error: None,
|
||||
|
||||
@@ -168,8 +168,8 @@ pub async fn frontend_client_metadata(
|
||||
client_name: "PDS Account Manager".to_string(),
|
||||
client_uri: base_url.clone(),
|
||||
redirect_uris: vec![
|
||||
format!("{}/", base_url),
|
||||
format!("{}/migrate", base_url),
|
||||
format!("{}/app/", base_url),
|
||||
format!("{}/app/migrate", base_url),
|
||||
],
|
||||
grant_types: vec![
|
||||
"authorization_code".to_string(),
|
||||
|
||||
@@ -94,9 +94,10 @@ pub async fn handle_authorization_code_grant(
|
||||
));
|
||||
}
|
||||
Some(result.jkt)
|
||||
} else if auth_request.parameters.dpop_jkt.is_some() {
|
||||
return Err(OAuthError::InvalidRequest(
|
||||
"DPoP proof required for this authorization".to_string(),
|
||||
} else if auth_request.parameters.dpop_jkt.is_some() || client_metadata.requires_dpop() {
|
||||
return Err(OAuthError::UseDpopNonce(
|
||||
crate::oauth::dpop::DPoPVerifier::new(AuthConfig::get().dpop_secret().as_bytes())
|
||||
.generate_nonce(),
|
||||
));
|
||||
} else {
|
||||
None
|
||||
@@ -138,6 +139,8 @@ pub async fn handle_authorization_code_grant(
|
||||
} else {
|
||||
REFRESH_TOKEN_EXPIRY_DAYS_CONFIDENTIAL
|
||||
};
|
||||
let mut stored_parameters = auth_request.parameters.clone();
|
||||
stored_parameters.dpop_jkt = dpop_jkt.clone();
|
||||
let token_data = TokenData {
|
||||
did: did.clone(),
|
||||
token_id: token_id.0.clone(),
|
||||
@@ -147,7 +150,7 @@ pub async fn handle_authorization_code_grant(
|
||||
client_id: auth_request.client_id.clone(),
|
||||
client_auth: stored_client_auth,
|
||||
device_id: auth_request.device_id,
|
||||
parameters: auth_request.parameters.clone(),
|
||||
parameters: stored_parameters,
|
||||
details: None,
|
||||
code: None,
|
||||
current_refresh_token: Some(refresh_token.0.clone()),
|
||||
|
||||
+73
-7
@@ -42,21 +42,38 @@ pub async fn verify_oauth_access_token(
|
||||
http_uri: &str,
|
||||
) -> Result<VerifyResult, OAuthError> {
|
||||
let token_info = extract_oauth_token_info(access_token)?;
|
||||
tracing::debug!(
|
||||
token_id = %token_info.token_id,
|
||||
has_dpop_proof = dpop_proof.is_some(),
|
||||
"Verifying OAuth access token"
|
||||
);
|
||||
let token_data = db::get_token_by_id(pool, &token_info.token_id)
|
||||
.await?
|
||||
.ok_or_else(|| OAuthError::InvalidToken("Token not found or revoked".to_string()))?;
|
||||
.ok_or_else(|| {
|
||||
tracing::warn!(token_id = %token_info.token_id, "Token not found in database");
|
||||
OAuthError::InvalidToken("Token not found or revoked".to_string())
|
||||
})?;
|
||||
let now = chrono::Utc::now();
|
||||
if token_data.expires_at < now {
|
||||
return Err(OAuthError::InvalidToken("Token has expired".to_string()));
|
||||
return Err(OAuthError::ExpiredToken(
|
||||
"Token session has expired".to_string(),
|
||||
));
|
||||
}
|
||||
if let Some(expected_jkt) = &token_data.parameters.dpop_jkt {
|
||||
let proof = dpop_proof
|
||||
.ok_or_else(|| OAuthError::UseDpopNonce("DPoP proof required".to_string()))?;
|
||||
tracing::debug!(expected_jkt = %expected_jkt, "Token requires DPoP");
|
||||
let proof = dpop_proof.ok_or_else(|| {
|
||||
tracing::warn!("DPoP proof required but not provided");
|
||||
OAuthError::UseDpopNonce("DPoP proof required".to_string())
|
||||
})?;
|
||||
let config = AuthConfig::get();
|
||||
let verifier = DPoPVerifier::new(config.dpop_secret().as_bytes());
|
||||
let access_token_hash = compute_ath(access_token);
|
||||
let result =
|
||||
verifier.verify_proof(proof, http_method, http_uri, Some(&access_token_hash))?;
|
||||
let result = verifier
|
||||
.verify_proof(proof, http_method, http_uri, Some(&access_token_hash))
|
||||
.map_err(|e| {
|
||||
tracing::warn!(error = ?e, http_method = %http_method, http_uri = %http_uri, "DPoP proof verification failed");
|
||||
e
|
||||
})?;
|
||||
if !db::check_and_record_dpop_jti(pool, &result.jti).await? {
|
||||
return Err(OAuthError::InvalidDpopProof(
|
||||
"DPoP proof has already been used".to_string(),
|
||||
@@ -123,7 +140,7 @@ pub fn extract_oauth_token_info(token: &str) -> Result<OAuthTokenInfo, OAuthErro
|
||||
.ok_or_else(|| OAuthError::InvalidToken("Missing exp claim".to_string()))?;
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
if exp < now {
|
||||
return Err(OAuthError::InvalidToken("Token has expired".to_string()));
|
||||
return Err(OAuthError::ExpiredToken("Token has expired".to_string()));
|
||||
}
|
||||
let token_id = payload
|
||||
.get("jti")
|
||||
@@ -191,6 +208,7 @@ pub struct OAuthAuthError {
|
||||
pub error: String,
|
||||
pub message: String,
|
||||
pub dpop_nonce: Option<String>,
|
||||
pub www_authenticate: Option<String>,
|
||||
}
|
||||
|
||||
impl IntoResponse for OAuthAuthError {
|
||||
@@ -208,6 +226,11 @@ impl IntoResponse for OAuthAuthError {
|
||||
.headers_mut()
|
||||
.insert("DPoP-Nonce", nonce.parse().unwrap());
|
||||
}
|
||||
if let Some(www_auth) = self.www_authenticate {
|
||||
response
|
||||
.headers_mut()
|
||||
.insert("WWW-Authenticate", www_auth.parse().unwrap());
|
||||
}
|
||||
response
|
||||
}
|
||||
}
|
||||
@@ -228,6 +251,7 @@ impl FromRequestParts<AppState> for OAuthUser {
|
||||
error: "AuthenticationRequired".to_string(),
|
||||
message: "Authorization header required".to_string(),
|
||||
dpop_nonce: None,
|
||||
www_authenticate: None,
|
||||
})?;
|
||||
let auth_header_trimmed = auth_header.trim();
|
||||
let (token, is_dpop_token) = if auth_header_trimmed.len() >= 7
|
||||
@@ -244,6 +268,7 @@ impl FromRequestParts<AppState> for OAuthUser {
|
||||
error: "InvalidRequest".to_string(),
|
||||
message: "Invalid authorization scheme".to_string(),
|
||||
dpop_nonce: None,
|
||||
www_authenticate: None,
|
||||
});
|
||||
};
|
||||
let dpop_proof = parts.headers.get("DPoP").and_then(|v| v.to_str().ok());
|
||||
@@ -275,6 +300,7 @@ impl FromRequestParts<AppState> for OAuthUser {
|
||||
error: "use_dpop_nonce".to_string(),
|
||||
message: "DPoP nonce required".to_string(),
|
||||
dpop_nonce: Some(nonce),
|
||||
www_authenticate: Some("DPoP error=\"use_dpop_nonce\"".to_string()),
|
||||
}),
|
||||
Err(OAuthError::InvalidDpopProof(msg)) => {
|
||||
let nonce = generate_dpop_nonce();
|
||||
@@ -283,6 +309,45 @@ impl FromRequestParts<AppState> for OAuthUser {
|
||||
error: "invalid_dpop_proof".to_string(),
|
||||
message: msg,
|
||||
dpop_nonce: Some(nonce),
|
||||
www_authenticate: None,
|
||||
})
|
||||
}
|
||||
Err(OAuthError::ExpiredToken(msg)) => {
|
||||
let nonce = if is_dpop_token {
|
||||
Some(generate_dpop_nonce())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let scheme = if is_dpop_token { "DPoP" } else { "Bearer" };
|
||||
let www_auth = format!(
|
||||
"{} error=\"invalid_token\", error_description=\"{}\"",
|
||||
scheme, msg
|
||||
);
|
||||
Err(OAuthAuthError {
|
||||
status: StatusCode::UNAUTHORIZED,
|
||||
error: "ExpiredToken".to_string(),
|
||||
message: msg,
|
||||
dpop_nonce: nonce,
|
||||
www_authenticate: Some(www_auth),
|
||||
})
|
||||
}
|
||||
Err(OAuthError::InvalidToken(msg)) => {
|
||||
let nonce = if is_dpop_token {
|
||||
Some(generate_dpop_nonce())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let scheme = if is_dpop_token { "DPoP" } else { "Bearer" };
|
||||
let www_auth = format!(
|
||||
"{} error=\"invalid_token\", error_description=\"{}\"",
|
||||
scheme, msg
|
||||
);
|
||||
Err(OAuthAuthError {
|
||||
status: StatusCode::UNAUTHORIZED,
|
||||
error: "InvalidToken".to_string(),
|
||||
message: msg,
|
||||
dpop_nonce: nonce,
|
||||
www_authenticate: Some(www_auth),
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -296,6 +361,7 @@ impl FromRequestParts<AppState> for OAuthUser {
|
||||
error: "AuthenticationFailed".to_string(),
|
||||
message: format!("{:?}", e),
|
||||
dpop_nonce: nonce,
|
||||
www_authenticate: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user