Skip to content

Commit

Permalink
feat(clerk-js,backend,types): Implement "disable additional identifie…
Browse files Browse the repository at this point in the history
…rs" for SAML connection (#4211)
  • Loading branch information
NicolasLopes7 authored Sep 25, 2024
1 parent ef9b239 commit 4749ed4
Show file tree
Hide file tree
Showing 16 changed files with 382 additions and 135 deletions.
7 changes: 7 additions & 0 deletions .changeset/fresh-forks-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@clerk/clerk-js": patch
"@clerk/backend": patch
"@clerk/types": patch
---

Conditionally renders identification sections on `UserProfile` based on the SAML connection configuration for disabling additional identifiers.
15 changes: 15 additions & 0 deletions packages/backend/src/api/resources/JSON.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export interface SamlAccountJSON extends ClerkResourceJSON {
first_name: string;
last_name: string;
verification: VerificationJSON | null;
saml_connection: SamlAccountConnectionJSON | null;
}

export interface IdentificationLinkJSON extends ClerkResourceJSON {
Expand Down Expand Up @@ -399,3 +400,17 @@ export interface PermissionJSON extends ClerkResourceJSON {
created_at: number;
updated_at: number;
}

export interface SamlAccountConnectionJSON extends ClerkResourceJSON {
id: string;
name: string;
domain: string;
active: boolean;
provider: string;
sync_user_attributes: boolean;
allow_subdomains: boolean;
allow_idp_initiated: boolean;
disable_additional_identifications: boolean;
created_at: number;
updated_at: number;
}
3 changes: 3 additions & 0 deletions packages/backend/src/api/resources/SamlAccount.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { SamlAccountJSON } from './JSON';
import { SamlAccountConnection } from './SamlConnection';
import { Verification } from './Verification';

export class SamlAccount {
Expand All @@ -11,6 +12,7 @@ export class SamlAccount {
readonly firstName: string,
readonly lastName: string,
readonly verification: Verification | null,
readonly samlConnection: SamlAccountConnection | null,
) {}

static fromJSON(data: SamlAccountJSON): SamlAccount {
Expand All @@ -23,6 +25,7 @@ export class SamlAccount {
data.first_name,
data.last_name,
data.verification && Verification.fromJSON(data.verification),
data.saml_connection && SamlAccountConnection.fromJSON(data.saml_connection),
);
}
}
31 changes: 30 additions & 1 deletion packages/backend/src/api/resources/SamlConnection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AttributeMappingJSON, SamlConnectionJSON } from './JSON';
import type { AttributeMappingJSON, SamlAccountConnectionJSON, SamlConnectionJSON } from './JSON';

