@qwickapps/server 1.5.1 → 1.6.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.
Files changed (135) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/core/control-panel.d.ts.map +1 -1
  3. package/dist/core/control-panel.js +41 -0
  4. package/dist/core/control-panel.js.map +1 -1
  5. package/dist/core/guards.d.ts.map +1 -1
  6. package/dist/core/guards.js +77 -0
  7. package/dist/core/guards.js.map +1 -1
  8. package/dist/core/health-manager.d.ts +4 -0
  9. package/dist/core/health-manager.d.ts.map +1 -1
  10. package/dist/core/health-manager.js +6 -1
  11. package/dist/core/health-manager.js.map +1 -1
  12. package/dist/core/plugin-registry.d.ts +55 -5
  13. package/dist/core/plugin-registry.d.ts.map +1 -1
  14. package/dist/core/plugin-registry.js +57 -19
  15. package/dist/core/plugin-registry.js.map +1 -1
  16. package/dist/core/types.d.ts +2 -0
  17. package/dist/core/types.d.ts.map +1 -1
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +3 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/plugins/api-keys/api-keys-plugin.d.ts +46 -0
  23. package/dist/plugins/api-keys/api-keys-plugin.d.ts.map +1 -0
  24. package/dist/plugins/api-keys/api-keys-plugin.js +329 -0
  25. package/dist/plugins/api-keys/api-keys-plugin.js.map +1 -0
  26. package/dist/plugins/api-keys/index.d.ts +14 -0
  27. package/dist/plugins/api-keys/index.d.ts.map +1 -0
  28. package/dist/plugins/api-keys/index.js +17 -0
  29. package/dist/plugins/api-keys/index.js.map +1 -0
  30. package/dist/plugins/api-keys/middleware/bearer-token-auth.d.ts +74 -0
  31. package/dist/plugins/api-keys/middleware/bearer-token-auth.d.ts.map +1 -0
  32. package/dist/plugins/api-keys/middleware/bearer-token-auth.js +201 -0
  33. package/dist/plugins/api-keys/middleware/bearer-token-auth.js.map +1 -0
  34. package/dist/plugins/api-keys/middleware/index.d.ts +7 -0
  35. package/dist/plugins/api-keys/middleware/index.d.ts.map +1 -0
  36. package/dist/plugins/api-keys/middleware/index.js +7 -0
  37. package/dist/plugins/api-keys/middleware/index.js.map +1 -0
  38. package/dist/plugins/api-keys/stores/index.d.ts +7 -0
  39. package/dist/plugins/api-keys/stores/index.d.ts.map +1 -0
  40. package/dist/plugins/api-keys/stores/index.js +7 -0
  41. package/dist/plugins/api-keys/stores/index.js.map +1 -0
  42. package/dist/plugins/api-keys/stores/postgres-store.d.ts +34 -0
  43. package/dist/plugins/api-keys/stores/postgres-store.d.ts.map +1 -0
  44. package/dist/plugins/api-keys/stores/postgres-store.js +360 -0
  45. package/dist/plugins/api-keys/stores/postgres-store.js.map +1 -0
  46. package/dist/plugins/api-keys/types.d.ts +268 -0
  47. package/dist/plugins/api-keys/types.d.ts.map +1 -0
  48. package/dist/plugins/api-keys/types.js +56 -0
  49. package/dist/plugins/api-keys/types.js.map +1 -0
  50. package/dist/plugins/auth/auth-plugin.d.ts.map +1 -1
  51. package/dist/plugins/auth/auth-plugin.js +17 -1
  52. package/dist/plugins/auth/auth-plugin.js.map +1 -1
  53. package/dist/plugins/auth/auth-plugin.test.js +133 -0
  54. package/dist/plugins/auth/auth-plugin.test.js.map +1 -1
  55. package/dist/plugins/auth/env-config.d.ts.map +1 -1
  56. package/dist/plugins/auth/env-config.js +6 -2
  57. package/dist/plugins/auth/env-config.js.map +1 -1
  58. package/dist/plugins/auth/types.d.ts +10 -0
  59. package/dist/plugins/auth/types.d.ts.map +1 -1
  60. package/dist/plugins/auth/types.js.map +1 -1
  61. package/dist/plugins/devices/__tests__/token-utils.test.js +4 -2
  62. package/dist/plugins/devices/__tests__/token-utils.test.js.map +1 -1
  63. package/dist/plugins/frontend-app-plugin.d.ts.map +1 -1
  64. package/dist/plugins/frontend-app-plugin.js +21 -4
  65. package/dist/plugins/frontend-app-plugin.js.map +1 -1
  66. package/dist/plugins/index.d.ts +2 -0
  67. package/dist/plugins/index.d.ts.map +1 -1
  68. package/dist/plugins/index.js +2 -0
  69. package/dist/plugins/index.js.map +1 -1
  70. package/dist/plugins/qwickbrain/index.d.ts +25 -0
  71. package/dist/plugins/qwickbrain/index.d.ts.map +1 -0
  72. package/dist/plugins/qwickbrain/index.js +24 -0
  73. package/dist/plugins/qwickbrain/index.js.map +1 -0
  74. package/dist/plugins/qwickbrain/qwickbrain-plugin.d.ts +23 -0
  75. package/dist/plugins/qwickbrain/qwickbrain-plugin.d.ts.map +1 -0
  76. package/dist/plugins/qwickbrain/qwickbrain-plugin.js +528 -0
  77. package/dist/plugins/qwickbrain/qwickbrain-plugin.js.map +1 -0
  78. package/dist/plugins/qwickbrain/types.d.ts +131 -0
  79. package/dist/plugins/qwickbrain/types.d.ts.map +1 -0
  80. package/dist/plugins/qwickbrain/types.js +9 -0
  81. package/dist/plugins/qwickbrain/types.js.map +1 -0
  82. package/dist/plugins/users/__tests__/postgres-store.test.js +1 -0
  83. package/dist/plugins/users/__tests__/postgres-store.test.js.map +1 -1
  84. package/dist/plugins/users/__tests__/users-plugin.test.js +3 -0
  85. package/dist/plugins/users/__tests__/users-plugin.test.js.map +1 -1
  86. package/dist/plugins/users/stores/postgres-store.d.ts.map +1 -1
  87. package/dist/plugins/users/stores/postgres-store.js +59 -1
  88. package/dist/plugins/users/stores/postgres-store.js.map +1 -1
  89. package/dist/plugins/users/types.d.ts +22 -0
  90. package/dist/plugins/users/types.d.ts.map +1 -1
  91. package/dist-ui/assets/index-5nX8fM1a.js +469 -0
  92. package/dist-ui/assets/index-5nX8fM1a.js.map +1 -0
  93. package/dist-ui/index.html +1 -1
  94. package/dist-ui-lib/api/controlPanelApi.d.ts +68 -0
  95. package/dist-ui-lib/components/index.d.ts +2 -1
  96. package/dist-ui-lib/index.js +2642 -2281
  97. package/dist-ui-lib/index.js.map +1 -1
  98. package/dist-ui-lib/pages/APIKeysPage.d.ts +13 -0
  99. package/dist-ui-lib/pages/AcceptInvitationPage.d.ts +28 -0
  100. package/package.json +3 -2
  101. package/src/core/control-panel.ts +47 -0
  102. package/src/core/guards.ts +89 -0
  103. package/src/core/health-manager.ts +6 -1
  104. package/src/core/plugin-registry.ts +123 -25
  105. package/src/core/types.ts +2 -0
  106. package/src/index.ts +11 -0
  107. package/src/plugins/api-keys/api-keys-plugin.ts +397 -0
  108. package/src/plugins/api-keys/index.ts +49 -0
  109. package/src/plugins/api-keys/middleware/bearer-token-auth.ts +250 -0
  110. package/src/plugins/api-keys/middleware/index.ts +12 -0
  111. package/src/plugins/api-keys/stores/index.ts +7 -0
  112. package/src/plugins/api-keys/stores/postgres-store.ts +487 -0
  113. package/src/plugins/api-keys/types.ts +243 -0
  114. package/src/plugins/auth/auth-plugin.test.ts +167 -0
  115. package/src/plugins/auth/auth-plugin.ts +17 -1
  116. package/src/plugins/auth/env-config.ts +6 -2
  117. package/src/plugins/auth/types.ts +10 -0
  118. package/src/plugins/devices/__tests__/token-utils.test.ts +4 -2
  119. package/src/plugins/frontend-app-plugin.ts +24 -4
  120. package/src/plugins/index.ts +15 -0
  121. package/src/plugins/qwickbrain/index.ts +33 -0
  122. package/src/plugins/qwickbrain/qwickbrain-plugin.ts +642 -0
  123. package/src/plugins/qwickbrain/types.ts +146 -0
  124. package/src/plugins/users/__tests__/postgres-store.test.ts +1 -0
  125. package/src/plugins/users/__tests__/users-plugin.test.ts +3 -0
  126. package/src/plugins/users/stores/postgres-store.ts +69 -0
  127. package/src/plugins/users/types.ts +25 -0
  128. package/ui/src/App.tsx +6 -1
  129. package/ui/src/api/controlPanelApi.ts +206 -37
  130. package/ui/src/components/index.ts +6 -0
  131. package/ui/src/pages/APIKeysPage.tsx +661 -0
  132. package/ui/src/pages/AcceptInvitationPage.tsx +169 -0
  133. package/ui/src/pages/UsersPage.tsx +225 -2
  134. package/dist-ui/assets/index-CynOqPkb.js +0 -469
  135. package/dist-ui/assets/index-CynOqPkb.js.map +0 -1
