Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,32 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { oktaAuth, makeOktaRequest, userIdDropdown } from '../common/common';
import { HttpMethod } from '@activepieces/pieces-common';
export const activateUserAction = createAction({
auth: oktaAuth,
name: 'activate_user',
displayName: 'Activate User',
description: 'Activate a previously deactivated or pending user',
props: {
userId: userIdDropdown(),
sendEmail: Property.Checkbox({
displayName: 'Send Email',
description: 'Send activation email to user',
required: false,
defaultValue: false,
}),
},
async run(context) {
const userId = context.propsValue.userId;
const sendEmail = context.propsValue.sendEmail ? 'true' : 'false';
const response = await makeOktaRequest(
context.auth,
`/users/${userId}/lifecycle/activate?sendEmail=${sendEmail}`,
HttpMethod.POST
);
return response.body;
},
});

View File

@@ -0,0 +1,33 @@
import { createAction } from '@activepieces/pieces-framework';
import { oktaAuth, makeOktaRequest, userIdDropdown, groupIdDropdown } from '../common/common';
import { HttpMethod } from '@activepieces/pieces-common';
export const addUserToGroupAction = createAction({
auth: oktaAuth,
name: 'add_user_to_group',
displayName: 'Add User to Group',
description: 'Add a user to a specific Okta group',
props: {
userId: userIdDropdown(),
groupId: groupIdDropdown,
},
async run(context) {
const userId = context.propsValue.userId;
const groupId = context.propsValue.groupId;
const response = await makeOktaRequest(
context.auth,
`/groups/${groupId}/users/${userId}`,
HttpMethod.PUT
);
return {
success: true,
userId,
groupId,
message: 'User added to group',
};
},
});

View File

@@ -0,0 +1,65 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { oktaAuth, makeOktaRequest } from '../common/common';
import { HttpMethod } from '@activepieces/pieces-common';
export const createUserAction = createAction({
auth: oktaAuth,
name: 'create_user',
displayName: 'Create User',
description: 'Creates a user without credentials and sends account creation prompt via email',
props: {
firstName: Property.ShortText({
displayName: 'First Name',
description: 'User first name',
required: true,
}),
lastName: Property.ShortText({
displayName: 'Last Name',
description: 'User last name',
required: true,
}),
email: Property.ShortText({
displayName: 'Email',
description: 'User email address',
required: true,
}),
login: Property.ShortText({
displayName: 'Login',
description: 'User login (typically same as email)',
required: false,
}),
mobilePhone: Property.ShortText({
displayName: 'Mobile Phone',
description: 'User mobile phone number',
required: false,
}),
sendEmail: Property.Checkbox({
displayName: 'Send Email',
description: 'Send account creation email to user',
required: false,
defaultValue: true,
}),
},
async run(context) {
const userData = {
profile: {
firstName: context.propsValue.firstName,
lastName: context.propsValue.lastName,
email: context.propsValue.email,
login: context.propsValue.login || context.propsValue.email,
},
};
const queryParam = context.propsValue.sendEmail ? '?activate=true' : '';
const response = await makeOktaRequest(
context.auth,
`/users${queryParam}`,
HttpMethod.POST,
userData
);
return response.body;
},
});

View File

@@ -0,0 +1,32 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { oktaAuth, makeOktaRequest, userIdDropdown } from '../common/common';
import { HttpMethod } from '@activepieces/pieces-common';
export const deactivateUserAction = createAction({
auth: oktaAuth,
name: 'deactivate_user',
displayName: 'Deactivate User',
description: 'Deactivate (disable) a user in Okta',
props: {
userId: userIdDropdown(),
sendEmail: Property.Checkbox({
displayName: 'Send Email',
description: 'Send deactivation email to user',
required: false,
defaultValue: false,
}),
},
async run(context) {
const userId = context.propsValue.userId;
const sendEmail = context.propsValue.sendEmail ? 'true' : 'false';
const response = await makeOktaRequest(
context.auth,
`/users/${userId}/lifecycle/deactivate?sendEmail=${sendEmail}`,
HttpMethod.POST
);
return response.body;
},
});

View File

@@ -0,0 +1,28 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { oktaAuth, makeOktaRequest } from '../common/common';
export const findGroupByNameAction = createAction({
auth: oktaAuth,
name: 'find_group_by_name',
displayName: 'Find Group by Name',
description: 'Search for an Okta group by name',
props: {
groupName: Property.ShortText({
displayName: 'Group Name',
description: 'The group name to search for',
required: true,
}),
},
async run(context) {
const groupName = context.propsValue.groupName;
const response = await makeOktaRequest(
context.auth,
`/groups?q=${encodeURIComponent(groupName)}`
);
return response.body;
},
});

