Feat/restrict metadata - #23
Conversation
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@johnf How do we fix this error? |
You need to add more tests to cover any extra code you added. You can check the coverage report in the web version Also, AI is excellent at doing this for you. |
|
@alvinsw Can you let me know when you have fixed the coverage issues, and then I'll review |
|
We fixed with Copilot, pushed to a new branch to see what was was wrong |
|
@johnf coverage has been fix, can you review please. |
johnf
left a comment
There was a problem hiding this comment.
- README.md needs updates to explain the new functionality
I'm not sure that this approach is general enough.
- resolveValidLicenses doesn't know if this is a meta or content license lookup
- What if I wanted to add other restrictions e.g. maybe I have special metadata in the entity I want to match against.
Let's discuss on Thursday I think we may need an approach where you can pass in a more complex Prisma element rather than just a list of licenses.
| return reply.code(404).send(createNotFoundError('The requested entity was not found', id)); | ||
| } | ||
|
|
||
| const standardEntity = { |
| const entity = { | ||
| ...dbFile.entity, | ||
| ...baseFileTransformer(dbFile), | ||
| memberOf: dbFile.entity.memberOf ? (refMap.get(dbFile.entity.memberOf) ?? null) : null, |
There was a problem hiding this comment.
Why did this code change? Is this fixing a bug?
There was a problem hiding this comment.
Because originally it only passes standardFile which does not have the metadataLicenseId and contentLicenseId required to determine access in the fileAccessTransformer function.
| let filters = request.body.filters; | ||
| if (resolveValidLicenses) { | ||
| filters = filters || {}; | ||
| filters.metadataLicenseId = (await resolveValidLicenses({ request, fastify })) || []; |
There was a problem hiding this comment.
I think this is right but we should have a quick discussion on Thursday.
Is it right that even for files we filter here by the metadataLicense (I think it is) but I think we need to document the rationale for this if we havne't already in the README.md
There was a problem hiding this comment.
What do you mean by "the rationale for this"? We can just discuss them here.
If the metadata access for files is restricted then such files should should be shown in the search result.
| size: number; | ||
| }; | ||
|
|
||
| export type FileEntity = StandardFile & StandardEntity; |
There was a problem hiding this comment.
This doesn't logically make sense. Why is it needed?
There was a problem hiding this comment.
As mentioned elsewhere, StandardFile type doesn't have all the required fields.
| * ``` | ||
| */ | ||
| export const AllPublicFileAccessTransformer = (file: StandardFile): AuthorisedFile => ({ | ||
| export const AllPublicFileAccessTransformer = (file: FileEntity): AuthorisedFile => ({ |
There was a problem hiding this comment.
This feels like a breaking change in the API. Why is this necessary?
There was a problem hiding this comment.
The file access transformer needs to check the the metadataLicenseId too, which is not defined in the StandardFile.
|
@alvinsw I also asked Claude to review the PR. It picked up somethings I've mentioned but also some other items worth looking at StandardsHard violations (documented standards)README.md — public API surface undocumented. resolveValidLicenses (src/app.ts:87) is a new consumer-facing Options field. README documents every other option (accessTransformer, queryBuilderClass, fileHandler, …); this one gets nothing. CLAUDE.md: "published npm library … be mindful of the public API surface". README.md stale — "Applied Routes" (README:489-495). "The /file/:id endpoint … uses the fileHandler system, not file transformers." src/routes/file.ts now runs fileAccessTransformer on both GET and HEAD. Same section's "File Transformation Pipeline" (README:481-487) no longer describes stage 1: files.ts feeds the access transformer {...dbFile.entity, CLAUDE.md pipeline — src/routes/crate.ts:42-46, 88-93 skips the base transformer. It hand-builds {...entity, memberOf: {id: entity.memberOf || '', name: ''}} from the raw Prisma row, so consumer accessTransformers receive DB fields (meta, createdAt) and a fabricated name: '' instead of resolved references. entity.ts/search.ts use baseEntityTransformer + resolveEntityReferences. Semver. FileAccessTransformer and AllPublicFileAccessTransformer change input from StandardFile to FileEntity, and AuthorisedFile gains all StandardEntity fields — consumer implementations that construct an AuthorisedFile break. Commits are feat:, no !/BREAKING CHANGE footer (cf. aa5c6b7 feat!:). Baseline smells (judgement calls)
Convention drift worth a look
Spec(a) Missing / partial/file doesn't implement the stated condition. Spec: "For endpoints /crate, /entity, /file: responds with 403 when access.metadata returned by accessTransformer function is false." src/routes/file.ts (both HEAD and GET) gates on !authorisedFile.access.content, not access.metadata — and FileAccessInfo has no metadata field at all (src/transformers/default.ts: "File metadata is No docs for the new public option. resolveValidLicenses is a new consumer-facing option in src/app.ts but appears nowhere in README.md, unlike every other option (accessTransformer, queryBuilderClass, etc.). (b) Scope creepBreaking public API change to FileAccessTransformer. Not asked for anywhere in the spec. src/types/transformers.ts changes the parameter from StandardFile to FileEntity (StandardFile & StandardEntity), and AuthorisedFile gains all entity fields. Every consumer's fileAccessTransformer and the documented example in README.md:414 now receive a different shape. This is a /files response shape changed. src/routes/files.ts now spreads the raw Prisma entity (...dbFile.entity) into each returned file, so /files responses leak meta, createdAt, updatedAt, name, description, entityType, and both licence IDs. Spec asked only to "filter the search query". (c) Implemented but looks wrong/crate builds a malformed entity for the access transformer. src/routes/crate.ts (both routes) uses memberOf: { id: entity.memberOf || '', name: '' } — never null and never a resolved name — instead of baseEntityTransformer + resolveEntityReferences as /entity does. A consumer transformer keying on memberOf/rootCollection (the common case) will see {id:'',name:''} for Falsy vs false. Spec says "when access.metadata ... is false"; all three sites use !access.metadata, so undefined also 403s. Probably fine, but it is stricter than specified. /search licence filter likely matches nothing. filters.metadataLicenseId is pushed through queryBuilder.buildQuery as terms: { metadataLicenseId: [...] } on a raw field; every other exact-match field in the codebase uses .keyword (queryBuilder.ts:31) and metadataLicenseId is absent from the index mapping in src/test/integration.setup.ts. Under dynamic mapping it becomes /search clobbers caller filters and mutates request.body.filters in place; a caller-supplied metadataLicenseId filter is silently replaced rather than intersected. |
Added access restriction functionality based on the:
/crate,/entity,/file: responds with 403 whenaccess.metadatareturned by accessTransformer function is false./search,/entities,/files: Use the resolveValidLicenses helper function (if it is specified in the options) to retrieve a list of valid licenses granted to the logged-in user and use the list to filter the search query.