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
18 changes: 9 additions & 9 deletions apps/docs/content/docs/integrations/okta.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ Permanently delete a user from your Okta organization. Can only be performed on
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Okta API token for authentication |
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
| `userId` | string | Yes | User ID to delete |
| `userId` | string | Yes | User ID or login \(email\) to delete |
Comment thread
waleedlatif1 marked this conversation as resolved.
| `sendEmail` | boolean | No | Send deactivation email to admin \(default: false\) |

#### Output
Expand Down Expand Up @@ -690,7 +690,7 @@ List the MFA factors a user has enrolled, with each factor type, provider, and e
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Okta API token for authentication |
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
| `userId` | string | Yes | User ID or login to list enrolled factors for |
| `userId` | string | Yes | Okta user ID \(not a login or email\) to list enrolled factors for |

#### Output

Expand Down Expand Up @@ -718,7 +718,7 @@ Retrieve a single enrolled MFA factor for a user, including its type, provider,
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Okta API token for authentication |
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
| `userId` | string | Yes | User ID or login the factor belongs to |
| `userId` | string | Yes | Okta user ID \(not a login or email\) the factor belongs to |
| `factorId` | string | Yes | Factor ID to look up |

#### Output
Expand All @@ -745,7 +745,7 @@ Enroll an MFA factor for a user. The profile fields required depend on the facto
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Okta API token for authentication |
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
| `userId` | string | Yes | User ID or login to enroll the factor for |
| `userId` | string | Yes | Okta user ID \(not a login or email\) to enroll the factor for |
| `factorType` | string | Yes | Factor type to enroll \(sms, call, email, question, push, token:software:totp, u2f, webauthn\) |
| `provider` | string | Yes | Factor provider \(OKTA, GOOGLE, FIDO, DUO, RSA, SYMANTEC, YUBICO, CUSTOM\). Each provider supports a subset of factor types |
| `phoneNumber` | string | No | Phone number in E.164 format. Required for the sms and call factor types |
Expand Down Expand Up @@ -779,7 +779,7 @@ Unenroll one specific MFA factor for a user so they can re-enroll it. Destructiv
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Okta API token for authentication |
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
| `userId` | string | Yes | User ID or login the factor belongs to |
| `userId` | string | Yes | Okta user ID \(not a login or email\) the factor belongs to |
| `factorId` | string | Yes | Factor ID to unenroll |
| `removeRecoveryEnrollment` | boolean | No | Also remove the phone number as a recovery method, not only as a factor. Applies to sms and call factors only \(default: false\) |

Expand Down Expand Up @@ -822,7 +822,7 @@ Revoke every active Okta session for a user, signing them out of all devices imm
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Okta API token for authentication |
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
| `userId` | string | Yes | User ID or login whose sessions will be revoked |
| `userId` | string | Yes | Okta user ID \(not a login or email\) whose sessions will be revoked |
| `oauthTokens` | boolean | No | Also revoke the user OpenID Connect and OAuth refresh and access tokens \(default: false\) |
| `forgetDevices` | boolean | No | Clear the user remembered factors for all devices \(default: true\) |

Expand Down Expand Up @@ -1136,7 +1136,7 @@ List the administrator roles assigned to a user. Returns both standard roles and
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Okta API token for authentication |
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
| `userId` | string | Yes | User ID or login to list admin roles for |
| `userId` | string | Yes | Okta user ID \(not a login or email\) to list admin roles for |

#### Output

