Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/organizations/fixtures/it-contact.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"object": "it_contact",
"id": "it_contact_01HXYZ123456789ABCDEFGHIJ",
"email": "it-contact@example.com",
"created_at": "2026-01-15T12:00:00.000Z",
"updated_at": "2026-01-15T12:00:00.000Z"
}
16 changes: 16 additions & 0 deletions src/organizations/fixtures/list-it-contacts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"object": "list",
"data": [
{
"object": "it_contact",
"id": "it_contact_01HXYZ123456789ABCDEFGHIJ",
"email": "it-contact@example.com",
"created_at": "2026-01-15T12:00:00.000Z",
"updated_at": "2026-01-15T12:00:00.000Z"
}
],
"list_metadata": {
"before": null,
"after": null
}
}
2 changes: 2 additions & 0 deletions src/organizations/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export * from './create-organization-options.interface';
export * from './domain-data.interface';
export * from './it-contact-options.interface';
export * from './it-contact.interface';
export * from './list-organization-feature-flags-options.interface';
export * from './list-organizations-options.interface';
export * from './organization.interface';
Expand Down
53 changes: 53 additions & 0 deletions src/organizations/interfaces/it-contact-options.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
export const ItContactIntent = {
SSO: 'sso',
DirectorySync: 'directory_sync',
LogStreams: 'log_streams',
DomainVerification: 'domain_verification',
BringYourOwnKey: 'bring_your_own_key',
} as const;

export type ItContactIntent =
(typeof ItContactIntent)[keyof typeof ItContactIntent];

export interface ListItContactsOptions {
/** Unique identifier of the Organization. */
organizationId: string;
}

export interface CreateItContactOptions {
/** Unique identifier of the Organization. */
organizationId: string;
/** The email address of the IT Contact. */
email: string;
}

export interface SerializedCreateItContactOptions {
email: string;
}

export interface DeleteItContactOptions {
/** Unique identifier of the Organization. */
organizationId: string;
/** Unique identifier of the IT Contact. */
contactId: string;
}

export interface InviteItContactOptions {
/** Unique identifier of the Organization. */
organizationId: string;
/** Unique identifier of the IT Contact. */
contactId: string;
/** The Admin Portal features that the IT Contact can configure. */
intents: ItContactIntent[];
}

export interface SerializedInviteItContactOptions {
intents: ItContactIntent[];
}

export interface RevokeItContactOptions {
/** Unique identifier of the Organization. */
organizationId: string;
/** Unique identifier of the IT Contact. */
contactId: string;
}
20 changes: 20 additions & 0 deletions src/organizations/interfaces/it-contact.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export interface ItContact {
/** Distinguishes the IT Contact object. */
object: 'it_contact';
/** Unique identifier of the IT Contact. */
id: string;
/** The email address of the IT Contact. */
email: string;
/** An ISO 8601 timestamp. */
createdAt: string;
/** An ISO 8601 timestamp. */
updatedAt: string;
}

export interface ItContactResponse {
object: 'it_contact';
id: string;
email: string;
created_at: string;
updated_at: string;
}
103 changes: 102 additions & 1 deletion src/organizations/organizations.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@ import {
fetchSearchParams,
fetchHeaders,
fetchBody,
fetchMethod,
} from '../common/utils/test-utils';
import { WorkOS } from '../workos';
import clearStripeCustomerId from './fixtures/clear-stripe-customer-id.json';
import createOrganizationInvalid from './fixtures/create-organization-invalid.json';
import createOrganization from './fixtures/create-organization.json';
import getOrganization from './fixtures/get-organization.json';
import itContact from './fixtures/it-contact.json';
import listItContacts from './fixtures/list-it-contacts.json';
import listOrganizationsFixture from './fixtures/list-organizations.json';
import updateOrganization from './fixtures/update-organization.json';
import setStripeCustomerId from './fixtures/set-stripe-customer-id.json';
import setStripeCustomerIdDisabled from './fixtures/set-stripe-customer-id-disabled.json';
import { DomainDataState } from './interfaces';
import { DomainDataState, ItContactIntent } from './interfaces';

const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');

Expand Down Expand Up @@ -363,4 +366,102 @@ describe('Organizations', () => {
});
});
});

