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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions apps/sim/app/api/tools/sap_concur/shared.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { createHash } from 'node:crypto'
import { createHmac } from 'node:crypto'
import { createLogger } from '@sim/logger'
import { isPrivateIpHost } from '@sim/security/ssrf'
import { getErrorMessage } from '@sim/utils/errors'
import { truncate } from '@sim/utils/string'
import { z } from 'zod'
import { coalesceLocally } from '@/lib/concurrency/singleflight'
import { env } from '@/lib/core/config/env'
import {
MAX_JSON_API_RESPONSE_BYTES,
secureFetchWithValidation,
Expand Down Expand Up @@ -226,11 +227,17 @@ function readCachedToken(key: string): SapConcurToken | undefined {
*
* The whole tuple is JSON-encoded before hashing rather than concatenated with a
* separator, so a free-form field (clientId, companyUuid) cannot span a field boundary
* and collide with a different tuple. The full sha256 digest is kept — truncating it
* would lower the collision/forgery bar for no measurable gain.
* and collide with a different tuple.
*
* Keyed with a server-side secret rather than a bare digest. The inputs include a
* user-chosen password, which is low-entropy enough to brute-force from a plain SHA-256
* if a key ever reached a heap dump or a debug log; an HMAC makes the key useless without
* the secret. A password-hashing KDF would be the wrong tool — this runs on every token
* fetch and the goal is collision-free partitioning, not verification of a stored
* credential.
*/
function tokenCacheKey(req: SapConcurAuth): string {
return createHash('sha256')
return createHmac('sha256', env.INTERNAL_API_SECRET)
.update(
JSON.stringify([
req.datacenter,
Expand Down
23 changes: 22 additions & 1 deletion apps/sim/blocks/blocks/sap_concur.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,18 @@ Return ONLY the YYYY-MM-DD date - no explanations, no extra text.`,
condition: { field: 'operation', value: 'sap_concur_create_report_comment' },
required: { field: 'operation', value: 'sap_concur_create_report_comment' },
},
{
id: 'sendbackComment',
title: 'Sendback Comment',
type: 'long-input',
placeholder: 'Visible wherever Request comments are shown',
condition: {
field: 'operation',
value: 'sap_concur_move_travel_request',
and: { field: 'action', value: 'sendback' },
},
mode: 'advanced',
},
Comment thread
waleedlatif1 marked this conversation as resolved.
{
id: 'includeAllComments',
title: 'Include All Comments',
Expand Down Expand Up @@ -1893,7 +1905,7 @@ Return ONLY the comma-separated travel config IDs - no explanations, no extra te
enabled: true,
prompt: `Generate the JSON request body for the selected SAP Concur operation from the user's request.

Match the payload to the resource being written. Every family below is camelCase.
Match the payload to the resource being written. Every family below is camelCase EXCEPT exchange rates, which is snake_case.

Expense reports (v4): name, businessPurpose, comment, policyId, countryCode, countrySubDivisionCode, reportDate, startDate, endDate, and reportSource — reportSource is REQUIRED when updating a report and must be one of EA, MOB, OTHER, SE, TR, UI.

Expand All @@ -1905,6 +1917,8 @@ SCIM users (Identity v4.1): create and update payloads use schemas, userName, na

List items: listId, level, value, shortCode. Cash advances: amountRequested as { currency, amount }, name and userId (all required), plus optional accountCode, comment and purpose.

Exchange rates are the one snake_case family: currency_sets as an array of up to 100 entries, each { from_crn_code, to_crn_code, start_date as YYYY-MM-DD, rate }.

Omit fields the user did not describe rather than inventing identifiers.

Return ONLY the JSON object - no explanations, no extra text.`,
Expand Down Expand Up @@ -2262,6 +2276,8 @@ Return ONLY the JSON object - no explanations, no extra text.`,
body: params.body || undefined,
userId: params.travelRequestUserId || undefined,
companyID: params.companyID || undefined,
comment:
params.action === 'sendback' ? params.sendbackComment || undefined : undefined,
}
case 'sap_concur_list_travel_request_comments':
return { ...auth, requestUuid: params.requestUuid }
Expand Down Expand Up @@ -2532,6 +2548,11 @@ Return ONLY the JSON object - no explanations, no extra text.`,
description:
'Optional company identifier for a travel request workflow action (documented as companyID, distinct from companyUuid)',
},
sendbackComment: {
type: 'string',
description:
'Optional comment on a travel request workflow action — Concur applies it only to the sendback action, and it is visible wherever Request comments are shown',
},
travelRequestApprovedBefore: { type: 'string', description: 'Travel requests approved before' },
travelRequestApprovedAfter: { type: 'string', description: 'Travel requests approved after' },
travelRequestModifiedBefore: { type: 'string', description: 'Travel requests modified before' },
Expand Down
Loading