diff --git a/docs/content/0.index.yml b/docs/content/0.index.yml index df309efb..49d2b071 100644 --- a/docs/content/0.index.yml +++ b/docs/content/0.index.yml @@ -10,8 +10,8 @@ hero: light: '/images/landing/hero-light.svg' dark: '/images/landing/hero-dark.svg' headline: - label: "Automatic Database Migrations" - to: /changelog/database-migrations + label: "Cloudflare Access integration" + to: /changelog/cloudflare-access icon: i-ph-arrow-right features: - name: Cloud Hosting diff --git a/docs/content/1.docs/3.recipes/6.cloudflare-access.md b/docs/content/1.docs/3.recipes/6.cloudflare-access.md new file mode 100644 index 00000000..c70be68f --- /dev/null +++ b/docs/content/1.docs/3.recipes/6.cloudflare-access.md @@ -0,0 +1,116 @@ +--- +title: Cloudflare Access Integration +navigation.title: Cloudflare Access +description: Learn how to use Cloudflare Access to protect your Nuxt application deployed on Cloudflare Pages. +--- + +[Cloudflare Access](https://www.cloudflare.com/zero-trust/products/access/) allows you to secure your web applications by restricting who can reach your application by applying configured identity-aware Access policies. Cloudflare Access is part of [Cloudflare's Zero Trust](https://www.cloudflare.com/plans/zero-trust-services/) offerings. + +NuxtHub fully supports Cloudflare Access across the NuxtHub admin, module and CLI. + +## Setup Cloudflare Access + +These steps covers setting up Cloudflare Access for a deployed NuxtHub project. + +::important{to="#nuxtdev-subdomain-with-cloudflare-access"} +When using Cloudflare Access with NuxtHub, the nuxt.dev subdomain is unavailable due to a Cloudflare limitation. [Learn more](#nuxtdev-subdomain-with-cloudflare-access). +:: + +1. Create a Cloudflare Access [service token](https://developers.cloudflare.com/cloudflare-one/identity/service-tokens/) in the [Cloudflare Zero Trust dashboard](https://one.dash.cloudflare.com/) + 1. In [Zero Trust](https://one.dash.cloudflare.com/), go to Access → Service Auth → Service Tokens + 2. Select Create Service Token + 3. Name the service token. For example, the NuxtHub project's name + - The name allows you to easily identify events related to the token in the logs + 4. Choose a Service Token Duration. This sets the expiration date for the token + 5. Select Generate token. You will see the generated Client ID and Client Secret for the service token + ::warning + This is the only time Cloudflare Access will display the Client Secret. If you lose the Client Secret, you will need to rotate the service token + :: +2. Configure the Cloudflare Access integration within [NuxtHub admin](https://admin.hub.nuxt.com/) + 1. In the [NuxtHub admin](https://admin.hub.nuxt.com/), go to Projects → Settings → General → Cloudflare Access + 2. Provide the Client ID and Client Secret generated in the previous step +3. Enable Cloudflare Access on the Pages project + 1. On the [Cloudflare dashboard](https://dash.cloudflare.com/login?to=/:account/workers-and-pages) → Workers & Pages → Your Pages project + 2. Go to Settings → General → Access Policy + 3. Select Enable to create a Cloudflare Access application. + ::note + The default policy covers the preview environments on the `pages.dev` subdomain, adds an allow policy with all members of the account, and uses the email one-time-pin IdP. + :: +4. Add an Access policy to permit the service token + 1. In [Zero Trust](https://one.dash.cloudflare.com/), go to Access → Applications and select the application + 2. Create a new policy with the name "NuxtHub" and the action Service Auth + 3. Enable the 401 Response boolean + 4. Within Configure rules, set Selector to Service Token and the value to the service token created earlier + 5. Save the policy +5. Optionally edit your Allow Access policy + ::callout{to="https://developers.cloudflare.com/cloudflare-one/policies/access/#allow"} + Learn more about Cloudflare Access policies on Cloudflare's documentation. + :: +6. Optionally add additional domains to your Access application + 1. In [Zero Trust](https://one.dash.cloudflare.com/), go to Access → Applications and select the application + 2. From the header, select Overview + 3. Add additional application domains, such as the production domain, or any custom domains assigned to the project + +### Importing Pages projects + +We plan to directly support importing existing Pages projects that are protected with Cloudflare Access enabled in the future. + +Currently, you will need to temporarily create an Access application which sets a Bypass policy for Everyone on the project's default pages.dev domain and the path `/api/_hub/manifest`. + +## Service token expiry and rotation + +### Service token expiry + +When a service token is expired, it can be rotated from the Cloudflare dashboard. + +1. In [Zero Trust](https://one.dash.cloudflare.com/), go to Access → Service Auth → Service Tokens +2. Click `...` on the expired token +3. Select Rotate + +::tip +The duration of active service tokens can be extended by refreshing it from the Zero Trust dashboard +:: + +### Service token rotation + +If a service token is rotated, the new Client Secret needs to be provided on [NuxtHub admin](https://admin.hub.nuxt.com/). +1. In the [NuxtHub admin](https://admin.hub.nuxt.com/), go to Projects → Settings → General → Cloudflare Access +2. Click Disable integration +3. Provide the Client ID and Client Secret generated in the previous step +4. Click Enable integration + +## Remote storage + +These steps will cover using [remote storage](/docs/getting-started/remote-storage) with an environment protected by Cloudflare Access. + +1. Open `.env` +2. Set the following variables with your service token's Client ID and Client Secret + - `NUXT_HUB_CLOUDFLARE_ACCESS_CLIENT_ID` + - `NUXT_HUB_CLOUDFLARE_ACCESS_CLIENT_SECRET` + +::note +A separate service token can optionally be created only for local development +:: + + +::tip +No configuration is required if using [Cloudflare WARP](https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/) with Cloudflare Zero Trust, as WARP handles authenticating with Cloudflare Access +:: + +## CLI + +The following environment variables can be passed to the CLI + - `NUXT_HUB_CLOUDFLARE_ACCESS_CLIENT_ID` + - `NUXT_HUB_CLOUDFLARE_ACCESS_CLIENT_SECRET` + +```bash [Terminal] +export NUXT_HUB_CLOUDFLARE_ACCESS_CLIENT_ID="" +export NUXT_HUB_CLOUDFLARE_ACCESS_CLIENT_SECRET="" +npx nuxthub database migrations list --preview +``` + +## `nuxt.dev` subdomain with Cloudflare Access + +Due to a technical Cloudflare limitation, when using Cloudflare Access with NuxtHub, the nuxt.dev subdomain is unavailable. + +If the nuxt.dev subdomain is the primary domain, enabling the Cloudflare Access integration will set the primary domain to the pages.dev subdomain. diff --git a/docs/content/4.changelog/cloudflare-access.md b/docs/content/4.changelog/cloudflare-access.md new file mode 100644 index 00000000..def0659e --- /dev/null +++ b/docs/content/4.changelog/cloudflare-access.md @@ -0,0 +1,32 @@ +--- +title: Cloudflare Access integration +description: "We now support Cloudflare Access within NuxtHub admin, module and CLI" +date: 2024-10-29 +image: '/images/changelog/cloudflare-access.png' +authors: + - name: Rihan Arfan + avatar: + src: https://avatars.githubusercontent.com/u/20425781?v=4 + to: https://x.com/RihanArfan + username: RihanArfan +--- + +::tip +This feature is available on all [NuxtHub plans](/pricing) and comes with the [v0.8.4](https://github.com/nuxt-hub/core/releases/tag/v0.8.4) release of `@nuxthub/core`. +:: + +We now fully support Cloudflare Access across admin, module and CLI. + +## What is Cloudflare Access + +:nuxt-img{src="/images/changelog/cloudflare-access.png" alt="Cloudflare Access" width="915" height="515"} + +[Cloudflare Access](https://www.cloudflare.com/zero-trust/products/access/) allows you to secure your web applications by restricting who can reach your application by applying configured identity-aware Access policies. Cloudflare Access is part of [Cloudflare's Zero Trust](https://www.cloudflare.com/plans/zero-trust-services/) offerings. + +## What does this mean for NuxtHub + +This enables you to create secure internal web applications on NuxtHub, without compromising on features like NuxtHub admin for management and remote storage during development. + +::callout{to="/docs/recipes/cloudflare-access" icon="i-ph-book-open-duotone"} +Learn more about enabling Cloudflare Access with NuxtHub. +:: diff --git a/docs/public/images/changelog/cloudflare-access.png b/docs/public/images/changelog/cloudflare-access.png new file mode 100644 index 00000000..60c430cf Binary files /dev/null and b/docs/public/images/changelog/cloudflare-access.png differ diff --git a/src/features.ts b/src/features.ts index dd991ceb..3b8be628 100644 --- a/src/features.ts +++ b/src/features.ts @@ -8,6 +8,7 @@ import { joinURL } from 'ufo' import { defu } from 'defu' import { $fetch } from 'ofetch' import { addDevToolsCustomTabs } from './utils/devtools' +import { getCloudflareAccessHeaders } from './runtime/utils/cloudflareAccess' const log = logger.withTag('nuxt:hub') const { resolve, resolvePath } = createResolver(import.meta.url) @@ -21,6 +22,10 @@ export interface HubConfig { userToken?: string env?: string version?: string + cloudflareAccess?: { + clientId: string + clientSecret: string + } ai?: boolean analytics?: boolean @@ -332,7 +337,8 @@ 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}`, + ...getCloudflareAccessHeaders(hub.cloudflareAccess) } }) .catch(async (err) => { diff --git a/src/module.ts b/src/module.ts index fc631938..73c2b8ae 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 4f4ab439..9cca0d9e 100644 --- a/src/runtime/ai/server/utils/ai.ts +++ b/src/runtime/ai/server/utils/ai.ts @@ -4,6 +4,7 @@ import { createError } from 'h3' import type { H3Error } from 'h3' import type { Ai } from '@cloudflare/workers-types/experimental' import { requireNuxtHubFeature } from '../../../utils/features' +import { getCloudflareAccessHeaders } from '../../../utils/cloudflareAccess' import { useRuntimeConfig } from '#imports' 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) } else if (import.meta.dev) { // Mock _ai to call NuxtHub Admin API to proxy CF account & API token _ai = { @@ -72,6 +74,7 @@ export function hubAI(): Ai { * * @param projectUrl The project URL (e.g. https://my-deployed-project.nuxt.dev) * @param secretKey The secret key to authenticate to the remote endpoint + * @param headers The headers to send with the request to the remote endpoint * * @example ```ts * const ai = proxyHubAI('https://my-deployed-project.nuxt.dev', 'my-secret-key') @@ -82,14 +85,15 @@ export function hubAI(): Ai { * * @see https://developers.cloudflare.com/workers-ai/configuration/bindings/#methods */ -export function proxyHubAI(projectUrl: string, secretKey?: string): Ai { +export function proxyHubAI(projectUrl: string, secretKey?: string, headers?: HeadersInit): Ai { requireNuxtHubFeature('ai') const aiAPI = ofetch.create({ baseURL: joinURL(projectUrl, '/api/_hub/ai'), method: 'POST', headers: { - Authorization: `Bearer ${secretKey}` + Authorization: `Bearer ${secretKey}`, + ...headers } }) return { diff --git a/src/runtime/analytics/server/utils/analytics.ts b/src/runtime/analytics/server/utils/analytics.ts index 793fae15..351dcf14 100644 --- a/src/runtime/analytics/server/utils/analytics.ts +++ b/src/runtime/analytics/server/utils/analytics.ts @@ -3,6 +3,7 @@ import { ofetch } from 'ofetch' import { joinURL } from 'ufo' import { createError } from 'h3' import { requireNuxtHubFeature } from '../../../utils/features' +import { getCloudflareAccessHeaders } from '../../../utils/cloudflareAccess' import { useRuntimeConfig } from '#imports' 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() @@ -57,13 +59,14 @@ export function hubAnalytics() { } } -export function proxyHubAnalytics(projectUrl: string, secretKey?: string) { +export function proxyHubAnalytics(projectUrl: string, secretKey?: string, headers?: HeadersInit) { requireNuxtHubFeature('analytics') const analyticsAPI = ofetch.create({ baseURL: joinURL(projectUrl, '/api/_hub/analytics'), headers: { - Authorization: `Bearer ${secretKey}` + Authorization: `Bearer ${secretKey}`, + ...headers } }) diff --git a/src/runtime/blob/server/utils/blob.ts b/src/runtime/blob/server/utils/blob.ts index 35209e0b..65bfc8d5 100644 --- a/src/runtime/blob/server/utils/blob.ts +++ b/src/runtime/blob/server/utils/blob.ts @@ -11,6 +11,7 @@ import { joinURL } from 'ufo' import type { BlobType, FileSizeUnit, BlobUploadedPart, BlobListResult, BlobMultipartUpload, HandleMPUResponse, BlobMultipartOptions, BlobUploadOptions, BlobPutOptions, BlobEnsureOptions, BlobObject, BlobListOptions, BlobCredentialsOptions, BlobCredentials } from '@nuxthub/core' import { streamToArrayBuffer } from '../../../utils/stream' import { requireNuxtHubFeature } from '../../../utils/features' +import { getCloudflareAccessHeaders } from '../../../utils/cloudflareAccess' import { useRuntimeConfig } from '#imports' const _r2_buckets: Record = {} @@ -166,7 +167,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() @@ -350,6 +352,7 @@ export function hubBlob(): HubBlob { * * @param projectUrl The project URL (e.g. https://my-deployed-project.nuxt.dev) * @param secretKey The secret key to authenticate to the remote endpoint + * @param headers The headers to send with the request to the remote endpoint * * @example ```ts * const blob = proxyHubBlob('https://my-deployed-project.nuxt.dev', 'my-secret-key') @@ -358,13 +361,14 @@ export function hubBlob(): HubBlob { * * @see https://hub.nuxt.com/docs/features/blob */ -export function proxyHubBlob(projectUrl: string, secretKey?: string): HubBlob { +export function proxyHubBlob(projectUrl: string, secretKey?: string, headers?: HeadersInit): HubBlob { requireNuxtHubFeature('blob') const blobAPI = ofetch.create({ baseURL: joinURL(projectUrl, '/api/_hub/blob'), headers: { - Authorization: `Bearer ${secretKey}` + Authorization: `Bearer ${secretKey}`, + ...headers } }) diff --git a/src/runtime/cache/server/utils/cache.ts b/src/runtime/cache/server/utils/cache.ts index b3a5dfb2..658fd3c8 100644 --- a/src/runtime/cache/server/utils/cache.ts +++ b/src/runtime/cache/server/utils/cache.ts @@ -17,6 +17,7 @@ export function hubCacheBinding(name: string = 'CACHE'): KVNamespace { * * @param projectUrl The project URL (e.g. https://my-deployed-project.nuxt.dev) * @param secretKey The secret key to authenticate to the remote endpoint + * @param headers The headers to send with the request to the remote endpoint * * @example ```ts * const cache = proxyHubCache('https://my-deployed-project.nuxt.dev', 'my-secret-key') @@ -24,13 +25,14 @@ export function hubCacheBinding(name: string = 'CACHE'): KVNamespace { * ``` * */ -export function proxyHubCache(projectUrl: string, secretKey?: string) { +export function proxyHubCache(projectUrl: string, secretKey?: string, headers?: HeadersInit) { requireNuxtHubFeature('cache') const cacheAPI = ofetch.create({ baseURL: joinURL(projectUrl, '/api/_hub/cache'), headers: { - Authorization: `Bearer ${secretKey}` + Authorization: `Bearer ${secretKey}`, + ...headers } }) diff --git a/src/runtime/database/server/utils/database.ts b/src/runtime/database/server/utils/database.ts index ff2b634e..d115bd45 100644 --- a/src/runtime/database/server/utils/database.ts +++ b/src/runtime/database/server/utils/database.ts @@ -4,6 +4,7 @@ import { createError } from 'h3' import type { H3Error } from 'h3' import type { D1Database } from '@nuxthub/core' import { requireNuxtHubFeature } from '../../../utils/features' +import { getCloudflareAccessHeaders } from '../../../utils/cloudflareAccess' import { useRuntimeConfig } from '#imports' 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) { @@ -43,6 +45,7 @@ export function hubDatabase(): D1Database { * * @param projectUrl The project URL (e.g. https://my-deployed-project.nuxt.dev) * @param secretKey The secret key to authenticate to the remote endpoint + * @param headers The headers to send with the request to the remote endpoint * * @example ```ts * const db = proxyHubDatabase('https://my-deployed-project.nuxt.dev', 'my-secret-key') @@ -51,14 +54,15 @@ export function hubDatabase(): D1Database { * * @see https://hub.nuxt.com/docs/features/database */ -export function proxyHubDatabase(projectUrl: string, secretKey?: string): D1Database { +export function proxyHubDatabase(projectUrl: string, secretKey?: string, headers?: HeadersInit): D1Database { requireNuxtHubFeature('database') const d1API = ofetch.create({ baseURL: joinURL(projectUrl, '/api/_hub/database'), method: 'POST', headers: { - Authorization: `Bearer ${secretKey}` + Authorization: `Bearer ${secretKey}`, + ...headers } }) return { diff --git a/src/runtime/kv/server/utils/kv.ts b/src/runtime/kv/server/utils/kv.ts index 88b922bf..6b69a11c 100644 --- a/src/runtime/kv/server/utils/kv.ts +++ b/src/runtime/kv/server/utils/kv.ts @@ -5,6 +5,7 @@ import { joinURL } from 'ufo' import { createError } from 'h3' import type { HubKV } from '@nuxthub/core' import { requireNuxtHubFeature } from '../../../utils/features' +import { getCloudflareAccessHeaders } from '../../../utils/cloudflareAccess' import { useRuntimeConfig } from '#imports' 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({ @@ -48,6 +50,7 @@ export function hubKV(): HubKV { * * @param projectUrl The project URL (e.g. https://my-deployed-project.nuxt.dev) * @param secretKey The secret key to authenticate to the remote endpoint + * @param headers The headers to send with the request to the remote endpoint * * @example ```ts * const kv = proxyHubKV('https://my-deployed-project.nuxt.dev', 'my-secret-key') @@ -56,14 +59,15 @@ export function hubKV(): HubKV { * * @see https://hub.nuxt.com/docs/features/kv */ -export function proxyHubKV(projectUrl: string, secretKey?: string): HubKV { +export function proxyHubKV(projectUrl: string, secretKey?: string, headers?: Record): HubKV { requireNuxtHubFeature('kv') const storage = createStorage({ driver: httpDriver({ base: joinURL(projectUrl, '/api/_hub/kv/'), headers: { - Authorization: `Bearer ${secretKey}` + Authorization: `Bearer ${secretKey}`, + ...headers } }) }) 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 46bcc818..fb4d1c27 100644 --- a/src/runtime/vectorize/server/utils/vectorize.ts +++ b/src/runtime/vectorize/server/utils/vectorize.ts @@ -5,6 +5,7 @@ import type { H3Error } from 'h3' import type { RuntimeConfig } from 'nuxt/schema' import type { Vectorize } from '@nuxthub/core' import { requireNuxtHubFeature } from '../../../utils/features' +import { getCloudflareAccessHeaders } from '../../../utils/cloudflareAccess' import { useRuntimeConfig } from '#imports' 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) { @@ -58,6 +60,7 @@ export function hubVectorize(index: VectorizeIndexes): Vectorize { * @param index The Vectorize index to access * @param projectUrl The project URL (e.g. https://my-deployed-project.nuxt.dev) * @param secretKey The secret key to authenticate to the remote endpoint + * @param headers The headers to send with the request to the remote endpoint * * @example ```ts * const db = proxyHubVectorize('https://my-deployed-project.nuxt.dev', 'my-secret-key') @@ -70,14 +73,15 @@ export function hubVectorize(index: VectorizeIndexes): Vectorize { * * @see https://developers.cloudflare.com/vectorize/reference/client-api/ */ -export function proxyHubVectorize(index: VectorizeIndexes, projectUrl: string, secretKey?: string): Vectorize { +export function proxyHubVectorize(index: VectorizeIndexes, projectUrl: string, secretKey?: string, headers?: HeadersInit): Vectorize { requireNuxtHubFeature('vectorize') const vectorizeAPI = ofetch.create({ baseURL: joinURL(projectUrl, `/api/_hub/vectorize/${index}`), method: 'POST', headers: { - Authorization: `Bearer ${secretKey}` + Authorization: `Bearer ${secretKey}`, + ...headers } }) return { diff --git a/src/types/module.ts b/src/types/module.ts index fa506099..d109d3cb 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 + } }