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

error handling: auth rejected sketch

connecting the bits

Changed files
+64 -23
who-am-i
+1 -1
who-am-i/src/lib.rs
···
mod server;
pub use expiring_task_map::ExpiringTaskMap;
-
pub use oauth::{OAuth, OauthCallbackParams, ResolveHandleError};
+
pub use oauth::{OAuth, OAuthCallbackParams, OAuthCompleteError, ResolveHandleError};
pub use server::serve;
+11 -13
who-am-i/src/oauth.rs
···
#[derive(Debug, Deserialize)]
#[serde(untagged)]
-
pub enum OauthCallbackParams {
+
pub enum OAuthCallbackParams {
Granted(CallbackParams),
Failed(CallbackErrorParams),
}
···
pub struct AuthStartError(#[from] atrium_oauth::Error);
#[derive(Debug, Error)]
-
pub enum AuthCompleteError {
+
pub enum OAuthCompleteError {
#[error("the user denied request: {description:?} (from {issuer:?})")]
Denied {
description: Option<String>,
issuer: Option<String>,
},
-
#[error(
-
"the request was denied for another reason: {error}: {description:?} (from {issuer:?})"
-
)]
+
#[error("the request failed: {error}: {description:?} (from {issuer:?})")]
Failed {
error: String,
description: Option<String>,
···
}
/// Finally, resolve the oauth flow to a verified DID
-
pub async fn complete(&self, params: OauthCallbackParams) -> Result<Did, AuthCompleteError> {
+
pub async fn complete(&self, params: OAuthCallbackParams) -> Result<Did, OAuthCompleteError> {
let params = match params {
-
OauthCallbackParams::Granted(params) => params,
-
OauthCallbackParams::Failed(p) if p.error == "access_denied" => {
-
return Err(AuthCompleteError::Denied {
+
OAuthCallbackParams::Granted(params) => params,
+
OAuthCallbackParams::Failed(p) if p.error == "access_denied" => {
+
return Err(OAuthCompleteError::Denied {
description: p.error_description.clone(),
issuer: p.iss.clone(),
});
}
-
OauthCallbackParams::Failed(p) => {
-
return Err(AuthCompleteError::Failed {
+
OAuthCallbackParams::Failed(p) => {
+
return Err(OAuthCompleteError::Failed {
error: p.error.clone(),
description: p.error_description.clone(),
issuer: p.iss.clone(),
···
.client
.callback(params)
.await
-
.map_err(AuthCompleteError::CallbackFailed)?;
+
.map_err(OAuthCompleteError::CallbackFailed)?;
let Some(did) = session.did().await else {
-
return Err(AuthCompleteError::NoDid);
+
return Err(OAuthCompleteError::NoDid);
};
Ok(did)
}
+37 -9
who-am-i/src/server.rs
···
use axum::{
Router,
extract::{FromRef, Query, State},
-
http::header::{HeaderMap, REFERER},
-
response::{Html, IntoResponse, Json, Redirect},
+
http::{
+
StatusCode,
+
header::{HeaderMap, REFERER},
+
},
+
response::{Html, IntoResponse, Json, Redirect, Response},
routing::get,
};
use axum_extra::extract::cookie::{Cookie, Key, SameSite, SignedCookieJar};
···
use tokio_util::sync::CancellationToken;
use url::Url;
-
use crate::{ExpiringTaskMap, OAuth, OauthCallbackParams, ResolveHandleError};
+
use crate::{ExpiringTaskMap, OAuth, OAuthCallbackParams, OAuthCompleteError, ResolveHandleError};
const FAVICON: &[u8] = include_bytes!("../static/favicon.ico");
const INDEX_HTML: &str = include_str!("../static/index.html");
···
(jar, Redirect::to(&auth_url))
}
+
impl OAuthCompleteError {
+
fn to_error_response(&self, engine: AppEngine) -> Response {
+
let (_level, _desc) = match self {
+
OAuthCompleteError::Denied { .. } => {
+
let status = StatusCode::FORBIDDEN;
+
return (status, RenderHtml("auth-fail", engine, json!({}))).into_response();
+
}
+
OAuthCompleteError::Failed { .. } => (
+
"error",
+
"Something went wrong while requesting permission, sorry!",
+
),
+
OAuthCompleteError::CallbackFailed(_) => (
+
"error",
+
"Something went wrong after permission was granted, sorry!",
+
),
+
OAuthCompleteError::NoDid => (
+
"error",
+
"Something went wrong when trying to confirm your identity, sorry!",
+
),
+
};
+
todo!();
+
}
+
}
+
async fn complete_oauth(
State(AppState {
engine,
···
shutdown,
..
}): State<AppState>,
-
Query(params): Query<OauthCallbackParams>,
+
Query(params): Query<OAuthCallbackParams>,
jar: SignedCookieJar,
-
) -> (SignedCookieJar, impl IntoResponse) {
-
let Ok(did) = oauth.complete(params).await else {
-
panic!("failed to do client callback");
+
) -> Result<(SignedCookieJar, impl IntoResponse), Response> {
+
let did = match oauth.complete(params).await {
+
Ok(did) => did,
+
Err(e) => return Err(e.to_error_response(engine)),
};
let cookie = Cookie::build((DID_COOKIE_KEY, did.to_string()))
···
shutdown.child_token(),
);
-
(
+
Ok((
jar,
RenderHtml(
"authorized",
···
"fetch_key": fetch_key,
}),
),
-
)
+
))
}
+15
who-am-i/templates/auth-fail.hbs
···
+
{{#*inline "main"}}
+
<p>
+
Share your identity with
+
<span class="parent-host">{{ parent_host }}</span>?
+
</p>
+
+
<div id="user-info">
+
<div id="action">
+
auth failed.
+
</form>
+
</div>
+
+
{{/inline}}
+
+
{{#> prompt-base}}{{/prompt-base}}