about summary refs log tree commit diff
path: root/kittybox-rs/src
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2022-07-19 04:39:48 +0300
committerVika <vika@fireburn.ru>2022-07-19 04:39:48 +0300
commita8f4690c11d31c901e3376308e50a54824f4d04f (patch)
treeae076cdb907a2002fe57d5653b94faae7334c919 /kittybox-rs/src
parent6fdb4c25c4557aa0b59b5c4ba348ecab502cb57e (diff)
downloadkittybox-a8f4690c11d31c901e3376308e50a54824f4d04f.tar.zst
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.
Diffstat (limited to 'kittybox-rs/src')
-rw-r--r--kittybox-rs/src/indieauth/mod.rs74
1 files changed, 60 insertions, 14 deletions
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<A: AuthBackend>(
             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<A: AuthBackend>(
 
                 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<A: AuthBackend>(
             // 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<A: AuthBackend>(
             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<A: AuthBackend>(
         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<A: AuthBackend>(
             };
 
             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!())