describe('IT Contacts', () => {
describe('listItContacts', () => {
it('returns the organization’s IT contacts', async () => {
fetchOnce(listItContacts);

const { data, listMetadata } =
await workos.organizations.listItContacts({
organizationId: 'org_01EHT88Z8J8795GZNQ4ZP1J81T',
});

expect(fetchMethod()).toBe('GET');
expect(fetchURL()).toContain(
'/organizations/org_01EHT88Z8J8795GZNQ4ZP1J81T/it_contacts',
);

expect(data).toEqual([
{
object: 'it_contact',
id: 'it_contact_01HXYZ123456789ABCDEFGHIJ',
email: 'it-contact@example.com',
createdAt: '2026-01-15T12:00:00.000Z',
updatedAt: '2026-01-15T12:00:00.000Z',
},
]);
expect(listMetadata).toEqual({ before: null, after: null });
});
});

describe('createItContact', () => {
it('creates an IT contact', async () => {
fetchOnce(itContact);

const subject = await workos.organizations.createItContact({
organizationId: 'org_01EHT88Z8J8795GZNQ4ZP1J81T',
email: 'it-contact@example.com',
});

expect(fetchMethod()).toBe('POST');
expect(fetchURL()).toContain(
'/organizations/org_01EHT88Z8J8795GZNQ4ZP1J81T/it_contacts',
);
expect(fetchBody()).toEqual({ email: 'it-contact@example.com' });

expect(subject.id).toBe('it_contact_01HXYZ123456789ABCDEFGHIJ');
});
});

describe('deleteItContact', () => {
it('deletes an IT contact', async () => {
fetchOnce({}, { status: 204 });

await workos.organizations.deleteItContact({
organizationId: 'org_01EHT88Z8J8795GZNQ4ZP1J81T',
contactId: 'it_contact_01HXYZ123456789ABCDEFGHIJ',
});

expect(fetchMethod()).toBe('DELETE');
expect(fetchURL()).toContain(
'/organizations/org_01EHT88Z8J8795GZNQ4ZP1J81T/it_contacts/it_contact_01HXYZ123456789ABCDEFGHIJ',
);
});
});

describe('inviteItContact', () => {
it('invites an IT contact', async () => {
fetchOnce({}, { status: 204 });

await workos.organizations.inviteItContact({
organizationId: 'org_01EHT88Z8J8795GZNQ4ZP1J81T',
contactId: 'it_contact_01HXYZ123456789ABCDEFGHIJ',
intents: [ItContactIntent.SSO, ItContactIntent.DirectorySync],
});

expect(fetchMethod()).toBe('POST');
expect(fetchURL()).toContain(
'/organizations/org_01EHT88Z8J8795GZNQ4ZP1J81T/it_contacts/it_contact_01HXYZ123456789ABCDEFGHIJ/invite',
);
expect(fetchBody()).toEqual({ intents: ['sso', 'directory_sync'] });
});
});

describe('revokeItContact', () => {
it('revokes an IT contact’s invitation', async () => {
fetchOnce({}, { status: 204 });

await workos.organizations.revokeItContact({
organizationId: 'org_01EHT88Z8J8795GZNQ4ZP1J81T',
contactId: 'it_contact_01HXYZ123456789ABCDEFGHIJ',
});

expect(fetchMethod()).toBe('POST');
expect(fetchURL()).toContain(
'/organizations/org_01EHT88Z8J8795GZNQ4ZP1J81T/it_contacts/it_contact_01HXYZ123456789ABCDEFGHIJ/revoke',
);
});
});
});
});
120 changes: 120 additions & 0 deletions src/organizations/organizations.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
import { AutoPaginatable } from '../common/utils/pagination';
import { WorkOS } from '../workos';
import { List, ListResponse } from '../common/interfaces';
import {
CreateItContactOptions,
CreateOrganizationOptions,
CreateOrganizationRequestOptions,
DeleteItContactOptions,
InviteItContactOptions,
ItContact,
ItContactResponse,
ListItContactsOptions,
ListOrganizationsOptions,
Organization,
OrganizationResponse,
RevokeItContactOptions,
UpdateOrganizationOptions,
} from './interfaces';
import {
deserializeItContact,
deserializeOrganization,
serializeCreateItContactOptions,
serializeCreateOrganizationOptions,
serializeInviteItContactOptions,
serializeUpdateOrganizationOptions,
} from './serializers';

