r2-explorer 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -19
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/src/buckets/api/multipart/createUpload.d.ts +1 -4
- package/dist/src/interfaces.d.ts +1 -0
- package/package.json +2 -2
- package/bin/cli.js +0 -29970
- package/dist/src/buckets/api/downloadObject.d.ts +0 -8
- package/dist/src/buckets/api/renameObject.d.ts +0 -8
- package/dist/src/buckets/api/uploadObject.d.ts +0 -10
- package/dist/src/server/api/createFolder.d.ts +0 -8
- package/dist/src/server/api/deleteObject.d.ts +0 -10
- package/dist/src/server/api/downloadObject.d.ts +0 -8
- package/dist/src/server/api/listBuckets.d.ts +0 -14
- package/dist/src/server/api/listObjects.d.ts +0 -11
- package/dist/src/server/api/multipart/completeUpload.d.ts +0 -11
- package/dist/src/server/api/multipart/createUpload.d.ts +0 -11
- package/dist/src/server/api/multipart/partUpload.d.ts +0 -8
- package/dist/src/server/api/renameObject.d.ts +0 -10
- package/dist/src/server/api/uploadObject.d.ts +0 -10
package/README.md
CHANGED
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
# R2-Explorer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<em>A Google Drive Interface for your Cloudflare R2 Buckets!</em>
|
|
5
|
+
</p>
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
<p>
|
|
8
|
+
This project is deployed/self-hosted in your own Cloudflare Account as a Worker, and no credential/token is required to
|
|
9
|
+
start using it.
|
|
10
|
+
</p>
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
- Live Demo: [demo.r2explorer.dev](https://demo.r2explorer.dev)
|
|
12
|
+
---
|
|
10
13
|
|
|
14
|
+
**Documentation**: <a href="https://r2explorer.dev" target="_blank">https://r2explorer.dev</a>
|
|
15
|
+
|
|
16
|
+
**Live Demo**: <a href="https://demo.r2explorer.dev" target="_blank">https://demo.r2explorer.dev</a>
|
|
17
|
+
|
|
18
|
+
---
|
|
11
19
|
|
|
12
20
|
## Features
|
|
13
21
|
|
|
14
22
|
- [Email Explorer](https://r2explorer.dev/guides/setup-email-explorer/) (using Cloudflare Email Routing)
|
|
23
|
+
- [Cloudflare Access Authentication](https://r2explorer.dev/getting-started/security/)
|
|
15
24
|
- Very quick bucket/folder navigation
|
|
16
25
|
- pdf, image, txt, markdown, csv, etc in-browser preview
|
|
17
26
|
- Drag-and-Drop upload
|
|
18
27
|
- Multiple files and folder uploads
|
|
19
28
|
- Create folders
|
|
20
|
-
- Rename files
|
|
21
|
-
- Download files
|
|
22
|
-
- Delete files
|
|
29
|
+
- Upload/Rename/Download/Delete files
|
|
23
30
|
- Right click in file for extra options
|
|
24
31
|
- Multipart upload for big files
|
|
25
|
-
- Cloudflare Access validation using jwt
|
|
26
32
|
|
|
27
33
|
## Getting Started
|
|
28
34
|
|
|
@@ -60,13 +66,3 @@ wrangler publish
|
|
|
60
66
|
|
|
61
67
|
- Rename files with special characters is not possible with
|
|
62
68
|
current [sdk issue here](https://github.com/aws/aws-sdk-js/issues/1949)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
## Development
|
|
66
|
-
|
|
67
|
-
Publish Dashboard into dev branch
|
|
68
|
-
```
|
|
69
|
-
cd packages/dashboard/
|
|
70
|
-
npm run build
|
|
71
|
-
wrangler pages publish --branch dev --project-name r2-explorer-dashboard dist/
|
|
72
|
-
```
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("itty-router"),t=require("@cloudflare/itty-router-openapi"),a=require("zod");const s=!0,o=!1,n="1.0.0";class r extends t.OpenAPIRoute{static schema={operationId:"get-bucket-list",tags:["Buckets"],summary:"List buckets"};async handle(e,t,a,s){const o=[];for(const[e,a]of Object.entries(t))a.get&&a.put&&o.push({name:e});return{buckets:o}}}class c extends t.OpenAPIRoute{static schema={operationId:"get-bucket-list-objects",tags:["Buckets"],summary:"List objects",parameters:{bucket:t.Path(String),limit:t.Query(a.z.number().optional()),prefix:t.Query(a.z.string().optional().describe("base64 encoded prefix")),cursor:t.Query(a.z.string().optional()),delimiter:t.Query(a.z.string().optional()),include:t.Query(a.z.string().array().optional())}};async handle(e,t,a,s){const o=t[s.params.bucket];return await o.list({limit:s.query.limit,prefix:s.query.prefix?decodeURIComponent(escape(atob(s.query.prefix))):void 0,cursor:s.query.cursor,delimiter:s.query.delimiter,include:s.query.include})}}class i extends t.OpenAPIRoute{static schema={operationId:"post-bucket-move-object",tags:["Buckets"],summary:"Move object",parameters:{bucket:t.Path(String)},requestBody:{oldKey:a.z.string().optional().describe("base64 encoded file key"),newKey:a.z.string().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.oldKey))),r=decodeURIComponent(escape(atob(s.body.newKey))),c=await o.get(n),i=await o.put(r,c.body,{customMetadata:c.customMetadata,httpMetadata:c.httpMetadata});return await o.delete(n),i}}class u extends t.OpenAPIRoute{static schema={operationId:"post-bucket-create-folder",tags:["Buckets"],summary:"Create folder",parameters:{bucket:t.Path(String)},requestBody:{key:a.z.string().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.key)));return await o.put(n,"R2 Explorer Folder")}}class d extends t.OpenAPIRoute{static schema={operationId:"post-bucket-upload-object",tags:["Buckets"],summary:"Upload object",parameters:{bucket:t.Path(String),key:t.Query(a.z.string().optional().describe("base64 encoded file key")),customMetadata:t.Query(a.z.string().optional().describe("base64 encoded json string")),httpMetadata:t.Query(a.z.string().optional().describe("base64 encoded json string"))}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket];let n,r,c=decodeURIComponent(escape(atob(s.query.key)));return s.query.customMetadata&&(n=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(r=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata))))),await o.put(c,e.body,{customMetadata:n,httpMetadata:r})}}class p extends t.OpenAPIRoute{static schema={operationId:"post-bucket-delete-object",tags:["Buckets"],summary:"Delete object",parameters:{bucket:t.Path(String)},requestBody:{key:a.z.string().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.key)));return await o.delete(n)}}class l extends t.OpenAPIRoute{static schema={operationId:"post-multipart-create-upload",tags:["Multipart"],summary:"Create upload",parameters:{bucket:t.Path(String),key:t.Query(a.z.string().optional().describe("base64 encoded file key")),customMetadata:t.Query(a.z.string().optional().describe("base64 encoded json string")),httpMetadata:t.Query(a.z.string().optional().describe("base64 encoded json string"))}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.query.key)));let r,c;s.query.customMetadata&&(r=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(c=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata)))));const i=await o.createMultipartUpload(n,{customMetadata:r,httpMetadata:c});return{uploadId:i.uploadId,key:i.key}}}class m extends t.OpenAPIRoute{static schema={operationId:"post-multipart-part-upload",tags:["Multipart"],summary:"Part upload",parameters:{bucket:t.Path(String),key:t.Query(a.z.string().optional().describe("base64 encoded file key")),uploadId:t.Query(String),partNumber:t.Query(t.Int)}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.query.key))),r=o.resumeMultipartUpload(n,s.query.uploadId);try{return await r.uploadPart(s.query.partNumber,e.body)}catch(e){return new Response(e.message,{status:400})}}}class y extends t.OpenAPIRoute{static schema={operationId:"post-multipart-complete-upload",tags:["Multipart"],summary:"Complete upload",parameters:{bucket:t.Path(String)},requestBody:{uploadId:String,key:a.z.string().optional().describe("base64 encoded file key"),parts:[{etag:String,partNumber:t.Int}]}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=s.body.uploadId,r=decodeURIComponent(escape(atob(s.body.key))),c=s.body.parts,i=await o.resumeMultipartUpload(r,n);try{return{success:!0,str:await i.complete(c)}}catch(e){return Response.json({msg:e.message},{status:400})}}}class b extends t.OpenAPIRoute{static schema={operationId:"get-bucket-object",tags:["Buckets"],summary:"Get Object",parameters:{bucket:t.Path(String),key:t.Path(a.z.string().optional().describe("base64 encoded file key"))},responses:{200:{description:"File binary",schema:a.z.string().openapi({format:"binary"})}}};async handle(e,t,a,s){const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.params.key))),r=await o.get(n);if(null===r)return Response.json({msg:"Object Not Found"},{status:404});const c=new Headers;return r.writeHttpMetadata(c),c.set("etag",r.httpEtag),c.set("Content-Disposition",`attachment; filename="${n.split("/").pop()}"`),new Response(r.body,{headers:c})}}class g extends t.OpenAPIRoute{static schema={operationId:"Head-bucket-object",tags:["Buckets"],summary:"Get Object",parameters:{bucket:t.Path(String),key:t.Query(a.z.string().optional().describe("base64 encoded file key"))}};async handle(e,t,a,s){const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.params.key))),r=await o.head(n);return null===r?Response.json({msg:"Object Not Found"},{status:404}):r}}class f extends t.OpenAPIRoute{static schema={operationId:"post-bucket-put-object-metadata",tags:["Buckets"],summary:"Update object metadata",parameters:{bucket:t.Path(String),key:t.Query(a.z.string().describe("base64 encoded file key")),customMetadata:t.Query(a.z.string().describe("base64 encoded json string"))}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.query.key))),r=decodeURIComponent(escape(atob(s.query.key))),c=await o.get(n);return await o.put(n,c.body,{customMetadata:r})}}const h=t.OpenAPIRouter({base:"/api/buckets",raiseUnknownParameters:s,generateOperationIds:o});h.get("",r),h.get("/:bucket",c),h.post("/:bucket/move",i),h.post("/:bucket/folder",u),h.post("/:bucket/upload",d),h.post("/:bucket/multipart/create",l),h.post("/:bucket/multipart/upload",m),h.post("/:bucket/multipart/complete",y),h.post("/:bucket/delete",p),h.head("/:bucket/:key",g),h.get("/:bucket/:key",b),h.post("/:bucket/:key",f);let k={},w=0;function R(e){return`https://${e}.cloudflareaccess.com`}async function I(e,t,a){let s=!1;try{s=await async function(e,t){const a=function(e){const t=e.headers.get("cf-access-jwt-assertion");if(!t)return null;return t.trim()}(e);if(null===a)return!1;(0===Object.keys(k).length||Math.floor(Date.now()/1e3)<w)&&(k=await async function(e){let t=`${R(e.cfAccessTeamName)}/cdn-cgi/access/certs`;const a=await fetch(t,{method:"GET",cf:{cacheTtlByStatus:{"200-299":30,"300-599":0}},headers:{"Content-Type":"application/json; charset=UTF-8"}}),s=await a.json();w=Math.floor(Date.now()/1e3)+3600;const o={};for(const e of s.keys)o[e.kid]=await crypto.subtle.importKey("jwk",e,{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["verify"]);return o}(t));let s;try{s=function(e){const t=e.split("."),a=JSON.parse(atob(t[0])),s=JSON.parse(atob(t[1])),o=atob(t[2].replace(/_/g,"/").replace(/-/g,"+"));return{header:a,payload:s,signature:o,raw:{header:t[0],payload:t[1],signature:t[2]}}}(a)}catch(e){return!1}const o=new Date(1e3*s.payload.exp),n=new Date(Date.now());if(o<=n)return console.log("expired token"),!1;if(s.payload?.iss!==R(t.cfAccessTeamName))return console.log("invalid access signer"),!1;if(!async function(e){const t=(new TextEncoder).encode([e.raw.header,e.raw.payload].join(".")),a=new Uint8Array(Array.from(e.signature).map((e=>e.charCodeAt(0))));for(const e of Object.values(k)){if(await j(e,a,t))return!0}return!1}(s))return!1;return s}(e,a.config)}catch(e){}if(!1===s)return Response.json({success:!1,errors:[{code:1e4,message:"Authentication error! Verify that the Cloudflare Access Team name is correct"}]},{status:401});a.username=s.payload.email}async function j(e,t,a){return crypto.subtle.verify("RSASSA-PKCS1-v1_5",e,t,a)}async function x(e,t,a){const s=caches.default;let o,n=new URL(e.url).pathname;if(n.includes(".")||(n="/"),!1!==a.config.cacheAssets&&(o=await s.match(e),o))return o;let r="https://demo.r2explorer.dev";a.config?.dashboardUrl&&(r=a.config.dashboardUrl.endsWith("/")?a.config.dashboardUrl.slice(0,-1):a.config.dashboardUrl);const c=await fetch(`${r}${n}`);return o=new Response(await c.text(),{status:c.status,headers:{"Content-Type":c.headers.get("Content-Type"),"Access-Control-Allow-Origin":"*","Cache-Control":"max-age: 300"}}),200===c.status&&!1!==a.config.cacheAssets&&a.executionContext.waitUntil(s.put(e,o.clone())),o}const M=require("postal-mime/dist/node").postalMime.default;async function P(e,t,a){let s;if(a.config?.emailRouting?.targetBucket&&t[a.config.emailRouting.targetBucket]&&(s=t[a.config.emailRouting.targetBucket]),!s)for(const[e,a]of Object.entries(t))if(a.get&&a.put){s=a;break}const o=await async function(e,t){let a=new Uint8Array(t),s=0;const o=e.getReader();for(;;){const{done:e,value:t}=await o.read();if(e)break;a.set(t,s),s+=t.length}return a}(e.raw,e.rawSize),n=new M,r=await n.parse(o),c=`${Math.floor(Date.now())}-${crypto.randomUUID()}`;await s.put(`.r2-explorer/emails/inbox/${c}.json`,JSON.stringify(r),{customMetadata:{subject:r.subject,from_address:r.from?.address,from_name:r.from?.name,to_address:r.to.length>0?r.to[0].address:null,to_name:r.to.length>0?r.to[0].name:null,has_attachments:r.attachments.length>0,read:!1}});for(const e of r.attachments)await s.put(`.r2-explorer/emails/inbox/${c}/${e.filename}`,e.content)}class A extends t.OpenAPIRoute{static schema={operationId:"get-server-info",tags:["Server"],summary:"Get server info"};async handle(e,t,a,s){return{version:n,config:a.config,user:{username:a.username}}}}const C=t.OpenAPIRouter({base:"/api/server",raiseUnknownParameters:s,generateOperationIds:o});C.get("/config",A),exports.R2Explorer=function(a){!1!==(a=a||{}).readonly&&(a.readonly=!0);const s=t.OpenAPIRouter({schema:{info:{title:"R2 Explorer API",version:n}}}),{preflight:o,corsify:r}=e.createCors();return a.cfAccessTeamName&&s.all("*",I),!0===a.cors&&s.all("*",o),s.all("/api/server/*",C),s.all("/api/buckets/*",h),s.original.get("*",x),s.all("*",(()=>Response.json({msg:"404, not found!"},{status:404}))),{async email(e,t,s){await P(e,t,{executionContext:s,config:a})},async fetch(e,t,o){let n=await s.handle(e,t,{executionContext:o,config:a});return!0===a.cors&&(n=r(n)),n}}};
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("itty-router"),t=require("@cloudflare/itty-router-openapi"),a=require("zod");const s=!0,o=!1,n="1.0.1";class r extends t.OpenAPIRoute{static schema={operationId:"get-bucket-list",tags:["Buckets"],summary:"List buckets"};async handle(e,t,a,s){const o=[];for(const[e,a]of Object.entries(t))a.get&&a.put&&o.push({name:e});return{buckets:o}}}class c extends t.OpenAPIRoute{static schema={operationId:"get-bucket-list-objects",tags:["Buckets"],summary:"List objects",parameters:{bucket:t.Path(String),limit:t.Query(a.z.number().optional()),prefix:t.Query(a.z.string().optional().describe("base64 encoded prefix")),cursor:t.Query(a.z.string().optional()),delimiter:t.Query(a.z.string().optional()),include:t.Query(a.z.string().array().optional())}};async handle(e,t,a,s){const o=t[s.params.bucket];return await o.list({limit:s.query.limit,prefix:s.query.prefix?decodeURIComponent(escape(atob(s.query.prefix))):void 0,cursor:s.query.cursor,delimiter:s.query.delimiter,include:s.query.include})}}class i extends t.OpenAPIRoute{static schema={operationId:"post-bucket-move-object",tags:["Buckets"],summary:"Move object",parameters:{bucket:t.Path(String)},requestBody:{oldKey:a.z.string().optional().describe("base64 encoded file key"),newKey:a.z.string().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.oldKey))),r=decodeURIComponent(escape(atob(s.body.newKey))),c=await o.get(n),i=await o.put(r,c.body,{customMetadata:c.customMetadata,httpMetadata:c.httpMetadata});return await o.delete(n),i}}class u extends t.OpenAPIRoute{static schema={operationId:"post-bucket-create-folder",tags:["Buckets"],summary:"Create folder",parameters:{bucket:t.Path(String)},requestBody:{key:a.z.string().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.key)));return await o.put(n,"R2 Explorer Folder")}}class d extends t.OpenAPIRoute{static schema={operationId:"post-bucket-upload-object",tags:["Buckets"],summary:"Upload object",parameters:{bucket:t.Path(String),key:t.Query(a.z.string().optional().describe("base64 encoded file key")),customMetadata:t.Query(a.z.string().optional().describe("base64 encoded json string")),httpMetadata:t.Query(a.z.string().optional().describe("base64 encoded json string"))}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket];let n,r,c=decodeURIComponent(escape(atob(s.query.key)));return s.query.customMetadata&&(n=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(r=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata))))),await o.put(c,e.body,{customMetadata:n,httpMetadata:r})}}class p extends t.OpenAPIRoute{static schema={operationId:"post-bucket-delete-object",tags:["Buckets"],summary:"Delete object",parameters:{bucket:t.Path(String)},requestBody:{key:a.z.string().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.key)));return await o.delete(n)}}class l extends t.OpenAPIRoute{static schema={operationId:"post-multipart-create-upload",tags:["Multipart"],summary:"Create upload",parameters:{bucket:t.Path(String),key:t.Query(a.z.string().optional().describe("base64 encoded file key")),customMetadata:t.Query(a.z.string().optional().describe("base64 encoded json string")),httpMetadata:t.Query(a.z.string().optional().describe("base64 encoded json string"))}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.query.key)));let r,c;return s.query.customMetadata&&(r=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(c=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata))))),await o.createMultipartUpload(n,{customMetadata:r,httpMetadata:c})}}class m extends t.OpenAPIRoute{static schema={operationId:"post-multipart-part-upload",tags:["Multipart"],summary:"Part upload",parameters:{bucket:t.Path(String),key:t.Query(a.z.string().optional().describe("base64 encoded file key")),uploadId:t.Query(String),partNumber:t.Query(t.Int)}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.query.key))),r=o.resumeMultipartUpload(n,s.query.uploadId);try{return await r.uploadPart(s.query.partNumber,e.body)}catch(e){return new Response(e.message,{status:400})}}}class y extends t.OpenAPIRoute{static schema={operationId:"post-multipart-complete-upload",tags:["Multipart"],summary:"Complete upload",parameters:{bucket:t.Path(String)},requestBody:{uploadId:String,key:a.z.string().optional().describe("base64 encoded file key"),parts:[{etag:String,partNumber:t.Int}]}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=s.body.uploadId,r=decodeURIComponent(escape(atob(s.body.key))),c=s.body.parts,i=await o.resumeMultipartUpload(r,n);try{return{success:!0,str:await i.complete(c)}}catch(e){return Response.json({msg:e.message},{status:400})}}}class b extends t.OpenAPIRoute{static schema={operationId:"get-bucket-object",tags:["Buckets"],summary:"Get Object",parameters:{bucket:t.Path(String),key:t.Path(a.z.string().optional().describe("base64 encoded file key"))},responses:{200:{description:"File binary",schema:a.z.string().openapi({format:"binary"})}}};async handle(e,t,a,s){const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.params.key))),r=await o.get(n);if(null===r)return Response.json({msg:"Object Not Found"},{status:404});const c=new Headers;return r.writeHttpMetadata(c),c.set("etag",r.httpEtag),c.set("Content-Disposition",`attachment; filename="${n.split("/").pop()}"`),new Response(r.body,{headers:c})}}class g extends t.OpenAPIRoute{static schema={operationId:"Head-bucket-object",tags:["Buckets"],summary:"Get Object",parameters:{bucket:t.Path(String),key:t.Query(a.z.string().optional().describe("base64 encoded file key"))}};async handle(e,t,a,s){const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.params.key))),r=await o.head(n);return null===r?Response.json({msg:"Object Not Found"},{status:404}):r}}class f extends t.OpenAPIRoute{static schema={operationId:"post-bucket-put-object-metadata",tags:["Buckets"],summary:"Update object metadata",parameters:{bucket:t.Path(String),key:t.Path(a.z.string().describe("base64 encoded file key"))},requestBody:{customMetadata:a.z.record(a.z.string(),a.z.any())}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.params.key))),r=await o.get(n);return await o.put(n,r.body,{customMetadata:s.body.customMetadata})}}const h=t.OpenAPIRouter({base:"/api/buckets",raiseUnknownParameters:s,generateOperationIds:o});h.get("",r),h.get("/:bucket",c),h.post("/:bucket/move",i),h.post("/:bucket/folder",u),h.post("/:bucket/upload",d),h.post("/:bucket/multipart/create",l),h.post("/:bucket/multipart/upload",m),h.post("/:bucket/multipart/complete",y),h.post("/:bucket/delete",p),h.head("/:bucket/:key",g),h.get("/:bucket/:key",b),h.post("/:bucket/:key",f);let k={},w=0;function R(e){return`https://${e}.cloudflareaccess.com`}async function I(e,t,a){let s=!1;try{s=await async function(e,t){const a=function(e){const t=e.headers.get("cf-access-jwt-assertion");if(!t)return null;return t.trim()}(e);if(null===a)return!1;(0===Object.keys(k).length||Math.floor(Date.now()/1e3)<w)&&(k=await async function(e){let t=`${R(e.cfAccessTeamName)}/cdn-cgi/access/certs`;const a=await fetch(t,{method:"GET",cf:{cacheTtlByStatus:{"200-299":30,"300-599":0}},headers:{"Content-Type":"application/json; charset=UTF-8"}}),s=await a.json();w=Math.floor(Date.now()/1e3)+3600;const o={};for(const e of s.keys)o[e.kid]=await crypto.subtle.importKey("jwk",e,{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["verify"]);return o}(t));let s;try{s=function(e){const t=e.split("."),a=JSON.parse(atob(t[0])),s=JSON.parse(atob(t[1])),o=atob(t[2].replace(/_/g,"/").replace(/-/g,"+"));return{header:a,payload:s,signature:o,raw:{header:t[0],payload:t[1],signature:t[2]}}}(a)}catch(e){return!1}const o=new Date(1e3*s.payload.exp),n=new Date(Date.now());if(o<=n)return console.log("expired token"),!1;if(s.payload?.iss!==R(t.cfAccessTeamName))return console.log("invalid access signer"),!1;if(!async function(e){const t=(new TextEncoder).encode([e.raw.header,e.raw.payload].join(".")),a=new Uint8Array(Array.from(e.signature).map((e=>e.charCodeAt(0))));for(const e of Object.values(k)){if(await j(e,a,t))return!0}return!1}(s))return!1;return s}(e,a.config)}catch(e){}if(!1===s)return Response.json({success:!1,errors:[{code:1e4,message:"Authentication error! Verify that the Cloudflare Access Team name is correct"}]},{status:401});a.username=s.payload.email}async function j(e,t,a){return crypto.subtle.verify("RSASSA-PKCS1-v1_5",e,t,a)}async function x(e,t,a){const s=caches.default;let o,n=new URL(e.url).pathname;if(n.includes(".")||(n="/"),!1!==a.config.cacheAssets&&(o=await s.match(e),o))return o;let r="https://demo.r2explorer.dev";a.config?.dashboardUrl&&(r=a.config.dashboardUrl.endsWith("/")?a.config.dashboardUrl.slice(0,-1):a.config.dashboardUrl);const c=await fetch(`${r}${n}`);return o=new Response(await c.text(),{status:c.status,headers:{"Content-Type":c.headers.get("Content-Type"),"Access-Control-Allow-Origin":"*","Cache-Control":"max-age: 300"}}),200===c.status&&!1!==a.config.cacheAssets&&a.executionContext.waitUntil(s.put(e,o.clone())),o}const M=require("postal-mime/dist/node").postalMime.default;async function P(e,t,a){let s;if(a.config?.emailRouting?.targetBucket&&t[a.config.emailRouting.targetBucket]&&(s=t[a.config.emailRouting.targetBucket]),!s)for(const[e,a]of Object.entries(t))if(a.get&&a.put){s=a;break}const o=await async function(e,t){let a=new Uint8Array(t),s=0;const o=e.getReader();for(;;){const{done:e,value:t}=await o.read();if(e)break;a.set(t,s),s+=t.length}return a}(e.raw,e.rawSize),n=new M,r=await n.parse(o),c=`${Math.floor(Date.now())}-${crypto.randomUUID()}`;await s.put(`.r2-explorer/emails/inbox/${c}.json`,JSON.stringify(r),{customMetadata:{subject:r.subject,from_address:r.from?.address,from_name:r.from?.name,to_address:r.to.length>0?r.to[0].address:null,to_name:r.to.length>0?r.to[0].name:null,has_attachments:r.attachments.length>0,read:!1}});for(const e of r.attachments)await s.put(`.r2-explorer/emails/inbox/${c}/${e.filename}`,e.content)}class A extends t.OpenAPIRoute{static schema={operationId:"get-server-info",tags:["Server"],summary:"Get server info"};async handle(e,t,a,s){return{version:n,config:a.config,user:{username:a.username}}}}const O=t.OpenAPIRouter({base:"/api/server",raiseUnknownParameters:s,generateOperationIds:o});O.get("/config",A),exports.R2Explorer=function(a){!1!==(a=a||{}).readonly&&(a.readonly=!0);const s=t.OpenAPIRouter({schema:{info:{title:"R2 Explorer API",version:n}}}),{preflight:o,corsify:r}=e.createCors();return a.cfAccessTeamName&&s.all("*",I),!0===a.cors&&s.all("*",o),s.all("/api/server/*",O),s.all("/api/buckets/*",h),s.original.get("*",x),s.all("*",(()=>Response.json({msg:"404, not found!"},{status:404}))),{async email(e,t,s){await P(e,t,{executionContext:s,config:a})},async fetch(e,t,o){let n=await s.handle(e,t,{executionContext:o,config:a});return!0===a.cors&&(n=r(n)),n}}};
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createCors as e}from"itty-router";import{OpenAPIRoute as t,Path as a,Query as s,Int as o,OpenAPIRouter as n}from"@cloudflare/itty-router-openapi";import{z as r}from"zod";const c=!0,i=!1,d="1.0.0";class u extends t{static schema={operationId:"get-bucket-list-objects",tags:["Buckets"],summary:"List objects",parameters:{bucket:a(String),limit:s(r.number().optional()),prefix:s(r.string().optional().describe("base64 encoded prefix")),cursor:s(r.string().optional()),delimiter:s(r.string().optional()),include:s(r.string().array().optional())}};async handle(e,t,a,s){const o=t[s.params.bucket];return await o.list({limit:s.query.limit,prefix:s.query.prefix?decodeURIComponent(escape(atob(s.query.prefix))):void 0,cursor:s.query.cursor,delimiter:s.query.delimiter,include:s.query.include})}}class p extends t{static schema={operationId:"post-bucket-move-object",tags:["Buckets"],summary:"Move object",parameters:{bucket:a(String)},requestBody:{oldKey:r.string().optional().describe("base64 encoded file key"),newKey:r.string().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.oldKey))),r=decodeURIComponent(escape(atob(s.body.newKey))),c=await o.get(n),i=await o.put(r,c.body,{customMetadata:c.customMetadata,httpMetadata:c.httpMetadata});return await o.delete(n),i}}class l extends t{static schema={operationId:"post-bucket-create-folder",tags:["Buckets"],summary:"Create folder",parameters:{bucket:a(String)},requestBody:{key:r.string().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.key)));return await o.put(n,"R2 Explorer Folder")}}class m extends t{static schema={operationId:"post-bucket-upload-object",tags:["Buckets"],summary:"Upload object",parameters:{bucket:a(String),key:s(r.string().optional().describe("base64 encoded file key")),customMetadata:s(r.string().optional().describe("base64 encoded json string")),httpMetadata:s(r.string().optional().describe("base64 encoded json string"))}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket];let n,r,c=decodeURIComponent(escape(atob(s.query.key)));return s.query.customMetadata&&(n=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(r=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata))))),await o.put(c,e.body,{customMetadata:n,httpMetadata:r})}}class y extends t{static schema={operationId:"post-bucket-delete-object",tags:["Buckets"],summary:"Delete object",parameters:{bucket:a(String)},requestBody:{key:r.string().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.key)));return await o.delete(n)}}class b extends t{static schema={operationId:"post-multipart-create-upload",tags:["Multipart"],summary:"Create upload",parameters:{bucket:a(String),key:s(r.string().optional().describe("base64 encoded file key")),customMetadata:s(r.string().optional().describe("base64 encoded json string")),httpMetadata:s(r.string().optional().describe("base64 encoded json string"))}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.query.key)));let r,c;s.query.customMetadata&&(r=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(c=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata)))));const i=await o.createMultipartUpload(n,{customMetadata:r,httpMetadata:c});return{uploadId:i.uploadId,key:i.key}}}class g extends t{static schema={operationId:"post-multipart-part-upload",tags:["Multipart"],summary:"Part upload",parameters:{bucket:a(String),key:s(r.string().optional().describe("base64 encoded file key")),uploadId:s(String),partNumber:s(o)}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.query.key))),r=o.resumeMultipartUpload(n,s.query.uploadId);try{return await r.uploadPart(s.query.partNumber,e.body)}catch(e){return new Response(e.message,{status:400})}}}class f extends t{static schema={operationId:"post-multipart-complete-upload",tags:["Multipart"],summary:"Complete upload",parameters:{bucket:a(String)},requestBody:{uploadId:String,key:r.string().optional().describe("base64 encoded file key"),parts:[{etag:String,partNumber:o}]}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=s.body.uploadId,r=decodeURIComponent(escape(atob(s.body.key))),c=s.body.parts,i=await o.resumeMultipartUpload(r,n);try{return{success:!0,str:await i.complete(c)}}catch(e){return Response.json({msg:e.message},{status:400})}}}class h extends t{static schema={operationId:"get-bucket-object",tags:["Buckets"],summary:"Get Object",parameters:{bucket:a(String),key:a(r.string().optional().describe("base64 encoded file key"))},responses:{200:{description:"File binary",schema:r.string().openapi({format:"binary"})}}};async handle(e,t,a,s){const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.params.key))),r=await o.get(n);if(null===r)return Response.json({msg:"Object Not Found"},{status:404});const c=new Headers;return r.writeHttpMetadata(c),c.set("etag",r.httpEtag),c.set("Content-Disposition",`attachment; filename="${n.split("/").pop()}"`),new Response(r.body,{headers:c})}}class k extends t{static schema={operationId:"Head-bucket-object",tags:["Buckets"],summary:"Get Object",parameters:{bucket:a(String),key:s(r.string().optional().describe("base64 encoded file key"))}};async handle(e,t,a,s){const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.params.key))),r=await o.head(n);return null===r?Response.json({msg:"Object Not Found"},{status:404}):r}}class w extends t{static schema={operationId:"post-bucket-put-object-metadata",tags:["Buckets"],summary:"Update object metadata",parameters:{bucket:a(String),key:s(r.string().describe("base64 encoded file key")),customMetadata:s(r.string().describe("base64 encoded json string"))}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.query.key))),r=decodeURIComponent(escape(atob(s.query.key))),c=await o.get(n);return await o.put(n,c.body,{customMetadata:r})}}const j=n({base:"/api/buckets",raiseUnknownParameters:c,generateOperationIds:i});j.get("",class extends t{static schema={operationId:"get-bucket-list",tags:["Buckets"],summary:"List buckets"};async handle(e,t,a,s){const o=[];for(const[e,a]of Object.entries(t))a.get&&a.put&&o.push({name:e});return{buckets:o}}}),j.get("/:bucket",u),j.post("/:bucket/move",p),j.post("/:bucket/folder",l),j.post("/:bucket/upload",m),j.post("/:bucket/multipart/create",b),j.post("/:bucket/multipart/upload",g),j.post("/:bucket/multipart/complete",f),j.post("/:bucket/delete",y),j.head("/:bucket/:key",k),j.get("/:bucket/:key",h),j.post("/:bucket/:key",w);let R={},I=0;function x(e){return`https://${e}.cloudflareaccess.com`}async function M(e,t,a){let s=!1;try{s=await async function(e,t){const a=function(e){const t=e.headers.get("cf-access-jwt-assertion");if(!t)return null;return t.trim()}(e);if(null===a)return!1;(0===Object.keys(R).length||Math.floor(Date.now()/1e3)<I)&&(R=await async function(e){let t=`${x(e.cfAccessTeamName)}/cdn-cgi/access/certs`;const a=await fetch(t,{method:"GET",cf:{cacheTtlByStatus:{"200-299":30,"300-599":0}},headers:{"Content-Type":"application/json; charset=UTF-8"}}),s=await a.json();I=Math.floor(Date.now()/1e3)+3600;const o={};for(const e of s.keys)o[e.kid]=await crypto.subtle.importKey("jwk",e,{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["verify"]);return o}(t));let s;try{s=function(e){const t=e.split("."),a=JSON.parse(atob(t[0])),s=JSON.parse(atob(t[1])),o=atob(t[2].replace(/_/g,"/").replace(/-/g,"+"));return{header:a,payload:s,signature:o,raw:{header:t[0],payload:t[1],signature:t[2]}}}(a)}catch(e){return!1}const o=new Date(1e3*s.payload.exp),n=new Date(Date.now());if(o<=n)return console.log("expired token"),!1;if(s.payload?.iss!==x(t.cfAccessTeamName))return console.log("invalid access signer"),!1;if(!async function(e){const t=(new TextEncoder).encode([e.raw.header,e.raw.payload].join(".")),a=new Uint8Array(Array.from(e.signature).map((e=>e.charCodeAt(0))));for(const e of Object.values(R)){if(await U(e,a,t))return!0}return!1}(s))return!1;return s}(e,a.config)}catch(e){}if(!1===s)return Response.json({success:!1,errors:[{code:1e4,message:"Authentication error! Verify that the Cloudflare Access Team name is correct"}]},{status:401});a.username=s.payload.email}async function U(e,t,a){return crypto.subtle.verify("RSASSA-PKCS1-v1_5",e,t,a)}async function C(e,t,a){const s=caches.default;let o,n=new URL(e.url).pathname;if(n.includes(".")||(n="/"),!1!==a.config.cacheAssets&&(o=await s.match(e),o))return o;let r="https://demo.r2explorer.dev";a.config?.dashboardUrl&&(r=a.config.dashboardUrl.endsWith("/")?a.config.dashboardUrl.slice(0,-1):a.config.dashboardUrl);const c=await fetch(`${r}${n}`);return o=new Response(await c.text(),{status:c.status,headers:{"Content-Type":c.headers.get("Content-Type"),"Access-Control-Allow-Origin":"*","Cache-Control":"max-age: 300"}}),200===c.status&&!1!==a.config.cacheAssets&&a.executionContext.waitUntil(s.put(e,o.clone())),o}const S=require("postal-mime/dist/node").postalMime.default;async function q(e,t,a){let s;if(a.config?.emailRouting?.targetBucket&&t[a.config.emailRouting.targetBucket]&&(s=t[a.config.emailRouting.targetBucket]),!s)for(const[e,a]of Object.entries(t))if(a.get&&a.put){s=a;break}const o=await async function(e,t){let a=new Uint8Array(t),s=0;const o=e.getReader();for(;;){const{done:e,value:t}=await o.read();if(e)break;a.set(t,s),s+=t.length}return a}(e.raw,e.rawSize),n=new S,r=await n.parse(o),c=`${Math.floor(Date.now())}-${crypto.randomUUID()}`;await s.put(`.r2-explorer/emails/inbox/${c}.json`,JSON.stringify(r),{customMetadata:{subject:r.subject,from_address:r.from?.address,from_name:r.from?.name,to_address:r.to.length>0?r.to[0].address:null,to_name:r.to.length>0?r.to[0].name:null,has_attachments:r.attachments.length>0,read:!1}});for(const e of r.attachments)await s.put(`.r2-explorer/emails/inbox/${c}/${e.filename}`,e.content)}const v=n({base:"/api/server",raiseUnknownParameters:c,generateOperationIds:i});function A(t){!1!==(t=t||{}).readonly&&(t.readonly=!0);const a=n({schema:{info:{title:"R2 Explorer API",version:d}}}),{preflight:s,corsify:o}=e();return t.cfAccessTeamName&&a.all("*",M),!0===t.cors&&a.all("*",s),a.all("/api/server/*",v),a.all("/api/buckets/*",j),a.original.get("*",C),a.all("*",(()=>Response.json({msg:"404, not found!"},{status:404}))),{async email(e,a,s){await q(e,a,{executionContext:s,config:t})},async fetch(e,s,n){let r=await a.handle(e,s,{executionContext:n,config:t});return!0===t.cors&&(r=o(r)),r}}}v.get("/config",class extends t{static schema={operationId:"get-server-info",tags:["Server"],summary:"Get server info"};async handle(e,t,a,s){return{version:d,config:a.config,user:{username:a.username}}}});export{A as R2Explorer};
|
|
1
|
+
import{createCors as e}from"itty-router";import{OpenAPIRoute as t,Path as a,Query as s,Int as o,OpenAPIRouter as n}from"@cloudflare/itty-router-openapi";import{z as r}from"zod";const c=!0,i=!1,d="1.0.1";class u extends t{static schema={operationId:"get-bucket-list-objects",tags:["Buckets"],summary:"List objects",parameters:{bucket:a(String),limit:s(r.number().optional()),prefix:s(r.string().optional().describe("base64 encoded prefix")),cursor:s(r.string().optional()),delimiter:s(r.string().optional()),include:s(r.string().array().optional())}};async handle(e,t,a,s){const o=t[s.params.bucket];return await o.list({limit:s.query.limit,prefix:s.query.prefix?decodeURIComponent(escape(atob(s.query.prefix))):void 0,cursor:s.query.cursor,delimiter:s.query.delimiter,include:s.query.include})}}class p extends t{static schema={operationId:"post-bucket-move-object",tags:["Buckets"],summary:"Move object",parameters:{bucket:a(String)},requestBody:{oldKey:r.string().optional().describe("base64 encoded file key"),newKey:r.string().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.oldKey))),r=decodeURIComponent(escape(atob(s.body.newKey))),c=await o.get(n),i=await o.put(r,c.body,{customMetadata:c.customMetadata,httpMetadata:c.httpMetadata});return await o.delete(n),i}}class l extends t{static schema={operationId:"post-bucket-create-folder",tags:["Buckets"],summary:"Create folder",parameters:{bucket:a(String)},requestBody:{key:r.string().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.key)));return await o.put(n,"R2 Explorer Folder")}}class m extends t{static schema={operationId:"post-bucket-upload-object",tags:["Buckets"],summary:"Upload object",parameters:{bucket:a(String),key:s(r.string().optional().describe("base64 encoded file key")),customMetadata:s(r.string().optional().describe("base64 encoded json string")),httpMetadata:s(r.string().optional().describe("base64 encoded json string"))}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket];let n,r,c=decodeURIComponent(escape(atob(s.query.key)));return s.query.customMetadata&&(n=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(r=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata))))),await o.put(c,e.body,{customMetadata:n,httpMetadata:r})}}class y extends t{static schema={operationId:"post-bucket-delete-object",tags:["Buckets"],summary:"Delete object",parameters:{bucket:a(String)},requestBody:{key:r.string().optional().describe("base64 encoded file key")}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.body.key)));return await o.delete(n)}}class b extends t{static schema={operationId:"post-multipart-create-upload",tags:["Multipart"],summary:"Create upload",parameters:{bucket:a(String),key:s(r.string().optional().describe("base64 encoded file key")),customMetadata:s(r.string().optional().describe("base64 encoded json string")),httpMetadata:s(r.string().optional().describe("base64 encoded json string"))}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.query.key)));let r,c;return s.query.customMetadata&&(r=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(c=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata))))),await o.createMultipartUpload(n,{customMetadata:r,httpMetadata:c})}}class g extends t{static schema={operationId:"post-multipart-part-upload",tags:["Multipart"],summary:"Part upload",parameters:{bucket:a(String),key:s(r.string().optional().describe("base64 encoded file key")),uploadId:s(String),partNumber:s(o)}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.query.key))),r=o.resumeMultipartUpload(n,s.query.uploadId);try{return await r.uploadPart(s.query.partNumber,e.body)}catch(e){return new Response(e.message,{status:400})}}}class f extends t{static schema={operationId:"post-multipart-complete-upload",tags:["Multipart"],summary:"Complete upload",parameters:{bucket:a(String)},requestBody:{uploadId:String,key:r.string().optional().describe("base64 encoded file key"),parts:[{etag:String,partNumber:o}]}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=s.body.uploadId,r=decodeURIComponent(escape(atob(s.body.key))),c=s.body.parts,i=await o.resumeMultipartUpload(r,n);try{return{success:!0,str:await i.complete(c)}}catch(e){return Response.json({msg:e.message},{status:400})}}}class h extends t{static schema={operationId:"get-bucket-object",tags:["Buckets"],summary:"Get Object",parameters:{bucket:a(String),key:a(r.string().optional().describe("base64 encoded file key"))},responses:{200:{description:"File binary",schema:r.string().openapi({format:"binary"})}}};async handle(e,t,a,s){const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.params.key))),r=await o.get(n);if(null===r)return Response.json({msg:"Object Not Found"},{status:404});const c=new Headers;return r.writeHttpMetadata(c),c.set("etag",r.httpEtag),c.set("Content-Disposition",`attachment; filename="${n.split("/").pop()}"`),new Response(r.body,{headers:c})}}class k extends t{static schema={operationId:"Head-bucket-object",tags:["Buckets"],summary:"Get Object",parameters:{bucket:a(String),key:s(r.string().optional().describe("base64 encoded file key"))}};async handle(e,t,a,s){const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.params.key))),r=await o.head(n);return null===r?Response.json({msg:"Object Not Found"},{status:404}):r}}class w extends t{static schema={operationId:"post-bucket-put-object-metadata",tags:["Buckets"],summary:"Update object metadata",parameters:{bucket:a(String),key:a(r.string().describe("base64 encoded file key"))},requestBody:{customMetadata:r.record(r.string(),r.any())}};async handle(e,t,a,s){if(!0===a.config.readonly)return Response.json({msg:"unauthorized"},{status:401});const o=t[s.params.bucket],n=decodeURIComponent(escape(atob(s.params.key))),r=await o.get(n);return await o.put(n,r.body,{customMetadata:s.body.customMetadata})}}const j=n({base:"/api/buckets",raiseUnknownParameters:c,generateOperationIds:i});j.get("",class extends t{static schema={operationId:"get-bucket-list",tags:["Buckets"],summary:"List buckets"};async handle(e,t,a,s){const o=[];for(const[e,a]of Object.entries(t))a.get&&a.put&&o.push({name:e});return{buckets:o}}}),j.get("/:bucket",u),j.post("/:bucket/move",p),j.post("/:bucket/folder",l),j.post("/:bucket/upload",m),j.post("/:bucket/multipart/create",b),j.post("/:bucket/multipart/upload",g),j.post("/:bucket/multipart/complete",f),j.post("/:bucket/delete",y),j.head("/:bucket/:key",k),j.get("/:bucket/:key",h),j.post("/:bucket/:key",w);let R={},x=0;function I(e){return`https://${e}.cloudflareaccess.com`}async function M(e,t,a){let s=!1;try{s=await async function(e,t){const a=function(e){const t=e.headers.get("cf-access-jwt-assertion");if(!t)return null;return t.trim()}(e);if(null===a)return!1;(0===Object.keys(R).length||Math.floor(Date.now()/1e3)<x)&&(R=await async function(e){let t=`${I(e.cfAccessTeamName)}/cdn-cgi/access/certs`;const a=await fetch(t,{method:"GET",cf:{cacheTtlByStatus:{"200-299":30,"300-599":0}},headers:{"Content-Type":"application/json; charset=UTF-8"}}),s=await a.json();x=Math.floor(Date.now()/1e3)+3600;const o={};for(const e of s.keys)o[e.kid]=await crypto.subtle.importKey("jwk",e,{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["verify"]);return o}(t));let s;try{s=function(e){const t=e.split("."),a=JSON.parse(atob(t[0])),s=JSON.parse(atob(t[1])),o=atob(t[2].replace(/_/g,"/").replace(/-/g,"+"));return{header:a,payload:s,signature:o,raw:{header:t[0],payload:t[1],signature:t[2]}}}(a)}catch(e){return!1}const o=new Date(1e3*s.payload.exp),n=new Date(Date.now());if(o<=n)return console.log("expired token"),!1;if(s.payload?.iss!==I(t.cfAccessTeamName))return console.log("invalid access signer"),!1;if(!async function(e){const t=(new TextEncoder).encode([e.raw.header,e.raw.payload].join(".")),a=new Uint8Array(Array.from(e.signature).map((e=>e.charCodeAt(0))));for(const e of Object.values(R)){if(await U(e,a,t))return!0}return!1}(s))return!1;return s}(e,a.config)}catch(e){}if(!1===s)return Response.json({success:!1,errors:[{code:1e4,message:"Authentication error! Verify that the Cloudflare Access Team name is correct"}]},{status:401});a.username=s.payload.email}async function U(e,t,a){return crypto.subtle.verify("RSASSA-PKCS1-v1_5",e,t,a)}async function C(e,t,a){const s=caches.default;let o,n=new URL(e.url).pathname;if(n.includes(".")||(n="/"),!1!==a.config.cacheAssets&&(o=await s.match(e),o))return o;let r="https://demo.r2explorer.dev";a.config?.dashboardUrl&&(r=a.config.dashboardUrl.endsWith("/")?a.config.dashboardUrl.slice(0,-1):a.config.dashboardUrl);const c=await fetch(`${r}${n}`);return o=new Response(await c.text(),{status:c.status,headers:{"Content-Type":c.headers.get("Content-Type"),"Access-Control-Allow-Origin":"*","Cache-Control":"max-age: 300"}}),200===c.status&&!1!==a.config.cacheAssets&&a.executionContext.waitUntil(s.put(e,o.clone())),o}const S=require("postal-mime/dist/node").postalMime.default;async function q(e,t,a){let s;if(a.config?.emailRouting?.targetBucket&&t[a.config.emailRouting.targetBucket]&&(s=t[a.config.emailRouting.targetBucket]),!s)for(const[e,a]of Object.entries(t))if(a.get&&a.put){s=a;break}const o=await async function(e,t){let a=new Uint8Array(t),s=0;const o=e.getReader();for(;;){const{done:e,value:t}=await o.read();if(e)break;a.set(t,s),s+=t.length}return a}(e.raw,e.rawSize),n=new S,r=await n.parse(o),c=`${Math.floor(Date.now())}-${crypto.randomUUID()}`;await s.put(`.r2-explorer/emails/inbox/${c}.json`,JSON.stringify(r),{customMetadata:{subject:r.subject,from_address:r.from?.address,from_name:r.from?.name,to_address:r.to.length>0?r.to[0].address:null,to_name:r.to.length>0?r.to[0].name:null,has_attachments:r.attachments.length>0,read:!1}});for(const e of r.attachments)await s.put(`.r2-explorer/emails/inbox/${c}/${e.filename}`,e.content)}const v=n({base:"/api/server",raiseUnknownParameters:c,generateOperationIds:i});function A(t){!1!==(t=t||{}).readonly&&(t.readonly=!0);const a=n({schema:{info:{title:"R2 Explorer API",version:d}}}),{preflight:s,corsify:o}=e();return t.cfAccessTeamName&&a.all("*",M),!0===t.cors&&a.all("*",s),a.all("/api/server/*",v),a.all("/api/buckets/*",j),a.original.get("*",C),a.all("*",(()=>Response.json({msg:"404, not found!"},{status:404}))),{async email(e,a,s){await q(e,a,{executionContext:s,config:t})},async fetch(e,s,n){let r=await a.handle(e,s,{executionContext:n,config:t});return!0===t.cors&&(r=o(r)),r}}}v.get("/config",class extends t{static schema={operationId:"get-server-info",tags:["Server"],summary:"Get server info"};async handle(e,t,a,s){return{version:d,config:a.config,user:{username:a.username}}}});export{A as R2Explorer};
|
|
@@ -4,8 +4,5 @@ import { Context } from "../../../interfaces";
|
|
|
4
4
|
import { OpenAPIRouteSchema } from "@cloudflare/itty-router-openapi/dist/src/types";
|
|
5
5
|
export declare class CreateUpload extends OpenAPIRoute {
|
|
6
6
|
static schema: OpenAPIRouteSchema;
|
|
7
|
-
handle(request: Request, env: any, context: Context, data: any): Promise<
|
|
8
|
-
uploadId: any;
|
|
9
|
-
key: any;
|
|
10
|
-
}>;
|
|
7
|
+
handle(request: Request, env: any, context: Context, data: any): Promise<any>;
|
|
11
8
|
}
|
package/dist/src/interfaces.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "r2-explorer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A Google Drive Interface for your Cloudflare R2 Buckets",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"wrangler": "^3.6.0"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"@cloudflare/itty-router-openapi": "
|
|
98
|
+
"@cloudflare/itty-router-openapi": "^1.0.1",
|
|
99
99
|
"postal-mime": "^1.0.16"
|
|
100
100
|
},
|
|
101
101
|
"bin": {
|