@ymys/directus-extension-sso 2.0.5 → 2.0.7

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 (3) hide show
  1. package/README.md +59 -125
  2. package/dist/index.js +3 -1
  3. package/package.json +4 -2
package/README.md CHANGED
@@ -1,169 +1,103 @@
1
- # Directus SSO - Directus Extension
1
+ # Directus SSO - Mobile & Web OAuth Proxy
2
2
 
3
- This extension allows you to use the web and mobile OAuth callback and logout endpoints directly through your Directus domain.
3
+ This extension provides optimized OAuth callback and logout endpoints for Directus, supporting both **Mobile App Deep Linking** and **Web Browser SSO** (Single Sign-On) with automatic detection.
4
4
 
5
- ## Installation
5
+ ## 🚀 Features
6
6
 
7
- 1. **Build the extension:**
7
+ - **Unified Callbacks**: Handle both Keycloak and Google OAuth redirects in one place.
8
+ - **Smart Detection**: Automatically detects if the request comes from a mobile app or a web browser.
9
+ - **Resilient Authentication**:
10
+ - **Cookie Brute-Force**: Tries multiple session cookies to handle domain conflicts.
11
+ - **Mega Brute-Force**: Scans all cookies for valid JWT tokens if named lookups fail.
12
+ - **Refresh Fallback**: Automatically attempts to use refresh tokens if the session is expired.
13
+ - **Dynamic Deep Linking**: Support for multi-tenant mobile apps via `app_scheme` and `app_path` parameters.
14
+ - **Seamless Logout**: Terminate both Directus and Keycloak sessions simultaneously.
15
+
16
+ ## 📦 Installation
17
+
18
+ 1. **Copy to extensions folder:**
19
+ Ensure this project is in your Directus `extensions/endpoints/sso` directory.
20
+
21
+ 2. **Install dependencies & build:**
8
22
  ```bash
9
- cd extensions/endpoints/directus-extension-sso
10
23
  npm install
11
24
  npm run build
12
25
  ```
13
26
 
14
- 2. **The extension will be automatically loaded by Directus** when you restart it.
27
+ 3. **Restart Directus**: The extension will be loaded automatically.
15
28
 
16
- ## Endpoints
17
-
18
- Once installed, the following endpoints will be available on your Directus domain:
29
+ ## 🔌 Endpoints
19
30
 
20
31
  - **Health Check:** `GET /sso/health`
21
- - **Mobile/Browser Callback (Keycloak):** `GET /sso/mobile-callback`
22
- - **Mobile/Browser Callback (Google):** `GET /sso/google-callback`
23
- - **Mobile Logout:** `POST /sso/mobile-logout`
32
+ - **Keycloak Callback:** `GET /sso/mobile-callback`
33
+ - **Google Callback:** `GET /sso/google-callback`
34
+ - **Logout Proxy:** `POST /sso/mobile-logout`
35
+
36
+ ## ⚙️ Configuration
24
37
 
25
- ## Configuration
38
+ Add these variables to your Directus `.env` file:
26
39
 
27
- The extension uses environment variables from your Directus configuration:
40
+ ```env
41
+ # Directus Public URL
42
+ PUBLIC_URL=https://directus.example.com
28
43
 
29
- # Add these to your Directus .env file
30
- KEYCLOAK_URL=http://keycloak:8080
31
- KEYCLOAK_REALM=testing
44
+ # Keycloak Configuration
45
+ KEYCLOAK_URL=https://keycloak.example.com
46
+ KEYCLOAK_REALM=production
32
47
  KEYCLOAK_CLIENT_ID=admin-cli
33
48
  KEYCLOAK_ADMIN_USER=admin
34
- KEYCLOAK_ADMIN_PASSWORD=admin
35
- PUBLIC_URL=http://localhost:8055
36
- MOBILE_APP_SCHEME=portalpipq
49
+ KEYCLOAK_ADMIN_PASSWORD=your-password
50
+
51
+ # Mobile App Defaults
52
+ MOBILE_APP_SCHEME=myapp
37
53
  MOBILE_APP_CALLBACK_PATH=/auth/callback
38
54
  GOOGLE_CALLBACK_PATH=/auth/callback/google
39
- COOKIE_DOMAIN=.your-domain.com
55
+
56
+ # Web SSO Settings
57
+ COOKIE_DOMAIN=.example.com
40
58
  COOKIE_SECURE=true
41
59
  COOKIE_SAME_SITE=lax
42
60
  SESSION_COOKIE_NAME=directus_session_token
43
61
  REFRESH_TOKEN_COOKIE_NAME=directus_refresh_token
62
+ DEFAULT_ROLE_ID=your-default-role-uuid
44
63
  ```
45
64
 
46
- ### Security Note
47
-
48
- Ensure specific values like `KEYCLOAK_Admin_PASSWORD` are kept secure and not committed to version control. The extension now uses environment variables for all sensitive configuration.
49
-
50
- ## Usage
65
+ ### 🍎 Apple Authentication Note
51
66
 
