@qwickapps/server 1.8.2 → 1.9.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/CHANGELOG.md +14 -0
- package/README.md +4 -4
- package/dist-ui/assets/{index-Cez_jyhl.js → index-DnEQCOGR.js} +51 -51
- package/dist-ui/assets/{index-Cez_jyhl.js.map → index-DnEQCOGR.js.map} +1 -1
- package/dist-ui/index.html +1 -1
- package/package.json +26 -24
- package/src/plugins/auth/adapters/supertokens-adapter.ts +9 -13
- package/src/plugins/auth/auth-plugin.ts +6 -5
- package/src/plugins/auth/env-config.ts +1 -0
- package/src/plugins/auth/types.ts +2 -0
- package/src/plugins/maintenance/seed-executor.ts +5 -4
- package/src/plugins/maintenance-plugin.ts +27 -3
- package/ui/src/dashboard/widgets/AuthStatusWidget.tsx +3 -8
package/dist-ui/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Control Panel</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-DnEQCOGR.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-De-dCl_t.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qwickapps/server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Plugin-based application server framework for building websites, APIs, admin dashboards, and full-stack products",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "dist/src/index.js",
|
|
6
7
|
"types": "dist/src/index.d.ts",
|
|
7
8
|
"exports": {
|
|
@@ -29,6 +30,29 @@
|
|
|
29
30
|
"ui",
|
|
30
31
|
"CHANGELOG.md"
|
|
31
32
|
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "npm run build:ui-lib && npm run build:server && npm run build:ui",
|
|
35
|
+
"build:server": "tsc",
|
|
36
|
+
"build:ui": "cd ui && vite build",
|
|
37
|
+
"build:ui-lib": "cd ui && vite build --config vite.lib.config.ts && tsc -p tsconfig.lib.json",
|
|
38
|
+
"build:clean": "rm -rf dist dist-ui dist-ui-lib && npm run build",
|
|
39
|
+
"dev": "tsc --watch",
|
|
40
|
+
"dev:ui": "cd ui && vite",
|
|
41
|
+
"clean": "rm -rf dist dist-ui dist-ui-lib node_modules",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"test:watch": "vitest",
|
|
44
|
+
"test:coverage": "vitest run --coverage",
|
|
45
|
+
"test:e2e": "playwright test",
|
|
46
|
+
"test:e2e:ui": "playwright test --ui",
|
|
47
|
+
"test:e2e:headed": "playwright test --headed",
|
|
48
|
+
"demo": "npm run build:server && pnpm tsx examples/demo-server.ts",
|
|
49
|
+
"demo:gateway": "npm run build:server && pnpm tsx examples/demo-gateway.ts",
|
|
50
|
+
"demo:all": "npm run build:server && pnpm tsx examples/demo-all.ts",
|
|
51
|
+
"type-check": "tsc --noEmit",
|
|
52
|
+
"type-check:ui": "cd ui && tsc --noEmit",
|
|
53
|
+
"validate:clean-install": "./qa/clean-install/validate.sh",
|
|
54
|
+
"prepublishOnly": "npm run test && npm run build && npm run validate:clean-install"
|
|
55
|
+
},
|
|
32
56
|
"dependencies": {
|
|
33
57
|
"@qwickapps/logging": "^1.0.2",
|
|
34
58
|
"compression": "^1.7.4",
|
|
@@ -120,27 +144,5 @@
|
|
|
120
144
|
"homepage": "https://github.com/qwickapps/server#readme",
|
|
121
145
|
"publishConfig": {
|
|
122
146
|
"access": "public"
|
|
123
|
-
},
|
|
124
|
-
"scripts": {
|
|
125
|
-
"build": "npm run build:ui-lib && npm run build:server && npm run build:ui",
|
|
126
|
-
"build:server": "tsc",
|
|
127
|
-
"build:ui": "cd ui && vite build",
|
|
128
|
-
"build:ui-lib": "cd ui && vite build --config vite.lib.config.ts && tsc -p tsconfig.lib.json",
|
|
129
|
-
"build:clean": "rm -rf dist dist-ui dist-ui-lib && npm run build",
|
|
130
|
-
"dev": "tsc --watch",
|
|
131
|
-
"dev:ui": "cd ui && vite",
|
|
132
|
-
"clean": "rm -rf dist dist-ui dist-ui-lib node_modules",
|
|
133
|
-
"test": "vitest run",
|
|
134
|
-
"test:watch": "vitest",
|
|
135
|
-
"test:coverage": "vitest run --coverage",
|
|
136
|
-
"test:e2e": "playwright test",
|
|
137
|
-
"test:e2e:ui": "playwright test --ui",
|
|
138
|
-
"test:e2e:headed": "playwright test --headed",
|
|
139
|
-
"demo": "npm run build:server && pnpm tsx examples/demo-server.ts",
|
|
140
|
-
"demo:gateway": "npm run build:server && pnpm tsx examples/demo-gateway.ts",
|
|
141
|
-
"demo:all": "npm run build:server && pnpm tsx examples/demo-all.ts",
|
|
142
|
-
"type-check": "tsc --noEmit",
|
|
143
|
-
"type-check:ui": "cd ui && tsc --noEmit",
|
|
144
|
-
"validate:clean-install": "./qa/clean-install/validate.sh"
|
|
145
147
|
}
|
|
146
|
-
}
|
|
148
|
+
}
|
|
@@ -47,14 +47,12 @@ export function supertokensAdapter(config: SupertokensAdapterConfig): AuthAdapte
|
|
|
47
47
|
// Store response on request for later use in getUser()
|
|
48
48
|
(req as SupertokensExtendedRequest)[REQUEST_RES_KEY] = res;
|
|
49
49
|
|
|
50
|
-
// Skip if already initialized with error
|
|
50
|
+
// Skip if already initialized with error — let auth-checking middleware
|
|
51
|
+
// decide whether to block the request based on authRequired config.
|
|
52
|
+
// Returning 500 here blocks ALL routes (including /auth/config/status)
|
|
53
|
+
// even when authRequired is false.
|
|
51
54
|
if (initializationError) {
|
|
52
|
-
return
|
|
53
|
-
error: 'Auth Configuration Error',
|
|
54
|
-
message:
|
|
55
|
-
'Supertokens is not properly configured. Install supertokens-node package: npm install supertokens-node',
|
|
56
|
-
details: initializationError.message,
|
|
57
|
-
});
|
|
55
|
+
return next();
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
// Lazy initialize Supertokens
|
|
@@ -163,12 +161,10 @@ export function supertokensAdapter(config: SupertokensAdapterConfig): AuthAdapte
|
|
|
163
161
|
initializationError =
|
|
164
162
|
error instanceof Error ? error : new Error('Failed to initialize Supertokens');
|
|
165
163
|
console.error('[SupertokensAdapter] Initialization error:', error);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
details: initializationError.message,
|
|
171
|
-
});
|
|
164
|
+
// Let the auth-checking middleware decide whether to block this request.
|
|
165
|
+
// Non-auth routes (e.g. /auth/config/status) must remain accessible
|
|
166
|
+
// so the UI can display the configuration error state.
|
|
167
|
+
return next();
|
|
172
168
|
}
|
|
173
169
|
}
|
|
174
170
|
|
|
@@ -73,13 +73,14 @@ export function createAuthPlugin(config: AuthPluginConfig): Plugin {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
// Register SuperTokens middleware on router for
|
|
77
|
-
//
|
|
78
|
-
//
|
|
76
|
+
// Register SuperTokens middleware on router for auth paths
|
|
77
|
+
// Uses config.apiBasePath so Gateway forwards requests to the correct path
|
|
78
|
+
// e.g. SUPERTOKENS_API_BASE_PATH=/qapi/auth → router.use('/qapi/auth', ...)
|
|
79
|
+
const authBasePath = config.apiBasePath ?? '/auth';
|
|
79
80
|
if (Array.isArray(primaryMiddleware)) {
|
|
80
|
-
router.use(
|
|
81
|
+
router.use(authBasePath, ...primaryMiddleware);
|
|
81
82
|
} else {
|
|
82
|
-
router.use(
|
|
83
|
+
router.use(authBasePath, primaryMiddleware);
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
// Add the auth checking middleware to router (not app)
|
|
@@ -402,6 +402,7 @@ export function createAuthPluginFromEnv(options?: AuthEnvPluginOptions): Plugin
|
|
|
402
402
|
excludePaths,
|
|
403
403
|
authRequired,
|
|
404
404
|
debug,
|
|
405
|
+
apiBasePath: getEnv('SUPERTOKENS_API_BASE_PATH') ?? getEnv('AUTH_API_BASE_PATH'),
|
|
405
406
|
onUnauthorized: options?.onUnauthorized,
|
|
406
407
|
onAuthenticated: options?.onAuthenticated,
|
|
407
408
|
};
|
|
@@ -180,6 +180,8 @@ export interface AuthPluginConfig {
|
|
|
180
180
|
excludePaths?: string[];
|
|
181
181
|
/** Whether auth is required for all routes (default: true) */
|
|
182
182
|
authRequired?: boolean;
|
|
183
|
+
/** API base path for auth routes registered on the Express router (default: '/auth') */
|
|
184
|
+
apiBasePath?: string;
|
|
183
185
|
/** Custom unauthorized handler */
|
|
184
186
|
onUnauthorized?: (req: Request, res: Response) => void;
|
|
185
187
|
/**
|
|
@@ -106,11 +106,12 @@ export class SeedExecutor {
|
|
|
106
106
|
this.outputSize = 0;
|
|
107
107
|
|
|
108
108
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
|
|
109
|
+
// .ts/.mts files require tsx for TypeScript compilation.
|
|
110
|
+
// .mjs files are native ESM and run directly with node — tsx is not
|
|
111
|
+
// available in the system PATH in Docker production builds.
|
|
112
|
+
const needsTsx = scriptPath.match(/\.(ts|mts)$/);
|
|
112
113
|
const execCommand = needsTsx ? 'tsx' : process.execPath;
|
|
113
|
-
const execArgs =
|
|
114
|
+
const execArgs = [scriptPath];
|
|
114
115
|
|
|
115
116
|
// Spawn process with TypeScript support if needed
|
|
116
117
|
this.child = spawn(execCommand, execArgs, {
|
|
@@ -416,6 +416,8 @@ export function createMaintenancePlugin(config: MaintenancePluginConfig = {}): P
|
|
|
416
416
|
} catch (error) {
|
|
417
417
|
logger.error('Seed execution failed', { name, error });
|
|
418
418
|
|
|
419
|
+
const duration = Date.now() - startTime;
|
|
420
|
+
|
|
419
421
|
// Send error event via SSE to notify client
|
|
420
422
|
res.write(`data: ${JSON.stringify({
|
|
421
423
|
type: 'error',
|
|
@@ -423,6 +425,13 @@ export function createMaintenancePlugin(config: MaintenancePluginConfig = {}): P
|
|
|
423
425
|
timestamp: new Date().toISOString()
|
|
424
426
|
})}\n\n`);
|
|
425
427
|
|
|
428
|
+
// Send exit event so the UI can transition out of the "Running..." state
|
|
429
|
+
res.write(`data: ${JSON.stringify({
|
|
430
|
+
type: 'exit',
|
|
431
|
+
data: JSON.stringify({ exitCode: 1, duration }),
|
|
432
|
+
timestamp: new Date().toISOString()
|
|
433
|
+
})}\n\n`);
|
|
434
|
+
|
|
426
435
|
// Update execution record as failed
|
|
427
436
|
if (hasPostgres() && executionId) {
|
|
428
437
|
const db = getPostgres();
|
|
@@ -476,16 +485,31 @@ export function createMaintenancePlugin(config: MaintenancePluginConfig = {}): P
|
|
|
476
485
|
|
|
477
486
|
const db = getPostgres();
|
|
478
487
|
|
|
488
|
+
// Derive the DB role from the connection URL so GRANT statements
|
|
489
|
+
// work regardless of which user owns the schema.
|
|
490
|
+
let dbRole = 'qwickapps';
|
|
491
|
+
if (config.databaseUrl) {
|
|
492
|
+
try {
|
|
493
|
+
const parsedUrl = new URL(config.databaseUrl);
|
|
494
|
+
const urlUser = parsedUrl.username;
|
|
495
|
+
if (urlUser && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(urlUser)) {
|
|
496
|
+
dbRole = urlUser;
|
|
497
|
+
}
|
|
498
|
+
} catch {
|
|
499
|
+
// Keep default if URL is unparseable
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
479
503
|
// Drop and recreate public schema (removes all tables, data, etc.)
|
|
480
504
|
await db.queryRaw('DROP SCHEMA IF EXISTS public CASCADE');
|
|
481
505
|
await db.queryRaw('CREATE SCHEMA public');
|
|
482
506
|
await db.queryRaw('GRANT ALL ON SCHEMA public TO public');
|
|
483
507
|
await db.queryRaw('GRANT ALL ON SCHEMA public TO postgres');
|
|
484
|
-
await db.queryRaw(
|
|
508
|
+
await db.queryRaw(`GRANT ALL ON SCHEMA public TO ${dbRole}`);
|
|
485
509
|
|
|
486
510
|
// Grant default privileges for future tables and sequences
|
|
487
|
-
await db.queryRaw(
|
|
488
|
-
await db.queryRaw(
|
|
511
|
+
await db.queryRaw(`ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO ${dbRole}`);
|
|
512
|
+
await db.queryRaw(`ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO ${dbRole}`);
|
|
489
513
|
|
|
490
514
|
res.json({
|
|
491
515
|
success: true,
|
|
@@ -12,14 +12,9 @@ import { Box, Typography, Chip, CircularProgress, Alert } from '@mui/material';
|
|
|
12
12
|
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
|
13
13
|
import ErrorIcon from '@mui/icons-material/Error';
|
|
14
14
|
import BlockIcon from '@mui/icons-material/Block';
|
|
15
|
-
import { api } from '../../api/controlPanelApi';
|
|
15
|
+
import { api, type AuthConfigStatus } from '../../api/controlPanelApi';
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
state: 'enabled' | 'disabled' | 'error';
|
|
19
|
-
adapter: string | null;
|
|
20
|
-
error?: string;
|
|
21
|
-
missingVars?: string[];
|
|
22
|
-
}
|
|
17
|
+
type AuthStatus = AuthConfigStatus;
|
|
23
18
|
|
|
24
19
|
const adapterLabels: Record<string, string> = {
|
|
25
20
|
supertokens: 'SuperTokens',
|
|
@@ -36,7 +31,7 @@ export function AuthStatusWidget() {
|
|
|
36
31
|
useEffect(() => {
|
|
37
32
|
const fetchStatus = async () => {
|
|
38
33
|
try {
|
|
39
|
-
const data = await api.
|
|
34
|
+
const data = await api.getAuthConfigStatus();
|
|
40
35
|
setStatus(data);
|
|
41
36
|
} catch (err) {
|
|
42
37
|
setError(err instanceof Error ? err.message : 'Failed to fetch auth status');
|