View File

@@ -0,0 +1,43 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { oktaAuth, makeOktaRequest } from '../common/common';
import { HttpMethod } from '@activepieces/pieces-common';
export const findUserByEmailAction = createAction({
auth: oktaAuth,
name: 'find_user_by_email',
displayName: 'Find User by Email',
description: 'Look up an Okta user by their email address',
props: {
domain: Property.ShortText({
displayName: 'Okta Domain',
description: 'Your Okta organization domain',
required: true,
}),
email: Property.ShortText({
displayName: 'Email',
description: 'The user email address',
required: true,
}),
},
async run(context) {
const email = context.propsValue.email;
const response = await makeOktaRequest(
context.auth,
`/users?search=profile.email eq "${email}"`,
HttpMethod.GET,
context.propsValue.domain
);
if (response.body && response.body.length > 0) {
return response.body[0];
}
return {
success: false,
message: `No user found with email: ${email}`,
data: [],
};
},
});

View File

@@ -0,0 +1,36 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import {
oktaAuth,
makeOktaRequest,
userIdDropdown,
groupIdDropdown,
} from '../common/common';
import { HttpMethod } from '@activepieces/pieces-common';
export const removeUserFromGroupAction = createAction({
auth: oktaAuth,
name: 'remove_user_from_group',
displayName: 'Remove User from Group',
description: 'Remove a user from an Okta group',
props: {
groupId: groupIdDropdown,
userId: userIdDropdown(true),
},
async run(context) {
const userId = context.propsValue.userId;
const groupId = context.propsValue.groupId;
const response = await makeOktaRequest(
context.auth,
`/groups/${groupId}/users/${userId}`,
HttpMethod.DELETE
);
return {
success: true,
userId,
groupId,
message: 'User removed from group',
};
},
});

View File

@@ -0,0 +1,25 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { oktaAuth, makeOktaRequest, userIdDropdown } from '../common/common';
import { HttpMethod } from '@activepieces/pieces-common';
export const suspendUserAction = createAction({
auth: oktaAuth,
name: 'suspend_user',
displayName: 'Suspend User',
description: 'Temporarily suspend a user in Okta',
props: {
userId: userIdDropdown(),
},
async run(context) {
const userId = context.propsValue.userId;
const response = await makeOktaRequest(
context.auth,
`/users/${userId}/lifecycle/suspend`,
HttpMethod.POST
);
return response.body;
},
});

View File

@@ -0,0 +1,71 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { oktaAuth, makeOktaRequest, userIdDropdown } from '../common/common';
import { HttpMethod } from '@activepieces/pieces-common';
export const updateUserAction = createAction({
auth: oktaAuth,
name: 'update_user',
displayName: 'Update User',
description: 'Update user profile information',
props: {
userId: userIdDropdown(),
firstName: Property.ShortText({
displayName: 'First Name',
description: 'Updated first name',
required: false,
}),
lastName: Property.ShortText({
displayName: 'Last Name',
description: 'Updated last name',
required: false,
}),
email: Property.ShortText({
displayName: 'Email',
description: 'Updated email address',
required: false,
}),
mobilePhone: Property.ShortText({
displayName: 'Mobile Phone',
description: 'Updated mobile phone number',
required: false,
}),
customAttributes: Property.Json({
displayName: 'Custom Attributes',
description: 'JSON object with custom profile attributes',
required: false,
}),
},
async run(context) {
const userId = context.propsValue.userId;
const userData: any = {
profile: {},
};
if (context.propsValue.firstName) {
userData.profile.firstName = context.propsValue.firstName;
}
if (context.propsValue.lastName) {
userData.profile.lastName = context.propsValue.lastName;
}
if (context.propsValue.email) {
userData.profile.email = context.propsValue.email;
}
if (context.propsValue.mobilePhone) {
userData.profile.mobilePhone = context.propsValue.mobilePhone;
}
if (context.propsValue.customAttributes) {
userData.profile = { ...userData.profile, ...context.propsValue.customAttributes };
}
const response = await makeOktaRequest(
context.auth,
`/users/${userId}`,
HttpMethod.POST,
userData
);
return response.body;
},
});

View File

