@qwickapps/server 1.3.0 → 1.4.0
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/README.md +311 -0
- package/dist/core/control-panel.d.ts.map +1 -1
- package/dist/core/control-panel.js +144 -2
- package/dist/core/control-panel.js.map +1 -1
- package/dist/core/plugin-registry.d.ts +36 -0
- package/dist/core/plugin-registry.d.ts.map +1 -1
- package/dist/core/plugin-registry.js +26 -0
- package/dist/core/plugin-registry.js.map +1 -1
- package/dist/core/types.d.ts +19 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/plugins/auth/adapter-wrapper.d.ts +47 -0
- package/dist/plugins/auth/adapter-wrapper.d.ts.map +1 -0
- package/dist/plugins/auth/adapter-wrapper.js +166 -0
- package/dist/plugins/auth/adapter-wrapper.js.map +1 -0
- package/dist/plugins/auth/adapter-wrapper.test.d.ts +7 -0
- package/dist/plugins/auth/adapter-wrapper.test.d.ts.map +1 -0
- package/dist/plugins/auth/adapter-wrapper.test.js +303 -0
- package/dist/plugins/auth/adapter-wrapper.test.js.map +1 -0
- package/dist/plugins/auth/adapters/index.d.ts +1 -0
- package/dist/plugins/auth/adapters/index.d.ts.map +1 -1
- package/dist/plugins/auth/adapters/index.js +1 -0
- package/dist/plugins/auth/adapters/index.js.map +1 -1
- package/dist/plugins/auth/adapters/supabase-adapter.d.ts.map +1 -1
- package/dist/plugins/auth/adapters/supabase-adapter.js.map +1 -1
- package/dist/plugins/auth/adapters/supertokens-adapter.d.ts +18 -0
- package/dist/plugins/auth/adapters/supertokens-adapter.d.ts.map +1 -0
- package/dist/plugins/auth/adapters/supertokens-adapter.js +267 -0
- package/dist/plugins/auth/adapters/supertokens-adapter.js.map +1 -0
- package/dist/plugins/auth/config-store.d.ts +11 -0
- package/dist/plugins/auth/config-store.d.ts.map +1 -0
- package/dist/plugins/auth/config-store.js +232 -0
- package/dist/plugins/auth/config-store.js.map +1 -0
- package/dist/plugins/auth/config-store.test.d.ts +7 -0
- package/dist/plugins/auth/config-store.test.d.ts.map +1 -0
- package/dist/plugins/auth/config-store.test.js +299 -0
- package/dist/plugins/auth/config-store.test.js.map +1 -0
- package/dist/plugins/auth/env-config.d.ts +138 -0
- package/dist/plugins/auth/env-config.d.ts.map +1 -0
- package/dist/plugins/auth/env-config.js +1122 -0
- package/dist/plugins/auth/env-config.js.map +1 -0
- package/dist/plugins/auth/index.d.ts +7 -1
- package/dist/plugins/auth/index.d.ts.map +1 -1
- package/dist/plugins/auth/index.js +7 -0
- package/dist/plugins/auth/index.js.map +1 -1
- package/dist/plugins/auth/supertokens-adapter.test.d.ts +10 -0
- package/dist/plugins/auth/supertokens-adapter.test.d.ts.map +1 -0
- package/dist/plugins/auth/supertokens-adapter.test.js +486 -0
- package/dist/plugins/auth/supertokens-adapter.test.js.map +1 -0
- package/dist/plugins/auth/types.d.ts +176 -0
- package/dist/plugins/auth/types.d.ts.map +1 -1
- package/dist/plugins/auth/types.js.map +1 -1
- package/dist/plugins/cache-plugin.test.js +3 -0
- package/dist/plugins/cache-plugin.test.js.map +1 -1
- package/dist/plugins/index.d.ts +6 -2
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +5 -1
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/postgres-plugin.test.js +3 -0
- package/dist/plugins/postgres-plugin.test.js.map +1 -1
- package/dist/plugins/preferences/__tests__/deep-merge.test.d.ts +7 -0
- package/dist/plugins/preferences/__tests__/deep-merge.test.d.ts.map +1 -0
- package/dist/plugins/preferences/__tests__/deep-merge.test.js +215 -0
- package/dist/plugins/preferences/__tests__/deep-merge.test.js.map +1 -0
- package/dist/plugins/preferences/__tests__/preferences-plugin.test.d.ts +7 -0
- package/dist/plugins/preferences/__tests__/preferences-plugin.test.d.ts.map +1 -0
- package/dist/plugins/preferences/__tests__/preferences-plugin.test.js +265 -0
- package/dist/plugins/preferences/__tests__/preferences-plugin.test.js.map +1 -0
- package/dist/plugins/preferences/index.d.ts +12 -0
- package/dist/plugins/preferences/index.d.ts.map +1 -0
- package/dist/plugins/preferences/index.js +13 -0
- package/dist/plugins/preferences/index.js.map +1 -0
- package/dist/plugins/preferences/preferences-plugin.d.ts +39 -0
- package/dist/plugins/preferences/preferences-plugin.d.ts.map +1 -0
- package/dist/plugins/preferences/preferences-plugin.js +226 -0
- package/dist/plugins/preferences/preferences-plugin.js.map +1 -0
- package/dist/plugins/preferences/stores/index.d.ts +9 -0
- package/dist/plugins/preferences/stores/index.d.ts.map +1 -0
- package/dist/plugins/preferences/stores/index.js +9 -0
- package/dist/plugins/preferences/stores/index.js.map +1 -0
- package/dist/plugins/preferences/stores/postgres-store.d.ts +41 -0
- package/dist/plugins/preferences/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/preferences/stores/postgres-store.js +181 -0
- package/dist/plugins/preferences/stores/postgres-store.js.map +1 -0
- package/dist/plugins/preferences/types.d.ts +91 -0
- package/dist/plugins/preferences/types.d.ts.map +1 -0
- package/dist/plugins/preferences/types.js +10 -0
- package/dist/plugins/preferences/types.js.map +1 -0
- package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts +7 -0
- package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.d.ts.map +1 -0
- package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js +220 -0
- package/dist/plugins/rate-limit/__tests__/rate-limit-plugin.test.js.map +1 -0
- package/dist/plugins/rate-limit/cleanup.d.ts +40 -0
- package/dist/plugins/rate-limit/cleanup.d.ts.map +1 -0
- package/dist/plugins/rate-limit/cleanup.js +72 -0
- package/dist/plugins/rate-limit/cleanup.js.map +1 -0
- package/dist/plugins/rate-limit/env-config.d.ts +91 -0
- package/dist/plugins/rate-limit/env-config.d.ts.map +1 -0
- package/dist/plugins/rate-limit/env-config.js +318 -0
- package/dist/plugins/rate-limit/env-config.js.map +1 -0
- package/dist/plugins/rate-limit/index.d.ts +76 -0
- package/dist/plugins/rate-limit/index.d.ts.map +1 -0
- package/dist/plugins/rate-limit/index.js +79 -0
- package/dist/plugins/rate-limit/index.js.map +1 -0
- package/dist/plugins/rate-limit/middleware.d.ts +40 -0
- package/dist/plugins/rate-limit/middleware.d.ts.map +1 -0
- package/dist/plugins/rate-limit/middleware.js +169 -0
- package/dist/plugins/rate-limit/middleware.js.map +1 -0
- package/dist/plugins/rate-limit/rate-limit-plugin.d.ts +44 -0
- package/dist/plugins/rate-limit/rate-limit-plugin.d.ts.map +1 -0
- package/dist/plugins/rate-limit/rate-limit-plugin.js +354 -0
- package/dist/plugins/rate-limit/rate-limit-plugin.js.map +1 -0
- package/dist/plugins/rate-limit/rate-limit-service.d.ts +110 -0
- package/dist/plugins/rate-limit/rate-limit-service.d.ts.map +1 -0
- package/dist/plugins/rate-limit/rate-limit-service.js +172 -0
- package/dist/plugins/rate-limit/rate-limit-service.js.map +1 -0
- package/dist/plugins/rate-limit/stores/cache-store.d.ts +33 -0
- package/dist/plugins/rate-limit/stores/cache-store.d.ts.map +1 -0
- package/dist/plugins/rate-limit/stores/cache-store.js +225 -0
- package/dist/plugins/rate-limit/stores/cache-store.js.map +1 -0
- package/dist/plugins/rate-limit/stores/index.d.ts +8 -0
- package/dist/plugins/rate-limit/stores/index.d.ts.map +1 -0
- package/dist/plugins/rate-limit/stores/index.js +8 -0
- package/dist/plugins/rate-limit/stores/index.js.map +1 -0
- package/dist/plugins/rate-limit/stores/postgres-store.d.ts +34 -0
- package/dist/plugins/rate-limit/stores/postgres-store.d.ts.map +1 -0
- package/dist/plugins/rate-limit/stores/postgres-store.js +320 -0
- package/dist/plugins/rate-limit/stores/postgres-store.js.map +1 -0
- package/dist/plugins/rate-limit/strategies/fixed-window.d.ts +21 -0
- package/dist/plugins/rate-limit/strategies/fixed-window.d.ts.map +1 -0
- package/dist/plugins/rate-limit/strategies/fixed-window.js +97 -0
- package/dist/plugins/rate-limit/strategies/fixed-window.js.map +1 -0
- package/dist/plugins/rate-limit/strategies/index.d.ts +14 -0
- package/dist/plugins/rate-limit/strategies/index.d.ts.map +1 -0
- package/dist/plugins/rate-limit/strategies/index.js +27 -0
- package/dist/plugins/rate-limit/strategies/index.js.map +1 -0
- package/dist/plugins/rate-limit/strategies/sliding-window.d.ts +22 -0
- package/dist/plugins/rate-limit/strategies/sliding-window.d.ts.map +1 -0
- package/dist/plugins/rate-limit/strategies/sliding-window.js +122 -0
- package/dist/plugins/rate-limit/strategies/sliding-window.js.map +1 -0
- package/dist/plugins/rate-limit/strategies/token-bucket.d.ts +28 -0
- package/dist/plugins/rate-limit/strategies/token-bucket.d.ts.map +1 -0
- package/dist/plugins/rate-limit/strategies/token-bucket.js +121 -0
- package/dist/plugins/rate-limit/strategies/token-bucket.js.map +1 -0
- package/dist/plugins/rate-limit/types.d.ts +265 -0
- package/dist/plugins/rate-limit/types.d.ts.map +1 -0
- package/dist/plugins/rate-limit/types.js +9 -0
- package/dist/plugins/rate-limit/types.js.map +1 -0
- package/dist/plugins/users/__tests__/users-plugin.test.d.ts +9 -0
- package/dist/plugins/users/__tests__/users-plugin.test.d.ts.map +1 -0
- package/dist/plugins/users/__tests__/users-plugin.test.js +546 -0
- package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -0
- package/dist/plugins/users/index.d.ts +2 -2
- package/dist/plugins/users/index.d.ts.map +1 -1
- package/dist/plugins/users/index.js +1 -1
- package/dist/plugins/users/index.js.map +1 -1
- package/dist/plugins/users/types.d.ts +36 -0
- package/dist/plugins/users/types.d.ts.map +1 -1
- package/dist/plugins/users/users-plugin.d.ts +8 -2
- package/dist/plugins/users/users-plugin.d.ts.map +1 -1
- package/dist/plugins/users/users-plugin.js +122 -5
- package/dist/plugins/users/users-plugin.js.map +1 -1
- package/dist-ui/assets/index-D7DoZ9rL.js +478 -0
- package/dist-ui/assets/index-D7DoZ9rL.js.map +1 -0
- package/dist-ui/index.html +1 -1
- package/dist-ui-lib/api/controlPanelApi.d.ts +194 -7
- package/dist-ui-lib/dashboard/WidgetComponentRegistry.d.ts +9 -5
- package/dist-ui-lib/dashboard/builtInWidgets.d.ts +7 -1
- package/dist-ui-lib/dashboard/widgets/AuthStatusWidget.d.ts +9 -0
- package/dist-ui-lib/dashboard/widgets/IntegrationStatusWidget.d.ts +9 -0
- package/dist-ui-lib/dashboard/widgets/index.d.ts +2 -0
- package/dist-ui-lib/index.js +3665 -3945
- package/dist-ui-lib/index.js.map +1 -1
- package/dist-ui-lib/pages/AuthPage.d.ts +1 -0
- package/dist-ui-lib/pages/IntegrationsPage.d.ts +1 -0
- package/dist-ui-lib/pages/PluginsPage.d.ts +1 -0
- package/dist-ui-lib/pages/RateLimitPage.d.ts +1 -0
- package/package.json +7 -2
- package/src/core/control-panel.ts +161 -2
- package/src/core/plugin-registry.ts +63 -0
- package/src/core/types.ts +17 -0
- package/src/index.ts +45 -0
- package/src/plugins/auth/adapter-wrapper.test.ts +395 -0
- package/src/plugins/auth/adapter-wrapper.ts +205 -0
- package/src/plugins/auth/adapters/index.ts +1 -0
- package/src/plugins/auth/adapters/supabase-adapter.ts +22 -14
- package/src/plugins/auth/adapters/supertokens-adapter.ts +326 -0
- package/src/plugins/auth/config-store.test.ts +417 -0
- package/src/plugins/auth/config-store.ts +305 -0
- package/src/plugins/auth/env-config.ts +1279 -0
- package/src/plugins/auth/index.ts +30 -0
- package/src/plugins/auth/supertokens-adapter.test.ts +621 -0
- package/src/plugins/auth/types.ts +218 -0
- package/src/plugins/cache-plugin.test.ts +3 -0
- package/src/plugins/index.ts +75 -0
- package/src/plugins/postgres-plugin.test.ts +3 -0
- package/src/plugins/preferences/__tests__/deep-merge.test.ts +242 -0
- package/src/plugins/preferences/__tests__/preferences-plugin.test.ts +350 -0
- package/src/plugins/preferences/index.ts +30 -0
- package/src/plugins/preferences/preferences-plugin.ts +270 -0
- package/src/plugins/preferences/stores/index.ts +9 -0
- package/src/plugins/preferences/stores/postgres-store.ts +252 -0
- package/src/plugins/preferences/types.ts +100 -0
- package/src/plugins/rate-limit/__tests__/rate-limit-plugin.test.ts +259 -0
- package/src/plugins/rate-limit/cleanup.ts +117 -0
- package/src/plugins/rate-limit/env-config.ts +400 -0
- package/src/plugins/rate-limit/index.ts +128 -0
- package/src/plugins/rate-limit/middleware.ts +212 -0
- package/src/plugins/rate-limit/rate-limit-plugin.ts +400 -0
- package/src/plugins/rate-limit/rate-limit-service.ts +228 -0
- package/src/plugins/rate-limit/stores/cache-store.ts +261 -0
- package/src/plugins/rate-limit/stores/index.ts +8 -0
- package/src/plugins/rate-limit/stores/postgres-store.ts +402 -0
- package/src/plugins/rate-limit/strategies/fixed-window.ts +116 -0
- package/src/plugins/rate-limit/strategies/index.ts +30 -0
- package/src/plugins/rate-limit/strategies/sliding-window.ts +157 -0
- package/src/plugins/rate-limit/strategies/token-bucket.ts +154 -0
- package/src/plugins/rate-limit/types.ts +338 -0
- package/src/plugins/users/__tests__/users-plugin.test.ts +690 -0
- package/src/plugins/users/index.ts +3 -0
- package/src/plugins/users/types.ts +38 -0
- package/src/plugins/users/users-plugin.ts +142 -5
- package/ui/src/App.tsx +35 -14
- package/ui/src/api/controlPanelApi.ts +326 -1
- package/ui/src/components/ControlPanelApp.tsx +3 -0
- package/ui/src/dashboard/PluginWidgetRenderer.tsx +13 -10
- package/ui/src/dashboard/WidgetComponentRegistry.tsx +13 -9
- package/ui/src/dashboard/builtInWidgets.tsx +13 -3
- package/ui/src/dashboard/widgets/AuthStatusWidget.tsx +143 -0
- package/ui/src/dashboard/widgets/IntegrationStatusWidget.tsx +135 -0
- package/ui/src/dashboard/widgets/index.ts +2 -0
- package/ui/src/pages/AuthPage.tsx +1103 -0
- package/ui/src/pages/IntegrationsPage.tsx +288 -0
- package/ui/src/pages/PluginsPage.tsx +394 -0
- package/ui/src/pages/RateLimitPage.tsx +292 -0
- package/ui/vite.lib.config.ts +5 -0
- package/dist-ui/assets/index-Bsp2ntcw.js +0 -465
- package/dist-ui/assets/index-Bsp2ntcw.js.map +0 -1
|
@@ -207,3 +207,41 @@ export interface UsersPluginConfig {
|
|
|
207
207
|
/** Enable debug logging */
|
|
208
208
|
debug?: boolean;
|
|
209
209
|
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Comprehensive user information aggregated from multiple plugins.
|
|
213
|
+
* Used by /users/:id/info and /users/sync endpoints.
|
|
214
|
+
*/
|
|
215
|
+
export interface UserInfo {
|
|
216
|
+
/** Core user data from users plugin */
|
|
217
|
+
user: User;
|
|
218
|
+
/** User's entitlements (if entitlements plugin loaded) */
|
|
219
|
+
entitlements?: string[];
|
|
220
|
+
/** User's preferences (if preferences plugin loaded) */
|
|
221
|
+
preferences?: Record<string, unknown>;
|
|
222
|
+
/** Active ban info (if bans plugin loaded, null if not banned) */
|
|
223
|
+
ban?: {
|
|
224
|
+
id: string;
|
|
225
|
+
reason: string;
|
|
226
|
+
banned_at: Date;
|
|
227
|
+
expires_at?: Date;
|
|
228
|
+
} | null;
|
|
229
|
+
/** User's roles (if roles plugin loaded - future) */
|
|
230
|
+
roles?: string[];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Input for POST /users/sync endpoint
|
|
235
|
+
*/
|
|
236
|
+
export interface UserSyncInput {
|
|
237
|
+
/** User's email address */
|
|
238
|
+
email: string;
|
|
239
|
+
/** External provider ID (e.g., Auth0 user_id) */
|
|
240
|
+
external_id: string;
|
|
241
|
+
/** Provider name (e.g., 'auth0', 'google') */
|
|
242
|
+
provider: string;
|
|
243
|
+
/** User's display name (optional) */
|
|
244
|
+
name?: string;
|
|
245
|
+
/** Profile picture URL (optional) */
|
|
246
|
+
picture?: string;
|
|
247
|
+
}
|
|
@@ -18,10 +18,18 @@ import type {
|
|
|
18
18
|
CreateUserInput,
|
|
19
19
|
UpdateUserInput,
|
|
20
20
|
UserSearchParams,
|
|
21
|
+
UserInfo,
|
|
22
|
+
UserSyncInput,
|
|
21
23
|
} from './types.js';
|
|
24
|
+
// Import helpers from other plugins for buildUserInfo
|
|
25
|
+
// Note: These imports are used dynamically based on registry.hasPlugin() checks
|
|
26
|
+
import { getEntitlements } from '../entitlements/entitlements-plugin.js';
|
|
27
|
+
import { getPreferences } from '../preferences/preferences-plugin.js';
|
|
28
|
+
import { getActiveBan } from '../bans/bans-plugin.js';
|
|
22
29
|
|
|
23
30
|
// Store instance for helper access
|
|
24
31
|
let currentStore: UserStore | null = null;
|
|
32
|
+
let currentRegistry: PluginRegistry | null = null;
|
|
25
33
|
|
|
26
34
|
/**
|
|
27
35
|
* Create the Users plugin
|
|
@@ -49,8 +57,9 @@ export function createUsersPlugin(config: UsersPluginConfig): Plugin {
|
|
|
49
57
|
await config.store.initialize();
|
|
50
58
|
log('Users plugin migrations complete');
|
|
51
59
|
|
|
52
|
-
// Store
|
|
60
|
+
// Store references for helper access
|
|
53
61
|
currentStore = config.store;
|
|
62
|
+
currentRegistry = registry;
|
|
54
63
|
|
|
55
64
|
// Register health check
|
|
56
65
|
registry.registerHealthCheck({
|
|
@@ -191,6 +200,69 @@ export function createUsersPlugin(config: UsersPluginConfig): Plugin {
|
|
|
191
200
|
}
|
|
192
201
|
},
|
|
193
202
|
});
|
|
203
|
+
|
|
204
|
+
// GET /users/:id/info - Get comprehensive user info
|
|
205
|
+
registry.addRoute({
|
|
206
|
+
method: 'get',
|
|
207
|
+
path: `${apiPrefix}/:id/info`,
|
|
208
|
+
pluginId: 'users',
|
|
209
|
+
handler: async (req: Request, res: Response) => {
|
|
210
|
+
try {
|
|
211
|
+
const user = await config.store.getById(req.params.id);
|
|
212
|
+
if (!user) {
|
|
213
|
+
return res.status(404).json({ error: 'User not found' });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const info = await buildUserInfo(user, registry);
|
|
217
|
+
res.json(info);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('[UsersPlugin] Get user info error:', error);
|
|
220
|
+
res.status(500).json({ error: 'Failed to get user info' });
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// POST /users/sync - Find or create user, return comprehensive info
|
|
226
|
+
registry.addRoute({
|
|
227
|
+
method: 'post',
|
|
228
|
+
path: `${apiPrefix}/sync`,
|
|
229
|
+
pluginId: 'users',
|
|
230
|
+
handler: async (req: Request, res: Response) => {
|
|
231
|
+
try {
|
|
232
|
+
const input = req.body as UserSyncInput;
|
|
233
|
+
|
|
234
|
+
// Normalize and validate email
|
|
235
|
+
const email = input.email?.trim().toLowerCase();
|
|
236
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
237
|
+
if (!email || !emailRegex.test(email)) {
|
|
238
|
+
return res.status(400).json({ error: 'Valid email is required' });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Validate required fields
|
|
242
|
+
if (!input.external_id) {
|
|
243
|
+
return res.status(400).json({ error: 'external_id is required' });
|
|
244
|
+
}
|
|
245
|
+
if (!input.provider) {
|
|
246
|
+
return res.status(400).json({ error: 'provider is required' });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Find or create user
|
|
250
|
+
const user = await findOrCreateUser({
|
|
251
|
+
email: email,
|
|
252
|
+
external_id: input.external_id,
|
|
253
|
+
provider: input.provider,
|
|
254
|
+
name: input.name?.trim(),
|
|
255
|
+
picture: input.picture?.trim(),
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const info = await buildUserInfo(user, registry);
|
|
259
|
+
res.json(info);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error('[UsersPlugin] User sync error:', error);
|
|
262
|
+
res.status(500).json({ error: 'Failed to sync user' });
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
});
|
|
194
266
|
}
|
|
195
267
|
|
|
196
268
|
log('Users plugin started');
|
|
@@ -200,6 +272,7 @@ export function createUsersPlugin(config: UsersPluginConfig): Plugin {
|
|
|
200
272
|
log('Stopping users plugin');
|
|
201
273
|
await config.store.shutdown();
|
|
202
274
|
currentStore = null;
|
|
275
|
+
currentRegistry = null;
|
|
203
276
|
log('Users plugin stopped');
|
|
204
277
|
},
|
|
205
278
|
};
|
|
@@ -260,10 +333,7 @@ export async function findOrCreateUser(data: {
|
|
|
260
333
|
// Try to find by email
|
|
261
334
|
user = await currentStore.getByEmail(data.email);
|
|
262
335
|
if (user) {
|
|
263
|
-
//
|
|
264
|
-
if (!user.external_id) {
|
|
265
|
-
await currentStore.update(user.id, {});
|
|
266
|
-
}
|
|
336
|
+
// Note: external_id cannot be updated after user creation for security reasons
|
|
267
337
|
await currentStore.updateLastLogin(user.id);
|
|
268
338
|
return user;
|
|
269
339
|
}
|
|
@@ -279,3 +349,70 @@ export async function findOrCreateUser(data: {
|
|
|
279
349
|
|
|
280
350
|
return user;
|
|
281
351
|
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Build comprehensive user info by aggregating data from all loaded plugins.
|
|
355
|
+
* This helper fetches data from entitlements, preferences, and bans plugins
|
|
356
|
+
* in parallel (if they are loaded) and returns a unified UserInfo object.
|
|
357
|
+
*/
|
|
358
|
+
export async function buildUserInfo(user: User, registry: PluginRegistry): Promise<UserInfo> {
|
|
359
|
+
const info: UserInfo = { user };
|
|
360
|
+
|
|
361
|
+
// Fetch data from other plugins in parallel
|
|
362
|
+
const promises: Promise<void>[] = [];
|
|
363
|
+
|
|
364
|
+
if (registry.hasPlugin('entitlements')) {
|
|
365
|
+
promises.push(
|
|
366
|
+
getEntitlements(user.email)
|
|
367
|
+
.then((result) => {
|
|
368
|
+
info.entitlements = result.entitlements;
|
|
369
|
+
})
|
|
370
|
+
.catch((error) => {
|
|
371
|
+
console.error('[UsersPlugin] Failed to fetch entitlements:', error);
|
|
372
|
+
// Continue without entitlements - don't fail the whole request
|
|
373
|
+
})
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (registry.hasPlugin('preferences')) {
|
|
378
|
+
promises.push(
|
|
379
|
+
getPreferences(user.id)
|
|
380
|
+
.then((prefs) => {
|
|
381
|
+
info.preferences = prefs;
|
|
382
|
+
})
|
|
383
|
+
.catch((error) => {
|
|
384
|
+
console.error('[UsersPlugin] Failed to fetch preferences:', error);
|
|
385
|
+
// Continue without preferences - don't fail the whole request
|
|
386
|
+
})
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (registry.hasPlugin('bans')) {
|
|
391
|
+
promises.push(
|
|
392
|
+
getActiveBan(user.id)
|
|
393
|
+
.then((ban) => {
|
|
394
|
+
// Transform Ban to UserInfo.ban shape (only include relevant fields)
|
|
395
|
+
info.ban = ban
|
|
396
|
+
? {
|
|
397
|
+
id: ban.id,
|
|
398
|
+
reason: ban.reason,
|
|
399
|
+
banned_at: ban.banned_at,
|
|
400
|
+
expires_at: ban.expires_at,
|
|
401
|
+
}
|
|
402
|
+
: null;
|
|
403
|
+
})
|
|
404
|
+
.catch((error) => {
|
|
405
|
+
console.error('[UsersPlugin] Failed to fetch ban status:', error);
|
|
406
|
+
// Continue without ban info - don't fail the whole request
|
|
407
|
+
})
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Future: roles plugin
|
|
412
|
+
// if (registry.hasPlugin('roles')) {
|
|
413
|
+
// promises.push(getUserRoles(user.id).then(roles => info.roles = roles));
|
|
414
|
+
// }
|
|
415
|
+
|
|
416
|
+
await Promise.all(promises);
|
|
417
|
+
return info;
|
|
418
|
+
}
|
package/ui/src/App.tsx
CHANGED
|
@@ -3,12 +3,20 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
|
|
3
3
|
import { QwickApp, ProductLogo, Text } from '@qwickapps/react-framework';
|
|
4
4
|
import { Link, Box } from '@mui/material';
|
|
5
5
|
import { defaultConfig } from './config/AppConfig';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
DashboardWidgetProvider,
|
|
8
|
+
WidgetComponentRegistryProvider,
|
|
9
|
+
getBuiltInWidgetComponents,
|
|
10
|
+
} from './dashboard';
|
|
7
11
|
import { DashboardPage } from './pages/DashboardPage';
|
|
8
12
|
import { LogsPage } from './pages/LogsPage';
|
|
9
13
|
import { SystemPage } from './pages/SystemPage';
|
|
14
|
+
import { PluginsPage } from './pages/PluginsPage';
|
|
10
15
|
import { UsersPage } from './pages/UsersPage';
|
|
11
16
|
import { EntitlementsPage } from './pages/EntitlementsPage';
|
|
17
|
+
import { AuthPage } from './pages/AuthPage';
|
|
18
|
+
import { RateLimitPage } from './pages/RateLimitPage';
|
|
19
|
+
import { IntegrationsPage } from './pages/IntegrationsPage';
|
|
12
20
|
import { PluginPage } from './pages/PluginPage';
|
|
13
21
|
import { NotFoundPage } from './pages/NotFoundPage';
|
|
14
22
|
import { api, type MenuContribution } from './api/controlPanelApi';
|
|
@@ -24,6 +32,7 @@ interface NavigationItem {
|
|
|
24
32
|
// Core navigation items always shown
|
|
25
33
|
const coreNavigationItems: NavigationItem[] = [
|
|
26
34
|
{ id: 'dashboard', label: 'Dashboard', route: '/', icon: 'dashboard' },
|
|
35
|
+
{ id: 'plugins', label: 'Plugins', route: '/plugins', icon: 'extension' },
|
|
27
36
|
{ id: 'logs', label: 'Logs', route: '/logs', icon: 'article' },
|
|
28
37
|
{ id: 'system', label: 'System', route: '/system', icon: 'settings' },
|
|
29
38
|
];
|
|
@@ -34,7 +43,7 @@ const builtInPluginNavItems: Record<string, NavigationItem> = {
|
|
|
34
43
|
};
|
|
35
44
|
|
|
36
45
|
// Routes that have dedicated page components
|
|
37
|
-
const dedicatedRoutes = new Set(['/', '/logs', '/system', '/users', '/entitlements']);
|
|
46
|
+
const dedicatedRoutes = new Set(['/', '/plugins', '/logs', '/system', '/users', '/entitlements', '/auth', '/rate-limits', '/integrations']);
|
|
38
47
|
|
|
39
48
|
// Package version - injected at build time or fallback
|
|
40
49
|
const SERVER_VERSION = '1.0.0';
|
|
@@ -175,19 +184,21 @@ export function App() {
|
|
|
175
184
|
|
|
176
185
|
return (
|
|
177
186
|
<BrowserRouter basename={basePath || undefined}>
|
|
178
|
-
<
|
|
179
|
-
<
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
187
|
+
<WidgetComponentRegistryProvider initialComponents={getBuiltInWidgetComponents()}>
|
|
188
|
+
<DashboardWidgetProvider>
|
|
189
|
+
<QwickApp
|
|
190
|
+
config={defaultConfig}
|
|
191
|
+
logo={logo}
|
|
192
|
+
footerContent={footerContent}
|
|
193
|
+
enableScaffolding={true}
|
|
194
|
+
navigationItems={navigationItems}
|
|
195
|
+
showThemeSwitcher={true}
|
|
196
|
+
showPaletteSwitcher={true}
|
|
197
|
+
>
|
|
188
198
|
<Routes>
|
|
189
199
|
{/* Core routes */}
|
|
190
200
|
<Route path="/" element={<DashboardPage />} />
|
|
201
|
+
<Route path="/plugins" element={<PluginsPage />} />
|
|
191
202
|
<Route path="/logs" element={<LogsPage />} />
|
|
192
203
|
<Route path="/system" element={<SystemPage />} />
|
|
193
204
|
|
|
@@ -198,6 +209,15 @@ export function App() {
|
|
|
198
209
|
{registeredPlugins.has('entitlements') && (
|
|
199
210
|
<Route path="/entitlements" element={<EntitlementsPage />} />
|
|
200
211
|
)}
|
|
212
|
+
{registeredPlugins.has('auth') && (
|
|
213
|
+
<Route path="/auth" element={<AuthPage />} />
|
|
214
|
+
)}
|
|
215
|
+
{registeredPlugins.has('rate-limit') && (
|
|
216
|
+
<Route path="/rate-limits" element={<RateLimitPage />} />
|
|
217
|
+
)}
|
|
218
|
+
{registeredPlugins.has('ai-proxy') && (
|
|
219
|
+
<Route path="/integrations" element={<IntegrationsPage />} />
|
|
220
|
+
)}
|
|
201
221
|
|
|
202
222
|
{/* Dynamic plugin routes - render generic PluginPage for non-dedicated routes */}
|
|
203
223
|
{pluginMenuItems
|
|
@@ -212,8 +232,9 @@ export function App() {
|
|
|
212
232
|
|
|
213
233
|
<Route path="*" element={<NotFoundPage />} />
|
|
214
234
|
</Routes>
|
|
215
|
-
|
|
216
|
-
|
|
235
|
+
</QwickApp>
|
|
236
|
+
</DashboardWidgetProvider>
|
|
237
|
+
</WidgetComponentRegistryProvider>
|
|
217
238
|
</BrowserRouter>
|
|
218
239
|
);
|
|
219
240
|
}
|
|
@@ -203,6 +203,180 @@ export interface UiContributionsResponse {
|
|
|
203
203
|
plugins: Array<{ id: string; name: string; version?: string; status: string }>;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
// ==================
|
|
207
|
+
// Plugin Detail Types
|
|
208
|
+
// ==================
|
|
209
|
+
|
|
210
|
+
export interface ConfigContribution {
|
|
211
|
+
id: string;
|
|
212
|
+
component: string;
|
|
213
|
+
title?: string;
|
|
214
|
+
pluginId: string;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface PluginContributions {
|
|
218
|
+
routes: Array<{ method: string; path: string }>;
|
|
219
|
+
menuItems: MenuContribution[];
|
|
220
|
+
pages: PageContribution[];
|
|
221
|
+
widgets: WidgetContribution[];
|
|
222
|
+
config?: ConfigContribution;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export interface PluginInfo {
|
|
226
|
+
id: string;
|
|
227
|
+
name: string;
|
|
228
|
+
version?: string;
|
|
229
|
+
status: 'starting' | 'active' | 'stopped' | 'error';
|
|
230
|
+
error?: string;
|
|
231
|
+
contributionCounts: {
|
|
232
|
+
routes: number;
|
|
233
|
+
menuItems: number;
|
|
234
|
+
pages: number;
|
|
235
|
+
widgets: number;
|
|
236
|
+
hasConfig: boolean;
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface PluginsResponse {
|
|
241
|
+
plugins: PluginInfo[];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface PluginDetailResponse {
|
|
245
|
+
id: string;
|
|
246
|
+
name: string;
|
|
247
|
+
version?: string;
|
|
248
|
+
status: 'starting' | 'active' | 'stopped' | 'error';
|
|
249
|
+
error?: string;
|
|
250
|
+
contributions: PluginContributions;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ==================
|
|
254
|
+
// Auth Config Types
|
|
255
|
+
// ==================
|
|
256
|
+
|
|
257
|
+
export type AuthPluginState = 'disabled' | 'enabled' | 'error';
|
|
258
|
+
export type AuthAdapterType = 'auth0' | 'supabase' | 'supertokens' | 'basic';
|
|
259
|
+
|
|
260
|
+
export interface AuthConfigStatus {
|
|
261
|
+
state: AuthPluginState;
|
|
262
|
+
adapter: string | null;
|
|
263
|
+
error?: string;
|
|
264
|
+
missingVars?: string[];
|
|
265
|
+
config?: Record<string, string>;
|
|
266
|
+
/** Runtime config from database (if available) */
|
|
267
|
+
runtimeConfig?: RuntimeAuthConfig;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export interface RuntimeAuthConfig {
|
|
271
|
+
adapter: AuthAdapterType | null;
|
|
272
|
+
config: {
|
|
273
|
+
auth0?: Auth0AdapterConfig;
|
|
274
|
+
supabase?: SupabaseAdapterConfig;
|
|
275
|
+
supertokens?: SupertokensAdapterConfig;
|
|
276
|
+
basic?: BasicAdapterConfig;
|
|
277
|
+
};
|
|
278
|
+
settings: {
|
|
279
|
+
authRequired?: boolean;
|
|
280
|
+
excludePaths?: string[];
|
|
281
|
+
debug?: boolean;
|
|
282
|
+
};
|
|
283
|
+
updatedAt: string;
|
|
284
|
+
updatedBy?: string;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export interface Auth0AdapterConfig {
|
|
288
|
+
domain: string;
|
|
289
|
+
clientId: string;
|
|
290
|
+
clientSecret: string;
|
|
291
|
+
baseUrl: string;
|
|
292
|
+
secret: string;
|
|
293
|
+
audience?: string;
|
|
294
|
+
scopes?: string[];
|
|
295
|
+
allowedRoles?: string[];
|
|
296
|
+
allowedDomains?: string[];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export interface SupabaseAdapterConfig {
|
|
300
|
+
url: string;
|
|
301
|
+
anonKey: string;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export interface BasicAdapterConfig {
|
|
305
|
+
username: string;
|
|
306
|
+
password: string;
|
|
307
|
+
realm?: string;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export interface SupertokensAdapterConfig {
|
|
311
|
+
connectionUri: string;
|
|
312
|
+
apiKey?: string;
|
|
313
|
+
appName: string;
|
|
314
|
+
apiDomain: string;
|
|
315
|
+
websiteDomain: string;
|
|
316
|
+
apiBasePath?: string;
|
|
317
|
+
websiteBasePath?: string;
|
|
318
|
+
enableEmailPassword?: boolean;
|
|
319
|
+
socialProviders?: {
|
|
320
|
+
google?: { clientId: string; clientSecret: string };
|
|
321
|
+
apple?: { clientId: string; clientSecret: string; keyId: string; teamId: string };
|
|
322
|
+
github?: { clientId: string; clientSecret: string };
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export interface UpdateAuthConfigRequest {
|
|
327
|
+
adapter: AuthAdapterType;
|
|
328
|
+
config: Record<string, unknown>;
|
|
329
|
+
settings?: {
|
|
330
|
+
authRequired?: boolean;
|
|
331
|
+
excludePaths?: string[];
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export interface TestProviderRequest {
|
|
336
|
+
adapter: AuthAdapterType;
|
|
337
|
+
config: Record<string, unknown>;
|
|
338
|
+
provider?: 'google' | 'github' | 'apple';
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export interface TestProviderResponse {
|
|
342
|
+
success: boolean;
|
|
343
|
+
message: string;
|
|
344
|
+
details?: {
|
|
345
|
+
latency?: number;
|
|
346
|
+
version?: string;
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ==================
|
|
351
|
+
// Rate Limit Config Types
|
|
352
|
+
// ==================
|
|
353
|
+
|
|
354
|
+
export type RateLimitStrategy = 'sliding-window' | 'fixed-window' | 'token-bucket';
|
|
355
|
+
|
|
356
|
+
export interface RateLimitConfig {
|
|
357
|
+
windowMs: number;
|
|
358
|
+
maxRequests: number;
|
|
359
|
+
strategy: RateLimitStrategy;
|
|
360
|
+
cleanupEnabled: boolean;
|
|
361
|
+
cleanupIntervalMs: number;
|
|
362
|
+
store: string;
|
|
363
|
+
cache: string;
|
|
364
|
+
cacheAvailable: boolean;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export interface RateLimitConfigUpdateRequest {
|
|
368
|
+
windowMs?: number;
|
|
369
|
+
maxRequests?: number;
|
|
370
|
+
strategy?: RateLimitStrategy;
|
|
371
|
+
cleanupEnabled?: boolean;
|
|
372
|
+
cleanupIntervalMs?: number;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export interface RateLimitConfigUpdateResponse {
|
|
376
|
+
success: boolean;
|
|
377
|
+
config: RateLimitConfig;
|
|
378
|
+
}
|
|
379
|
+
|
|
206
380
|
class ControlPanelApi {
|
|
207
381
|
private baseUrl: string;
|
|
208
382
|
|
|
@@ -218,6 +392,33 @@ class ControlPanelApi {
|
|
|
218
392
|
this.baseUrl = baseUrl;
|
|
219
393
|
}
|
|
220
394
|
|
|
395
|
+
/**
|
|
396
|
+
* Get the base URL for API requests.
|
|
397
|
+
*/
|
|
398
|
+
getBaseUrl(): string {
|
|
399
|
+
return this.baseUrl;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Generic fetch method for API requests.
|
|
404
|
+
* Automatically prepends the base URL and /api prefix.
|
|
405
|
+
*/
|
|
406
|
+
async fetch<T = unknown>(path: string, options?: RequestInit): Promise<T> {
|
|
407
|
+
const url = `${this.baseUrl}/api${path.startsWith('/') ? path : `/${path}`}`;
|
|
408
|
+
const response = await fetch(url, {
|
|
409
|
+
...options,
|
|
410
|
+
headers: {
|
|
411
|
+
'Content-Type': 'application/json',
|
|
412
|
+
...options?.headers,
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
if (!response.ok) {
|
|
416
|
+
const error = await response.json().catch(() => ({}));
|
|
417
|
+
throw new Error(error.error || error.message || `Request failed: ${response.statusText}`);
|
|
418
|
+
}
|
|
419
|
+
return response.json();
|
|
420
|
+
}
|
|
421
|
+
|
|
221
422
|
// ==================
|
|
222
423
|
// Plugin Feature Detection
|
|
223
424
|
// ==================
|
|
@@ -477,7 +678,7 @@ class ControlPanelApi {
|
|
|
477
678
|
// Plugins API
|
|
478
679
|
// ==================
|
|
479
680
|
|
|
480
|
-
async getPlugins(): Promise<
|
|
681
|
+
async getPlugins(): Promise<PluginsResponse> {
|
|
481
682
|
const response = await fetch(`${this.baseUrl}/api/plugins`);
|
|
482
683
|
if (!response.ok) {
|
|
483
684
|
throw new Error(`Plugins request failed: ${response.statusText}`);
|
|
@@ -485,6 +686,17 @@ class ControlPanelApi {
|
|
|
485
686
|
return response.json();
|
|
486
687
|
}
|
|
487
688
|
|
|
689
|
+
async getPluginDetail(id: string): Promise<PluginDetailResponse> {
|
|
690
|
+
const response = await fetch(`${this.baseUrl}/api/plugins/${encodeURIComponent(id)}`);
|
|
691
|
+
if (!response.ok) {
|
|
692
|
+
if (response.status === 404) {
|
|
693
|
+
throw new Error(`Plugin not found: ${id}`);
|
|
694
|
+
}
|
|
695
|
+
throw new Error(`Plugin detail request failed: ${response.statusText}`);
|
|
696
|
+
}
|
|
697
|
+
return response.json();
|
|
698
|
+
}
|
|
699
|
+
|
|
488
700
|
// ==================
|
|
489
701
|
// UI Contributions API
|
|
490
702
|
// ==================
|
|
@@ -496,6 +708,119 @@ class ControlPanelApi {
|
|
|
496
708
|
}
|
|
497
709
|
return response.json();
|
|
498
710
|
}
|
|
711
|
+
|
|
712
|
+
// ==================
|
|
713
|
+
// Auth Config API
|
|
714
|
+
// ==================
|
|
715
|
+
|
|
716
|
+
async getAuthConfigStatus(): Promise<AuthConfigStatus> {
|
|
717
|
+
const response = await fetch(`${this.baseUrl}/api/auth/config/status`);
|
|
718
|
+
if (!response.ok) {
|
|
719
|
+
// Return disabled state if endpoint not available
|
|
720
|
+
if (response.status === 404) {
|
|
721
|
+
return { state: 'disabled', adapter: null };
|
|
722
|
+
}
|
|
723
|
+
throw new Error(`Auth config status request failed: ${response.statusText}`);
|
|
724
|
+
}
|
|
725
|
+
return response.json();
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
async getAuthConfig(): Promise<AuthConfigStatus> {
|
|
729
|
+
const response = await fetch(`${this.baseUrl}/api/auth/config`);
|
|
730
|
+
if (!response.ok) {
|
|
731
|
+
if (response.status === 404) {
|
|
732
|
+
return { state: 'disabled', adapter: null };
|
|
733
|
+
}
|
|
734
|
+
throw new Error(`Auth config request failed: ${response.statusText}`);
|
|
735
|
+
}
|
|
736
|
+
return response.json();
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Update auth configuration (save to database for hot-reload)
|
|
741
|
+
*/
|
|
742
|
+
async updateAuthConfig(request: UpdateAuthConfigRequest): Promise<{ success: boolean; message: string }> {
|
|
743
|
+
const response = await fetch(`${this.baseUrl}/api/auth/config`, {
|
|
744
|
+
method: 'PUT',
|
|
745
|
+
headers: { 'Content-Type': 'application/json' },
|
|
746
|
+
body: JSON.stringify(request),
|
|
747
|
+
});
|
|
748
|
+
if (!response.ok) {
|
|
749
|
+
const error = await response.json().catch(() => ({}));
|
|
750
|
+
throw new Error(error.error || `Auth config update failed: ${response.statusText}`);
|
|
751
|
+
}
|
|
752
|
+
return response.json();
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Delete auth configuration (revert to environment variables)
|
|
757
|
+
*/
|
|
758
|
+
async deleteAuthConfig(): Promise<{ success: boolean; message: string }> {
|
|
759
|
+
const response = await fetch(`${this.baseUrl}/api/auth/config`, {
|
|
760
|
+
method: 'DELETE',
|
|
761
|
+
});
|
|
762
|
+
if (!response.ok) {
|
|
763
|
+
const error = await response.json().catch(() => ({}));
|
|
764
|
+
throw new Error(error.error || `Auth config delete failed: ${response.statusText}`);
|
|
765
|
+
}
|
|
766
|
+
return response.json();
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Test auth provider connection without saving
|
|
771
|
+
*/
|
|
772
|
+
async testAuthProvider(request: TestProviderRequest): Promise<TestProviderResponse> {
|
|
773
|
+
const response = await fetch(`${this.baseUrl}/api/auth/test-provider`, {
|
|
774
|
+
method: 'POST',
|
|
775
|
+
headers: { 'Content-Type': 'application/json' },
|
|
776
|
+
body: JSON.stringify(request),
|
|
777
|
+
});
|
|
778
|
+
if (!response.ok) {
|
|
779
|
+
const error = await response.json().catch(() => ({}));
|
|
780
|
+
throw new Error(error.error || `Provider test failed: ${response.statusText}`);
|
|
781
|
+
}
|
|
782
|
+
return response.json();
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Test current auth provider connection (uses existing env/runtime config)
|
|
787
|
+
*/
|
|
788
|
+
async testCurrentAuthProvider(): Promise<TestProviderResponse> {
|
|
789
|
+
const response = await fetch(`${this.baseUrl}/api/auth/test-current`, {
|
|
790
|
+
method: 'POST',
|
|
791
|
+
headers: { 'Content-Type': 'application/json' },
|
|
792
|
+
});
|
|
793
|
+
if (!response.ok) {
|
|
794
|
+
const error = await response.json().catch(() => ({}));
|
|
795
|
+
throw new Error(error.error || `Provider test failed: ${response.statusText}`);
|
|
796
|
+
}
|
|
797
|
+
return response.json();
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// ==================
|
|
801
|
+
// Rate Limit Config API
|
|
802
|
+
// ==================
|
|
803
|
+
|
|
804
|
+
async getRateLimitConfig(): Promise<RateLimitConfig> {
|
|
805
|
+
const response = await fetch(`${this.baseUrl}/api/rate-limit/config`);
|
|
806
|
+
if (!response.ok) {
|
|
807
|
+
throw new Error(`Rate limit config request failed: ${response.statusText}`);
|
|
808
|
+
}
|
|
809
|
+
return response.json();
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
async updateRateLimitConfig(updates: RateLimitConfigUpdateRequest): Promise<RateLimitConfigUpdateResponse> {
|
|
813
|
+
const response = await fetch(`${this.baseUrl}/api/rate-limit/config`, {
|
|
814
|
+
method: 'PUT',
|
|
815
|
+
headers: { 'Content-Type': 'application/json' },
|
|
816
|
+
body: JSON.stringify(updates),
|
|
817
|
+
});
|
|
818
|
+
if (!response.ok) {
|
|
819
|
+
const error = await response.json().catch(() => ({}));
|
|
820
|
+
throw new Error(error.error || `Rate limit config update failed: ${response.statusText}`);
|
|
821
|
+
}
|
|
822
|
+
return response.json();
|
|
823
|
+
}
|
|
499
824
|
}
|
|
500
825
|
|
|
501
826
|
export const api = new ControlPanelApi();
|