@opengeni/api-router 2.5.0 → 2.6.4

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.
Files changed (59) hide show
  1. package/dist/app.js +1 -1
  2. package/dist/auth/managed-auth-attempt-context.d.ts +5 -1
  3. package/dist/auth/managed-auth.d.ts +24 -1
  4. package/dist/{chunk-QESX7HDK.js → chunk-XTLI3CBH.js} +2236 -387
  5. package/dist/chunk-XTLI3CBH.js.map +1 -0
  6. package/dist/http/sse.d.ts +9 -0
  7. package/dist/index.js +104 -4
  8. package/dist/index.js.map +1 -1
  9. package/dist/interaction-metrics.d.ts +2 -0
  10. package/dist/mcp/company-brain-governed-writes.d.ts +1 -1
  11. package/dist/mcp/company-profile-agent-admin.d.ts +6 -6
  12. package/dist/mcp/remember.d.ts +2 -2
  13. package/dist/mcp/server.d.ts +1 -0
  14. package/dist/mcp/session-view.d.ts +1 -0
  15. package/dist/mcp/session-wait.d.ts +19 -0
  16. package/dist/routes/api-keys.d.ts +2 -0
  17. package/dist/routes/browser-sessions.d.ts +1 -0
  18. package/dist/routes/computer-sessions.d.ts +12 -0
  19. package/dist/routes/managed-auth-session-sets.d.ts +7 -0
  20. package/dist/routes/workspaces.d.ts +1 -1
  21. package/dist/sandbox/metrics-ingestion.d.ts +16 -0
  22. package/dist/workspace-delete-observability.d.ts +10 -0
  23. package/package.json +15 -15
  24. package/src/app.ts +172 -20
  25. package/src/auth/managed-auth-attempt-context.ts +40 -3
  26. package/src/auth/managed-auth-session-adapter.ts +1 -0
  27. package/src/auth/managed-auth.ts +164 -4
  28. package/src/http/sse.ts +279 -45
  29. package/src/integrations/oauth-client.ts +8 -1
  30. package/src/integrations/provider-oauth.ts +12 -2
  31. package/src/interaction-metrics.ts +30 -0
  32. package/src/mcp/company-brain-governed-writes.ts +38 -23
  33. package/src/mcp/company-profile-agent-admin.ts +7 -7
  34. package/src/mcp/remember.ts +19 -8
  35. package/src/mcp/server.ts +318 -26
  36. package/src/mcp/session-wait.ts +56 -8
  37. package/src/routes/api-integrations.ts +2 -2
  38. package/src/routes/api-keys.ts +149 -7
  39. package/src/routes/browser-sessions.ts +10 -2
  40. package/src/routes/capabilities.ts +3 -3
  41. package/src/routes/codex.ts +483 -74
  42. package/src/routes/company-profile.ts +64 -0
  43. package/src/routes/computer-sessions.ts +103 -1
  44. package/src/routes/integration-facets.ts +8 -5
  45. package/src/routes/interaction-resources.ts +7 -1
  46. package/src/routes/managed-auth-session-sets.ts +199 -2
  47. package/src/routes/organization-memberships.ts +28 -8
  48. package/src/routes/packs.ts +5 -5
  49. package/src/routes/plugins.ts +2 -2
  50. package/src/routes/scheduled-tasks.ts +9 -0
  51. package/src/routes/sessions.ts +3 -6
  52. package/src/routes/skills.ts +3 -3
  53. package/src/routes/workspaces.ts +293 -54
  54. package/src/sandbox/channel-a.ts +10 -4
  55. package/src/sandbox/machines.ts +13 -6
  56. package/src/sandbox/metrics-ingestion.ts +157 -3
  57. package/src/sandbox/viewer.ts +20 -1
  58. package/src/workspace-delete-observability.ts +75 -0
  59. package/dist/chunk-QESX7HDK.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import type { Settings } from "@opengeni/config";
