@ymys/directus-extension-sso 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/dist/index.js +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Yusuf M (ymys)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Directus SSO - Directus Extension
|
|
2
|
+
|
|
3
|
+
This extension allows you to use the web and mobile OAuth callback and logout endpoints directly through your Directus domain.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
1. **Build the extension:**
|
|
8
|
+
```bash
|
|
9
|
+
cd extensions/endpoints/directus-extension-sso
|
|
10
|
+
npm install
|
|
11
|
+
npm run build
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
2. **The extension will be automatically loaded by Directus** when you restart it.
|
|
15
|
+
|
|
16
|
+
## Endpoints
|
|
17
|
+
|
|
18
|
+
Once installed, the following endpoints will be available on your Directus domain:
|
|
19
|
+
|
|
20
|
+
- **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`
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
The extension uses environment variables from your Directus configuration:
|
|
28
|
+
|
|
29
|
+
# Add these to your Directus .env file
|
|
30
|
+
KEYCLOAK_URL=http://keycloak:8080
|
|
31
|
+
KEYCLOAK_REALM=testing
|
|
32
|
+
KEYCLOAK_CLIENT_ID=admin-cli
|
|
33
|
+
KEYCLOAK_ADMIN_USER=admin
|
|
34
|
+
KEYCLOAK_ADMIN_PASSWORD=admin
|
|
35
|
+
PUBLIC_URL=http://localhost:8055
|
|
36
|
+
MOBILE_APP_SCHEME=portalpipq
|
|
37
|
+
MOBILE_APP_CALLBACK_PATH=/auth/callback
|
|
38
|
+
GOOGLE_CALLBACK_PATH=/auth/callback/google
|
|
39
|
+
COOKIE_DOMAIN=.your-domain.com
|
|
40
|
+
COOKIE_SECURE=true
|
|
41
|
+
COOKIE_SAME_SITE=lax
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Security Note
|
|
45
|
+
|
|
46
|
+
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.
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
### Browser Authentication Flow
|
|
51
|
+
|
|
52
|
+
The extension now supports **both browser and mobile app authentication** with automatic detection. After successful Keycloak login:
|
|
53
|
+
|
|
54
|
+
- **Browser requests**: Session is saved with cookies, allowing SSO across multiple services
|
|
55
|
+
- **Mobile app requests**: Access token is returned via deep link as before
|
|
56
|
+
|
|
57
|
+
#### Browser Login Example:
|
|
58
|
+
|
|
59
|
+
**For Keycloak:**
|
|
60
|
+
```javascript
|
|
61
|
+
// Simply navigate to the login URL
|
|
62
|
+
window.location.href = 'http://your-directus-domain.com/auth/login/keycloak';
|
|
63
|
+
|
|
64
|
+
// Or with a custom redirect after login
|
|
65
|
+
window.location.href = 'http://your-directus-domain.com/auth/login/keycloak?redirect_uri=/dashboard';
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**For Google:**
|
|
69
|
+
```javascript
|
|
70
|
+
// Simply navigate to the Google login URL
|
|
71
|
+
window.location.href = 'http://your-directus-domain.com/auth/login/google';
|
|
72
|
+
|
|
73
|
+
// Or with a custom redirect after login
|
|
74
|
+
window.location.href = 'http://your-directus-domain.com/auth/login/google?redirect_uri=/dashboard';
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
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.
|
|
78
|
+
|
|
79
|
+
#### Forcing Browser Mode:
|
|
80
|
+
|
|
81
|
+
If auto-detection doesn't work correctly, you can force browser mode:
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
window.location.href = 'http://your-directus-domain.com/auth/login/keycloak?type=browser';
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### Query Parameters:
|
|
88
|
+
|
|
89
|
+
- `type`: Force `browser` or `mobile` mode (optional, auto-detected if not provided)
|
|
90
|
+
- `redirect_uri` or `redirect`: URL to redirect to after successful login (optional, defaults to `/admin`)
|
|
91
|
+
|
|
92
|
+
### Mobile App Authentication Flow
|
|
93
|
+
|
|
94
|
+
Update your mobile app to use the new Directus domain URLs:
|
|
95
|
+
|
|
96
|
+
### Before (separate proxy server):
|
|
97
|
+
```javascript
|
|
98
|
+
const PROXY_URL = 'http://your-proxy-domain.com:3000';
|
|
99
|
+
const callbackUrl = `${PROXY_URL}/mobile-callback`;
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### After (Directus extension):
|
|
103
|
+
```javascript
|
|
104
|
+
const DIRECTUS_URL = 'http://your-directus-domain.com';
|
|
105
|
+
const callbackUrl = `${DIRECTUS_URL}/sso/mobile-callback`;
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Login Flow:
|
|
109
|
+
```javascript
|
|
110
|
+
import * as WebBrowser from 'expo-web-browser';
|
|
111
|
+
|
|
112
|
+
const result = await WebBrowser.openAuthSessionAsync(
|
|
113
|
+
`${DIRECTUS_URL}/auth/login/keycloak`,
|
|
114
|
+
'myapp://auth/callback'
|
|
115
|
+
);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Logout:
|
|
119
|
+
```javascript
|
|
120
|
+
const response = await fetch(`${DIRECTUS_URL}/sso/mobile-logout`, {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: {
|
|
123
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Advantages Over Standalone Proxy
|
|
129
|
+
|
|
130
|
+
1. **Single Domain:** No need to deploy a separate server
|
|
131
|
+
2. **Unified Management:** Managed alongside your Directus instance
|
|
132
|
+
3. **Same Environment:** Uses Directus environment variables and configuration
|
|
133
|
+
4. **Built-in Logging:** Uses Directus logger for consistent logging
|
|
134
|
+
5. **Easier Deployment:** Deployed automatically with Directus
|
|
135
|
+
|
|
136
|
+
## Development
|
|
137
|
+
|
|
138
|
+
To make changes and test locally:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
cd extensions/endpoints/directus-extension-sso
|
|
142
|
+
npm run dev
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
This will watch for changes and rebuild automatically.
|
|
146
|
+
|
|
147
|
+
## Keycloak Client Configuration
|
|
148
|
+
|
|
149
|
+
Update your Keycloak client's redirect URI to use the Directus domain:
|
|
150
|
+
|
|
151
|
+
**Valid Redirect URIs:**
|
|
152
|
+
```
|
|
153
|
+
http://your-directus-domain.com/auth/login/keycloak/callback
|
|
154
|
+
http://your-directus-domain.com/sso/mobile-callback
|
|
155
|
+
myapp://auth/callback
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Web Origins:**
|
|
159
|
+
```
|
|
160
|
+
http://your-directus-domain.com
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Notes
|
|
164
|
+
|
|
165
|
+
- The extension requires Directus 11.0.0 or higher
|
|
166
|
+
- Make sure cookie-parser middleware is available (Directus includes this by default)
|
|
167
|
+
- The extension has access to the same network as Directus, so internal service URLs (like `http://keycloak:8080`) will work if using Docker
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var t={id:"sso",handler:(t,{env:e,logger:o})=>{const n=e.KEYCLOAK_URL||"http://keycloak:8080",r=e.KEYCLOAK_REALM||"testing",i=e.KEYCLOAK_ADMIN_USER||"admin",a=e.KEYCLOAK_ADMIN_PASSWORD||"admin",s=e.PUBLIC_URL||"http://localhost:8055",c=e.MOBILE_APP_SCHEME||"portalpipq",d=e.MOBILE_APP_CALLBACK_PATH||"/auth/callback",l=e.GOOGLE_CALLBACK_PATH||"/auth/callback/google",u=e.KEYCLOAK_CLIENT_ID||"admin-cli",g=e.COOKIE_DOMAIN||null,f="false"!==e.COOKIE_SECURE,h=e.COOKIE_SAME_SITE||"lax";o.info("🚀 Mobile Auth Proxy Extension loaded"),o.info("🔐 Keycloak URL: "+n),o.info("🌐 Keycloak Realm: "+r),o.info("📱 Mobile App Scheme: "+c+"://"+d),o.info("🔵 Google OAuth enabled"),t.get("/health",(t,e)=>{e.json({status:"ok",service:"directus-mobile-auth-proxy"})}),t.get("/mobile-callback",async(t,e)=>{o.info("📱 Mobile callback received"),o.info("Cookies: "+JSON.stringify(t.cookies)),o.info("Query: "+JSON.stringify(t.query));try{const n=t.cookies.directus_session_token;if(!n)return o.error("❌ No session token found in cookies"),e.send(`\n\t\t\t\t\t<html>\n\t\t\t\t\t\t<body>\n\t\t\t\t\t\t\t<h2>Authentication Failed</h2>\n\t\t\t\t\t\t\t<p>No session token found. Please try logging in again.</p>\n\t\t\t\t\t\t\t<a href="${s}/auth/login/keycloak">Try Again</a>\n\t\t\t\t\t\t</body>\n\t\t\t\t\t</html>\n\t\t\t\t`);o.info("✅ Session token found: "+n.substring(0,20)+"...");const r=await fetch(`${s}/users/me`,{headers:{Cookie:`directus_session_token=${n}`}});if(!r.ok)throw new Error("Failed to get user info: "+await r.text());const i=await r.json(),a=i.data.id,l=i.data.email;o.info("👤 User authenticated: "+a+", "+l);const u=n;if(o.info("🎫 Using session token as access token"),isBrowser){o.info("🌐 Browser request detected - maintaining session"),e.cookie("directus_session_token",n,{httpOnly:!0,secure:f,domain:g,sameSite:h,maxAge:6048e5,path:"/"});const r=t.query.redirect_uri||t.query.redirect||"/";return o.info("🔄 Redirecting browser to: "+r),e.redirect(r)}const p=new URL(`${c}://${d}`);p.searchParams.set("access_token",u),p.searchParams.set("user_id",a),p.searchParams.set("email",l||""),o.info("🔄 Redirecting to app: "+p.toString()),e.redirect(p.toString())}catch(t){o.error("❌ Error in callback:",t),e.status(500).send(`\n\t\t\t\t<html>\n\t\t\t\t\t<body>\n\t\t\t\t\t\t<h2>Error</h2>\n\t\t\t\t\t\t<p>${t.message}</p>\n\t\t\t\t\t\t<a href="${s}/auth/login/keycloak">Try Again</a>\n\t\t\t\t\t</body>\n\t\t\t\t</html>\n\t\t\t`)}}),t.get("/google-callback",async(t,e)=>{const n=function(t){if("browser"===t.query.type)return!0;if("mobile"===t.query.type)return!1;const e=t.headers["user-agent"]||"";return/Mozilla|Chrome|Safari|Firefox|Edge|Opera/i.test(e)&&!/Mobile.*App|ReactNative|Expo/i.test(e)}(t);o.info(`${n?"🌐":"📱"} ${n?"Browser":"Mobile"} Google callback received`),o.info("Cookies: "+JSON.stringify(t.cookies)),o.info("Query: "+JSON.stringify(t.query));try{const r=t.cookies.directus_session_token;if(!r)return o.error("❌ No session token found in cookies"),e.send("\n\t\t\t\t\t\t<html>\n\t\t\t\t\t\t\t<head>\n\t\t\t\t\t\t\t\t<title>Authentication Failed</title>\n\t\t\t\t\t\t\t\t<style>\n\t\t\t\t\t\t\t\t\tbody { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; \n\t\t\t\t\t\t\t\t\t padding: 40px; text-align: center; background: #f5f5f5; }\n\t\t\t\t\t\t\t\t\t.container { max-width: 500px; margin: 0 auto; background: white; \n\t\t\t\t\t\t\t\t\t padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }\n\t\t\t\t\t\t\t\t\th2 { color: #e74c3c; }\n\t\t\t\t\t\t\t\t\ta { display: inline-block; margin-top: 20px; padding: 10px 20px; \n\t\t\t\t\t\t\t\t\t background: #4285f4; color: white; text-decoration: none; border-radius: 4px; }\n\t\t\t\t\t\t\t\t\ta:hover { background: #357ae8; }\n\t\t\t\t\t\t\t\t</style>\n\t\t\t\t\t\t\t</head>\n\t\t\t\t\t\t\t<body>\n\t\t\t\t\t\t\t\t<div class=\"container\">\n\t\t\t\t\t\t\t\t\t<h2>Authentication Failed</h2>\n\t\t\t\t\t\t\t\t\t<p>No session token found. Please close this and try logging in again.</p>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</body>\n\t\t\t\t\t\t</html>\n\t\t\t\t\t");o.info("✅ Session token found: "+r.substring(0,20)+"...");const i=await fetch(`${s}/users/me`,{headers:{Cookie:`directus_session_token=${r}`}});if(!i.ok)throw new Error("Failed to get user info: "+await i.text());const a=await i.json(),d=a.data.id,u=a.data.email,p=a.data.first_name||a.data.email;o.info("👤 User authenticated via Google: "+d+", "+u);const m=r;if(o.info("🎫 Using session token as access token"),n){o.info("🌐 Browser request detected - maintaining session"),e.cookie("directus_session_token",r,{httpOnly:!0,secure:f,domain:g,sameSite:h,maxAge:6048e5,path:"/"});const n=t.query.redirect_uri||t.query.redirect||"/";return o.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, ${p}!</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`)}o.info("📱 Mobile app request - redirecting with token");const k=new URL(`${c}://${l}`);k.searchParams.set("access_token",m),k.searchParams.set("user_id",d),k.searchParams.set("email",u||""),k.searchParams.set("provider","google"),o.info("🔄 Redirecting to app (Google): "+k.toString()),e.redirect(k.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="${s}/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 c=t.headers.authorization,d=c?.replace("Bearer ","");if(!d)return e.status(400).json({error:"No token provided",message:"Authorization header with Bearer token is required"});o.info("🎫 Token received: "+d.substring(0,20)+"...");let l=null;try{const t=await fetch(`${s}/users/me`,{headers:{Authorization:`Bearer ${d}`}});if(t.ok){l=(await t.json()).data.email,o.info("👤 User email: "+l)}}catch(t){o.error("⚠️ Error getting user info: "+t.message)}try{const t=await fetch(`${s}/auth/logout`,{method:"POST",headers:{Authorization:`Bearer ${d}`,"Content-Type":"application/json"},body:JSON.stringify({refresh_token:d})});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(l)try{o.info("🔐 Getting Keycloak admin token...");const t=await async function(){try{const t=await fetch(`${n}/realms/master/protocol/openid-connect/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"password",client_id:u,username:i,password:a}).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 o=await fetch(`${n}/admin/realms/${r}/users?email=${encodeURIComponent(e)}`,{headers:{Authorization:`Bearer ${t}`}});if(!o.ok)throw new Error("Failed to get user");const i=await o.json();return i.length>0?i[0].id:null}catch(t){return o.error("Error getting user ID:",t),null}}(t,l);if(e){o.info("🚪 Logging out from Keycloak...");const i=await async function(t,e){try{const o=await fetch(`${n}/admin/realms/${r}/users/${e}/logout`,{method:"POST",headers:{Authorization:`Bearer ${t}`}});return o.ok||204===o.status}catch(t){return o.error("Error logging out user from Keycloak:",t),!1}}(t,e);i?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"})}})}};export{t as default};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ymys/directus-extension-sso",
|
|
3
|
+
"description": "Mobile OAuth proxy endpoints for Directus + Keycloak + Google",
|
|
4
|
+
"icon": "smartphone",
|
|
5
|
+
"version": "1.3.6",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"directus",
|
|
8
|
+
"directus-extension",
|
|
9
|
+
"directus-extension-endpoint",
|
|
10
|
+
"sso",
|
|
11
|
+
"keycloak",
|
|
12
|
+
"google",
|
|
13
|
+
"oauth",
|
|
14
|
+
"mobile",
|
|
15
|
+
"auth",
|
|
16
|
+
"proxy"
|
|
17
|
+
],
|
|
18
|
+
"author": "Yusuf M <ymys@users.noreply.github.com>",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"homepage": "https://github.com/ymys/directus-sso#readme",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/ymys/directus-sso/issues"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"package.json",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"directus:extension": {
|
|
35
|
+
"type": "endpoint",
|
|
36
|
+
"path": "dist/index.js",
|
|
37
|
+
"source": "src/index.js",
|
|
38
|
+
"host": "^11.0.0",
|
|
39
|
+
"id": "sso"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "directus-extension build",
|
|
43
|
+
"dev": "directus-extension build -w --no-minify",
|
|
44
|
+
"link": "directus-extension link"
|
|
45
|
+
},
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/ymys/directus-sso.git"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@directus/extensions-sdk": "^12.0.2"
|
|
52
|
+
}
|
|
53
|
+
}
|