Expand Down Expand Up @@ -1165,7 +1165,7 @@ Grant a user an administrator role. Use a standard role type such as USER_ADMIN
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Okta API token for authentication |
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
| `userId` | string | Yes | User ID or login to assign the admin role to |
| `userId` | string | Yes | Okta user ID \(not a login or email\) to assign the admin role to |
| `roleType` | string | Yes | Role type to assign: SUPER_ADMIN, ORG_ADMIN, APP_ADMIN, USER_ADMIN, HELP_DESK_ADMIN, READ_ONLY_ADMIN, API_ACCESS_MANAGEMENT_ADMIN, GROUP_MEMBERSHIP_ADMIN, REPORT_ADMIN, WORKFLOWS_ADMIN, ACCESS_CERTIFICATIONS_ADMIN, ACCESS_REQUESTS_ADMIN, or CUSTOM |
| `customRoleId` | string | No | Custom role ID. Required when the role type is CUSTOM |
| `resourceSetId` | string | No | Resource set ID the custom role applies to. Required when the role type is CUSTOM |
Expand Down Expand Up @@ -1197,7 +1197,7 @@ Revoke an administrator role from a user. Destructive: the user immediately lose
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Okta API token for authentication |
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
| `userId` | string | Yes | User ID or login to revoke the admin role from |
| `userId` | string | Yes | Okta user ID \(not a login or email\) to revoke the admin role from |
| `roleAssignmentId` | string | Yes | Role assignment ID to revoke, as returned by List User Roles. For a custom role this is the resource set binding ID |

#### Output
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/tools/generated/tool-metadata.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/sim/tools/okta/assign_user_role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const oktaAssignUserRoleTool: ToolConfig<
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'User ID or login to assign the admin role to',
description: 'Okta user ID (not a login or email) to assign the admin role to',
},
roleType: {
type: 'string',
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/tools/okta/clear_user_sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const oktaClearUserSessionsTool: ToolConfig<
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'User ID or login whose sessions will be revoked',
description: 'Okta user ID (not a login or email) whose sessions will be revoked',
},
oauthTokens: {
type: 'boolean',
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/tools/okta/delete_user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const oktaDeleteUserTool: ToolConfig<OktaDeleteUserParams, OktaDeleteUser
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'User ID to delete',
description: 'User ID or login (email) to delete',
},
sendEmail: {
type: 'boolean',
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/tools/okta/enroll_factor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const oktaEnrollFactorTool: ToolConfig<OktaEnrollFactorParams, OktaEnroll
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'User ID or login to enroll the factor for',
description: 'Okta user ID (not a login or email) to enroll the factor for',
},
factorType: {
type: 'string',
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/tools/okta/get_factor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const oktaGetFactorTool: ToolConfig<OktaGetFactorParams, OktaGetFactorRes
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'User ID or login the factor belongs to',
description: 'Okta user ID (not a login or email) the factor belongs to',
},
factorId: {
type: 'string',
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/tools/okta/list_factors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const oktaListFactorsTool: ToolConfig<OktaListFactorsParams, OktaListFact
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'User ID or login to list enrolled factors for',
description: 'Okta user ID (not a login or email) to list enrolled factors for',
},
},

Expand Down
2 changes: 1 addition & 1 deletion apps/sim/tools/okta/list_user_roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const oktaListUserRolesTool: ToolConfig<OktaListUserRolesParams, OktaList
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'User ID or login to list admin roles for',
description: 'Okta user ID (not a login or email) to list admin roles for',
},
},

