diff --git a/README.md b/README.md index b2719e9a..950766f4 100644 --- a/README.md +++ b/README.md @@ -400,7 +400,7 @@ For multi-tenant uses: ```typescript // You can validate specific permissions -const validTenantPermissions = await descopeClient.validateTenantPermissions( +const validTenantPermissions = descopeClient.validateTenantPermissions( authInfo, 'my-tenant-ID', ['Permission to validate'], @@ -410,30 +410,51 @@ if (!validTenantPermissions) { } // Or validate roles directly -const validTenantRoles = await descopeClient.validateTenantRoles(authInfo, 'my-tenant-ID', [ +const validTenantRoles = descopeClient.validateTenantRoles(authInfo, 'my-tenant-ID', [ 'Role to validate', ]); if (!validTenantRoles) { // Deny access } + +// Or get the matched roles/permissions +const matchedTenantRoles = descopeClient.getMatchedTenantRoles(authInfo, 'my-tenant-ID', [ + 'Role to validate', + 'Another role to validate' +]); + +const matchedTenantPermissions = descopeClient.getMatchedTenantPermissions( + authInfo, + 'my-tenant-ID', + ['Permission to validate', 'Another permission to validate']], +); ``` When not using tenants use: ```typescript // You can validate specific permissions -const validPermissions = await descopeClient.validatePermissions(authInfo, [ - 'Permission to validate', -]); +const validPermissions = descopeClient.validatePermissions(authInfo, ['Permission to validate']); if (!validPermissions) { // Deny access } // Or validate roles directly -const validRoles = await descopeClient.validateRoles(authInfo, ['Role to validate']); +const validRoles = descopeClient.validateRoles(authInfo, ['Role to validate']); if (!validRoles) { // Deny access } + +// Or get the matched roles/permissions +const matchedRoles = descopeClient.getMatchedRoles(authInfo, [ + 'Role to validate', + 'Another role to validate', +]); + +const matchedPermissions = descopeClient.getMatchedPermissions(authInfo, [ + 'Permission to validate', + 'Another permission to validate', +]); ``` ### Logging Out diff --git a/lib/index.test.ts b/lib/index.test.ts index 972e8213..3eacf5ad 100644 --- a/lib/index.test.ts +++ b/lib/index.test.ts @@ -331,6 +331,43 @@ describe('sdk', () => { }); }); + describe('getMatchedPermissionsRoles', () => { + it('should always succeed with empty requirements', () => { + expect(sdk.getMatchedPermissions(permAuthInfo, [])).toStrictEqual([]); + expect(sdk.getMatchedTenantPermissions(permTenantAuthInfo, 'kuku', [])).toStrictEqual([]); + expect(sdk.getMatchedRoles(permAuthInfo, [])).toStrictEqual([]); + expect(sdk.getMatchedTenantPermissions(permTenantAuthInfo, 'kuku', [])).toStrictEqual([]); + }); + it('should return the matched permissions or roles', () => { + // all permissions matched + expect(sdk.getMatchedPermissions(permAuthInfo, ['foo'])).toStrictEqual(['foo']); + // some permissions are matched + expect( + sdk.getMatchedTenantPermissions(permTenantAuthInfo, 'kuku', ['foo', 'bar', 'qux']), + ).toStrictEqual(['foo', 'bar']); + // all roles matched + expect(sdk.getMatchedRoles(permAuthInfo, ['abc'])).toStrictEqual(['abc']); + // some roles are matched + expect( + sdk.getMatchedTenantRoles(permTenantAuthInfo, 'kuku', ['abc', 'xyz', 'tuv']), + ).toStrictEqual(['abc', 'xyz']); + }); + + it('should return empty list when there are no matched permissions or roles', () => { + // no permissions matched + expect(sdk.getMatchedPermissions(permAuthInfo, ['qux'])).toStrictEqual([]); + expect( + sdk.getMatchedTenantPermissions(permTenantAuthInfo, 'kuku', ['qux', 'zuk']), + ).toStrictEqual([]); + // no roles matched + expect(sdk.getMatchedRoles(permAuthInfo, ['tuv'])).toStrictEqual([]); + // some roles are matched + expect(sdk.getMatchedTenantRoles(permTenantAuthInfo, 'kuku', ['tuv', 'rum'])).toStrictEqual( + [], + ); + }); + }); + describe('withCookies', () => { describe('when no cookie', () => { const paths = [ diff --git a/lib/index.ts b/lib/index.ts index 51599750..a44c17a6 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -219,12 +219,23 @@ const nodeSdk = ({ managementKey, publicKey, ...config }: NodeSdkArgs) => { * @returns true if all permissions exist, false otherwise */ validatePermissions(authInfo: AuthenticationInfo, permissions: string[]): boolean { - return sdk.validateTenantPermissions(authInfo, null, permissions); + return sdk.validateTenantPermissions(authInfo, '', permissions); + }, + + /** + * Retrieves the permissions from JWT top level claims that match the specified permissions list + * @param authInfo JWT parsed info containing the permissions + * @param permissions List of permissions to match against the JWT claims + * @returns An array of permissions that are both in the JWT claims and the specified list. Returns an empty array if no matches are found + */ + getMatchedPermissions(authInfo: AuthenticationInfo, permissions: string[]): string[] { + return sdk.getMatchedTenantPermissions(authInfo, '', permissions); }, /** * Make sure that all given permissions exist on the parsed JWT tenant claims * @param authInfo JWT parsed info + * @param tenant tenant to validate the permissions for * @param permissions list of permissions to make sure they exist on te JWT claims * @returns true if all permissions exist, false otherwise */ @@ -240,6 +251,24 @@ const nodeSdk = ({ managementKey, publicKey, ...config }: NodeSdkArgs) => { return permissions.every((perm) => granted.includes(perm)); }, + /** + * Retrieves the permissions from JWT tenant claims that match the specified permissions list + * @param authInfo JWT parsed info containing the permissions + * @param tenant tenant to match the permissions for + * @param permissions List of permissions to match against the JWT claims + * @returns An array of permissions that are both in the JWT claims and the specified list. Returns an empty array if no matches are found + * */ + getMatchedTenantPermissions( + authInfo: AuthenticationInfo, + tenant: string, + permissions: string[], + ): string[] { + if (tenant && !isUserAssociatedWithTenant(authInfo, tenant)) return []; + + const granted = getAuthorizationClaimItems(authInfo, permissionsClaimName, tenant); + return permissions.filter((perm) => granted.includes(perm)); + }, + /** * Make sure that all given roles exist on the parsed JWT top level claims * @param authInfo JWT parsed info @@ -247,12 +276,23 @@ const nodeSdk = ({ managementKey, publicKey, ...config }: NodeSdkArgs) => { * @returns true if all roles exist, false otherwise */ validateRoles(authInfo: AuthenticationInfo, roles: string[]): boolean { - return sdk.validateTenantRoles(authInfo, null, roles); + return sdk.validateTenantRoles(authInfo, '', roles); + }, + + /** + * Retrieves the roles from JWT top level claims that match the specified roles list + * @param authInfo JWT parsed info containing the roles + * @param roles List of roles to match against the JWT claims + * @returns An array of roles that are both in the JWT claims and the specified list. Returns an empty array if no matches are found + */ + getMatchedRoles(authInfo: AuthenticationInfo, roles: string[]): string[] { + return sdk.getMatchedTenantRoles(authInfo, '', roles); }, /** * Make sure that all given roles exist on the parsed JWT tenant claims * @param authInfo JWT parsed info + * @param tenant tenant to validate the roles for * @param roles list of roles to make sure they exist on te JWT claims * @returns true if all roles exist, false otherwise */ @@ -263,6 +303,20 @@ const nodeSdk = ({ managementKey, publicKey, ...config }: NodeSdkArgs) => { const membership = getAuthorizationClaimItems(authInfo, rolesClaimName, tenant); return roles.every((role) => membership.includes(role)); }, + + /** + * Retrieves the roles from JWT tenant claims that match the specified roles list + * @param authInfo JWT parsed info containing the roles + * @param tenant tenant to match the roles for + * @param roles List of roles to match against the JWT claims + * @returns An array of roles that are both in the JWT claims and the specified list. Returns an empty array if no matches are found + */ + getMatchedTenantRoles(authInfo: AuthenticationInfo, tenant: string, roles: string[]): string[] { + if (tenant && !isUserAssociatedWithTenant(authInfo, tenant)) return []; + + const membership = getAuthorizationClaimItems(authInfo, rolesClaimName, tenant); + return roles.filter((role) => membership.includes(role)); + }, }; return wrapWith(