Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm
0

Configure Feed

Select the types of activity you want to include in your feed.

error handling: auth rejected sketch

connecting the bits

+64 -23
+1 -1
who-am-i/src/lib.rs
··· 3 3 mod server; 4 4 5 5 pub use expiring_task_map::ExpiringTaskMap; 6 - pub use oauth::{OAuth, OauthCallbackParams, ResolveHandleError}; 6 + pub use oauth::{OAuth, OAuthCallbackParams, OAuthCompleteError, ResolveHandleError}; 7 7 pub use server::serve;
+11 -13
who-am-i/src/oauth.rs
··· 27 27 28 28 #[derive(Debug, Deserialize)] 29 29 #[serde(untagged)] 30 - pub enum OauthCallbackParams { 30 + pub enum OAuthCallbackParams { 31 31 Granted(CallbackParams), 32 32 Failed(CallbackErrorParams), 33 33 } ··· 58 58 pub struct AuthStartError(#[from] atrium_oauth::Error); 59 59 60 60 #[derive(Debug, Error)] 61 - pub enum AuthCompleteError { 61 + pub enum OAuthCompleteError { 62 62 #[error("the user denied request: {description:?} (from {issuer:?})")] 63 63 Denied { 64 64 description: Option<String>, 65 65 issuer: Option<String>, 66 66 }, 67 - #[error( 68 - "the request was denied for another reason: {error}: {description:?} (from {issuer:?})" 69 - )] 67 + #[error("the request failed: {error}: {description:?} (from {issuer:?})")] 70 68 Failed { 71 69 error: String, 72 70 description: Option<String>, ··· 135 133 } 136 134 137 135 /// Finally, resolve the oauth flow to a verified DID 138 - pub async fn complete(&self, params: OauthCallbackParams) -> Result<Did, AuthCompleteError> { 136 + pub async fn complete(&self, params: OAuthCallbackParams) -> Result<Did, OAuthCompleteError> { 139 137 let params = match params { 140 - OauthCallbackParams::Granted(params) => params, 141 - OauthCallbackParams::Failed(p) if p.error == "access_denied" => { 142 - return Err(AuthCompleteError::Denied { 138 + OAuthCallbackParams::Granted(params) => params, 139 + OAuthCallbackParams::Failed(p) if p.error == "access_denied" => { 140 + return Err(OAuthCompleteError::Denied { 143 141 description: p.error_description.clone(), 144 142 issuer: p.iss.clone(), 145 143 }); 146 144 } 147 - OauthCallbackParams::Failed(p) => { 148 - return Err(AuthCompleteError::Failed { 145 + OAuthCallbackParams::Failed(p) => { 146 + return Err(OAuthCompleteError::Failed { 149 147 error: p.error.clone(), 150 148 description: p.error_description.clone(), 151 149 issuer: p.iss.clone(), ··· 156 154 .client 157 155 .callback(params) 158 156 .await 159 - .map_err(AuthCompleteError::CallbackFailed)?; 157 + .map_err(OAuthCompleteError::CallbackFailed)?; 160 158 let Some(did) = session.did().await else { 161 - return Err(AuthCompleteError::NoDid); 159 + return Err(OAuthCompleteError::NoDid); 162 160 }; 163 161 Ok(did) 164 162 }
+37 -9
who-am-i/src/server.rs
··· 2 2 use axum::{ 3 3 Router, 4 4 extract::{FromRef, Query, State}, 5 - http::header::{HeaderMap, REFERER}, 6 - response::{Html, IntoResponse, Json, Redirect}, 5 + http::{ 6 + StatusCode, 7 + header::{HeaderMap, REFERER}, 8 + }, 9 + response::{Html, IntoResponse, Json, Redirect, Response}, 7 10 routing::get, 8 11 }; 9 12 use axum_extra::extract::cookie::{Cookie, Key, SameSite, SignedCookieJar}; ··· 19 22 use tokio_util::sync::CancellationToken; 20 23 use url::Url; 21 24 22 - use crate::{ExpiringTaskMap, OAuth, OauthCallbackParams, ResolveHandleError}; 25 + use crate::{ExpiringTaskMap, OAuth, OAuthCallbackParams, OAuthCompleteError, ResolveHandleError}; 23 26 24 27 const FAVICON: &[u8] = include_bytes!("../static/favicon.ico"); 25 28 const INDEX_HTML: &str = include_str!("../static/index.html"); ··· 190 193 (jar, Redirect::to(&auth_url)) 191 194 } 192 195 196 + impl OAuthCompleteError { 197 + fn to_error_response(&self, engine: AppEngine) -> Response { 198 + let (_level, _desc) = match self { 199 + OAuthCompleteError::Denied { .. } => { 200 + let status = StatusCode::FORBIDDEN; 201 + return (status, RenderHtml("auth-fail", engine, json!({}))).into_response(); 202 + } 203 + OAuthCompleteError::Failed { .. } => ( 204 + "error", 205 + "Something went wrong while requesting permission, sorry!", 206 + ), 207 + OAuthCompleteError::CallbackFailed(_) => ( 208 + "error", 209 + "Something went wrong after permission was granted, sorry!", 210 + ), 211 + OAuthCompleteError::NoDid => ( 212 + "error", 213 + "Something went wrong when trying to confirm your identity, sorry!", 214 + ), 215 + }; 216 + todo!(); 217 + } 218 + } 219 + 193 220 async fn complete_oauth( 194 221 State(AppState { 195 222 engine, ··· 198 225 shutdown, 199 226 .. 200 227 }): State<AppState>, 201 - Query(params): Query<OauthCallbackParams>, 228 + Query(params): Query<OAuthCallbackParams>, 202 229 jar: SignedCookieJar, 203 - ) -> (SignedCookieJar, impl IntoResponse) { 204 - let Ok(did) = oauth.complete(params).await else { 205 - panic!("failed to do client callback"); 230 + ) -> Result<(SignedCookieJar, impl IntoResponse), Response> { 231 + let did = match oauth.complete(params).await { 232 + Ok(did) => did, 233 + Err(e) => return Err(e.to_error_response(engine)), 206 234 }; 207 235 208 236 let cookie = Cookie::build((DID_COOKIE_KEY, did.to_string())) ··· 222 250 shutdown.child_token(), 223 251 ); 224 252 225 - ( 253 + Ok(( 226 254 jar, 227 255 RenderHtml( 228 256 "authorized", ··· 232 260 "fetch_key": fetch_key, 233 261 }), 234 262 ), 235 - ) 263 + )) 236 264 }
+15
who-am-i/templates/auth-fail.hbs
··· 1 + {{#*inline "main"}} 2 + <p> 3 + Share your identity with 4 + <span class="parent-host">{{ parent_host }}</span>? 5 + </p> 6 + 7 + <div id="user-info"> 8 + <div id="action"> 9 + auth failed. 10 + </form> 11 + </div> 12 + 13 + {{/inline}} 14 + 15 + {{#> prompt-base}}{{/prompt-base}}