From 6812d754ba9a446a3c3d0e669c7fa85bbad245d8 Mon Sep 17 00:00:00 2001 From: Brad Locking Date: Thu, 13 Aug 2026 11:27:45 +0100 Subject: [PATCH] fix(*): improve role autocomplete for server-side has() --- .changeset/has-role-autocomplete.md | 8 ++++ packages/nextjs/src/server/protect.ts | 5 ++- ...uthorizationFromSessionClaims.type.spec.ts | 44 +++++++++++++++++++ packages/shared/src/types/session.ts | 14 ++++-- 4 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 .changeset/has-role-autocomplete.md create mode 100644 packages/shared/src/types/__tests__/CheckAuthorizationFromSessionClaims.type.spec.ts diff --git a/.changeset/has-role-autocomplete.md b/.changeset/has-role-autocomplete.md new file mode 100644 index 00000000000..1474c52b101 --- /dev/null +++ b/.changeset/has-role-autocomplete.md @@ -0,0 +1,8 @@ +--- +'@clerk/shared': patch +'@clerk/nextjs': patch +--- + +Fix IDE autocomplete for custom Organization Roles in server-side `has({ role })` and `auth.protect({ role })`. + +Previously, defining roles via `ClerkAuthorization` typed the values correctly but editors often failed to suggest Role literals inside `has({ role: "…" })`, while Permission suggestions already worked. Role checks now use the same generic typing path as Permissions so Role completions appear as expected. diff --git a/packages/nextjs/src/server/protect.ts b/packages/nextjs/src/server/protect.ts index 1e72f7128fd..d37c4c951c3 100644 --- a/packages/nextjs/src/server/protect.ts +++ b/packages/nextjs/src/server/protect.ts @@ -14,6 +14,7 @@ import type { CheckAuthorizationParamsWithCustomPermissions, CheckAuthorizationWithCustomPermissions, OrganizationCustomPermissionKey, + OrganizationCustomRoleKey, } from '@clerk/shared/types'; import { constants as nextConstants } from '../constants'; @@ -43,8 +44,8 @@ export interface AuthProtect { * auth.protect({ permission: 'org:admin:example1' }); * auth.protect({ role: 'admin' }); */ -

( - params?: CheckAuthorizationParamsFromSessionClaims

, +

( + params?: CheckAuthorizationParamsFromSessionClaims, options?: AuthProtectOptions, ): Promise; diff --git a/packages/shared/src/types/__tests__/CheckAuthorizationFromSessionClaims.type.spec.ts b/packages/shared/src/types/__tests__/CheckAuthorizationFromSessionClaims.type.spec.ts new file mode 100644 index 00000000000..b343811415d --- /dev/null +++ b/packages/shared/src/types/__tests__/CheckAuthorizationFromSessionClaims.type.spec.ts @@ -0,0 +1,44 @@ +import { describe, expectTypeOf, it } from 'vitest'; + +import type { OrganizationCustomPermissionKey, OrganizationCustomRoleKey } from '../organizationMembership'; +import type { CheckAuthorizationFromSessionClaims, CheckAuthorizationParamsFromSessionClaims } from '../session'; + +type ParamsOfHas = Parameters[0]; + +describe('CheckAuthorizationFromSessionClaims', () => { + it('has({}) is allowed', () => { + expectTypeOf({} as const).toMatchTypeOf(); + }); + + it('has({ role }) is allowed', () => { + expectTypeOf({ role: 'org:admin' }).toMatchTypeOf(); + }); + + it('has({ permission }) is allowed', () => { + expectTypeOf({ + permission: 'org:feature:action', + }).toMatchTypeOf(); + }); + + it('has({ role, permission }) is NOT allowed', () => { + expectTypeOf({ + role: 'org:admin', + permission: 'org:feature:action', + }).not.toMatchTypeOf(); + }); + + it('accepts an explicit Role type parameter', () => { + type RoleParams = CheckAuthorizationParamsFromSessionClaims; + expectTypeOf({ role: 'org:admin' as const }).toMatchTypeOf(); + expectTypeOf({ + role: 'org:member' as const, + }).not.toMatchTypeOf(); + }); + + it('accepts an explicit Permission type parameter', () => { + type PermissionParams = CheckAuthorizationParamsFromSessionClaims<'org:reports:read', OrganizationCustomRoleKey>; + expectTypeOf({ + permission: 'org:reports:read' as const, + }).toMatchTypeOf(); + }); +}); diff --git a/packages/shared/src/types/session.ts b/packages/shared/src/types/session.ts index 878fd6e8ecb..811eff92d6c 100644 --- a/packages/shared/src/types/session.ts +++ b/packages/shared/src/types/session.ts @@ -148,19 +148,25 @@ type CheckAuthorizationParams = WithReverification< * System Permissions are not allowed since they are not included * in session claims and cannot be verified on the server side. */ -export type CheckAuthorizationFromSessionClaims =

( - isAuthorizedParams: CheckAuthorizationParamsFromSessionClaims

, +export type CheckAuthorizationFromSessionClaims = < + P extends OrganizationCustomPermissionKey, + R extends OrganizationCustomRoleKey = OrganizationCustomRoleKey, +>( + isAuthorizedParams: CheckAuthorizationParamsFromSessionClaims, ) => boolean; /** * @interface */ -export type CheckAuthorizationParamsFromSessionClaims

= WithReverification< +export type CheckAuthorizationParamsFromSessionClaims< + P extends OrganizationCustomPermissionKey, + R extends OrganizationCustomRoleKey = OrganizationCustomRoleKey, +> = WithReverification< | { /** * The [Role](https://clerk.com/docs/guides/organizations/control-access/roles-and-permissions) to check for. */ - role: OrganizationCustomRoleKey; + role: R; /** * The [Permission](https://clerk.com/docs/guides/organizations/control-access/roles-and-permissions) to check for. */