a collection of lightweight TypeScript packages for AT Protocol, the protocol powering Bluesky
atproto bluesky typescript npm
101
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(lexicon-doc): rectify permission set validation once again

Mary 32c0da67 dcca9fd6

+82 -62
+5
.changeset/shiny-hotels-glow.md
··· 1 + --- 2 + '@atcute/lexicon-doc': patch 3 + --- 4 + 5 + rectify permission set validation once again
+31 -49
packages/lexicons/lexicon-doc/lib/builder.ts
··· 1 - import { type AtprotoAudience, type Nsid } from '@atcute/lexicons/syntax'; 1 + import { type Nsid } from '@atcute/lexicons/syntax'; 2 2 3 3 import type * as t from './types.js'; 4 4 ··· 1298 1298 export interface LexRepoPermissionBuilder { 1299 1299 type: 'repo-permission'; 1300 1300 /** collections this permission covers */ 1301 - collection: '*' | (Nsid | LexRecordBuilder)[]; 1302 - /** allowed actions */ 1301 + collection: (Nsid | LexRecordBuilder)[]; 1302 + /** allowed actions; if omitted, all operations are permitted */ 1303 1303 action?: RepoAction[]; 1304 1304 } 1305 1305 ··· 1311 1311 export const repoPermission = (def: Omit<LexRepoPermissionBuilder, 'type'>): LexRepoPermissionBuilder => { 1312 1312 const { collection } = def; 1313 1313 1314 - if (Array.isArray(collection)) { 1315 - if (collection.length === 0) { 1316 - throw new Error(`repo-permission/collection: value can't be empty`); 1317 - } 1314 + if (collection.length === 0) { 1315 + throw new Error(`repo-permission/collection: value can't be empty`); 1318 1316 } 1319 1317 1320 1318 return { ...def, type: 'repo-permission' }; ··· 1325 1323 */ 1326 1324 export interface LexRpcPermissionBuilder { 1327 1325 type: 'rpc-permission'; 1328 - /** allowed rpc methods or wildcard */ 1329 - lxm: '*' | (Nsid | LexXrpcQueryBuilder | LexXrpcProcedureBuilder | LexXrpcSubscriptionBuilder)[]; 1330 - /** allowed audience or wildcard */ 1331 - aud?: '*' | AtprotoAudience; 1326 + /** allowed rpc methods */ 1327 + lxm: (Nsid | LexXrpcQueryBuilder | LexXrpcProcedureBuilder | LexXrpcSubscriptionBuilder)[]; 1328 + /** audience */ 1329 + aud?: '*'; 1332 1330 /** inherit the audience from the including permission scope */ 1333 1331 inheritAud?: boolean; 1334 1332 } ··· 1341 1339 export const rpcPermission = (def: Omit<LexRpcPermissionBuilder, 'type'>): LexRpcPermissionBuilder => { 1342 1340 const { lxm, aud, inheritAud = false } = def; 1343 1341 1344 - if (Array.isArray(lxm)) { 1345 - if (lxm.length === 0) { 1346 - throw new Error(`rpc-permission/lxm: value can't be empty`); 1347 - } 1348 - } 1349 - 1350 - if (aud === '*' && lxm === '*') { 1351 - throw new Error(`rpc-permission: aud and lxm can't both be '*'`); 1342 + if (lxm.length === 0) { 1343 + throw new Error(`rpc-permission/lxm: value can't be empty`); 1352 1344 } 1353 1345 1354 1346 if (inheritAud) { ··· 1369 1361 case 'repo-permission': { 1370 1362 const { collection, action } = def; 1371 1363 1372 - let builtCollection: string[]; 1373 - if (collection === '*') { 1374 - builtCollection = ['*']; 1375 - } else { 1376 - builtCollection = collection.map((item, index) => { 1377 - if (typeof item === 'string') { 1378 - return item; 1379 - } 1364 + const builtCollection = collection.map((item, index) => { 1365 + if (typeof item === 'string') { 1366 + return item; 1367 + } 1380 1368 1381 - const defPath = ctx.toplevelDefs.get(item); 1382 - if (defPath === undefined) { 1383 - throw new Error(`${ctx.dotPath}/collection/${index}: must be defined as a top-level definition`); 1384 - } 1369 + const defPath = ctx.toplevelDefs.get(item); 1370 + if (defPath === undefined) { 1371 + throw new Error(`${ctx.dotPath}/collection/${index}: must be defined as a top-level definition`); 1372 + } 1385 1373 1386 - return toLexUri(defPath); 1387 - }); 1388 - } 1374 + return toLexUri(defPath); 1375 + }); 1389 1376 1390 1377 return { 1391 1378 action: action, ··· 1397 1384 case 'rpc-permission': { 1398 1385 const { lxm, aud, inheritAud } = def; 1399 1386 1400 - let builtLxm: string[]; 1401 - if (lxm === '*') { 1402 - builtLxm = ['*']; 1403 - } else { 1404 - builtLxm = lxm.map((item, index) => { 1405 - if (typeof item === 'string') { 1406 - return item; 1407 - } 1387 + const builtLxm = lxm.map((item, index) => { 1388 + if (typeof item === 'string') { 1389 + return item; 1390 + } 1408 1391 1409 - const defPath = ctx.toplevelDefs.get(item); 1410 - if (defPath === undefined) { 1411 - throw new Error(`${ctx.dotPath}/lxm/${index}: must be defined as a top-level definition`); 1412 - } 1392 + const defPath = ctx.toplevelDefs.get(item); 1393 + if (defPath === undefined) { 1394 + throw new Error(`${ctx.dotPath}/lxm/${index}: must be defined as a top-level definition`); 1395 + } 1413 1396 1414 - return toLexUri(defPath); 1415 - }); 1416 - } 1397 + return toLexUri(defPath); 1398 + }); 1417 1399 1418 1400 return { 1419 1401 aud: aud,
+46 -13
packages/lexicons/lexicon-doc/lib/refinements.ts
··· 1 - import { isAtprotoAudience } from '@atcute/identity'; 2 1 import { isLanguageCode, isNsid } from '@atcute/lexicons/syntax'; 3 2 4 3 import { isWithinGraphemeBounds, isWithinUtf8Bounds } from './internal/utils.js'; ··· 701 700 return refineLexObject({ type: 'object', required: spec.required, properties: spec.properties }, deep); 702 701 }; 703 702 703 + const REPO_ACTIONS: string[] = ['create', 'update', 'delete']; 704 + 704 705 /** 705 706 * validates constraints in lexicon permission definitions. 706 707 * @param spec permission definition to validate ··· 712 713 713 714 switch (resource) { 714 715 case 'repo': { 715 - const { collection } = spec; 716 + const { collection, action } = spec as { collection?: unknown; action?: unknown }; 716 717 718 + // validate collection 717 719 if (!Array.isArray(collection)) { 718 720 issues.push({ 719 721 message: `collection must be an array`, ··· 733 735 message: `collection entries must be strings`, 734 736 path: ['collection', idx], 735 737 }); 738 + } else if (entry === '*') { 739 + issues.push({ 740 + message: `collection can't be a wildcard`, 741 + path: ['collection', idx], 742 + }); 736 743 } else if (!isNsid(entry)) { 737 744 issues.push({ 738 745 message: `invalid collection nsid`, ··· 742 749 } 743 750 } 744 751 752 + // validate action 753 + if (action !== undefined) { 754 + if (!Array.isArray(action)) { 755 + issues.push({ 756 + message: `action must be an array`, 757 + path: ['action'], 758 + }); 759 + } else { 760 + for (let idx = 0, len = action.length; idx < len; idx++) { 761 + const entry = action[idx]; 762 + 763 + if (typeof entry !== 'string') { 764 + issues.push({ 765 + message: `action entries must be strings`, 766 + path: ['action', idx], 767 + }); 768 + } else if (!REPO_ACTIONS.includes(entry)) { 769 + issues.push({ 770 + message: `invalid action`, 771 + path: ['action', idx], 772 + }); 773 + } 774 + } 775 + } 776 + } 777 + 745 778 break; 746 779 } 747 780 case 'rpc': { 748 - const { lxm, aud, inheritAud } = spec; 781 + const { lxm, aud, inheritAud } = spec as { lxm?: unknown; aud?: unknown; inheritAud?: unknown }; 749 782 783 + // validate lxm 750 784 if (!Array.isArray(lxm)) { 751 785 issues.push({ 752 786 message: `lxm must be an array`, ··· 758 792 path: ['lxm'], 759 793 }); 760 794 } else { 761 - if (aud === '*' && lxm.includes('*')) { 762 - issues.push({ 763 - message: `aud and lxm can't both be wildcards`, 764 - path: ['aud'], 765 - }); 766 - } 767 - 768 795 for (let idx = 0, len = lxm.length; idx < len; idx++) { 769 796 const entry = lxm[idx]; 770 797 ··· 776 803 continue; 777 804 } 778 805 779 - if (entry !== '*' && !isNsid(entry)) { 806 + if (entry === '*') { 807 + issues.push({ 808 + message: `lxm can't be a wildcard`, 809 + path: ['lxm', idx], 810 + }); 811 + } else if (!isNsid(entry)) { 780 812 issues.push({ 781 813 message: `invalid lxm nsid`, 782 814 path: ['lxm', idx], ··· 785 817 } 786 818 } 787 819 820 + // validate inheritAud and aud 788 821 if (inheritAud !== undefined && typeof inheritAud !== 'boolean') { 789 822 issues.push({ 790 823 message: `inheritAud must be a boolean`, ··· 807 840 message: `aud must be a string`, 808 841 path: ['aud'], 809 842 }); 810 - } else if (aud !== '*' && !isAtprotoAudience(aud)) { 843 + } else if (aud !== '*') { 811 844 issues.push({ 812 - message: `invalid audience`, 845 + message: `aud must be a wildcard`, 813 846 path: ['aud'], 814 847 }); 815 848 }