@taskforcehq/taskforce 0.3.304 → 0.3.305
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/README.md +140 -247
- package/dist/TaskforceCore.js +165 -70
- package/dist/TaskforceCore.test.js +799 -90
- package/dist/compat/workspaceSyncCompat.js +6 -0
- package/dist/components/features/DocumentIndex.d.ts +1 -1
- package/dist/components/features/DocumentIndex.js +1 -1
- package/dist/components/features/DocumentViewer.d.ts +1 -1
- package/dist/components/features/DocumentViewer.js +32 -9
- package/dist/components/features/DocumentWorkspace.d.ts +5 -22
- package/dist/components/features/DocumentWorkspace.js +12 -155
- package/dist/components/features/DocumentWorkspace.test.js +193 -6
- package/dist/components/features/TaskSettings.js +29 -3
- package/dist/components/features/TaskSettings.test.js +42 -1
- package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
- package/dist/components/features/documentWorkspaceModel.js +57 -0
- package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
- package/dist/components/features/useDocumentWorkspaceController.js +113 -0
- package/dist/components/task/TaskForm.d.ts +8 -8
- package/dist/components/task/TaskForm.js +358 -221
- package/dist/components/task/TaskForm.test.js +456 -381
- package/dist/components/task/TaskKanban.js +2 -1
- package/dist/components/views/PlanComparisonPage.js +5 -8
- package/dist/components/views/PlanComparisonPage.test.js +51 -41
- package/dist/components/views/PlansPage.js +126 -84
- package/dist/components/views/PlansPage.test.js +308 -39
- package/dist/components/views/StandaloneLayout.js +28 -22
- package/dist/components/views/panels/FilterToolbar.test.js +6 -4
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
- package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
- package/dist/config/envSchema.js +1 -1
- package/dist/core/AiProfileService.d.ts +6 -1
- package/dist/core/AiProfileService.js +161 -16
- package/dist/core/AiProfileService.test.js +3 -1
- package/dist/core/AiProfiles.test.js +200 -0
- package/dist/core/AuthTokenService.test.d.ts +1 -0
- package/dist/core/AuthTokenService.test.js +78 -0
- package/dist/core/EntitlementsPolicy.test.js +75 -13
- package/dist/core/PlanEntitlementService.d.ts +7 -1
- package/dist/core/PlanEntitlementService.js +117 -42
- package/dist/core/PlanVersionPolicy.test.js +19 -26
- package/dist/core/SignupMonetizationSettings.test.js +46 -21
- package/dist/core/SystemAdmin.test.js +212 -56
- package/dist/core/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +3 -0
- package/dist/core/Taskforce.js +50 -10
- package/dist/core/WorkspacePlanMode.test.js +12 -12
- package/dist/core/shared.d.ts +2 -0
- package/dist/core/shared.js +11 -0
- package/dist/hooks/sync/auth.js +3 -1
- package/dist/hooks/sync/transfers.d.ts +2 -1
- package/dist/hooks/sync/transfers.js +8 -4
- package/dist/hooks/useSyncOrchestrator.d.ts +7 -4
- package/dist/hooks/useSyncOrchestrator.js +75 -7
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +80 -10
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +12 -1
- package/dist/hooks/useTaskMutations.test.js +30 -1
- package/dist/hooks/useTaskforce.d.ts +5 -2
- package/dist/hooks/useTaskforce.js +17 -5
- package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
- package/dist/hooks/useWorkspaceSyncController.d.ts +3 -1
- package/dist/hooks/useWorkspaceSyncController.js +11 -1
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- package/dist/mcp/collaborationRegistrar.d.ts +2 -0
- package/dist/mcp/collaborationRegistrar.js +71 -0
- package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
- package/dist/mcp/documentAssetRegistrar.js +346 -0
- package/dist/mcp/runtime.js +2362 -3530
- package/dist/mcp/runtime.test.js +159 -0
- package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
- package/dist/mcp/taskPlanningRegistrar.js +418 -0
- package/dist/mcp/toolCatalog.d.ts +35 -0
- package/dist/mcp/toolCatalog.js +3 -0
- package/dist/server/routes/admin.d.ts +1 -5
- package/dist/server/routes/admin.js +138 -61
- package/dist/server/routes/annotatedAttachments.d.ts +1 -1
- package/dist/server/routes/annotatedAttachments.js +1 -1
- package/dist/server/routes/auth.d.ts +1 -4
- package/dist/server/routes/auth.js +23 -63
- package/dist/server/routes/authSupport.d.ts +30 -0
- package/dist/server/routes/authSupport.js +81 -0
- package/dist/server/routes/billing.d.ts +1 -1
- package/dist/server/routes/billing.js +104 -188
- package/dist/server/routes/billing.test.js +214 -86
- package/dist/server/routes/documentReviews.d.ts +1 -1
- package/dist/server/routes/documentReviews.js +1 -1
- package/dist/server/routes/documents.d.ts +4 -2
- package/dist/server/routes/documents.js +73 -7
- package/dist/server/routes/primitives.d.ts +11 -0
- package/dist/server/routes/primitives.js +73 -0
- package/dist/server/routes/resources.d.ts +1 -1
- package/dist/server/routes/resources.js +1 -1
- package/dist/server/routes/shared.d.ts +1 -1
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.js +1 -1
- package/dist/server/routes/tasks.d.ts +1 -1
- package/dist/server/routes/tasks.js +1 -1
- package/dist/server/routes/workspaces.d.ts +1 -1
- package/dist/server/routes/workspaces.js +1 -1
- package/dist/server/routes.d.ts +2 -9
- package/dist/server/routes.js +76 -162
- package/dist/server/routes.test.js +672 -30
- package/dist/sync/contentSyncState.js +16 -7
- package/dist/sync/syncApplyHandlers.test.js +2 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +8 -0
- package/dist/sync/syncService.test.js +14 -0
- package/dist/sync/workspaceSyncModel.d.ts +3 -0
- package/dist/sync/workspaceSyncModel.js +12 -3
- package/dist/sync/workspaceSyncModel.test.js +34 -0
- package/dist/sync/workspaceSyncState.d.ts +2 -0
- package/dist/sync/workspaceSyncState.js +1 -0
- package/dist/ui/assets/{AgentsModule-BLWVbuKP.js → AgentsModule-N2MnQBZk.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BlS-cqnl.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
- package/dist/ui/assets/{InitiativesModule-BjR6iBf9.js → InitiativesModule-Dhm7M8e7.js} +1 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
- package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-DnFqdUME.js → WorkflowsModule-BcS4afRn.js} +1 -1
- package/dist/ui/assets/{index-Ii0B0rTO.css → index-C2-OXZV7.css} +1 -1
- package/dist/ui/assets/index-DcSI5F86.js +6 -0
- package/dist/ui/assets/{vendor-icons-I7ZwG1z_.js → vendor-icons-BSUaRwF8.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +196 -0
- package/package.json +8 -1
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/playwright-auth.sh +5 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- package/dist/ui/assets/DocumentWorkspace-BH_6DF6x.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +0 -1
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +0 -9
- package/dist/ui/assets/index-DXUdtH1B.js +0 -6
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
import * as fs from 'fs';
|
|
51
51
|
import * as path from 'path';
|
|
52
52
|
import { TaskforceRuleError } from '../../core/Taskforce.js';
|
|
53
|
-
import { parseJsonBody } from '
|
|
53
|
+
import { escapeHtml, parseFormBody, parseJsonBody } from './primitives.js';
|
|
54
54
|
import { resolveRequestAccess } from '../auth.js';
|
|
55
|
-
import {
|
|
55
|
+
import { sendEmail } from '../email.js';
|
|
56
56
|
import { WorkspaceAssetStore } from '../../storage/workspaceAssetStore.js';
|
|
57
57
|
import { scanDocumentIntegrity } from '../../storage/documentIntegrity.js';
|
|
58
58
|
import { normalizeStorageIntegrityScheduleSettings, resolveStorageIntegrityScheduleSettings, } from '../../storage/integritySchedule.js';
|
|
@@ -64,67 +64,18 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/
|
|
|
64
64
|
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
65
65
|
import { randomUUID } from 'crypto';
|
|
66
66
|
import { resolveDeploymentConfig } from '../../config/deployment.js';
|
|
67
|
-
import { getTaskforceCloudEnvironmentConfig, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../../config/cloudEnvironment.js';
|
|
67
|
+
import { getTaskforceCloudEnvironmentConfig, resolveTaskforceCloudEnvironmentFromBaseUrl, resolveTaskforceMcpBaseUrlFromBaseUrl } from '../../config/cloudEnvironment.js';
|
|
68
68
|
import { buildCloudMcpRuntimeAuth, resolveErrorStatusCode } from './shared.js';
|
|
69
|
+
import { buildAuthBehavior, buildSiteBehavior, getRouteEmailConfig, resolvePublicAuthBaseUrl, resolveRuntimeConfigBaseUrl, } from './authSupport.js';
|
|
69
70
|
import { applyWorkspaceRepair, scanWorkspaceRepair } from '../../sync/workspaceRepair.js';
|
|
70
71
|
// === SECTION: Registration ===
|
|
71
72
|
export function registerAdminRoutes(deps) {
|
|
72
|
-
const { addRoute, core, context, requestWorkspaceId, requestUserId, requestRole, requestHeader, requestRuntimeMode, ensureAppAccess, ensureAdminRole, ensureCloudPlanData, requireCloudRuntime, resolveAuthenticatedUserId, resolveAdminRequestActor, auditAdminAction, resolveEffectiveObjectStorageConfig, getObjectStorageClient, taskAssetStore, normalizeDocumentPathInput, ensureWithinDir, getAssetMetadata, markAssetDeleted, clearAssetDeleted, removeAssetMetadata, getStorageTelemetry, appendStorageTelemetry, getStorageIntegrityHistory, appendStorageIntegrityHistory, getDocumentPurgeHistory, appendDocumentPurgeHistory, getCollaborationTelemetry, isCollaborationPolicyEnabled,
|
|
73
|
+
const { addRoute, core, context, requestWorkspaceId, requestUserId, requestRole, requestHeader, requestRuntimeMode, ensureAppAccess, ensureAdminRole, ensureCloudPlanData, requireCloudRuntime, resolveAuthenticatedUserId, resolveAdminRequestActor, auditAdminAction, resolveEffectiveObjectStorageConfig, getObjectStorageClient, taskAssetStore, normalizeDocumentPathInput, ensureWithinDir, getAssetMetadata, markAssetDeleted, clearAssetDeleted, removeAssetMetadata, getStorageTelemetry, appendStorageTelemetry, getStorageIntegrityHistory, appendStorageIntegrityHistory, getDocumentPurgeHistory, appendDocumentPurgeHistory, getCollaborationTelemetry, isCollaborationPolicyEnabled, normalizeMcpOauthScopeString, MCP_TOKEN_SCOPE_VALUES, MCP_CLOUD_TOOL_SCOPE_REQUIREMENTS, MCP_WRITE_RATE_LIMIT_ENABLED, MCP_WRITE_RATE_LIMIT_ACTOR_MAX_REQUESTS, MCP_WRITE_RATE_LIMIT_WORKSPACE_MAX_REQUESTS, MCP_WRITE_RATE_LIMIT_WINDOW_MS, } = deps;
|
|
73
74
|
const baseVirusScanConfig = resolveVirusScanConfigFromEnv(process.env);
|
|
74
75
|
const workspaceAssetStore = typeof core.getDatabaseAdapter === 'function' && typeof core.getTenantId === 'function'
|
|
75
76
|
? new WorkspaceAssetStore(core.getDatabaseAdapter(), core.getTenantId())
|
|
76
77
|
: null;
|
|
77
78
|
// === SECTION: Email helpers ===
|
|
78
|
-
const getEmailConfig = () => {
|
|
79
|
-
if (context.authConfig?.runtimeMode !== 'cloud') {
|
|
80
|
-
return resolveEffectiveEmailConfig(process.env, { provider: 'console' });
|
|
81
|
-
}
|
|
82
|
-
const globalSettings = core.getGlobalSettings();
|
|
83
|
-
return resolveEffectiveEmailConfig(process.env, globalSettings.email || null);
|
|
84
|
-
};
|
|
85
|
-
const resolveBooleanSetting = (value, fallback) => {
|
|
86
|
-
if (typeof value === 'boolean')
|
|
87
|
-
return value;
|
|
88
|
-
if (typeof value === 'number')
|
|
89
|
-
return value !== 0;
|
|
90
|
-
const normalized = String(value || '').trim().toLowerCase();
|
|
91
|
-
if (!normalized)
|
|
92
|
-
return fallback;
|
|
93
|
-
if (['1', 'true', 'yes', 'on'].includes(normalized))
|
|
94
|
-
return true;
|
|
95
|
-
if (['0', 'false', 'no', 'off'].includes(normalized))
|
|
96
|
-
return false;
|
|
97
|
-
return fallback;
|
|
98
|
-
};
|
|
99
|
-
const getAuthBehavior = () => {
|
|
100
|
-
const globalSettings = core.getGlobalSettings();
|
|
101
|
-
const authSettings = globalSettings.auth || {};
|
|
102
|
-
const authRateLimitMaxRequestsRaw = Number(authSettings.authRateLimitMaxRequests);
|
|
103
|
-
const authRateLimitWindowMsRaw = Number(authSettings.authRateLimitWindowMs);
|
|
104
|
-
const onboardingPolicy = core.getOnboardingPolicy();
|
|
105
|
-
return {
|
|
106
|
-
allowSelfRegistration: resolveBooleanSetting(authSettings.allowSelfRegistration, true),
|
|
107
|
-
requireVerifiedEmail: resolveBooleanSetting(authSettings.requireVerifiedEmail, true),
|
|
108
|
-
defaultNewUserBetaAccess: resolveBooleanSetting(authSettings.defaultNewUserBetaAccess, false),
|
|
109
|
-
authRateLimitEnabled: resolveBooleanSetting(authSettings.authRateLimitEnabled, true),
|
|
110
|
-
authRateLimitMaxRequests: Number.isFinite(authRateLimitMaxRequestsRaw) && authRateLimitMaxRequestsRaw >= 1
|
|
111
|
-
? Math.floor(authRateLimitMaxRequestsRaw)
|
|
112
|
-
: 10,
|
|
113
|
-
authRateLimitWindowMs: Number.isFinite(authRateLimitWindowMsRaw) && authRateLimitWindowMsRaw >= 1000
|
|
114
|
-
? Math.floor(authRateLimitWindowMsRaw)
|
|
115
|
-
: 15 * 60_000,
|
|
116
|
-
onboardingPolicyMode: onboardingPolicy.mode,
|
|
117
|
-
onboardingPlanVersionId: onboardingPolicy.planVersionId
|
|
118
|
-
};
|
|
119
|
-
};
|
|
120
|
-
const getSiteBehavior = () => {
|
|
121
|
-
const globalSettings = core.getGlobalSettings();
|
|
122
|
-
const siteSettings = globalSettings.site || {};
|
|
123
|
-
return {
|
|
124
|
-
showRunTaskforceLocally: resolveBooleanSetting(siteSettings.showRunTaskforceLocally, true),
|
|
125
|
-
pricingPageEnabled: resolveBooleanSetting(siteSettings.pricingPageEnabled, true)
|
|
126
|
-
};
|
|
127
|
-
};
|
|
128
79
|
const resolveEffectiveVirusScanSettings = () => {
|
|
129
80
|
const settings = core.getGlobalSettings().objectStorage || {};
|
|
130
81
|
const asyncEnabled = typeof settings.virusScanAsyncEnabled === 'boolean'
|
|
@@ -139,6 +90,7 @@ export function registerAdminRoutes(deps) {
|
|
|
139
90
|
};
|
|
140
91
|
};
|
|
141
92
|
// === SECTION: MCP helpers ===
|
|
93
|
+
const MCP_CONNECTION_TEST_TIMEOUT_MS = 5000;
|
|
142
94
|
const writeMcpHttpError = (res, statusCode, message) => {
|
|
143
95
|
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
144
96
|
res.end(JSON.stringify({
|
|
@@ -171,6 +123,112 @@ export function registerAdminRoutes(deps) {
|
|
|
171
123
|
const resolveMcpOauthTokenUrl = (req) => `${resolveMcpOauthIssuer(req)}/oauth/mcp/token`;
|
|
172
124
|
const resolveMcpOauthRegisterUrl = (req) => `${resolveMcpOauthIssuer(req)}/oauth/mcp/register`;
|
|
173
125
|
const resolveMcpOauthResourceUrl = (req) => `${resolveMcpOauthIssuer(req)}/mcp`;
|
|
126
|
+
const resolveCloudMcpEndpoint = (req) => {
|
|
127
|
+
const requestBaseUrl = resolveRuntimeConfigBaseUrl(req);
|
|
128
|
+
const deployment = resolveDeploymentConfig(process.env, { requestBaseUrl });
|
|
129
|
+
const inferredMcpBaseUrl = resolveTaskforceMcpBaseUrlFromBaseUrl(deployment.cloudMcpBaseUrl
|
|
130
|
+
|| deployment.cloudAuthBaseUrl
|
|
131
|
+
|| deployment.cloudBaseUrl
|
|
132
|
+
|| requestBaseUrl
|
|
133
|
+
|| '');
|
|
134
|
+
const baseUrl = String(inferredMcpBaseUrl || deployment.cloudMcpBaseUrl || resolveMcpOauthIssuer(req)).trim().replace(/\/+$/, '');
|
|
135
|
+
return `${baseUrl}/mcp`;
|
|
136
|
+
};
|
|
137
|
+
const probeCloudMcpEndpoint = async (req, token) => {
|
|
138
|
+
const endpoint = resolveCloudMcpEndpoint(req);
|
|
139
|
+
const controller = new AbortController();
|
|
140
|
+
const timeout = setTimeout(() => controller.abort(), MCP_CONNECTION_TEST_TIMEOUT_MS);
|
|
141
|
+
try {
|
|
142
|
+
const response = await fetch(endpoint, {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: {
|
|
145
|
+
Authorization: `Bearer ${token}`,
|
|
146
|
+
Accept: 'application/json, text/event-stream',
|
|
147
|
+
'Content-Type': 'application/json'
|
|
148
|
+
},
|
|
149
|
+
body: JSON.stringify({
|
|
150
|
+
jsonrpc: '2.0',
|
|
151
|
+
id: `mcp-connection-test-${randomUUID()}`,
|
|
152
|
+
method: 'initialize',
|
|
153
|
+
params: {
|
|
154
|
+
protocolVersion: '2025-03-26',
|
|
155
|
+
capabilities: {},
|
|
156
|
+
clientInfo: {
|
|
157
|
+
name: 'taskforce-settings-test',
|
|
158
|
+
version: '1.0.0'
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}),
|
|
162
|
+
signal: controller.signal
|
|
163
|
+
});
|
|
164
|
+
const responseText = await response.text();
|
|
165
|
+
let payload = null;
|
|
166
|
+
try {
|
|
167
|
+
payload = responseText ? JSON.parse(responseText) : null;
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
payload = null;
|
|
171
|
+
}
|
|
172
|
+
const remoteMessage = typeof payload?.error?.message === 'string'
|
|
173
|
+
? payload.error.message.trim()
|
|
174
|
+
: '';
|
|
175
|
+
if (!response.ok) {
|
|
176
|
+
if (response.status >= 500) {
|
|
177
|
+
return {
|
|
178
|
+
ok: false,
|
|
179
|
+
statusCode: 502,
|
|
180
|
+
code: 'MCP_ENDPOINT_GATEWAY_ERROR',
|
|
181
|
+
error: `Cloud MCP endpoint ${endpoint} returned ${response.status}. The dedicated MCP host may be degraded or unavailable.`,
|
|
182
|
+
details: {
|
|
183
|
+
endpoint,
|
|
184
|
+
upstreamStatus: response.status,
|
|
185
|
+
remoteMessage: remoteMessage || null
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
ok: false,
|
|
191
|
+
statusCode: 502,
|
|
192
|
+
code: 'MCP_ENDPOINT_REJECTED_VALIDATED_TOKEN',
|
|
193
|
+
error: `Cloud MCP endpoint ${endpoint} rejected a token that validated locally. This usually means the MCP service is stale, routed incorrectly, or out of sync with the auth database.`,
|
|
194
|
+
details: {
|
|
195
|
+
endpoint,
|
|
196
|
+
upstreamStatus: response.status,
|
|
197
|
+
remoteMessage: remoteMessage || null
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
ok: true,
|
|
203
|
+
endpoint,
|
|
204
|
+
serverName: typeof payload?.result?.serverInfo?.name === 'string'
|
|
205
|
+
? payload.result.serverInfo.name
|
|
206
|
+
: null,
|
|
207
|
+
protocolVersion: typeof payload?.result?.protocolVersion === 'string'
|
|
208
|
+
? payload.result.protocolVersion
|
|
209
|
+
: null
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
const timedOut = error?.name === 'AbortError';
|
|
214
|
+
return {
|
|
215
|
+
ok: false,
|
|
216
|
+
statusCode: 502,
|
|
217
|
+
code: timedOut ? 'MCP_ENDPOINT_TIMEOUT' : 'MCP_ENDPOINT_UNREACHABLE',
|
|
218
|
+
error: timedOut
|
|
219
|
+
? `Cloud MCP endpoint ${endpoint} did not respond within ${MCP_CONNECTION_TEST_TIMEOUT_MS}ms.`
|
|
220
|
+
: `Could not reach cloud MCP endpoint ${endpoint}. The dedicated MCP host may be unavailable or misconfigured.`,
|
|
221
|
+
details: {
|
|
222
|
+
endpoint,
|
|
223
|
+
timeoutMs: MCP_CONNECTION_TEST_TIMEOUT_MS,
|
|
224
|
+
reason: String(error?.message || error || '')
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
clearTimeout(timeout);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
174
232
|
const renderMcpOauthAuthorizePage = (input) => {
|
|
175
233
|
const options = input.workspaces.map((workspace) => {
|
|
176
234
|
const selected = workspace.id === input.preselectedWorkspaceId ? ' selected' : '';
|
|
@@ -1013,7 +1071,7 @@ export function registerAdminRoutes(deps) {
|
|
|
1013
1071
|
return;
|
|
1014
1072
|
}
|
|
1015
1073
|
if (context.authConfig?.runtimeMode !== 'cloud') {
|
|
1016
|
-
const effective =
|
|
1074
|
+
const effective = getRouteEmailConfig(core, context);
|
|
1017
1075
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1018
1076
|
res.end(JSON.stringify({
|
|
1019
1077
|
settings: { provider: 'console', from: effective.from, replyTo: '' },
|
|
@@ -1022,7 +1080,7 @@ export function registerAdminRoutes(deps) {
|
|
|
1022
1080
|
return;
|
|
1023
1081
|
}
|
|
1024
1082
|
const settings = core.getGlobalSettings().email || {};
|
|
1025
|
-
const effective =
|
|
1083
|
+
const effective = getRouteEmailConfig(core, context);
|
|
1026
1084
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1027
1085
|
res.end(JSON.stringify({
|
|
1028
1086
|
settings: {
|
|
@@ -1047,7 +1105,7 @@ export function registerAdminRoutes(deps) {
|
|
|
1047
1105
|
return;
|
|
1048
1106
|
}
|
|
1049
1107
|
const settings = core.getGlobalSettings().email || {};
|
|
1050
|
-
const effective =
|
|
1108
|
+
const effective = getRouteEmailConfig(core, context);
|
|
1051
1109
|
const keyRequired = effective.provider === 'resend' || effective.provider === 'google-workspace';
|
|
1052
1110
|
const ready = keyRequired ? Boolean(effective.providerApiKey) : true;
|
|
1053
1111
|
const reason = ready
|
|
@@ -1079,7 +1137,7 @@ export function registerAdminRoutes(deps) {
|
|
|
1079
1137
|
const globalSettings = core.getGlobalSettings();
|
|
1080
1138
|
const authSettings = globalSettings.auth || {};
|
|
1081
1139
|
const billingSettings = globalSettings.billing || {};
|
|
1082
|
-
const effective =
|
|
1140
|
+
const effective = buildAuthBehavior(core);
|
|
1083
1141
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1084
1142
|
res.end(JSON.stringify({
|
|
1085
1143
|
settings: {
|
|
@@ -1117,7 +1175,7 @@ export function registerAdminRoutes(deps) {
|
|
|
1117
1175
|
return;
|
|
1118
1176
|
}
|
|
1119
1177
|
const siteSettings = core.getGlobalSettings().site || {};
|
|
1120
|
-
const siteBehavior =
|
|
1178
|
+
const siteBehavior = buildSiteBehavior(core);
|
|
1121
1179
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1122
1180
|
res.end(JSON.stringify({
|
|
1123
1181
|
settings: {
|
|
@@ -1842,7 +1900,7 @@ export function registerAdminRoutes(deps) {
|
|
|
1842
1900
|
res.end(JSON.stringify({ success: false, error: 'Valid test email address is required.' }));
|
|
1843
1901
|
return;
|
|
1844
1902
|
}
|
|
1845
|
-
const config =
|
|
1903
|
+
const config = getRouteEmailConfig(core, context);
|
|
1846
1904
|
const result = await sendEmail({
|
|
1847
1905
|
to,
|
|
1848
1906
|
subject: 'Taskforce email settings test',
|
|
@@ -2267,9 +2325,28 @@ export function registerAdminRoutes(deps) {
|
|
|
2267
2325
|
res.end(JSON.stringify({ success: false, error: 'Token is missing required scopes.', code: 'MCP_TOKEN_MISSING_SCOPES', details: { requiredScopes, missingScopes, grantedScopes: inspection.scopes } }));
|
|
2268
2326
|
return;
|
|
2269
2327
|
}
|
|
2328
|
+
const probe = await probeCloudMcpEndpoint(req, token);
|
|
2329
|
+
if (!probe.ok) {
|
|
2330
|
+
res.writeHead(probe.statusCode, { 'Content-Type': 'application/json' });
|
|
2331
|
+
res.end(JSON.stringify({ success: false, error: probe.error, code: probe.code, details: probe.details }));
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2270
2334
|
const workspace = core.getWorkspaceProfile(wid);
|
|
2271
2335
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2272
|
-
res.end(JSON.stringify({
|
|
2336
|
+
res.end(JSON.stringify({
|
|
2337
|
+
success: true,
|
|
2338
|
+
result: {
|
|
2339
|
+
tokenId: inspection.tokenId,
|
|
2340
|
+
tokenName: inspection.name,
|
|
2341
|
+
workspaceId: wid,
|
|
2342
|
+
workspaceName: String(workspace?.name || wid),
|
|
2343
|
+
scopes: inspection.scopes,
|
|
2344
|
+
requiredScopes,
|
|
2345
|
+
endpoint: probe.endpoint,
|
|
2346
|
+
serverName: probe.serverName,
|
|
2347
|
+
protocolVersion: probe.protocolVersion
|
|
2348
|
+
}
|
|
2349
|
+
}));
|
|
2273
2350
|
}
|
|
2274
2351
|
catch (error) {
|
|
2275
2352
|
const statusCode = resolveErrorStatusCode(error);
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { IncomingMessage, ServerResponse } from 'http';
|
|
15
15
|
import type { TaskforceCore } from '../../core/Taskforce.js';
|
|
16
|
-
import type
|
|
16
|
+
import { type RouteHandler } from './primitives.js';
|
|
17
17
|
export interface AnnotatedAttachmentRouteDependencies {
|
|
18
18
|
addRoute: (method: string, path: string, handler: RouteHandler) => void;
|
|
19
19
|
core: TaskforceCore;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* GET /api/taskforce/annotated-attachments/sessions/:id/payload
|
|
13
13
|
*/
|
|
14
14
|
import { ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, resolveAppGateAccess, } from '../../runtime/appGates.js';
|
|
15
|
-
import { parseJsonBody } from '
|
|
15
|
+
import { parseJsonBody } from './primitives.js';
|
|
16
16
|
import { resolveErrorStatusCode } from './shared.js';
|
|
17
17
|
import { getImageReferenceLabel, parseImageReference } from '../../utils/imageReferences.js';
|
|
18
18
|
function writeJson(res, statusCode, payload) {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
*/
|
|
38
38
|
import type { IncomingMessage, ServerResponse } from 'http';
|
|
39
39
|
import type { TaskforceCore } from '../../core/Taskforce.js';
|
|
40
|
-
import type
|
|
40
|
+
import { type RouteHandler, type RouteContext } from './primitives.js';
|
|
41
41
|
import type { ResolvedObjectStorageConfig } from '../../storage/objectStorage.js';
|
|
42
42
|
import { ObjectStorageClient } from '../../storage/objectStorageClient.js';
|
|
43
43
|
export interface AuthRouteDependencies {
|
|
@@ -58,9 +58,6 @@ export interface AuthRouteDependencies {
|
|
|
58
58
|
userId: string;
|
|
59
59
|
systemRole: 'system_admin' | 'user';
|
|
60
60
|
};
|
|
61
|
-
shouldExposeDevAuthTokens: () => boolean;
|
|
62
|
-
resolveRuntimeConfigBaseUrl: (req: IncomingMessage) => string;
|
|
63
|
-
resolvePublicAuthBaseUrl: (req: IncomingMessage) => string;
|
|
64
61
|
resolveEffectiveObjectStorageConfig: () => ResolvedObjectStorageConfig;
|
|
65
62
|
getObjectStorageClient: () => ObjectStorageClient | null;
|
|
66
63
|
}
|
|
@@ -38,36 +38,23 @@
|
|
|
38
38
|
import * as fs from 'fs';
|
|
39
39
|
import * as path from 'path';
|
|
40
40
|
import { randomUUID } from 'crypto';
|
|
41
|
-
import { parseJsonBody } from '
|
|
41
|
+
import { parseJsonBody } from './primitives.js';
|
|
42
42
|
import { hasValidSession, createClearSessionCookie, createSessionTokenCookie, resolveRequestAccess, resolveSessionCookieAttributesForRequest, } from '../auth.js';
|
|
43
|
-
import {
|
|
43
|
+
import { sendEmail } from '../email.js';
|
|
44
44
|
import { resolveDeploymentConfig } from '../../config/deployment.js';
|
|
45
45
|
import { resolveRuntimeModeContract } from '../../shared/runtimeContract.js';
|
|
46
46
|
import { requestHeader, resolveErrorStatusCode } from './shared.js';
|
|
47
|
+
import { buildAuthFlowBehavior, buildSiteBehavior, getRouteEmailConfig, resolvePublicAuthBaseUrl, resolveRuntimeConfigBaseUrl, shouldExposeDevAuthTokens } from './authSupport.js';
|
|
47
48
|
import { normalizeTaskDataRelativePath } from '../../storage/objectStorageClient.js';
|
|
48
49
|
// === SECTION: Registration ===
|
|
49
50
|
export function registerAuthRoutes(deps) {
|
|
50
|
-
const { addRoute, core, context, requestWorkspaceId, requestUserId, ensureAppAccess, ensureAuthenticatedUser, ensureWorkspaceAdminRole, ensureCloudPlanData, requireCloudRuntime, auditAuthAction, resolveAdminRequestActor,
|
|
51
|
+
const { addRoute, core, context, requestWorkspaceId, requestUserId, ensureAppAccess, ensureAuthenticatedUser, ensureWorkspaceAdminRole, ensureCloudPlanData, requireCloudRuntime, auditAuthAction, resolveAdminRequestActor, resolveEffectiveObjectStorageConfig, getObjectStorageClient, } = deps;
|
|
51
52
|
const normalizeTeamWorkspaceRole = (value) => {
|
|
52
53
|
const role = String(value || '').trim().toLowerCase();
|
|
53
54
|
if (role === 'owner' || role === 'admin' || role === 'member' || role === 'read-only')
|
|
54
55
|
return role;
|
|
55
56
|
return null;
|
|
56
57
|
};
|
|
57
|
-
const resolveBooleanSetting = (value, fallback) => {
|
|
58
|
-
if (typeof value === 'boolean')
|
|
59
|
-
return value;
|
|
60
|
-
if (typeof value === 'number')
|
|
61
|
-
return value !== 0;
|
|
62
|
-
const normalized = String(value || '').trim().toLowerCase();
|
|
63
|
-
if (!normalized)
|
|
64
|
-
return fallback;
|
|
65
|
-
if (['1', 'true', 'yes', 'on'].includes(normalized))
|
|
66
|
-
return true;
|
|
67
|
-
if (['0', 'false', 'no', 'off'].includes(normalized))
|
|
68
|
-
return false;
|
|
69
|
-
return fallback;
|
|
70
|
-
};
|
|
71
58
|
const resolveTeamPlanModeFromEntitlements = (payload) => {
|
|
72
59
|
const state = String(payload?.state || '').trim().toLowerCase();
|
|
73
60
|
const teamAllowed = payload?.features?.['collaboration.team_management']?.allowed === true;
|
|
@@ -190,42 +177,6 @@ export function registerAuthRoutes(deps) {
|
|
|
190
177
|
};
|
|
191
178
|
};
|
|
192
179
|
// === SECTION: Email delivery helpers ===
|
|
193
|
-
const getEmailConfig = () => {
|
|
194
|
-
if (context.authConfig?.runtimeMode !== 'cloud') {
|
|
195
|
-
return resolveEffectiveEmailConfig(process.env, { provider: 'console' });
|
|
196
|
-
}
|
|
197
|
-
const globalSettings = core.getGlobalSettings();
|
|
198
|
-
return resolveEffectiveEmailConfig(process.env, globalSettings.email || null);
|
|
199
|
-
};
|
|
200
|
-
const getAuthBehavior = () => {
|
|
201
|
-
const globalSettings = core.getGlobalSettings();
|
|
202
|
-
const authSettings = globalSettings.auth || {};
|
|
203
|
-
const authRateLimitMaxRequestsRaw = Number(authSettings.authRateLimitMaxRequests);
|
|
204
|
-
const authRateLimitWindowMsRaw = Number(authSettings.authRateLimitWindowMs);
|
|
205
|
-
const onboardingPolicy = core.getOnboardingPolicy();
|
|
206
|
-
return {
|
|
207
|
-
allowSelfRegistration: resolveBooleanSetting(authSettings.allowSelfRegistration, true),
|
|
208
|
-
requireVerifiedEmail: resolveBooleanSetting(authSettings.requireVerifiedEmail, true),
|
|
209
|
-
defaultNewUserBetaAccess: resolveBooleanSetting(authSettings.defaultNewUserBetaAccess, false),
|
|
210
|
-
authRateLimitEnabled: resolveBooleanSetting(authSettings.authRateLimitEnabled, true),
|
|
211
|
-
authRateLimitMaxRequests: Number.isFinite(authRateLimitMaxRequestsRaw) && authRateLimitMaxRequestsRaw >= 1
|
|
212
|
-
? Math.floor(authRateLimitMaxRequestsRaw)
|
|
213
|
-
: 10,
|
|
214
|
-
authRateLimitWindowMs: Number.isFinite(authRateLimitWindowMsRaw) && authRateLimitWindowMsRaw >= 1000
|
|
215
|
-
? Math.floor(authRateLimitWindowMsRaw)
|
|
216
|
-
: 15 * 60_000,
|
|
217
|
-
onboardingPolicyMode: onboardingPolicy.mode,
|
|
218
|
-
onboardingPlanVersionId: onboardingPolicy.planVersionId
|
|
219
|
-
};
|
|
220
|
-
};
|
|
221
|
-
const getSiteBehavior = () => {
|
|
222
|
-
const globalSettings = core.getGlobalSettings();
|
|
223
|
-
const siteSettings = globalSettings.site || {};
|
|
224
|
-
return {
|
|
225
|
-
showRunTaskforceLocally: resolveBooleanSetting(siteSettings.showRunTaskforceLocally, true),
|
|
226
|
-
pricingPageEnabled: resolveBooleanSetting(siteSettings.pricingPageEnabled, true)
|
|
227
|
-
};
|
|
228
|
-
};
|
|
229
180
|
const buildPasswordSessionCookieForUser = (req, authConfig, userIdRaw, options) => {
|
|
230
181
|
const userId = String(userIdRaw || '').trim();
|
|
231
182
|
const fallbackRole = String(options?.fallbackRole || authConfig.defaultRole || 'member').trim() || 'member';
|
|
@@ -314,7 +265,7 @@ export function registerAuthRoutes(deps) {
|
|
|
314
265
|
to: email,
|
|
315
266
|
subject,
|
|
316
267
|
text
|
|
317
|
-
},
|
|
268
|
+
}, getRouteEmailConfig(core, context));
|
|
318
269
|
if (result.ok) {
|
|
319
270
|
console.info(`[Taskforce Auth Email] kind=${kind} provider=${result.provider} to=${email} messageId=${result.messageId || '-'}`);
|
|
320
271
|
}
|
|
@@ -332,7 +283,7 @@ export function registerAuthRoutes(deps) {
|
|
|
332
283
|
to: email,
|
|
333
284
|
subject,
|
|
334
285
|
text
|
|
335
|
-
},
|
|
286
|
+
}, getRouteEmailConfig(core, context));
|
|
336
287
|
if (result.ok) {
|
|
337
288
|
console.info(`[Taskforce Auth Email] kind=invite provider=${result.provider} to=${email} messageId=${result.messageId || '-'}`);
|
|
338
289
|
}
|
|
@@ -368,7 +319,7 @@ export function registerAuthRoutes(deps) {
|
|
|
368
319
|
}));
|
|
369
320
|
});
|
|
370
321
|
addRoute('GET', '/api/taskforce/public/site-config', async (_req, res) => {
|
|
371
|
-
const siteBehavior =
|
|
322
|
+
const siteBehavior = buildSiteBehavior(core);
|
|
372
323
|
res.writeHead(200, {
|
|
373
324
|
'Content-Type': 'application/json',
|
|
374
325
|
'Cross-Origin-Resource-Policy': 'cross-origin'
|
|
@@ -379,7 +330,7 @@ export function registerAuthRoutes(deps) {
|
|
|
379
330
|
}));
|
|
380
331
|
});
|
|
381
332
|
addRoute('GET', '/api/taskforce/public/site-config-script', async (_req, res) => {
|
|
382
|
-
const siteBehavior =
|
|
333
|
+
const siteBehavior = buildSiteBehavior(core);
|
|
383
334
|
const payload = JSON.stringify(siteBehavior).replace(/</g, '\\u003c');
|
|
384
335
|
res.writeHead(200, {
|
|
385
336
|
'Content-Type': 'application/javascript; charset=utf-8',
|
|
@@ -705,7 +656,7 @@ export function registerAuthRoutes(deps) {
|
|
|
705
656
|
const workspaceId = workspaceIdCandidate || 'default';
|
|
706
657
|
// Password flow (preferred)
|
|
707
658
|
if (email && password) {
|
|
708
|
-
const authBehavior =
|
|
659
|
+
const authBehavior = buildAuthFlowBehavior(core);
|
|
709
660
|
let authResult = core.authenticatePasswordUserDetailed({
|
|
710
661
|
email,
|
|
711
662
|
password,
|
|
@@ -802,7 +753,7 @@ export function registerAuthRoutes(deps) {
|
|
|
802
753
|
const selectedPlanVersionId = typeof body?.planVersionId === 'string'
|
|
803
754
|
? body.planVersionId.trim()
|
|
804
755
|
: '';
|
|
805
|
-
const authBehavior =
|
|
756
|
+
const authBehavior = buildAuthFlowBehavior(core);
|
|
806
757
|
if (!authBehavior.allowSelfRegistration) {
|
|
807
758
|
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
808
759
|
res.end(JSON.stringify({ success: false, error: 'Self-registration is currently disabled.' }));
|
|
@@ -875,12 +826,14 @@ export function registerAuthRoutes(deps) {
|
|
|
875
826
|
&& authBehavior.requireVerifiedEmail
|
|
876
827
|
&& email
|
|
877
828
|
&& !core.isEmailVerified(email)) {
|
|
878
|
-
let emailSent =
|
|
829
|
+
let emailSent = false;
|
|
830
|
+
let deliveryAttempted = false;
|
|
879
831
|
let emailError;
|
|
880
832
|
let verificationToken;
|
|
881
833
|
const verification = core.requestEmailVerification({ email });
|
|
882
834
|
verificationToken = verification.token || null;
|
|
883
835
|
if (verification.token) {
|
|
836
|
+
deliveryAttempted = true;
|
|
884
837
|
const delivery = await deliverAuthEmail('verify-email', email, verification.token, req);
|
|
885
838
|
emailSent = delivery.ok;
|
|
886
839
|
emailError = delivery.error;
|
|
@@ -898,6 +851,7 @@ export function registerAuthRoutes(deps) {
|
|
|
898
851
|
code: 'EMAIL_ALREADY_EXISTS_UNVERIFIED',
|
|
899
852
|
verificationRequired: true,
|
|
900
853
|
emailSent,
|
|
854
|
+
deliveryAttempted,
|
|
901
855
|
emailError,
|
|
902
856
|
verificationToken: shouldExposeDevAuthTokens()
|
|
903
857
|
? (verificationToken || null)
|
|
@@ -930,10 +884,12 @@ export function registerAuthRoutes(deps) {
|
|
|
930
884
|
return;
|
|
931
885
|
}
|
|
932
886
|
try {
|
|
933
|
-
let emailSent =
|
|
887
|
+
let emailSent = false;
|
|
888
|
+
let deliveryAttempted = false;
|
|
934
889
|
let emailError;
|
|
935
890
|
const verification = core.requestEmailVerification({ email });
|
|
936
891
|
if (verification.token) {
|
|
892
|
+
deliveryAttempted = true;
|
|
937
893
|
const delivery = await deliverAuthEmail('verify-email', email, verification.token, req);
|
|
938
894
|
emailSent = delivery.ok;
|
|
939
895
|
emailError = delivery.error;
|
|
@@ -946,6 +902,7 @@ export function registerAuthRoutes(deps) {
|
|
|
946
902
|
res.end(JSON.stringify({
|
|
947
903
|
success: true,
|
|
948
904
|
emailSent,
|
|
905
|
+
deliveryAttempted,
|
|
949
906
|
emailError,
|
|
950
907
|
verificationToken: shouldExposeDevAuthTokens() ? (verification.token || null) : undefined
|
|
951
908
|
}));
|
|
@@ -1018,10 +975,12 @@ export function registerAuthRoutes(deps) {
|
|
|
1018
975
|
return;
|
|
1019
976
|
}
|
|
1020
977
|
try {
|
|
1021
|
-
let emailSent =
|
|
978
|
+
let emailSent = false;
|
|
979
|
+
let deliveryAttempted = false;
|
|
1022
980
|
let emailError;
|
|
1023
981
|
const reset = core.requestPasswordReset({ email });
|
|
1024
982
|
if (reset.token) {
|
|
983
|
+
deliveryAttempted = true;
|
|
1025
984
|
const delivery = await deliverAuthEmail('password-reset', email, reset.token, req);
|
|
1026
985
|
emailSent = delivery.ok;
|
|
1027
986
|
emailError = delivery.error;
|
|
@@ -1034,6 +993,7 @@ export function registerAuthRoutes(deps) {
|
|
|
1034
993
|
res.end(JSON.stringify({
|
|
1035
994
|
success: true,
|
|
1036
995
|
emailSent,
|
|
996
|
+
deliveryAttempted,
|
|
1037
997
|
emailError,
|
|
1038
998
|
resetToken: shouldExposeDevAuthTokens() ? (reset.token || null) : undefined
|
|
1039
999
|
}));
|
|
@@ -1389,7 +1349,7 @@ export function registerAuthRoutes(deps) {
|
|
|
1389
1349
|
seatUsage,
|
|
1390
1350
|
upgradeHints: canInvite
|
|
1391
1351
|
? []
|
|
1392
|
-
: ['Team management requires
|
|
1352
|
+
: ['Team management requires an active plan with collaboration access.'],
|
|
1393
1353
|
supportLink: 'mailto:support@taskforcehq.ai'
|
|
1394
1354
|
}));
|
|
1395
1355
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'http';
|
|
2
|
+
import type { TaskforceCore } from '../../core/Taskforce.js';
|
|
3
|
+
import type { RouteContext } from './primitives.js';
|
|
4
|
+
export declare function resolveBooleanSetting(value: unknown, fallback: boolean): boolean;
|
|
5
|
+
export declare function getRouteEmailConfig(core: TaskforceCore, context: RouteContext): import("../email.js").TaskforceEmailConfig;
|
|
6
|
+
export declare function buildAuthFlowBehavior(core: TaskforceCore): {
|
|
7
|
+
allowSelfRegistration: boolean;
|
|
8
|
+
requireVerifiedEmail: boolean;
|
|
9
|
+
defaultNewUserBetaAccess: boolean;
|
|
10
|
+
authRateLimitEnabled: boolean;
|
|
11
|
+
authRateLimitMaxRequests: number;
|
|
12
|
+
authRateLimitWindowMs: number;
|
|
13
|
+
};
|
|
14
|
+
export declare function buildAuthBehavior(core: TaskforceCore): {
|
|
15
|
+
onboardingPolicyMode: import("../../core/types.js").OnboardingPolicyMode;
|
|
16
|
+
onboardingPlanVersionId: string | null;
|
|
17
|
+
allowSelfRegistration: boolean;
|
|
18
|
+
requireVerifiedEmail: boolean;
|
|
19
|
+
defaultNewUserBetaAccess: boolean;
|
|
20
|
+
authRateLimitEnabled: boolean;
|
|
21
|
+
authRateLimitMaxRequests: number;
|
|
22
|
+
authRateLimitWindowMs: number;
|
|
23
|
+
};
|
|
24
|
+
export declare function buildSiteBehavior(core: TaskforceCore): {
|
|
25
|
+
showRunTaskforceLocally: boolean;
|
|
26
|
+
pricingPageEnabled: boolean;
|
|
27
|
+
};
|
|
28
|
+
export declare function shouldExposeDevAuthTokens(): boolean;
|
|
29
|
+
export declare function resolveRuntimeConfigBaseUrl(req: IncomingMessage): string;
|
|
30
|
+
export declare function resolvePublicAuthBaseUrl(req: IncomingMessage): string;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { resolveEffectiveEmailConfig } from '../email.js';
|
|
2
|
+
import { resolveDeploymentConfig } from '../../config/deployment.js';
|
|
3
|
+
export function resolveBooleanSetting(value, fallback) {
|
|
4
|
+
if (typeof value === 'boolean')
|
|
5
|
+
return value;
|
|
6
|
+
if (typeof value === 'number')
|
|
7
|
+
return value !== 0;
|
|
8
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
9
|
+
if (!normalized)
|
|
10
|
+
return fallback;
|
|
11
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized))
|
|
12
|
+
return true;
|
|
13
|
+
if (['0', 'false', 'no', 'off'].includes(normalized))
|
|
14
|
+
return false;
|
|
15
|
+
return fallback;
|
|
16
|
+
}
|
|
17
|
+
export function getRouteEmailConfig(core, context) {
|
|
18
|
+
if (context.authConfig?.runtimeMode !== 'cloud') {
|
|
19
|
+
return resolveEffectiveEmailConfig(process.env, { provider: 'console' });
|
|
20
|
+
}
|
|
21
|
+
const globalSettings = core.getGlobalSettings();
|
|
22
|
+
return resolveEffectiveEmailConfig(process.env, globalSettings.email || null);
|
|
23
|
+
}
|
|
24
|
+
export function buildAuthFlowBehavior(core) {
|
|
25
|
+
const globalSettings = core.getGlobalSettings();
|
|
26
|
+
const authSettings = globalSettings.auth || {};
|
|
27
|
+
const authRateLimitMaxRequestsRaw = Number(authSettings.authRateLimitMaxRequests);
|
|
28
|
+
const authRateLimitWindowMsRaw = Number(authSettings.authRateLimitWindowMs);
|
|
29
|
+
return {
|
|
30
|
+
allowSelfRegistration: resolveBooleanSetting(authSettings.allowSelfRegistration, true),
|
|
31
|
+
requireVerifiedEmail: resolveBooleanSetting(authSettings.requireVerifiedEmail, true),
|
|
32
|
+
defaultNewUserBetaAccess: resolveBooleanSetting(authSettings.defaultNewUserBetaAccess, false),
|
|
33
|
+
authRateLimitEnabled: resolveBooleanSetting(authSettings.authRateLimitEnabled, true),
|
|
34
|
+
authRateLimitMaxRequests: Number.isFinite(authRateLimitMaxRequestsRaw) && authRateLimitMaxRequestsRaw >= 1
|
|
35
|
+
? Math.floor(authRateLimitMaxRequestsRaw)
|
|
36
|
+
: 10,
|
|
37
|
+
authRateLimitWindowMs: Number.isFinite(authRateLimitWindowMsRaw) && authRateLimitWindowMsRaw >= 1000
|
|
38
|
+
? Math.floor(authRateLimitWindowMsRaw)
|
|
39
|
+
: 15 * 60_000,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function buildAuthBehavior(core) {
|
|
43
|
+
const authBehavior = buildAuthFlowBehavior(core);
|
|
44
|
+
const onboardingPolicy = core.getOnboardingPolicy();
|
|
45
|
+
return {
|
|
46
|
+
...authBehavior,
|
|
47
|
+
onboardingPolicyMode: onboardingPolicy.mode,
|
|
48
|
+
onboardingPlanVersionId: onboardingPolicy.planVersionId
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function buildSiteBehavior(core) {
|
|
52
|
+
const globalSettings = core.getGlobalSettings();
|
|
53
|
+
const siteSettings = globalSettings.site || {};
|
|
54
|
+
return {
|
|
55
|
+
showRunTaskforceLocally: resolveBooleanSetting(siteSettings.showRunTaskforceLocally, true),
|
|
56
|
+
pricingPageEnabled: resolveBooleanSetting(siteSettings.pricingPageEnabled, true)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function shouldExposeDevAuthTokens() {
|
|
60
|
+
const explicit = String(process.env.TASKFORCE_AUTH_EXPOSE_DEV_TOKENS || '').trim().toLowerCase();
|
|
61
|
+
return explicit === '1' || explicit === 'true' || explicit === 'yes';
|
|
62
|
+
}
|
|
63
|
+
export function resolveRuntimeConfigBaseUrl(req) {
|
|
64
|
+
const host = String(req.headers['x-forwarded-host'] || req.headers.host || '').trim();
|
|
65
|
+
if (!host)
|
|
66
|
+
return '';
|
|
67
|
+
const proto = String(req.headers['x-forwarded-proto'] || '').trim().toLowerCase() === 'https'
|
|
68
|
+
? 'https'
|
|
69
|
+
: (req.socket?.encrypted ? 'https' : 'http');
|
|
70
|
+
return `${proto}://${host}`.replace(/\/+$/, '');
|
|
71
|
+
}
|
|
72
|
+
export function resolvePublicAuthBaseUrl(req) {
|
|
73
|
+
const configured = String(process.env.TASKFORCE_AUTH_PUBLIC_BASE_URL || '').trim().replace(/\/+$/, '');
|
|
74
|
+
if (configured)
|
|
75
|
+
return configured;
|
|
76
|
+
const host = String(req.headers['x-forwarded-host'] || req.headers.host || '').trim();
|
|
77
|
+
const proto = String(req.headers['x-forwarded-proto'] || '').trim().toLowerCase() === 'http' ? 'http' : 'https';
|
|
78
|
+
if (!host)
|
|
79
|
+
return resolveDeploymentConfig(process.env).cloudBaseUrl || 'https://app.taskforcehq.ai';
|
|
80
|
+
return `${proto}://${host}`;
|
|
81
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage } from 'http';
|
|
2
2
|
import type { TaskforceCore } from '../../core/Taskforce.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type RouteHandler, type RouteContext } from './primitives.js';
|
|
4
4
|
export type BillingInterval = 'month' | 'year';
|
|
5
5
|
export type BillingPricingType = 'stripe' | 'free';
|
|
6
6
|
export interface BillingRouteDependencies {
|