From a8f4690c11d31c901e3376308e50a54824f4d04f Mon Sep 17 00:00:00 2001 From: Vika Date: Tue, 19 Jul 2022 04:39:48 +0300 Subject: kittybox-indieauth: improve docs and the Error type `kittybox_indieauth::Error` now represents errors in the IndieAuth process itself. `IndieAuthError` got renamed to `ResourceErrorKind` to reflect errors that a resource server (i.e. IndieAuth consumer) might return to a client who somehow didn't authorize themselves properly. --- kittybox-rs/src/indieauth/mod.rs | 74 ++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 14 deletions(-) (limited to 'kittybox-rs/src') diff --git a/kittybox-rs/src/indieauth/mod.rs b/kittybox-rs/src/indieauth/mod.rs index a985d16..8100b2a 100644 --- a/kittybox-rs/src/indieauth/mod.rs +++ b/kittybox-rs/src/indieauth/mod.rs @@ -6,11 +6,11 @@ use axum::{ }; use kittybox_indieauth::{ Metadata, IntrospectionEndpointAuthMethod, RevocationEndpointAuthMethod, - Scope, Scopes, PKCEMethod, + Scope, Scopes, PKCEMethod, Error, ErrorKind, ResponseType, RequestMaybeAuthorizationEndpoint, AuthorizationRequest, AuthorizationResponse, GrantType, GrantRequest, GrantResponse, Profile, - TokenIntrospectionRequest, TokenIntrospectionResponse, TokenRevocationRequest, IndieAuthError, TokenData + TokenIntrospectionRequest, TokenIntrospectionResponse, TokenRevocationRequest, TokenData }; pub mod backend; @@ -109,14 +109,23 @@ async fn authorization_endpoint_post( GrantRequest::AuthorizationCode { code, client_id, redirect_uri, code_verifier } => { let request: AuthorizationRequest = match backend.get_code(&code).await { Ok(Some(request)) => request, - Ok(None) => return Json(IndieAuthError::InvalidRequest).into_response(), + Ok(None) => return Json(Error { + kind: ErrorKind::InvalidGrant, + msg: Some("The provided authorization code is invalid.".to_string()), + error_uri: None + }).into_response(), Err(err) => { tracing::error!("Error retrieving auth request: {}", err); return StatusCode::INTERNAL_SERVER_ERROR.into_response(); } }; if !request.code_challenge.verify(code_verifier) { - return Json(IndieAuthError::InvalidRequest).into_response() + return Json(Error { + kind: ErrorKind::InvalidGrant, + msg: Some("The PKCE challenge failed.".to_string()), + // are RFCs considered human-readable? 😝 + error_uri: "https://datatracker.ietf.org/doc/html/rfc7636#section-4.6".parse().ok() + }).into_response() } let profile = if request.scope .map(|s| s.has(&Scope::Profile)) @@ -130,7 +139,11 @@ async fn authorization_endpoint_post( Json(GrantResponse::ProfileUrl { me, profile }).into_response() }, - _ => Json(IndieAuthError::InvalidRequest).into_response() + _ => Json(Error { + kind: ErrorKind::InvalidGrant, + msg: Some("The provided grant_type is unusable on this endpoint.".to_string()), + error_uri: "https://indieauth.spec.indieweb.org/#redeeming-the-authorization-code".parse().ok() + }).into_response() } } } @@ -182,7 +195,11 @@ async fn token_endpoint_post( // TODO verify PKCE challenge using grant.code_verifier let request: AuthorizationRequest = match backend.get_code(&code).await { Ok(Some(request)) => request, - Ok(None) => return Json(IndieAuthError::InvalidRequest).into_response(), + Ok(None) => return Json(Error { + kind: ErrorKind::InvalidGrant, + msg: Some("The provided authorization code is invalid.".to_string()), + error_uri: None + }).into_response(), Err(err) => { tracing::error!("Error retrieving auth request: {}", err); return StatusCode::INTERNAL_SERVER_ERROR.into_response(); @@ -192,9 +209,21 @@ async fn token_endpoint_post( let me: url::Url = format!("https://{}/", host).parse().unwrap(); let scope = if let Some(scope) = request.scope { scope } else { - return Json(IndieAuthError::InvalidRequest).into_response(); + return Json(Error { + kind: ErrorKind::InvalidScope, + msg: Some("Tokens cannot be issued if no scopes are requested.".to_string()), + error_uri: "https://indieauth.spec.indieweb.org/#access-token-response".parse().ok() + }).into_response(); }; + if !request.code_challenge.verify(code_verifier) { + return Json(Error { + kind: ErrorKind::InvalidGrant, + msg: Some("The PKCE challenge failed.".to_string()), + error_uri: "https://datatracker.ietf.org/doc/html/rfc7636#section-4.6".parse().ok() + }).into_response(); + } + let profile = if scope.has(&Scope::Profile) { Some(todo!()) } else { @@ -232,7 +261,11 @@ async fn token_endpoint_post( GrantRequest::RefreshToken { refresh_token, client_id, scope } => { let data = match backend.get_refresh_token(&refresh_token).await { Ok(Some(token)) => token, - Ok(None) => return Json(IndieAuthError::InvalidToken).into_response(), + Ok(None) => return Json(Error { + kind: ErrorKind::InvalidGrant, + msg: Some("This refresh token is not valid.".to_string()), + error_uri: None + }).into_response(), Err(err) => { tracing::error!("Error retrieving refresh token: {}", err); return StatusCode::INTERNAL_SERVER_ERROR.into_response() @@ -240,15 +273,28 @@ async fn token_endpoint_post( }; if data.client_id != client_id { - return Json(IndieAuthError::InvalidRequest).into_response(); + return Json(Error { + kind: ErrorKind::InvalidGrant, + msg: Some("This refresh token is not yours.".to_string()), + error_uri: None + }).into_response(); } - let scope = if let Some(scope) = scope { scope } else { - return Json(IndieAuthError::InvalidRequest).into_response(); + let scope = if let Some(scope) = scope { + if !data.scope.has_all(scope.as_ref()) { + return Json(Error { + kind: ErrorKind::InvalidScope, + msg: Some("You can't request additional scopes through the refresh token grant.".to_string()), + error_uri: None + }).into_response(); + } + + scope + } else { + // Note: check skipped because of redundancy (comparing a scope list with itself) + data.scope }; - if !data.scope.has_all(scope.as_ref()) { - return Json(IndieAuthError::InsufficientScope).into_response(); - } + let profile = if scope.has(&Scope::Profile) { Some(todo!()) -- cgit 1.4.1