@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
|
@@ -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
|
|
@@ -73,6 +74,121 @@ export function registerAuthRoutes(deps) {
|
|
|
73
74
|
const stateAllows = state === 'active' || state === 'trialing' || state === 'grace';
|
|
74
75
|
return teamAllowed && stateAllows ? 'team' : 'personal';
|
|
75
76
|
};
|
|
77
|
+
const resolveBillingConflictCodeForUser = (userIdRaw, gateRaw) => {
|
|
78
|
+
const userId = String(userIdRaw || '').trim();
|
|
79
|
+
const gate = String(gateRaw || '').trim().toLowerCase();
|
|
80
|
+
if (!userId || !gate || gate === 'ok')
|
|
81
|
+
return null;
|
|
82
|
+
const db = core.getDatabaseAdapter();
|
|
83
|
+
const tenantId = core.getTenantId();
|
|
84
|
+
try {
|
|
85
|
+
const row = db.prepare(`
|
|
86
|
+
SELECT session_id, status, expires_at
|
|
87
|
+
FROM billing_checkout_attempts
|
|
88
|
+
WHERE tenant_id = ? AND user_id = ?
|
|
89
|
+
ORDER BY created_at DESC, session_id DESC
|
|
90
|
+
LIMIT 1
|
|
91
|
+
`).get(tenantId, userId);
|
|
92
|
+
const sessionId = String(row?.session_id || '').trim();
|
|
93
|
+
const status = String(row?.status || '').trim().toLowerCase();
|
|
94
|
+
const expiresAt = String(row?.expires_at || '').trim();
|
|
95
|
+
if (!sessionId)
|
|
96
|
+
return null;
|
|
97
|
+
if (status === 'open' && expiresAt) {
|
|
98
|
+
const expiresAtMs = Date.parse(expiresAt);
|
|
99
|
+
if (Number.isFinite(expiresAtMs) && expiresAtMs <= Date.now()) {
|
|
100
|
+
db.prepare(`
|
|
101
|
+
UPDATE billing_checkout_attempts
|
|
102
|
+
SET status = 'expired', updated_at = ?
|
|
103
|
+
WHERE tenant_id = ? AND session_id = ?
|
|
104
|
+
`).run(new Date().toISOString(), tenantId, sessionId);
|
|
105
|
+
return 'CHECKOUT_SESSION_EXPIRED';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (status === 'expired')
|
|
109
|
+
return 'CHECKOUT_SESSION_EXPIRED';
|
|
110
|
+
if (status === 'superseded')
|
|
111
|
+
return 'CHECKOUT_SESSION_SUPERSEDED';
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
};
|
|
118
|
+
const buildAccountProfileSummary = (req) => {
|
|
119
|
+
const userId = requestUserId(req);
|
|
120
|
+
const workspaceId = requestWorkspaceId(req);
|
|
121
|
+
const access = core.getAccountAccessStatus(userId);
|
|
122
|
+
const resolvedAccess = core.resolveUserAccess(userId, workspaceId, 'member');
|
|
123
|
+
const hintedRole = normalizeTeamWorkspaceRole(requestHeader(req, 'x-taskforce-workspace-role', ''));
|
|
124
|
+
const workspaceRole = normalizeTeamWorkspaceRole(resolvedAccess?.role || hintedRole || null);
|
|
125
|
+
const canManageWorkspace = workspaceRole === 'owner' || workspaceRole === 'admin';
|
|
126
|
+
const db = core.getDatabaseAdapter();
|
|
127
|
+
const tenantId = core.getTenantId();
|
|
128
|
+
let billingRow;
|
|
129
|
+
try {
|
|
130
|
+
billingRow = db.prepare(`
|
|
131
|
+
SELECT stripe_status, stripe_customer_id, stripe_subscription_id, stripe_price_id, billing_interval,
|
|
132
|
+
trial_end, current_period_end, effective_until, entitlement_state, tier
|
|
133
|
+
FROM user_billing
|
|
134
|
+
WHERE tenant_id = ? AND user_id = ?
|
|
135
|
+
LIMIT 1
|
|
136
|
+
`).get(tenantId, userId);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
billingRow = undefined;
|
|
140
|
+
}
|
|
141
|
+
const planId = String(access.entitlement?.planId || billingRow?.tier || '').trim() || null;
|
|
142
|
+
const planVersionId = String(access.entitlement?.planVersionId || '').trim() || null;
|
|
143
|
+
let planRow;
|
|
144
|
+
if (planId) {
|
|
145
|
+
try {
|
|
146
|
+
planRow = db.prepare(`
|
|
147
|
+
SELECT display_name
|
|
148
|
+
FROM plan_catalog
|
|
149
|
+
WHERE tenant_id = ? AND plan_id = ?
|
|
150
|
+
LIMIT 1
|
|
151
|
+
`).get(tenantId, planId);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
planRow = undefined;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
let teamPlanMode = 'unknown';
|
|
158
|
+
try {
|
|
159
|
+
const entitlements = core.getAccountEntitlementsPayload({ userId, workspaceId });
|
|
160
|
+
teamPlanMode = resolveTeamPlanModeFromEntitlements(entitlements);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
if (error?.code === 'ENTITLEMENT_NOT_FOUND' && (access.gate === 'plan_selection_required' || access.gate === 'checkout_pending')) {
|
|
164
|
+
teamPlanMode = 'personal';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const billingConflictCode = resolveBillingConflictCodeForUser(userId, access.gate);
|
|
168
|
+
return {
|
|
169
|
+
stripeStatus: billingRow?.stripe_status || null,
|
|
170
|
+
stripeCustomerId: billingRow?.stripe_customer_id || null,
|
|
171
|
+
stripeSubscriptionId: billingRow?.stripe_subscription_id || null,
|
|
172
|
+
stripePriceId: billingRow?.stripe_price_id || null,
|
|
173
|
+
billingInterval: billingRow?.billing_interval || null,
|
|
174
|
+
trialEnd: billingRow?.trial_end || null,
|
|
175
|
+
currentPeriodEnd: billingRow?.current_period_end || null,
|
|
176
|
+
effectiveUntil: billingRow?.effective_until || null,
|
|
177
|
+
entitlementState: access.entitlement?.state || billingRow?.entitlement_state || null,
|
|
178
|
+
planVersionId,
|
|
179
|
+
planId,
|
|
180
|
+
planName: planRow?.display_name ? String(planRow.display_name) : planId,
|
|
181
|
+
gate: access.gate,
|
|
182
|
+
message: access.message,
|
|
183
|
+
billingConflictCode,
|
|
184
|
+
planSelectionRequired: access.planSelectionRequired,
|
|
185
|
+
workspaceId,
|
|
186
|
+
workspaceRole,
|
|
187
|
+
canManageWorkspace,
|
|
188
|
+
teamPlanMode,
|
|
189
|
+
teamManagementAllowed: canManageWorkspace && teamPlanMode === 'team'
|
|
190
|
+
};
|
|
191
|
+
};
|
|
76
192
|
// === SECTION: Email delivery helpers ===
|
|
77
193
|
const getEmailConfig = () => {
|
|
78
194
|
if (context.authConfig?.runtimeMode !== 'cloud') {
|
|
@@ -86,7 +202,7 @@ export function registerAuthRoutes(deps) {
|
|
|
86
202
|
const authSettings = globalSettings.auth || {};
|
|
87
203
|
const authRateLimitMaxRequestsRaw = Number(authSettings.authRateLimitMaxRequests);
|
|
88
204
|
const authRateLimitWindowMsRaw = Number(authSettings.authRateLimitWindowMs);
|
|
89
|
-
const
|
|
205
|
+
const onboardingPolicy = core.getOnboardingPolicy();
|
|
90
206
|
return {
|
|
91
207
|
allowSelfRegistration: resolveBooleanSetting(authSettings.allowSelfRegistration, true),
|
|
92
208
|
requireVerifiedEmail: resolveBooleanSetting(authSettings.requireVerifiedEmail, true),
|
|
@@ -98,8 +214,8 @@ export function registerAuthRoutes(deps) {
|
|
|
98
214
|
authRateLimitWindowMs: Number.isFinite(authRateLimitWindowMsRaw) && authRateLimitWindowMsRaw >= 1000
|
|
99
215
|
? Math.floor(authRateLimitWindowMsRaw)
|
|
100
216
|
: 15 * 60_000,
|
|
101
|
-
|
|
102
|
-
|
|
217
|
+
onboardingPolicyMode: onboardingPolicy.mode,
|
|
218
|
+
onboardingPlanVersionId: onboardingPolicy.planVersionId
|
|
103
219
|
};
|
|
104
220
|
};
|
|
105
221
|
const getSiteBehavior = () => {
|
|
@@ -110,6 +226,23 @@ export function registerAuthRoutes(deps) {
|
|
|
110
226
|
pricingPageEnabled: resolveBooleanSetting(siteSettings.pricingPageEnabled, true)
|
|
111
227
|
};
|
|
112
228
|
};
|
|
229
|
+
const buildPasswordSessionCookieForUser = (req, authConfig, userIdRaw, options) => {
|
|
230
|
+
const userId = String(userIdRaw || '').trim();
|
|
231
|
+
const fallbackRole = String(options?.fallbackRole || authConfig.defaultRole || 'member').trim() || 'member';
|
|
232
|
+
const resolvedWorkspaceId = core.resolvePreferredUserWorkspaceId(userId, {
|
|
233
|
+
preferredWorkspaceId: options?.preferredWorkspaceId || null,
|
|
234
|
+
persistedWorkspaceId: options?.persistedWorkspaceId || null
|
|
235
|
+
});
|
|
236
|
+
const access = resolvedWorkspaceId
|
|
237
|
+
? core.resolveUserAccess(userId, resolvedWorkspaceId, fallbackRole)
|
|
238
|
+
: null;
|
|
239
|
+
return createSessionTokenCookie(`pwd:${userId}`, authConfig, {
|
|
240
|
+
role: (access?.role || fallbackRole),
|
|
241
|
+
workspaceId: access?.workspaceId || '',
|
|
242
|
+
userId,
|
|
243
|
+
...resolveSessionCookieAttributesForRequest(req, authConfig)
|
|
244
|
+
});
|
|
245
|
+
};
|
|
113
246
|
const AVATAR_DRAFT_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
114
247
|
const MAX_AVATAR_UPLOAD_BYTES = 5 * 1024 * 1024;
|
|
115
248
|
const AVATAR_MIME_EXTENSION = {
|
|
@@ -175,7 +308,7 @@ export function registerAuthRoutes(deps) {
|
|
|
175
308
|
? 'Verify your Taskforce account'
|
|
176
309
|
: 'Reset your Taskforce password';
|
|
177
310
|
const text = kind === 'verify-email'
|
|
178
|
-
? `Welcome to Taskforce.\n\
|
|
311
|
+
? `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.`
|
|
179
312
|
: `Reset your Taskforce password using this link:\n${link}\n\nIf you did not request this reset, you can ignore this message.`;
|
|
180
313
|
const result = await sendEmail({
|
|
181
314
|
to: email,
|
|
@@ -268,7 +401,8 @@ export function registerAuthRoutes(deps) {
|
|
|
268
401
|
let avatarUrl = null;
|
|
269
402
|
let betaAccess = true;
|
|
270
403
|
if (authenticated && access?.userId) {
|
|
271
|
-
|
|
404
|
+
// Keep the session endpoint fast on reload; avatar draft cleanup is best-effort housekeeping.
|
|
405
|
+
void cleanupStaleAvatarDrafts(access.userId).catch(() => { });
|
|
272
406
|
betaAccess = core.getUserBetaAccess(access.userId);
|
|
273
407
|
const resolved = core.resolveUserAccess(access.userId, workspaceId, role);
|
|
274
408
|
if (resolved) {
|
|
@@ -643,7 +777,7 @@ export function registerAuthRoutes(deps) {
|
|
|
643
777
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
644
778
|
res.end(JSON.stringify({ success: false, error: 'Email and password are required.' }));
|
|
645
779
|
});
|
|
646
|
-
// POST /api/taskforce/auth/register - Create account and
|
|
780
|
+
// POST /api/taskforce/auth/register - Create account and optionally start a session
|
|
647
781
|
addRoute('POST', '/api/taskforce/auth/register', async (req, res) => {
|
|
648
782
|
if (!requireCloudRuntime(res, 'Authentication'))
|
|
649
783
|
return;
|
|
@@ -693,13 +827,6 @@ export function registerAuthRoutes(deps) {
|
|
|
693
827
|
role,
|
|
694
828
|
betaAccess: authBehavior.defaultNewUserBetaAccess
|
|
695
829
|
});
|
|
696
|
-
const sessionWorkspaceId = created.workspaceId || '';
|
|
697
|
-
const cookie = createSessionTokenCookie(`pwd:${created.userId}`, authConfig, {
|
|
698
|
-
role: created.role,
|
|
699
|
-
workspaceId: sessionWorkspaceId,
|
|
700
|
-
userId: created.userId,
|
|
701
|
-
...resolveSessionCookieAttributesForRequest(req, authConfig)
|
|
702
|
-
});
|
|
703
830
|
let emailSent = true;
|
|
704
831
|
let emailError;
|
|
705
832
|
let verificationToken;
|
|
@@ -719,7 +846,13 @@ export function registerAuthRoutes(deps) {
|
|
|
719
846
|
role: created.role,
|
|
720
847
|
emailSent
|
|
721
848
|
});
|
|
722
|
-
|
|
849
|
+
if (!authBehavior.requireVerifiedEmail) {
|
|
850
|
+
res.setHeader('Set-Cookie', buildPasswordSessionCookieForUser(req, authConfig, created.userId, {
|
|
851
|
+
preferredWorkspaceId: created.workspaceId || null,
|
|
852
|
+
persistedWorkspaceId: created.workspaceId || null,
|
|
853
|
+
fallbackRole: created.role
|
|
854
|
+
}));
|
|
855
|
+
}
|
|
723
856
|
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
724
857
|
res.end(JSON.stringify({
|
|
725
858
|
success: true,
|
|
@@ -727,6 +860,8 @@ export function registerAuthRoutes(deps) {
|
|
|
727
860
|
role: created.role,
|
|
728
861
|
workspaceSetupRequired: !created.workspaceId,
|
|
729
862
|
planSelectionRequired: created.planSelectionRequired === true,
|
|
863
|
+
checkoutPending: created.checkoutPending === true,
|
|
864
|
+
commercialState: created.commercialState,
|
|
730
865
|
verificationRequired: authBehavior.requireVerifiedEmail,
|
|
731
866
|
emailSent,
|
|
732
867
|
emailError,
|
|
@@ -736,6 +871,40 @@ export function registerAuthRoutes(deps) {
|
|
|
736
871
|
}));
|
|
737
872
|
}
|
|
738
873
|
catch (error) {
|
|
874
|
+
if (error?.code === 'EMAIL_ALREADY_EXISTS'
|
|
875
|
+
&& authBehavior.requireVerifiedEmail
|
|
876
|
+
&& email
|
|
877
|
+
&& !core.isEmailVerified(email)) {
|
|
878
|
+
let emailSent = true;
|
|
879
|
+
let emailError;
|
|
880
|
+
let verificationToken;
|
|
881
|
+
const verification = core.requestEmailVerification({ email });
|
|
882
|
+
verificationToken = verification.token || null;
|
|
883
|
+
if (verification.token) {
|
|
884
|
+
const delivery = await deliverAuthEmail('verify-email', email, verification.token, req);
|
|
885
|
+
emailSent = delivery.ok;
|
|
886
|
+
emailError = delivery.error;
|
|
887
|
+
}
|
|
888
|
+
auditAuthAction(req, 'auth-register-existing-unverified', {
|
|
889
|
+
email,
|
|
890
|
+
workspaceId: workspaceIdInput || null,
|
|
891
|
+
code: 'EMAIL_ALREADY_EXISTS_UNVERIFIED',
|
|
892
|
+
emailSent
|
|
893
|
+
});
|
|
894
|
+
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
895
|
+
res.end(JSON.stringify({
|
|
896
|
+
success: false,
|
|
897
|
+
error: 'This email already has an unverified account. Continue with email verification instead.',
|
|
898
|
+
code: 'EMAIL_ALREADY_EXISTS_UNVERIFIED',
|
|
899
|
+
verificationRequired: true,
|
|
900
|
+
emailSent,
|
|
901
|
+
emailError,
|
|
902
|
+
verificationToken: shouldExposeDevAuthTokens()
|
|
903
|
+
? (verificationToken || null)
|
|
904
|
+
: undefined
|
|
905
|
+
}));
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
739
908
|
const statusCode = Number(error?.statusCode || 400);
|
|
740
909
|
const message = String(error?.message || 'Unable to register');
|
|
741
910
|
auditAuthAction(req, 'auth-register-failed', {
|
|
@@ -794,6 +963,12 @@ export function registerAuthRoutes(deps) {
|
|
|
794
963
|
addRoute('POST', '/api/taskforce/auth/verify-email/confirm', async (req, res) => {
|
|
795
964
|
if (!requireCloudRuntime(res, 'Authentication'))
|
|
796
965
|
return;
|
|
966
|
+
const authConfig = context.authConfig;
|
|
967
|
+
if (!authConfig?.enabled) {
|
|
968
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
969
|
+
res.end(JSON.stringify({ success: false, error: 'Authentication is not configured.' }));
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
797
972
|
const body = await parseJsonBody(req);
|
|
798
973
|
const token = String(body?.token || '').trim();
|
|
799
974
|
if (!token) {
|
|
@@ -801,6 +976,15 @@ export function registerAuthRoutes(deps) {
|
|
|
801
976
|
res.end(JSON.stringify({ success: false, error: 'Token is required.' }));
|
|
802
977
|
return;
|
|
803
978
|
}
|
|
979
|
+
const inspection = core.inspectOneTimeAuthToken(token, 'email_verification');
|
|
980
|
+
if (!inspection.userId) {
|
|
981
|
+
auditAuthAction(req, 'auth-verify-confirm-failed', {
|
|
982
|
+
tokenProvided: true
|
|
983
|
+
});
|
|
984
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
985
|
+
res.end(JSON.stringify({ success: false, error: 'Invalid or expired token.' }));
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
804
988
|
const verified = core.verifyEmailWithToken(token);
|
|
805
989
|
if (!verified) {
|
|
806
990
|
auditAuthAction(req, 'auth-verify-confirm-failed', {
|
|
@@ -813,6 +997,11 @@ export function registerAuthRoutes(deps) {
|
|
|
813
997
|
auditAuthAction(req, 'auth-verify-confirm-success', {
|
|
814
998
|
tokenProvided: true
|
|
815
999
|
});
|
|
1000
|
+
const cookie = buildPasswordSessionCookieForUser(req, authConfig, inspection.userId, {
|
|
1001
|
+
preferredWorkspaceId: inspection.workspaceId || null,
|
|
1002
|
+
persistedWorkspaceId: inspection.workspaceId || null
|
|
1003
|
+
});
|
|
1004
|
+
res.setHeader('Set-Cookie', cookie);
|
|
816
1005
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
817
1006
|
res.end(JSON.stringify({ success: true }));
|
|
818
1007
|
});
|
|
@@ -1098,6 +1287,24 @@ export function registerAuthRoutes(deps) {
|
|
|
1098
1287
|
res.end(JSON.stringify({ error: String(error?.message || 'Unable to resolve account access status'), code: error?.code, details: error?.details }));
|
|
1099
1288
|
}
|
|
1100
1289
|
});
|
|
1290
|
+
// GET /api/taskforce/account/profile-summary - Fast authoritative summary for the visible account surface.
|
|
1291
|
+
addRoute('GET', '/api/taskforce/account/profile-summary', async (req, res) => {
|
|
1292
|
+
if (!ensureAuthenticatedUser(req)) {
|
|
1293
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
1294
|
+
res.end(JSON.stringify({ error: 'Authentication required.' }));
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
try {
|
|
1298
|
+
const payload = buildAccountProfileSummary(req);
|
|
1299
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1300
|
+
res.end(JSON.stringify(payload));
|
|
1301
|
+
}
|
|
1302
|
+
catch (error) {
|
|
1303
|
+
const statusCode = resolveErrorStatusCode(error, 400);
|
|
1304
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
1305
|
+
res.end(JSON.stringify({ error: String(error?.message || 'Unable to resolve account profile summary'), code: error?.code, details: error?.details }));
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1101
1308
|
// GET /api/taskforce/account/entitlements - Resolve effective plan/features for the active workspace.
|
|
1102
1309
|
addRoute('GET', '/api/taskforce/account/entitlements', async (req, res) => {
|
|
1103
1310
|
if (!ensureAuthenticatedUser(req)) {
|
|
@@ -1115,11 +1322,11 @@ export function registerAuthRoutes(deps) {
|
|
|
1115
1322
|
catch (error) {
|
|
1116
1323
|
if (error?.code === 'ENTITLEMENT_NOT_FOUND') {
|
|
1117
1324
|
const access = core.getAccountAccessStatus(requestUserId(req));
|
|
1118
|
-
if (access.gate === 'plan_selection_required') {
|
|
1325
|
+
if (access.gate === 'plan_selection_required' || access.gate === 'checkout_pending') {
|
|
1119
1326
|
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
1120
1327
|
res.end(JSON.stringify({
|
|
1121
1328
|
error: access.message,
|
|
1122
|
-
code: 'PLAN_SELECTION_REQUIRED',
|
|
1329
|
+
code: access.gate === 'checkout_pending' ? 'CHECKOUT_PENDING' : 'PLAN_SELECTION_REQUIRED',
|
|
1123
1330
|
details: {
|
|
1124
1331
|
gate: access.gate,
|
|
1125
1332
|
planSelectionRequired: access.planSelectionRequired,
|
|
@@ -1141,51 +1348,20 @@ export function registerAuthRoutes(deps) {
|
|
|
1141
1348
|
res.end(JSON.stringify({ error: 'Authentication required.' }));
|
|
1142
1349
|
return;
|
|
1143
1350
|
}
|
|
1144
|
-
const wid = requestWorkspaceId(req);
|
|
1145
|
-
const userId = requestUserId(req);
|
|
1146
|
-
const hintedRole = normalizeTeamWorkspaceRole(requestHeader(req, 'x-taskforce-workspace-role', ''));
|
|
1147
|
-
const resolvedAccess = core.resolveUserAccess(userId, wid, 'member');
|
|
1148
|
-
const role = normalizeTeamWorkspaceRole(resolvedAccess?.role || hintedRole || null);
|
|
1149
|
-
const canManageWorkspace = role === 'owner' || role === 'admin';
|
|
1150
|
-
if (!canManageWorkspace) {
|
|
1151
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1152
|
-
res.end(JSON.stringify({
|
|
1153
|
-
workspaceId: wid,
|
|
1154
|
-
role,
|
|
1155
|
-
canManageWorkspace: false,
|
|
1156
|
-
teamPlanMode: 'unknown',
|
|
1157
|
-
allowed: false
|
|
1158
|
-
}));
|
|
1159
|
-
return;
|
|
1160
|
-
}
|
|
1161
1351
|
try {
|
|
1162
|
-
const
|
|
1163
|
-
const teamPlanMode = resolveTeamPlanModeFromEntitlements(entitlements);
|
|
1352
|
+
const payload = buildAccountProfileSummary(req);
|
|
1164
1353
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1165
1354
|
res.end(JSON.stringify({
|
|
1166
|
-
workspaceId:
|
|
1167
|
-
role,
|
|
1168
|
-
canManageWorkspace:
|
|
1169
|
-
teamPlanMode,
|
|
1170
|
-
allowed:
|
|
1355
|
+
workspaceId: payload.workspaceId,
|
|
1356
|
+
role: payload.workspaceRole,
|
|
1357
|
+
canManageWorkspace: payload.canManageWorkspace,
|
|
1358
|
+
teamPlanMode: payload.teamPlanMode,
|
|
1359
|
+
allowed: payload.teamManagementAllowed,
|
|
1360
|
+
gate: payload.gate,
|
|
1361
|
+
planSelectionRequired: payload.planSelectionRequired
|
|
1171
1362
|
}));
|
|
1172
1363
|
}
|
|
1173
1364
|
catch (error) {
|
|
1174
|
-
if (error?.code === 'ENTITLEMENT_NOT_FOUND') {
|
|
1175
|
-
const access = core.getAccountAccessStatus(userId);
|
|
1176
|
-
const teamPlanMode = access.gate === 'plan_selection_required' ? 'personal' : 'unknown';
|
|
1177
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1178
|
-
res.end(JSON.stringify({
|
|
1179
|
-
workspaceId: wid,
|
|
1180
|
-
role,
|
|
1181
|
-
canManageWorkspace: true,
|
|
1182
|
-
teamPlanMode,
|
|
1183
|
-
allowed: false,
|
|
1184
|
-
gate: access.gate,
|
|
1185
|
-
planSelectionRequired: access.planSelectionRequired
|
|
1186
|
-
}));
|
|
1187
|
-
return;
|
|
1188
|
-
}
|
|
1189
1365
|
const statusCode = resolveErrorStatusCode(error, 400);
|
|
1190
1366
|
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
1191
1367
|
res.end(JSON.stringify({ error: String(error?.message || 'Unable to resolve team management access'), code: error?.code, details: error?.details }));
|