Skip to content

Commit 8383df1

Browse files
authored
fix(shared): Reverse permissions array on parsePermissions to match bitmask correctly (#5622)
1 parent e60e3aa commit 8383df1

5 files changed

Lines changed: 61 additions & 36 deletions

File tree

.changeset/fluffy-teeth-decide.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
'@clerk/shared': patch
33
---
44

5-
Bug fix: Remove padding when parsing permissions from the JWT v2 format.
5+
Bug fix: Reverse permissions array on `parsePermissions` to match bitmask correctly.

packages/backend/src/tokens/__tests__/authObjects.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ describe('signedInAuthObject', () => {
117117

118118
const authObject = signedInAuthObject(mockAuthenticateContext, 'token', partialJwtPayload as JwtPayload);
119119

120-
expect(authObject.has({ permission: 'org:reservations:read' })).toBe(true);
121-
expect(authObject.has({ permission: 'org:reservations:manage' })).toBe(false);
120+
expect(authObject.has({ permission: 'org:reservations:read' })).toBe(false);
121+
expect(authObject.has({ permission: 'org:reservations:manage' })).toBe(true);
122122

123123
expect(authObject.has({ permission: 'org:support-chat:read' })).toBe(true);
124124
expect(authObject.has({ permission: 'org:support-chat:manage' })).toBe(true);

packages/clerk-js/bundlewatch.config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"files": [
33
{ "path": "./dist/clerk.js", "maxSize": "590kB" },
4-
{ "path": "./dist/clerk.browser.js", "maxSize": "73.69KB" },
4+
{ "path": "./dist/clerk.browser.js", "maxSize": "73.75KB" },
55
{ "path": "./dist/clerk.headless*.js", "maxSize": "55KB" },
66
{ "path": "./dist/ui-common*.js", "maxSize": "99KB" },
77
{ "path": "./dist/vendors*.js", "maxSize": "36KB" },

packages/shared/src/__tests__/jwtPayloadParser.test.ts

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ describe('JWTPayloadToAuthObjectProperties', () => {
3838
o: {
3939
id: 'org_xxxxxxx',
4040
rol: 'admin',
41-
slg: '/test',
41+
slg: 'org_test',
4242
per: 'read,manage',
4343
fpm: '3',
4444
},
@@ -48,32 +48,8 @@ describe('JWTPayloadToAuthObjectProperties', () => {
4848
...baseClaims,
4949
org_id: 'org_xxxxxxx',
5050
org_role: 'org:admin',
51-
org_slug: '/test',
52-
org_permissions: ['org:impersonation:read', 'org:impersonation:manage'],
53-
});
54-
expect(signedInAuthObjectV1).toEqual(signedInAuthObjectV2);
55-
});
56-
57-
test('produced auth object is the same for v1 and v2', () => {
58-
const { sessionClaims: v2Claims, ...signedInAuthObjectV2 } = JWTPayloadToAuthObjectProperties({
59-
...baseClaims,
60-
v: 2,
61-
fea: 'o:impersonation',
62-
o: {
63-
id: 'org_xxxxxxx',
64-
rol: 'admin',
65-
slg: '/test',
66-
per: 'read,manage',
67-
fpm: '3',
68-
},
69-
});
70-
71-
const { sessionClaims: v1Claims, ...signedInAuthObjectV1 } = JWTPayloadToAuthObjectProperties({
72-
...baseClaims,
73-
org_id: 'org_xxxxxxx',
74-
org_role: 'org:admin',
75-
org_slug: '/test',
76-
org_permissions: ['org:impersonation:read', 'org:impersonation:manage'],
51+
org_slug: 'org_test',
52+
org_permissions: ['org:impersonation:manage', 'org:impersonation:read'],
7753
});
7854
expect(signedInAuthObjectV1).toEqual(signedInAuthObjectV2);
7955
});
@@ -86,9 +62,9 @@ describe('JWTPayloadToAuthObjectProperties', () => {
8662
o: {
8763
id: 'org_xxxxxxx',
8864
rol: 'admin',
89-
slg: '/test',
65+
slg: 'org_test',
9066
per: 'read,manage',
91-
fpm: '2,3',
67+
fpm: '1,3',
9268
},
9369
});
9470

@@ -107,7 +83,7 @@ describe('JWTPayloadToAuthObjectProperties', () => {
10783
rol: 'admin',
10884
slg: 'org_slug',
10985
per: 'read,manage',
110-
fpm: '2,3',
86+
fpm: '1,3',
11187
},
11288
});
11389

@@ -126,7 +102,7 @@ describe('JWTPayloadToAuthObjectProperties', () => {
126102
rol: 'admin',
127103
slg: 'org_slug',
128104
per: 'read,manage',
129-
fpm: '2,3,2',
105+
fpm: '1,3,1',
130106
},
131107
});
132108

@@ -238,6 +214,51 @@ describe('JWTPayloadToAuthObjectProperties', () => {
238214
].sort(),
239215
);
240216
});
217+
218+
test('org permissions are constructed correctly case 4', () => {
219+
const { sessionClaims: v2Claims, ...signedInAuthObject } = JWTPayloadToAuthObjectProperties({
220+
...baseClaims,
221+
v: 2,
222+
fea: 'o:repositories,o:projects,o:webhooks,o:impersonation',
223+
o: {
224+
id: 'org_id',
225+
rol: 'admin',
226+
slg: 'org_slug',
227+
per: 'read,manage',
228+
fpm: '1,2,3',
229+
},
230+
});
231+
232+
expect(signedInAuthObject.orgPermissions?.sort()).toEqual(
233+
['org:repositories:read', 'org:projects:manage', 'org:webhooks:read', 'org:webhooks:manage'].sort(),
234+
);
235+
});
236+
237+
test('org permissions are constructed correctly case 5', () => {
238+
const { sessionClaims: v2Claims, ...signedInAuthObject } = JWTPayloadToAuthObjectProperties({
239+
...baseClaims,
240+
v: 2,
241+
fea: 'o:repositories,uo:projects,u:goldprofileborder',
242+
o: {
243+
id: 'org_id',
244+
rol: 'admin',
245+
slg: 'org_slug',
246+
per: 'read,create,update,delete,revoke',
247+
fpm: '7,21',
248+
},
249+
});
250+
251+
expect(signedInAuthObject.orgPermissions?.sort()).toEqual(
252+
[
253+
'org:repositories:read',
254+
'org:repositories:create',
255+
'org:repositories:update',
256+
'org:projects:read',
257+
'org:projects:update',
258+
'org:projects:revoke',
259+
].sort(),
260+
);
261+
});
241262
});
242263

243264
describe('splitByScope ', () => {

packages/shared/src/jwtPayloadParser.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ export const parsePermissions = ({ per, fpm }: { per?: string; fpm?: string }) =
1212
return { permissions: [], featurePermissionMap: [] };
1313
}
1414

15-
const permissions = per.split(',').map(p => p.trim());
15+
const permissions = per
16+
.split(',')
17+
.map(p => p.trim())
18+
.reverse();
1619

1720
// TODO: make this more efficient
1821
const featurePermissionMap = fpm
@@ -21,6 +24,7 @@ export const parsePermissions = ({ per, fpm }: { per?: string; fpm?: string }) =
2124
.map((permission: number) =>
2225
permission
2326
.toString(2)
27+
.padStart(permissions.length, '0')
2428
.split('')
2529
.map(bit => Number.parseInt(bit, 10)),
2630
)

0 commit comments

Comments
 (0)