@@ -0,0 +1,146 @@
1
+ /**
2
+ * QwickBrain Plugin Types
3
+ *
4
+ * Type definitions for the QwickBrain MCP proxy plugin.
5
+ *
6
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
7
+ */
8
+
9
+ /**
10
+ * Configuration for the QwickBrain plugin
11
+ */
12
+ export interface QwickBrainPluginConfig {
13
+ /**
14
+ * Base URL of the QwickBrain instance (via Tailscale)
15
+ * Example: "http://macmini.tailnet-xxx.ts.net:8080"
16
+ */
17
+ qwickbrainUrl: string;
18
+
19
+ /**
20
+ * Request timeout in milliseconds
21
+ * @default 30000
22
+ */
23
+ timeout?: number;
24
+
25
+ /**
26
+ * Which MCP tools to expose publicly
27
+ * Use '*' to expose all tools, or provide a list of tool names
28
+ * @default '*'
29
+ */
30
+ exposedTools?: string[] | '*';
31
+
32
+ /**
33
+ * API configuration
34
+ */
35
+ api?: {
36
+ /**
37
+ * Enable API routes
38
+ * @default true
39
+ */
40
+ enabled?: boolean;
41
+
42
+ /**
43
+ * API route prefix (mounted under /api)
44
+ * @default '/mcp'
45
+ */
46
+ prefix?: string;
47
+ };
48
+
49
+ /**
50
+ * Authentication configuration
51
+ */
52
+ auth?: {
53
+ /**
54
+ * Require authentication for MCP tool endpoints
55
+ * When true, /mcp/tools and /mcp/tools/:name require valid auth
56
+ * Status endpoint (/mcp/status) is always public
57
+ * @default true
58
+ */
59
+ required?: boolean;
60
+
61
+ /**
62
+ * Roles required to access MCP tools (optional)
63
+ * If set, user must have at least one of these roles
64
+ */
65
+ allowedRoles?: string[];
66
+ };
67
+
68
+ /**
69
+ * Rate limiting configuration
70
+ */
71
+ rateLimit?: MCPRateLimitConfig;
72
+
73
+ /**
74
+ * Enable debug logging
75
+ * @default false
76
+ */
77
+ debug?: boolean;
78
+ }
79
+
80
+ /**
81
+ * MCP Tool definition from QwickBrain
82
+ */
83
+ export interface MCPToolDefinition {
84
+ name: string;
85
+ description: string;
86
+ inputSchema: {
87
+ type: 'object';
88
+ properties: Record<string, unknown>;
89
+ required?: string[];
90
+ };
91
+ }
92
+
93
+ /**
94
+ * MCP Tool call request
95
+ */
96
+ export interface MCPToolCallRequest {
97
+ name: string;
98
+ arguments: Record<string, unknown>;
99
+ }
100
+
101
+ /**
102
+ * MCP Tool call response
103
+ */
104
+ export interface MCPToolCallResponse {
105
+ content: Array<{
106
+ type: 'text' | 'image' | 'resource';
107
+ text?: string;
108
+ data?: string;
109
+ mimeType?: string;
110
+ }>;
111
+ isError?: boolean;
112
+ }
113
+
114
+ /**
115
+ * QwickBrain connection status
116
+ */
117
+ export interface QwickBrainConnectionStatus {
118
+ connected: boolean;
119
+ lastCheck: Date;
120
+ latencyMs?: number;
121
+ error?: string;
122
+ tailscaleStatus?: 'connected' | 'disconnected' | 'unknown';
123
+ }
124
+
125
+ /**
126
+ * Rate limit configuration for MCP requests
127
+ */
128
+ export interface MCPRateLimitConfig {
129
+ /**
130
+ * Enable rate limiting
131
+ * @default true
132
+ */
133
+ enabled?: boolean;
134
+
135
+ /**
136
+ * Requests per minute per client
137
+ * @default 60
138
+ */
139
+ perClientPerMinute?: number;
140
+
141
+ /**
142
+ * Total requests per minute (global)
143
+ * @default 1000
144
+ */
145
+ globalPerMinute?: number;
146
+ }
@@ -19,6 +19,7 @@ const mockUser: User = {
19
19
  external_id: 'auth0|abc123',
20
20
  provider: 'auth0',
21
21
  picture: 'https://example.com/avatar.jpg',
22
+ status: 'active',
22
23
  metadata: {
23
24
  identifiers: {
24
25
  auth0_user_id: 'auth0|abc123',
@@ -44,6 +44,7 @@ describe('Users Plugin', () => {
44
44
  external_id: 'auth0|abc123',
45
45
  provider: 'auth0',
46
46
  picture: 'https://example.com/avatar.jpg',
47
+ status: 'active',
47
48
  created_at: new Date('2025-01-01'),
48
49
  updated_at: new Date('2025-01-01'),
49
50
  last_login_at: new Date('2025-12-13'),
@@ -64,6 +65,8 @@ describe('Users Plugin', () => {
64
65
  delete: vi.fn().mockResolvedValue(true),
65
66
  search: vi.fn().mockResolvedValue({ users: [mockUser], total: 1, page: 1, limit: 20, totalPages: 1 }),
66
67
  updateLastLogin: vi.fn().mockResolvedValue(undefined),
68
+ getByInvitationToken: vi.fn().mockResolvedValue(mockUser),
69
+ acceptInvitation: vi.fn().mockResolvedValue(mockUser),
67
70
  shutdown: vi.fn().mockResolvedValue(undefined),
68
71
  });
69
72
 
@@ -72,6 +72,9 @@ export function postgresUserStore(config: PostgresUserStoreConfig): UserStore {
72
72
  external_id VARCHAR(255),
73
73
  provider VARCHAR(50),
74
74
  picture TEXT,
75
+ status VARCHAR(20) DEFAULT 'active',
76
+ invitation_token VARCHAR(255),
77
+ invitation_expires_at TIMESTAMPTZ,
75
78
  metadata JSONB DEFAULT '{}',
76
79
  created_at TIMESTAMPTZ DEFAULT NOW(),
77
80
  updated_at TIMESTAMPTZ DEFAULT NOW(),
@@ -80,6 +83,41 @@ export function postgresUserStore(config: PostgresUserStoreConfig): UserStore {
80
83
 
81
84
  CREATE INDEX IF NOT EXISTS idx_${usersTable}_email ON ${usersTableFull}(email);
82
85
  CREATE INDEX IF NOT EXISTS idx_${usersTable}_external_id ON ${usersTableFull}(external_id, provider);
86
+ CREATE INDEX IF NOT EXISTS idx_${usersTable}_invitation_token ON ${usersTableFull}(invitation_token);
87
+ CREATE INDEX IF NOT EXISTS idx_${usersTable}_status ON ${usersTableFull}(status);
88
+ `);
89
+
90
+ // Add new columns to existing tables (migration)
91
+ await getPool().query(`
92
+ DO $$
93
+ BEGIN
94
+ IF NOT EXISTS (
95
+ SELECT 1 FROM information_schema.columns
96
+ WHERE table_schema = '${schema}'
97
+ AND table_name = '${usersTable}'
98
+ AND column_name = 'status'
99
+ ) THEN
100
+ ALTER TABLE ${usersTableFull} ADD COLUMN status VARCHAR(20) DEFAULT 'active';
101
+ END IF;
102
+
103
+ IF NOT EXISTS (
104
+ SELECT 1 FROM information_schema.columns
105
+ WHERE table_schema = '${schema}'
106
+ AND table_name = '${usersTable}'
107
+ AND column_name = 'invitation_token'
108
+ ) THEN
109
+ ALTER TABLE ${usersTableFull} ADD COLUMN invitation_token VARCHAR(255);
110
+ END IF;
111
+
112
+ IF NOT EXISTS (
113
+ SELECT 1 FROM information_schema.columns
114
+ WHERE table_schema = '${schema}'
115
+ AND table_name = '${usersTable}'
116
+ AND column_name = 'invitation_expires_at'
117
+ ) THEN
118
+ ALTER TABLE ${usersTableFull} ADD COLUMN invitation_expires_at TIMESTAMPTZ;
119
+ END IF;
120
+ END $$;
83
121
  `);
84
122
  },
85
123
 
@@ -263,6 +301,7 @@ export function postgresUserStore(config: PostgresUserStoreConfig): UserStore {
263
301
  const {
264
302
  query,
265
303
  provider,
304
+ status,
266
305
  page = 1,
267
306
  limit = 20,
268
307
  sortBy = 'created_at',
@@ -285,6 +324,12 @@ export function postgresUserStore(config: PostgresUserStoreConfig): UserStore {
285
324
  paramIndex++;
286
325
  }
287
326
 
327
+ if (status) {
328
+ conditions.push(`status = $${paramIndex}`);
329
+ values.push(status);
330
+ paramIndex++;
331
+ }
332
+
288
333
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
289
334
 
290
335
  // Validate sort column to prevent SQL injection
@@ -322,6 +367,30 @@ export function postgresUserStore(config: PostgresUserStoreConfig): UserStore {
322
367
  await getPool().query(`UPDATE ${usersTableFull} SET last_login_at = NOW() WHERE id = $1`, [id]);
323
368
  },
324
369
 
370
+ async getByInvitationToken(token: string): Promise<User | null> {
371
+ const result = await getPool().query(
372
+ `SELECT * FROM ${usersTableFull} WHERE invitation_token = $1 AND invitation_expires_at > NOW()`,
373
+ [token]
374
+ );
375
+ return (result.rows[0] as User) || null;
376
+ },
377
+
378
+ async acceptInvitation(token: string): Promise<User | null> {
379
+ const result = await getPool().query(
380
+ `UPDATE ${usersTableFull}
381
+ SET status = 'active',
382
+ invitation_token = NULL,
383
+ invitation_expires_at = NULL,
384
+ updated_at = NOW()
385
+ WHERE invitation_token = $1
386
+ AND invitation_expires_at > NOW()
387
+ AND status = 'invited'
388
+ RETURNING *`,
389
+ [token]
390
+ );
391
+ return (result.rows[0] as User) || null;
392
+ },
393
+
325
394
  async shutdown(): Promise<void> {
326
395
  // Pool is managed externally, nothing to do here
327
396
  },
@@ -9,6 +9,11 @@
9
9
  * Copyright (c) 2025 QwickApps.com. All rights reserved.
10
10
  */
11
11
 
12
+ /**
13
+ * User status in the system
14
+ */
15
+ export type UserStatus = 'invited' | 'active' | 'suspended';
16
+
12
17
  /**
13
18
  * User record in the database
14
19
  */
@@ -25,6 +30,12 @@ export interface User {
25
30
  provider?: string;
26
31
  /** Profile picture URL */
27
32
  picture?: string;
33
+ /** User status */
34
+ status: UserStatus;
35
+ /** Invitation token (set when user is invited) */
36
+ invitation_token?: string;
37
+ /** Invitation expiration timestamp */
38
+ invitation_expires_at?: Date;
28
39
  /** Additional metadata (JSON) */
29
40
  metadata?: Record<string, unknown>;
30
41
  /** When the user was created */
@@ -90,6 +101,8 @@ export interface UserSearchParams {
90
101
  query?: string;
91
102
  /** Filter by provider */
92
103
  provider?: string;
104
+ /** Filter by status */
105
+ status?: UserStatus;
93
106
  /** Page number (1-indexed) */
94
107
  page?: number;
95
108
  /** Items per page */
@@ -182,6 +195,18 @@ export interface UserStore {
182
195
  */
183
196
  updateLastLogin(id: string): Promise<void>;
184
197
 
198
+ /**
199
+ * Get a user by invitation token (only if invitation is valid and not expired)
200
+ */
201
+ getByInvitationToken(token: string): Promise<User | null>;
202
+
203
+ /**
204
+ * Accept an invitation by token.
205
+ * Sets status to 'active' and clears invitation token fields.
206
+ * Returns the updated user or null if token is invalid/expired.
207
+ */
208
+ acceptInvitation(token: string): Promise<User | null>;
209
+
185
210
  /**
186
211
  * Shutdown the store
187
212
  */
package/ui/src/App.tsx CHANGED
@@ -18,6 +18,7 @@ import { AuthPage } from './pages/AuthPage';
18
18
  import { RateLimitPage } from './pages/RateLimitPage';
19
19
  import { NotificationsPage } from './pages/NotificationsPage';
20
20
  import { IntegrationsPage } from './pages/IntegrationsPage';
21
+ import { APIKeysPage } from './pages/APIKeysPage';
21
22
  import { PluginPage } from './pages/PluginPage';
22
23
  import { NotFoundPage } from './pages/NotFoundPage';
23
24
  import { api, type MenuContribution } from './api/controlPanelApi';
@@ -41,10 +42,11 @@ const coreNavigationItems: NavigationItem[] = [
41
42
  // Built-in optional navigation items - shown if corresponding plugin is registered
42
43
  const builtInPluginNavItems: Record<string, NavigationItem> = {
43
44
  users: { id: 'users', label: 'Users', route: '/users', icon: 'people' },
45
+ 'api-keys': { id: 'api-keys', label: 'API Keys', route: '/api-keys', icon: 'key' },
44
46
  };
45
47
 
46
48
  // Routes that have dedicated page components
47
- const dedicatedRoutes = new Set(['/', '/plugins', '/logs', '/system', '/users', '/entitlements', '/auth', '/rate-limits', '/notifications', '/integrations']);
49
+ const dedicatedRoutes = new Set(['/', '/plugins', '/logs', '/system', '/users', '/entitlements', '/auth', '/rate-limits', '/notifications', '/integrations', '/api-keys']);
48
50
 
49
51
  // Package version - injected at build time or fallback
50
52
  const SERVER_VERSION = '1.0.0';
@@ -222,6 +224,9 @@ export function App() {
222
224
  {registeredPlugins.has('ai-proxy') && (
223
225
  <Route path="/integrations" element={<IntegrationsPage />} />
224
226
  )}
227
+ {registeredPlugins.has('api-keys') && (
228
+ <Route path="/api-keys" element={<APIKeysPage />} />
229
+ )}
225
230
 
226
231
  {/* Dynamic plugin routes - render generic PluginPage for non-dedicated routes */}
227
232
  {pluginMenuItems