Skip to content

Commit

Permalink
feat(integrations): add okta integrations (#65)
Browse files Browse the repository at this point in the history
## Describe your changes
Add okta actions:

- create-user
- add-group
- add-user-group
- remove-user-group

## Issue ticket number and link

[EXT-190](https://linear.app/nango/issue/EXT-190/basic-user-and-group-actions-with-okta)
## Checklist before requesting a review (skip if just adding/editing
APIs & templates)
- [x] I added tests, otherwise the reason is:
- [x] External API requests have `retries`
- [ ] Pagination is used where appropriate
- [ ] The built in `nango.paginate` call is used instead of a `while
(true)` loop
- [x] Third party requests are NOT parallelized (this can cause issues
with rate limits)
- [ ] If a sync requires metadata the `nango.yaml` has `auto_start:
false`
- [ ] If the sync is a `full` sync then `track_deletes: true` is set
  • Loading branch information
hassan254-prog authored Oct 22, 2024
1 parent 7d7b609 commit 620bbfd
Show file tree
Hide file tree
Showing 30 changed files with 846 additions and 0 deletions.
75 changes: 75 additions & 0 deletions flows.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4893,6 +4893,81 @@ integrations:
firstName: string
lastName: string
isBot: boolean
okta:
actions:
create-user:
description: Creates a new user in your Okta org without credentials.
output: User
endpoint: POST /user
input: OktaCreateUser
add-group:
description: Adds a new group with the OKTA_GROUP type to your org
output: Group
endpoint: POST /group
input: OktaAddGroup
add-user-group:
description: Assigns a user to a group with the OKTA_GROUP type
output: SuccessResponse
endpoint: PUT /user-group
input: OktaAssignRemoveUserGroup
remove-user-group:
description: Unassigns a user from a group with the OKTA_GROUP type
output: SuccessResponse
endpoint: DELETE /user-group
input: OktaAssignRemoveUserGroup
models:
ActionResponseError:
message: string
OktaAddGroup:
description?: string | undefined
name: string
OktaUserGroupProfile:
description: string | null
name: string
OktaActiveDirectoryGroupProfile:
description: string
dn: string
externalId: string
name: string
samAccountName: string
windowsDomainQualifiedName: string
Group:
id: string
created: string
lastMembershipUpdated: string
lastUpdated: string
objectClass: string[]
type: APP_GROUP | BUILT_IN | OKTA_GROUP
profile: OktaUserGroupProfile | OktaActiveDirectoryGroupProfile
OktaAssignRemoveUserGroup:
groupId: string
userId: string
SuccessResponse:
success: boolean
OktaCreateUser:
firstName: string
lastName: string
email: string
login: string
mobilePhone?: string | undefined | null
User:
id: string
status: string
created: string
activated: string
statusChanged: string
lastLogin: string | null
lastUpdated: string
passwordChanged: string | null
type:
id: string
profile:
firstName: string | null
lastName: string | null
mobilePhone: string | null
secondEmail: string | null
login: string
email: string
outlook:
syncs:
emails:
Expand Down
28 changes: 28 additions & 0 deletions integrations/okta/actions/add-group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { NangoAction, Group, OktaAddGroup, ProxyConfiguration, ActionResponseError } from '../../models';
import { toGroup, createGroup } from '../mappers/toGroup.js';
import { oktaAddGroupSchema } from '../schema.zod.js';

export default async function runAction(nango: NangoAction, input: OktaAddGroup): Promise<Group> {
const parsedInput = oktaAddGroupSchema.safeParse(input);

if (!parsedInput.success) {
for (const error of parsedInput.error.errors) {
await nango.log(`Invalid input provided to add a group: ${error.message} at path ${error.path.join('.')}`, { level: 'error' });
}
throw new nango.ActionError<ActionResponseError>({
message: 'Invalid input provided to add a group'
});
}

const oktaGroup = createGroup(parsedInput.data);
const config: ProxyConfiguration = {
// https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/#tag/Group/operation/addGroup
endpoint: '/api/v1/groups',
data: oktaGroup,
retries: 10
};

const response = await nango.post(config);

return toGroup(response.data);
}
26 changes: 26 additions & 0 deletions integrations/okta/actions/add-user-group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { NangoAction, ProxyConfiguration, SuccessResponse, OktaAssignRemoveUserGroup, ActionResponseError } from '../../models';
import { oktaAssignRemoveUserGroupSchema } from '../schema.zod.js';

export default async function runAction(nango: NangoAction, input: OktaAssignRemoveUserGroup): Promise<SuccessResponse> {
const parsedInput = oktaAssignRemoveUserGroupSchema.safeParse(input);

if (!parsedInput.success) {
for (const error of parsedInput.error.errors) {
await nango.log(`Invalid input provided to assign user to group: ${error.message} at path ${error.path.join('.')}`, { level: 'error' });
}
throw new nango.ActionError<ActionResponseError>({
message: 'Invalid input provided to assign a user to a group'
});
}
const config: ProxyConfiguration = {
// https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/#tag/Group/operation/assignUserToGroup
endpoint: `/api/v1/groups/${parsedInput.data.groupId}/users/${parsedInput.data.userId}`,
retries: 10
};

await nango.put(config);

return {
success: true
};
}
36 changes: 36 additions & 0 deletions integrations/okta/actions/create-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { NangoAction, User, OktaAddGroup, ProxyConfiguration, ActionResponseError, OktaCreateUser } from '../../models';
import { toUser, createUser } from '../mappers/toUser.js';
import { oktaCreateUserSchema } from '../schema.zod.js';

export default async function runAction(nango: NangoAction, input: OktaAddGroup): Promise<User> {
const parsedInput = oktaCreateUserSchema.safeParse(input);

if (!parsedInput.success) {
for (const error of parsedInput.error.errors) {
await nango.log(`Invalid input provided to add a user: ${error.message} at path ${error.path.join('.')}`, { level: 'error' });
}
throw new nango.ActionError<ActionResponseError>({
message: 'Invalid input provided to add a user'
});
}

const oktaCreateUser: OktaCreateUser = {
firstName: parsedInput.data.firstName,
lastName: parsedInput.data.lastName,
email: parsedInput.data.email,
login: parsedInput.data.login,
mobilePhone: parsedInput.data.mobilePhone
};

const oktaGroup = createUser(oktaCreateUser);
const config: ProxyConfiguration = {
// https://developer.okta.com/docs/api/openapi/okta-management/management/tag/User/#tag/User/operation/createUser
endpoint: '/api/v1/users',
data: oktaGroup,
retries: 10
};

const response = await nango.post(config);

return toUser(response.data);
}
26 changes: 26 additions & 0 deletions integrations/okta/actions/remove-user-group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { NangoAction, ProxyConfiguration, SuccessResponse, OktaAssignRemoveUserGroup, ActionResponseError } from '../../models';
import { oktaAssignRemoveUserGroupSchema } from '../schema.zod.js';

export default async function runAction(nango: NangoAction, input: OktaAssignRemoveUserGroup): Promise<SuccessResponse> {
const parsedInput = oktaAssignRemoveUserGroupSchema.safeParse(input);

if (!parsedInput.success) {
for (const error of parsedInput.error.errors) {
await nango.log(`Invalid input provided to unassigns user to group: ${error.message} at path ${error.path.join('.')}`, { level: 'error' });
}
throw new nango.ActionError<ActionResponseError>({
message: 'Invalid input provided to unassigns a user to a group'
});
}
const config: ProxyConfiguration = {
// https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Group/#tag/Group/operation/unassignUserFromGroup
endpoint: `/api/v1/groups/${parsedInput.data.groupId}/users/${parsedInput.data.userId}`,
retries: 10
};

await nango.delete(config);

return {
success: true
};
}
4 changes: 4 additions & 0 deletions integrations/okta/fixtures/add-group.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "All users West of The Rockies-4",
"name": "West Coast users-4"
}
4 changes: 4 additions & 0 deletions integrations/okta/fixtures/add-user-group.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"groupId": "00gkbzqeosaN1oHI3697",
"userId": "00ukbzq8w1QIKpArM697"
}
6 changes: 6 additions & 0 deletions integrations/okta/fixtures/create-user.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"firstName": "Isaac",
"lastName": "Brock",
"email": "mocktest2@gmail.com",
"login": "mocktest2@gmail.com"
}
4 changes: 4 additions & 0 deletions integrations/okta/fixtures/remove-user-group.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"groupId": "00gkbzqeosaN1oHI3697",
"userId": "00ukbzq8w1QIKpArM697"
}
50 changes: 50 additions & 0 deletions integrations/okta/mappers/toGroup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { OktaUserGroupProfile, OktaActiveDirectoryGroupProfile, OktaGroup, CreateOktaGroup } from '../types';
import type { Group, OktaAddGroup } from '../../models';

