@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/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
- /** Optional: Authentication configuration */
30
- auth?: {
31
- enabled: boolean;
32
- provider: 'basic' | 'jwt' | 'custom';
33
- users?: Array<{ username: string; password: string }>;
34
- jwtSecret?: string;
35
- customMiddleware?: RequestHandler;
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
+ }
@@ -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';