r2-explorer 1.0.1 → 1.0.2
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
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
## Features
|
|
21
21
|
|
|
22
22
|
- [Email Explorer](https://r2explorer.dev/guides/setup-email-explorer/) (using Cloudflare Email Routing)
|
|
23
|
+
- [Basic Auth](https://r2explorer.dev/getting-started/security/#basic-auth)
|
|
23
24
|
- [Cloudflare Access Authentication](https://r2explorer.dev/getting-started/security/)
|
|
24
25
|
- Very quick bucket/folder navigation
|
|
25
26
|
- pdf, image, txt, markdown, csv, etc in-browser preview
|
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.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}}};
|
|
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,r="1.0.2";class n 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],r=decodeURIComponent(escape(atob(s.body.oldKey))),n=decodeURIComponent(escape(atob(s.body.newKey))),c=await o.get(r),i=await o.put(n,c.body,{customMetadata:c.customMetadata,httpMetadata:c.httpMetadata});return await o.delete(r),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],r=decodeURIComponent(escape(atob(s.body.key)));return await o.put(r,"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 r,n,c=decodeURIComponent(escape(atob(s.query.key)));return s.query.customMetadata&&(r=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(n=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata))))),await o.put(c,e.body,{customMetadata:r,httpMetadata:n})}}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],r=decodeURIComponent(escape(atob(s.body.key)));return await o.delete(r)}}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],r=decodeURIComponent(escape(atob(s.query.key)));let n,c;return s.query.customMetadata&&(n=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(c=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata))))),await o.createMultipartUpload(r,{customMetadata:n,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],r=decodeURIComponent(escape(atob(s.query.key))),n=o.resumeMultipartUpload(r,s.query.uploadId);try{return await n.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],r=s.body.uploadId,n=decodeURIComponent(escape(atob(s.body.key))),c=s.body.parts,i=await o.resumeMultipartUpload(n,r);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],r=decodeURIComponent(escape(atob(s.params.key))),n=await o.get(r);if(null===n)return Response.json({msg:"Object Not Found"},{status:404});const c=new Headers;return n.writeHttpMetadata(c),c.set("etag",n.httpEtag),c.set("Content-Disposition",`attachment; filename="${r.split("/").pop()}"`),new Response(n.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],r=decodeURIComponent(escape(atob(s.params.key))),n=await o.head(r);return null===n?Response.json({msg:"Object Not Found"},{status:404}):n}}class h 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],r=decodeURIComponent(escape(atob(s.params.key))),n=await o.get(r);return await o.put(r,n.body,{customMetadata:s.body.customMetadata})}}const f=t.OpenAPIRouter({base:"/api/buckets",raiseUnknownParameters:s,generateOperationIds:o});f.get("",n),f.get("/:bucket",c),f.post("/:bucket/move",i),f.post("/:bucket/folder",u),f.post("/:bucket/upload",d),f.post("/:bucket/multipart/create",l),f.post("/:bucket/multipart/upload",m),f.post("/:bucket/multipart/complete",y),f.post("/:bucket/delete",p),f.head("/:bucket/:key",g),f.get("/:bucket/:key",b),f.post("/:bucket/:key",h);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),r=new Date(Date.now());if(o<=r)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 A(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 A(e,t,a){return crypto.subtle.verify("RSASSA-PKCS1-v1_5",e,t,a)}async function j(e,t,a){const s=caches.default;let o,r=new URL(e.url).pathname;if(r.includes(".")||(r="/"),!1!==a.config.cacheAssets&&(o=await s.match(e),o))return o;let n="https://demo.r2explorer.dev";a.config?.dashboardUrl&&(n=a.config.dashboardUrl.endsWith("/")?a.config.dashboardUrl.slice(0,-1):a.config.dashboardUrl);const c=await fetch(`${n}${r}`);return o=new Response(await c.body,{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 x=require("postal-mime/dist/node").postalMime.default;async function M(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),r=new x,n=await r.parse(o),c=`${Math.floor(Date.now())}-${crypto.randomUUID()}`;await s.put(`.r2-explorer/emails/inbox/${c}.json`,JSON.stringify(n),{customMetadata:{subject:n.subject,from_address:n.from?.address,from_name:n.from?.name,to_address:n.to.length>0?n.to[0].address:null,to_name:n.to.length>0?n.to[0].name:null,has_attachments:n.attachments.length>0,read:!1}});for(const e of n.attachments)await s.put(`.r2-explorer/emails/inbox/${c}/${e.filename}`,e.content)}class P extends t.OpenAPIRoute{static schema={operationId:"get-server-info",tags:["Server"],summary:"Get server info"};async handle(e,t,a,s){const o={...a.config};return delete o.basicAuth,{version:r,config:o,user:{username:a.username}}}}const z=t.OpenAPIRouter({base:"/api/server",raiseUnknownParameters:s,generateOperationIds:o});async function C(e,t,a){let s,o=!1;try{o=function(e){const t=e.headers.get("Authorization");if(!t)return null;return atob(t.replace("Basic","").trim())}(e).split(":"),Array.isArray(a.config.basicAuth)||(a.config.basicAuth=[a.config.basicAuth]);for(const e of a.config.basicAuth)if(e.username===o[0]&&e.password===o[1]){s=e.username;break}}catch(e){}if(!1===o||void 0===s)return Response.json({success:!1,errors:[{code:1e4,message:"Authentication error! This server requires Basic Auth"}]},{status:401});a.username=s}z.get("/config",P),exports.R2Explorer=function(a){!1!==(a=a||{}).readonly&&(a.readonly=!0);const s={info:{title:"R2 Explorer API",version:r}};a.basicAuth&&(s.security=[{basicAuth:[]}]);const o=t.OpenAPIRouter({schema:s}),{preflight:n,corsify:c}=e.createCors();return!0===a.cors&&o.all("/api*",n),a.cfAccessTeamName&&o.all("*",I),a.basicAuth&&(o.registry.registerComponent("securitySchemes","basicAuth",{type:"http",scheme:"basic"}),o.all("/api*",C)),o.all("/api/server/*",z),o.all("/api/buckets/*",f),o.original.get("*",j),o.all("*",(()=>Response.json({msg:"404, not found!"},{status:404}))),{async email(e,t,s){await M(e,t,{executionContext:s,config:a})},async fetch(e,t,s){let r=await o.handle(e,t,{executionContext:s,config:a});return!0===a.cors&&(r=c(r)),r}}};
|
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.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};
|
|
1
|
+
import{createCors as e}from"itty-router";import{OpenAPIRoute as t,Path as a,Query as s,Int as o,OpenAPIRouter as r}from"@cloudflare/itty-router-openapi";import{z as n}from"zod";const c=!0,i=!1,u="1.0.2";class d extends t{static schema={operationId:"get-bucket-list-objects",tags:["Buckets"],summary:"List objects",parameters:{bucket:a(String),limit:s(n.number().optional()),prefix:s(n.string().optional().describe("base64 encoded prefix")),cursor:s(n.string().optional()),delimiter:s(n.string().optional()),include:s(n.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:n.string().optional().describe("base64 encoded file key"),newKey:n.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],r=decodeURIComponent(escape(atob(s.body.oldKey))),n=decodeURIComponent(escape(atob(s.body.newKey))),c=await o.get(r),i=await o.put(n,c.body,{customMetadata:c.customMetadata,httpMetadata:c.httpMetadata});return await o.delete(r),i}}class l extends t{static schema={operationId:"post-bucket-create-folder",tags:["Buckets"],summary:"Create folder",parameters:{bucket:a(String)},requestBody:{key:n.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],r=decodeURIComponent(escape(atob(s.body.key)));return await o.put(r,"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(n.string().optional().describe("base64 encoded file key")),customMetadata:s(n.string().optional().describe("base64 encoded json string")),httpMetadata:s(n.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 r,n,c=decodeURIComponent(escape(atob(s.query.key)));return s.query.customMetadata&&(r=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(n=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata))))),await o.put(c,e.body,{customMetadata:r,httpMetadata:n})}}class b extends t{static schema={operationId:"post-bucket-delete-object",tags:["Buckets"],summary:"Delete object",parameters:{bucket:a(String)},requestBody:{key:n.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],r=decodeURIComponent(escape(atob(s.body.key)));return await o.delete(r)}}class y extends t{static schema={operationId:"post-multipart-create-upload",tags:["Multipart"],summary:"Create upload",parameters:{bucket:a(String),key:s(n.string().optional().describe("base64 encoded file key")),customMetadata:s(n.string().optional().describe("base64 encoded json string")),httpMetadata:s(n.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],r=decodeURIComponent(escape(atob(s.query.key)));let n,c;return s.query.customMetadata&&(n=JSON.parse(decodeURIComponent(escape(atob(s.query.customMetadata))))),s.query.httpMetadata&&(c=JSON.parse(decodeURIComponent(escape(atob(s.query.httpMetadata))))),await o.createMultipartUpload(r,{customMetadata:n,httpMetadata:c})}}class g extends t{static schema={operationId:"post-multipart-part-upload",tags:["Multipart"],summary:"Part upload",parameters:{bucket:a(String),key:s(n.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],r=decodeURIComponent(escape(atob(s.query.key))),n=o.resumeMultipartUpload(r,s.query.uploadId);try{return await n.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:n.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],r=s.body.uploadId,n=decodeURIComponent(escape(atob(s.body.key))),c=s.body.parts,i=await o.resumeMultipartUpload(n,r);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(n.string().optional().describe("base64 encoded file key"))},responses:{200:{description:"File binary",schema:n.string().openapi({format:"binary"})}}};async handle(e,t,a,s){const o=t[s.params.bucket],r=decodeURIComponent(escape(atob(s.params.key))),n=await o.get(r);if(null===n)return Response.json({msg:"Object Not Found"},{status:404});const c=new Headers;return n.writeHttpMetadata(c),c.set("etag",n.httpEtag),c.set("Content-Disposition",`attachment; filename="${r.split("/").pop()}"`),new Response(n.body,{headers:c})}}class k extends t{static schema={operationId:"Head-bucket-object",tags:["Buckets"],summary:"Get Object",parameters:{bucket:a(String),key:s(n.string().optional().describe("base64 encoded file key"))}};async handle(e,t,a,s){const o=t[s.params.bucket],r=decodeURIComponent(escape(atob(s.params.key))),n=await o.head(r);return null===n?Response.json({msg:"Object Not Found"},{status:404}):n}}class w extends t{static schema={operationId:"post-bucket-put-object-metadata",tags:["Buckets"],summary:"Update object metadata",parameters:{bucket:a(String),key:a(n.string().describe("base64 encoded file key"))},requestBody:{customMetadata:n.record(n.string(),n.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],r=decodeURIComponent(escape(atob(s.params.key))),n=await o.get(r);return await o.put(r,n.body,{customMetadata:s.body.customMetadata})}}const j=r({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",d),j.post("/:bucket/move",p),j.post("/:bucket/folder",l),j.post("/:bucket/upload",m),j.post("/:bucket/multipart/create",y),j.post("/:bucket/multipart/upload",g),j.post("/:bucket/multipart/complete",f),j.post("/:bucket/delete",b),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),r=new Date(Date.now());if(o<=r)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 C(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 C(e,t,a){return crypto.subtle.verify("RSASSA-PKCS1-v1_5",e,t,a)}async function S(e,t,a){const s=caches.default;let o,r=new URL(e.url).pathname;if(r.includes(".")||(r="/"),!1!==a.config.cacheAssets&&(o=await s.match(e),o))return o;let n="https://demo.r2explorer.dev";a.config?.dashboardUrl&&(n=a.config.dashboardUrl.endsWith("/")?a.config.dashboardUrl.slice(0,-1):a.config.dashboardUrl);const c=await fetch(`${n}${r}`);return o=new Response(await c.body,{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 U=require("postal-mime/dist/node").postalMime.default;async function A(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),r=new U,n=await r.parse(o),c=`${Math.floor(Date.now())}-${crypto.randomUUID()}`;await s.put(`.r2-explorer/emails/inbox/${c}.json`,JSON.stringify(n),{customMetadata:{subject:n.subject,from_address:n.from?.address,from_name:n.from?.name,to_address:n.to.length>0?n.to[0].address:null,to_name:n.to.length>0?n.to[0].name:null,has_attachments:n.attachments.length>0,read:!1}});for(const e of n.attachments)await s.put(`.r2-explorer/emails/inbox/${c}/${e.filename}`,e.content)}const q=r({base:"/api/server",raiseUnknownParameters:c,generateOperationIds:i});async function v(e,t,a){let s,o=!1;try{o=function(e){const t=e.headers.get("Authorization");if(!t)return null;return atob(t.replace("Basic","").trim())}(e).split(":"),Array.isArray(a.config.basicAuth)||(a.config.basicAuth=[a.config.basicAuth]);for(const e of a.config.basicAuth)if(e.username===o[0]&&e.password===o[1]){s=e.username;break}}catch(e){}if(!1===o||void 0===s)return Response.json({success:!1,errors:[{code:1e4,message:"Authentication error! This server requires Basic Auth"}]},{status:401});a.username=s}function B(t){!1!==(t=t||{}).readonly&&(t.readonly=!0);const a={info:{title:"R2 Explorer API",version:u}};t.basicAuth&&(a.security=[{basicAuth:[]}]);const s=r({schema:a}),{preflight:o,corsify:n}=e();return!0===t.cors&&s.all("/api*",o),t.cfAccessTeamName&&s.all("*",M),t.basicAuth&&(s.registry.registerComponent("securitySchemes","basicAuth",{type:"http",scheme:"basic"}),s.all("/api*",v)),s.all("/api/server/*",q),s.all("/api/buckets/*",j),s.original.get("*",S),s.all("*",(()=>Response.json({msg:"404, not found!"},{status:404}))),{async email(e,a,s){await A(e,a,{executionContext:s,config:t})},async fetch(e,a,o){let r=await s.handle(e,a,{executionContext:o,config:t});return!0===t.cors&&(r=n(r)),r}}}q.get("/config",class extends t{static schema={operationId:"get-server-info",tags:["Server"],summary:"Get server info"};async handle(e,t,a,s){const o={...a.config};return delete o.basicAuth,{version:u,config:o,user:{username:a.username}}}});export{B as R2Explorer};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
2
|
import { Context, R2ExplorerConfig } from "../../interfaces";
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function validateAccessJwt(request: any, env: any, context: Context): Promise<Response>;
|
|
4
4
|
export declare function isValidJwt(request: any, config: R2ExplorerConfig): Promise<any>;
|
|
@@ -6,7 +6,18 @@ export declare class GetInfo extends OpenAPIRoute {
|
|
|
6
6
|
static schema: OpenAPIRouteSchema;
|
|
7
7
|
handle(request: Request, env: any, context: Context, data: any): Promise<{
|
|
8
8
|
version: string;
|
|
9
|
-
config:
|
|
9
|
+
config: {
|
|
10
|
+
readonly?: boolean;
|
|
11
|
+
cors?: boolean;
|
|
12
|
+
cfAccessTeamName?: string;
|
|
13
|
+
dashboardUrl?: string;
|
|
14
|
+
emailRouting?: {
|
|
15
|
+
targetBucket: string;
|
|
16
|
+
};
|
|
17
|
+
showHiddenFiles?: string;
|
|
18
|
+
cacheAssets?: boolean;
|
|
19
|
+
basicAuth?: import("../../interfaces").BasicAuth | import("../../interfaces").BasicAuth[];
|
|
20
|
+
};
|
|
10
21
|
user: {
|
|
11
22
|
username: string;
|
|
12
23
|
};
|