@opengeni/api-router 4.1.2 → 5.0.0-canary.36239117573001

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 (81) hide show
  1. package/dist/app.d.ts +7 -1
  2. package/dist/app.js +1 -1
  3. package/dist/auth/managed-auth-rate-limits.d.ts +167 -0
  4. package/dist/auth/managed-auth.d.ts +37 -1
  5. package/dist/auth/signup-funnel-metrics.d.ts +44 -0
  6. package/dist/{chunk-2SSU5KWV.js → chunk-QDIBJSM4.js} +2655 -1323
  7. package/dist/{chunk-2SSU5KWV.js.map → chunk-QDIBJSM4.js.map} +1 -1
  8. package/dist/http/cors.d.ts +12 -0
  9. package/dist/http/local-browser-boundary.d.ts +72 -0
  10. package/dist/http/metrics-listener.d.ts +22 -0
  11. package/dist/http/registered-route-label.d.ts +16 -0
  12. package/dist/http/request-source.d.ts +41 -3
  13. package/dist/http/user-content.d.ts +36 -0
  14. package/dist/http/websocket-upgrade-dispatch.d.ts +18 -0
  15. package/dist/index.js +90 -31
  16. package/dist/index.js.map +1 -1
  17. package/dist/integrations/github-lens-connect.d.ts +1 -1
  18. package/dist/integrations/oauth-client.d.ts +30 -0
  19. package/dist/integrations/slack-bot.d.ts +48 -2
  20. package/dist/integrations/slack-interactions.d.ts +1 -1
  21. package/dist/mcp/first-party-tool-permissions.d.ts +4 -0
  22. package/dist/mcp/scheduled-task-view.d.ts +3 -3
  23. package/dist/mcp/server.d.ts +35 -0
  24. package/dist/model-catalog.d.ts +9 -3
  25. package/dist/observability.d.ts +3 -1
  26. package/dist/routes/billing.d.ts +27 -0
  27. package/dist/routes/client-errors.d.ts +42 -0
  28. package/dist/routes/computer-sessions.d.ts +2 -0
  29. package/dist/routes/github-browser-pages.d.ts +7 -0
  30. package/dist/sandbox/auth-callout.d.ts +8 -0
  31. package/package.json +19 -19
  32. package/src/app.ts +124 -12
  33. package/src/auth/managed-auth-rate-limits.ts +252 -0
  34. package/src/auth/managed-auth.ts +137 -17
  35. package/src/auth/signup-funnel-metrics.ts +164 -0
  36. package/src/http/auth.ts +6 -0
  37. package/src/http/cors.ts +37 -5
  38. package/src/http/local-browser-boundary.ts +343 -0
  39. package/src/http/metrics-listener.ts +57 -0
  40. package/src/http/registered-route-label.ts +34 -0
  41. package/src/http/request-source.ts +154 -13
  42. package/src/http/sse.ts +36 -16
  43. package/src/http/user-content.ts +158 -0
  44. package/src/http/websocket-upgrade-dispatch.ts +29 -0
  45. package/src/index.ts +22 -8
  46. package/src/integrations/atlassian.ts +7 -3
  47. package/src/integrations/fiken.ts +9 -3
  48. package/src/integrations/github-app-connect.ts +12 -2
  49. package/src/integrations/google-drive.ts +7 -3
  50. package/src/integrations/oauth-client.ts +99 -12
  51. package/src/integrations/personal-github.ts +4 -2
  52. package/src/integrations/provider-oauth.ts +10 -3
  53. package/src/integrations/slack-bot.ts +43 -1
  54. package/src/integrations/social-oauth.ts +7 -2
  55. package/src/mcp/files.ts +5 -1
  56. package/src/mcp/first-party-tool-permissions.ts +4 -0
  57. package/src/mcp/server.ts +140 -15
  58. package/src/mcp-oauth.ts +4 -6
  59. package/src/model-catalog.ts +30 -3
  60. package/src/observability.ts +15 -1
  61. package/src/routes/billing.ts +70 -9
  62. package/src/routes/browser-sessions.ts +29 -0
  63. package/src/routes/client-errors.ts +182 -0
  64. package/src/routes/company-brain.ts +4 -0
  65. package/src/routes/computer-sessions.ts +19 -8
  66. package/src/routes/connections.ts +7 -1
  67. package/src/routes/documents.ts +5 -1
  68. package/src/routes/editable-artifacts.ts +2 -1
  69. package/src/routes/enrollments.ts +2 -13
  70. package/src/routes/files.ts +13 -3
  71. package/src/routes/github-browser-pages.ts +60 -0
  72. package/src/routes/github.ts +116 -2
  73. package/src/routes/knowledge.ts +5 -1
  74. package/src/routes/managed-auth-session-sets.ts +26 -6
  75. package/src/routes/managed-onboarding.ts +23 -22
  76. package/src/routes/scheduled-tasks.ts +9 -2
  77. package/src/routes/workspace-artifacts.ts +9 -0
  78. package/src/routes/workspace-state.ts +4 -0
  79. package/src/routes/workspaces.ts +38 -15
  80. package/src/sandbox/auth-callout.ts +62 -11
  81. package/src/site-uploads.ts +8 -1
