@getstrata/bootstrap 0.2.23 → 0.2.26
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/dist/bootstrap/applicationRegistry.d.ts +1 -1
- package/dist/bootstrap/buildWebModuleRoutes.d.ts +9 -0
- package/dist/bootstrap/config.d.ts +1 -1
- package/dist/bootstrap/env.d.ts +1 -1
- package/dist/bootstrap/membershipService.d.ts +1 -1
- package/dist/bootstrap/public-api.d.ts +4 -2
- package/dist/bootstrap/routeRegistry.d.ts +1 -1
- package/dist/entries/applicationRegistry.js +1 -1
- package/dist/entries/buildModuleRoutes.js +4 -219
- package/dist/entries/buildWebModuleRoutes.js +435 -0
- package/dist/entries/config.js +1 -1
- package/dist/entries/context.js +3 -19
- package/dist/entries/createWebRoutes.js +100 -682
- package/dist/entries/dependencies.js +3 -19
- package/dist/entries/http/securedRouteModelBinding.js +0 -7
- package/dist/entries/httpKernel.js +1 -1
- package/dist/entries/membershipService.js +2 -392
- package/dist/entries/providers/view.js +1 -12
- package/dist/entries/providers.js +3 -19
- package/dist/entries/queue/defaultJobs.js +0 -3
- package/dist/entries/web/routing.js +1 -8
- package/dist/index.js +139 -373
- package/package.json +8 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../../src/bootstrap/createWebRoutes.ts
|
|
3
|
-
import { join as
|
|
3
|
+
import { join as join2 } from "path";
|
|
4
|
+
import { htmlResponse } from "@getstrata/core/view";
|
|
4
5
|
|
|
5
6
|
// ../../src/config/frontend.ts
|
|
6
7
|
function readFrontendMode() {
|
|
@@ -17,672 +18,13 @@ function isViewsEnabled() {
|
|
|
17
18
|
return readFrontendMode() === "server-htmx";
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
// ../../src/
|
|
21
|
-
|
|
22
|
-
return typeof value === "function";
|
|
23
|
-
}
|
|
24
|
-
function isMethodRouteMap(value) {
|
|
25
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
const entries = Object.entries(value);
|
|
29
|
-
return entries.length > 0 && entries.every(([, handler]) => isRouteHandler(handler));
|
|
30
|
-
}
|
|
31
|
-
function composeMiddleware(...middleware) {
|
|
32
|
-
return (handler) => {
|
|
33
|
-
return async (request) => {
|
|
34
|
-
let index = 0;
|
|
35
|
-
const dispatch = async () => {
|
|
36
|
-
if (index >= middleware.length) {
|
|
37
|
-
return await handler(request);
|
|
38
|
-
}
|
|
39
|
-
const current = middleware[index];
|
|
40
|
-
index += 1;
|
|
41
|
-
if (!current) {
|
|
42
|
-
return await handler(request);
|
|
43
|
-
}
|
|
44
|
-
return await current(request, dispatch);
|
|
45
|
-
};
|
|
46
|
-
return await dispatch();
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
function wrapRouteHandler(handler, middleware) {
|
|
51
|
-
if (isMethodRouteMap(handler)) {
|
|
52
|
-
const wrapped = {};
|
|
53
|
-
for (const [method, routeHandler] of Object.entries(handler)) {
|
|
54
|
-
wrapped[method] = composeMiddleware(...middleware)(routeHandler);
|
|
55
|
-
}
|
|
56
|
-
return wrapped;
|
|
57
|
-
}
|
|
58
|
-
if (isRouteHandler(handler)) {
|
|
59
|
-
return composeMiddleware(...middleware)(handler);
|
|
60
|
-
}
|
|
61
|
-
return handler;
|
|
62
|
-
}
|
|
63
|
-
function applyMiddlewareToRoutes(routes, middleware) {
|
|
64
|
-
const wrapped = {};
|
|
65
|
-
for (const [path, routeHandler] of Object.entries(routes)) {
|
|
66
|
-
wrapped[path] = wrapRouteHandler(routeHandler, middleware);
|
|
67
|
-
}
|
|
68
|
-
return wrapped;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ../../src/core/view/etaViewEngine.ts
|
|
72
|
-
import { join } from "path";
|
|
73
|
-
import { Eta } from "eta";
|
|
74
|
-
var DEFAULT_VIEWS_DIRECTORY = join(process.cwd(), "resources/views");
|
|
75
|
-
var DEFAULT_LAYOUT = "layouts/app.eta";
|
|
76
|
-
|
|
77
|
-
class EtaViewEngine {
|
|
78
|
-
eta;
|
|
79
|
-
resolveLayoutData;
|
|
80
|
-
constructor(viewsDirectory = DEFAULT_VIEWS_DIRECTORY, resolveLayoutData) {
|
|
81
|
-
this.eta = new Eta({
|
|
82
|
-
views: viewsDirectory,
|
|
83
|
-
autoTrim: false
|
|
84
|
-
});
|
|
85
|
-
this.resolveLayoutData = resolveLayoutData;
|
|
86
|
-
}
|
|
87
|
-
async render(name, data = {}, options = {}) {
|
|
88
|
-
const template = name.endsWith(".eta") ? name : `${name}.eta`;
|
|
89
|
-
const layoutData = this.resolveLayoutData ? await this.resolveLayoutData() : {};
|
|
90
|
-
const mergedData = { ...layoutData, ...data };
|
|
91
|
-
const body = await this.eta.renderAsync(template, mergedData);
|
|
92
|
-
const layout = options.layout ?? DEFAULT_LAYOUT;
|
|
93
|
-
if (layout === false) {
|
|
94
|
-
return body;
|
|
95
|
-
}
|
|
96
|
-
const layoutTemplate = layout.endsWith(".eta") ? layout : `${layout}.eta`;
|
|
97
|
-
return await this.eta.renderAsync(layoutTemplate, {
|
|
98
|
-
...mergedData,
|
|
99
|
-
body
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
// ../../src/core/view/htmlResponse.ts
|
|
104
|
-
function htmlResponse(html, init = {}) {
|
|
105
|
-
return new Response(html, {
|
|
106
|
-
status: init.status ?? 200,
|
|
107
|
-
statusText: init.statusText,
|
|
108
|
-
headers: {
|
|
109
|
-
"Content-Type": "text/html; charset=utf-8"
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
// ../../src/bootstrap/config.ts
|
|
114
|
-
import {
|
|
115
|
-
CORE_AUTH_TOKEN,
|
|
116
|
-
CORE_CACHE_TOKEN,
|
|
117
|
-
CORE_CONFIG_TOKEN,
|
|
118
|
-
CORE_EVENT_BUS_TOKEN,
|
|
119
|
-
CORE_POLICY_GATE_TOKEN,
|
|
120
|
-
CORE_QUEUE_TOKEN,
|
|
121
|
-
CORE_TOKEN_SERVICE_TOKEN
|
|
122
|
-
} from "@getstrata/core/contracts/serviceTokens.ts";
|
|
123
|
-
var APP_PORT_CONFIG_KEY = "app.port";
|
|
124
|
-
var CACHE_TTL_MS_CONFIG_KEY = "cache.ttlMs";
|
|
125
|
-
var CACHE_MAX_ENTRIES_CONFIG_KEY = "cache.maxEntries";
|
|
126
|
-
var CACHE_DRIVER_CONFIG_KEY = "cache.driver";
|
|
127
|
-
var REDIS_URL_CONFIG_KEY = "cache.redisUrl";
|
|
128
|
-
var DATABASE_URL_CONFIG_KEY = "database.url";
|
|
129
|
-
var AUTH_DEV_HEADERS_CONFIG_KEY = "auth.allowDevHeaders";
|
|
130
|
-
var DEFAULT_APP_PORT = 3000;
|
|
131
|
-
var DEFAULT_CACHE_TTL_MS = 3600000;
|
|
132
|
-
var DEFAULT_CACHE_MAX_ENTRIES = 100;
|
|
133
|
-
var DEFAULT_CACHE_DRIVER = "array";
|
|
134
|
-
var DEFAULT_API_TOKEN = "";
|
|
135
|
-
var DEFAULT_QUEUE_DRIVER = "sync";
|
|
136
|
-
|
|
137
|
-
// ../../src/modules/user/provider.ts
|
|
138
|
-
import { OidcProvider } from "@getstrata/core/auth/oauth/oidcProvider";
|
|
139
|
-
import { GitHubOAuthProvider, MockOAuthProvider } from "@getstrata/core/auth/oauth/providers";
|
|
140
|
-
import { SamlProvider } from "@getstrata/core/auth/oauth/samlProvider";
|
|
141
|
-
|
|
142
|
-
// ../../src/config/features.ts
|
|
143
|
-
function readFeatureFlags() {
|
|
144
|
-
return {
|
|
145
|
-
webhooks: (process.env.FEATURE_WEBHOOKS ?? "true") !== "false",
|
|
146
|
-
fullTextSearch: (process.env.FEATURE_SEARCH ?? "true") !== "false",
|
|
147
|
-
auditLog: (process.env.FEATURE_AUDIT_LOG ?? "true") !== "false",
|
|
148
|
-
oauthLogin: (process.env.FEATURE_OAUTH ?? "true") !== "false",
|
|
149
|
-
samlLogin: (process.env.FEATURE_SAML ?? "false") === "true",
|
|
150
|
-
scim: (process.env.FEATURE_SCIM ?? "true") !== "false",
|
|
151
|
-
billing: (process.env.FEATURE_BILLING ?? "true") !== "false",
|
|
152
|
-
siemExport: (process.env.FEATURE_SIEM_EXPORT ?? "true") !== "false",
|
|
153
|
-
publicReads: (process.env.FEATURE_PUBLIC_READS ?? "true") !== "false",
|
|
154
|
-
emailVerification: (process.env.FEATURE_EMAIL_VERIFICATION ?? "false") === "true",
|
|
155
|
-
mfa: (process.env.FEATURE_MFA ?? "false") === "true"
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
var featureFlags = readFeatureFlags();
|
|
159
|
-
function isFeatureEnabled(feature) {
|
|
160
|
-
return readFeatureFlags()[feature];
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ../../src/modules/user/apiTokenRepository.ts
|
|
164
|
-
import { BaseRepository } from "@getstrata/core/database";
|
|
165
|
-
|
|
166
|
-
// ../../src/config/database.ts
|
|
167
|
-
function readInteger(name, fallback) {
|
|
168
|
-
const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
|
|
169
|
-
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
170
|
-
}
|
|
171
|
-
var databaseConfig = {
|
|
172
|
-
url: process.env.DATABASE_URL ?? "",
|
|
173
|
-
poolMax: readInteger("DB_POOL_MAX", 10),
|
|
174
|
-
idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
|
|
175
|
-
maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
|
|
176
|
-
connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
// ../../src/core/runtime/asyncContextStore.ts
|
|
180
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
181
|
-
function createAsyncContextStore(key) {
|
|
182
|
-
const symbol = Symbol.for(key);
|
|
183
|
-
const globalRecord = globalThis;
|
|
184
|
-
const existing = globalRecord[symbol];
|
|
185
|
-
if (existing) {
|
|
186
|
-
return existing;
|
|
187
|
-
}
|
|
188
|
-
const store = new AsyncLocalStorage;
|
|
189
|
-
globalRecord[symbol] = store;
|
|
190
|
-
return store;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ../../src/core/database/connectionContext.ts
|
|
194
|
-
var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
195
|
-
function getActiveDatabaseConnection(fallback) {
|
|
196
|
-
return activeConnection.getStore() ?? fallback;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// ../../src/core/database/queryProxy.ts
|
|
200
|
-
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
201
|
-
function createDatabaseQueryProxy(pool) {
|
|
202
|
-
function resolveDatabase() {
|
|
203
|
-
return getActiveDatabaseConnection(pool);
|
|
204
|
-
}
|
|
205
|
-
function resolveDatabaseForProperty(property) {
|
|
206
|
-
if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
|
|
207
|
-
return pool;
|
|
208
|
-
}
|
|
209
|
-
return resolveDatabase();
|
|
210
|
-
}
|
|
211
|
-
return new Proxy(function database() {}, {
|
|
212
|
-
apply(_target, _thisArg, args) {
|
|
213
|
-
return resolveDatabase()(...args);
|
|
214
|
-
},
|
|
215
|
-
get(_target, property) {
|
|
216
|
-
const connection = resolveDatabaseForProperty(property);
|
|
217
|
-
const value = connection[property];
|
|
218
|
-
return typeof value === "function" ? value.bind(connection) : value;
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// ../../src/core/database/defaultConnection.ts
|
|
224
|
-
var defaultPool = {
|
|
225
|
-
connection: null
|
|
226
|
-
};
|
|
227
|
-
var defaultQuery = {
|
|
228
|
-
connection: null
|
|
229
|
-
};
|
|
230
|
-
function registerDefaultDatabasePool(connection) {
|
|
231
|
-
defaultPool.connection = connection;
|
|
232
|
-
defaultQuery.connection = createDatabaseQueryProxy(connection);
|
|
233
|
-
}
|
|
234
|
-
function getDefaultDatabaseQuery() {
|
|
235
|
-
if (!defaultQuery.connection) {
|
|
236
|
-
throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
|
|
237
|
-
}
|
|
238
|
-
return defaultQuery.connection;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// ../../src/db/connection/createConnection.ts
|
|
242
|
-
var {SQL } = globalThis.Bun;
|
|
243
|
-
function createDatabaseConnection(config) {
|
|
244
|
-
if (!config.url) {
|
|
245
|
-
throw new Error("DATABASE_URL is not configured. Set DATABASE_URL before starting the app or running integration tests.");
|
|
246
|
-
}
|
|
247
|
-
return new SQL({
|
|
248
|
-
url: config.url,
|
|
249
|
-
max: config.poolMax,
|
|
250
|
-
idleTimeout: config.idleTimeoutSeconds,
|
|
251
|
-
maxLifetime: config.maxLifetimeSeconds,
|
|
252
|
-
connectionTimeout: config.connectionTimeoutSeconds
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// ../../src/db/connection/index.ts
|
|
257
|
-
var connectionHolder = {
|
|
258
|
-
connection: null
|
|
259
|
-
};
|
|
260
|
-
function getDatabase() {
|
|
261
|
-
if (!connectionHolder.connection) {
|
|
262
|
-
connectionHolder.connection = createDatabaseConnection(databaseConfig);
|
|
263
|
-
registerDefaultDatabasePool(connectionHolder.connection);
|
|
264
|
-
}
|
|
265
|
-
return connectionHolder.connection;
|
|
266
|
-
}
|
|
267
|
-
function getDb() {
|
|
268
|
-
getDatabase();
|
|
269
|
-
return getDefaultDatabaseQuery();
|
|
270
|
-
}
|
|
271
|
-
var db = new Proxy(function database() {}, {
|
|
272
|
-
apply(_target, _thisArg, args) {
|
|
273
|
-
return getDb()(...args);
|
|
274
|
-
},
|
|
275
|
-
get(_target, property) {
|
|
276
|
-
const connection = getDb();
|
|
277
|
-
const value = connection[property];
|
|
278
|
-
return typeof value === "function" ? value.bind(connection) : value;
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
var connection_default = db;
|
|
282
|
-
|
|
283
|
-
// ../../src/modules/user/apiTokenTable.ts
|
|
284
|
-
import { defineTable } from "@getstrata/core/database";
|
|
285
|
-
var apiTokenTable = defineTable({
|
|
286
|
-
name: "api_token",
|
|
287
|
-
primaryKey: "id",
|
|
288
|
-
columns: [
|
|
289
|
-
"id",
|
|
290
|
-
"user_id",
|
|
291
|
-
"name",
|
|
292
|
-
"token_hash",
|
|
293
|
-
"abilities",
|
|
294
|
-
"last_used_at",
|
|
295
|
-
"expires_at",
|
|
296
|
-
"created_at"
|
|
297
|
-
],
|
|
298
|
-
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
// ../../src/modules/user/authService.ts
|
|
302
|
-
import { verifyPassword } from "@getstrata/core/auth/password";
|
|
303
|
-
import { revealMfaSecret } from "@getstrata/core/crypto/mfaSecret";
|
|
304
|
-
import { UnauthorizedError } from "@getstrata/core/errors/http";
|
|
305
|
-
import { logSecurityEvent } from "@getstrata/core/security/securityEvents";
|
|
306
|
-
import { resolveDefaultTokenExpiryDays } from "@getstrata/core/security/tokenExpiry";
|
|
307
|
-
import { verifyTotp } from "@getstrata/core/security/totp";
|
|
308
|
-
import { currentTenantId } from "@getstrata/core/tenant/tenantContext";
|
|
309
|
-
|
|
310
|
-
// ../../src/domain/abilities.ts
|
|
311
|
-
var MEMBER_ABILITIES = [
|
|
312
|
-
"organizations:read",
|
|
313
|
-
"projects:read",
|
|
314
|
-
"projects:create",
|
|
315
|
-
"tasks:read",
|
|
316
|
-
"tasks:create",
|
|
317
|
-
"comments:read",
|
|
318
|
-
"comments:create",
|
|
319
|
-
"attachments:read",
|
|
320
|
-
"attachments:create",
|
|
321
|
-
"auth:tokens:read",
|
|
322
|
-
"auth:tokens:write"
|
|
323
|
-
];
|
|
324
|
-
var ADMIN_ABILITIES = [
|
|
325
|
-
...MEMBER_ABILITIES,
|
|
326
|
-
"organizations:create",
|
|
327
|
-
"organizations:update",
|
|
328
|
-
"organizations:delete",
|
|
329
|
-
"projects:update",
|
|
330
|
-
"projects:delete",
|
|
331
|
-
"tasks:update",
|
|
332
|
-
"tasks:delete",
|
|
333
|
-
"comments:update",
|
|
334
|
-
"comments:delete",
|
|
335
|
-
"attachments:delete",
|
|
336
|
-
"webhooks:read",
|
|
337
|
-
"webhooks:write",
|
|
338
|
-
"audit:read"
|
|
339
|
-
];
|
|
340
|
-
var PLATFORM_ADMIN_ABILITIES = ["*"];
|
|
341
|
-
function resolveAbilitiesForRole(role) {
|
|
342
|
-
if (role === "admin") {
|
|
343
|
-
return [...PLATFORM_ADMIN_ABILITIES];
|
|
344
|
-
}
|
|
345
|
-
return [...MEMBER_ABILITIES];
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// ../../src/modules/user/authService.ts
|
|
349
|
-
class AuthService {
|
|
350
|
-
users;
|
|
351
|
-
tokens;
|
|
352
|
-
oauthIdentities;
|
|
353
|
-
oauthProviders = new Map;
|
|
354
|
-
constructor(users, tokens, oauthIdentities) {
|
|
355
|
-
this.users = users;
|
|
356
|
-
this.tokens = tokens;
|
|
357
|
-
this.oauthIdentities = oauthIdentities;
|
|
358
|
-
}
|
|
359
|
-
registerOAuthProvider(provider) {
|
|
360
|
-
this.oauthProviders.set(provider.name, provider);
|
|
361
|
-
}
|
|
362
|
-
getOAuthProvider(name) {
|
|
363
|
-
return this.oauthProviders.get(name);
|
|
364
|
-
}
|
|
365
|
-
async loginWithPassword(email, password, options = {}) {
|
|
366
|
-
const user = await this.users.findByEmail(email);
|
|
367
|
-
if (!user?.password_hash) {
|
|
368
|
-
logSecurityEvent("auth_login_failed", { reason: "unknown_user", email });
|
|
369
|
-
throw new UnauthorizedError("Invalid credentials.");
|
|
370
|
-
}
|
|
371
|
-
const valid = await verifyPassword(password, user.password_hash);
|
|
372
|
-
if (!valid) {
|
|
373
|
-
logSecurityEvent("auth_login_failed", { reason: "invalid_password", user_id: user.id });
|
|
374
|
-
throw new UnauthorizedError("Invalid credentials.");
|
|
375
|
-
}
|
|
376
|
-
if (isFeatureEnabled("emailVerification") && !user.email_verified_at) {
|
|
377
|
-
logSecurityEvent("auth_login_blocked", { reason: "email_unverified", user_id: user.id });
|
|
378
|
-
throw new UnauthorizedError("Email address is not verified.");
|
|
379
|
-
}
|
|
380
|
-
if (isFeatureEnabled("mfa") && user.mfa_enabled) {
|
|
381
|
-
const mfaSecret = revealMfaSecret(user.mfa_secret);
|
|
382
|
-
if (!mfaSecret || !options.mfaCode || !verifyTotp(mfaSecret, options.mfaCode)) {
|
|
383
|
-
logSecurityEvent("auth_login_failed", { reason: "invalid_mfa", user_id: user.id });
|
|
384
|
-
throw new UnauthorizedError("Invalid MFA code.");
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
logSecurityEvent("auth_login_success", { user_id: user.id, method: "password" });
|
|
388
|
-
return await this.tokens.createToken(user.id, {
|
|
389
|
-
name: "password-login",
|
|
390
|
-
abilities: resolveAbilitiesForRole(user.role),
|
|
391
|
-
expiresInDays: resolveDefaultTokenExpiryDays() ?? undefined
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
async loginWithOAuth(providerName, code) {
|
|
395
|
-
const provider = this.oauthProviders.get(providerName);
|
|
396
|
-
if (!provider) {
|
|
397
|
-
throw new UnauthorizedError("Unsupported OAuth provider.");
|
|
398
|
-
}
|
|
399
|
-
const profile = await provider.exchangeCode(code);
|
|
400
|
-
const user = await this.findOrCreateOAuthUser(providerName, profile);
|
|
401
|
-
logSecurityEvent("auth_login_success", { user_id: user.id, method: `oauth:${providerName}` });
|
|
402
|
-
return await this.tokens.createToken(user.id, {
|
|
403
|
-
name: `${providerName}-oauth`,
|
|
404
|
-
abilities: resolveAbilitiesForRole(user.role),
|
|
405
|
-
expiresInDays: resolveDefaultTokenExpiryDays() ?? undefined
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
buildOAuthAuthorizationUrl(providerName, state) {
|
|
409
|
-
const provider = this.oauthProviders.get(providerName);
|
|
410
|
-
if (!provider) {
|
|
411
|
-
throw new UnauthorizedError("Unsupported OAuth provider.");
|
|
412
|
-
}
|
|
413
|
-
return provider.getAuthorizationUrl(state);
|
|
414
|
-
}
|
|
415
|
-
async findOrCreateOAuthUser(providerName, profile) {
|
|
416
|
-
const existingIdentity = await this.oauthIdentities.findByProviderUser(providerName, profile.providerUserId);
|
|
417
|
-
if (existingIdentity) {
|
|
418
|
-
return await this.users.findByIdOrThrow(existingIdentity.user_id);
|
|
419
|
-
}
|
|
420
|
-
const existingUser = await this.users.findByEmail(profile.email);
|
|
421
|
-
const user = existingUser ?? await this.users.create({
|
|
422
|
-
name: profile.name,
|
|
423
|
-
email: profile.email,
|
|
424
|
-
role: "member",
|
|
425
|
-
tenant_id: currentTenantId(),
|
|
426
|
-
email_verified_at: new Date,
|
|
427
|
-
created_at: new Date,
|
|
428
|
-
updated_at: new Date
|
|
429
|
-
});
|
|
430
|
-
await this.oauthIdentities.create({
|
|
431
|
-
user_id: user.id,
|
|
432
|
-
provider: providerName,
|
|
433
|
-
provider_user_id: profile.providerUserId,
|
|
434
|
-
email: profile.email,
|
|
435
|
-
created_at: new Date
|
|
436
|
-
});
|
|
437
|
-
return user;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// ../../src/modules/user/notificationRepository.ts
|
|
442
|
-
import { BaseRepository as BaseRepository2 } from "@getstrata/core/database";
|
|
443
|
-
|
|
444
|
-
// ../../src/modules/user/notificationTable.ts
|
|
445
|
-
import { defineTable as defineTable2 } from "@getstrata/core/database";
|
|
446
|
-
var notificationTable = defineTable2({
|
|
447
|
-
name: "notification",
|
|
448
|
-
primaryKey: "id",
|
|
449
|
-
columns: ["id", "user_id", "tenant_id", "type", "title", "body", "data", "read_at", "created_at"],
|
|
450
|
-
defaultOrderBy: { column: "created_at", direction: "DESC" }
|
|
451
|
-
});
|
|
21
|
+
// ../../src/bootstrap/buildWebModuleRoutes.ts
|
|
22
|
+
import { applyMiddlewareToRoutes as applyMiddlewareToRoutes2 } from "@getstrata/core/http/middleware";
|
|
452
23
|
|
|
453
|
-
// ../../src/
|
|
454
|
-
import {
|
|
24
|
+
// ../../src/bootstrap/buildModuleRoutes.ts
|
|
25
|
+
import { conditionalJsonResponse } from "@getstrata/core/http";
|
|
26
|
+
import { applyMiddlewareToRoutes } from "@getstrata/core/http/middleware";
|
|
455
27
|
|
|
456
|
-
// ../../src/modules/user/oauthIdentityRepository.ts
|
|
457
|
-
import { BaseRepository as BaseRepository3, defineTable as defineTable3 } from "@getstrata/core/database";
|
|
458
|
-
var oauthIdentityTable = defineTable3({
|
|
459
|
-
name: "oauth_identity",
|
|
460
|
-
primaryKey: "id",
|
|
461
|
-
columns: ["id", "user_id", "provider", "provider_user_id", "email", "created_at"]
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
// ../../src/modules/user/repository.ts
|
|
465
|
-
import {
|
|
466
|
-
emailLookupForQuery,
|
|
467
|
-
protectEmail,
|
|
468
|
-
revealEmail
|
|
469
|
-
} from "@getstrata/core/crypto/fieldEncryption";
|
|
470
|
-
import { revealMfaSecret as revealMfaSecret2 } from "@getstrata/core/crypto/mfaSecret";
|
|
471
|
-
import { BaseRepository as BaseRepository4 } from "@getstrata/core/database";
|
|
472
|
-
import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
|
|
473
|
-
|
|
474
|
-
// ../../src/modules/user/table.ts
|
|
475
|
-
import { defineTable as defineTable4 } from "@getstrata/core/database";
|
|
476
|
-
var userTable = defineTable4({
|
|
477
|
-
name: "users",
|
|
478
|
-
primaryKey: "id",
|
|
479
|
-
columns: [
|
|
480
|
-
"id",
|
|
481
|
-
"name",
|
|
482
|
-
"email",
|
|
483
|
-
"email_lookup",
|
|
484
|
-
"role",
|
|
485
|
-
"tenant_id",
|
|
486
|
-
"password_hash",
|
|
487
|
-
"email_verified_at",
|
|
488
|
-
"mfa_secret",
|
|
489
|
-
"mfa_enabled",
|
|
490
|
-
"created_at",
|
|
491
|
-
"updated_at"
|
|
492
|
-
],
|
|
493
|
-
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
// ../../src/modules/user/tokenService.ts
|
|
497
|
-
import { hashApiToken } from "@getstrata/core/auth/tokenHash";
|
|
498
|
-
import { ForbiddenError, NotFoundError as NotFoundError2 } from "@getstrata/core/errors/http";
|
|
499
|
-
import { resolveDefaultTokenExpiryDays as resolveDefaultTokenExpiryDays2 } from "@getstrata/core/security/tokenExpiry";
|
|
500
|
-
|
|
501
|
-
// ../../src/modules/user/provider.ts
|
|
502
|
-
var tokenServiceToken = CORE_TOKEN_SERVICE_TOKEN;
|
|
503
|
-
|
|
504
|
-
// ../../src/core/auth/authContext.ts
|
|
505
|
-
var authContext = createAsyncContextStore("@getstrata/authContext");
|
|
506
|
-
function currentAuthUser() {
|
|
507
|
-
return authContext.getStore() ?? null;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// ../../src/core/http/cookies.ts
|
|
511
|
-
function readRequestCookie(request, name) {
|
|
512
|
-
const cookies = request.cookies;
|
|
513
|
-
if (cookies && typeof cookies.get === "function") {
|
|
514
|
-
const value = cookies.get(name);
|
|
515
|
-
if (value) {
|
|
516
|
-
return value;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
const header = request.headers.get("cookie");
|
|
520
|
-
if (!header) {
|
|
521
|
-
return null;
|
|
522
|
-
}
|
|
523
|
-
for (const part of header.split(";")) {
|
|
524
|
-
const idx = part.indexOf("=");
|
|
525
|
-
if (idx === -1)
|
|
526
|
-
continue;
|
|
527
|
-
const cookieName = part.slice(0, idx).trim();
|
|
528
|
-
if (cookieName !== name)
|
|
529
|
-
continue;
|
|
530
|
-
return decodeURIComponent(part.slice(idx + 1).trim());
|
|
531
|
-
}
|
|
532
|
-
return null;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// ../../src/core/http/requestMetaContext.ts
|
|
536
|
-
var requestMetaContext = createAsyncContextStore("@getstrata/requestMetaContext");
|
|
537
|
-
function currentRequestMeta() {
|
|
538
|
-
return requestMetaContext.getStore() ?? {
|
|
539
|
-
ipAddress: null,
|
|
540
|
-
userAgent: null
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// ../../src/core/http/csrfToken.ts
|
|
545
|
-
var CSRF_COOKIE = "workhub_csrf";
|
|
546
|
-
var CSRF_TTL_MS = 60 * 60 * 1000;
|
|
547
|
-
function resolveCsrfSecret() {
|
|
548
|
-
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-csrf-secret";
|
|
549
|
-
}
|
|
550
|
-
function csrfVerifyOptions() {
|
|
551
|
-
return { secret: resolveCsrfSecret(), maxAge: CSRF_TTL_MS };
|
|
552
|
-
}
|
|
553
|
-
function createCsrfTokenCookie() {
|
|
554
|
-
const token = Bun.CSRF.generate(resolveCsrfSecret(), { expiresIn: CSRF_TTL_MS });
|
|
555
|
-
return {
|
|
556
|
-
token,
|
|
557
|
-
cookie: `${CSRF_COOKIE}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}`
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
function resolveCsrfToken(request) {
|
|
561
|
-
const cookieValue = readRequestCookie(request, CSRF_COOKIE);
|
|
562
|
-
if (cookieValue && Bun.CSRF.verify(cookieValue, csrfVerifyOptions())) {
|
|
563
|
-
return { token: cookieValue };
|
|
564
|
-
}
|
|
565
|
-
return createCsrfTokenCookie();
|
|
566
|
-
}
|
|
567
|
-
function resolveCsrfTokenForRequest(request) {
|
|
568
|
-
const metaToken = currentRequestMeta().csrfToken;
|
|
569
|
-
if (metaToken) {
|
|
570
|
-
return metaToken;
|
|
571
|
-
}
|
|
572
|
-
return resolveCsrfToken(request).token;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// ../../src/core/http/flashSession.ts
|
|
576
|
-
import { createHmac, timingSafeEqual } from "crypto";
|
|
577
|
-
var FLASH_COOKIE = "workhub_flash";
|
|
578
|
-
var FLASH_TTL_MS = 60 * 1000;
|
|
579
|
-
function resolveFlashSecret() {
|
|
580
|
-
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || "workhub-dev-flash-secret";
|
|
581
|
-
}
|
|
582
|
-
function signFlashPayload(payload, issuedAt) {
|
|
583
|
-
const signature = createHmac("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
|
|
584
|
-
return `${payload}.${issuedAt}.${signature}`;
|
|
585
|
-
}
|
|
586
|
-
function readFlashCookie(request) {
|
|
587
|
-
const cookieHeader = request.headers.get("cookie");
|
|
588
|
-
if (!cookieHeader) {
|
|
589
|
-
return null;
|
|
590
|
-
}
|
|
591
|
-
for (const part of cookieHeader.split(";")) {
|
|
592
|
-
const [name, ...rest] = part.trim().split("=");
|
|
593
|
-
if (name === FLASH_COOKIE) {
|
|
594
|
-
return decodeURIComponent(rest.join("="));
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
return null;
|
|
598
|
-
}
|
|
599
|
-
function parseFlashCookie(cookieValue) {
|
|
600
|
-
const parts = cookieValue.split(".");
|
|
601
|
-
if (parts.length < 3) {
|
|
602
|
-
return null;
|
|
603
|
-
}
|
|
604
|
-
const signature = parts.pop();
|
|
605
|
-
const issuedAtRaw = parts.pop();
|
|
606
|
-
const payload = parts.join(".");
|
|
607
|
-
if (!signature || !issuedAtRaw || !payload) {
|
|
608
|
-
return null;
|
|
609
|
-
}
|
|
610
|
-
const issuedAt = Number.parseInt(issuedAtRaw, 10);
|
|
611
|
-
if (!Number.isFinite(issuedAt) || Date.now() - issuedAt > FLASH_TTL_MS) {
|
|
612
|
-
return null;
|
|
613
|
-
}
|
|
614
|
-
const expectedSignature = signFlashPayload(payload, issuedAt).split(".").pop();
|
|
615
|
-
if (!expectedSignature) {
|
|
616
|
-
return null;
|
|
617
|
-
}
|
|
618
|
-
const expectedBuffer = Buffer.from(expectedSignature);
|
|
619
|
-
const actualBuffer = Buffer.from(signature);
|
|
620
|
-
if (expectedBuffer.length !== actualBuffer.length) {
|
|
621
|
-
return null;
|
|
622
|
-
}
|
|
623
|
-
if (!timingSafeEqual(expectedBuffer, actualBuffer)) {
|
|
624
|
-
return null;
|
|
625
|
-
}
|
|
626
|
-
try {
|
|
627
|
-
const parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
628
|
-
if (!parsed?.message || typeof parsed.message !== "string") {
|
|
629
|
-
return null;
|
|
630
|
-
}
|
|
631
|
-
if (parsed.level !== "success" && parsed.level !== "error" && parsed.level !== "info") {
|
|
632
|
-
return null;
|
|
633
|
-
}
|
|
634
|
-
return parsed;
|
|
635
|
-
} catch {
|
|
636
|
-
return null;
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
function pullFlash(request) {
|
|
640
|
-
const cookieValue = readFlashCookie(request);
|
|
641
|
-
if (!cookieValue) {
|
|
642
|
-
return null;
|
|
643
|
-
}
|
|
644
|
-
return parseFlashCookie(cookieValue);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
// ../../src/core/view/webLayoutData.ts
|
|
648
|
-
async function resolveWebLayoutData(container, request) {
|
|
649
|
-
const csrfToken = request ? resolveCsrfTokenForRequest(request) : "";
|
|
650
|
-
const flash = request ? currentRequestMeta().flash ?? pullFlash(request) : null;
|
|
651
|
-
const authUser = currentAuthUser();
|
|
652
|
-
if (!authUser) {
|
|
653
|
-
return { authUser: null, csrfToken, flash };
|
|
654
|
-
}
|
|
655
|
-
const userId = Number(authUser.id);
|
|
656
|
-
if (!Number.isInteger(userId) || userId <= 0) {
|
|
657
|
-
return { authUser: null, csrfToken, flash };
|
|
658
|
-
}
|
|
659
|
-
if (!container.has(tokenServiceToken)) {
|
|
660
|
-
return {
|
|
661
|
-
authUser: {
|
|
662
|
-
id: userId,
|
|
663
|
-
email: "",
|
|
664
|
-
role: authUser.role ?? "member"
|
|
665
|
-
},
|
|
666
|
-
csrfToken,
|
|
667
|
-
flash
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
const tokenService = container.resolve(tokenServiceToken);
|
|
671
|
-
try {
|
|
672
|
-
const user = await tokenService.findByIdOrThrow(userId);
|
|
673
|
-
return {
|
|
674
|
-
authUser: {
|
|
675
|
-
id: userId,
|
|
676
|
-
email: user.email ?? "",
|
|
677
|
-
role: authUser.role ?? user.role ?? "member"
|
|
678
|
-
},
|
|
679
|
-
csrfToken,
|
|
680
|
-
flash
|
|
681
|
-
};
|
|
682
|
-
} catch {
|
|
683
|
-
return { authUser: null, csrfToken, flash };
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
28
|
// ../../src/bootstrap/httpKernel.ts
|
|
687
29
|
import {
|
|
688
30
|
createAuthMiddleware,
|
|
@@ -743,6 +85,30 @@ function resolveRegisterRateLimit() {
|
|
|
743
85
|
};
|
|
744
86
|
}
|
|
745
87
|
|
|
88
|
+
// ../../src/bootstrap/config.ts
|
|
89
|
+
import {
|
|
90
|
+
CORE_AUTH_TOKEN,
|
|
91
|
+
CORE_CACHE_TOKEN,
|
|
92
|
+
CORE_CONFIG_TOKEN,
|
|
93
|
+
CORE_EVENT_BUS_TOKEN,
|
|
94
|
+
CORE_POLICY_GATE_TOKEN,
|
|
95
|
+
CORE_QUEUE_TOKEN,
|
|
96
|
+
CORE_TOKEN_SERVICE_TOKEN
|
|
97
|
+
} from "@getstrata/core/contracts/serviceTokens";
|
|
98
|
+
var APP_PORT_CONFIG_KEY = "app.port";
|
|
99
|
+
var CACHE_TTL_MS_CONFIG_KEY = "cache.ttlMs";
|
|
100
|
+
var CACHE_MAX_ENTRIES_CONFIG_KEY = "cache.maxEntries";
|
|
101
|
+
var CACHE_DRIVER_CONFIG_KEY = "cache.driver";
|
|
102
|
+
var REDIS_URL_CONFIG_KEY = "cache.redisUrl";
|
|
103
|
+
var DATABASE_URL_CONFIG_KEY = "database.url";
|
|
104
|
+
var AUTH_DEV_HEADERS_CONFIG_KEY = "auth.allowDevHeaders";
|
|
105
|
+
var DEFAULT_APP_PORT = 3000;
|
|
106
|
+
var DEFAULT_CACHE_TTL_MS = 3600000;
|
|
107
|
+
var DEFAULT_CACHE_MAX_ENTRIES = 100;
|
|
108
|
+
var DEFAULT_CACHE_DRIVER = "array";
|
|
109
|
+
var DEFAULT_API_TOKEN = "";
|
|
110
|
+
var DEFAULT_QUEUE_DRIVER = "sync";
|
|
111
|
+
|
|
746
112
|
// ../../src/bootstrap/httpKernel.ts
|
|
747
113
|
class HttpKernel {
|
|
748
114
|
dependencies;
|
|
@@ -909,7 +275,7 @@ function createHttpKernel(dependencies) {
|
|
|
909
275
|
|
|
910
276
|
// ../../src/bootstrap/discoverModules.ts
|
|
911
277
|
import { readdirSync } from "fs";
|
|
912
|
-
import { join
|
|
278
|
+
import { join } from "path";
|
|
913
279
|
import { pathToFileURL } from "url";
|
|
914
280
|
var DISCOVER_MODULES_STATE_KEY = Symbol.for("@getstrata/discoverModulesState");
|
|
915
281
|
function readDiscoverModulesState() {
|
|
@@ -932,7 +298,7 @@ function resolveModulesDirectory(options) {
|
|
|
932
298
|
if (state.configuredModulesDir) {
|
|
933
299
|
return state.configuredModulesDir;
|
|
934
300
|
}
|
|
935
|
-
return
|
|
301
|
+
return join(import.meta.dir, "../modules");
|
|
936
302
|
}
|
|
937
303
|
async function loadDiscoveredModules(options) {
|
|
938
304
|
const modulesDirectory = resolveModulesDirectory(options);
|
|
@@ -946,7 +312,7 @@ async function loadDiscoveredModules(options) {
|
|
|
946
312
|
throw error;
|
|
947
313
|
}
|
|
948
314
|
const modules = await Promise.all(moduleNames.map(async (moduleName) => {
|
|
949
|
-
const moduleUrl = pathToFileURL(
|
|
315
|
+
const moduleUrl = pathToFileURL(join(modulesDirectory, moduleName, "index.ts")).href;
|
|
950
316
|
const loaded = await import(moduleUrl);
|
|
951
317
|
return loaded.default;
|
|
952
318
|
}));
|
|
@@ -966,6 +332,17 @@ async function ensureModulesLoaded(options) {
|
|
|
966
332
|
function discoverModules() {
|
|
967
333
|
return readDiscoverModulesState().appModules;
|
|
968
334
|
}
|
|
335
|
+
// ../../src/bootstrap/prefixRouteMap.ts
|
|
336
|
+
function prefixRouteMap(prefix, routes) {
|
|
337
|
+
const normalizedPrefix = prefix.replace(/\/$/, "");
|
|
338
|
+
const prefixed = {};
|
|
339
|
+
for (const [path, handler] of Object.entries(routes)) {
|
|
340
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
341
|
+
prefixed[`${normalizedPrefix}${normalizedPath}`] = handler;
|
|
342
|
+
}
|
|
343
|
+
return prefixed;
|
|
344
|
+
}
|
|
345
|
+
|
|
969
346
|
// ../../src/bootstrap/routeRegistry.ts
|
|
970
347
|
class RouteRegistry {
|
|
971
348
|
routes = [];
|
|
@@ -991,49 +368,90 @@ function readSharedRouteRegistry() {
|
|
|
991
368
|
}
|
|
992
369
|
var routeRegistry = readSharedRouteRegistry();
|
|
993
370
|
|
|
994
|
-
// ../../src/bootstrap/
|
|
995
|
-
function
|
|
371
|
+
// ../../src/bootstrap/buildModuleRoutes.ts
|
|
372
|
+
function registerOpenApiRoute(method, path, middleware) {
|
|
996
373
|
routeRegistry.register({ method, path, middleware });
|
|
997
374
|
}
|
|
998
|
-
function
|
|
375
|
+
function registerOpenApiRouteMap(routes, middleware) {
|
|
999
376
|
const registered = {};
|
|
1000
377
|
for (const [path, handler] of Object.entries(routes)) {
|
|
1001
378
|
if (handler && typeof handler === "object" && !Array.isArray(handler)) {
|
|
1002
379
|
const methodMap = handler;
|
|
1003
380
|
registered[path] = methodMap;
|
|
1004
381
|
for (const method of Object.keys(methodMap)) {
|
|
1005
|
-
|
|
382
|
+
registerOpenApiRoute(method.toUpperCase(), path, middleware);
|
|
1006
383
|
}
|
|
1007
384
|
continue;
|
|
1008
385
|
}
|
|
1009
386
|
registered[path] = handler;
|
|
1010
|
-
|
|
387
|
+
registerOpenApiRoute("GET", path, middleware);
|
|
1011
388
|
}
|
|
1012
389
|
return registered;
|
|
1013
390
|
}
|
|
1014
|
-
function
|
|
391
|
+
function createCachedJson(dependencies) {
|
|
392
|
+
return async (cacheKey, loader, tags = [], request) => {
|
|
393
|
+
const data = tags.length > 0 ? await dependencies.cache.tags(...tags).remember(cacheKey, loader) : await dependencies.cache.remember(cacheKey, loader);
|
|
394
|
+
return conditionalJsonResponse(request, data);
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function buildModuleRoutes(dependencies, options = {}) {
|
|
398
|
+
const { apiPrefix = "", modules = discoverModules(), clearRegistry = true } = options;
|
|
399
|
+
if (clearRegistry) {
|
|
400
|
+
routeRegistry.clear();
|
|
401
|
+
}
|
|
402
|
+
const kernel = createHttpKernel(dependencies);
|
|
403
|
+
const middleware = [...kernel.globalMiddleware(), ...kernel.group("api")];
|
|
404
|
+
const cachedJson = createCachedJson(dependencies);
|
|
405
|
+
const moduleRoutes = {};
|
|
406
|
+
for (const module of modules) {
|
|
407
|
+
if (!module.routes) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
Object.assign(moduleRoutes, module.routes({ dependencies, cachedJson, kernel }));
|
|
411
|
+
}
|
|
412
|
+
const prefixedModuleRoutes = prefixRouteMap(apiPrefix, moduleRoutes);
|
|
413
|
+
return applyMiddlewareToRoutes(registerOpenApiRouteMap(prefixedModuleRoutes, ["global", "api"]), middleware);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ../../src/bootstrap/buildWebModuleRoutes.ts
|
|
417
|
+
function buildWebModuleRoutes(dependencies, options = {}) {
|
|
418
|
+
const { modules = discoverModules(), clearRegistry = true, seedRoutes = {} } = options;
|
|
419
|
+
if (clearRegistry) {
|
|
420
|
+
routeRegistry.clear();
|
|
421
|
+
}
|
|
1015
422
|
const kernel = createHttpKernel(dependencies);
|
|
1016
423
|
const middleware = [...kernel.globalMiddleware(), ...kernel.group("web")];
|
|
1017
|
-
const moduleRoutes = {
|
|
1018
|
-
|
|
1019
|
-
};
|
|
1020
|
-
registerRoute("GET", "/", ["global", "web"]);
|
|
1021
|
-
for (const module of discoverModules()) {
|
|
424
|
+
const moduleRoutes = { ...seedRoutes };
|
|
425
|
+
for (const module of modules) {
|
|
1022
426
|
if (!module.webRoutes) {
|
|
1023
427
|
continue;
|
|
1024
428
|
}
|
|
1025
429
|
Object.assign(moduleRoutes, module.webRoutes({
|
|
1026
430
|
dependencies,
|
|
1027
|
-
cachedJson: async () =>
|
|
431
|
+
cachedJson: async () => new Response(""),
|
|
1028
432
|
kernel
|
|
1029
433
|
}));
|
|
1030
434
|
}
|
|
1031
|
-
|
|
435
|
+
return applyMiddlewareToRoutes2(registerOpenApiRouteMap(moduleRoutes, ["global", "web"]), middleware);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ../../src/bootstrap/createWebRoutes.ts
|
|
439
|
+
function registerRoute(method, path, middleware) {
|
|
440
|
+
routeRegistry.register({ method, path, middleware });
|
|
441
|
+
}
|
|
442
|
+
function createWebRoutes(dependencies) {
|
|
443
|
+
const wrappedRoutes = buildWebModuleRoutes(dependencies, {
|
|
444
|
+
clearRegistry: false,
|
|
445
|
+
seedRoutes: {
|
|
446
|
+
"/": () => Response.redirect("/organizations", 302)
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
registerRoute("GET", "/", ["global", "web"]);
|
|
1032
450
|
wrappedRoutes["/assets/*"] = async (request) => {
|
|
1033
451
|
registerRoute("GET", "/assets/*", ["global", "web"]);
|
|
1034
452
|
const pathname = new URL(request.url).pathname;
|
|
1035
453
|
const relativePath = pathname.replace(/^\//, "");
|
|
1036
|
-
const file = Bun.file(
|
|
454
|
+
const file = Bun.file(join2(process.cwd(), "public", relativePath));
|
|
1037
455
|
if (!await file.exists()) {
|
|
1038
456
|
return htmlResponse("Not Found", { status: 404 });
|
|
1039
457
|
}
|