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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion apps/sim/app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Suspense } from 'react'
import type { Metadata } from 'next'
import { redirect } from 'next/navigation'
import { getSession } from '@/lib/auth'
import { isAuthDisabled } from '@/lib/core/config/feature-flags'
import { validateCallbackUrl } from '@/lib/core/security/input-validation'
import { getOAuthProviderStatus } from '@/app/(auth)/components/oauth-provider-checker'
import LoginForm from '@/app/(auth)/login/login-form'

Expand All @@ -9,7 +13,22 @@ export const metadata: Metadata = {

export const dynamic = 'force-dynamic'

export default async function LoginPage() {
export default async function LoginPage({
searchParams,
}: {
searchParams: Promise<Record<string, string | string[] | undefined>>
}) {
const session = await getSession()
if (session?.user || isAuthDisabled) {
const resolvedSearchParams = await searchParams
const callbackUrl =
typeof resolvedSearchParams.callbackUrl === 'string' &&
validateCallbackUrl(resolvedSearchParams.callbackUrl)
? resolvedSearchParams.callbackUrl
: '/workspace'
redirect(callbackUrl)
}

const { githubAvailable, googleAvailable, isProduction } = await getOAuthProviderStatus()

return (
Expand Down
22 changes: 20 additions & 2 deletions apps/sim/app/(auth)/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { Metadata } from 'next'
import { isRegistrationDisabled } from '@/lib/core/config/feature-flags'
import { redirect } from 'next/navigation'
import { getSession } from '@/lib/auth'
import { isAuthDisabled, isRegistrationDisabled } from '@/lib/core/config/feature-flags'
import { validateCallbackUrl } from '@/lib/core/security/input-validation'
import { getOAuthProviderStatus } from '@/app/(auth)/components/oauth-provider-checker'
import SignupForm from '@/app/(auth)/signup/signup-form'

Expand All @@ -9,7 +12,22 @@ export const metadata: Metadata = {

export const dynamic = 'force-dynamic'

export default async function SignupPage() {
export default async function SignupPage({
searchParams,
}: {
searchParams: Promise<Record<string, string | string[] | undefined>>
}) {
const session = await getSession()
if (session?.user || isAuthDisabled) {
const resolvedSearchParams = await searchParams
const callbackUrl =
typeof resolvedSearchParams.callbackUrl === 'string' &&
validateCallbackUrl(resolvedSearchParams.callbackUrl)
? resolvedSearchParams.callbackUrl
: '/workspace'
redirect(callbackUrl)
}

if (isRegistrationDisabled) {
return <div>Registration is disabled, please contact your admin.</div>
}
Expand Down
287 changes: 287 additions & 0 deletions apps/sim/blocks/blocks/todoist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
import { TodoistIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
import type { TodoistResponse } from '@/tools/todoist/types'

export const TodoistBlock: BlockConfig<TodoistResponse> = {
type: 'todoist',
name: 'Todoist',
description: 'Manage tasks and projects in Todoist',
authMode: AuthMode.ApiKey,
longDescription:
'Integrate Todoist into workflows to create, update, get, list, and complete tasks, list projects, and add comments.',
category: 'tools',
integrationType: IntegrationType.Productivity,
tags: ['automation'],
docsLink: 'https://docs.sim.ai/tools/todoist',
bgColor: '#E44332',
icon: TodoistIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Create Task', id: 'todoist_create_task' },
{ label: 'Get Task', id: 'todoist_get_task' },
{ label: 'List Tasks', id: 'todoist_list_tasks' },
{ label: 'Update Task', id: 'todoist_update_task' },
{ label: 'Close Task', id: 'todoist_close_task' },
{ label: 'Delete Task', id: 'todoist_delete_task' },
{ label: 'List Projects', id: 'todoist_list_projects' },
{ label: 'Add Comment', id: 'todoist_add_comment' },
],
value: () => 'todoist_create_task',
},
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
placeholder: 'Enter your Todoist API token',
password: true,
required: true,
},
{
id: 'taskId',
title: 'Task ID',
type: 'short-input',
placeholder: 'Enter task ID',
required: true,
condition: {
field: 'operation',
value: [
'todoist_get_task',
'todoist_update_task',
'todoist_close_task',
'todoist_delete_task',
'todoist_add_comment',
],
},
},
{
id: 'content',
title: 'Task Content',
type: 'short-input',
placeholder: 'e.g. Buy milk',
required: {
field: 'operation',
value: 'todoist_create_task',
},
condition: {
field: 'operation',
value: ['todoist_create_task', 'todoist_update_task'],
},
},
{
id: 'description',
title: 'Description',
type: 'long-input',
placeholder: 'Enter task details/description',
condition: {
field: 'operation',
value: ['todoist_create_task', 'todoist_update_task'],
},
},
{
id: 'projectId',
title: 'Project ID',
type: 'short-input',
placeholder: 'Filter by or add to Project ID',
condition: {
field: 'operation',
value: ['todoist_create_task', 'todoist_list_tasks'],
},
},
{
id: 'priority',
title: 'Priority',
type: 'dropdown',
options: [
{ label: 'No change', id: '0' },
{ label: 'Priority 1 (Normal)', id: '1' },
{ label: 'Priority 2', id: '2' },
{ label: 'Priority 3', id: '3' },
{ label: 'Priority 4 (Urgent)', id: '4' },
],
value: () => '0',
condition: {
field: 'operation',
value: ['todoist_create_task', 'todoist_update_task'],
},
},
{
id: 'dueString',
title: 'Due Date',
type: 'short-input',
placeholder: 'e.g. tomorrow, next Friday, YYYY-MM-DD',
condition: {
field: 'operation',
value: ['todoist_create_task', 'todoist_update_task'],
},
wandConfig: {
enabled: true,
prompt: `Generate a due date description string (e.g. "tomorrow", "Friday at 2pm", "YYYY-MM-DD").
Return ONLY the description string.`,
placeholder: 'Describe the due date...',
},
},
{
id: 'labels',
title: 'Labels',
type: 'short-input',
placeholder: 'comma, separated, labels',
condition: {
field: 'operation',
value: ['todoist_create_task', 'todoist_update_task'],
},
},
{
id: 'filter',
title: 'Filter Query',
type: 'short-input',
placeholder: 'e.g. today, overdue, p1',
condition: {
field: 'operation',
value: 'todoist_list_tasks',
},
},
{
id: 'label',
title: 'Label Name',
type: 'short-input',
placeholder: 'Filter by label name',
condition: {
field: 'operation',
value: 'todoist_list_tasks',
},
},
{
id: 'commentContent',
title: 'Comment Content',
type: 'long-input',
placeholder: 'Enter comment content',
required: {
field: 'operation',
value: 'todoist_add_comment',
},
condition: {
field: 'operation',
value: 'todoist_add_comment',
},
},
],
tools: {
access: [
'todoist_create_task',
'todoist_get_task',
'todoist_list_tasks',
'todoist_update_task',
'todoist_close_task',
'todoist_delete_task',
'todoist_list_projects',
'todoist_add_comment',
],
config: {
tool: (params) => params.operation || 'todoist_create_task',
params: (params) => {
const { operation, apiKey } = params
const baseParams = { apiKey }

const labelsArray = params.labels
? params.labels
.split(',')
.map((l: string) => l.trim())
.filter((l: string) => l.length > 0)
: undefined

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Labels param crashes on arrays

Medium Severity

tools.config.params builds labelsArray by calling .split(',') on params.labels whenever it is truthy. Wired workflow inputs or agent-provided values can be a string array, which causes a runtime TypeError and fails the block run.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2e92c72. Configure here.


switch (operation) {
case 'todoist_create_task':
return {
...baseParams,
content: params.content,
description: params.description || undefined,
projectId: params.projectId || undefined,
priority:
params.priority && params.priority !== '0' ? Number(params.priority) : undefined,
dueString: params.dueString || undefined,
labels: labelsArray,
}
case 'todoist_get_task':
return {
...baseParams,
taskId: params.taskId,
}
case 'todoist_list_tasks':
return {
...baseParams,
projectId: params.projectId || undefined,
filter: params.filter || undefined,
label: params.label || undefined,
}
case 'todoist_update_task':
return {
...baseParams,
taskId: params.taskId,
content: params.content || undefined,
description: params.description || undefined,
priority:
params.priority && params.priority !== '0' ? Number(params.priority) : undefined,
dueString: params.dueString || undefined,
labels: labelsArray,
}
Comment thread
cursor[bot] marked this conversation as resolved.
case 'todoist_close_task':
return {
...baseParams,
taskId: params.taskId,
}
case 'todoist_delete_task':
return {
...baseParams,
taskId: params.taskId,
}
case 'todoist_list_projects':
return baseParams
case 'todoist_add_comment':
return {
...baseParams,
taskId: params.taskId,
content: params.commentContent,
}
default:
return baseParams
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
apiKey: { type: 'string', description: 'Todoist API Key' },
taskId: { type: 'string', description: 'The ID of the task' },
content: { type: 'string', description: 'Task title or content' },
description: { type: 'string', description: 'Task description' },
projectId: { type: 'string', description: 'The project ID' },
priority: { type: 'string', description: 'Priority level (1-4)' },
dueString: { type: 'string', description: 'Due date string representation' },
labels: { type: 'string', description: 'Comma-separated labels' },
filter: { type: 'string', description: 'Todoist filter query' },
label: { type: 'string', description: 'Label filter name' },
commentContent: { type: 'string', description: 'Comment text content' },
},
outputs: {
id: { type: 'string', description: 'The unique ID of the task or comment' },
content: { type: 'string', description: 'The title/content' },
description: { type: 'string', description: 'Detailed description' },
projectId: { type: 'string', description: 'The project ID' },
priority: { type: 'number', description: 'Priority level (1-4)' },
url: { type: 'string', description: 'Todoist web URL' },
isCompleted: { type: 'boolean', description: 'Completion status' },
createdAt: { type: 'string', description: 'Creation timestamp' },
due: { type: 'json', description: 'Due date information' },
labels: { type: 'array', description: 'List of labels' },
success: { type: 'boolean', description: 'Operation success status' },
taskId: { type: 'string', description: 'Target task ID' },
tasks: { type: 'json', description: 'Array of tasks' },
projects: { type: 'json', description: 'Array of projects' },
postedAt: { type: 'string', description: 'Comment post timestamp' },
},
}
2 changes: 2 additions & 0 deletions apps/sim/blocks/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ import { TelegramBlock } from '@/blocks/blocks/telegram'
import { TextractBlock, TextractV2Block } from '@/blocks/blocks/textract'
import { ThinkingBlock } from '@/blocks/blocks/thinking'
import { TinybirdBlock } from '@/blocks/blocks/tinybird'
import { TodoistBlock } from '@/blocks/blocks/todoist'
import { TranslateBlock } from '@/blocks/blocks/translate'
import { TrelloBlock } from '@/blocks/blocks/trello'
import { TtsBlock } from '@/blocks/blocks/tts'
Expand Down Expand Up @@ -492,6 +493,7 @@ export const registry: Record<string, BlockConfig> = {
textract_v2: TextractV2Block,
thinking: ThinkingBlock,
tinybird: TinybirdBlock,
todoist: TodoistBlock,
translate: TranslateBlock,
trello: TrelloBlock,
tts: TtsBlock,
Expand Down
12 changes: 12 additions & 0 deletions apps/sim/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7391,3 +7391,15 @@ export function WizaIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}

export function TodoistIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'>
<path d='m0 0H512V512H0' fill='#E44232' />
<path
fill='#fff'
d='M167.1 240.2l103.6-59.7h.1l106.8-61.6c4.5-2.6 4.7-10.6-.3-13.5l-3.6-2-14.6-8.4a16.6 16.6 0 00-16.2.2L166.6 196.7a22 22 0 01-21.8 0L56 145v44l1 .5c22 12.9 74.5 43.5 87.4 50.8a21.3 21.3 0 0022.7 0zm0 87.8 103.5-59.7.4-.2 106.6-61.4c4.5-2.6 4.7-10.6-.3-13.5l-3.6-2-14.6-8.4a16.6 16.6 0 00-16.2.2L166.6 284.4c-6.8 3.9-15 4-21.8 0L56 232.8v43.9l1 .5c22.1 13 74.5 43.5 87.4 50.8a21.4 21.4 0 0022.7 0zm103.6 28-103.6 59.7c-7.5 4.3-15 4.4-22.7 0A15382 15382 0 0156.9 365l-.9-.6v-43.9l88.8 51.7a22 22 0 0021.8 0l176.3-101.5c6-3.5 12.5-2.3 16.2-.2l14.6 8.4 3.6 2c5 3 4.8 11 .3 13.5z'
/>
</svg>
)
}
1 change: 1 addition & 0 deletions apps/sim/lib/core/security/csp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface CSPDirectives {
const STATIC_SCRIPT_SRC = [
"'self'",
"'unsafe-inline'",
...(isDev ? ["'unsafe-eval'"] : []),
'https://*.google.com',
'https://apis.google.com',
'https://challenges.cloudflare.com',
Expand Down
Loading