@ymys/directus-extension-sso 1.5.1 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{createRequire as t}from"module";const e=t(import.meta.url),{version:o}=e("../package.json");var n={id:"sso",handler:(t,{env:e,logger:n})=>{const r=e.KEYCLOAK_URL||"http://keycloak:8080",a=e.KEYCLOAK_REALM||"testing",i=e.KEYCLOAK_ADMIN_USER||"admin",s=e.KEYCLOAK_ADMIN_PASSWORD||"admin",c=e.PUBLIC_URL||"http://localhost:8055",l=e.MOBILE_APP_SCHEME||"portalpipq",d=e.MOBILE_APP_CALLBACK_PATH||"/auth/callback",g=e.GOOGLE_CALLBACK_PATH||"/auth/callback/google",u=e.KEYCLOAK_CLIENT_ID||"admin-cli",h=e.COOKIE_DOMAIN||null,p="false"!==e.COOKIE_SECURE,f=e.COOKIE_SAME_SITE||"lax",m=e.SESSION_COOKIE_NAME||"directus_session_token",k="directus_session_token";function y(t){if("browser"===t.query.type)return!0;if("mobile"===t.query.type)return!1;const e=t.headers["user-agent"]||"";return/Mozilla|Chrome|Safari|Firefox|Edge|Opera/i.test(e)&&!/Mobile.*App|ReactNative|Expo/i.test(e)}n.info("🚀 Mobile Auth Proxy Extension loaded"),n.info("🔐 Keycloak URL: "+r),n.info("🌐 Keycloak Realm: "+a),n.info("📱 Mobile App Scheme: "+l+"://"+d),n.info("🔵 Google OAuth enabled"),t.get("/health",(t,e)=>{e.json({status:"ok",service:"directus-extension-sso",version:o})}),t.get("/mobile-callback",async(t,e)=>{const o=y(t);n.info(`${o?"🌐":"📱"} ${o?"Browser":"Mobile"} callback received`),n.info("Cookies: "+JSON.stringify(t.cookies)),n.info("Query: "+JSON.stringify(t.query));try{let r=t.cookies[m];if(r||m===k||(r=t.cookies[k],r&&n.info(`🔄 Bridging core token to ${m}`)),!r)return n.error("❌ No session token found in cookies"),e.send(`\n\t\t\t<html>\n\t\t\t\t<body>\n\t\t\t\t\t<h2>Authentication Failed</h2>\n\t\t\t\t\t<p>No session token found. Please try logging in again.</p>\n\t\t\t\t\t<a href="${c}/auth/login/keycloak">Try Again</a>\n\t\t\t\t</body>\n\t\t\t</html>\n\t\t`);n.info("✅ Session token found, length: "+r.length);const a=await fetch(`${c}/users/me`,{headers:{Cookie:`${k}=${r}`}});if(!a.ok)throw new Error("Failed to get user info: "+await a.text());const i=await a.json(),s=i.data.id,g=i.data.email;n.info("👤 User authenticated: "+s+", "+g);const u=r;if(n.info("🎫 Using session token as access token"),o){n.info("🌐 Browser request detected - maintaining session"),e.cookie(m,r,{httpOnly:!0,secure:p,domain:h,sameSite:f,maxAge:6048e5,path:"/"}),m!==k&&e.cookie(k,"",{maxAge:0,path:"/"});let o=t.query.redirect_uri||t.query.redirect||"/";try{if(o.startsWith("http")){const t=new URL(o);t.searchParams.set("access_token",u),t.searchParams.set("user_id",s),t.searchParams.set("email",g||""),o=t.toString()}}catch(t){n.error("❌ Failed to parse Web redirect URL: "+o)}return n.info("🔄 Redirecting browser to: "+o),e.redirect(o)}const y=d.startsWith("/")?d:`//${d}`,b=`${l}:${y}?access_token=${u}&user_id=${s}&email=${encodeURIComponent(g||"")}`;n.info("🔄 Redirecting to app: "+b),e.redirect(b)}catch(t){n.error("❌ Error in callback:",t),e.status(500).send(`\n\t\t<html>\n\t\t\t<body>\n\t\t\t\t<h2>Error</h2>\n\t\t\t\t<p>${t.message}</p>\n\t\t\t\t<a href="${c}/auth/login/keycloak">Try Again</a>\n\t\t\t</body>\n\t\t</html>\n\t`)}}),t.get("/google-callback",async(t,e)=>{const o=y(t);n.info(`${o?"🌐":"📱"} ${o?"Browser":"Mobile"} Google callback received`),n.info("Cookies: "+JSON.stringify(t.cookies)),n.info("Query: "+JSON.stringify(t.query));try{let r=t.cookies[m];if(r||m===k||(r=t.cookies[k],r&&n.info(`🔄 Bridging core token to ${m} (Google)`)),!r)return n.error("❌ No session token found in cookies (Google)"),e.send("\n\t\t\t\t\t\t<html>\n\t\t\t\t\t\t\t<head>\n\t\t\t\t\t\t\t\t<title>Authentication Failed</title>\n\t\t\t\t\t\t\t\t<style>\n\t\t\t\t\t\t\t\t\tbody { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; \n\t\t\t\t\t\t\t\t\t padding: 40px; text-align: center; background: #f5f5f5; }\n\t\t\t\t\t\t\t\t\t.container { max-width: 500px; margin: 0 auto; background: white; \n\t\t\t\t\t\t\t\t\t padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }\n\t\t\t\t\t\t\t\t\th2 { color: #e74c3c; }\n\t\t\t\t\t\t\t\t\ta { display: inline-block; margin-top: 20px; padding: 10px 20px; \n\t\t\t\t\t\t\t\t\t background: #4285f4; color: white; text-decoration: none; border-radius: 4px; }\n\t\t\t\t\t\t\t\t\ta:hover { background: #357ae8; }\n\t\t\t\t\t\t\t\t</style>\n\t\t\t\t\t\t\t</head>\n\t\t\t\t\t\t\t<body>\n\t\t\t\t\t\t\t\t<div class=\"container\">\n\t\t\t\t\t\t\t\t\t<h2>Authentication Failed</h2>\n\t\t\t\t\t\t\t\t\t<p>No session token found. Please close this and try logging in again.</p>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</body>\n\t\t\t\t\t\t</html>\n\t\t\t\t\t");n.info("✅ Session token found (Google), length: "+r.length);const a=await fetch(`${c}/users/me`,{headers:{Cookie:`${k}=${r}`}});if(!a.ok)throw new Error("Failed to get user info: "+await a.text());const i=await a.json(),s=i.data.id,d=i.data.email,u=i.data.first_name||i.data.email;n.info("👤 User authenticated via Google: "+s+", "+d);const y=r;if(n.info("🎫 Using session token as access token"),o){n.info("🌐 Browser request detected - maintaining session"),e.cookie(m,r,{httpOnly:!0,secure:p,domain:h,sameSite:f,maxAge:6048e5,path:"/"}),m!==k&&e.cookie(k,"",{maxAge:0,path:"/"});let o=t.query.redirect_uri||t.query.redirect||"/";try{if(o.startsWith("http")){const t=new URL(o);t.searchParams.set("access_token",y),t.searchParams.set("user_id",s),t.searchParams.set("email",d||""),t.searchParams.set("provider","google"),o=t.toString()}}catch(t){n.error("❌ Failed to parse Web redirect URL: "+o)}return n.info("🔄 Redirecting browser to: "+o),e.send(`\n\t\t\t\t\t\t<html>\n\t\t\t\t\t\t\t<head>\n\t\t\t\t\t\t\t\t<title>Login Successful</title>\n\t\t\t\t\t\t\t\t<meta http-equiv="refresh" content="2;url=${o}">\n\t\t\t\t\t\t\t\t<style>\n\t\t\t\t\t\t\t\t\tbody { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; \n\t\t\t\t\t\t\t\t\t padding: 40px; text-align: center; background: #f5f5f5; }\n\t\t\t\t\t\t\t\t\t.container { max-width: 500px; margin: 0 auto; background: white; \n\t\t\t\t\t\t\t\t\t padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }\n\t\t\t\t\t\t\t\t\th2 { color: #27ae60; }\n\t\t\t\t\t\t\t\t\t.checkmark { font-size: 48px; color: #27ae60; margin-bottom: 20px; }\n\t\t\t\t\t\t\t\t\tp { color: #666; margin: 10px 0; }\n\t\t\t\t\t\t\t\t\t.spinner { border: 3px solid #f3f3f3; border-top: 3px solid #4285f4; \n\t\t\t\t\t\t\t\t\t border-radius: 50%; width: 40px; height: 40px; \n\t\t\t\t\t\t\t\t\t animation: spin 1s linear infinite; margin: 20px auto; }\n\t\t\t\t\t\t\t\t\t@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }\n\t\t\t\t\t\t\t\t\ta { color: #4285f4; text-decoration: none; }\n\t\t\t\t\t\t\t\t\ta:hover { text-decoration: underline; }\n\t\t\t\t\t\t\t\t\t.google-icon { color: #4285f4; margin-right: 5px; }\n\t\t\t\t\t\t\t\t</style>\n\t\t\t\t\t\t\t</head>\n\t\t\t\t\t\t\t<body>\n\t\t\t\t\t\t\t\t<div class="container">\n\t\t\t\t\t\t\t\t\t<div class="checkmark">✓</div>\n\t\t\t\t\t\t\t\t\t<h2><span class="google-icon">🔵</span>Google Login Successful!</h2>\n\t\t\t\t\t\t\t\t\t<p>Welcome, ${u}!</p>\n\t\t\t\t\t\t\t\t\t<p>Your session has been saved. You can now access other services using the same login.</p>\n\t\t\t\t\t\t\t\t\t<div class="spinner"></div>\n\t\t\t\t\t\t\t\t\t<p style="margin-top: 20px;">Redirecting you automatically...</p>\n\t\t\t\t\t\t\t\t\t<p style="font-size: 14px; margin-top: 20px;">\n\t\t\t\t\t\t\t\t\t\t<a href="${o}">Click here if not redirected automatically</a>\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</body>\n\t\t\t\t\t\t</html>\n\t\t\t\t\t`)}n.info("📱 Mobile app request - redirecting with token");const b=new URL(`${l}://${g}`);b.searchParams.set("access_token",y),b.searchParams.set("user_id",s),b.searchParams.set("email",d||""),b.searchParams.set("provider","google"),n.info("🔄 Redirecting to app (Google): "+b.toString()),e.redirect(b.toString())}catch(t){n.error("❌ Error in Google callback:",t),e.status(500).send(`\n\t\t\t\t\t<html>\n\t\t\t\t\t\t<head>\n\t\t\t\t\t\t\t<title>Error</title>\n\t\t\t\t\t\t\t<style>\n\t\t\t\t\t\t\t\tbody { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; \n\t\t\t\t\t\t\t\t padding: 40px; text-align: center; background: #f5f5f5; }\n\t\t\t\t\t\t\t\t.container { max-width: 500px; margin: 0 auto; background: white; \n\t\t\t\t\t\t\t\t padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }\n\t\t\t\t\t\t\t\th2 { color: #e74c3c; }\n\t\t\t\t\t\t\t\t.error-icon { font-size: 48px; color: #e74c3c; margin-bottom: 20px; }\n\t\t\t\t\t\t\t\tpre { background: #f8f8f8; padding: 15px; border-radius: 4px; \n\t\t\t\t\t\t\t\t text-align: left; overflow-x: auto; font-size: 12px; }\n\t\t\t\t\t\t\t\ta { display: inline-block; margin-top: 20px; padding: 10px 20px; \n\t\t\t\t\t\t\t\t background: #4285f4; color: white; text-decoration: none; border-radius: 4px; }\n\t\t\t\t\t\t\t\ta:hover { background: #357ae8; }\n\t\t\t\t\t\t\t</style>\n\t\t\t\t\t\t</head>\n\t\t\t\t\t\t<body>\n\t\t\t\t\t\t\t<div class="container">\n\t\t\t\t\t\t\t\t<div class="error-icon">⚠️</div>\n\t\t\t\t\t\t\t\t<h2>Error</h2>\n\t\t\t\t\t\t\t\t<p>An error occurred during Google authentication:</p>\n\t\t\t\t\t\t\t\t<pre>${t.message}</pre>\n\t\t\t\t\t\t\t\t<a href="${c}/auth/login/google">Try Again</a>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</body>\n\t\t\t\t\t</html>\n\t\t\t\t`)}}),t.post("/mobile-logout",async(t,e)=>{n.info("🚪 Logout request received");try{const o=t.headers.authorization,l=o?.replace("Bearer ","");if(!l)return e.status(400).json({error:"No token provided",message:"Authorization header with Bearer token is required"});n.info("🎫 Token received: "+l.substring(0,20)+"...");let d=null;try{const t=await fetch(`${c}/users/me`,{headers:{Authorization:`Bearer ${l}`}});if(t.ok){d=(await t.json()).data.email,n.info("👤 User email: "+d)}}catch(t){n.error("⚠️ Error getting user info: "+t.message)}try{const t=await fetch(`${c}/auth/logout`,{method:"POST",headers:{Authorization:`Bearer ${l}`,"Content-Type":"application/json"},body:JSON.stringify({refresh_token:l})});t.ok?n.info("✅ Directus session invalidated"):n.info("⚠️ Directus logout response: "+t.status)}catch(t){n.error("⚠️ Error logging out from Directus: "+t.message)}if(d)try{n.info("🔐 Getting Keycloak admin token...");const t=await async function(){try{const t=await fetch(`${r}/realms/master/protocol/openid-connect/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"password",client_id:u,username:i,password:s}).toString()});if(!t.ok)throw new Error("Failed to get admin token");return(await t.json()).access_token}catch(t){return n.error("Error getting admin token:",t),null}}();if(t){n.info("🔍 Looking up Keycloak user...");const e=await async function(t,e){try{const o=await fetch(`${r}/admin/realms/${a}/users?email=${encodeURIComponent(e)}`,{headers:{Authorization:`Bearer ${t}`}});if(!o.ok)throw new Error("Failed to get user");const n=await o.json();return n.length>0?n[0].id:null}catch(t){return n.error("Error getting user ID:",t),null}}(t,d);if(e){n.info("🚪 Logging out from Keycloak...");const o=await async function(t,e){try{const o=await fetch(`${r}/admin/realms/${a}/users/${e}/logout`,{method:"POST",headers:{Authorization:`Bearer ${t}`}});return o.ok||204===o.status}catch(t){return n.error("Error logging out user from Keycloak:",t),!1}}(t,e);o?n.info("✅ Keycloak sessions terminated"):n.info("⚠️ Failed to logout from Keycloak")}else n.info("⚠️ User not found in Keycloak")}else n.info("⚠️ Failed to get Keycloak admin token")}catch(t){n.error("⚠️ Error logging out from Keycloak: "+t.message)}n.info("🎉 Logout completed"),e.json({success:!0,message:"Logged out successfully from Directus and Keycloak"})}catch(t){n.error("❌ Error in logout:",t),e.status(500).json({error:t.message,message:"Failed to logout"})}})}};export{n as default};
1
+ import{createRequire as t}from"module";const e=t(import.meta.url),{version:o}=e("../package.json");var n={id:"sso",handler:(t,{env:e,logger:n})=>{const r=e.KEYCLOAK_URL||"http://keycloak:8080",a=e.KEYCLOAK_REALM||"testing",i=e.KEYCLOAK_ADMIN_USER||"admin",s=e.KEYCLOAK_ADMIN_PASSWORD||"admin",c=e.PUBLIC_URL||"http://localhost:8055",l=e.MOBILE_APP_SCHEME||"portalpipq",d=e.MOBILE_APP_CALLBACK_PATH||"/auth/callback",u=e.GOOGLE_CALLBACK_PATH||"/auth/callback/google",g=e.KEYCLOAK_CLIENT_ID||"admin-cli",h=e.COOKIE_DOMAIN||null,p="false"!==e.COOKIE_SECURE,f=e.COOKIE_SAME_SITE||"lax",m=e.SESSION_COOKIE_NAME||"directus_session_token",y="directus_session_token";function k(t){if("browser"===t.query.type)return!0;if("mobile"===t.query.type)return!1;const e=t.headers["user-agent"]||"";return/Mozilla|Chrome|Safari|Firefox|Edge|Opera/i.test(e)&&!/Mobile.*App|ReactNative|Expo/i.test(e)}async function b(t,e){const o=t.headers.cookie;if(!o)return null;const r=o.split(";").map(t=>t.trim()).filter(t=>t.startsWith(`${e}=`)).map(t=>t.substring(e.length+1));if(0===r.length)return null;n.info(`🔍 [${e}] Found ${r.length} possible tokens. Trying them...`);for(let t=0;t<r.length;t++){const o=r[t];try{const r=await fetch(`${c}/users/me`,{headers:{Cookie:`${e}=${o}`}});if(r.ok){const a=await r.json();return n.info(`✅ [${e}] Token #${t+1} succeeded for ${a.data.email}`),{token:o,userData:a.data}}n.info(`❌ [${e}] Token #${t+1} failed (${r.status})`)}catch(o){n.error(`⚠️ [${e}] Error trying token #${t+1}: ${o.message}`)}}return null}n.info("🚀 Mobile Auth Proxy Extension loaded"),n.info("🔐 Keycloak URL: "+r),n.info("🌐 Keycloak Realm: "+a),n.info("📡 Public URL: "+c),n.info("🍪 Session Cookie Name: "+m),n.info("📱 Mobile App Scheme: "+l+"://"+d),n.info("🔵 Google OAuth enabled"),t.get("/health",(t,e)=>{e.json({status:"ok",service:"directus-extension-sso",version:o})}),t.get("/mobile-callback",async(t,e)=>{const o=k(t);n.info(`${o?"🌐":"📱"} ${o?"Browser":"Mobile"} callback received`),n.info("Cookies: "+JSON.stringify(t.cookies)),n.info("Query: "+JSON.stringify(t.query));try{let r=null;if(m!==y&&(r=await b(t,m)),r||(r=await b(t,y)),!r)return n.error("❌ No valid session token found in any cookies"),e.send(`\n\t\t\t\t\t\t<html>\n\t\t\t\t\t\t\t<body>\n\t\t\t\t\t\t\t\t<h2>Authentication Failed</h2>\n\t\t\t\t\t\t\t\t<p>No valid session found. Please try logging in again.</p>\n\t\t\t\t\t\t\t\t<a href="${c}/auth/login/keycloak">Try Again</a>\n\t\t\t\t\t\t\t</body>\n\t\t\t\t\t\t</html>\n\t\t\t\t\t`);const{token:a,userData:i}=r,s=i.id,u=i.email;n.info("👤 User authenticated: "+s+", "+u);const g=a;if(n.info("🎫 Using session token as access token"),o){n.info("🌐 Browser request detected - maintaining session"),e.cookie(m,a,{httpOnly:!0,secure:p,domain:h,sameSite:f,maxAge:6048e5,path:"/"}),m!==y&&e.cookie(y,"",{maxAge:0,path:"/"});let o=t.query.redirect_uri||t.query.redirect||"/";try{if(o.startsWith("http")){const t=new URL(o);t.searchParams.set("access_token",g),t.searchParams.set("user_id",s),t.searchParams.set("email",u||""),o=t.toString()}}catch(t){n.error("❌ Failed to parse Web redirect URL: "+o)}return n.info("🔄 Redirecting browser to: "+o),e.redirect(o)}const k=d.startsWith("/")?d:`//${d}`,x=`${l}:${k}?access_token=${g}&user_id=${s}&email=${encodeURIComponent(u||"")}`;n.info("🔄 Redirecting to app: "+x),e.redirect(x)}catch(t){n.error("❌ Error in callback:",t),e.status(500).send(`\n\t\t<html>\n\t\t\t<body>\n\t\t\t\t<h2>Error</h2>\n\t\t\t\t<p>${t.message}</p>\n\t\t\t\t<a href="${c}/auth/login/keycloak">Try Again</a>\n\t\t\t</body>\n\t\t</html>\n\t`)}}),t.get("/google-callback",async(t,e)=>{const o=k(t);n.info(`${o?"🌐":"📱"} ${o?"Browser":"Mobile"} Google callback received`),n.info("Cookies: "+JSON.stringify(t.cookies)),n.info("Query: "+JSON.stringify(t.query));try{let r=null;if(m!==y&&(r=await b(t,m)),r||(r=await b(t,y)),!r)return n.error("❌ No valid session token found in any cookies (Google)"),e.send("\n\t\t\t\t\t\t<html>\n\t\t\t\t\t\t\t<head>\n\t\t\t\t\t\t\t\t<title>Authentication Failed</title>\n\t\t\t\t\t\t\t\t<style>\n\t\t\t\t\t\t\t\t\tbody { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; \n\t\t\t\t\t\t\t\t\t padding: 40px; text-align: center; background: #f5f5f5; }\n\t\t\t\t\t\t\t\t\t.container { max-width: 500px; margin: 0 auto; background: white; \n\t\t\t\t\t\t\t\t\t padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }\n\t\t\t\t\t\t\t\t\th2 { color: #e74c3c; }\n\t\t\t\t\t\t\t\t\ta { display: inline-block; margin-top: 20px; padding: 10px 20px; \n\t\t\t\t\t\t\t\t\t background: #4285f4; color: white; text-decoration: none; border-radius: 4px; }\n\t\t\t\t\t\t\t\t\ta:hover { background: #357ae8; }\n\t\t\t\t\t\t\t\t</style>\n\t\t\t\t\t\t\t</head>\n\t\t\t\t\t\t\t<body>\n\t\t\t\t\t\t\t\t<div class=\"container\">\n\t\t\t\t\t\t\t\t\t<h2>Authentication Failed</h2>\n\t\t\t\t\t\t\t\t\t<p>No valid session found. Please close this and try logging in again.</p>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</body>\n\t\t\t\t\t\t</html>\n\t\t\t\t\t");const{token:a,userData:i}=r,s=i.id,c=i.email,d=i.first_name||i.email;n.info("👤 User authenticated via Google: "+s+", "+c);const g=a;if(n.info("🎫 Using session token as access token"),o){n.info("🌐 Browser request detected - maintaining session"),e.cookie(m,a,{httpOnly:!0,secure:p,domain:h,sameSite:f,maxAge:6048e5,path:"/"}),m!==y&&e.cookie(y,"",{maxAge:0,path:"/"});let o=t.query.redirect_uri||t.query.redirect||"/";try{if(o.startsWith("http")){const t=new URL(o);t.searchParams.set("access_token",g),t.searchParams.set("user_id",s),t.searchParams.set("email",c||""),t.searchParams.set("provider","google"),o=t.toString()}}catch(t){n.error("❌ Failed to parse Web redirect URL: "+o)}return n.info("🔄 Redirecting browser to: "+o),e.send(`\n\t\t\t\t\t\t<html>\n\t\t\t\t\t\t\t<head>\n\t\t\t\t\t\t\t\t<title>Login Successful</title>\n\t\t\t\t\t\t\t\t<meta http-equiv="refresh" content="2;url=${o}">\n\t\t\t\t\t\t\t\t<style>\n\t\t\t\t\t\t\t\t\tbody { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; \n\t\t\t\t\t\t\t\t\t padding: 40px; text-align: center; background: #f5f5f5; }\n\t\t\t\t\t\t\t\t\t.container { max-width: 500px; margin: 0 auto; background: white; \n\t\t\t\t\t\t\t\t\t padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }\n\t\t\t\t\t\t\t\t\th2 { color: #27ae60; }\n\t\t\t\t\t\t\t\t\t.checkmark { font-size: 48px; color: #27ae60; margin-bottom: 20px; }\n\t\t\t\t\t\t\t\t\tp { color: #666; margin: 10px 0; }\n\t\t\t\t\t\t\t\t\t.spinner { border: 3px solid #f3f3f3; border-top: 3px solid #4285f4; \n\t\t\t\t\t\t\t\t\t border-radius: 50%; width: 40px; height: 40px; \n\t\t\t\t\t\t\t\t\t animation: spin 1s linear infinite; margin: 20px auto; }\n\t\t\t\t\t\t\t\t\t@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }\n\t\t\t\t\t\t\t\t\ta { color: #4285f4; text-decoration: none; }\n\t\t\t\t\t\t\t\t\ta:hover { text-decoration: underline; }\n\t\t\t\t\t\t\t\t\t.google-icon { color: #4285f4; margin-right: 5px; }\n\t\t\t\t\t\t\t\t</style>\n\t\t\t\t\t\t\t</head>\n\t\t\t\t\t\t\t<body>\n\t\t\t\t\t\t\t\t<div class="container">\n\t\t\t\t\t\t\t\t\t<div class="checkmark">✓</div>\n\t\t\t\t\t\t\t\t\t<h2><span class="google-icon">🔵</span>Google Login Successful!</h2>\n\t\t\t\t\t\t\t\t\t<p>Welcome, ${d}!</p>\n\t\t\t\t\t\t\t\t\t<p>Your session has been saved. You can now access other services using the same login.</p>\n\t\t\t\t\t\t\t\t\t<div class="spinner"></div>\n\t\t\t\t\t\t\t\t\t<p style="margin-top: 20px;">Redirecting you automatically...</p>\n\t\t\t\t\t\t\t\t\t<p style="font-size: 14px; margin-top: 20px;">\n\t\t\t\t\t\t\t\t\t\t<a href="${o}">Click here if not redirected automatically</a>\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</body>\n\t\t\t\t\t\t</html>\n\t\t\t\t\t`)}n.info("📱 Mobile app request - redirecting with token");const k=new URL(`${l}://${u}`);k.searchParams.set("access_token",g),k.searchParams.set("user_id",s),k.searchParams.set("email",c||""),k.searchParams.set("provider","google"),n.info("🔄 Redirecting to app (Google): "+k.toString()),e.redirect(k.toString())}catch(t){n.error("❌ Error in Google callback:",t),e.status(500).send(`\n\t\t\t\t\t<html>\n\t\t\t\t\t\t<head>\n\t\t\t\t\t\t\t<title>Error</title>\n\t\t\t\t\t\t\t<style>\n\t\t\t\t\t\t\t\tbody { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; \n\t\t\t\t\t\t\t\t padding: 40px; text-align: center; background: #f5f5f5; }\n\t\t\t\t\t\t\t\t.container { max-width: 500px; margin: 0 auto; background: white; \n\t\t\t\t\t\t\t\t padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }\n\t\t\t\t\t\t\t\th2 { color: #e74c3c; }\n\t\t\t\t\t\t\t\t.error-icon { font-size: 48px; color: #e74c3c; margin-bottom: 20px; }\n\t\t\t\t\t\t\t\tpre { background: #f8f8f8; padding: 15px; border-radius: 4px; \n\t\t\t\t\t\t\t\t text-align: left; overflow-x: auto; font-size: 12px; }\n\t\t\t\t\t\t\t\ta { display: inline-block; margin-top: 20px; padding: 10px 20px; \n\t\t\t\t\t\t\t\t background: #4285f4; color: white; text-decoration: none; border-radius: 4px; }\n\t\t\t\t\t\t\t\ta:hover { background: #357ae8; }\n\t\t\t\t\t\t\t</style>\n\t\t\t\t\t\t</head>\n\t\t\t\t\t\t<body>\n\t\t\t\t\t\t\t<div class="container">\n\t\t\t\t\t\t\t\t<div class="error-icon">⚠️</div>\n\t\t\t\t\t\t\t\t<h2>Error</h2>\n\t\t\t\t\t\t\t\t<p>An error occurred during Google authentication:</p>\n\t\t\t\t\t\t\t\t<pre>${t.message}</pre>\n\t\t\t\t\t\t\t\t<a href="${c}/auth/login/google">Try Again</a>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</body>\n\t\t\t\t\t</html>\n\t\t\t\t`)}}),t.post("/mobile-logout",async(t,e)=>{n.info("🚪 Logout request received");try{const o=t.headers.authorization,l=o?.replace("Bearer ","");if(!l)return e.status(400).json({error:"No token provided",message:"Authorization header with Bearer token is required"});n.info("🎫 Token received: "+l.substring(0,20)+"...");let d=null;try{const t=await fetch(`${c}/users/me`,{headers:{Authorization:`Bearer ${l}`}});if(t.ok){d=(await t.json()).data.email,n.info("👤 User email: "+d)}}catch(t){n.error("⚠️ Error getting user info: "+t.message)}try{const t=await fetch(`${c}/auth/logout`,{method:"POST",headers:{Authorization:`Bearer ${l}`,"Content-Type":"application/json"},body:JSON.stringify({refresh_token:l})});t.ok?n.info("✅ Directus session invalidated"):n.info("⚠️ Directus logout response: "+t.status)}catch(t){n.error("⚠️ Error logging out from Directus: "+t.message)}if(d)try{n.info("🔐 Getting Keycloak admin token...");const t=await async function(){try{const t=await fetch(`${r}/realms/master/protocol/openid-connect/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"password",client_id:g,username:i,password:s}).toString()});if(!t.ok)throw new Error("Failed to get admin token");return(await t.json()).access_token}catch(t){return n.error("Error getting admin token:",t),null}}();if(t){n.info("🔍 Looking up Keycloak user...");const e=await async function(t,e){try{const o=await fetch(`${r}/admin/realms/${a}/users?email=${encodeURIComponent(e)}`,{headers:{Authorization:`Bearer ${t}`}});if(!o.ok)throw new Error("Failed to get user");const n=await o.json();return n.length>0?n[0].id:null}catch(t){return n.error("Error getting user ID:",t),null}}(t,d);if(e){n.info("🚪 Logging out from Keycloak...");const o=await async function(t,e){try{const o=await fetch(`${r}/admin/realms/${a}/users/${e}/logout`,{method:"POST",headers:{Authorization:`Bearer ${t}`}});return o.ok||204===o.status}catch(t){return n.error("Error logging out user from Keycloak:",t),!1}}(t,e);o?n.info("✅ Keycloak sessions terminated"):n.info("⚠️ Failed to logout from Keycloak")}else n.info("⚠️ User not found in Keycloak")}else n.info("⚠️ Failed to get Keycloak admin token")}catch(t){n.error("⚠️ Error logging out from Keycloak: "+t.message)}n.info("🎉 Logout completed"),e.json({success:!0,message:"Logged out successfully from Directus and Keycloak"})}catch(t){n.error("❌ Error in logout:",t),e.status(500).json({error:t.message,message:"Failed to logout"})}})}};export{n as default};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@ymys/directus-extension-sso",
3
3
  "description": "Mobile OAuth proxy endpoints for Directus + Keycloak + Google",
4
4
  "icon": "smartphone",
5
- "version": "1.5.1",
5
+ "version": "1.5.3",
6
6
  "keywords": [
7
7
  "directus",
8
8
  "directus-extension",