Expand Down
2 changes: 1 addition & 1 deletion apps/sim/tools/okta/remove_user_role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const oktaRemoveUserRoleTool: ToolConfig<
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'User ID or login to revoke the admin role from',
description: 'Okta user ID (not a login or email) to revoke the admin role from',
},
roleAssignmentId: {
type: 'string',
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/tools/okta/reset_factor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const oktaResetFactorTool: ToolConfig<OktaResetFactorParams, OktaResetFac
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'User ID or login the factor belongs to',
description: 'Okta user ID (not a login or email) the factor belongs to',
},
factorId: {
type: 'string',
Expand Down
112 changes: 112 additions & 0 deletions apps/sim/tools/okta/user_path_descriptions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* @vitest-environment node
*/
import { describe, expect, it, vi } from 'vitest'
import { tools as toolRegistry } from '@/tools/registry'
import type { ToolConfig } from '@/tools/types'

/**
* Uses the real tool registry: these assertions are about registered Okta tool
* params, which the global `@/tools/registry` mock in vitest.setup.ts empties.
*/
vi.unmock('@/tools/registry')

/**
* Okta's Management API spec (`okta/okta-management-openapi-spec`,
* `dist/2026.08.1/management-oneOfInheritance-noExamples.yaml`) distinguishes
* two user path parameters:
*
* - `pathId` — "An ID, login, or login shortname (as long as the shortname is
* unambiguous) of an existing Okta user". Used by `/api/v1/users/{id}` and
* every `/api/v1/users/{id}/lifecycle/*` operation.
* - `pathUserId` / `pathAppUserId` — "ID of an existing Okta user". Used by
* `/api/v1/users/{userId}/factors`, `/roles`, `/sessions`, and by the
* group- and app-membership paths.
*
* The `userId` param on every Okta tool is `user-or-llm`, so its description is
* the only thing a model reads before choosing what to pass. Advertising a
* login on a `pathUserId` endpoint produces a 404; withholding it on a `pathId`
* endpoint makes the model resolve an ID it never needed.
*/
const USER_SENTINEL = 'SIM-USER-SENTINEL'

/** The `{id}` positions Okta documents as ID-, login-, or shortname-addressable. */
const LOGIN_CAPABLE_PATH = new RegExp(`^/api/v1/users/${USER_SENTINEL}(?:/lifecycle/[^/]+)?/?$`)

const AUTH_PARAMS: Record<string, unknown> = {
apiKey: 'token',
domain: 'dev-123456.okta.com',
}

interface OktaUserTool {
id: string
description: string
pathname: string
}

/** Fills every declared param so a declarative `url` builder can run. */
function sentinelParams(tool: ToolConfig): Record<string, unknown> {
const params: Record<string, unknown> = { ...AUTH_PARAMS }
for (const [name, schema] of Object.entries(tool.params ?? {})) {
if (name in params) continue
if (name === 'userId') {
params[name] = USER_SENTINEL
continue
}
params[name] = schema.type === 'number' ? 1 : schema.type === 'boolean' ? false : `sim-${name}`
}
return params
}

/**
* Calls a declarative `url` builder with the untyped shape a tool really
* receives — the typed params interface is erased at the call boundary.
*/
function builtUrl(tool: ToolConfig): string {
const build = tool.request?.url
if (typeof build !== 'function') throw new Error(`${tool.id} has no url builder`)
return build(sentinelParams(tool) as never)
}

const oktaUserTools: OktaUserTool[] = Object.values(toolRegistry)
.filter((tool): tool is ToolConfig => Boolean(tool?.id?.startsWith('okta_')))
.filter((tool) => Boolean(tool.params?.userId))
.map((tool) => ({
id: tool.id,
description: tool.params.userId.description ?? '',
pathname: new URL(builtUrl(tool)).pathname,
}))

/**
* A description advertises a login when it offers a login or an email as an
* accepted value. A negated clause ("not a login or email") withholds one, so
* it is stripped before the check — otherwise the warning an ID-only tool
* carries would read as the promise it exists to deny.
*/
function advertisesLogin(description: string): boolean {
const affirmative = description.replace(/\bnot an? [^.)]*/gi, '')
return /\blogins?\b|\bemail\b/i.test(affirmative)
}

describe('okta user path param descriptions', () => {
it('finds Okta tools carrying a userId param', () => {
expect(oktaUserTools.length).toBeGreaterThan(15)
})

it('covers both Okta path-parameter kinds', () => {
const loginCapable = oktaUserTools.filter((tool) => LOGIN_CAPABLE_PATH.test(tool.pathname))
expect(loginCapable.length).toBeGreaterThan(0)
expect(oktaUserTools.length - loginCapable.length).toBeGreaterThan(0)
})

it.each(oktaUserTools.map((tool) => [tool.id, tool] as const))(
'%s describes userId the way its endpoint accepts it',
(_id, tool) => {
const loginCapable = LOGIN_CAPABLE_PATH.test(tool.pathname)
expect({
pathname: tool.pathname,
advertisesLogin: advertisesLogin(tool.description),
}).toEqual({ pathname: tool.pathname, advertisesLogin: loginCapable })
}
)
})
Loading