mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-21 13:00:04 +08:00
feat(okta): add System Log, MFA, sessions, apps, roles, and group rules (#6741)
Expands the Okta block from 18 to 44 operations, covering the System Log, MFA factors, sessions, applications, administrator roles, and group rules. Adds shared helpers for the SSWS auth header, Okta error parsing, and the Link-header `after` cursor, and routes every tool through them so there is one auth and error path. All eight list operations now return `nextCursor` and `hasMore`. Makes the block's param transform authoritative over the serialized inputs: the executor merges it on top of them, so a key the transform omits keeps the raw subBlock string. Assigning `undefined` is what actually drops it, which is what keeps a non-numeric `limit` from reaching Okta verbatim and stops a blank field in a partial `update_user` from overwriting the stored value with an empty string.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Okta
|
||||
description: Manage users and groups in Okta
|
||||
description: Manage users, groups, apps, and MFA in Okta
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
@@ -32,7 +32,7 @@ If you encounter issues with the Okta integration, contact us at [help@sim.ai](m
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Okta identity management into your workflow. List, create, update, activate, suspend, and delete users. Reset passwords. Manage groups and group membership.
|
||||
Integrate Okta identity management into your workflow. Manage users, groups, and group rules. Run service desk actions like resetting MFA factors and clearing sessions. Review and change application assignments and admin roles. Query the System Log to audit sign-ins and admin changes.
|
||||
|
||||
|
||||
|
||||
@@ -50,7 +50,8 @@ List all users in your Okta organization with optional search and filtering
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `search` | string | No | Okta search expression \(e.g., profile.firstName eq "John" or profile.email co "example.com"\) |
|
||||
| `filter` | string | No | Okta filter expression \(e.g., status eq "ACTIVE"\) |
|
||||
| `limit` | number | No | Maximum number of users to return \(default: 200, max: 200\) |
|
||||
| `after` | string | No | Opaque pagination cursor returned as nextCursor by a previous call |
|
||||
| `limit` | number | No | Maximum number of users to return per page \(default: 200\) |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -72,6 +73,8 @@ List all users in your Okta organization with optional search and filtering
|
||||
| ↳ `activated` | string | Activation timestamp |
|
||||
| ↳ `statusChanged` | string | Status change timestamp |
|
||||
| `count` | number | Number of users returned |
|
||||
| `nextCursor` | string | Cursor for the next page, or null on the last page |
|
||||
| `hasMore` | boolean | Whether more users are available |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Get User from Okta
|
||||
@@ -320,7 +323,8 @@ List all groups in your Okta organization with optional search and filtering
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `search` | string | No | Okta search expression for groups \(e.g., profile.name sw "Engineering" or type eq "OKTA_GROUP"\) |
|
||||
| `filter` | string | No | Okta filter expression \(e.g., type eq "OKTA_GROUP"\) |
|
||||
| `limit` | number | No | Maximum number of groups to return \(default: 10000, max: 10000\) |
|
||||
| `after` | string | No | Opaque pagination cursor returned as nextCursor by a previous call |
|
||||
| `limit` | number | No | Maximum number of groups to return per page \(max: 10000\) |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -335,6 +339,8 @@ List all groups in your Okta organization with optional search and filtering
|
||||
| ↳ `lastUpdated` | string | Last update timestamp |
|
||||
| ↳ `lastMembershipUpdated` | string | Last membership change timestamp |
|
||||
| `count` | number | Number of groups returned |
|
||||
| `nextCursor` | string | Cursor for the next page, or null on the last page |
|
||||
| `hasMore` | boolean | Whether more groups are available |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Get Group from Okta
|
||||
@@ -490,7 +496,8 @@ List all members of a specific group in your Okta organization
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `groupId` | string | Yes | Group ID to list members for |
|
||||
| `limit` | number | No | Maximum number of members to return \(default: 1000, max: 1000\) |
|
||||
| `after` | string | No | Opaque pagination cursor returned as nextCursor by a previous call |
|
||||
| `limit` | number | No | Maximum number of members to return per page \(default: 1000, but Okta recommends 200\) |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -512,6 +519,756 @@ List all members of a specific group in your Okta organization
|
||||
| ↳ `activated` | string | Activation timestamp |
|
||||
| ↳ `statusChanged` | string | Status change timestamp |
|
||||
| `count` | number | Number of members returned |
|
||||
| `nextCursor` | string | Cursor for the next page, or null on the last page |
|
||||
| `hasMore` | boolean | Whether more members are available |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### List Group Rules from Okta
|
||||
|
||||
List the group rules in your Okta organization. Each rule assigns users to groups automatically based on an expression over their profile, so this shows how group membership is being driven.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `search` | string | No | Keyword to search group rules for |
|
||||
| `after` | string | No | Opaque pagination cursor returned as nextCursor by a previous call |
|
||||
| `limit` | number | No | Maximum number of rules to return \(default: 50, max: 200\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `rules` | array | Array of group rules |
|
||||
| ↳ `id` | string | Group rule ID |
|
||||
| ↳ `name` | string | Group rule name |
|
||||
| ↳ `type` | string | Rule type, always group_rule |
|
||||
| ↳ `status` | string | Rule status \(ACTIVE, INACTIVE, INVALID\) |
|
||||
| ↳ `created` | string | Creation timestamp |
|
||||
| ↳ `lastUpdated` | string | Last update timestamp |
|
||||
| ↳ `expression` | string | Okta expression that decides which users the rule matches |
|
||||
| ↳ `expressionType` | string | Expression language, typically urn:okta:expression:1.0 |
|
||||
| ↳ `assignUserToGroupIds` | array | Groups that matching users are assigned to |
|
||||
| ↳ `excludedUserIds` | array | Users excluded from the rule |
|
||||
| ↳ `excludedGroupIds` | array | Groups excluded from the rule |
|
||||
| `count` | number | Number of rules returned |
|
||||
| `nextCursor` | string | Cursor for the next page, or null on the last page |
|
||||
| `hasMore` | boolean | Whether more rules are available |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Get Group Rule from Okta
|
||||
|
||||
Retrieve a single Okta group rule by ID, including the expression that decides which users it matches and the groups those users are assigned to.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `groupRuleId` | string | Yes | Group rule ID to look up |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | Group rule ID |
|
||||
| `name` | string | Group rule name |
|
||||
| `type` | string | Rule type, always group_rule |
|
||||
| `status` | string | Rule status \(ACTIVE, INACTIVE, INVALID\) |
|
||||
| `created` | string | Creation timestamp |
|
||||
| `lastUpdated` | string | Last update timestamp |
|
||||
| `expression` | string | Okta expression that decides which users the rule matches |
|
||||
| `expressionType` | string | Expression language, typically urn:okta:expression:1.0 |
|
||||
| `assignUserToGroupIds` | array | Groups that matching users are assigned to |
|
||||
| `excludedUserIds` | array | Users excluded from the rule |
|
||||
| `excludedGroupIds` | array | Groups excluded from the rule |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Create Group Rule in Okta
|
||||
|
||||
Create a group rule that automatically assigns users matching an Okta expression to one or more groups. New rules are created INACTIVE, so run Activate Group Rule afterwards to start applying it.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `ruleName` | string | Yes | Name for the group rule \(maximum 50 characters\) |
|
||||
| `expression` | string | Yes | Okta expression that must evaluate to a boolean \(e.g., user.department=="Engineering"\) |
|
||||
| `assignUserToGroupIds` | string | Yes | Comma-separated group IDs that matching users are assigned to |
|
||||
| `excludedUserIds` | string | No | Comma-separated user IDs to exclude from the rule |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | Created group rule ID |
|
||||
| `name` | string | Group rule name |
|
||||
| `type` | string | Rule type, always group_rule |
|
||||
| `status` | string | Rule status, which is INACTIVE for a newly created rule |
|
||||
| `created` | string | Creation timestamp |
|
||||
| `lastUpdated` | string | Last update timestamp |
|
||||
| `expression` | string | Okta expression that decides which users the rule matches |
|
||||
| `expressionType` | string | Expression language, typically urn:okta:expression:1.0 |
|
||||
| `assignUserToGroupIds` | array | Groups that matching users are assigned to |
|
||||
| `excludedUserIds` | array | Users excluded from the rule |
|
||||
| `excludedGroupIds` | array | Groups excluded from the rule |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Activate Group Rule in Okta
|
||||
|
||||
Activate a group rule so Okta starts applying it, assigning every matching user to the target groups.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `groupRuleId` | string | Yes | Group rule ID to activate |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `groupRuleId` | string | Activated group rule ID |
|
||||
| `activated` | boolean | Whether the rule was activated |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Deactivate Group Rule in Okta
|
||||
|
||||
Deactivate a group rule so Okta stops applying it. Existing memberships the rule created are left in place. A rule must be INACTIVE before it can be edited.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `groupRuleId` | string | Yes | Group rule ID to deactivate |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `groupRuleId` | string | Deactivated group rule ID |
|
||||
| `deactivated` | boolean | Whether the rule was deactivated |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Delete Group Rule in Okta
|
||||
|
||||
Permanently delete a group rule. Destructive and irreversible. Optionally also removes the users that this rule had assigned from those groups, which revokes any access those groups grant.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `groupRuleId` | string | Yes | Group rule ID to delete |
|
||||
| `removeUsers` | boolean | No | Also remove the users this rule assigned from the groups it targeted \(default: false\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `groupRuleId` | string | Deleted group rule ID |
|
||||
| `deleted` | boolean | Whether the rule was deleted |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### List Factors from Okta
|
||||
|
||||
List the MFA factors a user has enrolled, with each factor type, provider, and enrollment status. Use this before resetting a factor to confirm which one to target.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `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 |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `factors` | array | Array of enrolled MFA factors |
|
||||
| ↳ `id` | string | Factor ID |
|
||||
| ↳ `factorType` | string | Factor type \(sms, call, email, push, question, token:software:totp, webauthn, etc.\) |
|
||||
| ↳ `provider` | string | Factor provider \(OKTA, GOOGLE, FIDO, DUO, RSA, SYMANTEC, YUBICO, CUSTOM\) |
|
||||
| ↳ `vendorName` | string | Factor vendor name |
|
||||
| ↳ `status` | string | Enrollment status \(ACTIVE, PENDING_ACTIVATION, NOT_SETUP, etc.\) |
|
||||
| ↳ `created` | string | Enrollment timestamp |
|
||||
| ↳ `lastUpdated` | string | Last update timestamp |
|
||||
| ↳ `profile` | json | Factor-specific attributes, which vary by factor type \(phone number, email, question, credential ID\) |
|
||||
| `count` | number | Number of enrolled factors |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Get Factor from Okta
|
||||
|
||||
Retrieve a single enrolled MFA factor for a user, including its type, provider, enrollment status, and factor-specific profile.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `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 |
|
||||
| `factorId` | string | Yes | Factor ID to look up |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | Factor ID |
|
||||
| `factorType` | string | Factor type |
|
||||
| `provider` | string | Factor provider |
|
||||
| `vendorName` | string | Factor vendor name |
|
||||
| `status` | string | Enrollment status |
|
||||
| `created` | string | Enrollment timestamp |
|
||||
| `lastUpdated` | string | Last update timestamp |
|
||||
| `profile` | json | Factor-specific attributes, which vary by factor type \(phone number, email, question, credential ID\) |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Enroll Factor in Okta
|
||||
|
||||
Enroll an MFA factor for a user. The profile fields required depend on the factor type: a phone number for sms and call, an email address for email, and a question and answer for question. Factors that enroll from the user device, such as webauthn and push, need no profile fields.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `factorEmail` | string | No | Email address to enroll. Required for the email factor type |
|
||||
| `securityQuestion` | string | No | Security question key \(e.g., disliked_food\). Required for the question factor type |
|
||||
| `securityAnswer` | string | No | Answer to the security question, minimum 4 characters. Required for the question factor type |
|
||||
| `activate` | boolean | No | Activate the factor immediately as part of enrollment. Supported by the sms, call, email, and token:hotp factor types \(default: false\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | Enrolled factor ID |
|
||||
| `factorType` | string | Factor type |
|
||||
| `provider` | string | Factor provider |
|
||||
| `vendorName` | string | Factor vendor name |
|
||||
| `status` | string | Enrollment status, typically PENDING_ACTIVATION until the user activates it |
|
||||
| `created` | string | Enrollment timestamp |
|
||||
| `lastUpdated` | string | Last update timestamp |
|
||||
| `profile` | json | Factor-specific attributes, which vary by factor type \(phone number, email, question, credential ID\) |
|
||||
| `enrolled` | boolean | Whether the factor was enrolled |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Reset Factor in Okta
|
||||
|
||||
Unenroll one specific MFA factor for a user so they can re-enroll it. Destructive and irreversible: the existing enrollment is removed. Unenrolling a push or signed_nonce factor also unenrolls the related Okta Verify factors. Factors cannot be unenrolled from a deactivated user.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `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 |
|
||||
| `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\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `userId` | string | User the factor belonged to |
|
||||
| `factorId` | string | Unenrolled factor ID |
|
||||
| `reset` | boolean | Whether the factor was unenrolled |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Reset All Factors in Okta
|
||||
|
||||
Reset every MFA factor for a user, returning all enrollments to the unenrolled state. Destructive and irreversible: the user must re-enroll each factor before they can complete MFA again. The user status stays ACTIVE.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `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 MFA factors will all be reset |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `userId` | string | User whose factors were reset |
|
||||
| `reset` | boolean | Whether all factors were reset |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Clear User Sessions in Okta
|
||||
|
||||
Revoke every active Okta session for a user, signing them out of all devices immediately. Destructive and irreversible: the user must sign in again. Optionally also revokes their OAuth and OpenID Connect tokens, and clears remembered factors.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `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 |
|
||||
| `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\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `userId` | string | User whose sessions were revoked |
|
||||
| `cleared` | boolean | Whether the sessions were revoked |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Get Session from Okta
|
||||
|
||||
Retrieve an Okta session by ID, including who it belongs to, when it expires, and which authentication methods were used to establish it.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `sessionId` | string | Yes | Session ID to look up |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | Session ID |
|
||||
| `login` | string | Login of the session user |
|
||||
| `userId` | string | ID of the session user |
|
||||
| `status` | string | Session status \(ACTIVE, MFA_ENROLL, MFA_REQUIRED\) |
|
||||
| `createdAt` | string | Session creation timestamp |
|
||||
| `expiresAt` | string | Session expiry timestamp |
|
||||
| `lastPasswordVerification` | string | Timestamp of the last password verification |
|
||||
| `lastFactorVerification` | string | Timestamp of the last factor verification |
|
||||
| `amr` | array | Authentication methods used to establish the session |
|
||||
| `idpId` | string | Identity provider ID |
|
||||
| `idpType` | string | Identity provider type |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Revoke Session in Okta
|
||||
|
||||
Revoke a single Okta session by ID, ending that sign-in immediately. Destructive and irreversible: the affected user must sign in again on that device.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `sessionId` | string | Yes | Session ID to revoke |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `sessionId` | string | Revoked session ID |
|
||||
| `revoked` | boolean | Whether the session was revoked |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### List Applications from Okta
|
||||
|
||||
List the applications configured in your Okta organization, with optional name search, filtering, and cursor pagination.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `q` | string | No | Search for applications whose name or label starts with this value |
|
||||
| `filter` | string | No | Okta filter expression \(e.g., status eq "ACTIVE"\) |
|
||||
| `includeNonDeleted` | boolean | No | Also return inactive applications. Deleted applications stay excluded either way \(default: false\) |
|
||||
| `after` | string | No | Opaque pagination cursor returned as nextCursor by a previous call |
|
||||
| `limit` | number | No | Maximum number of applications to return \(max: 200\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `apps` | array | Array of Okta applications |
|
||||
| ↳ `id` | string | Application ID |
|
||||
| ↳ `name` | string | Application name \(the app template key\) |
|
||||
| ↳ `label` | string | Application display label |
|
||||
| ↳ `status` | string | Application status \(ACTIVE, INACTIVE, DELETED\) |
|
||||
| ↳ `signOnMode` | string | Sign-on mode \(SAML_2_0, OPENID_CONNECT, BOOKMARK, etc.\) |
|
||||
| ↳ `features` | array | Enabled provisioning features |
|
||||
| ↳ `created` | string | Creation timestamp |
|
||||
| ↳ `lastUpdated` | string | Last update timestamp |
|
||||
| `count` | number | Number of applications returned |
|
||||
| `nextCursor` | string | Cursor for the next page, or null on the last page |
|
||||
| `hasMore` | boolean | Whether more applications are available |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Get Application from Okta
|
||||
|
||||
Retrieve a single Okta application by ID, including its sign-on mode, status, enabled provisioning features, and configuration objects.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `appId` | string | Yes | Application ID to look up |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | Application ID |
|
||||
| `name` | string | Application name \(the app template key\) |
|
||||
| `label` | string | Application display label |
|
||||
| `status` | string | Application status \(ACTIVE, INACTIVE, DELETED\) |
|
||||
| `signOnMode` | string | Sign-on mode |
|
||||
| `features` | array | Enabled provisioning features |
|
||||
| `created` | string | Creation timestamp |
|
||||
| `lastUpdated` | string | Last update timestamp |
|
||||
| `accessibility` | json | Access settings for the app |
|
||||
| ↳ `errorRedirectUrl` | string | Custom error page URL |
|
||||
| ↳ `loginRedirectUrl` | string | Custom login page URL |
|
||||
| ↳ `selfService` | boolean | Whether users can self-assign the app |
|
||||
| `visibility` | json | Visibility settings for the app |
|
||||
| ↳ `appLinks` | json | Map of app link name to whether it appears on the End-User Dashboard |
|
||||
| ↳ `autoLaunch` | boolean | Signs in to the app automatically when the user signs in to Okta |
|
||||
| ↳ `autoSubmitToolbar` | boolean | Signs in automatically when the user lands on the sign-in page |
|
||||
| ↳ `hide` | json | Which end-user apps hide this app |
|
||||
| ↳ `iOS` | boolean | Hidden in Okta Mobile |
|
||||
| ↳ `web` | boolean | Hidden on the Okta End-User Dashboard |
|
||||
| `settings` | json | Application settings. Okta types these per app kind, so settings.app differs between a SAML, OIDC, bookmark, or SWA app |
|
||||
| `profile` | json | Application profile attributes. Okta accepts any valid JSON schema here, so the shape is whatever the org configured |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### List Application Users from Okta
|
||||
|
||||
List the users assigned to an Okta application, including how each assignment was made and its provisioning sync state. Use this to audit who has access to an app.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `appId` | string | Yes | Application ID to list assigned users for |
|
||||
| `q` | string | No | Search assigned users whose userName, firstName, lastName, or email starts with this value |
|
||||
| `after` | string | No | Opaque pagination cursor returned as nextCursor by a previous call |
|
||||
| `limit` | number | No | Maximum number of assigned users to return \(default: 50, max: 500\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `appUsers` | array | Array of application user assignments |
|
||||
| ↳ `id` | string | Okta user ID |
|
||||
| ↳ `externalId` | string | ID of the user in the downstream application |
|
||||
| ↳ `created` | string | Assignment creation timestamp |
|
||||
| ↳ `lastUpdated` | string | Last update timestamp |
|
||||
| ↳ `scope` | string | How the assignment was made: USER \(direct\) or GROUP \(inherited\) |
|
||||
| ↳ `status` | string | Assignment status |
|
||||
| ↳ `statusChanged` | string | Status change timestamp |
|
||||
| ↳ `passwordChanged` | string | App password change timestamp |
|
||||
| ↳ `syncState` | string | Provisioning sync state |
|
||||
| ↳ `lastSync` | string | Last provisioning sync |
|
||||
| ↳ `userName` | string | Username the user signs in to the application with |
|
||||
| ↳ `profile` | json | App-specific profile attributes, whose shape is set by the app schema |
|
||||
| `count` | number | Number of assignments returned |
|
||||
| `nextCursor` | string | Cursor for the next page, or null on the last page |
|
||||
| `hasMore` | boolean | Whether more assignments are available |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Assign User to Application in Okta
|
||||
|
||||
Assign a user to an Okta application, granting them access to it. Applications that require credentials also need the username the user signs in with.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `appId` | string | Yes | Application ID to assign the user to |
|
||||
| `userId` | string | Yes | Okta user ID to assign |
|
||||
| `scope` | string | No | Assignment scope: USER for a direct assignment, or GROUP |
|
||||
| `appUserName` | string | No | Username the user signs in to the application with. Required by applications that store credentials |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | Okta user ID that was assigned |
|
||||
| `externalId` | string | ID of the user in the downstream application |
|
||||
| `created` | string | Assignment creation timestamp |
|
||||
| `lastUpdated` | string | Last update timestamp |
|
||||
| `scope` | string | Assignment scope \(USER or GROUP\) |
|
||||
| `status` | string | Assignment status |
|
||||
| `statusChanged` | string | Status change timestamp |
|
||||
| `passwordChanged` | string | App password change timestamp |
|
||||
| `syncState` | string | Provisioning sync state |
|
||||
| `lastSync` | string | Last provisioning sync |
|
||||
| `userName` | string | Username the user signs in to the application with |
|
||||
| `profile` | json | App-specific profile attributes, whose shape is set by the app schema |
|
||||
| `assigned` | boolean | Whether the user was assigned |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Remove User from Application in Okta
|
||||
|
||||
Unassign a user from an Okta application, revoking their access. Destructive and irreversible: the app profile for that user is permanently removed, and if provisioning is enabled the downstream account is deactivated.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `appId` | string | Yes | Application ID to remove the user from |
|
||||
| `userId` | string | Yes | Okta user ID to unassign |
|
||||
| `sendEmail` | boolean | No | Send a deactivation email to the administrator \(default: false\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `appId` | string | Application ID |
|
||||
| `userId` | string | User unassigned from the application |
|
||||
| `removed` | boolean | Whether the user was unassigned |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### List Application Groups from Okta
|
||||
|
||||
List the groups assigned to an Okta application. Every member of an assigned group inherits access to the app, so this is the starting point for an app access review.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `appId` | string | Yes | Application ID to list assigned groups for |
|
||||
| `q` | string | No | Search assigned groups whose name starts with this value |
|
||||
| `after` | string | No | Opaque pagination cursor returned as nextCursor by a previous call |
|
||||
| `limit` | number | No | Maximum number of assigned groups to return \(default: 20, range: 20 to 200\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `appGroups` | array | Array of application group assignments |
|
||||
| ↳ `id` | string | Assigned group ID |
|
||||
| ↳ `priority` | number | Assignment priority, which resolves conflicting profile mappings |
|
||||
| ↳ `lastUpdated` | string | Last update timestamp |
|
||||
| ↳ `profile` | json | App-specific profile attributes, whose shape is set by the app schema |
|
||||
| `count` | number | Number of assignments returned |
|
||||
| `nextCursor` | string | Cursor for the next page, or null on the last page |
|
||||
| `hasMore` | boolean | Whether more assignments are available |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Assign Group to Application in Okta
|
||||
|
||||
Assign a group to an Okta application so every member of the group inherits access to it.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `appId` | string | Yes | Application ID to assign the group to |
|
||||
| `groupId` | string | Yes | Group ID to assign |
|
||||
| `priority` | number | No | Assignment priority, which resolves conflicting profile mappings when a user belongs to several assigned groups |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | Assigned group ID |
|
||||
| `priority` | number | Assignment priority |
|
||||
| `lastUpdated` | string | Last update timestamp |
|
||||
| `profile` | json | App-specific profile attributes, whose shape is set by the app schema |
|
||||
| `assigned` | boolean | Whether the group was assigned |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Remove Group from Application in Okta
|
||||
|
||||
Unassign a group from an Okta application. Destructive: every member who had access only through this group loses access to the app.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `appId` | string | Yes | Application ID to remove the group from |
|
||||
| `groupId` | string | Yes | Group ID to unassign |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `appId` | string | Application ID |
|
||||
| `groupId` | string | Group unassigned from the application |
|
||||
| `removed` | boolean | Whether the group was unassigned |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### List User Roles from Okta
|
||||
|
||||
List the administrator roles assigned to a user. Returns both standard roles and custom role bindings, so you can review who holds privileged access.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `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 |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `roles` | array | Array of admin role assignments |
|
||||
| ↳ `id` | string | Role assignment ID, which is the resource set binding ID for a custom role. Pass this to Remove User Role |
|
||||
| ↳ `label` | string | Role label |
|
||||
| ↳ `type` | string | Role type \(SUPER_ADMIN, ORG_ADMIN, APP_ADMIN, USER_ADMIN, HELP_DESK_ADMIN, READ_ONLY_ADMIN, CUSTOM, etc.\) |
|
||||
| ↳ `status` | string | Role status \(ACTIVE, INACTIVE\) |
|
||||
| ↳ `created` | string | Assignment timestamp |
|
||||
| ↳ `lastUpdated` | string | Last update timestamp |
|
||||
| ↳ `assignmentType` | string | How the role was assigned \(USER, GROUP, CLIENT\) |
|
||||
| ↳ `role` | string | Custom role ID, present only on custom role assignments |
|
||||
| ↳ `resourceSet` | string | Resource set ID, present only on custom role assignments |
|
||||
| `count` | number | Number of role assignments returned |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Assign User Role in Okta
|
||||
|
||||
Grant a user an administrator role. Use a standard role type such as USER_ADMIN or HELP_DESK_ADMIN, or CUSTOM together with a custom role ID and a resource set ID. This grants privileged access, so confirm the role is the least privilege that fits.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `disableNotifications` | boolean | No | Grant the user third-party admin status, which suppresses Okta admin notifications \(default: false\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | Role assignment ID, which is what Remove User Role takes |
|
||||
| `label` | string | Role label |
|
||||
| `type` | string | Assigned role type |
|
||||
| `status` | string | Role status |
|
||||
| `created` | string | Assignment timestamp |
|
||||
| `lastUpdated` | string | Last update timestamp |
|
||||
| `assignmentType` | string | How the role was assigned \(USER, GROUP, CLIENT\) |
|
||||
| `role` | string | Custom role ID, for custom roles |
|
||||
| `resourceSet` | string | Resource set ID, for custom roles |
|
||||
| `assigned` | boolean | Whether the role was assigned |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Remove User Role in Okta
|
||||
|
||||
Revoke an administrator role from a user. Destructive: the user immediately loses the admin permissions that role granted. Takes the role assignment ID, not the role type, which List User Roles returns as the role id field.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `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 |
|
||||
| `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
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `userId` | string | User the role was revoked from |
|
||||
| `roleAssignmentId` | string | Revoked role assignment ID |
|
||||
| `removed` | boolean | Whether the role was revoked |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
### Get System Log Events from Okta
|
||||
|
||||
Query the Okta System Log for sign-ins, admin changes, and security events. Supports a time window, SCIM filter expressions, keyword search, and cursor pagination for audit and investigation workflows.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `apiKey` | string | Yes | Okta API token for authentication |
|
||||
| `domain` | string | Yes | Okta domain \(e.g., dev-123456.okta.com\) |
|
||||
| `since` | string | No | Start of the query time window as an ISO 8601 timestamp \(default: 7 days before "until"\) |
|
||||
| `until` | string | No | End of the query time window as an ISO 8601 timestamp \(default: now\) |
|
||||
| `filter` | string | No | SCIM filter expression \(e.g., eventType eq "user.session.start" or outcome.result eq "FAILURE"\) |
|
||||
| `q` | string | No | Keyword search across the event payload \(max 40 characters per keyword, max 10 keywords\) |
|
||||
| `sortOrder` | string | No | Sort order: ASCENDING \(default\) or DESCENDING |
|
||||
| `after` | string | No | Opaque pagination cursor returned as nextCursor by a previous call |
|
||||
| `limit` | number | No | Maximum number of events to return \(default: 100, max: 1000\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `events` | array | Array of System Log events |
|
||||
| ↳ `uuid` | string | Unique event ID |
|
||||
| ↳ `published` | string | Event timestamp |
|
||||
| ↳ `eventType` | string | Event type \(e.g., user.session.start, user.account.update_password\) |
|
||||
| ↳ `severity` | string | Event severity \(DEBUG, ERROR, INFO, WARN\) |
|
||||
| ↳ `legacyEventType` | string | Legacy event type |
|
||||
| ↳ `displayMessage` | string | Human-readable event description |
|
||||
| ↳ `outcomeResult` | string | Event outcome \(SUCCESS, FAILURE, CHALLENGE, DENY, etc.\) |
|
||||
| ↳ `outcomeReason` | string | Reason for the outcome |
|
||||
| ↳ `actorId` | string | ID of the actor |
|
||||
| ↳ `actorType` | string | Actor type \(User, Client, etc.\) |
|
||||
| ↳ `actorAlternateId` | string | Actor alternate ID, usually the login |
|
||||
| ↳ `actorDisplayName` | string | Actor display name |
|
||||
| ↳ `clientIpAddress` | string | Client IP address |
|
||||
| ↳ `clientDevice` | string | Client device category \(e.g., Computer\) |
|
||||
| ↳ `clientZone` | string | Network zone |
|
||||
| ↳ `clientBrowser` | string | Client browser |
|
||||
| ↳ `clientOs` | string | Client operating system |
|
||||
| ↳ `clientCity` | string | Client city |
|
||||
| ↳ `clientState` | string | Client state or region |
|
||||
| ↳ `clientCountry` | string | Client country |
|
||||
| ↳ `authenticationProvider` | string | Authentication provider used |
|
||||
| ↳ `credentialType` | string | Credential type used |
|
||||
| ↳ `externalSessionId` | string | External session ID for correlating events |
|
||||
| ↳ `securityAsOrg` | string | Autonomous system organization |
|
||||
| ↳ `securityIsp` | string | Internet service provider |
|
||||
| ↳ `securityIsProxy` | boolean | Whether the request came through a proxy |
|
||||
| ↳ `transactionId` | string | Transaction ID |
|
||||
| ↳ `transactionType` | string | Transaction type \(e.g., WEB, JOB\) |
|
||||
| ↳ `targets` | array | Entities the event acted upon |
|
||||
| ↳ `id` | string | Target ID |
|
||||
| ↳ `type` | string | Target type |
|
||||
| ↳ `alternateId` | string | Target alternate ID |
|
||||
| ↳ `displayName` | string | Target display name |
|
||||
| ↳ `debugData` | json | Extra context whose keys depend on the event type. Okta states these keys and values can change between releases, so treat them as a debugging aid rather than a contract |
|
||||
| `count` | number | Number of events returned |
|
||||
| `nextCursor` | string | Cursor for the next page, or null on the last page |
|
||||
| `hasMore` | boolean | Whether more events are available |
|
||||
| `success` | boolean | Operation success status |
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { OktaBlock } from '@/blocks/blocks/okta'
|
||||
|
||||
/**
|
||||
* The generic block handler runs `{ ...inputs, ...transformedParams }`, so the
|
||||
* transform can only drop a value by assigning `undefined` to its key. Omitting
|
||||
* the key leaves the raw subBlock string in place. These cases pin that
|
||||
* behavior by asserting on the merge, not on the transform alone.
|
||||
*/
|
||||
function merge(inputs: Record<string, unknown>): Record<string, unknown> {
|
||||
const transform = OktaBlock.tools.config?.params
|
||||
if (!transform) throw new Error('Okta block has no params transform')
|
||||
return { ...inputs, ...transform(inputs) }
|
||||
}
|
||||
|
||||
const BASE = { operation: 'okta_list_users', apiKey: 'token', domain: 'dev-1.okta.com' }
|
||||
|
||||
describe('Okta block params transform', () => {
|
||||
it('coerces a numeric limit', () => {
|
||||
expect(merge({ ...BASE, limit: '25' }).limit).toBe(25)
|
||||
})
|
||||
|
||||
it('drops a non-numeric limit rather than forwarding the raw entry', () => {
|
||||
expect(merge({ ...BASE, limit: 'twenty' }).limit).toBeUndefined()
|
||||
})
|
||||
|
||||
it('drops a non-numeric priority rather than forwarding the raw entry', () => {
|
||||
const merged = merge({
|
||||
...BASE,
|
||||
operation: 'okta_assign_group_to_app',
|
||||
priority: 'high',
|
||||
})
|
||||
expect(merged.priority).toBeUndefined()
|
||||
})
|
||||
|
||||
it('drops a blank profile field so a partial update leaves Okta untouched', () => {
|
||||
const merged = merge({
|
||||
...BASE,
|
||||
operation: 'okta_update_user',
|
||||
userId: '00u1',
|
||||
firstName: 'Ada',
|
||||
lastName: '',
|
||||
})
|
||||
expect(merged.firstName).toBe('Ada')
|
||||
expect(merged.lastName).toBeUndefined()
|
||||
})
|
||||
|
||||
it('maps the group name and description onto the tool param names', () => {
|
||||
const merged = merge({
|
||||
...BASE,
|
||||
operation: 'okta_create_group',
|
||||
groupName: 'Engineering',
|
||||
groupDescription: 'Eng team',
|
||||
})
|
||||
expect(merged.name).toBe('Engineering')
|
||||
expect(merged.description).toBe('Eng team')
|
||||
})
|
||||
|
||||
it('keeps a false toggle, which is a real choice rather than a blank field', () => {
|
||||
const merged = merge({
|
||||
...BASE,
|
||||
operation: 'okta_activate_user',
|
||||
userId: '00u1',
|
||||
sendEmail: false,
|
||||
})
|
||||
expect(merged.sendEmail).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Okta block outputs', () => {
|
||||
it('declares only fields a tool emits at the top level', () => {
|
||||
const declared = Object.keys(OktaBlock.outputs)
|
||||
expect(declared).not.toContain('targets')
|
||||
expect(declared).not.toContain('debugData')
|
||||
expect(declared).toContain('events')
|
||||
})
|
||||
})
|
||||
+786
-22
@@ -1,16 +1,36 @@
|
||||
import { OktaIcon } from '@/components/icons'
|
||||
import type { BlockConfig, BlockMeta } from '@/blocks/types'
|
||||
import { IntegrationType } from '@/blocks/types'
|
||||
import { AuthMode, IntegrationType } from '@/blocks/types'
|
||||
import type { OktaResponse } from '@/tools/okta/types'
|
||||
|
||||
/**
|
||||
* Coerces a numeric subBlock value, dropping anything that is not a real number.
|
||||
*
|
||||
* These fields are free-text inputs, so a stray non-numeric entry would otherwise
|
||||
* become `NaN` and serialize to `null`, which Okta rejects with a validation
|
||||
* error that points at the wrong thing. Dropping the field instead lets Okta
|
||||
* apply its own default.
|
||||
*/
|
||||
function toFiniteNumber(value: unknown): number | undefined {
|
||||
if (value === undefined || value === null || value === '') return undefined
|
||||
const parsed = Number(value)
|
||||
return Number.isFinite(parsed) ? parsed : undefined
|
||||
}
|
||||
|
||||
/** Treats a blank subBlock value as absent. */
|
||||
function blankToUndefined(value: unknown): unknown {
|
||||
return value === null || value === '' ? undefined : value
|
||||
}
|
||||
|
||||
export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
type: 'okta',
|
||||
name: 'Okta',
|
||||
description: 'Manage users and groups in Okta',
|
||||
description: 'Manage users, groups, apps, and MFA in Okta',
|
||||
longDescription:
|
||||
'Integrate Okta identity management into your workflow. List, create, update, activate, suspend, and delete users. Reset passwords. Manage groups and group membership.',
|
||||
'Integrate Okta identity management into your workflow. Manage users, groups, and group rules. Run service desk actions like resetting MFA factors and clearing sessions. Review and change application assignments and admin roles. Query the System Log to audit sign-ins and admin changes.',
|
||||
docsLink: 'https://docs.sim.ai/integrations/okta',
|
||||
category: 'tools',
|
||||
authMode: AuthMode.ApiKey,
|
||||
integrationType: IntegrationType.Security,
|
||||
bgColor: '#191919',
|
||||
iconColor: '#007DC1',
|
||||
@@ -66,6 +86,92 @@ export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
{ text: 'List the members of group', field: 'groupId', core: true },
|
||||
{ text: ', up to', field: 'limit' },
|
||||
],
|
||||
okta_get_logs: [
|
||||
'Read System Log events',
|
||||
{ text: ', matching', field: 'q' },
|
||||
{ text: ', filtered by', field: 'filter' },
|
||||
{ text: ', since', field: 'since' },
|
||||
{ text: ', until', field: 'until' },
|
||||
],
|
||||
okta_clear_user_sessions: [
|
||||
{ text: 'Clear every session of user', field: 'userId', core: true },
|
||||
],
|
||||
okta_get_session: [{ text: 'Read session', field: 'sessionId', core: true }],
|
||||
okta_revoke_session: [{ text: 'Revoke session', field: 'sessionId', core: true }],
|
||||
okta_list_factors: [{ text: 'List the MFA factors of user', field: 'userId', core: true }],
|
||||
okta_get_factor: [
|
||||
{ text: 'Read factor', field: 'factorId', core: true },
|
||||
{ text: 'of user', field: 'userId' },
|
||||
],
|
||||
okta_enroll_factor: [
|
||||
{ text: 'Enroll factor', field: 'factorType', core: true },
|
||||
{ text: 'for user', field: 'userId' },
|
||||
],
|
||||
okta_reset_factor: [
|
||||
{ text: 'Reset factor', field: 'factorId', core: true },
|
||||
{ text: 'for user', field: 'userId' },
|
||||
],
|
||||
okta_reset_all_factors: [
|
||||
{ text: 'Reset every MFA factor of user', field: 'userId', core: true },
|
||||
],
|
||||
okta_list_apps: [
|
||||
'List applications',
|
||||
{ text: ', matching', field: 'q' },
|
||||
{ text: ', filtered by', field: 'filter' },
|
||||
{ text: ', up to', field: 'limit' },
|
||||
],
|
||||
okta_get_app: [{ text: 'Read application', field: 'appId', core: true }],
|
||||
okta_list_app_users: [
|
||||
{ text: 'List the users assigned to application', field: 'appId', core: true },
|
||||
{ text: ', matching', field: 'q' },
|
||||
],
|
||||
okta_assign_user_to_app: [
|
||||
{ text: 'Assign user', field: 'userId', core: true },
|
||||
{ text: 'to application', field: 'appId' },
|
||||
],
|
||||
okta_remove_user_from_app: [
|
||||
{ text: 'Remove user', field: 'userId', core: true },
|
||||
{ text: 'from application', field: 'appId', core: true },
|
||||
],
|
||||
okta_list_app_groups: [
|
||||
{ text: 'List the groups assigned to application', field: 'appId', core: true },
|
||||
],
|
||||
okta_assign_group_to_app: [
|
||||
{ text: 'Assign group', field: 'groupId', core: true },
|
||||
{ text: 'to application', field: 'appId' },
|
||||
],
|
||||
okta_remove_group_from_app: [
|
||||
{ text: 'Remove group', field: 'groupId', core: true },
|
||||
{ text: 'from application', field: 'appId' },
|
||||
],
|
||||
okta_list_user_roles: [
|
||||
{ text: 'List the admin roles of user', field: 'userId', core: true },
|
||||
],
|
||||
okta_assign_user_role: [
|
||||
{ text: 'Assign admin role', field: 'roleType', core: true },
|
||||
{ text: 'to user', field: 'userId' },
|
||||
],
|
||||
okta_remove_user_role: [
|
||||
{ text: 'Remove admin role assignment', field: 'roleAssignmentId', core: true },
|
||||
{ text: 'from user', field: 'userId' },
|
||||
],
|
||||
okta_list_group_rules: [
|
||||
'List group rules',
|
||||
{ text: ', matching', field: 'search' },
|
||||
{ text: ', up to', field: 'limit' },
|
||||
],
|
||||
okta_get_group_rule: [{ text: 'Read group rule', field: 'groupRuleId', core: true }],
|
||||
okta_create_group_rule: [
|
||||
{ text: 'Create group rule', field: 'ruleName', core: true },
|
||||
{ text: ', matching users where', field: 'expression' },
|
||||
],
|
||||
okta_activate_group_rule: [
|
||||
{ text: 'Activate group rule', field: 'groupRuleId', core: true },
|
||||
],
|
||||
okta_deactivate_group_rule: [
|
||||
{ text: 'Deactivate group rule', field: 'groupRuleId', core: true },
|
||||
],
|
||||
okta_delete_group_rule: [{ text: 'Delete group rule', field: 'groupRuleId', core: true }],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -94,6 +200,32 @@ export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
{ label: 'Add User to Group', id: 'okta_add_user_to_group' },
|
||||
{ label: 'Remove User from Group', id: 'okta_remove_user_from_group' },
|
||||
{ label: 'List Group Members', id: 'okta_list_group_members' },
|
||||
{ label: 'List Group Rules', id: 'okta_list_group_rules' },
|
||||
{ label: 'Get Group Rule', id: 'okta_get_group_rule' },
|
||||
{ label: 'Create Group Rule', id: 'okta_create_group_rule' },
|
||||
{ label: 'Activate Group Rule', id: 'okta_activate_group_rule' },
|
||||
{ label: 'Deactivate Group Rule', id: 'okta_deactivate_group_rule' },
|
||||
{ label: 'Delete Group Rule', id: 'okta_delete_group_rule' },
|
||||
{ label: 'List Factors', id: 'okta_list_factors' },
|
||||
{ label: 'Get Factor', id: 'okta_get_factor' },
|
||||
{ label: 'Enroll Factor', id: 'okta_enroll_factor' },
|
||||
{ label: 'Reset Factor', id: 'okta_reset_factor' },
|
||||
{ label: 'Reset All Factors', id: 'okta_reset_all_factors' },
|
||||
{ label: 'Clear User Sessions', id: 'okta_clear_user_sessions' },
|
||||
{ label: 'Get Session', id: 'okta_get_session' },
|
||||
{ label: 'Revoke Session', id: 'okta_revoke_session' },
|
||||
{ label: 'List Applications', id: 'okta_list_apps' },
|
||||
{ label: 'Get Application', id: 'okta_get_app' },
|
||||
{ label: 'List Application Users', id: 'okta_list_app_users' },
|
||||
{ label: 'Assign User to Application', id: 'okta_assign_user_to_app' },
|
||||
{ label: 'Remove User from Application', id: 'okta_remove_user_from_app' },
|
||||
{ label: 'List Application Groups', id: 'okta_list_app_groups' },
|
||||
{ label: 'Assign Group to Application', id: 'okta_assign_group_to_app' },
|
||||
{ label: 'Remove Group from Application', id: 'okta_remove_group_from_app' },
|
||||
{ label: 'List User Roles', id: 'okta_list_user_roles' },
|
||||
{ label: 'Assign User Role', id: 'okta_assign_user_role' },
|
||||
{ label: 'Remove User Role', id: 'okta_remove_user_role' },
|
||||
{ label: 'Get System Log Events', id: 'okta_get_logs' },
|
||||
],
|
||||
value: () => 'okta_list_users',
|
||||
},
|
||||
@@ -118,15 +250,43 @@ export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
title: 'Search',
|
||||
type: 'short-input',
|
||||
placeholder: 'profile.firstName eq "John"',
|
||||
condition: { field: 'operation', value: ['okta_list_users', 'okta_list_groups'] },
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['okta_list_users', 'okta_list_groups', 'okta_list_group_rules'],
|
||||
},
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
placeholder: 'Describe who or what to search for',
|
||||
prompt:
|
||||
'Generate an Okta search expression. The grammar is SCIM-style: a property, an operator (eq, sw, co, gt, ge, lt, le), and a quoted value, combined with and/or and parentheses. User properties are prefixed with profile (profile.firstName, profile.email, profile.department) plus the top-level id, status, created, activated, statusChanged and lastUpdated. Group properties are type plus profile.name and profile.description. Example: profile.department eq "Engineering" and status eq "ACTIVE". Return ONLY the expression - no explanations, no extra text.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'filter',
|
||||
title: 'Filter',
|
||||
type: 'short-input',
|
||||
placeholder: 'status eq "ACTIVE"',
|
||||
condition: { field: 'operation', value: ['okta_list_users', 'okta_list_groups'] },
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['okta_list_users', 'okta_list_groups', 'okta_list_apps', 'okta_get_logs'],
|
||||
},
|
||||
mode: 'advanced',
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
placeholder: 'Describe how to narrow the results',
|
||||
prompt:
|
||||
'Generate an Okta filter expression. The grammar is SCIM-style: a property, an operator (eq, and for lastUpdated also gt, ge, lt, le), and a quoted value, combined with and/or. Each listing supports a limited property set. Users: status, lastUpdated, id, profile.login, profile.email, profile.firstName, profile.lastName. Groups: id, type, lastUpdated, lastMembershipUpdated. Applications: id, status, name. System Log: any event property, such as eventType, outcome.result, actor.alternateId or client.ipAddress. Example: eventType eq "user.session.start" and outcome.result eq "FAILURE". Return ONLY the expression - no explanations, no extra text.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'q',
|
||||
title: 'Query',
|
||||
type: 'short-input',
|
||||
placeholder: 'Keyword to search for',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['okta_get_logs', 'okta_list_apps', 'okta_list_app_users', 'okta_list_app_groups'],
|
||||
},
|
||||
},
|
||||
// User ID (shared across user operations that need it)
|
||||
{
|
||||
@@ -147,6 +307,17 @@ export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
'okta_delete_user',
|
||||
'okta_add_user_to_group',
|
||||
'okta_remove_user_from_group',
|
||||
'okta_clear_user_sessions',
|
||||
'okta_list_factors',
|
||||
'okta_get_factor',
|
||||
'okta_enroll_factor',
|
||||
'okta_reset_factor',
|
||||
'okta_reset_all_factors',
|
||||
'okta_assign_user_to_app',
|
||||
'okta_remove_user_from_app',
|
||||
'okta_list_user_roles',
|
||||
'okta_assign_user_role',
|
||||
'okta_remove_user_role',
|
||||
],
|
||||
},
|
||||
required: {
|
||||
@@ -162,6 +333,17 @@ export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
'okta_delete_user',
|
||||
'okta_add_user_to_group',
|
||||
'okta_remove_user_from_group',
|
||||
'okta_clear_user_sessions',
|
||||
'okta_list_factors',
|
||||
'okta_get_factor',
|
||||
'okta_enroll_factor',
|
||||
'okta_reset_factor',
|
||||
'okta_reset_all_factors',
|
||||
'okta_assign_user_to_app',
|
||||
'okta_remove_user_from_app',
|
||||
'okta_list_user_roles',
|
||||
'okta_assign_user_role',
|
||||
'okta_remove_user_role',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -180,6 +362,8 @@ export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
'okta_add_user_to_group',
|
||||
'okta_remove_user_from_group',
|
||||
'okta_list_group_members',
|
||||
'okta_assign_group_to_app',
|
||||
'okta_remove_group_from_app',
|
||||
],
|
||||
},
|
||||
required: {
|
||||
@@ -191,6 +375,8 @@ export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
'okta_add_user_to_group',
|
||||
'okta_remove_user_from_group',
|
||||
'okta_list_group_members',
|
||||
'okta_assign_group_to_app',
|
||||
'okta_remove_group_from_app',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -264,7 +450,7 @@ export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
id: 'activate',
|
||||
title: 'Activate Immediately',
|
||||
type: 'switch',
|
||||
condition: { field: 'operation', value: 'okta_create_user' },
|
||||
condition: { field: 'operation', value: ['okta_create_user', 'okta_enroll_factor'] },
|
||||
mode: 'advanced',
|
||||
},
|
||||
// Group name (for create/update group)
|
||||
@@ -295,10 +481,360 @@ export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
'okta_deactivate_user',
|
||||
'okta_reset_password',
|
||||
'okta_delete_user',
|
||||
'okta_remove_user_from_app',
|
||||
],
|
||||
},
|
||||
mode: 'advanced',
|
||||
},
|
||||
// Group rule params
|
||||
{
|
||||
id: 'groupRuleId',
|
||||
title: 'Group Rule ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Okta group rule ID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'okta_get_group_rule',
|
||||
'okta_activate_group_rule',
|
||||
'okta_deactivate_group_rule',
|
||||
'okta_delete_group_rule',
|
||||
],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'okta_get_group_rule',
|
||||
'okta_activate_group_rule',
|
||||
'okta_deactivate_group_rule',
|
||||
'okta_delete_group_rule',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'ruleName',
|
||||
title: 'Rule Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'Engineers',
|
||||
condition: { field: 'operation', value: 'okta_create_group_rule' },
|
||||
required: { field: 'operation', value: 'okta_create_group_rule' },
|
||||
},
|
||||
{
|
||||
id: 'expression',
|
||||
title: 'Expression',
|
||||
type: 'short-input',
|
||||
placeholder: 'user.department=="Engineering"',
|
||||
condition: { field: 'operation', value: 'okta_create_group_rule' },
|
||||
required: { field: 'operation', value: 'okta_create_group_rule' },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
placeholder: 'Describe which users the rule should match',
|
||||
prompt:
|
||||
'Generate an Okta Expression Language predicate that evaluates to a boolean over a user profile. Reference profile attributes as user.<attribute>, combine them with && and ||, and compare with == or !=. Example: user.department=="Engineering" && user.countryCode=="US". Return ONLY the expression - no explanations, no extra text.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'assignUserToGroupIds',
|
||||
title: 'Assign to Group IDs',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated group IDs',
|
||||
condition: { field: 'operation', value: 'okta_create_group_rule' },
|
||||
required: { field: 'operation', value: 'okta_create_group_rule' },
|
||||
},
|
||||
{
|
||||
id: 'excludedUserIds',
|
||||
title: 'Excluded User IDs',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated user IDs to exclude',
|
||||
condition: { field: 'operation', value: 'okta_create_group_rule' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'removeUsers',
|
||||
title: 'Remove Assigned Users',
|
||||
type: 'switch',
|
||||
condition: { field: 'operation', value: 'okta_delete_group_rule' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
// Factor params
|
||||
{
|
||||
id: 'factorId',
|
||||
title: 'Factor ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Okta factor ID',
|
||||
condition: { field: 'operation', value: ['okta_get_factor', 'okta_reset_factor'] },
|
||||
required: { field: 'operation', value: ['okta_get_factor', 'okta_reset_factor'] },
|
||||
},
|
||||
{
|
||||
id: 'factorType',
|
||||
title: 'Factor Type',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'SMS', id: 'sms' },
|
||||
{ label: 'Voice Call', id: 'call' },
|
||||
{ label: 'Email', id: 'email' },
|
||||
{ label: 'Security Question', id: 'question' },
|
||||
{ label: 'Okta Verify Push', id: 'push' },
|
||||
{ label: 'Software TOTP', id: 'token:software:totp' },
|
||||
{ label: 'U2F', id: 'u2f' },
|
||||
{ label: 'WebAuthn', id: 'webauthn' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'okta_enroll_factor' },
|
||||
required: { field: 'operation', value: 'okta_enroll_factor' },
|
||||
},
|
||||
{
|
||||
id: 'provider',
|
||||
title: 'Factor Provider',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Okta', id: 'OKTA' },
|
||||
{ label: 'Google', id: 'GOOGLE' },
|
||||
{ label: 'FIDO', id: 'FIDO' },
|
||||
{ label: 'Duo', id: 'DUO' },
|
||||
{ label: 'RSA', id: 'RSA' },
|
||||
{ label: 'Symantec', id: 'SYMANTEC' },
|
||||
{ label: 'Yubico', id: 'YUBICO' },
|
||||
{ label: 'Custom', id: 'CUSTOM' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'okta_enroll_factor' },
|
||||
required: { field: 'operation', value: 'okta_enroll_factor' },
|
||||
},
|
||||
{
|
||||
id: 'phoneNumber',
|
||||
title: 'Phone Number',
|
||||
type: 'short-input',
|
||||
placeholder: '+14155550123 (required for SMS and voice call)',
|
||||
condition: { field: 'operation', value: 'okta_enroll_factor' },
|
||||
},
|
||||
{
|
||||
id: 'factorEmail',
|
||||
title: 'Factor Email',
|
||||
type: 'short-input',
|
||||
placeholder: 'user@example.com (required for the email factor)',
|
||||
condition: { field: 'operation', value: 'okta_enroll_factor' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'securityQuestion',
|
||||
title: 'Security Question',
|
||||
type: 'short-input',
|
||||
placeholder: 'disliked_food (required for the question factor)',
|
||||
condition: { field: 'operation', value: 'okta_enroll_factor' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'securityAnswer',
|
||||
title: 'Security Answer',
|
||||
type: 'short-input',
|
||||
password: true,
|
||||
placeholder: 'Answer, minimum 4 characters',
|
||||
condition: { field: 'operation', value: 'okta_enroll_factor' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'removeRecoveryEnrollment',
|
||||
title: 'Remove Recovery Enrollment',
|
||||
type: 'switch',
|
||||
condition: { field: 'operation', value: 'okta_reset_factor' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
// Session params
|
||||
{
|
||||
id: 'sessionId',
|
||||
title: 'Session ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Okta session ID',
|
||||
condition: { field: 'operation', value: ['okta_get_session', 'okta_revoke_session'] },
|
||||
required: { field: 'operation', value: ['okta_get_session', 'okta_revoke_session'] },
|
||||
},
|
||||
{
|
||||
id: 'oauthTokens',
|
||||
title: 'Revoke OAuth Tokens',
|
||||
type: 'switch',
|
||||
condition: { field: 'operation', value: 'okta_clear_user_sessions' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'forgetDevices',
|
||||
title: 'Forget Devices',
|
||||
type: 'switch',
|
||||
condition: { field: 'operation', value: 'okta_clear_user_sessions' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
// Application params
|
||||
{
|
||||
id: 'appId',
|
||||
title: 'Application ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Okta application ID',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'okta_get_app',
|
||||
'okta_list_app_users',
|
||||
'okta_assign_user_to_app',
|
||||
'okta_remove_user_from_app',
|
||||
'okta_list_app_groups',
|
||||
'okta_assign_group_to_app',
|
||||
'okta_remove_group_from_app',
|
||||
],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'okta_get_app',
|
||||
'okta_list_app_users',
|
||||
'okta_assign_user_to_app',
|
||||
'okta_remove_user_from_app',
|
||||
'okta_list_app_groups',
|
||||
'okta_assign_group_to_app',
|
||||
'okta_remove_group_from_app',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'appUserName',
|
||||
title: 'Application Username',
|
||||
type: 'short-input',
|
||||
placeholder: 'Username the user signs in to the app with',
|
||||
condition: { field: 'operation', value: 'okta_assign_user_to_app' },
|
||||
},
|
||||
{
|
||||
id: 'scope',
|
||||
title: 'Assignment Scope',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'User', id: 'USER' },
|
||||
{ label: 'Group', id: 'GROUP' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'okta_assign_user_to_app' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'priority',
|
||||
title: 'Priority',
|
||||
type: 'short-input',
|
||||
placeholder: 'Assignment priority',
|
||||
condition: { field: 'operation', value: 'okta_assign_group_to_app' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'includeNonDeleted',
|
||||
title: 'Include Non-Deleted',
|
||||
type: 'switch',
|
||||
condition: { field: 'operation', value: 'okta_list_apps' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
// Admin role params
|
||||
{
|
||||
id: 'roleType',
|
||||
title: 'Role Type',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Super Admin', id: 'SUPER_ADMIN' },
|
||||
{ label: 'Org Admin', id: 'ORG_ADMIN' },
|
||||
{ label: 'App Admin', id: 'APP_ADMIN' },
|
||||
{ label: 'User Admin', id: 'USER_ADMIN' },
|
||||
{ label: 'Help Desk Admin', id: 'HELP_DESK_ADMIN' },
|
||||
{ label: 'Read Only Admin', id: 'READ_ONLY_ADMIN' },
|
||||
{ label: 'API Access Management Admin', id: 'API_ACCESS_MANAGEMENT_ADMIN' },
|
||||
{ label: 'Group Membership Admin', id: 'GROUP_MEMBERSHIP_ADMIN' },
|
||||
{ label: 'Report Admin', id: 'REPORT_ADMIN' },
|
||||
{ label: 'Workflows Admin', id: 'WORKFLOWS_ADMIN' },
|
||||
{ label: 'Access Certifications Admin', id: 'ACCESS_CERTIFICATIONS_ADMIN' },
|
||||
{ label: 'Access Requests Admin', id: 'ACCESS_REQUESTS_ADMIN' },
|
||||
{ label: 'Custom', id: 'CUSTOM' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'okta_assign_user_role' },
|
||||
required: { field: 'operation', value: 'okta_assign_user_role' },
|
||||
},
|
||||
{
|
||||
id: 'customRoleId',
|
||||
title: 'Custom Role ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'ID of the custom role to grant',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'okta_assign_user_role',
|
||||
and: { field: 'roleType', value: 'CUSTOM' },
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: 'okta_assign_user_role',
|
||||
and: { field: 'roleType', value: 'CUSTOM' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'resourceSetId',
|
||||
title: 'Resource Set ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Resource set the custom role applies to',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'okta_assign_user_role',
|
||||
and: { field: 'roleType', value: 'CUSTOM' },
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: 'okta_assign_user_role',
|
||||
and: { field: 'roleType', value: 'CUSTOM' },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'disableNotifications',
|
||||
title: 'Third-Party Admin',
|
||||
type: 'switch',
|
||||
condition: { field: 'operation', value: 'okta_assign_user_role' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'roleAssignmentId',
|
||||
title: 'Role Assignment ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Role assignment ID from List User Roles',
|
||||
condition: { field: 'operation', value: 'okta_remove_user_role' },
|
||||
required: { field: 'operation', value: 'okta_remove_user_role' },
|
||||
},
|
||||
// System Log params
|
||||
{
|
||||
id: 'since',
|
||||
title: 'Since',
|
||||
type: 'short-input',
|
||||
placeholder: '2026-08-01T00:00:00.000Z',
|
||||
condition: { field: 'operation', value: 'okta_get_logs' },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
placeholder: 'Describe the start of the time window',
|
||||
prompt:
|
||||
'Generate an ISO 8601 UTC timestamp for the start of a System Log query window, for example 2026-08-01T00:00:00.000Z. Return ONLY the timestamp - no explanations, no extra text.',
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'until',
|
||||
title: 'Until',
|
||||
type: 'short-input',
|
||||
placeholder: '2026-08-15T00:00:00.000Z',
|
||||
condition: { field: 'operation', value: 'okta_get_logs' },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
placeholder: 'Describe the end of the time window',
|
||||
prompt:
|
||||
'Generate an ISO 8601 UTC timestamp for the end of a System Log query window, for example 2026-08-15T00:00:00.000Z. Return ONLY the timestamp - no explanations, no extra text.',
|
||||
generationType: 'timestamp',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'sortOrder',
|
||||
title: 'Sort Order',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'Ascending', id: 'ASCENDING' },
|
||||
{ label: 'Descending', id: 'DESCENDING' },
|
||||
],
|
||||
condition: { field: 'operation', value: 'okta_get_logs' },
|
||||
mode: 'advanced',
|
||||
},
|
||||
// Pagination
|
||||
{
|
||||
id: 'limit',
|
||||
@@ -307,7 +843,36 @@ export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
placeholder: 'Max results to return',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['okta_list_users', 'okta_list_groups', 'okta_list_group_members'],
|
||||
value: [
|
||||
'okta_list_users',
|
||||
'okta_list_groups',
|
||||
'okta_list_group_members',
|
||||
'okta_get_logs',
|
||||
'okta_list_apps',
|
||||
'okta_list_app_users',
|
||||
'okta_list_app_groups',
|
||||
'okta_list_group_rules',
|
||||
],
|
||||
},
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'after',
|
||||
title: 'Cursor',
|
||||
type: 'short-input',
|
||||
placeholder: 'nextCursor from a previous run',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'okta_list_users',
|
||||
'okta_list_groups',
|
||||
'okta_list_group_members',
|
||||
'okta_get_logs',
|
||||
'okta_list_apps',
|
||||
'okta_list_app_users',
|
||||
'okta_list_app_groups',
|
||||
'okta_list_group_rules',
|
||||
],
|
||||
},
|
||||
mode: 'advanced',
|
||||
},
|
||||
@@ -333,38 +898,69 @@ export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
'okta_add_user_to_group',
|
||||
'okta_remove_user_from_group',
|
||||
'okta_list_group_members',
|
||||
'okta_list_group_rules',
|
||||
'okta_get_group_rule',
|
||||
'okta_create_group_rule',
|
||||
'okta_activate_group_rule',
|
||||
'okta_deactivate_group_rule',
|
||||
'okta_delete_group_rule',
|
||||
'okta_list_factors',
|
||||
'okta_get_factor',
|
||||
'okta_enroll_factor',
|
||||
'okta_reset_factor',
|
||||
'okta_reset_all_factors',
|
||||
'okta_clear_user_sessions',
|
||||
'okta_get_session',
|
||||
'okta_revoke_session',
|
||||
'okta_list_apps',
|
||||
'okta_get_app',
|
||||
'okta_list_app_users',
|
||||
'okta_assign_user_to_app',
|
||||
'okta_remove_user_from_app',
|
||||
'okta_list_app_groups',
|
||||
'okta_assign_group_to_app',
|
||||
'okta_remove_group_from_app',
|
||||
'okta_list_user_roles',
|
||||
'okta_assign_user_role',
|
||||
'okta_remove_user_role',
|
||||
'okta_get_logs',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => params.operation as string,
|
||||
/**
|
||||
* Every param this block can send is assigned here, including the ones it
|
||||
* decides to drop.
|
||||
*
|
||||
* The executor merges the result over the raw serialized inputs
|
||||
* (`{ ...inputs, ...transformedParams }`), so a key this function *omits*
|
||||
* keeps the raw subBlock string instead of being dropped. Assigning
|
||||
* `undefined` is what actually removes it: otherwise a non-numeric `limit`
|
||||
* would still reach Okta verbatim, and a blank field in a partial update
|
||||
* (`update_user` is a POST merge) would overwrite the stored Okta value
|
||||
* with an empty string rather than leaving it untouched.
|
||||
*/
|
||||
params: (params) => {
|
||||
const result: Record<string, unknown> = {
|
||||
apiKey: params.apiKey,
|
||||
domain: params.domain,
|
||||
limit: toFiniteNumber(params.limit),
|
||||
priority: toFiniteNumber(params.priority),
|
||||
// Group-specific UI fields carry the tool's generic param names.
|
||||
name: blankToUndefined(params.groupName),
|
||||
description: blankToUndefined(params.groupDescription),
|
||||
}
|
||||
|
||||
if (params.limit) result.limit = Number(params.limit)
|
||||
|
||||
// Map group-specific UI fields to tool param names
|
||||
if (params.groupName) result.name = params.groupName
|
||||
if (params.groupDescription !== undefined) result.description = params.groupDescription
|
||||
|
||||
// Pass through all other params, skipping empty values. Blank fields in a
|
||||
// partial update (e.g. update_user, a POST merge) must be omitted so they
|
||||
// leave the existing Okta value unchanged rather than overwriting it with
|
||||
// an empty string. This mirrors the agent tool-call path, which already
|
||||
// filters empty params before execution.
|
||||
const skipKeys = new Set([
|
||||
const mappedKeys = new Set([
|
||||
'operation',
|
||||
'apiKey',
|
||||
'domain',
|
||||
'limit',
|
||||
'priority',
|
||||
'groupName',
|
||||
'groupDescription',
|
||||
])
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (!skipKeys.has(key) && value !== undefined && value !== null && value !== '') {
|
||||
result[key] = value
|
||||
}
|
||||
if (!mappedKeys.has(key)) result[key] = blankToUndefined(value)
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -393,6 +989,53 @@ export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
groupName: { type: 'string', description: 'Group name' },
|
||||
groupDescription: { type: 'string', description: 'Group description' },
|
||||
sendEmail: { type: 'boolean', description: 'Whether to send email notification' },
|
||||
q: { type: 'string', description: 'Keyword search query' },
|
||||
after: { type: 'string', description: 'Cursor for the next page of results' },
|
||||
since: { type: 'string', description: 'Start of the System Log time window' },
|
||||
until: { type: 'string', description: 'End of the System Log time window' },
|
||||
sortOrder: { type: 'string', description: 'System Log sort order' },
|
||||
sessionId: { type: 'string', description: 'Session ID' },
|
||||
oauthTokens: { type: 'boolean', description: 'Also revoke OAuth and OIDC tokens' },
|
||||
forgetDevices: { type: 'boolean', description: 'Clear remembered factors on all devices' },
|
||||
factorId: { type: 'string', description: 'MFA factor ID' },
|
||||
factorType: { type: 'string', description: 'MFA factor type to enroll' },
|
||||
provider: { type: 'string', description: 'MFA factor provider' },
|
||||
phoneNumber: { type: 'string', description: 'Phone number for the sms or call factor' },
|
||||
factorEmail: { type: 'string', description: 'Email address for the email factor' },
|
||||
securityQuestion: { type: 'string', description: 'Security question key' },
|
||||
securityAnswer: { type: 'string', description: 'Security question answer' },
|
||||
removeRecoveryEnrollment: {
|
||||
type: 'boolean',
|
||||
description: 'Also remove the phone number as a recovery method',
|
||||
},
|
||||
appId: { type: 'string', description: 'Application ID' },
|
||||
appUserName: { type: 'string', description: 'Username the user signs in to the app with' },
|
||||
scope: { type: 'string', description: 'Application assignment scope' },
|
||||
priority: { type: 'number', description: 'Application group assignment priority' },
|
||||
includeNonDeleted: {
|
||||
type: 'boolean',
|
||||
description: 'Include applications that are not deleted',
|
||||
},
|
||||
roleType: { type: 'string', description: 'Admin role type to assign' },
|
||||
customRoleId: { type: 'string', description: 'Custom role ID' },
|
||||
resourceSetId: { type: 'string', description: 'Resource set ID for a custom role' },
|
||||
disableNotifications: { type: 'boolean', description: 'Grant third-party admin status' },
|
||||
roleAssignmentId: { type: 'string', description: 'Role assignment ID to revoke' },
|
||||
groupRuleId: { type: 'string', description: 'Group rule ID' },
|
||||
ruleName: { type: 'string', description: 'Group rule name' },
|
||||
expression: { type: 'string', description: 'Okta expression driving the group rule' },
|
||||
assignUserToGroupIds: {
|
||||
type: 'string',
|
||||
description: 'Comma-separated group IDs the rule assigns users to',
|
||||
},
|
||||
excludedUserIds: {
|
||||
type: 'string',
|
||||
description: 'Comma-separated user IDs excluded from the rule',
|
||||
},
|
||||
removeUsers: {
|
||||
type: 'boolean',
|
||||
description: 'Remove users the rule assigned when deleting it',
|
||||
},
|
||||
},
|
||||
|
||||
outputs: {
|
||||
@@ -420,6 +1063,24 @@ export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
name: { type: 'string', description: 'Group name' },
|
||||
description: { type: 'string', description: 'Group description' },
|
||||
type: { type: 'string', description: 'Group type' },
|
||||
mobilePhone: { type: 'string', description: 'Mobile phone number' },
|
||||
secondEmail: { type: 'string', description: 'Secondary email address' },
|
||||
displayName: { type: 'string', description: 'Display name' },
|
||||
title: { type: 'string', description: 'Job title' },
|
||||
department: { type: 'string', description: 'Department' },
|
||||
organization: { type: 'string', description: 'Organization' },
|
||||
manager: { type: 'string', description: 'Manager name' },
|
||||
managerId: { type: 'string', description: 'Manager ID' },
|
||||
division: { type: 'string', description: 'Division' },
|
||||
employeeNumber: { type: 'string', description: 'Employee number' },
|
||||
userType: { type: 'string', description: 'User type' },
|
||||
lastLogin: { type: 'string', description: 'Last sign-in timestamp' },
|
||||
statusChanged: { type: 'string', description: 'Status change timestamp' },
|
||||
passwordChanged: { type: 'string', description: 'Password change timestamp' },
|
||||
lastMembershipUpdated: {
|
||||
type: 'string',
|
||||
description: 'Timestamp of the last group membership change',
|
||||
},
|
||||
count: { type: 'number', description: 'Number of results' },
|
||||
added: { type: 'boolean', description: 'Whether user was added to group' },
|
||||
removed: { type: 'boolean', description: 'Whether user was removed from group' },
|
||||
@@ -436,6 +1097,91 @@ export const OktaBlock: BlockConfig<OktaResponse> = {
|
||||
},
|
||||
created: { type: 'string', description: 'Creation timestamp' },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp' },
|
||||
events: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Array of System Log events (uuid, published, eventType, severity, displayMessage, outcomeResult, actor and client details, targets)',
|
||||
},
|
||||
factors: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Array of enrolled MFA factors (id, factorType, provider, vendorName, status, created, lastUpdated, profile)',
|
||||
},
|
||||
apps: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Array of applications (id, name, label, status, signOnMode, features, created, lastUpdated)',
|
||||
},
|
||||
appUsers: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Array of application user assignments (id, externalId, scope, status, syncState, userName, profile)',
|
||||
},
|
||||
appGroups: {
|
||||
type: 'json',
|
||||
description: 'Array of application group assignments (id, priority, lastUpdated, profile)',
|
||||
},
|
||||
roles: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Array of admin role assignments (id, label, type, status, assignmentType, role, resourceSet)',
|
||||
},
|
||||
rules: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Array of group rules (id, name, type, status, expression, assignUserToGroupIds, excludedUserIds)',
|
||||
},
|
||||
profile: { type: 'json', description: 'Factor, application, or app user profile attributes' },
|
||||
settings: { type: 'json', description: 'Application settings' },
|
||||
visibility: { type: 'json', description: 'Application visibility settings' },
|
||||
accessibility: { type: 'json', description: 'Application accessibility settings' },
|
||||
assignUserToGroupIds: { type: 'json', description: 'Groups a rule assigns matching users to' },
|
||||
excludedUserIds: { type: 'json', description: 'Users excluded from a group rule' },
|
||||
excludedGroupIds: { type: 'json', description: 'Groups excluded from a group rule' },
|
||||
amr: { type: 'json', description: 'Authentication methods used to establish a session' },
|
||||
features: { type: 'json', description: 'Provisioning features enabled on an application' },
|
||||
label: { type: 'string', description: 'Application or role label' },
|
||||
signOnMode: { type: 'string', description: 'Application sign-on mode' },
|
||||
factorType: { type: 'string', description: 'MFA factor type' },
|
||||
provider: { type: 'string', description: 'MFA factor provider' },
|
||||
vendorName: { type: 'string', description: 'MFA factor vendor name' },
|
||||
scope: { type: 'string', description: 'Application assignment scope' },
|
||||
externalId: { type: 'string', description: 'ID of the user in the downstream application' },
|
||||
syncState: { type: 'string', description: 'Application provisioning sync state' },
|
||||
lastSync: { type: 'string', description: 'Last application provisioning sync' },
|
||||
userName: { type: 'string', description: 'Username the user signs in to the application with' },
|
||||
priority: { type: 'number', description: 'Application group assignment priority' },
|
||||
assignmentType: { type: 'string', description: 'How an admin role was assigned' },
|
||||
role: { type: 'string', description: 'Custom role ID' },
|
||||
resourceSet: { type: 'string', description: 'Resource set ID for a custom role' },
|
||||
expression: { type: 'string', description: 'Okta expression driving a group rule' },
|
||||
expressionType: { type: 'string', description: 'Group rule expression language' },
|
||||
userId: { type: 'string', description: 'User ID the operation acted on' },
|
||||
groupId: { type: 'string', description: 'Group ID the operation acted on' },
|
||||
appId: { type: 'string', description: 'Application ID the operation acted on' },
|
||||
factorId: { type: 'string', description: 'Factor ID the operation acted on' },
|
||||
sessionId: { type: 'string', description: 'Session ID the operation acted on' },
|
||||
groupRuleId: { type: 'string', description: 'Group rule ID the operation acted on' },
|
||||
roleAssignmentId: { type: 'string', description: 'Role assignment ID that was revoked' },
|
||||
createdAt: { type: 'string', description: 'Session creation timestamp' },
|
||||
expiresAt: { type: 'string', description: 'Session expiry timestamp' },
|
||||
lastPasswordVerification: {
|
||||
type: 'string',
|
||||
description: 'Timestamp of the last password verification',
|
||||
},
|
||||
lastFactorVerification: {
|
||||
type: 'string',
|
||||
description: 'Timestamp of the last factor verification',
|
||||
},
|
||||
idpId: { type: 'string', description: 'Identity provider ID' },
|
||||
idpType: { type: 'string', description: 'Identity provider type' },
|
||||
nextCursor: { type: 'string', description: 'Cursor for the next page of results' },
|
||||
hasMore: { type: 'boolean', description: 'Whether more results are available' },
|
||||
assigned: { type: 'boolean', description: 'Whether the assignment was created' },
|
||||
enrolled: { type: 'boolean', description: 'Whether the factor was enrolled' },
|
||||
reset: { type: 'boolean', description: 'Whether the factors were reset' },
|
||||
cleared: { type: 'boolean', description: 'Whether the sessions were cleared' },
|
||||
revoked: { type: 'boolean', description: 'Whether the session was revoked' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -532,6 +1278,24 @@ export const OktaBlockMeta = {
|
||||
content:
|
||||
'# Audit Group Membership\n\nReview who belongs to Okta groups, focusing on privileged access.\n\n## Steps\n1. Run List Groups to enumerate the groups, or Get Group for a specific one.\n2. For each group of interest, run List Group Members.\n3. Highlight privileged or admin groups and call out any unexpected members.\n\n## Output\nA per-group roster with member counts, and a short list of access concerns to review.',
|
||||
},
|
||||
{
|
||||
name: 'reset-user-mfa',
|
||||
description: 'Reset a locked-out user MFA enrollment so they can enroll a factor again.',
|
||||
content:
|
||||
'# Reset User MFA\n\nClear a stuck multifactor enrollment, the most common Okta help desk request.\n\n## Steps\n1. Run List Factors for the user to see which factors are enrolled and their status.\n2. Reset the narrowest thing that fixes it: Reset Factor for one factor id, or Reset All Factors when every enrollment must go.\n3. Note that resetting push also unenrolls the related Okta Verify factors, and that factors cannot be reset on a deactivated user.\n4. Run Clear User Sessions if the user must be signed out of existing sessions before re-enrolling.\n\n## Output\nName the factors that were reset and state that the user must re-enroll before they can complete MFA again. Both resets are irreversible, so confirm the target user before running either.',
|
||||
},
|
||||
{
|
||||
name: 'investigate-sign-in-failures',
|
||||
description: 'Query the Okta System Log for failed sign-ins and suspicious authentication.',
|
||||
content:
|
||||
'# Investigate Sign-In Failures\n\nUse the System Log to explain why a user cannot sign in, or to review suspicious authentication.\n\n## Steps\n1. Run Get System Log Events with a Since and Until that bracket the incident.\n2. Filter to the events that matter, for example eventType eq "user.session.start" and outcome.result eq "FAILURE" for failed sign-ins.\n3. Narrow to one person or origin by adding actor.alternateId or client.ipAddress to the filter, or use the keyword query for a free-text sweep.\n4. Read outcomeReason, clientIpAddress, and the client geography on each event to separate a wrong password from an unexpected location.\n5. Page with the returned cursor while hasMore is true when the window is wide.\n\n## Output\nA short timeline of the matching events with actor, time, outcome, reason, and source IP, plus a plain statement of the likely cause.',
|
||||
},
|
||||
{
|
||||
name: 'review-app-access',
|
||||
description: 'Audit who can reach an Okta application through direct and group assignments.',
|
||||
content:
|
||||
'# Review Application Access\n\nEstablish who has access to an application and how they got it.\n\n## Steps\n1. Run List Applications to resolve the application id, or Get Application when the id is known.\n2. Run List Application Users and read the scope on each assignment: USER means a direct grant, GROUP means it was inherited.\n3. Run List Application Groups to see which groups confer access, since every member of those groups reaches the app.\n4. Expand any group of interest with List Group Members to get the real roster.\n5. Revoke with Remove User from Application for a direct grant, or Remove Group from Application to cut the whole group.\n\n## Output\nA roster split into direct and group-inherited access, naming the groups that grant it, and a list of assignments that look unjustified.',
|
||||
},
|
||||
{
|
||||
name: 'reset-user-password',
|
||||
description: 'Trigger an Okta password reset for a user who is locked out.',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"updatedAt": "2026-08-14",
|
||||
"updatedAt": "2026-08-15",
|
||||
"integrations": [
|
||||
{
|
||||
"type": "onepassword",
|
||||
@@ -13723,8 +13723,8 @@
|
||||
"type": "okta",
|
||||
"slug": "okta",
|
||||
"name": "Okta",
|
||||
"description": "Manage users and groups in Okta",
|
||||
"longDescription": "Integrate Okta identity management into your workflow. List, create, update, activate, suspend, and delete users. Reset passwords. Manage groups and group membership.",
|
||||
"description": "Manage users, groups, apps, and MFA in Okta",
|
||||
"longDescription": "Integrate Okta identity management into your workflow. Manage users, groups, and group rules. Run service desk actions like resetting MFA factors and clearing sessions. Review and change application assignments and admin roles. Query the System Log to audit sign-ins and admin changes.",
|
||||
"bgColor": "#191919",
|
||||
"iconName": "OktaIcon",
|
||||
"docsUrl": "https://docs.sim.ai/integrations/okta",
|
||||
@@ -13800,9 +13800,113 @@
|
||||
{
|
||||
"name": "List Group Members",
|
||||
"description": "List all members of a specific group in your Okta organization"
|
||||
},
|
||||
{
|
||||
"name": "List Group Rules",
|
||||
"description": "List the group rules in your Okta organization. Each rule assigns users to groups automatically based on an expression over their profile, so this shows how group membership is being driven."
|
||||
},
|
||||
{
|
||||
"name": "Get Group Rule",
|
||||
"description": "Retrieve a single Okta group rule by ID, including the expression that decides which users it matches and the groups those users are assigned to."
|
||||
},
|
||||
{
|
||||
"name": "Create Group Rule",
|
||||
"description": "Create a group rule that automatically assigns users matching an Okta expression to one or more groups. New rules are created INACTIVE, so run Activate Group Rule afterwards to start applying it."
|
||||
},
|
||||
{
|
||||
"name": "Activate Group Rule",
|
||||
"description": "Activate a group rule so Okta starts applying it, assigning every matching user to the target groups."
|
||||
},
|
||||
{
|
||||
"name": "Deactivate Group Rule",
|
||||
"description": "Deactivate a group rule so Okta stops applying it. Existing memberships the rule created are left in place. A rule must be INACTIVE before it can be edited."
|
||||
},
|
||||
{
|
||||
"name": "Delete Group Rule",
|
||||
"description": "Permanently delete a group rule. Destructive and irreversible. Optionally also removes the users that this rule had assigned from those groups, which revokes any access those groups grant."
|
||||
},
|
||||
{
|
||||
"name": "List Factors",
|
||||
"description": "List the MFA factors a user has enrolled, with each factor type, provider, and enrollment status. Use this before resetting a factor to confirm which one to target."
|
||||
},
|
||||
{
|
||||
"name": "Get Factor",
|
||||
"description": "Retrieve a single enrolled MFA factor for a user, including its type, provider, enrollment status, and factor-specific profile."
|
||||
},
|
||||
{
|
||||
"name": "Enroll Factor",
|
||||
"description": "Enroll an MFA factor for a user. The profile fields required depend on the factor type: a phone number for sms and call, an email address for email, and a question and answer for question. Factors that enroll from the user device, such as webauthn and push, need no profile fields."
|
||||
},
|
||||
{
|
||||
"name": "Reset Factor",
|
||||
"description": "Unenroll one specific MFA factor for a user so they can re-enroll it. Destructive and irreversible: the existing enrollment is removed. Unenrolling a push or signed_nonce factor also unenrolls the related Okta Verify factors. Factors cannot be unenrolled from a deactivated user."
|
||||
},
|
||||
{
|
||||
"name": "Reset All Factors",
|
||||
"description": "Reset every MFA factor for a user, returning all enrollments to the unenrolled state. Destructive and irreversible: the user must re-enroll each factor before they can complete MFA again. The user status stays ACTIVE."
|
||||
},
|
||||
{
|
||||
"name": "Clear User Sessions",
|
||||
"description": "Revoke every active Okta session for a user, signing them out of all devices immediately. Destructive and irreversible: the user must sign in again. Optionally also revokes their OAuth and OpenID Connect tokens, and clears remembered factors."
|
||||
},
|
||||
{
|
||||
"name": "Get Session",
|
||||
"description": "Retrieve an Okta session by ID, including who it belongs to, when it expires, and which authentication methods were used to establish it."
|
||||
},
|
||||
{
|
||||
"name": "Revoke Session",
|
||||
"description": "Revoke a single Okta session by ID, ending that sign-in immediately. Destructive and irreversible: the affected user must sign in again on that device."
|
||||
},
|
||||
{
|
||||
"name": "List Applications",
|
||||
"description": "List the applications configured in your Okta organization, with optional name search, filtering, and cursor pagination."
|
||||
},
|
||||
{
|
||||
"name": "Get Application",
|
||||
"description": "Retrieve a single Okta application by ID, including its sign-on mode, status, enabled provisioning features, and configuration objects."
|
||||
},
|
||||
{
|
||||
"name": "List Application Users",
|
||||
"description": "List the users assigned to an Okta application, including how each assignment was made and its provisioning sync state. Use this to audit who has access to an app."
|
||||
},
|
||||
{
|
||||
"name": "Assign User to Application",
|
||||
"description": "Assign a user to an Okta application, granting them access to it. Applications that require credentials also need the username the user signs in with."
|
||||
},
|
||||
{
|
||||
"name": "Remove User from Application",
|
||||
"description": "Unassign a user from an Okta application, revoking their access. Destructive and irreversible: the app profile for that user is permanently removed, and if provisioning is enabled the downstream account is deactivated."
|
||||
},
|
||||
{
|
||||
"name": "List Application Groups",
|
||||
"description": "List the groups assigned to an Okta application. Every member of an assigned group inherits access to the app, so this is the starting point for an app access review."
|
||||
},
|
||||
{
|
||||
"name": "Assign Group to Application",
|
||||
"description": "Assign a group to an Okta application so every member of the group inherits access to it."
|
||||
},
|
||||
{
|
||||
"name": "Remove Group from Application",
|
||||
"description": "Unassign a group from an Okta application. Destructive: every member who had access only through this group loses access to the app."
|
||||
},
|
||||
{
|
||||
"name": "List User Roles",
|
||||
"description": "List the administrator roles assigned to a user. Returns both standard roles and custom role bindings, so you can review who holds privileged access."
|
||||
},
|
||||
{
|
||||
"name": "Assign User Role",
|
||||
"description": "Grant a user an administrator role. Use a standard role type such as USER_ADMIN or HELP_DESK_ADMIN, or CUSTOM together with a custom role ID and a resource set ID. This grants privileged access, so confirm the role is the least privilege that fits."
|
||||
},
|
||||
{
|
||||
"name": "Remove User Role",
|
||||
"description": "Revoke an administrator role from a user. Destructive: the user immediately loses the admin permissions that role granted. Takes the role assignment ID, not the role type, which List User Roles returns as the role id field."
|
||||
},
|
||||
{
|
||||
"name": "Get System Log Events",
|
||||
"description": "Query the Okta System Log for sign-ins, admin changes, and security events. Supports a time window, SCIM filter expressions, keyword search, and cursor pagination for audit and investigation workflows."
|
||||
}
|
||||
],
|
||||
"operationCount": 18,
|
||||
"operationCount": 44,
|
||||
"triggers": [],
|
||||
"triggerCount": 0,
|
||||
"authType": "api-key",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,69 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type { OktaActivateGroupRuleParams, OktaActivateGroupRuleResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaActivateGroupRule')
|
||||
|
||||
export const oktaActivateGroupRuleTool: ToolConfig<
|
||||
OktaActivateGroupRuleParams,
|
||||
OktaActivateGroupRuleResponse
|
||||
> = {
|
||||
id: 'okta_activate_group_rule',
|
||||
name: 'Activate Group Rule in Okta',
|
||||
description:
|
||||
'Activate a group rule so Okta starts applying it, assigning every matching user to the target groups.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
groupRuleId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group rule ID to activate',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/groups/rules/${encodeURIComponent(params.groupRuleId.trim())}/lifecycle/activate`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to activate group rule in Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
groupRuleId: params?.groupRuleId ?? '',
|
||||
activated: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
groupRuleId: { type: 'string', description: 'Activated group rule ID' },
|
||||
activated: { type: 'boolean', description: 'Whether the rule was activated' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaActivateUserParams,
|
||||
OktaActivateUserResponse,
|
||||
OktaApiError,
|
||||
} from '@/tools/okta/types'
|
||||
import type { OktaActivateUserParams, OktaActivateUserResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaActivateUser')
|
||||
@@ -50,23 +47,12 @@ export const oktaActivateUserTool: ToolConfig<OktaActivateUserParams, OktaActiva
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/lifecycle/activate?sendEmail=${sendEmail}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// empty response body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to activate user in Okta')
|
||||
await throwOktaError(response, logger, 'Failed to activate user in Okta')
|
||||
}
|
||||
|
||||
let activationUrl: string | null = null
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaAddUserToGroupParams,
|
||||
OktaAddUserToGroupResponse,
|
||||
OktaApiError,
|
||||
} from '@/tools/okta/types'
|
||||
import type { OktaAddUserToGroupParams, OktaAddUserToGroupResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaAddUserToGroup')
|
||||
@@ -51,23 +48,12 @@ export const oktaAddUserToGroupTool: ToolConfig<
|
||||
return `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId.trim())}/users/${encodeURIComponent(params.userId.trim())}`
|
||||
},
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// empty response body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to add user to group in Okta')
|
||||
await throwOktaError(response, logger, 'Failed to add user to group in Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaAppGroupAssignment,
|
||||
OktaAssignGroupToAppParams,
|
||||
OktaAssignGroupToAppResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaAssignGroupToApp')
|
||||
|
||||
export const oktaAssignGroupToAppTool: ToolConfig<
|
||||
OktaAssignGroupToAppParams,
|
||||
OktaAssignGroupToAppResponse
|
||||
> = {
|
||||
id: 'okta_assign_group_to_app',
|
||||
name: 'Assign Group to Application in Okta',
|
||||
description:
|
||||
'Assign a group to an Okta application so every member of the group inherits access to it.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
appId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Application ID to assign the group to',
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group ID to assign',
|
||||
},
|
||||
priority: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Assignment priority, which resolves conflicting profile mappings when a user belongs to several assigned groups',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/apps/${encodeURIComponent(params.appId.trim())}/groups/${encodeURIComponent(params.groupId.trim())}`
|
||||
},
|
||||
method: 'PUT',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
body: (params) => (params.priority === undefined ? {} : { priority: params.priority }),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to assign group to application in Okta')
|
||||
}
|
||||
|
||||
const assignment: OktaAppGroupAssignment = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: assignment.id,
|
||||
priority: assignment.priority ?? null,
|
||||
lastUpdated: assignment.lastUpdated,
|
||||
profile: assignment.profile ?? null,
|
||||
assigned: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Assigned group ID' },
|
||||
priority: { type: 'number', description: 'Assignment priority', optional: true },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp' },
|
||||
profile: {
|
||||
type: 'json',
|
||||
description: 'App-specific profile attributes, whose shape is set by the app schema',
|
||||
optional: true,
|
||||
},
|
||||
assigned: { type: 'boolean', description: 'Whether the group was assigned' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaAssignUserRoleParams,
|
||||
OktaAssignUserRoleResponse,
|
||||
OktaRoleAssignment,
|
||||
} from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaAssignUserRole')
|
||||
|
||||
export const oktaAssignUserRoleTool: ToolConfig<
|
||||
OktaAssignUserRoleParams,
|
||||
OktaAssignUserRoleResponse
|
||||
> = {
|
||||
id: 'okta_assign_user_role',
|
||||
name: 'Assign User Role in Okta',
|
||||
description:
|
||||
'Grant a user an administrator role. Use a standard role type such as USER_ADMIN or HELP_DESK_ADMIN, or CUSTOM together with a custom role ID and a resource set ID. This grants privileged access, so confirm the role is the least privilege that fits.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID or login to assign the admin role to',
|
||||
},
|
||||
roleType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'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: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Custom role ID. Required when the role type is CUSTOM',
|
||||
},
|
||||
resourceSetId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Resource set ID the custom role applies to. Required when the role type is CUSTOM',
|
||||
},
|
||||
disableNotifications: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Grant the user third-party admin status, which suppresses Okta admin notifications (default: false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const base = `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/roles`
|
||||
return params.disableNotifications === undefined
|
||||
? base
|
||||
: `${base}?disableNotifications=${params.disableNotifications}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
body: (params) => {
|
||||
if (params.roleType === 'CUSTOM') {
|
||||
return {
|
||||
type: 'CUSTOM',
|
||||
role: params.customRoleId,
|
||||
'resource-set': params.resourceSetId,
|
||||
}
|
||||
}
|
||||
return { type: params.roleType }
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to assign user role in Okta')
|
||||
}
|
||||
|
||||
const role: OktaRoleAssignment = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: role.id ?? null,
|
||||
label: role.label ?? null,
|
||||
type: role.type,
|
||||
status: role.status ?? null,
|
||||
created: role.created ?? null,
|
||||
lastUpdated: role.lastUpdated ?? null,
|
||||
assignmentType: role.assignmentType ?? null,
|
||||
role: role.role ?? null,
|
||||
resourceSet: role['resource-set'] ?? null,
|
||||
assigned: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'Role assignment ID, which is what Remove User Role takes',
|
||||
optional: true,
|
||||
},
|
||||
label: { type: 'string', description: 'Role label', optional: true },
|
||||
type: { type: 'string', description: 'Assigned role type' },
|
||||
status: { type: 'string', description: 'Role status', optional: true },
|
||||
created: { type: 'string', description: 'Assignment timestamp', optional: true },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp', optional: true },
|
||||
assignmentType: {
|
||||
type: 'string',
|
||||
description: 'How the role was assigned (USER, GROUP, CLIENT)',
|
||||
optional: true,
|
||||
},
|
||||
role: { type: 'string', description: 'Custom role ID, for custom roles', optional: true },
|
||||
resourceSet: {
|
||||
type: 'string',
|
||||
description: 'Resource set ID, for custom roles',
|
||||
optional: true,
|
||||
},
|
||||
assigned: { type: 'boolean', description: 'Whether the role was assigned' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaAppUser,
|
||||
OktaAssignUserToAppParams,
|
||||
OktaAssignUserToAppResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaAssignUserToApp')
|
||||
|
||||
export const oktaAssignUserToAppTool: ToolConfig<
|
||||
OktaAssignUserToAppParams,
|
||||
OktaAssignUserToAppResponse
|
||||
> = {
|
||||
id: 'okta_assign_user_to_app',
|
||||
name: 'Assign User to Application in Okta',
|
||||
description:
|
||||
'Assign a user to an Okta application, granting them access to it. Applications that require credentials also need the username the user signs in with.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
appId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Application ID to assign the user to',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Okta user ID to assign',
|
||||
},
|
||||
scope: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Assignment scope: USER for a direct assignment, or GROUP',
|
||||
},
|
||||
appUserName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Username the user signs in to the application with. Required by applications that store credentials',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/apps/${encodeURIComponent(params.appId.trim())}/users`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = { id: params.userId }
|
||||
if (params.scope) body.scope = params.scope
|
||||
if (params.appUserName) body.credentials = { userName: params.appUserName }
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to assign user to application in Okta')
|
||||
}
|
||||
|
||||
const appUser: OktaAppUser = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: appUser.id,
|
||||
externalId: appUser.externalId ?? null,
|
||||
created: appUser.created,
|
||||
lastUpdated: appUser.lastUpdated,
|
||||
scope: appUser.scope,
|
||||
status: appUser.status,
|
||||
statusChanged: appUser.statusChanged ?? null,
|
||||
passwordChanged: appUser.passwordChanged ?? null,
|
||||
syncState: appUser.syncState ?? null,
|
||||
lastSync: appUser.lastSync ?? null,
|
||||
userName: appUser.credentials?.userName ?? null,
|
||||
profile: appUser.profile ?? null,
|
||||
assigned: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Okta user ID that was assigned' },
|
||||
externalId: {
|
||||
type: 'string',
|
||||
description: 'ID of the user in the downstream application',
|
||||
optional: true,
|
||||
},
|
||||
created: { type: 'string', description: 'Assignment creation timestamp' },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp' },
|
||||
scope: { type: 'string', description: 'Assignment scope (USER or GROUP)' },
|
||||
status: { type: 'string', description: 'Assignment status' },
|
||||
statusChanged: { type: 'string', description: 'Status change timestamp', optional: true },
|
||||
passwordChanged: {
|
||||
type: 'string',
|
||||
description: 'App password change timestamp',
|
||||
optional: true,
|
||||
},
|
||||
syncState: { type: 'string', description: 'Provisioning sync state', optional: true },
|
||||
lastSync: { type: 'string', description: 'Last provisioning sync', optional: true },
|
||||
userName: {
|
||||
type: 'string',
|
||||
description: 'Username the user signs in to the application with',
|
||||
optional: true,
|
||||
},
|
||||
profile: {
|
||||
type: 'json',
|
||||
description: 'App-specific profile attributes, whose shape is set by the app schema',
|
||||
optional: true,
|
||||
},
|
||||
assigned: { type: 'boolean', description: 'Whether the user was assigned' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type { OktaClearUserSessionsParams, OktaClearUserSessionsResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaClearUserSessions')
|
||||
|
||||
export const oktaClearUserSessionsTool: ToolConfig<
|
||||
OktaClearUserSessionsParams,
|
||||
OktaClearUserSessionsResponse
|
||||
> = {
|
||||
id: 'okta_clear_user_sessions',
|
||||
name: 'Clear User Sessions in Okta',
|
||||
description:
|
||||
'Revoke every active Okta session for a user, signing them out of all devices immediately. Destructive and irreversible: the user must sign in again. Optionally also revokes their OAuth and OpenID Connect tokens, and clears remembered factors.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID or login whose sessions will be revoked',
|
||||
},
|
||||
oauthTokens: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Also revoke the user OpenID Connect and OAuth refresh and access tokens (default: false)',
|
||||
},
|
||||
forgetDevices: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Clear the user remembered factors for all devices (default: true)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.oauthTokens !== undefined) {
|
||||
queryParams.append('oauthTokens', String(params.oauthTokens))
|
||||
}
|
||||
if (params.forgetDevices !== undefined) {
|
||||
queryParams.append('forgetDevices', String(params.forgetDevices))
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
const base = `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/sessions`
|
||||
return queryString ? `${base}?${queryString}` : base
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to clear user sessions in Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
userId: params?.userId ?? '',
|
||||
cleared: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
userId: { type: 'string', description: 'User whose sessions were revoked' },
|
||||
cleared: { type: 'boolean', description: 'Whether the sessions were revoked' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaCreateGroupParams,
|
||||
OktaCreateGroupResponse,
|
||||
OktaGroup,
|
||||
} from '@/tools/okta/types'
|
||||
import type { OktaCreateGroupParams, OktaCreateGroupResponse, OktaGroup } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaCreateGroup')
|
||||
@@ -49,11 +45,7 @@ export const oktaCreateGroupTool: ToolConfig<OktaCreateGroupParams, OktaCreateGr
|
||||
return `https://${domain}/api/v1/groups`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
body: (params) => {
|
||||
const profile: Record<string, string> = { name: params.name }
|
||||
if (params.description) profile.description = params.description
|
||||
@@ -63,14 +55,7 @@ export const oktaCreateGroupTool: ToolConfig<OktaCreateGroupParams, OktaCreateGr
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// non-JSON error body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to create group in Okta')
|
||||
await throwOktaError(response, logger, 'Failed to create group in Okta')
|
||||
}
|
||||
|
||||
const group: OktaGroup = await response.json()
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaCreateGroupRuleParams,
|
||||
OktaCreateGroupRuleResponse,
|
||||
OktaGroupRule,
|
||||
} from '@/tools/okta/types'
|
||||
import { mapOktaGroupRule, oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaCreateGroupRule')
|
||||
|
||||
/**
|
||||
* Splits a comma or newline separated ID list into trimmed, non-empty entries.
|
||||
*/
|
||||
function parseIdList(value: string | undefined): string[] {
|
||||
if (!value) return []
|
||||
return value
|
||||
.split(/[\n,]/)
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id.length > 0)
|
||||
}
|
||||
|
||||
export const oktaCreateGroupRuleTool: ToolConfig<
|
||||
OktaCreateGroupRuleParams,
|
||||
OktaCreateGroupRuleResponse
|
||||
> = {
|
||||
id: 'okta_create_group_rule',
|
||||
name: 'Create Group Rule in Okta',
|
||||
description:
|
||||
'Create a group rule that automatically assigns users matching an Okta expression to one or more groups. New rules are created INACTIVE, so run Activate Group Rule afterwards to start applying it.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
ruleName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name for the group rule (maximum 50 characters)',
|
||||
},
|
||||
expression: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Okta expression that must evaluate to a boolean (e.g., user.department=="Engineering")',
|
||||
},
|
||||
assignUserToGroupIds: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated group IDs that matching users are assigned to',
|
||||
},
|
||||
excludedUserIds: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated user IDs to exclude from the rule',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/groups/rules`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
body: (params) => {
|
||||
const conditions: Record<string, unknown> = {
|
||||
expression: {
|
||||
type: 'urn:okta:expression:1.0',
|
||||
value: params.expression,
|
||||
},
|
||||
}
|
||||
|
||||
const excludedUserIds = parseIdList(params.excludedUserIds)
|
||||
if (excludedUserIds.length > 0) {
|
||||
conditions.people = { users: { exclude: excludedUserIds } }
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'group_rule',
|
||||
name: params.ruleName,
|
||||
conditions,
|
||||
actions: {
|
||||
assignUserToGroups: { groupIds: parseIdList(params.assignUserToGroupIds) },
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to create group rule in Okta')
|
||||
}
|
||||
|
||||
const rule: OktaGroupRule = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
...mapOktaGroupRule(rule),
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Created group rule ID' },
|
||||
name: { type: 'string', description: 'Group rule name' },
|
||||
type: { type: 'string', description: 'Rule type, always group_rule' },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Rule status, which is INACTIVE for a newly created rule',
|
||||
},
|
||||
created: { type: 'string', description: 'Creation timestamp', optional: true },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp', optional: true },
|
||||
expression: {
|
||||
type: 'string',
|
||||
description: 'Okta expression that decides which users the rule matches',
|
||||
optional: true,
|
||||
},
|
||||
expressionType: {
|
||||
type: 'string',
|
||||
description: 'Expression language, typically urn:okta:expression:1.0',
|
||||
optional: true,
|
||||
},
|
||||
assignUserToGroupIds: {
|
||||
type: 'array',
|
||||
description: 'Groups that matching users are assigned to',
|
||||
items: { type: 'string', description: 'Group ID' },
|
||||
},
|
||||
excludedUserIds: {
|
||||
type: 'array',
|
||||
description: 'Users excluded from the rule',
|
||||
items: { type: 'string', description: 'User ID' },
|
||||
},
|
||||
excludedGroupIds: {
|
||||
type: 'array',
|
||||
description: 'Groups excluded from the rule',
|
||||
items: { type: 'string', description: 'Group ID' },
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaCreateUserParams,
|
||||
OktaCreateUserResponse,
|
||||
OktaUser,
|
||||
} from '@/tools/okta/types'
|
||||
import type { OktaCreateUserParams, OktaCreateUserResponse, OktaUser } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaCreateUser')
|
||||
@@ -92,11 +88,7 @@ export const oktaCreateUserTool: ToolConfig<OktaCreateUserParams, OktaCreateUser
|
||||
return `https://${domain}/api/v1/users?activate=${activate}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
body: (params) => {
|
||||
const profile: Record<string, string> = {
|
||||
firstName: params.firstName,
|
||||
@@ -123,14 +115,7 @@ export const oktaCreateUserTool: ToolConfig<OktaCreateUserParams, OktaCreateUser
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// non-JSON error body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to create user in Okta')
|
||||
await throwOktaError(response, logger, 'Failed to create user in Okta')
|
||||
}
|
||||
|
||||
const user: OktaUser = await response.json()
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaDeactivateGroupRuleParams,
|
||||
OktaDeactivateGroupRuleResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaDeactivateGroupRule')
|
||||
|
||||
export const oktaDeactivateGroupRuleTool: ToolConfig<
|
||||
OktaDeactivateGroupRuleParams,
|
||||
OktaDeactivateGroupRuleResponse
|
||||
> = {
|
||||
id: 'okta_deactivate_group_rule',
|
||||
name: 'Deactivate Group Rule in Okta',
|
||||
description:
|
||||
'Deactivate a group rule so Okta stops applying it. Existing memberships the rule created are left in place. A rule must be INACTIVE before it can be edited.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
groupRuleId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group rule ID to deactivate',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/groups/rules/${encodeURIComponent(params.groupRuleId.trim())}/lifecycle/deactivate`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to deactivate group rule in Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
groupRuleId: params?.groupRuleId ?? '',
|
||||
deactivated: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
groupRuleId: { type: 'string', description: 'Deactivated group rule ID' },
|
||||
deactivated: { type: 'boolean', description: 'Whether the rule was deactivated' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaDeactivateUserParams,
|
||||
OktaDeactivateUserResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import type { OktaDeactivateUserParams, OktaDeactivateUserResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaDeactivateUser')
|
||||
@@ -53,23 +50,12 @@ export const oktaDeactivateUserTool: ToolConfig<
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/lifecycle/deactivate?sendEmail=${sendEmail}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// empty response body on some error codes
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to deactivate user in Okta')
|
||||
await throwOktaError(response, logger, 'Failed to deactivate user in Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaDeleteGroupParams,
|
||||
OktaDeleteGroupResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import type { OktaDeleteGroupParams, OktaDeleteGroupResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaDeleteGroup')
|
||||
@@ -43,23 +40,12 @@ export const oktaDeleteGroupTool: ToolConfig<OktaDeleteGroupParams, OktaDeleteGr
|
||||
return `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId.trim())}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// empty response body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to delete group from Okta')
|
||||
await throwOktaError(response, logger, 'Failed to delete group from Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type { OktaDeleteGroupRuleParams, OktaDeleteGroupRuleResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaDeleteGroupRule')
|
||||
|
||||
export const oktaDeleteGroupRuleTool: ToolConfig<
|
||||
OktaDeleteGroupRuleParams,
|
||||
OktaDeleteGroupRuleResponse
|
||||
> = {
|
||||
id: 'okta_delete_group_rule',
|
||||
name: 'Delete Group Rule in Okta',
|
||||
description:
|
||||
'Permanently delete a group rule. Destructive and irreversible. Optionally also removes the users that this rule had assigned from those groups, which revokes any access those groups grant.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
groupRuleId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group rule ID to delete',
|
||||
},
|
||||
removeUsers: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Also remove the users this rule assigned from the groups it targeted (default: false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const base = `https://${domain}/api/v1/groups/rules/${encodeURIComponent(params.groupRuleId.trim())}`
|
||||
return params.removeUsers === undefined ? base : `${base}?removeUsers=${params.removeUsers}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to delete group rule in Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
groupRuleId: params?.groupRuleId ?? '',
|
||||
deleted: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
groupRuleId: { type: 'string', description: 'Deleted group rule ID' },
|
||||
deleted: { type: 'boolean', description: 'Whether the rule was deleted' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type { OktaApiError, OktaDeleteUserParams, OktaDeleteUserResponse } from '@/tools/okta/types'
|
||||
import type { OktaDeleteUserParams, OktaDeleteUserResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaDeleteUser')
|
||||
@@ -46,23 +47,12 @@ export const oktaDeleteUserTool: ToolConfig<OktaDeleteUserParams, OktaDeleteUser
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}?sendEmail=${sendEmail}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// empty response body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to delete user from Okta')
|
||||
await throwOktaError(response, logger, 'Failed to delete user from Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaEnrollFactorParams,
|
||||
OktaEnrollFactorResponse,
|
||||
OktaFactor,
|
||||
} from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaEnrollFactor')
|
||||
|
||||
export const oktaEnrollFactorTool: ToolConfig<OktaEnrollFactorParams, OktaEnrollFactorResponse> = {
|
||||
id: 'okta_enroll_factor',
|
||||
name: 'Enroll Factor in Okta',
|
||||
description:
|
||||
'Enroll an MFA factor for a user. The profile fields required depend on the factor type: a phone number for sms and call, an email address for email, and a question and answer for question. Factors that enroll from the user device, such as webauthn and push, need no profile fields.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID or login to enroll the factor for',
|
||||
},
|
||||
factorType: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Factor type to enroll (sms, call, email, question, push, token:software:totp, u2f, webauthn)',
|
||||
},
|
||||
provider: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Factor provider (OKTA, GOOGLE, FIDO, DUO, RSA, SYMANTEC, YUBICO, CUSTOM). Each provider supports a subset of factor types',
|
||||
},
|
||||
phoneNumber: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Phone number in E.164 format. Required for the sms and call factor types',
|
||||
},
|
||||
factorEmail: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email address to enroll. Required for the email factor type',
|
||||
},
|
||||
securityQuestion: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Security question key (e.g., disliked_food). Required for the question factor type',
|
||||
},
|
||||
securityAnswer: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Answer to the security question, minimum 4 characters. Required for the question factor type',
|
||||
},
|
||||
activate: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Activate the factor immediately as part of enrollment. Supported by the sms, call, email, and token:hotp factor types (default: false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const base = `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/factors`
|
||||
return params.activate === undefined ? base : `${base}?activate=${params.activate}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
body: (params) => {
|
||||
const profile: Record<string, string> = {}
|
||||
|
||||
if (params.phoneNumber) profile.phoneNumber = params.phoneNumber
|
||||
if (params.factorEmail) profile.email = params.factorEmail
|
||||
if (params.securityQuestion) profile.question = params.securityQuestion
|
||||
if (params.securityAnswer) profile.answer = params.securityAnswer
|
||||
|
||||
const body: Record<string, unknown> = {
|
||||
factorType: params.factorType,
|
||||
provider: params.provider,
|
||||
}
|
||||
if (Object.keys(profile).length > 0) body.profile = profile
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to enroll factor in Okta')
|
||||
}
|
||||
|
||||
const factor: OktaFactor = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: factor.id,
|
||||
factorType: factor.factorType,
|
||||
provider: factor.provider ?? null,
|
||||
vendorName: factor.vendorName ?? null,
|
||||
status: factor.status ?? null,
|
||||
created: factor.created ?? null,
|
||||
lastUpdated: factor.lastUpdated ?? null,
|
||||
profile: factor.profile ?? null,
|
||||
enrolled: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Enrolled factor ID' },
|
||||
factorType: { type: 'string', description: 'Factor type' },
|
||||
provider: { type: 'string', description: 'Factor provider', optional: true },
|
||||
vendorName: { type: 'string', description: 'Factor vendor name', optional: true },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Enrollment status, typically PENDING_ACTIVATION until the user activates it',
|
||||
optional: true,
|
||||
},
|
||||
created: { type: 'string', description: 'Enrollment timestamp', optional: true },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp', optional: true },
|
||||
profile: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Factor-specific attributes, which vary by factor type (phone number, email, question, credential ID)',
|
||||
optional: true,
|
||||
},
|
||||
enrolled: { type: 'boolean', description: 'Whether the factor was enrolled' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type { OktaApplication, OktaGetAppParams, OktaGetAppResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaGetApp')
|
||||
|
||||
export const oktaGetAppTool: ToolConfig<OktaGetAppParams, OktaGetAppResponse> = {
|
||||
id: 'okta_get_app',
|
||||
name: 'Get Application from Okta',
|
||||
description:
|
||||
'Retrieve a single Okta application by ID, including its sign-on mode, status, enabled provisioning features, and configuration objects.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
appId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Application ID to look up',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/apps/${encodeURIComponent(params.appId.trim())}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to get application from Okta')
|
||||
}
|
||||
|
||||
const app: OktaApplication = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: app.id,
|
||||
name: app.name,
|
||||
label: app.label,
|
||||
status: app.status,
|
||||
signOnMode: app.signOnMode,
|
||||
features: app.features ?? [],
|
||||
created: app.created,
|
||||
lastUpdated: app.lastUpdated,
|
||||
accessibility: app.accessibility ?? null,
|
||||
visibility: app.visibility ?? null,
|
||||
settings: app.settings ?? null,
|
||||
profile: app.profile ?? null,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Application ID' },
|
||||
name: { type: 'string', description: 'Application name (the app template key)' },
|
||||
label: { type: 'string', description: 'Application display label' },
|
||||
status: { type: 'string', description: 'Application status (ACTIVE, INACTIVE, DELETED)' },
|
||||
signOnMode: { type: 'string', description: 'Sign-on mode' },
|
||||
features: {
|
||||
type: 'array',
|
||||
description: 'Enabled provisioning features',
|
||||
items: { type: 'string', description: 'Feature name' },
|
||||
},
|
||||
created: { type: 'string', description: 'Creation timestamp' },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp' },
|
||||
accessibility: {
|
||||
type: 'json',
|
||||
description: 'Access settings for the app',
|
||||
optional: true,
|
||||
properties: {
|
||||
errorRedirectUrl: {
|
||||
type: 'string',
|
||||
description: 'Custom error page URL',
|
||||
optional: true,
|
||||
},
|
||||
loginRedirectUrl: {
|
||||
type: 'string',
|
||||
description: 'Custom login page URL',
|
||||
optional: true,
|
||||
},
|
||||
selfService: {
|
||||
type: 'boolean',
|
||||
description: 'Whether users can self-assign the app',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
visibility: {
|
||||
type: 'json',
|
||||
description: 'Visibility settings for the app',
|
||||
optional: true,
|
||||
properties: {
|
||||
appLinks: {
|
||||
type: 'json',
|
||||
description: 'Map of app link name to whether it appears on the End-User Dashboard',
|
||||
optional: true,
|
||||
},
|
||||
autoLaunch: {
|
||||
type: 'boolean',
|
||||
description: 'Signs in to the app automatically when the user signs in to Okta',
|
||||
optional: true,
|
||||
},
|
||||
autoSubmitToolbar: {
|
||||
type: 'boolean',
|
||||
description: 'Signs in automatically when the user lands on the sign-in page',
|
||||
optional: true,
|
||||
},
|
||||
hide: {
|
||||
type: 'json',
|
||||
description: 'Which end-user apps hide this app',
|
||||
optional: true,
|
||||
properties: {
|
||||
iOS: {
|
||||
type: 'boolean',
|
||||
description: 'Hidden in Okta Mobile',
|
||||
optional: true,
|
||||
},
|
||||
web: {
|
||||
type: 'boolean',
|
||||
description: 'Hidden on the Okta End-User Dashboard',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Application settings. Okta types these per app kind, so settings.app differs between a SAML, OIDC, bookmark, or SWA app',
|
||||
optional: true,
|
||||
},
|
||||
profile: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Application profile attributes. Okta accepts any valid JSON schema here, so the shape is whatever the org configured',
|
||||
optional: true,
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type { OktaFactor, OktaGetFactorParams, OktaGetFactorResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaGetFactor')
|
||||
|
||||
export const oktaGetFactorTool: ToolConfig<OktaGetFactorParams, OktaGetFactorResponse> = {
|
||||
id: 'okta_get_factor',
|
||||
name: 'Get Factor from Okta',
|
||||
description:
|
||||
'Retrieve a single enrolled MFA factor for a user, including its type, provider, enrollment status, and factor-specific profile.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID or login the factor belongs to',
|
||||
},
|
||||
factorId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Factor ID to look up',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/factors/${encodeURIComponent(params.factorId.trim())}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to get factor from Okta')
|
||||
}
|
||||
|
||||
const factor: OktaFactor = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: factor.id,
|
||||
factorType: factor.factorType,
|
||||
provider: factor.provider ?? null,
|
||||
vendorName: factor.vendorName ?? null,
|
||||
status: factor.status ?? null,
|
||||
created: factor.created ?? null,
|
||||
lastUpdated: factor.lastUpdated ?? null,
|
||||
profile: factor.profile ?? null,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Factor ID' },
|
||||
factorType: { type: 'string', description: 'Factor type' },
|
||||
provider: { type: 'string', description: 'Factor provider', optional: true },
|
||||
vendorName: { type: 'string', description: 'Factor vendor name', optional: true },
|
||||
status: { type: 'string', description: 'Enrollment status', optional: true },
|
||||
created: { type: 'string', description: 'Enrollment timestamp', optional: true },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp', optional: true },
|
||||
profile: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Factor-specific attributes, which vary by factor type (phone number, email, question, credential ID)',
|
||||
optional: true,
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaGetGroupParams,
|
||||
OktaGetGroupResponse,
|
||||
OktaGroup,
|
||||
} from '@/tools/okta/types'
|
||||
import type { OktaGetGroupParams, OktaGetGroupResponse, OktaGroup } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaGetGroup')
|
||||
@@ -43,23 +39,12 @@ export const oktaGetGroupTool: ToolConfig<OktaGetGroupParams, OktaGetGroupRespon
|
||||
return `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId.trim())}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// non-JSON error body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to get group from Okta')
|
||||
await throwOktaError(response, logger, 'Failed to get group from Okta')
|
||||
}
|
||||
|
||||
const group: OktaGroup = await response.json()
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaGetGroupRuleParams,
|
||||
OktaGetGroupRuleResponse,
|
||||
OktaGroupRule,
|
||||
} from '@/tools/okta/types'
|
||||
import { mapOktaGroupRule, oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaGetGroupRule')
|
||||
|
||||
export const oktaGetGroupRuleTool: ToolConfig<OktaGetGroupRuleParams, OktaGetGroupRuleResponse> = {
|
||||
id: 'okta_get_group_rule',
|
||||
name: 'Get Group Rule from Okta',
|
||||
description:
|
||||
'Retrieve a single Okta group rule by ID, including the expression that decides which users it matches and the groups those users are assigned to.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
groupRuleId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group rule ID to look up',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/groups/rules/${encodeURIComponent(params.groupRuleId.trim())}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to get group rule from Okta')
|
||||
}
|
||||
|
||||
const rule: OktaGroupRule = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
...mapOktaGroupRule(rule),
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Group rule ID' },
|
||||
name: { type: 'string', description: 'Group rule name' },
|
||||
type: { type: 'string', description: 'Rule type, always group_rule' },
|
||||
status: { type: 'string', description: 'Rule status (ACTIVE, INACTIVE, INVALID)' },
|
||||
created: { type: 'string', description: 'Creation timestamp', optional: true },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp', optional: true },
|
||||
expression: {
|
||||
type: 'string',
|
||||
description: 'Okta expression that decides which users the rule matches',
|
||||
optional: true,
|
||||
},
|
||||
expressionType: {
|
||||
type: 'string',
|
||||
description: 'Expression language, typically urn:okta:expression:1.0',
|
||||
optional: true,
|
||||
},
|
||||
assignUserToGroupIds: {
|
||||
type: 'array',
|
||||
description: 'Groups that matching users are assigned to',
|
||||
items: { type: 'string', description: 'Group ID' },
|
||||
},
|
||||
excludedUserIds: {
|
||||
type: 'array',
|
||||
description: 'Users excluded from the rule',
|
||||
items: { type: 'string', description: 'User ID' },
|
||||
},
|
||||
excludedGroupIds: {
|
||||
type: 'array',
|
||||
description: 'Groups excluded from the rule',
|
||||
items: { type: 'string', description: 'Group ID' },
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type { OktaGetLogsParams, OktaGetLogsResponse, OktaLogEvent } from '@/tools/okta/types'
|
||||
import { oktaHeaders, parseOktaPagination, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaGetLogs')
|
||||
|
||||
export const oktaGetLogsTool: ToolConfig<OktaGetLogsParams, OktaGetLogsResponse> = {
|
||||
id: 'okta_get_logs',
|
||||
name: 'Get System Log Events from Okta',
|
||||
description:
|
||||
'Query the Okta System Log for sign-ins, admin changes, and security events. Supports a time window, SCIM filter expressions, keyword search, and cursor pagination for audit and investigation workflows.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
since: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Start of the query time window as an ISO 8601 timestamp (default: 7 days before "until")',
|
||||
},
|
||||
until: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'End of the query time window as an ISO 8601 timestamp (default: now)',
|
||||
},
|
||||
filter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'SCIM filter expression (e.g., eventType eq "user.session.start" or outcome.result eq "FAILURE")',
|
||||
},
|
||||
q: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Keyword search across the event payload (max 40 characters per keyword, max 10 keywords)',
|
||||
},
|
||||
sortOrder: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort order: ASCENDING (default) or DESCENDING',
|
||||
},
|
||||
after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Opaque pagination cursor returned as nextCursor by a previous call',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of events to return (default: 100, max: 1000)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.since) queryParams.append('since', params.since)
|
||||
if (params.until) queryParams.append('until', params.until)
|
||||
if (params.filter) queryParams.append('filter', params.filter)
|
||||
if (params.q) queryParams.append('q', params.q)
|
||||
if (params.sortOrder) queryParams.append('sortOrder', params.sortOrder)
|
||||
if (params.after) queryParams.append('after', params.after)
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString
|
||||
? `https://${domain}/api/v1/logs?${queryString}`
|
||||
: `https://${domain}/api/v1/logs`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to get System Log events from Okta')
|
||||
}
|
||||
|
||||
const { nextCursor, hasMore } = parseOktaPagination(response)
|
||||
const data: OktaLogEvent[] = await response.json()
|
||||
|
||||
const events = data.map((event) => ({
|
||||
uuid: event.uuid,
|
||||
published: event.published,
|
||||
eventType: event.eventType,
|
||||
severity: event.severity,
|
||||
legacyEventType: event.legacyEventType ?? null,
|
||||
displayMessage: event.displayMessage ?? null,
|
||||
outcomeResult: event.outcome?.result ?? null,
|
||||
outcomeReason: event.outcome?.reason ?? null,
|
||||
actorId: event.actor?.id ?? null,
|
||||
actorType: event.actor?.type ?? null,
|
||||
actorAlternateId: event.actor?.alternateId ?? null,
|
||||
actorDisplayName: event.actor?.displayName ?? null,
|
||||
clientIpAddress: event.client?.ipAddress ?? null,
|
||||
clientDevice: event.client?.device ?? null,
|
||||
clientZone: event.client?.zone ?? null,
|
||||
clientBrowser: event.client?.userAgent?.browser ?? null,
|
||||
clientOs: event.client?.userAgent?.os ?? null,
|
||||
clientCity: event.client?.geographicalContext?.city ?? null,
|
||||
clientState: event.client?.geographicalContext?.state ?? null,
|
||||
clientCountry: event.client?.geographicalContext?.country ?? null,
|
||||
authenticationProvider: event.authenticationContext?.authenticationProvider ?? null,
|
||||
credentialType: event.authenticationContext?.credentialType ?? null,
|
||||
externalSessionId: event.authenticationContext?.externalSessionId ?? null,
|
||||
securityAsOrg: event.securityContext?.asOrg ?? null,
|
||||
securityIsp: event.securityContext?.isp ?? null,
|
||||
securityIsProxy: event.securityContext?.isProxy ?? null,
|
||||
transactionId: event.transaction?.id ?? null,
|
||||
transactionType: event.transaction?.type ?? null,
|
||||
targets: (event.target ?? []).map((target) => ({
|
||||
id: target.id ?? null,
|
||||
type: target.type ?? null,
|
||||
alternateId: target.alternateId ?? null,
|
||||
displayName: target.displayName ?? null,
|
||||
})),
|
||||
debugData: event.debugContext?.debugData ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
events,
|
||||
count: events.length,
|
||||
nextCursor,
|
||||
hasMore,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
events: {
|
||||
type: 'array',
|
||||
description: 'Array of System Log events',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
uuid: { type: 'string', description: 'Unique event ID' },
|
||||
published: { type: 'string', description: 'Event timestamp' },
|
||||
eventType: {
|
||||
type: 'string',
|
||||
description: 'Event type (e.g., user.session.start, user.account.update_password)',
|
||||
},
|
||||
severity: { type: 'string', description: 'Event severity (DEBUG, ERROR, INFO, WARN)' },
|
||||
legacyEventType: { type: 'string', description: 'Legacy event type', optional: true },
|
||||
displayMessage: {
|
||||
type: 'string',
|
||||
description: 'Human-readable event description',
|
||||
optional: true,
|
||||
},
|
||||
outcomeResult: {
|
||||
type: 'string',
|
||||
description: 'Event outcome (SUCCESS, FAILURE, CHALLENGE, DENY, etc.)',
|
||||
optional: true,
|
||||
},
|
||||
outcomeReason: { type: 'string', description: 'Reason for the outcome', optional: true },
|
||||
actorId: { type: 'string', description: 'ID of the actor', optional: true },
|
||||
actorType: {
|
||||
type: 'string',
|
||||
description: 'Actor type (User, Client, etc.)',
|
||||
optional: true,
|
||||
},
|
||||
actorAlternateId: {
|
||||
type: 'string',
|
||||
description: 'Actor alternate ID, usually the login',
|
||||
optional: true,
|
||||
},
|
||||
actorDisplayName: { type: 'string', description: 'Actor display name', optional: true },
|
||||
clientIpAddress: { type: 'string', description: 'Client IP address', optional: true },
|
||||
clientDevice: {
|
||||
type: 'string',
|
||||
description: 'Client device category (e.g., Computer)',
|
||||
optional: true,
|
||||
},
|
||||
clientZone: { type: 'string', description: 'Network zone', optional: true },
|
||||
clientBrowser: { type: 'string', description: 'Client browser', optional: true },
|
||||
clientOs: { type: 'string', description: 'Client operating system', optional: true },
|
||||
clientCity: { type: 'string', description: 'Client city', optional: true },
|
||||
clientState: { type: 'string', description: 'Client state or region', optional: true },
|
||||
clientCountry: { type: 'string', description: 'Client country', optional: true },
|
||||
authenticationProvider: {
|
||||
type: 'string',
|
||||
description: 'Authentication provider used',
|
||||
optional: true,
|
||||
},
|
||||
credentialType: { type: 'string', description: 'Credential type used', optional: true },
|
||||
externalSessionId: {
|
||||
type: 'string',
|
||||
description: 'External session ID for correlating events',
|
||||
optional: true,
|
||||
},
|
||||
securityAsOrg: {
|
||||
type: 'string',
|
||||
description: 'Autonomous system organization',
|
||||
optional: true,
|
||||
},
|
||||
securityIsp: { type: 'string', description: 'Internet service provider', optional: true },
|
||||
securityIsProxy: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the request came through a proxy',
|
||||
optional: true,
|
||||
},
|
||||
transactionId: { type: 'string', description: 'Transaction ID', optional: true },
|
||||
transactionType: {
|
||||
type: 'string',
|
||||
description: 'Transaction type (e.g., WEB, JOB)',
|
||||
optional: true,
|
||||
},
|
||||
targets: {
|
||||
type: 'array',
|
||||
description: 'Entities the event acted upon',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Target ID', optional: true },
|
||||
type: { type: 'string', description: 'Target type', optional: true },
|
||||
alternateId: { type: 'string', description: 'Target alternate ID', optional: true },
|
||||
displayName: { type: 'string', description: 'Target display name', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
debugData: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Extra context whose keys depend on the event type. Okta states these keys and values can change between releases, so treat them as a debugging aid rather than a contract',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of events returned' },
|
||||
nextCursor: {
|
||||
type: 'string',
|
||||
description: 'Cursor for the next page, or null on the last page',
|
||||
optional: true,
|
||||
},
|
||||
hasMore: { type: 'boolean', description: 'Whether more events are available' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type { OktaGetSessionParams, OktaGetSessionResponse, OktaSession } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaGetSession')
|
||||
|
||||
export const oktaGetSessionTool: ToolConfig<OktaGetSessionParams, OktaGetSessionResponse> = {
|
||||
id: 'okta_get_session',
|
||||
name: 'Get Session from Okta',
|
||||
description:
|
||||
'Retrieve an Okta session by ID, including who it belongs to, when it expires, and which authentication methods were used to establish it.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
sessionId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Session ID to look up',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/sessions/${encodeURIComponent(params.sessionId.trim())}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to get session from Okta')
|
||||
}
|
||||
|
||||
const session: OktaSession = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: session.id,
|
||||
login: session.login ?? null,
|
||||
userId: session.userId ?? null,
|
||||
status: session.status ?? null,
|
||||
createdAt: session.createdAt ?? null,
|
||||
expiresAt: session.expiresAt ?? null,
|
||||
lastPasswordVerification: session.lastPasswordVerification ?? null,
|
||||
lastFactorVerification: session.lastFactorVerification ?? null,
|
||||
amr: session.amr ?? [],
|
||||
idpId: session.idp?.id ?? null,
|
||||
idpType: session.idp?.type ?? null,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Session ID' },
|
||||
login: { type: 'string', description: 'Login of the session user', optional: true },
|
||||
userId: { type: 'string', description: 'ID of the session user', optional: true },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Session status (ACTIVE, MFA_ENROLL, MFA_REQUIRED)',
|
||||
optional: true,
|
||||
},
|
||||
createdAt: { type: 'string', description: 'Session creation timestamp', optional: true },
|
||||
expiresAt: { type: 'string', description: 'Session expiry timestamp', optional: true },
|
||||
lastPasswordVerification: {
|
||||
type: 'string',
|
||||
description: 'Timestamp of the last password verification',
|
||||
optional: true,
|
||||
},
|
||||
lastFactorVerification: {
|
||||
type: 'string',
|
||||
description: 'Timestamp of the last factor verification',
|
||||
optional: true,
|
||||
},
|
||||
amr: {
|
||||
type: 'array',
|
||||
description: 'Authentication methods used to establish the session',
|
||||
items: { type: 'string', description: 'Authentication method reference' },
|
||||
},
|
||||
idpId: { type: 'string', description: 'Identity provider ID', optional: true },
|
||||
idpType: { type: 'string', description: 'Identity provider type', optional: true },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaGetUserParams,
|
||||
OktaGetUserResponse,
|
||||
OktaUser,
|
||||
} from '@/tools/okta/types'
|
||||
import type { OktaGetUserParams, OktaGetUserResponse, OktaUser } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaGetUser')
|
||||
@@ -43,23 +39,12 @@ export const oktaGetUserTool: ToolConfig<OktaGetUserParams, OktaGetUserResponse>
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// non-JSON error body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to get user from Okta')
|
||||
await throwOktaError(response, logger, 'Failed to get user from Okta')
|
||||
}
|
||||
|
||||
const user: OktaUser = await response.json()
|
||||
|
||||
@@ -1,17 +1,43 @@
|
||||
export { oktaActivateGroupRuleTool } from './activate_group_rule'
|
||||
export { oktaActivateUserTool } from './activate_user'
|
||||
export { oktaAddUserToGroupTool } from './add_user_to_group'
|
||||
export { oktaAssignGroupToAppTool } from './assign_group_to_app'
|
||||
export { oktaAssignUserRoleTool } from './assign_user_role'
|
||||
export { oktaAssignUserToAppTool } from './assign_user_to_app'
|
||||
export { oktaClearUserSessionsTool } from './clear_user_sessions'
|
||||
export { oktaCreateGroupTool } from './create_group'
|
||||
export { oktaCreateGroupRuleTool } from './create_group_rule'
|
||||
export { oktaCreateUserTool } from './create_user'
|
||||
export { oktaDeactivateGroupRuleTool } from './deactivate_group_rule'
|
||||
export { oktaDeactivateUserTool } from './deactivate_user'
|
||||
export { oktaDeleteGroupTool } from './delete_group'
|
||||
export { oktaDeleteGroupRuleTool } from './delete_group_rule'
|
||||
export { oktaDeleteUserTool } from './delete_user'
|
||||
export { oktaEnrollFactorTool } from './enroll_factor'
|
||||
export { oktaGetAppTool } from './get_app'
|
||||
export { oktaGetFactorTool } from './get_factor'
|
||||
export { oktaGetGroupTool } from './get_group'
|
||||
export { oktaGetGroupRuleTool } from './get_group_rule'
|
||||
export { oktaGetLogsTool } from './get_logs'
|
||||
export { oktaGetSessionTool } from './get_session'
|
||||
export { oktaGetUserTool } from './get_user'
|
||||
export { oktaListAppGroupsTool } from './list_app_groups'
|
||||
export { oktaListAppUsersTool } from './list_app_users'
|
||||
export { oktaListAppsTool } from './list_apps'
|
||||
export { oktaListFactorsTool } from './list_factors'
|
||||
export { oktaListGroupMembersTool } from './list_group_members'
|
||||
export { oktaListGroupRulesTool } from './list_group_rules'
|
||||
export { oktaListGroupsTool } from './list_groups'
|
||||
export { oktaListUserRolesTool } from './list_user_roles'
|
||||
export { oktaListUsersTool } from './list_users'
|
||||
export { oktaRemoveGroupFromAppTool } from './remove_group_from_app'
|
||||
export { oktaRemoveUserFromAppTool } from './remove_user_from_app'
|
||||
export { oktaRemoveUserFromGroupTool } from './remove_user_from_group'
|
||||
export { oktaRemoveUserRoleTool } from './remove_user_role'
|
||||
export { oktaResetAllFactorsTool } from './reset_all_factors'
|
||||
export { oktaResetFactorTool } from './reset_factor'
|
||||
export { oktaResetPasswordTool } from './reset_password'
|
||||
export { oktaRevokeSessionTool } from './revoke_session'
|
||||
export { oktaSuspendUserTool } from './suspend_user'
|
||||
export * from './types'
|
||||
export { oktaUnsuspendUserTool } from './unsuspend_user'
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaAppGroupAssignment,
|
||||
OktaListAppGroupsParams,
|
||||
OktaListAppGroupsResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import { oktaHeaders, parseOktaPagination, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaListAppGroups')
|
||||
|
||||
export const oktaListAppGroupsTool: ToolConfig<OktaListAppGroupsParams, OktaListAppGroupsResponse> =
|
||||
{
|
||||
id: 'okta_list_app_groups',
|
||||
name: 'List Application Groups from Okta',
|
||||
description:
|
||||
'List the groups assigned to an Okta application. Every member of an assigned group inherits access to the app, so this is the starting point for an app access review.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
appId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Application ID to list assigned groups for',
|
||||
},
|
||||
q: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search assigned groups whose name starts with this value',
|
||||
},
|
||||
after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Opaque pagination cursor returned as nextCursor by a previous call',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of assigned groups to return (default: 20, range: 20 to 200)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.q) queryParams.append('q', params.q)
|
||||
if (params.after) queryParams.append('after', params.after)
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
const base = `https://${domain}/api/v1/apps/${encodeURIComponent(params.appId.trim())}/groups`
|
||||
return queryString ? `${base}?${queryString}` : base
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to list application groups from Okta')
|
||||
}
|
||||
|
||||
const { nextCursor, hasMore } = parseOktaPagination(response)
|
||||
const data: OktaAppGroupAssignment[] = await response.json()
|
||||
|
||||
const appGroups = data.map((assignment) => ({
|
||||
id: assignment.id,
|
||||
priority: assignment.priority ?? null,
|
||||
lastUpdated: assignment.lastUpdated,
|
||||
profile: assignment.profile ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
appGroups,
|
||||
count: appGroups.length,
|
||||
nextCursor,
|
||||
hasMore,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
appGroups: {
|
||||
type: 'array',
|
||||
description: 'Array of application group assignments',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Assigned group ID' },
|
||||
priority: {
|
||||
type: 'number',
|
||||
description: 'Assignment priority, which resolves conflicting profile mappings',
|
||||
optional: true,
|
||||
},
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp' },
|
||||
profile: {
|
||||
type: 'json',
|
||||
description: 'App-specific profile attributes, whose shape is set by the app schema',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of assignments returned' },
|
||||
nextCursor: {
|
||||
type: 'string',
|
||||
description: 'Cursor for the next page, or null on the last page',
|
||||
optional: true,
|
||||
},
|
||||
hasMore: { type: 'boolean', description: 'Whether more assignments are available' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaAppUser,
|
||||
OktaListAppUsersParams,
|
||||
OktaListAppUsersResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import { oktaHeaders, parseOktaPagination, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaListAppUsers')
|
||||
|
||||
export const oktaListAppUsersTool: ToolConfig<OktaListAppUsersParams, OktaListAppUsersResponse> = {
|
||||
id: 'okta_list_app_users',
|
||||
name: 'List Application Users from Okta',
|
||||
description:
|
||||
'List the users assigned to an Okta application, including how each assignment was made and its provisioning sync state. Use this to audit who has access to an app.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
appId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Application ID to list assigned users for',
|
||||
},
|
||||
q: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Search assigned users whose userName, firstName, lastName, or email starts with this value',
|
||||
},
|
||||
after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Opaque pagination cursor returned as nextCursor by a previous call',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of assigned users to return (default: 50, max: 500)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.q) queryParams.append('q', params.q)
|
||||
if (params.after) queryParams.append('after', params.after)
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
const base = `https://${domain}/api/v1/apps/${encodeURIComponent(params.appId.trim())}/users`
|
||||
return queryString ? `${base}?${queryString}` : base
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to list application users from Okta')
|
||||
}
|
||||
|
||||
const { nextCursor, hasMore } = parseOktaPagination(response)
|
||||
const data: OktaAppUser[] = await response.json()
|
||||
|
||||
const appUsers = data.map((appUser) => ({
|
||||
id: appUser.id,
|
||||
externalId: appUser.externalId ?? null,
|
||||
created: appUser.created,
|
||||
lastUpdated: appUser.lastUpdated,
|
||||
scope: appUser.scope,
|
||||
status: appUser.status,
|
||||
statusChanged: appUser.statusChanged ?? null,
|
||||
passwordChanged: appUser.passwordChanged ?? null,
|
||||
syncState: appUser.syncState ?? null,
|
||||
lastSync: appUser.lastSync ?? null,
|
||||
userName: appUser.credentials?.userName ?? null,
|
||||
profile: appUser.profile ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
appUsers,
|
||||
count: appUsers.length,
|
||||
nextCursor,
|
||||
hasMore,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
appUsers: {
|
||||
type: 'array',
|
||||
description: 'Array of application user assignments',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Okta user ID' },
|
||||
externalId: {
|
||||
type: 'string',
|
||||
description: 'ID of the user in the downstream application',
|
||||
optional: true,
|
||||
},
|
||||
created: { type: 'string', description: 'Assignment creation timestamp' },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp' },
|
||||
scope: {
|
||||
type: 'string',
|
||||
description: 'How the assignment was made: USER (direct) or GROUP (inherited)',
|
||||
},
|
||||
status: { type: 'string', description: 'Assignment status' },
|
||||
statusChanged: {
|
||||
type: 'string',
|
||||
description: 'Status change timestamp',
|
||||
optional: true,
|
||||
},
|
||||
passwordChanged: {
|
||||
type: 'string',
|
||||
description: 'App password change timestamp',
|
||||
optional: true,
|
||||
},
|
||||
syncState: { type: 'string', description: 'Provisioning sync state', optional: true },
|
||||
lastSync: { type: 'string', description: 'Last provisioning sync', optional: true },
|
||||
userName: {
|
||||
type: 'string',
|
||||
description: 'Username the user signs in to the application with',
|
||||
optional: true,
|
||||
},
|
||||
profile: {
|
||||
type: 'json',
|
||||
description: 'App-specific profile attributes, whose shape is set by the app schema',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of assignments returned' },
|
||||
nextCursor: {
|
||||
type: 'string',
|
||||
description: 'Cursor for the next page, or null on the last page',
|
||||
optional: true,
|
||||
},
|
||||
hasMore: { type: 'boolean', description: 'Whether more assignments are available' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type { OktaApplication, OktaListAppsParams, OktaListAppsResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, parseOktaPagination, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaListApps')
|
||||
|
||||
export const oktaListAppsTool: ToolConfig<OktaListAppsParams, OktaListAppsResponse> = {
|
||||
id: 'okta_list_apps',
|
||||
name: 'List Applications from Okta',
|
||||
description:
|
||||
'List the applications configured in your Okta organization, with optional name search, filtering, and cursor pagination.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
q: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search for applications whose name or label starts with this value',
|
||||
},
|
||||
filter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Okta filter expression (e.g., status eq "ACTIVE")',
|
||||
},
|
||||
includeNonDeleted: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Also return inactive applications. Deleted applications stay excluded either way (default: false)',
|
||||
},
|
||||
after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Opaque pagination cursor returned as nextCursor by a previous call',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of applications to return (max: 200)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.q) queryParams.append('q', params.q)
|
||||
if (params.filter) queryParams.append('filter', params.filter)
|
||||
if (params.includeNonDeleted !== undefined) {
|
||||
queryParams.append('includeNonDeleted', String(params.includeNonDeleted))
|
||||
}
|
||||
if (params.after) queryParams.append('after', params.after)
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString
|
||||
? `https://${domain}/api/v1/apps?${queryString}`
|
||||
: `https://${domain}/api/v1/apps`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to list applications from Okta')
|
||||
}
|
||||
|
||||
const { nextCursor, hasMore } = parseOktaPagination(response)
|
||||
const data: OktaApplication[] = await response.json()
|
||||
|
||||
const apps = data.map((app) => ({
|
||||
id: app.id,
|
||||
name: app.name,
|
||||
label: app.label,
|
||||
status: app.status,
|
||||
signOnMode: app.signOnMode,
|
||||
features: app.features ?? [],
|
||||
created: app.created,
|
||||
lastUpdated: app.lastUpdated,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
apps,
|
||||
count: apps.length,
|
||||
nextCursor,
|
||||
hasMore,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
apps: {
|
||||
type: 'array',
|
||||
description: 'Array of Okta applications',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Application ID' },
|
||||
name: { type: 'string', description: 'Application name (the app template key)' },
|
||||
label: { type: 'string', description: 'Application display label' },
|
||||
status: { type: 'string', description: 'Application status (ACTIVE, INACTIVE, DELETED)' },
|
||||
signOnMode: {
|
||||
type: 'string',
|
||||
description: 'Sign-on mode (SAML_2_0, OPENID_CONNECT, BOOKMARK, etc.)',
|
||||
},
|
||||
features: {
|
||||
type: 'array',
|
||||
description: 'Enabled provisioning features',
|
||||
items: { type: 'string', description: 'Feature name' },
|
||||
},
|
||||
created: { type: 'string', description: 'Creation timestamp' },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of applications returned' },
|
||||
nextCursor: {
|
||||
type: 'string',
|
||||
description: 'Cursor for the next page, or null on the last page',
|
||||
optional: true,
|
||||
},
|
||||
hasMore: { type: 'boolean', description: 'Whether more applications are available' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type { OktaFactor, OktaListFactorsParams, OktaListFactorsResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaListFactors')
|
||||
|
||||
export const oktaListFactorsTool: ToolConfig<OktaListFactorsParams, OktaListFactorsResponse> = {
|
||||
id: 'okta_list_factors',
|
||||
name: 'List Factors from Okta',
|
||||
description:
|
||||
'List the MFA factors a user has enrolled, with each factor type, provider, and enrollment status. Use this before resetting a factor to confirm which one to target.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID or login to list enrolled factors for',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/factors`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to list factors from Okta')
|
||||
}
|
||||
|
||||
const data: OktaFactor[] = await response.json()
|
||||
|
||||
const factors = data.map((factor) => ({
|
||||
id: factor.id,
|
||||
factorType: factor.factorType,
|
||||
provider: factor.provider ?? null,
|
||||
vendorName: factor.vendorName ?? null,
|
||||
status: factor.status ?? null,
|
||||
created: factor.created ?? null,
|
||||
lastUpdated: factor.lastUpdated ?? null,
|
||||
profile: factor.profile ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
factors,
|
||||
count: factors.length,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
factors: {
|
||||
type: 'array',
|
||||
description: 'Array of enrolled MFA factors',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Factor ID' },
|
||||
factorType: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Factor type (sms, call, email, push, question, token:software:totp, webauthn, etc.)',
|
||||
},
|
||||
provider: {
|
||||
type: 'string',
|
||||
description: 'Factor provider (OKTA, GOOGLE, FIDO, DUO, RSA, SYMANTEC, YUBICO, CUSTOM)',
|
||||
optional: true,
|
||||
},
|
||||
vendorName: { type: 'string', description: 'Factor vendor name', optional: true },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Enrollment status (ACTIVE, PENDING_ACTIVATION, NOT_SETUP, etc.)',
|
||||
optional: true,
|
||||
},
|
||||
created: { type: 'string', description: 'Enrollment timestamp', optional: true },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp', optional: true },
|
||||
profile: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Factor-specific attributes, which vary by factor type (phone number, email, question, credential ID)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of enrolled factors' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaListGroupMembersParams,
|
||||
OktaListGroupMembersResponse,
|
||||
OktaUser,
|
||||
} from '@/tools/okta/types'
|
||||
import { oktaHeaders, parseOktaPagination, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaListGroupMembers')
|
||||
@@ -38,11 +38,18 @@ export const oktaListGroupMembersTool: ToolConfig<
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group ID to list members for',
|
||||
},
|
||||
after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Opaque pagination cursor returned as nextCursor by a previous call',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of members to return (default: 1000, max: 1000)',
|
||||
description:
|
||||
'Maximum number of members to return per page (default: 1000, but Okta recommends 200)',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -51,6 +58,7 @@ export const oktaListGroupMembersTool: ToolConfig<
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.after) queryParams.append('after', params.after)
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
@@ -58,25 +66,15 @@ export const oktaListGroupMembersTool: ToolConfig<
|
||||
return queryString ? `${base}?${queryString}` : base
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// non-JSON error body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to list group members from Okta')
|
||||
await throwOktaError(response, logger, 'Failed to list group members from Okta')
|
||||
}
|
||||
|
||||
const { nextCursor, hasMore } = parseOktaPagination(response)
|
||||
const data: OktaUser[] = await response.json()
|
||||
|
||||
const members = data.map((user) => ({
|
||||
@@ -101,6 +99,8 @@ export const oktaListGroupMembersTool: ToolConfig<
|
||||
output: {
|
||||
members,
|
||||
count: members.length,
|
||||
nextCursor,
|
||||
hasMore,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
@@ -135,6 +135,12 @@ export const oktaListGroupMembersTool: ToolConfig<
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of members returned' },
|
||||
nextCursor: {
|
||||
type: 'string',
|
||||
description: 'Cursor for the next page, or null on the last page',
|
||||
optional: true,
|
||||
},
|
||||
hasMore: { type: 'boolean', description: 'Whether more members are available' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaGroupRule,
|
||||
OktaListGroupRulesParams,
|
||||
OktaListGroupRulesResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import {
|
||||
mapOktaGroupRule,
|
||||
oktaHeaders,
|
||||
parseOktaPagination,
|
||||
throwOktaError,
|
||||
} from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaListGroupRules')
|
||||
|
||||
export const oktaListGroupRulesTool: ToolConfig<
|
||||
OktaListGroupRulesParams,
|
||||
OktaListGroupRulesResponse
|
||||
> = {
|
||||
id: 'okta_list_group_rules',
|
||||
name: 'List Group Rules from Okta',
|
||||
description:
|
||||
'List the group rules in your Okta organization. Each rule assigns users to groups automatically based on an expression over their profile, so this shows how group membership is being driven.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
search: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Keyword to search group rules for',
|
||||
},
|
||||
after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Opaque pagination cursor returned as nextCursor by a previous call',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of rules to return (default: 50, max: 200)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.search) queryParams.append('search', params.search)
|
||||
if (params.after) queryParams.append('after', params.after)
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString
|
||||
? `https://${domain}/api/v1/groups/rules?${queryString}`
|
||||
: `https://${domain}/api/v1/groups/rules`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to list group rules from Okta')
|
||||
}
|
||||
|
||||
const { nextCursor, hasMore } = parseOktaPagination(response)
|
||||
const data: OktaGroupRule[] = await response.json()
|
||||
const rules = data.map(mapOktaGroupRule)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
rules,
|
||||
count: rules.length,
|
||||
nextCursor,
|
||||
hasMore,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
rules: {
|
||||
type: 'array',
|
||||
description: 'Array of group rules',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Group rule ID' },
|
||||
name: { type: 'string', description: 'Group rule name' },
|
||||
type: { type: 'string', description: 'Rule type, always group_rule' },
|
||||
status: { type: 'string', description: 'Rule status (ACTIVE, INACTIVE, INVALID)' },
|
||||
created: { type: 'string', description: 'Creation timestamp', optional: true },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp', optional: true },
|
||||
expression: {
|
||||
type: 'string',
|
||||
description: 'Okta expression that decides which users the rule matches',
|
||||
optional: true,
|
||||
},
|
||||
expressionType: {
|
||||
type: 'string',
|
||||
description: 'Expression language, typically urn:okta:expression:1.0',
|
||||
optional: true,
|
||||
},
|
||||
assignUserToGroupIds: {
|
||||
type: 'array',
|
||||
description: 'Groups that matching users are assigned to',
|
||||
items: { type: 'string', description: 'Group ID' },
|
||||
},
|
||||
excludedUserIds: {
|
||||
type: 'array',
|
||||
description: 'Users excluded from the rule',
|
||||
items: { type: 'string', description: 'User ID' },
|
||||
},
|
||||
excludedGroupIds: {
|
||||
type: 'array',
|
||||
description: 'Groups excluded from the rule',
|
||||
items: { type: 'string', description: 'Group ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of rules returned' },
|
||||
nextCursor: {
|
||||
type: 'string',
|
||||
description: 'Cursor for the next page, or null on the last page',
|
||||
optional: true,
|
||||
},
|
||||
hasMore: { type: 'boolean', description: 'Whether more rules are available' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaGroup,
|
||||
OktaListGroupsParams,
|
||||
OktaListGroupsResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import type { OktaGroup, OktaListGroupsParams, OktaListGroupsResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, parseOktaPagination, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaListGroups')
|
||||
@@ -42,11 +38,17 @@ export const oktaListGroupsTool: ToolConfig<OktaListGroupsParams, OktaListGroups
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Okta filter expression (e.g., type eq "OKTA_GROUP")',
|
||||
},
|
||||
after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Opaque pagination cursor returned as nextCursor by a previous call',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of groups to return (default: 10000, max: 10000)',
|
||||
description: 'Maximum number of groups to return per page (max: 10000)',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -57,6 +59,7 @@ export const oktaListGroupsTool: ToolConfig<OktaListGroupsParams, OktaListGroups
|
||||
|
||||
if (params.search) queryParams.append('search', params.search)
|
||||
if (params.filter) queryParams.append('filter', params.filter)
|
||||
if (params.after) queryParams.append('after', params.after)
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
@@ -65,25 +68,15 @@ export const oktaListGroupsTool: ToolConfig<OktaListGroupsParams, OktaListGroups
|
||||
: `https://${domain}/api/v1/groups`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// non-JSON error body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to list groups from Okta')
|
||||
await throwOktaError(response, logger, 'Failed to list groups from Okta')
|
||||
}
|
||||
|
||||
const { nextCursor, hasMore } = parseOktaPagination(response)
|
||||
const data: OktaGroup[] = await response.json()
|
||||
|
||||
const groups = data.map((group) => ({
|
||||
@@ -101,6 +94,8 @@ export const oktaListGroupsTool: ToolConfig<OktaListGroupsParams, OktaListGroups
|
||||
output: {
|
||||
groups,
|
||||
count: groups.length,
|
||||
nextCursor,
|
||||
hasMore,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
@@ -128,6 +123,12 @@ export const oktaListGroupsTool: ToolConfig<OktaListGroupsParams, OktaListGroups
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of groups returned' },
|
||||
nextCursor: {
|
||||
type: 'string',
|
||||
description: 'Cursor for the next page, or null on the last page',
|
||||
optional: true,
|
||||
},
|
||||
hasMore: { type: 'boolean', description: 'Whether more groups are available' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaListUserRolesParams,
|
||||
OktaListUserRolesResponse,
|
||||
OktaRoleAssignment,
|
||||
} from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaListUserRoles')
|
||||
|
||||
export const oktaListUserRolesTool: ToolConfig<OktaListUserRolesParams, OktaListUserRolesResponse> =
|
||||
{
|
||||
id: 'okta_list_user_roles',
|
||||
name: 'List User Roles from Okta',
|
||||
description:
|
||||
'List the administrator roles assigned to a user. Returns both standard roles and custom role bindings, so you can review who holds privileged access.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID or login to list admin roles for',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/roles`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to list user roles from Okta')
|
||||
}
|
||||
|
||||
const data: OktaRoleAssignment[] = await response.json()
|
||||
|
||||
const roles = data.map((role) => ({
|
||||
id: role.id ?? null,
|
||||
label: role.label ?? null,
|
||||
type: role.type,
|
||||
status: role.status ?? null,
|
||||
created: role.created ?? null,
|
||||
lastUpdated: role.lastUpdated ?? null,
|
||||
assignmentType: role.assignmentType ?? null,
|
||||
role: role.role ?? null,
|
||||
resourceSet: role['resource-set'] ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
roles,
|
||||
count: roles.length,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
roles: {
|
||||
type: 'array',
|
||||
description: 'Array of admin role assignments',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Role assignment ID, which is the resource set binding ID for a custom role. Pass this to Remove User Role',
|
||||
optional: true,
|
||||
},
|
||||
label: { type: 'string', description: 'Role label', optional: true },
|
||||
type: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Role type (SUPER_ADMIN, ORG_ADMIN, APP_ADMIN, USER_ADMIN, HELP_DESK_ADMIN, READ_ONLY_ADMIN, CUSTOM, etc.)',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Role status (ACTIVE, INACTIVE)',
|
||||
optional: true,
|
||||
},
|
||||
created: { type: 'string', description: 'Assignment timestamp', optional: true },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp', optional: true },
|
||||
assignmentType: {
|
||||
type: 'string',
|
||||
description: 'How the role was assigned (USER, GROUP, CLIENT)',
|
||||
optional: true,
|
||||
},
|
||||
role: {
|
||||
type: 'string',
|
||||
description: 'Custom role ID, present only on custom role assignments',
|
||||
optional: true,
|
||||
},
|
||||
resourceSet: {
|
||||
type: 'string',
|
||||
description: 'Resource set ID, present only on custom role assignments',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of role assignments returned' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaListUsersParams,
|
||||
OktaListUsersResponse,
|
||||
OktaUser,
|
||||
} from '@/tools/okta/types'
|
||||
import type { OktaListUsersParams, OktaListUsersResponse, OktaUser } from '@/tools/okta/types'
|
||||
import { oktaHeaders, parseOktaPagination, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaListUsers')
|
||||
@@ -42,11 +38,17 @@ export const oktaListUsersTool: ToolConfig<OktaListUsersParams, OktaListUsersRes
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Okta filter expression (e.g., status eq "ACTIVE")',
|
||||
},
|
||||
after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Opaque pagination cursor returned as nextCursor by a previous call',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of users to return (default: 200, max: 200)',
|
||||
description: 'Maximum number of users to return per page (default: 200)',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -57,6 +59,7 @@ export const oktaListUsersTool: ToolConfig<OktaListUsersParams, OktaListUsersRes
|
||||
|
||||
if (params.search) queryParams.append('search', params.search)
|
||||
if (params.filter) queryParams.append('filter', params.filter)
|
||||
if (params.after) queryParams.append('after', params.after)
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
@@ -65,25 +68,15 @@ export const oktaListUsersTool: ToolConfig<OktaListUsersParams, OktaListUsersRes
|
||||
: `https://${domain}/api/v1/users`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// non-JSON error body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to list users from Okta')
|
||||
await throwOktaError(response, logger, 'Failed to list users from Okta')
|
||||
}
|
||||
|
||||
const { nextCursor, hasMore } = parseOktaPagination(response)
|
||||
const data: OktaUser[] = await response.json()
|
||||
|
||||
const users = data.map((user) => ({
|
||||
@@ -108,6 +101,8 @@ export const oktaListUsersTool: ToolConfig<OktaListUsersParams, OktaListUsersRes
|
||||
output: {
|
||||
users,
|
||||
count: users.length,
|
||||
nextCursor,
|
||||
hasMore,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
@@ -141,6 +136,12 @@ export const oktaListUsersTool: ToolConfig<OktaListUsersParams, OktaListUsersRes
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of users returned' },
|
||||
nextCursor: {
|
||||
type: 'string',
|
||||
description: 'Cursor for the next page, or null on the last page',
|
||||
optional: true,
|
||||
},
|
||||
hasMore: { type: 'boolean', description: 'Whether more users are available' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaRemoveGroupFromAppParams,
|
||||
OktaRemoveGroupFromAppResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaRemoveGroupFromApp')
|
||||
|
||||
export const oktaRemoveGroupFromAppTool: ToolConfig<
|
||||
OktaRemoveGroupFromAppParams,
|
||||
OktaRemoveGroupFromAppResponse
|
||||
> = {
|
||||
id: 'okta_remove_group_from_app',
|
||||
name: 'Remove Group from Application in Okta',
|
||||
description:
|
||||
'Unassign a group from an Okta application. Destructive: every member who had access only through this group loses access to the app.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
appId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Application ID to remove the group from',
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group ID to unassign',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/apps/${encodeURIComponent(params.appId.trim())}/groups/${encodeURIComponent(params.groupId.trim())}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to remove group from application in Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
appId: params?.appId ?? '',
|
||||
groupId: params?.groupId ?? '',
|
||||
removed: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
appId: { type: 'string', description: 'Application ID' },
|
||||
groupId: { type: 'string', description: 'Group unassigned from the application' },
|
||||
removed: { type: 'boolean', description: 'Whether the group was unassigned' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type { OktaRemoveUserFromAppParams, OktaRemoveUserFromAppResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaRemoveUserFromApp')
|
||||
|
||||
export const oktaRemoveUserFromAppTool: ToolConfig<
|
||||
OktaRemoveUserFromAppParams,
|
||||
OktaRemoveUserFromAppResponse
|
||||
> = {
|
||||
id: 'okta_remove_user_from_app',
|
||||
name: 'Remove User from Application in Okta',
|
||||
description:
|
||||
'Unassign a user from an Okta application, revoking their access. Destructive and irreversible: the app profile for that user is permanently removed, and if provisioning is enabled the downstream account is deactivated.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
appId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Application ID to remove the user from',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Okta user ID to unassign',
|
||||
},
|
||||
sendEmail: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Send a deactivation email to the administrator (default: false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const base = `https://${domain}/api/v1/apps/${encodeURIComponent(params.appId.trim())}/users/${encodeURIComponent(params.userId.trim())}`
|
||||
return params.sendEmail === undefined ? base : `${base}?sendEmail=${params.sendEmail}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to remove user from application in Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
appId: params?.appId ?? '',
|
||||
userId: params?.userId ?? '',
|
||||
removed: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
appId: { type: 'string', description: 'Application ID' },
|
||||
userId: { type: 'string', description: 'User unassigned from the application' },
|
||||
removed: { type: 'boolean', description: 'Whether the user was unassigned' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaRemoveUserFromGroupParams,
|
||||
OktaRemoveUserFromGroupResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaRemoveUserFromGroup')
|
||||
@@ -51,23 +51,12 @@ export const oktaRemoveUserFromGroupTool: ToolConfig<
|
||||
return `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId.trim())}/users/${encodeURIComponent(params.userId.trim())}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// empty response body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to remove user from group in Okta')
|
||||
await throwOktaError(response, logger, 'Failed to remove user from group in Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type { OktaRemoveUserRoleParams, OktaRemoveUserRoleResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaRemoveUserRole')
|
||||
|
||||
export const oktaRemoveUserRoleTool: ToolConfig<
|
||||
OktaRemoveUserRoleParams,
|
||||
OktaRemoveUserRoleResponse
|
||||
> = {
|
||||
id: 'okta_remove_user_role',
|
||||
name: 'Remove User Role in Okta',
|
||||
description:
|
||||
'Revoke an administrator role from a user. Destructive: the user immediately loses the admin permissions that role granted. Takes the role assignment ID, not the role type, which List User Roles returns as the role id field.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID or login to revoke the admin role from',
|
||||
},
|
||||
roleAssignmentId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Role assignment ID to revoke, as returned by List User Roles. For a custom role this is the resource set binding ID',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/roles/${encodeURIComponent(params.roleAssignmentId.trim())}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to remove user role in Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
userId: params?.userId ?? '',
|
||||
roleAssignmentId: params?.roleAssignmentId ?? '',
|
||||
removed: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
userId: { type: 'string', description: 'User the role was revoked from' },
|
||||
roleAssignmentId: { type: 'string', description: 'Revoked role assignment ID' },
|
||||
removed: { type: 'boolean', description: 'Whether the role was revoked' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type { OktaResetAllFactorsParams, OktaResetAllFactorsResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaResetAllFactors')
|
||||
|
||||
export const oktaResetAllFactorsTool: ToolConfig<
|
||||
OktaResetAllFactorsParams,
|
||||
OktaResetAllFactorsResponse
|
||||
> = {
|
||||
id: 'okta_reset_all_factors',
|
||||
name: 'Reset All Factors in Okta',
|
||||
description:
|
||||
'Reset every MFA factor for a user, returning all enrollments to the unenrolled state. Destructive and irreversible: the user must re-enroll each factor before they can complete MFA again. The user status stays ACTIVE.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID or login whose MFA factors will all be reset',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/lifecycle/reset_factors`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to reset all factors in Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
userId: params?.userId ?? '',
|
||||
reset: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
userId: { type: 'string', description: 'User whose factors were reset' },
|
||||
reset: { type: 'boolean', description: 'Whether all factors were reset' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type { OktaResetFactorParams, OktaResetFactorResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaResetFactor')
|
||||
|
||||
export const oktaResetFactorTool: ToolConfig<OktaResetFactorParams, OktaResetFactorResponse> = {
|
||||
id: 'okta_reset_factor',
|
||||
name: 'Reset Factor in Okta',
|
||||
description:
|
||||
'Unenroll one specific MFA factor for a user so they can re-enroll it. Destructive and irreversible: the existing enrollment is removed. Unenrolling a push or signed_nonce factor also unenrolls the related Okta Verify factors. Factors cannot be unenrolled from a deactivated user.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID or login the factor belongs to',
|
||||
},
|
||||
factorId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Factor ID to unenroll',
|
||||
},
|
||||
removeRecoveryEnrollment: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Also remove the phone number as a recovery method, not only as a factor. Applies to sms and call factors only (default: false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const base = `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/factors/${encodeURIComponent(params.factorId.trim())}`
|
||||
return params.removeRecoveryEnrollment === undefined
|
||||
? base
|
||||
: `${base}?removeRecoveryEnrollment=${params.removeRecoveryEnrollment}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to reset factor in Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
userId: params?.userId ?? '',
|
||||
factorId: params?.factorId ?? '',
|
||||
reset: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
userId: { type: 'string', description: 'User the factor belonged to' },
|
||||
factorId: { type: 'string', description: 'Unenrolled factor ID' },
|
||||
reset: { type: 'boolean', description: 'Whether the factor was unenrolled' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaResetPasswordParams,
|
||||
OktaResetPasswordResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import type { OktaResetPasswordParams, OktaResetPasswordResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaResetPassword')
|
||||
@@ -51,23 +48,12 @@ export const oktaResetPasswordTool: ToolConfig<OktaResetPasswordParams, OktaRese
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/lifecycle/reset_password?sendEmail=${sendEmail}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// empty response body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to reset password in Okta')
|
||||
await throwOktaError(response, logger, 'Failed to reset password in Okta')
|
||||
}
|
||||
|
||||
let resetPasswordUrl: string | null = null
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type { OktaRevokeSessionParams, OktaRevokeSessionResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaRevokeSession')
|
||||
|
||||
export const oktaRevokeSessionTool: ToolConfig<OktaRevokeSessionParams, OktaRevokeSessionResponse> =
|
||||
{
|
||||
id: 'okta_revoke_session',
|
||||
name: 'Revoke Session in Okta',
|
||||
description:
|
||||
'Revoke a single Okta session by ID, ending that sign-in immediately. Destructive and irreversible: the affected user must sign in again on that device.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta API token for authentication',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Okta domain (e.g., dev-123456.okta.com)',
|
||||
},
|
||||
sessionId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Session ID to revoke',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/sessions/${encodeURIComponent(params.sessionId.trim())}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
await throwOktaError(response, logger, 'Failed to revoke session in Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
sessionId: params?.sessionId ?? '',
|
||||
revoked: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
sessionId: { type: 'string', description: 'Revoked session ID' },
|
||||
revoked: { type: 'boolean', description: 'Whether the session was revoked' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaSuspendUserParams,
|
||||
OktaSuspendUserResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import type { OktaSuspendUserParams, OktaSuspendUserResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaSuspendUser')
|
||||
@@ -43,23 +40,12 @@ export const oktaSuspendUserTool: ToolConfig<OktaSuspendUserParams, OktaSuspendU
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/lifecycle/suspend`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// empty response body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to suspend user in Okta')
|
||||
await throwOktaError(response, logger, 'Failed to suspend user in Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -113,6 +113,7 @@ interface OktaGroupOutput {
|
||||
export interface OktaListUsersParams extends OktaBaseParams {
|
||||
search?: string
|
||||
filter?: string
|
||||
after?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
@@ -120,6 +121,8 @@ export interface OktaListUsersResponse extends ToolResponse {
|
||||
output: {
|
||||
users: OktaUserOutput[]
|
||||
count: number
|
||||
nextCursor: string | null
|
||||
hasMore: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
@@ -229,6 +232,7 @@ export interface OktaDeactivateUserResponse extends ToolResponse {
|
||||
export interface OktaListGroupsParams extends OktaBaseParams {
|
||||
search?: string
|
||||
filter?: string
|
||||
after?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
@@ -236,6 +240,8 @@ export interface OktaListGroupsResponse extends ToolResponse {
|
||||
output: {
|
||||
groups: OktaGroupOutput[]
|
||||
count: number
|
||||
nextCursor: string | null
|
||||
hasMore: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
@@ -291,6 +297,7 @@ export interface OktaRemoveUserFromGroupResponse extends ToolResponse {
|
||||
// List Group Members
|
||||
export interface OktaListGroupMembersParams extends OktaBaseParams {
|
||||
groupId: string
|
||||
after?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
@@ -298,6 +305,8 @@ export interface OktaListGroupMembersResponse extends ToolResponse {
|
||||
output: {
|
||||
members: OktaUserOutput[]
|
||||
count: number
|
||||
nextCursor: string | null
|
||||
hasMore: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
@@ -424,6 +433,709 @@ export interface OktaDeleteGroupResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Okta Application object from the API.
|
||||
*
|
||||
* `accessibility`, `visibility`, `settings`, and `profile` are polymorphic per
|
||||
* `signOnMode`, so they stay untyped maps rather than a guessed fixed shape.
|
||||
*/
|
||||
export interface OktaApplication {
|
||||
id: string
|
||||
name: string
|
||||
label: string
|
||||
status: string
|
||||
signOnMode: string
|
||||
features?: string[] | null
|
||||
created: string
|
||||
lastUpdated: string
|
||||
accessibility?: Record<string, unknown> | null
|
||||
visibility?: Record<string, unknown> | null
|
||||
settings?: Record<string, unknown> | null
|
||||
profile?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformed application summary output
|
||||
*/
|
||||
interface OktaApplicationOutput {
|
||||
id: string
|
||||
name: string
|
||||
label: string
|
||||
status: string
|
||||
signOnMode: string
|
||||
features: string[]
|
||||
created: string
|
||||
lastUpdated: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Okta Application User (assignment) object from the API
|
||||
*/
|
||||
export interface OktaAppUser {
|
||||
id: string
|
||||
externalId?: string | null
|
||||
created: string
|
||||
lastUpdated: string
|
||||
scope: string
|
||||
status: string
|
||||
statusChanged?: string | null
|
||||
passwordChanged?: string | null
|
||||
syncState?: string | null
|
||||
lastSync?: string | null
|
||||
credentials?: { userName?: string | null } | null
|
||||
profile?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformed application user output
|
||||
*/
|
||||
interface OktaAppUserOutput {
|
||||
id: string
|
||||
externalId: string | null
|
||||
created: string
|
||||
lastUpdated: string
|
||||
scope: string
|
||||
status: string
|
||||
statusChanged: string | null
|
||||
passwordChanged: string | null
|
||||
syncState: string | null
|
||||
lastSync: string | null
|
||||
userName: string | null
|
||||
profile: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Okta Application Group Assignment object from the API
|
||||
*/
|
||||
export interface OktaAppGroupAssignment {
|
||||
id: string
|
||||
priority?: number | null
|
||||
lastUpdated: string
|
||||
profile?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformed application group assignment output
|
||||
*/
|
||||
interface OktaAppGroupOutput {
|
||||
id: string
|
||||
priority: number | null
|
||||
lastUpdated: string
|
||||
profile: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Okta admin role assignment object.
|
||||
*
|
||||
* Standard and custom assignments form a union discriminated on `type`. A custom
|
||||
* assignment is a resource-set binding, so its `id` is the binding id and it
|
||||
* carries `role` plus the hyphenated `resource-set` rather than a label alone.
|
||||
*/
|
||||
export interface OktaRoleAssignment {
|
||||
id?: string | null
|
||||
label?: string | null
|
||||
type: string
|
||||
status?: string | null
|
||||
created?: string | null
|
||||
lastUpdated?: string | null
|
||||
assignmentType?: string | null
|
||||
role?: string | null
|
||||
'resource-set'?: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformed admin role assignment output
|
||||
*/
|
||||
interface OktaRoleAssignmentOutput {
|
||||
id: string | null
|
||||
label: string | null
|
||||
type: string
|
||||
status: string | null
|
||||
created: string | null
|
||||
lastUpdated: string | null
|
||||
assignmentType: string | null
|
||||
role: string | null
|
||||
resourceSet: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Okta Group Rule object from the API
|
||||
*/
|
||||
export interface OktaGroupRule {
|
||||
id: string
|
||||
name: string
|
||||
type: string
|
||||
status: string
|
||||
created?: string | null
|
||||
lastUpdated?: string | null
|
||||
conditions?: {
|
||||
expression?: { type?: string | null; value?: string | null } | null
|
||||
people?: {
|
||||
users?: { exclude?: string[] | null } | null
|
||||
groups?: { exclude?: string[] | null } | null
|
||||
} | null
|
||||
} | null
|
||||
actions?: { assignUserToGroups?: { groupIds?: string[] | null } | null } | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformed group rule output
|
||||
*/
|
||||
interface OktaGroupRuleOutput {
|
||||
id: string
|
||||
name: string
|
||||
type: string
|
||||
status: string
|
||||
created: string | null
|
||||
lastUpdated: string | null
|
||||
expression: string | null
|
||||
expressionType: string | null
|
||||
assignUserToGroupIds: string[]
|
||||
excludedUserIds: string[]
|
||||
excludedGroupIds: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Okta System Log event object from the API
|
||||
*/
|
||||
export interface OktaLogEvent {
|
||||
uuid: string
|
||||
published: string
|
||||
eventType: string
|
||||
severity: string
|
||||
legacyEventType?: string | null
|
||||
displayMessage?: string | null
|
||||
actor?: {
|
||||
id?: string | null
|
||||
type?: string | null
|
||||
alternateId?: string | null
|
||||
displayName?: string | null
|
||||
} | null
|
||||
client?: {
|
||||
id?: string | null
|
||||
ipAddress?: string | null
|
||||
device?: string | null
|
||||
zone?: string | null
|
||||
userAgent?: { browser?: string | null; os?: string | null; rawUserAgent?: string | null } | null
|
||||
geographicalContext?: {
|
||||
city?: string | null
|
||||
state?: string | null
|
||||
country?: string | null
|
||||
} | null
|
||||
} | null
|
||||
authenticationContext?: {
|
||||
externalSessionId?: string | null
|
||||
authenticationProvider?: string | null
|
||||
credentialProvider?: string | null
|
||||
credentialType?: string | null
|
||||
interface?: string | null
|
||||
} | null
|
||||
securityContext?: {
|
||||
asOrg?: string | null
|
||||
isp?: string | null
|
||||
domain?: string | null
|
||||
isProxy?: boolean | null
|
||||
} | null
|
||||
target?:
|
||||
| {
|
||||
id?: string | null
|
||||
type?: string | null
|
||||
alternateId?: string | null
|
||||
displayName?: string | null
|
||||
}[]
|
||||
| null
|
||||
transaction?: { id?: string | null; type?: string | null } | null
|
||||
debugContext?: { debugData?: Record<string, unknown> | null } | null
|
||||
outcome?: { result?: string | null; reason?: string | null } | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformed System Log event output
|
||||
*/
|
||||
interface OktaLogEventOutput {
|
||||
uuid: string
|
||||
published: string
|
||||
eventType: string
|
||||
severity: string
|
||||
legacyEventType: string | null
|
||||
displayMessage: string | null
|
||||
outcomeResult: string | null
|
||||
outcomeReason: string | null
|
||||
actorId: string | null
|
||||
actorType: string | null
|
||||
actorAlternateId: string | null
|
||||
actorDisplayName: string | null
|
||||
clientIpAddress: string | null
|
||||
clientDevice: string | null
|
||||
clientZone: string | null
|
||||
clientBrowser: string | null
|
||||
clientOs: string | null
|
||||
clientCity: string | null
|
||||
clientState: string | null
|
||||
clientCountry: string | null
|
||||
authenticationProvider: string | null
|
||||
credentialType: string | null
|
||||
externalSessionId: string | null
|
||||
securityAsOrg: string | null
|
||||
securityIsp: string | null
|
||||
securityIsProxy: boolean | null
|
||||
transactionId: string | null
|
||||
transactionType: string | null
|
||||
targets: {
|
||||
id: string | null
|
||||
type: string | null
|
||||
alternateId: string | null
|
||||
displayName: string | null
|
||||
}[]
|
||||
debugData: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Okta Session object from the API
|
||||
*/
|
||||
export interface OktaSession {
|
||||
id: string
|
||||
login?: string | null
|
||||
userId?: string | null
|
||||
status?: string | null
|
||||
createdAt?: string | null
|
||||
expiresAt?: string | null
|
||||
lastPasswordVerification?: string | null
|
||||
lastFactorVerification?: string | null
|
||||
amr?: string[] | null
|
||||
idp?: { id?: string | null; type?: string | null } | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Okta UserFactor object from the API.
|
||||
*
|
||||
* `profile` is a discriminated union keyed on `factorType` (phone number for
|
||||
* `sms`/`call`, email for `email`, question/answer for `question`, credential id
|
||||
* for the token families), so it stays an untyped map rather than one guessed
|
||||
* shape. `_embedded` is free-form in the schema and is not surfaced.
|
||||
*/
|
||||
export interface OktaFactor {
|
||||
id: string
|
||||
factorType: string
|
||||
provider?: string | null
|
||||
vendorName?: string | null
|
||||
status?: string | null
|
||||
created?: string | null
|
||||
lastUpdated?: string | null
|
||||
profile?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformed factor output
|
||||
*/
|
||||
interface OktaFactorOutput {
|
||||
id: string
|
||||
factorType: string
|
||||
provider: string | null
|
||||
vendorName: string | null
|
||||
status: string | null
|
||||
created: string | null
|
||||
lastUpdated: string | null
|
||||
profile: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
// List Factors
|
||||
export interface OktaListFactorsParams extends OktaBaseParams {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface OktaListFactorsResponse extends ToolResponse {
|
||||
output: {
|
||||
factors: OktaFactorOutput[]
|
||||
count: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Get Factor
|
||||
export interface OktaGetFactorParams extends OktaBaseParams {
|
||||
userId: string
|
||||
factorId: string
|
||||
}
|
||||
|
||||
export interface OktaGetFactorResponse extends ToolResponse {
|
||||
output: OktaFactorOutput & { success: boolean }
|
||||
}
|
||||
|
||||
// Reset Factor
|
||||
export interface OktaResetFactorParams extends OktaBaseParams {
|
||||
userId: string
|
||||
factorId: string
|
||||
removeRecoveryEnrollment?: boolean
|
||||
}
|
||||
|
||||
export interface OktaResetFactorResponse extends ToolResponse {
|
||||
output: {
|
||||
userId: string
|
||||
factorId: string
|
||||
reset: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Reset All Factors
|
||||
export interface OktaResetAllFactorsParams extends OktaBaseParams {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface OktaResetAllFactorsResponse extends ToolResponse {
|
||||
output: {
|
||||
userId: string
|
||||
reset: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Enroll Factor
|
||||
export interface OktaEnrollFactorParams extends OktaBaseParams {
|
||||
userId: string
|
||||
factorType: string
|
||||
provider: string
|
||||
phoneNumber?: string
|
||||
factorEmail?: string
|
||||
securityQuestion?: string
|
||||
securityAnswer?: string
|
||||
activate?: boolean
|
||||
}
|
||||
|
||||
export interface OktaEnrollFactorResponse extends ToolResponse {
|
||||
output: OktaFactorOutput & { enrolled: boolean; success: boolean }
|
||||
}
|
||||
|
||||
// Get Logs
|
||||
export interface OktaGetLogsParams extends OktaBaseParams {
|
||||
since?: string
|
||||
until?: string
|
||||
filter?: string
|
||||
q?: string
|
||||
sortOrder?: string
|
||||
after?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface OktaGetLogsResponse extends ToolResponse {
|
||||
output: {
|
||||
events: OktaLogEventOutput[]
|
||||
count: number
|
||||
nextCursor: string | null
|
||||
hasMore: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Clear User Sessions
|
||||
export interface OktaClearUserSessionsParams extends OktaBaseParams {
|
||||
userId: string
|
||||
oauthTokens?: boolean
|
||||
forgetDevices?: boolean
|
||||
}
|
||||
|
||||
export interface OktaClearUserSessionsResponse extends ToolResponse {
|
||||
output: {
|
||||
userId: string
|
||||
cleared: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Get Session
|
||||
export interface OktaGetSessionParams extends OktaBaseParams {
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
export interface OktaGetSessionResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
login: string | null
|
||||
userId: string | null
|
||||
status: string | null
|
||||
createdAt: string | null
|
||||
expiresAt: string | null
|
||||
lastPasswordVerification: string | null
|
||||
lastFactorVerification: string | null
|
||||
amr: string[]
|
||||
idpId: string | null
|
||||
idpType: string | null
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Revoke Session
|
||||
export interface OktaRevokeSessionParams extends OktaBaseParams {
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
export interface OktaRevokeSessionResponse extends ToolResponse {
|
||||
output: {
|
||||
sessionId: string
|
||||
revoked: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// List Applications
|
||||
export interface OktaListAppsParams extends OktaBaseParams {
|
||||
q?: string
|
||||
filter?: string
|
||||
includeNonDeleted?: boolean
|
||||
after?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface OktaListAppsResponse extends ToolResponse {
|
||||
output: {
|
||||
apps: OktaApplicationOutput[]
|
||||
count: number
|
||||
nextCursor: string | null
|
||||
hasMore: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Get Application
|
||||
export interface OktaGetAppParams extends OktaBaseParams {
|
||||
appId: string
|
||||
}
|
||||
|
||||
export interface OktaGetAppResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
name: string
|
||||
label: string
|
||||
status: string
|
||||
signOnMode: string
|
||||
features: string[]
|
||||
created: string
|
||||
lastUpdated: string
|
||||
accessibility: Record<string, unknown> | null
|
||||
visibility: Record<string, unknown> | null
|
||||
settings: Record<string, unknown> | null
|
||||
profile: Record<string, unknown> | null
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// List Application Users
|
||||
export interface OktaListAppUsersParams extends OktaBaseParams {
|
||||
appId: string
|
||||
q?: string
|
||||
after?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface OktaListAppUsersResponse extends ToolResponse {
|
||||
output: {
|
||||
appUsers: OktaAppUserOutput[]
|
||||
count: number
|
||||
nextCursor: string | null
|
||||
hasMore: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Assign User to Application
|
||||
export interface OktaAssignUserToAppParams extends OktaBaseParams {
|
||||
appId: string
|
||||
userId: string
|
||||
scope?: string
|
||||
appUserName?: string
|
||||
}
|
||||
|
||||
export interface OktaAssignUserToAppResponse extends ToolResponse {
|
||||
output: OktaAppUserOutput & { assigned: boolean; success: boolean }
|
||||
}
|
||||
|
||||
// Remove User from Application
|
||||
export interface OktaRemoveUserFromAppParams extends OktaBaseParams {
|
||||
appId: string
|
||||
userId: string
|
||||
sendEmail?: boolean
|
||||
}
|
||||
|
||||
export interface OktaRemoveUserFromAppResponse extends ToolResponse {
|
||||
output: {
|
||||
appId: string
|
||||
userId: string
|
||||
removed: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// List Application Groups
|
||||
export interface OktaListAppGroupsParams extends OktaBaseParams {
|
||||
appId: string
|
||||
q?: string
|
||||
after?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface OktaListAppGroupsResponse extends ToolResponse {
|
||||
output: {
|
||||
appGroups: OktaAppGroupOutput[]
|
||||
count: number
|
||||
nextCursor: string | null
|
||||
hasMore: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Assign Group to Application
|
||||
export interface OktaAssignGroupToAppParams extends OktaBaseParams {
|
||||
appId: string
|
||||
groupId: string
|
||||
priority?: number
|
||||
}
|
||||
|
||||
export interface OktaAssignGroupToAppResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
priority: number | null
|
||||
lastUpdated: string
|
||||
profile: Record<string, unknown> | null
|
||||
assigned: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Remove Group from Application
|
||||
export interface OktaRemoveGroupFromAppParams extends OktaBaseParams {
|
||||
appId: string
|
||||
groupId: string
|
||||
}
|
||||
|
||||
export interface OktaRemoveGroupFromAppResponse extends ToolResponse {
|
||||
output: {
|
||||
appId: string
|
||||
groupId: string
|
||||
removed: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// List User Roles
|
||||
export interface OktaListUserRolesParams extends OktaBaseParams {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface OktaListUserRolesResponse extends ToolResponse {
|
||||
output: {
|
||||
roles: OktaRoleAssignmentOutput[]
|
||||
count: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Assign User Role
|
||||
export interface OktaAssignUserRoleParams extends OktaBaseParams {
|
||||
userId: string
|
||||
roleType: string
|
||||
customRoleId?: string
|
||||
resourceSetId?: string
|
||||
disableNotifications?: boolean
|
||||
}
|
||||
|
||||
export interface OktaAssignUserRoleResponse extends ToolResponse {
|
||||
output: OktaRoleAssignmentOutput & { assigned: boolean; success: boolean }
|
||||
}
|
||||
|
||||
// Remove User Role
|
||||
export interface OktaRemoveUserRoleParams extends OktaBaseParams {
|
||||
userId: string
|
||||
roleAssignmentId: string
|
||||
}
|
||||
|
||||
export interface OktaRemoveUserRoleResponse extends ToolResponse {
|
||||
output: {
|
||||
userId: string
|
||||
roleAssignmentId: string
|
||||
removed: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// List Group Rules
|
||||
export interface OktaListGroupRulesParams extends OktaBaseParams {
|
||||
search?: string
|
||||
after?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface OktaListGroupRulesResponse extends ToolResponse {
|
||||
output: {
|
||||
rules: OktaGroupRuleOutput[]
|
||||
count: number
|
||||
nextCursor: string | null
|
||||
hasMore: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Get Group Rule
|
||||
export interface OktaGetGroupRuleParams extends OktaBaseParams {
|
||||
groupRuleId: string
|
||||
}
|
||||
|
||||
export interface OktaGetGroupRuleResponse extends ToolResponse {
|
||||
output: OktaGroupRuleOutput & { success: boolean }
|
||||
}
|
||||
|
||||
// Create Group Rule
|
||||
export interface OktaCreateGroupRuleParams extends OktaBaseParams {
|
||||
ruleName: string
|
||||
expression: string
|
||||
assignUserToGroupIds: string
|
||||
excludedUserIds?: string
|
||||
}
|
||||
|
||||
export interface OktaCreateGroupRuleResponse extends ToolResponse {
|
||||
output: OktaGroupRuleOutput & { success: boolean }
|
||||
}
|
||||
|
||||
// Activate Group Rule
|
||||
export interface OktaActivateGroupRuleParams extends OktaBaseParams {
|
||||
groupRuleId: string
|
||||
}
|
||||
|
||||
export interface OktaActivateGroupRuleResponse extends ToolResponse {
|
||||
output: {
|
||||
groupRuleId: string
|
||||
activated: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Deactivate Group Rule
|
||||
export interface OktaDeactivateGroupRuleParams extends OktaBaseParams {
|
||||
groupRuleId: string
|
||||
}
|
||||
|
||||
export interface OktaDeactivateGroupRuleResponse extends ToolResponse {
|
||||
output: {
|
||||
groupRuleId: string
|
||||
deactivated: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Delete Group Rule
|
||||
export interface OktaDeleteGroupRuleParams extends OktaBaseParams {
|
||||
groupRuleId: string
|
||||
removeUsers?: boolean
|
||||
}
|
||||
|
||||
export interface OktaDeleteGroupRuleResponse extends ToolResponse {
|
||||
output: {
|
||||
groupRuleId: string
|
||||
deleted: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Generic response type for the block
|
||||
export type OktaResponse =
|
||||
| OktaListUsersResponse
|
||||
@@ -444,3 +1156,29 @@ export type OktaResponse =
|
||||
| OktaAddUserToGroupResponse
|
||||
| OktaRemoveUserFromGroupResponse
|
||||
| OktaListGroupMembersResponse
|
||||
| OktaGetLogsResponse
|
||||
| OktaClearUserSessionsResponse
|
||||
| OktaGetSessionResponse
|
||||
| OktaRevokeSessionResponse
|
||||
| OktaListFactorsResponse
|
||||
| OktaGetFactorResponse
|
||||
| OktaResetFactorResponse
|
||||
| OktaResetAllFactorsResponse
|
||||
| OktaEnrollFactorResponse
|
||||
| OktaListAppsResponse
|
||||
| OktaGetAppResponse
|
||||
| OktaListAppUsersResponse
|
||||
| OktaAssignUserToAppResponse
|
||||
| OktaRemoveUserFromAppResponse
|
||||
| OktaListAppGroupsResponse
|
||||
| OktaAssignGroupToAppResponse
|
||||
| OktaRemoveGroupFromAppResponse
|
||||
| OktaListUserRolesResponse
|
||||
| OktaAssignUserRoleResponse
|
||||
| OktaRemoveUserRoleResponse
|
||||
| OktaListGroupRulesResponse
|
||||
| OktaGetGroupRuleResponse
|
||||
| OktaCreateGroupRuleResponse
|
||||
| OktaActivateGroupRuleResponse
|
||||
| OktaDeactivateGroupRuleResponse
|
||||
| OktaDeleteGroupRuleResponse
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaUnsuspendUserParams,
|
||||
OktaUnsuspendUserResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import type { OktaUnsuspendUserParams, OktaUnsuspendUserResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaUnsuspendUser')
|
||||
@@ -44,23 +41,12 @@ export const oktaUnsuspendUserTool: ToolConfig<OktaUnsuspendUserParams, OktaUnsu
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}/lifecycle/unsuspend`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// empty response body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to unsuspend user in Okta')
|
||||
await throwOktaError(response, logger, 'Failed to unsuspend user in Okta')
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaGroup,
|
||||
OktaUpdateGroupParams,
|
||||
OktaUpdateGroupResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import type { OktaGroup, OktaUpdateGroupParams, OktaUpdateGroupResponse } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaUpdateGroup')
|
||||
@@ -56,11 +52,7 @@ export const oktaUpdateGroupTool: ToolConfig<OktaUpdateGroupParams, OktaUpdateGr
|
||||
return `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId.trim())}`
|
||||
},
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
body: (params) => ({
|
||||
profile: {
|
||||
name: params.name,
|
||||
@@ -71,14 +63,7 @@ export const oktaUpdateGroupTool: ToolConfig<OktaUpdateGroupParams, OktaUpdateGr
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// non-JSON error body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to update group in Okta')
|
||||
await throwOktaError(response, logger, 'Failed to update group in Okta')
|
||||
}
|
||||
|
||||
const group: OktaGroup = await response.json()
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaUpdateUserParams,
|
||||
OktaUpdateUserResponse,
|
||||
OktaUser,
|
||||
} from '@/tools/okta/types'
|
||||
import type { OktaUpdateUserParams, OktaUpdateUserResponse, OktaUser } from '@/tools/okta/types'
|
||||
import { oktaHeaders, throwOktaError } from '@/tools/okta/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaUpdateUser')
|
||||
@@ -85,11 +81,7 @@ export const oktaUpdateUserTool: ToolConfig<OktaUpdateUserParams, OktaUpdateUser
|
||||
return `https://${domain}/api/v1/users/${encodeURIComponent(params.userId.trim())}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
headers: (params) => oktaHeaders(params.apiKey),
|
||||
body: (params) => {
|
||||
const profile: Record<string, string> = {}
|
||||
|
||||
@@ -107,14 +99,7 @@ export const oktaUpdateUserTool: ToolConfig<OktaUpdateUserParams, OktaUpdateUser
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// non-JSON error body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || 'Failed to update user in Okta')
|
||||
await throwOktaError(response, logger, 'Failed to update user in Okta')
|
||||
}
|
||||
|
||||
const user: OktaUser = await response.json()
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { mapOktaGroupRule, oktaHeaders, parseOktaPagination } from '@/tools/okta/utils'
|
||||
|
||||
/** Obvious non-secret so credential scanners do not flag these fixtures. */
|
||||
const PLACEHOLDER_TOKEN = 'not-a-real-api-token'
|
||||
|
||||
const BASE = 'https://example.okta.com/api/v1/users'
|
||||
|
||||
function responseWithLink(link?: string): Response {
|
||||
return new Response('[]', {
|
||||
status: 200,
|
||||
headers: link ? { Link: link } : {},
|
||||
})
|
||||
}
|
||||
|
||||
describe('oktaHeaders', () => {
|
||||
it('authenticates with the SSWS scheme rather than Bearer', () => {
|
||||
expect(oktaHeaders(PLACEHOLDER_TOKEN).Authorization).toBe(`SSWS ${PLACEHOLDER_TOKEN}`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseOktaPagination', () => {
|
||||
it('extracts the after cursor from a rel="next" link', () => {
|
||||
const link = `<${BASE}?after=cursor123&limit=200>; rel="next"`
|
||||
expect(parseOktaPagination(responseWithLink(link))).toEqual({
|
||||
nextCursor: 'cursor123',
|
||||
hasMore: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('reports the last page when only a self link is present', () => {
|
||||
const link = `<${BASE}?limit=200>; rel="self"`
|
||||
expect(parseOktaPagination(responseWithLink(link))).toEqual({
|
||||
nextCursor: null,
|
||||
hasMore: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('picks the next link when self is advertised alongside it', () => {
|
||||
const link = `<${BASE}?limit=200>; rel="self", <${BASE}?after=page2>; rel="next"`
|
||||
expect(parseOktaPagination(responseWithLink(link))).toEqual({
|
||||
nextCursor: 'page2',
|
||||
hasMore: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('reports the last page when no Link header is sent at all', () => {
|
||||
expect(parseOktaPagination(responseWithLink())).toEqual({ nextCursor: null, hasMore: false })
|
||||
})
|
||||
|
||||
it('reports more results without a cursor when the next link is malformed', () => {
|
||||
expect(parseOktaPagination(responseWithLink('<not a url>; rel="next"'))).toEqual({
|
||||
nextCursor: null,
|
||||
hasMore: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('mapOktaGroupRule', () => {
|
||||
it('lifts the expression, target groups, and exclusions to the top level', () => {
|
||||
const mapped = mapOktaGroupRule({
|
||||
id: '0pr1',
|
||||
name: 'Engineers',
|
||||
type: 'group_rule',
|
||||
status: 'ACTIVE',
|
||||
created: '2026-01-01T00:00:00.000Z',
|
||||
lastUpdated: '2026-01-02T00:00:00.000Z',
|
||||
conditions: {
|
||||
expression: { value: 'user.department == "Eng"', type: 'urn:okta:expression:1.0' },
|
||||
people: { users: { exclude: ['00u1'] }, groups: { exclude: ['00g1'] } },
|
||||
},
|
||||
actions: { assignUserToGroups: { groupIds: ['00g2'] } },
|
||||
})
|
||||
|
||||
expect(mapped).toMatchObject({
|
||||
id: '0pr1',
|
||||
expression: 'user.department == "Eng"',
|
||||
expressionType: 'urn:okta:expression:1.0',
|
||||
assignUserToGroupIds: ['00g2'],
|
||||
excludedUserIds: ['00u1'],
|
||||
excludedGroupIds: ['00g1'],
|
||||
})
|
||||
})
|
||||
|
||||
it('defaults absent nested conditions instead of throwing', () => {
|
||||
expect(
|
||||
mapOktaGroupRule({ id: '0pr2', name: 'Bare', type: 'group_rule', status: 'INACTIVE' })
|
||||
).toMatchObject({
|
||||
created: null,
|
||||
lastUpdated: null,
|
||||
expression: null,
|
||||
expressionType: null,
|
||||
assignUserToGroupIds: [],
|
||||
excludedUserIds: [],
|
||||
excludedGroupIds: [],
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,90 @@
|
||||
import type { Logger } from '@sim/logger'
|
||||
import type { OktaApiError, OktaGroupRule } from '@/tools/okta/types'
|
||||
|
||||
/**
|
||||
* Standard headers for every Okta Management API request.
|
||||
*
|
||||
* Okta authenticates API tokens with the `SSWS` scheme rather than `Bearer`.
|
||||
*/
|
||||
export function oktaHeaders(apiKey: string): Record<string, string> {
|
||||
return {
|
||||
Authorization: `SSWS ${apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an Okta error body and throws a `ToolResponse`-friendly `Error`.
|
||||
*
|
||||
* Okta returns `errorSummary` on failure, but lifecycle endpoints answer with an
|
||||
* empty body, so the JSON parse is best-effort and falls back to the caller's
|
||||
* message.
|
||||
*/
|
||||
export async function throwOktaError(
|
||||
response: Response,
|
||||
logger: Logger,
|
||||
fallbackMessage: string
|
||||
): Promise<never> {
|
||||
let error: OktaApiError = {}
|
||||
try {
|
||||
error = await response.json()
|
||||
} catch {
|
||||
// Lifecycle endpoints answer with an empty or non-JSON body
|
||||
}
|
||||
logger.error('Okta API request failed', { data: error, status: response.status })
|
||||
throw new Error(error.errorSummary || fallbackMessage)
|
||||
}
|
||||
|
||||
const NEXT_LINK_PATTERN = /<([^>]+)>\s*;\s*rel="next"/i
|
||||
|
||||
/**
|
||||
* Extracts Okta's cursor pagination state from the `Link` response header.
|
||||
*
|
||||
* Okta paginates with an opaque `after` cursor advertised in a `Link` header
|
||||
* entry marked `rel="next"` — never in the response body, which is a bare JSON
|
||||
* array. The absence of that entry is what marks the final page.
|
||||
*/
|
||||
export function parseOktaPagination(response: Response): {
|
||||
nextCursor: string | null
|
||||
hasMore: boolean
|
||||
} {
|
||||
const linkHeader = response.headers.get('Link')
|
||||
if (!linkHeader) return { nextCursor: null, hasMore: false }
|
||||
|
||||
const nextMatch = linkHeader.match(NEXT_LINK_PATTERN)
|
||||
if (!nextMatch) return { nextCursor: null, hasMore: false }
|
||||
|
||||
let nextCursor: string | null = null
|
||||
try {
|
||||
nextCursor = new URL(nextMatch[1]).searchParams.get('after')
|
||||
} catch {
|
||||
// Malformed next link — report more results without a usable cursor
|
||||
}
|
||||
|
||||
return { nextCursor, hasMore: true }
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens a group rule into the shape the list, get, and create tools all emit.
|
||||
*
|
||||
* The API nests the driving expression and the target groups several levels
|
||||
* deep, which is awkward to reference from a workflow, so the fields callers act
|
||||
* on are lifted to the top level. Okta documents `exclude` lists only — there is
|
||||
* no `include` counterpart on either people condition.
|
||||
*/
|
||||
export function mapOktaGroupRule(rule: OktaGroupRule) {
|
||||
return {
|
||||
id: rule.id,
|
||||
name: rule.name,
|
||||
type: rule.type,
|
||||
status: rule.status,
|
||||
created: rule.created ?? null,
|
||||
lastUpdated: rule.lastUpdated ?? null,
|
||||
expression: rule.conditions?.expression?.value ?? null,
|
||||
expressionType: rule.conditions?.expression?.type ?? null,
|
||||
assignUserToGroupIds: rule.actions?.assignUserToGroups?.groupIds ?? [],
|
||||
excludedUserIds: rule.conditions?.people?.users?.exclude ?? [],
|
||||
excludedGroupIds: rule.conditions?.people?.groups?.exclude ?? [],
|
||||
}
|
||||
}
|
||||
@@ -2868,20 +2868,46 @@ import {
|
||||
obsidianSearchTool,
|
||||
} from '@/tools/obsidian'
|
||||
import {
|
||||
oktaActivateGroupRuleTool,
|
||||
oktaActivateUserTool,
|
||||
oktaAddUserToGroupTool,
|
||||
oktaAssignGroupToAppTool,
|
||||
oktaAssignUserRoleTool,
|
||||
oktaAssignUserToAppTool,
|
||||
oktaClearUserSessionsTool,
|
||||
oktaCreateGroupRuleTool,
|
||||
oktaCreateGroupTool,
|
||||
oktaCreateUserTool,
|
||||
oktaDeactivateGroupRuleTool,
|
||||
oktaDeactivateUserTool,
|
||||
oktaDeleteGroupRuleTool,
|
||||
oktaDeleteGroupTool,
|
||||
oktaDeleteUserTool,
|
||||
oktaEnrollFactorTool,
|
||||
oktaGetAppTool,
|
||||
oktaGetFactorTool,
|
||||
oktaGetGroupRuleTool,
|
||||
oktaGetGroupTool,
|
||||
oktaGetLogsTool,
|
||||
oktaGetSessionTool,
|
||||
oktaGetUserTool,
|
||||
oktaListAppGroupsTool,
|
||||
oktaListAppsTool,
|
||||
oktaListAppUsersTool,
|
||||
oktaListFactorsTool,
|
||||
oktaListGroupMembersTool,
|
||||
oktaListGroupRulesTool,
|
||||
oktaListGroupsTool,
|
||||
oktaListUserRolesTool,
|
||||
oktaListUsersTool,
|
||||
oktaRemoveGroupFromAppTool,
|
||||
oktaRemoveUserFromAppTool,
|
||||
oktaRemoveUserFromGroupTool,
|
||||
oktaRemoveUserRoleTool,
|
||||
oktaResetAllFactorsTool,
|
||||
oktaResetFactorTool,
|
||||
oktaResetPasswordTool,
|
||||
oktaRevokeSessionTool,
|
||||
oktaSuspendUserTool,
|
||||
oktaUnsuspendUserTool,
|
||||
oktaUpdateGroupTool,
|
||||
@@ -6371,6 +6397,32 @@ export const tools: Record<string, ToolConfig> = {
|
||||
okta_add_user_to_group: oktaAddUserToGroupTool,
|
||||
okta_remove_user_from_group: oktaRemoveUserFromGroupTool,
|
||||
okta_list_group_members: oktaListGroupMembersTool,
|
||||
okta_get_logs: oktaGetLogsTool,
|
||||
okta_clear_user_sessions: oktaClearUserSessionsTool,
|
||||
okta_get_session: oktaGetSessionTool,
|
||||
okta_revoke_session: oktaRevokeSessionTool,
|
||||
okta_list_factors: oktaListFactorsTool,
|
||||
okta_get_factor: oktaGetFactorTool,
|
||||
okta_enroll_factor: oktaEnrollFactorTool,
|
||||
okta_reset_factor: oktaResetFactorTool,
|
||||
okta_reset_all_factors: oktaResetAllFactorsTool,
|
||||
okta_list_apps: oktaListAppsTool,
|
||||
okta_get_app: oktaGetAppTool,
|
||||
okta_list_app_users: oktaListAppUsersTool,
|
||||
okta_assign_user_to_app: oktaAssignUserToAppTool,
|
||||
okta_remove_user_from_app: oktaRemoveUserFromAppTool,
|
||||
okta_list_app_groups: oktaListAppGroupsTool,
|
||||
okta_assign_group_to_app: oktaAssignGroupToAppTool,
|
||||
okta_remove_group_from_app: oktaRemoveGroupFromAppTool,
|
||||
okta_list_user_roles: oktaListUserRolesTool,
|
||||
okta_assign_user_role: oktaAssignUserRoleTool,
|
||||
okta_remove_user_role: oktaRemoveUserRoleTool,
|
||||
okta_list_group_rules: oktaListGroupRulesTool,
|
||||
okta_get_group_rule: oktaGetGroupRuleTool,
|
||||
okta_create_group_rule: oktaCreateGroupRuleTool,
|
||||
okta_activate_group_rule: oktaActivateGroupRuleTool,
|
||||
okta_deactivate_group_rule: oktaDeactivateGroupRuleTool,
|
||||
okta_delete_group_rule: oktaDeleteGroupRuleTool,
|
||||
onepassword_list_vaults: onepasswordListVaultsTool,
|
||||
onepassword_get_vault: onepasswordGetVaultTool,
|
||||
onepassword_list_items: onepasswordListItemsTool,
|
||||
|
||||
Reference in New Issue
Block a user