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
5 changes: 5 additions & 0 deletions .changeset/stale-pans-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clerk/backend": minor
---

Updates `organizationPatterns` to take precedence over `personalAccountPatterns` in `organizationSyncOptions`
11 changes: 6 additions & 5 deletions packages/backend/src/tokens/__tests__/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,14 +298,15 @@ export default (QUnit: QUnit) => {
},
},
{
name: 'personal account match precedes org match',
name: 'org match match precedes personal account',
whenOrgSyncOptions: {
organizationPatterns: ['/personal-account'], // bad practice
personalAccountPatterns: ['/personal-account'],
personalAccountPatterns: ['/', '/(.*)'], // Personal account captures everything
organizationPatterns: ['/orgs/:slug'], // that isn't org scoped
},
whenAppRequestPath: '/personal-account',
whenAppRequestPath: '/orgs/my-org',
thenExpectActivationEntity: {
type: 'personalAccount',
type: 'organization',
organizationSlug: 'my-org',
},
},
{
Expand Down
32 changes: 16 additions & 16 deletions packages/backend/src/tokens/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -735,22 +735,6 @@ export function getOrganizationSyncTarget(
return null;
}

// Check for personal account activation
if (matchers.PersonalAccountMatcher) {
let personalResult: Match<Partial<Record<string, string | string[]>>>;
try {
personalResult = matchers.PersonalAccountMatcher(url.pathname);
} catch (e) {
// Intentionally not logging the path to avoid potentially leaking anything sensitive
console.error(`Failed to apply personal account pattern "${options.personalAccountPatterns}" to a path`, e);
return null;
}

if (personalResult) {
return { type: 'personalAccount' };
}
}

// Check for organization activation
if (matchers.OrganizationMatcher) {
let orgResult: Match<Partial<Record<string, string | string[]>>>;
Expand All @@ -776,6 +760,22 @@ export function getOrganizationSyncTarget(
);
}
}

// Check for personal account activation
if (matchers.PersonalAccountMatcher) {
let personalResult: Match<Partial<Record<string, string | string[]>>>;
try {
personalResult = matchers.PersonalAccountMatcher(url.pathname);
} catch (e) {
// Intentionally not logging the path to avoid potentially leaking anything sensitive
console.error(`Failed to apply personal account pattern "${options.personalAccountPatterns}" to a path`, e);
return null;
}

if (personalResult) {
return { type: 'personalAccount' };
}
}
return null;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/tokens/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export type OrganizationSyncOptions = {
* organization-related fields will be set to null. The server component must detect this and respond
* with an appropriate error (e.g., notFound()).
*
* If the route also matches the personalAccountPatterns, the personalAccountPattern takes precedence.
* If the route also matches the personalAccountPatterns, this takes precedence.
*
* Must have a path token named either ":id" (matches a clerk organization ID) or ":slug" (matches a clerk
* organization slug).
Expand All @@ -50,7 +50,7 @@ export type OrganizationSyncOptions = {

/**
* URL patterns for resources in the context of a clerk personal account (user-specific, outside any organization).
* If the route also matches the organizationPattern, this takes precedence.
* If the route also matches the organizationPattern, the organizationPatterns takes precedence.
*
* Common examples:
* - ["/user", "/user/(.*)"]
Expand Down
Loading