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
6 changes: 5 additions & 1 deletion apps/docs/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9523,7 +9523,7 @@ export function NewRelicIcon(props: SVGProps<SVGSVGElement>) {
)
}

export function NetSuiteIcon(props: SVGProps<SVGSVGElement>) {
export function OracleIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 93.9 59.4' xmlns='http://www.w3.org/2000/svg'>
<path
Expand All @@ -9534,6 +9534,10 @@ export function NetSuiteIcon(props: SVGProps<SVGSVGElement>) {
)
}

export function NetSuiteIcon(props: SVGProps<SVGSVGElement>) {
return <OracleIcon {...props} />
}

export function WizaIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 51 49' fill='none' xmlns='http://www.w3.org/2000/svg'>
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/components/ui/icon-mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ import {
OktaIcon,
OnePasswordIcon,
OpenAIIcon,
OracleIcon,
OtterIcon,
OutlookIcon,
PackageSearchIcon,
Expand Down Expand Up @@ -547,6 +548,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
notion: NotionIcon,
notion_v2: NotionIcon,
obsidian: ObsidianIcon,
oci: OracleIcon,
okta: OktaIcon,
onedrive: MicrosoftOneDriveIcon,
onepassword: OnePasswordIcon,
Expand Down
5 changes: 5 additions & 0 deletions apps/docs/content/docs/cli/credentials.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@ Update Credential (OAuth login or personal API key required)
| `--auth-method <value>` | No | Provider authentication method. |
| `--private-key <value>` | No | Write-only PEM private key. |
| `--username <value>` | No | Provider run-as username. |
| `--tenancy-ocid <value>` | No | OCI tenancy OCID. |
| `--user-ocid <value>` | No | OCI user OCID. |
| `--fingerprint <value>` | No | OCI API-key fingerprint. |
| `--private-key-passphrase <value>` | No | Write-only OCI private-key passphrase. |
| `--region <value>` | No | OCI home region. |
| `--name <displayName>` | No | Alias for --display-name. |

</CommandTable>
Expand Down
5 changes: 5 additions & 0 deletions apps/docs/content/docs/cli/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,11 @@ sim credentials update <credentialId> [options]
| `--auth-method <value>` | No | Provider authentication method. |
| `--private-key <value>` | No | Write-only PEM private key. |
| `--username <value>` | No | Provider run-as username. |
| `--tenancy-ocid <value>` | No | OCI tenancy OCID. |
| `--user-ocid <value>` | No | OCI user OCID. |
| `--fingerprint <value>` | No | OCI API-key fingerprint. |
| `--private-key-passphrase <value>` | No | Write-only OCI private-key passphrase. |
| `--region <value>` | No | OCI home region. |
| `--name <displayName>` | No | Alias for --display-name. |

</CommandTable>
Expand Down
30 changes: 30 additions & 0 deletions apps/docs/openapi-v2-resources.json
Original file line number Diff line number Diff line change
Expand Up @@ -15339,6 +15339,36 @@
"type": "string",
"minLength": 1,
"maxLength": 255
},
"tenancyOcid": {
"description": "OCI tenancy OCID.",
"type": "string",
"minLength": 1,
"maxLength": 255
},
"userOcid": {
"description": "OCI user OCID.",
"type": "string",
"minLength": 1,
"maxLength": 255
},
"fingerprint": {
"description": "OCI API-key fingerprint.",
"type": "string",
"minLength": 1,
"maxLength": 128
},
"privateKeyPassphrase": {
"description": "Write-only OCI private-key passphrase.",
"writeOnly": true,
"type": "string",
"maxLength": 4096
},
"region": {
"description": "OCI home region.",
"type": "string",
"minLength": 1,
"maxLength": 128
}
},
"additionalProperties": false,
Expand Down
64 changes: 64 additions & 0 deletions apps/sim/app/api/credentials/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,4 +345,68 @@ describe('POST /api/credentials', () => {
expect(dbChainMockFns.insert).not.toHaveBeenCalled()
})
})

