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 898db50058c..2fa9fe5506a 100644 --- a/packages/backend/src/api/endpoints/index.ts +++ b/packages/backend/src/api/endpoints/index.ts @@ -6,6 +6,7 @@ export * from './BlocklistIdentifierApi'; export * from './ClientApi'; export * from './DomainApi'; export * from './EmailAddressApi'; +export * from './InstanceApi'; export * from './InvitationApi'; export * from './JwksApi'; export * from './JwtTemplatesApi'; diff --git a/packages/backend/src/api/factory.ts b/packages/backend/src/api/factory.ts index 9ae60b84f03..2daab6a8665 100644 --- a/packages/backend/src/api/factory.ts +++ b/packages/backend/src/api/factory.ts @@ -6,6 +6,7 @@ import { ClientAPI, DomainAPI, EmailAddressAPI, + InstanceAPI, InvitationAPI, JwksAPI, JwtTemplatesApi, @@ -40,6 +41,7 @@ export function createBackendApiClient(options: CreateBackendApiOptions) { clients: new ClientAPI(request), domains: new DomainAPI(request), emailAddresses: new EmailAddressAPI(request), + instance: new InstanceAPI(request), invitations: new InvitationAPI(request), jwks: new JwksAPI(request), jwtTemplates: new JwtTemplatesApi(request), diff --git a/packages/backend/src/api/resources/Deserializer.ts b/packages/backend/src/api/resources/Deserializer.ts index e20b69968d2..7f3990cdd32 100644 --- a/packages/backend/src/api/resources/Deserializer.ts +++ b/packages/backend/src/api/resources/Deserializer.ts @@ -8,6 +8,9 @@ import { Domain, Email, EmailAddress, + Instance, + InstanceRestrictions, + InstanceSettings, Invitation, JwtTemplate, OauthAccessToken, @@ -15,6 +18,7 @@ import { Organization, OrganizationInvitation, OrganizationMembership, + OrganizationSettings, PhoneNumber, ProxyCheck, RedirectUrl, @@ -91,6 +95,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.JwtTemplate: @@ -105,6 +115,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 9f9798af19c..4f3a606ca9f 100644 --- a/packages/backend/src/api/resources/Enums.ts +++ b/packages/backend/src/api/resources/Enums.ts @@ -37,6 +37,13 @@ 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]; + export const ActorTokenStatus = { Pending: 'pending', Accepted: 'accepted', 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 97fc0360baf..c2d5bc808fd 100644 --- a/packages/backend/src/api/resources/JSON.ts +++ b/packages/backend/src/api/resources/JSON.ts @@ -2,6 +2,7 @@ import type { ActorTokenStatus, AllowlistIdentifierType, BlocklistIdentifierType, + DomainsEnrollmentModes, InvitationStatus, OrganizationDomainVerificationStatus, OrganizationDomainVerificationStrategy, @@ -25,6 +26,9 @@ export const ObjectType = { ExternalAccount: 'external_account', FacebookAccount: 'facebook_account', GoogleAccount: 'google_account', + Instance: 'instance', + InstanceRestrictions: 'instance_restrictions', + InstanceSettings: 'instance_settings', Invitation: 'invitation', JwtTemplate: 'jwt_template', OauthAccessToken: 'oauth_access_token', @@ -33,6 +37,7 @@ export const ObjectType = { OrganizationDomain: 'organization_domain', OrganizationInvitation: 'organization_invitation', OrganizationMembership: 'organization_membership', + OrganizationSettings: 'organization_settings', PhoneNumber: 'phone_number', ProxyCheck: 'proxy_check', RedirectUrl: 'redirect_url', @@ -224,6 +229,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 60c869234bd..2103e5fb733 100644 --- a/packages/backend/src/api/resources/index.ts +++ b/packages/backend/src/api/resources/index.ts @@ -22,6 +22,9 @@ 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 './JwtTemplate'; @@ -30,6 +33,7 @@ export * from './OAuthApplication'; 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 b06866e4327..0b9e1d9f6a9 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -67,6 +67,9 @@ export type { EmailAddressJSON, ExternalAccountJSON, IdentificationLinkJSON, + InstanceJSON, + InstanceRestrictionsJSON, + InstanceSettingsJSON, InvitationJSON, JwtTemplateJSON, OauthAccessTokenJSON, @@ -75,6 +78,7 @@ export type { OrganizationDomainJSON, OrganizationDomainVerificationJSON, OrganizationInvitationJSON, + OrganizationSettingsJSON, PublicOrganizationDataJSON, OrganizationMembershipJSON, OrganizationMembershipPublicUserDataJSON, @@ -109,6 +113,9 @@ export type { Domain, EmailAddress, ExternalAccount, + Instance, + InstanceRestrictions, + InstanceSettings, Invitation, JwtTemplate, OauthAccessToken, @@ -119,6 +126,7 @@ export type { OrganizationInvitation, OrganizationMembership, OrganizationMembershipPublicUserData, + OrganizationSettings, PhoneNumber, Session, SignInToken,