Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions docker/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,6 @@ BETTER_AUTH_SECRET=
# Per-tenant concurrent in-flight request limit for each backend process.
# MANIFEST_CONCURRENCY_MAX=10

# Managed free providers (optional). A master key enables automatic
# virtual-key provisioning; users can otherwise paste a key obtained through
# the provider's "Get API key" link.
# CREDITS_BASE_URL=https://credits.manifest.build
# CREDITS_MASTER_KEY=
# CREDITS_AUTO_PROVISION_ALLOWLIST= # Optional comma-separated user emails
# CREDITS_GEMINI_FREE_MAX_BUDGET=10 # Budget in USD for each generated key

# ─── Email provider (optional) ─────────────────────────────────────────────
#
# Unified config — used for BOTH login emails (signup verification, password
Expand Down
18 changes: 2 additions & 16 deletions docs/dev/manifest-guide.md

Large diffs are not rendered by default.

11 changes: 0 additions & 11 deletions packages/backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,6 @@ BETTER_AUTH_URL=http://localhost:3001
# MANIFEST_RATE_MAX_REQUESTS=200
# MANIFEST_IP_RATE_MAX_REQUESTS=500

# ── Managed free providers (optional) ───────────────
# Managed gateway used by providers such as Gemini Free. A master key enables
# automatic virtual-key provisioning; without it, users can still paste a
# virtual key obtained through the provider's "Get API key" link.
# CREDITS_BASE_URL=https://credits.manifest.build
# CREDITS_MASTER_KEY=
# CREDITS_AUTO_PROVISION_ALLOWLIST= # Optional comma-separated user emails
# CREDITS_GEMINI_FREE_MAX_BUDGET=10 # Budget in USD for each generated key

# ── Email provider (optional, unified) ──────────────
# Used for BOTH Better Auth transactional emails (login verification,
# password reset) and threshold alert notifications when no per-user
Expand Down Expand Up @@ -144,8 +135,6 @@ BETTER_AUTH_URL=http://localhost:3001
# ── Internal ────────────────────────────────────────
# MANIFEST_FRONTEND_DIR= # Custom path to frontend dist directory
# MANIFEST_EMBEDDED= # Set to skip auto-start (used by embedded server)
# ERROR_PAGE_PUSH_SECRET= # Shared secret for /api/v1/internal/error-pages (CMS push API)
# CRM_METRICS_SECRET= # Cloud only. Secret for /api/v1/internal/crm-metrics; min 32 chars, exports user emails

# ── Release announcement email (Auto-fix / Doctor version) ──
# The sending tool lives OUTSIDE this repo (sebconejo/autofix-email-to-subscribers);
Expand Down
17 changes: 0 additions & 17 deletions packages/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,12 @@ import { RoutingModule } from './routing/routing.module';
import { PlaygroundModule } from './playground/playground.module';
import { CommonModule } from './common/common.module';
import { SseModule } from './sse/sse.module';
import { GithubModule } from './github/github.module';
import { VersionModule } from './version/version.module';
import { ErrorPagesModule } from './error-pages/error-pages.module';
import { SetupModule } from './setup/setup.module';
import { FreeModelsModule } from './free-models/free-models.module';
import { TelemetryModule } from './telemetry/telemetry.module';
import { WaitlistModule } from './waitlist/waitlist.module';
import { DiscoveryModule } from './discovery/discovery.module';
import { CrmMetricsModule } from './crm-metrics/crm-metrics.module';
import { McpModule } from './mcp/mcp.module';
import { mcpAvailability } from './auth/mcp-availability';
import { isSelfHosted } from './common/utils/detect-self-hosted';
import { DebugSentryController } from './sentry/debug-sentry.controller';

const frontendPath = resolveFrontendDir();
Expand Down Expand Up @@ -69,12 +63,6 @@ const sentryProviders = sentryEnabled
const sentryDebugControllers =
sentryEnabled && process.env['NODE_ENV'] !== 'production' ? [DebugSentryController] : [];

// The CRM metrics feed drives Cloud outreach. Leaving it unregistered on
// self-hosted means the routes do not exist there at all, rather than existing
// and answering 401 forever, and pairs with migration 1802200000000 skipping
// its index so a self-hosted install sees no trace of this feature.
const crmMetricsImports = isSelfHosted() ? [] : [CrmMetricsModule];