it('forwards OCI API-key fields without returning secret material', async () => {
mockVerifyAndBuildServiceAccountSecret.mockResolvedValueOnce({
providerId: 'oci-api-key-service-account',
encryptedServiceAccountKey: 'encrypted-oci-blob',
displayName: 'ocid1.user.oc1..principal',
auditMetadata: {
principalKind: 'user',
principalId: 'ocid1.user.oc1..principal',
},
principal: { kind: 'user', id: 'ocid1.user.oc1..principal' },
})
queueTableRows(credential, [])
queueTableRows(credential, [])
queueTableRows(credential, [
{
id: 'credential-oci',
workspaceId: WORKSPACE_ID,
type: 'service_account',
displayName: 'ocid1.user.oc1..principal',
description: null,
unredacted: false,
providerId: 'oci-api-key-service-account',
accountId: null,
envKey: null,
envOwnerUserId: null,
encryptedServiceAccountKey: 'encrypted-oci-blob',
createdBy: 'user-1',
createdAt: new Date('2026-08-11T00:00:00.000Z'),
updatedAt: new Date('2026-08-11T00:00:00.000Z'),
},
])

const response = await POST(
createMockRequest('POST', {
workspaceId: WORKSPACE_ID,
type: 'service_account',
providerId: 'oci-api-key-service-account',
tenancyOcid: 'ocid1.tenancy.oc1..tenant',
userOcid: 'ocid1.user.oc1..principal',
fingerprint: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff',
privateKey: '-----BEGIN PRIVATE KEY-----\nkey\n-----END PRIVATE KEY-----',
privateKeyPassphrase: ' exact passphrase ',
region: 'us-ashburn-1',
})
)
const body = await response.text()

expect(response.status).toBe(201)
expect(mockVerifyAndBuildServiceAccountSecret).toHaveBeenCalledWith(
'oci-api-key-service-account',
expect.objectContaining({
tenancyOcid: 'ocid1.tenancy.oc1..tenant',
userOcid: 'ocid1.user.oc1..principal',
fingerprint: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff',
privateKey: '-----BEGIN PRIVATE KEY-----\nkey\n-----END PRIVATE KEY-----',
privateKeyPassphrase: ' exact passphrase ',
region: 'us-ashburn-1',
})
)
expect(body).not.toContain('PRIVATE KEY')
expect(body).not.toContain('exact passphrase')
expect(body).not.toContain('encrypted-oci-blob')
})
})
27 changes: 27 additions & 0 deletions apps/sim/app/api/v2/credentials/[credentialId]/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,33 @@ describe('PATCH /api/v2/credentials/[credentialId]', () => {
expect(body).not.toContain('MUST_NOT_LEAK_CIPHERTEXT')
})

it('forwards a complete OCI rotation tuple with an omitted replacement passphrase', async () => {
const request = patchRequest({
tenancyOcid: 'ocid1.tenancy.oc1..tenant',
userOcid: 'ocid1.user.oc1..replacement',
fingerprint: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff',
privateKey: '-----BEGIN PRIVATE KEY-----\nreplacement\n-----END PRIVATE KEY-----',
region: 'us-ashburn-1',
})
const response = await PATCH(request, context)

expect(response.status).toBe(200)
expect(mocks.update).toHaveBeenCalledWith({
principal: auth.principal,
input: {
tenancyOcid: 'ocid1.tenancy.oc1..tenant',
userOcid: 'ocid1.user.oc1..replacement',
fingerprint: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff',
privateKey: '-----BEGIN PRIVATE KEY-----\nreplacement\n-----END PRIVATE KEY-----',
region: 'us-ashburn-1',
credentialId: CREDENTIAL_ID,
assertedWorkspaceId: WORKSPACE_ID,
},
request,
})
expect(JSON.stringify(await response.json())).not.toContain('PRIVATE KEY')
})

it('clears a description with an explicit null and leaves an omitted field alone', async () => {
await PATCH(patchRequest({ description: null }), context)

Expand Down
44 changes: 44 additions & 0 deletions apps/sim/app/api/v2/credentials/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,55 @@ describe('POST /api/v2/credentials', () => {
authMethod: undefined,
privateKey: undefined,
username: undefined,
tenancyOcid: undefined,
userOcid: undefined,
fingerprint: undefined,
privateKeyPassphrase: undefined,
region: undefined,
},
request,
})
})

it('forwards OCI credential fields from the write-only credentials envelope', async () => {
const request = new NextRequest('http://localhost:3000/api/v2/credentials', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
workspaceId: WORKSPACE_ID,
type: 'service_account',
providerId: 'oci-api-key-service-account',
credentials: JSON.stringify({
tenancyOcid: 'ocid1.tenancy.oc1..tenant',
userOcid: 'ocid1.user.oc1..user',
fingerprint: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff',
privateKey: '-----BEGIN PRIVATE KEY-----\nkey\n-----END PRIVATE KEY-----',
privateKeyPassphrase: ' exact passphrase ',
region: 'us-ashburn-1',
}),
}),
})
const response = await POST(request)
const body = await response.text()

expect(response.status).toBe(201)
expect(mocks.create).toHaveBeenCalledWith({
principal: { kind: 'personal_api_key', userId: 'user-1', keyId: 'key-1' },
input: expect.objectContaining({
providerId: 'oci-api-key-service-account',
tenancyOcid: 'ocid1.tenancy.oc1..tenant',
userOcid: 'ocid1.user.oc1..user',
fingerprint: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff',
privateKey: '-----BEGIN PRIVATE KEY-----\nkey\n-----END PRIVATE KEY-----',
privateKeyPassphrase: ' exact passphrase ',
region: 'us-ashburn-1',
}),
request,
})
expect(body).not.toContain('PRIVATE KEY')
expect(body).not.toContain('exact passphrase')
})

/**
* `credentials create slack-custom-bot` used to fail twice over: discovery
* demanded a client-generated id, and a caller who then supplied a slug was
Expand Down
Loading
Loading