52
- ### Browser Authentication Flow
67
+ Unlike Google (which uses Directus's native settings), the **Apple Native Token Flow** (`/apple-token`) manages user creation within this extension. You **must** provide a `DEFAULT_ROLE_ID` in your environment variables to ensure new Apple users are assigned the correct permissions.
53
68
 
54
- The extension now supports **both browser and mobile app authentication** with automatic detection. After successful Keycloak login:
69
+ ## 📱 Mobile Usage
55
70
 
56
- - **Browser requests**: Session is saved with cookies, allowing SSO across multiple services
57
- - **Mobile app requests**: Access token is returned via deep link as before
71
+ In your mobile app (e.g., React Native / Expo), point your OAuth login to Directus but use the proxy callbacks:
58
72
 
59
- #### Browser Login Example:
60
-
61
- **For Keycloak:**
73
+ ### 1. Keycloak Flow
62
74
  ```javascript
63
- // Simply navigate to the login URL
64
- window.location.href = 'http://your-directus-domain.com/auth/login/keycloak';
65
-
66
- // Or with a custom redirect after login
67
- window.location.href = 'http://your-directus-domain.com/auth/login/keycloak?redirect_uri=/dashboard';
75
+ const loginUrl = `${DIRECTUS_URL}/auth/login/keycloak?redirect_uri=${DIRECTUS_URL}/sso/mobile-callback`;
68
76
  ```
69
77
 
70
- **For Google:**
78
+ ### 2. Google Flow
71
79
  ```javascript
72
- // Simply navigate to the Google login URL
73
- window.location.href = 'http://your-directus-domain.com/auth/login/google';
74
-
75
- // Or with a custom redirect after login
76
- window.location.href = 'http://your-directus-domain.com/auth/login/google?redirect_uri=/dashboard';
80
+ const loginUrl = `${DIRECTUS_URL}/auth/login/google?redirect_uri=${DIRECTUS_URL}/sso/google-callback`;
77
81
  ```
78
82
 
79
- After successful login, the user will see a success page and the session cookie will be saved. The user can then access other URLs using the same SSO without logging in again.
80
-
81
- #### Forcing Browser Mode:
82
-
83
- If auto-detection doesn't work correctly, you can force browser mode:
84
-
83
+ ### 3. Dynamic App Redirection
84
+ If you have multiple apps or environments, you can override the scheme/path:
85
85
  ```javascript
86
- window.location.href = 'http://your-directus-domain.com/auth/login/keycloak?type=browser';
86
+ const loginUrl = `${DIRECTUS_URL}/auth/login/google?redirect_uri=${DIRECTUS_URL}/sso/google-callback&app_scheme=alternateapp&app_path=/custom/callback`;
87
87
  ```
88
88
 
89
- #### Query Parameters:
90
-
91
- - `type`: Force `browser` or `mobile` mode (optional, auto-detected if not provided)
92
- - `redirect_uri` or `redirect`: URL to redirect to after successful login (optional, defaults to `/admin`)
89
+ ## 🌐 Web SSO Usage
93
90
 
94
- ### Mobile App Authentication Flow
91
+ For browser-based apps, simply navigate to the login endpoints. The extension will set the appropriate cookies and redirect the user:
95
92
 
96
- Update your mobile app to use the new Directus domain URLs:
97
-
98
- ### Before (separate proxy server):
99
93
  ```javascript
100
- const PROXY_URL = 'http://your-proxy-domain.com:3000';
101
- const callbackUrl = `${PROXY_URL}/mobile-callback`;
94
+ // Navigates to Directus login, then redirects back to your dashboard with session cookies set
95
+ window.location.href = `${DIRECTUS_URL}/auth/login/keycloak?redirect_uri=https://dashboard.example.com`;
102
96
  ```
103
97
 
104
- ### After (Directus extension):
105
- ```javascript
106
- const DIRECTUS_URL = 'http://your-directus-domain.com';
107
- const callbackUrl = `${DIRECTUS_URL}/sso/mobile-callback`;
108
- ```
109
-
110
- ### Login Flow:
111
- ```javascript
112
- import * as WebBrowser from 'expo-web-browser';
113
-
114
- const result = await WebBrowser.openAuthSessionAsync(
115
- `${DIRECTUS_URL}/auth/login/keycloak`,
116
- 'myapp://auth/callback'
117
- );
118
- ```
119
-
120
- ### Logout:
121
- ```javascript
122
- const response = await fetch(`${DIRECTUS_URL}/sso/mobile-logout`, {
123
- method: 'POST',
124
- headers: {
125
- 'Authorization': `Bearer ${accessToken}`,
126
- },
127
- });
128
- ```
129
-
130
- ## Advantages Over Standalone Proxy
131
-
132
- 1. **Single Domain:** No need to deploy a separate server
133
- 2. **Unified Management:** Managed alongside your Directus instance
134
- 3. **Same Environment:** Uses Directus environment variables and configuration
135
- 4. **Built-in Logging:** Uses Directus logger for consistent logging
136
- 5. **Easier Deployment:** Deployed automatically with Directus
137
-
138
- ## Development
139
-
140
- To make changes and test locally:
141
-
142
- ```bash
143
- cd extensions/endpoints/directus-extension-sso
144
- npm run dev
145
- ```
146
-
147
- This will watch for changes and rebuild automatically.
148
-
149
- ## Keycloak Client Configuration
150
-
151
- Update your Keycloak client's redirect URI to use the Directus domain:
152
-
153
- **Valid Redirect URIs:**
154
- ```
155
- http://your-directus-domain.com/auth/login/keycloak/callback
156
- http://your-directus-domain.com/sso/mobile-callback
157
- myapp://auth/callback
158
- ```
159
-
160
- **Web Origins:**
161
- ```
162
- http://your-directus-domain.com
163
- ```
98
+ ## 🔐 Security Note
164
99
 
165
- ## Notes
100
+ This extension facilitates session bridging. Ensure `COOKIE_SECURE=true` is used in production and `COOKIE_DOMAIN` is correctly scoped to your parent domain (e.g., `.example.com`) to enable SSO between subdomains.
166
101
 
167
- - The extension requires Directus 11.0.0 or higher
168
- - Make sure cookie-parser middleware is available (Directus includes this by default)
169
- - The extension has access to the same network as Directus, so internal service URLs (like `http://keycloak:8080`) will work if using Docker
102
+ ---
103
+ Built with ❤️ for Directus.
package/dist/index.js CHANGED
@@ -1 +1,3 @@
1
- import{createRequire as t}from"module";import e from"node:crypto";import n from"jsonwebtoken";const o=t(import.meta.url),{version:r}=o("../package.json");var i={id:"sso",handler:(t,o)=>{const{env:i,logger:a,services:s,database:c,getSchema:l}=o,d=i.KEYCLOAK_URL||"http://keycloak:8080",p=i.KEYCLOAK_REALM||"testing",u=i.KEYCLOAK_ADMIN_USER||"admin",f=i.KEYCLOAK_ADMIN_PASSWORD||"admin",g=i.PUBLIC_URL||"http://localhost:8055",h=i.MOBILE_APP_SCHEME||"portalpipq",m=i.MOBILE_APP_CALLBACK_PATH||"/auth/callback",y=i.GOOGLE_CALLBACK_PATH||"/auth/callback/google",k=i.KEYCLOAK_CLIENT_ID||"admin-cli",b=i.COOKIE_DOMAIN||null,x="false"!==i.COOKIE_SECURE,w=i.COOKIE_SAME_SITE||"lax",_=i.SESSION_COOKIE_NAME||"directus_session_token",$=i.REFRESH_TOKEN_COOKIE_NAME||"directus_refresh_token",v="directus_session_token";function E(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(`${g}/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 S(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(`${g}/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 O(t){const e=t.cookies[$];if(!e)return null;a.info(`🔄 [REFRESH] Attempting to use ${$}...`);try{const t=await fetch(`${g}/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(`${g}/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: "+d),a.info("🌐 Keycloak Realm: "+p),a.info("📡 Public URL: "+g),a.info("🍪 Session Cookie Name: "+_),a.info("🍪 Refresh Token Cookie Name: "+$),a.info("📱 Mobile App Scheme: "+h+"://"+m),a.info("🔵 Google OAuth enabled"),t.get("/health",(t,e)=>{e.json({status:"ok",service:"directus-extension-sso",version:r})}),t.get("/mobile-callback",async(t,e)=>{const n=E(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(_!==v&&(o=await A(t,_)),o||(o=await A(t,v)),o||(o=await S(t)),o||(o=await O(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: ${_}<br>\n\t\t\t\t\t\t\t\t\tURL: ${g}<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="${g}/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(_,r,{httpOnly:!0,secure:x,domain:b,sameSite:w,maxAge:6048e5,path:"/"}),_!==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||h,p=`${d}://${(t.query.app_path||m).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="${g}/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=E(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||_===v||(o=await A(t,_)),o||(o=await A(t,v)),o||(o=await S(t)),o||(o=await O(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: ${_} | 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(_,r,{httpOnly:!0,secure:x,domain:b,sameSite:w,maxAge:6048e5,path:"/"}),_!==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 p=t.query.app_scheme||h,u=t.query.app_path||y,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="${g}/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(`${g}/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(`${g}/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(`${d}/realms/master/protocol/openid-connect/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"password",client_id:k,username:u,password:f}).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(`${d}/admin/realms/${p}/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(`${d}/admin/realms/${p}/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,o)=>{const{identityToken:r,firstName:d,lastName:p}=t.body;if(a.info("🍎 Apple token exchange request received"),!r)return o.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 p=await fetch("https://appleid.apple.com/auth/keys"),{keys:u}=await p.json(),f=u.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},f=await u(r),{email:g,sub:h}=f;if(!g)throw new Error("Apple token did not contain an email");a.info(`✅ Apple token verified for: ${g} (${h})`);const{UsersService:m,AuthenticationService:y}=s,k=new m({schema:await l(),knex:c}),b=await k.readByQuery({filter:{email:{_eq:g}}});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: ${g}`),x=await k.createOne({email:g,first_name:d||"Apple User",last_name:p||"",role:i.DEFAULT_ROLE_ID||"36010211-604f-4ce3-84d9-4e69d16781a1",status:"active",provider:"apple",external_identifier:h}),w=await k.readOne(x));try{const t={id:x,role:w.role||i.DEFAULT_ROLE_ID||"36010211-604f-4ce3-84d9-4e69d16781a1",app_access:!0,admin_access:!1},e=n.sign(t,i.SECRET,{expiresIn:"7d",issuer:"directus"}),r={id:x,type:"refresh"},a=n.sign(r,i.SECRET,{expiresIn:"30d",issuer:"directus"});o.json({success:!0,data:{access_token:e,refresh_token:a,expires:604800,user:w},user_id:x,email:g,provider:"apple"})}catch(t){a.error("⚠️ Could not generate JWT session token: "+t.message),o.status(500).json({error:"Session error",message:"Apple authentication verified but session generation failed."})}}catch(t){a.error("❌ Error in Apple token exchange:",t),o.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(`${g}/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(_,i,{httpOnly:!0,secure:x,domain:b,sameSite:w,maxAge:6048e5,path:"/"}),_!==v&&e.cookie(v,i,{httpOnly:!0,secure:x,domain:b,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{i as default};
1
+ import{createRequire as t}from"module";import e from"node:crypto";import r from"buffer";import n from"stream";import o from"util";import i from"crypto";function s(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var a={},c={exports:{}};
2
+ /*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
3
+ !function(t,e){var n=r,o=n.Buffer;function i(t,e){for(var r in t)e[r]=t[r]}function s(t,e,r){return o(t,e,r)}o.from&&o.alloc&&o.allocUnsafe&&o.allocUnsafeSlow?t.exports=n:(i(n,e),e.Buffer=s),s.prototype=Object.create(o.prototype),i(o,s),s.from=function(t,e,r){if("number"==typeof t)throw new TypeError("Argument must not be a number");return o(t,e,r)},s.alloc=function(t,e,r){if("number"!=typeof t)throw new TypeError("Argument must be a number");var n=o(t);return void 0!==e?"string"==typeof r?n.fill(e,r):n.fill(e):n.fill(0),n},s.allocUnsafe=function(t){if("number"!=typeof t)throw new TypeError("Argument must be a number");return o(t)},s.allocUnsafeSlow=function(t){if("number"!=typeof t)throw new TypeError("Argument must be a number");return n.SlowBuffer(t)}}(c,c.exports);var l=c.exports,u=l.Buffer,f=n;function p(t){if(this.buffer=null,this.writable=!0,this.readable=!0,!t)return this.buffer=u.alloc(0),this;if("function"==typeof t.pipe)return this.buffer=u.alloc(0),t.pipe(this),this;if(t.length||"object"==typeof t)return this.buffer=t,this.writable=!1,process.nextTick(function(){this.emit("end",t),this.readable=!1,this.emit("close")}.bind(this)),this;throw new TypeError("Unexpected data type ("+typeof t+")")}o.inherits(p,f),p.prototype.write=function(t){this.buffer=u.concat([this.buffer,u.from(t)]),this.emit("data",t)},p.prototype.end=function(t){t&&this.write(t),this.emit("end",t),this.emit("close"),this.writable=!1,this.readable=!1};var h=p;function d(t){return(t/8|0)+(t%8==0?0:1)}var m={ES256:d(256),ES384:d(384),ES512:d(521)};var g=function(t){var e=m[t];if(e)return e;throw new Error('Unknown algorithm "'+t+'"')},y=l.Buffer,E=g,v=128;function b(t){if(y.isBuffer(t))return t;if("string"==typeof t)return y.from(t,"base64");throw new TypeError("ECDSA signature must be a Base64 string or a Buffer")}function w(t,e,r){for(var n=0;e+n<r&&0===t[e+n];)++n;return t[e+n]>=v&&--n,n}var S,$,R={derToJose:function(t,e){t=b(t);var r=E(e),n=r+1,o=t.length,i=0;if(48!==t[i++])throw new Error('Could not find expected "seq"');var s=t[i++];if(129===s&&(s=t[i++]),o-i<s)throw new Error('"seq" specified length of "'+s+'", only "'+(o-i)+'" remaining');if(2!==t[i++])throw new Error('Could not find expected "int" for "r"');var a=t[i++];if(o-i-2<a)throw new Error('"r" specified length of "'+a+'", only "'+(o-i-2)+'" available');if(n<a)throw new Error('"r" specified length of "'+a+'", max of "'+n+'" is acceptable');var c=i;if(i+=a,2!==t[i++])throw new Error('Could not find expected "int" for "s"');var l=t[i++];if(o-i!==l)throw new Error('"s" specified length of "'+l+'", expected "'+(o-i)+'"');if(n<l)throw new Error('"s" specified length of "'+l+'", max of "'+n+'" is acceptable');var u=i;if((i+=l)!==o)throw new Error('Expected to consume entire buffer, but "'+(o-i)+'" bytes remain');var f=r-a,p=r-l,h=y.allocUnsafe(f+a+p+l);for(i=0;i<f;++i)h[i]=0;t.copy(h,i,c+Math.max(-f,0),c+a);for(var d=i=r;i<d+p;++i)h[i]=0;return t.copy(h,i,u+Math.max(-p,0),u+l),h=(h=h.toString("base64")).replace(/=/g,"").replace(/\+/g,"-").replace(/\//g,"_")},joseToDer:function(t,e){t=b(t);var r=E(e),n=t.length;if(n!==2*r)throw new TypeError('"'+e+'" signatures must be "'+2*r+'" bytes, saw "'+n+'"');var o=w(t,0,r),i=w(t,r,t.length),s=r-o,a=r-i,c=2+s+1+1+a,l=c<v,u=y.allocUnsafe((l?2:3)+c),f=0;return u[f++]=48,l?u[f++]=c:(u[f++]=129,u[f++]=255&c),u[f++]=2,u[f++]=s,o<0?(u[f++]=0,f+=t.copy(u,f,0,r)):f+=t.copy(u,f,o,r),u[f++]=2,u[f++]=a,i<0?(u[f++]=0,t.copy(u,f,r)):t.copy(u,f,r+i),u}};var I,A=l.Buffer,O=i,k=R,L=o,T="secret must be a string or buffer",N="key must be a string or a buffer",x="function"==typeof O.createPublicKey;function P(t){if(!A.isBuffer(t)&&"string"!=typeof t){if(!x)throw D(N);if("object"!=typeof t)throw D(N);if("string"!=typeof t.type)throw D(N);if("string"!=typeof t.asymmetricKeyType)throw D(N);if("function"!=typeof t.export)throw D(N)}}function j(t){if(!A.isBuffer(t)&&"string"!=typeof t&&"object"!=typeof t)throw D("key must be a string, a buffer or an object")}function _(t){return t.replace(/=/g,"").replace(/\+/g,"-").replace(/\//g,"_")}function C(t){var e=4-(t=t.toString()).length%4;if(4!==e)for(var r=0;r<e;++r)t+="=";return t.replace(/\-/g,"+").replace(/_/g,"/")}function D(t){var e=[].slice.call(arguments,1),r=L.format.bind(L,t).apply(null,e);return new TypeError(r)}function M(t){var e;return e=t,A.isBuffer(e)||"string"==typeof e||(t=JSON.stringify(t)),t}function B(t){return function(e,r){!function(t){if(!A.isBuffer(t)){if("string"==typeof t)return t;if(!x)throw D(T);if("object"!=typeof t)throw D(T);if("secret"!==t.type)throw D(T);if("function"!=typeof t.export)throw D(T)}}(r),e=M(e);var n=O.createHmac("sha"+t,r);return _((n.update(e),n.digest("base64")))}}x&&(N+=" or a KeyObject",T+="or a KeyObject");var F="timingSafeEqual"in O?function(t,e){return t.byteLength===e.byteLength&&O.timingSafeEqual(t,e)}:function(t,e){return I||(I=function(){if($)return S;$=1;var t=r.Buffer,e=r.SlowBuffer;function n(e,r){if(!t.isBuffer(e)||!t.isBuffer(r))return!1;if(e.length!==r.length)return!1;for(var n=0,o=0;o<e.length;o++)n|=e[o]^r[o];return 0===n}S=n,n.install=function(){t.prototype.equal=e.prototype.equal=function(t){return n(this,t)}};var o=t.prototype.equal,i=e.prototype.equal;return n.restore=function(){t.prototype.equal=o,e.prototype.equal=i},S}()),I(t,e)};function U(t){return function(e,r,n){var o=B(t)(e,n);return F(A.from(r),A.from(o))}}function K(t){return function(e,r){j(r),e=M(e);var n=O.createSign("RSA-SHA"+t);return _((n.update(e),n.sign(r,"base64")))}}function G(t){return function(e,r,n){P(n),e=M(e),r=C(r);var o=O.createVerify("RSA-SHA"+t);return o.update(e),o.verify(n,r,"base64")}}function q(t){return function(e,r){j(r),e=M(e);var n=O.createSign("RSA-SHA"+t);return _((n.update(e),n.sign({key:r,padding:O.constants.RSA_PKCS1_PSS_PADDING,saltLength:O.constants.RSA_PSS_SALTLEN_DIGEST},"base64")))}}function H(t){return function(e,r,n){P(n),e=M(e),r=C(r);var o=O.createVerify("RSA-SHA"+t);return o.update(e),o.verify({key:n,padding:O.constants.RSA_PKCS1_PSS_PADDING,saltLength:O.constants.RSA_PSS_SALTLEN_DIGEST},r,"base64")}}function V(t){var e=K(t);return function(){var r=e.apply(null,arguments);return r=k.derToJose(r,"ES"+t)}}function X(t){var e=G(t);return function(r,n,o){return n=k.joseToDer(n,"ES"+t).toString("base64"),e(r,n,o)}}function z(){return function(){return""}}function W(){return function(t,e){return""===e}}var J=function(t){var e={hs:B,rs:K,ps:q,es:V,none:z},r={hs:U,rs:G,ps:H,es:X,none:W},n=t.match(/^(RS|PS|ES|HS)(256|384|512)$|^(none)$/);if(!n)throw D('"%s" is not a valid algorithm.\n Supported algorithms are:\n "HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "ES256", "ES384", "ES512" and "none".',t);var o=(n[1]||n[3]).toLowerCase(),i=n[2];return{sign:e[o](i),verify:r[o](i)}},Y=r.Buffer,Z=function(t){return"string"==typeof t?t:"number"==typeof t||Y.isBuffer(t)?t.toString():JSON.stringify(t)},Q=l.Buffer,tt=h,et=J,rt=n,nt=Z,ot=o;function it(t,e){return Q.from(t,e).toString("base64").replace(/=/g,"").replace(/\+/g,"-").replace(/\//g,"_")}function st(t){var e=t.header,r=t.payload,n=t.secret||t.privateKey,o=t.encoding,i=et(e.alg),s=function(t,e,r){r=r||"utf8";var n=it(nt(t),"binary"),o=it(nt(e),r);return ot.format("%s.%s",n,o)}(e,r,o),a=i.sign(s,n);return ot.format("%s.%s",s,a)}function at(t){var e=t.secret;if(e=null==(e=null==e?t.privateKey:e)?t.key:e,!0===/^hs/i.test(t.header.alg)&&null==e)throw new TypeError("secret must be a string or buffer or a KeyObject");var r=new tt(e);this.readable=!0,this.header=t.header,this.encoding=t.encoding,this.secret=this.privateKey=this.key=r,this.payload=new tt(t.payload),this.secret.once("close",function(){!this.payload.writable&&this.readable&&this.sign()}.bind(this)),this.payload.once("close",function(){!this.secret.writable&&this.readable&&this.sign()}.bind(this))}ot.inherits(at,rt),at.prototype.sign=function(){try{var t=st({header:this.header,payload:this.payload.buffer,secret:this.secret.buffer,encoding:this.encoding});return this.emit("done",t),this.emit("data",t),this.emit("end"),this.readable=!1,t}catch(t){this.readable=!1,this.emit("error",t),this.emit("close")}},at.sign=st;var ct=at,lt=l.Buffer,ut=h,ft=J,pt=n,ht=Z,dt=/^[a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)?$/;function mt(t){if(function(t){return"[object Object]"===Object.prototype.toString.call(t)}(t))return t;try{return JSON.parse(t)}catch(t){return}}function gt(t){var e=t.split(".",1)[0];return mt(lt.from(e,"base64").toString("binary"))}function yt(t){return t.split(".")[2]}function Et(t){return dt.test(t)&&!!gt(t)}function vt(t,e,r){if(!e){var n=new Error("Missing algorithm parameter for jws.verify");throw n.code="MISSING_ALGORITHM",n}var o=yt(t=ht(t)),i=function(t){return t.split(".",2).join(".")}(t);return ft(e).verify(i,o,r)}function bt(t,e){if(e=e||{},!Et(t=ht(t)))return null;var r=gt(t);if(!r)return null;var n=function(t,e){e=e||"utf8";var r=t.split(".")[1];return lt.from(r,"base64").toString(e)}(t);return("JWT"===r.typ||e.json)&&(n=JSON.parse(n,e.encoding)),{header:r,payload:n,signature:yt(t)}}function wt(t){var e=(t=t||{}).secret;if(e=null==(e=null==e?t.publicKey:e)?t.key:e,!0===/^hs/i.test(t.algorithm)&&null==e)throw new TypeError("secret must be a string or buffer or a KeyObject");var r=new ut(e);this.readable=!0,this.algorithm=t.algorithm,this.encoding=t.encoding,this.secret=this.publicKey=this.key=r,this.signature=new ut(t.signature),this.secret.once("close",function(){!this.signature.writable&&this.readable&&this.verify()}.bind(this)),this.signature.once("close",function(){!this.secret.writable&&this.readable&&this.verify()}.bind(this))}o.inherits(wt,pt),wt.prototype.verify=function(){try{var t=vt(this.signature.buffer,this.algorithm,this.key.buffer),e=bt(this.signature.buffer,this.encoding);return this.emit("done",t,e),this.emit("data",t),this.emit("end"),this.readable=!1,t}catch(t){this.readable=!1,this.emit("error",t),this.emit("close")}},wt.decode=bt,wt.isValid=Et,wt.verify=vt;var St=ct,$t=wt;a.ALGORITHMS=["HS256","HS384","HS512","RS256","RS384","RS512","PS256","PS384","PS512","ES256","ES384","ES512"],a.sign=St.sign,a.verify=$t.verify,a.decode=$t.decode,a.isValid=$t.isValid,a.createSign=function(t){return new St(t)},a.createVerify=function(t){return new $t(t)};var Rt=a,It=function(t,e){e=e||{};var r=Rt.decode(t,e);if(!r)return null;var n=r.payload;if("string"==typeof n)try{var o=JSON.parse(n);null!==o&&"object"==typeof o&&(n=o)}catch(t){}return!0===e.complete?{header:r.header,payload:n,signature:r.signature}:n},At=function(t,e){Error.call(this,t),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),this.name="JsonWebTokenError",this.message=t,e&&(this.inner=e)};(At.prototype=Object.create(Error.prototype)).constructor=At;var Ot=At,kt=Ot,Lt=function(t,e){kt.call(this,t),this.name="NotBeforeError",this.date=e};(Lt.prototype=Object.create(kt.prototype)).constructor=Lt;var Tt=Lt,Nt=Ot,xt=function(t,e){Nt.call(this,t),this.name="TokenExpiredError",this.expiredAt=e};(xt.prototype=Object.create(Nt.prototype)).constructor=xt;var Pt=xt,jt=1e3,_t=60*jt,Ct=60*_t,Dt=24*Ct,Mt=7*Dt,Bt=365.25*Dt;function Ft(t,e,r,n){var o=e>=1.5*r;return Math.round(t/r)+" "+n+(o?"s":"")}var Ut=function(t,e){e=e||{};var r=typeof t;if("string"===r&&t.length>0)return function(t){if((t=String(t)).length>100)return;var e=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(t);if(!e)return;var r=parseFloat(e[1]);switch((e[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return r*Bt;case"weeks":case"week":case"w":return r*Mt;case"days":case"day":case"d":return r*Dt;case"hours":case"hour":case"hrs":case"hr":case"h":return r*Ct;case"minutes":case"minute":case"mins":case"min":case"m":return r*_t;case"seconds":case"second":case"secs":case"sec":case"s":return r*jt;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return r;default:return}}(t);if("number"===r&&isFinite(t))return e.long?function(t){var e=Math.abs(t);if(e>=Dt)return Ft(t,e,Dt,"day");if(e>=Ct)return Ft(t,e,Ct,"hour");if(e>=_t)return Ft(t,e,_t,"minute");if(e>=jt)return Ft(t,e,jt,"second");return t+" ms"}(t):function(t){var e=Math.abs(t);if(e>=Dt)return Math.round(t/Dt)+"d";if(e>=Ct)return Math.round(t/Ct)+"h";if(e>=_t)return Math.round(t/_t)+"m";if(e>=jt)return Math.round(t/jt)+"s";return t+"ms"}(t);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(t))},Kt=function(t,e){var r=e||Math.floor(Date.now()/1e3);if("string"==typeof t){var n=Ut(t);if(void 0===n)return;return Math.floor(r+n/1e3)}return"number"==typeof t?r+t:void 0},Gt={exports:{}};var qt={MAX_LENGTH:256,MAX_SAFE_COMPONENT_LENGTH:16,MAX_SAFE_BUILD_LENGTH:250,MAX_SAFE_INTEGER:Number.MAX_SAFE_INTEGER||9007199254740991,RELEASE_TYPES:["major","premajor","minor","preminor","patch","prepatch","prerelease"],SEMVER_SPEC_VERSION:"2.0.0",FLAG_INCLUDE_PRERELEASE:1,FLAG_LOOSE:2};var Ht="object"==typeof process&&process.env&&process.env.NODE_DEBUG&&/\bsemver\b/i.test(process.env.NODE_DEBUG)?(...t)=>console.error("SEMVER",...t):()=>{};!function(t,e){const{MAX_SAFE_COMPONENT_LENGTH:r,MAX_SAFE_BUILD_LENGTH:n,MAX_LENGTH:o}=qt,i=Ht,s=(e=t.exports={}).re=[],a=e.safeRe=[],c=e.src=[],l=e.safeSrc=[],u=e.t={};let f=0;const p="[a-zA-Z0-9-]",h=[["\\s",1],["\\d",o],[p,n]],d=(t,e,r)=>{const n=(t=>{for(const[e,r]of h)t=t.split(`${e}*`).join(`${e}{0,${r}}`).split(`${e}+`).join(`${e}{1,${r}}`);return t})(e),o=f++;i(t,o,e),u[t]=o,c[o]=e,l[o]=n,s[o]=new RegExp(e,r?"g":void 0),a[o]=new RegExp(n,r?"g":void 0)};d("NUMERICIDENTIFIER","0|[1-9]\\d*"),d("NUMERICIDENTIFIERLOOSE","\\d+"),d("NONNUMERICIDENTIFIER",`\\d*[a-zA-Z-]${p}*`),d("MAINVERSION",`(${c[u.NUMERICIDENTIFIER]})\\.(${c[u.NUMERICIDENTIFIER]})\\.(${c[u.NUMERICIDENTIFIER]})`),d("MAINVERSIONLOOSE",`(${c[u.NUMERICIDENTIFIERLOOSE]})\\.(${c[u.NUMERICIDENTIFIERLOOSE]})\\.(${c[u.NUMERICIDENTIFIERLOOSE]})`),d("PRERELEASEIDENTIFIER",`(?:${c[u.NONNUMERICIDENTIFIER]}|${c[u.NUMERICIDENTIFIER]})`),d("PRERELEASEIDENTIFIERLOOSE",`(?:${c[u.NONNUMERICIDENTIFIER]}|${c[u.NUMERICIDENTIFIERLOOSE]})`),d("PRERELEASE",`(?:-(${c[u.PRERELEASEIDENTIFIER]}(?:\\.${c[u.PRERELEASEIDENTIFIER]})*))`),d("PRERELEASELOOSE",`(?:-?(${c[u.PRERELEASEIDENTIFIERLOOSE]}(?:\\.${c[u.PRERELEASEIDENTIFIERLOOSE]})*))`),d("BUILDIDENTIFIER",`${p}+`),d("BUILD",`(?:\\+(${c[u.BUILDIDENTIFIER]}(?:\\.${c[u.BUILDIDENTIFIER]})*))`),d("FULLPLAIN",`v?${c[u.MAINVERSION]}${c[u.PRERELEASE]}?${c[u.BUILD]}?`),d("FULL",`^${c[u.FULLPLAIN]}$`),d("LOOSEPLAIN",`[v=\\s]*${c[u.MAINVERSIONLOOSE]}${c[u.PRERELEASELOOSE]}?${c[u.BUILD]}?`),d("LOOSE",`^${c[u.LOOSEPLAIN]}$`),d("GTLT","((?:<|>)?=?)"),d("XRANGEIDENTIFIERLOOSE",`${c[u.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`),d("XRANGEIDENTIFIER",`${c[u.NUMERICIDENTIFIER]}|x|X|\\*`),d("XRANGEPLAIN",`[v=\\s]*(${c[u.XRANGEIDENTIFIER]})(?:\\.(${c[u.XRANGEIDENTIFIER]})(?:\\.(${c[u.XRANGEIDENTIFIER]})(?:${c[u.PRERELEASE]})?${c[u.BUILD]}?)?)?`),d("XRANGEPLAINLOOSE",`[v=\\s]*(${c[u.XRANGEIDENTIFIERLOOSE]})(?:\\.(${c[u.XRANGEIDENTIFIERLOOSE]})(?:\\.(${c[u.XRANGEIDENTIFIERLOOSE]})(?:${c[u.PRERELEASELOOSE]})?${c[u.BUILD]}?)?)?`),d("XRANGE",`^${c[u.GTLT]}\\s*${c[u.XRANGEPLAIN]}$`),d("XRANGELOOSE",`^${c[u.GTLT]}\\s*${c[u.XRANGEPLAINLOOSE]}$`),d("COERCEPLAIN",`(^|[^\\d])(\\d{1,${r}})(?:\\.(\\d{1,${r}}))?(?:\\.(\\d{1,${r}}))?`),d("COERCE",`${c[u.COERCEPLAIN]}(?:$|[^\\d])`),d("COERCEFULL",c[u.COERCEPLAIN]+`(?:${c[u.PRERELEASE]})?`+`(?:${c[u.BUILD]})?(?:$|[^\\d])`),d("COERCERTL",c[u.COERCE],!0),d("COERCERTLFULL",c[u.COERCEFULL],!0),d("LONETILDE","(?:~>?)"),d("TILDETRIM",`(\\s*)${c[u.LONETILDE]}\\s+`,!0),e.tildeTrimReplace="$1~",d("TILDE",`^${c[u.LONETILDE]}${c[u.XRANGEPLAIN]}$`),d("TILDELOOSE",`^${c[u.LONETILDE]}${c[u.XRANGEPLAINLOOSE]}$`),d("LONECARET","(?:\\^)"),d("CARETTRIM",`(\\s*)${c[u.LONECARET]}\\s+`,!0),e.caretTrimReplace="$1^",d("CARET",`^${c[u.LONECARET]}${c[u.XRANGEPLAIN]}$`),d("CARETLOOSE",`^${c[u.LONECARET]}${c[u.XRANGEPLAINLOOSE]}$`),d("COMPARATORLOOSE",`^${c[u.GTLT]}\\s*(${c[u.LOOSEPLAIN]})$|^$`),d("COMPARATOR",`^${c[u.GTLT]}\\s*(${c[u.FULLPLAIN]})$|^$`),d("COMPARATORTRIM",`(\\s*)${c[u.GTLT]}\\s*(${c[u.LOOSEPLAIN]}|${c[u.XRANGEPLAIN]})`,!0),e.comparatorTrimReplace="$1$2$3",d("HYPHENRANGE",`^\\s*(${c[u.XRANGEPLAIN]})\\s+-\\s+(${c[u.XRANGEPLAIN]})\\s*$`),d("HYPHENRANGELOOSE",`^\\s*(${c[u.XRANGEPLAINLOOSE]})\\s+-\\s+(${c[u.XRANGEPLAINLOOSE]})\\s*$`),d("STAR","(<|>)?=?\\s*\\*"),d("GTE0","^\\s*>=\\s*0\\.0\\.0\\s*$"),d("GTE0PRE","^\\s*>=\\s*0\\.0\\.0-0\\s*$")}(Gt,Gt.exports);var Vt=Gt.exports;const Xt=Object.freeze({loose:!0}),zt=Object.freeze({});var Wt=t=>t?"object"!=typeof t?Xt:t:zt;const Jt=/^[0-9]+$/,Yt=(t,e)=>{if("number"==typeof t&&"number"==typeof e)return t===e?0:t<e?-1:1;const r=Jt.test(t),n=Jt.test(e);return r&&n&&(t=+t,e=+e),t===e?0:r&&!n?-1:n&&!r?1:t<e?-1:1};var Zt={compareIdentifiers:Yt,rcompareIdentifiers:(t,e)=>Yt(e,t)};const Qt=Ht,{MAX_LENGTH:te,MAX_SAFE_INTEGER:ee}=qt,{safeRe:re,t:ne}=Vt,oe=Wt,{compareIdentifiers:ie}=Zt;var se=class t{constructor(e,r){if(r=oe(r),e instanceof t){if(e.loose===!!r.loose&&e.includePrerelease===!!r.includePrerelease)return e;e=e.version}else if("string"!=typeof e)throw new TypeError(`Invalid version. Must be a string. Got type "${typeof e}".`);if(e.length>te)throw new TypeError(`version is longer than ${te} characters`);Qt("SemVer",e,r),this.options=r,this.loose=!!r.loose,this.includePrerelease=!!r.includePrerelease;const n=e.trim().match(r.loose?re[ne.LOOSE]:re[ne.FULL]);if(!n)throw new TypeError(`Invalid Version: ${e}`);if(this.raw=e,this.major=+n[1],this.minor=+n[2],this.patch=+n[3],this.major>ee||this.major<0)throw new TypeError("Invalid major version");if(this.minor>ee||this.minor<0)throw new TypeError("Invalid minor version");if(this.patch>ee||this.patch<0)throw new TypeError("Invalid patch version");n[4]?this.prerelease=n[4].split(".").map(t=>{if(/^[0-9]+$/.test(t)){const e=+t;if(e>=0&&e<ee)return e}return t}):this.prerelease=[],this.build=n[5]?n[5].split("."):[],this.format()}format(){return this.version=`${this.major}.${this.minor}.${this.patch}`,this.prerelease.length&&(this.version+=`-${this.prerelease.join(".")}`),this.version}toString(){return this.version}compare(e){if(Qt("SemVer.compare",this.version,this.options,e),!(e instanceof t)){if("string"==typeof e&&e===this.version)return 0;e=new t(e,this.options)}return e.version===this.version?0:this.compareMain(e)||this.comparePre(e)}compareMain(e){return e instanceof t||(e=new t(e,this.options)),this.major<e.major?-1:this.major>e.major?1:this.minor<e.minor?-1:this.minor>e.minor?1:this.patch<e.patch?-1:this.patch>e.patch?1:0}comparePre(e){if(e instanceof t||(e=new t(e,this.options)),this.prerelease.length&&!e.prerelease.length)return-1;if(!this.prerelease.length&&e.prerelease.length)return 1;if(!this.prerelease.length&&!e.prerelease.length)return 0;let r=0;do{const t=this.prerelease[r],n=e.prerelease[r];if(Qt("prerelease compare",r,t,n),void 0===t&&void 0===n)return 0;if(void 0===n)return 1;if(void 0===t)return-1;if(t!==n)return ie(t,n)}while(++r)}compareBuild(e){e instanceof t||(e=new t(e,this.options));let r=0;do{const t=this.build[r],n=e.build[r];if(Qt("build compare",r,t,n),void 0===t&&void 0===n)return 0;if(void 0===n)return 1;if(void 0===t)return-1;if(t!==n)return ie(t,n)}while(++r)}inc(t,e,r){if(t.startsWith("pre")){if(!e&&!1===r)throw new Error("invalid increment argument: identifier is empty");if(e){const t=`-${e}`.match(this.options.loose?re[ne.PRERELEASELOOSE]:re[ne.PRERELEASE]);if(!t||t[1]!==e)throw new Error(`invalid identifier: ${e}`)}}switch(t){case"premajor":this.prerelease.length=0,this.patch=0,this.minor=0,this.major++,this.inc("pre",e,r);break;case"preminor":this.prerelease.length=0,this.patch=0,this.minor++,this.inc("pre",e,r);break;case"prepatch":this.prerelease.length=0,this.inc("patch",e,r),this.inc("pre",e,r);break;case"prerelease":0===this.prerelease.length&&this.inc("patch",e,r),this.inc("pre",e,r);break;case"release":if(0===this.prerelease.length)throw new Error(`version ${this.raw} is not a prerelease`);this.prerelease.length=0;break;case"major":0===this.minor&&0===this.patch&&0!==this.prerelease.length||this.major++,this.minor=0,this.patch=0,this.prerelease=[];break;case"minor":0===this.patch&&0!==this.prerelease.length||this.minor++,this.patch=0,this.prerelease=[];break;case"patch":0===this.prerelease.length&&this.patch++,this.prerelease=[];break;case"pre":{const t=Number(r)?1:0;if(0===this.prerelease.length)this.prerelease=[t];else{let n=this.prerelease.length;for(;--n>=0;)"number"==typeof this.prerelease[n]&&(this.prerelease[n]++,n=-2);if(-1===n){if(e===this.prerelease.join(".")&&!1===r)throw new Error("invalid increment argument: identifier already exists");this.prerelease.push(t)}}if(e){let n=[e,t];!1===r&&(n=[e]),0===ie(this.prerelease[0],e)?isNaN(this.prerelease[1])&&(this.prerelease=n):this.prerelease=n}break}default:throw new Error(`invalid increment argument: ${t}`)}return this.raw=this.format(),this.build.length&&(this.raw+=`+${this.build.join(".")}`),this}};const ae=se;var ce=(t,e,r=!1)=>{if(t instanceof ae)return t;try{return new ae(t,e)}catch(t){if(!r)return null;throw t}};const le=ce;var ue=(t,e)=>{const r=le(t,e);return r?r.version:null};const fe=ce;var pe=(t,e)=>{const r=fe(t.trim().replace(/^[=v]+/,""),e);return r?r.version:null};const he=se;var de=(t,e,r,n,o)=>{"string"==typeof r&&(o=n,n=r,r=void 0);try{return new he(t instanceof he?t.version:t,r).inc(e,n,o).version}catch(t){return null}};const me=ce;var ge=(t,e)=>{const r=me(t,null,!0),n=me(e,null,!0),o=r.compare(n);if(0===o)return null;const i=o>0,s=i?r:n,a=i?n:r,c=!!s.prerelease.length;if(!!a.prerelease.length&&!c){if(!a.patch&&!a.minor)return"major";if(0===a.compareMain(s))return a.minor&&!a.patch?"minor":"patch"}const l=c?"pre":"";return r.major!==n.major?l+"major":r.minor!==n.minor?l+"minor":r.patch!==n.patch?l+"patch":"prerelease"};const ye=se;var Ee=(t,e)=>new ye(t,e).major;const ve=se;var be=(t,e)=>new ve(t,e).minor;const we=se;var Se=(t,e)=>new we(t,e).patch;const $e=ce;var Re=(t,e)=>{const r=$e(t,e);return r&&r.prerelease.length?r.prerelease:null};const Ie=se;var Ae=(t,e,r)=>new Ie(t,r).compare(new Ie(e,r));const Oe=Ae;var ke=(t,e,r)=>Oe(e,t,r);const Le=Ae;var Te=(t,e)=>Le(t,e,!0);const Ne=se;var xe=(t,e,r)=>{const n=new Ne(t,r),o=new Ne(e,r);return n.compare(o)||n.compareBuild(o)};const Pe=xe;var je=(t,e)=>t.sort((t,r)=>Pe(t,r,e));const _e=xe;var Ce=(t,e)=>t.sort((t,r)=>_e(r,t,e));const De=Ae;var Me=(t,e,r)=>De(t,e,r)>0;const Be=Ae;var Fe=(t,e,r)=>Be(t,e,r)<0;const Ue=Ae;var Ke=(t,e,r)=>0===Ue(t,e,r);const Ge=Ae;var qe=(t,e,r)=>0!==Ge(t,e,r);const He=Ae;var Ve=(t,e,r)=>He(t,e,r)>=0;const Xe=Ae;var ze=(t,e,r)=>Xe(t,e,r)<=0;const We=Ke,Je=qe,Ye=Me,Ze=Ve,Qe=Fe,tr=ze;var er=(t,e,r,n)=>{switch(e){case"===":return"object"==typeof t&&(t=t.version),"object"==typeof r&&(r=r.version),t===r;case"!==":return"object"==typeof t&&(t=t.version),"object"==typeof r&&(r=r.version),t!==r;case"":case"=":case"==":return We(t,r,n);case"!=":return Je(t,r,n);case">":return Ye(t,r,n);case">=":return Ze(t,r,n);case"<":return Qe(t,r,n);case"<=":return tr(t,r,n);default:throw new TypeError(`Invalid operator: ${e}`)}};const rr=se,nr=ce,{safeRe:or,t:ir}=Vt;var sr=(t,e)=>{if(t instanceof rr)return t;if("number"==typeof t&&(t=String(t)),"string"!=typeof t)return null;let r=null;if((e=e||{}).rtl){const n=e.includePrerelease?or[ir.COERCERTLFULL]:or[ir.COERCERTL];let o;for(;(o=n.exec(t))&&(!r||r.index+r[0].length!==t.length);)r&&o.index+o[0].length===r.index+r[0].length||(r=o),n.lastIndex=o.index+o[1].length+o[2].length;n.lastIndex=-1}else r=t.match(e.includePrerelease?or[ir.COERCEFULL]:or[ir.COERCE]);if(null===r)return null;const n=r[2],o=r[3]||"0",i=r[4]||"0",s=e.includePrerelease&&r[5]?`-${r[5]}`:"",a=e.includePrerelease&&r[6]?`+${r[6]}`:"";return nr(`${n}.${o}.${i}${s}${a}`,e)};var ar,cr,lr,ur,fr=class{constructor(){this.max=1e3,this.map=new Map}get(t){const e=this.map.get(t);return void 0===e?void 0:(this.map.delete(t),this.map.set(t,e),e)}delete(t){return this.map.delete(t)}set(t,e){if(!this.delete(t)&&void 0!==e){if(this.map.size>=this.max){const t=this.map.keys().next().value;this.delete(t)}this.map.set(t,e)}return this}};function pr(){if(cr)return ar;cr=1;const t=/\s+/g;class e{constructor(r,i){if(i=n(i),r instanceof e)return r.loose===!!i.loose&&r.includePrerelease===!!i.includePrerelease?r:new e(r.raw,i);if(r instanceof o)return this.raw=r.value,this.set=[[r]],this.formatted=void 0,this;if(this.options=i,this.loose=!!i.loose,this.includePrerelease=!!i.includePrerelease,this.raw=r.trim().replace(t," "),this.set=this.raw.split("||").map(t=>this.parseRange(t.trim())).filter(t=>t.length),!this.set.length)throw new TypeError(`Invalid SemVer Range: ${this.raw}`);if(this.set.length>1){const t=this.set[0];if(this.set=this.set.filter(t=>!d(t[0])),0===this.set.length)this.set=[t];else if(this.set.length>1)for(const t of this.set)if(1===t.length&&m(t[0])){this.set=[t];break}}this.formatted=void 0}get range(){if(void 0===this.formatted){this.formatted="";for(let t=0;t<this.set.length;t++){t>0&&(this.formatted+="||");const e=this.set[t];for(let t=0;t<e.length;t++)t>0&&(this.formatted+=" "),this.formatted+=e[t].toString().trim()}}return this.formatted}format(){return this.range}toString(){return this.range}parseRange(t){const e=((this.options.includePrerelease&&p)|(this.options.loose&&h))+":"+t,n=r.get(e);if(n)return n;const s=this.options.loose,m=s?a[c.HYPHENRANGELOOSE]:a[c.HYPHENRANGE];t=t.replace(m,O(this.options.includePrerelease)),i("hyphen replace",t),t=t.replace(a[c.COMPARATORTRIM],l),i("comparator trim",t),t=t.replace(a[c.TILDETRIM],u),i("tilde trim",t),t=t.replace(a[c.CARETTRIM],f),i("caret trim",t);let g=t.split(" ").map(t=>y(t,this.options)).join(" ").split(/\s+/).map(t=>A(t,this.options));s&&(g=g.filter(t=>(i("loose invalid filter",t,this.options),!!t.match(a[c.COMPARATORLOOSE])))),i("range list",g);const E=new Map,v=g.map(t=>new o(t,this.options));for(const t of v){if(d(t))return[t];E.set(t.value,t)}E.size>1&&E.has("")&&E.delete("");const b=[...E.values()];return r.set(e,b),b}intersects(t,r){if(!(t instanceof e))throw new TypeError("a Range is required");return this.set.some(e=>g(e,r)&&t.set.some(t=>g(t,r)&&e.every(e=>t.every(t=>e.intersects(t,r)))))}test(t){if(!t)return!1;if("string"==typeof t)try{t=new s(t,this.options)}catch(t){return!1}for(let e=0;e<this.set.length;e++)if(k(this.set[e],t,this.options))return!0;return!1}}ar=e;const r=new fr,n=Wt,o=hr(),i=Ht,s=se,{safeRe:a,t:c,comparatorTrimReplace:l,tildeTrimReplace:u,caretTrimReplace:f}=Vt,{FLAG_INCLUDE_PRERELEASE:p,FLAG_LOOSE:h}=qt,d=t=>"<0.0.0-0"===t.value,m=t=>""===t.value,g=(t,e)=>{let r=!0;const n=t.slice();let o=n.pop();for(;r&&n.length;)r=n.every(t=>o.intersects(t,e)),o=n.pop();return r},y=(t,e)=>(t=t.replace(a[c.BUILD],""),i("comp",t,e),t=w(t,e),i("caret",t),t=v(t,e),i("tildes",t),t=$(t,e),i("xrange",t),t=I(t,e),i("stars",t),t),E=t=>!t||"x"===t.toLowerCase()||"*"===t,v=(t,e)=>t.trim().split(/\s+/).map(t=>b(t,e)).join(" "),b=(t,e)=>{const r=e.loose?a[c.TILDELOOSE]:a[c.TILDE];return t.replace(r,(e,r,n,o,s)=>{let a;return i("tilde",t,e,r,n,o,s),E(r)?a="":E(n)?a=`>=${r}.0.0 <${+r+1}.0.0-0`:E(o)?a=`>=${r}.${n}.0 <${r}.${+n+1}.0-0`:s?(i("replaceTilde pr",s),a=`>=${r}.${n}.${o}-${s} <${r}.${+n+1}.0-0`):a=`>=${r}.${n}.${o} <${r}.${+n+1}.0-0`,i("tilde return",a),a})},w=(t,e)=>t.trim().split(/\s+/).map(t=>S(t,e)).join(" "),S=(t,e)=>{i("caret",t,e);const r=e.loose?a[c.CARETLOOSE]:a[c.CARET],n=e.includePrerelease?"-0":"";return t.replace(r,(e,r,o,s,a)=>{let c;return i("caret",t,e,r,o,s,a),E(r)?c="":E(o)?c=`>=${r}.0.0${n} <${+r+1}.0.0-0`:E(s)?c="0"===r?`>=${r}.${o}.0${n} <${r}.${+o+1}.0-0`:`>=${r}.${o}.0${n} <${+r+1}.0.0-0`:a?(i("replaceCaret pr",a),c="0"===r?"0"===o?`>=${r}.${o}.${s}-${a} <${r}.${o}.${+s+1}-0`:`>=${r}.${o}.${s}-${a} <${r}.${+o+1}.0-0`:`>=${r}.${o}.${s}-${a} <${+r+1}.0.0-0`):(i("no pr"),c="0"===r?"0"===o?`>=${r}.${o}.${s}${n} <${r}.${o}.${+s+1}-0`:`>=${r}.${o}.${s}${n} <${r}.${+o+1}.0-0`:`>=${r}.${o}.${s} <${+r+1}.0.0-0`),i("caret return",c),c})},$=(t,e)=>(i("replaceXRanges",t,e),t.split(/\s+/).map(t=>R(t,e)).join(" ")),R=(t,e)=>{t=t.trim();const r=e.loose?a[c.XRANGELOOSE]:a[c.XRANGE];return t.replace(r,(r,n,o,s,a,c)=>{i("xRange",t,r,n,o,s,a,c);const l=E(o),u=l||E(s),f=u||E(a),p=f;return"="===n&&p&&(n=""),c=e.includePrerelease?"-0":"",l?r=">"===n||"<"===n?"<0.0.0-0":"*":n&&p?(u&&(s=0),a=0,">"===n?(n=">=",u?(o=+o+1,s=0,a=0):(s=+s+1,a=0)):"<="===n&&(n="<",u?o=+o+1:s=+s+1),"<"===n&&(c="-0"),r=`${n+o}.${s}.${a}${c}`):u?r=`>=${o}.0.0${c} <${+o+1}.0.0-0`:f&&(r=`>=${o}.${s}.0${c} <${o}.${+s+1}.0-0`),i("xRange return",r),r})},I=(t,e)=>(i("replaceStars",t,e),t.trim().replace(a[c.STAR],"")),A=(t,e)=>(i("replaceGTE0",t,e),t.trim().replace(a[e.includePrerelease?c.GTE0PRE:c.GTE0],"")),O=t=>(e,r,n,o,i,s,a,c,l,u,f,p)=>`${r=E(n)?"":E(o)?`>=${n}.0.0${t?"-0":""}`:E(i)?`>=${n}.${o}.0${t?"-0":""}`:s?`>=${r}`:`>=${r}${t?"-0":""}`} ${c=E(l)?"":E(u)?`<${+l+1}.0.0-0`:E(f)?`<${l}.${+u+1}.0-0`:p?`<=${l}.${u}.${f}-${p}`:t?`<${l}.${u}.${+f+1}-0`:`<=${c}`}`.trim(),k=(t,e,r)=>{for(let r=0;r<t.length;r++)if(!t[r].test(e))return!1;if(e.prerelease.length&&!r.includePrerelease){for(let r=0;r<t.length;r++)if(i(t[r].semver),t[r].semver!==o.ANY&&t[r].semver.prerelease.length>0){const n=t[r].semver;if(n.major===e.major&&n.minor===e.minor&&n.patch===e.patch)return!0}return!1}return!0};return ar}function hr(){if(ur)return lr;ur=1;const t=Symbol("SemVer ANY");class e{static get ANY(){return t}constructor(n,o){if(o=r(o),n instanceof e){if(n.loose===!!o.loose)return n;n=n.value}n=n.trim().split(/\s+/).join(" "),s("comparator",n,o),this.options=o,this.loose=!!o.loose,this.parse(n),this.semver===t?this.value="":this.value=this.operator+this.semver.version,s("comp",this)}parse(e){const r=this.options.loose?n[o.COMPARATORLOOSE]:n[o.COMPARATOR],i=e.match(r);if(!i)throw new TypeError(`Invalid comparator: ${e}`);this.operator=void 0!==i[1]?i[1]:"","="===this.operator&&(this.operator=""),i[2]?this.semver=new a(i[2],this.options.loose):this.semver=t}toString(){return this.value}test(e){if(s("Comparator.test",e,this.options.loose),this.semver===t||e===t)return!0;if("string"==typeof e)try{e=new a(e,this.options)}catch(t){return!1}return i(e,this.operator,this.semver,this.options)}intersects(t,n){if(!(t instanceof e))throw new TypeError("a Comparator is required");return""===this.operator?""===this.value||new c(t.value,n).test(this.value):""===t.operator?""===t.value||new c(this.value,n).test(t.semver):(!(n=r(n)).includePrerelease||"<0.0.0-0"!==this.value&&"<0.0.0-0"!==t.value)&&(!(!n.includePrerelease&&(this.value.startsWith("<0.0.0")||t.value.startsWith("<0.0.0")))&&(!(!this.operator.startsWith(">")||!t.operator.startsWith(">"))||(!(!this.operator.startsWith("<")||!t.operator.startsWith("<"))||(!(this.semver.version!==t.semver.version||!this.operator.includes("=")||!t.operator.includes("="))||(!!(i(this.semver,"<",t.semver,n)&&this.operator.startsWith(">")&&t.operator.startsWith("<"))||!!(i(this.semver,">",t.semver,n)&&this.operator.startsWith("<")&&t.operator.startsWith(">")))))))}}lr=e;const r=Wt,{safeRe:n,t:o}=Vt,i=er,s=Ht,a=se,c=pr();return lr}const dr=pr();var mr=(t,e,r)=>{try{e=new dr(e,r)}catch(t){return!1}return e.test(t)};const gr=pr();var yr=(t,e)=>new gr(t,e).set.map(t=>t.map(t=>t.value).join(" ").trim().split(" "));const Er=se,vr=pr();var br=(t,e,r)=>{let n=null,o=null,i=null;try{i=new vr(e,r)}catch(t){return null}return t.forEach(t=>{i.test(t)&&(n&&-1!==o.compare(t)||(n=t,o=new Er(n,r)))}),n};const wr=se,Sr=pr();var $r=(t,e,r)=>{let n=null,o=null,i=null;try{i=new Sr(e,r)}catch(t){return null}return t.forEach(t=>{i.test(t)&&(n&&1!==o.compare(t)||(n=t,o=new wr(n,r)))}),n};const Rr=se,Ir=pr(),Ar=Me;var Or=(t,e)=>{t=new Ir(t,e);let r=new Rr("0.0.0");if(t.test(r))return r;if(r=new Rr("0.0.0-0"),t.test(r))return r;r=null;for(let e=0;e<t.set.length;++e){const n=t.set[e];let o=null;n.forEach(t=>{const e=new Rr(t.semver.version);switch(t.operator){case">":0===e.prerelease.length?e.patch++:e.prerelease.push(0),e.raw=e.format();case"":case">=":o&&!Ar(e,o)||(o=e);break;case"<":case"<=":break;default:throw new Error(`Unexpected operation: ${t.operator}`)}}),!o||r&&!Ar(r,o)||(r=o)}return r&&t.test(r)?r:null};const kr=pr();var Lr=(t,e)=>{try{return new kr(t,e).range||"*"}catch(t){return null}};const Tr=se,Nr=hr(),{ANY:xr}=Nr,Pr=pr(),jr=mr,_r=Me,Cr=Fe,Dr=ze,Mr=Ve;var Br=(t,e,r,n)=>{let o,i,s,a,c;switch(t=new Tr(t,n),e=new Pr(e,n),r){case">":o=_r,i=Dr,s=Cr,a=">",c=">=";break;case"<":o=Cr,i=Mr,s=_r,a="<",c="<=";break;default:throw new TypeError('Must provide a hilo val of "<" or ">"')}if(jr(t,e,n))return!1;for(let r=0;r<e.set.length;++r){const l=e.set[r];let u=null,f=null;if(l.forEach(t=>{t.semver===xr&&(t=new Nr(">=0.0.0")),u=u||t,f=f||t,o(t.semver,u.semver,n)?u=t:s(t.semver,f.semver,n)&&(f=t)}),u.operator===a||u.operator===c)return!1;if((!f.operator||f.operator===a)&&i(t,f.semver))return!1;if(f.operator===c&&s(t,f.semver))return!1}return!0};const Fr=Br;var Ur=(t,e,r)=>Fr(t,e,">",r);const Kr=Br;var Gr=(t,e,r)=>Kr(t,e,"<",r);const qr=pr();var Hr=(t,e,r)=>(t=new qr(t,r),e=new qr(e,r),t.intersects(e,r));const Vr=mr,Xr=Ae;const zr=pr(),Wr=hr(),{ANY:Jr}=Wr,Yr=mr,Zr=Ae,Qr=[new Wr(">=0.0.0-0")],tn=[new Wr(">=0.0.0")],en=(t,e,r)=>{if(t===e)return!0;if(1===t.length&&t[0].semver===Jr){if(1===e.length&&e[0].semver===Jr)return!0;t=r.includePrerelease?Qr:tn}if(1===e.length&&e[0].semver===Jr){if(r.includePrerelease)return!0;e=tn}const n=new Set;let o,i,s,a,c,l,u;for(const e of t)">"===e.operator||">="===e.operator?o=rn(o,e,r):"<"===e.operator||"<="===e.operator?i=nn(i,e,r):n.add(e.semver);if(n.size>1)return null;if(o&&i){if(s=Zr(o.semver,i.semver,r),s>0)return null;if(0===s&&(">="!==o.operator||"<="!==i.operator))return null}for(const t of n){if(o&&!Yr(t,String(o),r))return null;if(i&&!Yr(t,String(i),r))return null;for(const n of e)if(!Yr(t,String(n),r))return!1;return!0}let f=!(!i||r.includePrerelease||!i.semver.prerelease.length)&&i.semver,p=!(!o||r.includePrerelease||!o.semver.prerelease.length)&&o.semver;f&&1===f.prerelease.length&&"<"===i.operator&&0===f.prerelease[0]&&(f=!1);for(const t of e){if(u=u||">"===t.operator||">="===t.operator,l=l||"<"===t.operator||"<="===t.operator,o)if(p&&t.semver.prerelease&&t.semver.prerelease.length&&t.semver.major===p.major&&t.semver.minor===p.minor&&t.semver.patch===p.patch&&(p=!1),">"===t.operator||">="===t.operator){if(a=rn(o,t,r),a===t&&a!==o)return!1}else if(">="===o.operator&&!Yr(o.semver,String(t),r))return!1;if(i)if(f&&t.semver.prerelease&&t.semver.prerelease.length&&t.semver.major===f.major&&t.semver.minor===f.minor&&t.semver.patch===f.patch&&(f=!1),"<"===t.operator||"<="===t.operator){if(c=nn(i,t,r),c===t&&c!==i)return!1}else if("<="===i.operator&&!Yr(i.semver,String(t),r))return!1;if(!t.operator&&(i||o)&&0!==s)return!1}return!(o&&l&&!i&&0!==s)&&(!(i&&u&&!o&&0!==s)&&(!p&&!f))},rn=(t,e,r)=>{if(!t)return e;const n=Zr(t.semver,e.semver,r);return n>0?t:n<0||">"===e.operator&&">="===t.operator?e:t},nn=(t,e,r)=>{if(!t)return e;const n=Zr(t.semver,e.semver,r);return n<0?t:n>0||"<"===e.operator&&"<="===t.operator?e:t};var on=(t,e,r={})=>{if(t===e)return!0;t=new zr(t,r),e=new zr(e,r);let n=!1;t:for(const o of t.set){for(const t of e.set){const e=en(o,t,r);if(n=n||null!==e,e)continue t}if(n)return!1}return!0};const sn=Vt,an=qt,cn=se,ln=Zt,un=(t,e,r)=>{const n=[];let o=null,i=null;const s=t.sort((t,e)=>Xr(t,e,r));for(const t of s){Vr(t,e,r)?(i=t,o||(o=t)):(i&&n.push([o,i]),i=null,o=null)}o&&n.push([o,null]);const a=[];for(const[t,e]of n)t===e?a.push(t):e||t!==s[0]?e?t===s[0]?a.push(`<=${e}`):a.push(`${t} - ${e}`):a.push(`>=${t}`):a.push("*");const c=a.join(" || "),l="string"==typeof e.raw?e.raw:String(e);return c.length<l.length?c:e};var fn={parse:ce,valid:ue,clean:pe,inc:de,diff:ge,major:Ee,minor:be,patch:Se,prerelease:Re,compare:Ae,rcompare:ke,compareLoose:Te,compareBuild:xe,sort:je,rsort:Ce,gt:Me,lt:Fe,eq:Ke,neq:qe,gte:Ve,lte:ze,cmp:er,coerce:sr,Comparator:hr(),Range:pr(),satisfies:mr,toComparators:yr,maxSatisfying:br,minSatisfying:$r,minVersion:Or,validRange:Lr,outside:Br,gtr:Ur,ltr:Gr,intersects:Hr,simplifyRange:un,subset:on,SemVer:cn,re:sn.re,src:sn.src,tokens:sn.t,SEMVER_SPEC_VERSION:an.SEMVER_SPEC_VERSION,RELEASE_TYPES:an.RELEASE_TYPES,compareIdentifiers:ln.compareIdentifiers,rcompareIdentifiers:ln.rcompareIdentifiers};var pn=fn.satisfies(process.version,">=15.7.0");var hn=fn.satisfies(process.version,">=16.9.0");const dn=pn,mn=hn,gn={ec:["ES256","ES384","ES512"],rsa:["RS256","PS256","RS384","PS384","RS512","PS512"],"rsa-pss":["PS256","PS384","PS512"]},yn={ES256:"prime256v1",ES384:"secp384r1",ES512:"secp521r1"};var En=function(t,e){if(!t||!e)return;const r=e.asymmetricKeyType;if(!r)return;const n=gn[r];if(!n)throw new Error(`Unknown key type "${r}".`);if(!n.includes(t))throw new Error(`"alg" parameter for "${r}" key type must be one of: ${n.join(", ")}.`);if(dn)switch(r){case"ec":const r=e.asymmetricKeyDetails.namedCurve,n=yn[t];if(r!==n)throw new Error(`"alg" parameter "${t}" requires curve "${n}".`);break;case"rsa-pss":if(mn){const r=parseInt(t.slice(-3),10),{hashAlgorithm:n,mgf1HashAlgorithm:o,saltLength:i}=e.asymmetricKeyDetails;if(n!==`sha${r}`||o!==n)throw new Error(`Invalid key for this operation, its RSA-PSS parameters do not meet the requirements of "alg" ${t}.`);if(void 0!==i&&i>r>>3)throw new Error(`Invalid key for this operation, its RSA-PSS parameter saltLength does not meet the requirements of "alg" ${t}.`)}}},vn=fn.satisfies(process.version,"^6.12.0 || >=8.0.0");const bn=Ot,wn=Tt,Sn=Pt,$n=It,Rn=Kt,In=En,An=vn,On=a,{KeyObject:kn,createSecretKey:Ln,createPublicKey:Tn}=i,Nn=["RS256","RS384","RS512"],xn=["ES256","ES384","ES512"],Pn=["RS256","RS384","RS512"],jn=["HS256","HS384","HS512"];An&&(Nn.splice(Nn.length,0,"PS256","PS384","PS512"),Pn.splice(Pn.length,0,"PS256","PS384","PS512"));var _n=1/0,Cn=9007199254740991,Dn=17976931348623157e292,Mn=NaN,Bn="[object Arguments]",Fn="[object Function]",Un="[object GeneratorFunction]",Kn="[object String]",Gn="[object Symbol]",qn=/^\s+|\s+$/g,Hn=/^[-+]0x[0-9a-f]+$/i,Vn=/^0b[01]+$/i,Xn=/^0o[0-7]+$/i,zn=/^(?:0|[1-9]\d*)$/,Wn=parseInt;function Jn(t){return t!=t}function Yn(t,e){return function(t,e){for(var r=-1,n=t?t.length:0,o=Array(n);++r<n;)o[r]=e(t[r],r,t);return o}(e,function(e){return t[e]})}var Zn,Qn,to=Object.prototype,eo=to.hasOwnProperty,ro=to.toString,no=to.propertyIsEnumerable,oo=(Zn=Object.keys,Qn=Object,function(t){return Zn(Qn(t))}),io=Math.max;function so(t,e){var r=lo(t)||function(t){return function(t){return po(t)&&uo(t)}(t)&&eo.call(t,"callee")&&(!no.call(t,"callee")||ro.call(t)==Bn)}(t)?function(t,e){for(var r=-1,n=Array(t);++r<t;)n[r]=e(r);return n}(t.length,String):[],n=r.length,o=!!n;for(var i in t)!e&&!eo.call(t,i)||o&&("length"==i||co(i,n))||r.push(i);return r}function ao(t){if(r=(e=t)&&e.constructor,n="function"==typeof r&&r.prototype||to,e!==n)return oo(t);var e,r,n,o=[];for(var i in Object(t))eo.call(t,i)&&"constructor"!=i&&o.push(i);return o}function co(t,e){return!!(e=null==e?Cn:e)&&("number"==typeof t||zn.test(t))&&t>-1&&t%1==0&&t<e}var lo=Array.isArray;function uo(t){return null!=t&&function(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=Cn}(t.length)&&!function(t){var e=fo(t)?ro.call(t):"";return e==Fn||e==Un}(t)}function fo(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}function po(t){return!!t&&"object"==typeof t}var ho=function(t,e,r,n){var o;t=uo(t)?t:(o=t)?Yn(o,function(t){return uo(t)?so(t):ao(t)}(o)):[],r=r&&!n?function(t){var e=function(t){if(!t)return 0===t?t:0;if(t=function(t){if("number"==typeof t)return t;if(function(t){return"symbol"==typeof t||po(t)&&ro.call(t)==Gn}(t))return Mn;if(fo(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=fo(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(qn,"");var r=Vn.test(t);return r||Xn.test(t)?Wn(t.slice(2),r?2:8):Hn.test(t)?Mn:+t}(t),t===_n||t===-1/0){return(t<0?-1:1)*Dn}return t==t?t:0}(t),r=e%1;return e==e?r?e-r:e:0}(r):0;var i=t.length;return r<0&&(r=io(i+r,0)),function(t){return"string"==typeof t||!lo(t)&&po(t)&&ro.call(t)==Kn}(t)?r<=i&&t.indexOf(e,r)>-1:!!i&&function(t,e,r){if(e!=e)return function(t,e,r,n){for(var o=t.length,i=r+(n?1:-1);n?i--:++i<o;)if(e(t[i],i,t))return i;return-1}(t,Jn,r);for(var n=r-1,o=t.length;++n<o;)if(t[n]===e)return n;return-1}(t,e,r)>-1},mo=Object.prototype.toString;var go=function(t){return!0===t||!1===t||function(t){return!!t&&"object"==typeof t}(t)&&"[object Boolean]"==mo.call(t)},yo=1/0,Eo=17976931348623157e292,vo=NaN,bo="[object Symbol]",wo=/^\s+|\s+$/g,So=/^[-+]0x[0-9a-f]+$/i,$o=/^0b[01]+$/i,Ro=/^0o[0-7]+$/i,Io=parseInt,Ao=Object.prototype.toString;function Oo(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}var ko=function(t){return"number"==typeof t&&t==function(t){var e=function(t){if(!t)return 0===t?t:0;if(t=function(t){if("number"==typeof t)return t;if(function(t){return"symbol"==typeof t||function(t){return!!t&&"object"==typeof t}(t)&&Ao.call(t)==bo}(t))return vo;if(Oo(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=Oo(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(wo,"");var r=$o.test(t);return r||Ro.test(t)?Io(t.slice(2),r?2:8):So.test(t)?vo:+t}(t),t===yo||t===-1/0){return(t<0?-1:1)*Eo}return t==t?t:0}(t),r=e%1;return e==e?r?e-r:e:0}(t)},Lo=Object.prototype.toString;var To=function(t){return"number"==typeof t||function(t){return!!t&&"object"==typeof t}(t)&&"[object Number]"==Lo.call(t)};var No=Function.prototype,xo=Object.prototype,Po=No.toString,jo=xo.hasOwnProperty,_o=Po.call(Object),Co=xo.toString,Do=function(t,e){return function(r){return t(e(r))}}(Object.getPrototypeOf,Object);var Mo=function(t){if(!function(t){return!!t&&"object"==typeof t}(t)||"[object Object]"!=Co.call(t)||function(t){var e=!1;if(null!=t&&"function"!=typeof t.toString)try{e=!!(t+"")}catch(t){}return e}(t))return!1;var e=Do(t);if(null===e)return!0;var r=jo.call(e,"constructor")&&e.constructor;return"function"==typeof r&&r instanceof r&&Po.call(r)==_o},Bo=Object.prototype.toString,Fo=Array.isArray;var Uo=function(t){return"string"==typeof t||!Fo(t)&&function(t){return!!t&&"object"==typeof t}(t)&&"[object String]"==Bo.call(t)},Ko=1/0,Go=17976931348623157e292,qo=NaN,Ho="[object Symbol]",Vo=/^\s+|\s+$/g,Xo=/^[-+]0x[0-9a-f]+$/i,zo=/^0b[01]+$/i,Wo=/^0o[0-7]+$/i,Jo=parseInt,Yo=Object.prototype.toString;function Zo(t,e){var r;if("function"!=typeof e)throw new TypeError("Expected a function");return t=function(t){var e=function(t){if(!t)return 0===t?t:0;if(t=function(t){if("number"==typeof t)return t;if(function(t){return"symbol"==typeof t||function(t){return!!t&&"object"==typeof t}(t)&&Yo.call(t)==Ho}(t))return qo;if(Qo(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=Qo(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(Vo,"");var r=zo.test(t);return r||Wo.test(t)?Jo(t.slice(2),r?2:8):Xo.test(t)?qo:+t}(t),t===Ko||t===-1/0){return(t<0?-1:1)*Go}return t==t?t:0}(t),r=e%1;return e==e?r?e-r:e:0}(t),function(){return--t>0&&(r=e.apply(this,arguments)),t<=1&&(e=void 0),r}}function Qo(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}var ti=function(t){return Zo(2,t)};const ei=Kt,ri=vn,ni=En,oi=a,ii=ho,si=go,ai=ko,ci=To,li=Mo,ui=Uo,fi=ti,{KeyObject:pi,createSecretKey:hi,createPrivateKey:di}=i,mi=["RS256","RS384","RS512","ES256","ES384","ES512","HS256","HS384","HS512","none"];ri&&mi.splice(3,0,"PS256","PS384","PS512");const gi={expiresIn:{isValid:function(t){return ai(t)||ui(t)&&t},message:'"expiresIn" should be a number of seconds or string representing a timespan'},notBefore:{isValid:function(t){return ai(t)||ui(t)&&t},message:'"notBefore" should be a number of seconds or string representing a timespan'},audience:{isValid:function(t){return ui(t)||Array.isArray(t)},message:'"audience" must be a string or array'},algorithm:{isValid:ii.bind(null,mi),message:'"algorithm" must be a valid string enum value'},header:{isValid:li,message:'"header" must be an object'},encoding:{isValid:ui,message:'"encoding" must be a string'},issuer:{isValid:ui,message:'"issuer" must be a string'},subject:{isValid:ui,message:'"subject" must be a string'},jwtid:{isValid:ui,message:'"jwtid" must be a string'},noTimestamp:{isValid:si,message:'"noTimestamp" must be a boolean'},keyid:{isValid:ui,message:'"keyid" must be a string'},mutatePayload:{isValid:si,message:'"mutatePayload" must be a boolean'},allowInsecureKeySizes:{isValid:si,message:'"allowInsecureKeySizes" must be a boolean'},allowInvalidAsymmetricKeyTypes:{isValid:si,message:'"allowInvalidAsymmetricKeyTypes" must be a boolean'}},yi={iat:{isValid:ci,message:'"iat" should be a number of seconds'},exp:{isValid:ci,message:'"exp" should be a number of seconds'},nbf:{isValid:ci,message:'"nbf" should be a number of seconds'}};function Ei(t,e,r,n){if(!li(r))throw new Error('Expected "'+n+'" to be a plain object.');Object.keys(r).forEach(function(o){const i=t[o];if(i){if(!i.isValid(r[o]))throw new Error(i.message)}else if(!e)throw new Error('"'+o+'" is not allowed in "'+n+'"')})}const vi={audience:"aud",issuer:"iss",subject:"sub",jwtid:"jti"},bi=["expiresIn","notBefore","noTimestamp","audience","issuer","subject","jwtid"];var wi={decode:It,verify:function(t,e,r,n){let o;if("function"!=typeof r||n||(n=r,r={}),r||(r={}),r=Object.assign({},r),o=n||function(t,e){if(t)throw t;return e},r.clockTimestamp&&"number"!=typeof r.clockTimestamp)return o(new bn("clockTimestamp must be a number"));if(void 0!==r.nonce&&("string"!=typeof r.nonce||""===r.nonce.trim()))return o(new bn("nonce must be a non-empty string"));if(void 0!==r.allowInvalidAsymmetricKeyTypes&&"boolean"!=typeof r.allowInvalidAsymmetricKeyTypes)return o(new bn("allowInvalidAsymmetricKeyTypes must be a boolean"));const i=r.clockTimestamp||Math.floor(Date.now()/1e3);if(!t)return o(new bn("jwt must be provided"));if("string"!=typeof t)return o(new bn("jwt must be a string"));const s=t.split(".");if(3!==s.length)return o(new bn("jwt malformed"));let a;try{a=$n(t,{complete:!0})}catch(t){return o(t)}if(!a)return o(new bn("invalid token"));const c=a.header;let l;if("function"==typeof e){if(!n)return o(new bn("verify must be called asynchronous if secret or public key is provided as a callback"));l=e}else l=function(t,r){return r(null,e)};return l(c,function(e,n){if(e)return o(new bn("error in secret or public key callback: "+e.message));const l=""!==s[2].trim();if(!l&&n)return o(new bn("jwt signature is required"));if(l&&!n)return o(new bn("secret or public key must be provided"));if(!l&&!r.algorithms)return o(new bn('please specify "none" in "algorithms" to verify unsigned tokens'));if(null!=n&&!(n instanceof kn))try{n=Tn(n)}catch(t){try{n=Ln("string"==typeof n?Buffer.from(n):n)}catch(t){return o(new bn("secretOrPublicKey is not valid key material"))}}if(r.algorithms||("secret"===n.type?r.algorithms=jn:["rsa","rsa-pss"].includes(n.asymmetricKeyType)?r.algorithms=Pn:"ec"===n.asymmetricKeyType?r.algorithms=xn:r.algorithms=Nn),-1===r.algorithms.indexOf(a.header.alg))return o(new bn("invalid algorithm"));if(c.alg.startsWith("HS")&&"secret"!==n.type)return o(new bn(`secretOrPublicKey must be a symmetric key when using ${c.alg}`));if(/^(?:RS|PS|ES)/.test(c.alg)&&"public"!==n.type)return o(new bn(`secretOrPublicKey must be an asymmetric key when using ${c.alg}`));if(!r.allowInvalidAsymmetricKeyTypes)try{In(c.alg,n)}catch(t){return o(t)}let u;try{u=On.verify(t,a.header.alg,n)}catch(t){return o(t)}if(!u)return o(new bn("invalid signature"));const f=a.payload;if(void 0!==f.nbf&&!r.ignoreNotBefore){if("number"!=typeof f.nbf)return o(new bn("invalid nbf value"));if(f.nbf>i+(r.clockTolerance||0))return o(new wn("jwt not active",new Date(1e3*f.nbf)))}if(void 0!==f.exp&&!r.ignoreExpiration){if("number"!=typeof f.exp)return o(new bn("invalid exp value"));if(i>=f.exp+(r.clockTolerance||0))return o(new Sn("jwt expired",new Date(1e3*f.exp)))}if(r.audience){const t=Array.isArray(r.audience)?r.audience:[r.audience];if(!(Array.isArray(f.aud)?f.aud:[f.aud]).some(function(e){return t.some(function(t){return t instanceof RegExp?t.test(e):t===e})}))return o(new bn("jwt audience invalid. expected: "+t.join(" or ")))}if(r.issuer){if("string"==typeof r.issuer&&f.iss!==r.issuer||Array.isArray(r.issuer)&&-1===r.issuer.indexOf(f.iss))return o(new bn("jwt issuer invalid. expected: "+r.issuer))}if(r.subject&&f.sub!==r.subject)return o(new bn("jwt subject invalid. expected: "+r.subject));if(r.jwtid&&f.jti!==r.jwtid)return o(new bn("jwt jwtid invalid. expected: "+r.jwtid));if(r.nonce&&f.nonce!==r.nonce)return o(new bn("jwt nonce invalid. expected: "+r.nonce));if(r.maxAge){if("number"!=typeof f.iat)return o(new bn("iat required when maxAge is specified"));const t=Rn(r.maxAge,f.iat);if(void 0===t)return o(new bn('"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'));if(i>=t+(r.clockTolerance||0))return o(new Sn("maxAge exceeded",new Date(1e3*t)))}if(!0===r.complete){const t=a.signature;return o(null,{header:c,payload:f,signature:t})}return o(null,f)})},sign:function(t,e,r,n){"function"==typeof r?(n=r,r={}):r=r||{};const o="object"==typeof t&&!Buffer.isBuffer(t),i=Object.assign({alg:r.algorithm||"HS256",typ:o?"JWT":void 0,kid:r.keyid},r.header);function s(t){if(n)return n(t);throw t}if(!e&&"none"!==r.algorithm)return s(new Error("secretOrPrivateKey must have a value"));if(null!=e&&!(e instanceof pi))try{e=di(e)}catch(t){try{e=hi("string"==typeof e?Buffer.from(e):e)}catch(t){return s(new Error("secretOrPrivateKey is not valid key material"))}}if(i.alg.startsWith("HS")&&"secret"!==e.type)return s(new Error(`secretOrPrivateKey must be a symmetric key when using ${i.alg}`));if(/^(?:RS|PS|ES)/.test(i.alg)){if("private"!==e.type)return s(new Error(`secretOrPrivateKey must be an asymmetric key when using ${i.alg}`));if(!r.allowInsecureKeySizes&&!i.alg.startsWith("ES")&&void 0!==e.asymmetricKeyDetails&&e.asymmetricKeyDetails.modulusLength<2048)return s(new Error(`secretOrPrivateKey has a minimum key size of 2048 bits for ${i.alg}`))}if(void 0===t)return s(new Error("payload is required"));if(o){try{!function(t){Ei(yi,!0,t,"payload")}(t)}catch(t){return s(t)}r.mutatePayload||(t=Object.assign({},t))}else{const e=bi.filter(function(t){return void 0!==r[t]});if(e.length>0)return s(new Error("invalid "+e.join(",")+" option for "+typeof t+" payload"))}if(void 0!==t.exp&&void 0!==r.expiresIn)return s(new Error('Bad "options.expiresIn" option the payload already has an "exp" property.'));if(void 0!==t.nbf&&void 0!==r.notBefore)return s(new Error('Bad "options.notBefore" option the payload already has an "nbf" property.'));try{!function(t){Ei(gi,!1,t,"options")}(r)}catch(t){return s(t)}if(!r.allowInvalidAsymmetricKeyTypes)try{ni(i.alg,e)}catch(t){return s(t)}const a=t.iat||Math.floor(Date.now()/1e3);if(r.noTimestamp?delete t.iat:o&&(t.iat=a),void 0!==r.notBefore){try{t.nbf=ei(r.notBefore,a)}catch(t){return s(t)}if(void 0===t.nbf)return s(new Error('"notBefore" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'))}if(void 0!==r.expiresIn&&"object"==typeof t){try{t.exp=ei(r.expiresIn,a)}catch(t){return s(t)}if(void 0===t.exp)return s(new Error('"expiresIn" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'))}Object.keys(vi).forEach(function(e){const n=vi[e];if(void 0!==r[e]){if(void 0!==t[n])return s(new Error('Bad "options.'+e+'" option. The payload already has an "'+n+'" property.'));t[n]=r[e]}});const c=r.encoding||"utf8";if("function"!=typeof n){let n=oi.sign({header:i,payload:t,secret:e,encoding:c});if(!r.allowInsecureKeySizes&&/^(?:RS|PS)/.test(i.alg)&&n.length<256)throw new Error(`secretOrPrivateKey has a minimum key size of 2048 bits for ${i.alg}`);return n}n=n&&fi(n),oi.createSign({header:i,privateKey:e,payload:t,encoding:c}).once("error",n).once("done",function(t){if(!r.allowInsecureKeySizes&&/^(?:RS|PS)/.test(i.alg)&&t.length<256)return n(new Error(`secretOrPrivateKey has a minimum key size of 2048 bits for ${i.alg}`));n(null,t)})},JsonWebTokenError:Ot,NotBeforeError:Tt,TokenExpiredError:Pt},Si=s(wi);const $i=t(import.meta.url),{version:Ri}=$i("../package.json");var Ii={id:"sso",handler:(t,r)=>{const{env:n,logger:o,services:i,database:s,getSchema:a}=r,c=n.KEYCLOAK_URL||"http://keycloak:8080",l=n.KEYCLOAK_REALM||"testing",u=n.KEYCLOAK_ADMIN_USER||"admin",f=n.KEYCLOAK_ADMIN_PASSWORD||"admin",p=n.PUBLIC_URL||"http://localhost:8055",h=n.MOBILE_APP_SCHEME||"portalpipq",d=n.MOBILE_APP_CALLBACK_PATH||"/auth/callback",m=n.GOOGLE_CALLBACK_PATH||"/auth/callback/google",g=n.KEYCLOAK_CLIENT_ID||"admin-cli",y=n.COOKIE_DOMAIN||null,E="false"!==n.COOKIE_SECURE,v=n.COOKIE_SAME_SITE||"lax",b=n.SESSION_COOKIE_NAME||"directus_session_token",w=n.REFRESH_TOKEN_COOKIE_NAME||"directus_refresh_token",S=n.DEFAULT_ROLE_ID||null,$="directus_session_token";function R(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 I(t,e){const r=t.headers.cookie;if(!r)return null;const n=r.split(";").map(t=>t.trim()).filter(t=>t.startsWith(`${e}=`)).map(t=>t.substring(e.length+1));if(0===n.length)return null;o.info(`🔍 [${e}] Found ${n.length} possible tokens. Trying them...`);for(let t=0;t<n.length;t++){const r=n[t];try{const n=await fetch(`${p}/users/me`,{headers:{Cookie:`${e}=${r}`}});if(n.ok){const i=await n.json();return o.info(`✅ [${e}] Token #${t+1} succeeded for ${i.data.email}`),{token:r,userData:i.data}}o.info(`❌ [${e}] Token #${t+1} failed (${n.status})`)}catch(r){o.error(`⚠️ [${e}] Error trying token #${t+1}: ${r.message}`)}}return null}async function A(t){const e=t.headers.cookie;if(!e)return null;const r=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===r.length)return null;o.info(`🔍 [MEGA] Found ${r.length} potential JWTs in cookies. Trying them...`);for(let t=0;t<r.length;t++){const e=r[t];try{const r=await fetch(`${p}/users/me`,{headers:{Authorization:`Bearer ${e}`}});if(r.ok){const n=await r.json();return o.info(`✅ [MEGA] Token #${t+1} succeeded for ${n.data.email}`),{token:e,userData:n.data}}}catch(t){}}return null}async function O(t){const e=t.cookies[w];if(!e)return null;o.info(`🔄 [REFRESH] Attempting to use ${w}...`);try{const t=await fetch(`${p}/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;o.info("✅ [REFRESH] Successfully refreshed session");const r=await fetch(`${p}/users/me`,{headers:{Authorization:`Bearer ${e}`}});if(r.ok){return{token:e,userData:(await r.json()).data}}}}catch(t){o.error("❌ [REFRESH] Failed: "+t.message)}return null}o.info("🚀 Mobile Auth Proxy Extension loaded"),o.info("🔐 Keycloak URL: "+c),o.info("🌐 Keycloak Realm: "+l),o.info("📡 Public URL: "+p),o.info("🍪 Session Cookie Name: "+b),o.info("🍪 Refresh Token Cookie Name: "+w),o.info("📱 Mobile App Scheme: "+h+"://"+d),o.info("🔵 Google OAuth enabled"),t.get("/health",(t,e)=>{e.json({status:"ok",service:"directus-extension-sso",version:Ri})}),t.get("/mobile-callback",async(t,e)=>{const r=R(t);o.info(`${r?"🌐":"📱"} ${r?"Browser":"Mobile"} callback received`),o.info("Host Header: "+t.headers.host),o.info("Cookies Header: "+t.headers.cookie),o.info("Parsed Cookies: "+JSON.stringify(t.cookies)),o.info("Query: "+JSON.stringify(t.query));try{let n=null;if(b!==$&&(n=await I(t,b)),n||(n=await I(t,$)),n||(n=await A(t)),n||(n=await O(t)),!n)return o.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: ${b}<br>\n\t\t\t\t\t\t\t\t\tURL: ${p}<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="${p}/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:i,userData:s}=n,a=s.id,c=s.email;o.info("👤 User authenticated: "+a+", "+c);const l=i;if(o.info("🎫 Using session token as access token"),r){o.info("🌐 Browser request detected - maintaining session"),e.cookie(b,i,{httpOnly:!0,secure:E,domain:y,sameSite:v,maxAge:6048e5,path:"/"}),b!==$&&e.cookie($,"",{maxAge:0,path:"/"});let r=t.query.redirect_uri||t.query.redirect||"/";try{if(r.startsWith("http")){const t=new URL(r);t.searchParams.set("access_token",l),t.searchParams.set("user_id",a),t.searchParams.set("email",c||""),r=t.toString()}}catch(t){o.error("❌ Failed to parse Web redirect URL: "+r)}return o.info("🔄 Redirecting browser to: "+r),e.redirect(r)}const u=t.query.app_scheme||h,f=`${u}://${(t.query.app_path||d).replace(/^\/+/,"")}?access_token=${l}&user_id=${a}&email=${encodeURIComponent(c||"")}`;return o.info("🔄 Attempting AUTO-REDIRECT to app: "+f),e.setHeader("Location",f),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=${f}">\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="${f}" 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 = "${f}";\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){o.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="${p}/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 r=R(t);o.info(`${r?"🌐":"📱"} ${r?"Browser":"Mobile"} Google callback received`),o.info("Cookies: "+JSON.stringify(t.cookies)),o.info("Query: "+JSON.stringify(t.query));try{let n=null;if(t.query.access_token){o.info("🎟️ Found access_token in URL query string! Bypassing cookie check entirely."),n={token:t.query.access_token,refresh_token:t.query.refresh_token||null,expires:t.query.expires||null};try{const t=await fetch(`${p}/users/me`,{headers:{Authorization:`Bearer ${n.token}`}});if(t.ok){const e=await t.json();n.userData=e.data,o.info(`✅ Successfully fetched userData for ${n.userData.email}`)}else o.error(`❌ Failed to fetch userData with query token (${t.status})`),n=null}catch(t){o.error(`⚠️ Error fetching userData with query token: ${t.message}`),n=null}}if(n||b===$||(n=await I(t,b)),n||(n=await I(t,$)),n||(n=await A(t)),n||(n=await O(t)),!n)return o.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: ${b} | 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:i,userData:s}=n,a=s.id,c=s.email,l=s.first_name||s.email;o.info("👤 User authenticated via Google: "+a+", "+c);const u=i;if(o.info("🎫 Using session token as access token"),r){o.info("🌐 Browser request detected - maintaining session"),e.cookie(b,i,{httpOnly:!0,secure:E,domain:y,sameSite:v,maxAge:6048e5,path:"/"}),b!==$&&e.cookie($,"",{maxAge:0,path:"/"});let r=t.query.redirect_uri||t.query.redirect||"/";try{if(r.startsWith("http")){const t=new URL(r);t.searchParams.set("access_token",u),t.searchParams.set("user_id",a),t.searchParams.set("email",c||""),t.searchParams.set("provider","google"),r=t.toString()}}catch(t){o.error("❌ Failed to parse Web redirect URL: "+r)}return o.info("🔄 Redirecting browser to: "+r),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=${r}">\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="${r}">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`)}o.info("📱 Mobile app request - redirecting with token");const f=t.query.app_scheme||h,d=t.query.app_path||m,g=new URL(`${f}://${d.replace(/^\/+/,"")}`);return g.searchParams.set("access_token",u),g.searchParams.set("user_id",a),g.searchParams.set("email",c||""),g.searchParams.set("provider","google"),o.info("🔄 Redirecting to app (Google): "+g.toString()),o.info("🚀 Performing direct 302 redirect to app: "+g.toString()),e.redirect(302,g.toString())}catch(t){o.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="${p}/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)=>{o.info("🚪 Logout request received");try{const r=t.headers.authorization,n=r?.replace("Bearer ","");if(!n)return e.status(400).json({error:"No token provided",message:"Authorization header with Bearer token is required"});o.info("🎫 Token received: "+n.substring(0,20)+"...");let i=null;try{const t=await fetch(`${p}/users/me`,{headers:{Authorization:`Bearer ${n}`}});if(t.ok){i=(await t.json()).data.email,o.info("👤 User email: "+i)}}catch(t){o.error("⚠️ Error getting user info: "+t.message)}try{const t=await fetch(`${p}/auth/logout`,{method:"POST",headers:{Authorization:`Bearer ${n}`,"Content-Type":"application/json"},body:JSON.stringify({refresh_token:n})});t.ok?o.info("✅ Directus session invalidated"):o.info("⚠️ Directus logout response: "+t.status)}catch(t){o.error("⚠️ Error logging out from Directus: "+t.message)}if(i)try{o.info("🔐 Getting Keycloak admin token...");const t=await async function(){try{const t=await fetch(`${c}/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:u,password:f}).toString()});if(!t.ok)throw new Error("Failed to get admin token");return(await t.json()).access_token}catch(t){return o.error("Error getting admin token:",t),null}}();if(t){o.info("🔍 Looking up Keycloak user...");const e=await async function(t,e){try{const r=await fetch(`${c}/admin/realms/${l}/users?email=${encodeURIComponent(e)}`,{headers:{Authorization:`Bearer ${t}`}});if(!r.ok)throw new Error("Failed to get user");const n=await r.json();return n.length>0?n[0].id:null}catch(t){return o.error("Error getting user ID:",t),null}}(t,i);if(e){o.info("🚪 Logging out from Keycloak...");const r=await async function(t,e){try{const r=await fetch(`${c}/admin/realms/${l}/users/${e}/logout`,{method:"POST",headers:{Authorization:`Bearer ${t}`}});return r.ok||204===r.status}catch(t){return o.error("Error logging out user from Keycloak:",t),!1}}(t,e);r?o.info("✅ Keycloak sessions terminated"):o.info("⚠️ Failed to logout from Keycloak")}else o.info("⚠️ User not found in Keycloak")}else o.info("⚠️ Failed to get Keycloak admin token")}catch(t){o.error("⚠️ Error logging out from Keycloak: "+t.message)}o.info("🎉 Logout completed"),e.json({success:!0,message:"Logged out successfully from Directus and Keycloak"})}catch(t){o.error("❌ Error in logout:",t),e.status(500).json({error:t.message,message:"Failed to logout"})}}),t.post("/apple-token",async(t,r)=>{const{identityToken:c,firstName:l,lastName:u}=t.body;if(o.info("🍎 Apple token exchange request received"),!c)return r.status(400).json({error:"identityToken is required",message:"Apple identityToken must be provided in the request body"});try{const t="com.forumbandung.app",f=async r=>{const[n,i,s]=r.split("."),a=JSON.parse(Buffer.from(n,"base64").toString()),c=JSON.parse(Buffer.from(i,"base64").toString());if(o.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"],u=c.aud.toLowerCase();if(!l.includes(u))throw o.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 f=await fetch("https://appleid.apple.com/auth/keys"),{keys:p}=await f.json(),h=p.find(t=>t.kid===a.kid);if(!h)throw new Error("Apple public key not found");const d=e.createPublicKey({key:h,format:"jwk"}),m=e.createVerify("RSA-SHA256");m.update(`${n}.${i}`);if(!m.verify(d,s,"base64url"))throw new Error("Invalid signature");return c},p=await f(c),{email:h,sub:d}=p;if(!h)throw new Error("Apple token did not contain an email");o.info(`✅ Apple token verified for: ${h} (${d})`);const{UsersService:m,AuthenticationService:g}=i,y=new m({schema:await a(),knex:s}),E=await y.readByQuery({filter:{email:{_eq:h}}});let v,b;E.length>0?(b=E[0],v=b.id,o.info(`👤 Found existing user: ${v}`),b.external_identifier||await y.updateOne(v,{external_identifier:d,provider:"apple"})):(o.info(`📝 Creating new user for: ${h}`),v=await y.createOne({email:h,first_name:l||"Apple User",last_name:u||"",role:S,status:"active",provider:"apple",external_identifier:d}),b=await y.readOne(v));try{const t={id:v,role:b.role||S,app_access:!0,admin_access:!1},e=Si.sign(t,n.SECRET,{expiresIn:"7d",issuer:"directus"}),o={id:v,type:"refresh"},i=Si.sign(o,n.SECRET,{expiresIn:"30d",issuer:"directus"});r.json({success:!0,data:{access_token:e,refresh_token:i,expires:604800,user:b},user_id:v,email:h,provider:"apple"})}catch(t){o.error("⚠️ Could not generate JWT session token: "+t.message),r.status(500).json({error:"Session error",message:"Apple authentication verified but session generation failed."})}}catch(t){o.error("❌ Error in Apple token exchange:",t),r.status(500).json({error:t.message,message:"Failed to verify Apple token"})}}),t.get("/bridge",async(t,e)=>{const{token:r,redirect_uri:n,redirect:i}=t.query,s=r,a=n||i||"/";if(o.info("🌉 WebView Bridge request received"),!s)return e.status(400).json({error:"Token required",message:"No access token provided"});try{const t=await fetch(`${p}/users/me`,{headers:{Authorization:`Bearer ${s}`}});if(!t.ok)return o.error("❌ Bridge: Invalid token provided"),e.status(401).json({error:"Invalid token",message:"The provided token is invalid or expired"});const r=await t.json();return o.info(`✅ Bridge: Session established for ${r.data.email}`),e.cookie(b,s,{httpOnly:!0,secure:E,domain:y,sameSite:v,maxAge:6048e5,path:"/"}),b!==$&&e.cookie($,s,{httpOnly:!0,secure:E,domain:y,sameSite:v,maxAge:6048e5,path:"/"}),o.info("🔄 Bridge: Redirecting to "+a),e.redirect(a)}catch(t){o.error("❌ Bridge error: "+t.message),e.status(500).json({error:"Bridge failure",message:t.message})}})}};export{Ii 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": "2.0.5",
5
+ "version": "2.0.7",
6
6
  "keywords": [
7
7
  "directus",
8
8
  "directus-extension",
@@ -47,7 +47,9 @@
47
47
  "type": "git",
48
48
  "url": "git+https://github.com/ymys/directus-sso.git"
49
49
  },
50
- "dependencies": {},
50
+ "dependencies": {
51
+ "jsonwebtoken": "^9.0.3"
52
+ },
51
53
  "devDependencies": {
52
54
  "@directus/extensions-sdk": "^12.0.2"
53
55
  }