@superdangerous/app-framework 4.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/LICENSE +21 -0
- package/README.md +652 -0
- package/dist/api/logsRouter.d.ts +20 -0
- package/dist/api/logsRouter.d.ts.map +1 -0
- package/dist/api/logsRouter.js +515 -0
- package/dist/api/logsRouter.js.map +1 -0
- package/dist/cli/dev-server.d.ts +7 -0
- package/dist/cli/dev-server.d.ts.map +1 -0
- package/dist/cli/dev-server.js +640 -0
- package/dist/cli/dev-server.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +26 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/StandardServer.d.ts +129 -0
- package/dist/core/StandardServer.d.ts.map +1 -0
- package/dist/core/StandardServer.js +453 -0
- package/dist/core/StandardServer.js.map +1 -0
- package/dist/core/apiResponse.d.ts +69 -0
- package/dist/core/apiResponse.d.ts.map +1 -0
- package/dist/core/apiResponse.js +127 -0
- package/dist/core/apiResponse.js.map +1 -0
- package/dist/core/healthCheck.d.ts +160 -0
- package/dist/core/healthCheck.d.ts.map +1 -0
- package/dist/core/healthCheck.js +398 -0
- package/dist/core/healthCheck.js.map +1 -0
- package/dist/core/index.d.ts +40 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +40 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/logger.d.ts +117 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +826 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/portUtils.d.ts +71 -0
- package/dist/core/portUtils.d.ts.map +1 -0
- package/dist/core/portUtils.js +240 -0
- package/dist/core/portUtils.js.map +1 -0
- package/dist/core/storageService.d.ts +119 -0
- package/dist/core/storageService.d.ts.map +1 -0
- package/dist/core/storageService.js +405 -0
- package/dist/core/storageService.js.map +1 -0
- package/dist/desktop/bundler.d.ts +40 -0
- package/dist/desktop/bundler.d.ts.map +1 -0
- package/dist/desktop/bundler.js +176 -0
- package/dist/desktop/bundler.js.map +1 -0
- package/dist/desktop/index.d.ts +25 -0
- package/dist/desktop/index.d.ts.map +1 -0
- package/dist/desktop/index.js +15 -0
- package/dist/desktop/index.js.map +1 -0
- package/dist/desktop/native-modules.d.ts +66 -0
- package/dist/desktop/native-modules.d.ts.map +1 -0
- package/dist/desktop/native-modules.js +200 -0
- package/dist/desktop/native-modules.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/LogCategories.d.ts +87 -0
- package/dist/logging/LogCategories.d.ts.map +1 -0
- package/dist/logging/LogCategories.js +205 -0
- package/dist/logging/LogCategories.js.map +1 -0
- package/dist/middleware/aiErrorHandler.d.ts +31 -0
- package/dist/middleware/aiErrorHandler.d.ts.map +1 -0
- package/dist/middleware/aiErrorHandler.js +181 -0
- package/dist/middleware/aiErrorHandler.js.map +1 -0
- package/dist/middleware/auth.d.ts +101 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +230 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/cors.d.ts +56 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +123 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/errorHandler.d.ts +13 -0
- package/dist/middleware/errorHandler.d.ts.map +1 -0
- package/dist/middleware/errorHandler.js +85 -0
- package/dist/middleware/errorHandler.js.map +1 -0
- package/dist/middleware/fileUpload.d.ts +62 -0
- package/dist/middleware/fileUpload.d.ts.map +1 -0
- package/dist/middleware/fileUpload.js +175 -0
- package/dist/middleware/fileUpload.js.map +1 -0
- package/dist/middleware/health.d.ts +48 -0
- package/dist/middleware/health.d.ts.map +1 -0
- package/dist/middleware/health.js +143 -0
- package/dist/middleware/health.js.map +1 -0
- package/dist/middleware/index.d.ts +20 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +18 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/openapi.d.ts +64 -0
- package/dist/middleware/openapi.d.ts.map +1 -0
- package/dist/middleware/openapi.js +258 -0
- package/dist/middleware/openapi.js.map +1 -0
- package/dist/middleware/requestLogging.d.ts +22 -0
- package/dist/middleware/requestLogging.d.ts.map +1 -0
- package/dist/middleware/requestLogging.js +61 -0
- package/dist/middleware/requestLogging.js.map +1 -0
- package/dist/middleware/session.d.ts +84 -0
- package/dist/middleware/session.d.ts.map +1 -0
- package/dist/middleware/session.js +189 -0
- package/dist/middleware/session.js.map +1 -0
- package/dist/middleware/validation.d.ts +1337 -0
- package/dist/middleware/validation.d.ts.map +1 -0
- package/dist/middleware/validation.js +483 -0
- package/dist/middleware/validation.js.map +1 -0
- package/dist/services/aiService.d.ts +180 -0
- package/dist/services/aiService.d.ts.map +1 -0
- package/dist/services/aiService.js +547 -0
- package/dist/services/aiService.js.map +1 -0
- package/dist/services/conversationStorage.d.ts +38 -0
- package/dist/services/conversationStorage.d.ts.map +1 -0
- package/dist/services/conversationStorage.js +158 -0
- package/dist/services/conversationStorage.js.map +1 -0
- package/dist/services/crossPlatformBuffer.d.ts +84 -0
- package/dist/services/crossPlatformBuffer.d.ts.map +1 -0
- package/dist/services/crossPlatformBuffer.js +246 -0
- package/dist/services/crossPlatformBuffer.js.map +1 -0
- package/dist/services/index.d.ts +17 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +18 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/networkService.d.ts +81 -0
- package/dist/services/networkService.d.ts.map +1 -0
- package/dist/services/networkService.js +268 -0
- package/dist/services/networkService.js.map +1 -0
- package/dist/services/queueService.d.ts +112 -0
- package/dist/services/queueService.d.ts.map +1 -0
- package/dist/services/queueService.js +338 -0
- package/dist/services/queueService.js.map +1 -0
- package/dist/services/settingsService.d.ts +135 -0
- package/dist/services/settingsService.d.ts.map +1 -0
- package/dist/services/settingsService.js +425 -0
- package/dist/services/settingsService.js.map +1 -0
- package/dist/services/systemMonitor.d.ts +208 -0
- package/dist/services/systemMonitor.d.ts.map +1 -0
- package/dist/services/systemMonitor.js +693 -0
- package/dist/services/systemMonitor.js.map +1 -0
- package/dist/services/updateService.d.ts +78 -0
- package/dist/services/updateService.d.ts.map +1 -0
- package/dist/services/updateService.js +252 -0
- package/dist/services/updateService.js.map +1 -0
- package/dist/services/websocketEvents.d.ts +372 -0
- package/dist/services/websocketEvents.d.ts.map +1 -0
- package/dist/services/websocketEvents.js +338 -0
- package/dist/services/websocketEvents.js.map +1 -0
- package/dist/services/websocketServer.d.ts +80 -0
- package/dist/services/websocketServer.d.ts.map +1 -0
- package/dist/services/websocketServer.js +299 -0
- package/dist/services/websocketServer.js.map +1 -0
- package/dist/settings/SettingsSchema.d.ts +151 -0
- package/dist/settings/SettingsSchema.d.ts.map +1 -0
- package/dist/settings/SettingsSchema.js +424 -0
- package/dist/settings/SettingsSchema.js.map +1 -0
- package/dist/testing/TestServer.d.ts +69 -0
- package/dist/testing/TestServer.d.ts.map +1 -0
- package/dist/testing/TestServer.js +250 -0
- package/dist/testing/TestServer.js.map +1 -0
- package/dist/types/index.d.ts +137 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/appPaths.d.ts +74 -0
- package/dist/utils/appPaths.d.ts.map +1 -0
- package/dist/utils/appPaths.js +162 -0
- package/dist/utils/appPaths.js.map +1 -0
- package/dist/utils/fs-utils.d.ts +50 -0
- package/dist/utils/fs-utils.d.ts.map +1 -0
- package/dist/utils/fs-utils.js +114 -0
- package/dist/utils/fs-utils.js.map +1 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +10 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/standardConfig.d.ts +61 -0
- package/dist/utils/standardConfig.d.ts.map +1 -0
- package/dist/utils/standardConfig.js +109 -0
- package/dist/utils/standardConfig.js.map +1 -0
- package/dist/utils/startupBanner.d.ts +34 -0
- package/dist/utils/startupBanner.d.ts.map +1 -0
- package/dist/utils/startupBanner.js +169 -0
- package/dist/utils/startupBanner.js.map +1 -0
- package/dist/utils/startupLogger.d.ts +45 -0
- package/dist/utils/startupLogger.d.ts.map +1 -0
- package/dist/utils/startupLogger.js +200 -0
- package/dist/utils/startupLogger.js.map +1 -0
- package/package.json +151 -0
- package/src/api/logsRouter.ts +600 -0
- package/src/cli/dev-server.ts +803 -0
- package/src/cli/index.ts +31 -0
- package/src/core/StandardServer.ts +587 -0
- package/src/core/apiResponse.ts +202 -0
- package/src/core/healthCheck.ts +565 -0
- package/src/core/index.ts +80 -0
- package/src/core/logger.ts +1092 -0
- package/src/core/portUtils.ts +319 -0
- package/src/core/storageService.ts +595 -0
- package/src/desktop/bundler.ts +271 -0
- package/src/desktop/index.ts +18 -0
- package/src/desktop/native-modules.ts +289 -0
- package/src/index.ts +142 -0
- package/src/logging/LogCategories.ts +302 -0
- package/src/middleware/aiErrorHandler.ts +278 -0
- package/src/middleware/auth.ts +329 -0
- package/src/middleware/cors.ts +187 -0
- package/src/middleware/errorHandler.ts +103 -0
- package/src/middleware/fileUpload.ts +252 -0
- package/src/middleware/health.ts +206 -0
- package/src/middleware/index.ts +71 -0
- package/src/middleware/openapi.ts +305 -0
- package/src/middleware/requestLogging.ts +92 -0
- package/src/middleware/session.ts +238 -0
- package/src/middleware/validation.ts +603 -0
- package/src/services/aiService.ts +789 -0
- package/src/services/conversationStorage.ts +232 -0
- package/src/services/crossPlatformBuffer.ts +341 -0
- package/src/services/index.ts +47 -0
- package/src/services/networkService.ts +351 -0
- package/src/services/queueService.ts +446 -0
- package/src/services/settingsService.ts +549 -0
- package/src/services/systemMonitor.ts +936 -0
- package/src/services/updateService.ts +334 -0
- package/src/services/websocketEvents.ts +409 -0
- package/src/services/websocketServer.ts +394 -0
- package/src/settings/SettingsSchema.ts +664 -0
- package/src/testing/TestServer.ts +312 -0
- package/src/types/index.ts +154 -0
- package/src/utils/appPaths.ts +196 -0
- package/src/utils/fs-utils.ts +130 -0
- package/src/utils/index.ts +15 -0
- package/src/utils/standardConfig.ts +178 -0
- package/src/utils/startupBanner.ts +287 -0
- package/src/utils/startupLogger.ts +268 -0
- package/ui/dist/index.d.mts +1221 -0
- package/ui/dist/index.d.ts +1221 -0
- package/ui/dist/index.js +73 -0
- package/ui/dist/index.js.map +1 -0
- package/ui/dist/index.mjs +73 -0
- package/ui/dist/index.mjs.map +1 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Middleware
|
|
3
|
+
* Generic authentication utilities for Express applications
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Request, Response, NextFunction } from "express";
|
|
7
|
+
import { createLogger } from "../core/index.js";
|
|
8
|
+
|
|
9
|
+
let logger: any; // Will be initialized when needed
|
|
10
|
+
|
|
11
|
+
function ensureLogger() {
|
|
12
|
+
if (!logger) {
|
|
13
|
+
logger = createLogger("AuthMiddleware");
|
|
14
|
+
}
|
|
15
|
+
return logger;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Extend Express Request type to include user
|
|
19
|
+
declare global {
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
21
|
+
namespace Express {
|
|
22
|
+
interface Request {
|
|
23
|
+
user?: {
|
|
24
|
+
id: string;
|
|
25
|
+
username: string;
|
|
26
|
+
roles?: string[];
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Extend express-session types
|
|
33
|
+
declare module "express-session" {
|
|
34
|
+
interface SessionData {
|
|
35
|
+
authenticated?: boolean;
|
|
36
|
+
username?: string;
|
|
37
|
+
userId?: string;
|
|
38
|
+
roles?: string[];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface AuthConfig {
|
|
43
|
+
loginPath?: string;
|
|
44
|
+
apiPrefix?: string;
|
|
45
|
+
excludePaths?: string[];
|
|
46
|
+
onUnauthorized?: (req: Request, res: Response) => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates authentication middleware with configuration
|
|
51
|
+
*/
|
|
52
|
+
export function createAuthMiddleware(config: AuthConfig = {}) {
|
|
53
|
+
const {
|
|
54
|
+
loginPath = "/login",
|
|
55
|
+
apiPrefix = "/api/",
|
|
56
|
+
excludePaths = [],
|
|
57
|
+
onUnauthorized,
|
|
58
|
+
} = config;
|
|
59
|
+
|
|
60
|
+
return function requireAuth(
|
|
61
|
+
req: Request,
|
|
62
|
+
res: Response,
|
|
63
|
+
next: NextFunction,
|
|
64
|
+
): void {
|
|
65
|
+
// Check if path is excluded
|
|
66
|
+
if (excludePaths.some((path) => req.path.startsWith(path))) {
|
|
67
|
+
return next();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check if user is authenticated
|
|
71
|
+
if (req.session && req.session.authenticated) {
|
|
72
|
+
// Attach user to request
|
|
73
|
+
req.user = {
|
|
74
|
+
id: req.session.userId || "",
|
|
75
|
+
username: req.session.username || "",
|
|
76
|
+
roles: req.session.roles || [],
|
|
77
|
+
};
|
|
78
|
+
return next();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Handle unauthorized access
|
|
82
|
+
if (onUnauthorized) {
|
|
83
|
+
return onUnauthorized(req, res);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Default behavior
|
|
87
|
+
if (req.path.startsWith(apiPrefix)) {
|
|
88
|
+
// For API routes, return JSON error
|
|
89
|
+
res.status(401).json({
|
|
90
|
+
success: false,
|
|
91
|
+
error: "Authentication required",
|
|
92
|
+
message: "Please log in to access this resource",
|
|
93
|
+
});
|
|
94
|
+
} else {
|
|
95
|
+
// For web routes, redirect to login
|
|
96
|
+
res.redirect(loginPath);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Login handler factory
|
|
103
|
+
*/
|
|
104
|
+
export interface LoginCredentials {
|
|
105
|
+
username: string;
|
|
106
|
+
password: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface AuthService {
|
|
110
|
+
validateCredentials: (credentials: LoginCredentials) => Promise<{
|
|
111
|
+
valid: boolean;
|
|
112
|
+
user?: {
|
|
113
|
+
id: string;
|
|
114
|
+
username: string;
|
|
115
|
+
roles?: string[];
|
|
116
|
+
};
|
|
117
|
+
error?: string;
|
|
118
|
+
}>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function createLoginHandler(authService: AuthService) {
|
|
122
|
+
return async (req: Request, res: Response): Promise<void> => {
|
|
123
|
+
try {
|
|
124
|
+
const { username, password } = req.body;
|
|
125
|
+
|
|
126
|
+
if (!username || !password) {
|
|
127
|
+
res.status(400).json({
|
|
128
|
+
success: false,
|
|
129
|
+
error: "Missing credentials",
|
|
130
|
+
message: "Username and password are required",
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const result = await authService.validateCredentials({
|
|
136
|
+
username,
|
|
137
|
+
password,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (result.valid && result.user) {
|
|
141
|
+
// Set session
|
|
142
|
+
req.session.authenticated = true;
|
|
143
|
+
req.session.username = result.user.username;
|
|
144
|
+
req.session.userId = result.user.id;
|
|
145
|
+
req.session.roles = result.user.roles;
|
|
146
|
+
|
|
147
|
+
ensureLogger().info(`User logged in: ${username}`);
|
|
148
|
+
|
|
149
|
+
res.json({
|
|
150
|
+
success: true,
|
|
151
|
+
user: {
|
|
152
|
+
username: result.user.username,
|
|
153
|
+
roles: result.user.roles,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
} else {
|
|
157
|
+
ensureLogger().warn(`Failed login attempt for user: ${username}`);
|
|
158
|
+
res.status(401).json({
|
|
159
|
+
success: false,
|
|
160
|
+
error: "Invalid credentials",
|
|
161
|
+
message: result.error || "Invalid username or password",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
} catch (_error: any) {
|
|
165
|
+
ensureLogger().error("Login error:", _error);
|
|
166
|
+
res.status(500).json({
|
|
167
|
+
success: false,
|
|
168
|
+
error: "Login failed",
|
|
169
|
+
message: "An error occurred during login",
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Logout handler factory
|
|
177
|
+
*/
|
|
178
|
+
export function createLogoutHandler() {
|
|
179
|
+
return (req: Request, res: Response): void => {
|
|
180
|
+
const username = req.session?.username;
|
|
181
|
+
|
|
182
|
+
req.session.destroy((err: any) => {
|
|
183
|
+
if (err) {
|
|
184
|
+
ensureLogger().error("Error destroying session:", err);
|
|
185
|
+
res.status(500).json({
|
|
186
|
+
success: false,
|
|
187
|
+
error: "Logout failed",
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (username) {
|
|
193
|
+
ensureLogger().info(`User logged out: ${username}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
res.json({ success: true });
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Auth check handler
|
|
203
|
+
*/
|
|
204
|
+
export function createAuthCheckHandler() {
|
|
205
|
+
return (req: Request, res: Response): void => {
|
|
206
|
+
if (req.session?.authenticated) {
|
|
207
|
+
res.json({
|
|
208
|
+
authenticated: true,
|
|
209
|
+
user: {
|
|
210
|
+
username: req.session.username,
|
|
211
|
+
roles: req.session.roles,
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
} else {
|
|
215
|
+
res.status(401).json({
|
|
216
|
+
authenticated: false,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Role-based access control middleware
|
|
224
|
+
*/
|
|
225
|
+
export function requireRole(roles: string | string[]) {
|
|
226
|
+
const requiredRoles = Array.isArray(roles) ? roles : [roles];
|
|
227
|
+
|
|
228
|
+
return (req: Request, res: Response, next: NextFunction): void => {
|
|
229
|
+
if (!req.user) {
|
|
230
|
+
res.status(401).json({
|
|
231
|
+
success: false,
|
|
232
|
+
error: "Authentication required",
|
|
233
|
+
});
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const userRoles = req.user.roles || [];
|
|
238
|
+
const hasRole = requiredRoles.some((role) => userRoles.includes(role));
|
|
239
|
+
|
|
240
|
+
if (!hasRole) {
|
|
241
|
+
res.status(403).json({
|
|
242
|
+
success: false,
|
|
243
|
+
error: "Insufficient permissions",
|
|
244
|
+
message: `This action requires one of the following roles: ${requiredRoles.join(", ")}`,
|
|
245
|
+
});
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
next();
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Simple in-memory auth service for development
|
|
255
|
+
*/
|
|
256
|
+
export class SimpleAuthService implements AuthService {
|
|
257
|
+
private users: Map<
|
|
258
|
+
string,
|
|
259
|
+
{ password: string; id: string; roles?: string[] }
|
|
260
|
+
>;
|
|
261
|
+
|
|
262
|
+
constructor(
|
|
263
|
+
users: Array<{
|
|
264
|
+
username: string;
|
|
265
|
+
password: string;
|
|
266
|
+
id?: string;
|
|
267
|
+
roles?: string[];
|
|
268
|
+
}> = [],
|
|
269
|
+
) {
|
|
270
|
+
this.users = new Map();
|
|
271
|
+
|
|
272
|
+
// Add default admin user if no users provided
|
|
273
|
+
if (users.length === 0) {
|
|
274
|
+
users = [
|
|
275
|
+
{
|
|
276
|
+
username: "admin",
|
|
277
|
+
password: "admin",
|
|
278
|
+
id: "admin-1",
|
|
279
|
+
roles: ["admin"],
|
|
280
|
+
},
|
|
281
|
+
];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
users.forEach((user) => {
|
|
285
|
+
this.users.set(user.username, {
|
|
286
|
+
password: user.password,
|
|
287
|
+
id: user.id || `user-${user.username}`,
|
|
288
|
+
roles: user.roles,
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async validateCredentials(credentials: LoginCredentials) {
|
|
294
|
+
const user = this.users.get(credentials.username);
|
|
295
|
+
|
|
296
|
+
if (!user || user.password !== credentials.password) {
|
|
297
|
+
return {
|
|
298
|
+
valid: false,
|
|
299
|
+
error: "Invalid username or password",
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
valid: true,
|
|
305
|
+
user: {
|
|
306
|
+
id: user.id,
|
|
307
|
+
username: credentials.username,
|
|
308
|
+
roles: user.roles,
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
addUser(username: string, password: string, roles?: string[]) {
|
|
314
|
+
this.users.set(username, {
|
|
315
|
+
password,
|
|
316
|
+
id: `user-${username}`,
|
|
317
|
+
roles,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export default {
|
|
323
|
+
createAuthMiddleware,
|
|
324
|
+
createLoginHandler,
|
|
325
|
+
createLogoutHandler,
|
|
326
|
+
createAuthCheckHandler,
|
|
327
|
+
requireRole,
|
|
328
|
+
SimpleAuthService,
|
|
329
|
+
};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic CORS Middleware
|
|
3
|
+
* Intelligently handles CORS based on environment and configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import cors from "cors";
|
|
7
|
+
import { createLogger } from "../core/logger.js";
|
|
8
|
+
|
|
9
|
+
const logger = createLogger("CORS");
|
|
10
|
+
|
|
11
|
+
export interface DynamicCorsOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Explicitly allowed origins (optional)
|
|
14
|
+
*/
|
|
15
|
+
allowedOrigins?: string[];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Port ranges to automatically allow in development
|
|
19
|
+
*/
|
|
20
|
+
devPortRanges?: Array<{ start: number; end: number }>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Common development ports to always allow
|
|
24
|
+
*/
|
|
25
|
+
commonDevPorts?: number[];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Whether to allow credentials
|
|
29
|
+
*/
|
|
30
|
+
credentials?: boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Custom origin validator function
|
|
34
|
+
*/
|
|
35
|
+
customValidator?: (origin: string) => boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates dynamic CORS middleware that intelligently handles origins
|
|
40
|
+
*/
|
|
41
|
+
export function createDynamicCors(options: DynamicCorsOptions = {}) {
|
|
42
|
+
const {
|
|
43
|
+
allowedOrigins = [],
|
|
44
|
+
devPortRanges = [
|
|
45
|
+
{ start: 3000, end: 3010 },
|
|
46
|
+
{ start: 5170, end: 5180 },
|
|
47
|
+
{ start: 8080, end: 8090 },
|
|
48
|
+
],
|
|
49
|
+
commonDevPorts = [3000, 3001, 5173, 5174, 5175, 8080, 8081],
|
|
50
|
+
credentials = true,
|
|
51
|
+
customValidator,
|
|
52
|
+
} = options;
|
|
53
|
+
|
|
54
|
+
// Build allowed origins list for development
|
|
55
|
+
const buildDevOrigins = (): string[] => {
|
|
56
|
+
const origins = new Set<string>();
|
|
57
|
+
|
|
58
|
+
// Add explicitly allowed origins
|
|
59
|
+
allowedOrigins.forEach((origin) => origins.add(origin));
|
|
60
|
+
|
|
61
|
+
// Add common development ports
|
|
62
|
+
commonDevPorts.forEach((port) => {
|
|
63
|
+
origins.add(`http://localhost:${port}`);
|
|
64
|
+
origins.add(`http://127.0.0.1:${port}`);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Add port ranges
|
|
68
|
+
devPortRanges.forEach((range) => {
|
|
69
|
+
for (let port = range.start; port <= range.end; port++) {
|
|
70
|
+
origins.add(`http://localhost:${port}`);
|
|
71
|
+
origins.add(`http://127.0.0.1:${port}`);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return Array.from(origins);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// CORS origin function
|
|
79
|
+
const originFunction = (
|
|
80
|
+
origin: string | undefined,
|
|
81
|
+
callback: (err: Error | null, allow?: boolean | string) => void,
|
|
82
|
+
) => {
|
|
83
|
+
// Allow requests with no origin (same-origin, Postman, etc.)
|
|
84
|
+
if (!origin) {
|
|
85
|
+
return callback(null, true);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Production mode
|
|
89
|
+
if (process.env.NODE_ENV === "production") {
|
|
90
|
+
// Check against explicit allowed origins
|
|
91
|
+
if (allowedOrigins.length > 0 && allowedOrigins.includes(origin)) {
|
|
92
|
+
return callback(null, true);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check custom validator
|
|
96
|
+
if (customValidator && customValidator(origin)) {
|
|
97
|
+
return callback(null, true);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check if origin matches the app's domain
|
|
101
|
+
const appDomain = process.env.APP_DOMAIN || process.env.PRODUCTION_URL;
|
|
102
|
+
if (appDomain && origin.includes(appDomain)) {
|
|
103
|
+
return callback(null, true);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Reject by default in production
|
|
107
|
+
logger.warn(`CORS rejected origin in production: ${origin}`);
|
|
108
|
+
return callback(new Error("Not allowed by CORS"));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Development mode - be more permissive
|
|
112
|
+
const devOrigins = buildDevOrigins();
|
|
113
|
+
|
|
114
|
+
// Check if origin matches any development origin
|
|
115
|
+
if (devOrigins.some((allowed) => origin.startsWith(allowed))) {
|
|
116
|
+
return callback(null, true);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Check if it's any localhost/127.0.0.1 origin
|
|
120
|
+
if (origin.includes("localhost") || origin.includes("127.0.0.1")) {
|
|
121
|
+
logger.debug(`Allowing localhost origin: ${origin}`);
|
|
122
|
+
return callback(null, true);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check custom validator
|
|
126
|
+
if (customValidator && customValidator(origin)) {
|
|
127
|
+
return callback(null, true);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Log and allow in development (with warning)
|
|
131
|
+
logger.warn(`CORS: Allowing unrecognized origin in development: ${origin}`);
|
|
132
|
+
return callback(null, true);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Create and return configured CORS middleware
|
|
136
|
+
const corsMiddleware = cors({
|
|
137
|
+
origin: originFunction,
|
|
138
|
+
credentials,
|
|
139
|
+
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
140
|
+
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
|
|
141
|
+
exposedHeaders: ["X-Total-Count", "X-Page-Count"],
|
|
142
|
+
maxAge: 86400, // 24 hours
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Attach the origin function for testing purposes
|
|
146
|
+
(corsMiddleware as any)._originFunction = originFunction;
|
|
147
|
+
|
|
148
|
+
return corsMiddleware;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Simple CORS middleware for production with specific domains
|
|
153
|
+
*/
|
|
154
|
+
export function createProductionCors(allowedDomains: string[]) {
|
|
155
|
+
const originFunction = (
|
|
156
|
+
origin: string | undefined,
|
|
157
|
+
callback: (err: Error | null, allow?: boolean) => void,
|
|
158
|
+
) => {
|
|
159
|
+
if (!origin || allowedDomains.includes(origin)) {
|
|
160
|
+
callback(null, true);
|
|
161
|
+
} else {
|
|
162
|
+
callback(new Error("Not allowed by CORS"));
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const corsMiddleware = cors({
|
|
167
|
+
origin: originFunction,
|
|
168
|
+
credentials: true,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Attach for testing
|
|
172
|
+
(corsMiddleware as any)._originFunction = originFunction;
|
|
173
|
+
|
|
174
|
+
return corsMiddleware;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Permissive CORS for development
|
|
179
|
+
*/
|
|
180
|
+
export function createDevCors() {
|
|
181
|
+
return createDynamicCors({
|
|
182
|
+
allowedOrigins: [],
|
|
183
|
+
credentials: true,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export default createDynamicCors;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handling Middleware
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Request, Response, NextFunction } from "express";
|
|
6
|
+
import { createLogger } from "../core/index.js";
|
|
7
|
+
|
|
8
|
+
const logger = createLogger("ErrorHandler");
|
|
9
|
+
|
|
10
|
+
// Custom error class
|
|
11
|
+
export class AppError extends Error {
|
|
12
|
+
public readonly statusCode: number;
|
|
13
|
+
public readonly isOperational: boolean;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
message: string,
|
|
17
|
+
statusCode: number = 500,
|
|
18
|
+
isOperational: boolean = true,
|
|
19
|
+
) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.statusCode = statusCode;
|
|
22
|
+
this.isOperational = isOperational;
|
|
23
|
+
|
|
24
|
+
Error.captureStackTrace(this, this.constructor);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 404 Not Found handler
|
|
29
|
+
export function notFoundHandler(req: Request, res: Response) {
|
|
30
|
+
res.status(404).json({
|
|
31
|
+
success: false,
|
|
32
|
+
error: "Not Found",
|
|
33
|
+
message: `Cannot ${req.method} ${req.path}`,
|
|
34
|
+
path: req.path,
|
|
35
|
+
method: req.method,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Global error handler
|
|
40
|
+
export function errorHandler(
|
|
41
|
+
err: any,
|
|
42
|
+
req: Request,
|
|
43
|
+
res: Response,
|
|
44
|
+
_next: NextFunction,
|
|
45
|
+
) {
|
|
46
|
+
// Default to 500 server error
|
|
47
|
+
let statusCode = err.statusCode || 500;
|
|
48
|
+
let message = err.message || "Internal Server Error";
|
|
49
|
+
|
|
50
|
+
// Log the error
|
|
51
|
+
if (statusCode >= 500) {
|
|
52
|
+
logger.error("Server error", {
|
|
53
|
+
error: err,
|
|
54
|
+
request: {
|
|
55
|
+
method: req.method,
|
|
56
|
+
path: req.path,
|
|
57
|
+
query: req.query,
|
|
58
|
+
body: req.body,
|
|
59
|
+
headers: req.headers,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
logger.warn("Client error", {
|
|
64
|
+
statusCode,
|
|
65
|
+
message,
|
|
66
|
+
path: req.path,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Handle specific error types
|
|
71
|
+
if (err.name === "ValidationError") {
|
|
72
|
+
statusCode = 400;
|
|
73
|
+
message = "Validation failed";
|
|
74
|
+
} else if (err.name === "UnauthorizedError") {
|
|
75
|
+
statusCode = 401;
|
|
76
|
+
message = "Unauthorized";
|
|
77
|
+
} else if (err.name === "JsonWebTokenError") {
|
|
78
|
+
statusCode = 401;
|
|
79
|
+
message = "Invalid token";
|
|
80
|
+
} else if (err.name === "TokenExpiredError") {
|
|
81
|
+
statusCode = 401;
|
|
82
|
+
message = "Token expired";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Send error response
|
|
86
|
+
res.status(statusCode).json({
|
|
87
|
+
success: false,
|
|
88
|
+
error: message,
|
|
89
|
+
...(process.env.NODE_ENV === "development" && {
|
|
90
|
+
stack: err.stack,
|
|
91
|
+
details: err,
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Async handler wrapper to catch errors in async routes
|
|
97
|
+
export function asyncHandler(
|
|
98
|
+
fn: (req: Request, res: Response, next: NextFunction) => any,
|
|
99
|
+
) {
|
|
100
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
101
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
102
|
+
};
|
|
103
|
+
}
|