@taskforcehq/taskforce 0.3.302 → 0.3.304
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/dist/Taskforce.module.css +113 -5
- package/dist/TaskforceCore.js +560 -208
- package/dist/TaskforceCore.test.js +1833 -91
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/TaskSettings.js +58 -38
- package/dist/components/features/TaskSettings.test.js +139 -40
- 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 +2 -1
- package/dist/components/task/TaskForm.js +22 -4
- package/dist/components/task/TaskForm.test.js +46 -0
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +31 -160
- package/dist/components/task/TaskKanban.test.js +17 -0
- 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 +4 -2
- package/dist/components/views/PlanComparisonPage.test.js +4 -0
- package/dist/components/views/PlansPage.d.ts +7 -1
- package/dist/components/views/PlansPage.js +139 -53
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +157 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +216 -186
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
- package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +31 -5
- package/dist/core/PlanEntitlementService.js +350 -222
- package/dist/core/SignupMonetizationSettings.test.js +208 -49
- package/dist/core/SystemAdmin.test.js +186 -96
- 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/Taskforce.d.ts +65 -20
- package/dist/core/Taskforce.js +261 -51
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
- package/dist/core/WorkspacePlanMode.test.js +65 -0
- package/dist/core/types.d.ts +11 -12
- 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 +61 -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 +122 -0
- package/dist/hooks/sync/transfers.js +431 -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 +5 -4
- package/dist/hooks/useSyncOrchestrator.js +315 -860
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +665 -8
- package/dist/hooks/useTaskMutations.js +45 -6
- package/dist/hooks/useTaskMutations.test.js +147 -0
- package/dist/hooks/useTaskforce.d.ts +11 -2
- package/dist/hooks/useTaskforce.js +290 -812
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +22 -10
- 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/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/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +264 -505
- package/dist/mcp/runtime.test.js +353 -89
- 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/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +15 -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.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +48 -3
- package/dist/server/index.test.js +14 -2
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +91 -40
- package/dist/server/routes/auth.d.ts +1 -0
- package/dist/server/routes/auth.js +231 -55
- package/dist/server/routes/billing.js +473 -39
- package/dist/server/routes/billing.test.js +972 -37
- package/dist/server/routes/shared.d.ts +30 -0
- package/dist/server/routes/shared.js +31 -3
- package/dist/server/routes/sync.integration.test.js +180 -0
- package/dist/server/routes/sync.js +125 -242
- package/dist/server/routes/tasks.js +49 -7
- package/dist/server/routes.js +11 -22
- package/dist/server/routes.test.js +530 -81
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/cloudSyncApi.d.ts +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.d.ts +10 -0
- package/dist/sync/contentSyncState.js +277 -14
- 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 +377 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +57 -10
- package/dist/sync/syncService.test.js +48 -0
- package/dist/sync/workspacePullFeed.d.ts +11 -0
- package/dist/sync/workspacePullFeed.js +123 -68
- package/dist/sync/workspacePullFeed.test.js +180 -0
- package/dist/sync/workspaceSyncModel.d.ts +22 -1
- package/dist/sync/workspaceSyncModel.js +65 -0
- package/dist/sync/workspaceSyncModel.test.js +119 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-BjR6iBf9.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-DnFqdUME.js} +1 -1
- package/dist/ui/assets/index-DXUdtH1B.js +6 -0
- package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
- package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-I7ZwG1z_.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
- package/dist/utils/accountProfileSummaryCache.js +97 -0
- package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
- package/dist/utils/accountProfileSummaryCache.test.js +63 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +5 -3
- package/scripts/playwright-auth.sh +4 -0
- package/dist/ui/assets/AgentsModule-JG5jc3he.js +0 -1
- package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
- package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +0 -8
- package/dist/ui/assets/index-MXeWoebC.css +0 -1
- package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
- package/dist/utils/billingStatusCache.d.ts +0 -21
- package/dist/utils/billingStatusCache.js +0 -70
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { WorkspaceAssetKind } from '../storage/workspaceAssetStore.js';
|
|
2
|
+
export type TaskAttachmentRecord = {
|
|
3
|
+
path: string;
|
|
4
|
+
fsPath?: string;
|
|
5
|
+
caption?: string;
|
|
6
|
+
displayName?: string;
|
|
7
|
+
originalFilename?: string;
|
|
8
|
+
timestamp?: string;
|
|
9
|
+
assetId?: string;
|
|
10
|
+
referenceNumber?: number | null;
|
|
11
|
+
referenceLabel?: string | null;
|
|
12
|
+
taskId?: string | null;
|
|
13
|
+
};
|
|
14
|
+
export type McpTaskAttachmentDescriptor = {
|
|
15
|
+
attachmentId: string;
|
|
16
|
+
assetId?: string;
|
|
17
|
+
referenceNumber?: number | null;
|
|
18
|
+
referenceLabel?: string | null;
|
|
19
|
+
documentId?: string | null;
|
|
20
|
+
path: string;
|
|
21
|
+
fsPath?: string;
|
|
22
|
+
caption?: string;
|
|
23
|
+
displayName?: string;
|
|
24
|
+
originalFilename?: string;
|
|
25
|
+
kind: WorkspaceAssetKind;
|
|
26
|
+
mimeType: string;
|
|
27
|
+
sizeBytes: number | null;
|
|
28
|
+
timestamp?: string;
|
|
29
|
+
source: 'canonical' | 'external';
|
|
30
|
+
readableAsText: boolean;
|
|
31
|
+
preferredNextTool?: string;
|
|
32
|
+
preferredWorkflow?: string;
|
|
33
|
+
suggestedArgs?: {
|
|
34
|
+
documentId: string;
|
|
35
|
+
};
|
|
36
|
+
guidance?: string;
|
|
37
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -25,6 +25,7 @@ describe('billing schema parity', () => {
|
|
|
25
25
|
expect(names.has('user_billing')).toBe(true);
|
|
26
26
|
expect(names.has('stripe_webhook_events')).toBe(true);
|
|
27
27
|
expect(names.has('stripe_webhook_failures')).toBe(true);
|
|
28
|
+
expect(names.has('billing_checkout_attempts')).toBe(true);
|
|
28
29
|
expect(names.has('billing_config')).toBe(true);
|
|
29
30
|
expect(names.has('billing_price_mappings')).toBe(true);
|
|
30
31
|
}
|
|
@@ -101,9 +102,11 @@ describe('billing schema parity', () => {
|
|
|
101
102
|
expect(sql).toContain('CREATE TABLE IF NOT EXISTS user_billing');
|
|
102
103
|
expect(sql).toContain('CREATE TABLE IF NOT EXISTS stripe_webhook_events');
|
|
103
104
|
expect(sql).toContain('CREATE TABLE IF NOT EXISTS stripe_webhook_failures');
|
|
105
|
+
expect(sql).toContain('CREATE TABLE IF NOT EXISTS billing_checkout_attempts');
|
|
104
106
|
expect(sql).toContain('CREATE TABLE IF NOT EXISTS billing_config');
|
|
105
107
|
expect(sql).toContain('CREATE TABLE IF NOT EXISTS billing_price_mappings');
|
|
106
108
|
expect(sql).toContain('CREATE INDEX IF NOT EXISTS idx_stripe_webhook_failures_purge_after');
|
|
109
|
+
expect(sql).toContain('CREATE INDEX IF NOT EXISTS idx_billing_checkout_attempts_user_status');
|
|
107
110
|
expect(sql).toContain('CREATE INDEX IF NOT EXISTS idx_billing_price_mappings_plan');
|
|
108
111
|
expect(sql).toContain('CREATE UNIQUE INDEX IF NOT EXISTS idx_billing_price_mappings_active_interval');
|
|
109
112
|
});
|
|
@@ -493,6 +493,18 @@ export function ensureBillingSchema(db) {
|
|
|
493
493
|
created_at TEXT NOT NULL
|
|
494
494
|
);
|
|
495
495
|
|
|
496
|
+
CREATE TABLE IF NOT EXISTS billing_checkout_attempts (
|
|
497
|
+
session_id TEXT PRIMARY KEY,
|
|
498
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
499
|
+
user_id TEXT NOT NULL,
|
|
500
|
+
plan_version_id TEXT NOT NULL,
|
|
501
|
+
billing_interval TEXT,
|
|
502
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
503
|
+
expires_at TEXT,
|
|
504
|
+
created_at TEXT NOT NULL,
|
|
505
|
+
updated_at TEXT NOT NULL
|
|
506
|
+
);
|
|
507
|
+
|
|
496
508
|
CREATE TABLE IF NOT EXISTS billing_config (
|
|
497
509
|
tenant_id TEXT PRIMARY KEY,
|
|
498
510
|
config_json TEXT NOT NULL DEFAULT '{}',
|
|
@@ -520,6 +532,8 @@ export function ensureBillingSchema(db) {
|
|
|
520
532
|
CREATE INDEX IF NOT EXISTS idx_stripe_webhook_failures_tenant ON stripe_webhook_failures(tenant_id);
|
|
521
533
|
CREATE INDEX IF NOT EXISTS idx_stripe_webhook_failures_event ON stripe_webhook_failures(event_id);
|
|
522
534
|
CREATE INDEX IF NOT EXISTS idx_stripe_webhook_failures_purge_after ON stripe_webhook_failures(purge_after);
|
|
535
|
+
CREATE INDEX IF NOT EXISTS idx_billing_checkout_attempts_user_status ON billing_checkout_attempts(tenant_id, user_id, status, created_at);
|
|
536
|
+
CREATE INDEX IF NOT EXISTS idx_billing_checkout_attempts_plan_status ON billing_checkout_attempts(tenant_id, plan_version_id, status, created_at);
|
|
523
537
|
CREATE INDEX IF NOT EXISTS idx_billing_price_mappings_plan ON billing_price_mappings(tenant_id, plan_version_id);
|
|
524
538
|
CREATE INDEX IF NOT EXISTS idx_billing_price_mappings_active ON billing_price_mappings(tenant_id, active);
|
|
525
539
|
`);
|
|
@@ -723,6 +737,7 @@ export function ensureEntitlementsSchema(db) {
|
|
|
723
737
|
updated_by TEXT,
|
|
724
738
|
created_at TEXT NOT NULL,
|
|
725
739
|
updated_at TEXT NOT NULL,
|
|
740
|
+
FOREIGN KEY (account_id) REFERENCES users(id) ON DELETE CASCADE,
|
|
726
741
|
FOREIGN KEY (plan_version_id) REFERENCES plan_versions(plan_version_id)
|
|
727
742
|
);
|
|
728
743
|
|
package/dist/plugins/vite.js
CHANGED
|
@@ -190,7 +190,7 @@ export function taskforcePlugin(options) {
|
|
|
190
190
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
191
191
|
<link rel="icon" type="image/svg+xml" href="/src/assets/favicon/favicon.svg" />
|
|
192
192
|
<link rel="icon" type="image/x-icon" href="/src/assets/favicon/favicon.ico" />
|
|
193
|
-
<title>Taskforce
|
|
193
|
+
<title>Taskforce</title>
|
|
194
194
|
<style>
|
|
195
195
|
body { margin: 0; background: #0f0f1a; color: white; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
|
|
196
196
|
#root { height: 100vh; overflow: hidden; }
|
|
@@ -12,7 +12,7 @@ export declare const APP_GATE_DEFINITIONS: {
|
|
|
12
12
|
readonly removalMilestone: "Document Manager GA";
|
|
13
13
|
};
|
|
14
14
|
readonly 'workflows.workspace_access': {
|
|
15
|
-
readonly enabled:
|
|
15
|
+
readonly enabled: false;
|
|
16
16
|
readonly description: "Workflow workspace readiness gate.";
|
|
17
17
|
readonly owner: "taskforce";
|
|
18
18
|
readonly removalMilestone: "Workflow workspace GA";
|
|
@@ -12,7 +12,7 @@ export const APP_GATE_DEFINITIONS = {
|
|
|
12
12
|
removalMilestone: 'Document Manager GA'
|
|
13
13
|
},
|
|
14
14
|
'workflows.workspace_access': {
|
|
15
|
-
enabled:
|
|
15
|
+
enabled: false,
|
|
16
16
|
description: 'Workflow workspace readiness gate.',
|
|
17
17
|
owner: 'taskforce',
|
|
18
18
|
removalMilestone: 'Workflow workspace GA'
|
|
@@ -22,6 +22,7 @@ describe('proxy cookie rewrite helpers', () => {
|
|
|
22
22
|
expect(shouldProxyCloudPath('/api/taskforce/public/site-config-script')).toBe(true);
|
|
23
23
|
expect(shouldProxyCloudPath('/api/taskforce/version')).toBe(true);
|
|
24
24
|
expect(shouldProxyCloudPath('/api/taskforce/account/entitlements')).toBe(true);
|
|
25
|
+
expect(shouldProxyCloudPath('/api/taskforce/account/profile-summary')).toBe(true);
|
|
25
26
|
expect(shouldProxyCloudPath('/api/taskforce/account/team-management-access')).toBe(true);
|
|
26
27
|
expect(shouldProxyCloudPath('/api/taskforce/admin/mcp-audit-logs')).toBe(true);
|
|
27
28
|
expect(shouldProxyCloudPath('/api/taskforce/billing/status')).toBe(true);
|
package/dist/server/index.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export declare function shouldRewriteSetCookieForLocalDevHost(hostHeader: string
|
|
|
28
28
|
export declare function writeJsonErrorIfPossible(res: http.ServerResponse, statusCode: number, payload: Record<string, unknown>): boolean;
|
|
29
29
|
export declare function applyCloudProxyForwardedHeaders(headers: Headers, req: http.IncomingMessage): void;
|
|
30
30
|
export declare function rewriteProxySetCookieForLocalHost(cookie: string): string;
|
|
31
|
+
export declare function shouldRewriteProxySetCookieDomain(cookie: string, hostHeader: string | undefined): boolean;
|
|
31
32
|
export declare function shouldApplyAuthRateLimitForPath(pathname: string): boolean;
|
|
32
33
|
export declare function resolveCloudProxyBaseUrl(env?: NodeJS.ProcessEnv): string;
|
|
33
34
|
export declare function ensureBillingWebhookAuthExclusion(excludedPaths: string[]): string[];
|
package/dist/server/index.js
CHANGED
|
@@ -109,6 +109,8 @@ export function shouldProxyCloudPath(pathname) {
|
|
|
109
109
|
|| pathname === PUBLIC_SITE_CONFIG_SCRIPT_PATH
|
|
110
110
|
|| pathname === '/api/taskforce/version'
|
|
111
111
|
|| pathname === '/api/taskforce/account/entitlements'
|
|
112
|
+
|| pathname === '/api/taskforce/account/profile-summary'
|
|
113
|
+
|| pathname === '/api/taskforce/account/team-management-access'
|
|
112
114
|
|| pathname.startsWith('/api/taskforce/admin/')
|
|
113
115
|
|| pathname.startsWith('/api/taskforce/billing/')
|
|
114
116
|
|| pathname.startsWith('/api/taskforce/auth/')
|
|
@@ -203,6 +205,43 @@ export function rewriteProxySetCookieForLocalHost(cookie) {
|
|
|
203
205
|
const filteredAttrs = attrs.filter((attr) => !attr.toLowerCase().startsWith('domain='));
|
|
204
206
|
return [nameValue, ...filteredAttrs].join('; ');
|
|
205
207
|
}
|
|
208
|
+
function normalizeHostHeaderHost(hostHeader) {
|
|
209
|
+
const raw = String(hostHeader || '').trim().toLowerCase();
|
|
210
|
+
if (!raw)
|
|
211
|
+
return '';
|
|
212
|
+
if (raw.startsWith('[')) {
|
|
213
|
+
const closing = raw.indexOf(']');
|
|
214
|
+
if (closing >= 0)
|
|
215
|
+
return raw.slice(1, closing);
|
|
216
|
+
}
|
|
217
|
+
const portSeparator = raw.lastIndexOf(':');
|
|
218
|
+
if (portSeparator <= 0)
|
|
219
|
+
return raw;
|
|
220
|
+
const maybePort = raw.slice(portSeparator + 1);
|
|
221
|
+
if (/^\d+$/.test(maybePort)) {
|
|
222
|
+
return raw.slice(0, portSeparator);
|
|
223
|
+
}
|
|
224
|
+
return raw;
|
|
225
|
+
}
|
|
226
|
+
function parseSetCookieDomain(cookie) {
|
|
227
|
+
const parts = String(cookie || '').split(';').map((part) => part.trim()).filter(Boolean);
|
|
228
|
+
for (const part of parts.slice(1)) {
|
|
229
|
+
const lower = part.toLowerCase();
|
|
230
|
+
if (!lower.startsWith('domain='))
|
|
231
|
+
continue;
|
|
232
|
+
return part.slice('domain='.length).trim().replace(/^\.+/, '').toLowerCase();
|
|
233
|
+
}
|
|
234
|
+
return '';
|
|
235
|
+
}
|
|
236
|
+
export function shouldRewriteProxySetCookieDomain(cookie, hostHeader) {
|
|
237
|
+
const requestHost = normalizeHostHeaderHost(hostHeader);
|
|
238
|
+
const cookieDomain = parseSetCookieDomain(cookie);
|
|
239
|
+
if (!requestHost || !cookieDomain)
|
|
240
|
+
return false;
|
|
241
|
+
if (requestHost === cookieDomain)
|
|
242
|
+
return false;
|
|
243
|
+
return !requestHost.endsWith(`.${cookieDomain}`);
|
|
244
|
+
}
|
|
206
245
|
async function readRequestBody(req, maxBytes) {
|
|
207
246
|
const chunks = [];
|
|
208
247
|
let total = 0;
|
|
@@ -548,9 +587,15 @@ export function createStandaloneServer(options) {
|
|
|
548
587
|
}
|
|
549
588
|
const setCookies = upstream.headers.getSetCookie?.();
|
|
550
589
|
if (Array.isArray(setCookies) && setCookies.length > 0) {
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
590
|
+
const requestHost = String(req.headers.host || '').trim();
|
|
591
|
+
const normalizedSetCookies = setCookies.map((cookie) => {
|
|
592
|
+
if (shouldRewriteSetCookieForLocalDevHost(requestHost)) {
|
|
593
|
+
return rewriteProxySetCookieForLocalHost(cookie);
|
|
594
|
+
}
|
|
595
|
+
return shouldRewriteProxySetCookieDomain(cookie, requestHost)
|
|
596
|
+
? rewriteProxySetCookieForLocalHost(cookie)
|
|
597
|
+
: cookie;
|
|
598
|
+
});
|
|
554
599
|
res.setHeader('Set-Cookie', normalizedSetCookies);
|
|
555
600
|
}
|
|
556
601
|
res.writeHead(upstream.status, outgoingHeaders);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { ensureBillingWebhookAuthExclusion, ensurePublicSiteConfigAuthExclusion, isBillingWebhookPath, resolveEffectiveRequestUserId, resolveAuthorizedWorkspaceAccess, shouldApplyAuthRateLimitForPath, shouldProxyCloudPath, writeJsonErrorIfPossible } from './index.js';
|
|
2
|
+
import { ensureBillingWebhookAuthExclusion, ensurePublicSiteConfigAuthExclusion, isBillingWebhookPath, rewriteProxySetCookieForLocalHost, resolveEffectiveRequestUserId, resolveAuthorizedWorkspaceAccess, shouldRewriteProxySetCookieDomain, shouldApplyAuthRateLimitForPath, shouldProxyCloudPath, writeJsonErrorIfPossible } from './index.js';
|
|
3
3
|
describe('server index helper policies', () => {
|
|
4
4
|
it('adds webhook path to auth exclusions without duplicates', () => {
|
|
5
5
|
const base = ['/api/taskforce/health', '/api/taskforce/auth'];
|
|
@@ -86,11 +86,23 @@ describe('shouldProxyCloudPath', () => {
|
|
|
86
86
|
expect(shouldProxyCloudPath('/api/taskforce/admin/users')).toBe(true);
|
|
87
87
|
expect(shouldProxyCloudPath('/api/taskforce/billing/plans')).toBe(true);
|
|
88
88
|
expect(shouldProxyCloudPath('/api/taskforce/auth/login')).toBe(true);
|
|
89
|
-
expect(shouldProxyCloudPath('/api/taskforce/account/
|
|
89
|
+
expect(shouldProxyCloudPath('/api/taskforce/account/profile-summary')).toBe(true);
|
|
90
|
+
expect(shouldProxyCloudPath('/api/taskforce/account/team-management-access')).toBe(true);
|
|
90
91
|
expect(shouldProxyCloudPath('/api/taskforce/public/site-config')).toBe(true);
|
|
91
92
|
expect(shouldProxyCloudPath('/api/taskforce/public/site-config-script')).toBe(true);
|
|
92
93
|
});
|
|
93
94
|
});
|
|
95
|
+
describe('proxy cookie helpers', () => {
|
|
96
|
+
it('rewrites mismatched cookie domains for non-loopback local hosts', () => {
|
|
97
|
+
const cookie = 'taskforce_session=abc; Path=/; HttpOnly; SameSite=Lax; Domain=taskforcehq.ai';
|
|
98
|
+
expect(shouldRewriteProxySetCookieDomain(cookie, 'taskforce.localtest.me:3847')).toBe(true);
|
|
99
|
+
expect(rewriteProxySetCookieForLocalHost(cookie)).not.toContain('Domain=');
|
|
100
|
+
});
|
|
101
|
+
it('preserves cookie domains that already match the request host', () => {
|
|
102
|
+
const cookie = 'taskforce_session=abc; Path=/; HttpOnly; SameSite=Lax; Domain=taskforcehq.ai';
|
|
103
|
+
expect(shouldRewriteProxySetCookieDomain(cookie, 'app.taskforcehq.ai')).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
94
106
|
describe('resolveAuthorizedWorkspaceAccess', () => {
|
|
95
107
|
it('prefers an explicitly requested workspace when the user is authorized for it', () => {
|
|
96
108
|
const core = {
|
|
@@ -122,6 +122,7 @@ function formatMockCheckoutSession(record, expandSubscription = false) {
|
|
|
122
122
|
metadata: { ...record.metadata },
|
|
123
123
|
payment_status: record.paymentStatus,
|
|
124
124
|
status: record.status,
|
|
125
|
+
expires_at: record.expiresAt,
|
|
125
126
|
subscription: record.subscriptionId
|
|
126
127
|
? (expandSubscription ? formatMockSubscription(getMockStripeSubscription(record.subscriptionId)) : record.subscriptionId)
|
|
127
128
|
: null
|
|
@@ -243,6 +244,7 @@ export function createMockStripeClient() {
|
|
|
243
244
|
successUrl: String(params.success_url || '').trim(),
|
|
244
245
|
cancelUrl: String(params.cancel_url || '').trim(),
|
|
245
246
|
subscriptionId: null,
|
|
247
|
+
expiresAt: Math.floor(Date.now() / 1000) + (24 * 60 * 60),
|
|
246
248
|
paymentStatus: 'unpaid',
|
|
247
249
|
status: 'open'
|
|
248
250
|
};
|
|
@@ -65,7 +65,7 @@ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
|
65
65
|
import { randomUUID } from 'crypto';
|
|
66
66
|
import { resolveDeploymentConfig } from '../../config/deployment.js';
|
|
67
67
|
import { getTaskforceCloudEnvironmentConfig, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../../config/cloudEnvironment.js';
|
|
68
|
-
import { resolveErrorStatusCode } from './shared.js';
|
|
68
|
+
import { buildCloudMcpRuntimeAuth, resolveErrorStatusCode } from './shared.js';
|
|
69
69
|
import { applyWorkspaceRepair, scanWorkspaceRepair } from '../../sync/workspaceRepair.js';
|
|
70
70
|
// === SECTION: Registration ===
|
|
71
71
|
export function registerAdminRoutes(deps) {
|
|
@@ -101,7 +101,7 @@ export function registerAdminRoutes(deps) {
|
|
|
101
101
|
const authSettings = globalSettings.auth || {};
|
|
102
102
|
const authRateLimitMaxRequestsRaw = Number(authSettings.authRateLimitMaxRequests);
|
|
103
103
|
const authRateLimitWindowMsRaw = Number(authSettings.authRateLimitWindowMs);
|
|
104
|
-
const
|
|
104
|
+
const onboardingPolicy = core.getOnboardingPolicy();
|
|
105
105
|
return {
|
|
106
106
|
allowSelfRegistration: resolveBooleanSetting(authSettings.allowSelfRegistration, true),
|
|
107
107
|
requireVerifiedEmail: resolveBooleanSetting(authSettings.requireVerifiedEmail, true),
|
|
@@ -113,8 +113,8 @@ export function registerAdminRoutes(deps) {
|
|
|
113
113
|
authRateLimitWindowMs: Number.isFinite(authRateLimitWindowMsRaw) && authRateLimitWindowMsRaw >= 1000
|
|
114
114
|
? Math.floor(authRateLimitWindowMsRaw)
|
|
115
115
|
: 15 * 60_000,
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
onboardingPolicyMode: onboardingPolicy.mode,
|
|
117
|
+
onboardingPlanVersionId: onboardingPolicy.planVersionId
|
|
118
118
|
};
|
|
119
119
|
};
|
|
120
120
|
const getSiteBehavior = () => {
|
|
@@ -163,6 +163,10 @@ export function registerAdminRoutes(deps) {
|
|
|
163
163
|
}
|
|
164
164
|
return String(configured || resolvePublicAuthBaseUrl(req)).replace(/\/+$/, '');
|
|
165
165
|
};
|
|
166
|
+
const resolveAdminCloudEnvironment = (req) => {
|
|
167
|
+
return resolveTaskforceCloudEnvironmentFromBaseUrl(resolveRuntimeConfigBaseUrl(req))
|
|
168
|
+
|| resolveTaskforceCloudEnvironmentFromBaseUrl(resolveCloudLoginBaseUrl(req));
|
|
169
|
+
};
|
|
166
170
|
const resolveMcpOauthAuthorizeUrl = (req) => `${resolveMcpOauthIssuer(req)}/oauth/mcp/authorize`;
|
|
167
171
|
const resolveMcpOauthTokenUrl = (req) => `${resolveMcpOauthIssuer(req)}/oauth/mcp/token`;
|
|
168
172
|
const resolveMcpOauthRegisterUrl = (req) => `${resolveMcpOauthIssuer(req)}/oauth/mcp/register`;
|
|
@@ -470,22 +474,13 @@ export function registerAdminRoutes(deps) {
|
|
|
470
474
|
serverName: 'taskforce-cloud',
|
|
471
475
|
logStartup: false,
|
|
472
476
|
runtimeMode: 'cloud',
|
|
473
|
-
auth: {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
aiProfileToken: auth.aiProfileToken,
|
|
478
|
-
aiProfileName: auth.aiProfileName,
|
|
479
|
-
aiProfileDescription: auth.aiProfileDescription,
|
|
480
|
-
aiProfileRole: auth.aiProfileRole,
|
|
481
|
-
aiProfileProvider: auth.aiProfileProvider,
|
|
482
|
-
aiProfileModel: auth.aiProfileModel,
|
|
483
|
-
aiProfileSurfaceType: auth.aiProfileSurfaceType,
|
|
484
|
-
actorType: auth.authenticatedViaToken ? 'token' : auth.authenticatedViaOAuth ? 'oauth' : 'user',
|
|
477
|
+
auth: buildCloudMcpRuntimeAuth({
|
|
478
|
+
req,
|
|
479
|
+
auth,
|
|
480
|
+
requestHeader,
|
|
485
481
|
requestId: String(req.headers['x-request-id'] || req.headers['x-railway-request-id'] || '').trim() || randomUUID(),
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
},
|
|
482
|
+
authScopes,
|
|
483
|
+
}),
|
|
489
484
|
});
|
|
490
485
|
const transport = new StreamableHTTPServerTransport({
|
|
491
486
|
sessionIdGenerator: undefined,
|
|
@@ -964,6 +959,51 @@ export function registerAdminRoutes(deps) {
|
|
|
964
959
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
965
960
|
res.end(JSON.stringify({ success: true }));
|
|
966
961
|
});
|
|
962
|
+
// DELETE /api/taskforce/admin/system-users/:id - Purge a test user in non-production cloud environments
|
|
963
|
+
addRoute('DELETE', '/api/taskforce/admin/system-users/:id', async (req, res, params) => {
|
|
964
|
+
if (!requireCloudRuntime(res, 'System user purge'))
|
|
965
|
+
return;
|
|
966
|
+
if (!ensureAdminRole(req)) {
|
|
967
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
968
|
+
res.end(JSON.stringify({ error: 'Forbidden: system admin role required.' }));
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
const environment = resolveAdminCloudEnvironment(req);
|
|
972
|
+
if (environment !== 'staging' && environment !== 'performance') {
|
|
973
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
974
|
+
res.end(JSON.stringify({
|
|
975
|
+
error: 'System user purge is only available in staging and performance.',
|
|
976
|
+
code: 'SYSTEM_USER_PURGE_ENVIRONMENT_FORBIDDEN'
|
|
977
|
+
}));
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
const body = await parseJsonBody(req);
|
|
981
|
+
const actor = resolveAdminRequestActor(req);
|
|
982
|
+
try {
|
|
983
|
+
const summary = core.purgeSystemUser({
|
|
984
|
+
targetUserId: params.id,
|
|
985
|
+
actorUserId: actor.userId,
|
|
986
|
+
emailConfirmation: String(body?.emailConfirmation || '')
|
|
987
|
+
});
|
|
988
|
+
auditAdminAction(req, 'purge-system-user', {
|
|
989
|
+
targetUserId: params.id,
|
|
990
|
+
targetEmail: summary.email,
|
|
991
|
+
environment,
|
|
992
|
+
summary
|
|
993
|
+
});
|
|
994
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
995
|
+
res.end(JSON.stringify({ success: true, environment, summary }));
|
|
996
|
+
}
|
|
997
|
+
catch (error) {
|
|
998
|
+
const statusCode = resolveErrorStatusCode(error, 400);
|
|
999
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
1000
|
+
res.end(JSON.stringify({
|
|
1001
|
+
error: String(error?.message || 'Unable to purge system user'),
|
|
1002
|
+
code: error?.code,
|
|
1003
|
+
details: error?.details || null
|
|
1004
|
+
}));
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
967
1007
|
// === SECTION: Admin email settings ===
|
|
968
1008
|
// GET /api/taskforce/admin/email-settings - Read email provider settings
|
|
969
1009
|
addRoute('GET', '/api/taskforce/admin/email-settings', async (req, res) => {
|
|
@@ -1036,7 +1076,9 @@ export function registerAdminRoutes(deps) {
|
|
|
1036
1076
|
res.end(JSON.stringify({ error: 'Forbidden: admin role required.' }));
|
|
1037
1077
|
return;
|
|
1038
1078
|
}
|
|
1039
|
-
const
|
|
1079
|
+
const globalSettings = core.getGlobalSettings();
|
|
1080
|
+
const authSettings = globalSettings.auth || {};
|
|
1081
|
+
const billingSettings = globalSettings.billing || {};
|
|
1040
1082
|
const effective = getAuthBehavior();
|
|
1041
1083
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1042
1084
|
res.end(JSON.stringify({
|
|
@@ -1047,11 +1089,13 @@ export function registerAdminRoutes(deps) {
|
|
|
1047
1089
|
...(typeof authSettings.authRateLimitEnabled === 'boolean' ? { authRateLimitEnabled: authSettings.authRateLimitEnabled } : {}),
|
|
1048
1090
|
...(typeof authSettings.authRateLimitMaxRequests === 'number' ? { authRateLimitMaxRequests: authSettings.authRateLimitMaxRequests } : {}),
|
|
1049
1091
|
...(typeof authSettings.authRateLimitWindowMs === 'number' ? { authRateLimitWindowMs: authSettings.authRateLimitWindowMs } : {}),
|
|
1050
|
-
...((
|
|
1051
|
-
|
|
1092
|
+
...((billingSettings.onboardingPolicy?.mode === 'require_plan_selection'
|
|
1093
|
+
|| billingSettings.onboardingPolicy?.mode === 'auto_grant_free'
|
|
1094
|
+
|| billingSettings.onboardingPolicy?.mode === 'auto_start_trial')
|
|
1095
|
+
? { onboardingPolicyMode: billingSettings.onboardingPolicy.mode }
|
|
1052
1096
|
: {}),
|
|
1053
|
-
...(typeof
|
|
1054
|
-
? {
|
|
1097
|
+
...(typeof billingSettings.onboardingPolicy?.planVersionId === 'string' || billingSettings.onboardingPolicy?.planVersionId === null
|
|
1098
|
+
? { onboardingPlanVersionId: billingSettings.onboardingPolicy?.planVersionId ?? null }
|
|
1055
1099
|
: {})
|
|
1056
1100
|
},
|
|
1057
1101
|
status: {
|
|
@@ -1061,8 +1105,8 @@ export function registerAdminRoutes(deps) {
|
|
|
1061
1105
|
authRateLimitEnabled: effective.authRateLimitEnabled,
|
|
1062
1106
|
authRateLimitMaxRequests: effective.authRateLimitMaxRequests,
|
|
1063
1107
|
authRateLimitWindowMs: effective.authRateLimitWindowMs,
|
|
1064
|
-
|
|
1065
|
-
|
|
1108
|
+
onboardingPolicyMode: effective.onboardingPolicyMode,
|
|
1109
|
+
onboardingPlanVersionId: effective.onboardingPlanVersionId
|
|
1066
1110
|
}
|
|
1067
1111
|
}));
|
|
1068
1112
|
});
|
|
@@ -1688,21 +1732,24 @@ export function registerAdminRoutes(deps) {
|
|
|
1688
1732
|
return;
|
|
1689
1733
|
}
|
|
1690
1734
|
const body = await parseJsonBody(req);
|
|
1691
|
-
const
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1735
|
+
const currentOnboardingPolicy = core.getOnboardingPolicy();
|
|
1736
|
+
const onboardingPolicyMode = body?.onboardingPolicyMode === 'auto_grant_free' || body?.onboardingPolicyMode === 'auto_start_trial'
|
|
1737
|
+
? body.onboardingPolicyMode
|
|
1738
|
+
: body?.onboardingPolicyMode === 'require_plan_selection'
|
|
1739
|
+
? 'require_plan_selection'
|
|
1740
|
+
: currentOnboardingPolicy.mode;
|
|
1741
|
+
const onboardingPlanVersionId = typeof body?.onboardingPlanVersionId === 'string'
|
|
1742
|
+
? body.onboardingPlanVersionId.trim() || null
|
|
1743
|
+
: body?.onboardingPlanVersionId === null
|
|
1697
1744
|
? null
|
|
1698
|
-
:
|
|
1699
|
-
const validation = core.
|
|
1700
|
-
|
|
1701
|
-
|
|
1745
|
+
: currentOnboardingPolicy.planVersionId;
|
|
1746
|
+
const validation = core.validateOnboardingPolicy({
|
|
1747
|
+
mode: onboardingPolicyMode,
|
|
1748
|
+
planVersionId: onboardingPlanVersionId
|
|
1702
1749
|
});
|
|
1703
1750
|
if (!validation.valid) {
|
|
1704
1751
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1705
|
-
res.end(JSON.stringify({ error: validation.message || 'Invalid
|
|
1752
|
+
res.end(JSON.stringify({ error: validation.message || 'Invalid onboarding policy.', code: validation.code, details: validation.details }));
|
|
1706
1753
|
return;
|
|
1707
1754
|
}
|
|
1708
1755
|
core.updateGlobalSettings({
|
|
@@ -1714,9 +1761,13 @@ export function registerAdminRoutes(deps) {
|
|
|
1714
1761
|
...(typeof body?.authRateLimitMaxRequests === 'number' && Number.isFinite(body.authRateLimitMaxRequests)
|
|
1715
1762
|
? { authRateLimitMaxRequests: Math.max(1, Math.floor(body.authRateLimitMaxRequests)) } : {}),
|
|
1716
1763
|
...(typeof body?.authRateLimitWindowMs === 'number' && Number.isFinite(body.authRateLimitWindowMs)
|
|
1717
|
-
? { authRateLimitWindowMs: Math.max(1000, Math.floor(body.authRateLimitWindowMs)) } : {})
|
|
1718
|
-
|
|
1719
|
-
|
|
1764
|
+
? { authRateLimitWindowMs: Math.max(1000, Math.floor(body.authRateLimitWindowMs)) } : {})
|
|
1765
|
+
},
|
|
1766
|
+
billing: {
|
|
1767
|
+
onboardingPolicy: {
|
|
1768
|
+
mode: onboardingPolicyMode,
|
|
1769
|
+
planVersionId: onboardingPlanVersionId ?? null
|
|
1770
|
+
}
|
|
1720
1771
|
}
|
|
1721
1772
|
});
|
|
1722
1773
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* POST /api/taskforce/auth/invite/join
|
|
21
21
|
* POST /api/taskforce/auth/logout
|
|
22
22
|
* GET /api/taskforce/account/access-status
|
|
23
|
+
* GET /api/taskforce/account/profile-summary
|
|
23
24
|
* GET /api/taskforce/account/entitlements
|
|
24
25
|
* GET /api/taskforce/account/team-management-access
|
|
25
26
|
* GET /api/taskforce/account/plan-preview
|