@getstrata/bootstrap 0.2.30 → 0.2.31
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/entries/context.js +24 -532
- package/dist/entries/createRoutes.js +0 -1
- package/dist/entries/dependencies.js +24 -532
- package/dist/entries/providers.js +3 -532
- package/dist/index.js +3 -490
- package/package.json +1 -1
|
@@ -91,6 +91,7 @@ import {
|
|
|
91
91
|
DatabaseTokenGuard,
|
|
92
92
|
GuestGuard
|
|
93
93
|
} from "@getstrata/core/auth/guard";
|
|
94
|
+
import { SessionGuard } from "@getstrata/core/auth/sessionGuard";
|
|
94
95
|
|
|
95
96
|
// ../../src/config/auth.ts
|
|
96
97
|
var authConfig = {
|
|
@@ -98,44 +99,6 @@ var authConfig = {
|
|
|
98
99
|
tokenDefaultAbilities: ["*"]
|
|
99
100
|
};
|
|
100
101
|
|
|
101
|
-
// ../../src/domain/abilities.ts
|
|
102
|
-
var MEMBER_ABILITIES = [
|
|
103
|
-
"organizations:read",
|
|
104
|
-
"projects:read",
|
|
105
|
-
"projects:create",
|
|
106
|
-
"tasks:read",
|
|
107
|
-
"tasks:create",
|
|
108
|
-
"comments:read",
|
|
109
|
-
"comments:create",
|
|
110
|
-
"attachments:read",
|
|
111
|
-
"attachments:create",
|
|
112
|
-
"auth:tokens:read",
|
|
113
|
-
"auth:tokens:write"
|
|
114
|
-
];
|
|
115
|
-
var ADMIN_ABILITIES = [
|
|
116
|
-
...MEMBER_ABILITIES,
|
|
117
|
-
"organizations:create",
|
|
118
|
-
"organizations:update",
|
|
119
|
-
"organizations:delete",
|
|
120
|
-
"projects:update",
|
|
121
|
-
"projects:delete",
|
|
122
|
-
"tasks:update",
|
|
123
|
-
"tasks:delete",
|
|
124
|
-
"comments:update",
|
|
125
|
-
"comments:delete",
|
|
126
|
-
"attachments:delete",
|
|
127
|
-
"webhooks:read",
|
|
128
|
-
"webhooks:write",
|
|
129
|
-
"audit:read"
|
|
130
|
-
];
|
|
131
|
-
var PLATFORM_ADMIN_ABILITIES = ["*"];
|
|
132
|
-
function resolveAbilitiesForRole(role) {
|
|
133
|
-
if (role === "admin") {
|
|
134
|
-
return [...PLATFORM_ADMIN_ABILITIES];
|
|
135
|
-
}
|
|
136
|
-
return [...MEMBER_ABILITIES];
|
|
137
|
-
}
|
|
138
|
-
|
|
139
102
|
// ../../src/bootstrap/config.ts
|
|
140
103
|
import {
|
|
141
104
|
CORE_AUTH_TOKEN,
|
|
@@ -160,439 +123,6 @@ var DEFAULT_CACHE_DRIVER = "array";
|
|
|
160
123
|
var DEFAULT_API_TOKEN = "";
|
|
161
124
|
var DEFAULT_QUEUE_DRIVER = "sync";
|
|
162
125
|
|
|
163
|
-
// ../../src/modules/user/provider.ts
|
|
164
|
-
import { OidcProvider } from "@getstrata/core/auth/oauth/oidcProvider";
|
|
165
|
-
import { GitHubOAuthProvider, MockOAuthProvider } from "@getstrata/core/auth/oauth/providers";
|
|
166
|
-
import { SamlProvider } from "@getstrata/core/auth/oauth/samlProvider";
|
|
167
|
-
|
|
168
|
-
// ../../src/config/features.ts
|
|
169
|
-
function readFeatureFlags() {
|
|
170
|
-
return {
|
|
171
|
-
webhooks: (process.env.FEATURE_WEBHOOKS ?? "true") !== "false",
|
|
172
|
-
fullTextSearch: (process.env.FEATURE_SEARCH ?? "true") !== "false",
|
|
173
|
-
auditLog: (process.env.FEATURE_AUDIT_LOG ?? "true") !== "false",
|
|
174
|
-
oauthLogin: (process.env.FEATURE_OAUTH ?? "true") !== "false",
|
|
175
|
-
samlLogin: (process.env.FEATURE_SAML ?? "false") === "true",
|
|
176
|
-
scim: (process.env.FEATURE_SCIM ?? "true") !== "false",
|
|
177
|
-
billing: (process.env.FEATURE_BILLING ?? "true") !== "false",
|
|
178
|
-
siemExport: (process.env.FEATURE_SIEM_EXPORT ?? "true") !== "false",
|
|
179
|
-
publicReads: (process.env.FEATURE_PUBLIC_READS ?? "true") !== "false",
|
|
180
|
-
emailVerification: (process.env.FEATURE_EMAIL_VERIFICATION ?? "false") === "true",
|
|
181
|
-
mfa: (process.env.FEATURE_MFA ?? "false") === "true"
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
var featureFlags = readFeatureFlags();
|
|
185
|
-
function isFeatureEnabled(feature) {
|
|
186
|
-
return readFeatureFlags()[feature];
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// ../../src/modules/user/apiTokenRepository.ts
|
|
190
|
-
import { BaseRepository } from "@getstrata/core/database";
|
|
191
|
-
|
|
192
|
-
// ../../src/config/database.ts
|
|
193
|
-
function readInteger(name, fallback) {
|
|
194
|
-
const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
|
|
195
|
-
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
196
|
-
}
|
|
197
|
-
var databaseConfig = {
|
|
198
|
-
url: process.env.DATABASE_URL ?? "",
|
|
199
|
-
poolMax: readInteger("DB_POOL_MAX", 10),
|
|
200
|
-
idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
|
|
201
|
-
maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
|
|
202
|
-
connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
// ../../src/core/runtime/asyncContextStore.ts
|
|
206
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
207
|
-
function createAsyncContextStore(key) {
|
|
208
|
-
const symbol = Symbol.for(key);
|
|
209
|
-
const globalRecord = globalThis;
|
|
210
|
-
const existing = globalRecord[symbol];
|
|
211
|
-
if (existing) {
|
|
212
|
-
return existing;
|
|
213
|
-
}
|
|
214
|
-
const store = new AsyncLocalStorage;
|
|
215
|
-
globalRecord[symbol] = store;
|
|
216
|
-
return store;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ../../src/core/database/connectionContext.ts
|
|
220
|
-
var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
221
|
-
function getActiveDatabaseConnection(fallback) {
|
|
222
|
-
return activeConnection.getStore() ?? fallback;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// ../../src/core/database/queryProxy.ts
|
|
226
|
-
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
227
|
-
function createDatabaseQueryProxy(pool) {
|
|
228
|
-
function resolveDatabase() {
|
|
229
|
-
return getActiveDatabaseConnection(pool);
|
|
230
|
-
}
|
|
231
|
-
function resolveDatabaseForProperty(property) {
|
|
232
|
-
if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
|
|
233
|
-
return pool;
|
|
234
|
-
}
|
|
235
|
-
return resolveDatabase();
|
|
236
|
-
}
|
|
237
|
-
return new Proxy(function database() {}, {
|
|
238
|
-
apply(_target, _thisArg, args) {
|
|
239
|
-
return resolveDatabase()(...args);
|
|
240
|
-
},
|
|
241
|
-
get(_target, property) {
|
|
242
|
-
const connection = resolveDatabaseForProperty(property);
|
|
243
|
-
const value = connection[property];
|
|
244
|
-
return typeof value === "function" ? value.bind(connection) : value;
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// ../../src/core/database/defaultConnection.ts
|
|
250
|
-
var defaultPool = {
|
|
251
|
-
connection: null
|
|
252
|
-
};
|
|
253
|
-
var defaultQuery = {
|
|
254
|
-
connection: null
|
|
255
|
-
};
|
|
256
|
-
function registerDefaultDatabasePool(connection) {
|
|
257
|
-
defaultPool.connection = connection;
|
|
258
|
-
defaultQuery.connection = createDatabaseQueryProxy(connection);
|
|
259
|
-
}
|
|
260
|
-
function getDefaultDatabaseQuery() {
|
|
261
|
-
if (!defaultQuery.connection) {
|
|
262
|
-
throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
|
|
263
|
-
}
|
|
264
|
-
return defaultQuery.connection;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// ../../src/db/connection/createConnection.ts
|
|
268
|
-
var {SQL } = globalThis.Bun;
|
|
269
|
-
function createDatabaseConnection(config) {
|
|
270
|
-
if (!config.url) {
|
|
271
|
-
throw new Error("DATABASE_URL is not configured. Set DATABASE_URL before starting the app or running integration tests.");
|
|
272
|
-
}
|
|
273
|
-
return new SQL({
|
|
274
|
-
url: config.url,
|
|
275
|
-
max: config.poolMax,
|
|
276
|
-
idleTimeout: config.idleTimeoutSeconds,
|
|
277
|
-
maxLifetime: config.maxLifetimeSeconds,
|
|
278
|
-
connectionTimeout: config.connectionTimeoutSeconds
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// ../../src/db/connection/index.ts
|
|
283
|
-
var connectionHolder = {
|
|
284
|
-
connection: null
|
|
285
|
-
};
|
|
286
|
-
function getDatabase() {
|
|
287
|
-
if (!connectionHolder.connection) {
|
|
288
|
-
connectionHolder.connection = createDatabaseConnection(databaseConfig);
|
|
289
|
-
registerDefaultDatabasePool(connectionHolder.connection);
|
|
290
|
-
}
|
|
291
|
-
return connectionHolder.connection;
|
|
292
|
-
}
|
|
293
|
-
function getDb() {
|
|
294
|
-
getDatabase();
|
|
295
|
-
return getDefaultDatabaseQuery();
|
|
296
|
-
}
|
|
297
|
-
async function pingDatabase(connection = getDatabase()) {
|
|
298
|
-
try {
|
|
299
|
-
await connection`SELECT 1`;
|
|
300
|
-
return true;
|
|
301
|
-
} catch {
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
async function ensureDatabaseConnection() {
|
|
306
|
-
if (await pingDatabase()) {
|
|
307
|
-
return getDatabase();
|
|
308
|
-
}
|
|
309
|
-
await getDatabase().close().catch(() => {
|
|
310
|
-
return;
|
|
311
|
-
});
|
|
312
|
-
connectionHolder.connection = createDatabaseConnection(databaseConfig);
|
|
313
|
-
registerDefaultDatabasePool(connectionHolder.connection);
|
|
314
|
-
return getDatabase();
|
|
315
|
-
}
|
|
316
|
-
var db = new Proxy(function database() {}, {
|
|
317
|
-
apply(_target, _thisArg, args) {
|
|
318
|
-
return getDb()(...args);
|
|
319
|
-
},
|
|
320
|
-
get(_target, property) {
|
|
321
|
-
const connection = getDb();
|
|
322
|
-
const value = connection[property];
|
|
323
|
-
return typeof value === "function" ? value.bind(connection) : value;
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
var connection_default = db;
|
|
327
|
-
|
|
328
|
-
// ../../src/modules/user/apiTokenTable.ts
|
|
329
|
-
import { defineTable } from "@getstrata/core/database";
|
|
330
|
-
var apiTokenTable = defineTable({
|
|
331
|
-
name: "api_token",
|
|
332
|
-
primaryKey: "id",
|
|
333
|
-
columns: [
|
|
334
|
-
"id",
|
|
335
|
-
"user_id",
|
|
336
|
-
"name",
|
|
337
|
-
"token_hash",
|
|
338
|
-
"abilities",
|
|
339
|
-
"last_used_at",
|
|
340
|
-
"expires_at",
|
|
341
|
-
"created_at"
|
|
342
|
-
],
|
|
343
|
-
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
// ../../src/modules/user/authService.ts
|
|
347
|
-
import { verifyPassword } from "@getstrata/core/auth/password";
|
|
348
|
-
import { revealMfaSecret } from "@getstrata/core/crypto/mfaSecret";
|
|
349
|
-
import { UnauthorizedError } from "@getstrata/core/errors/http";
|
|
350
|
-
import { logSecurityEvent } from "@getstrata/core/security/securityEvents";
|
|
351
|
-
import { resolveDefaultTokenExpiryDays } from "@getstrata/core/security/tokenExpiry";
|
|
352
|
-
import { verifyTotp } from "@getstrata/core/security/totp";
|
|
353
|
-
import { currentTenantId } from "@getstrata/core/tenant/tenantContext";
|
|
354
|
-
class AuthService {
|
|
355
|
-
users;
|
|
356
|
-
tokens;
|
|
357
|
-
oauthIdentities;
|
|
358
|
-
oauthProviders = new Map;
|
|
359
|
-
constructor(users, tokens, oauthIdentities) {
|
|
360
|
-
this.users = users;
|
|
361
|
-
this.tokens = tokens;
|
|
362
|
-
this.oauthIdentities = oauthIdentities;
|
|
363
|
-
}
|
|
364
|
-
registerOAuthProvider(provider) {
|
|
365
|
-
this.oauthProviders.set(provider.name, provider);
|
|
366
|
-
}
|
|
367
|
-
getOAuthProvider(name) {
|
|
368
|
-
return this.oauthProviders.get(name);
|
|
369
|
-
}
|
|
370
|
-
async loginWithPassword(email, password, options = {}) {
|
|
371
|
-
const user = await this.users.findByEmail(email);
|
|
372
|
-
if (!user?.password_hash) {
|
|
373
|
-
logSecurityEvent("auth_login_failed", { reason: "unknown_user", email });
|
|
374
|
-
throw new UnauthorizedError("Invalid credentials.");
|
|
375
|
-
}
|
|
376
|
-
const valid = await verifyPassword(password, user.password_hash);
|
|
377
|
-
if (!valid) {
|
|
378
|
-
logSecurityEvent("auth_login_failed", { reason: "invalid_password", user_id: user.id });
|
|
379
|
-
throw new UnauthorizedError("Invalid credentials.");
|
|
380
|
-
}
|
|
381
|
-
if (isFeatureEnabled("emailVerification") && !user.email_verified_at) {
|
|
382
|
-
logSecurityEvent("auth_login_blocked", { reason: "email_unverified", user_id: user.id });
|
|
383
|
-
throw new UnauthorizedError("Email address is not verified.");
|
|
384
|
-
}
|
|
385
|
-
if (isFeatureEnabled("mfa") && user.mfa_enabled) {
|
|
386
|
-
const mfaSecret = revealMfaSecret(user.mfa_secret);
|
|
387
|
-
if (!mfaSecret || !options.mfaCode || !verifyTotp(mfaSecret, options.mfaCode)) {
|
|
388
|
-
logSecurityEvent("auth_login_failed", { reason: "invalid_mfa", user_id: user.id });
|
|
389
|
-
throw new UnauthorizedError("Invalid MFA code.");
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
logSecurityEvent("auth_login_success", { user_id: user.id, method: "password" });
|
|
393
|
-
return await this.tokens.createToken(user.id, {
|
|
394
|
-
name: "password-login",
|
|
395
|
-
abilities: resolveAbilitiesForRole(user.role),
|
|
396
|
-
expiresInDays: resolveDefaultTokenExpiryDays() ?? undefined
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
async loginWithOAuth(providerName, code) {
|
|
400
|
-
const provider = this.oauthProviders.get(providerName);
|
|
401
|
-
if (!provider) {
|
|
402
|
-
throw new UnauthorizedError("Unsupported OAuth provider.");
|
|
403
|
-
}
|
|
404
|
-
const profile = await provider.exchangeCode(code);
|
|
405
|
-
const user = await this.findOrCreateOAuthUser(providerName, profile);
|
|
406
|
-
logSecurityEvent("auth_login_success", { user_id: user.id, method: `oauth:${providerName}` });
|
|
407
|
-
return await this.tokens.createToken(user.id, {
|
|
408
|
-
name: `${providerName}-oauth`,
|
|
409
|
-
abilities: resolveAbilitiesForRole(user.role),
|
|
410
|
-
expiresInDays: resolveDefaultTokenExpiryDays() ?? undefined
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
buildOAuthAuthorizationUrl(providerName, state) {
|
|
414
|
-
const provider = this.oauthProviders.get(providerName);
|
|
415
|
-
if (!provider) {
|
|
416
|
-
throw new UnauthorizedError("Unsupported OAuth provider.");
|
|
417
|
-
}
|
|
418
|
-
return provider.getAuthorizationUrl(state);
|
|
419
|
-
}
|
|
420
|
-
async findOrCreateOAuthUser(providerName, profile) {
|
|
421
|
-
const existingIdentity = await this.oauthIdentities.findByProviderUser(providerName, profile.providerUserId);
|
|
422
|
-
if (existingIdentity) {
|
|
423
|
-
return await this.users.findByIdOrThrow(existingIdentity.user_id);
|
|
424
|
-
}
|
|
425
|
-
const existingUser = await this.users.findByEmail(profile.email);
|
|
426
|
-
const user = existingUser ?? await this.users.create({
|
|
427
|
-
name: profile.name,
|
|
428
|
-
email: profile.email,
|
|
429
|
-
role: "member",
|
|
430
|
-
tenant_id: currentTenantId(),
|
|
431
|
-
email_verified_at: new Date,
|
|
432
|
-
created_at: new Date,
|
|
433
|
-
updated_at: new Date
|
|
434
|
-
});
|
|
435
|
-
await this.oauthIdentities.create({
|
|
436
|
-
user_id: user.id,
|
|
437
|
-
provider: providerName,
|
|
438
|
-
provider_user_id: profile.providerUserId,
|
|
439
|
-
email: profile.email,
|
|
440
|
-
created_at: new Date
|
|
441
|
-
});
|
|
442
|
-
return user;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// ../../src/modules/user/notificationRepository.ts
|
|
447
|
-
import { BaseRepository as BaseRepository2 } from "@getstrata/core/database";
|
|
448
|
-
|
|
449
|
-
// ../../src/modules/user/notificationTable.ts
|
|
450
|
-
import { defineTable as defineTable2 } from "@getstrata/core/database";
|
|
451
|
-
var notificationTable = defineTable2({
|
|
452
|
-
name: "notification",
|
|
453
|
-
primaryKey: "id",
|
|
454
|
-
columns: ["id", "user_id", "tenant_id", "type", "title", "body", "data", "read_at", "created_at"],
|
|
455
|
-
defaultOrderBy: { column: "created_at", direction: "DESC" }
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
// ../../src/modules/user/notificationService.ts
|
|
459
|
-
import { NotFoundError } from "@getstrata/core/errors/http";
|
|
460
|
-
|
|
461
|
-
// ../../src/modules/user/oauthIdentityRepository.ts
|
|
462
|
-
import { BaseRepository as BaseRepository3, defineTable as defineTable3 } from "@getstrata/core/database";
|
|
463
|
-
var oauthIdentityTable = defineTable3({
|
|
464
|
-
name: "oauth_identity",
|
|
465
|
-
primaryKey: "id",
|
|
466
|
-
columns: ["id", "user_id", "provider", "provider_user_id", "email", "created_at"]
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
// ../../src/modules/user/repository.ts
|
|
470
|
-
import {
|
|
471
|
-
emailLookupForQuery,
|
|
472
|
-
protectEmail,
|
|
473
|
-
revealEmail
|
|
474
|
-
} from "@getstrata/core/crypto/fieldEncryption";
|
|
475
|
-
import { revealMfaSecret as revealMfaSecret2 } from "@getstrata/core/crypto/mfaSecret";
|
|
476
|
-
import { BaseRepository as BaseRepository4 } from "@getstrata/core/database";
|
|
477
|
-
import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
|
|
478
|
-
|
|
479
|
-
// ../../src/modules/user/table.ts
|
|
480
|
-
import { defineTable as defineTable4 } from "@getstrata/core/database";
|
|
481
|
-
var userTable = defineTable4({
|
|
482
|
-
name: "users",
|
|
483
|
-
primaryKey: "id",
|
|
484
|
-
columns: [
|
|
485
|
-
"id",
|
|
486
|
-
"name",
|
|
487
|
-
"email",
|
|
488
|
-
"email_lookup",
|
|
489
|
-
"role",
|
|
490
|
-
"tenant_id",
|
|
491
|
-
"password_hash",
|
|
492
|
-
"email_verified_at",
|
|
493
|
-
"mfa_secret",
|
|
494
|
-
"mfa_enabled",
|
|
495
|
-
"created_at",
|
|
496
|
-
"updated_at"
|
|
497
|
-
],
|
|
498
|
-
defaultOrderBy: { column: "id", direction: "ASC" }
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
// ../../src/modules/user/tokenService.ts
|
|
502
|
-
import { hashApiToken } from "@getstrata/core/auth/tokenHash";
|
|
503
|
-
import { ForbiddenError, NotFoundError as NotFoundError2 } from "@getstrata/core/errors/http";
|
|
504
|
-
import { resolveDefaultTokenExpiryDays as resolveDefaultTokenExpiryDays2 } from "@getstrata/core/security/tokenExpiry";
|
|
505
|
-
|
|
506
|
-
// ../../src/modules/user/provider.ts
|
|
507
|
-
var userRepositoryToken = "user.repository";
|
|
508
|
-
var tokenServiceToken = CORE_TOKEN_SERVICE_TOKEN;
|
|
509
|
-
|
|
510
|
-
// ../../src/core/auth/sessionCookie.ts
|
|
511
|
-
import { createHmac, timingSafeEqual } from "crypto";
|
|
512
|
-
var SESSION_COOKIE = "workhub_session";
|
|
513
|
-
var SESSION_TTL_SECONDS = 60 * 60 * 24 * 7;
|
|
514
|
-
function resolveSessionSecret() {
|
|
515
|
-
return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || "workhub-dev-session-secret";
|
|
516
|
-
}
|
|
517
|
-
function signSession(userId, issuedAt) {
|
|
518
|
-
const payload = `${userId}.${issuedAt}`;
|
|
519
|
-
const signature = createHmac("sha256", resolveSessionSecret()).update(payload).digest("hex");
|
|
520
|
-
return `${payload}.${signature}`;
|
|
521
|
-
}
|
|
522
|
-
function readCookieValue(request, cookieName) {
|
|
523
|
-
const cookieHeader = request.headers.get("cookie");
|
|
524
|
-
if (!cookieHeader) {
|
|
525
|
-
return null;
|
|
526
|
-
}
|
|
527
|
-
for (const part of cookieHeader.split(";")) {
|
|
528
|
-
const [name, ...rest] = part.trim().split("=");
|
|
529
|
-
if (name === cookieName) {
|
|
530
|
-
return decodeURIComponent(rest.join("="));
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
return null;
|
|
534
|
-
}
|
|
535
|
-
function readSessionUserId(request) {
|
|
536
|
-
const cookieValue = readCookieValue(request, SESSION_COOKIE);
|
|
537
|
-
if (!cookieValue) {
|
|
538
|
-
return null;
|
|
539
|
-
}
|
|
540
|
-
const parts = cookieValue.split(".");
|
|
541
|
-
if (parts.length !== 3) {
|
|
542
|
-
return null;
|
|
543
|
-
}
|
|
544
|
-
const [userIdRaw, issuedAtRaw, cookieSignature] = parts;
|
|
545
|
-
const userId = Number.parseInt(String(userIdRaw), 10);
|
|
546
|
-
const issuedAt = Number.parseInt(String(issuedAtRaw), 10);
|
|
547
|
-
if (!Number.isInteger(userId) || userId <= 0 || !Number.isFinite(issuedAt)) {
|
|
548
|
-
return null;
|
|
549
|
-
}
|
|
550
|
-
if (Date.now() - issuedAt > SESSION_TTL_SECONDS * 1000) {
|
|
551
|
-
return null;
|
|
552
|
-
}
|
|
553
|
-
const expectedSignature = signSession(userId, issuedAt).split(".").pop();
|
|
554
|
-
if (!expectedSignature || !cookieSignature) {
|
|
555
|
-
return null;
|
|
556
|
-
}
|
|
557
|
-
const expectedBuffer = Buffer.from(expectedSignature);
|
|
558
|
-
const actualBuffer = Buffer.from(cookieSignature);
|
|
559
|
-
if (expectedBuffer.length !== actualBuffer.length) {
|
|
560
|
-
return null;
|
|
561
|
-
}
|
|
562
|
-
if (!timingSafeEqual(expectedBuffer, actualBuffer)) {
|
|
563
|
-
return null;
|
|
564
|
-
}
|
|
565
|
-
return userId;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// ../../src/core/auth/sessionGuard.ts
|
|
569
|
-
class SessionGuard {
|
|
570
|
-
container;
|
|
571
|
-
constructor(container) {
|
|
572
|
-
this.container = container;
|
|
573
|
-
}
|
|
574
|
-
async resolve(request) {
|
|
575
|
-
const userId = readSessionUserId(request);
|
|
576
|
-
if (!userId) {
|
|
577
|
-
return null;
|
|
578
|
-
}
|
|
579
|
-
if (!this.container.has(tokenServiceToken)) {
|
|
580
|
-
return null;
|
|
581
|
-
}
|
|
582
|
-
const tokenService = this.container.resolve(tokenServiceToken);
|
|
583
|
-
try {
|
|
584
|
-
const user = await tokenService.findByIdOrThrow(userId);
|
|
585
|
-
return {
|
|
586
|
-
id: user.id,
|
|
587
|
-
role: user.role,
|
|
588
|
-
abilities: resolveAbilitiesForRole(user.role)
|
|
589
|
-
};
|
|
590
|
-
} catch {
|
|
591
|
-
return null;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
126
|
// ../../src/bootstrap/providers/auth.ts
|
|
597
127
|
var authProvider = {
|
|
598
128
|
name: "core.auth",
|
|
@@ -609,66 +139,7 @@ var auth_default = authProvider;
|
|
|
609
139
|
|
|
610
140
|
// ../../src/bootstrap/providers/cache.ts
|
|
611
141
|
import { createCacheStore } from "@getstrata/core/cache/createCacheStore";
|
|
612
|
-
|
|
613
|
-
// ../../src/core/cache/taggedCache.ts
|
|
614
|
-
class TaggedCache {
|
|
615
|
-
store;
|
|
616
|
-
tags;
|
|
617
|
-
constructor(store, tags) {
|
|
618
|
-
this.store = store;
|
|
619
|
-
this.tags = tags;
|
|
620
|
-
}
|
|
621
|
-
async remember(key, callback, ttlMs) {
|
|
622
|
-
const value = await this.store.getOrSet(key, callback, ttlMs);
|
|
623
|
-
await this.store.attachTags(key, this.tags);
|
|
624
|
-
return value;
|
|
625
|
-
}
|
|
626
|
-
async flush() {
|
|
627
|
-
return this.store.flushTags(this.tags);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
var taggedCache_default = TaggedCache;
|
|
631
|
-
|
|
632
|
-
// ../../src/core/cache/repository.ts
|
|
633
|
-
class CacheRepository {
|
|
634
|
-
store;
|
|
635
|
-
constructor(store) {
|
|
636
|
-
this.store = store;
|
|
637
|
-
}
|
|
638
|
-
async get(key) {
|
|
639
|
-
return this.store.get(key);
|
|
640
|
-
}
|
|
641
|
-
async remember(key, callback, ttlMs) {
|
|
642
|
-
return this.store.getOrSet(key, callback, ttlMs);
|
|
643
|
-
}
|
|
644
|
-
async forget(key) {
|
|
645
|
-
return this.store.invalidate(key);
|
|
646
|
-
}
|
|
647
|
-
async flush() {
|
|
648
|
-
await this.store.clear();
|
|
649
|
-
}
|
|
650
|
-
tags(...names) {
|
|
651
|
-
return new taggedCache_default(this.store, names);
|
|
652
|
-
}
|
|
653
|
-
async getOrSet(key, loader, ttlMs) {
|
|
654
|
-
return this.remember(key, loader, ttlMs);
|
|
655
|
-
}
|
|
656
|
-
async invalidate(key) {
|
|
657
|
-
return this.forget(key);
|
|
658
|
-
}
|
|
659
|
-
async invalidateByPrefix(prefix) {
|
|
660
|
-
return this.store.invalidateByPrefix(prefix);
|
|
661
|
-
}
|
|
662
|
-
async clear() {
|
|
663
|
-
await this.flush();
|
|
664
|
-
}
|
|
665
|
-
async size() {
|
|
666
|
-
return this.store.size();
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
var repository_default2 = CacheRepository;
|
|
670
|
-
|
|
671
|
-
// ../../src/bootstrap/providers/cache.ts
|
|
142
|
+
import { CacheRepository } from "@getstrata/core/cache/repository";
|
|
672
143
|
var cacheProvider = {
|
|
673
144
|
name: "core.cache",
|
|
674
145
|
register({ container, config, dependencies }) {
|
|
@@ -679,7 +150,7 @@ var cacheProvider = {
|
|
|
679
150
|
maxEntries: config.require(CACHE_MAX_ENTRIES_CONFIG_KEY),
|
|
680
151
|
redisUrl: config.get(REDIS_URL_CONFIG_KEY) || undefined
|
|
681
152
|
});
|
|
682
|
-
return new
|
|
153
|
+
return new CacheRepository(store);
|
|
683
154
|
});
|
|
684
155
|
dependencies.cache = container.resolve(CORE_CACHE_TOKEN);
|
|
685
156
|
}
|
|
@@ -1028,6 +499,27 @@ var coreProviders = [
|
|
|
1028
499
|
viewProvider
|
|
1029
500
|
];
|
|
1030
501
|
|
|
502
|
+
// ../../src/config/features.ts
|
|
503
|
+
function readFeatureFlags() {
|
|
504
|
+
return {
|
|
505
|
+
webhooks: (process.env.FEATURE_WEBHOOKS ?? "true") !== "false",
|
|
506
|
+
fullTextSearch: (process.env.FEATURE_SEARCH ?? "true") !== "false",
|
|
507
|
+
auditLog: (process.env.FEATURE_AUDIT_LOG ?? "true") !== "false",
|
|
508
|
+
oauthLogin: (process.env.FEATURE_OAUTH ?? "true") !== "false",
|
|
509
|
+
samlLogin: (process.env.FEATURE_SAML ?? "false") === "true",
|
|
510
|
+
scim: (process.env.FEATURE_SCIM ?? "true") !== "false",
|
|
511
|
+
billing: (process.env.FEATURE_BILLING ?? "true") !== "false",
|
|
512
|
+
siemExport: (process.env.FEATURE_SIEM_EXPORT ?? "true") !== "false",
|
|
513
|
+
publicReads: (process.env.FEATURE_PUBLIC_READS ?? "true") !== "false",
|
|
514
|
+
emailVerification: (process.env.FEATURE_EMAIL_VERIFICATION ?? "false") === "true",
|
|
515
|
+
mfa: (process.env.FEATURE_MFA ?? "false") === "true"
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
var featureFlags = readFeatureFlags();
|
|
519
|
+
function isFeatureEnabled(feature) {
|
|
520
|
+
return readFeatureFlags()[feature];
|
|
521
|
+
}
|
|
522
|
+
|
|
1031
523
|
// ../../src/domain/auth.ts
|
|
1032
524
|
var TEST_ADMIN_API_TOKEN = "workhub-admin-test-token";
|
|
1033
525
|
var TEST_MEMBER_API_TOKEN = "workhub-member-test-token";
|