Expand Down Expand Up @@ -151,4 +162,113 @@ export class Organizations {

return deserializeOrganization(data);
}

/**
* List IT Contacts
*
* Get the IT Contacts for an Organization.
* @param options - Object containing the Organization ID.
* @returns {Promise<List<ItContact>>}
* @throws {AuthorizationException} 403
* @throws {NotFoundException} 404
*/
async listItContacts(
options: ListItContactsOptions,
): Promise<List<ItContact>> {
const { organizationId } = options;

const { data } = await this.workos.get<ListResponse<ItContactResponse>>(
`/organizations/${organizationId}/it_contacts`,
);

return {
object: data.object,
data: data.data.map(deserializeItContact),
listMetadata: {
before: data.list_metadata.before,
after: data.list_metadata.after,
},
};
}

/**
* Create an IT Contact
*
* Add an IT Contact to an Organization. No Admin Portal invitation is sent,
* though the contact is notified if the Organization has a connection
* certificate nearing expiry.
* @param options - Object containing the Organization ID and the email address.
* @returns {Promise<ItContact>}
* @throws {AuthorizationException} 403
* @throws {NotFoundException} 404
* @throws {ConflictException} 409
* @throws {UnprocessableEntityException} 422
*/
async createItContact(options: CreateItContactOptions): Promise<ItContact> {
const { organizationId, ...payload } = options;

const { data } = await this.workos.post<ItContactResponse>(
`/organizations/${organizationId}/it_contacts`,
serializeCreateItContactOptions(payload),
);

return deserializeItContact(data);
}

/**
* Delete an IT Contact
*
* Remove an IT Contact from an Organization and revoke the contact's active
* setup links.
* @param options - Object containing the Organization ID and the IT Contact ID.
* @returns {Promise<void>}
* @throws {AuthorizationException} 403
* @throws {NotFoundException} 404
*/
async deleteItContact(options: DeleteItContactOptions): Promise<void> {
const { organizationId, contactId } = options;

await this.workos.delete(
`/organizations/${organizationId}/it_contacts/${contactId}`,
);
}

/**
* Invite an IT Contact
*
* Create an Admin Portal setup link and email it to the IT Contact. An
* Organization can have at most one active invitation.
* @param options - Object containing the Organization ID, the IT Contact ID and the intents.
* @returns {Promise<void>}
* @throws {AuthorizationException} 403
* @throws {NotFoundException} 404
* @throws {ConflictException} 409
* @throws {UnprocessableEntityException} 422
*/
async inviteItContact(options: InviteItContactOptions): Promise<void> {
const { organizationId, contactId, ...payload } = options;

await this.workos.post(
`/organizations/${organizationId}/it_contacts/${contactId}/invite`,
serializeInviteItContactOptions(payload),
);
}

/**
* Revoke an IT Contact's invitation
*
* Revoke the Organization's active Admin Portal invitation.
* @param options - Object containing the Organization ID and the IT Contact ID.
* @returns {Promise<void>}
* @throws {AuthorizationException} 403
* @throws {NotFoundException} 404
*/
async revokeItContact(options: RevokeItContactOptions): Promise<void> {
const { organizationId, contactId } = options;

await this.workos.post(
`/organizations/${organizationId}/it_contacts/${contactId}/revoke`,
{},
);
}
}
2 changes: 2 additions & 0 deletions src/organizations/serializers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './create-organization-options.serializer';
export * from './it-contact-options.serializer';
export * from './it-contact.serializer';
export * from './organization.serializer';
export * from './update-organization-options.serializer';
18 changes: 18 additions & 0 deletions src/organizations/serializers/it-contact-options.serializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {
CreateItContactOptions,
InviteItContactOptions,
SerializedCreateItContactOptions,
SerializedInviteItContactOptions,
} from '../interfaces';

export const serializeCreateItContactOptions = (
options: Omit<CreateItContactOptions, 'organizationId'>,
): SerializedCreateItContactOptions => ({
email: options.email,
});

export const serializeInviteItContactOptions = (
options: Omit<InviteItContactOptions, 'organizationId' | 'contactId'>,
): SerializedInviteItContactOptions => ({
intents: options.intents,
});
Loading