From 3096ac3a83bfdb1917c3ece13e11b9cc428579be Mon Sep 17 00:00:00 2001 From: Rihan Arfan Date: Sat, 26 Oct 2024 23:38:04 +0100 Subject: [PATCH] feat: support cloudflare access when access remote storage --- src/features.ts | 10 +++++++++- src/module.ts | 5 +++++ src/runtime/ai/server/utils/ai.ts | 4 +++- src/runtime/analytics/server/utils/analytics.ts | 4 +++- src/runtime/blob/server/utils/blob.ts | 4 +++- src/runtime/database/server/utils/database.ts | 4 +++- src/runtime/kv/server/utils/kv.ts | 4 +++- src/runtime/utils/cloudflareAccess.ts | 10 ++++++++++ src/runtime/vectorize/server/utils/vectorize.ts | 4 +++- src/types/module.ts | 16 ++++++++++++++++ 10 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 src/runtime/utils/cloudflareAccess.ts diff --git a/src/features.ts b/src/features.ts index de97bc7f..28b7e679 100644 --- a/src/features.ts +++ b/src/features.ts @@ -19,6 +19,10 @@ export interface HubConfig { userToken?: string env?: string version?: string + cloudflareAccess?: { + clientId: string + clientSecret: string + } ai?: boolean analytics?: boolean @@ -310,7 +314,11 @@ export async function setupRemote(_nuxt: Nuxt, hub: HubConfig) { const remoteManifest = hub.remoteManifest = await $fetch('/api/_hub/manifest', { baseURL: hub.projectUrl as string, headers: { - authorization: `Bearer ${hub.projectSecretKey || hub.userToken}` + authorization: `Bearer ${hub.projectSecretKey || hub.userToken}`, + ...(hub.cloudflareAccess?.clientId && hub.cloudflareAccess?.clientSecret && { + 'CF-Access-Client-Id': hub.cloudflareAccess?.clientId, + 'CF-Access-Client-Secret': hub.cloudflareAccess?.clientSecret + }) } }) .catch(async (err) => { diff --git a/src/module.ts b/src/module.ts index d781c285..6eb43658 100644 --- a/src/module.ts +++ b/src/module.ts @@ -69,6 +69,11 @@ export default defineNuxtModule({ hyperdrive: {}, // @ts-expect-error nitro.cloudflare.wrangler is not yet typed compatibilityFlags: nuxt.options.nitro.cloudflare?.wrangler?.compatibility_flags + }, + // Cloudflare Access + cloudflareAccess: { + clientId: process.env.NUXT_HUB_CLOUDFLARE_ACCESS_CLIENT_ID || null, + clientSecret: process.env.NUXT_HUB_CLOUDFLARE_ACCESS_CLIENT_SECRET || null } }) runtimeConfig.hub = hub diff --git a/src/runtime/ai/server/utils/ai.ts b/src/runtime/ai/server/utils/ai.ts index 89525ffa..c334f1a7 100644 --- a/src/runtime/ai/server/utils/ai.ts +++ b/src/runtime/ai/server/utils/ai.ts @@ -5,6 +5,7 @@ import type { H3Error } from 'h3' import type { Ai } from '@cloudflare/workers-types/experimental' import { requireNuxtHubFeature } from '../../../utils/features' import { useRuntimeConfig } from '#imports' +import { getCloudflareAccessHeaders } from '~/src/runtime/utils/cloudflareAccess' let _ai: Ai @@ -30,7 +31,8 @@ export function hubAI(): Ai { // @ts-expect-error globalThis.__env__ is not defined const binding = process.env.AI || globalThis.__env__?.AI || globalThis.AI if (hub.remote && hub.projectUrl && !binding) { - _ai = proxyHubAI(hub.projectUrl, hub.projectSecretKey || hub.userToken) + const cfAccessHeaders = getCloudflareAccessHeaders(hub.cloudflareAccess) + _ai = proxyHubAI(hub.projectUrl, hub.projectSecretKey || hub.userToken, cfAccessHeaders) return _ai } if (binding) { diff --git a/src/runtime/analytics/server/utils/analytics.ts b/src/runtime/analytics/server/utils/analytics.ts index 4318ab3d..79b4ea79 100644 --- a/src/runtime/analytics/server/utils/analytics.ts +++ b/src/runtime/analytics/server/utils/analytics.ts @@ -4,6 +4,7 @@ import { joinURL } from 'ufo' import { createError } from 'h3' import { requireNuxtHubFeature } from '../../../utils/features' import { useRuntimeConfig } from '#imports' +import { getCloudflareAccessHeaders } from '~/src/runtime/utils/cloudflareAccess' const _datasets: Record = {} @@ -45,7 +46,8 @@ export function hubAnalytics() { const hub = useRuntimeConfig().hub const binding = getAnalyticsBinding() if (hub.remote && hub.projectUrl && !binding) { - return proxyHubAnalytics(hub.projectUrl, hub.projectSecretKey || hub.userToken) + const cfAccessHeaders = getCloudflareAccessHeaders(hub.cloudflareAccess) + return proxyHubAnalytics(hub.projectUrl, hub.projectSecretKey || hub.userToken, cfAccessHeaders) } const dataset = _useDataset() diff --git a/src/runtime/blob/server/utils/blob.ts b/src/runtime/blob/server/utils/blob.ts index b69c10ed..544c23f3 100644 --- a/src/runtime/blob/server/utils/blob.ts +++ b/src/runtime/blob/server/utils/blob.ts @@ -12,6 +12,7 @@ import type { BlobType, FileSizeUnit, BlobUploadedPart, BlobListResult, BlobMult import { streamToArrayBuffer } from '../../../utils/stream' import { requireNuxtHubFeature } from '../../../utils/features' import { useRuntimeConfig } from '#imports' +import { getCloudflareAccessHeaders } from '~/src/runtime/utils/cloudflareAccess' const _r2_buckets: Record = {} @@ -154,7 +155,8 @@ export function hubBlob(): HubBlob { const hub = useRuntimeConfig().hub const binding = getBlobBinding() if (hub.remote && hub.projectUrl && !binding) { - return proxyHubBlob(hub.projectUrl, hub.projectSecretKey || hub.userToken) + const cfAccessHeaders = getCloudflareAccessHeaders(hub.cloudflareAccess) + return proxyHubBlob(hub.projectUrl, hub.projectSecretKey || hub.userToken, cfAccessHeaders) } const bucket = _useBucket() diff --git a/src/runtime/database/server/utils/database.ts b/src/runtime/database/server/utils/database.ts index de2eb573..de1c8b79 100644 --- a/src/runtime/database/server/utils/database.ts +++ b/src/runtime/database/server/utils/database.ts @@ -5,6 +5,7 @@ import type { H3Error } from 'h3' import type { D1Database } from '@nuxthub/core' import { requireNuxtHubFeature } from '../../../utils/features' import { useRuntimeConfig } from '#imports' +import { getCloudflareAccessHeaders } from '~/src/runtime/utils/cloudflareAccess' let _db: D1Database @@ -28,7 +29,8 @@ export function hubDatabase(): D1Database { // @ts-expect-error globalThis.__env__ is not defined const binding = process.env.DB || globalThis.__env__?.DB || globalThis.DB if (hub.remote && hub.projectUrl && !binding) { - _db = proxyHubDatabase(hub.projectUrl, hub.projectSecretKey || hub.userToken) + const cfAccessHeaders = getCloudflareAccessHeaders(hub.cloudflareAccess) + _db = proxyHubDatabase(hub.projectUrl, hub.projectSecretKey || hub.userToken, cfAccessHeaders) return _db } if (binding) { diff --git a/src/runtime/kv/server/utils/kv.ts b/src/runtime/kv/server/utils/kv.ts index 1f2486b5..eeca1167 100644 --- a/src/runtime/kv/server/utils/kv.ts +++ b/src/runtime/kv/server/utils/kv.ts @@ -6,6 +6,7 @@ import { createError } from 'h3' import type { HubKV } from '@nuxthub/core' import { requireNuxtHubFeature } from '../../../utils/features' import { useRuntimeConfig } from '#imports' +import { getCloudflareAccessHeaders } from '~/src/runtime/utils/cloudflareAccess' let _kv: HubKV @@ -29,7 +30,8 @@ export function hubKV(): HubKV { // @ts-expect-error globalThis.__env__ is not defined const binding = process.env.KV || globalThis.__env__?.KV || globalThis.KV if (hub.remote && hub.projectUrl && !binding) { - return proxyHubKV(hub.projectUrl, hub.projectSecretKey || hub.userToken) + const cfAccessHeaders = getCloudflareAccessHeaders(hub.cloudflareAccess) + return proxyHubKV(hub.projectUrl, hub.projectSecretKey || hub.userToken, cfAccessHeaders) } if (binding) { const storage = createStorage({ diff --git a/src/runtime/utils/cloudflareAccess.ts b/src/runtime/utils/cloudflareAccess.ts new file mode 100644 index 00000000..d75a9c91 --- /dev/null +++ b/src/runtime/utils/cloudflareAccess.ts @@ -0,0 +1,10 @@ +import type { HubConfig } from '~/src/features' + +export function getCloudflareAccessHeaders(access: HubConfig['cloudflareAccess']): Record { + const isCloudflareAccessEnabled = !!(access?.clientId && access?.clientSecret) + if (!isCloudflareAccessEnabled) return {} + return { + 'CF-Access-Client-Id': access?.clientId, + 'CF-Access-Client-Secret': access?.clientSecret + } +} diff --git a/src/runtime/vectorize/server/utils/vectorize.ts b/src/runtime/vectorize/server/utils/vectorize.ts index f600cf2d..be702057 100644 --- a/src/runtime/vectorize/server/utils/vectorize.ts +++ b/src/runtime/vectorize/server/utils/vectorize.ts @@ -6,6 +6,7 @@ import type { RuntimeConfig } from 'nuxt/schema' import type { Vectorize } from '../../../../types/vectorize' import { requireNuxtHubFeature } from '../../../utils/features' import { useRuntimeConfig } from '#imports' +import { getCloudflareAccessHeaders } from '~/src/runtime/utils/cloudflareAccess' const _vectorize: Record = {} @@ -42,7 +43,8 @@ export function hubVectorize(index: VectorizeIndexes): Vectorize { // @ts-expect-error globalThis.__env__ is not defined const binding = process.env[bindingName] || globalThis.__env__?.[bindingName] || globalThis[bindingName] if (hub.remote && hub.projectUrl && !binding) { - _vectorize[index] = proxyHubVectorize(index, hub.projectUrl, hub.projectSecretKey || hub.userToken) + const cfAccessHeaders = getCloudflareAccessHeaders(hub.cloudflareAccess) + _vectorize[index] = proxyHubVectorize(index, hub.projectUrl, hub.projectSecretKey || hub.userToken, cfAccessHeaders) return _vectorize[index] } if (binding) { diff --git a/src/types/module.ts b/src/types/module.ts index e07c5ec3..44e40893 100644 --- a/src/types/module.ts +++ b/src/types/module.ts @@ -155,4 +155,20 @@ export interface ModuleOptions { [key: string]: string } } + /** + * Cloudflare Access authentication for remote storage. + * @see https://hub.nuxt.com/recipes/cloudflare-access + */ + cloudflareAccess?: { + /** + * The client ID for Cloudflare Access. + * @default process.env.NUXT_HUB_CLOUDFLARE_ACCESS_CLIENT_ID + */ + clientId?: string + /** + * The client secret for Cloudflare Access. + * @default process.env.NUXT_HUB_CLOUDFLARE_ACCESS_CLIENT_SECRET + */ + clientSecret?: string + } }