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

refresh cookie + frame headers

Changed files
+55 -31
who-am-i
src
+55 -31
who-am-i/src/server.rs
··· 4 4 extract::{FromRef, Query, State}, 5 5 http::{ 6 6 StatusCode, 7 - header::{CONTENT_TYPE, HeaderMap, REFERER}, 7 + header::{CONTENT_SECURITY_POLICY, CONTENT_TYPE, HeaderMap, REFERER, X_FRAME_OPTIONS}, 8 8 }, 9 9 response::{IntoResponse, Json, Redirect, Response}, 10 10 routing::get, ··· 29 29 30 30 const DID_COOKIE_KEY: &str = "did"; 31 31 32 + const COOKIE_EXPIRATION: Duration = Duration::from_secs(30 * 86_400); 33 + 32 34 type AppEngine = Engine<Handlebars<'static>>; 33 - type Rendered = RenderHtml<&'static str, AppEngine, Value>; 34 35 35 36 #[derive(Clone)] 36 37 struct AppState { ··· 96 97 .unwrap(); 97 98 } 98 99 99 - async fn hello(State(AppState { engine, .. }): State<AppState>) -> Rendered { 100 - RenderHtml("hello", engine, json!({})) 100 + async fn hello( 101 + State(AppState { engine, .. }): State<AppState>, 102 + mut jar: SignedCookieJar, 103 + ) -> Response { 104 + // push expiry (or clean up) the current cookie 105 + if let Some(did) = jar.get(DID_COOKIE_KEY) { 106 + if let Ok(did) = Did::new(did.value_trimmed().to_string()) { 107 + jar = jar.add(cookie(&did)); 108 + } else { 109 + jar = jar.remove(DID_COOKIE_KEY); 110 + } 111 + } 112 + let frame_headers = [ 113 + (X_FRAME_OPTIONS, "deny"), 114 + (CONTENT_SECURITY_POLICY, "frame-ancestors 'none'"), 115 + ]; 116 + (frame_headers, jar, RenderHtml("hello", engine, json!({}))).into_response() 101 117 } 102 118 103 119 async fn css() -> impl IntoResponse { ··· 110 126 111 127 async fn favicon() -> impl IntoResponse { 112 128 ([(CONTENT_TYPE, "image/x-icon")], FAVICON) 129 + } 130 + 131 + fn cookie(did: &Did) -> Cookie<'static> { 132 + Cookie::build((DID_COOKIE_KEY, did.to_string())) 133 + .http_only(true) 134 + .secure(true) 135 + .same_site(SameSite::None) 136 + .max_age(COOKIE_EXPIRATION.try_into().unwrap()) 137 + .into() 113 138 } 114 139 115 140 async fn prompt( ··· 149 174 if parent_origin == "null" { 150 175 return err("Referer origin is opaque", true); 151 176 } 177 + 178 + let frame_headers = [ 179 + (X_FRAME_OPTIONS, format!("allow-from {parent_origin}")), 180 + ( 181 + CONTENT_SECURITY_POLICY, 182 + format!("frame-ancestors {parent_host}"), 183 + ), 184 + ]; 185 + 152 186 if let Some(did) = jar.get(DID_COOKIE_KEY) { 153 187 let Ok(did) = Did::new(did.value_trimmed().to_string()) else { 154 188 return err("Bad cookie", false); 155 189 }; 156 190 191 + // push cookie expiry 192 + let jar = jar.add(cookie(&did)); 193 + 157 194 let fetch_key = resolve_handles.dispatch( 158 195 { 159 196 let oauth = oauth.clone(); ··· 163 200 shutdown.child_token(), 164 201 ); 165 202 166 - RenderHtml( 167 - "prompt", 168 - engine, 169 - json!({ 170 - "did": did, 171 - "fetch_key": fetch_key, 172 - "parent_host": parent_host, 173 - "parent_origin": parent_origin, 174 - }), 175 - ) 176 - .into_response() 203 + let info = json!({ 204 + "did": did, 205 + "fetch_key": fetch_key, 206 + "parent_host": parent_host, 207 + "parent_origin": parent_origin, 208 + }); 209 + 210 + (frame_headers, jar, RenderHtml("prompt", engine, info)).into_response() 177 211 } else { 178 - RenderHtml( 179 - "prompt", 180 - engine, 181 - json!({ 182 - "parent_host": parent_host, 183 - "parent_origin": parent_origin, 184 - }), 185 - ) 186 - .into_response() 212 + let info = json!({ 213 + "parent_host": parent_host, 214 + "parent_origin": parent_origin, 215 + }); 216 + (frame_headers, RenderHtml("prompt", engine, info)).into_response() 187 217 } 188 218 } 189 219 ··· 316 346 } 317 347 }; 318 348 319 - let cookie = Cookie::build((DID_COOKIE_KEY, did.to_string())) 320 - .http_only(true) 321 - .secure(true) 322 - .same_site(SameSite::None) 323 - .max_age(std::time::Duration::from_secs(86_400).try_into().unwrap()); 324 - 325 - let jar = jar.add(cookie); 349 + let jar = jar.add(cookie(&did)); 326 350 327 351 let fetch_key = resolve_handles.dispatch( 328 352 {