// The remote MCP server is off on installs whose origin cannot carry an MCP
// resource, and on installs that set MCP_ENABLED=false. Leaving the module
// unregistered means `/api/v1/mcp` does not exist rather than answering an
Expand Down Expand Up @@ -110,17 +98,12 @@ const mcpImports = mcpAvailability().enabled ? [McpModule] : [];
RoutingModule,
PlaygroundModule,
SseModule,
GithubModule,
VersionModule,
ErrorPagesModule,
SetupModule,
FreeModelsModule,
TelemetryModule,
BackfillModule,
WaitlistModule,
DiscoveryModule,
...mcpImports,
...crmMetricsImports,
],
providers: [
...sentryProviders,
Expand Down

This file was deleted.

85 changes: 0 additions & 85 deletions packages/backend/src/common/constants/managed-free-providers.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/backend/src/common/utils/dashboard-url.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** Dashboard origin only; API and waitlist destinations are configured separately. */
/** Dashboard origin only; API destinations are configured separately. */
export const DEFAULT_DASHBOARD_URL = 'https://app.manifest.build';

/** Emails need absolute URLs because they are read outside the dashboard. */
Expand Down
16 changes: 0 additions & 16 deletions packages/backend/src/config/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,6 @@ export const appConfig = registerAs('app', () => ({
// routing during the post-SIGTERM deregistration lag (the rolling-deploy 5xx
// spike). Must be shorter than Railway's `drainingSeconds`. 0 disables it.
shutdownDrainMs: Number(process.env['SHUTDOWN_DRAIN_MS'] ?? 10000),
// When true, /api/v1/public/error-pages serves the published error pages
// without auth. Off by default; only Manifest Cloud's marketing site uses it.
// The variable keeps its historical name so existing deployments stay on.
publicErrorPagesEnabled: process.env['MANIFEST_PUBLIC_STATS'] === 'true',
// Optional instance-wide override. When unset, request recordings are kept
// for the 365-day default.
requestRecordingRetentionDays: optionalPositiveInteger(
Expand All @@ -72,18 +68,6 @@ export const appConfig = registerAs('app', () => ({
requestRecordingS3AccessKeyId: process.env['REQUEST_RECORDING_S3_ACCESS_KEY_ID'] ?? '',
requestRecordingS3SecretAccessKey: process.env['REQUEST_RECORDING_S3_SECRET_ACCESS_KEY'] ?? '',
requestRecordingS3ForcePathStyle: process.env['REQUEST_RECORDING_S3_FORCE_PATH_STYLE'] === 'true',
// Shared secret guarding the internal error-page push endpoint
// (/api/v1/internal/error-pages). The Peacock CMS sends it in the
// `x-internal-secret` header to publish/unpublish curated error pages.
// Empty by default — the endpoint rejects all writes until it is set.
errorPagePushSecret: process.env['ERROR_PAGE_PUSH_SECRET'] ?? '',
// Shared secret guarding the internal CRM metrics feed
// (/api/v1/internal/crm-metrics), which the outreach CRM polls daily. Sent in
// the `x-internal-secret` header. Deliberately separate from
// errorPagePushSecret: different consumer, different credential. Empty by
// default, and anything shorter than 32 chars counts as unset — this route
// exports user email addresses across tenants.
crmMetricsSecret: process.env['CRM_METRICS_SECRET'] ?? '',
// Sliding lifetime (days) of a CLI-minted management PAT. Every successful
// authentication pushes the key's `expires_at` this far into the future, so
// an active CLI never has to re-login and an abandoned one lapses.
Expand Down
30 changes: 0 additions & 30 deletions packages/backend/src/cors-csp-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,33 +270,3 @@ describe('applyPrivateNetworkAllow', () => {
expect(setHeader).not.toHaveBeenCalled();
});
});

describe('applyPivotClaimCors', () => {
const { applyPivotClaimCors, PIVOT_CLAIM_PATH } = jest.requireActual('./cors-csp-config');

function collect() {
const headers: Record<string, string> = {};
return { headers, set: (name: string, value: string) => (headers[name] = value) };
}

it('ignores other routes entirely', () => {
const { headers, set } = collect();
expect(applyPivotClaimCors({ method: 'POST', path: '/api/v1/overview' }, set)).toBe(false);
expect(headers).toEqual({});
});

it('opens CORS on the claim POST without ending the response', () => {
const { headers, set } = collect();
expect(applyPivotClaimCors({ method: 'POST', path: PIVOT_CLAIM_PATH }, set)).toBe(false);
expect(headers['Access-Control-Allow-Origin']).toBe('*');
expect(headers['Access-Control-Allow-Methods']).toBe('POST, OPTIONS');
expect(headers['Access-Control-Allow-Headers']).toBe('Content-Type');
});

it('fully answers the claim preflight', () => {
const { headers, set } = collect();
expect(applyPivotClaimCors({ method: 'OPTIONS', path: PIVOT_CLAIM_PATH }, set)).toBe(true);
expect(headers['Access-Control-Allow-Origin']).toBe('*');
expect(headers['Access-Control-Max-Age']).toBe('600');
});
});
36 changes: 0 additions & 36 deletions packages/backend/src/cors-csp-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,39 +182,3 @@ export function applyPrivateNetworkAllow(
if (typeof origin !== 'string' || !allowedOrigins.includes(origin)) return;
setHeader('Access-Control-Allow-Private-Network', 'true');
}

/** Route path of the public pivot waiting-list claim. */
export const PIVOT_CLAIM_PATH = '/api/v1/waitlist/pivot/claim';

export interface PivotCorsRequest {
method: string;
path: string;
}

/**
* Open CORS for the pivot waiting-list claim only. Self-hosted dashboards on
* versions that still ship the waiting-list card post the claim straight
* from the browser, so any origin must be allowed on
* this one route. Safe because no credentials ride along (`fetch` sends none
* cross-origin by default and the allow-list CORS runs with
* `credentials: false`) and the route only accepts an email. Returns true
* when it fully answered a preflight, so the caller ends the response.
*/
export function applyPivotClaimCors(
req: PivotCorsRequest,
setHeader: (name: string, value: string) => void,
): boolean {
if (req.path !== PIVOT_CLAIM_PATH) return false;
setHeader('Access-Control-Allow-Origin', '*');
setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
setHeader('Access-Control-Allow-Headers', 'Content-Type');
setHeader('Access-Control-Max-Age', '600');
return req.method === 'OPTIONS';
}

/**
* Origin of the cloud pivot claim endpoint. Production self-hosted dashboards
* post the claim cross-origin from the browser, so the CSP `connect-src`
* must allow it — CORS alone is not enough, the CSP blocks the fetch first.
*/
export const PIVOT_CLAIM_CLOUD_ORIGIN = 'https://app.manifest.build';
Loading
Loading