diff --git a/alchemy-web/src/content/docs/providers/cloudflare/account-api-token.md b/alchemy-web/src/content/docs/providers/cloudflare/account-api-token.md index f6e60d9f0..44857afbd 100644 --- a/alchemy-web/src/content/docs/providers/cloudflare/account-api-token.md +++ b/alchemy-web/src/content/docs/providers/cloudflare/account-api-token.md @@ -1,13 +1,17 @@ --- title: AccountApiToken -description: Learn how to create and manage Cloudflare Account API Tokens using Alchemy for secure access to the Cloudflare API. +description: Learn how to create and manage Cloudflare Account API Tokens using Alchemy for secure access to the Cloudflare API and R2 storage. --- -Creates a [Cloudflare API Token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) with specified permissions and access controls. +Creates a [Cloudflare API Token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) with specified permissions and access controls. Account API Tokens are the primary way to generate S3-compatible credentials for R2 storage, enabling pre-signed URLs and direct S3 API access. + +:::caution +Account API Tokens can not be created with OAuth tokens because of a Cloudflare limitation. use the Global API Key or an API Token instead. See the [Cloudflare Auth guide](/guides/cloudflare) for more details. +::: ## Minimal Example -Create a basic API token with read-only permissions. +Create a basic API token with read-only permissions for zones. ```ts import { AccountApiToken } from "alchemy/cloudflare"; @@ -26,9 +30,64 @@ const token = await AccountApiToken("readonly-token", { }); ``` +## R2 Bucket with Pre-Signed URLs + +Create an API token for R2 bucket access and use it to generate pre-signed URLs. + +```ts +import { AccountApiToken, R2Bucket } from "alchemy/cloudflare"; +import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"; +import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; + +// Create R2 bucket +const bucket = await R2Bucket("my-bucket", { + name: "my-bucket", +}); + +// Create API token with R2 permissions +const r2Token = await AccountApiToken("r2-access-token", { + name: "R2 Access Token", + policies: [ + { + effect: "allow", + permissionGroups: [ + "Workers R2 Storage Read", + "Workers R2 Storage Write", + ], + resources: { + [`com.cloudflare.edge.r2.bucket.${process.env.CLOUDFLARE_ACCOUNT_ID}_${bucket.jurisdiction ?? "default"}_${bucket.name}`]: + "*", + }, + }, + ], +}); + +// Configure S3 client with the token +const s3Client = new S3Client({ + region: "auto", + endpoint: `https://${process.env.CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`, + credentials: { + accessKeyId: r2Token.accessKeyId.unencrypted, + secretAccessKey: r2Token.secretAccessKey.unencrypted, + }, +}); + +// Generate a pre-signed URL for an object +const command = new GetObjectCommand({ + Bucket: bucket.name, + Key: "path/to/file.pdf", +}); + +const signedUrl = await getSignedUrl(s3Client, command, { + expiresIn: 3600, // URL expires in 1 hour +}); + +console.log("Pre-signed URL:", signedUrl); +``` + ## With Time and IP Restrictions -Create a token with time-based and IP address restrictions. +Create a token with time-based and IP address restrictions for enhanced security. ```ts import { AccountApiToken } from "alchemy/cloudflare"; @@ -55,19 +114,19 @@ const restrictedToken = await AccountApiToken("restricted-token", { }); ``` -## R2 Storage Access Token +## Account-Level Permissions -Create a token with R2 storage write permissions. +Create a token with broad account-level permissions for R2 storage operations. ```ts import { AccountApiToken } from "alchemy/cloudflare"; -const storageToken = await AccountApiToken("account-access-token", { - name: "alchemy-account-access-token", +const accountToken = await AccountApiToken("account-access-token", { + name: "Account R2 Token", policies: [ { effect: "allow", - permissionGroups: ["Workers R2 Storage Write"], + permissionGroups: ["Workers R2 Storage Write", "Workers R2 Storage Read"], resources: { "com.cloudflare.api.account": "*", }, @@ -76,31 +135,24 @@ const storageToken = await AccountApiToken("account-access-token", { }); ``` -## Bind to a Worker +## Understanding Access Credentials -Use the token in a Worker binding. +The `AccountApiToken` resource provides S3-compatible credentials through two properties: -```ts -import { Worker, AccountApiToken } from "alchemy/cloudflare"; +- **`accessKeyId`**: The token's ID, used as the AWS access key ID +- **`secretAccessKey`**: A SHA-256 hash of the token value, used as the AWS secret access key + +These credentials work with any S3-compatible client library, including the AWS SDK. -const token = await AccountApiToken("api-token", { - name: "Worker API Token", +```ts +const token = await AccountApiToken("my-token", { + name: "My Token", policies: [ - { - effect: "allow", - permissionGroups: ["Zone Read"], - resources: { - "com.cloudflare.api.account.zone.*": "*", - }, - }, + /* ... */ ], }); -await Worker("my-worker", { - name: "my-worker", - script: "console.log('Hello, world!')", - bindings: { - API_TOKEN: token, - }, -}); +// Use with S3 SDK +console.log("Access Key ID:", token.accessKeyId.unencrypted); +console.log("Secret Access Key:", token.secretAccessKey.unencrypted); ``` diff --git a/alchemy-web/src/content/docs/providers/cloudflare/account-id.md b/alchemy-web/src/content/docs/providers/cloudflare/account-id.md index 6ef410c42..ac7da8dee 100644 --- a/alchemy-web/src/content/docs/providers/cloudflare/account-id.md +++ b/alchemy-web/src/content/docs/providers/cloudflare/account-id.md @@ -12,7 +12,7 @@ Get the account ID from environment variables or API token: ```ts import { AccountId } from "alchemy/cloudflare"; -const accountId = await AccountId("my-account"); +const accountId = await AccountId(); ``` ## With Explicit API Key @@ -22,7 +22,7 @@ Provide an API key and email directly: ```ts import { AccountId } from "alchemy/cloudflare"; -const accountId = await AccountId("my-account", { +const accountId = await AccountId(, { apiKey: alchemy.secret(process.env.CF_API_KEY), email: "user@example.com", }); @@ -35,7 +35,7 @@ Use the account ID with a Worker: ```ts import { Worker, AccountId } from "alchemy/cloudflare"; -const accountId = await AccountId("my-account"); +const accountId = await AccountId(); await Worker("my-worker", { name: "my-worker",