Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .changeset/pink-dolls-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/clerk-js': minor
'@clerk/shared': minor
---

Add internal API methods to manage enterprise connections
4 changes: 2 additions & 2 deletions packages/clerk-js/bundlewatch.config.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"files": [
{ "path": "./dist/clerk.js", "maxSize": "543KB" },
{ "path": "./dist/clerk.browser.js", "maxSize": "68KB" },
{ "path": "./dist/clerk.browser.js", "maxSize": "70KB" },
{ "path": "./dist/clerk.legacy.browser.js", "maxSize": "110KB" },
{ "path": "./dist/clerk.no-rhc.js", "maxSize": "309KB" },
{ "path": "./dist/clerk.native.js", "maxSize": "68KB" },
{ "path": "./dist/clerk.native.js", "maxSize": "70KB" },
{ "path": "./dist/vendors*.js", "maxSize": "7KB" },
{ "path": "./dist/coinbase*.js", "maxSize": "36KB" },
{ "path": "./dist/base-account-sdk*.js", "maxSize": "203KB" },
Expand Down
164 changes: 164 additions & 0 deletions packages/clerk-js/src/core/resources/EnterpriseConnectionTestRun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import type {
ClerkResourceReloadParams,
EnterpriseConnectionTestRunJSON,
EnterpriseConnectionTestRunJSONSnapshot,
EnterpriseConnectionTestRunLogResource,
EnterpriseConnectionTestRunOauthPayloadJSON,
EnterpriseConnectionTestRunOauthPayloadResource,
EnterpriseConnectionTestRunParsedUserInfoJSON,
EnterpriseConnectionTestRunParsedUserInfoResource,
EnterpriseConnectionTestRunResource,
EnterpriseConnectionTestRunSamlPayloadJSON,
EnterpriseConnectionTestRunSamlPayloadResource,
} from '@clerk/shared/types';

import { unixEpochToDate } from '../../utils/date';
import { clerkUnsupportedReloadMethod } from '../errors';

export class EnterpriseConnectionTestRun implements EnterpriseConnectionTestRunResource {
pathRoot = '/me';

id!: string;
status!: string;
connectionType!: 'saml' | 'oauth';
parsedUserInfo: EnterpriseConnectionTestRunParsedUserInfoResource | null = null;
logs: EnterpriseConnectionTestRunLogResource[] = [];
saml: EnterpriseConnectionTestRunSamlPayloadResource | null = null;
oauth: EnterpriseConnectionTestRunOauthPayloadResource | null = null;
createdAt: Date | null = null;

constructor(data: EnterpriseConnectionTestRunJSON) {
this.fromJSON(data);
}

reload(_?: ClerkResourceReloadParams): Promise<this> {
clerkUnsupportedReloadMethod('EnterpriseConnectionTestRun');
}

private fromJSON(data: EnterpriseConnectionTestRunJSON | null): this {
if (!data) {
return this;
}

this.id = data.id;
this.status = data.status;
this.connectionType = data.connection_type;
this.parsedUserInfo = parsedUserInfoFromJSON(data.parsed_user_info ?? null);
this.saml = samlPayloadFromJSON(data.saml ?? null);
this.oauth = oauthPayloadFromJSON(data.oauth ?? null);
this.createdAt = unixEpochToDate(data.created_at);
this.logs = (data.logs ?? []).map(log => ({
level: log.level,
code: log.code,
shortMessage: log.short_message,
message: log.message,
}));

return this;
}

public __internal_toSnapshot(): EnterpriseConnectionTestRunJSONSnapshot {
return {
object: 'enterprise_connection_test_run',
id: this.id,
status: this.status,
connection_type: this.connectionType,
parsed_user_info: parsedUserInfoToJSON(this.parsedUserInfo),
saml: samlPayloadToJSON(this.saml),
oauth: oauthPayloadToJSON(this.oauth),
logs: this.logs.map(log => ({
level: log.level,
code: log.code,
short_message: log.shortMessage,
message: log.message,
})),
created_at: this.createdAt?.getTime() ?? 0,
};
}
}

function parsedUserInfoFromJSON(
data: EnterpriseConnectionTestRunParsedUserInfoJSON | null | undefined,
): EnterpriseConnectionTestRunParsedUserInfoResource | null {
if (!data) {
return null;
}

return {
emailAddress: data.email_address,
firstName: data.first_name,
lastName: data.last_name,
userId: data.user_id,
};
}

function parsedUserInfoToJSON(
data: EnterpriseConnectionTestRunParsedUserInfoResource | null,
): EnterpriseConnectionTestRunParsedUserInfoJSON | null {
if (!data) {
return null;
}

return {
email_address: data.emailAddress,
first_name: data.firstName,
last_name: data.lastName,
user_id: data.userId,
};
}

function samlPayloadFromJSON(
data: EnterpriseConnectionTestRunSamlPayloadJSON | null | undefined,
): EnterpriseConnectionTestRunSamlPayloadResource | null {
if (!data) {
return null;
}

return {
samlRequest: data.saml_request,
samlResponse: data.saml_response,
relayState: data.relay_state,
};
}

function samlPayloadToJSON(
data: EnterpriseConnectionTestRunSamlPayloadResource | null,
): EnterpriseConnectionTestRunSamlPayloadJSON | null {
if (!data) {
return null;
}

return {
saml_request: data.samlRequest,
saml_response: data.samlResponse,
relay_state: data.relayState,
};
}

function oauthPayloadFromJSON(
data: EnterpriseConnectionTestRunOauthPayloadJSON | null | undefined,
): EnterpriseConnectionTestRunOauthPayloadResource | null {
if (!data) {
return null;
}

return {
idToken: data.id_token,
accessToken: data.access_token,
userInfo: data.user_info,
};
}

function oauthPayloadToJSON(
data: EnterpriseConnectionTestRunOauthPayloadResource | null,
): EnterpriseConnectionTestRunOauthPayloadJSON | null {
if (!data) {
return null;
}

return {
id_token: data.idToken,
access_token: data.accessToken,
user_info: data.userInfo,
};
}
118 changes: 118 additions & 0 deletions packages/clerk-js/src/core/resources/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { getFullName } from '@clerk/shared/internal/clerk-js/user';
import type {
BackupCodeJSON,
BackupCodeResource,
ClerkPaginatedResponse,
CreateEmailAddressParams,
CreateExternalAccountParams,
CreateMeEnterpriseConnectionParams,
CreatePhoneNumberParams,
CreateWeb3WalletParams,
DeletedObjectJSON,
Expand All @@ -12,9 +14,15 @@ import type {
EnterpriseAccountResource,
EnterpriseConnectionJSON,
EnterpriseConnectionResource,
EnterpriseConnectionTestRunInitJSON,
EnterpriseConnectionTestRunInitResource,
EnterpriseConnectionTestRunJSON,
EnterpriseConnectionTestRunResource,
EnterpriseConnectionTestRunsPaginatedJSON,
ExternalAccountJSON,
ExternalAccountResource,
GetEnterpriseConnectionsParams,
GetEnterpriseConnectionTestRunsParams,
GetOrganizationMemberships,
GetUserOrganizationInvitationsParams,
GetUserOrganizationSuggestionsParams,
Expand All @@ -26,6 +34,7 @@ import type {
SetProfileImageParams,
TOTPJSON,
TOTPResource,
UpdateMeEnterpriseConnectionParams,
UpdateUserParams,
UpdateUserPasswordParams,
UserJSON,
Expand All @@ -34,7 +43,9 @@ import type {
VerifyTOTPParams,
Web3WalletResource,
} from '@clerk/shared/types';
import { deepCamelToSnake } from '@clerk/shared/underscore';

import { convertPageToOffsetSearchParams } from '../../utils/convertPageToOffsetSearchParams';
import { unixEpochToDate } from '../../utils/date';
import { normalizeUnsafeMetadata } from '../../utils/resourceParams';
import { eventBus, events } from '../events';
Expand All @@ -46,6 +57,7 @@ import {
EmailAddress,
EnterpriseAccount,
EnterpriseConnection,
EnterpriseConnectionTestRun,
ExternalAccount,
Image,
OrganizationMembership,
Expand Down Expand Up @@ -316,6 +328,85 @@ export class User extends BaseResource implements UserResource {
return (json || []).map(connection => new EnterpriseConnection(connection));
};

createEnterpriseConnection = async (
params: CreateMeEnterpriseConnectionParams,
): Promise<EnterpriseConnectionResource> => {
const json = (
await BaseResource._fetch<EnterpriseConnectionJSON>({
path: `${this.path()}/enterprise_connections`,
method: 'POST',
body: toMeEnterpriseConnectionBody(params) as any,
})
)?.response as unknown as EnterpriseConnectionJSON;

return new EnterpriseConnection(json);
};

updateEnterpriseConnection = async (
enterpriseConnectionId: string,
params: UpdateMeEnterpriseConnectionParams,
): Promise<EnterpriseConnectionResource> => {
const json = (
await BaseResource._fetch<EnterpriseConnectionJSON>({
path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}`,
method: 'PATCH',
body: toMeEnterpriseConnectionBody(params) as any,
})
)?.response as unknown as EnterpriseConnectionJSON;

return new EnterpriseConnection(json);
};

deleteEnterpriseConnection = async (enterpriseConnectionId: string): Promise<DeletedObjectResource> => {
const json = (
await BaseResource._fetch<DeletedObjectJSON>({
path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}`,
method: 'DELETE',
})
)?.response as unknown as DeletedObjectJSON;

return new DeletedObject(json);
};

createEnterpriseConnectionTestRun = async (
enterpriseConnectionId: string,
): Promise<EnterpriseConnectionTestRunInitResource> => {
const json = (
await BaseResource._fetch({
path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}/test_runs`,
method: 'POST',
})
)?.response as unknown as EnterpriseConnectionTestRunInitJSON;

return { url: json.url };
};

getEnterpriseConnectionTestRuns = async (
enterpriseConnectionId: string,
params?: GetEnterpriseConnectionTestRunsParams,
): Promise<ClerkPaginatedResponse<EnterpriseConnectionTestRunResource>> => {
const { status, ...rest } = params || {};
const search = convertPageToOffsetSearchParams(rest);
if (status?.length) {
for (const s of status) {
search.append('status', s);
}
}

const res = await BaseResource._fetch({
path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}/test_runs`,
method: 'GET',
search,
});

const payload = res?.response as unknown as EnterpriseConnectionTestRunsPaginatedJSON | undefined;

return {
total_count: payload?.total_count ?? 0,
data: (payload?.data ?? []).map((row: EnterpriseConnectionTestRunJSON) => new EnterpriseConnectionTestRun(row)),
};
};

initializePaymentMethod: typeof initializePaymentMethod = params => {
return initializePaymentMethod(params);
};
Expand Down Expand Up @@ -455,3 +546,30 @@ export class User extends BaseResource implements UserResource {
};
}
}

/**
* Serializes `CreateMeEnterpriseConnectionParams` / `UpdateMeEnterpriseConnectionParams`
* for the `/me/enterprise_connections` FAPI endpoints.
*
* Uses `deepCamelToSnake` but preserves `saml.attributeMapping` and `customAttributes` as-is. Their keys are
* user-supplied data and must not be camel→snake transformed.
*/
function toMeEnterpriseConnectionBody(
params: CreateMeEnterpriseConnectionParams | UpdateMeEnterpriseConnectionParams,
): Record<string, unknown> {
const originalAttributeMapping =
params.saml && typeof params.saml === 'object' ? params.saml.attributeMapping : undefined;
const originalCustomAttributes = 'customAttributes' in params ? params.customAttributes : undefined;

const body = deepCamelToSnake(params) as Record<string, any>;

if (originalAttributeMapping !== undefined && body.saml && typeof body.saml === 'object') {
body.saml.attribute_mapping = originalAttributeMapping;
}

if (originalCustomAttributes !== undefined) {
body.custom_attributes = originalCustomAttributes;
}

return body;
}
Loading
Loading