mirror of
https://github.com/Nezumi-2711/google-drive-s3.git
synced 2026-09-22 13:38:30 +00:00
Merge pull request #123 from Musenxi/main
Add support for PUBLIC_READ_BUCKETS for unauthenticated access
This commit is contained in:
@@ -68,6 +68,7 @@ https://developers.cloudflare.com/workers/configuration/secrets/#via-the-dashboa
|
|||||||
| `REGION` | The region used by the S3 client. |
|
| `REGION` | The region used by the S3 client. |
|
||||||
| `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_REFRESH_TOKEN` | Google API credentials obtained from rclone. |
|
| `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_REFRESH_TOKEN` | Google API credentials obtained from rclone. |
|
||||||
| `ALLOWED_BUCKETS` | Set the buckets allowed, separated by `,`. A directory with the bucket name will be created directly under Google Drive. |
|
| `ALLOWED_BUCKETS` | Set the buckets allowed, separated by `,`. A directory with the bucket name will be created directly under Google Drive. |
|
||||||
|
| `PUBLIC_READ_BUCKETS` | *(Optional)* Buckets that allow unauthenticated GET/HEAD access without signature, separated by `,`. Write operations (PUT/POST/DELETE) still require authentication. Must be a subset of `ALLOWED_BUCKETS`. |
|
||||||
|
|
||||||
|
|
||||||
### 4. CORS Configuration
|
### 4. CORS Configuration
|
||||||
|
|||||||
@@ -22,10 +22,15 @@ export default {
|
|||||||
return new Response("Access denied to this bucket", { status: 403 });
|
return new Response("Access denied to this bucket", { status: 403 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isPublicRead = isPublicReadBucket(bucket, env);
|
||||||
|
const isReadMethod = method === "GET" || method === "HEAD";
|
||||||
|
|
||||||
|
if (!(isPublicRead && isReadMethod)) {
|
||||||
const isValid = await verifySignature(request, env);
|
const isValid = await verifySignature(request, env);
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
return new Response("Invalid Signature", { status: 403 });
|
return new Response("Invalid Signature", { status: 403 });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const accessToken = await getAccessToken(env);
|
const accessToken = await getAccessToken(env);
|
||||||
|
|
||||||
@@ -135,6 +140,7 @@ interface Env {
|
|||||||
AUTH_KV: KVNamespace;
|
AUTH_KV: KVNamespace;
|
||||||
FOLDER_CACHE: KVNamespace;
|
FOLDER_CACHE: KVNamespace;
|
||||||
ALLOWED_BUCKETS?: string;
|
ALLOWED_BUCKETS?: string;
|
||||||
|
PUBLIC_READ_BUCKETS?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GoogleDriveFile {
|
interface GoogleDriveFile {
|
||||||
@@ -169,6 +175,18 @@ function isAllowedBucket(bucket: string, env: Env): boolean {
|
|||||||
return allowedBuckets.includes(bucket);
|
return allowedBuckets.includes(bucket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPublicReadBucket(bucket: string, env: Env): boolean {
|
||||||
|
if (!env.PUBLIC_READ_BUCKETS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicReadBuckets = env.PUBLIC_READ_BUCKETS.split(",")
|
||||||
|
.map((b) => b.trim())
|
||||||
|
.filter((b) => b);
|
||||||
|
|
||||||
|
return publicReadBuckets.includes(bucket);
|
||||||
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// Google Drive API Functions
|
// Google Drive API Functions
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|||||||
@@ -1017,4 +1017,81 @@ describe("S3 API Server with Google Drive Backend", () => {
|
|||||||
|
|
||||||
global.fetch = originalFetch;
|
global.fetch = originalFetch;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Public Read Tests
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
const PUBLIC_READ_ENV = {
|
||||||
|
...ENV,
|
||||||
|
PUBLIC_READ_BUCKETS: "public-bucket",
|
||||||
|
ALLOWED_BUCKETS: "test-bucket,empty-bucket,my-bucket,public-bucket",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 16. Public read bucket - unsigned GET should succeed
|
||||||
|
it("should allow unsigned GET on public read bucket", async () => {
|
||||||
|
const request = new Request(`${endpoint}/public-bucket/test-file.txt`, {
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await worker.fetch(request, PUBLIC_READ_ENV, CTX);
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(await response.text()).toBe("Hello World");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 17. Public read bucket - unsigned HEAD should succeed
|
||||||
|
it("should allow unsigned HEAD on public read bucket", async () => {
|
||||||
|
const request = new Request(`${endpoint}/public-bucket/test-file.txt`, {
|
||||||
|
method: "HEAD",
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await worker.fetch(request, PUBLIC_READ_ENV, CTX);
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.headers.get("Content-Type")).toBe("text/plain");
|
||||||
|
expect(response.headers.get("Content-Length")).toBe("11");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 18. Public read bucket - unsigned PUT should be rejected
|
||||||
|
it("should reject unsigned PUT on public read bucket", async () => {
|
||||||
|
const request = new Request(`${endpoint}/public-bucket/test-file.txt`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Content-Type": "text/plain" },
|
||||||
|
body: "Hello World",
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await worker.fetch(request, PUBLIC_READ_ENV, CTX);
|
||||||
|
expect(response.status).toBe(403);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 19. Public read bucket - unsigned DELETE should be rejected
|
||||||
|
it("should reject unsigned DELETE on public read bucket", async () => {
|
||||||
|
const request = new Request(`${endpoint}/public-bucket/test-file.txt`, {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await worker.fetch(request, PUBLIC_READ_ENV, CTX);
|
||||||
|
expect(response.status).toBe(403);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 20. Non-public bucket - unsigned GET should still be rejected
|
||||||
|
it("should reject unsigned GET on non-public bucket", async () => {
|
||||||
|
const request = new Request(`${endpoint}/test-bucket/test-file.txt`, {
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await worker.fetch(request, PUBLIC_READ_ENV, CTX);
|
||||||
|
expect(response.status).toBe(403);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 21. Public read bucket - unsigned list (GET without key) should succeed
|
||||||
|
it("should allow unsigned list on public read bucket", async () => {
|
||||||
|
const request = new Request(`${endpoint}/public-bucket/`, {
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await worker.fetch(request, PUBLIC_READ_ENV, CTX);
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
const xmlText = await response.text();
|
||||||
|
expect(xmlText).toContain("<ListBucketResult");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user