@@ -0,0 +1,162 @@
import { PieceAuth } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { Property } from '@activepieces/pieces-framework';
export const oktaAuth = PieceAuth.CustomAuth({
required: true,
props: {
domain: Property.ShortText({
displayName: 'Okta Domain',
description: 'Your Okta organization domain (e.g., https://dev-12345.okta.com or dev-12345.okta.com)',
required: true,
}),
apiToken: Property.ShortText({
displayName: 'API Token',
description: 'Your Okta API token (from Admin → Security → API → Tokens)',
required: true,
}),
},
});
export async function makeOktaRequest(
auth: any,
endpoint: string,
method: HttpMethod = HttpMethod.GET,
body?: any
) {
const apiToken = auth.apiToken;
let domain = auth.domain;
if (!domain) {
throw new Error('Okta domain is required');
}
if (!domain.startsWith('https://') && !domain.startsWith('http://')) {
domain = `https://${domain}`;
}
domain = domain.replace(/\/$/, '');
return await httpClient.sendRequest({
method,
url: `${domain}/api/v1${endpoint}`,
headers: {
'Authorization': `SSWS ${apiToken}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body,
});
}
export const userIdDropdown = (groupusers = false) =>
Property.Dropdown({
auth: oktaAuth,
displayName: 'User',
description: 'Select a user',
required: true,
refreshers: ['auth', 'groupId'],
options: async ({ auth, groupId }) => {
try {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'select auth first',
};
}
if (groupusers && !groupId) {
return {
disabled: true,
options: [],
placeholder: 'please select groupId first',
};
}
const path = groupusers
? `/groups/${groupId}/users`
: '/users';
const response = await makeOktaRequest(
auth,
path,
HttpMethod.GET,
undefined
);
const users = await response.body;
console.log(JSON.stringify(users, null, 2));
if (!Array.isArray(users)) {
return {
disabled: true,
options: [],
placeholder: 'No users found',
};
}
return {
disabled: false,
options: users.map((user: any) => ({
label:
user.profile.firstName +
' ' +
user.profile.lastName +
' (' +
user.profile.email +
')',
value: user.id,
})),
};
} catch (e) {
return {
disabled: true,
options: [],
placeholder: 'Error fetching users',
};
}
},
});
export const groupIdDropdown =
Property.Dropdown({
auth: oktaAuth,
displayName: 'Group',
description: 'Select a group',
required: true,
refreshers: ['auth'],
options: async ({ auth }) => {
try {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'No groups found',
};
}
const response = await makeOktaRequest(
auth,
'/groups',
HttpMethod.GET
);
const groups = await response.body;
if (!Array.isArray(groups)) {
return {
disabled: true,
options: [],
placeholder: 'No groups found',
};
}
return {
disabled: false,
options: groups.map((group: any) => ({
label: group.profile.name,
value: group.id,
})),
};
} catch (e) {
return {
disabled: true,
options: [],
placeholder: 'Error fetching groups',
};
}
},
});

View File

@@ -0,0 +1,268 @@
import {
createTrigger,
TriggerStrategy,
Property,
} from '@activepieces/pieces-framework';
import { oktaAuth, makeOktaRequest } from '../common/common';
import { HttpMethod } from '@activepieces/pieces-common';
import { WebhookHandshakeStrategy } from '@activepieces/shared';
export const newEventTrigger = createTrigger({
auth: oktaAuth,
name: 'new_event',
displayName: 'New Event',
description: 'Fires when a new Okta event is generated',
type: TriggerStrategy.WEBHOOK,
props: {
eventTypes: Property.StaticMultiSelectDropdown({
displayName: 'Event Types to Monitor',
description: 'Select which event types to trigger on',
required: false,
options: {
options: [
{ label: 'User Created', value: 'user.lifecycle.create' },
{ label: 'User Activated', value: 'user.lifecycle.activate' },
{ label: 'User Deactivated', value: 'user.lifecycle.deactivate' },
{ label: 'User Suspended', value: 'user.lifecycle.suspend' },
{ label: 'User Unsuspended', value: 'user.lifecycle.unsuspend' },
{ label: 'User Deleted', value: 'user.lifecycle.delete.completed' },
{ label: 'User Added to Group', value: 'group.user_membership.add' },
{
label: 'User Removed from Group',
value: 'group.user_membership.remove',
},
{ label: 'Group Created', value: 'group.lifecycle.create' },
{ label: 'Group Deleted', value: 'group.lifecycle.delete' },
{ label: 'User Login', value: 'user.session.start' },
{ label: 'User Logout', value: 'user.session.end' },
],
},
}),
hookName: Property.ShortText({
displayName: 'Hook Name',
description: 'Name for the Okta Event Hook (optional)',
required: false,
defaultValue: 'Activepieces Webhook',
}),
},
handshakeConfiguration: {
strategy: WebhookHandshakeStrategy.HEADER_PRESENT,
paramName: 'x-okta-verification-challenge',
},
async onHandshake(context) {
const challengeValue =
context.payload.headers['x-okta-verification-challenge'];
if (challengeValue) {
console.log('Okta verification challenge received:', challengeValue);
return {
status: 200,
body: {
verification: challengeValue,
},
headers: {
'Content-Type': 'application/json',
},
};
}
return {
status: 400,
body: { error: 'No verification challenge found' },
};
},
async onEnable(context) {
try {
const eventTypes = context.propsValue.eventTypes || [];
const hookName = context.propsValue.hookName || 'Webhook';
const existingHooks = await makeOktaRequest(
context.auth,
'/eventHooks',
HttpMethod.GET
);
const existingHook = existingHooks.body?.find(
(hook: any) =>
hook.name === hookName &&
hook.channel?.config?.uri === context.webhookUrl
);
let hookId: string;
if (existingHook) {
hookId = existingHook.id;
} else {
const eventHookPayload = {
name: hookName,
events: {
type: 'EVENT_TYPE',
items:
eventTypes.length > 0
? eventTypes
: [
'user.lifecycle.create',
'user.lifecycle.activate',
'user.lifecycle.deactivate',
'user.lifecycle.suspend',
'user.lifecycle.unsuspend',
'user.lifecycle.update',
'user.lifecycle.delete',
'group.user_membership.add',
'group.user_membership.remove',
'group.lifecycle.create',
'group.lifecycle.update',
'group.lifecycle.delete',
'user.session.start',
'user.session.end',
'user.authentication.auth_failed',
],
},
channel: {
type: 'HTTP',
version: '1.0.0',
config: {
uri: context.webhookUrl,
method: 'POST',
},
},
};
const response = await makeOktaRequest(
context.auth,
'/eventHooks',
HttpMethod.POST,
eventHookPayload
);
if (!response.body?.id) {
throw new Error(
'Failed to create event hook: ' + JSON.stringify(response.body)
);
}
hookId = response.body.id;
console.log(`Created new event hook: ${hookId}`);
}
const response = await makeOktaRequest(
context.auth,
`/eventHooks/${hookId}/lifecycle/verify`,
HttpMethod.POST
);
if (response.status !== 200) {
throw new Error(
'Failed to verify event hook: ' + JSON.stringify(response.body)
);
}
} catch (error) {
console.error('Error creating Okta event hook:', error);
throw new Error(`Failed to setup Okta event hook: ${error}`);
}
},
async onDisable(context) {
try {
const hookId = await context.store.get('hookId');
if (hookId) {
try {
await makeOktaRequest(
context.auth,
`/eventHooks/${hookId}/lifecycle/deactivate`,
HttpMethod.POST
);
console.log(`Deactivated event hook: ${hookId}`);
} catch (deactivateError) {
console.warn('Failed to deactivate event hook:', deactivateError);
}
try {
await makeOktaRequest(
context.auth,
`/eventHooks/${hookId}`,
HttpMethod.DELETE
);
console.log(`Deleted event hook: ${hookId}`);
} catch (deleteError) {
console.warn('Failed to delete event hook:', deleteError);
}
}
await context.store.delete('hookId');
} catch (error) {
console.error('Error cleaning up Okta event hook:', error);
}
},
async run(context) {
const payload: any = context.payload.body;
console.log("firstfdsdfsdf",JSON.stringify(payload))
const configuredEventTypes = context.propsValue.eventTypes || [];
if (!payload.data?.events || !Array.isArray(payload.data.events)) {
console.log('No events found in payload, skipping');
return [];
}
const filteredEvents = [];
for (const event of payload.data.events) {
if (configuredEventTypes.length > 0) {
if (!configuredEventTypes.includes(event.eventType)) {
console.log(
`Event type ${event.eventType} not in configured types, skipping`
);
continue;
}
}
filteredEvents.push(event);
}
return filteredEvents;
},
async test(context) {
try {
const response = await makeOktaRequest(
context.auth,
'/logs?limit=1',
HttpMethod.GET
);
return response.body || [];
} catch (error) {
console.error('Test error:', error);
return [];
}
},
sampleData: {
eventId: 'evt_123456789',
timestamp: new Date().toISOString(),
version: '0',
severity: 'INFO',
eventType: 'user.lifecycle.create',
displayMessage: 'User created: user@example.com',
actor: {
id: 'admin123',
type: 'User',
alternateId: 'admin@example.com',
displayName: 'Admin User',
},
outcome: {
result: 'SUCCESS',
reason: '',
},
target: [
{
id: 'user123',
type: 'User',
alternateId: 'user@example.com',
displayName: 'New User',
},
],
},
});