chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaActivateUserParams,
|
||||
OktaActivateUserResponse,
|
||||
OktaApiError,
|
||||
} from '@/tools/okta/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaActivateUser')
|
||||
|
||||
export const oktaActivateUserTool: ToolConfig<OktaActivateUserParams, OktaActivateUserResponse> = {
|
||||
id: 'okta_activate_user',
|
||||
name: 'Activate User in Okta',
|
||||
description:
|
||||
'Activate a user in your Okta organization. Can only be performed on users with STAGED or DEPROVISIONED status. Optionally sends an activation email.',
|
||||
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 activate',
|
||||
},
|
||||
sendEmail: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Send activation email to the user (default: true)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const sendEmail = params.sendEmail ?? true
|
||||
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',
|
||||
}),
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
let activationUrl: string | null = null
|
||||
let activationToken: string | null = null
|
||||
try {
|
||||
const data = await response.json()
|
||||
activationUrl = data.activationUrl ?? null
|
||||
activationToken = data.activationToken ?? null
|
||||
} catch {
|
||||
// empty body when sendEmail=true
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
userId: params?.userId ?? '',
|
||||
activated: true,
|
||||
activationUrl,
|
||||
activationToken,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
userId: { type: 'string', description: 'Activated user ID' },
|
||||
activated: { type: 'boolean', description: 'Whether the user was activated' },
|
||||
activationUrl: {
|
||||
type: 'string',
|
||||
description: 'Activation URL (only returned when sendEmail is false)',
|
||||
optional: true,
|
||||
},
|
||||
activationToken: {
|
||||
type: 'string',
|
||||
description: 'Activation token (only returned when sendEmail is false)',
|
||||
optional: true,
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaAddUserToGroupParams,
|
||||
OktaAddUserToGroupResponse,
|
||||
OktaApiError,
|
||||
} from '@/tools/okta/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaAddUserToGroup')
|
||||
|
||||
export const oktaAddUserToGroupTool: ToolConfig<
|
||||
OktaAddUserToGroupParams,
|
||||
OktaAddUserToGroupResponse
|
||||
> = {
|
||||
id: 'okta_add_user_to_group',
|
||||
name: 'Add User to Group in Okta',
|
||||
description: 'Add a user to a group in your Okta organization',
|
||||
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)',
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group ID to add the user to',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID to add to the group',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
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',
|
||||
}),
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
groupId: params?.groupId ?? '',
|
||||
userId: params?.userId ?? '',
|
||||
added: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
groupId: { type: 'string', description: 'Group ID' },
|
||||
userId: { type: 'string', description: 'User ID added to the group' },
|
||||
added: { type: 'boolean', description: 'Whether the user was added' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
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 { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaCreateGroup')
|
||||
|
||||
export const oktaCreateGroupTool: ToolConfig<OktaCreateGroupParams, OktaCreateGroupResponse> = {
|
||||
id: 'okta_create_group',
|
||||
name: 'Create Group in Okta',
|
||||
description: 'Create a new group in your Okta organization',
|
||||
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)',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name of the group',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Description of the group',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
return `https://${domain}/api/v1/groups`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const profile: Record<string, string> = { name: params.name }
|
||||
if (params.description) profile.description = params.description
|
||||
return { profile }
|
||||
},
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
const group: OktaGroup = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: group.id,
|
||||
name: group.profile?.name ?? '',
|
||||
description: group.profile?.description ?? null,
|
||||
type: group.type,
|
||||
created: group.created,
|
||||
lastUpdated: group.lastUpdated,
|
||||
lastMembershipUpdated: group.lastMembershipUpdated ?? null,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Created group ID' },
|
||||
name: { type: 'string', description: 'Group name' },
|
||||
description: { type: 'string', description: 'Group description', optional: true },
|
||||
type: { type: 'string', description: 'Group type' },
|
||||
created: { type: 'string', description: 'Creation timestamp' },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp' },
|
||||
lastMembershipUpdated: {
|
||||
type: 'string',
|
||||
description: 'Last membership change timestamp',
|
||||
optional: true,
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
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 { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaCreateUser')
|
||||
|
||||
export const oktaCreateUserTool: ToolConfig<OktaCreateUserParams, OktaCreateUserResponse> = {
|
||||
id: 'okta_create_user',
|
||||
name: 'Create User in Okta',
|
||||
description: 'Create a new user in your Okta organization',
|
||||
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)',
|
||||
},
|
||||
firstName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'First name of the user',
|
||||
},
|
||||
lastName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Last name of the user',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email address of the user',
|
||||
},
|
||||
login: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Login for the user (defaults to email if not provided)',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Password for the user (if not set, user will be emailed to set password)',
|
||||
},
|
||||
mobilePhone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Mobile phone number',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Job title',
|
||||
},
|
||||
department: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Department',
|
||||
},
|
||||
activate: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to activate the user immediately (default: true)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const activate = params.activate ?? true
|
||||
return `https://${domain}/api/v1/users?activate=${activate}`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const profile: Record<string, string> = {
|
||||
firstName: params.firstName,
|
||||
lastName: params.lastName,
|
||||
email: params.email,
|
||||
login: params.login || params.email,
|
||||
}
|
||||
|
||||
if (params.mobilePhone) profile.mobilePhone = params.mobilePhone
|
||||
if (params.title) profile.title = params.title
|
||||
if (params.department) profile.department = params.department
|
||||
|
||||
const body: Record<string, unknown> = { profile }
|
||||
|
||||
if (params.password) {
|
||||
body.credentials = {
|
||||
password: { value: params.password },
|
||||
}
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
const user: OktaUser = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: user.id,
|
||||
status: user.status,
|
||||
firstName: user.profile?.firstName ?? null,
|
||||
lastName: user.profile?.lastName ?? null,
|
||||
email: user.profile?.email ?? null,
|
||||
login: user.profile?.login ?? null,
|
||||
created: user.created,
|
||||
lastUpdated: user.lastUpdated,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Created user ID' },
|
||||
status: { type: 'string', description: 'User status' },
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
email: { type: 'string', description: 'Email address', optional: true },
|
||||
login: { type: 'string', description: 'Login', optional: true },
|
||||
created: { type: 'string', description: 'Creation timestamp' },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaDeactivateUserParams,
|
||||
OktaDeactivateUserResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaDeactivateUser')
|
||||
|
||||
export const oktaDeactivateUserTool: ToolConfig<
|
||||
OktaDeactivateUserParams,
|
||||
OktaDeactivateUserResponse
|
||||
> = {
|
||||
id: 'okta_deactivate_user',
|
||||
name: 'Deactivate User in Okta',
|
||||
description:
|
||||
'Deactivate a user in your Okta organization. This transitions the user to DEPROVISIONED status.',
|
||||
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 deactivate',
|
||||
},
|
||||
sendEmail: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Send deactivation email to admin (default: false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const sendEmail = params.sendEmail === true
|
||||
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',
|
||||
}),
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
userId: params?.userId ?? '',
|
||||
deactivated: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
userId: { type: 'string', description: 'Deactivated user ID' },
|
||||
deactivated: { type: 'boolean', description: 'Whether the user was deactivated' },
|
||||
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 {
|
||||
OktaApiError,
|
||||
OktaDeleteGroupParams,
|
||||
OktaDeleteGroupResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaDeleteGroup')
|
||||
|
||||
export const oktaDeleteGroupTool: ToolConfig<OktaDeleteGroupParams, OktaDeleteGroupResponse> = {
|
||||
id: 'okta_delete_group',
|
||||
name: 'Delete Group from Okta',
|
||||
description:
|
||||
'Delete a group from your Okta organization. Groups of OKTA_GROUP or APP_GROUP type can be removed.',
|
||||
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)',
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group ID to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
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',
|
||||
}),
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
groupId: params?.groupId ?? '',
|
||||
deleted: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
groupId: { type: 'string', description: 'Deleted group ID' },
|
||||
deleted: { type: 'boolean', description: 'Whether the group was deleted' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type { OktaApiError, OktaDeleteUserParams, OktaDeleteUserResponse } from '@/tools/okta/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaDeleteUser')
|
||||
|
||||
export const oktaDeleteUserTool: ToolConfig<OktaDeleteUserParams, OktaDeleteUserResponse> = {
|
||||
id: 'okta_delete_user',
|
||||
name: 'Delete User from Okta',
|
||||
description:
|
||||
'Permanently delete a user from your Okta organization. Can only be performed on DEPROVISIONED users. If the user is active, this will first deactivate them and a second call is needed to delete.',
|
||||
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 to delete',
|
||||
},
|
||||
sendEmail: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Send deactivation email to admin (default: false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const sendEmail = params.sendEmail === true
|
||||
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',
|
||||
}),
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
userId: params?.userId ?? '',
|
||||
deleted: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
userId: { type: 'string', description: 'Deleted user ID' },
|
||||
deleted: { type: 'boolean', description: 'Whether the user was deleted' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
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 { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaGetGroup')
|
||||
|
||||
export const oktaGetGroupTool: ToolConfig<OktaGetGroupParams, OktaGetGroupResponse> = {
|
||||
id: 'okta_get_group',
|
||||
name: 'Get Group from Okta',
|
||||
description: 'Get a specific group by ID from your Okta organization',
|
||||
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)',
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group ID to look up',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
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',
|
||||
}),
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
const group: OktaGroup = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: group.id,
|
||||
name: group.profile?.name ?? '',
|
||||
description: group.profile?.description ?? null,
|
||||
type: group.type,
|
||||
created: group.created,
|
||||
lastUpdated: group.lastUpdated,
|
||||
lastMembershipUpdated: group.lastMembershipUpdated ?? null,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Group ID' },
|
||||
name: { type: 'string', description: 'Group name' },
|
||||
description: { type: 'string', description: 'Group description', optional: true },
|
||||
type: { type: 'string', description: 'Group type' },
|
||||
created: { type: 'string', description: 'Creation timestamp' },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp' },
|
||||
lastMembershipUpdated: {
|
||||
type: 'string',
|
||||
description: 'Last membership change timestamp',
|
||||
optional: true,
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
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 { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaGetUser')
|
||||
|
||||
export const oktaGetUserTool: ToolConfig<OktaGetUserParams, OktaGetUserResponse> = {
|
||||
id: 'okta_get_user',
|
||||
name: 'Get User from Okta',
|
||||
description: 'Get a specific user by ID or login from your Okta organization',
|
||||
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 (email) to look up',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
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',
|
||||
}),
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
const user: OktaUser = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: user.id,
|
||||
status: user.status,
|
||||
firstName: user.profile?.firstName ?? null,
|
||||
lastName: user.profile?.lastName ?? null,
|
||||
email: user.profile?.email ?? null,
|
||||
login: user.profile?.login ?? null,
|
||||
mobilePhone: user.profile?.mobilePhone ?? null,
|
||||
secondEmail: user.profile?.secondEmail ?? null,
|
||||
displayName: user.profile?.displayName ?? null,
|
||||
title: user.profile?.title ?? null,
|
||||
department: user.profile?.department ?? null,
|
||||
organization: user.profile?.organization ?? null,
|
||||
manager: user.profile?.manager ?? null,
|
||||
managerId: user.profile?.managerId ?? null,
|
||||
division: user.profile?.division ?? null,
|
||||
employeeNumber: user.profile?.employeeNumber ?? null,
|
||||
userType: user.profile?.userType ?? null,
|
||||
created: user.created,
|
||||
activated: user.activated ?? null,
|
||||
lastLogin: user.lastLogin ?? null,
|
||||
lastUpdated: user.lastUpdated,
|
||||
statusChanged: user.statusChanged ?? null,
|
||||
passwordChanged: user.passwordChanged ?? null,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
status: { type: 'string', description: 'User status' },
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
email: { type: 'string', description: 'Email address', optional: true },
|
||||
login: { type: 'string', description: 'Login (usually email)', optional: true },
|
||||
mobilePhone: { type: 'string', description: 'Mobile phone', optional: true },
|
||||
secondEmail: { type: 'string', description: 'Secondary email', optional: true },
|
||||
displayName: { type: 'string', description: 'Display name', optional: true },
|
||||
title: { type: 'string', description: 'Job title', optional: true },
|
||||
department: { type: 'string', description: 'Department', optional: true },
|
||||
organization: { type: 'string', description: 'Organization', optional: true },
|
||||
manager: { type: 'string', description: 'Manager name', optional: true },
|
||||
managerId: { type: 'string', description: 'Manager ID', optional: true },
|
||||
division: { type: 'string', description: 'Division', optional: true },
|
||||
employeeNumber: { type: 'string', description: 'Employee number', optional: true },
|
||||
userType: { type: 'string', description: 'User type', optional: true },
|
||||
created: { type: 'string', description: 'Creation timestamp' },
|
||||
activated: { type: 'string', description: 'Activation timestamp', optional: true },
|
||||
lastLogin: { type: 'string', description: 'Last login timestamp', optional: true },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp' },
|
||||
statusChanged: { type: 'string', description: 'Status change timestamp', optional: true },
|
||||
passwordChanged: { type: 'string', description: 'Password change timestamp', optional: true },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export { oktaActivateUserTool } from './activate_user'
|
||||
export { oktaAddUserToGroupTool } from './add_user_to_group'
|
||||
export { oktaCreateGroupTool } from './create_group'
|
||||
export { oktaCreateUserTool } from './create_user'
|
||||
export { oktaDeactivateUserTool } from './deactivate_user'
|
||||
export { oktaDeleteGroupTool } from './delete_group'
|
||||
export { oktaDeleteUserTool } from './delete_user'
|
||||
export { oktaGetGroupTool } from './get_group'
|
||||
export { oktaGetUserTool } from './get_user'
|
||||
export { oktaListGroupMembersTool } from './list_group_members'
|
||||
export { oktaListGroupsTool } from './list_groups'
|
||||
export { oktaListUsersTool } from './list_users'
|
||||
export { oktaRemoveUserFromGroupTool } from './remove_user_from_group'
|
||||
export { oktaResetPasswordTool } from './reset_password'
|
||||
export { oktaSuspendUserTool } from './suspend_user'
|
||||
export * from './types'
|
||||
export { oktaUnsuspendUserTool } from './unsuspend_user'
|
||||
export { oktaUpdateGroupTool } from './update_group'
|
||||
export { oktaUpdateUserTool } from './update_user'
|
||||
@@ -0,0 +1,140 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaListGroupMembersParams,
|
||||
OktaListGroupMembersResponse,
|
||||
OktaUser,
|
||||
} from '@/tools/okta/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaListGroupMembers')
|
||||
|
||||
export const oktaListGroupMembersTool: ToolConfig<
|
||||
OktaListGroupMembersParams,
|
||||
OktaListGroupMembersResponse
|
||||
> = {
|
||||
id: 'okta_list_group_members',
|
||||
name: 'List Group Members from Okta',
|
||||
description: 'List all members of a specific group in your Okta organization',
|
||||
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)',
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group ID to list members for',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of members to return (default: 1000, max: 1000)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
const base = `https://${domain}/api/v1/groups/${encodeURIComponent(params.groupId.trim())}/users`
|
||||
return queryString ? `${base}?${queryString}` : base
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
const data: OktaUser[] = await response.json()
|
||||
|
||||
const members = data.map((user) => ({
|
||||
id: user.id,
|
||||
status: user.status,
|
||||
firstName: user.profile?.firstName ?? null,
|
||||
lastName: user.profile?.lastName ?? null,
|
||||
email: user.profile?.email ?? null,
|
||||
login: user.profile?.login ?? null,
|
||||
mobilePhone: user.profile?.mobilePhone ?? null,
|
||||
title: user.profile?.title ?? null,
|
||||
department: user.profile?.department ?? null,
|
||||
created: user.created,
|
||||
lastLogin: user.lastLogin ?? null,
|
||||
lastUpdated: user.lastUpdated,
|
||||
activated: user.activated ?? null,
|
||||
statusChanged: user.statusChanged ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
members,
|
||||
count: members.length,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
members: {
|
||||
type: 'array',
|
||||
description: 'Array of group member user objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
status: { type: 'string', description: 'User status' },
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
email: { type: 'string', description: 'Email address', optional: true },
|
||||
login: { type: 'string', description: 'Login', optional: true },
|
||||
mobilePhone: { type: 'string', description: 'Mobile phone', optional: true },
|
||||
title: { type: 'string', description: 'Job title', optional: true },
|
||||
department: { type: 'string', description: 'Department', optional: true },
|
||||
created: { type: 'string', description: 'Creation timestamp' },
|
||||
lastLogin: { type: 'string', description: 'Last login timestamp', optional: true },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp' },
|
||||
activated: { type: 'string', description: 'Activation timestamp', optional: true },
|
||||
statusChanged: {
|
||||
type: 'string',
|
||||
description: 'Status change timestamp',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of members returned' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
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 { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaListGroups')
|
||||
|
||||
export const oktaListGroupsTool: ToolConfig<OktaListGroupsParams, OktaListGroupsResponse> = {
|
||||
id: 'okta_list_groups',
|
||||
name: 'List Groups from Okta',
|
||||
description: 'List all groups in your Okta organization with optional search and filtering',
|
||||
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:
|
||||
'Okta search expression for groups (e.g., profile.name sw "Engineering" or type eq "OKTA_GROUP")',
|
||||
},
|
||||
filter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Okta filter expression (e.g., type eq "OKTA_GROUP")',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of groups to return (default: 10000, max: 10000)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.search) queryParams.append('search', params.search)
|
||||
if (params.filter) queryParams.append('filter', params.filter)
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString
|
||||
? `https://${domain}/api/v1/groups?${queryString}`
|
||||
: `https://${domain}/api/v1/groups`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
const data: OktaGroup[] = await response.json()
|
||||
|
||||
const groups = data.map((group) => ({
|
||||
id: group.id,
|
||||
name: group.profile?.name ?? '',
|
||||
description: group.profile?.description ?? null,
|
||||
type: group.type,
|
||||
created: group.created,
|
||||
lastUpdated: group.lastUpdated,
|
||||
lastMembershipUpdated: group.lastMembershipUpdated ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
groups,
|
||||
count: groups.length,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
groups: {
|
||||
type: 'array',
|
||||
description: 'Array of Okta group objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Group ID' },
|
||||
name: { type: 'string', description: 'Group name' },
|
||||
description: { type: 'string', description: 'Group description', optional: true },
|
||||
type: { type: 'string', description: 'Group type (OKTA_GROUP, APP_GROUP, BUILT_IN)' },
|
||||
created: { type: 'string', description: 'Creation timestamp' },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp' },
|
||||
lastMembershipUpdated: {
|
||||
type: 'string',
|
||||
description: 'Last membership change timestamp',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of groups returned' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
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 { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaListUsers')
|
||||
|
||||
export const oktaListUsersTool: ToolConfig<OktaListUsersParams, OktaListUsersResponse> = {
|
||||
id: 'okta_list_users',
|
||||
name: 'List Users from Okta',
|
||||
description: 'List all users in your Okta organization with optional search and filtering',
|
||||
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:
|
||||
'Okta search expression (e.g., profile.firstName eq "John" or profile.email co "example.com")',
|
||||
},
|
||||
filter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Okta filter expression (e.g., status eq "ACTIVE")',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of users to return (default: 200, max: 200)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.search) queryParams.append('search', params.search)
|
||||
if (params.filter) queryParams.append('filter', params.filter)
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString
|
||||
? `https://${domain}/api/v1/users?${queryString}`
|
||||
: `https://${domain}/api/v1/users`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `SSWS ${params.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
const data: OktaUser[] = await response.json()
|
||||
|
||||
const users = data.map((user) => ({
|
||||
id: user.id,
|
||||
status: user.status,
|
||||
firstName: user.profile?.firstName ?? null,
|
||||
lastName: user.profile?.lastName ?? null,
|
||||
email: user.profile?.email ?? null,
|
||||
login: user.profile?.login ?? null,
|
||||
mobilePhone: user.profile?.mobilePhone ?? null,
|
||||
title: user.profile?.title ?? null,
|
||||
department: user.profile?.department ?? null,
|
||||
created: user.created,
|
||||
lastLogin: user.lastLogin ?? null,
|
||||
lastUpdated: user.lastUpdated,
|
||||
activated: user.activated ?? null,
|
||||
statusChanged: user.statusChanged ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
users,
|
||||
count: users.length,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Array of Okta user objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'User status (ACTIVE, STAGED, PROVISIONED, etc.)',
|
||||
},
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
email: { type: 'string', description: 'Email address', optional: true },
|
||||
login: { type: 'string', description: 'Login (usually email)', optional: true },
|
||||
mobilePhone: { type: 'string', description: 'Mobile phone', optional: true },
|
||||
title: { type: 'string', description: 'Job title', optional: true },
|
||||
department: { type: 'string', description: 'Department', optional: true },
|
||||
created: { type: 'string', description: 'Creation timestamp' },
|
||||
lastLogin: { type: 'string', description: 'Last login timestamp', optional: true },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp' },
|
||||
activated: { type: 'string', description: 'Activation timestamp', optional: true },
|
||||
statusChanged: { type: 'string', description: 'Status change timestamp', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of users returned' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaRemoveUserFromGroupParams,
|
||||
OktaRemoveUserFromGroupResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaRemoveUserFromGroup')
|
||||
|
||||
export const oktaRemoveUserFromGroupTool: ToolConfig<
|
||||
OktaRemoveUserFromGroupParams,
|
||||
OktaRemoveUserFromGroupResponse
|
||||
> = {
|
||||
id: 'okta_remove_user_from_group',
|
||||
name: 'Remove User from Group in Okta',
|
||||
description: 'Remove a user from a group in your Okta organization',
|
||||
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)',
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group ID to remove the user from',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID to remove from the group',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
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',
|
||||
}),
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
groupId: params?.groupId ?? '',
|
||||
userId: params?.userId ?? '',
|
||||
removed: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
groupId: { type: 'string', description: 'Group ID' },
|
||||
userId: { type: 'string', description: 'User ID removed from the group' },
|
||||
removed: { type: 'boolean', description: 'Whether the user was removed' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaResetPasswordParams,
|
||||
OktaResetPasswordResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaResetPassword')
|
||||
|
||||
export const oktaResetPasswordTool: ToolConfig<OktaResetPasswordParams, OktaResetPasswordResponse> =
|
||||
{
|
||||
id: 'okta_reset_password',
|
||||
name: 'Reset Password in Okta',
|
||||
description:
|
||||
'Generate a one-time token to reset a user password. Can email the reset link to the user or return it directly. Transitions the user to RECOVERY status.',
|
||||
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 reset password for',
|
||||
},
|
||||
sendEmail: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Send password reset email to the user (default: true)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
const sendEmail = params.sendEmail ?? true
|
||||
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',
|
||||
}),
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
let resetPasswordUrl: string | null = null
|
||||
try {
|
||||
const data = await response.json()
|
||||
resetPasswordUrl = data.resetPasswordUrl ?? null
|
||||
} catch {
|
||||
// empty body when sendEmail=true
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
userId: params?.userId ?? '',
|
||||
resetPasswordUrl,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
userId: { type: 'string', description: 'User ID' },
|
||||
resetPasswordUrl: {
|
||||
type: 'string',
|
||||
description: 'Password reset URL (only returned when sendEmail is false)',
|
||||
optional: true,
|
||||
},
|
||||
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 {
|
||||
OktaApiError,
|
||||
OktaSuspendUserParams,
|
||||
OktaSuspendUserResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaSuspendUser')
|
||||
|
||||
export const oktaSuspendUserTool: ToolConfig<OktaSuspendUserParams, OktaSuspendUserResponse> = {
|
||||
id: 'okta_suspend_user',
|
||||
name: 'Suspend User in Okta',
|
||||
description:
|
||||
'Suspend a user in your Okta organization. Only users with ACTIVE status can be suspended. Suspended users cannot log in but retain group and app assignments.',
|
||||
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 suspend',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
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',
|
||||
}),
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
userId: params?.userId ?? '',
|
||||
suspended: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
userId: { type: 'string', description: 'Suspended user ID' },
|
||||
suspended: { type: 'boolean', description: 'Whether the user was suspended' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Okta API error response
|
||||
*/
|
||||
export interface OktaApiError {
|
||||
errorCode?: string
|
||||
errorSummary?: string
|
||||
errorCauses?: { errorSummary: string }[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Common params for all Okta tools
|
||||
*/
|
||||
interface OktaBaseParams {
|
||||
apiKey: string
|
||||
domain: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Okta User profile object from the API
|
||||
*/
|
||||
interface OktaUserProfile {
|
||||
firstName?: string | null
|
||||
lastName?: string | null
|
||||
email?: string | null
|
||||
login?: string | null
|
||||
mobilePhone?: string | null
|
||||
secondEmail?: string | null
|
||||
displayName?: string | null
|
||||
nickName?: string | null
|
||||
title?: string | null
|
||||
department?: string | null
|
||||
organization?: string | null
|
||||
manager?: string | null
|
||||
managerId?: string | null
|
||||
division?: string | null
|
||||
costCenter?: string | null
|
||||
employeeNumber?: string | null
|
||||
userType?: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Okta User object from the API
|
||||
*/
|
||||
export interface OktaUser {
|
||||
id: string
|
||||
status: string
|
||||
created: string
|
||||
activated: string | null
|
||||
statusChanged: string | null
|
||||
lastLogin: string | null
|
||||
lastUpdated: string
|
||||
passwordChanged: string | null
|
||||
type: { id: string }
|
||||
profile: OktaUserProfile
|
||||
}
|
||||
|
||||
/**
|
||||
* Okta Group profile from the API
|
||||
*/
|
||||
interface OktaGroupProfile {
|
||||
name: string
|
||||
description?: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Okta Group object from the API
|
||||
*/
|
||||
export interface OktaGroup {
|
||||
id: string
|
||||
created: string
|
||||
lastUpdated: string
|
||||
lastMembershipUpdated: string | null
|
||||
type: string
|
||||
profile: OktaGroupProfile
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformed user output
|
||||
*/
|
||||
interface OktaUserOutput {
|
||||
id: string
|
||||
status: string
|
||||
firstName: string | null
|
||||
lastName: string | null
|
||||
email: string | null
|
||||
login: string | null
|
||||
mobilePhone: string | null
|
||||
title: string | null
|
||||
department: string | null
|
||||
created: string
|
||||
lastLogin: string | null
|
||||
lastUpdated: string
|
||||
activated: string | null
|
||||
statusChanged: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformed group output
|
||||
*/
|
||||
interface OktaGroupOutput {
|
||||
id: string
|
||||
name: string
|
||||
description: string | null
|
||||
type: string
|
||||
created: string
|
||||
lastUpdated: string
|
||||
lastMembershipUpdated: string | null
|
||||
}
|
||||
|
||||
// List Users
|
||||
export interface OktaListUsersParams extends OktaBaseParams {
|
||||
search?: string
|
||||
filter?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface OktaListUsersResponse extends ToolResponse {
|
||||
output: {
|
||||
users: OktaUserOutput[]
|
||||
count: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Get User
|
||||
export interface OktaGetUserParams extends OktaBaseParams {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface OktaGetUserResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
status: string
|
||||
firstName: string | null
|
||||
lastName: string | null
|
||||
email: string | null
|
||||
login: string | null
|
||||
mobilePhone: string | null
|
||||
secondEmail: string | null
|
||||
displayName: string | null
|
||||
title: string | null
|
||||
department: string | null
|
||||
organization: string | null
|
||||
manager: string | null
|
||||
managerId: string | null
|
||||
division: string | null
|
||||
employeeNumber: string | null
|
||||
userType: string | null
|
||||
created: string
|
||||
activated: string | null
|
||||
lastLogin: string | null
|
||||
lastUpdated: string
|
||||
statusChanged: string | null
|
||||
passwordChanged: string | null
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Create User
|
||||
export interface OktaCreateUserParams extends OktaBaseParams {
|
||||
firstName: string
|
||||
lastName: string
|
||||
email: string
|
||||
login?: string
|
||||
password?: string
|
||||
mobilePhone?: string
|
||||
title?: string
|
||||
department?: string
|
||||
activate?: boolean
|
||||
}
|
||||
|
||||
export interface OktaCreateUserResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
status: string
|
||||
firstName: string | null
|
||||
lastName: string | null
|
||||
email: string | null
|
||||
login: string | null
|
||||
created: string
|
||||
lastUpdated: string
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Update User
|
||||
export interface OktaUpdateUserParams extends OktaBaseParams {
|
||||
userId: string
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
email?: string
|
||||
login?: string
|
||||
mobilePhone?: string
|
||||
title?: string
|
||||
department?: string
|
||||
}
|
||||
|
||||
export interface OktaUpdateUserResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
status: string
|
||||
firstName: string | null
|
||||
lastName: string | null
|
||||
email: string | null
|
||||
login: string | null
|
||||
created: string
|
||||
lastUpdated: string
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Deactivate User
|
||||
export interface OktaDeactivateUserParams extends OktaBaseParams {
|
||||
userId: string
|
||||
sendEmail?: boolean
|
||||
}
|
||||
|
||||
export interface OktaDeactivateUserResponse extends ToolResponse {
|
||||
output: {
|
||||
userId: string
|
||||
deactivated: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// List Groups
|
||||
export interface OktaListGroupsParams extends OktaBaseParams {
|
||||
search?: string
|
||||
filter?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface OktaListGroupsResponse extends ToolResponse {
|
||||
output: {
|
||||
groups: OktaGroupOutput[]
|
||||
count: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Get Group
|
||||
export interface OktaGetGroupParams extends OktaBaseParams {
|
||||
groupId: string
|
||||
}
|
||||
|
||||
export interface OktaGetGroupResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
name: string
|
||||
description: string | null
|
||||
type: string
|
||||
created: string
|
||||
lastUpdated: string
|
||||
lastMembershipUpdated: string | null
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Add User to Group
|
||||
export interface OktaAddUserToGroupParams extends OktaBaseParams {
|
||||
groupId: string
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface OktaAddUserToGroupResponse extends ToolResponse {
|
||||
output: {
|
||||
groupId: string
|
||||
userId: string
|
||||
added: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Remove User from Group
|
||||
export interface OktaRemoveUserFromGroupParams extends OktaBaseParams {
|
||||
groupId: string
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface OktaRemoveUserFromGroupResponse extends ToolResponse {
|
||||
output: {
|
||||
groupId: string
|
||||
userId: string
|
||||
removed: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// List Group Members
|
||||
export interface OktaListGroupMembersParams extends OktaBaseParams {
|
||||
groupId: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface OktaListGroupMembersResponse extends ToolResponse {
|
||||
output: {
|
||||
members: OktaUserOutput[]
|
||||
count: number
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Suspend User
|
||||
export interface OktaSuspendUserParams extends OktaBaseParams {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface OktaSuspendUserResponse extends ToolResponse {
|
||||
output: {
|
||||
userId: string
|
||||
suspended: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Unsuspend User
|
||||
export interface OktaUnsuspendUserParams extends OktaBaseParams {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface OktaUnsuspendUserResponse extends ToolResponse {
|
||||
output: {
|
||||
userId: string
|
||||
unsuspended: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Activate User
|
||||
export interface OktaActivateUserParams extends OktaBaseParams {
|
||||
userId: string
|
||||
sendEmail?: boolean
|
||||
}
|
||||
|
||||
export interface OktaActivateUserResponse extends ToolResponse {
|
||||
output: {
|
||||
userId: string
|
||||
activated: boolean
|
||||
activationUrl: string | null
|
||||
activationToken: string | null
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Reset Password
|
||||
export interface OktaResetPasswordParams extends OktaBaseParams {
|
||||
userId: string
|
||||
sendEmail?: boolean
|
||||
}
|
||||
|
||||
export interface OktaResetPasswordResponse extends ToolResponse {
|
||||
output: {
|
||||
userId: string
|
||||
resetPasswordUrl: string | null
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Delete User
|
||||
export interface OktaDeleteUserParams extends OktaBaseParams {
|
||||
userId: string
|
||||
sendEmail?: boolean
|
||||
}
|
||||
|
||||
export interface OktaDeleteUserResponse extends ToolResponse {
|
||||
output: {
|
||||
userId: string
|
||||
deleted: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Create Group
|
||||
export interface OktaCreateGroupParams extends OktaBaseParams {
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export interface OktaCreateGroupResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
name: string
|
||||
description: string | null
|
||||
type: string
|
||||
created: string
|
||||
lastUpdated: string
|
||||
lastMembershipUpdated: string | null
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Update Group
|
||||
export interface OktaUpdateGroupParams extends OktaBaseParams {
|
||||
groupId: string
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export interface OktaUpdateGroupResponse extends ToolResponse {
|
||||
output: {
|
||||
id: string
|
||||
name: string
|
||||
description: string | null
|
||||
type: string
|
||||
created: string
|
||||
lastUpdated: string
|
||||
lastMembershipUpdated: string | null
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Delete Group
|
||||
export interface OktaDeleteGroupParams extends OktaBaseParams {
|
||||
groupId: string
|
||||
}
|
||||
|
||||
export interface OktaDeleteGroupResponse extends ToolResponse {
|
||||
output: {
|
||||
groupId: string
|
||||
deleted: boolean
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Generic response type for the block
|
||||
export type OktaResponse =
|
||||
| OktaListUsersResponse
|
||||
| OktaGetUserResponse
|
||||
| OktaCreateUserResponse
|
||||
| OktaUpdateUserResponse
|
||||
| OktaDeactivateUserResponse
|
||||
| OktaSuspendUserResponse
|
||||
| OktaUnsuspendUserResponse
|
||||
| OktaActivateUserResponse
|
||||
| OktaResetPasswordResponse
|
||||
| OktaDeleteUserResponse
|
||||
| OktaListGroupsResponse
|
||||
| OktaGetGroupResponse
|
||||
| OktaCreateGroupResponse
|
||||
| OktaUpdateGroupResponse
|
||||
| OktaDeleteGroupResponse
|
||||
| OktaAddUserToGroupResponse
|
||||
| OktaRemoveUserFromGroupResponse
|
||||
| OktaListGroupMembersResponse
|
||||
@@ -0,0 +1,81 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { validateOktaDomain } from '@/lib/core/security/input-validation'
|
||||
import type {
|
||||
OktaApiError,
|
||||
OktaUnsuspendUserParams,
|
||||
OktaUnsuspendUserResponse,
|
||||
} from '@/tools/okta/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaUnsuspendUser')
|
||||
|
||||
export const oktaUnsuspendUserTool: ToolConfig<OktaUnsuspendUserParams, OktaUnsuspendUserResponse> =
|
||||
{
|
||||
id: 'okta_unsuspend_user',
|
||||
name: 'Unsuspend User in Okta',
|
||||
description:
|
||||
'Unsuspend a previously suspended user in your Okta organization. Returns the user to ACTIVE status.',
|
||||
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 unsuspend',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
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',
|
||||
}),
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
userId: params?.userId ?? '',
|
||||
unsuspended: true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
userId: { type: 'string', description: 'Unsuspended user ID' },
|
||||
unsuspended: { type: 'boolean', description: 'Whether the user was unsuspended' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
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 { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaUpdateGroup')
|
||||
|
||||
export const oktaUpdateGroupTool: ToolConfig<OktaUpdateGroupParams, OktaUpdateGroupResponse> = {
|
||||
id: 'okta_update_group',
|
||||
name: 'Update Group in Okta',
|
||||
description:
|
||||
'Update a group profile in your Okta organization. Only groups of OKTA_GROUP type can be updated. All profile properties must be specified (full replacement).',
|
||||
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)',
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Group ID to update',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated group name',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated group description',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
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',
|
||||
}),
|
||||
body: (params) => ({
|
||||
profile: {
|
||||
name: params.name,
|
||||
description: params.description ?? '',
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
const group: OktaGroup = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: group.id,
|
||||
name: group.profile?.name ?? '',
|
||||
description: group.profile?.description ?? null,
|
||||
type: group.type,
|
||||
created: group.created,
|
||||
lastUpdated: group.lastUpdated,
|
||||
lastMembershipUpdated: group.lastMembershipUpdated ?? null,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Group ID' },
|
||||
name: { type: 'string', description: 'Group name' },
|
||||
description: { type: 'string', description: 'Group description', optional: true },
|
||||
type: { type: 'string', description: 'Group type' },
|
||||
created: { type: 'string', description: 'Creation timestamp' },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp' },
|
||||
lastMembershipUpdated: {
|
||||
type: 'string',
|
||||
description: 'Last membership change timestamp',
|
||||
optional: true,
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
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 { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('OktaUpdateUser')
|
||||
|
||||
export const oktaUpdateUserTool: ToolConfig<OktaUpdateUserParams, OktaUpdateUserResponse> = {
|
||||
id: 'okta_update_user',
|
||||
name: 'Update User in Okta',
|
||||
description: 'Update a user profile in your Okta organization',
|
||||
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 update',
|
||||
},
|
||||
firstName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated first name',
|
||||
},
|
||||
lastName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated last name',
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated email address',
|
||||
},
|
||||
login: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated login',
|
||||
},
|
||||
mobilePhone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated mobile phone number',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated job title',
|
||||
},
|
||||
department: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated department',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const domain = validateOktaDomain(params.domain)
|
||||
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',
|
||||
}),
|
||||
body: (params) => {
|
||||
const profile: Record<string, string> = {}
|
||||
|
||||
if (params.firstName !== undefined) profile.firstName = params.firstName
|
||||
if (params.lastName !== undefined) profile.lastName = params.lastName
|
||||
if (params.email !== undefined) profile.email = params.email
|
||||
if (params.login !== undefined) profile.login = params.login
|
||||
if (params.mobilePhone !== undefined) profile.mobilePhone = params.mobilePhone
|
||||
if (params.title !== undefined) profile.title = params.title
|
||||
if (params.department !== undefined) profile.department = params.department
|
||||
|
||||
return { profile }
|
||||
},
|
||||
},
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
const user: OktaUser = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: user.id,
|
||||
status: user.status,
|
||||
firstName: user.profile?.firstName ?? null,
|
||||
lastName: user.profile?.lastName ?? null,
|
||||
email: user.profile?.email ?? null,
|
||||
login: user.profile?.login ?? null,
|
||||
created: user.created,
|
||||
lastUpdated: user.lastUpdated,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
status: { type: 'string', description: 'User status' },
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
email: { type: 'string', description: 'Email address', optional: true },
|
||||
login: { type: 'string', description: 'Login', optional: true },
|
||||
created: { type: 'string', description: 'Creation timestamp' },
|
||||
lastUpdated: { type: 'string', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user