From 26089b2e975da3117dc16ee0df0a36892d4639db Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Fri, 11 Apr 2025 16:29:45 -0400 Subject: [PATCH] feat(backend): Add Instance to Backend API client --- .changeset/heavy-foxes-check.md | 17 +++ .../backend/src/api/endpoints/InstanceApi.ts | 101 ++++++++++++++++++ packages/backend/src/api/endpoints/index.ts | 1 + packages/backend/src/api/factory.ts | 2 + .../backend/src/api/resources/Deserializer.ts | 12 +++ packages/backend/src/api/resources/Enums.ts | 7 ++ .../backend/src/api/resources/Instance.ts | 13 +++ .../src/api/resources/InstanceRestrictions.ts | 21 ++++ .../src/api/resources/InstanceSettings.ts | 21 ++++ packages/backend/src/api/resources/JSON.ts | 43 ++++++++ .../src/api/resources/OrganizationSettings.ts | 30 ++++++ packages/backend/src/api/resources/index.ts | 4 + packages/backend/src/index.ts | 8 ++ 13 files changed, 280 insertions(+) create mode 100644 .changeset/heavy-foxes-check.md create mode 100644 packages/backend/src/api/endpoints/InstanceApi.ts create mode 100644 packages/backend/src/api/resources/Instance.ts create mode 100644 packages/backend/src/api/resources/InstanceRestrictions.ts create mode 100644 packages/backend/src/api/resources/InstanceSettings.ts create mode 100644 packages/backend/src/api/resources/OrganizationSettings.ts diff --git a/.changeset/heavy-foxes-check.md b/.changeset/heavy-foxes-check.md new file mode 100644 index 00000000000..93e8f2bef5f --- /dev/null +++ b/.changeset/heavy-foxes-check.md @@ -0,0 +1,17 @@ +--- +'@clerk/backend': minor +--- + +Adds the following functionality for Instances to the Backend API client. + + +```ts + import { createClerkClient } from '@clerk/backend'; + + const clerkClient = createClerkClient(...); + + await clerkClient.instance.get(); + await clerkClient.instance.update({...}); + await clerkClient.instance.updateRestrictions({...}); + await clerkClient.instance.updateOrganizationSettings({...}); +``` \ No newline at end of file diff --git a/packages/backend/src/api/endpoints/InstanceApi.ts b/packages/backend/src/api/endpoints/InstanceApi.ts new file mode 100644 index 00000000000..cc65a288631 --- /dev/null +++ b/packages/backend/src/api/endpoints/InstanceApi.ts @@ -0,0 +1,101 @@ +import { joinPaths } from '../../util/path'; +import type { Instance } from '../resources/Instance'; +import type { InstanceRestrictions } from '../resources/InstanceRestrictions'; +import type { OrganizationSettings } from '../resources/OrganizationSettings'; +import { AbstractAPI } from './AbstractApi'; + +const basePath = '/instance'; + +type UpdateParams = { + /** + * Toggles test mode for this instance, allowing the use of test email addresses and phone numbers. + * + * @remarks Defaults to true for development instances. + */ + testMode?: boolean | null | undefined; + /** + * Whether the instance should be using the HIBP service to check passwords for breaches + */ + hibp?: boolean | null | undefined; + /** + * The "enhanced_email_deliverability" feature will send emails from "verifications@clerk.dev" instead of your domain. + * + * @remarks This can be helpful if you do not have a high domain reputation. + */ + enhancedEmailDeliverability?: boolean | null | undefined; + supportEmail?: string | null | undefined; + clerkJsVersion?: string | null | undefined; + developmentOrigin?: string | null | undefined; + /** + * For browser-like stacks such as browser extensions, Electron, or Capacitor.js the instance allowed origins need to be updated with the request origin value. + * + * @remarks For Chrome extensions popup, background, or service worker pages the origin is chrome-extension://extension_uiid. For Electron apps the default origin is http://localhost:3000. For Capacitor, the origin is capacitor://localhost. + */ + allowedOrigins?: Array | undefined; + /** + * Whether the instance should use URL-based session syncing in development mode (i.e. without third-party cookies). + */ + urlBasedSessionSyncing?: boolean | null | undefined; +}; + +type UpdateRestrictionsParams = { + allowlist?: boolean | null | undefined; + blocklist?: boolean | null | undefined; + blockEmailSubaddresses?: boolean | null | undefined; + blockDisposableEmailDomains?: boolean | null | undefined; + ignoreDotsForGmailAddresses?: boolean | null | undefined; +}; + +type UpdateOrganizationSettingsParams = { + enabled?: boolean | null | undefined; + maxAllowedMemberships?: number | null | undefined; + adminDeleteEnabled?: boolean | null | undefined; + domainsEnabled?: boolean | null | undefined; + /** + * Specify which enrollment modes to enable for your Organization Domains. + * + * @remarks Supported modes are 'automatic_invitation' & 'automatic_suggestion'. + */ + domainsEnrollmentModes?: Array | undefined; + /** + * Specify what the default organization role is for an organization creator. + */ + creatorRoleId?: string | null | undefined; + /** + * Specify what the default organization role is for the organization domains. + */ + domainsDefaultRoleId?: string | null | undefined; +}; + +export class InstanceAPI extends AbstractAPI { + public async get() { + return this.request({ + method: 'GET', + path: basePath, + }); + } + + public async update(params: UpdateParams) { + return this.request({ + method: 'PATCH', + path: basePath, + bodyParams: params, + }); + } + + public async updateRestrictions(params: UpdateRestrictionsParams) { + return this.request({ + method: 'PATCH', + path: joinPaths(basePath, 'restrictions'), + bodyParams: params, + }); + } + + public async updateOrganizationSettings(params: UpdateOrganizationSettingsParams) { + return this.request({ + method: 'PATCH', + path: joinPaths(basePath, 'organization_settings'), + bodyParams: params, + }); + } +} diff --git a/packages/backend/src/api/endpoints/index.ts b/packages/backend/src/api/endpoints/index.ts index 9c9fa8b1745..25af6762c04 100644 --- a/packages/backend/src/api/endpoints/index.ts +++ b/packages/backend/src/api/endpoints/index.ts @@ -4,6 +4,7 @@ export * from './AllowlistIdentifierApi'; export * from './ClientApi'; export * from './DomainApi'; export * from './EmailAddressApi'; +export * from './InstanceApi'; export * from './InvitationApi'; export * from './JwksApi'; export * from './OrganizationApi'; diff --git a/packages/backend/src/api/factory.ts b/packages/backend/src/api/factory.ts index 75f443e6a14..7e2480699fb 100644 --- a/packages/backend/src/api/factory.ts +++ b/packages/backend/src/api/factory.ts @@ -4,6 +4,7 @@ import { ClientAPI, DomainAPI, EmailAddressAPI, + InstanceAPI, InvitationAPI, JwksAPI, OrganizationAPI, @@ -32,6 +33,7 @@ export function createBackendApiClient(options: CreateBackendApiOptions) { allowlistIdentifiers: new AllowlistIdentifierAPI(request), clients: new ClientAPI(request), emailAddresses: new EmailAddressAPI(request), + instance: new InstanceAPI(request), invitations: new InvitationAPI(request), jwks: new JwksAPI(request), organizations: new OrganizationAPI(request), diff --git a/packages/backend/src/api/resources/Deserializer.ts b/packages/backend/src/api/resources/Deserializer.ts index 8ed7fc730e1..0aafd01c2c0 100644 --- a/packages/backend/src/api/resources/Deserializer.ts +++ b/packages/backend/src/api/resources/Deserializer.ts @@ -5,11 +5,15 @@ import { DeletedObject, Email, EmailAddress, + Instance, + InstanceRestrictions, + InstanceSettings, Invitation, OauthAccessToken, Organization, OrganizationInvitation, OrganizationMembership, + OrganizationSettings, PhoneNumber, ProxyCheck, RedirectUrl, @@ -80,6 +84,12 @@ function jsonToObject(item: any): any { return EmailAddress.fromJSON(item); case ObjectType.Email: return Email.fromJSON(item); + case ObjectType.Instance: + return Instance.fromJSON(item); + case ObjectType.InstanceRestrictions: + return InstanceRestrictions.fromJSON(item); + case ObjectType.InstanceSettings: + return InstanceSettings.fromJSON(item); case ObjectType.Invitation: return Invitation.fromJSON(item); case ObjectType.OauthAccessToken: @@ -90,6 +100,8 @@ function jsonToObject(item: any): any { return OrganizationInvitation.fromJSON(item); case ObjectType.OrganizationMembership: return OrganizationMembership.fromJSON(item); + case ObjectType.OrganizationSettings: + return OrganizationSettings.fromJSON(item); case ObjectType.PhoneNumber: return PhoneNumber.fromJSON(item); case ObjectType.ProxyCheck: diff --git a/packages/backend/src/api/resources/Enums.ts b/packages/backend/src/api/resources/Enums.ts index c0e3c6d3de2..81f9c481c20 100644 --- a/packages/backend/src/api/resources/Enums.ts +++ b/packages/backend/src/api/resources/Enums.ts @@ -36,3 +36,10 @@ export type SignInStatus = 'needs_identifier' | 'needs_factor_one' | 'needs_fact export type SignUpStatus = 'missing_requirements' | 'complete' | 'abandoned'; export type InvitationStatus = 'pending' | 'accepted' | 'revoked' | 'expired'; + +export const DomainsEnrollmentModes = { + ManualInvitation: 'manual_invitation', + AutomaticInvitation: 'automatic_invitation', + AutomaticSuggestion: 'automatic_suggestion', +} as const; +export type DomainsEnrollmentModes = (typeof DomainsEnrollmentModes)[keyof typeof DomainsEnrollmentModes]; diff --git a/packages/backend/src/api/resources/Instance.ts b/packages/backend/src/api/resources/Instance.ts new file mode 100644 index 00000000000..d90cec3d7cb --- /dev/null +++ b/packages/backend/src/api/resources/Instance.ts @@ -0,0 +1,13 @@ +import type { InstanceJSON } from './JSON'; + +export class Instance { + constructor( + readonly id: string, + readonly environmentType: string, + readonly allowedOrigins: Array | null, + ) {} + + static fromJSON(data: InstanceJSON): Instance { + return new Instance(data.id, data.environment_type, data.allowed_origins); + } +} diff --git a/packages/backend/src/api/resources/InstanceRestrictions.ts b/packages/backend/src/api/resources/InstanceRestrictions.ts new file mode 100644 index 00000000000..b9aa234d35e --- /dev/null +++ b/packages/backend/src/api/resources/InstanceRestrictions.ts @@ -0,0 +1,21 @@ +import type { InstanceRestrictionsJSON } from './JSON'; + +export class InstanceRestrictions { + constructor( + readonly allowlist: boolean, + readonly blocklist: boolean, + readonly blockEmailSubaddresses: boolean, + readonly blockDisposableEmailDomains: boolean, + readonly ignoreDotsForGmailAddresses: boolean, + ) {} + + static fromJSON(data: InstanceRestrictionsJSON): InstanceRestrictions { + return new InstanceRestrictions( + data.allowlist, + data.blocklist, + data.block_email_subaddresses, + data.block_disposable_email_domains, + data.ignore_dots_for_gmail_addresses, + ); + } +} diff --git a/packages/backend/src/api/resources/InstanceSettings.ts b/packages/backend/src/api/resources/InstanceSettings.ts new file mode 100644 index 00000000000..47cc03eecad --- /dev/null +++ b/packages/backend/src/api/resources/InstanceSettings.ts @@ -0,0 +1,21 @@ +import type { InstanceSettingsJSON } from './JSON'; + +export class InstanceSettings { + constructor( + readonly id?: string | undefined, + readonly restrictedToAllowlist?: boolean | undefined, + readonly fromEmailAddress?: string | undefined, + readonly progressiveSignUp?: boolean | undefined, + readonly enhancedEmailDeliverability?: boolean | undefined, + ) {} + + static fromJSON(data: InstanceSettingsJSON): InstanceSettings { + return new InstanceSettings( + data.id, + data.restricted_to_allowlist, + data.from_email_address, + data.progressive_sign_up, + data.enhanced_email_deliverability, + ); + } +} diff --git a/packages/backend/src/api/resources/JSON.ts b/packages/backend/src/api/resources/JSON.ts index b06ae457751..a2da4532b56 100644 --- a/packages/backend/src/api/resources/JSON.ts +++ b/packages/backend/src/api/resources/JSON.ts @@ -1,4 +1,5 @@ import type { + DomainsEnrollmentModes, InvitationStatus, OrganizationDomainVerificationStatus, OrganizationDomainVerificationStrategy, @@ -19,12 +20,16 @@ export const ObjectType = { ExternalAccount: 'external_account', FacebookAccount: 'facebook_account', GoogleAccount: 'google_account', + Instance: 'instance', + InstanceRestrictions: 'instance_restrictions', + InstanceSettings: 'instance_settings', Invitation: 'invitation', OauthAccessToken: 'oauth_access_token', Organization: 'organization', OrganizationDomain: 'organization_domain', OrganizationInvitation: 'organization_invitation', OrganizationMembership: 'organization_membership', + OrganizationSettings: 'organization_settings', PhoneNumber: 'phone_number', ProxyCheck: 'proxy_check', RedirectUrl: 'redirect_url', @@ -156,6 +161,44 @@ export interface IdentificationLinkJSON extends ClerkResourceJSON { type: string; } +export interface OrganizationSettingsJSON extends ClerkResourceJSON { + object: typeof ObjectType.OrganizationSettings; + enabled: boolean; + max_allowed_memberships: number; + max_allowed_roles: number; + max_allowed_permissions: number; + creator_role: string; + admin_delete_enabled: boolean; + domains_enabled: boolean; + domains_enrollment_modes: Array; + domains_default_role: string; +} + +export interface InstanceJSON extends ClerkResourceJSON { + object: typeof ObjectType.Instance; + id: string; + environment_type: string; + allowed_origins: Array | null; +} + +export interface InstanceRestrictionsJSON extends ClerkResourceJSON { + object: typeof ObjectType.InstanceRestrictions; + allowlist: boolean; + blocklist: boolean; + block_email_subaddresses: boolean; + block_disposable_email_domains: boolean; + ignore_dots_for_gmail_addresses: boolean; +} + +export interface InstanceSettingsJSON extends ClerkResourceJSON { + object: typeof ObjectType.InstanceSettings; + id: string; + restricted_to_allowlist: boolean; + from_email_address: string; + progressive_sign_up: boolean; + enhanced_email_deliverability: boolean; +} + export interface InvitationJSON extends ClerkResourceJSON { object: typeof ObjectType.Invitation; email_address: string; diff --git a/packages/backend/src/api/resources/OrganizationSettings.ts b/packages/backend/src/api/resources/OrganizationSettings.ts new file mode 100644 index 00000000000..bf28297ddfa --- /dev/null +++ b/packages/backend/src/api/resources/OrganizationSettings.ts @@ -0,0 +1,30 @@ +import type { DomainsEnrollmentModes } from './Enums'; +import type { OrganizationSettingsJSON } from './JSON'; + +export class OrganizationSettings { + constructor( + readonly enabled: boolean, + readonly maxAllowedMemberships: number, + readonly maxAllowedRoles: number, + readonly maxAllowedPermissions: number, + readonly creatorRole: string, + readonly adminDeleteEnabled: boolean, + readonly domainsEnabled: boolean, + readonly domainsEnrollmentModes: Array, + readonly domainsDefaultRole: string, + ) {} + + static fromJSON(data: OrganizationSettingsJSON): OrganizationSettings { + return new OrganizationSettings( + data.enabled, + data.max_allowed_memberships, + data.max_allowed_roles, + data.max_allowed_permissions, + data.creator_role, + data.admin_delete_enabled, + data.domains_enabled, + data.domains_enrollment_modes, + data.domains_default_role, + ); + } +} diff --git a/packages/backend/src/api/resources/index.ts b/packages/backend/src/api/resources/index.ts index efaa43dfbb6..1b7930adcd1 100644 --- a/packages/backend/src/api/resources/index.ts +++ b/packages/backend/src/api/resources/index.ts @@ -18,12 +18,16 @@ export type { export * from './ExternalAccount'; export * from './IdentificationLink'; +export * from './Instance'; +export * from './InstanceRestrictions'; +export * from './InstanceSettings'; export * from './Invitation'; export * from './JSON'; export * from './OauthAccessToken'; export * from './Organization'; export * from './OrganizationInvitation'; export * from './OrganizationMembership'; +export * from './OrganizationSettings'; export * from './PhoneNumber'; export * from './ProxyCheck'; export * from './RedirectUrl'; diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 786f0d1c837..e488b60a7fa 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -63,12 +63,16 @@ export type { EmailAddressJSON, ExternalAccountJSON, IdentificationLinkJSON, + InstanceJSON, + InstanceRestrictionsJSON, + InstanceSettingsJSON, InvitationJSON, OauthAccessTokenJSON, OrganizationJSON, OrganizationDomainJSON, OrganizationDomainVerificationJSON, OrganizationInvitationJSON, + OrganizationSettingsJSON, PublicOrganizationDataJSON, OrganizationMembershipJSON, OrganizationMembershipPublicUserDataJSON, @@ -98,6 +102,9 @@ export type { Client, EmailAddress, ExternalAccount, + Instance, + InstanceRestrictions, + InstanceSettings, Invitation, OauthAccessToken, Organization, @@ -106,6 +113,7 @@ export type { OrganizationInvitation, OrganizationMembership, OrganizationMembershipPublicUserData, + OrganizationSettings, PhoneNumber, Session, SignInToken,