export function toGroup(group: OktaGroup): Group {
let profile: OktaUserGroupProfile | OktaActiveDirectoryGroupProfile | null = null;

if (group.type === 'OKTA_GROUP' || group.type === 'BUILT_IN' || group.type === 'APP_GROUP') {
if ('dn' in group.profile) {
profile = {
description: group.profile.description,
dn: group.profile.dn,
externalId: group.profile.externalId,
name: group.profile.name,
samAccountName: group.profile.samAccountName,
windowsDomainQualifiedName: group.profile.windowsDomainQualifiedName
};
} else {
profile = {
description: group.profile.description || null,
name: group.profile.name
};
}
}

return {
id: group.id,
created: group.created,
lastMembershipUpdated: group.lastMembershipUpdated,
lastUpdated: group.lastUpdated,
objectClass: group.objectClass,
type: group.type,
profile: profile!
};
}

export function createGroup(group: OktaAddGroup): Partial<CreateOktaGroup> {
const oktaGroup: Partial<CreateOktaGroup> = {
profile: {}
};

if (group.name) {
oktaGroup.profile!.name = group.name;
}

if (group.description) {
oktaGroup.profile!.description = group.description;
}

return oktaGroup;
}
49 changes: 49 additions & 0 deletions integrations/okta/mappers/toUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { OktaUser, CreateOktaUser } from '../types';
import type { User, OktaCreateUser } from '../../models';

