diff --git a/src/admin-portal/admin-portal.spec.ts b/src/admin-portal/admin-portal.spec.ts new file mode 100644 index 000000000..5743c9aa0 --- /dev/null +++ b/src/admin-portal/admin-portal.spec.ts @@ -0,0 +1,46 @@ +// This file is auto-generated by oagen. Do not edit. + +import fetch from 'jest-fetch-mock'; +import { + fetchOnce, + fetchURL, + fetchMethod, + fetchBody, + testUnauthorized, +} from '../common/utils/test-utils'; +import { WorkOS } from '../workos'; + +import portalLinkResponseFixture from './fixtures/portal-link-response.fixture.json'; + +const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); + +describe('AdminPortal', () => { + beforeEach(() => fetch.resetMocks()); + + describe('generatePortalLink', () => { + it('sends the correct request and returns result', async () => { + fetchOnce(portalLinkResponseFixture); + + const result = await workos.adminPortal.generatePortalLink({ + organization: 'test_organization', + }); + + expect(fetchMethod()).toBe('POST'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/portal/generate_link', + ); + expect(fetchBody()).toEqual( + expect.objectContaining({ organization: 'test_organization' }), + ); + expect(result.link).toBe( + 'https://setup.workos.com?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', + ); + }); + + testUnauthorized(() => + workos.adminPortal.generatePortalLink({ + organization: 'test_organization', + }), + ); + }); +}); diff --git a/src/admin-portal/admin-portal.ts b/src/admin-portal/admin-portal.ts new file mode 100644 index 000000000..bfcfcf291 --- /dev/null +++ b/src/admin-portal/admin-portal.ts @@ -0,0 +1,32 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { WorkOS } from '../workos'; +import type { + PortalLinkResponse, + PortalLinkResponseWire, +} from './interfaces/portal-link-response.interface'; +import type { GenerateLink } from './interfaces/generate-link.interface'; +import { deserializePortalLinkResponse } from './serializers/portal-link-response.serializer'; +import { serializeGenerateLink } from './serializers/generate-link.serializer'; + +export class AdminPortal { + constructor(private readonly workos: WorkOS) {} + + /** + * Generate a Portal Link + * + * Generate a Portal Link scoped to an Organization. + * @param payload - Object containing organization. + * @returns {PortalLinkResponse} + * @throws {BadRequestException} 400 + * @throws {NotFoundException} 404 + * @throws {UnprocessableEntityException} 422 + */ + async generatePortalLink(payload: GenerateLink): Promise { + const { data } = await this.workos.post( + '/portal/generate_link', + serializeGenerateLink(payload), + ); + return deserializePortalLinkResponse(data); + } +} diff --git a/src/admin-portal/fixtures/generate-link.fixture.json b/src/admin-portal/fixtures/generate-link.fixture.json new file mode 100644 index 000000000..f7fd7b0ad --- /dev/null +++ b/src/admin-portal/fixtures/generate-link.fixture.json @@ -0,0 +1,12 @@ +{ + "return_url": "https://example.com/admin-portal/return", + "success_url": "https://example.com/admin-portal/success", + "organization": "org_01EHZNVPK3SFK441A1RGBFSHRT", + "intent": "sso", + "intent_options": { + "sso": { + "bookmark_slug": "chatgpt", + "provider_type": "GoogleSAML" + } + } +} diff --git a/src/admin-portal/fixtures/intent-options.fixture.json b/src/admin-portal/fixtures/intent-options.fixture.json new file mode 100644 index 000000000..31bad9130 --- /dev/null +++ b/src/admin-portal/fixtures/intent-options.fixture.json @@ -0,0 +1,6 @@ +{ + "sso": { + "bookmark_slug": "chatgpt", + "provider_type": "GoogleSAML" + } +} diff --git a/src/admin-portal/fixtures/portal-link-response.fixture.json b/src/admin-portal/fixtures/portal-link-response.fixture.json new file mode 100644 index 000000000..811f32a96 --- /dev/null +++ b/src/admin-portal/fixtures/portal-link-response.fixture.json @@ -0,0 +1,3 @@ +{ + "link": "https://setup.workos.com?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +} diff --git a/src/admin-portal/fixtures/sso-intent-options.fixture.json b/src/admin-portal/fixtures/sso-intent-options.fixture.json new file mode 100644 index 000000000..79ab315ed --- /dev/null +++ b/src/admin-portal/fixtures/sso-intent-options.fixture.json @@ -0,0 +1,4 @@ +{ + "bookmark_slug": "chatgpt", + "provider_type": "GoogleSAML" +} diff --git a/src/admin-portal/interfaces/generate-link.interface.ts b/src/admin-portal/interfaces/generate-link.interface.ts new file mode 100644 index 000000000..89e47e23f --- /dev/null +++ b/src/admin-portal/interfaces/generate-link.interface.ts @@ -0,0 +1,38 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + IntentOptions, + IntentOptionsResponse, +} from './intent-options.interface'; +import type { GenerateLinkIntent } from '../../common/interfaces/generate-link-intent.interface'; + +export interface GenerateLink { + /** The URL to go to when an admin clicks on your logo in the Admin Portal. If not specified, the return URL configured on the [Redirects](https://dashboard.workos.com/redirects) page will be used. */ + returnUrl?: string; + /** The URL to redirect the admin to when they finish setup. If not specified, the success URL configured on the [Redirects](https://dashboard.workos.com/redirects) page will be used. */ + successUrl?: string; + /** An [Organization](https://workos.com/docs/reference/organization) identifier. */ + organization: string; + /** + * + * The intent of the Admin Portal. + * - `sso` - Launch Admin Portal for creating SSO connections + * - `dsync` - Launch Admin Portal for creating Directory Sync connections + * - `audit_logs` - Launch Admin Portal for viewing Audit Logs + * - `log_streams` - Launch Admin Portal for creating Log Streams + * - `domain_verification` - Launch Admin Portal for Domain Verification + * - `certificate_renewal` - Launch Admin Portal for renewing SAML Certificates + * - `bring_your_own_key` - Launch Admin Portal for configuring Bring Your Own Key + */ + intent?: GenerateLinkIntent; + /** Options to configure the Admin Portal based on the intent. */ + intentOptions?: IntentOptions; +} + +export interface GenerateLinkResponse { + return_url?: string; + success_url?: string; + organization: string; + intent?: GenerateLinkIntent; + intent_options?: IntentOptionsResponse; +} diff --git a/src/admin-portal/interfaces/index.ts b/src/admin-portal/interfaces/index.ts new file mode 100644 index 000000000..eafac3f0c --- /dev/null +++ b/src/admin-portal/interfaces/index.ts @@ -0,0 +1,6 @@ +// This file is auto-generated by oagen. Do not edit. + +export * from './generate-link.interface'; +export * from './intent-options.interface'; +export * from './portal-link-response.interface'; +export * from './sso-intent-options.interface'; diff --git a/src/admin-portal/interfaces/intent-options.interface.ts b/src/admin-portal/interfaces/intent-options.interface.ts new file mode 100644 index 000000000..229e9e1af --- /dev/null +++ b/src/admin-portal/interfaces/intent-options.interface.ts @@ -0,0 +1,15 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + SSOIntentOptions, + SSOIntentOptionsResponse, +} from './sso-intent-options.interface'; + +export interface IntentOptions { + /** SSO-specific options for the Admin Portal. */ + sso: SSOIntentOptions; +} + +export interface IntentOptionsResponse { + sso: SSOIntentOptionsResponse; +} diff --git a/src/admin-portal/interfaces/portal-link-response.interface.ts b/src/admin-portal/interfaces/portal-link-response.interface.ts new file mode 100644 index 000000000..a7ea95f54 --- /dev/null +++ b/src/admin-portal/interfaces/portal-link-response.interface.ts @@ -0,0 +1,10 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface PortalLinkResponse { + /** An ephemeral link to initiate the Admin Portal. */ + link: string; +} + +export interface PortalLinkResponseWire { + link: string; +} diff --git a/src/admin-portal/interfaces/sso-intent-options.interface.ts b/src/admin-portal/interfaces/sso-intent-options.interface.ts new file mode 100644 index 000000000..6450eabb9 --- /dev/null +++ b/src/admin-portal/interfaces/sso-intent-options.interface.ts @@ -0,0 +1,13 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface SSOIntentOptions { + /** The bookmark slug to use for SSO. */ + bookmarkSlug?: string; + /** The SSO provider type to configure. */ + providerType?: 'GoogleSAML'; +} + +export interface SSOIntentOptionsResponse { + bookmark_slug?: string; + provider_type?: 'GoogleSAML'; +} diff --git a/src/admin-portal/serializers/generate-link.serializer.ts b/src/admin-portal/serializers/generate-link.serializer.ts new file mode 100644 index 000000000..c96f34ca9 --- /dev/null +++ b/src/admin-portal/serializers/generate-link.serializer.ts @@ -0,0 +1,36 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + GenerateLink, + GenerateLinkResponse, +} from '../interfaces/generate-link.interface'; +import { + deserializeIntentOptions, + serializeIntentOptions, +} from './intent-options.serializer'; + +export const deserializeGenerateLink = ( + response: GenerateLinkResponse, +): GenerateLink => ({ + returnUrl: response.return_url, + successUrl: response.success_url, + organization: response.organization, + intent: response.intent, + intentOptions: + response.intent_options != null + ? deserializeIntentOptions(response.intent_options) + : undefined, +}); + +export const serializeGenerateLink = ( + model: GenerateLink, +): GenerateLinkResponse => ({ + return_url: model.returnUrl, + success_url: model.successUrl, + organization: model.organization, + intent: model.intent, + intent_options: + model.intentOptions != null + ? serializeIntentOptions(model.intentOptions) + : undefined, +}); diff --git a/src/admin-portal/serializers/intent-options.serializer.ts b/src/admin-portal/serializers/intent-options.serializer.ts new file mode 100644 index 000000000..a7d16e1df --- /dev/null +++ b/src/admin-portal/serializers/intent-options.serializer.ts @@ -0,0 +1,22 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + IntentOptions, + IntentOptionsResponse, +} from '../interfaces/intent-options.interface'; +import { + deserializeSSOIntentOptions, + serializeSSOIntentOptions, +} from './sso-intent-options.serializer'; + +export const deserializeIntentOptions = ( + response: IntentOptionsResponse, +): IntentOptions => ({ + sso: deserializeSSOIntentOptions(response.sso), +}); + +export const serializeIntentOptions = ( + model: IntentOptions, +): IntentOptionsResponse => ({ + sso: serializeSSOIntentOptions(model.sso), +}); diff --git a/src/admin-portal/serializers/portal-link-response.serializer.ts b/src/admin-portal/serializers/portal-link-response.serializer.ts new file mode 100644 index 000000000..75a96c945 --- /dev/null +++ b/src/admin-portal/serializers/portal-link-response.serializer.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + PortalLinkResponse, + PortalLinkResponseWire, +} from '../interfaces/portal-link-response.interface'; + +export const deserializePortalLinkResponse = ( + response: PortalLinkResponseWire, +): PortalLinkResponse => ({ + link: response.link, +}); + +export const serializePortalLinkResponse = ( + model: PortalLinkResponse, +): PortalLinkResponseWire => ({ + link: model.link, +}); diff --git a/src/admin-portal/serializers/sso-intent-options.serializer.ts b/src/admin-portal/serializers/sso-intent-options.serializer.ts new file mode 100644 index 000000000..5c36252fa --- /dev/null +++ b/src/admin-portal/serializers/sso-intent-options.serializer.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + SSOIntentOptions, + SSOIntentOptionsResponse, +} from '../interfaces/sso-intent-options.interface'; + +export const deserializeSSOIntentOptions = ( + response: SSOIntentOptionsResponse, +): SSOIntentOptions => ({ + bookmarkSlug: response.bookmark_slug, + providerType: response.provider_type, +}); + +export const serializeSSOIntentOptions = ( + model: SSOIntentOptions, +): SSOIntentOptionsResponse => ({ + bookmark_slug: model.bookmarkSlug, + provider_type: model.providerType, +}); diff --git a/src/authorization/authorization.ts b/src/authorization/authorization.ts index 534a31e71..158c060a6 100644 --- a/src/authorization/authorization.ts +++ b/src/authorization/authorization.ts @@ -230,6 +230,7 @@ export class Authorization { ); } + /** @deprecated Use `workos.permissions.create()` instead. */ async createPermission( options: CreatePermissionOptions, ): Promise { @@ -240,6 +241,7 @@ export class Authorization { return deserializePermission(data); } + /** @deprecated Use `workos.permissions.list()` instead. */ async listPermissions( options?: ListPermissionsOptions, ): Promise { @@ -257,6 +259,7 @@ export class Authorization { }; } + /** @deprecated Use `workos.permissions.find()` instead. */ async getPermission(slug: string): Promise { const { data } = await this.workos.get( `/authorization/permissions/${slug}`, @@ -264,6 +267,7 @@ export class Authorization { return deserializePermission(data); } + /** @deprecated Use `workos.permissions.update()` instead. */ async updatePermission( slug: string, options: UpdatePermissionOptions, @@ -275,6 +279,7 @@ export class Authorization { return deserializePermission(data); } + /** @deprecated Use `workos.permissions.delete()` instead. */ async deletePermission(slug: string): Promise { await this.workos.delete(`/authorization/permissions/${slug}`); } diff --git a/src/authorization/interfaces/update-organization-role.interface.ts b/src/authorization/interfaces/update-organization-role.interface.ts new file mode 100644 index 000000000..2630368ae --- /dev/null +++ b/src/authorization/interfaces/update-organization-role.interface.ts @@ -0,0 +1,13 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface UpdateOrganizationRole { + /** A descriptive name for the role. */ + name?: string; + /** An optional description of the role's purpose. */ + description?: string | null; +} + +export interface UpdateOrganizationRoleResponse { + name?: string; + description?: string | null; +} diff --git a/src/authorization/serializers/update-organization-role.serializer.ts b/src/authorization/serializers/update-organization-role.serializer.ts new file mode 100644 index 000000000..0c4734357 --- /dev/null +++ b/src/authorization/serializers/update-organization-role.serializer.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + UpdateOrganizationRole, + UpdateOrganizationRoleResponse, +} from '../interfaces/update-organization-role.interface'; + +export const deserializeUpdateOrganizationRole = ( + response: UpdateOrganizationRoleResponse, +): UpdateOrganizationRole => ({ + name: response.name, + description: response.description ?? null, +}); + +export const serializeUpdateOrganizationRole = ( + model: UpdateOrganizationRole, +): UpdateOrganizationRoleResponse => ({ + name: model.name, + description: model.description ?? null, +}); diff --git a/src/common/fixtures/authentication-factor-totp-2.fixture.json b/src/common/fixtures/authentication-factor-totp-2.fixture.json new file mode 100644 index 000000000..14ea6dc07 --- /dev/null +++ b/src/common/fixtures/authentication-factor-totp-2.fixture.json @@ -0,0 +1,4 @@ +{ + "issuer": "WorkOS", + "user": "user@example.com" +} diff --git a/src/common/interfaces/authentication-factor-totp-2.interface.ts b/src/common/interfaces/authentication-factor-totp-2.interface.ts new file mode 100644 index 000000000..1b9adfc74 --- /dev/null +++ b/src/common/interfaces/authentication-factor-totp-2.interface.ts @@ -0,0 +1,13 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface AuthenticationFactorTotp2 { + /** Your application or company name displayed in the user's authenticator app. Defaults to your WorkOS team name. */ + issuer: string; + /** The user's account name displayed in their authenticator app. Defaults to the user's email. */ + user: string; +} + +export interface AuthenticationFactorTotp2Response { + issuer: string; + user: string; +} diff --git a/src/common/interfaces/create-webhook-endpoint-events.interface.ts b/src/common/interfaces/create-webhook-endpoint-events.interface.ts new file mode 100644 index 000000000..f6403c55c --- /dev/null +++ b/src/common/interfaces/create-webhook-endpoint-events.interface.ts @@ -0,0 +1,72 @@ +// This file is auto-generated by oagen. Do not edit. + +export type CreateWebhookEndpointEvents = + | 'authentication.email_verification_succeeded' + | 'authentication.magic_auth_failed' + | 'authentication.magic_auth_succeeded' + | 'authentication.mfa_succeeded' + | 'authentication.oauth_failed' + | 'authentication.oauth_succeeded' + | 'authentication.password_failed' + | 'authentication.password_succeeded' + | 'authentication.passkey_failed' + | 'authentication.passkey_succeeded' + | 'authentication.sso_failed' + | 'authentication.sso_started' + | 'authentication.sso_succeeded' + | 'authentication.sso_timed_out' + | 'authentication.radar_risk_detected' + | 'api_key.created' + | 'api_key.revoked' + | 'connection.activated' + | 'connection.deactivated' + | 'connection.saml_certificate_renewal_required' + | 'connection.saml_certificate_renewed' + | 'connection.deleted' + | 'dsync.activated' + | 'dsync.deleted' + | 'dsync.group.created' + | 'dsync.group.deleted' + | 'dsync.group.updated' + | 'dsync.group.user_added' + | 'dsync.group.user_removed' + | 'dsync.user.created' + | 'dsync.user.deleted' + | 'dsync.user.updated' + | 'email_verification.created' + | 'flag.created' + | 'flag.deleted' + | 'flag.updated' + | 'flag.rule_updated' + | 'invitation.accepted' + | 'invitation.created' + | 'invitation.resent' + | 'invitation.revoked' + | 'magic_auth.created' + | 'organization.created' + | 'organization.deleted' + | 'organization.updated' + | 'organization_domain.created' + | 'organization_domain.deleted' + | 'organization_domain.updated' + | 'organization_domain.verified' + | 'organization_domain.verification_failed' + | 'password_reset.created' + | 'password_reset.succeeded' + | 'user.created' + | 'user.updated' + | 'user.deleted' + | 'organization_membership.created' + | 'organization_membership.deleted' + | 'organization_membership.updated' + | 'role.created' + | 'role.deleted' + | 'role.updated' + | 'organization_role.created' + | 'organization_role.deleted' + | 'organization_role.updated' + | 'permission.created' + | 'permission.deleted' + | 'permission.updated' + | 'session.created' + | 'session.revoked'; diff --git a/src/common/interfaces/generate-link-intent.interface.ts b/src/common/interfaces/generate-link-intent.interface.ts new file mode 100644 index 000000000..696d9d318 --- /dev/null +++ b/src/common/interfaces/generate-link-intent.interface.ts @@ -0,0 +1,10 @@ +// This file is auto-generated by oagen. Do not edit. + +export type GenerateLinkIntent = + | 'sso' + | 'dsync' + | 'audit_logs' + | 'log_streams' + | 'domain_verification' + | 'certificate_renewal' + | 'bring_your_own_key'; diff --git a/src/common/interfaces/radar-standalone-assess-request-action.interface.ts b/src/common/interfaces/radar-standalone-assess-request-action.interface.ts new file mode 100644 index 000000000..f28b709c0 --- /dev/null +++ b/src/common/interfaces/radar-standalone-assess-request-action.interface.ts @@ -0,0 +1,11 @@ +// This file is auto-generated by oagen. Do not edit. + +export type RadarStandaloneAssessRequestAction = + | 'login' + | 'signup' + | 'sign-up' + | 'sign-in' + | 'sign_up' + | 'sign_in' + | 'sign in' + | 'sign up'; diff --git a/src/common/interfaces/radar-standalone-assess-request-auth-method.interface.ts b/src/common/interfaces/radar-standalone-assess-request-auth-method.interface.ts new file mode 100644 index 000000000..3dcfbd26e --- /dev/null +++ b/src/common/interfaces/radar-standalone-assess-request-auth-method.interface.ts @@ -0,0 +1,11 @@ +// This file is auto-generated by oagen. Do not edit. + +export type RadarStandaloneAssessRequestAuthMethod = + | 'Password' + | 'Passkey' + | 'Authenticator' + | 'SMS_OTP' + | 'Email_OTP' + | 'Social' + | 'SSO' + | 'Other'; diff --git a/src/common/interfaces/radar-standalone-response-blocklist-type.interface.ts b/src/common/interfaces/radar-standalone-response-blocklist-type.interface.ts new file mode 100644 index 000000000..d467469f3 --- /dev/null +++ b/src/common/interfaces/radar-standalone-response-blocklist-type.interface.ts @@ -0,0 +1,10 @@ +// This file is auto-generated by oagen. Do not edit. + +export type RadarStandaloneResponseBlocklistType = + | 'ip_address' + | 'domain' + | 'email' + | 'device' + | 'user_agent' + | 'device_fingerprint' + | 'country'; diff --git a/src/common/interfaces/radar-standalone-response-control.interface.ts b/src/common/interfaces/radar-standalone-response-control.interface.ts new file mode 100644 index 000000000..7ca68f0c9 --- /dev/null +++ b/src/common/interfaces/radar-standalone-response-control.interface.ts @@ -0,0 +1,13 @@ +// This file is auto-generated by oagen. Do not edit. + +export type RadarStandaloneResponseControl = + | 'bot_detection' + | 'brute_force_attack' + | 'credential_stuffing' + | 'domain_sign_up_rate_limit' + | 'ip_sign_up_rate_limit' + | 'impossible_travel' + | 'repeat_sign_up' + | 'stale_account' + | 'unrecognized_device' + | 'restriction'; diff --git a/src/common/interfaces/radar-standalone-response-verdict.interface.ts b/src/common/interfaces/radar-standalone-response-verdict.interface.ts new file mode 100644 index 000000000..08f083690 --- /dev/null +++ b/src/common/interfaces/radar-standalone-response-verdict.interface.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by oagen. Do not edit. + +export type RadarStandaloneResponseVerdict = 'allow' | 'block' | 'challenge'; diff --git a/src/common/interfaces/update-webhook-endpoint-events.interface.ts b/src/common/interfaces/update-webhook-endpoint-events.interface.ts new file mode 100644 index 000000000..e5b863473 --- /dev/null +++ b/src/common/interfaces/update-webhook-endpoint-events.interface.ts @@ -0,0 +1,72 @@ +// This file is auto-generated by oagen. Do not edit. + +export type UpdateWebhookEndpointEvents = + | 'authentication.email_verification_succeeded' + | 'authentication.magic_auth_failed' + | 'authentication.magic_auth_succeeded' + | 'authentication.mfa_succeeded' + | 'authentication.oauth_failed' + | 'authentication.oauth_succeeded' + | 'authentication.password_failed' + | 'authentication.password_succeeded' + | 'authentication.passkey_failed' + | 'authentication.passkey_succeeded' + | 'authentication.sso_failed' + | 'authentication.sso_started' + | 'authentication.sso_succeeded' + | 'authentication.sso_timed_out' + | 'authentication.radar_risk_detected' + | 'api_key.created' + | 'api_key.revoked' + | 'connection.activated' + | 'connection.deactivated' + | 'connection.saml_certificate_renewal_required' + | 'connection.saml_certificate_renewed' + | 'connection.deleted' + | 'dsync.activated' + | 'dsync.deleted' + | 'dsync.group.created' + | 'dsync.group.deleted' + | 'dsync.group.updated' + | 'dsync.group.user_added' + | 'dsync.group.user_removed' + | 'dsync.user.created' + | 'dsync.user.deleted' + | 'dsync.user.updated' + | 'email_verification.created' + | 'flag.created' + | 'flag.deleted' + | 'flag.updated' + | 'flag.rule_updated' + | 'invitation.accepted' + | 'invitation.created' + | 'invitation.resent' + | 'invitation.revoked' + | 'magic_auth.created' + | 'organization.created' + | 'organization.deleted' + | 'organization.updated' + | 'organization_domain.created' + | 'organization_domain.deleted' + | 'organization_domain.updated' + | 'organization_domain.verified' + | 'organization_domain.verification_failed' + | 'password_reset.created' + | 'password_reset.succeeded' + | 'user.created' + | 'user.updated' + | 'user.deleted' + | 'organization_membership.created' + | 'organization_membership.deleted' + | 'organization_membership.updated' + | 'role.created' + | 'role.deleted' + | 'role.updated' + | 'organization_role.created' + | 'organization_role.deleted' + | 'organization_role.updated' + | 'permission.created' + | 'permission.deleted' + | 'permission.updated' + | 'session.created' + | 'session.revoked'; diff --git a/src/common/interfaces/update-webhook-endpoint-status.interface.ts b/src/common/interfaces/update-webhook-endpoint-status.interface.ts new file mode 100644 index 000000000..773e9c083 --- /dev/null +++ b/src/common/interfaces/update-webhook-endpoint-status.interface.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by oagen. Do not edit. + +export type UpdateWebhookEndpointStatus = 'enabled' | 'disabled'; diff --git a/src/common/interfaces/webhook-endpoint-json-status.interface.ts b/src/common/interfaces/webhook-endpoint-json-status.interface.ts new file mode 100644 index 000000000..a3baae761 --- /dev/null +++ b/src/common/interfaces/webhook-endpoint-json-status.interface.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by oagen. Do not edit. + +export type WebhookEndpointJsonStatus = 'enabled' | 'disabled'; diff --git a/src/common/utils/fetch-and-deserialize.ts b/src/common/utils/fetch-and-deserialize.ts index 61c646c73..694ccc39e 100644 --- a/src/common/utils/fetch-and-deserialize.ts +++ b/src/common/utils/fetch-and-deserialize.ts @@ -1,3 +1,5 @@ +// This file is auto-generated by oagen. Do not edit. + import { WorkOS } from '../../workos'; import { GetOptions, @@ -5,6 +7,7 @@ import { ListResponse, PaginationOptions, } from '../interfaces'; +import { AutoPaginatable } from './pagination'; import { deserializeList } from '../serializers'; const setDefaultOptions = (options?: PaginationOptions): PaginationOptions => { @@ -28,3 +31,31 @@ export const fetchAndDeserialize = async ( return deserializeList(data, deserializeFn); }; + +export async function createPaginatedList< + TResponse, + TModel, + TOptions extends PaginationOptions, +>( + workos: WorkOS, + endpoint: string, + deserializeFn: (r: TResponse) => TModel, + options?: TOptions, +): Promise> { + return new AutoPaginatable( + await fetchAndDeserialize( + workos, + endpoint, + deserializeFn, + options, + ), + (params) => + fetchAndDeserialize( + workos, + endpoint, + deserializeFn, + params, + ), + options, + ); +} diff --git a/src/common/utils/test-utils.ts b/src/common/utils/test-utils.ts index 89269a88c..5c54812c8 100644 --- a/src/common/utils/test-utils.ts +++ b/src/common/utils/test-utils.ts @@ -1,3 +1,5 @@ +// This file is auto-generated by oagen. Do not edit. + import fetch, { MockParams } from 'jest-fetch-mock'; export function fetchOnce( @@ -37,3 +39,59 @@ export function fetchBody({ raw = false } = {}) { } return JSON.parse(String(body)); } + +/** + * Shared test helper: asserts that the given async function throws when the + * server responds with 401 Unauthorized. + */ +export function testUnauthorized(fn: () => Promise) { + it('throws on unauthorized', async () => { + fetchOnce({ message: 'Unauthorized' }, { status: 401 }); + await expect(fn()).rejects.toThrow('Could not authorize the request'); + }); +} + +/** + * Shared test helper: asserts that a paginated list call returns the expected + * shape (data array + listMetadata) and hits the correct endpoint. + */ +export function testPaginatedList( + fn: () => Promise, + pathContains: string, +) { + it('returns paginated results', async () => { + // Caller must have called fetchOnce with the list fixture before invoking fn + const { data, listMetadata } = await fn(); + expect(fetchURL()).toContain(pathContains); + expect(fetchSearchParams()).toHaveProperty('order'); + expect(Array.isArray(data)).toBe(true); + expect(listMetadata).toBeDefined(); + }); +} + +/** + * Shared test helper: asserts that a paginated list call returns empty data + * when the server responds with an empty list. + */ +export function testEmptyResults(fn: () => Promise) { + it('handles empty results', async () => { + fetchOnce({ data: [], list_metadata: { before: null, after: null } }); + const { data } = await fn(); + expect(data).toEqual([]); + }); +} + +/** + * Shared test helper: asserts that pagination params are forwarded correctly. + */ +export function testPaginationParams( + fn: (opts: any) => Promise, + fixture: any, +) { + it('forwards pagination params', async () => { + fetchOnce(fixture); + await fn({ limit: 10, after: 'cursor_abc' }); + expect(fetchSearchParams()['limit']).toBe('10'); + expect(fetchSearchParams()['after']).toBe('cursor_abc'); + }); +} diff --git a/src/permissions/fixtures/authorization-permission.fixture.json b/src/permissions/fixtures/authorization-permission.fixture.json new file mode 100644 index 000000000..0424eed6e --- /dev/null +++ b/src/permissions/fixtures/authorization-permission.fixture.json @@ -0,0 +1,11 @@ +{ + "object": "permission", + "id": "perm_01HXYZ123456789ABCDEFGHIJ", + "slug": "documents:read", + "name": "View Documents", + "description": "Allows viewing document contents", + "system": false, + "resource_type_slug": "workspace", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/src/permissions/fixtures/create-authorization-permission.fixture.json b/src/permissions/fixtures/create-authorization-permission.fixture.json new file mode 100644 index 000000000..6c9dc2aed --- /dev/null +++ b/src/permissions/fixtures/create-authorization-permission.fixture.json @@ -0,0 +1,6 @@ +{ + "slug": "documents:read", + "name": "View Documents", + "description": "Allows viewing document contents", + "resource_type_slug": "document" +} diff --git a/src/permissions/fixtures/list-authorization-permission.fixture.json b/src/permissions/fixtures/list-authorization-permission.fixture.json new file mode 100644 index 000000000..348f9d773 --- /dev/null +++ b/src/permissions/fixtures/list-authorization-permission.fixture.json @@ -0,0 +1,19 @@ +{ + "data": [ + { + "object": "permission", + "id": "perm_01HXYZ123456789ABCDEFGHIJ", + "slug": "documents:read", + "name": "View Documents", + "description": "Allows viewing document contents", + "system": false, + "resource_type_slug": "workspace", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + } + ], + "list_metadata": { + "before": null, + "after": null + } +} diff --git a/src/permissions/fixtures/permission.fixture.json b/src/permissions/fixtures/permission.fixture.json new file mode 100644 index 000000000..e4ac8b822 --- /dev/null +++ b/src/permissions/fixtures/permission.fixture.json @@ -0,0 +1,11 @@ +{ + "object": "permission", + "id": "perm_01HXYZ123456789ABCDEFGHIJ", + "slug": "documents:read", + "name": "View Documents", + "description": "Allows viewing document contents", + "system": false, + "resource_type_slug": "document", + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/src/permissions/fixtures/update-authorization-permission.fixture.json b/src/permissions/fixtures/update-authorization-permission.fixture.json new file mode 100644 index 000000000..5aed776b7 --- /dev/null +++ b/src/permissions/fixtures/update-authorization-permission.fixture.json @@ -0,0 +1,4 @@ +{ + "name": "View Documents", + "description": "Allows viewing document contents" +} diff --git a/src/permissions/interfaces/authorization-permission.interface.ts b/src/permissions/interfaces/authorization-permission.interface.ts new file mode 100644 index 000000000..ac727d924 --- /dev/null +++ b/src/permissions/interfaces/authorization-permission.interface.ts @@ -0,0 +1,34 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface AuthorizationPermission { + /** Distinguishes the Permission object. */ + object: 'permission'; + /** Unique identifier of the Permission. */ + id: string; + /** A unique key to reference the permission. Must be lowercase and contain only letters, numbers, hyphens, underscores, colons, periods, and asterisks. */ + slug: string; + /** A descriptive name for the Permission. */ + name: string; + /** An optional description of the Permission. */ + description: string | null; + /** Whether the permission is a system permission. System permissions are managed by WorkOS and cannot be deleted. */ + system: boolean; + /** The slug of the resource type associated with the permission. */ + resourceTypeSlug: string; + /** An ISO 8601 timestamp. */ + createdAt: string; + /** An ISO 8601 timestamp. */ + updatedAt: string; +} + +export interface AuthorizationPermissionResponse { + object: 'permission'; + id: string; + slug: string; + name: string; + description: string | null; + system: boolean; + resource_type_slug: string; + created_at: string; + updated_at: string; +} diff --git a/src/permissions/interfaces/create-authorization-permission.interface.ts b/src/permissions/interfaces/create-authorization-permission.interface.ts new file mode 100644 index 000000000..ddcfb825e --- /dev/null +++ b/src/permissions/interfaces/create-authorization-permission.interface.ts @@ -0,0 +1,19 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface CreateAuthorizationPermission { + /** A unique key to reference the permission. Must be lowercase and contain only letters, numbers, hyphens, underscores, colons, periods, and asterisks. */ + slug: string; + /** A descriptive name for the Permission. */ + name: string; + /** An optional description of the Permission. */ + description?: string | null; + /** The slug of the resource type this permission is scoped to. */ + resourceTypeSlug?: string; +} + +export interface CreateAuthorizationPermissionResponse { + slug: string; + name: string; + description?: string | null; + resource_type_slug?: string; +} diff --git a/src/permissions/interfaces/index.ts b/src/permissions/interfaces/index.ts new file mode 100644 index 000000000..8f156997c --- /dev/null +++ b/src/permissions/interfaces/index.ts @@ -0,0 +1,5 @@ +// This file is auto-generated by oagen. Do not edit. + +export * from './authorization-permission.interface'; +export * from './create-authorization-permission.interface'; +export * from './update-authorization-permission.interface'; diff --git a/src/permissions/interfaces/permission.interface.ts b/src/permissions/interfaces/permission.interface.ts new file mode 100644 index 000000000..864e38042 --- /dev/null +++ b/src/permissions/interfaces/permission.interface.ts @@ -0,0 +1,9 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + AuthorizationPermission, + AuthorizationPermissionResponse, +} from './authorization-permission.interface'; + +export type Permission = AuthorizationPermission; +export type PermissionResponse = AuthorizationPermissionResponse; diff --git a/src/permissions/interfaces/update-authorization-permission.interface.ts b/src/permissions/interfaces/update-authorization-permission.interface.ts new file mode 100644 index 000000000..f15bcb0fa --- /dev/null +++ b/src/permissions/interfaces/update-authorization-permission.interface.ts @@ -0,0 +1,10 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + UpdateOrganizationRole, + UpdateOrganizationRoleResponse, +} from '../../authorization/interfaces/update-organization-role.interface'; + +export type UpdateAuthorizationPermission = UpdateOrganizationRole; +export type UpdateAuthorizationPermissionResponse = + UpdateOrganizationRoleResponse; diff --git a/src/permissions/permissions.spec.ts b/src/permissions/permissions.spec.ts new file mode 100644 index 000000000..6b17fe6c2 --- /dev/null +++ b/src/permissions/permissions.spec.ts @@ -0,0 +1,160 @@ +// This file is auto-generated by oagen. Do not edit. + +import fetch from 'jest-fetch-mock'; +import { + fetchOnce, + fetchURL, + fetchMethod, + fetchSearchParams, + fetchBody, + testEmptyResults, + testPaginationParams, + testUnauthorized, +} from '../common/utils/test-utils'; +import { WorkOS } from '../workos'; + +import listAuthorizationPermissionFixture from './fixtures/list-authorization-permission.fixture.json'; +import permissionFixture from './fixtures/permission.fixture.json'; +import authorizationPermissionFixture from './fixtures/authorization-permission.fixture.json'; + +const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); + +function expectAuthorizationPermission(result: any) { + expect(result.object).toBe('permission'); + expect(result.id).toBe('perm_01HXYZ123456789ABCDEFGHIJ'); + expect(result.slug).toBe('documents:read'); + expect(result.name).toBe('View Documents'); + expect(result.description).toBe('Allows viewing document contents'); + expect(result.system).toBe(false); + expect(result.resourceTypeSlug).toBe('workspace'); + expect(result.createdAt).toBe('2026-01-15T12:00:00.000Z'); + expect(result.updatedAt).toBe('2026-01-15T12:00:00.000Z'); +} + +describe('Permissions', () => { + beforeEach(() => fetch.resetMocks()); + + describe('list', () => { + it('returns paginated results', async () => { + fetchOnce(listAuthorizationPermissionFixture); + + const { data, listMetadata } = await workos.permissions.list(); + + expect(fetchMethod()).toBe('GET'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/authorization/permissions', + ); + expect(fetchSearchParams()).toHaveProperty('order'); + expect(Array.isArray(data)).toBe(true); + expect(listMetadata).toBeDefined(); + expect(data.length).toBeGreaterThan(0); + expectAuthorizationPermission(data[0]); + }); + + testEmptyResults(() => workos.permissions.list()); + + testPaginationParams( + (opts) => workos.permissions.list(opts), + listAuthorizationPermissionFixture, + ); + + testUnauthorized(() => workos.permissions.list()); + }); + + describe('create', () => { + it('sends the correct request and returns result', async () => { + fetchOnce(permissionFixture); + + const result = await workos.permissions.create({ + slug: 'test_slug', + name: 'Test', + }); + + expect(fetchMethod()).toBe('POST'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/authorization/permissions', + ); + expect(fetchBody()).toEqual( + expect.objectContaining({ slug: 'test_slug', name: 'Test' }), + ); + expect(result.object).toBe('permission'); + expect(result.id).toBe('perm_01HXYZ123456789ABCDEFGHIJ'); + expect(result.slug).toBe('documents:read'); + expect(result.name).toBe('View Documents'); + expect(result.description).toBe('Allows viewing document contents'); + expect(result.system).toBe(false); + expect(result.resourceTypeSlug).toBe('document'); + expect(result.createdAt).toBe('2026-01-15T12:00:00.000Z'); + expect(result.updatedAt).toBe('2026-01-15T12:00:00.000Z'); + }); + + testUnauthorized(() => + workos.permissions.create({ slug: 'test_slug', name: 'Test' }), + ); + + it('throws UnprocessableEntityException on 422', async () => { + fetchOnce('', { status: 422 }); + await expect( + workos.permissions.create({ slug: 'test_slug', name: 'Test' }), + ).rejects.toThrow(); + }); + }); + + describe('find', () => { + it('returns the expected result', async () => { + fetchOnce(authorizationPermissionFixture); + + const result = await workos.permissions.find('test_slug'); + + expect(fetchMethod()).toBe('GET'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/authorization/permissions/test_slug', + ); + expectAuthorizationPermission(result); + }); + + testUnauthorized(() => workos.permissions.find('test_slug')); + + it('throws NotFoundException on 404', async () => { + fetchOnce('', { status: 404 }); + await expect(workos.permissions.find('test_slug')).rejects.toThrow(); + }); + }); + + describe('update', () => { + it('sends the correct request and returns result', async () => { + fetchOnce(authorizationPermissionFixture); + + const result = await workos.permissions.update('test_slug', {}); + + expect(fetchMethod()).toBe('PATCH'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/authorization/permissions/test_slug', + ); + expect(fetchBody()).toBeDefined(); + expectAuthorizationPermission(result); + }); + + testUnauthorized(() => workos.permissions.update('test_slug', {})); + + it('throws UnprocessableEntityException on 422', async () => { + fetchOnce('', { status: 422 }); + await expect( + workos.permissions.update('test_slug', {}), + ).rejects.toThrow(); + }); + }); + + describe('delete', () => { + it('sends a DELETE request', async () => { + fetchOnce({}, { status: 204 }); + + await workos.permissions.delete('test_slug'); + + expect(fetchMethod()).toBe('DELETE'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/authorization/permissions/test_slug', + ); + }); + }); +}); diff --git a/src/permissions/permissions.ts b/src/permissions/permissions.ts new file mode 100644 index 000000000..b7b8539d4 --- /dev/null +++ b/src/permissions/permissions.ts @@ -0,0 +1,117 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { WorkOS } from '../workos'; +import type { PaginationOptions } from '../common/interfaces/pagination-options.interface'; +import type { AutoPaginatable } from '../common/utils/pagination'; +import { createPaginatedList } from '../common/utils/fetch-and-deserialize'; +import type { + AuthorizationPermission, + AuthorizationPermissionResponse, +} from './interfaces/authorization-permission.interface'; +import type { + Permission, + PermissionResponse, +} from './interfaces/permission.interface'; +import type { CreateAuthorizationPermission } from './interfaces/create-authorization-permission.interface'; +import type { UpdateAuthorizationPermission } from './interfaces/update-authorization-permission.interface'; +import { deserializeAuthorizationPermission } from './serializers/authorization-permission.serializer'; +import { deserializePermission } from './serializers/permission.serializer'; +import { serializeCreateAuthorizationPermission } from './serializers/create-authorization-permission.serializer'; +import { serializeUpdateAuthorizationPermission } from './serializers/update-authorization-permission.serializer'; + +export class Permissions { + constructor(private readonly workos: WorkOS) {} + + /** + * List permissions + * + * Get a list of all permissions in your WorkOS environment. + * @param options - Pagination and filter options. + * @returns {AutoPaginatable} + * @throws {NotFoundException} 404 + */ + async list( + options?: PaginationOptions, + ): Promise> { + return createPaginatedList< + AuthorizationPermissionResponse, + AuthorizationPermission, + PaginationOptions + >( + this.workos, + '/authorization/permissions', + deserializeAuthorizationPermission, + options, + ); + } + + /** + * Create a permission + * + * Create a new permission in your WorkOS environment. The permission can then be assigned to environment roles and organization roles. + * @param payload - Object containing slug, name. + * @returns {Permission} + * @throws {BadRequestException} 400 + * @throws {NotFoundException} 404 + * @throws {ConflictException} 409 + * @throws {UnprocessableEntityException} 422 + */ + async create(payload: CreateAuthorizationPermission): Promise { + const { data } = await this.workos.post( + '/authorization/permissions', + serializeCreateAuthorizationPermission(payload), + ); + return deserializePermission(data); + } + + /** + * Get a permission + * + * Retrieve a permission by its unique slug. + * @param slug - A unique key to reference the permission. Must be lowercase and contain only letters, numbers, hyphens, underscores, colons, periods, and asterisks. + * @example "documents:read" + * @returns {AuthorizationPermission} + * @throws {NotFoundException} 404 + */ + async find(slug: string): Promise { + const { data } = await this.workos.get( + `/authorization/permissions/${slug}`, + ); + return deserializeAuthorizationPermission(data); + } + + /** + * Update a permission + * + * Update an existing permission. Only the fields provided in the request body will be updated. + * @param slug - A unique key to reference the permission. Must be lowercase and contain only letters, numbers, hyphens, underscores, colons, periods, and asterisks. + * @example "documents:read" + * @param payload - The request body. + * @returns {AuthorizationPermission} + * @throws {NotFoundException} 404 + * @throws {UnprocessableEntityException} 422 + */ + async update( + slug: string, + payload: UpdateAuthorizationPermission, + ): Promise { + const { data } = await this.workos.patch( + `/authorization/permissions/${slug}`, + serializeUpdateAuthorizationPermission(payload), + ); + return deserializeAuthorizationPermission(data); + } + + /** + * Delete a permission + * + * Delete an existing permission. System permissions cannot be deleted. + * @param slug - A unique key to reference the permission. Must be lowercase and contain only letters, numbers, hyphens, underscores, colons, periods, and asterisks. + * @example "documents:read" + * @returns {void} + * @throws {NotFoundException} 404 + */ + async delete(slug: string): Promise { + await this.workos.delete(`/authorization/permissions/${slug}`); + } +} diff --git a/src/permissions/serializers/authorization-permission.serializer.ts b/src/permissions/serializers/authorization-permission.serializer.ts new file mode 100644 index 000000000..1f228af35 --- /dev/null +++ b/src/permissions/serializers/authorization-permission.serializer.ts @@ -0,0 +1,34 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + AuthorizationPermission, + AuthorizationPermissionResponse, +} from '../interfaces/authorization-permission.interface'; + +export const deserializeAuthorizationPermission = ( + response: AuthorizationPermissionResponse, +): AuthorizationPermission => ({ + object: response.object, + id: response.id, + slug: response.slug, + name: response.name, + description: response.description ?? null, + system: response.system, + resourceTypeSlug: response.resource_type_slug, + createdAt: response.created_at, + updatedAt: response.updated_at, +}); + +export const serializeAuthorizationPermission = ( + model: AuthorizationPermission, +): AuthorizationPermissionResponse => ({ + object: model.object, + id: model.id, + slug: model.slug, + name: model.name, + description: model.description, + system: model.system, + resource_type_slug: model.resourceTypeSlug, + created_at: model.createdAt, + updated_at: model.updatedAt, +}); diff --git a/src/permissions/serializers/create-authorization-permission.serializer.ts b/src/permissions/serializers/create-authorization-permission.serializer.ts new file mode 100644 index 000000000..1d1346562 --- /dev/null +++ b/src/permissions/serializers/create-authorization-permission.serializer.ts @@ -0,0 +1,24 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + CreateAuthorizationPermission, + CreateAuthorizationPermissionResponse, +} from '../interfaces/create-authorization-permission.interface'; + +export const deserializeCreateAuthorizationPermission = ( + response: CreateAuthorizationPermissionResponse, +): CreateAuthorizationPermission => ({ + slug: response.slug, + name: response.name, + description: response.description ?? null, + resourceTypeSlug: response.resource_type_slug, +}); + +export const serializeCreateAuthorizationPermission = ( + model: CreateAuthorizationPermission, +): CreateAuthorizationPermissionResponse => ({ + slug: model.slug, + name: model.name, + description: model.description ?? null, + resource_type_slug: model.resourceTypeSlug, +}); diff --git a/src/permissions/serializers/permission.serializer.ts b/src/permissions/serializers/permission.serializer.ts new file mode 100644 index 000000000..78f0d3f24 --- /dev/null +++ b/src/permissions/serializers/permission.serializer.ts @@ -0,0 +1,6 @@ +// This file is auto-generated by oagen. Do not edit. + +export { + deserializeAuthorizationPermission as deserializePermission, + serializeAuthorizationPermission as serializePermission, +} from './authorization-permission.serializer'; diff --git a/src/permissions/serializers/update-authorization-permission.serializer.ts b/src/permissions/serializers/update-authorization-permission.serializer.ts new file mode 100644 index 000000000..078a2f9b8 --- /dev/null +++ b/src/permissions/serializers/update-authorization-permission.serializer.ts @@ -0,0 +1,6 @@ +// This file is auto-generated by oagen. Do not edit. + +export { + deserializeUpdateOrganizationRole as deserializeUpdateAuthorizationPermission, + serializeUpdateOrganizationRole as serializeUpdateAuthorizationPermission, +} from '../../authorization/serializers/update-organization-role.serializer'; diff --git a/src/portal/interfaces/generate-portal-link-intent.interface.ts b/src/portal/interfaces/generate-portal-link-intent.interface.ts index 5b1dc2e98..2ed62e10e 100644 --- a/src/portal/interfaces/generate-portal-link-intent.interface.ts +++ b/src/portal/interfaces/generate-portal-link-intent.interface.ts @@ -1,3 +1,4 @@ +/** @deprecated Use `AdminPortal` instead. */ export enum GeneratePortalLinkIntent { AuditLogs = 'audit_logs', DomainVerification = 'domain_verification', diff --git a/src/portal/portal.ts b/src/portal/portal.ts index fb466131f..63eb25b39 100644 --- a/src/portal/portal.ts +++ b/src/portal/portal.ts @@ -1,6 +1,7 @@ import { WorkOS } from '../workos'; import { GeneratePortalLinkIntent } from './interfaces/generate-portal-link-intent.interface'; +/** @deprecated Use `AdminPortal` instead. */ export class Portal { constructor(private readonly workos: WorkOS) {} diff --git a/src/radar/fixtures/radar-list-entry-already-present-response.fixture.json b/src/radar/fixtures/radar-list-entry-already-present-response.fixture.json new file mode 100644 index 000000000..d20390c36 --- /dev/null +++ b/src/radar/fixtures/radar-list-entry-already-present-response.fixture.json @@ -0,0 +1,3 @@ +{ + "message": "Entry already present in list" +} diff --git a/src/radar/fixtures/radar-standalone-assess-request.fixture.json b/src/radar/fixtures/radar-standalone-assess-request.fixture.json new file mode 100644 index 000000000..6f3673e82 --- /dev/null +++ b/src/radar/fixtures/radar-standalone-assess-request.fixture.json @@ -0,0 +1,9 @@ +{ + "ip_address": "49.78.240.97", + "user_agent": "Mozilla/5.0", + "email": "user@example.com", + "auth_method": "Password", + "action": "login", + "device_fingerprint": "fp_abc123", + "bot_score": "0.1" +} diff --git a/src/radar/fixtures/radar-standalone-delete-radar-list-entry-request.fixture.json b/src/radar/fixtures/radar-standalone-delete-radar-list-entry-request.fixture.json new file mode 100644 index 000000000..a123eb26f --- /dev/null +++ b/src/radar/fixtures/radar-standalone-delete-radar-list-entry-request.fixture.json @@ -0,0 +1,3 @@ +{ + "entry": "198.51.100.42" +} diff --git a/src/radar/fixtures/radar-standalone-response.fixture.json b/src/radar/fixtures/radar-standalone-response.fixture.json new file mode 100644 index 000000000..1670848e1 --- /dev/null +++ b/src/radar/fixtures/radar-standalone-response.fixture.json @@ -0,0 +1,7 @@ +{ + "verdict": "block", + "reason": "Detected enabled Radar control", + "attempt_id": "radar_att_01HZBC6N1EB1ZY7KG32X", + "control": "bot_detection", + "blocklist_type": "ip_address" +} diff --git a/src/radar/fixtures/radar-standalone-update-radar-attempt-request.fixture.json b/src/radar/fixtures/radar-standalone-update-radar-attempt-request.fixture.json new file mode 100644 index 000000000..b9598708a --- /dev/null +++ b/src/radar/fixtures/radar-standalone-update-radar-attempt-request.fixture.json @@ -0,0 +1,4 @@ +{ + "challenge_status": "success", + "attempt_status": "success" +} diff --git a/src/radar/fixtures/radar-standalone-update-radar-list-request.fixture.json b/src/radar/fixtures/radar-standalone-update-radar-list-request.fixture.json new file mode 100644 index 000000000..a123eb26f --- /dev/null +++ b/src/radar/fixtures/radar-standalone-update-radar-list-request.fixture.json @@ -0,0 +1,3 @@ +{ + "entry": "198.51.100.42" +} diff --git a/src/radar/interfaces/index.ts b/src/radar/interfaces/index.ts new file mode 100644 index 000000000..34baa9936 --- /dev/null +++ b/src/radar/interfaces/index.ts @@ -0,0 +1,8 @@ +// This file is auto-generated by oagen. Do not edit. + +export * from './radar-list-entry-already-present-response.interface'; +export * from './radar-standalone-assess-request.interface'; +export * from './radar-standalone-delete-radar-list-entry-request.interface'; +export * from './radar-standalone-response.interface'; +export * from './radar-standalone-update-radar-attempt-request.interface'; +export * from './radar-standalone-update-radar-list-request.interface'; diff --git a/src/radar/interfaces/radar-list-entry-already-present-response.interface.ts b/src/radar/interfaces/radar-list-entry-already-present-response.interface.ts new file mode 100644 index 000000000..06bdba196 --- /dev/null +++ b/src/radar/interfaces/radar-list-entry-already-present-response.interface.ts @@ -0,0 +1,10 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface RadarListEntryAlreadyPresentResponse { + /** A message indicating the entry already exists. */ + message: string; +} + +export interface RadarListEntryAlreadyPresentResponseWire { + message: string; +} diff --git a/src/radar/interfaces/radar-standalone-assess-request.interface.ts b/src/radar/interfaces/radar-standalone-assess-request.interface.ts new file mode 100644 index 000000000..0e5c3c5c1 --- /dev/null +++ b/src/radar/interfaces/radar-standalone-assess-request.interface.ts @@ -0,0 +1,31 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { RadarStandaloneAssessRequestAuthMethod } from '../../common/interfaces/radar-standalone-assess-request-auth-method.interface'; +import type { RadarStandaloneAssessRequestAction } from '../../common/interfaces/radar-standalone-assess-request-action.interface'; + +export interface RadarStandaloneAssessRequest { + /** The IP address of the request to assess. */ + ipAddress: string; + /** The user agent string of the request to assess. */ + userAgent: string; + /** The email address of the user making the request. */ + email: string; + /** The authentication method being used. */ + authMethod: RadarStandaloneAssessRequestAuthMethod; + /** The action being performed. */ + action: RadarStandaloneAssessRequestAction; + /** An optional device fingerprint for the request. */ + deviceFingerprint?: string; + /** An optional bot detection score for the request. */ + botScore?: string; +} + +export interface RadarStandaloneAssessRequestResponse { + ip_address: string; + user_agent: string; + email: string; + auth_method: RadarStandaloneAssessRequestAuthMethod; + action: RadarStandaloneAssessRequestAction; + device_fingerprint?: string; + bot_score?: string; +} diff --git a/src/radar/interfaces/radar-standalone-delete-radar-list-entry-request.interface.ts b/src/radar/interfaces/radar-standalone-delete-radar-list-entry-request.interface.ts new file mode 100644 index 000000000..c891d5c2b --- /dev/null +++ b/src/radar/interfaces/radar-standalone-delete-radar-list-entry-request.interface.ts @@ -0,0 +1,11 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + RadarStandaloneUpdateRadarListRequest, + RadarStandaloneUpdateRadarListRequestResponse, +} from './radar-standalone-update-radar-list-request.interface'; + +export type RadarStandaloneDeleteRadarListEntryRequest = + RadarStandaloneUpdateRadarListRequest; +export type RadarStandaloneDeleteRadarListEntryRequestResponse = + RadarStandaloneUpdateRadarListRequestResponse; diff --git a/src/radar/interfaces/radar-standalone-response.interface.ts b/src/radar/interfaces/radar-standalone-response.interface.ts new file mode 100644 index 000000000..8693db365 --- /dev/null +++ b/src/radar/interfaces/radar-standalone-response.interface.ts @@ -0,0 +1,26 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { RadarStandaloneResponseVerdict } from '../../common/interfaces/radar-standalone-response-verdict.interface'; +import type { RadarStandaloneResponseControl } from '../../common/interfaces/radar-standalone-response-control.interface'; +import type { RadarStandaloneResponseBlocklistType } from '../../common/interfaces/radar-standalone-response-blocklist-type.interface'; + +export interface RadarStandaloneResponse { + /** The verdict of the risk assessment. */ + verdict: RadarStandaloneResponseVerdict; + /** A human-readable reason for the verdict. */ + reason: string; + /** Unique identifier of the authentication attempt. */ + attemptId: string; + /** The Radar control that triggered the verdict. Only present if the verdict is `block` or `challenge`. */ + control?: RadarStandaloneResponseControl; + /** The type of blocklist entry that triggered the verdict. Only present if the control is `restriction`. */ + blocklistType?: RadarStandaloneResponseBlocklistType; +} + +export interface RadarStandaloneResponseWire { + verdict: RadarStandaloneResponseVerdict; + reason: string; + attempt_id: string; + control?: RadarStandaloneResponseControl; + blocklist_type?: RadarStandaloneResponseBlocklistType; +} diff --git a/src/radar/interfaces/radar-standalone-update-radar-attempt-request.interface.ts b/src/radar/interfaces/radar-standalone-update-radar-attempt-request.interface.ts new file mode 100644 index 000000000..815829f74 --- /dev/null +++ b/src/radar/interfaces/radar-standalone-update-radar-attempt-request.interface.ts @@ -0,0 +1,13 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface RadarStandaloneUpdateRadarAttemptRequest { + /** Set to `"success"` to mark the challenge as completed. */ + challengeStatus?: 'success'; + /** Set to `"success"` to mark the authentication attempt as successful. */ + attemptStatus?: 'success'; +} + +export interface RadarStandaloneUpdateRadarAttemptRequestResponse { + challenge_status?: 'success'; + attempt_status?: 'success'; +} diff --git a/src/radar/interfaces/radar-standalone-update-radar-list-request.interface.ts b/src/radar/interfaces/radar-standalone-update-radar-list-request.interface.ts new file mode 100644 index 000000000..7abe700f0 --- /dev/null +++ b/src/radar/interfaces/radar-standalone-update-radar-list-request.interface.ts @@ -0,0 +1,10 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface RadarStandaloneUpdateRadarListRequest { + /** The value to add to the list. Must match the format of the list type (e.g. a valid IP address for `ip_address`, a valid email for `email`). */ + entry: string; +} + +export interface RadarStandaloneUpdateRadarListRequestResponse { + entry: string; +} diff --git a/src/radar/radar.spec.ts b/src/radar/radar.spec.ts new file mode 100644 index 000000000..76d78cad6 --- /dev/null +++ b/src/radar/radar.spec.ts @@ -0,0 +1,115 @@ +// This file is auto-generated by oagen. Do not edit. + +import fetch from 'jest-fetch-mock'; +import { + fetchOnce, + fetchURL, + fetchMethod, + fetchBody, + testUnauthorized, +} from '../common/utils/test-utils'; +import { WorkOS } from '../workos'; + +import radarStandaloneResponseFixture from './fixtures/radar-standalone-response.fixture.json'; +import radarListEntryAlreadyPresentResponseFixture from './fixtures/radar-list-entry-already-present-response.fixture.json'; + +const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); + +describe('Radar', () => { + beforeEach(() => fetch.resetMocks()); + + describe('assess', () => { + it('sends the correct request and returns result', async () => { + fetchOnce(radarStandaloneResponseFixture); + + const result = await workos.radar.assess({ + ipAddress: 'test_ip_address', + userAgent: 'test_user_agent', + email: 'test@example.com', + authMethod: 'Password', + action: 'login', + }); + + expect(fetchMethod()).toBe('POST'); + expect(new URL(String(fetchURL())).pathname).toBe('/radar/attempts'); + expect(fetchBody()).toEqual( + expect.objectContaining({ + ip_address: 'test_ip_address', + user_agent: 'test_user_agent', + email: 'test@example.com', + auth_method: 'Password', + action: 'login', + }), + ); + expect(result.verdict).toBe('block'); + expect(result.reason).toBe('Detected enabled Radar control'); + expect(result.attemptId).toBe('radar_att_01HZBC6N1EB1ZY7KG32X'); + }); + + testUnauthorized(() => + workos.radar.assess({ + ipAddress: 'test_ip_address', + userAgent: 'test_user_agent', + email: 'test@example.com', + authMethod: 'Password', + action: 'login', + }), + ); + }); + + describe('updateRadarAttempt', () => { + it('sends the request', async () => { + fetchOnce({}); + + await workos.radar.updateRadarAttempt('test_id', {}); + + expect(fetchMethod()).toBe('PUT'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/radar/attempts/test_id', + ); + }); + }); + + describe('updateRadarList', () => { + it('sends the correct request and returns result', async () => { + fetchOnce(radarListEntryAlreadyPresentResponseFixture); + + const result = await workos.radar.updateRadarList('ip_address', 'block', { + entry: 'test_entry', + }); + + expect(fetchMethod()).toBe('POST'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/radar/lists/ip_address/block', + ); + expect(fetchBody()).toEqual( + expect.objectContaining({ entry: 'test_entry' }), + ); + expect(result.message).toBe('Entry already present in list'); + }); + + testUnauthorized(() => + workos.radar.updateRadarList('ip_address', 'block', { + entry: 'test_entry', + }), + ); + }); + + describe('deleteRadarListEntry', () => { + it('sends a DELETE request', async () => { + fetchOnce({}, { status: 204 }); + + await workos.radar.deleteRadarListEntry('ip_address', 'block', { + entry: 'test_entry', + }); + + expect(fetchMethod()).toBe('DELETE'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/radar/lists/ip_address/block', + ); + expect(fetchBody()).toEqual( + expect.objectContaining({ entry: 'test_entry' }), + ); + }); + }); +}); diff --git a/src/radar/radar.ts b/src/radar/radar.ts new file mode 100644 index 000000000..d9749403f --- /dev/null +++ b/src/radar/radar.ts @@ -0,0 +1,127 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { WorkOS } from '../workos'; +import type { + RadarStandaloneResponse, + RadarStandaloneResponseWire, +} from './interfaces/radar-standalone-response.interface'; +import type { + RadarListEntryAlreadyPresentResponse, + RadarListEntryAlreadyPresentResponseWire, +} from './interfaces/radar-list-entry-already-present-response.interface'; +import type { RadarStandaloneAssessRequest } from './interfaces/radar-standalone-assess-request.interface'; +import type { RadarStandaloneUpdateRadarAttemptRequest } from './interfaces/radar-standalone-update-radar-attempt-request.interface'; +import type { RadarStandaloneUpdateRadarListRequest } from './interfaces/radar-standalone-update-radar-list-request.interface'; +import type { RadarStandaloneDeleteRadarListEntryRequest } from './interfaces/radar-standalone-delete-radar-list-entry-request.interface'; +import { deserializeRadarStandaloneResponse } from './serializers/radar-standalone-response.serializer'; +import { deserializeRadarListEntryAlreadyPresentResponse } from './serializers/radar-list-entry-already-present-response.serializer'; +import { serializeRadarStandaloneAssessRequest } from './serializers/radar-standalone-assess-request.serializer'; +import { serializeRadarStandaloneUpdateRadarAttemptRequest } from './serializers/radar-standalone-update-radar-attempt-request.serializer'; +import { serializeRadarStandaloneUpdateRadarListRequest } from './serializers/radar-standalone-update-radar-list-request.serializer'; +import { serializeRadarStandaloneDeleteRadarListEntryRequest } from './serializers/radar-standalone-delete-radar-list-entry-request.serializer'; + +export class Radar { + constructor(private readonly workos: WorkOS) {} + + /** + * Create an attempt + * + * Assess a request for risk using the Radar engine and receive a verdict. + * @param payload - Object containing ipAddress, userAgent, email, authMethod, action. + * @returns {RadarStandaloneResponse} + * @throws {BadRequestException} 400 + */ + async assess( + payload: RadarStandaloneAssessRequest, + ): Promise { + const { data } = await this.workos.post( + '/radar/attempts', + serializeRadarStandaloneAssessRequest(payload), + ); + return deserializeRadarStandaloneResponse(data); + } + + /** + * Update a Radar attempt + * + * You may optionally inform Radar that an authentication attempt or challenge was successful using this endpoint. Some Radar controls depend on tracking recent successful attempts, such as impossible travel. + * @param id - The unique identifier of the Radar attempt to update. + * @example "radar_att_01HZBC6N1EB1ZY7KG32X" + * @param payload - The request body. + * @returns {void} + * @throws {BadRequestException} 400 + * @throws {NotFoundException} 404 + */ + async updateRadarAttempt( + id: string, + payload: RadarStandaloneUpdateRadarAttemptRequest, + ): Promise { + await this.workos.put( + `/radar/attempts/${id}`, + serializeRadarStandaloneUpdateRadarAttemptRequest(payload), + ); + } + + /** + * Add an entry to a Radar list + * + * Add an entry to a Radar list. + * @param type - The type of the Radar list (e.g. ip_address, domain, email). + * @example "ip_address" + * @param action - The list action indicating whether to add the entry to the allow or block list. + * @example "block" + * @param payload - Object containing entry. + * @returns {RadarListEntryAlreadyPresentResponse} + * @throws {BadRequestException} 400 + */ + async updateRadarList( + type: + | 'ip_address' + | 'domain' + | 'email' + | 'device' + | 'user_agent' + | 'device_fingerprint' + | 'country', + action: 'block' | 'allow', + payload: RadarStandaloneUpdateRadarListRequest, + ): Promise { + const { data } = + await this.workos.post( + `/radar/lists/${type}/${action}`, + serializeRadarStandaloneUpdateRadarListRequest(payload), + ); + return deserializeRadarListEntryAlreadyPresentResponse(data); + } + + /** + * Remove an entry from a Radar list + * + * Remove an entry from a Radar list. + * @param type - The type of the Radar list (e.g. ip_address, domain, email). + * @example "ip_address" + * @param action - The list action indicating whether to remove the entry from the allow or block list. + * @example "block" + * @param payload - Object containing entry. + * @returns {void} + * @throws {BadRequestException} 400 + * @throws {NotFoundException} 404 + */ + async deleteRadarListEntry( + type: + | 'ip_address' + | 'domain' + | 'email' + | 'device' + | 'user_agent' + | 'device_fingerprint' + | 'country', + action: 'block' | 'allow', + payload: RadarStandaloneDeleteRadarListEntryRequest, + ): Promise { + await this.workos.deleteWithBody( + `/radar/lists/${type}/${action}`, + serializeRadarStandaloneDeleteRadarListEntryRequest(payload), + ); + } +} diff --git a/src/radar/serializers/radar-list-entry-already-present-response.serializer.ts b/src/radar/serializers/radar-list-entry-already-present-response.serializer.ts new file mode 100644 index 000000000..48eaf29aa --- /dev/null +++ b/src/radar/serializers/radar-list-entry-already-present-response.serializer.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + RadarListEntryAlreadyPresentResponse, + RadarListEntryAlreadyPresentResponseWire, +} from '../interfaces/radar-list-entry-already-present-response.interface'; + +export const deserializeRadarListEntryAlreadyPresentResponse = ( + response: RadarListEntryAlreadyPresentResponseWire, +): RadarListEntryAlreadyPresentResponse => ({ + message: response.message, +}); + +export const serializeRadarListEntryAlreadyPresentResponse = ( + model: RadarListEntryAlreadyPresentResponse, +): RadarListEntryAlreadyPresentResponseWire => ({ + message: model.message, +}); diff --git a/src/radar/serializers/radar-standalone-assess-request.serializer.ts b/src/radar/serializers/radar-standalone-assess-request.serializer.ts new file mode 100644 index 000000000..8df63d779 --- /dev/null +++ b/src/radar/serializers/radar-standalone-assess-request.serializer.ts @@ -0,0 +1,30 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + RadarStandaloneAssessRequest, + RadarStandaloneAssessRequestResponse, +} from '../interfaces/radar-standalone-assess-request.interface'; + +export const deserializeRadarStandaloneAssessRequest = ( + response: RadarStandaloneAssessRequestResponse, +): RadarStandaloneAssessRequest => ({ + ipAddress: response.ip_address, + userAgent: response.user_agent, + email: response.email, + authMethod: response.auth_method, + action: response.action, + deviceFingerprint: response.device_fingerprint, + botScore: response.bot_score, +}); + +export const serializeRadarStandaloneAssessRequest = ( + model: RadarStandaloneAssessRequest, +): RadarStandaloneAssessRequestResponse => ({ + ip_address: model.ipAddress, + user_agent: model.userAgent, + email: model.email, + auth_method: model.authMethod, + action: model.action, + device_fingerprint: model.deviceFingerprint, + bot_score: model.botScore, +}); diff --git a/src/radar/serializers/radar-standalone-delete-radar-list-entry-request.serializer.ts b/src/radar/serializers/radar-standalone-delete-radar-list-entry-request.serializer.ts new file mode 100644 index 000000000..0b5fc3c5a --- /dev/null +++ b/src/radar/serializers/radar-standalone-delete-radar-list-entry-request.serializer.ts @@ -0,0 +1,6 @@ +// This file is auto-generated by oagen. Do not edit. + +export { + deserializeRadarStandaloneUpdateRadarListRequest as deserializeRadarStandaloneDeleteRadarListEntryRequest, + serializeRadarStandaloneUpdateRadarListRequest as serializeRadarStandaloneDeleteRadarListEntryRequest, +} from './radar-standalone-update-radar-list-request.serializer'; diff --git a/src/radar/serializers/radar-standalone-response.serializer.ts b/src/radar/serializers/radar-standalone-response.serializer.ts new file mode 100644 index 000000000..f53a56728 --- /dev/null +++ b/src/radar/serializers/radar-standalone-response.serializer.ts @@ -0,0 +1,26 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + RadarStandaloneResponse, + RadarStandaloneResponseWire, +} from '../interfaces/radar-standalone-response.interface'; + +export const deserializeRadarStandaloneResponse = ( + response: RadarStandaloneResponseWire, +): RadarStandaloneResponse => ({ + verdict: response.verdict, + reason: response.reason, + attemptId: response.attempt_id, + control: response.control, + blocklistType: response.blocklist_type, +}); + +export const serializeRadarStandaloneResponse = ( + model: RadarStandaloneResponse, +): RadarStandaloneResponseWire => ({ + verdict: model.verdict, + reason: model.reason, + attempt_id: model.attemptId, + control: model.control, + blocklist_type: model.blocklistType, +}); diff --git a/src/radar/serializers/radar-standalone-update-radar-attempt-request.serializer.ts b/src/radar/serializers/radar-standalone-update-radar-attempt-request.serializer.ts new file mode 100644 index 000000000..5c16e5685 --- /dev/null +++ b/src/radar/serializers/radar-standalone-update-radar-attempt-request.serializer.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + RadarStandaloneUpdateRadarAttemptRequest, + RadarStandaloneUpdateRadarAttemptRequestResponse, +} from '../interfaces/radar-standalone-update-radar-attempt-request.interface'; + +export const deserializeRadarStandaloneUpdateRadarAttemptRequest = ( + response: RadarStandaloneUpdateRadarAttemptRequestResponse, +): RadarStandaloneUpdateRadarAttemptRequest => ({ + challengeStatus: response.challenge_status, + attemptStatus: response.attempt_status, +}); + +export const serializeRadarStandaloneUpdateRadarAttemptRequest = ( + model: RadarStandaloneUpdateRadarAttemptRequest, +): RadarStandaloneUpdateRadarAttemptRequestResponse => ({ + challenge_status: model.challengeStatus, + attempt_status: model.attemptStatus, +}); diff --git a/src/radar/serializers/radar-standalone-update-radar-list-request.serializer.ts b/src/radar/serializers/radar-standalone-update-radar-list-request.serializer.ts new file mode 100644 index 000000000..2c964d833 --- /dev/null +++ b/src/radar/serializers/radar-standalone-update-radar-list-request.serializer.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + RadarStandaloneUpdateRadarListRequest, + RadarStandaloneUpdateRadarListRequestResponse, +} from '../interfaces/radar-standalone-update-radar-list-request.interface'; + +export const deserializeRadarStandaloneUpdateRadarListRequest = ( + response: RadarStandaloneUpdateRadarListRequestResponse, +): RadarStandaloneUpdateRadarListRequest => ({ + entry: response.entry, +}); + +export const serializeRadarStandaloneUpdateRadarListRequest = ( + model: RadarStandaloneUpdateRadarListRequest, +): RadarStandaloneUpdateRadarListRequestResponse => ({ + entry: model.entry, +}); diff --git a/src/webhooks/fixtures/create-webhook-endpoint.fixture.json b/src/webhooks/fixtures/create-webhook-endpoint.fixture.json new file mode 100644 index 000000000..81a07af09 --- /dev/null +++ b/src/webhooks/fixtures/create-webhook-endpoint.fixture.json @@ -0,0 +1,4 @@ +{ + "endpoint_url": "https://example.com/webhooks", + "events": ["user.created", "dsync.user.created"] +} diff --git a/src/webhooks/fixtures/list-webhook-endpoint-json.fixture.json b/src/webhooks/fixtures/list-webhook-endpoint-json.fixture.json new file mode 100644 index 000000000..e4587c5c1 --- /dev/null +++ b/src/webhooks/fixtures/list-webhook-endpoint-json.fixture.json @@ -0,0 +1,18 @@ +{ + "data": [ + { + "object": "webhook_endpoint", + "id": "we_0123456789", + "endpoint_url": "https://example.com/webhooks", + "secret": "whsec_0FWAiVGkEfGBqqsJH4aNAGBJ4", + "status": "enabled", + "events": ["user.created", "dsync.user.created"], + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + } + ], + "list_metadata": { + "before": null, + "after": null + } +} diff --git a/src/webhooks/fixtures/update-webhook-endpoint.fixture.json b/src/webhooks/fixtures/update-webhook-endpoint.fixture.json new file mode 100644 index 000000000..e73642781 --- /dev/null +++ b/src/webhooks/fixtures/update-webhook-endpoint.fixture.json @@ -0,0 +1,5 @@ +{ + "endpoint_url": "https://example.com/webhooks", + "status": "enabled", + "events": ["user.created", "dsync.user.created"] +} diff --git a/src/webhooks/fixtures/webhook-endpoint-json.fixture.json b/src/webhooks/fixtures/webhook-endpoint-json.fixture.json new file mode 100644 index 000000000..592c7cd23 --- /dev/null +++ b/src/webhooks/fixtures/webhook-endpoint-json.fixture.json @@ -0,0 +1,10 @@ +{ + "object": "webhook_endpoint", + "id": "we_0123456789", + "endpoint_url": "https://example.com/webhooks", + "secret": "whsec_0FWAiVGkEfGBqqsJH4aNAGBJ4", + "status": "enabled", + "events": ["user.created", "dsync.user.created"], + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/src/webhooks/interfaces/create-webhook-endpoint.interface.ts b/src/webhooks/interfaces/create-webhook-endpoint.interface.ts new file mode 100644 index 000000000..55813e1ec --- /dev/null +++ b/src/webhooks/interfaces/create-webhook-endpoint.interface.ts @@ -0,0 +1,15 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { CreateWebhookEndpointEvents } from '../../common/interfaces/create-webhook-endpoint-events.interface'; + +export interface CreateWebhookEndpoint { + /** The HTTPS URL where webhooks will be sent. */ + endpointUrl: string; + /** The events that the Webhook Endpoint is subscribed to. */ + events: CreateWebhookEndpointEvents[]; +} + +export interface CreateWebhookEndpointResponse { + endpoint_url: string; + events: CreateWebhookEndpointEvents[]; +} diff --git a/src/webhooks/interfaces/index.ts b/src/webhooks/interfaces/index.ts new file mode 100644 index 000000000..cb2832263 --- /dev/null +++ b/src/webhooks/interfaces/index.ts @@ -0,0 +1,5 @@ +// This file is auto-generated by oagen. Do not edit. + +export * from './create-webhook-endpoint.interface'; +export * from './update-webhook-endpoint.interface'; +export * from './webhook-endpoint-json.interface'; diff --git a/src/webhooks/interfaces/update-webhook-endpoint.interface.ts b/src/webhooks/interfaces/update-webhook-endpoint.interface.ts new file mode 100644 index 000000000..be19e7f3e --- /dev/null +++ b/src/webhooks/interfaces/update-webhook-endpoint.interface.ts @@ -0,0 +1,19 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { UpdateWebhookEndpointStatus } from '../../common/interfaces/update-webhook-endpoint-status.interface'; +import type { UpdateWebhookEndpointEvents } from '../../common/interfaces/update-webhook-endpoint-events.interface'; + +export interface UpdateWebhookEndpoint { + /** The HTTPS URL where webhooks will be sent. */ + endpointUrl?: string; + /** Whether the Webhook Endpoint is enabled or disabled. */ + status?: UpdateWebhookEndpointStatus; + /** The events that the Webhook Endpoint is subscribed to. */ + events?: UpdateWebhookEndpointEvents[]; +} + +export interface UpdateWebhookEndpointResponse { + endpoint_url?: string; + status?: UpdateWebhookEndpointStatus; + events?: UpdateWebhookEndpointEvents[]; +} diff --git a/src/webhooks/interfaces/webhook-endpoint-json.interface.ts b/src/webhooks/interfaces/webhook-endpoint-json.interface.ts new file mode 100644 index 000000000..ad0e2878e --- /dev/null +++ b/src/webhooks/interfaces/webhook-endpoint-json.interface.ts @@ -0,0 +1,33 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { WebhookEndpointJsonStatus } from '../../common/interfaces/webhook-endpoint-json-status.interface'; + +export interface WebhookEndpointJson { + /** Distinguishes the Webhook Endpoint object. */ + object: 'webhook_endpoint'; + /** Unique identifier of the Webhook Endpoint. */ + id: string; + /** The URL to which webhooks are sent. */ + endpointUrl: string; + /** The secret used to sign webhook payloads. */ + secret: string; + /** Whether the Webhook Endpoint is enabled or disabled. */ + status: WebhookEndpointJsonStatus; + /** The events that the Webhook Endpoint is subscribed to. */ + events: string[]; + /** An ISO 8601 timestamp. */ + createdAt: string; + /** An ISO 8601 timestamp. */ + updatedAt: string; +} + +export interface WebhookEndpointJsonResponse { + object: 'webhook_endpoint'; + id: string; + endpoint_url: string; + secret: string; + status: WebhookEndpointJsonStatus; + events: string[]; + created_at: string; + updated_at: string; +} diff --git a/src/webhooks/serializers/create-webhook-endpoint.serializer.ts b/src/webhooks/serializers/create-webhook-endpoint.serializer.ts new file mode 100644 index 000000000..d7fed4fe4 --- /dev/null +++ b/src/webhooks/serializers/create-webhook-endpoint.serializer.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + CreateWebhookEndpoint, + CreateWebhookEndpointResponse, +} from '../interfaces/create-webhook-endpoint.interface'; + +export const deserializeCreateWebhookEndpoint = ( + response: CreateWebhookEndpointResponse, +): CreateWebhookEndpoint => ({ + endpointUrl: response.endpoint_url, + events: response.events, +}); + +export const serializeCreateWebhookEndpoint = ( + model: CreateWebhookEndpoint, +): CreateWebhookEndpointResponse => ({ + endpoint_url: model.endpointUrl, + events: model.events, +}); diff --git a/src/webhooks/serializers/update-webhook-endpoint.serializer.ts b/src/webhooks/serializers/update-webhook-endpoint.serializer.ts new file mode 100644 index 000000000..981692ad8 --- /dev/null +++ b/src/webhooks/serializers/update-webhook-endpoint.serializer.ts @@ -0,0 +1,22 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + UpdateWebhookEndpoint, + UpdateWebhookEndpointResponse, +} from '../interfaces/update-webhook-endpoint.interface'; + +export const deserializeUpdateWebhookEndpoint = ( + response: UpdateWebhookEndpointResponse, +): UpdateWebhookEndpoint => ({ + endpointUrl: response.endpoint_url, + status: response.status, + events: response.events, +}); + +export const serializeUpdateWebhookEndpoint = ( + model: UpdateWebhookEndpoint, +): UpdateWebhookEndpointResponse => ({ + endpoint_url: model.endpointUrl, + status: model.status, + events: model.events, +}); diff --git a/src/webhooks/serializers/webhook-endpoint-json.serializer.ts b/src/webhooks/serializers/webhook-endpoint-json.serializer.ts new file mode 100644 index 000000000..048c10f77 --- /dev/null +++ b/src/webhooks/serializers/webhook-endpoint-json.serializer.ts @@ -0,0 +1,32 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + WebhookEndpointJson, + WebhookEndpointJsonResponse, +} from '../interfaces/webhook-endpoint-json.interface'; + +export const deserializeWebhookEndpointJson = ( + response: WebhookEndpointJsonResponse, +): WebhookEndpointJson => ({ + object: response.object, + id: response.id, + endpointUrl: response.endpoint_url, + secret: response.secret, + status: response.status, + events: response.events, + createdAt: response.created_at, + updatedAt: response.updated_at, +}); + +export const serializeWebhookEndpointJson = ( + model: WebhookEndpointJson, +): WebhookEndpointJsonResponse => ({ + object: model.object, + id: model.id, + endpoint_url: model.endpointUrl, + secret: model.secret, + status: model.status, + events: model.events, + created_at: model.createdAt, + updated_at: model.updatedAt, +}); diff --git a/src/webhooks/webhooks-endpoints.spec.ts b/src/webhooks/webhooks-endpoints.spec.ts new file mode 100644 index 000000000..11a7f3236 --- /dev/null +++ b/src/webhooks/webhooks-endpoints.spec.ts @@ -0,0 +1,134 @@ +// This file is auto-generated by oagen. Do not edit. + +import fetch from 'jest-fetch-mock'; +import { + fetchOnce, + fetchURL, + fetchMethod, + fetchSearchParams, + fetchBody, + testEmptyResults, + testPaginationParams, + testUnauthorized, +} from '../common/utils/test-utils'; +import { WorkOS } from '../workos'; + +import listWebhookEndpointJsonFixture from './fixtures/list-webhook-endpoint-json.fixture.json'; +import webhookEndpointJsonFixture from './fixtures/webhook-endpoint-json.fixture.json'; + +const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); + +function expectWebhookEndpointJson(result: any) { + expect(result.object).toBe('webhook_endpoint'); + expect(result.id).toBe('we_0123456789'); + expect(result.endpointUrl).toBe('https://example.com/webhooks'); + expect(result.secret).toBe('whsec_0FWAiVGkEfGBqqsJH4aNAGBJ4'); + expect(result.status).toBe('enabled'); + expect(result.events).toEqual(['user.created', 'dsync.user.created']); + expect(result.createdAt).toBe('2026-01-15T12:00:00.000Z'); + expect(result.updatedAt).toBe('2026-01-15T12:00:00.000Z'); +} + +describe('WebhooksEndpoints', () => { + beforeEach(() => fetch.resetMocks()); + + describe('list', () => { + it('returns paginated results', async () => { + fetchOnce(listWebhookEndpointJsonFixture); + + const { data, listMetadata } = await workos.webhooksEndpoints.list(); + + expect(fetchMethod()).toBe('GET'); + expect(new URL(String(fetchURL())).pathname).toBe('/webhook_endpoints'); + expect(fetchSearchParams()).toHaveProperty('order'); + expect(Array.isArray(data)).toBe(true); + expect(listMetadata).toBeDefined(); + expect(data.length).toBeGreaterThan(0); + expectWebhookEndpointJson(data[0]); + }); + + testEmptyResults(() => workos.webhooksEndpoints.list()); + + testPaginationParams( + (opts) => workos.webhooksEndpoints.list(opts), + listWebhookEndpointJsonFixture, + ); + + testUnauthorized(() => workos.webhooksEndpoints.list()); + }); + + describe('create', () => { + it('sends the correct request and returns result', async () => { + fetchOnce(webhookEndpointJsonFixture); + + const result = await workos.webhooksEndpoints.create({ + endpointUrl: 'https://example.com', + events: ['authentication.email_verification_succeeded'], + }); + + expect(fetchMethod()).toBe('POST'); + expect(new URL(String(fetchURL())).pathname).toBe('/webhook_endpoints'); + expect(fetchBody()).toEqual( + expect.objectContaining({ + endpoint_url: 'https://example.com', + events: ['authentication.email_verification_succeeded'], + }), + ); + expectWebhookEndpointJson(result); + }); + + testUnauthorized(() => + workos.webhooksEndpoints.create({ + endpointUrl: 'https://example.com', + events: ['authentication.email_verification_succeeded'], + }), + ); + + it('throws UnprocessableEntityException on 422', async () => { + fetchOnce('', { status: 422 }); + await expect( + workos.webhooksEndpoints.create({ + endpointUrl: 'https://example.com', + events: ['authentication.email_verification_succeeded'], + }), + ).rejects.toThrow(); + }); + }); + + describe('update', () => { + it('sends the correct request and returns result', async () => { + fetchOnce(webhookEndpointJsonFixture); + + const result = await workos.webhooksEndpoints.update('test_id', {}); + + expect(fetchMethod()).toBe('PATCH'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/webhook_endpoints/test_id', + ); + expect(fetchBody()).toBeDefined(); + expectWebhookEndpointJson(result); + }); + + testUnauthorized(() => workos.webhooksEndpoints.update('test_id', {})); + + it('throws UnprocessableEntityException on 422', async () => { + fetchOnce('', { status: 422 }); + await expect( + workos.webhooksEndpoints.update('test_id', {}), + ).rejects.toThrow(); + }); + }); + + describe('delete', () => { + it('sends a DELETE request', async () => { + fetchOnce({}, { status: 204 }); + + await workos.webhooksEndpoints.delete('test_id'); + + expect(fetchMethod()).toBe('DELETE'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/webhook_endpoints/test_id', + ); + }); + }); +}); diff --git a/src/webhooks/webhooks-endpoints.ts b/src/webhooks/webhooks-endpoints.ts new file mode 100644 index 000000000..3273b0f47 --- /dev/null +++ b/src/webhooks/webhooks-endpoints.ts @@ -0,0 +1,94 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { WorkOS } from '../workos'; +import type { PaginationOptions } from '../common/interfaces/pagination-options.interface'; +import type { AutoPaginatable } from '../common/utils/pagination'; +import { createPaginatedList } from '../common/utils/fetch-and-deserialize'; +import type { + WebhookEndpointJson, + WebhookEndpointJsonResponse, +} from './interfaces/webhook-endpoint-json.interface'; +import type { CreateWebhookEndpoint } from './interfaces/create-webhook-endpoint.interface'; +import type { UpdateWebhookEndpoint } from './interfaces/update-webhook-endpoint.interface'; +import { deserializeWebhookEndpointJson } from './serializers/webhook-endpoint-json.serializer'; +import { serializeCreateWebhookEndpoint } from './serializers/create-webhook-endpoint.serializer'; +import { serializeUpdateWebhookEndpoint } from './serializers/update-webhook-endpoint.serializer'; + +export class WebhooksEndpoints { + constructor(private readonly workos: WorkOS) {} + + /** + * List Webhook Endpoints + * + * Get a list of all of your existing webhook endpoints. + * @param options - Pagination and filter options. + * @returns {AutoPaginatable} + */ + async list( + options?: PaginationOptions, + ): Promise> { + return createPaginatedList< + WebhookEndpointJsonResponse, + WebhookEndpointJson, + PaginationOptions + >( + this.workos, + '/webhook_endpoints', + deserializeWebhookEndpointJson, + options, + ); + } + + /** + * Create a Webhook Endpoint + * + * Create a new webhook endpoint to receive event notifications. + * @param payload - Object containing endpointUrl, events. + * @returns {WebhookEndpointJson} + * @throws {ConflictException} 409 + * @throws {UnprocessableEntityException} 422 + */ + async create(payload: CreateWebhookEndpoint): Promise { + const { data } = await this.workos.post( + '/webhook_endpoints', + serializeCreateWebhookEndpoint(payload), + ); + return deserializeWebhookEndpointJson(data); + } + + /** + * Update a Webhook Endpoint + * + * Update the properties of an existing webhook endpoint. + * @param id - Unique identifier of the Webhook Endpoint. + * @example "we_0123456789" + * @param payload - The request body. + * @returns {WebhookEndpointJson} + * @throws {NotFoundException} 404 + * @throws {ConflictException} 409 + * @throws {UnprocessableEntityException} 422 + */ + async update( + id: string, + payload: UpdateWebhookEndpoint, + ): Promise { + const { data } = await this.workos.patch( + `/webhook_endpoints/${id}`, + serializeUpdateWebhookEndpoint(payload), + ); + return deserializeWebhookEndpointJson(data); + } + + /** + * Delete a Webhook Endpoint + * + * Delete an existing webhook endpoint. + * @param id - Unique identifier of the Webhook Endpoint. + * @example "we_0123456789" + * @returns {void} + * @throws {NotFoundException} 404 + */ + async delete(id: string): Promise { + await this.workos.delete(`/webhook_endpoints/${id}`); + } +} diff --git a/src/workos-connect/application-client-secrets.spec.ts b/src/workos-connect/application-client-secrets.spec.ts new file mode 100644 index 000000000..8ddfec808 --- /dev/null +++ b/src/workos-connect/application-client-secrets.spec.ts @@ -0,0 +1,91 @@ +// This file is auto-generated by oagen. Do not edit. + +import fetch from 'jest-fetch-mock'; +import { + fetchOnce, + fetchURL, + fetchMethod, + fetchBody, + testUnauthorized, +} from '../common/utils/test-utils'; +import { WorkOS } from '../workos'; + +import applicationCredentialsListItemFixture from './fixtures/application-credentials-list-item.fixture.json'; +import newConnectApplicationSecretFixture from './fixtures/new-connect-application-secret.fixture.json'; + +const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); + +describe('ApplicationClientSecrets', () => { + beforeEach(() => fetch.resetMocks()); + + describe('list', () => { + it('returns the expected result', async () => { + fetchOnce(applicationCredentialsListItemFixture); + + const result = await workos.applicationClientSecrets.list('test_id'); + + expect(fetchMethod()).toBe('GET'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/connect/applications/test_id/client_secrets', + ); + expect(result.object).toBe('connect_application_secret'); + expect(result.id).toBe('secret_01J9Q2Z3X4Y5W6V7U8T9S0R1Q'); + expect(result.secretHint).toBe('abc123'); + expect(result.lastUsedAt).toBe(null); + expect(result.createdAt).toBe('2026-01-15T12:00:00.000Z'); + expect(result.updatedAt).toBe('2026-01-15T12:00:00.000Z'); + }); + + testUnauthorized(() => workos.applicationClientSecrets.list('test_id')); + }); + + describe('create', () => { + it('sends the correct request and returns result', async () => { + fetchOnce(newConnectApplicationSecretFixture); + + const result = await workos.applicationClientSecrets.create( + 'test_id', + {}, + ); + + expect(fetchMethod()).toBe('POST'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/connect/applications/test_id/client_secrets', + ); + expect(fetchBody()).toBeDefined(); + expect(result.object).toBe('connect_application_secret'); + expect(result.id).toBe('secret_01J9Q2Z3X4Y5W6V7U8T9S0R1Q'); + expect(result.secretHint).toBe('abc123'); + expect(result.lastUsedAt).toBe(null); + expect(result.createdAt).toBe('2026-01-15T12:00:00.000Z'); + expect(result.updatedAt).toBe('2026-01-15T12:00:00.000Z'); + expect(result.secret).toBe( + 'abc123def456ghi789jkl012mno345pqr678stu901vwx234yz', + ); + }); + + testUnauthorized(() => + workos.applicationClientSecrets.create('test_id', {}), + ); + + it('throws UnprocessableEntityException on 422', async () => { + fetchOnce('', { status: 422 }); + await expect( + workos.applicationClientSecrets.create('test_id', {}), + ).rejects.toThrow(); + }); + }); + + describe('delete', () => { + it('sends a DELETE request', async () => { + fetchOnce({}, { status: 204 }); + + await workos.applicationClientSecrets.delete('test_id'); + + expect(fetchMethod()).toBe('DELETE'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/connect/client_secrets/test_id', + ); + }); + }); +}); diff --git a/src/workos-connect/application-client-secrets.ts b/src/workos-connect/application-client-secrets.ts new file mode 100644 index 000000000..afe58e2ae --- /dev/null +++ b/src/workos-connect/application-client-secrets.ts @@ -0,0 +1,72 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { WorkOS } from '../workos'; +import type { + ApplicationCredentialsListItem, + ApplicationCredentialsListItemResponse, +} from './interfaces/application-credentials-list-item.interface'; +import type { + NewConnectApplicationSecret, + NewConnectApplicationSecretResponse, +} from './interfaces/new-connect-application-secret.interface'; +import type { CreateApplicationSecret } from './interfaces/create-application-secret.interface'; +import { deserializeApplicationCredentialsListItem } from './serializers/application-credentials-list-item.serializer'; +import { deserializeNewConnectApplicationSecret } from './serializers/new-connect-application-secret.serializer'; +import { serializeCreateApplicationSecret } from './serializers/create-application-secret.serializer'; + +export class ApplicationClientSecrets { + constructor(private readonly workos: WorkOS) {} + + /** + * List Client Secrets for a Connect Application + * + * List all client secrets associated with a Connect Application. + * @param id - The application ID or client ID of the Connect Application. + * @example "conn_app_01HXYZ123456789ABCDEFGHIJ" + * @returns {ApplicationCredentialsListItem} + * @throws {NotFoundException} 404 + */ + async list(id: string): Promise { + const { data } = + await this.workos.get( + `/connect/applications/${id}/client_secrets`, + ); + return deserializeApplicationCredentialsListItem(data); + } + + /** + * Create a new client secret for a Connect Application + * + * Create new secrets for a Connect Application. + * @param id - The application ID or client ID of the Connect Application. + * @example "conn_app_01HXYZ123456789ABCDEFGHIJ" + * @param payload - The request body. + * @returns {NewConnectApplicationSecret} + * @throws {NotFoundException} 404 + * @throws {UnprocessableEntityException} 422 + */ + async create( + id: string, + payload: CreateApplicationSecret, + ): Promise { + const { data } = + await this.workos.post( + `/connect/applications/${id}/client_secrets`, + serializeCreateApplicationSecret(payload), + ); + return deserializeNewConnectApplicationSecret(data); + } + + /** + * Delete a Client Secret + * + * Delete (revoke) an existing client secret. + * @param id - The unique ID of the client secret. + * @example "secret_01J9Q2Z3X4Y5W6V7U8T9S0R1Q" + * @returns {void} + * @throws {NotFoundException} 404 + */ + async delete(id: string): Promise { + await this.workos.delete(`/connect/client_secrets/${id}`); + } +} diff --git a/src/workos-connect/applications.spec.ts b/src/workos-connect/applications.spec.ts new file mode 100644 index 000000000..ab07f194d --- /dev/null +++ b/src/workos-connect/applications.spec.ts @@ -0,0 +1,139 @@ +// This file is auto-generated by oagen. Do not edit. + +import fetch from 'jest-fetch-mock'; +import { + fetchOnce, + fetchURL, + fetchMethod, + fetchSearchParams, + fetchBody, + testEmptyResults, + testPaginationParams, + testUnauthorized, +} from '../common/utils/test-utils'; +import { WorkOS } from '../workos'; + +import listConnectApplicationFixture from './fixtures/list-connect-application.fixture.json'; +import connectApplicationFixture from './fixtures/connect-application.fixture.json'; + +const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); + +function expectConnectApplication(result: any) { + expect(result.object).toBe('connect_application'); + expect(result.id).toBe('conn_app_01HXYZ123456789ABCDEFGHIJ'); + expect(result.clientId).toBe('client_01HXYZ123456789ABCDEFGHIJ'); + expect(result.description).toBe('An application for managing user access'); + expect(result.name).toBe('My Application'); + expect(result.scopes).toEqual(['openid', 'profile', 'email']); + expect(result.createdAt).toBe('2026-01-15T12:00:00.000Z'); + expect(result.updatedAt).toBe('2026-01-15T12:00:00.000Z'); +} + +describe('Applications', () => { + beforeEach(() => fetch.resetMocks()); + + describe('list', () => { + it('returns paginated results', async () => { + fetchOnce(listConnectApplicationFixture); + + const { data, listMetadata } = await workos.applications.list(); + + expect(fetchMethod()).toBe('GET'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/connect/applications', + ); + expect(fetchSearchParams()).toHaveProperty('order'); + expect(Array.isArray(data)).toBe(true); + expect(listMetadata).toBeDefined(); + expect(data.length).toBeGreaterThan(0); + expectConnectApplication(data[0]); + }); + + testEmptyResults(() => workos.applications.list()); + + testPaginationParams( + (opts) => workos.applications.list(opts), + listConnectApplicationFixture, + ); + + testUnauthorized(() => workos.applications.list()); + }); + + describe('create', () => { + it('sends the correct request and returns result', async () => { + fetchOnce(connectApplicationFixture); + + const result = await workos.applications.create({} as any); + + expect(fetchMethod()).toBe('POST'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/connect/applications', + ); + expect(fetchBody()).toBeDefined(); + expectConnectApplication(result); + }); + + testUnauthorized(() => workos.applications.create({} as any)); + + it('throws UnprocessableEntityException on 422', async () => { + fetchOnce('', { status: 422 }); + await expect(workos.applications.create({} as any)).rejects.toThrow(); + }); + }); + + describe('find', () => { + it('returns the expected result', async () => { + fetchOnce(connectApplicationFixture); + + const result = await workos.applications.find('test_id'); + + expect(fetchMethod()).toBe('GET'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/connect/applications/test_id', + ); + expectConnectApplication(result); + }); + + testUnauthorized(() => workos.applications.find('test_id')); + + it('throws NotFoundException on 404', async () => { + fetchOnce('', { status: 404 }); + await expect(workos.applications.find('test_id')).rejects.toThrow(); + }); + }); + + describe('update', () => { + it('sends the correct request and returns result', async () => { + fetchOnce(connectApplicationFixture); + + const result = await workos.applications.update('test_id', {}); + + expect(fetchMethod()).toBe('PUT'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/connect/applications/test_id', + ); + expect(fetchBody()).toBeDefined(); + expectConnectApplication(result); + }); + + testUnauthorized(() => workos.applications.update('test_id', {})); + + it('throws UnprocessableEntityException on 422', async () => { + fetchOnce('', { status: 422 }); + await expect(workos.applications.update('test_id', {})).rejects.toThrow(); + }); + }); + + describe('delete', () => { + it('sends a DELETE request', async () => { + fetchOnce({}, { status: 204 }); + + await workos.applications.delete('test_id'); + + expect(fetchMethod()).toBe('DELETE'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/connect/applications/test_id', + ); + }); + }); +}); diff --git a/src/workos-connect/applications.ts b/src/workos-connect/applications.ts new file mode 100644 index 000000000..aa6d6f537 --- /dev/null +++ b/src/workos-connect/applications.ts @@ -0,0 +1,128 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { WorkOS } from '../workos'; +import type { PaginationOptions } from '../common/interfaces/pagination-options.interface'; +import type { AutoPaginatable } from '../common/utils/pagination'; +import { createPaginatedList } from '../common/utils/fetch-and-deserialize'; +import type { + ConnectApplication, + ConnectApplicationResponse, +} from './interfaces/connect-application.interface'; +import type { CreateOAuthApplication } from './interfaces/create-oauth-application.interface'; +import type { CreateM2MApplication } from './interfaces/create-m2m-application.interface'; +import type { UpdateOAuthApplication } from './interfaces/update-oauth-application.interface'; +import { deserializeConnectApplication } from './serializers/connect-application.serializer'; +import { serializeCreateOAuthApplication } from './serializers/create-oauth-application.serializer'; +import { serializeCreateM2MApplication } from './serializers/create-m2m-application.serializer'; +import { serializeUpdateOAuthApplication } from './serializers/update-oauth-application.serializer'; + +export interface ApplicationsListOptions extends PaginationOptions { + /** Filter Connect Applications by organization ID. */ + organizationId?: string; +} + +export class Applications { + constructor(private readonly workos: WorkOS) {} + + /** + * List Connect Applications + * + * List all Connect Applications in the current environment with optional filtering. + * @param options - Pagination and filter options. + * @returns {AutoPaginatable} + * @throws {UnprocessableEntityException} 422 + */ + async list( + options?: ApplicationsListOptions, + ): Promise> { + return createPaginatedList< + ConnectApplicationResponse, + ConnectApplication, + ApplicationsListOptions + >( + this.workos, + '/connect/applications', + deserializeConnectApplication, + options, + ); + } + + /** + * Create a Connect Application + * + * Create a new Connect Application. Supports both OAuth and Machine-to-Machine (M2M) application types. + * @param payload - The request body. + * @returns {ConnectApplication} + * @throws {NotFoundException} 404 + * @throws {UnprocessableEntityException} 422 + */ + async create( + payload: CreateOAuthApplication | CreateM2MApplication, + ): Promise { + const { data } = await this.workos.post( + '/connect/applications', + (() => { + switch ((payload as any).applicationType) { + case 'oauth': + return serializeCreateOAuthApplication(payload as any); + case 'm2m': + return serializeCreateM2MApplication(payload as any); + default: + return payload; + } + })(), + ); + return deserializeConnectApplication(data); + } + + /** + * Get a Connect Application + * + * Retrieve details for a specific Connect Application by ID or client ID. + * @param id - The application ID or client ID of the Connect Application. + * @example "conn_app_01HXYZ123456789ABCDEFGHIJ" + * @returns {ConnectApplication} + * @throws {NotFoundException} 404 + */ + async find(id: string): Promise { + const { data } = await this.workos.get( + `/connect/applications/${id}`, + ); + return deserializeConnectApplication(data); + } + + /** + * Update a Connect Application + * + * Update an existing Connect Application. For OAuth applications, you can update redirect URIs. For all applications, you can update the name, description, and scopes. + * @param id - The application ID or client ID of the Connect Application. + * @example "conn_app_01HXYZ123456789ABCDEFGHIJ" + * @param payload - The request body. + * @returns {ConnectApplication} + * @throws {NotFoundException} 404 + * @throws {UnprocessableEntityException} 422 + */ + async update( + id: string, + payload: UpdateOAuthApplication, + ): Promise { + const { data } = await this.workos.put( + `/connect/applications/${id}`, + serializeUpdateOAuthApplication(payload), + ); + return deserializeConnectApplication(data); + } + + /** + * Delete a Connect Application + * + * Delete an existing Connect Application. + * @param id - The application ID or client ID of the Connect Application. + * @example "conn_app_01HXYZ123456789ABCDEFGHIJ" + * @returns {void} + * @throws {NotFoundException} 404 + */ + async delete(id: string): Promise { + await this.workos.delete(`/connect/applications/${id}`); + } +} diff --git a/src/workos-connect/fixtures/application-credentials-list-item.fixture.json b/src/workos-connect/fixtures/application-credentials-list-item.fixture.json new file mode 100644 index 000000000..4c03cc2e5 --- /dev/null +++ b/src/workos-connect/fixtures/application-credentials-list-item.fixture.json @@ -0,0 +1,8 @@ +{ + "object": "connect_application_secret", + "id": "secret_01J9Q2Z3X4Y5W6V7U8T9S0R1Q", + "secret_hint": "abc123", + "last_used_at": null, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/src/workos-connect/fixtures/connect-application.fixture.json b/src/workos-connect/fixtures/connect-application.fixture.json new file mode 100644 index 000000000..4cd701a43 --- /dev/null +++ b/src/workos-connect/fixtures/connect-application.fixture.json @@ -0,0 +1,10 @@ +{ + "object": "connect_application", + "id": "conn_app_01HXYZ123456789ABCDEFGHIJ", + "client_id": "client_01HXYZ123456789ABCDEFGHIJ", + "description": "An application for managing user access", + "name": "My Application", + "scopes": ["openid", "profile", "email"], + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/src/workos-connect/fixtures/create-application-secret.fixture.json b/src/workos-connect/fixtures/create-application-secret.fixture.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/src/workos-connect/fixtures/create-application-secret.fixture.json @@ -0,0 +1 @@ +{} diff --git a/src/workos-connect/fixtures/create-m2m-application.fixture.json b/src/workos-connect/fixtures/create-m2m-application.fixture.json new file mode 100644 index 000000000..130fc51a9 --- /dev/null +++ b/src/workos-connect/fixtures/create-m2m-application.fixture.json @@ -0,0 +1,7 @@ +{ + "name": "My Application", + "application_type": "m2m", + "description": "An application for managing user access", + "scopes": ["openid", "profile", "email"], + "organization_id": "org_01EHZNVPK3SFK441A1RGBFSHRT" +} diff --git a/src/workos-connect/fixtures/create-oauth-application.fixture.json b/src/workos-connect/fixtures/create-oauth-application.fixture.json new file mode 100644 index 000000000..28f4bb301 --- /dev/null +++ b/src/workos-connect/fixtures/create-oauth-application.fixture.json @@ -0,0 +1,15 @@ +{ + "name": "My Application", + "application_type": "oauth", + "description": "An application for managing user access", + "scopes": ["openid", "profile", "email"], + "redirect_uris": [ + { + "uri": "https://example.com/callback", + "default": true + } + ], + "uses_pkce": true, + "is_first_party": true, + "organization_id": "org_01EHZNVPK3SFK441A1RGBFSHRT" +} diff --git a/src/workos-connect/fixtures/external-auth-complete-response.fixture.json b/src/workos-connect/fixtures/external-auth-complete-response.fixture.json new file mode 100644 index 000000000..bda60f725 --- /dev/null +++ b/src/workos-connect/fixtures/external-auth-complete-response.fixture.json @@ -0,0 +1,3 @@ +{ + "redirect_uri": "https://your-authkit-domain.workos.com/oauth/authorize/complete?state=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdGF0ZSI6InJhbmRvbV9zdGF0ZV9zdHJpbmciLCJpYXQiOjE3NDI2MDQ4NTN9.abc123def456ghi789" +} diff --git a/src/workos-connect/fixtures/list-connect-application.fixture.json b/src/workos-connect/fixtures/list-connect-application.fixture.json new file mode 100644 index 000000000..53bec71d3 --- /dev/null +++ b/src/workos-connect/fixtures/list-connect-application.fixture.json @@ -0,0 +1,18 @@ +{ + "data": [ + { + "object": "connect_application", + "id": "conn_app_01HXYZ123456789ABCDEFGHIJ", + "client_id": "client_01HXYZ123456789ABCDEFGHIJ", + "description": "An application for managing user access", + "name": "My Application", + "scopes": ["openid", "profile", "email"], + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" + } + ], + "list_metadata": { + "before": null, + "after": null + } +} diff --git a/src/workos-connect/fixtures/new-connect-application-secret.fixture.json b/src/workos-connect/fixtures/new-connect-application-secret.fixture.json new file mode 100644 index 000000000..9b772b11c --- /dev/null +++ b/src/workos-connect/fixtures/new-connect-application-secret.fixture.json @@ -0,0 +1,9 @@ +{ + "object": "connect_application_secret", + "id": "secret_01J9Q2Z3X4Y5W6V7U8T9S0R1Q", + "secret_hint": "abc123", + "last_used_at": null, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z", + "secret": "abc123def456ghi789jkl012mno345pqr678stu901vwx234yz" +} diff --git a/src/workos-connect/fixtures/redirect-uri.fixture.json b/src/workos-connect/fixtures/redirect-uri.fixture.json new file mode 100644 index 000000000..dcd6ad8d6 --- /dev/null +++ b/src/workos-connect/fixtures/redirect-uri.fixture.json @@ -0,0 +1,8 @@ +{ + "object": "redirect_uri", + "id": "ruri_01EHZNVPK3SFK441A1RGBFSHRT", + "uri": "https://example.com/callback", + "default": true, + "created_at": "2026-01-15T12:00:00.000Z", + "updated_at": "2026-01-15T12:00:00.000Z" +} diff --git a/src/workos-connect/fixtures/update-oauth-application.fixture.json b/src/workos-connect/fixtures/update-oauth-application.fixture.json new file mode 100644 index 000000000..160cacad0 --- /dev/null +++ b/src/workos-connect/fixtures/update-oauth-application.fixture.json @@ -0,0 +1,11 @@ +{ + "name": "My Application", + "description": "An application for managing user access", + "scopes": ["openid", "profile", "email"], + "redirect_uris": [ + { + "uri": "https://example.com/callback", + "default": true + } + ] +} diff --git a/src/workos-connect/fixtures/user-consent-option-choice.fixture.json b/src/workos-connect/fixtures/user-consent-option-choice.fixture.json new file mode 100644 index 000000000..c5f1f838c --- /dev/null +++ b/src/workos-connect/fixtures/user-consent-option-choice.fixture.json @@ -0,0 +1,4 @@ +{ + "value": "accepted", + "label": "I accept the Terms of Service" +} diff --git a/src/workos-connect/fixtures/user-consent-option.fixture.json b/src/workos-connect/fixtures/user-consent-option.fixture.json new file mode 100644 index 000000000..ba21c6190 --- /dev/null +++ b/src/workos-connect/fixtures/user-consent-option.fixture.json @@ -0,0 +1,11 @@ +{ + "claim": "tos_accepted", + "type": "enum", + "label": "Terms of Service", + "choices": [ + { + "value": "accepted", + "label": "I accept the Terms of Service" + } + ] +} diff --git a/src/workos-connect/fixtures/user-management-login-request.fixture.json b/src/workos-connect/fixtures/user-management-login-request.fixture.json new file mode 100644 index 000000000..952fbbf45 --- /dev/null +++ b/src/workos-connect/fixtures/user-management-login-request.fixture.json @@ -0,0 +1,26 @@ +{ + "external_auth_id": "ext_auth_01HXYZ123456789ABCDEFGHIJ", + "user": { + "id": "user_12345", + "email": "marcelina.davis@example.com", + "first_name": "Marcelina", + "last_name": "Davis", + "metadata": { + "department": "Engineering", + "role": "Developer" + } + }, + "user_consent_options": [ + { + "claim": "tos_accepted", + "type": "enum", + "label": "Terms of Service", + "choices": [ + { + "value": "accepted", + "label": "I accept the Terms of Service" + } + ] + } + ] +} diff --git a/src/workos-connect/fixtures/user-object.fixture.json b/src/workos-connect/fixtures/user-object.fixture.json new file mode 100644 index 000000000..27b814fd2 --- /dev/null +++ b/src/workos-connect/fixtures/user-object.fixture.json @@ -0,0 +1,10 @@ +{ + "id": "user_12345", + "email": "marcelina.davis@example.com", + "first_name": "Marcelina", + "last_name": "Davis", + "metadata": { + "department": "Engineering", + "role": "Developer" + } +} diff --git a/src/workos-connect/interfaces/application-credentials-list-item.interface.ts b/src/workos-connect/interfaces/application-credentials-list-item.interface.ts new file mode 100644 index 000000000..c06b29616 --- /dev/null +++ b/src/workos-connect/interfaces/application-credentials-list-item.interface.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface ApplicationCredentialsListItem { + /** Distinguishes the connect application secret object. */ + object: 'connect_application_secret'; + /** The unique ID of the client secret. */ + id: string; + /** A hint showing the last few characters of the secret value. */ + secretHint: string; + /** The timestamp when the client secret was last used, or null if never used. */ + lastUsedAt: string | null; + /** An ISO 8601 timestamp. */ + createdAt: string; + /** An ISO 8601 timestamp. */ + updatedAt: string; +} + +export interface ApplicationCredentialsListItemResponse { + object: 'connect_application_secret'; + id: string; + secret_hint: string; + last_used_at: string | null; + created_at: string; + updated_at: string; +} diff --git a/src/workos-connect/interfaces/connect-application.interface.ts b/src/workos-connect/interfaces/connect-application.interface.ts new file mode 100644 index 000000000..26551a22d --- /dev/null +++ b/src/workos-connect/interfaces/connect-application.interface.ts @@ -0,0 +1,31 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface ConnectApplication { + /** Distinguishes the connect application object. */ + object: 'connect_application'; + /** The unique ID of the connect application. */ + id: string; + /** The client ID of the connect application. */ + clientId: string; + /** A description of the connect application. */ + description: string | null; + /** The name of the connect application. */ + name: string; + /** The scopes available for this application. */ + scopes: string[]; + /** An ISO 8601 timestamp. */ + createdAt: string; + /** An ISO 8601 timestamp. */ + updatedAt: string; +} + +export interface ConnectApplicationResponse { + object: 'connect_application'; + id: string; + client_id: string; + description: string | null; + name: string; + scopes: string[]; + created_at: string; + updated_at: string; +} diff --git a/src/workos-connect/interfaces/create-application-secret.interface.ts b/src/workos-connect/interfaces/create-application-secret.interface.ts new file mode 100644 index 000000000..3265d757a --- /dev/null +++ b/src/workos-connect/interfaces/create-application-secret.interface.ts @@ -0,0 +1,5 @@ +// This file is auto-generated by oagen. Do not edit. + +export type CreateApplicationSecret = object; + +export type CreateApplicationSecretResponse = object; diff --git a/src/workos-connect/interfaces/create-m2m-application.interface.ts b/src/workos-connect/interfaces/create-m2m-application.interface.ts new file mode 100644 index 000000000..307232690 --- /dev/null +++ b/src/workos-connect/interfaces/create-m2m-application.interface.ts @@ -0,0 +1,22 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface CreateM2MApplication { + /** The name of the application. */ + name: string; + /** The type of application to create. */ + applicationType: 'm2m'; + /** A description for the application. */ + description?: string | null; + /** The OAuth scopes granted to the application. */ + scopes?: string[] | null; + /** The organization ID this application belongs to. */ + organizationId: string; +} + +export interface CreateM2MApplicationResponse { + name: string; + application_type: 'm2m'; + description?: string | null; + scopes?: string[] | null; + organization_id: string; +} diff --git a/src/workos-connect/interfaces/create-oauth-application.interface.ts b/src/workos-connect/interfaces/create-oauth-application.interface.ts new file mode 100644 index 000000000..b50d788ea --- /dev/null +++ b/src/workos-connect/interfaces/create-oauth-application.interface.ts @@ -0,0 +1,36 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + RedirectUri, + RedirectUriResponse, +} from './redirect-uri.interface'; + +export interface CreateOAuthApplication { + /** The name of the application. */ + name: string; + /** The type of application to create. */ + applicationType: 'oauth'; + /** A description for the application. */ + description?: string | null; + /** The OAuth scopes granted to the application. */ + scopes?: string[] | null; + /** Redirect URIs for the application. */ + redirectUris?: RedirectUri[] | null; + /** Whether the application uses PKCE (Proof Key for Code Exchange). */ + usesPkce?: boolean | null; + /** Whether this is a first-party application. Third-party applications require an organization_id. */ + isFirstParty: boolean; + /** The organization ID this application belongs to. Required when is_first_party is false. */ + organizationId?: string | null; +} + +export interface CreateOAuthApplicationResponse { + name: string; + application_type: 'oauth'; + description?: string | null; + scopes?: string[] | null; + redirect_uris?: RedirectUriResponse[] | null; + uses_pkce?: boolean | null; + is_first_party: boolean; + organization_id?: string | null; +} diff --git a/src/workos-connect/interfaces/external-auth-complete-response.interface.ts b/src/workos-connect/interfaces/external-auth-complete-response.interface.ts new file mode 100644 index 000000000..67a663e5d --- /dev/null +++ b/src/workos-connect/interfaces/external-auth-complete-response.interface.ts @@ -0,0 +1,10 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface ExternalAuthCompleteResponse { + /** URI to redirect the user back to AuthKit to complete the OAuth flow. */ + redirectUri: string; +} + +export interface ExternalAuthCompleteResponseWire { + redirect_uri: string; +} diff --git a/src/workos-connect/interfaces/index.ts b/src/workos-connect/interfaces/index.ts new file mode 100644 index 000000000..d70f57db7 --- /dev/null +++ b/src/workos-connect/interfaces/index.ts @@ -0,0 +1,15 @@ +// This file is auto-generated by oagen. Do not edit. + +export * from './application-credentials-list-item.interface'; +export * from './connect-application.interface'; +export * from './create-application-secret.interface'; +export * from './create-m2m-application.interface'; +export * from './create-oauth-application.interface'; +export * from './external-auth-complete-response.interface'; +export * from './new-connect-application-secret.interface'; +export * from './redirect-uri.interface'; +export * from './update-oauth-application.interface'; +export * from './user-consent-option-choice.interface'; +export * from './user-consent-option.interface'; +export * from './user-management-login-request.interface'; +export * from './user-object.interface'; diff --git a/src/workos-connect/interfaces/new-connect-application-secret.interface.ts b/src/workos-connect/interfaces/new-connect-application-secret.interface.ts new file mode 100644 index 000000000..3064fd13d --- /dev/null +++ b/src/workos-connect/interfaces/new-connect-application-secret.interface.ts @@ -0,0 +1,28 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface NewConnectApplicationSecret { + /** Distinguishes the connect application secret object. */ + object: 'connect_application_secret'; + /** The unique ID of the client secret. */ + id: string; + /** A hint showing the last few characters of the secret value. */ + secretHint: string; + /** The timestamp when the client secret was last used, or null if never used. */ + lastUsedAt: string | null; + /** An ISO 8601 timestamp. */ + createdAt: string; + /** An ISO 8601 timestamp. */ + updatedAt: string; + /** The plaintext secret value. Only returned at creation time and cannot be retrieved later. */ + secret: string; +} + +export interface NewConnectApplicationSecretResponse { + object: 'connect_application_secret'; + id: string; + secret_hint: string; + last_used_at: string | null; + created_at: string; + updated_at: string; + secret: string; +} diff --git a/src/workos-connect/interfaces/redirect-uri.interface.ts b/src/workos-connect/interfaces/redirect-uri.interface.ts new file mode 100644 index 000000000..d32ed8e25 --- /dev/null +++ b/src/workos-connect/interfaces/redirect-uri.interface.ts @@ -0,0 +1,25 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface RedirectUri { + /** The object type. */ + object: 'redirect_uri'; + /** The ID of the redirect URI. */ + id: string; + /** The redirect URI. */ + uri: string; + /** Whether this is the default redirect URI. */ + default: boolean; + /** The timestamp when the redirect URI was created. */ + createdAt: string; + /** The timestamp when the redirect URI was last updated. */ + updatedAt: string; +} + +export interface RedirectUriResponse { + object: 'redirect_uri'; + id: string; + uri: string; + default: boolean; + created_at: string; + updated_at: string; +} diff --git a/src/workos-connect/interfaces/update-oauth-application.interface.ts b/src/workos-connect/interfaces/update-oauth-application.interface.ts new file mode 100644 index 000000000..9fd5c8090 --- /dev/null +++ b/src/workos-connect/interfaces/update-oauth-application.interface.ts @@ -0,0 +1,24 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + RedirectUri, + RedirectUriResponse, +} from './redirect-uri.interface'; + +export interface UpdateOAuthApplication { + /** The name of the application. */ + name?: string; + /** A description for the application. */ + description?: string | null; + /** The OAuth scopes granted to the application. */ + scopes?: string[] | null; + /** Updated redirect URIs for the application. OAuth applications only. */ + redirectUris?: RedirectUri[] | null; +} + +export interface UpdateOAuthApplicationResponse { + name?: string; + description?: string | null; + scopes?: string[] | null; + redirect_uris?: RedirectUriResponse[] | null; +} diff --git a/src/workos-connect/interfaces/user-consent-option-choice.interface.ts b/src/workos-connect/interfaces/user-consent-option-choice.interface.ts new file mode 100644 index 000000000..0e5ef7d54 --- /dev/null +++ b/src/workos-connect/interfaces/user-consent-option-choice.interface.ts @@ -0,0 +1,13 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface UserConsentOptionChoice { + /** The value of this choice. */ + value?: string; + /** A human-readable label for this choice. */ + label?: string; +} + +export interface UserConsentOptionChoiceResponse { + value?: string; + label?: string; +} diff --git a/src/workos-connect/interfaces/user-consent-option.interface.ts b/src/workos-connect/interfaces/user-consent-option.interface.ts new file mode 100644 index 000000000..1a9690b17 --- /dev/null +++ b/src/workos-connect/interfaces/user-consent-option.interface.ts @@ -0,0 +1,24 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + UserConsentOptionChoice, + UserConsentOptionChoiceResponse, +} from './user-consent-option-choice.interface'; + +export interface UserConsentOption { + /** The claim name for this consent option. */ + claim: string; + /** The type of consent option. */ + type: 'enum'; + /** A human-readable label for this consent option. */ + label: string; + /** The available choices for this consent option. */ + choices: UserConsentOptionChoice[]; +} + +export interface UserConsentOptionResponse { + claim: string; + type: 'enum'; + label: string; + choices: UserConsentOptionChoiceResponse[]; +} diff --git a/src/workos-connect/interfaces/user-management-login-request.interface.ts b/src/workos-connect/interfaces/user-management-login-request.interface.ts new file mode 100644 index 000000000..8f37d4c9d --- /dev/null +++ b/src/workos-connect/interfaces/user-management-login-request.interface.ts @@ -0,0 +1,22 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { UserObject, UserObjectResponse } from './user-object.interface'; +import type { + UserConsentOption, + UserConsentOptionResponse, +} from './user-consent-option.interface'; + +export interface UserManagementLoginRequest { + /** Identifier provided when AuthKit redirected to your login page. */ + externalAuthId: string; + /** The user to create or update in AuthKit. */ + user: UserObject; + /** Array of [User Consent Options](https://workos.com/docs/reference/workos-connect/standalone/user-consent-options) to store with the session. */ + userConsentOptions?: UserConsentOption[]; +} + +export interface UserManagementLoginRequestResponse { + external_auth_id: string; + user: UserObjectResponse; + user_consent_options?: UserConsentOptionResponse[]; +} diff --git a/src/workos-connect/interfaces/user-object.interface.ts b/src/workos-connect/interfaces/user-object.interface.ts new file mode 100644 index 000000000..c28a23e2d --- /dev/null +++ b/src/workos-connect/interfaces/user-object.interface.ts @@ -0,0 +1,22 @@ +// This file is auto-generated by oagen. Do not edit. + +export interface UserObject { + /** Your application's user identifier, which will be stored as an [`external_id`](https://workos.com/docs/authkit/metadata/external-identifiers). Used for upserting and deduplication. */ + id: string; + /** The user's email address. */ + email: string; + /** The user's first name. */ + firstName?: string; + /** The user's last name. */ + lastName?: string; + /** A set of key-value pairs to attach to the user. */ + metadata?: Record; +} + +export interface UserObjectResponse { + id: string; + email: string; + first_name?: string; + last_name?: string; + metadata?: Record; +} diff --git a/src/workos-connect/serializers/application-credentials-list-item.serializer.ts b/src/workos-connect/serializers/application-credentials-list-item.serializer.ts new file mode 100644 index 000000000..0c6dd084f --- /dev/null +++ b/src/workos-connect/serializers/application-credentials-list-item.serializer.ts @@ -0,0 +1,28 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + ApplicationCredentialsListItem, + ApplicationCredentialsListItemResponse, +} from '../interfaces/application-credentials-list-item.interface'; + +export const deserializeApplicationCredentialsListItem = ( + response: ApplicationCredentialsListItemResponse, +): ApplicationCredentialsListItem => ({ + object: response.object, + id: response.id, + secretHint: response.secret_hint, + lastUsedAt: response.last_used_at ?? null, + createdAt: response.created_at, + updatedAt: response.updated_at, +}); + +export const serializeApplicationCredentialsListItem = ( + model: ApplicationCredentialsListItem, +): ApplicationCredentialsListItemResponse => ({ + object: model.object, + id: model.id, + secret_hint: model.secretHint, + last_used_at: model.lastUsedAt, + created_at: model.createdAt, + updated_at: model.updatedAt, +}); diff --git a/src/workos-connect/serializers/connect-application.serializer.ts b/src/workos-connect/serializers/connect-application.serializer.ts new file mode 100644 index 000000000..0319d485c --- /dev/null +++ b/src/workos-connect/serializers/connect-application.serializer.ts @@ -0,0 +1,32 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + ConnectApplication, + ConnectApplicationResponse, +} from '../interfaces/connect-application.interface'; + +export const deserializeConnectApplication = ( + response: ConnectApplicationResponse, +): ConnectApplication => ({ + object: response.object, + id: response.id, + clientId: response.client_id, + description: response.description ?? null, + name: response.name, + scopes: response.scopes, + createdAt: response.created_at, + updatedAt: response.updated_at, +}); + +export const serializeConnectApplication = ( + model: ConnectApplication, +): ConnectApplicationResponse => ({ + object: model.object, + id: model.id, + client_id: model.clientId, + description: model.description, + name: model.name, + scopes: model.scopes, + created_at: model.createdAt, + updated_at: model.updatedAt, +}); diff --git a/src/workos-connect/serializers/create-application-secret.serializer.ts b/src/workos-connect/serializers/create-application-secret.serializer.ts new file mode 100644 index 000000000..d55dad86e --- /dev/null +++ b/src/workos-connect/serializers/create-application-secret.serializer.ts @@ -0,0 +1,14 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + CreateApplicationSecret, + CreateApplicationSecretResponse, +} from '../interfaces/create-application-secret.interface'; + +export const deserializeCreateApplicationSecret = ( + _response: CreateApplicationSecretResponse, +): CreateApplicationSecret => ({}); + +export const serializeCreateApplicationSecret = ( + _model: CreateApplicationSecret, +): CreateApplicationSecretResponse => ({}); diff --git a/src/workos-connect/serializers/create-m2m-application.serializer.ts b/src/workos-connect/serializers/create-m2m-application.serializer.ts new file mode 100644 index 000000000..6b219da6d --- /dev/null +++ b/src/workos-connect/serializers/create-m2m-application.serializer.ts @@ -0,0 +1,26 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + CreateM2MApplication, + CreateM2MApplicationResponse, +} from '../interfaces/create-m2m-application.interface'; + +export const deserializeCreateM2MApplication = ( + response: CreateM2MApplicationResponse, +): CreateM2MApplication => ({ + name: response.name, + applicationType: response.application_type, + description: response.description ?? null, + scopes: response.scopes ?? null, + organizationId: response.organization_id, +}); + +export const serializeCreateM2MApplication = ( + model: CreateM2MApplication, +): CreateM2MApplicationResponse => ({ + name: model.name, + application_type: model.applicationType, + description: model.description ?? null, + scopes: model.scopes ?? null, + organization_id: model.organizationId, +}); diff --git a/src/workos-connect/serializers/create-oauth-application.serializer.ts b/src/workos-connect/serializers/create-oauth-application.serializer.ts new file mode 100644 index 000000000..4896feb6a --- /dev/null +++ b/src/workos-connect/serializers/create-oauth-application.serializer.ts @@ -0,0 +1,42 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + CreateOAuthApplication, + CreateOAuthApplicationResponse, +} from '../interfaces/create-oauth-application.interface'; +import { + deserializeRedirectUri, + serializeRedirectUri, +} from './redirect-uri.serializer'; + +export const deserializeCreateOAuthApplication = ( + response: CreateOAuthApplicationResponse, +): CreateOAuthApplication => ({ + name: response.name, + applicationType: response.application_type, + description: response.description ?? null, + scopes: response.scopes ?? null, + redirectUris: + response.redirect_uris != null + ? response.redirect_uris.map(deserializeRedirectUri) + : null, + usesPkce: response.uses_pkce ?? null, + isFirstParty: response.is_first_party, + organizationId: response.organization_id ?? null, +}); + +export const serializeCreateOAuthApplication = ( + model: CreateOAuthApplication, +): CreateOAuthApplicationResponse => ({ + name: model.name, + application_type: model.applicationType, + description: model.description ?? null, + scopes: model.scopes ?? null, + redirect_uris: + model.redirectUris != null + ? model.redirectUris.map(serializeRedirectUri) + : null, + uses_pkce: model.usesPkce ?? null, + is_first_party: model.isFirstParty, + organization_id: model.organizationId ?? null, +}); diff --git a/src/workos-connect/serializers/external-auth-complete-response.serializer.ts b/src/workos-connect/serializers/external-auth-complete-response.serializer.ts new file mode 100644 index 000000000..c74e57d5c --- /dev/null +++ b/src/workos-connect/serializers/external-auth-complete-response.serializer.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + ExternalAuthCompleteResponse, + ExternalAuthCompleteResponseWire, +} from '../interfaces/external-auth-complete-response.interface'; + +export const deserializeExternalAuthCompleteResponse = ( + response: ExternalAuthCompleteResponseWire, +): ExternalAuthCompleteResponse => ({ + redirectUri: response.redirect_uri, +}); + +export const serializeExternalAuthCompleteResponse = ( + model: ExternalAuthCompleteResponse, +): ExternalAuthCompleteResponseWire => ({ + redirect_uri: model.redirectUri, +}); diff --git a/src/workos-connect/serializers/new-connect-application-secret.serializer.ts b/src/workos-connect/serializers/new-connect-application-secret.serializer.ts new file mode 100644 index 000000000..c1b6fbb36 --- /dev/null +++ b/src/workos-connect/serializers/new-connect-application-secret.serializer.ts @@ -0,0 +1,30 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + NewConnectApplicationSecret, + NewConnectApplicationSecretResponse, +} from '../interfaces/new-connect-application-secret.interface'; + +export const deserializeNewConnectApplicationSecret = ( + response: NewConnectApplicationSecretResponse, +): NewConnectApplicationSecret => ({ + object: response.object, + id: response.id, + secretHint: response.secret_hint, + lastUsedAt: response.last_used_at ?? null, + createdAt: response.created_at, + updatedAt: response.updated_at, + secret: response.secret, +}); + +export const serializeNewConnectApplicationSecret = ( + model: NewConnectApplicationSecret, +): NewConnectApplicationSecretResponse => ({ + object: model.object, + id: model.id, + secret_hint: model.secretHint, + last_used_at: model.lastUsedAt, + created_at: model.createdAt, + updated_at: model.updatedAt, + secret: model.secret, +}); diff --git a/src/workos-connect/serializers/redirect-uri.serializer.ts b/src/workos-connect/serializers/redirect-uri.serializer.ts new file mode 100644 index 000000000..99fa1b53b --- /dev/null +++ b/src/workos-connect/serializers/redirect-uri.serializer.ts @@ -0,0 +1,28 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + RedirectUri, + RedirectUriResponse, +} from '../interfaces/redirect-uri.interface'; + +export const deserializeRedirectUri = ( + response: RedirectUriResponse, +): RedirectUri => ({ + object: response.object, + id: response.id, + uri: response.uri, + default: response.default, + createdAt: response.created_at, + updatedAt: response.updated_at, +}); + +export const serializeRedirectUri = ( + model: RedirectUri, +): RedirectUriResponse => ({ + object: model.object, + id: model.id, + uri: model.uri, + default: model.default, + created_at: model.createdAt, + updated_at: model.updatedAt, +}); diff --git a/src/workos-connect/serializers/update-oauth-application.serializer.ts b/src/workos-connect/serializers/update-oauth-application.serializer.ts new file mode 100644 index 000000000..9fa4e0cd2 --- /dev/null +++ b/src/workos-connect/serializers/update-oauth-application.serializer.ts @@ -0,0 +1,34 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + UpdateOAuthApplication, + UpdateOAuthApplicationResponse, +} from '../interfaces/update-oauth-application.interface'; +import { + deserializeRedirectUri, + serializeRedirectUri, +} from './redirect-uri.serializer'; + +export const deserializeUpdateOAuthApplication = ( + response: UpdateOAuthApplicationResponse, +): UpdateOAuthApplication => ({ + name: response.name, + description: response.description ?? null, + scopes: response.scopes ?? null, + redirectUris: + response.redirect_uris != null + ? response.redirect_uris.map(deserializeRedirectUri) + : null, +}); + +export const serializeUpdateOAuthApplication = ( + model: UpdateOAuthApplication, +): UpdateOAuthApplicationResponse => ({ + name: model.name, + description: model.description ?? null, + scopes: model.scopes ?? null, + redirect_uris: + model.redirectUris != null + ? model.redirectUris.map(serializeRedirectUri) + : null, +}); diff --git a/src/workos-connect/serializers/user-consent-option-choice.serializer.ts b/src/workos-connect/serializers/user-consent-option-choice.serializer.ts new file mode 100644 index 000000000..9933a0a18 --- /dev/null +++ b/src/workos-connect/serializers/user-consent-option-choice.serializer.ts @@ -0,0 +1,20 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + UserConsentOptionChoice, + UserConsentOptionChoiceResponse, +} from '../interfaces/user-consent-option-choice.interface'; + +export const deserializeUserConsentOptionChoice = ( + response: UserConsentOptionChoiceResponse, +): UserConsentOptionChoice => ({ + value: response.value, + label: response.label, +}); + +export const serializeUserConsentOptionChoice = ( + model: UserConsentOptionChoice, +): UserConsentOptionChoiceResponse => ({ + value: model.value, + label: model.label, +}); diff --git a/src/workos-connect/serializers/user-consent-option.serializer.ts b/src/workos-connect/serializers/user-consent-option.serializer.ts new file mode 100644 index 000000000..61c03031e --- /dev/null +++ b/src/workos-connect/serializers/user-consent-option.serializer.ts @@ -0,0 +1,28 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + UserConsentOption, + UserConsentOptionResponse, +} from '../interfaces/user-consent-option.interface'; +import { + deserializeUserConsentOptionChoice, + serializeUserConsentOptionChoice, +} from './user-consent-option-choice.serializer'; + +export const deserializeUserConsentOption = ( + response: UserConsentOptionResponse, +): UserConsentOption => ({ + claim: response.claim, + type: response.type, + label: response.label, + choices: response.choices.map(deserializeUserConsentOptionChoice), +}); + +export const serializeUserConsentOption = ( + model: UserConsentOption, +): UserConsentOptionResponse => ({ + claim: model.claim, + type: model.type, + label: model.label, + choices: model.choices.map(serializeUserConsentOptionChoice), +}); diff --git a/src/workos-connect/serializers/user-management-login-request.serializer.ts b/src/workos-connect/serializers/user-management-login-request.serializer.ts new file mode 100644 index 000000000..81c5b7864 --- /dev/null +++ b/src/workos-connect/serializers/user-management-login-request.serializer.ts @@ -0,0 +1,36 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + UserManagementLoginRequest, + UserManagementLoginRequestResponse, +} from '../interfaces/user-management-login-request.interface'; +import { + deserializeUserObject, + serializeUserObject, +} from './user-object.serializer'; +import { + deserializeUserConsentOption, + serializeUserConsentOption, +} from './user-consent-option.serializer'; + +export const deserializeUserManagementLoginRequest = ( + response: UserManagementLoginRequestResponse, +): UserManagementLoginRequest => ({ + externalAuthId: response.external_auth_id, + user: deserializeUserObject(response.user), + userConsentOptions: + response.user_consent_options != null + ? response.user_consent_options.map(deserializeUserConsentOption) + : undefined, +}); + +export const serializeUserManagementLoginRequest = ( + model: UserManagementLoginRequest, +): UserManagementLoginRequestResponse => ({ + external_auth_id: model.externalAuthId, + user: serializeUserObject(model.user), + user_consent_options: + model.userConsentOptions != null + ? model.userConsentOptions.map(serializeUserConsentOption) + : undefined, +}); diff --git a/src/workos-connect/serializers/user-object.serializer.ts b/src/workos-connect/serializers/user-object.serializer.ts new file mode 100644 index 000000000..94dbd1409 --- /dev/null +++ b/src/workos-connect/serializers/user-object.serializer.ts @@ -0,0 +1,24 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { + UserObject, + UserObjectResponse, +} from '../interfaces/user-object.interface'; + +export const deserializeUserObject = ( + response: UserObjectResponse, +): UserObject => ({ + id: response.id, + email: response.email, + firstName: response.first_name, + lastName: response.last_name, + metadata: response.metadata, +}); + +export const serializeUserObject = (model: UserObject): UserObjectResponse => ({ + id: model.id, + email: model.email, + first_name: model.firstName, + last_name: model.lastName, + metadata: model.metadata, +}); diff --git a/src/workos-connect/work-os-connect.spec.ts b/src/workos-connect/work-os-connect.spec.ts new file mode 100644 index 000000000..0bcd863f2 --- /dev/null +++ b/src/workos-connect/work-os-connect.spec.ts @@ -0,0 +1,51 @@ +// This file is auto-generated by oagen. Do not edit. + +import fetch from 'jest-fetch-mock'; +import { + fetchOnce, + fetchURL, + fetchMethod, + fetchBody, + testUnauthorized, +} from '../common/utils/test-utils'; +import { WorkOS } from '../workos'; + +import externalAuthCompleteResponseFixture from './fixtures/external-auth-complete-response.fixture.json'; + +const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); + +describe('WorkOSConnect', () => { + beforeEach(() => fetch.resetMocks()); + + describe('completeLogin', () => { + it('sends the correct request and returns result', async () => { + fetchOnce(externalAuthCompleteResponseFixture); + + const result = await workos.workOsConnect.completeLogin({ + externalAuthId: 'external_auth_id_01234', + user: { id: '01234', email: 'test@example.com' }, + }); + + expect(fetchMethod()).toBe('POST'); + expect(new URL(String(fetchURL())).pathname).toBe( + '/authkit/oauth2/complete', + ); + expect(fetchBody()).toEqual( + expect.objectContaining({ + external_auth_id: 'external_auth_id_01234', + user: { id: '01234', email: 'test@example.com' }, + }), + ); + expect(result.redirectUri).toBe( + 'https://your-authkit-domain.workos.com/oauth/authorize/complete?state=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdGF0ZSI6InJhbmRvbV9zdGF0ZV9zdHJpbmciLCJpYXQiOjE3NDI2MDQ4NTN9.abc123def456ghi789', + ); + }); + + testUnauthorized(() => + workos.workOsConnect.completeLogin({ + externalAuthId: 'external_auth_id_01234', + user: { id: '01234', email: 'test@example.com' }, + }), + ); + }); +}); diff --git a/src/workos-connect/work-os-connect.ts b/src/workos-connect/work-os-connect.ts new file mode 100644 index 000000000..751f524e3 --- /dev/null +++ b/src/workos-connect/work-os-connect.ts @@ -0,0 +1,43 @@ +// This file is auto-generated by oagen. Do not edit. + +import type { WorkOS } from '../workos'; +import type { + ExternalAuthCompleteResponse, + ExternalAuthCompleteResponseWire, +} from './interfaces/external-auth-complete-response.interface'; +import type { UserManagementLoginRequest } from './interfaces/user-management-login-request.interface'; +import { deserializeExternalAuthCompleteResponse } from './serializers/external-auth-complete-response.serializer'; +import { serializeUserManagementLoginRequest } from './serializers/user-management-login-request.serializer'; + +export class WorkOSConnect { + constructor(private readonly workos: WorkOS) {} + + /** + * Complete external authentication + * + * Completes an external authentication flow and returns control to AuthKit. This endpoint is used with [Standalone Connect](https://workos.com/docs/authkit/connect/standalone) to bridge your existing authentication system with the Connect OAuth API infrastructure. + * + * After successfully authenticating a user in your application, calling this endpoint will: + * + * - Create or update the user in AuthKit, using the given `id` as its `external_id`. + * - Return a `redirect_uri` your application should redirect to in order for AuthKit to complete the flow + * + * Users are automatically created or updated based on the `id` and `email` provided. If a user with the same `id` exists, their information is updated. Otherwise, a new user is created. + * + * If you provide a new `id` with an `email` that already belongs to an existing user, the request will fail with an error as email addresses are unique to a user. + * @param payload - Object containing externalAuthId, user. + * @returns {ExternalAuthCompleteResponse} + * @throws {BadRequestException} 400 + * @throws {NotFoundException} 404 + * @throws {UnprocessableEntityException} 422 + */ + async completeLogin( + payload: UserManagementLoginRequest, + ): Promise { + const { data } = await this.workos.post( + '/authkit/oauth2/complete', + serializeUserManagementLoginRequest(payload), + ); + return deserializeExternalAuthCompleteResponse(data); + } +} diff --git a/src/workos.ts b/src/workos.ts index 4d5b45156..d24d82b78 100644 --- a/src/workos.ts +++ b/src/workos.ts @@ -1,3 +1,5 @@ +// This file is auto-generated by oagen. Do not edit. + import { ApiKeyRequiredException, GenericServerException, @@ -47,6 +49,13 @@ import { ParseError } from './common/exceptions/parse-error'; import { getEnv } from './common/utils/env'; import { getRuntimeInfo } from './common/utils/runtime-info'; import { version as VERSION } from '../package.json' with { type: 'json' }; +import { AdminPortal } from './admin-portal/admin-portal'; +import { Permissions } from './permissions/permissions'; +import { Radar } from './radar/radar'; +import { WebhooksEndpoints } from './webhooks/webhooks-endpoints'; +import { WorkOSConnect } from './workos-connect/work-os-connect'; +import { Applications } from './workos-connect/applications'; +import { ApplicationClientSecrets } from './workos-connect/application-client-secrets'; const DEFAULT_HOSTNAME = 'api.workos.com'; @@ -54,6 +63,7 @@ const HEADER_AUTHORIZATION = 'Authorization'; const HEADER_IDEMPOTENCY_KEY = 'Idempotency-Key'; const HEADER_WARRANT_TOKEN = 'Warrant-Token'; +/** WorkOS REST API */ export class WorkOS { readonly baseURL: string; readonly client: HttpClient; @@ -508,4 +518,16 @@ export class WorkOS { } } } + + readonly adminPortal = new AdminPortal(this); + readonly permissions = new Permissions(this); + readonly radar = new Radar(this); + readonly webhooksEndpoints = new WebhooksEndpoints(this); + readonly workOsConnect = new WorkOSConnect(this); + readonly applications = new Applications(this); + readonly applicationClientSecrets = new ApplicationClientSecrets(this); + /** Production */ + static readonly SERVER_PRODUCTION = 'https://api.workos.com'; + /** Staging */ + static readonly SERVER_STAGING = 'https://api.workos-test.com'; }