Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8aea3bc
Remove old localization keys and update title/subtitle
LauraBeatris Jun 9, 2026
d89f1e8
Introduce domains form
LauraBeatris Jun 9, 2026
11ed1e4
Add localizations for TXT paragraphs
LauraBeatris Jun 9, 2026
5be1ebb
Add hardcoded version of TXT records table
LauraBeatris Jun 9, 2026
75f8932
Move to new module
LauraBeatris Jun 9, 2026
15096ae
Remove references to user primary email
LauraBeatris Jun 9, 2026
70ea9ca
Add new organization domains API for TXT verification
LauraBeatris Jun 10, 2026
ed031ae
Fetch domains for `enterprise_sso` enrollment mode
LauraBeatris Jun 10, 2026
505724e
Rename domains to plural
LauraBeatris Jun 10, 2026
15434d8
Create separate mutation object for domains
LauraBeatris Jun 10, 2026
549f4ff
Add enterprise SSO as enrollment mode badge
LauraBeatris Jun 10, 2026
5e4afa7
Allow creating new domains
LauraBeatris Jun 10, 2026
e92940e
Create enterprise connection with organization domains
LauraBeatris Jun 10, 2026
0b2e861
Adjust UI tests
LauraBeatris Jun 10, 2026
294e577
Refactor table UI to use cards
LauraBeatris Jun 11, 2026
24cd319
Apply renaming
LauraBeatris Jun 11, 2026
b15c2fc
Add methods for bulk attempt and prepare
LauraBeatris Jun 12, 2026
dfd77c0
Add internal logic for attempt
LauraBeatris Jun 12, 2026
0495897
Add domain suggestion card
LauraBeatris Jun 12, 2026
d6d4cf8
Add changeset
LauraBeatris Jun 12, 2026
d6c8f02
Allow going back on select provider step
LauraBeatris Jun 12, 2026
e8c1b0e
Use readonly input for TXT record value
LauraBeatris Jun 12, 2026
fe8b686
Apply animation on the height on status transition
LauraBeatris Jun 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/curvy-hounds-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@clerk/localizations': minor
'@clerk/clerk-js': minor
'@clerk/shared': minor
'@clerk/ui': minor
---

