(app: Express, cache: Keyv)
| 160 | * @param cache - a cache to store presigned URLs so we don't needlessly create URLs for every download |
| 161 | */ |
| 162 | export const setupFileDownloadProxyHandler = (app: Express, cache: Keyv) => { |
| 163 | app.get("/file/*splat", async (req, res) => { |
| 164 | const key = (req.params as { splat: string[] }).splat.join("/"); |
| 165 | const urlOnly = req.query.urlOnly; |
| 166 | |
| 167 | // We purposefully return 404 for all error cases. |
| 168 | if (!key) { |
| 169 | res.sendStatus(404); |
| 170 | return; |
| 171 | } |
| 172 | |
| 173 | const keyParts = key.split("/"); |
| 174 | const [entityId, editionTimestamp, filename] = keyParts.slice(-3); |
| 175 | |
| 176 | if (!entityId || !editionTimestamp || !filename) { |
| 177 | res.status(400).json({ |
| 178 | error: `File path ${key} is invalid – should be of the form [EntityId]/[EditionTimestamp]/[Filename], with an optional leading [Prefix]/`, |
| 179 | }); |
| 180 | return; |
| 181 | } |
| 182 | if (!isEntityId(entityId)) { |
| 183 | res.status(400).json({ |
| 184 | error: `File path contains invalid entityId ${entityId} in ${key}`, |
| 185 | }); |
| 186 | return; |
| 187 | } |
| 188 | |
| 189 | const actorId = getActorIdFromRequest(req); |
| 190 | |
| 191 | const fileEntity = await getFileEntity( |
| 192 | req.context, |
| 193 | { actorId }, |
| 194 | { |
| 195 | entityId, |
| 196 | key, |
| 197 | }, |
| 198 | ); |
| 199 | |
| 200 | if (!fileEntity) { |
| 201 | res.status(404).json({ |
| 202 | error: `Could not find file entity ${entityId} with edition timestamp ${editionTimestamp}, either it does not exist or you do not have permission to access it.`, |
| 203 | }); |
| 204 | return; |
| 205 | } |
| 206 | |
| 207 | if (!isFileEntity(fileEntity)) { |
| 208 | res.status(400).json({ |
| 209 | error: `Found entity ${fileEntity.metadata.recordId.entityId} is not a file entity – has type(s) ${fileEntity.metadata.entityTypeIds.join(", ")}`, |
| 210 | }); |
| 211 | return; |
| 212 | } |
| 213 | |
| 214 | const { fileStorageKey } = simplifyProperties(fileEntity.properties); |
| 215 | if (!fileStorageKey) { |
| 216 | res.status(400).json({ |
| 217 | error: `File entity ${fileEntity.metadata.recordId.entityId} is missing the necessary properties for file retrieval`, |
| 218 | }); |
| 219 | return; |
no test coverage detected