export function toUser(user: OktaUser): User {
return {
id: user.id,
status: user.status,
created: user.created,
activated: user.activated,
statusChanged: user.statusChanged,
lastLogin: user.lastLogin || null,
lastUpdated: user.lastUpdated,
passwordChanged: user.passwordChanged || null,
type: {
id: user.type.id
},
profile: {
firstName: user.profile.firstName || null,
lastName: user.profile.lastName || null,
mobilePhone: user.profile.mobilePhone || null,
secondEmail: user.profile.secondEmail || null,
login: user.profile.login,
email: user.profile.email
}
};
}

export function createUser(user: OktaCreateUser): Partial<CreateOktaUser> {
const oktaUser: Partial<CreateOktaUser> = {
profile: {}
};

if (user.email) {
oktaUser.profile!.email = user.email;
}
if (user.firstName) {
oktaUser.profile!.firstName = user.firstName;
}
if (user.lastName) {
oktaUser.profile!.lastName = user.lastName;
}
if (user.login) {
oktaUser.profile!.login = user.login;
}
if (user.mobilePhone) {
oktaUser.profile!.mobilePhone = user.mobilePhone;
}
return oktaUser;
}
4 changes: 4 additions & 0 deletions integrations/okta/mocks/add-group/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "All users West of The Rockies-4",
"name": "West Coast users-4"
}
12 changes: 12 additions & 0 deletions integrations/okta/mocks/add-group/output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"id": "00gkchm69mlK5gLVc697",
"created": "2024-10-22T06:25:16.000Z",
"lastMembershipUpdated": "2024-10-22T06:25:16.000Z",
"lastUpdated": "2024-10-22T06:25:16.000Z",
"objectClass": ["okta:user_group"],
"type": "OKTA_GROUP",
"profile": {
"description": "All users West of The Rockies-4",
"name": "West Coast users-4"
}
}
4 changes: 4 additions & 0 deletions integrations/okta/mocks/add-user-group/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"groupId": "00gkchm69mlK5gLVc697",
"userId": "00ukchnhgwqj8qtUY697"
}
3 changes: 3 additions & 0 deletions integrations/okta/mocks/add-user-group/output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"success": true
}
6 changes: 6 additions & 0 deletions integrations/okta/mocks/create-user/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"firstName": "Isaac",
"lastName": "Brock",
"email": "mocktest2@gmail.com",
"login": "mocktest2@gmail.com"
}
21 changes: 21 additions & 0 deletions integrations/okta/mocks/create-user/output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"id": "00ukchnhgwqj8qtUY697",
"status": "PROVISIONED",
"created": "2024-10-22T06:24:35.000Z",
"activated": "2024-10-22T06:24:35.000Z",
"statusChanged": "2024-10-22T06:24:35.000Z",
"lastLogin": null,
"lastUpdated": "2024-10-22T06:24:35.000Z",
"passwordChanged": null,
"type": {
"id": "otyk6uwil7SusQigU697"
},
"profile": {
"firstName": "Isaac",
"lastName": "Brock",
"mobilePhone": null,
"secondEmail": null,
"login": "mocktest2@gmail.com",
"email": "mocktest2@gmail.com"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
""
Loading

0 comments on commit 620bbfd

Please sign in to comment.