Introduce organization domains with TXT verification on self-serve SSO flow
53 changes: 50 additions & 3 deletions packages/clerk-js/src/core/resources/Organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
AddMemberParams,
ClerkPaginatedResponse,
ClerkResourceReloadParams,
CreateOrganizationDomainParams,
CreateOrganizationEnterpriseConnectionParams,
CreateOrganizationParams,
DeletedObjectJSON,
Expand All @@ -24,6 +25,8 @@ import type {
InviteMembersParams,
OrganizationDomainJSON,
OrganizationDomainResource,
OrganizationDomainsBulkOwnershipVerificationJSON,
OrganizationDomainsBulkOwnershipVerificationResource,
OrganizationInvitationJSON,
OrganizationInvitationResource,
OrganizationJSON,
Expand Down Expand Up @@ -134,11 +137,17 @@ export class Organization extends BaseResource implements OrganizationResource {
getDomains = async (
getDomainParams?: GetDomainsParams,
): Promise<ClerkPaginatedResponse<OrganizationDomainResource>> => {
const { enrollmentMode, ...rest } = getDomainParams || {};
const search = convertPageToOffsetSearchParams(rest);
if (enrollmentMode) {
search.set('enrollment_mode', enrollmentMode);
}

return await BaseResource._fetch(
{
path: `/organizations/${this.id}/domains`,
method: 'GET',
search: convertPageToOffsetSearchParams(getDomainParams),
search,
},
{
forceUpdateClient: true,
Expand Down Expand Up @@ -282,8 +291,46 @@ export class Organization extends BaseResource implements OrganizationResource {
});
};

createDomain = async (name: string): Promise<OrganizationDomainResource> => {
return OrganizationDomain.create(this.id, { name });
createDomain = async (
name: string,
params?: Pick<CreateOrganizationDomainParams, 'enrollmentMode'>,
): Promise<OrganizationDomainResource> => {
return OrganizationDomain.create(this.id, { name, enrollmentMode: params?.enrollmentMode });
};

prepareOwnershipVerification = async (
domainIds: string[],
): Promise<OrganizationDomainsBulkOwnershipVerificationResource> => {
return this.bulkOwnershipVerification('prepare_ownership_verification', domainIds);
};

attemptOwnershipVerification = async (
domainIds: string[],
): Promise<OrganizationDomainsBulkOwnershipVerificationResource> => {
return this.bulkOwnershipVerification('attempt_ownership_verification', domainIds);
};

private bulkOwnershipVerification = async (
action: 'prepare_ownership_verification' | 'attempt_ownership_verification',
domainIds: string[],
): Promise<OrganizationDomainsBulkOwnershipVerificationResource> => {
const { data, errors } = (
await BaseResource._fetch(
{
path: `/organizations/${this.id}/domains/${action}/bulk`,
method: 'POST',
body: { organization_domain_ids: domainIds } as any,
},
{
forceUpdateClient: true,
},
)
)?.response as unknown as OrganizationDomainsBulkOwnershipVerificationJSON;

return {
data: (data ?? []).map(domain => new OrganizationDomain(domain)),
errors: errors ?? [],
};
};

getMemberships: GetMemberships = async getMembershipsParams => {
Expand Down
47 changes: 39 additions & 8 deletions packages/clerk-js/src/core/resources/OrganizationDomain.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type {
AttemptAffiliationVerificationParams,
CreateOrganizationDomainParams,
OrganizationDomainJSON,
OrganizationDomainOwnershipVerification,
OrganizationDomainResource,
OrganizationDomainVerification,
OrganizationEnrollmentMode,
Expand All @@ -17,6 +19,8 @@ export class OrganizationDomain extends BaseResource implements OrganizationDoma
organizationId!: string;
enrollmentMode!: OrganizationEnrollmentMode;
verification!: OrganizationDomainVerification | null;
affiliationVerification!: OrganizationDomainVerification | null;
ownershipVerification!: OrganizationDomainOwnershipVerification | null;
affiliationEmailAddress!: string | null;
createdAt!: Date;
updatedAt!: Date;
Expand All @@ -28,12 +32,15 @@ export class OrganizationDomain extends BaseResource implements OrganizationDoma
this.fromJSON(data);
}

static async create(organizationId: string, { name }: { name: string }): Promise<OrganizationDomainResource> {
static async create(
organizationId: string,
{ name, enrollmentMode }: CreateOrganizationDomainParams,
): Promise<OrganizationDomainResource> {
const json = (
await BaseResource._fetch<OrganizationDomainJSON>({
path: `/organizations/${organizationId}/domains`,
method: 'POST',
body: { name } as any,
body: { name, enrollment_mode: enrollmentMode } as any,
})
)?.response as unknown as OrganizationDomainJSON;
return new OrganizationDomain(json);
Expand Down Expand Up @@ -81,16 +88,40 @@ export class OrganizationDomain extends BaseResource implements OrganizationDoma
this.affiliationEmailAddress = data.affiliation_email_address;
this.totalPendingSuggestions = data.total_pending_suggestions;
this.totalPendingInvitations = data.total_pending_invitations;
if (data.verification) {
this.verification = {
status: data.verification.status,
strategy: data.verification.strategy,
attempts: data.verification.attempts,
expiresAt: unixEpochToDate(data.verification.expires_at),

const affiliationVerificationJSON = data.affiliation_verification ?? data.verification;
if (affiliationVerificationJSON) {
const affiliationVerification: OrganizationDomainVerification = {
status: affiliationVerificationJSON.status,
strategy: affiliationVerificationJSON.strategy,
attempts: affiliationVerificationJSON.attempts,
expiresAt: unixEpochToDate(affiliationVerificationJSON.expires_at),
};
this.affiliationVerification = affiliationVerification;
// Deprecated alias, kept in sync for backwards compatibility.
this.verification = affiliationVerification;
} else {
this.affiliationVerification = null;
this.verification = null;
}

if (data.ownership_verification) {
this.ownershipVerification = {
status: data.ownership_verification.status,
strategy: data.ownership_verification.strategy,
attempts: data.ownership_verification.attempts,
expiresAt: data.ownership_verification.expire_at
? unixEpochToDate(data.ownership_verification.expire_at)
: null,
verifiedAt: data.ownership_verification.verified_at
? unixEpochToDate(data.ownership_verification.verified_at)
: null,
txtRecordName: data.ownership_verification.txt_record_name ?? null,
txtRecordValue: data.ownership_verification.txt_record_value ?? null,
};
} else {
this.ownershipVerification = null;
}
}
return this;
}
Expand Down
40 changes: 19 additions & 21 deletions packages/localizations/src/ar-SA.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,29 +196,27 @@ export const arSA: LocalizationResource = {
},
warning: 'بمجرد اختيار المزود لا يمكنك التغيير مرة أخرى حتى انتهاء التكوين',
},
verifyEmailDomainStep: {
title: 'التحقق من البريد الإلكتروني',
subtitle: 'تحقق من عنوان البريد الإلكتروني الذي تريد تفعيل اتصال المؤسسة عليه.',
addEmailAddress: {
formTitle: 'نحتاج إلى بريدك الإلكتروني',
formSubtitle: 'للبدء، نحتاج إلى عنوان بريدك الإلكتروني',
inputPlaceholder: 'name@company.com',
inputLabel: 'عنوان البريد الإلكتروني',
},
emailCode: {
formTitle: 'تحقق من عنوان بريدك الإلكتروني',
formSubtitle: 'أدخل رمز التحقق المرسل إلى {{identifier}}',
resendButton: 'لم تتلقَّ الرمز؟ إعادة الإرسال',
verified: {
title: 'لقد تلقينا بريدك الإلكتروني',
subtitle: 'لقد تحققت من عنوان بريدك الإلكتروني التالي',
inputLabel: 'عنوان البريد الإلكتروني الذي تم التحقق منه',
organizationDomainsStep: {
title: 'إضافة نطاقات SSO',
subtitle: 'أضِف نطاقات مؤسستك المستخدمة لتسجيل الدخول وتحقّق من ملكيتها.',
formFieldLabel__domain: 'النطاقات',
formFieldInputPlaceholder__domain: 'اكتب نطاقك هنا وانقر على إضافة للبدء',
formButtonPrimary__add: 'إضافة',
domainSuggestion: {
messageLabel: 'بريدك الإلكتروني يستخدم {{domain}}. هل تريد إضافته؟',
formButtonPrimary__add: 'إضافة {{domain}}',
},
domainCard: {
badge__verified: 'تم التحقق',
badge__unverified: 'لم يتم التحقق',
verifiedAtLabel: "تم التحقق في {{ date | shortDate('en-US') }}",
txtRecord: {
instructions: 'أضِف سجل TXT هذا إلى مزوّد DNS الخاص بك. سنتحقق تلقائيًا بمجرد أن يصبح السجل نشطًا.',
typeLabel: 'النوع',
hostLabel: 'المضيف / الاسم',
valueLabel: 'القيمة',
},
},
domainTaken: {
title: 'هذا النطاق ({{domain}}) لديه بالفعل اتصال SSO',
subtitle: 'تواصل مع مسؤول التطبيق للحصول على الوصول من خلال الاتصال الحالي.',
},
},
Comment thread
LauraBeatris marked this conversation as resolved.
},
createOrganization: {
Expand Down
41 changes: 20 additions & 21 deletions packages/localizations/src/be-BY.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,29 +196,28 @@ export const beBY: LocalizationResource = {
},
warning: 'Пасля выбару правайдэра вы не зможаце змяніць яго, пакуль не скончыце канфігурацыю',
},
verifyEmailDomainStep: {
title: 'Пацвердзіць адрас электроннай пошты',
subtitle: 'Пацвердзіце адрас электроннай пошты, на якім вы хочаце ўключыць карпаратыўнае падключэнне.',
addEmailAddress: {
formTitle: 'Нам патрэбна ваша пошта',
formSubtitle: 'Каб пачаць, нам спатрэбіцца ваш адрас электроннай пошты',
inputPlaceholder: 'name@company.com',
inputLabel: 'Адрас электроннай пошты',
},
emailCode: {
formTitle: 'Пацвердзіце ваш адрас электроннай пошты',
formSubtitle: 'Увядзіце код пацверджання, дасланы на {{identifier}}',
resendButton: 'Не атрымалі код? Адправіць паўторна',
verified: {
title: 'Мы атрымалі вашу пошту',
subtitle: 'Вы пацвердзілі свой адрас электроннай пошты з наступнай поштай',
inputLabel: 'Пацверджаны адрас электроннай пошты',
organizationDomainsStep: {
title: 'Дадаць дамены SSO',
subtitle: 'Дадайце і пацвердзіце права ўласнасці на дамены, якія ваша арганізацыя выкарыстоўвае для ўваходу.',
formFieldLabel__domain: 'Дамены',
formFieldInputPlaceholder__domain: 'Увядзіце свой дамен тут і націсніце «Дадаць», каб пачаць',
formButtonPrimary__add: 'Дадаць',
domainSuggestion: {
messageLabel: 'Ваша электронная пошта выкарыстоўвае {{domain}}. Хочаце дадаць яго?',
formButtonPrimary__add: 'Дадаць {{domain}}',
},
domainCard: {
badge__verified: 'Пацверджана',
badge__unverified: 'Не пацверджана',
verifiedAtLabel: "Пацверджана {{ date | shortDate('be-BY') }}",
txtRecord: {
instructions:
'Дадайце гэты TXT-запіс да вашага DNS-правайдара. Мы аўтаматычна выканаем праверку, як толькі запіс стане актыўным.',
typeLabel: 'Тып',
hostLabel: 'Хост / Імя',
valueLabel: 'Значэнне',
},
},
domainTaken: {
title: 'Гэты дамен ({{domain}}) ужо мае SSO-падключэнне',
subtitle: 'Звяжыцеся з адміністратарам прыкладання, каб атрымаць доступ праз існуючае падключэнне.',
},
},
},
createOrganization: {
Expand Down
41 changes: 20 additions & 21 deletions packages/localizations/src/bg-BG.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,29 +197,28 @@ export const bgBG: LocalizationResource = {
},
warning: 'След като изберете доставчик, не можете да го промените, докато конфигурацията не приключи',
},
verifyEmailDomainStep: {
title: 'Потвърди имейл адреса',
subtitle: 'Потвърдете имейл адреса, на който искате да активирате корпоративната връзка.',
addEmailAddress: {
formTitle: 'Нуждаем се от вашия имейл',
formSubtitle: 'За да започнем, ще ни е необходим вашият имейл адрес',
inputPlaceholder: 'name@company.com',
inputLabel: 'Имейл адрес',
},
emailCode: {
formTitle: 'Потвърдете имейл адреса си',
formSubtitle: 'Въведете кода за потвърждение, изпратен на {{identifier}}',
resendButton: 'Не получихте код? Изпрати отново',
verified: {
title: 'Получихме имейла ви',
subtitle: 'Потвърдихте имейл адреса си със следния имейл',
inputLabel: 'Потвърден имейл адрес',
organizationDomainsStep: {
title: 'Добавяне на SSO домейни',
subtitle: 'Добавете и потвърдете собствеността върху домейните, които вашата организация използва за вход.',
formFieldLabel__domain: 'Домейни',
formFieldInputPlaceholder__domain: 'Въведете домейна си тук и щракнете върху добавяне, за да започнете',
formButtonPrimary__add: 'Добави',
domainSuggestion: {
messageLabel: 'Вашият имейл използва {{domain}}. Искате ли да го добавите?',
formButtonPrimary__add: 'Добавяне на {{domain}}',
},
domainCard: {
badge__verified: 'Потвърден',
badge__unverified: 'Непотвърден',
verifiedAtLabel: "Потвърден на {{ date | shortDate('bg-BG') }}",
txtRecord: {
instructions:
'Добавете този TXT запис към вашия DNS доставчик. Ще потвърдим автоматично, след като записът стане активен.',
typeLabel: 'Тип',
hostLabel: 'Хост / Име',
valueLabel: 'Стойност',
},
},
domainTaken: {
title: 'Този домейн ({{domain}}) вече има SSO връзка',
subtitle: 'Свържете се с администратора на приложението, за да получите достъп чрез съществуващата връзка.',
},
},
},
createOrganization: {
Expand Down
41 changes: 20 additions & 21 deletions packages/localizations/src/bn-IN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,29 +202,28 @@ export const bnIN: LocalizationResource = {
},
warning: 'একবার প্রদানকারী নির্বাচন করার পরে, কনফিগারেশন শেষ না হওয়া পর্যন্ত আপনি আবার পরিবর্তন করতে পারবেন না',
},
verifyEmailDomainStep: {
title: 'ইমেইল ঠিকানা যাচাই করুন',
subtitle: 'যে ইমেইল ঠিকানায় আপনি এন্টারপ্রাইজ সংযোগ সক্রিয় করতে চান তা যাচাই করুন।',
addEmailAddress: {
formTitle: 'আমাদের আপনার ইমেইল প্রয়োজন',
formSubtitle: 'শুরু করতে আমাদের আপনার ইমেইল ঠিকানা প্রয়োজন হবে',
inputPlaceholder: 'name@company.com',
inputLabel: 'ইমেইল ঠিকানা',
},
emailCode: {
formTitle: 'আপনার ইমেইল ঠিকানা যাচাই করুন',
formSubtitle: '{{identifier}} এ পাঠানো যাচাইকরণ কোড লিখুন',
resendButton: 'কোড পাননি? পুনরায় পাঠান',
verified: {
title: 'আমরা আপনার ইমেইল পেয়েছি',
subtitle: 'আপনি নিম্নলিখিত ইমেইল দিয়ে আপনার ইমেইল ঠিকানা যাচাই করেছেন',
inputLabel: 'যাচাইকৃত ইমেইল ঠিকানা',
organizationDomainsStep: {
title: 'SSO ডোমেইন যোগ করুন',
subtitle: 'আপনার প্রতিষ্ঠান সাইন ইন করতে যে ডোমেইনগুলি ব্যবহার করে তার মালিকানা যোগ করুন এবং যাচাই করুন।',
formFieldLabel__domain: 'ডোমেইন',
formFieldInputPlaceholder__domain: 'আপনার ডোমেইন এখানে টাইপ করুন এবং শুরু করতে যোগ করুন-এ ক্লিক করুন',
formButtonPrimary__add: 'যোগ করুন',
domainSuggestion: {
messageLabel: 'আপনার ইমেল {{domain}} ব্যবহার করে। আপনি কি এটি যোগ করতে চান?',
formButtonPrimary__add: '{{domain}} যোগ করুন',
},
domainCard: {
badge__verified: 'যাচাই করা হয়েছে',
badge__unverified: 'যাচাই করা হয়নি',
verifiedAtLabel: "{{ date | shortDate('bn-IN') }} তারিখে যাচাই করা হয়েছে",
txtRecord: {
instructions:
'আপনার DNS প্রদানকারীতে এই TXT রেকর্ডটি যোগ করুন। রেকর্ডটি সক্রিয় হলে আমরা স্বয়ংক্রিয়ভাবে যাচাই করব।',
typeLabel: 'ধরন',
hostLabel: 'হোস্ট / নাম',
valueLabel: 'মান',
},
},
domainTaken: {
title: 'এই ডোমেইনে ({{domain}}) ইতিমধ্যে একটি SSO সংযোগ রয়েছে',
subtitle: 'বিদ্যমান সংযোগের মাধ্যমে অ্যাক্সেস পেতে অ্যাপ্লিকেশন প্রশাসকের সাথে যোগাযোগ করুন।',
},
},
},
createOrganization: {
Expand Down
Loading
Loading