export class SamlConnection {
constructor(
Expand Down Expand Up @@ -49,6 +49,35 @@ export class SamlConnection {
}
}

export class SamlAccountConnection {
constructor(
readonly id: string,
readonly name: string,
readonly domain: string,
readonly active: boolean,
readonly provider: string,
readonly syncUserAttributes: boolean,
readonly allowSubdomains: boolean,
readonly allowIdpInitiated: boolean,
readonly createdAt: number,
readonly updatedAt: number,
) {}
static fromJSON(data: SamlAccountConnectionJSON): SamlAccountConnection {
return new SamlAccountConnection(
data.id,
data.name,
data.domain,
data.active,
data.provider,
data.sync_user_attributes,
data.allow_subdomains,
data.allow_idp_initiated,
data.created_at,
data.updated_at,
);
}
}

class AttributeMapping {
constructor(
readonly userId: string,
Expand Down
2 changes: 1 addition & 1 deletion packages/clerk-js/bundlewatch.config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"files": [
{ "path": "./dist/clerk.browser.js", "maxSize": "64.1kB" },
{ "path": "./dist/clerk.browser.js", "maxSize": "65kB" },
{ "path": "./dist/clerk.headless.js", "maxSize": "43kB" },
{ "path": "./dist/ui-common*.js", "maxSize": "86KB" },
{ "path": "./dist/vendors*.js", "maxSize": "70KB" },
Expand Down
51 changes: 50 additions & 1 deletion packages/clerk-js/src/core/resources/SamlAccount.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import type { SamlAccountJSON, SamlAccountResource, SamlIdpSlug, VerificationResource } from '@clerk/types';
import type {
SamlAccountConnectionJSON,
SamlAccountConnectionResource,
SamlAccountJSON,
SamlAccountResource,
SamlIdpSlug,
VerificationResource,
} from '@clerk/types';

import { unixEpochToDate } from '../../utils/date';
import { BaseResource } from './Base';
import { Verification } from './Verification';

Expand All @@ -12,6 +20,7 @@ export class SamlAccount extends BaseResource implements SamlAccountResource {
firstName = '';
lastName = '';
verification: VerificationResource | null = null;
samlConnection: SamlAccountConnectionResource | null = null;

public constructor(data: Partial<SamlAccountJSON>, pathRoot: string);
public constructor(data: SamlAccountJSON, pathRoot: string) {
Expand All @@ -37,6 +46,46 @@ export class SamlAccount extends BaseResource implements SamlAccountResource {
this.verification = new Verification(data.verification);
}

if (data.saml_connection) {
this.samlConnection = new SamlAccountConnection(data.saml_connection);
}

return this;
}
}

export class SamlAccountConnection extends BaseResource implements SamlAccountConnectionResource {
id!: string;
name!: string;
domain!: string;
active!: boolean;
provider!: string;
syncUserAttributes!: boolean;
allowSubdomains!: boolean;
allowIdpInitiated!: boolean;
disableAdditionalIdentifications!: boolean;
createdAt!: Date;
updatedAt!: Date;

constructor(data: SamlAccountConnectionJSON | null) {
super();
this.fromJSON(data);
}
protected fromJSON(data: SamlAccountConnectionJSON | null): this {
if (data) {
this.id = data.id;
this.name = data.name;
this.domain = data.domain;
this.active = data.active;
this.provider = data.provider;
this.syncUserAttributes = data.sync_user_attributes;
this.allowSubdomains = data.allow_subdomains;
this.allowIdpInitiated = data.allow_idp_initiated;
this.disableAdditionalIdentifications = data.disable_additional_identifications;
this.createdAt = unixEpochToDate(data.created_at);
this.updatedAt = unixEpochToDate(data.updated_at);
}

return this;
}
}
15 changes: 11 additions & 4 deletions packages/clerk-js/src/ui/components/UserProfile/AccountPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,20 @@ export const AccountPage = withCardStateProvider(() => {
const { attributes, saml, social } = useEnvironment().userSettings;
const card = useCardState();
const { user } = useUser();

const showUsername = attributes.username.enabled;
const showEmail = attributes.email_address.enabled;
const showPhone = attributes.phone_number.enabled;
const showConnectedAccounts = social && Object.values(social).filter(p => p.enabled).length > 0;
const showSamlAccounts = saml && saml.enabled && user && user.samlAccounts.length > 0;
const showWeb3 = attributes.web3_wallet.enabled;

const shouldAllowIdentificationCreation =
!showSamlAccounts ||
!user?.samlAccounts?.some(
samlAccount => samlAccount.active && samlAccount.samlConnection?.disableAdditionalIdentifications,
);

return (
<Col
elementDescriptor={descriptors.page}
Expand All @@ -43,11 +50,11 @@ export const AccountPage = withCardStateProvider(() => {

<UserProfileSection />
{showUsername && <UsernameSection />}
{showEmail && <EmailsSection />}
{showPhone && <PhoneSection />}
{showConnectedAccounts && <ConnectedAccountsSection />}
{showEmail && <EmailsSection shouldAllowCreation={shouldAllowIdentificationCreation} />}
{showPhone && <PhoneSection shouldAllowCreation={shouldAllowIdentificationCreation} />}
{showConnectedAccounts && <ConnectedAccountsSection shouldAllowCreation={shouldAllowIdentificationCreation} />}
{showSamlAccounts && <EnterpriseAccountsSection />}
{showWeb3 && <Web3Section />}
{showWeb3 && <Web3Section shouldAllowCreation={shouldAllowIdentificationCreation} />}
</Col>
</Col>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,41 +46,43 @@ const errorCodesForReconnect = [
'external_account_email_address_verification_required',
];

export const ConnectedAccountsSection = withCardStateProvider(() => {
const { user } = useUser();
const card = useCardState();

if (!user) {
return null;
}

const accounts = [
...user.verifiedExternalAccounts,
...user.unverifiedExternalAccounts.filter(a => a.verification?.error),
];
export const ConnectedAccountsSection = withCardStateProvider(
({ shouldAllowCreation = true }: { shouldAllowCreation?: boolean }) => {
const { user } = useUser();
const card = useCardState();
const hasExternalAccounts = Boolean(user?.externalAccounts?.length);

if (!user || (!shouldAllowCreation && !hasExternalAccounts)) {
return null;
}

return (
<ProfileSection.Root
title={localizationKeys('userProfile.start.connectedAccountsSection.title')}
centered={false}
id='connectedAccounts'
>
<Card.Alert>{card.error}</Card.Alert>
<Action.Root>
<ProfileSection.ItemList id='connectedAccounts'>
{accounts.map(account => (
<ConnectedAccount
key={account.id}
account={account}
/>
))}
</ProfileSection.ItemList>

<AddConnectedAccount />
</Action.Root>
</ProfileSection.Root>
);
});
const accounts = [
...user.verifiedExternalAccounts,
...user.unverifiedExternalAccounts.filter(a => a.verification?.error),
];

return (
<ProfileSection.Root
title={localizationKeys('userProfile.start.connectedAccountsSection.title')}
centered={false}
id='connectedAccounts'
>
<Card.Alert>{card.error}</Card.Alert>
<Action.Root>
<ProfileSection.ItemList id='connectedAccounts'>
{accounts.map(account => (
<ConnectedAccount
key={account.id}
account={account}
/>
))}
</ProfileSection.ItemList>
{shouldAllowCreation && <AddConnectedAccount />}
</Action.Root>
</ProfileSection.Root>
);
},
);

const ConnectedAccount = ({ account }: { account: ExternalAccountResource }) => {
const { additionalOAuthScopes, componentName, mode } = useUserProfileContext();
Expand Down
30 changes: 16 additions & 14 deletions packages/clerk-js/src/ui/components/UserProfile/EmailsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const EmailScreen = (props: EmailScreenProps) => {
);
};

export const EmailsSection = () => {
export const EmailsSection = ({ shouldAllowCreation = true }) => {
const { user } = useUser();

return (
Expand Down Expand Up @@ -79,19 +79,21 @@ export const EmailsSection = () => {
</Action.Open>
</Action.Root>
))}

<Action.Trigger value='add'>
<ProfileSection.ArrowButton
id='emailAddresses'
localizationKey={localizationKeys('userProfile.start.emailAddressesSection.primaryButton')}
/>
</Action.Trigger>

<Action.Open value='add'>
<Action.Card>
<EmailScreen />
</Action.Card>
</Action.Open>
{shouldAllowCreation && (
<>
<Action.Trigger value='add'>
<ProfileSection.ArrowButton
id='emailAddresses'
localizationKey={localizationKeys('userProfile.start.emailAddressesSection.primaryButton')}
/>
</Action.Trigger>
<Action.Open value='add'>
<Action.Card>
<EmailScreen />
</Action.Card>
</Action.Open>
</>
)}
</ProfileSection.ItemList>
</Action.Root>
</ProfileSection.Root>
Expand Down
35 changes: 21 additions & 14 deletions packages/clerk-js/src/ui/components/UserProfile/PhoneSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,13 @@ const PhoneScreen = (props: PhoneScreenProps) => {
);
};

export const PhoneSection = () => {
export const PhoneSection = ({ shouldAllowCreation = true }: { shouldAllowCreation?: boolean }) => {
const { user } = useUser();
const hasPhoneNumbers = Boolean(user?.phoneNumbers?.length);

if (!shouldAllowCreation && !hasPhoneNumbers) {
return null;
}

return (
<ProfileSection.Root
Expand Down Expand Up @@ -84,19 +89,21 @@ export const PhoneSection = () => {
</Action.Open>
</Action.Root>
))}

<Action.Trigger value='add'>
<ProfileSection.ArrowButton
id='phoneNumbers'
localizationKey={localizationKeys('userProfile.start.phoneNumbersSection.primaryButton')}
/>
</Action.Trigger>

<Action.Open value='add'>
<Action.Card>
<PhoneScreen />
</Action.Card>
</Action.Open>
{shouldAllowCreation && (
<>
<Action.Trigger value='add'>
<ProfileSection.ArrowButton
id='phoneNumbers'
localizationKey={localizationKeys('userProfile.start.phoneNumbersSection.primaryButton')}
/>
</Action.Trigger>
<Action.Open value='add'>
<Action.Card>
<PhoneScreen />
</Action.Card>
</Action.Open>
</>
)}
</ProfileSection.ItemList>
</Action.Root>
</ProfileSection.Root>
Expand Down
Loading

0 comments on commit 4749ed4

Please sign in to comment.