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

Configure Feed

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

sucks but kind of works now in safariiiiiii

phil 27995605 21a41584

+60 -22
+43 -15
who-am-i/src/server.rs
··· 5 5 extract::{FromRef, Json as ExtractJson, Query, State}, 6 6 http::{ 7 7 StatusCode, 8 - header::{CONTENT_SECURITY_POLICY, CONTENT_TYPE, HeaderMap, REFERER}, 8 + header::{CONTENT_SECURITY_POLICY, CONTENT_TYPE, HeaderMap, ORIGIN, REFERER}, 9 9 }, 10 10 response::{IntoResponse, Json, Redirect, Response}, 11 11 routing::{get, post}, ··· 211 211 .into() 212 212 } 213 213 214 + #[derive(Debug, Deserialize)] 215 + struct PromptQuery { 216 + // this must *ONLY* be used for the postmessage target origin 217 + app: Option<String>, 218 + } 214 219 async fn prompt( 215 220 State(AppState { 216 221 allowed_hosts, ··· 221 226 tokens, 222 227 .. 223 228 }): State<AppState>, 229 + Query(params): Query<PromptQuery>, 224 230 jar: SignedCookieJar, 225 231 headers: HeaderMap, 226 232 ) -> impl IntoResponse { 227 - let err = |reason, check_frame| { 233 + let err = |reason, check_frame, detail| { 228 234 metrics::counter!("whoami_auth_prompt", "ok" => "false", "reason" => reason).increment(1); 229 - let info = json!({ "reason": reason, "check_frame": check_frame }); 235 + let info = json!({ 236 + "reason": reason, 237 + "check_frame": check_frame, 238 + "detail": detail, 239 + }); 230 240 let html = RenderHtml("prompt-error", engine.clone(), info); 231 241 (StatusCode::BAD_REQUEST, html).into_response() 232 242 }; 233 243 234 - let Some(referrer) = headers.get(REFERER) else { 235 - return err("Missing referer", true); 244 + let Some(parent) = headers.get(ORIGIN).or_else(|| { 245 + eprintln!("referrer fallback"); 246 + // TODO: referer should only be used for localhost?? 247 + headers.get(REFERER) 248 + }) else { 249 + return err("Missing origin and no referrer for fallback", true, None); 236 250 }; 237 - let Ok(referrer) = referrer.to_str() else { 238 - return err("Unreadable referer", true); 251 + let Ok(parent) = parent.to_str() else { 252 + return err("Unreadable origin or referrer", true, None); 239 253 }; 240 - let Ok(url) = Url::parse(referrer) else { 241 - return err("Bad referer", true); 254 + eprintln!( 255 + "rolling with parent: {parent:?} (from origin? {})", 256 + headers.get(ORIGIN).is_some() 257 + ); 258 + let Ok(url) = Url::parse(parent) else { 259 + return err("Bad origin or referrer", true, None); 242 260 }; 243 261 let Some(parent_host) = url.host_str() else { 244 - return err("Referer missing host", true); 262 + return err("Origin or referrer missing host", true, None); 245 263 }; 246 264 if !allowed_hosts.contains(parent_host) { 247 - return err("Login is not allowed on this page", false); 265 + return err( 266 + "Login is not allowed on this page", 267 + false, 268 + Some(parent_host), 269 + ); 248 270 } 249 271 let parent_origin = url.origin().ascii_serialization(); 250 272 if parent_origin == "null" { 251 - return err("Referer origin is opaque", true); 273 + return err("Origin or referrer header value is opaque", true, None); 252 274 } 253 275 254 - let csp = format!("frame-ancestors {parent_origin}"); 276 + let all_allowed = allowed_hosts 277 + .iter() 278 + .map(|h| format!("https://{h}")) 279 + .collect::<Vec<_>>() 280 + .join(" "); 281 + let csp = format!("frame-ancestors 'self' {parent_origin} {all_allowed}"); 255 282 let frame_headers = [(CONTENT_SECURITY_POLICY, &csp)]; 256 283 257 284 if let Some(did) = jar.get(DID_COOKIE_KEY) { 258 285 let Ok(did) = Did::new(did.value_trimmed().to_string()) else { 259 - return err("Bad cookie", false); 286 + return err("Bad cookie", false, None); 260 287 }; 261 288 262 289 // push cookie expiry ··· 266 293 Ok(t) => t, 267 294 Err(e) => { 268 295 eprintln!("failed to create JWT: {e:?}"); 269 - return err("failed to create JWT", false); 296 + return err("failed to create JWT", false, None); 270 297 } 271 298 }; 272 299 ··· 286 313 "fetch_key": fetch_key, 287 314 "parent_host": parent_host, 288 315 "parent_origin": parent_origin, 316 + "parent_target": params.app.map(|h| format!("https://{h}")), 289 317 }); 290 318 (frame_headers, jar, RenderHtml("prompt", engine, info)).into_response() 291 319 } else {
+1 -1
who-am-i/static/style.css
··· 12 12 overflow: hidden; 13 13 display: flex; 14 14 flex-direction: column; 15 - height: 100vh; 15 + min-height: 100vh; 16 16 } 17 17 .wrap.unframed { 18 18 border-radius: 0;
+1
who-am-i/templates/prompt-error.hbs
··· 2 2 <div class="prompt-error"> 3 3 <p class="went-wrong">Something went wrong :(</p> 4 4 <p class="reason">{{ reason }}</p> 5 + <p class="reason detail">{{ detail }}</p> 5 6 <p id="maybe-not-in-iframe" class="hidden"> 6 7 Possibly related: this prompt is meant to be shown in an iframe, but it seems like it's not. 7 8 </p>
+15 -6
who-am-i/templates/prompt.hbs
··· 89 89 cookies: true, 90 90 localStorage: true, 91 91 }).then( 92 - () => desperation.textContent = "(maybe helped?)", 92 + () => { 93 + desperation.textContent = "(maybe helped?)"; 94 + setTimeout(() => location.reload(), 350); 95 + }, 93 96 () => desperation.textContent = "(doubtful)", 94 97 ); 95 98 }) ··· 157 160 return info.handle; 158 161 } 159 162 163 + const parentTarget = {{{json parent_target}}} ?? {{{json parent_origin}}}; 164 + 160 165 const shareAllow = (handle, token) => { 161 - top.postMessage( 162 - { action: "allow", handle, token }, 163 - {{{json parent_origin}}}, 164 - ); 166 + try { 167 + top.postMessage( 168 + { action: "allow", handle, token }, 169 + parentTarget, 170 + ); 171 + } catch (e) { 172 + err(e, 'Identity verified but failed to connect with app'); 173 + }; 165 174 promptEl.textContent = '✔️ shared'; 166 175 } 167 176 168 177 const shareDeny = reason => { 169 178 top.postMessage( 170 179 { action: "deny", reason }, 171 - {{{json parent_origin}}}, 180 + parentTarget, 172 181 ); 173 182 } 174 183 </script>