@taskforcehq/taskforce 0.3.303 → 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/Taskforce.module.css +102 -5
- package/dist/TaskforceCore.js +635 -238
- package/dist/TaskforceCore.test.js +2243 -495
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/compat/workspaceSyncCompat.js +6 -0
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- 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 +87 -41
- package/dist/components/features/TaskSettings.test.js +180 -40
- 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/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +9 -8
- package/dist/components/task/TaskForm.js +374 -219
- package/dist/components/task/TaskForm.test.js +482 -361
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +33 -161
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +9 -10
- package/dist/components/views/PlanComparisonPage.test.js +55 -41
- package/dist/components/views/PlansPage.d.ts +5 -1
- package/dist/components/views/PlansPage.js +150 -81
- package/dist/components/views/PlansPage.test.js +367 -41
- package/dist/components/views/StandaloneLayout.js +135 -54
- package/dist/components/views/WidgetView.js +11 -2
- 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/components/views/standalone/modals/SyncStatusModal.js +1 -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/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +75 -13
- package/dist/core/PlanEntitlementService.d.ts +9 -1
- package/dist/core/PlanEntitlementService.js +144 -19
- package/dist/core/PlanVersionPolicy.test.js +19 -26
- package/dist/core/SignupMonetizationSettings.test.js +75 -15
- package/dist/core/SystemAdmin.test.js +212 -56
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +18 -11
- package/dist/core/Taskforce.js +93 -12
- package/dist/core/WorkspacePlanMode.test.js +73 -8
- package/dist/core/shared.d.ts +2 -0
- package/dist/core/shared.js +11 -0
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +63 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +123 -0
- package/dist/hooks/sync/transfers.js +435 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +12 -8
- package/dist/hooks/useSyncOrchestrator.js +388 -879
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +641 -6
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +57 -7
- package/dist/hooks/useTaskMutations.test.js +176 -0
- package/dist/hooks/useTaskforce.d.ts +13 -4
- package/dist/hooks/useTaskforce.js +302 -817
- package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
- package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
- package/dist/hooks/useWorkspaceSyncController.js +33 -11
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- 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/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +2455 -3925
- package/dist/mcp/runtime.test.js +240 -8
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -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/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +46 -3
- package/dist/server/index.test.js +12 -1
- package/dist/server/routes/admin.d.ts +1 -5
- package/dist/server/routes/admin.js +151 -80
- 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 +59 -64
- 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 +122 -255
- package/dist/server/routes/billing.test.js +257 -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 +31 -1
- package/dist/server/routes/shared.js +26 -0
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.integration.test.js +20 -0
- package/dist/server/routes/sync.js +111 -238
- package/dist/server/routes/tasks.d.ts +1 -1
- package/dist/server/routes/tasks.js +50 -8
- 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 +84 -181
- package/dist/server/routes.test.js +912 -32
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.js +28 -17
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +379 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +44 -2
- package/dist/sync/syncService.test.js +62 -0
- package/dist/sync/workspacePullFeed.d.ts +1 -0
- package/dist/sync/workspacePullFeed.js +66 -59
- package/dist/sync/workspacePullFeed.test.js +72 -4
- package/dist/sync/workspaceSyncModel.d.ts +25 -1
- package/dist/sync/workspaceSyncModel.js +77 -3
- package/dist/sync/workspaceSyncModel.test.js +153 -0
- package/dist/sync/workspaceSyncState.d.ts +2 -0
- package/dist/sync/workspaceSyncState.js +1 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-N2MnQBZk.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.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-4-Rh2K2n.js → InitiativesModule-Dhm7M8e7.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +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-CGk9s3NJ.js → WorkflowsModule-BcS4afRn.js} +1 -1
- package/dist/ui/assets/index-C2-OXZV7.css +1 -0
- package/dist/ui/assets/index-DcSI5F86.js +6 -0
- package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-BSUaRwF8.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +196 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +10 -1
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/playwright-auth.sh +9 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-C3GyzrWm.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
- package/dist/ui/assets/index-CLIMgR6g.js +0 -6
- package/dist/ui/assets/index-DneETxcu.css +0 -1
|
@@ -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';
|
|
68
|
-
import { resolveErrorStatusCode } from './shared.js';
|
|
67
|
+
import { getTaskforceCloudEnvironmentConfig, resolveTaskforceCloudEnvironmentFromBaseUrl, resolveTaskforceMcpBaseUrlFromBaseUrl } from '../../config/cloudEnvironment.js';
|
|
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' : '';
|
|
@@ -474,22 +532,13 @@ export function registerAdminRoutes(deps) {
|
|
|
474
532
|
serverName: 'taskforce-cloud',
|
|
475
533
|
logStartup: false,
|
|
476
534
|
runtimeMode: 'cloud',
|
|
477
|
-
auth: {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
aiProfileToken: auth.aiProfileToken,
|
|
482
|
-
aiProfileName: auth.aiProfileName,
|
|
483
|
-
aiProfileDescription: auth.aiProfileDescription,
|
|
484
|
-
aiProfileRole: auth.aiProfileRole,
|
|
485
|
-
aiProfileProvider: auth.aiProfileProvider,
|
|
486
|
-
aiProfileModel: auth.aiProfileModel,
|
|
487
|
-
aiProfileSurfaceType: auth.aiProfileSurfaceType,
|
|
488
|
-
actorType: auth.authenticatedViaToken ? 'token' : auth.authenticatedViaOAuth ? 'oauth' : 'user',
|
|
535
|
+
auth: buildCloudMcpRuntimeAuth({
|
|
536
|
+
req,
|
|
537
|
+
auth,
|
|
538
|
+
requestHeader,
|
|
489
539
|
requestId: String(req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || randomUUID(),
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
},
|
|
540
|
+
authScopes,
|
|
541
|
+
}),
|
|
493
542
|
});
|
|
494
543
|
const transport = new StreamableHTTPServerTransport({
|
|
495
544
|
sessionIdGenerator: undefined,
|
|
@@ -1022,7 +1071,7 @@ export function registerAdminRoutes(deps) {
|
|
|
1022
1071
|
return;
|
|
1023
1072
|
}
|
|
1024
1073
|
if (context.authConfig?.runtimeMode !== 'cloud') {
|
|
1025
|
-
const effective =
|
|
1074
|
+
const effective = getRouteEmailConfig(core, context);
|
|
1026
1075
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1027
1076
|
res.end(JSON.stringify({
|
|
1028
1077
|
settings: { provider: 'console', from: effective.from, replyTo: '' },
|
|
@@ -1031,7 +1080,7 @@ export function registerAdminRoutes(deps) {
|
|
|
1031
1080
|
return;
|
|
1032
1081
|
}
|
|
1033
1082
|
const settings = core.getGlobalSettings().email || {};
|
|
1034
|
-
const effective =
|
|
1083
|
+
const effective = getRouteEmailConfig(core, context);
|
|
1035
1084
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1036
1085
|
res.end(JSON.stringify({
|
|
1037
1086
|
settings: {
|
|
@@ -1056,7 +1105,7 @@ export function registerAdminRoutes(deps) {
|
|
|
1056
1105
|
return;
|
|
1057
1106
|
}
|
|
1058
1107
|
const settings = core.getGlobalSettings().email || {};
|
|
1059
|
-
const effective =
|
|
1108
|
+
const effective = getRouteEmailConfig(core, context);
|
|
1060
1109
|
const keyRequired = effective.provider === 'resend' || effective.provider === 'google-workspace';
|
|
1061
1110
|
const ready = keyRequired ? Boolean(effective.providerApiKey) : true;
|
|
1062
1111
|
const reason = ready
|
|
@@ -1088,7 +1137,7 @@ export function registerAdminRoutes(deps) {
|
|
|
1088
1137
|
const globalSettings = core.getGlobalSettings();
|
|
1089
1138
|
const authSettings = globalSettings.auth || {};
|
|
1090
1139
|
const billingSettings = globalSettings.billing || {};
|
|
1091
|
-
const effective =
|
|
1140
|
+
const effective = buildAuthBehavior(core);
|
|
1092
1141
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1093
1142
|
res.end(JSON.stringify({
|
|
1094
1143
|
settings: {
|
|
@@ -1126,7 +1175,7 @@ export function registerAdminRoutes(deps) {
|
|
|
1126
1175
|
return;
|
|
1127
1176
|
}
|
|
1128
1177
|
const siteSettings = core.getGlobalSettings().site || {};
|
|
1129
|
-
const siteBehavior =
|
|
1178
|
+
const siteBehavior = buildSiteBehavior(core);
|
|
1130
1179
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1131
1180
|
res.end(JSON.stringify({
|
|
1132
1181
|
settings: {
|
|
@@ -1741,17 +1790,20 @@ export function registerAdminRoutes(deps) {
|
|
|
1741
1790
|
return;
|
|
1742
1791
|
}
|
|
1743
1792
|
const body = await parseJsonBody(req);
|
|
1793
|
+
const currentOnboardingPolicy = core.getOnboardingPolicy();
|
|
1744
1794
|
const onboardingPolicyMode = body?.onboardingPolicyMode === 'auto_grant_free' || body?.onboardingPolicyMode === 'auto_start_trial'
|
|
1745
1795
|
? body.onboardingPolicyMode
|
|
1746
|
-
: 'require_plan_selection'
|
|
1796
|
+
: body?.onboardingPolicyMode === 'require_plan_selection'
|
|
1797
|
+
? 'require_plan_selection'
|
|
1798
|
+
: currentOnboardingPolicy.mode;
|
|
1747
1799
|
const onboardingPlanVersionId = typeof body?.onboardingPlanVersionId === 'string'
|
|
1748
1800
|
? body.onboardingPlanVersionId.trim() || null
|
|
1749
1801
|
: body?.onboardingPlanVersionId === null
|
|
1750
1802
|
? null
|
|
1751
|
-
:
|
|
1803
|
+
: currentOnboardingPolicy.planVersionId;
|
|
1752
1804
|
const validation = core.validateOnboardingPolicy({
|
|
1753
1805
|
mode: onboardingPolicyMode,
|
|
1754
|
-
|
|
1806
|
+
planVersionId: onboardingPlanVersionId
|
|
1755
1807
|
});
|
|
1756
1808
|
if (!validation.valid) {
|
|
1757
1809
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
@@ -1848,7 +1900,7 @@ export function registerAdminRoutes(deps) {
|
|
|
1848
1900
|
res.end(JSON.stringify({ success: false, error: 'Valid test email address is required.' }));
|
|
1849
1901
|
return;
|
|
1850
1902
|
}
|
|
1851
|
-
const config =
|
|
1903
|
+
const config = getRouteEmailConfig(core, context);
|
|
1852
1904
|
const result = await sendEmail({
|
|
1853
1905
|
to,
|
|
1854
1906
|
subject: 'Taskforce email settings test',
|
|
@@ -2273,9 +2325,28 @@ export function registerAdminRoutes(deps) {
|
|
|
2273
2325
|
res.end(JSON.stringify({ success: false, error: 'Token is missing required scopes.', code: 'MCP_TOKEN_MISSING_SCOPES', details: { requiredScopes, missingScopes, grantedScopes: inspection.scopes } }));
|
|
2274
2326
|
return;
|
|
2275
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
|
+
}
|
|
2276
2334
|
const workspace = core.getWorkspaceProfile(wid);
|
|
2277
2335
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2278
|
-
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
|
+
}));
|
|
2279
2350
|
}
|
|
2280
2351
|
catch (error) {
|
|
2281
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';
|
|
@@ -308,13 +259,13 @@ export function registerAuthRoutes(deps) {
|
|
|
308
259
|
? 'Verify your Taskforce account'
|
|
309
260
|
: 'Reset your Taskforce password';
|
|
310
261
|
const text = kind === 'verify-email'
|
|
311
|
-
? `Welcome to Taskforce.\n\
|
|
262
|
+
? `Welcome to Taskforce.\n\nYou're almost in. Enter this verification token on the verification screen to activate your account:\n\n${token}\n\nPrefer the faster route? Open the link below to verify your email automatically:\n${link}\n\nIf you didn't create a Taskforce account, you can safely ignore this email.`
|
|
312
263
|
: `Reset your Taskforce password using this link:\n${link}\n\nIf you did not request this reset, you can ignore this message.`;
|
|
313
264
|
const result = await sendEmail({
|
|
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',
|
|
@@ -401,7 +352,8 @@ export function registerAuthRoutes(deps) {
|
|
|
401
352
|
let avatarUrl = null;
|
|
402
353
|
let betaAccess = true;
|
|
403
354
|
if (authenticated && access?.userId) {
|
|
404
|
-
|
|
355
|
+
// Keep the session endpoint fast on reload; avatar draft cleanup is best-effort housekeeping.
|
|
356
|
+
void cleanupStaleAvatarDrafts(access.userId).catch(() => { });
|
|
405
357
|
betaAccess = core.getUserBetaAccess(access.userId);
|
|
406
358
|
const resolved = core.resolveUserAccess(access.userId, workspaceId, role);
|
|
407
359
|
if (resolved) {
|
|
@@ -704,7 +656,7 @@ export function registerAuthRoutes(deps) {
|
|
|
704
656
|
const workspaceId = workspaceIdCandidate || 'default';
|
|
705
657
|
// Password flow (preferred)
|
|
706
658
|
if (email && password) {
|
|
707
|
-
const authBehavior =
|
|
659
|
+
const authBehavior = buildAuthFlowBehavior(core);
|
|
708
660
|
let authResult = core.authenticatePasswordUserDetailed({
|
|
709
661
|
email,
|
|
710
662
|
password,
|
|
@@ -801,7 +753,7 @@ export function registerAuthRoutes(deps) {
|
|
|
801
753
|
const selectedPlanVersionId = typeof body?.planVersionId === 'string'
|
|
802
754
|
? body.planVersionId.trim()
|
|
803
755
|
: '';
|
|
804
|
-
const authBehavior =
|
|
756
|
+
const authBehavior = buildAuthFlowBehavior(core);
|
|
805
757
|
if (!authBehavior.allowSelfRegistration) {
|
|
806
758
|
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
807
759
|
res.end(JSON.stringify({ success: false, error: 'Self-registration is currently disabled.' }));
|
|
@@ -870,6 +822,43 @@ export function registerAuthRoutes(deps) {
|
|
|
870
822
|
}));
|
|
871
823
|
}
|
|
872
824
|
catch (error) {
|
|
825
|
+
if (error?.code === 'EMAIL_ALREADY_EXISTS'
|
|
826
|
+
&& authBehavior.requireVerifiedEmail
|
|
827
|
+
&& email
|
|
828
|
+
&& !core.isEmailVerified(email)) {
|
|
829
|
+
let emailSent = false;
|
|
830
|
+
let deliveryAttempted = false;
|
|
831
|
+
let emailError;
|
|
832
|
+
let verificationToken;
|
|
833
|
+
const verification = core.requestEmailVerification({ email });
|
|
834
|
+
verificationToken = verification.token || null;
|
|
835
|
+
if (verification.token) {
|
|
836
|
+
deliveryAttempted = true;
|
|
837
|
+
const delivery = await deliverAuthEmail('verify-email', email, verification.token, req);
|
|
838
|
+
emailSent = delivery.ok;
|
|
839
|
+
emailError = delivery.error;
|
|
840
|
+
}
|
|
841
|
+
auditAuthAction(req, 'auth-register-existing-unverified', {
|
|
842
|
+
email,
|
|
843
|
+
workspaceId: workspaceIdInput || null,
|
|
844
|
+
code: 'EMAIL_ALREADY_EXISTS_UNVERIFIED',
|
|
845
|
+
emailSent
|
|
846
|
+
});
|
|
847
|
+
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
848
|
+
res.end(JSON.stringify({
|
|
849
|
+
success: false,
|
|
850
|
+
error: 'This email already has an unverified account. Continue with email verification instead.',
|
|
851
|
+
code: 'EMAIL_ALREADY_EXISTS_UNVERIFIED',
|
|
852
|
+
verificationRequired: true,
|
|
853
|
+
emailSent,
|
|
854
|
+
deliveryAttempted,
|
|
855
|
+
emailError,
|
|
856
|
+
verificationToken: shouldExposeDevAuthTokens()
|
|
857
|
+
? (verificationToken || null)
|
|
858
|
+
: undefined
|
|
859
|
+
}));
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
873
862
|
const statusCode = Number(error?.statusCode || 400);
|
|
874
863
|
const message = String(error?.message || 'Unable to register');
|
|
875
864
|
auditAuthAction(req, 'auth-register-failed', {
|
|
@@ -895,10 +884,12 @@ export function registerAuthRoutes(deps) {
|
|
|
895
884
|
return;
|
|
896
885
|
}
|
|
897
886
|
try {
|
|
898
|
-
let emailSent =
|
|
887
|
+
let emailSent = false;
|
|
888
|
+
let deliveryAttempted = false;
|
|
899
889
|
let emailError;
|
|
900
890
|
const verification = core.requestEmailVerification({ email });
|
|
901
891
|
if (verification.token) {
|
|
892
|
+
deliveryAttempted = true;
|
|
902
893
|
const delivery = await deliverAuthEmail('verify-email', email, verification.token, req);
|
|
903
894
|
emailSent = delivery.ok;
|
|
904
895
|
emailError = delivery.error;
|
|
@@ -911,6 +902,7 @@ export function registerAuthRoutes(deps) {
|
|
|
911
902
|
res.end(JSON.stringify({
|
|
912
903
|
success: true,
|
|
913
904
|
emailSent,
|
|
905
|
+
deliveryAttempted,
|
|
914
906
|
emailError,
|
|
915
907
|
verificationToken: shouldExposeDevAuthTokens() ? (verification.token || null) : undefined
|
|
916
908
|
}));
|
|
@@ -983,10 +975,12 @@ export function registerAuthRoutes(deps) {
|
|
|
983
975
|
return;
|
|
984
976
|
}
|
|
985
977
|
try {
|
|
986
|
-
let emailSent =
|
|
978
|
+
let emailSent = false;
|
|
979
|
+
let deliveryAttempted = false;
|
|
987
980
|
let emailError;
|
|
988
981
|
const reset = core.requestPasswordReset({ email });
|
|
989
982
|
if (reset.token) {
|
|
983
|
+
deliveryAttempted = true;
|
|
990
984
|
const delivery = await deliverAuthEmail('password-reset', email, reset.token, req);
|
|
991
985
|
emailSent = delivery.ok;
|
|
992
986
|
emailError = delivery.error;
|
|
@@ -999,6 +993,7 @@ export function registerAuthRoutes(deps) {
|
|
|
999
993
|
res.end(JSON.stringify({
|
|
1000
994
|
success: true,
|
|
1001
995
|
emailSent,
|
|
996
|
+
deliveryAttempted,
|
|
1002
997
|
emailError,
|
|
1003
998
|
resetToken: shouldExposeDevAuthTokens() ? (reset.token || null) : undefined
|
|
1004
999
|
}));
|
|
@@ -1354,7 +1349,7 @@ export function registerAuthRoutes(deps) {
|
|
|
1354
1349
|
seatUsage,
|
|
1355
1350
|
upgradeHints: canInvite
|
|
1356
1351
|
? []
|
|
1357
|
-
: ['Team management requires
|
|
1352
|
+
: ['Team management requires an active plan with collaboration access.'],
|
|
1358
1353
|
supportLink: 'mailto:support@taskforcehq.ai'
|
|
1359
1354
|
}));
|
|
1360
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;
|