package/dist/app.d.ts CHANGED
@@ -22,7 +22,13 @@ export declare function appendVary(current: string | null, value: string): strin
22
22
  export declare function workspaceActorContextExempt(method: string, pathname: string): boolean;
23
23
  export declare function httpStatusForError(error: unknown): number;
24
24
  export declare function errorCodeForStatus(status: number): ErrorCode;
25
- export declare function routeLabel(pathname: string): string;
25
+ /**
26
+ * Bounded route label for metrics, spans, and request logs. Explicit patterns
27
+ * keep established label spellings stable; any other request answered by a
28
+ * registered handler uses that handler's code-owned path template (see
29
+ * `registeredHandlerRoutePath`). Only unregistered paths fall into `unknown`.
30
+ */
31
+ export declare function routeLabel(pathname: string, registeredRoutePath?: string | null): string;
26
32
  /**
27
33
  * State-changing OpenGeni HTTP calls must never cross an incompatible rollout
28
34
  * boundary. Standard third-party protocols and externally initiated callbacks
package/dist/app.js CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  withDefaultEnabledCapabilityMcpTools,
25
25
  workflowIdForSession,
26
26
  workspaceActorContextExempt
27
- } from "./chunk-2SSU5KWV.js";
27
+ } from "./chunk-QDIBJSM4.js";
28
28
  export {
29
29
  API_MAX_REQUEST_BODY_BYTES,
30
30
  allowedCorsOrigin,
@@ -0,0 +1,167 @@
1
+ import type { Database } from "@opengeni/db";
2
+ import { APIError } from "better-auth/api";
3
+ export type ManagedAuthClientRateLimitRule = {
4
+ window: number;
5
+ max: number;
6
+ };
7
+ /**
8
+ * Better Auth's per-client-address limits, keyed by endpoint path relative to
9
+ * `/v1/auth`. The client address is the trusted source address stamped by the
10
+ * API (see `TRUSTED_CLIENT_ADDRESS_HEADER`), never a caller-supplied header.
11
+ *
12
+ * Better Auth keys each counter on `(client address, path)` and resets it only
13
+ * after `window` seconds pass without an admitted request. Its database store
14
+ * prunes rows idle for longer than 60 s, so no window here exceeds 60 s; a
15
+ * longer window would silently shrink to that pruning horizon. The limits are
16
+ * sized for launch traffic that shares one address (office and carrier NAT)
17
+ * while bounding scripted credential guessing and mail sending from one
18
+ * source. Per-email throttles below cap the same abuse across many addresses.
19
+ *
20
+ * Email sign-in and sign-up allow 20 per minute, at least Better Auth's
21
+ * previous default (3 per 10 s, about 18 per minute). Until the edge forwards
22
+ * real client addresses (for example ingress-nginx behind a cloud load
23
+ * balancer with `externalTrafficPolicy: Cluster`), every user shares a few
24
+ * node addresses, and these two limits then bound the whole deployment's
25
+ * email sign-in and sign-up rate; they must not fall below that default.
26
+ */
27
+ export declare const MANAGED_AUTH_CLIENT_RATE_LIMIT_RULES: {
28
+ readonly "/sign-in/email": {
29
+ readonly window: 60;
30
+ readonly max: 20;
31
+ };
32
+ readonly "/sign-up/email": {
33
+ readonly window: 60;
34
+ readonly max: 20;
35
+ };
36
+ readonly "/sign-in/social": {
37
+ readonly window: 60;
38
+ readonly max: 20;
39
+ };
40
+ readonly "/callback/*": {
41
+ readonly window: 60;
42
+ readonly max: 30;
43
+ };
44
+ readonly "/request-password-reset": {
45
+ readonly window: 60;
46
+ readonly max: 5;
47
+ };
48
+ readonly "/send-verification-email": {
49
+ readonly window: 60;
50
+ readonly max: 5;
51
+ };
52
+ readonly "/verify-email": {
53
+ readonly window: 60;
54
+ readonly max: 20;
55
+ };
56
+ readonly "/reset-password": {
57
+ readonly window: 60;
58
+ readonly max: 10;
59
+ };
60
+ readonly "/reset-password/*": {
61
+ readonly window: 60;
62
+ readonly max: 20;
63
+ };
64
+ };
65
+ export type ManagedAuthEmailThrottle = {
66
+ purpose: "sign_in" | "sign_up" | "password_reset" | "verification_email";
67
+ windowSeconds: number;
68
+ /** Attempts one client address (an IPv6 /64) may make against one email. */
69
+ perSourceMax: number;
70
+ /** Attempts all client addresses together may make against one email. */
71
+ perEmailMax: number;
72
+ };
73
+ /**
74
+ * Per-email throttles for the endpoints that verify a password or send mail
75
+ * to an address. They bound password guessing against one account and mail
76
+ * bombing of one inbox regardless of how many client addresses an attacker
77
+ * rotates through. Every attempt counts, including successful sign-ins.
78
+ *
79
+ * Each email has two fixed windows. The tight one is per (email, client
80
+ * address); an attempt it refuses never reaches the looser per-email window.
81
+ * One source can therefore spend only its own share of an email's budget:
82
+ * locking a person out of email/password sign-in, sign-up, password reset, or
83
+ * verification mail takes at least `perEmailMax / perSourceMax` (5) distinct
84
+ * client addresses (IPv6: /64 blocks) inside the window, while one address
85
+ * can exhaust that email's budget only for requests from the same address.
86
+ * Social sign-in is never throttled per email. The accepted cost is that a
87
+ * distributed attacker may make up to `perEmailMax` attempts per window
88
+ * against one email, and can deny that email's password sign-in for the rest
89
+ * of the window once it spends them.
90
+ */
91
+ export declare const MANAGED_AUTH_EMAIL_THROTTLES: {
92
+ readonly "/sign-in/email": {
93
+ readonly purpose: "sign_in";
94
+ readonly windowSeconds: number;
95
+ readonly perSourceMax: 10;
96
+ readonly perEmailMax: 50;
97
+ };
98
+ readonly "/sign-up/email": {
99
+ readonly purpose: "sign_up";
100
+ readonly windowSeconds: number;
101
+ readonly perSourceMax: 5;
102
+ readonly perEmailMax: 25;
103
+ };
104
+ readonly "/request-password-reset": {
105
+ readonly purpose: "password_reset";
106
+ readonly windowSeconds: number;
107
+ readonly perSourceMax: 5;
108
+ readonly perEmailMax: 25;
109
+ };
110
+ readonly "/send-verification-email": {
111
+ readonly purpose: "verification_email";
112
+ readonly windowSeconds: number;
113
+ readonly perSourceMax: 5;
114
+ readonly perEmailMax: 25;
115
+ };
116
+ };
117
+ export declare const MANAGED_AUTH_EMAIL_THROTTLE_KEY_PREFIX = "opengeni:email-throttle:v1:";
118
+ /** A per-email throttle refusal; an ordinary Better Auth 429 on the wire. */
119
+ export declare class ManagedAuthEmailThrottleError extends APIError {
120
+ readonly retryAfterSeconds: number;
121
+ constructor(retryAfterSeconds: number);
122
+ }
123
+ export declare function managedAuthEmailThrottleFor(path: string): ManagedAuthEmailThrottle | null;
124
+ /**
125
+ * Content-free counter key for one email. The address is normalized the way
126
+ * Better Auth stores it and keyed-hashed so the rate-limit table never holds
127
+ * an email.
128
+ */
129
+ export declare function managedAuthEmailThrottleKey(secret: string, purpose: ManagedAuthEmailThrottle["purpose"], email: string): string;
130
+ /**
131
+ * Content-free counter key for one (email, client address) pair; `source` is
132
+ * a {@link requestSourceRateLimitKey}. Neither value is stored.
133
+ */
134
+ export declare function managedAuthEmailSourceThrottleKey(secret: string, purpose: ManagedAuthEmailThrottle["purpose"], email: string, source: string): string;
135
+ /**
136
+ * The limiter source for a Better Auth request: the trusted address the API
137
+ * stamped on it, as a rate-limit key. The API strips caller-supplied copies,
138
+ * so a request without one (an in-process call without a transport peer)
139
+ * shares the unknown-source bucket.
140
+ */
141
+ export declare function managedAuthThrottleSource(headers: Headers | null | undefined): string;
142
+ /**
143
+ * Atomically count one attempt in a fixed window shared by every API replica.
144
+ *
145
+ * Rows live in Better Auth's `auth_rate_limits` table. For these keys
146
+ * `last_request` holds the window deadline (epoch milliseconds) rather than
147
+ * the last request time: Better Auth prunes rows whose `last_request` is more
148
+ * than 60 s in the past, so storing the deadline keeps an active window alive
149
+ * and lets pruning reclaim it 60 s after it expires. Database time is the only
150
+ * clock, so replicas with skewed clocks agree.
151
+ */
152
+ export declare function consumeManagedAuthEmailThrottle(db: Pick<Database, "execute">, input: {
153
+ key: string;
154
+ windowSeconds: number;
155
+ max: number;
156
+ }): Promise<{
157
+ allowed: boolean;
158
+ retryAfterSeconds: number;
159
+ }>;
160
+ /**
161
+ * Better Auth `hooks.before` middleware. It runs after Better Auth's own
162
+ * per-client-address limiter and for both HTTP requests and server-side
163
+ * `auth.api.*` calls, so the product session-set sign-in is covered too. The
164
+ * per-(email, address) window is consumed first; only an attempt it admits
165
+ * counts toward the email's shared window.
166
+ */
167
+ export declare function createManagedAuthEmailThrottleHook(db: Pick<Database, "execute">, secret: string): (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<void>;
@@ -1,6 +1,8 @@
1
1
  import { type Settings } from "@opengeni/config";
2
2
  import { type ManagedAuth, type ManagedEmailMessage, type ManagedEmailTransport } from "@opengeni/core";
3
3
  import type { Database } from "@opengeni/db";
4
+ import type { Observability } from "@opengeni/observability";
5
+ import { Pool } from "pg";
4
6
  export type { ManagedAuth };
5
7
  export declare function managedAuthRequiresEmailVerification(settings: Pick<Settings, "environment">): boolean;
6
8
  export declare function managedAuthUserCreateOverride(settings: Pick<Settings, "environment">, user: {
@@ -16,7 +18,41 @@ export declare function managedAuthUserCreateAdmission(settings: Pick<Settings,
16
18
  /** Keep Better Auth password policy and storage format behind this boundary. */
17
19
  export declare function hashManagedAuthPassword(password: string): Promise<string>;
18
20
  export declare function verifyManagedAuthPassword(password: string, hash: string): Promise<boolean>;
19
- export declare function createManagedAuth(settings: Settings, db: Database, managedEmailTransport: ManagedEmailTransport): ManagedAuth | null;
21
+ type ManagedAuthPoolObservability = Pick<Observability, "warn" | "incrementCounter">;
22
+ /**
23
+ * Bounded pool settings for Better Auth's dedicated `pg` pool. A connection
24
+ * attempt fails after 10 s instead of queueing requests indefinitely while the
25
+ * database is unreachable, and connections recycle like the main postgres-js
26
+ * pool so a pooler- or failover-replaced backend is not held forever.
27
+ */
28
+ export declare const MANAGED_AUTH_DATABASE_POOL_OPTIONS: {
29
+ readonly max: 10;
30
+ readonly connectionTimeoutMillis: 10000;
31
+ readonly idleTimeoutMillis: 30000;
32
+ readonly maxLifetimeSeconds: 1800;
33
+ readonly keepAlive: true;
34
+ };
35
+ /**
36
+ * Better Auth's `pg` pool. When the server closes a connection (a restart,
37
+ * failover, or maintenance window) `pg` emits `error`: on the pool for an idle
38
+ * client, and on the client itself for one checked out by Better Auth's query
39
+ * layer. Without listeners either becomes an uncaught exception and the API's
40
+ * fatal process boundary exits. Both listeners record one content-free signal
41
+ * and keep serving. The pool has already discarded a failed idle client and
42
+ * opens a fresh connection for the next checkout; a checked-out client's
43
+ * in-flight query already rejects to its caller, and the pool discards the
44
+ * dead client on release. An idle client's error also reaches its own
45
+ * listener, so that listener records only while the client is checked out,
46
+ * and at most once per checkout.
47
+ */
48
+ export declare function createManagedAuthDatabasePool(databaseUrl: string, observability?: ManagedAuthPoolObservability): Pool;
49
+ export declare const MANAGED_AUTH_DATABASE_POOL_ERRORS_METRIC: {
50
+ readonly name: "opengeni_managed_auth_database_pool_errors_total";
51
+ readonly help: "Managed-auth database connections the server closed or lost, by pool state.";
52
+ };
53
+ export declare function createManagedAuth(settings: Settings, db: Database, managedEmailTransport: ManagedEmailTransport, options?: {
54
+ observability?: ManagedAuthPoolObservability;
55
+ }): ManagedAuth | null;
20
56
  export type ManagedAuthOAuthAttempt = {
21
57
  transactionId: string;
22
58
  provider: "google" | "github";
@@ -0,0 +1,44 @@
1
+ import type { Observability } from "@opengeni/observability";
2
+ /**
3
+ * Content-free sign-up funnel counters. Every label is a closed set: no user,
4
+ * email, organization, workspace, or campaign value ever becomes a label.
5
+ */
6
+ export declare const AUTH_FUNNEL_EVENTS: readonly ["sign_up", "email_verified", "sign_in"];
7
+ export type AuthFunnelEvent = (typeof AUTH_FUNNEL_EVENTS)[number];
8
+ export declare const AUTH_FUNNEL_METHODS: readonly ["email", "google", "github", "other"];
9
+ export type AuthFunnelMethod = (typeof AUTH_FUNNEL_METHODS)[number];
10
+ export declare const ORGANIZATION_SETUP_OUTCOMES: readonly ["created", "failed"];
11
+ export type OrganizationSetupOutcome = (typeof ORGANIZATION_SETUP_OUTCOMES)[number];
12
+ /** The only observability surface funnel telemetry uses. */
13
+ export type SignupFunnelObservability = Pick<Observability, "incrementCounter">;
14
+ /** Better Auth request-body / OAuth-state key that carries first-touch attribution. */
15
+ export declare const SIGNUP_ATTRIBUTION_AUTH_FIELD = "opengeniAttribution";
16
+ export type SignupFunnelMetrics = {
17
+ /** A managed auth user was created (Better Auth `user.create.after`). */
18
+ recordSignUp(authContext: unknown): Promise<void>;
19
+ /** A managed user verified their email address. */
20
+ recordEmailVerified(): void;
21
+ /**
22
+ * A provider session was created that is not an internally discarded one.
23
+ * In the legacy session-set mode this includes the session the first
24
+ * successful email-verification click creates: that is the new user's first
25
+ * sign-in, so it is counted rather than folded into `email_verified`.
26
+ */
27
+ recordSignIn(): void;
28
+ };
29
+ export declare function authFunnelMethod(providerId: string | null | undefined): AuthFunnelMethod;
30
+ /**
31
+ * Publish every closed series at zero so dashboards and alerts can tell a quiet
32
+ * funnel from missing instrumentation. Never throws: a metric-registry failure
33
+ * must not stop managed auth (and with it API startup) from composing.
34
+ */
35
+ export declare function registerSignupFunnelMetricBaselines(observability: SignupFunnelObservability): void;
36
+ export declare function createSignupFunnelMetrics(observability: SignupFunnelObservability): SignupFunnelMetrics;
37
+ /** Count one self-service organization setup request outcome. */
38
+ export declare function recordOrganizationSetupOutcome(observability: SignupFunnelObservability | undefined, outcome: OrganizationSetupOutcome): void;
39
+ /**
40
+ * Read untrusted first-touch attribution from the Better Auth endpoint that is
41
+ * creating the user: the email sign-up body, or the server-side OAuth state
42
+ * (`additionalData`) that the social start stored for its callback.
43
+ */
44
+ export declare function signupAttributionFromAuthContext(authContext: unknown): Promise<unknown>;