diff --git a/README.md b/README.md index 6a404c9..4bbe1c8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Private Satis Repository CloudFlare Worker -This is a [CloudFlare Worker](https://workers.cloudflare.com/) that allows you to host your own +This is a [CloudFlare Worker](https://workers.cloudflare.com/) that allows you to host your own private Composer repository directly from a [CloudFlare R2 Storage bucket](https://developers.cloudflare.com/r2/), without the need to run any servers. Authentication is done using usernames and passwords stored in [CloudFlare KV](https://developers.cloudflare.com/kv/). @@ -14,15 +14,18 @@ To deploy this worker, you need to clone repository, update `wrangler.toml` conf You need to update `wrangler.toml` file with your own values: -| Variable Name | Description | -|--------------------------------|----------------------------------------------------------------------------------------------------------------| -| `name` | Name of your CloudFlare Worker | -| `vars.PUBLIC_ACCESS_TO_INDEX` | If true, index page will be accessible without authentication | -| `vars.PUBLIC_ACCESS_TO_JSON` | If true, JSON indexes will be accessible without authentication | -| `vars.CHECK_FILE_RESTRICTIONS` | See [selective access](https://opensource.duma.sh/systems/serverless-satis/cloudflare-worker#selective-access) | -| `routes.pattern` | Domain you want to expose your private repository on (need to use CloudFlare DNS) | -| `kv_namespaces.id` | Namespace ID of your Worker KV to read users from | -| `r2_buckets.bucket_name` | Name of private bucket where are stored files generated by `s3-satis` tool | +| Variable Name | Description | +|--------------------------------------|----------------------------------------------------------------------------------------------------------------| +| `name` | Name of your CloudFlare Worker | +| `vars.PUBLIC_ACCESS_TO_INDEX` | If true, index page will be accessible without authentication | +| `vars.PUBLIC_ACCESS_TO_JSON` | If true, JSON indexes will be accessible without authentication | +| `vars.CHECK_FILE_RESTRICTIONS` | See [Selective Access](https://opensource.duma.sh/systems/serverless-satis/cloudflare-worker#selective-access) | +| `vars.CHECK_EXTRA_JSON_RESTRICTIONS` | See [Selective Access](https://opensource.duma.sh/systems/serverless-satis/cloudflare-worker#selective-access) | +| `vars.STORE_PASSWORDS_HASHED` | See [Authentication](https://opensource.duma.sh/systems/serverless-satis/cloudflare-worker#authentication) | +| `vars.ENABLE_USER_ENDPOINT` | See [User Endpoint](https://opensource.duma.sh/systems/serverless-satis/cloudflare-worker#user-endpoint) | +| `routes.pattern` | Domain you want to expose your private repository on (need to use CloudFlare DNS) | +| `kv_namespaces.id` | Namespace ID of your Worker KV to read users from | +| `r2_buckets.bucket_name` | Name of private bucket where are stored files generated by `s3-satis` tool | ```toml name = "" @@ -33,6 +36,9 @@ compatibility_date = "2023-12-06" PUBLIC_ACCESS_TO_INDEX = false PUBLIC_ACCESS_TO_JSON = false CHECK_FILE_RESTRICTIONS = false +CHECK_EXTRA_JSON_RESTRICTIONS = false +STORE_PASSWORDS_HASHED = false +ENABLE_USER_ENDPOINT = false [[routes]] pattern = "" diff --git a/src/helpers/buckets.ts b/src/helpers/buckets.ts index b5f612c..0a9aa55 100644 --- a/src/helpers/buckets.ts +++ b/src/helpers/buckets.ts @@ -86,8 +86,12 @@ export default { }) }, async getPermissions(pathname: string, env: Env): Promise { - let key = '.tags' + pathname + '.json'; - const object = await env.R2_BUCKET.get(key) + // @ts-ignore + return await this.loadJson('/.tags' + pathname + '.json', env); + }, + async loadJson(pathname: string, env: Env): Promise { + let objectName = pathname.slice(1) + const object = await env.R2_BUCKET.get(objectName) if (object === null) { return null; diff --git a/src/index.ts b/src/index.ts index d04ffb3..0bcd1dd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -79,6 +79,76 @@ export default { } } + if(env.CHECK_EXTRA_JSON_RESTRICTIONS && url.pathname.startsWith('/p2/') && url.pathname.endsWith('.json')) { + let json: any = await bucketsHelper.loadJson(url.pathname, env); + + if(!json) { + return bucketsHelper.objectNotFound(url.pathname); + } + + Object.keys(json['packages']).forEach(function(key, index) { + json['packages'][key] = json['packages'][key].map((version: any) => { + if (!('extra' in version)) { + return version; + } + + if (!('s3-satis-file-restrictions' in version['extra'])) { + return version; + } + + if(user !== null) { + let hasAccess = user.permissions.includes('*'); + version['extra']['s3-satis-file-restrictions'].forEach((p: string) => { + // @ts-ignore + if(user.permissions.includes(p)) { + hasAccess = true; + } + }); + + if (!hasAccess) { + user.permissions.forEach((p: string) => { + if(!p.includes('*')) { + return; + } + let pattern = '^' + p.replace('*', '.*') + '$'; + // @ts-ignore + version['extra']['s3-satis-file-restrictions'].forEach((p: string) => { + // @ts-ignore + if(p.match(pattern)) { + hasAccess = true; + } + }); + }); + } + + if(!hasAccess) { + return null; + } + } + + delete version['extra']['s3-satis-file-restrictions']; + + if(Object.keys(version['extra']).length == 0) { + delete version['extra']; + } + + return version; + }).filter((version: any) => { + return version !== null; + }); + + if(json['packages'][key].length == 0) { + delete json['packages'][key]; + } + }); + + return new Response(JSON.stringify(json, null, 2), { + headers: { + 'content-type': 'application/json', + }, + }); + } + return bucketsHelper.fetch(request, env); }, }; diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index 8b5fde3..6a8f595 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -6,6 +6,7 @@ interface Env { PUBLIC_ACCESS_TO_INDEX: boolean; PUBLIC_ACCESS_TO_JSON: boolean; CHECK_FILE_RESTRICTIONS: boolean; + CHECK_EXTRA_JSON_RESTRICTIONS: boolean; STORE_PASSWORDS_HASHED: boolean; ENABLE_USER_ENDPOINT: boolean; } diff --git a/wrangler.toml b/wrangler.toml index e36ca98..11516d5 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -6,6 +6,7 @@ compatibility_date = "2023-12-06" PUBLIC_ACCESS_TO_INDEX = false PUBLIC_ACCESS_TO_JSON = false CHECK_FILE_RESTRICTIONS = false +CHECK_EXTRA_JSON_RESTRICTIONS = false STORE_PASSWORDS_HASHED = false ENABLE_USER_ENDPOINT = false