···4646 Ok(auth) => auth,
4747 Err(e) => {
4848 let error_str = e.to_string();
4949- if error_str.contains("401") || error_str.contains("Unauthorized") {
5050- tracing::debug!("AIP session exchange returned 401 - session is stale");
4949+ if error_str.contains("401")
5050+ || error_str.contains("Unauthorized")
5151+ || error_str.contains("invalid_token")
5252+ || error_str.contains("expired")
5353+ {
5454+ tracing::debug!("AIP session exchange returned stale token - session is stale");
5155 return Ok(false);
5256 }
5357 return Err(e);
···7478 }
7579 Err(e) => {
7680 let error_str = e.to_string();
7777- if error_str.contains("401") || error_str.contains("Unauthorized") {
7878- tracing::debug!("PDS getServiceAuth returned 401 - session is stale");
8181+ if error_str.contains("401")
8282+ || error_str.contains("Unauthorized")
8383+ || error_str.contains("invalid_token")
8484+ || error_str.contains("expired")
8585+ {
8686+ tracing::debug!("PDS getServiceAuth returned stale token - session is stale");
7987 Ok(false)
8088 } else {
8189 Err(anyhow::anyhow!(
+11-4
src/http/errors/web_error.rs
···163163 ///
164164 /// This error occurs when an AT Protocol operation is attempted with a stale
165165 /// AIP session token. The user should be redirected to the login page.
166166+ /// The contained string is the destination URL to redirect to after re-authentication.
166167 ///
167168 /// **Error Code:** `error-smokesignal-web-3`
168168- #[error("error-smokesignal-web-3 Session expired, re-authentication required")]
169169- SessionStale,
169169+ #[error("error-smokesignal-web-3 Session expired, re-authentication required (destination: {0})")]
170170+ SessionStale(String),
170171171172 /// An internal server error occurred.
172173 ///
···187188 fn into_response(self) -> Response {
188189 match self {
189190 WebError::MiddlewareAuthError(err) => err.into_response(),
190190- WebError::SessionStale => {
191191+ WebError::SessionStale(destination) => {
191192 // For HTMX requests, use HX-Redirect to force a full page navigation to login
192193 // For regular requests, this will also work as the browser follows the redirect
194194+ // Include the destination so users return to where they were after re-authenticating
195195+ let encoded_destination = urlencoding::encode(&destination);
196196+ let redirect_url = format!(
197197+ "/oauth/login?reason=session_expired&destination={}",
198198+ encoded_destination
199199+ );
193200 (
194201 StatusCode::UNAUTHORIZED,
195195- [("HX-Redirect", "/oauth/login?reason=session_expired")],
202202+ [("HX-Redirect", redirect_url)],
196203 "Session expired. Please log in again.",
197204 )
198205 .into_response()
+1-1
src/http/handle_accept_rsvp.rs
···115115116116 // Check AIP session validity before attempting AT Protocol operation
117117 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
118118- return Err(WebError::SessionStale);
118118+ return Err(WebError::SessionStale("/".to_string()));
119119 }
120120121121 // Create DPoP auth based on OAuth backend type
+5-5
src/http/handle_blob.rs
···328328329329 // Check AIP session validity before attempting AT Protocol operation
330330 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
331331- return Err(WebError::SessionStale);
331331+ return Err(WebError::SessionStale("/settings".to_string()));
332332 }
333333334334 let dpop_auth = match (&auth, &web_context.config.oauth_backend) {
···503503504504 // Check AIP session validity before attempting AT Protocol operation
505505 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
506506- return Err(WebError::SessionStale);
506506+ return Err(WebError::SessionStale("/settings".to_string()));
507507 }
508508509509 let dpop_auth = match (&auth, &web_context.config.oauth_backend) {
···619619620620 // Check AIP session validity before attempting AT Protocol operation
621621 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
622622- return Err(WebError::SessionStale);
622622+ return Err(WebError::SessionStale("/settings".to_string()));
623623 }
624624625625 let dpop_auth = match (&auth, &web_context.config.oauth_backend) {
···734734735735 // Check AIP session validity before attempting AT Protocol operation
736736 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
737737- return Err(WebError::SessionStale);
737737+ return Err(WebError::SessionStale("/event".to_string()));
738738 }
739739740740 // Create DPoP authentication based on backend type
···942942943943 // Check AIP session validity before attempting AT Protocol operation
944944 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
945945- return Err(WebError::SessionStale);
945945+ return Err(WebError::SessionStale("/event".to_string()));
946946 }
947947948948 // Create DPoP authentication based on backend type
+2-2
src/http/handle_create_event.rs
···133133134134 // Check AIP session validity
135135 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
136136- return Err(WebError::SessionStale);
136136+ return Err(WebError::SessionStale("/event".to_string()));
137137 }
138138139139 let is_development = cfg!(debug_assertions);
···265265266266 // Check AIP session validity before attempting AT Protocol operation
267267 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
268268- return Err(WebError::SessionStale);
268268+ return Err(WebError::SessionStale("/event".to_string()));
269269 }
270270271271 // Create DPoP auth
+2-2
src/http/handle_create_rsvp.rs
···4747 // Check AIP session validity before displaying RSVP form
4848 // This prevents users from filling out forms with stale sessions
4949 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
5050- return Err(WebError::SessionStale);
5050+ return Err(WebError::SessionStale("/rsvp".to_string()));
5151 }
52525353 let default_context = template_context! {
···151151152152 // Check AIP session validity before attempting AT Protocol operation
153153 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
154154- return Err(WebError::SessionStale);
154154+ return Err(WebError::SessionStale("/rsvp".to_string()));
155155 }
156156157157 // Create DPoP auth based on OAuth backend type
+1-1
src/http/handle_delete_event.rs
···8383 if form.confirm.as_deref() == Some("true") {
8484 // Check AIP session validity before attempting AT Protocol operation
8585 if let AipSessionStatus::Stale = require_valid_aip_session(&ctx.web_context, &ctx.auth).await? {
8686- return Err(WebError::SessionStale);
8686+ return Err(WebError::SessionStale("/".to_string()));
8787 }
88888989 // Create DPoP authentication based on auth type
+1-1
src/http/handle_edit_event.rs
···111111112112 // Check AIP session validity before attempting AT Protocol operation
113113 if let AipSessionStatus::Stale = require_valid_aip_session(&ctx.web_context, &ctx.auth).await? {
114114- return Err(WebError::SessionStale);
114114+ return Err(WebError::SessionStale("/event".to_string()));
115115 }
116116117117 // Create DPoP auth
+1-1
src/http/handle_finalize_acceptance.rs
···237237238238 // Check AIP session validity before attempting AT Protocol operation
239239 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
240240- return Err(WebError::SessionStale);
240240+ return Err(WebError::SessionStale("/".to_string()));
241241 }
242242243243 // Create DPoP auth based on OAuth backend type
+2-2
src/http/handle_lfg.rs
···490490491491 // Check AIP session validity before attempting AT Protocol operation
492492 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
493493- return Err(WebError::SessionStale);
493493+ return Err(WebError::SessionStale("/lfg".to_string()));
494494 }
495495496496 // Validate the request
···641641642642 // Check AIP session validity
643643 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
644644- return Err(WebError::SessionStale);
644644+ return Err(WebError::SessionStale("/lfg".to_string()));
645645 }
646646647647 // Get the active LFG record
+1-1
src/http/handle_quick_event.rs
···2525 // Check AIP session validity before displaying quick event form
2626 // This prevents users from filling out forms with stale sessions
2727 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
2828- return Err(WebError::SessionStale);
2828+ return Err(WebError::SessionStale("/quick-event".to_string()));
2929 }
30303131 let render_template = select_template!("quick_event", hx_boosted, false, language);
+2-2
src/http/handle_settings.rs
···8383 // Check AIP session validity before displaying settings form
8484 // This prevents users from filling out forms with stale sessions
8585 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
8686- return Err(WebError::SessionStale);
8686+ return Err(WebError::SessionStale("/settings".to_string()));
8787 }
88888989 let default_context = template_context! {
···521521522522 // Check AIP session validity before attempting AT Protocol operation
523523 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
524524- return Err(WebError::SessionStale);
524524+ return Err(WebError::SessionStale("/settings".to_string()));
525525 }
526526527527 // Create DPoP authentication based on backend type
+1-1
src/http/handle_unaccept_rsvp.rs
···80808181 // Check AIP session validity before attempting AT Protocol operation
8282 if let AipSessionStatus::Stale = require_valid_aip_session(&web_context, &auth).await? {
8383- return Err(WebError::SessionStale);
8383+ return Err(WebError::SessionStale("/".to_string()));
8484 }
85858686 // Create DPoP auth based on OAuth backend type
···11+const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["js/event-map-BMN8EESL.js","js/main-CuQd5Sql.js","css/main-DsS_JPFh.css","js/globe-map-v0wYAEFW.js","js/location-heatmap-qsLOB3hq.js"])))=>i.map(i=>d[i]);
22+import{_ as i}from"./main-CuQd5Sql.js";async function o(){if(!document.getElementById("event-map"))return;const{initEventMap:t}=await i(async()=>{const{initEventMap:n}=await import("./event-map-BMN8EESL.js");return{initEventMap:n}},__vite__mapDeps([0,1,2]));await t()}async function e(){if(!document.getElementById("globe-map"))return;const{initGlobeMap:t}=await i(async()=>{const{initGlobeMap:n}=await import("./globe-map-v0wYAEFW.js");return{initGlobeMap:n}},__vite__mapDeps([3,1,2]));await t()}async function c(){if(!document.getElementById("location-heatmap"))return;const{initLocationHeatmap:t}=await i(async()=>{const{initLocationHeatmap:n}=await import("./location-heatmap-qsLOB3hq.js");return{initLocationHeatmap:n}},__vite__mapDeps([4,1,2]));await t()}function p(){o(),e(),c()}export{o as initEventMap,e as initGlobeMap,c as initLocationHeatmap,p as initMaps};
+2
static/js/index-Bcnf8oUZ.js
···11+const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["js/cropper-DyNc_82c.js","css/cropper-BJgXXBRK.css"])))=>i.map(i=>d[i]);
22+import{_ as a}from"./main-CuQd5Sql.js";let t=null;async function o(){return t||(t=(async()=>{const[n]=await Promise.all([a(()=>import("./cropper-DyNc_82c.js").then(r=>r.c),__vite__mapDeps([0,1])),a(()=>import("./cropper-DyNc_82c.js").then(r=>r.a),__vite__mapDeps([0,1]))]),e=n.default;return window.Cropper=e,e})(),t)}async function s(){const n=document.getElementById("headerCanvas"),e=document.getElementById("thumbnailCanvas");!n&&!e||await o()}export{s as initCropper,o as loadCropper};