Compare commits

..

34 Commits

Author SHA1 Message Date
Waleed
cc2be33d6b v0.5.67: loading, password reset, ui improvements, helm updates (#2928)
* fix(zustand): updated to useShallow from deprecated createWithEqualityFn (#2919)

* fix(logger): use direct env access for webpack inlining (#2920)

* fix(notifications): text overflow with line-clamp (#2921)

* chore(helm): add env vars for Vertex AI, orgs, and telemetry (#2922)

* fix(auth): improve reset password flow and consolidate brand detection (#2924)

* fix(auth): improve reset password flow and consolidate brand detection

* fix(auth): set errorHandled for EMAIL_NOT_VERIFIED to prevent duplicate error

* fix(auth): clear success message on login errors

* chore(auth): fix import order per lint

* fix(action-bar): duplicate subflows with children (#2923)

* fix(action-bar): duplicate subflows with children

* fix(action-bar): add validateTriggerPaste for subflow duplicate

* fix(resolver): agent response format, input formats, root level (#2925)

* fix(resolvers): agent response format, input formats, root level

* fix response block initial seeding

* fix tests

* fix(messages-input): fix cursor alignment and auto-resize with overlay (#2926)

* fix(messages-input): fix cursor alignment and auto-resize with overlay

* fixed remaining zustand warnings

* fix(stores): remove dead code causing log spam on startup (#2927)

* fix(stores): remove dead code causing log spam on startup

* fix(stores): replace custom tools zustand store with react query cache

* improvement(ui): use BrandedButton and BrandedLink components (#2930)

- Refactor auth forms to use BrandedButton component
- Add BrandedLink component for changelog page
- Reduce code duplication in login, signup, reset-password forms
- Update star count default value

* fix(custom-tools): remove unsafe title fallback in getCustomTool (#2929)

* fix(custom-tools): remove unsafe title fallback in getCustomTool

* fix(custom-tools): restore title fallback in getCustomTool lookup

Custom tools are referenced by title (custom_${title}), not database ID.
The title fallback is required for client-side tool resolution to work.

* fix(null-bodies): empty bodies handling (#2931)

* fix(null-statuses): empty bodies handling

* address bugbot comment

* fix(token-refresh): microsoft, notion, x, linear (#2933)

* fix(microsoft): proactive refresh needed

* fix(x): missing token refresh flag

* notion and linear missing flag too

* address bugbot comment

* fix(auth): handle EMAIL_NOT_VERIFIED in onError callback (#2932)

* fix(auth): handle EMAIL_NOT_VERIFIED in onError callback

* refactor(auth): extract redirectToVerify helper to reduce duplication

* fix(workflow-selector): use dedicated selector for workflow dropdown (#2934)

* feat(workflow-block): preview (#2935)

* improvement(copilot): tool configs to show nested props (#2936)

* fix(auth): add genericOAuth providers to trustedProviders (#2937)

---------

Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Emir Karabeg <78010029+emir-karabeg@users.noreply.github.com>
2026-01-21 22:53:25 -08:00
Vikhyath Mondreti
45371e521e v0.5.66: external http requests fix, ring highlighting 2026-01-21 02:55:39 -08:00
Waleed
0ce0f98aa5 v0.5.65: gemini updates, textract integration, ui updates (#2909)
* fix(google): wrap primitive tool responses for Gemini API compatibility (#2900)

* fix(canonical): copilot path + update parent (#2901)

* fix(rss): add top-level title, link, pubDate fields to RSS trigger output (#2902)

* fix(rss): add top-level title, link, pubDate fields to RSS trigger output

* fix(imap): add top-level fields to IMAP trigger output

* improvement(browseruse): add profile id param (#2903)

* improvement(browseruse): add profile id param

* make request a stub since we have directExec

* improvement(executor): upgraded abort controller to handle aborts for loops and parallels (#2880)

* improvement(executor): upgraded abort controller to handle aborts for loops and parallels

* comments

* improvement(files): update execution for passing base64 strings (#2906)

* progress

* improvement(execution): update execution for passing base64 strings

* fix types

* cleanup comments

* path security vuln

* reject promise correctly

* fix redirect case

* remove proxy routes

* fix tests

* use ipaddr

* feat(tools): added textract, added v2 for mistral, updated tag dropdown (#2904)

* feat(tools): added textract

* cleanup

* ack pr comments

* reorder

* removed upload for textract async version

* fix additional fields dropdown in editor, update parser to leave validation to be done on the server

* added mistral v2, files v2, and finalized textract

* updated the rest of the old file patterns, updated mistral outputs for v2

* updated tag dropdown to parse non-operation fields as well

* updated extension finder

* cleanup

* added description for inputs to workflow

* use helper for internal route check

* fix tag dropdown merge conflict change

* remove duplicate code

---------

Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>

* fix(ui): change add inputs button to match output selector (#2907)

* fix(canvas): removed invite to workspace from canvas popover (#2908)

* fix(canvas): removed invite to workspace

* removed unused props

* fix(copilot): legacy tool display names (#2911)

* fix(a2a): canonical merge  (#2912)

* fix canonical merge

* fix empty array case

* fix(change-detection): copilot diffs have extra field (#2913)

* improvement(logs): improved logs ui bugs, added subflow disable UI (#2910)

* improvement(logs): improved logs ui bugs, added subflow disable UI

* added duplicate to action bar for subflows

* feat(broadcast): email v0.5 (#2905)

---------

Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Emir Karabeg <78010029+emir-karabeg@users.noreply.github.com>
2026-01-20 23:54:55 -08:00
Waleed
dff1c9d083 v0.5.64: unsubscribe, search improvements, metrics, additional SSO configuration 2026-01-20 00:34:11 -08:00
Vikhyath Mondreti
b09f683072 v0.5.63: ui and performance improvements, more google tools 2026-01-18 15:22:42 -08:00
Vikhyath Mondreti
a8bb0db660 v0.5.62: webhook bug fixes, seeding default subblock values, block selection fixes 2026-01-16 20:27:06 -08:00
Waleed
af82820a28 v0.5.61: webhook improvements, workflow controls, react query for deployment status, chat fixes, reducto and pulse OCR, linear fixes 2026-01-16 18:06:23 -08:00
Waleed
4372841797 v0.5.60: invitation flow improvements, chat fixes, a2a improvements, additional copilot actions 2026-01-15 00:02:18 -08:00
Waleed
5e8c843241 v0.5.59: a2a support, documentation 2026-01-13 13:21:21 -08:00
Waleed
7bf3d73ee6 v0.5.58: export folders, new tools, permissions groups enhancements 2026-01-13 00:56:59 -08:00
Vikhyath Mondreti
7ffc11a738 v0.5.57: subagents, context menu improvements, bug fixes 2026-01-11 11:38:40 -08:00
Waleed
be578e2ed7 v0.5.56: batch operations, access control and permission groups, billing fixes 2026-01-10 00:31:34 -08:00
Waleed
f415e5edc4 v0.5.55: polling groups, bedrock provider, devcontainer fixes, workflow preview enhancements 2026-01-08 23:36:56 -08:00
Waleed
13a6e6c3fa v0.5.54: seo, model blacklist, helm chart updates, fireflies integration, autoconnect improvements, billing fixes 2026-01-07 16:09:45 -08:00
Waleed
f5ab7f21ae v0.5.53: hotkey improvements, added redis fallback, fixes for workflow tool 2026-01-06 23:34:52 -08:00
Waleed
bfb6fffe38 v0.5.52: new port-based router block, combobox expression and variable support 2026-01-06 16:14:10 -08:00
Waleed
4fbec0a43f v0.5.51: triggers, kb, condition block improvements, supabase and grain integration updates 2026-01-06 14:26:46 -08:00
Waleed
585f5e365b v0.5.50: import improvements, ui upgrades, kb styling and performance improvements 2026-01-05 00:35:55 -08:00
Waleed
3792bdd252 v0.5.49: hitl improvements, new email styles, imap trigger, logs context menu (#2672)
* feat(logs-context-menu): consolidated logs utils and types, added logs record context menu (#2659)

* feat(email): welcome email; improvement(emails): ui/ux (#2658)

* feat(email): welcome email; improvement(emails): ui/ux

* improvement(emails): links, accounts, preview

* refactor(emails): file structure and wrapper components

* added envvar for personal emails sent, added isHosted gate

* fixed failing tests, added env mock

* fix: removed comment

---------

Co-authored-by: waleed <walif6@gmail.com>

* fix(logging): hitl + trigger dev crash protection (#2664)

* hitl gaps

* deal with trigger worker crashes

* cleanup import strcuture

* feat(imap): added support for imap trigger (#2663)

* feat(tools): added support for imap trigger

* feat(imap): added parity, tested

* ack PR comments

* final cleanup

* feat(i18n): update translations (#2665)

Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>

* fix(grain): updated grain trigger to auto-establish trigger (#2666)

Co-authored-by: aadamgough <adam@sim.ai>

* feat(admin): routes to manage deployments (#2667)

* feat(admin): routes to manage deployments

* fix naming fo deployed by

* feat(time-picker): added timepicker emcn component, added to playground, added searchable prop for dropdown, added more timezones for schedule, updated license and notice date (#2668)

* feat(time-picker): added timepicker emcn component, added to playground, added searchable prop for dropdown, added more timezones for schedule, updated license and notice date

* removed unused params, cleaned up redundant utils

* improvement(invite): aligned styling (#2669)

* improvement(invite): aligned with rest of app

* fix(invite): error handling

* fix: addressed comments

---------

Co-authored-by: Emir Karabeg <78010029+emir-karabeg@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>
Co-authored-by: Adam Gough <77861281+aadamgough@users.noreply.github.com>
Co-authored-by: aadamgough <adam@sim.ai>
2026-01-03 13:19:18 -08:00
Waleed
eb5d1f3e5b v0.5.48: copy-paste workflow blocks, docs updates, mcp tool fixes 2025-12-31 18:00:04 -08:00
Waleed
54ab82c8dd v0.5.47: deploy workflow as mcp, kb chunks tokenizer, UI improvements, jira service management tools 2025-12-30 23:18:58 -08:00
Waleed
f895bf469b v0.5.46: build improvements, greptile, light mode improvements 2025-12-29 02:17:52 -08:00
Waleed
dd3209af06 v0.5.45: light mode fixes, realtime usage indicator, docker build improvements 2025-12-27 19:57:42 -08:00
Waleed
b6ba3b50a7 v0.5.44: keyboard shortcuts, autolayout, light mode, byok, testing improvements 2025-12-26 21:25:19 -08:00
Waleed
b304233062 v0.5.43: export logs, circleback, grain, vertex, code hygiene, schedule improvements 2025-12-23 19:19:18 -08:00
Vikhyath Mondreti
57e4b49bd6 v0.5.42: fix memory migration 2025-12-23 01:24:54 -08:00
Vikhyath Mondreti
e12dd204ed v0.5.41: memory fixes, copilot improvements, knowledgebase improvements, LLM providers standardization 2025-12-23 00:15:18 -08:00
Vikhyath Mondreti
3d9d9cbc54 v0.5.40: supabase ops to allow non-public schemas, jira uuid 2025-12-21 22:28:05 -08:00
Waleed
0f4ec962ad v0.5.39: notion, workflow variables fixes 2025-12-20 20:44:00 -08:00
Waleed
4827866f9a v0.5.38: snap to grid, copilot ux improvements, billing line items 2025-12-20 17:24:38 -08:00
Waleed
3e697d9ed9 v0.5.37: redaction utils consolidation, logs updates, autoconnect improvements, additional kb tag types 2025-12-19 22:31:55 -08:00
Martin Yankov
4431a1a484 fix(helm): add custom egress rules to realtime network policy (#2481)
The realtime service network policy was missing the custom egress rules section
that allows configuration of additional egress rules via values.yaml. This caused
the realtime pods to be unable to connect to external databases (e.g., PostgreSQL
on port 5432) when using external database configurations.

The app network policy already had this section, but the realtime network policy
was missing it, creating an inconsistency and preventing the realtime service
from accessing external databases configured via networkPolicy.egress values.

This fix adds the same custom egress rules template section to the realtime
network policy, matching the app network policy behavior and allowing users to
configure database connectivity via values.yaml.
2025-12-19 18:59:08 -08:00
Waleed
4d1a9a3f22 v0.5.36: hitl improvements, opengraph, slack fixes, one-click unsubscribe, auth checks, new db indexes 2025-12-19 01:27:49 -08:00
Vikhyath Mondreti
eb07a080fb v0.5.35: helm updates, copilot improvements, 404 for docs, salesforce fixes, subflow resize clamping 2025-12-18 16:23:19 -08:00
316 changed files with 4739 additions and 20084 deletions

View File

@@ -27,9 +27,8 @@ jobs:
steps: steps:
- name: Extract version from commit message - name: Extract version from commit message
id: extract id: extract
env:
COMMIT_MSG: ${{ github.event.head_commit.message }}
run: | run: |
COMMIT_MSG="${{ github.event.head_commit.message }}"
# Only tag versions on main branch # Only tag versions on main branch
if [ "${{ github.ref }}" = "refs/heads/main" ] && [[ "$COMMIT_MSG" =~ ^(v[0-9]+\.[0-9]+\.[0-9]+): ]]; then if [ "${{ github.ref }}" = "refs/heads/main" ] && [[ "$COMMIT_MSG" =~ ^(v[0-9]+\.[0-9]+\.[0-9]+): ]]; then
VERSION="${BASH_REMATCH[1]}" VERSION="${BASH_REMATCH[1]}"

View File

@@ -124,44 +124,11 @@ Choose between four types of loops:
3. Drag other blocks inside the loop container 3. Drag other blocks inside the loop container
4. Connect the blocks as needed 4. Connect the blocks as needed
### Referencing Loop Data ### Accessing Results
There's an important distinction between referencing loop data from **inside** vs **outside** the loop: After a loop completes, you can access aggregated results:
<Tabs items={['Inside the Loop', 'Outside the Loop']}> - **`<loop.results>`**: Array of results from all loop iterations
<Tab>
**Inside the loop**, use `<loop.>` references to access the current iteration context:
- **`<loop.index>`**: Current iteration number (0-based)
- **`<loop.currentItem>`**: Current item being processed (forEach only)
- **`<loop.items>`**: Full collection being iterated (forEach only)
```
// Inside a Function block within the loop
const idx = <loop.index>; // 0, 1, 2, ...
const item = <loop.currentItem>; // Current item
```
<Callout type="info">
These references are only available for blocks **inside** the loop container. They give you access to the current iteration's context.
</Callout>
</Tab>
<Tab>
**Outside the loop** (after it completes), reference the loop block by its name to access aggregated results:
- **`<LoopBlockName.results>`**: Array of results from all iterations
```
// If your loop block is named "Process Items"
const allResults = <processitems.results>;
// Returns: [result1, result2, result3, ...]
```
<Callout type="info">
After the loop completes, use the loop's block name (not `loop.`) to access the collected results. The block name is normalized (lowercase, no spaces).
</Callout>
</Tab>
</Tabs>
## Example Use Cases ## Example Use Cases
@@ -217,29 +184,28 @@ Variables (i=0) → Loop (While i<10) → Agent (Process) → Variables (i++)
</ul> </ul>
</Tab> </Tab>
<Tab> <Tab>
Available **inside** the loop only:
<ul className="list-disc space-y-2 pl-6"> <ul className="list-disc space-y-2 pl-6">
<li> <li>
<strong>{"<loop.index>"}</strong>: Current iteration number (0-based) <strong>loop.currentItem</strong>: Current item being processed
</li> </li>
<li> <li>
<strong>{"<loop.currentItem>"}</strong>: Current item being processed (forEach only) <strong>loop.index</strong>: Current iteration number (0-based)
</li> </li>
<li> <li>
<strong>{"<loop.items>"}</strong>: Full collection (forEach only) <strong>loop.items</strong>: Full collection (forEach loops)
</li> </li>
</ul> </ul>
</Tab> </Tab>
<Tab> <Tab>
<ul className="list-disc space-y-2 pl-6"> <ul className="list-disc space-y-2 pl-6">
<li> <li>
<strong>{"<blockname.results>"}</strong>: Array of all iteration results (accessed via block name) <strong>loop.results</strong>: Array of all iteration results
</li> </li>
<li> <li>
<strong>Structure</strong>: Results maintain iteration order <strong>Structure</strong>: Results maintain iteration order
</li> </li>
<li> <li>
<strong>Access</strong>: Available in blocks after the loop completes <strong>Access</strong>: Available in blocks after the loop
</li> </li>
</ul> </ul>
</Tab> </Tab>

View File

@@ -76,44 +76,11 @@ Choose between two types of parallel execution:
3. Drag a single block inside the parallel container 3. Drag a single block inside the parallel container
4. Connect the block as needed 4. Connect the block as needed
### Referencing Parallel Data ### Accessing Results
There's an important distinction between referencing parallel data from **inside** vs **outside** the parallel block: After a parallel block completes, you can access aggregated results:
<Tabs items={['Inside the Parallel', 'Outside the Parallel']}> - **`<parallel.results>`**: Array of results from all parallel instances
<Tab>
**Inside the parallel**, use `<parallel.>` references to access the current instance context:
- **`<parallel.index>`**: Current instance number (0-based)
- **`<parallel.currentItem>`**: Item for this instance (collection-based only)
- **`<parallel.items>`**: Full collection being distributed (collection-based only)
```
// Inside a Function block within the parallel
const idx = <parallel.index>; // 0, 1, 2, ...
const item = <parallel.currentItem>; // This instance's item
```
<Callout type="info">
These references are only available for blocks **inside** the parallel container. They give you access to the current instance's context.
</Callout>
</Tab>
<Tab>
**Outside the parallel** (after it completes), reference the parallel block by its name to access aggregated results:
- **`<ParallelBlockName.results>`**: Array of results from all instances
```
// If your parallel block is named "Process Tasks"
const allResults = <processtasks.results>;
// Returns: [result1, result2, result3, ...]
```
<Callout type="info">
After the parallel completes, use the parallel's block name (not `parallel.`) to access the collected results. The block name is normalized (lowercase, no spaces).
</Callout>
</Tab>
</Tabs>
## Example Use Cases ## Example Use Cases
@@ -131,11 +98,11 @@ Parallel (["gpt-4o", "claude-3.7-sonnet", "gemini-2.5-pro"]) → Agent → Evalu
### Result Aggregation ### Result Aggregation
Results from all parallel instances are automatically collected and accessible via the block name: Results from all parallel instances are automatically collected:
```javascript ```javascript
// In a Function block after a parallel named "Process Tasks" // In a Function block after the parallel
const allResults = <processtasks.results>; const allResults = input.parallel.results;
// Returns: [result1, result2, result3, ...] // Returns: [result1, result2, result3, ...]
``` ```
@@ -191,26 +158,25 @@ Understanding when to use each:
</ul> </ul>
</Tab> </Tab>
<Tab> <Tab>
Available **inside** the parallel only:
<ul className="list-disc space-y-2 pl-6"> <ul className="list-disc space-y-2 pl-6">
<li> <li>
<strong>{"<parallel.index>"}</strong>: Instance number (0-based) <strong>parallel.currentItem</strong>: Item for this instance
</li> </li>
<li> <li>
<strong>{"<parallel.currentItem>"}</strong>: Item for this instance (collection-based only) <strong>parallel.index</strong>: Instance number (0-based)
</li> </li>
<li> <li>
<strong>{"<parallel.items>"}</strong>: Full collection (collection-based only) <strong>parallel.items</strong>: Full collection (collection-based)
</li> </li>
</ul> </ul>
</Tab> </Tab>
<Tab> <Tab>
<ul className="list-disc space-y-2 pl-6"> <ul className="list-disc space-y-2 pl-6">
<li> <li>
<strong>{"<blockname.results>"}</strong>: Array of all instance results (accessed via block name) <strong>parallel.results</strong>: Array of all instance results
</li> </li>
<li> <li>
<strong>Access</strong>: Available in blocks after the parallel completes <strong>Access</strong>: Available in blocks after the parallel
</li> </li>
</ul> </ul>
</Tab> </Tab>

View File

@@ -59,7 +59,7 @@ export default function StatusIndicator() {
href={statusUrl} href={statusUrl}
target='_blank' target='_blank'
rel='noopener noreferrer' rel='noopener noreferrer'
className={`flex min-w-[165px] items-center gap-[6px] whitespace-nowrap text-[12px] transition-colors ${STATUS_COLORS[status]}`} className={`flex items-center gap-[6px] whitespace-nowrap text-[12px] transition-colors ${STATUS_COLORS[status]}`}
aria-label={`System status: ${message}`} aria-label={`System status: ${message}`}
> >
<StatusDotIcon status={status} className='h-[6px] w-[6px]' aria-hidden='true' /> <StatusDotIcon status={status} className='h-[6px] w-[6px]' aria-hidden='true' />

View File

@@ -10,8 +10,8 @@ export { LandingLoopNode } from './landing-canvas/landing-block/landing-loop-nod
export { LandingNode } from './landing-canvas/landing-block/landing-node' export { LandingNode } from './landing-canvas/landing-block/landing-node'
export type { LoopBlockProps } from './landing-canvas/landing-block/loop-block' export type { LoopBlockProps } from './landing-canvas/landing-block/loop-block'
export { LoopBlock } from './landing-canvas/landing-block/loop-block' export { LoopBlock } from './landing-canvas/landing-block/loop-block'
export type { SubBlockRowProps, TagProps } from './landing-canvas/landing-block/tag' export type { TagProps } from './landing-canvas/landing-block/tag'
export { SubBlockRow, Tag } from './landing-canvas/landing-block/tag' export { Tag } from './landing-canvas/landing-block/tag'
export type { export type {
LandingBlockNode, LandingBlockNode,
LandingCanvasProps, LandingCanvasProps,

View File

@@ -1,12 +1,12 @@
import React from 'react' import React from 'react'
import { BookIcon } from 'lucide-react'
import { import {
SubBlockRow, Tag,
type SubBlockRowProps, type TagProps,
} from '@/app/(landing)/components/hero/components/landing-canvas/landing-block/tag' } from '@/app/(landing)/components/hero/components/landing-canvas/landing-block/tag'
/** /**
* Data structure for a landing card component * Data structure for a landing card component
* Matches the workflow block structure from the application
*/ */
export interface LandingCardData { export interface LandingCardData {
/** Icon element to display in the card header */ /** Icon element to display in the card header */
@@ -15,8 +15,8 @@ export interface LandingCardData {
color: string | '#f6f6f6' color: string | '#f6f6f6'
/** Name/title of the card */ /** Name/title of the card */
name: string name: string
/** Optional subblock rows to display below the header */ /** Optional tags to display at the bottom of the card */
tags?: SubBlockRowProps[] tags?: TagProps[]
} }
/** /**
@@ -28,8 +28,7 @@ export interface LandingBlockProps extends LandingCardData {
} }
/** /**
* Landing block component that displays a card with icon, name, and optional subblock rows * Landing block component that displays a card with icon, name, and optional tags
* Styled to match the application's workflow blocks
* @param props - Component properties including icon, color, name, tags, and className * @param props - Component properties including icon, color, name, tags, and className
* @returns A styled block card component * @returns A styled block card component
*/ */
@@ -40,37 +39,33 @@ export const LandingBlock = React.memo(function LandingBlock({
tags, tags,
className, className,
}: LandingBlockProps) { }: LandingBlockProps) {
const hasContentBelowHeader = tags && tags.length > 0
return ( return (
<div <div
className={`z-10 flex w-[250px] flex-col rounded-[8px] border border-[#E5E5E5] bg-white ${className ?? ''}`} className={`z-10 flex w-64 flex-col items-start gap-3 rounded-[14px] border border-[#E5E5E5] bg-[#FEFEFE] p-3 ${className ?? ''}`}
style={{
boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
}}
> >
{/* Header - matches workflow-block.tsx header styling */} <div className='flex w-full items-center justify-between'>
<div className='flex items-center gap-2.5'>
<div <div
className={`flex items-center justify-between p-[8px] ${hasContentBelowHeader ? 'border-[#E5E5E5] border-b' : ''}`} className='flex h-6 w-6 items-center justify-center rounded-[8px] text-white'
> style={{ backgroundColor: color as string }}
<div className='flex min-w-0 flex-1 items-center gap-[10px]'>
<div
className='flex h-[24px] w-[24px] flex-shrink-0 items-center justify-center rounded-[6px]'
style={{ background: color as string }}
> >
{icon} {icon}
</div> </div>
<span className='truncate font-medium text-[#171717] text-[16px]' title={name}> <p className='text-base text-card-foreground'>{name}</p>
{name}
</span>
</div> </div>
<BookIcon className='h-4 w-4 text-muted-foreground' />
</div> </div>
{/* Content - SubBlock Rows matching workflow-block.tsx */} {tags && tags.length > 0 ? (
{hasContentBelowHeader && ( <div className='flex flex-wrap gap-2'>
<div className='flex flex-col gap-[8px] p-[8px]'>
{tags.map((tag) => ( {tags.map((tag) => (
<SubBlockRow key={tag.label} icon={tag.icon} label={tag.label} /> <Tag key={tag.label} icon={tag.icon} label={tag.label} />
))} ))}
</div> </div>
)} ) : null}
</div> </div>
) )
}) })

View File

@@ -7,14 +7,9 @@ import {
type LandingCardData, type LandingCardData,
} from '@/app/(landing)/components/hero/components/landing-canvas/landing-block/landing-block' } from '@/app/(landing)/components/hero/components/landing-canvas/landing-block/landing-block'
/**
* Handle Y offset from block top - matches HANDLE_POSITIONS.DEFAULT_Y_OFFSET
*/
const HANDLE_Y_OFFSET = 20
/** /**
* React Flow node component for the landing canvas * React Flow node component for the landing canvas
* Styled to match the application's workflow blocks * Includes CSS animations and connection handles
* @param props - Component properties containing node data * @param props - Component properties containing node data
* @returns A React Flow compatible node component * @returns A React Flow compatible node component
*/ */
@@ -46,15 +41,15 @@ export const LandingNode = React.memo(function LandingNode({ data }: { data: Lan
type='target' type='target'
position={Position.Left} position={Position.Left}
style={{ style={{
width: '7px', width: '12px',
height: '20px', height: '12px',
background: '#D1D1D1', background: '#FEFEFE',
border: 'none', border: '1px solid #E5E5E5',
borderRadius: '2px 0 0 2px', borderRadius: '50%',
top: `${HANDLE_Y_OFFSET}px`, top: '50%',
left: '-7px', left: '-20px',
transform: 'translateY(-50%)', transform: 'translateY(-50%)',
zIndex: 10, zIndex: 2,
}} }}
isConnectable={false} isConnectable={false}
/> />
@@ -64,15 +59,15 @@ export const LandingNode = React.memo(function LandingNode({ data }: { data: Lan
type='source' type='source'
position={Position.Right} position={Position.Right}
style={{ style={{
width: '7px', width: '12px',
height: '20px', height: '12px',
background: '#D1D1D1', background: '#FEFEFE',
border: 'none', border: '1px solid #E5E5E5',
borderRadius: '0 2px 2px 0', borderRadius: '50%',
top: `${HANDLE_Y_OFFSET}px`, top: '50%',
right: '-7px', right: '-20px',
transform: 'translateY(-50%)', transform: 'translateY(-50%)',
zIndex: 10, zIndex: 2,
}} }}
isConnectable={false} isConnectable={false}
/> />

View File

@@ -15,7 +15,6 @@ export interface LoopBlockProps {
/** /**
* Loop block container component that provides a styled container * Loop block container component that provides a styled container
* for grouping related elements with a dashed border * for grouping related elements with a dashed border
* Styled to match the application's subflow containers
* @param props - Component properties including children and styling * @param props - Component properties including children and styling
* @returns A styled loop container component * @returns A styled loop container component
*/ */
@@ -30,33 +29,33 @@ export const LoopBlock = React.memo(function LoopBlock({
style={{ style={{
width: '1198px', width: '1198px',
height: '528px', height: '528px',
borderRadius: '8px', borderRadius: '14px',
background: 'rgba(59, 130, 246, 0.08)', background: 'rgba(59, 130, 246, 0.10)',
position: 'relative', position: 'relative',
...style, ...style,
}} }}
> >
{/* Custom dashed border with SVG - 8px border radius to match blocks */} {/* Custom dashed border with SVG */}
<svg <svg
className='pointer-events-none absolute inset-0 h-full w-full' className='pointer-events-none absolute inset-0 h-full w-full'
style={{ borderRadius: '8px' }} style={{ borderRadius: '14px' }}
preserveAspectRatio='none' preserveAspectRatio='none'
> >
<path <path
className='landing-loop-animated-dash' className='landing-loop-animated-dash'
d='M 1190 527.5 d='M 1183.5 527.5
L 8 527.5 L 14 527.5
A 7.5 7.5 0 0 1 0.5 520 A 13.5 13.5 0 0 1 0.5 514
L 0.5 8 L 0.5 14
A 7.5 7.5 0 0 1 8 0.5 A 13.5 13.5 0 0 1 14 0.5
L 1190 0.5 L 1183.5 0.5
A 7.5 7.5 0 0 1 1197.5 8 A 13.5 13.5 0 0 1 1197 14
L 1197.5 520 L 1197 514
A 7.5 7.5 0 0 1 1190 527.5 Z' A 13.5 13.5 0 0 1 1183.5 527.5 Z'
fill='none' fill='none'
stroke='#3B82F6' stroke='#3B82F6'
strokeWidth='1' strokeWidth='1'
strokeDasharray='8 8' strokeDasharray='12 12'
strokeLinecap='round' strokeLinecap='round'
/> />
</svg> </svg>

View File

@@ -1,52 +1,25 @@
import React from 'react' import React from 'react'
/** /**
* Properties for a subblock row component * Properties for a tag component
* Matches the SubBlockRow pattern from workflow-block.tsx
*/ */
export interface SubBlockRowProps { export interface TagProps {
/** Icon element to display (optional, for visual context) */ /** Icon element to display in the tag */
icon?: React.ReactNode icon: React.ReactNode
/** Text label for the row title */ /** Text label for the tag */
label: string label: string
/** Optional value to display on the right side */
value?: string
} }
/** /**
* Kept for backwards compatibility * Tag component for displaying labeled icons in a compact format
* @param props - Tag properties including icon and label
* @returns A styled tag component
*/ */
export type TagProps = SubBlockRowProps export const Tag = React.memo(function Tag({ icon, label }: TagProps) {
/**
* SubBlockRow component matching the workflow block's subblock row style
* @param props - Row properties including label and optional value
* @returns A styled row component
*/
export const SubBlockRow = React.memo(function SubBlockRow({ label, value }: SubBlockRowProps) {
// Split label by colon to separate title and value if present
const [title, displayValue] = label.includes(':')
? label.split(':').map((s) => s.trim())
: [label, value]
return ( return (
<div className='flex items-center gap-[8px]'> <div className='flex w-fit items-center gap-1 rounded-[8px] border border-gray-300 bg-white px-2 py-0.5'>
<span className='min-w-0 truncate text-[#888888] text-[14px] capitalize' title={title}> <div className='h-3 w-3 text-muted-foreground'>{icon}</div>
{title} <p className='text-muted-foreground text-xs leading-normal'>{label}</p>
</span>
{displayValue && (
<span
className='flex-1 truncate text-right text-[#171717] text-[14px]'
title={displayValue}
>
{displayValue}
</span>
)}
</div> </div>
) )
}) })
/**
* Tag component - alias for SubBlockRow for backwards compatibility
*/
export const Tag = SubBlockRow

View File

@@ -9,10 +9,9 @@ import { LandingFlow } from '@/app/(landing)/components/hero/components/landing-
/** /**
* Visual constants for landing node dimensions * Visual constants for landing node dimensions
* Matches BLOCK_DIMENSIONS from the application
*/ */
export const CARD_WIDTH = 250 export const CARD_WIDTH = 256
export const CARD_HEIGHT = 100 export const CARD_HEIGHT = 92
/** /**
* Landing block node with positioning information * Landing block node with positioning information

View File

@@ -4,29 +4,33 @@ import React from 'react'
import { type EdgeProps, getSmoothStepPath, Position } from 'reactflow' import { type EdgeProps, getSmoothStepPath, Position } from 'reactflow'
/** /**
* Custom edge component with animated dashed line * Custom edge component with animated dotted line that floats between handles
* Styled to match the application's workflow edges with rectangular handles
* @param props - React Flow edge properties * @param props - React Flow edge properties
* @returns An animated dashed edge component * @returns An animated dotted edge component
*/ */
export const LandingEdge = React.memo(function LandingEdge(props: EdgeProps) { export const LandingEdge = React.memo(function LandingEdge(props: EdgeProps) {
const { id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style } = props const { id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style, data } =
props
// Adjust the connection points to connect flush with rectangular handles // Adjust the connection points to create floating effect
// Handle width is 7px, positioned at -7px from edge // Account for handle size (12px) and additional spacing
const handleRadius = 6 // Half of handle width (12px)
const floatingGap = 1 // Additional gap for floating effect
// Calculate adjusted positions based on edge direction
let adjustedSourceX = sourceX let adjustedSourceX = sourceX
let adjustedTargetX = targetX let adjustedTargetX = targetX
if (sourcePosition === Position.Right) { if (sourcePosition === Position.Right) {
adjustedSourceX = sourceX + 1 adjustedSourceX = sourceX + handleRadius + floatingGap
} else if (sourcePosition === Position.Left) { } else if (sourcePosition === Position.Left) {
adjustedSourceX = sourceX - 1 adjustedSourceX = sourceX - handleRadius - floatingGap
} }
if (targetPosition === Position.Left) { if (targetPosition === Position.Left) {
adjustedTargetX = targetX - 1 adjustedTargetX = targetX - handleRadius - floatingGap
} else if (targetPosition === Position.Right) { } else if (targetPosition === Position.Right) {
adjustedTargetX = targetX + 1 adjustedTargetX = targetX + handleRadius + floatingGap
} }
const [path] = getSmoothStepPath({ const [path] = getSmoothStepPath({
@@ -36,8 +40,8 @@ export const LandingEdge = React.memo(function LandingEdge(props: EdgeProps) {
targetY, targetY,
sourcePosition, sourcePosition,
targetPosition, targetPosition,
borderRadius: 8, borderRadius: 20,
offset: 16, offset: 10,
}) })
return ( return (

View File

@@ -1,7 +1,16 @@
'use client' 'use client'
import React from 'react' import React from 'react'
import { ArrowUp, CodeIcon } from 'lucide-react' import {
ArrowUp,
BinaryIcon,
BookIcon,
CalendarIcon,
CodeIcon,
Globe2Icon,
MessageSquareIcon,
VariableIcon,
} from 'lucide-react'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { type Edge, type Node, Position } from 'reactflow' import { type Edge, type Node, Position } from 'reactflow'
import { import {
@@ -14,6 +23,7 @@ import {
JiraIcon, JiraIcon,
LinearIcon, LinearIcon,
NotionIcon, NotionIcon,
OpenAIIcon,
OutlookIcon, OutlookIcon,
PackageSearchIcon, PackageSearchIcon,
PineconeIcon, PineconeIcon,
@@ -55,56 +65,67 @@ const SERVICE_TEMPLATES = {
/** /**
* Landing blocks for the canvas preview * Landing blocks for the canvas preview
* Styled to match the application's workflow blocks with subblock rows
*/ */
const LANDING_BLOCKS: LandingManualBlock[] = [ const LANDING_BLOCKS: LandingManualBlock[] = [
{ {
id: 'schedule', id: 'schedule',
name: 'Schedule', name: 'Schedule',
color: '#7B68EE', color: '#7B68EE',
icon: <ScheduleIcon className='h-[16px] w-[16px] text-white' />, icon: <ScheduleIcon className='h-4 w-4' />,
positions: { positions: {
mobile: { x: 8, y: 60 }, mobile: { x: 8, y: 60 },
tablet: { x: 40, y: 120 }, tablet: { x: 40, y: 120 },
desktop: { x: 60, y: 180 }, desktop: { x: 60, y: 180 },
}, },
tags: [{ label: 'Time: 09:00AM Daily' }, { label: 'Timezone: PST' }], tags: [
{ icon: <CalendarIcon className='h-3 w-3' />, label: '09:00AM Daily' },
{ icon: <Globe2Icon className='h-3 w-3' />, label: 'PST' },
],
}, },
{ {
id: 'knowledge', id: 'knowledge',
name: 'Knowledge', name: 'Knowledge',
color: '#00B0B0', color: '#00B0B0',
icon: <PackageSearchIcon className='h-[16px] w-[16px] text-white' />, icon: <PackageSearchIcon className='h-4 w-4' />,
positions: { positions: {
mobile: { x: 120, y: 140 }, mobile: { x: 120, y: 140 },
tablet: { x: 220, y: 200 }, tablet: { x: 220, y: 200 },
desktop: { x: 420, y: 241 }, desktop: { x: 420, y: 241 },
}, },
tags: [{ label: 'Source: Product Vector DB' }, { label: 'Limit: 10' }], tags: [
{ icon: <BookIcon className='h-3 w-3' />, label: 'Product Vector DB' },
{ icon: <BinaryIcon className='h-3 w-3' />, label: 'Limit: 10' },
],
}, },
{ {
id: 'agent', id: 'agent',
name: 'Agent', name: 'Agent',
color: '#802FFF', color: '#802FFF',
icon: <AgentIcon className='h-[16px] w-[16px] text-white' />, icon: <AgentIcon className='h-4 w-4' />,
positions: { positions: {
mobile: { x: 340, y: 60 }, mobile: { x: 340, y: 60 },
tablet: { x: 540, y: 120 }, tablet: { x: 540, y: 120 },
desktop: { x: 880, y: 142 }, desktop: { x: 880, y: 142 },
}, },
tags: [{ label: 'Model: gpt-5' }, { label: 'Prompt: You are a support ag...' }], tags: [
{ icon: <OpenAIIcon className='h-3 w-3' />, label: 'gpt-5' },
{ icon: <MessageSquareIcon className='h-3 w-3' />, label: 'You are a support ag...' },
],
}, },
{ {
id: 'function', id: 'function',
name: 'Function', name: 'Function',
color: '#FF402F', color: '#FF402F',
icon: <CodeIcon className='h-[16px] w-[16px] text-white' />, icon: <CodeIcon className='h-4 w-4' />,
positions: { positions: {
mobile: { x: 480, y: 220 }, mobile: { x: 480, y: 220 },
tablet: { x: 740, y: 280 }, tablet: { x: 740, y: 280 },
desktop: { x: 880, y: 340 }, desktop: { x: 880, y: 340 },
}, },
tags: [{ label: 'Language: Python' }, { label: 'Code: time = "2025-09-01...' }], tags: [
{ icon: <CodeIcon className='h-3 w-3' />, label: 'Python' },
{ icon: <VariableIcon className='h-3 w-3' />, label: 'time = "2025-09-01...' },
],
}, },
] ]

View File

@@ -229,7 +229,7 @@ function PricingCard({
*/ */
export default function LandingPricing() { export default function LandingPricing() {
return ( return (
<section id='pricing' className='px-4 pt-[23px] sm:px-0 sm:pt-[4px]' aria-label='Pricing plans'> <section id='pricing' className='px-4 pt-[19px] sm:px-0 sm:pt-0' aria-label='Pricing plans'>
<h2 className='sr-only'>Pricing Plans</h2> <h2 className='sr-only'>Pricing Plans</h2>
<div className='relative mx-auto w-full max-w-[1289px]'> <div className='relative mx-auto w-full max-w-[1289px]'>
<div className='grid grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-0 lg:grid-cols-4'> <div className='grid grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-0 lg:grid-cols-4'>

View File

@@ -21,7 +21,7 @@ interface NavProps {
} }
export default function Nav({ hideAuthButtons = false, variant = 'landing' }: NavProps = {}) { export default function Nav({ hideAuthButtons = false, variant = 'landing' }: NavProps = {}) {
const [githubStars, setGithubStars] = useState('26.1k') const [githubStars, setGithubStars] = useState('25.8k')
const [isHovered, setIsHovered] = useState(false) const [isHovered, setIsHovered] = useState(false)
const [isLoginHovered, setIsLoginHovered] = useState(false) const [isLoginHovered, setIsLoginHovered] = useState(false)
const router = useRouter() const router = useRouter()

View File

@@ -1,27 +0,0 @@
'use client'
import { useState } from 'react'
import { ArrowLeft, ChevronLeft } from 'lucide-react'
import Link from 'next/link'
export function BackLink() {
const [isHovered, setIsHovered] = useState(false)
return (
<Link
href='/studio'
className='group flex items-center gap-1 text-gray-600 text-sm hover:text-gray-900'
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<span className='group-hover:-translate-x-0.5 inline-flex transition-transform duration-200'>
{isHovered ? (
<ArrowLeft className='h-4 w-4' aria-hidden='true' />
) : (
<ChevronLeft className='h-4 w-4' aria-hidden='true' />
)}
</span>
Back to Sim Studio
</Link>
)
}

View File

@@ -5,10 +5,7 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/emcn'
import { FAQ } from '@/lib/blog/faq' import { FAQ } from '@/lib/blog/faq'
import { getAllPostMeta, getPostBySlug, getRelatedPosts } from '@/lib/blog/registry' import { getAllPostMeta, getPostBySlug, getRelatedPosts } from '@/lib/blog/registry'
import { buildArticleJsonLd, buildBreadcrumbJsonLd, buildPostMetadata } from '@/lib/blog/seo' import { buildArticleJsonLd, buildBreadcrumbJsonLd, buildPostMetadata } from '@/lib/blog/seo'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { soehne } from '@/app/_styles/fonts/soehne/soehne' import { soehne } from '@/app/_styles/fonts/soehne/soehne'
import { BackLink } from '@/app/(landing)/studio/[slug]/back-link'
import { ShareButton } from '@/app/(landing)/studio/[slug]/share-button'
export async function generateStaticParams() { export async function generateStaticParams() {
const posts = await getAllPostMeta() const posts = await getAllPostMeta()
@@ -51,7 +48,9 @@ export default async function Page({ params }: { params: Promise<{ slug: string
/> />
<header className='mx-auto max-w-[1450px] px-6 pt-8 sm:px-8 sm:pt-12 md:px-12 md:pt-16'> <header className='mx-auto max-w-[1450px] px-6 pt-8 sm:px-8 sm:pt-12 md:px-12 md:pt-16'>
<div className='mb-6'> <div className='mb-6'>
<BackLink /> <Link href='/studio' className='text-gray-600 text-sm hover:text-gray-900'>
Back to Sim Studio
</Link>
</div> </div>
<div className='flex flex-col gap-8 md:flex-row md:gap-12'> <div className='flex flex-col gap-8 md:flex-row md:gap-12'>
<div className='w-full flex-shrink-0 md:w-[450px]'> <div className='w-full flex-shrink-0 md:w-[450px]'>
@@ -76,8 +75,7 @@ export default async function Page({ params }: { params: Promise<{ slug: string
> >
{post.title} {post.title}
</h1> </h1>
<div className='mt-4 flex items-center justify-between'> <div className='mt-4 flex items-center gap-3'>
<div className='flex items-center gap-3'>
{(post.authors || [post.author]).map((a, idx) => ( {(post.authors || [post.author]).map((a, idx) => (
<div key={idx} className='flex items-center gap-2'> <div key={idx} className='flex items-center gap-2'>
{a?.avatarUrl ? ( {a?.avatarUrl ? (
@@ -100,8 +98,6 @@ export default async function Page({ params }: { params: Promise<{ slug: string
</div> </div>
))} ))}
</div> </div>
<ShareButton url={`${getBaseUrl()}/studio/${slug}`} title={post.title} />
</div>
</div> </div>
</div> </div>
<hr className='mt-8 border-gray-200 border-t sm:mt-12' /> <hr className='mt-8 border-gray-200 border-t sm:mt-12' />

View File

@@ -1,65 +0,0 @@
'use client'
import { useState } from 'react'
import { Share2 } from 'lucide-react'
import { Popover, PopoverContent, PopoverItem, PopoverTrigger } from '@/components/emcn'
interface ShareButtonProps {
url: string
title: string
}
export function ShareButton({ url, title }: ShareButtonProps) {
const [open, setOpen] = useState(false)
const [copied, setCopied] = useState(false)
const handleCopyLink = async () => {
try {
await navigator.clipboard.writeText(url)
setCopied(true)
setTimeout(() => {
setCopied(false)
setOpen(false)
}, 1000)
} catch {
setOpen(false)
}
}
const handleShareTwitter = () => {
const tweetUrl = `https://twitter.com/intent/tweet?url=${encodeURIComponent(url)}&text=${encodeURIComponent(title)}`
window.open(tweetUrl, '_blank', 'noopener,noreferrer')
setOpen(false)
}
const handleShareLinkedIn = () => {
const linkedInUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}`
window.open(linkedInUrl, '_blank', 'noopener,noreferrer')
setOpen(false)
}
return (
<Popover
open={open}
onOpenChange={setOpen}
variant='secondary'
size='sm'
colorScheme='inverted'
>
<PopoverTrigger asChild>
<button
className='flex items-center gap-1.5 text-gray-600 text-sm hover:text-gray-900'
aria-label='Share this post'
>
<Share2 className='h-4 w-4' />
<span>Share</span>
</button>
</PopoverTrigger>
<PopoverContent align='end' minWidth={140}>
<PopoverItem onClick={handleCopyLink}>{copied ? 'Copied!' : 'Copy link'}</PopoverItem>
<PopoverItem onClick={handleShareTwitter}>Share on X</PopoverItem>
<PopoverItem onClick={handleShareLinkedIn}>Share on LinkedIn</PopoverItem>
</PopoverContent>
</Popover>
)
}

View File

@@ -22,7 +22,7 @@ export default async function StudioIndex({
? filtered.sort((a, b) => { ? filtered.sort((a, b) => {
if (a.featured && !b.featured) return -1 if (a.featured && !b.featured) return -1
if (!a.featured && b.featured) return 1 if (!a.featured && b.featured) return 1
return new Date(b.date).getTime() - new Date(a.date).getTime() return 0
}) })
: filtered : filtered

View File

@@ -8,7 +8,6 @@ import type { AgentCapabilities, AgentSkill } from '@/lib/a2a/types'
import { checkHybridAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { getRedisClient } from '@/lib/core/config/redis' import { getRedisClient } from '@/lib/core/config/redis'
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils' import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
const logger = createLogger('A2AAgentCardAPI') const logger = createLogger('A2AAgentCardAPI')
@@ -96,11 +95,6 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<Ro
return NextResponse.json({ error: 'Agent not found' }, { status: 404 }) return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
} }
const workspaceAccess = await checkWorkspaceAccess(existingAgent.workspaceId, auth.userId)
if (!workspaceAccess.canWrite) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
const body = await request.json() const body = await request.json()
if ( if (
@@ -166,11 +160,6 @@ export async function DELETE(request: NextRequest, { params }: { params: Promise
return NextResponse.json({ error: 'Agent not found' }, { status: 404 }) return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
} }
const workspaceAccess = await checkWorkspaceAccess(existingAgent.workspaceId, auth.userId)
if (!workspaceAccess.canWrite) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
await db.delete(a2aAgent).where(eq(a2aAgent.id, agentId)) await db.delete(a2aAgent).where(eq(a2aAgent.id, agentId))
logger.info(`Deleted A2A agent: ${agentId}`) logger.info(`Deleted A2A agent: ${agentId}`)
@@ -205,11 +194,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<R
return NextResponse.json({ error: 'Agent not found' }, { status: 404 }) return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
} }
const workspaceAccess = await checkWorkspaceAccess(existingAgent.workspaceId, auth.userId)
if (!workspaceAccess.canWrite) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
const body = await request.json() const body = await request.json()
const action = body.action as 'publish' | 'unpublish' | 'refresh' const action = body.action as 'publish' | 'unpublish' | 'refresh'

View File

@@ -16,7 +16,6 @@ import {
import { checkHybridAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { getBrandConfig } from '@/lib/branding/branding' import { getBrandConfig } from '@/lib/branding/branding'
import { acquireLock, getRedisClient, releaseLock } from '@/lib/core/config/redis' import { acquireLock, getRedisClient, releaseLock } from '@/lib/core/config/redis'
import { validateExternalUrl } from '@/lib/core/security/input-validation'
import { SSE_HEADERS } from '@/lib/core/utils/sse' import { SSE_HEADERS } from '@/lib/core/utils/sse'
import { getBaseUrl } from '@/lib/core/utils/urls' import { getBaseUrl } from '@/lib/core/utils/urls'
import { markExecutionCancelled } from '@/lib/execution/cancellation' import { markExecutionCancelled } from '@/lib/execution/cancellation'
@@ -1119,13 +1118,17 @@ async function handlePushNotificationSet(
) )
} }
const urlValidation = validateExternalUrl( try {
params.pushNotificationConfig.url, const url = new URL(params.pushNotificationConfig.url)
'Push notification URL' if (url.protocol !== 'https:') {
)
if (!urlValidation.isValid) {
return NextResponse.json( return NextResponse.json(
createError(id, A2A_ERROR_CODES.INVALID_PARAMS, urlValidation.error || 'Invalid URL'), createError(id, A2A_ERROR_CODES.INVALID_PARAMS, 'Push notification URL must use HTTPS'),
{ status: 400 }
)
}
} catch {
return NextResponse.json(
createError(id, A2A_ERROR_CODES.INVALID_PARAMS, 'Invalid push notification URL'),
{ status: 400 } { status: 400 }
) )
} }

View File

@@ -104,11 +104,17 @@ export async function POST(req: NextRequest) {
}) })
// Build execution params starting with LLM-provided arguments // Build execution params starting with LLM-provided arguments
// Resolve all {{ENV_VAR}} references in the arguments (deep for nested objects) // Resolve all {{ENV_VAR}} references in the arguments
const executionParams: Record<string, any> = resolveEnvVarReferences( const executionParams: Record<string, any> = resolveEnvVarReferences(
toolArgs, toolArgs,
decryptedEnvVars, decryptedEnvVars,
{ deep: true } {
resolveExactMatch: true,
allowEmbedded: true,
trimKeys: true,
onMissing: 'keep',
deep: true,
}
) as Record<string, any> ) as Record<string, any>
logger.info(`[${tracker.requestId}] Resolved env var references in arguments`, { logger.info(`[${tracker.requestId}] Resolved env var references in arguments`, {

View File

@@ -84,14 +84,6 @@ vi.mock('@/lib/execution/isolated-vm', () => ({
vi.mock('@sim/logger', () => loggerMock) vi.mock('@sim/logger', () => loggerMock)
vi.mock('@/lib/auth/hybrid', () => ({
checkInternalAuth: vi.fn().mockResolvedValue({
success: true,
userId: 'user-123',
authType: 'internal_jwt',
}),
}))
vi.mock('@/lib/execution/e2b', () => ({ vi.mock('@/lib/execution/e2b', () => ({
executeInE2B: vi.fn(), executeInE2B: vi.fn(),
})) }))
@@ -118,24 +110,6 @@ describe('Function Execute API Route', () => {
}) })
describe('Security Tests', () => { describe('Security Tests', () => {
it('should reject unauthorized requests', async () => {
const { checkInternalAuth } = await import('@/lib/auth/hybrid')
vi.mocked(checkInternalAuth).mockResolvedValueOnce({
success: false,
error: 'Unauthorized',
})
const req = createMockRequest('POST', {
code: 'return "test"',
})
const response = await POST(req)
const data = await response.json()
expect(response.status).toBe(401)
expect(data).toHaveProperty('error', 'Unauthorized')
})
it.concurrent('should use isolated-vm for secure sandboxed execution', async () => { it.concurrent('should use isolated-vm for secure sandboxed execution', async () => {
const req = createMockRequest('POST', { const req = createMockRequest('POST', {
code: 'return "test"', code: 'return "test"',
@@ -339,7 +313,7 @@ describe('Function Execute API Route', () => {
'block-2': 'world', 'block-2': 'world',
}, },
blockNameMapping: { blockNameMapping: {
validvar: 'block-1', validVar: 'block-1',
another_valid: 'block-2', another_valid: 'block-2',
}, },
}) })
@@ -565,7 +539,7 @@ describe('Function Execute API Route', () => {
'block-complex': complexData, 'block-complex': complexData,
}, },
blockNameMapping: { blockNameMapping: {
complexdata: 'block-complex', complexData: 'block-complex',
}, },
}) })

View File

@@ -1,17 +1,16 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { isE2bEnabled } from '@/lib/core/config/feature-flags' import { isE2bEnabled } from '@/lib/core/config/feature-flags'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { executeInE2B } from '@/lib/execution/e2b' import { executeInE2B } from '@/lib/execution/e2b'
import { executeInIsolatedVM } from '@/lib/execution/isolated-vm' import { executeInIsolatedVM } from '@/lib/execution/isolated-vm'
import { CodeLanguage, DEFAULT_CODE_LANGUAGE, isValidCodeLanguage } from '@/lib/execution/languages' import { CodeLanguage, DEFAULT_CODE_LANGUAGE, isValidCodeLanguage } from '@/lib/execution/languages'
import { escapeRegExp, normalizeName, REFERENCE } from '@/executor/constants' import { escapeRegExp, normalizeName, REFERENCE } from '@/executor/constants'
import { type OutputSchema, resolveBlockReference } from '@/executor/utils/block-reference'
import { import {
createEnvVarPattern, createEnvVarPattern,
createWorkflowVariablePattern, createWorkflowVariablePattern,
} from '@/executor/utils/reference-validation' } from '@/executor/utils/reference-validation'
import { navigatePath } from '@/executor/variables/resolvers/reference'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
export const runtime = 'nodejs' export const runtime = 'nodejs'
@@ -471,17 +470,14 @@ function resolveEnvironmentVariables(
function resolveTagVariables( function resolveTagVariables(
code: string, code: string,
blockData: Record<string, unknown>, blockData: Record<string, any>,
blockNameMapping: Record<string, string>, blockNameMapping: Record<string, string>,
blockOutputSchemas: Record<string, OutputSchema>, contextVariables: Record<string, any>
contextVariables: Record<string, unknown>,
language = 'javascript'
): string { ): string {
let resolvedCode = code let resolvedCode = code
const undefinedLiteral = language === 'python' ? 'None' : 'undefined'
const tagPattern = new RegExp( const tagPattern = new RegExp(
`${REFERENCE.START}([a-zA-Z_](?:[a-zA-Z0-9_${REFERENCE.PATH_DELIMITER}]*[a-zA-Z0-9_])?)${REFERENCE.END}`, `${REFERENCE.START}([a-zA-Z_][a-zA-Z0-9_${REFERENCE.PATH_DELIMITER}]*[a-zA-Z0-9_])${REFERENCE.END}`,
'g' 'g'
) )
const tagMatches = resolvedCode.match(tagPattern) || [] const tagMatches = resolvedCode.match(tagPattern) || []
@@ -490,37 +486,41 @@ function resolveTagVariables(
const tagName = match.slice(REFERENCE.START.length, -REFERENCE.END.length).trim() const tagName = match.slice(REFERENCE.START.length, -REFERENCE.END.length).trim()
const pathParts = tagName.split(REFERENCE.PATH_DELIMITER) const pathParts = tagName.split(REFERENCE.PATH_DELIMITER)
const blockName = pathParts[0] const blockName = pathParts[0]
const fieldPath = pathParts.slice(1)
const result = resolveBlockReference(blockName, fieldPath, { const blockId = blockNameMapping[blockName]
blockNameMapping, if (!blockId) {
blockData,
blockOutputSchemas,
})
if (!result) {
continue continue
} }
let tagValue = result.value const blockOutput = blockData[blockId]
if (blockOutput === undefined) {
continue
}
let tagValue: any
if (pathParts.length === 1) {
tagValue = blockOutput
} else {
tagValue = navigatePath(blockOutput, pathParts.slice(1))
}
if (tagValue === undefined) { if (tagValue === undefined) {
resolvedCode = resolvedCode.replace(new RegExp(escapeRegExp(match), 'g'), undefinedLiteral)
continue continue
} }
if (typeof tagValue === 'string') { if (
const trimmed = tagValue.trimStart() typeof tagValue === 'string' &&
if (trimmed.startsWith('{') || trimmed.startsWith('[')) { tagValue.length > 100 &&
(tagValue.startsWith('{') || tagValue.startsWith('['))
) {
try { try {
tagValue = JSON.parse(tagValue) tagValue = JSON.parse(tagValue)
} catch { } catch {
// Keep as string if not valid JSON // Keep as-is
}
} }
} }
const safeVarName = `__tag_${tagName.replace(/_/g, '_1').replace(/\./g, '_0')}` const safeVarName = `__tag_${tagName.replace(/[^a-zA-Z0-9_]/g, '_')}`
contextVariables[safeVarName] = tagValue contextVariables[safeVarName] = tagValue
resolvedCode = resolvedCode.replace(new RegExp(escapeRegExp(match), 'g'), safeVarName) resolvedCode = resolvedCode.replace(new RegExp(escapeRegExp(match), 'g'), safeVarName)
} }
@@ -537,27 +537,18 @@ function resolveTagVariables(
*/ */
function resolveCodeVariables( function resolveCodeVariables(
code: string, code: string,
params: Record<string, unknown>, params: Record<string, any>,
envVars: Record<string, string> = {}, envVars: Record<string, string> = {},
blockData: Record<string, unknown> = {}, blockData: Record<string, any> = {},
blockNameMapping: Record<string, string> = {}, blockNameMapping: Record<string, string> = {},
blockOutputSchemas: Record<string, OutputSchema> = {}, workflowVariables: Record<string, any> = {}
workflowVariables: Record<string, unknown> = {}, ): { resolvedCode: string; contextVariables: Record<string, any> } {
language = 'javascript'
): { resolvedCode: string; contextVariables: Record<string, unknown> } {
let resolvedCode = code let resolvedCode = code
const contextVariables: Record<string, unknown> = {} const contextVariables: Record<string, any> = {}
resolvedCode = resolveWorkflowVariables(resolvedCode, workflowVariables, contextVariables) resolvedCode = resolveWorkflowVariables(resolvedCode, workflowVariables, contextVariables)
resolvedCode = resolveEnvironmentVariables(resolvedCode, params, envVars, contextVariables) resolvedCode = resolveEnvironmentVariables(resolvedCode, params, envVars, contextVariables)
resolvedCode = resolveTagVariables( resolvedCode = resolveTagVariables(resolvedCode, blockData, blockNameMapping, contextVariables)
resolvedCode,
blockData,
blockNameMapping,
blockOutputSchemas,
contextVariables,
language
)
return { resolvedCode, contextVariables } return { resolvedCode, contextVariables }
} }
@@ -582,12 +573,6 @@ export async function POST(req: NextRequest) {
let resolvedCode = '' // Store resolved code for error reporting let resolvedCode = '' // Store resolved code for error reporting
try { try {
const auth = await checkInternalAuth(req)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized function execution attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await req.json() const body = await req.json()
const { DEFAULT_EXECUTION_TIMEOUT_MS } = await import('@/lib/execution/constants') const { DEFAULT_EXECUTION_TIMEOUT_MS } = await import('@/lib/execution/constants')
@@ -600,7 +585,6 @@ export async function POST(req: NextRequest) {
envVars = {}, envVars = {},
blockData = {}, blockData = {},
blockNameMapping = {}, blockNameMapping = {},
blockOutputSchemas = {},
workflowVariables = {}, workflowVariables = {},
workflowId, workflowId,
isCustomTool = false, isCustomTool = false,
@@ -617,21 +601,20 @@ export async function POST(req: NextRequest) {
isCustomTool, isCustomTool,
}) })
const lang = isValidCodeLanguage(language) ? language : DEFAULT_CODE_LANGUAGE // Resolve variables in the code with workflow environment variables
const codeResolution = resolveCodeVariables( const codeResolution = resolveCodeVariables(
code, code,
executionParams, executionParams,
envVars, envVars,
blockData, blockData,
blockNameMapping, blockNameMapping,
blockOutputSchemas, workflowVariables
workflowVariables,
lang
) )
resolvedCode = codeResolution.resolvedCode resolvedCode = codeResolution.resolvedCode
const contextVariables = codeResolution.contextVariables const contextVariables = codeResolution.contextVariables
const lang = isValidCodeLanguage(language) ? language : DEFAULT_CODE_LANGUAGE
let jsImports = '' let jsImports = ''
let jsRemainingCode = resolvedCode let jsRemainingCode = resolvedCode
let hasImports = false let hasImports = false
@@ -687,11 +670,7 @@ export async function POST(req: NextRequest) {
prologue += `const environmentVariables = JSON.parse(${JSON.stringify(JSON.stringify(envVars))});\n` prologue += `const environmentVariables = JSON.parse(${JSON.stringify(JSON.stringify(envVars))});\n`
prologueLineCount++ prologueLineCount++
for (const [k, v] of Object.entries(contextVariables)) { for (const [k, v] of Object.entries(contextVariables)) {
if (v === undefined) {
prologue += `const ${k} = undefined;\n`
} else {
prologue += `const ${k} = JSON.parse(${JSON.stringify(JSON.stringify(v))});\n` prologue += `const ${k} = JSON.parse(${JSON.stringify(JSON.stringify(v))});\n`
}
prologueLineCount++ prologueLineCount++
} }
@@ -762,11 +741,7 @@ export async function POST(req: NextRequest) {
prologue += `environmentVariables = json.loads(${JSON.stringify(JSON.stringify(envVars))})\n` prologue += `environmentVariables = json.loads(${JSON.stringify(JSON.stringify(envVars))})\n`
prologueLineCount++ prologueLineCount++
for (const [k, v] of Object.entries(contextVariables)) { for (const [k, v] of Object.entries(contextVariables)) {
if (v === undefined) {
prologue += `${k} = None\n`
} else {
prologue += `${k} = json.loads(${JSON.stringify(JSON.stringify(v))})\n` prologue += `${k} = json.loads(${JSON.stringify(JSON.stringify(v))})\n`
}
prologueLineCount++ prologueLineCount++
} }
const wrapped = [ const wrapped = [

View File

@@ -157,7 +157,7 @@ describe('Knowledge Base Documents API Route', () => {
expect(vi.mocked(getDocuments)).toHaveBeenCalledWith( expect(vi.mocked(getDocuments)).toHaveBeenCalledWith(
'kb-123', 'kb-123',
{ {
enabledFilter: undefined, includeDisabled: false,
search: undefined, search: undefined,
limit: 50, limit: 50,
offset: 0, offset: 0,
@@ -166,7 +166,7 @@ describe('Knowledge Base Documents API Route', () => {
) )
}) })
it('should return documents with default filter', async () => { it('should filter disabled documents by default', async () => {
const { checkKnowledgeBaseAccess } = await import('@/app/api/knowledge/utils') const { checkKnowledgeBaseAccess } = await import('@/app/api/knowledge/utils')
const { getDocuments } = await import('@/lib/knowledge/documents/service') const { getDocuments } = await import('@/lib/knowledge/documents/service')
@@ -194,7 +194,7 @@ describe('Knowledge Base Documents API Route', () => {
expect(vi.mocked(getDocuments)).toHaveBeenCalledWith( expect(vi.mocked(getDocuments)).toHaveBeenCalledWith(
'kb-123', 'kb-123',
{ {
enabledFilter: undefined, includeDisabled: false,
search: undefined, search: undefined,
limit: 50, limit: 50,
offset: 0, offset: 0,
@@ -203,7 +203,7 @@ describe('Knowledge Base Documents API Route', () => {
) )
}) })
it('should filter documents by enabled status when requested', async () => { it('should include disabled documents when requested', async () => {
const { checkKnowledgeBaseAccess } = await import('@/app/api/knowledge/utils') const { checkKnowledgeBaseAccess } = await import('@/app/api/knowledge/utils')
const { getDocuments } = await import('@/lib/knowledge/documents/service') const { getDocuments } = await import('@/lib/knowledge/documents/service')
@@ -223,7 +223,7 @@ describe('Knowledge Base Documents API Route', () => {
}, },
}) })
const url = 'http://localhost:3000/api/knowledge/kb-123/documents?enabledFilter=disabled' const url = 'http://localhost:3000/api/knowledge/kb-123/documents?includeDisabled=true'
const req = new Request(url, { method: 'GET' }) as any const req = new Request(url, { method: 'GET' }) as any
const { GET } = await import('@/app/api/knowledge/[id]/documents/route') const { GET } = await import('@/app/api/knowledge/[id]/documents/route')
@@ -233,7 +233,7 @@ describe('Knowledge Base Documents API Route', () => {
expect(vi.mocked(getDocuments)).toHaveBeenCalledWith( expect(vi.mocked(getDocuments)).toHaveBeenCalledWith(
'kb-123', 'kb-123',
{ {
enabledFilter: 'disabled', includeDisabled: true,
search: undefined, search: undefined,
limit: 50, limit: 50,
offset: 0, offset: 0,
@@ -361,7 +361,8 @@ describe('Knowledge Base Documents API Route', () => {
expect(vi.mocked(createSingleDocument)).toHaveBeenCalledWith( expect(vi.mocked(createSingleDocument)).toHaveBeenCalledWith(
validDocumentData, validDocumentData,
'kb-123', 'kb-123',
expect.any(String) expect.any(String),
'user-123'
) )
}) })
@@ -469,7 +470,8 @@ describe('Knowledge Base Documents API Route', () => {
expect(vi.mocked(createDocumentRecords)).toHaveBeenCalledWith( expect(vi.mocked(createDocumentRecords)).toHaveBeenCalledWith(
validBulkData.documents, validBulkData.documents,
'kb-123', 'kb-123',
expect.any(String) expect.any(String),
'user-123'
) )
expect(vi.mocked(processDocumentsWithQueue)).toHaveBeenCalled() expect(vi.mocked(processDocumentsWithQueue)).toHaveBeenCalled()
}) })

View File

@@ -5,7 +5,6 @@ import { z } from 'zod'
import { getSession } from '@/lib/auth' import { getSession } from '@/lib/auth'
import { import {
bulkDocumentOperation, bulkDocumentOperation,
bulkDocumentOperationByFilter,
createDocumentRecords, createDocumentRecords,
createSingleDocument, createSingleDocument,
getDocuments, getDocuments,
@@ -58,19 +57,12 @@ const BulkCreateDocumentsSchema = z.object({
bulk: z.literal(true), bulk: z.literal(true),
}) })
const BulkUpdateDocumentsSchema = z const BulkUpdateDocumentsSchema = z.object({
.object({
operation: z.enum(['enable', 'disable', 'delete']), operation: z.enum(['enable', 'disable', 'delete']),
documentIds: z documentIds: z
.array(z.string()) .array(z.string())
.min(1, 'At least one document ID is required') .min(1, 'At least one document ID is required')
.max(100, 'Cannot operate on more than 100 documents at once') .max(100, 'Cannot operate on more than 100 documents at once'),
.optional(),
selectAll: z.boolean().optional(),
enabledFilter: z.enum(['all', 'enabled', 'disabled']).optional(),
})
.refine((data) => data.selectAll || (data.documentIds && data.documentIds.length > 0), {
message: 'Either selectAll must be true or documentIds must be provided',
}) })
export async function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { export async function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
@@ -98,17 +90,14 @@ export async function GET(req: NextRequest, { params }: { params: Promise<{ id:
} }
const url = new URL(req.url) const url = new URL(req.url)
const enabledFilter = url.searchParams.get('enabledFilter') as const includeDisabled = url.searchParams.get('includeDisabled') === 'true'
| 'all'
| 'enabled'
| 'disabled'
| null
const search = url.searchParams.get('search') || undefined const search = url.searchParams.get('search') || undefined
const limit = Number.parseInt(url.searchParams.get('limit') || '50') const limit = Number.parseInt(url.searchParams.get('limit') || '50')
const offset = Number.parseInt(url.searchParams.get('offset') || '0') const offset = Number.parseInt(url.searchParams.get('offset') || '0')
const sortByParam = url.searchParams.get('sortBy') const sortByParam = url.searchParams.get('sortBy')
const sortOrderParam = url.searchParams.get('sortOrder') const sortOrderParam = url.searchParams.get('sortOrder')
// Validate sort parameters
const validSortFields: DocumentSortField[] = [ const validSortFields: DocumentSortField[] = [
'filename', 'filename',
'fileSize', 'fileSize',
@@ -116,7 +105,6 @@ export async function GET(req: NextRequest, { params }: { params: Promise<{ id:
'chunkCount', 'chunkCount',
'uploadedAt', 'uploadedAt',
'processingStatus', 'processingStatus',
'enabled',
] ]
const validSortOrders: SortOrder[] = ['asc', 'desc'] const validSortOrders: SortOrder[] = ['asc', 'desc']
@@ -132,7 +120,7 @@ export async function GET(req: NextRequest, { params }: { params: Promise<{ id:
const result = await getDocuments( const result = await getDocuments(
knowledgeBaseId, knowledgeBaseId,
{ {
enabledFilter: enabledFilter || undefined, includeDisabled,
search, search,
limit, limit,
offset, offset,
@@ -202,7 +190,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
const createdDocuments = await createDocumentRecords( const createdDocuments = await createDocumentRecords(
validatedData.documents, validatedData.documents,
knowledgeBaseId, knowledgeBaseId,
requestId requestId,
userId
) )
logger.info( logger.info(
@@ -261,10 +250,16 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
throw validationError throw validationError
} }
} else { } else {
// Handle single document creation
try { try {
const validatedData = CreateDocumentSchema.parse(body) const validatedData = CreateDocumentSchema.parse(body)
const newDocument = await createSingleDocument(validatedData, knowledgeBaseId, requestId) const newDocument = await createSingleDocument(
validatedData,
knowledgeBaseId,
requestId,
userId
)
try { try {
const { PlatformEvents } = await import('@/lib/core/telemetry') const { PlatformEvents } = await import('@/lib/core/telemetry')
@@ -299,6 +294,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
} catch (error) { } catch (error) {
logger.error(`[${requestId}] Error creating document`, error) logger.error(`[${requestId}] Error creating document`, error)
// Check if it's a storage limit error
const errorMessage = error instanceof Error ? error.message : 'Failed to create document' const errorMessage = error instanceof Error ? error.message : 'Failed to create document'
const isStorageLimitError = const isStorageLimitError =
errorMessage.includes('Storage limit exceeded') || errorMessage.includes('storage limit') errorMessage.includes('Storage limit exceeded') || errorMessage.includes('storage limit')
@@ -335,22 +331,16 @@ export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id
try { try {
const validatedData = BulkUpdateDocumentsSchema.parse(body) const validatedData = BulkUpdateDocumentsSchema.parse(body)
const { operation, documentIds, selectAll, enabledFilter } = validatedData const { operation, documentIds } = validatedData
try { try {
let result const result = await bulkDocumentOperation(
if (selectAll) {
result = await bulkDocumentOperationByFilter(
knowledgeBaseId, knowledgeBaseId,
operation, operation,
enabledFilter, documentIds,
requestId requestId,
session.user.id
) )
} else if (documentIds && documentIds.length > 0) {
result = await bulkDocumentOperation(knowledgeBaseId, operation, documentIds, requestId)
} else {
return NextResponse.json({ error: 'No documents specified' }, { status: 400 })
}
return NextResponse.json({ return NextResponse.json({
success: true, success: true,

View File

@@ -1,10 +1,11 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import type { NextRequest } from 'next/server' import type { NextRequest } from 'next/server'
import { getEffectiveDecryptedEnv } from '@/lib/environment/utils'
import { McpClient } from '@/lib/mcp/client' import { McpClient } from '@/lib/mcp/client'
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
import { resolveMcpConfigEnvVars } from '@/lib/mcp/resolve-config' import type { McpServerConfig, McpTransport } from '@/lib/mcp/types'
import type { McpTransport } from '@/lib/mcp/types'
import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils' import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils'
import { resolveEnvVarReferences } from '@/executor/utils/reference-validation'
const logger = createLogger('McpServerTestAPI') const logger = createLogger('McpServerTestAPI')
@@ -18,6 +19,30 @@ function isUrlBasedTransport(transport: McpTransport): boolean {
return transport === 'streamable-http' return transport === 'streamable-http'
} }
/**
* Resolve environment variables in strings
*/
function resolveEnvVars(value: string, envVars: Record<string, string>): string {
const missingVars: string[] = []
const resolvedValue = resolveEnvVarReferences(value, envVars, {
allowEmbedded: true,
resolveExactMatch: true,
trimKeys: true,
onMissing: 'keep',
deep: false,
missingKeys: missingVars,
}) as string
if (missingVars.length > 0) {
const uniqueMissing = Array.from(new Set(missingVars))
uniqueMissing.forEach((envKey) => {
logger.warn(`Environment variable "${envKey}" not found in MCP server test`)
})
}
return resolvedValue
}
interface TestConnectionRequest { interface TestConnectionRequest {
name: string name: string
transport: McpTransport transport: McpTransport
@@ -71,30 +96,39 @@ export const POST = withMcpAuth('write')(
) )
} }
// Build initial config for resolution let resolvedUrl = body.url
const initialConfig = { let resolvedHeaders = body.headers || {}
try {
const envVars = await getEffectiveDecryptedEnv(userId, workspaceId)
if (resolvedUrl) {
resolvedUrl = resolveEnvVars(resolvedUrl, envVars)
}
const resolvedHeadersObj: Record<string, string> = {}
for (const [key, value] of Object.entries(resolvedHeaders)) {
resolvedHeadersObj[key] = resolveEnvVars(value, envVars)
}
resolvedHeaders = resolvedHeadersObj
} catch (envError) {
logger.warn(
`[${requestId}] Failed to resolve environment variables, using raw values:`,
envError
)
}
const testConfig: McpServerConfig = {
id: `test-${requestId}`, id: `test-${requestId}`,
name: body.name, name: body.name,
transport: body.transport, transport: body.transport,
url: body.url, url: resolvedUrl,
headers: body.headers || {}, headers: resolvedHeaders,
timeout: body.timeout || 10000, timeout: body.timeout || 10000,
retries: 1, // Only one retry for tests retries: 1, // Only one retry for tests
enabled: true, enabled: true,
} }
// Resolve env vars using shared utility (non-strict mode for testing)
const { config: testConfig, missingVars } = await resolveMcpConfigEnvVars(
initialConfig,
userId,
workspaceId,
{ strict: false }
)
if (missingVars.length > 0) {
logger.warn(`[${requestId}] Some environment variables not found:`, { missingVars })
}
const testSecurityPolicy = { const testSecurityPolicy = {
requireConsent: false, requireConsent: false,
auditLevel: 'none' as const, auditLevel: 'none' as const,

View File

@@ -3,9 +3,7 @@ import { account } from '@sim/db/schema'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { eq } from 'drizzle-orm' import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
import { refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils'
import type { StreamingExecution } from '@/executor/types' import type { StreamingExecution } from '@/executor/types'
import { executeProviderRequest } from '@/providers' import { executeProviderRequest } from '@/providers'
@@ -22,11 +20,6 @@ export async function POST(request: NextRequest) {
const startTime = Date.now() const startTime = Date.now()
try { try {
const auth = await checkInternalAuth(request, { requireWorkflowId: false })
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
logger.info(`[${requestId}] Provider API request started`, { logger.info(`[${requestId}] Provider API request started`, {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
userAgent: request.headers.get('User-Agent'), userAgent: request.headers.get('User-Agent'),
@@ -92,13 +85,6 @@ export async function POST(request: NextRequest) {
verbosity, verbosity,
}) })
if (workspaceId) {
const workspaceAccess = await checkWorkspaceAccess(workspaceId, auth.userId)
if (!workspaceAccess.hasAccess) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
}
let finalApiKey: string | undefined = apiKey let finalApiKey: string | undefined = apiKey
try { try {
if (provider === 'vertex' && vertexCredential) { if (provider === 'vertex' && vertexCredential) {

View File

@@ -3,7 +3,6 @@ import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { createA2AClient } from '@/lib/a2a/utils' import { createA2AClient } from '@/lib/a2a/utils'
import { checkHybridAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { validateExternalUrl } from '@/lib/core/security/input-validation'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -40,18 +39,6 @@ export async function POST(request: NextRequest) {
const body = await request.json() const body = await request.json()
const validatedData = A2ASetPushNotificationSchema.parse(body) const validatedData = A2ASetPushNotificationSchema.parse(body)
const urlValidation = validateExternalUrl(validatedData.webhookUrl, 'Webhook URL')
if (!urlValidation.isValid) {
logger.warn(`[${requestId}] Invalid webhook URL`, { error: urlValidation.error })
return NextResponse.json(
{
success: false,
error: urlValidation.error,
},
{ status: 400 }
)
}
logger.info(`[${requestId}] A2A set push notification request`, { logger.info(`[${requestId}] A2A set push notification request`, {
agentUrl: validatedData.agentUrl, agentUrl: validatedData.agentUrl,
taskId: validatedData.taskId, taskId: validatedData.taskId,

View File

@@ -181,7 +181,7 @@ describe('Custom Tools API Routes', () => {
})) }))
vi.doMock('@/lib/auth/hybrid', () => ({ vi.doMock('@/lib/auth/hybrid', () => ({
checkSessionOrInternalAuth: vi.fn().mockResolvedValue({ checkHybridAuth: vi.fn().mockResolvedValue({
success: true, success: true,
userId: 'user-123', userId: 'user-123',
authType: 'session', authType: 'session',
@@ -254,7 +254,7 @@ describe('Custom Tools API Routes', () => {
) )
vi.doMock('@/lib/auth/hybrid', () => ({ vi.doMock('@/lib/auth/hybrid', () => ({
checkSessionOrInternalAuth: vi.fn().mockResolvedValue({ checkHybridAuth: vi.fn().mockResolvedValue({
success: false, success: false,
error: 'Unauthorized', error: 'Unauthorized',
}), }),
@@ -304,7 +304,7 @@ describe('Custom Tools API Routes', () => {
describe('POST /api/tools/custom', () => { describe('POST /api/tools/custom', () => {
it('should reject unauthorized requests', async () => { it('should reject unauthorized requests', async () => {
vi.doMock('@/lib/auth/hybrid', () => ({ vi.doMock('@/lib/auth/hybrid', () => ({
checkSessionOrInternalAuth: vi.fn().mockResolvedValue({ checkHybridAuth: vi.fn().mockResolvedValue({
success: false, success: false,
error: 'Unauthorized', error: 'Unauthorized',
}), }),
@@ -390,7 +390,7 @@ describe('Custom Tools API Routes', () => {
it('should prevent unauthorized deletion of user-scoped tool', async () => { it('should prevent unauthorized deletion of user-scoped tool', async () => {
vi.doMock('@/lib/auth/hybrid', () => ({ vi.doMock('@/lib/auth/hybrid', () => ({
checkSessionOrInternalAuth: vi.fn().mockResolvedValue({ checkHybridAuth: vi.fn().mockResolvedValue({
success: true, success: true,
userId: 'user-456', userId: 'user-456',
authType: 'session', authType: 'session',
@@ -413,7 +413,7 @@ describe('Custom Tools API Routes', () => {
it('should reject unauthorized requests', async () => { it('should reject unauthorized requests', async () => {
vi.doMock('@/lib/auth/hybrid', () => ({ vi.doMock('@/lib/auth/hybrid', () => ({
checkSessionOrInternalAuth: vi.fn().mockResolvedValue({ checkHybridAuth: vi.fn().mockResolvedValue({
success: false, success: false,
error: 'Unauthorized', error: 'Unauthorized',
}), }),

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, desc, eq, isNull, or } from 'drizzle-orm' import { and, desc, eq, isNull, or } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { upsertCustomTools } from '@/lib/workflows/custom-tools/operations' import { upsertCustomTools } from '@/lib/workflows/custom-tools/operations'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
@@ -42,8 +42,8 @@ export async function GET(request: NextRequest) {
const workflowId = searchParams.get('workflowId') const workflowId = searchParams.get('workflowId')
try { try {
// Use session/internal auth to support session and internal JWT (no API key access) // Use hybrid auth to support session, API key, and internal JWT
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success || !authResult.userId) { if (!authResult.success || !authResult.userId) {
logger.warn(`[${requestId}] Unauthorized custom tools access attempt`) logger.warn(`[${requestId}] Unauthorized custom tools access attempt`)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
@@ -69,8 +69,8 @@ export async function GET(request: NextRequest) {
} }
// Check workspace permissions // Check workspace permissions
// For internal JWT with workflowId: checkSessionOrInternalAuth already resolved userId from workflow owner // For internal JWT with workflowId: checkHybridAuth already resolved userId from workflow owner
// For session: verify user has access to the workspace // For session/API key: verify user has access to the workspace
// For legacy (no workspaceId): skip workspace check, rely on userId match // For legacy (no workspaceId): skip workspace check, rely on userId match
if (resolvedWorkspaceId && !(authResult.authType === 'internal_jwt' && workflowId)) { if (resolvedWorkspaceId && !(authResult.authType === 'internal_jwt' && workflowId)) {
const userPermission = await getUserEntityPermissions( const userPermission = await getUserEntityPermissions(
@@ -116,8 +116,8 @@ export async function POST(req: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
// Use session/internal auth (no API key access) // Use hybrid auth (though this endpoint is only called from UI)
const authResult = await checkSessionOrInternalAuth(req, { requireWorkflowId: false }) const authResult = await checkHybridAuth(req, { requireWorkflowId: false })
if (!authResult.success || !authResult.userId) { if (!authResult.success || !authResult.userId) {
logger.warn(`[${requestId}] Unauthorized custom tools update attempt`) logger.warn(`[${requestId}] Unauthorized custom tools update attempt`)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
@@ -193,8 +193,8 @@ export async function DELETE(request: NextRequest) {
} }
try { try {
// Use session/internal auth (no API key access) // Use hybrid auth (though this endpoint is only called from UI)
const authResult = await checkSessionOrInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success || !authResult.userId) { if (!authResult.success || !authResult.userId) {
logger.warn(`[${requestId}] Unauthorized custom tool deletion attempt`) logger.warn(`[${requestId}] Unauthorized custom tool deletion attempt`)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { validateNumericId } from '@/lib/core/security/input-validation' import { validateNumericId } from '@/lib/core/security/input-validation'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
@@ -22,7 +22,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Discord send attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Discord send attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { validateAlphanumericId } from '@/lib/core/security/input-validation'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
@@ -21,7 +21,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Gmail add label attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Gmail add label attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -19,7 +19,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Gmail archive attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Gmail archive attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -19,7 +19,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Gmail delete attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Gmail delete attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
@@ -35,7 +35,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Gmail draft attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Gmail draft attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -19,7 +19,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Gmail mark read attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Gmail mark read attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -19,7 +19,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Gmail mark unread attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Gmail mark unread attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -21,7 +21,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Gmail move attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Gmail move attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { validateAlphanumericId } from '@/lib/core/security/input-validation'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
@@ -21,7 +21,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Gmail remove label attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Gmail remove label attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
@@ -35,7 +35,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Gmail send attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Gmail send attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -19,7 +19,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Gmail unarchive attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Gmail unarchive attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils' import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
@@ -56,7 +56,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Google Drive upload attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Google Drive upload attempt: ${authResult.error}`)

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { validateImageUrl } from '@/lib/core/security/input-validation' import { validateImageUrl } from '@/lib/core/security/input-validation'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
@@ -15,7 +15,7 @@ export async function GET(request: NextRequest) {
const imageUrl = url.searchParams.get('url') const imageUrl = url.searchParams.get('url')
const requestId = generateRequestId() const requestId = generateRequestId()
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.error(`[${requestId}] Authentication failed for image proxy:`, authResult.error) logger.error(`[${requestId}] Authentication failed for image proxy:`, authResult.error)
return new NextResponse('Unauthorized', { status: 401 }) return new NextResponse('Unauthorized', { status: 401 })

View File

@@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { Resend } from 'resend' import { Resend } from 'resend'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -22,7 +22,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized mail send attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized mail send attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -18,7 +18,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Teams chat delete attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Teams chat delete attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
@@ -23,7 +23,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Teams channel write attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Teams channel write attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
@@ -22,7 +22,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Teams chat write attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Teams chat write attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { getBaseUrl } from '@/lib/core/utils/urls' import { getBaseUrl } from '@/lib/core/utils/urls'
import { StorageService } from '@/lib/uploads' import { StorageService } from '@/lib/uploads'
@@ -30,7 +30,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success || !authResult.userId) { if (!authResult.success || !authResult.userId) {
logger.warn(`[${requestId}] Unauthorized Mistral parse attempt`, { logger.warn(`[${requestId}] Unauthorized Mistral parse attempt`, {

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { buildDeleteQuery, createMySQLConnection, executeQuery } from '@/app/api/tools/mysql/utils' import { buildDeleteQuery, createMySQLConnection, executeQuery } from '@/app/api/tools/mysql/utils'
const logger = createLogger('MySQLDeleteAPI') const logger = createLogger('MySQLDeleteAPI')
@@ -22,12 +21,6 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized MySQL delete attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = DeleteSchema.parse(body) const params = DeleteSchema.parse(body)

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createMySQLConnection, executeQuery, validateQuery } from '@/app/api/tools/mysql/utils' import { createMySQLConnection, executeQuery, validateQuery } from '@/app/api/tools/mysql/utils'
const logger = createLogger('MySQLExecuteAPI') const logger = createLogger('MySQLExecuteAPI')
@@ -21,12 +20,6 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized MySQL execute attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = ExecuteSchema.parse(body) const params = ExecuteSchema.parse(body)

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { buildInsertQuery, createMySQLConnection, executeQuery } from '@/app/api/tools/mysql/utils' import { buildInsertQuery, createMySQLConnection, executeQuery } from '@/app/api/tools/mysql/utils'
const logger = createLogger('MySQLInsertAPI') const logger = createLogger('MySQLInsertAPI')
@@ -43,12 +42,6 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized MySQL insert attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = InsertSchema.parse(body) const params = InsertSchema.parse(body)

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createMySQLConnection, executeIntrospect } from '@/app/api/tools/mysql/utils' import { createMySQLConnection, executeIntrospect } from '@/app/api/tools/mysql/utils'
const logger = createLogger('MySQLIntrospectAPI') const logger = createLogger('MySQLIntrospectAPI')
@@ -20,12 +19,6 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized MySQL introspect attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = IntrospectSchema.parse(body) const params = IntrospectSchema.parse(body)

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createMySQLConnection, executeQuery, validateQuery } from '@/app/api/tools/mysql/utils' import { createMySQLConnection, executeQuery, validateQuery } from '@/app/api/tools/mysql/utils'
const logger = createLogger('MySQLQueryAPI') const logger = createLogger('MySQLQueryAPI')
@@ -21,12 +20,6 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized MySQL query attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = QuerySchema.parse(body) const params = QuerySchema.parse(body)

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { buildUpdateQuery, createMySQLConnection, executeQuery } from '@/app/api/tools/mysql/utils' import { buildUpdateQuery, createMySQLConnection, executeQuery } from '@/app/api/tools/mysql/utils'
const logger = createLogger('MySQLUpdateAPI') const logger = createLogger('MySQLUpdateAPI')
@@ -41,12 +40,6 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized MySQL update attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = UpdateSchema.parse(body) const params = UpdateSchema.parse(body)

View File

@@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import * as XLSX from 'xlsx' import * as XLSX from 'xlsx'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { import {
@@ -39,7 +39,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized OneDrive upload attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized OneDrive upload attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -18,7 +18,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Outlook copy attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Outlook copy attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -17,7 +17,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Outlook delete attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Outlook delete attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
@@ -25,7 +25,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Outlook draft attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Outlook draft attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -17,7 +17,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Outlook mark read attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Outlook mark read attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -17,7 +17,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Outlook mark unread attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Outlook mark unread attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -18,7 +18,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Outlook move attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Outlook move attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
@@ -27,7 +27,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Outlook send attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Outlook send attempt: ${authResult.error}`)

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createPostgresConnection, executeDelete } from '@/app/api/tools/postgresql/utils' import { createPostgresConnection, executeDelete } from '@/app/api/tools/postgresql/utils'
const logger = createLogger('PostgreSQLDeleteAPI') const logger = createLogger('PostgreSQLDeleteAPI')
@@ -22,12 +21,6 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized PostgreSQL delete attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = DeleteSchema.parse(body) const params = DeleteSchema.parse(body)

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { import {
createPostgresConnection, createPostgresConnection,
executeQuery, executeQuery,
@@ -25,12 +24,6 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized PostgreSQL execute attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = ExecuteSchema.parse(body) const params = ExecuteSchema.parse(body)

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createPostgresConnection, executeInsert } from '@/app/api/tools/postgresql/utils' import { createPostgresConnection, executeInsert } from '@/app/api/tools/postgresql/utils'
const logger = createLogger('PostgreSQLInsertAPI') const logger = createLogger('PostgreSQLInsertAPI')
@@ -43,12 +42,6 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized PostgreSQL insert attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = InsertSchema.parse(body) const params = InsertSchema.parse(body)

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createPostgresConnection, executeIntrospect } from '@/app/api/tools/postgresql/utils' import { createPostgresConnection, executeIntrospect } from '@/app/api/tools/postgresql/utils'
const logger = createLogger('PostgreSQLIntrospectAPI') const logger = createLogger('PostgreSQLIntrospectAPI')
@@ -21,12 +20,6 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized PostgreSQL introspect attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = IntrospectSchema.parse(body) const params = IntrospectSchema.parse(body)

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createPostgresConnection, executeQuery } from '@/app/api/tools/postgresql/utils' import { createPostgresConnection, executeQuery } from '@/app/api/tools/postgresql/utils'
const logger = createLogger('PostgreSQLQueryAPI') const logger = createLogger('PostgreSQLQueryAPI')
@@ -21,12 +20,6 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized PostgreSQL query attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = QuerySchema.parse(body) const params = QuerySchema.parse(body)

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createPostgresConnection, executeUpdate } from '@/app/api/tools/postgresql/utils' import { createPostgresConnection, executeUpdate } from '@/app/api/tools/postgresql/utils'
const logger = createLogger('PostgreSQLUpdateAPI') const logger = createLogger('PostgreSQLUpdateAPI')
@@ -41,12 +40,6 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized PostgreSQL update attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = UpdateSchema.parse(body) const params = UpdateSchema.parse(body)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { getBaseUrl } from '@/lib/core/utils/urls' import { getBaseUrl } from '@/lib/core/utils/urls'
import { StorageService } from '@/lib/uploads' import { StorageService } from '@/lib/uploads'
@@ -31,7 +31,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success || !authResult.userId) { if (!authResult.success || !authResult.userId) {
logger.warn(`[${requestId}] Unauthorized Pulse parse attempt`, { logger.warn(`[${requestId}] Unauthorized Pulse parse attempt`, {

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { getBaseUrl } from '@/lib/core/utils/urls' import { getBaseUrl } from '@/lib/core/utils/urls'
import { StorageService } from '@/lib/uploads' import { StorageService } from '@/lib/uploads'
@@ -27,7 +27,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success || !authResult.userId) { if (!authResult.success || !authResult.userId) {
logger.warn(`[${requestId}] Unauthorized Reducto parse attempt`, { logger.warn(`[${requestId}] Unauthorized Reducto parse attempt`, {

View File

@@ -2,7 +2,7 @@ import { CopyObjectCommand, type ObjectCannedACL, S3Client } from '@aws-sdk/clie
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -24,7 +24,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized S3 copy object attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized S3 copy object attempt: ${authResult.error}`)

View File

@@ -2,7 +2,7 @@ import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -21,7 +21,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized S3 delete object attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized S3 delete object attempt: ${authResult.error}`)

View File

@@ -2,7 +2,7 @@ import { ListObjectsV2Command, S3Client } from '@aws-sdk/client-s3'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -23,7 +23,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized S3 list objects attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized S3 list objects attempt: ${authResult.error}`)

View File

@@ -2,7 +2,7 @@ import { type ObjectCannedACL, PutObjectCommand, S3Client } from '@aws-sdk/clien
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils' import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
@@ -27,7 +27,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized S3 put object attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized S3 put object attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { SEARCH_TOOL_COST } from '@/lib/billing/constants' import { SEARCH_TOOL_COST } from '@/lib/billing/constants'
import { env } from '@/lib/core/config/env' import { env } from '@/lib/core/config/env'
import { executeTool } from '@/tools' import { executeTool } from '@/tools'
@@ -22,7 +22,7 @@ export async function POST(request: NextRequest) {
const { searchParams: urlParams } = new URL(request.url) const { searchParams: urlParams } = new URL(request.url)
const workflowId = urlParams.get('workflowId') || undefined const workflowId = urlParams.get('workflowId') || undefined
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success || !authResult.userId) { if (!authResult.success || !authResult.userId) {
const errorMessage = workflowId ? 'Workflow not found' : authResult.error || 'Unauthorized' const errorMessage = workflowId ? 'Workflow not found' : authResult.error || 'Unauthorized'

View File

@@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import type { SFTPWrapper } from 'ssh2' import type { SFTPWrapper } from 'ssh2'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { import {
createSftpConnection, createSftpConnection,
@@ -72,7 +72,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized SFTP delete attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized SFTP delete attempt: ${authResult.error}`)

View File

@@ -2,7 +2,7 @@ import path from 'path'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { createSftpConnection, getSftp, isPathSafe, sanitizePath } from '@/app/api/tools/sftp/utils' import { createSftpConnection, getSftp, isPathSafe, sanitizePath } from '@/app/api/tools/sftp/utils'
@@ -25,7 +25,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized SFTP download attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized SFTP download attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { import {
createSftpConnection, createSftpConnection,
@@ -31,7 +31,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized SFTP list attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized SFTP list attempt: ${authResult.error}`)

View File

@@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import type { SFTPWrapper } from 'ssh2' import type { SFTPWrapper } from 'ssh2'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { import {
createSftpConnection, createSftpConnection,
@@ -60,7 +60,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized SFTP mkdir attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized SFTP mkdir attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
@@ -44,7 +44,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized SFTP upload attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized SFTP upload attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
@@ -23,7 +23,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized SharePoint upload attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized SharePoint upload attempt: ${authResult.error}`)

View File

@@ -1,6 +1,6 @@
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -13,7 +13,7 @@ const SlackAddReactionSchema = z.object({
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
return NextResponse.json( return NextResponse.json(

View File

@@ -1,6 +1,6 @@
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -12,7 +12,7 @@ const SlackDeleteMessageSchema = z.object({
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
return NextResponse.json( return NextResponse.json(

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { openDMChannel } from '../utils' import { openDMChannel } from '../utils'
@@ -31,7 +31,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Slack read messages attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Slack read messages attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { sendSlackMessage } from '../utils' import { sendSlackMessage } from '../utils'
@@ -26,7 +26,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Slack send attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Slack send attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
@@ -19,7 +19,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized Slack update message attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized Slack update message attempt: ${authResult.error}`)

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { env } from '@/lib/core/config/env' import { env } from '@/lib/core/config/env'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { type SMSOptions, sendSMS } from '@/lib/messaging/sms/service' import { type SMSOptions, sendSMS } from '@/lib/messaging/sms/service'
@@ -19,7 +19,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized SMS send attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized SMS send attempt: ${authResult.error}`)

View File

@@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import nodemailer from 'nodemailer' import nodemailer from 'nodemailer'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid' import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request' import { generateRequestId } from '@/lib/core/utils/request'
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
@@ -35,7 +35,7 @@ export async function POST(request: NextRequest) {
const requestId = generateRequestId() const requestId = generateRequestId()
try { try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success) { if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized SMTP send attempt: ${authResult.error}`) logger.warn(`[${requestId}] Unauthorized SMTP send attempt: ${authResult.error}`)

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createSSHConnection, escapeShellArg, executeSSHCommand } from '@/app/api/tools/ssh/utils' import { createSSHConnection, escapeShellArg, executeSSHCommand } from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHCheckCommandExistsAPI') const logger = createLogger('SSHCheckCommandExistsAPI')
@@ -21,12 +20,6 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized SSH check command exists attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = CheckCommandExistsSchema.parse(body) const params = CheckCommandExistsSchema.parse(body)

View File

@@ -3,7 +3,6 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import type { Client, SFTPWrapper, Stats } from 'ssh2' import type { Client, SFTPWrapper, Stats } from 'ssh2'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { import {
createSSHConnection, createSSHConnection,
getFileType, getFileType,
@@ -40,15 +39,10 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized SSH check file exists attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = CheckFileExistsSchema.parse(body) const params = CheckFileExistsSchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) { if (!params.password && !params.privateKey) {
return NextResponse.json( return NextResponse.json(
{ error: 'Either password or privateKey must be provided' }, { error: 'Either password or privateKey must be provided' },

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { import {
createSSHConnection, createSSHConnection,
escapeShellArg, escapeShellArg,
@@ -28,15 +27,10 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized SSH create directory attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = CreateDirectorySchema.parse(body) const params = CreateDirectorySchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) { if (!params.password && !params.privateKey) {
return NextResponse.json( return NextResponse.json(
{ error: 'Either password or privateKey must be provided' }, { error: 'Either password or privateKey must be provided' },
@@ -59,6 +53,7 @@ export async function POST(request: NextRequest) {
const dirPath = sanitizePath(params.path) const dirPath = sanitizePath(params.path)
const escapedPath = escapeShellArg(dirPath) const escapedPath = escapeShellArg(dirPath)
// Check if directory already exists
const checkResult = await executeSSHCommand( const checkResult = await executeSSHCommand(
client, client,
`test -d '${escapedPath}' && echo "exists"` `test -d '${escapedPath}' && echo "exists"`
@@ -75,6 +70,7 @@ export async function POST(request: NextRequest) {
}) })
} }
// Create directory
const mkdirFlag = params.recursive ? '-p' : '' const mkdirFlag = params.recursive ? '-p' : ''
const command = `mkdir ${mkdirFlag} -m ${params.permissions} '${escapedPath}'` const command = `mkdir ${mkdirFlag} -m ${params.permissions} '${escapedPath}'`
const result = await executeSSHCommand(client, command) const result = await executeSSHCommand(client, command)

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { import {
createSSHConnection, createSSHConnection,
escapeShellArg, escapeShellArg,
@@ -28,15 +27,10 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized SSH delete file attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = DeleteFileSchema.parse(body) const params = DeleteFileSchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) { if (!params.password && !params.privateKey) {
return NextResponse.json( return NextResponse.json(
{ error: 'Either password or privateKey must be provided' }, { error: 'Either password or privateKey must be provided' },
@@ -59,6 +53,7 @@ export async function POST(request: NextRequest) {
const filePath = sanitizePath(params.path) const filePath = sanitizePath(params.path)
const escapedPath = escapeShellArg(filePath) const escapedPath = escapeShellArg(filePath)
// Check if path exists
const checkResult = await executeSSHCommand( const checkResult = await executeSSHCommand(
client, client,
`test -e '${escapedPath}' && echo "exists"` `test -e '${escapedPath}' && echo "exists"`
@@ -67,6 +62,7 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: `Path does not exist: ${filePath}` }, { status: 404 }) return NextResponse.json({ error: `Path does not exist: ${filePath}` }, { status: 404 })
} }
// Build delete command
let command: string let command: string
if (params.recursive) { if (params.recursive) {
command = params.force ? `rm -rf '${escapedPath}'` : `rm -r '${escapedPath}'` command = params.force ? `rm -rf '${escapedPath}'` : `rm -r '${escapedPath}'`

View File

@@ -4,7 +4,6 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import type { Client, SFTPWrapper } from 'ssh2' import type { Client, SFTPWrapper } from 'ssh2'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils' import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHDownloadFileAPI') const logger = createLogger('SSHDownloadFileAPI')
@@ -35,15 +34,10 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized SSH download file attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = DownloadFileSchema.parse(body) const params = DownloadFileSchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) { if (!params.password && !params.privateKey) {
return NextResponse.json( return NextResponse.json(
{ error: 'Either password or privateKey must be provided' }, { error: 'Either password or privateKey must be provided' },

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createSSHConnection, executeSSHCommand, sanitizeCommand } from '@/app/api/tools/ssh/utils' import { createSSHConnection, executeSSHCommand, sanitizeCommand } from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHExecuteCommandAPI') const logger = createLogger('SSHExecuteCommandAPI')
@@ -22,15 +21,10 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized SSH execute command attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = ExecuteCommandSchema.parse(body) const params = ExecuteCommandSchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) { if (!params.password && !params.privateKey) {
return NextResponse.json( return NextResponse.json(
{ error: 'Either password or privateKey must be provided' }, { error: 'Either password or privateKey must be provided' },
@@ -50,6 +44,7 @@ export async function POST(request: NextRequest) {
}) })
try { try {
// Build command with optional working directory
let command = sanitizeCommand(params.command) let command = sanitizeCommand(params.command)
if (params.workingDirectory) { if (params.workingDirectory) {
command = `cd "${params.workingDirectory}" && ${command}` command = `cd "${params.workingDirectory}" && ${command}`

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createSSHConnection, escapeShellArg, executeSSHCommand } from '@/app/api/tools/ssh/utils' import { createSSHConnection, escapeShellArg, executeSSHCommand } from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHExecuteScriptAPI') const logger = createLogger('SSHExecuteScriptAPI')
@@ -23,15 +22,10 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized SSH execute script attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = ExecuteScriptSchema.parse(body) const params = ExecuteScriptSchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) { if (!params.password && !params.privateKey) {
return NextResponse.json( return NextResponse.json(
{ error: 'Either password or privateKey must be provided' }, { error: 'Either password or privateKey must be provided' },
@@ -51,10 +45,13 @@ export async function POST(request: NextRequest) {
}) })
try { try {
// Create a temporary script file, execute it, and clean up
const scriptPath = `/tmp/sim_script_${requestId}.sh` const scriptPath = `/tmp/sim_script_${requestId}.sh`
const escapedScriptPath = escapeShellArg(scriptPath) const escapedScriptPath = escapeShellArg(scriptPath)
const escapedInterpreter = escapeShellArg(params.interpreter) const escapedInterpreter = escapeShellArg(params.interpreter)
// Build the command to create, execute, and clean up the script
// Note: heredoc with quoted delimiter ('SIMEOF') prevents variable expansion
let command = `cat > '${escapedScriptPath}' << 'SIMEOF' let command = `cat > '${escapedScriptPath}' << 'SIMEOF'
${params.script} ${params.script}
SIMEOF SIMEOF

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createSSHConnection, executeSSHCommand } from '@/app/api/tools/ssh/utils' import { createSSHConnection, executeSSHCommand } from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHGetSystemInfoAPI') const logger = createLogger('SSHGetSystemInfoAPI')
@@ -20,15 +19,10 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized SSH get system info attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = GetSystemInfoSchema.parse(body) const params = GetSystemInfoSchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) { if (!params.password && !params.privateKey) {
return NextResponse.json( return NextResponse.json(
{ error: 'Either password or privateKey must be provided' }, { error: 'Either password or privateKey must be provided' },

View File

@@ -3,7 +3,6 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import type { Client, FileEntry, SFTPWrapper } from 'ssh2' import type { Client, FileEntry, SFTPWrapper } from 'ssh2'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { import {
createSSHConnection, createSSHConnection,
getFileType, getFileType,
@@ -61,15 +60,10 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized SSH list directory attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = ListDirectorySchema.parse(body) const params = ListDirectorySchema.parse(body)
// Validate authentication
if (!params.password && !params.privateKey) { if (!params.password && !params.privateKey) {
return NextResponse.json( return NextResponse.json(
{ error: 'Either password or privateKey must be provided' }, { error: 'Either password or privateKey must be provided' },

View File

@@ -2,7 +2,6 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger' import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { import {
createSSHConnection, createSSHConnection,
escapeShellArg, escapeShellArg,
@@ -28,16 +27,9 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized SSH move/rename attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = MoveRenameSchema.parse(body) const params = MoveRenameSchema.parse(body)
// Validate SSH authentication
if (!params.password && !params.privateKey) { if (!params.password && !params.privateKey) {
return NextResponse.json( return NextResponse.json(
{ error: 'Either password or privateKey must be provided' }, { error: 'Either password or privateKey must be provided' },

View File

@@ -3,7 +3,6 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import type { Client, SFTPWrapper } from 'ssh2' import type { Client, SFTPWrapper } from 'ssh2'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils' import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHReadFileContentAPI') const logger = createLogger('SSHReadFileContentAPI')
@@ -36,12 +35,6 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized SSH read file content attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = ReadFileContentSchema.parse(body) const params = ReadFileContentSchema.parse(body)

View File

@@ -3,7 +3,6 @@ import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server' import { type NextRequest, NextResponse } from 'next/server'
import type { Client, SFTPWrapper } from 'ssh2' import type { Client, SFTPWrapper } from 'ssh2'
import { z } from 'zod' import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils' import { createSSHConnection, sanitizePath } from '@/app/api/tools/ssh/utils'
const logger = createLogger('SSHUploadFileAPI') const logger = createLogger('SSHUploadFileAPI')
@@ -38,12 +37,6 @@ export async function POST(request: NextRequest) {
const requestId = randomUUID().slice(0, 8) const requestId = randomUUID().slice(0, 8)
try { try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
logger.warn(`[${requestId}] Unauthorized SSH upload file attempt`)
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json() const body = await request.json()
const params = UploadFileSchema.parse(body) const params = UploadFileSchema.parse(body)

Some files were not shown because too many files have changed in this diff Show More