@ymys/directus-extension-sso 2.0.1 → 2.0.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.
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createRequire as t}from"module";import e from"node:crypto";const n=t(import.meta.url),{version:o}=n("../package.json");var r={id:"sso",handler:(t,n)=>{const{env:r,logger:a,services:i,database:s,getSchema:c}=n,l=r.KEYCLOAK_URL||"http://keycloak:8080",d=r.KEYCLOAK_REALM||"testing",p=r.KEYCLOAK_ADMIN_USER||"admin",u=r.KEYCLOAK_ADMIN_PASSWORD||"admin",f=r.PUBLIC_URL||"http://localhost:8055",h=r.MOBILE_APP_SCHEME||"portalpipq",g=r.MOBILE_APP_CALLBACK_PATH||"/auth/callback",m=r.GOOGLE_CALLBACK_PATH||"/auth/callback/google",y=r.KEYCLOAK_CLIENT_ID||"admin-cli",k=r.COOKIE_DOMAIN||null,b="false"!==r.COOKIE_SECURE,x=r.COOKIE_SAME_SITE||"lax",w=r.SESSION_COOKIE_NAME||"directus_session_token",$=r.REFRESH_TOKEN_COOKIE_NAME||"directus_refresh_token",_="directus_session_token";function v(t){if("browser"===t.query.type)return!0;if("mobile"===t.query.type)return!1;if(t.query.app_scheme||t.query.app_path)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 E(t,e){const n=t.headers.cookie;if(!n)return null;const o=n.split(";").map(t=>t.trim()).filter(t=>t.startsWith(`${e}=`)).map(t=>t.substring(e.length+1));if(0===o.length)return null;a.info(`🔍 [${e}] Found ${o.length} possible tokens. Trying them...`);for(let t=0;t<o.length;t++){const n=o[t];try{const o=await fetch(`${f}/users/me`,{headers:{Cookie:`${e}=${n}`}});if(o.ok){const r=await o.json();return a.info(`✅ [${e}] Token #${t+1} succeeded for ${r.data.email}`),{token:n,userData:r.data}}a.info(`❌ [${e}] Token #${t+1} failed (${o.status})`)}catch(n){a.error(`⚠️ [${e}] Error trying token #${t+1}: ${n.message}`)}}return null}async function A(t){const e=t.headers.cookie;if(!e)return null;const n=e.split(";").map(t=>t.trim()).map(t=>{const e=t.split("=");return e.length>1?e[1]:null}).filter(t=>t&&t.startsWith("eyJ")&&t.length>50);if(0===n.length)return null;a.info(`🔍 [MEGA] Found ${n.length} potential JWTs in cookies. Trying them...`);for(let t=0;t<n.length;t++){const e=n[t];try{const n=await fetch(`${f}/users/me`,{headers:{Authorization:`Bearer ${e}`}});if(n.ok){const o=await n.json();return a.info(`✅ [MEGA] Token #${t+1} succeeded for ${o.data.email}`),{token:e,userData:o.data}}}catch(t){}}return null}async function S(t){const e=t.cookies[$];if(!e)return null;a.info(`🔄 [REFRESH] Attempting to use ${$}...`);try{const t=await fetch(`${f}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e})});if(t.ok){const e=(await t.json()).data.access_token;a.info("✅ [REFRESH] Successfully refreshed session");const n=await fetch(`${f}/users/me`,{headers:{Authorization:`Bearer ${e}`}});if(n.ok){return{token:e,userData:(await n.json()).data}}}}catch(t){a.error("❌ [REFRESH] Failed: "+t.message)}return null}a.info("🚀 Mobile Auth Proxy Extension loaded"),a.info("🔐 Keycloak URL: "+l),a.info("🌐 Keycloak Realm: "+d),a.info("📡 Public URL: "+f),a.info("🍪 Session Cookie Name: "+w),a.info("🍪 Refresh Token Cookie Name: "+$),a.info("📱 Mobile App Scheme: "+h+"://"+g),a.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 n=v(t);a.info(`${n?"🌐":"📱"} ${n?"Browser":"Mobile"} callback received`),a.info("Host Header: "+t.headers.host),a.info("Cookies Header: "+t.headers.cookie),a.info("Parsed Cookies: "+JSON.stringify(t.cookies)),a.info("Query: "+JSON.stringify(t.query));try{let o=null;if(w!==_&&(o=await E(t,w)),o||(o=await E(t,_)),o||(o=await A(t)),o||(o=await S(t)),!o)return a.error("❌ No valid session or refresh 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<div style="font-size: 11px; color: #999; margin-top: 20px; text-align: left;">\n\t\t\t\t\t\t\t\t\t<strong>Debug Info (v1.5.4):</strong><br>\n\t\t\t\t\t\t\t\t\tInstance: ${w}<br>\n\t\t\t\t\t\t\t\t\tURL: ${f}<br>\n\t\t\t\t\t\t\t\t\tHost: ${t.headers.host}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<a href="${f}/auth/login/keycloak" style="display: inline-block; margin-top: 20px;">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:r,userData:i}=o,s=i.id,c=i.email;a.info("👤 User authenticated: "+s+", "+c);const l=r;if(a.info("🎫 Using session token as access token"),n){a.info("🌐 Browser request detected - maintaining session"),e.cookie(w,r,{httpOnly:!0,secure:b,domain:k,sameSite:x,maxAge:6048e5,path:"/"}),w!==_&&e.cookie(_,"",{maxAge:0,path:"/"});let n=t.query.redirect_uri||t.query.redirect||"/";try{if(n.startsWith("http")){const t=new URL(n);t.searchParams.set("access_token",l),t.searchParams.set("user_id",s),t.searchParams.set("email",c||""),n=t.toString()}}catch(t){a.error("❌ Failed to parse Web redirect URL: "+n)}return a.info("🔄 Redirecting browser to: "+n),e.redirect(n)}const d=t.query.app_scheme||h,p=`${d}://${(t.query.app_path||g).replace(/^\/+/,"")}?access_token=${l}&user_id=${s}&email=${encodeURIComponent(c||"")}`;return a.info("🔄 Attempting AUTO-REDIRECT to app: "+p),e.setHeader("Location",p),e.status(302).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>Authenticating...</title>\n\t\t\t\t\t\t\t<meta name="viewport" content="width=device-width, initial-scale=1">\n\t\t\t\t\t\t\t<meta http-equiv="refresh" content="0;url=${p}">\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: #fff; }\n\t\t\t\t\t\t\t\t.lds-dual-ring { display: inline-block; width: 40px; height: 40px; margin-bottom: 20px; }\n\t\t\t\t\t\t\t\t.lds-dual-ring:after { content: " "; display: block; width: 32px; height: 32px; margin: 8px; \n\t\t\t\t\t\t\t\t border-radius: 50%; border: 4px solid #4f46e5; \n\t\t\t\t\t\t\t\t\t\t\t\t\t border-color: #4f46e5 transparent #4f46e5 transparent; \n\t\t\t\t\t\t\t\t\t\t\t\t\t animation: lds-dual-ring 1.2s linear infinite; }\n\t\t\t\t\t\t\t\t@keyframes lds-dual-ring { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }\n\t\t\t\t\t\t\t\t.btn { display: inline-block; padding: 12px 24px; background: #4f46e5; color: white; \n\t\t\t\t\t\t\t\t text-decoration: none; border-radius: 6px; font-weight: 500; margin-top: 20px; }\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="lds-dual-ring"></div>\n\t\t\t\t\t\t\t<h2>Finishing Login...</h2>\n\t\t\t\t\t\t\t<p>You are being redirected back to the app.</p>\n\t\t\t\t\t\t\t<p style="font-size: 14px; color: #666; margin-top: 20px;">If the app doesn't open automatically, please click below:</p>\n\t\t\t\t\t\t\t<a id="redirect-btn" href="${p}" class="btn">Return to App</a>\n\t\t\t\t\t\t\t<script>\n\t\t\t\t\t\t\t\t// JavaScript fallback for auto-redirect\n\t\t\t\t\t\t\t\twindow.onload = function() {\n\t\t\t\t\t\t\t\t\twindow.location.href = "${p}";\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t<\/script>\n\t\t\t\t\t\t</body>\n\t\t\t\t\t</html>\n\t\t\t\t`)}catch(t){a.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="${f}/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 n=v(t);a.info(`${n?"🌐":"📱"} ${n?"Browser":"Mobile"} Google callback received`),a.info("Cookies: "+JSON.stringify(t.cookies)),a.info("Query: "+JSON.stringify(t.query));try{let o=null;if(t.query.access_token&&(a.info("🎟️ Found access_token in URL query string! Bypassing cookie check entirely."),o={access_token:t.query.access_token,refresh_token:t.query.refresh_token||null,expires:t.query.expires||null}),o||w===_||(o=await E(t,w)),o||(o=await E(t,_)),o||(o=await A(t)),o||(o=await S(t)),!o)return a.error("❌ No valid session or refresh 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\t.debug { font-size: 11px; color: #999; margin-top: 20px; text-align: left; }\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\t<div class="debug">\n\t\t\t\t\t\t\t\t\t\t<strong>Debug Info (v1.5.4):</strong><br>\n\t\t\t\t\t\t\t\t\t\tInstance: ${w} | Google\n\t\t\t\t\t\t\t\t\t</div>\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:r,userData:i}=o,s=i.id,c=i.email,l=i.first_name||i.email;a.info("👤 User authenticated via Google: "+s+", "+c);const d=r;if(a.info("🎫 Using session token as access token"),n){a.info("🌐 Browser request detected - maintaining session"),e.cookie(w,r,{httpOnly:!0,secure:b,domain:k,sameSite:x,maxAge:6048e5,path:"/"}),w!==_&&e.cookie(_,"",{maxAge:0,path:"/"});let n=t.query.redirect_uri||t.query.redirect||"/";try{if(n.startsWith("http")){const t=new URL(n);t.searchParams.set("access_token",d),t.searchParams.set("user_id",s),t.searchParams.set("email",c||""),t.searchParams.set("provider","google"),n=t.toString()}}catch(t){a.error("❌ Failed to parse Web redirect URL: "+n)}return a.info("🔄 Redirecting browser to: "+n),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=${n}">\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, ${l}!</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="${n}">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`)}a.info("📱 Mobile app request - redirecting with token");const p=t.query.app_scheme||h,u=t.query.app_path||m,f=new URL(`${p}://${u.replace(/^\/+/,"")}`);return f.searchParams.set("access_token",d),f.searchParams.set("user_id",s),f.searchParams.set("email",c||""),f.searchParams.set("provider","google"),a.info("🔄 Redirecting to app (Google): "+f.toString()),a.info("🚀 Performing direct 302 redirect to app: "+f.toString()),e.redirect(302,f.toString())}catch(t){a.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="${f}/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)=>{a.info("🚪 Logout request received");try{const n=t.headers.authorization,o=n?.replace("Bearer ","");if(!o)return e.status(400).json({error:"No token provided",message:"Authorization header with Bearer token is required"});a.info("🎫 Token received: "+o.substring(0,20)+"...");let r=null;try{const t=await fetch(`${f}/users/me`,{headers:{Authorization:`Bearer ${o}`}});if(t.ok){r=(await t.json()).data.email,a.info("👤 User email: "+r)}}catch(t){a.error("⚠️ Error getting user info: "+t.message)}try{const t=await fetch(`${f}/auth/logout`,{method:"POST",headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json"},body:JSON.stringify({refresh_token:o})});t.ok?a.info("✅ Directus session invalidated"):a.info("⚠️ Directus logout response: "+t.status)}catch(t){a.error("⚠️ Error logging out from Directus: "+t.message)}if(r)try{a.info("🔐 Getting Keycloak admin token...");const t=await async function(){try{const t=await fetch(`${l}/realms/master/protocol/openid-connect/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"password",client_id:y,username:p,password:u}).toString()});if(!t.ok)throw new Error("Failed to get admin token");return(await t.json()).access_token}catch(t){return a.error("Error getting admin token:",t),null}}();if(t){a.info("🔍 Looking up Keycloak user...");const e=await async function(t,e){try{const n=await fetch(`${l}/admin/realms/${d}/users?email=${encodeURIComponent(e)}`,{headers:{Authorization:`Bearer ${t}`}});if(!n.ok)throw new Error("Failed to get user");const o=await n.json();return o.length>0?o[0].id:null}catch(t){return a.error("Error getting user ID:",t),null}}(t,r);if(e){a.info("🚪 Logging out from Keycloak...");const n=await async function(t,e){try{const n=await fetch(`${l}/admin/realms/${d}/users/${e}/logout`,{method:"POST",headers:{Authorization:`Bearer ${t}`}});return n.ok||204===n.status}catch(t){return a.error("Error logging out user from Keycloak:",t),!1}}(t,e);n?a.info("✅ Keycloak sessions terminated"):a.info("⚠️ Failed to logout from Keycloak")}else a.info("⚠️ User not found in Keycloak")}else a.info("⚠️ Failed to get Keycloak admin token")}catch(t){a.error("⚠️ Error logging out from Keycloak: "+t.message)}a.info("🎉 Logout completed"),e.json({success:!0,message:"Logged out successfully from Directus and Keycloak"})}catch(t){a.error("❌ Error in logout:",t),e.status(500).json({error:t.message,message:"Failed to logout"})}}),t.post("/apple-token",async(t,n)=>{const{identityToken:o,firstName:l,lastName:d}=t.body;if(a.info("🍎 Apple token exchange request received"),!o)return n.status(400).json({error:"identityToken is required",message:"Apple identityToken must be provided in the request body"});try{const t="com.forumbandung.app",p=async n=>{const[o,r,i]=n.split("."),s=JSON.parse(Buffer.from(o,"base64").toString()),c=JSON.parse(Buffer.from(r,"base64").toString());if(a.info("🍎 Apple Token Payload: "+JSON.stringify(c)),"https://appleid.apple.com"!==c.iss)throw new Error("Invalid issuer");const l=[t,"host.exp.exponent"];if(!l.includes(c.aud))throw a.error(`❌ Invalid audience: ${c.aud}. Expected one of: ${l.join(", ")}`),new Error("Invalid audience");if(c.exp<Math.floor(Date.now()/1e3))throw new Error("Token expired");const d=await fetch("https://appleid.apple.com/auth/keys"),{keys:p}=await d.json(),u=p.find(t=>t.kid===s.kid);if(!u)throw new Error("Apple public key not found");const f=e.createPublicKey({key:u,format:"jwk"}),h=e.createVerify("RSA-SHA256");h.update(`${o}.${r}`);if(!h.verify(f,i,"base64url"))throw new Error("Invalid signature");return c},u=await p(o),{email:f,sub:h}=u;if(!f)throw new Error("Apple token did not contain an email");a.info(`✅ Apple token verified for: ${f} (${h})`);const{UsersService:g,AuthenticationService:m}=i,y=await c(),k=new g({schema:y,knex:s}),b=await k.readByQuery({filter:{email:{_eq:f}}});let x,w;b.length>0?(w=b[0],x=w.id,a.info(`👤 Found existing user: ${x}`),w.external_identifier||await k.updateOne(x,{external_identifier:h,provider:"apple"})):(a.info(`📝 Creating new user for: ${f}`),x=await k.createOne({email:f,first_name:l||"Apple User",last_name:d||"",role:r.DEFAULT_ROLE_ID||"36010211-604f-4ce3-84d9-4e69d16781a1",status:"active",provider:"apple",external_identifier:h}),w=await k.readOne(x));new m({schema:y,knex:s});try{n.json({success:!0,data:{user:w,token:"Apple session established"},user_id:x,email:f,provider:"apple"})}catch(t){a.error("⚠️ Could not generate internal session token: "+t.message),n.json({success:!0,user_id:x,email:f,message:"Apple authentication verified but session generation failed. Check server logs."})}}catch(t){a.error("❌ Error in Apple token exchange:",t),n.status(500).json({error:t.message,message:"Failed to verify Apple token"})}})}};export{r as default};
|
|
1
|
+
import{createRequire as t}from"module";import e from"node:crypto";const n=t(import.meta.url),{version:o}=n("../package.json");var r={id:"sso",handler:(t,n)=>{const{env:r,logger:a,services:i,database:s,getSchema:c}=n,l=r.KEYCLOAK_URL||"http://keycloak:8080",d=r.KEYCLOAK_REALM||"testing",u=r.KEYCLOAK_ADMIN_USER||"admin",p=r.KEYCLOAK_ADMIN_PASSWORD||"admin",f=r.PUBLIC_URL||"http://localhost:8055",g=r.MOBILE_APP_SCHEME||"portalpipq",h=r.MOBILE_APP_CALLBACK_PATH||"/auth/callback",m=r.GOOGLE_CALLBACK_PATH||"/auth/callback/google",y=r.KEYCLOAK_CLIENT_ID||"admin-cli",k=r.COOKIE_DOMAIN||null,b="false"!==r.COOKIE_SECURE,w=r.COOKIE_SAME_SITE||"lax",x=r.SESSION_COOKIE_NAME||"directus_session_token",$=r.REFRESH_TOKEN_COOKIE_NAME||"directus_refresh_token",v="directus_session_token";function _(t){if("browser"===t.query.type)return!0;if("mobile"===t.query.type)return!1;if(t.query.app_scheme||t.query.app_path)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 A(t,e){const n=t.headers.cookie;if(!n)return null;const o=n.split(";").map(t=>t.trim()).filter(t=>t.startsWith(`${e}=`)).map(t=>t.substring(e.length+1));if(0===o.length)return null;a.info(`🔍 [${e}] Found ${o.length} possible tokens. Trying them...`);for(let t=0;t<o.length;t++){const n=o[t];try{const o=await fetch(`${f}/users/me`,{headers:{Cookie:`${e}=${n}`}});if(o.ok){const r=await o.json();return a.info(`✅ [${e}] Token #${t+1} succeeded for ${r.data.email}`),{token:n,userData:r.data}}a.info(`❌ [${e}] Token #${t+1} failed (${o.status})`)}catch(n){a.error(`⚠️ [${e}] Error trying token #${t+1}: ${n.message}`)}}return null}async function E(t){const e=t.headers.cookie;if(!e)return null;const n=e.split(";").map(t=>t.trim()).map(t=>{const e=t.split("=");return e.length>1?e[1]:null}).filter(t=>t&&t.startsWith("eyJ")&&t.length>50);if(0===n.length)return null;a.info(`🔍 [MEGA] Found ${n.length} potential JWTs in cookies. Trying them...`);for(let t=0;t<n.length;t++){const e=n[t];try{const n=await fetch(`${f}/users/me`,{headers:{Authorization:`Bearer ${e}`}});if(n.ok){const o=await n.json();return a.info(`✅ [MEGA] Token #${t+1} succeeded for ${o.data.email}`),{token:e,userData:o.data}}}catch(t){}}return null}async function S(t){const e=t.cookies[$];if(!e)return null;a.info(`🔄 [REFRESH] Attempting to use ${$}...`);try{const t=await fetch(`${f}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e})});if(t.ok){const e=(await t.json()).data.access_token;a.info("✅ [REFRESH] Successfully refreshed session");const n=await fetch(`${f}/users/me`,{headers:{Authorization:`Bearer ${e}`}});if(n.ok){return{token:e,userData:(await n.json()).data}}}}catch(t){a.error("❌ [REFRESH] Failed: "+t.message)}return null}a.info("🚀 Mobile Auth Proxy Extension loaded"),a.info("🔐 Keycloak URL: "+l),a.info("🌐 Keycloak Realm: "+d),a.info("📡 Public URL: "+f),a.info("🍪 Session Cookie Name: "+x),a.info("🍪 Refresh Token Cookie Name: "+$),a.info("📱 Mobile App Scheme: "+g+"://"+h),a.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 n=_(t);a.info(`${n?"🌐":"📱"} ${n?"Browser":"Mobile"} callback received`),a.info("Host Header: "+t.headers.host),a.info("Cookies Header: "+t.headers.cookie),a.info("Parsed Cookies: "+JSON.stringify(t.cookies)),a.info("Query: "+JSON.stringify(t.query));try{let o=null;if(x!==v&&(o=await A(t,x)),o||(o=await A(t,v)),o||(o=await E(t)),o||(o=await S(t)),!o)return a.error("❌ No valid session or refresh 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<div style="font-size: 11px; color: #999; margin-top: 20px; text-align: left;">\n\t\t\t\t\t\t\t\t\t<strong>Debug Info (v1.5.4):</strong><br>\n\t\t\t\t\t\t\t\t\tInstance: ${x}<br>\n\t\t\t\t\t\t\t\t\tURL: ${f}<br>\n\t\t\t\t\t\t\t\t\tHost: ${t.headers.host}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<a href="${f}/auth/login/keycloak" style="display: inline-block; margin-top: 20px;">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:r,userData:i}=o,s=i.id,c=i.email;a.info("👤 User authenticated: "+s+", "+c);const l=r;if(a.info("🎫 Using session token as access token"),n){a.info("🌐 Browser request detected - maintaining session"),e.cookie(x,r,{httpOnly:!0,secure:b,domain:k,sameSite:w,maxAge:6048e5,path:"/"}),x!==v&&e.cookie(v,"",{maxAge:0,path:"/"});let n=t.query.redirect_uri||t.query.redirect||"/";try{if(n.startsWith("http")){const t=new URL(n);t.searchParams.set("access_token",l),t.searchParams.set("user_id",s),t.searchParams.set("email",c||""),n=t.toString()}}catch(t){a.error("❌ Failed to parse Web redirect URL: "+n)}return a.info("🔄 Redirecting browser to: "+n),e.redirect(n)}const d=t.query.app_scheme||g,u=`${d}://${(t.query.app_path||h).replace(/^\/+/,"")}?access_token=${l}&user_id=${s}&email=${encodeURIComponent(c||"")}`;return a.info("🔄 Attempting AUTO-REDIRECT to app: "+u),e.setHeader("Location",u),e.status(302).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>Authenticating...</title>\n\t\t\t\t\t\t\t<meta name="viewport" content="width=device-width, initial-scale=1">\n\t\t\t\t\t\t\t<meta http-equiv="refresh" content="0;url=${u}">\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: #fff; }\n\t\t\t\t\t\t\t\t.lds-dual-ring { display: inline-block; width: 40px; height: 40px; margin-bottom: 20px; }\n\t\t\t\t\t\t\t\t.lds-dual-ring:after { content: " "; display: block; width: 32px; height: 32px; margin: 8px; \n\t\t\t\t\t\t\t\t border-radius: 50%; border: 4px solid #4f46e5; \n\t\t\t\t\t\t\t\t\t\t\t\t\t border-color: #4f46e5 transparent #4f46e5 transparent; \n\t\t\t\t\t\t\t\t\t\t\t\t\t animation: lds-dual-ring 1.2s linear infinite; }\n\t\t\t\t\t\t\t\t@keyframes lds-dual-ring { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }\n\t\t\t\t\t\t\t\t.btn { display: inline-block; padding: 12px 24px; background: #4f46e5; color: white; \n\t\t\t\t\t\t\t\t text-decoration: none; border-radius: 6px; font-weight: 500; margin-top: 20px; }\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="lds-dual-ring"></div>\n\t\t\t\t\t\t\t<h2>Finishing Login...</h2>\n\t\t\t\t\t\t\t<p>You are being redirected back to the app.</p>\n\t\t\t\t\t\t\t<p style="font-size: 14px; color: #666; margin-top: 20px;">If the app doesn't open automatically, please click below:</p>\n\t\t\t\t\t\t\t<a id="redirect-btn" href="${u}" class="btn">Return to App</a>\n\t\t\t\t\t\t\t<script>\n\t\t\t\t\t\t\t\t// JavaScript fallback for auto-redirect\n\t\t\t\t\t\t\t\twindow.onload = function() {\n\t\t\t\t\t\t\t\t\twindow.location.href = "${u}";\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t<\/script>\n\t\t\t\t\t\t</body>\n\t\t\t\t\t</html>\n\t\t\t\t`)}catch(t){a.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="${f}/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 n=_(t);a.info(`${n?"🌐":"📱"} ${n?"Browser":"Mobile"} Google callback received`),a.info("Cookies: "+JSON.stringify(t.cookies)),a.info("Query: "+JSON.stringify(t.query));try{let o=null;if(t.query.access_token&&(a.info("🎟️ Found access_token in URL query string! Bypassing cookie check entirely."),o={access_token:t.query.access_token,refresh_token:t.query.refresh_token||null,expires:t.query.expires||null}),o||x===v||(o=await A(t,x)),o||(o=await A(t,v)),o||(o=await E(t)),o||(o=await S(t)),!o)return a.error("❌ No valid session or refresh 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\t.debug { font-size: 11px; color: #999; margin-top: 20px; text-align: left; }\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\t<div class="debug">\n\t\t\t\t\t\t\t\t\t\t<strong>Debug Info (v1.5.4):</strong><br>\n\t\t\t\t\t\t\t\t\t\tInstance: ${x} | Google\n\t\t\t\t\t\t\t\t\t</div>\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:r,userData:i}=o,s=i.id,c=i.email,l=i.first_name||i.email;a.info("👤 User authenticated via Google: "+s+", "+c);const d=r;if(a.info("🎫 Using session token as access token"),n){a.info("🌐 Browser request detected - maintaining session"),e.cookie(x,r,{httpOnly:!0,secure:b,domain:k,sameSite:w,maxAge:6048e5,path:"/"}),x!==v&&e.cookie(v,"",{maxAge:0,path:"/"});let n=t.query.redirect_uri||t.query.redirect||"/";try{if(n.startsWith("http")){const t=new URL(n);t.searchParams.set("access_token",d),t.searchParams.set("user_id",s),t.searchParams.set("email",c||""),t.searchParams.set("provider","google"),n=t.toString()}}catch(t){a.error("❌ Failed to parse Web redirect URL: "+n)}return a.info("🔄 Redirecting browser to: "+n),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=${n}">\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, ${l}!</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="${n}">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`)}a.info("📱 Mobile app request - redirecting with token");const u=t.query.app_scheme||g,p=t.query.app_path||m,f=new URL(`${u}://${p.replace(/^\/+/,"")}`);return f.searchParams.set("access_token",d),f.searchParams.set("user_id",s),f.searchParams.set("email",c||""),f.searchParams.set("provider","google"),a.info("🔄 Redirecting to app (Google): "+f.toString()),a.info("🚀 Performing direct 302 redirect to app: "+f.toString()),e.redirect(302,f.toString())}catch(t){a.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="${f}/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)=>{a.info("🚪 Logout request received");try{const n=t.headers.authorization,o=n?.replace("Bearer ","");if(!o)return e.status(400).json({error:"No token provided",message:"Authorization header with Bearer token is required"});a.info("🎫 Token received: "+o.substring(0,20)+"...");let r=null;try{const t=await fetch(`${f}/users/me`,{headers:{Authorization:`Bearer ${o}`}});if(t.ok){r=(await t.json()).data.email,a.info("👤 User email: "+r)}}catch(t){a.error("⚠️ Error getting user info: "+t.message)}try{const t=await fetch(`${f}/auth/logout`,{method:"POST",headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json"},body:JSON.stringify({refresh_token:o})});t.ok?a.info("✅ Directus session invalidated"):a.info("⚠️ Directus logout response: "+t.status)}catch(t){a.error("⚠️ Error logging out from Directus: "+t.message)}if(r)try{a.info("🔐 Getting Keycloak admin token...");const t=await async function(){try{const t=await fetch(`${l}/realms/master/protocol/openid-connect/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"password",client_id:y,username:u,password:p}).toString()});if(!t.ok)throw new Error("Failed to get admin token");return(await t.json()).access_token}catch(t){return a.error("Error getting admin token:",t),null}}();if(t){a.info("🔍 Looking up Keycloak user...");const e=await async function(t,e){try{const n=await fetch(`${l}/admin/realms/${d}/users?email=${encodeURIComponent(e)}`,{headers:{Authorization:`Bearer ${t}`}});if(!n.ok)throw new Error("Failed to get user");const o=await n.json();return o.length>0?o[0].id:null}catch(t){return a.error("Error getting user ID:",t),null}}(t,r);if(e){a.info("🚪 Logging out from Keycloak...");const n=await async function(t,e){try{const n=await fetch(`${l}/admin/realms/${d}/users/${e}/logout`,{method:"POST",headers:{Authorization:`Bearer ${t}`}});return n.ok||204===n.status}catch(t){return a.error("Error logging out user from Keycloak:",t),!1}}(t,e);n?a.info("✅ Keycloak sessions terminated"):a.info("⚠️ Failed to logout from Keycloak")}else a.info("⚠️ User not found in Keycloak")}else a.info("⚠️ Failed to get Keycloak admin token")}catch(t){a.error("⚠️ Error logging out from Keycloak: "+t.message)}a.info("🎉 Logout completed"),e.json({success:!0,message:"Logged out successfully from Directus and Keycloak"})}catch(t){a.error("❌ Error in logout:",t),e.status(500).json({error:t.message,message:"Failed to logout"})}}),t.post("/apple-token",async(t,n)=>{const{identityToken:o,firstName:l,lastName:d}=t.body;if(a.info("🍎 Apple token exchange request received"),!o)return n.status(400).json({error:"identityToken is required",message:"Apple identityToken must be provided in the request body"});try{const t="com.forumbandung.app",u=async n=>{const[o,r,i]=n.split("."),s=JSON.parse(Buffer.from(o,"base64").toString()),c=JSON.parse(Buffer.from(r,"base64").toString());if(a.info("🍎 Apple Token Payload: "+JSON.stringify(c)),"https://appleid.apple.com"!==c.iss)throw new Error("Invalid issuer");const l=[t.toLowerCase(),"host.exp.exponent"],d=c.aud.toLowerCase();if(!l.includes(d))throw a.error(`❌ Invalid audience: ${c.aud}. Expected one of: ${l.join(", ")}`),new Error("Invalid audience");if(c.exp<Math.floor(Date.now()/1e3))throw new Error("Token expired");const u=await fetch("https://appleid.apple.com/auth/keys"),{keys:p}=await u.json(),f=p.find(t=>t.kid===s.kid);if(!f)throw new Error("Apple public key not found");const g=e.createPublicKey({key:f,format:"jwk"}),h=e.createVerify("RSA-SHA256");h.update(`${o}.${r}`);if(!h.verify(g,i,"base64url"))throw new Error("Invalid signature");return c},p=await u(o),{email:f,sub:g}=p;if(!f)throw new Error("Apple token did not contain an email");a.info(`✅ Apple token verified for: ${f} (${g})`);const{UsersService:h,AuthenticationService:m}=i,y=await c(),k=new h({schema:y,knex:s}),b=await k.readByQuery({filter:{email:{_eq:f}}});let w,x;b.length>0?(x=b[0],w=x.id,a.info(`👤 Found existing user: ${w}`),x.external_identifier||await k.updateOne(w,{external_identifier:g,provider:"apple"})):(a.info(`📝 Creating new user for: ${f}`),w=await k.createOne({email:f,first_name:l||"Apple User",last_name:d||"",role:r.DEFAULT_ROLE_ID||"36010211-604f-4ce3-84d9-4e69d16781a1",status:"active",provider:"apple",external_identifier:g}),x=await k.readOne(w));new m({schema:y,knex:s});try{const t=await k.readOne(w,{fields:["*","token"]});let o=t.token;o||(a.info(`🔑 Generating new static token for user: ${w}`),o=e.randomBytes(32).toString("hex"),await k.updateOne(w,{token:o})),n.json({success:!0,data:{user:t,token:o},user_id:w,email:f,provider:"apple"})}catch(t){a.error("⚠️ Could not generate static token: "+t.message),n.status(500).json({error:"Session error",message:"Apple authentication verified but session generation failed."})}}catch(t){a.error("❌ Error in Apple token exchange:",t),n.status(500).json({error:t.message,message:"Failed to verify Apple token"})}}),t.get("/bridge",async(t,e)=>{const{token:n,redirect_uri:o,redirect:r}=t.query,i=n,s=o||r||"/";if(a.info("🌉 WebView Bridge request received"),!i)return e.status(400).json({error:"Token required",message:"No access token provided"});try{const t=await fetch(`${f}/users/me`,{headers:{Authorization:`Bearer ${i}`}});if(!t.ok)return a.error("❌ Bridge: Invalid token provided"),e.status(401).json({error:"Invalid token",message:"The provided token is invalid or expired"});const n=await t.json();return a.info(`✅ Bridge: Session established for ${n.data.email}`),e.cookie(x,i,{httpOnly:!0,secure:b,domain:k,sameSite:w,maxAge:6048e5,path:"/"}),x!==v&&e.cookie(v,i,{httpOnly:!0,secure:b,domain:k,sameSite:w,maxAge:6048e5,path:"/"}),a.info("🔄 Bridge: Redirecting to "+s),e.redirect(s)}catch(t){a.error("❌ Bridge error: "+t.message),e.status(500).json({error:"Bridge failure",message:t.message})}})}};export{r as default};
|
package/package.json
CHANGED