1
+ import { canonicalPublicOrigin, type Settings } from "@opengeni/config";
2
2
  import {
3
3
  type ManagedAuth,
4
4
  type ManagedEmailMessage,
@@ -20,7 +20,9 @@ import { Pool } from "pg";
20
20
 
21
21
  import { decideCanonicalHumanSessionAdmission } from "./canonical-human-session-admission";
22
22
  import {
23
+ currentManagedAuthProviderId,
23
24
  currentManagedAuthAttemptId,
25
+ recordCurrentManagedAuthSession,
24
26
  shouldDiscardCurrentManagedAuthProviderSession,
25
27
  } from "./managed-auth-attempt-context";
26
28
 
@@ -44,6 +46,21 @@ export function managedAuthUserCreateOverride(
44
46
  return { data: { ...user, emailVerified: true } };
45
47
  }
46
48
 
49
+ export function managedAuthUserCreateAdmission(
50
+ settings: Pick<Settings, "environment">,
51
+ user: { emailVerified: boolean } & Record<string, unknown>,
52
+ providerId: string,
53
+ ): { data: typeof user } | false | undefined {
54
+ if (
55
+ managedAuthRequiresEmailVerification(settings) &&
56
+ providerId !== "credential" &&
57
+ !user.emailVerified
58
+ ) {
59
+ return false;
60
+ }
61
+ return managedAuthUserCreateOverride(settings, user);
62
+ }
63
+
47
64
  /** Keep Better Auth password policy and storage format behind this boundary. */
48
65
  export async function hashManagedAuthPassword(password: string): Promise<string> {
49
66
  return await hashPassword(password);
@@ -165,6 +182,27 @@ export function createManagedAuth(
165
182
  accountLinking: {
166
183
  enabled: false,
167
184
  },
185
+ encryptOAuthTokens: true,
186
+ storeStateStrategy: "database",
187
+ },
188
+ socialProviders: {
189
+ ...(settings.managedAuthGoogleClientId && settings.managedAuthGoogleClientSecret
190
+ ? {
191
+ google: {
192
+ clientId: settings.managedAuthGoogleClientId,
193
+ clientSecret: settings.managedAuthGoogleClientSecret,
194
+ prompt: "select_account" as const,
195
+ },
196
+ }
197
+ : {}),
198
+ ...(settings.managedAuthGithubClientId && settings.managedAuthGithubClientSecret
199
+ ? {
200
+ github: {
201
+ clientId: settings.managedAuthGithubClientId,
202
+ clientSecret: settings.managedAuthGithubClientSecret,
203
+ },
204
+ }
205
+ : {}),
168
206
  },
169
207
  verification: {
170
208
  modelName: "auth_verifications",
@@ -229,6 +267,7 @@ export function createManagedAuth(
229
267
  session: {
230
268
  create: {
231
269
  before: async (session) => {
270
+ const providerId = currentManagedAuthProviderId();
232
271
  await ensureCanonicalHumanIdentityForAuthUser(db, session.userId);
233
272
  const preflightProjection = await getCanonicalHumanIdentityProjection(
234
273
  db,
@@ -242,7 +281,7 @@ export function createManagedAuth(
242
281
  if (!preflight.allowed) {
243
282
  const exactRecoveryBinding = await getCanonicalHumanExactLoginBindingForAuthUser(db, {
244
283
  authUserId: session.userId,
245
- providerId: "credential",
284
+ providerId,
246
285
  });
247
286
  const recoveryBinding = preflightProjection.loginBindings.find(
248
287
  (binding) =>
@@ -282,7 +321,7 @@ export function createManagedAuth(
282
321
  const projection = await getCanonicalHumanIdentityProjection(db, session.userId);
283
322
  const exactBinding = await getCanonicalHumanExactLoginBindingForAuthUser(db, {
284
323
  authUserId: session.userId,
285
- providerId: "credential",
324
+ providerId,
286
325
  });
287
326
  const activeBinding = projection.loginBindings.find(
288
327
  (binding) => binding.id === exactBinding.id,
@@ -318,6 +357,7 @@ export function createManagedAuth(
318
357
  };
319
358
  },
320
359
  after: async (session) => {
360
+ recordCurrentManagedAuthSession(session.id);
321
361
  if (!shouldDiscardCurrentManagedAuthProviderSession()) return;
322
362
  await db.execute(sql`delete from auth_sessions where id = ${session.id}`);
323
363
  },
@@ -325,7 +365,8 @@ export function createManagedAuth(
325
365
  },
326
366
  user: {
327
367
  create: {
328
- before: async (user) => managedAuthUserCreateOverride(settings, user),
368
+ before: async (user) =>
369
+ managedAuthUserCreateAdmission(settings, user, currentManagedAuthProviderId()),
329
370
  after: async (user) => {
330
371
  if (!user.emailVerified) return;
331
372
  await ensureManagedAccessForUser(db, {
@@ -342,6 +383,125 @@ export function createManagedAuth(
342
383
  }) as ManagedAuth;
343
384
  }
344
385
 
386
+ export type ManagedAuthOAuthAttempt = {
387
+ transactionId: string;
388
+ provider: "google" | "github";
389
+ authorityHash: string;
390
+ transactionSecretHash: string;
391
+ expectedGeneration: string;
392
+ expectedActorEpoch: string;
393
+ };
394
+
395
+ /**
396
+ * Resolve OpenGeni's server-only login transaction proof from Better Auth's
397
+ * database-backed OAuth state before the provider callback consumes it.
398
+ */
399
+ export async function resolveManagedAuthOAuthAttempt(
400
+ auth: ManagedAuth,
401
+ request: Request,
402
+ provider: "google" | "github",
403
+ publicBaseUrl: string,
404
+ ): Promise<ManagedAuthOAuthAttempt | null> {
405
+ const expectedOrigin = canonicalPublicOrigin(publicBaseUrl);
406
+ if (!expectedOrigin) return null;
407
+ const state = new URL(request.url).searchParams.get("state");
408
+ if (!state) return null;
409
+ const verification = await (await auth.$context).internalAdapter.findVerificationValue(state);
410
+ if (!verification?.value) return null;
411
+ let parsed: unknown;
412
+ try {
413
+ parsed = JSON.parse(verification.value);
414
+ } catch {
415
+ return null;
416
+ }
417
+ if (!parsed || typeof parsed !== "object") return null;
418
+ const stateData = parsed as Record<string, unknown>;
419
+ const proof = stateData.opengeniManagedAuth;
420
+ if (!proof || typeof proof !== "object") return null;
421
+ const value = proof as Record<string, unknown>;
422
+ if (
423
+ value.version !== 1 ||
424
+ value.provider !== provider ||
425
+ typeof value.transactionId !== "string" ||
426
+ typeof value.authorityHash !== "string" ||
427
+ typeof value.transactionSecretHash !== "string" ||
428
+ typeof value.expectedGeneration !== "string" ||
429
+ typeof value.expectedActorEpoch !== "string" ||
430
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(
431
+ value.transactionId,
432
+ ) ||
433
+ !/^[0-9a-f]{64}$/u.test(value.authorityHash) ||
434
+ !/^[0-9a-f]{64}$/u.test(value.transactionSecretHash) ||
435
+ !/^[1-9][0-9]*$/u.test(value.expectedGeneration) ||
436
+ !/^[1-9][0-9]*$/u.test(value.expectedActorEpoch)
437
+ ) {
438
+ return null;
439
+ }
440
+ const callbackURL = typeof stateData.callbackURL === "string" ? stateData.callbackURL : null;
441
+ const errorURL = typeof stateData.errorURL === "string" ? stateData.errorURL : null;
442
+ if (
443
+ !callbackURL ||
444
+ !errorURL ||
445
+ !managedAuthOAuthReturnMatches(callbackURL, expectedOrigin, value.transactionId, "complete") ||
446
+ !managedAuthOAuthReturnMatches(errorURL, expectedOrigin, value.transactionId, "error")
447
+ ) {
448
+ return null;
449
+ }
450
+ return {
451
+ transactionId: value.transactionId,
452
+ provider,
453
+ authorityHash: value.authorityHash,
454
+ transactionSecretHash: value.transactionSecretHash,
455
+ expectedGeneration: value.expectedGeneration,
456
+ expectedActorEpoch: value.expectedActorEpoch,
457
+ };
458
+ }
459
+
460
+ export async function isolatedManagedAuthOAuthCallbackRequest(
461
+ auth: ManagedAuth,
462
+ request: Request,
463
+ ): Promise<{ request: Request; stateCookieName: string }> {
464
+ const context = await auth.$context;
465
+ const stateCookieName = context.createAuthCookie("state").name;
466
+ const headers = new Headers(request.headers);
467
+ const stateCookie = cookiePair(headers.get("cookie"), stateCookieName);
468
+ if (stateCookie) headers.set("cookie", stateCookie);
469
+ else headers.delete("cookie");
470
+ headers.delete("authorization");
471
+ headers.delete("x-forwarded-user");
472
+ return { request: new Request(request, { headers }), stateCookieName };
473
+ }
474
+
475
+ export function managedAuthOAuthReturnMatches(
476
+ raw: string,
477
+ expectedOrigin: string,
478
+ transactionId: string,
479
+ outcome: "complete" | "error",
480
+ ): boolean {
481
+ try {
482
+ const url = new URL(raw);
483
+ return (
484
+ url.origin === expectedOrigin &&
485
+ url.pathname === "/account-auth" &&
486
+ url.searchParams.size === 2 &&
487
+ url.searchParams.get("transaction") === transactionId &&
488
+ url.searchParams.get("social") === outcome &&
489
+ !url.hash
490
+ );
491
+ } catch {
492
+ return false;
493
+ }
494
+ }
495
+
496
+ function cookiePair(header: string | null, name: string): string | null {
497
+ for (const part of header?.split(";") ?? []) {
498
+ const separator = part.indexOf("=");
499
+ if (separator < 0 || part.slice(0, separator).trim() !== name) continue;
500
+ return `${name}=${part.slice(separator + 1).trim()}`;
501
+ }
502
+ return null;
503
+ }
504
+
345
505
  function betterAuthBaseUrl(settings: Settings) {
346
506
  const allowedHosts = splitCsv(settings.betterAuthAllowedHosts);
347
507
  if (allowedHosts.length === 0) {