@qwickapps/server 1.0.0 → 1.1.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/README.md +178 -79
- package/dist/core/control-panel.d.ts.map +1 -1
- package/dist/core/control-panel.js +37 -42
- package/dist/core/control-panel.js.map +1 -1
- package/dist/core/gateway.d.ts +32 -13
- package/dist/core/gateway.d.ts.map +1 -1
- package/dist/core/gateway.js +144 -81
- package/dist/core/gateway.js.map +1 -1
- package/dist/core/guards.d.ts +22 -0
- package/dist/core/guards.d.ts.map +1 -0
- package/dist/core/guards.js +167 -0
- package/dist/core/guards.js.map +1 -0
- package/dist/core/types.d.ts +104 -11
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/frontend-app-plugin.d.ts +39 -0
- package/dist/plugins/frontend-app-plugin.d.ts.map +1 -0
- package/dist/plugins/frontend-app-plugin.js +176 -0
- package/dist/plugins/frontend-app-plugin.js.map +1 -0
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +1 -0
- package/dist/plugins/index.js.map +1 -1
- package/package.json +7 -2
- package/src/core/control-panel.ts +41 -50
- package/src/core/gateway.ts +186 -105
- package/src/core/guards.ts +190 -0
- package/src/core/types.ts +115 -9
- package/src/index.ts +18 -0
- package/src/plugins/frontend-app-plugin.ts +211 -0
- package/src/plugins/index.ts +3 -0
package/src/core/types.ts
CHANGED
|
@@ -4,7 +4,110 @@
|
|
|
4
4
|
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { Application, RequestHandler, Router } from 'express';
|
|
7
|
+
import type { Application, RequestHandler, Router, Request, Response, NextFunction } from 'express';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Route guard types for protecting routes
|
|
11
|
+
*/
|
|
12
|
+
export type RouteGuardType = 'none' | 'basic' | 'supabase' | 'auth0';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Basic auth guard configuration
|
|
16
|
+
*/
|
|
17
|
+
export interface BasicAuthGuardConfig {
|
|
18
|
+
type: 'basic';
|
|
19
|
+
/** Username for basic auth */
|
|
20
|
+
username: string;
|
|
21
|
+
/** Password for basic auth */
|
|
22
|
+
password: string;
|
|
23
|
+
/** Realm name for the WWW-Authenticate header */
|
|
24
|
+
realm?: string;
|
|
25
|
+
/** Paths to exclude from authentication (e.g., ['/health']) */
|
|
26
|
+
excludePaths?: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Supabase auth guard configuration
|
|
31
|
+
*/
|
|
32
|
+
export interface SupabaseAuthGuardConfig {
|
|
33
|
+
type: 'supabase';
|
|
34
|
+
/** Supabase project URL */
|
|
35
|
+
supabaseUrl: string;
|
|
36
|
+
/** Supabase anon key */
|
|
37
|
+
supabaseAnonKey: string;
|
|
38
|
+
/** Paths to exclude from authentication */
|
|
39
|
+
excludePaths?: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Auth0 guard configuration
|
|
44
|
+
*/
|
|
45
|
+
export interface Auth0GuardConfig {
|
|
46
|
+
type: 'auth0';
|
|
47
|
+
/** Auth0 domain (e.g., 'myapp.auth0.com') */
|
|
48
|
+
domain: string;
|
|
49
|
+
/** Auth0 client ID */
|
|
50
|
+
clientId: string;
|
|
51
|
+
/** Auth0 client secret */
|
|
52
|
+
clientSecret: string;
|
|
53
|
+
/** Base URL of the application */
|
|
54
|
+
baseUrl: string;
|
|
55
|
+
/** Session secret for cookie encryption */
|
|
56
|
+
secret: string;
|
|
57
|
+
/** Auth routes configuration */
|
|
58
|
+
routes?: {
|
|
59
|
+
login?: string;
|
|
60
|
+
logout?: string;
|
|
61
|
+
callback?: string;
|
|
62
|
+
};
|
|
63
|
+
/** Paths to exclude from authentication */
|
|
64
|
+
excludePaths?: string[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* No authentication guard
|
|
69
|
+
*/
|
|
70
|
+
export interface NoAuthGuardConfig {
|
|
71
|
+
type: 'none';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Union type for all guard configurations
|
|
76
|
+
*/
|
|
77
|
+
export type RouteGuardConfig =
|
|
78
|
+
| NoAuthGuardConfig
|
|
79
|
+
| BasicAuthGuardConfig
|
|
80
|
+
| SupabaseAuthGuardConfig
|
|
81
|
+
| Auth0GuardConfig;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Mount path configuration for applications
|
|
85
|
+
*/
|
|
86
|
+
export interface MountConfig {
|
|
87
|
+
/** Path where this app is mounted (e.g., '/', '/cpanel', '/app') */
|
|
88
|
+
path: string;
|
|
89
|
+
/** Route guard configuration for this mount point */
|
|
90
|
+
guard?: RouteGuardConfig;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Frontend app configuration
|
|
95
|
+
*/
|
|
96
|
+
export interface FrontendAppConfig {
|
|
97
|
+
/** Mount configuration */
|
|
98
|
+
mount: MountConfig;
|
|
99
|
+
/** Redirect to another URL instead of serving content */
|
|
100
|
+
redirectUrl?: string;
|
|
101
|
+
/** Path to static files to serve */
|
|
102
|
+
staticPath?: string;
|
|
103
|
+
/** Landing page HTML (used if no staticPath or redirectUrl) */
|
|
104
|
+
landingPage?: {
|
|
105
|
+
title: string;
|
|
106
|
+
heading?: string;
|
|
107
|
+
description?: string;
|
|
108
|
+
links?: Array<{ label: string; url: string }>;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
8
111
|
|
|
9
112
|
/**
|
|
10
113
|
* Control Panel Configuration
|
|
@@ -26,14 +129,17 @@ export interface ControlPanelConfig {
|
|
|
26
129
|
favicon?: string;
|
|
27
130
|
};
|
|
28
131
|
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Mount path for the control panel.
|
|
134
|
+
* Defaults to '/cpanel'.
|
|
135
|
+
*/
|
|
136
|
+
mountPath?: string;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Route guard for the control panel.
|
|
140
|
+
* Defaults to basic auth in production.
|
|
141
|
+
*/
|
|
142
|
+
guard?: RouteGuardConfig;
|
|
37
143
|
|
|
38
144
|
/** Optional: CORS configuration */
|
|
39
145
|
cors?: {
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,13 @@ export { createControlPanel } from './core/control-panel.js';
|
|
|
12
12
|
export { createGateway } from './core/gateway.js';
|
|
13
13
|
export { HealthManager } from './core/health-manager.js';
|
|
14
14
|
|
|
15
|
+
// Guards exports
|
|
16
|
+
export {
|
|
17
|
+
createRouteGuard,
|
|
18
|
+
isAuthenticated,
|
|
19
|
+
getAuthenticatedUser,
|
|
20
|
+
} from './core/guards.js';
|
|
21
|
+
|
|
15
22
|
// Logging exports
|
|
16
23
|
export {
|
|
17
24
|
initializeLogging,
|
|
@@ -33,6 +40,15 @@ export type {
|
|
|
33
40
|
ConfigDisplayOptions,
|
|
34
41
|
Logger,
|
|
35
42
|
DiagnosticsReport,
|
|
43
|
+
// New mount path and guard types
|
|
44
|
+
RouteGuardType,
|
|
45
|
+
RouteGuardConfig,
|
|
46
|
+
BasicAuthGuardConfig,
|
|
47
|
+
SupabaseAuthGuardConfig,
|
|
48
|
+
Auth0GuardConfig,
|
|
49
|
+
NoAuthGuardConfig,
|
|
50
|
+
MountConfig,
|
|
51
|
+
FrontendAppConfig,
|
|
36
52
|
} from './core/types.js';
|
|
37
53
|
export type {
|
|
38
54
|
GatewayConfig,
|
|
@@ -46,10 +62,12 @@ export {
|
|
|
46
62
|
createLogsPlugin,
|
|
47
63
|
createConfigPlugin,
|
|
48
64
|
createDiagnosticsPlugin,
|
|
65
|
+
createFrontendAppPlugin,
|
|
49
66
|
} from './plugins/index.js';
|
|
50
67
|
export type {
|
|
51
68
|
HealthPluginConfig,
|
|
52
69
|
LogsPluginConfig,
|
|
53
70
|
ConfigPluginConfig,
|
|
54
71
|
DiagnosticsPluginConfig,
|
|
72
|
+
FrontendAppPluginConfig,
|
|
55
73
|
} from './plugins/index.js';
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend App Plugin
|
|
3
|
+
*
|
|
4
|
+
* Plugin for serving a frontend application at the root path (/).
|
|
5
|
+
* Supports:
|
|
6
|
+
* - Redirect to another URL
|
|
7
|
+
* - Serve static files
|
|
8
|
+
* - Display a landing page
|
|
9
|
+
*
|
|
10
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import express from 'express';
|
|
14
|
+
import { existsSync } from 'node:fs';
|
|
15
|
+
import { resolve } from 'node:path';
|
|
16
|
+
import type { ControlPanelPlugin, FrontendAppConfig } from '../core/types.js';
|
|
17
|
+
import { createRouteGuard } from '../core/guards.js';
|
|
18
|
+
|
|
19
|
+
export interface FrontendAppPluginConfig {
|
|
20
|
+
/** Redirect to another URL */
|
|
21
|
+
redirectUrl?: string;
|
|
22
|
+
/** Path to static files to serve */
|
|
23
|
+
staticPath?: string;
|
|
24
|
+
/** Landing page configuration */
|
|
25
|
+
landingPage?: {
|
|
26
|
+
title: string;
|
|
27
|
+
heading?: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
links?: Array<{ label: string; url: string }>;
|
|
30
|
+
branding?: {
|
|
31
|
+
logo?: string;
|
|
32
|
+
primaryColor?: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
/** Route guard configuration */
|
|
36
|
+
guard?: FrontendAppConfig['mount']['guard'];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create a frontend app plugin that handles the root path
|
|
41
|
+
*/
|
|
42
|
+
export function createFrontendAppPlugin(config: FrontendAppPluginConfig): ControlPanelPlugin {
|
|
43
|
+
return {
|
|
44
|
+
name: 'frontend-app',
|
|
45
|
+
order: 0, // Run first to capture root path
|
|
46
|
+
|
|
47
|
+
async onInit(context) {
|
|
48
|
+
const { app, logger } = context;
|
|
49
|
+
|
|
50
|
+
// Apply guard if configured
|
|
51
|
+
if (config.guard && config.guard.type !== 'none') {
|
|
52
|
+
const guardMiddleware = createRouteGuard(config.guard);
|
|
53
|
+
// Apply guard only to root path
|
|
54
|
+
app.use('/', (req, res, next) => {
|
|
55
|
+
// Skip if not at root
|
|
56
|
+
if (req.path !== '/' && !req.path.startsWith('/?')) {
|
|
57
|
+
return next();
|
|
58
|
+
}
|
|
59
|
+
guardMiddleware(req, res, next);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Priority 1: Redirect
|
|
64
|
+
if (config.redirectUrl) {
|
|
65
|
+
logger.info(`Frontend app: Redirecting / to ${config.redirectUrl}`);
|
|
66
|
+
app.get('/', (_req, res) => {
|
|
67
|
+
res.redirect(config.redirectUrl!);
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Priority 2: Serve static files
|
|
73
|
+
if (config.staticPath && existsSync(config.staticPath)) {
|
|
74
|
+
logger.info(`Frontend app: Serving static files from ${config.staticPath}`);
|
|
75
|
+
app.use('/', express.static(config.staticPath));
|
|
76
|
+
|
|
77
|
+
// SPA fallback
|
|
78
|
+
app.get('/', (_req, res) => {
|
|
79
|
+
res.sendFile(resolve(config.staticPath!, 'index.html'));
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Priority 3: Landing page
|
|
85
|
+
if (config.landingPage) {
|
|
86
|
+
logger.info(`Frontend app: Serving landing page`);
|
|
87
|
+
app.get('/', (_req, res) => {
|
|
88
|
+
const html = generateLandingPageHtml(config.landingPage!);
|
|
89
|
+
res.type('html').send(html);
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Default: Simple welcome page
|
|
95
|
+
logger.info(`Frontend app: Serving default welcome page`);
|
|
96
|
+
app.get('/', (_req, res) => {
|
|
97
|
+
const html = generateLandingPageHtml({
|
|
98
|
+
title: context.config.productName,
|
|
99
|
+
heading: `Welcome to ${context.config.productName}`,
|
|
100
|
+
description: 'Your application is running.',
|
|
101
|
+
links: [
|
|
102
|
+
{ label: 'Control Panel', url: context.config.mountPath || '/cpanel' },
|
|
103
|
+
],
|
|
104
|
+
});
|
|
105
|
+
res.type('html').send(html);
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Generate landing page HTML
|
|
113
|
+
*/
|
|
114
|
+
function generateLandingPageHtml(config: NonNullable<FrontendAppPluginConfig['landingPage']>): string {
|
|
115
|
+
const primaryColor = config.branding?.primaryColor || '#6366f1';
|
|
116
|
+
|
|
117
|
+
const linksHtml = (config.links || [])
|
|
118
|
+
.map(
|
|
119
|
+
(link) =>
|
|
120
|
+
`<a href="${link.url}" class="link">${link.label}</a>`
|
|
121
|
+
)
|
|
122
|
+
.join('');
|
|
123
|
+
|
|
124
|
+
return `<!DOCTYPE html>
|
|
125
|
+
<html lang="en">
|
|
126
|
+
<head>
|
|
127
|
+
<meta charset="UTF-8">
|
|
128
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
129
|
+
<title>${config.title}</title>
|
|
130
|
+
<style>
|
|
131
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
132
|
+
body {
|
|
133
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
134
|
+
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
|
|
135
|
+
color: #e2e8f0;
|
|
136
|
+
min-height: 100vh;
|
|
137
|
+
display: flex;
|
|
138
|
+
align-items: center;
|
|
139
|
+
justify-content: center;
|
|
140
|
+
}
|
|
141
|
+
.container {
|
|
142
|
+
text-align: center;
|
|
143
|
+
max-width: 600px;
|
|
144
|
+
padding: 2rem;
|
|
145
|
+
}
|
|
146
|
+
${config.branding?.logo ? `
|
|
147
|
+
.logo {
|
|
148
|
+
width: 80px;
|
|
149
|
+
height: 80px;
|
|
150
|
+
margin-bottom: 1.5rem;
|
|
151
|
+
}
|
|
152
|
+
` : ''}
|
|
153
|
+
h1 {
|
|
154
|
+
font-size: 2.5rem;
|
|
155
|
+
color: ${primaryColor};
|
|
156
|
+
margin-bottom: 1rem;
|
|
157
|
+
}
|
|
158
|
+
p {
|
|
159
|
+
font-size: 1.125rem;
|
|
160
|
+
color: #94a3b8;
|
|
161
|
+
margin-bottom: 2rem;
|
|
162
|
+
line-height: 1.6;
|
|
163
|
+
}
|
|
164
|
+
.links {
|
|
165
|
+
display: flex;
|
|
166
|
+
flex-wrap: wrap;
|
|
167
|
+
gap: 1rem;
|
|
168
|
+
justify-content: center;
|
|
169
|
+
}
|
|
170
|
+
.link {
|
|
171
|
+
display: inline-block;
|
|
172
|
+
padding: 0.875rem 2rem;
|
|
173
|
+
background: ${primaryColor};
|
|
174
|
+
color: white;
|
|
175
|
+
text-decoration: none;
|
|
176
|
+
border-radius: 0.5rem;
|
|
177
|
+
font-weight: 500;
|
|
178
|
+
transition: all 0.2s;
|
|
179
|
+
}
|
|
180
|
+
.link:hover {
|
|
181
|
+
transform: translateY(-2px);
|
|
182
|
+
box-shadow: 0 10px 20px rgba(0,0,0,0.3);
|
|
183
|
+
}
|
|
184
|
+
.footer {
|
|
185
|
+
position: fixed;
|
|
186
|
+
bottom: 1rem;
|
|
187
|
+
left: 0;
|
|
188
|
+
right: 0;
|
|
189
|
+
text-align: center;
|
|
190
|
+
color: #64748b;
|
|
191
|
+
font-size: 0.875rem;
|
|
192
|
+
}
|
|
193
|
+
.footer a {
|
|
194
|
+
color: ${primaryColor};
|
|
195
|
+
text-decoration: none;
|
|
196
|
+
}
|
|
197
|
+
</style>
|
|
198
|
+
</head>
|
|
199
|
+
<body>
|
|
200
|
+
<div class="container">
|
|
201
|
+
${config.branding?.logo ? `<img src="${config.branding.logo}" alt="Logo" class="logo">` : ''}
|
|
202
|
+
<h1>${config.heading || config.title}</h1>
|
|
203
|
+
${config.description ? `<p>${config.description}</p>` : ''}
|
|
204
|
+
${linksHtml ? `<div class="links">${linksHtml}</div>` : ''}
|
|
205
|
+
</div>
|
|
206
|
+
<div class="footer">
|
|
207
|
+
Powered by <a href="https://qwickapps.com" target="_blank">QwickApps</a>
|
|
208
|
+
</div>
|
|
209
|
+
</body>
|
|
210
|
+
</html>`;
|
|
211
|
+
}
|
package/src/plugins/index.ts
CHANGED
|
@@ -15,3 +15,6 @@ export type { ConfigPluginConfig } from './config-plugin.js';
|
|
|
15
15
|
|
|
16
16
|
export { createDiagnosticsPlugin } from './diagnostics-plugin.js';
|
|
17
17
|
export type { DiagnosticsPluginConfig } from './diagnostics-plugin.js';
|
|
18
|
+
|
|
19
|
+
export { createFrontendAppPlugin } from './frontend-app-plugin.js';
|
|
20
|
+
export type { FrontendAppPluginConfig } from './frontend-app-plugin.js';
|