├── .gitignore ├── .astro └── types.d.ts ├── src └── env.d.ts ├── package ├── src │ ├── env.d.ts │ ├── index.ts │ ├── index.d.ts │ ├── middleware.ts │ ├── stubs.ts │ ├── strings.ts │ ├── schema.ts │ ├── toolbar.ts │ └── integration.ts ├── lib │ ├── env.d.ts │ ├── server.ts │ └── components │ │ ├── TurnstileForm.astro │ │ └── TurnstileWidget.astro ├── tsconfig.json ├── package.json ├── CHANGELOG.md └── README.md ├── playground ├── .env.example ├── src │ ├── env.d.ts │ ├── pages │ │ ├── error.astro │ │ ├── success.astro │ │ └── index.astro │ ├── layouts │ │ └── Layout.astro │ └── components │ │ └── Card.astro ├── .vscode │ ├── extensions.json │ └── launch.json ├── tsconfig.json ├── tailwind.config.mjs ├── .gitignore ├── astro.config.mjs ├── package.json ├── public │ └── favicon.svg └── README.md ├── .vscode └── settings.json ├── pnpm-workspace.yaml ├── renovate.json ├── .changeset ├── config.json └── README.md ├── biome.json ├── .github ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── release.yml ├── README.md ├── package.json ├── LICENSE ├── scripts └── release.mjs ├── CODE_OF_CONDUCT.md └── CONTRIBUTING.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.astro/types.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /package/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /playground/.env.example: -------------------------------------------------------------------------------- 1 | TURNSTILE_TOKEN="1x0000000000000000000000000000000AA" -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "biomejs.biome" 3 | } 4 | -------------------------------------------------------------------------------- /package/lib/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - package 3 | - "packages/*" 4 | - "playground" 5 | -------------------------------------------------------------------------------- /package/src/index.ts: -------------------------------------------------------------------------------- 1 | import astroTurnstile from "./integration.js"; 2 | 3 | export default astroTurnstile; 4 | -------------------------------------------------------------------------------- /playground/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /package/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strictest", 3 | "compilerOptions": { 4 | "jsx": "preserve" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /playground/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "jsx": "preserve" 5 | }, 6 | "include": [".astro/types.d.ts", "**/*"], 7 | "exclude": ["dist"] 8 | } 9 | -------------------------------------------------------------------------------- /playground/tailwind.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /playground/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": ["playground"] 11 | } 12 | -------------------------------------------------------------------------------- /playground/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.5.2/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "linter": { 7 | "enabled": true, 8 | "rules": { 9 | "recommended": true, 10 | "suspicious": { 11 | "noExplicitAny": "warn" 12 | } 13 | } 14 | }, 15 | "files": { 16 | "ignore": ["dist", ".astro"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /playground/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import node from '@astrojs/node'; 2 | import tailwind from '@astrojs/tailwind'; 3 | import astroTurnstile from 'astro-turnstile'; 4 | import {defineConfig} from 'astro/config'; 5 | 6 | // https://astro.build/config 7 | export default defineConfig({ 8 | adapter: node({mode: 'standalone'}), 9 | integrations: [ 10 | tailwind(), 11 | astroTurnstile({ 12 | verbose: true, 13 | }), 14 | ], 15 | }); 16 | -------------------------------------------------------------------------------- /playground/src/pages/error.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from '../layouts/Layout.astro'; 3 | --- 4 | 5 | 6 |
7 |

Error

8 |
9 |
10 | 11 | 24 | -------------------------------------------------------------------------------- /playground/src/pages/success.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from '../layouts/Layout.astro'; 3 | --- 4 | 5 | 6 |
7 |

Success

8 |
9 |
10 | 11 | 24 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /playground/src/layouts/Layout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | interface Props { 3 | title: string; 4 | } 5 | 6 | const {title} = Astro.props; 7 | --- 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {title} 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Astro Turnstile 2 | 3 | [![npm version](https://badge.fury.io/js/astro-turnstile.svg)](https://badge.fury.io/js/astro-turnstile) 4 | 5 | Astro Turnstile is a package that provides a simple way to add a turnstile to your Astro site. It is a simple and easy to use package that can be added to your site with just a few lines of code. 6 | 7 | 8 | ## Installation 9 | 10 | To see how to get started, check out the [package README](./package/README.md) 11 | 12 | ## Licensing 13 | 14 | [MIT Licensed](./LICENSE). Made with ❤️ by [Hunter Bertoson](https://github.com/hkbertoson). 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "packageManager": "pnpm@10.11.0", 5 | "engines": { 6 | "node": ">=20.10.0" 7 | }, 8 | "scripts": { 9 | "playground:dev": "pnpm --filter playground dev", 10 | "changeset": "changeset", 11 | "release": "node scripts/release.mjs", 12 | "ci:version": "pnpm changeset version", 13 | "ci:publish": "pnpm changeset publish", 14 | "lint": "biome check .", 15 | "lint:fix": "biome check --write ." 16 | }, 17 | "devDependencies": { 18 | "@biomejs/biome": "1.9.2", 19 | "@changesets/cli": "^2.27.10" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "private": true, 6 | "scripts": { 7 | "dev": "astro dev", 8 | "start": "astro dev", 9 | "build": "astro check && astro build", 10 | "preview": "astro preview", 11 | "astro": "astro" 12 | }, 13 | "dependencies": { 14 | "@astrojs/tailwind": "^5.1.4", 15 | "@astrojs/node": "^9.0.0", 16 | "astro": "^5.0.2", 17 | "astro-turnstile": "workspace:*", 18 | "tailwindcss": "^3.4.16" 19 | }, 20 | "devDependencies": { 21 | "@astrojs/check": "^0.9.4", 22 | "@types/node": "^22.10.1", 23 | "typescript": "^5.7.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /playground/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /package/src/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { AstroIntegration } from "astro"; 2 | import type { AstroTurnstileOptions } from "./schema"; 3 | 4 | /** 5 | * # Astro Turnstile integration. 6 | * 7 | * @description An [Astro](https://astro.build) integration that enables ease of use within Astro for Cloudflare Turnstile Captcha. 8 | * @requires `TURNSTILE_SECRET_KEY` .env variable - The secret key for the Turnstile API. 9 | * @requires `TURNSTILE_SITE_KEY` .env variable - The site key for the Turnstile API. 10 | * @see [Cloudflare: Get started with Turnstile](https://developers.cloudflare.com/turnstile/get-started/#get-a-sitekey-and-secret-key) For instructions on how to get your Turnstile API keys. 11 | * @param {string} options.endpointPath - The path to use for the injected Turnstile API endpoint. 12 | * @param {boolean} options.verbose - Enable verbose logging. 13 | * @returns {AstroIntegration & {}} The Astro Turnstile integration. 14 | */ 15 | export default function astroTurnstile( 16 | options?: AstroTurnstileOptions, 17 | ): AstroIntegration & {}; 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Cyber Logical Development 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Repo 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup pnpm 19 | uses: pnpm/action-setup@v4 20 | 21 | - name: Setup Node.js 20.x 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: 20 25 | cache: "pnpm" 26 | 27 | - name: Install dependencies 28 | run: pnpm install 29 | 30 | - name: Create Release Pull Request or Publish to npm 31 | id: changesets 32 | uses: changesets/action@v1 33 | with: 34 | version: pnpm ci:version 35 | commit: "[ci]: release" 36 | title: "[ci] Release" 37 | publish: pnpm ci:publish 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 41 | -------------------------------------------------------------------------------- /package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-turnstile", 3 | "version": "2.1.0", 4 | "description": "Astro cloudflare turnstile integration", 5 | "author": { 6 | "email": "dev@hunterbertoson.tech", 7 | "name": "Hunter Bertoson", 8 | "url": "https://github.com/hkbertoson/astro-turnstile" 9 | }, 10 | "license": "MIT", 11 | "keywords": [ 12 | "astro-integration", 13 | "astro-component", 14 | "withastro", 15 | "astro", 16 | "turnstile" 17 | ], 18 | "homepage": "https://github.com/hkbertoson/astro-turnstile", 19 | "publishConfig": { 20 | "access": "public" 21 | }, 22 | "sideEffects": false, 23 | "files": [ 24 | "src", 25 | "lib" 26 | ], 27 | "exports": { 28 | ".": { 29 | "types": "./src/index.d.ts", 30 | "default": "./src/index.ts" 31 | }, 32 | "./schema": "./src/schema.ts", 33 | "./server": "./lib/server.ts", 34 | "./components/TurnstileWidget": "./lib/components/TurnstileWidget.astro", 35 | "./components/TurnstileForm": "./lib/components/TurnstileForm.astro" 36 | }, 37 | "scripts": {}, 38 | "type": "module", 39 | "peerDependencies": { 40 | "astro": ">=5.0.2" 41 | }, 42 | "dependencies": { 43 | "@matthiesenxyz/astrodtsbuilder": "^0.2.0", 44 | "astro-integration-kit": "^0.18.0", 45 | "vite": "^6.0.5" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /playground/src/components/Card.astro: -------------------------------------------------------------------------------- 1 | --- 2 | interface Props { 3 | title: string; 4 | body: string; 5 | href: string; 6 | } 7 | 8 | const { href, title, body } = Astro.props; 9 | --- 10 | 11 | 22 | 62 | -------------------------------------------------------------------------------- /scripts/release.mjs: -------------------------------------------------------------------------------- 1 | import { spawn } from "node:child_process"; 2 | import { resolve } from "node:path"; 3 | 4 | /** 5 | * 6 | * @param {string} command 7 | * @param {...Array} args 8 | * 9 | * @returns {Promise} 10 | */ 11 | const run = async (command, ...args) => { 12 | const cwd = resolve(); 13 | return new Promise((resolve) => { 14 | const cmd = spawn(command, args, { 15 | stdio: ["inherit", "pipe", "pipe"], // Inherit stdin, pipe stdout, pipe stderr 16 | shell: true, 17 | cwd, 18 | }); 19 | 20 | let output = ""; 21 | 22 | cmd.stdout.on("data", (data) => { 23 | process.stdout.write(data.toString()); 24 | output += data.toString(); 25 | }); 26 | 27 | cmd.stderr.on("data", (data) => { 28 | process.stderr.write(data.toString()); 29 | }); 30 | 31 | cmd.on("close", () => { 32 | resolve(output); 33 | }); 34 | }); 35 | }; 36 | 37 | const main = async () => { 38 | await run("pnpm changeset version"); 39 | await run("git add ."); 40 | await run('git commit -m "chore: update version"'); 41 | await run("git push"); 42 | await run("pnpm changeset publish"); 43 | await run("git push --follow-tags"); 44 | const tag = (await run("git describe --abbrev=0")).replace("\n", ""); 45 | await run( 46 | `gh release create ${tag} --title ${tag} --notes "Please refer to [CHANGELOG.md](https://github.com/hkbertoson/astro-turnstile/blob/main/package/CHANGELOG.md) for details."`, 47 | ); 48 | }; 49 | 50 | main(); 51 | -------------------------------------------------------------------------------- /playground/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import TurnstileWidget from 'astro-turnstile:components/TurnstileWidget'; 3 | import TurnstileForm from 'astro-turnstile/components/TurnstileForm'; 4 | import Layout from '../layouts/Layout.astro'; 5 | 6 | if (Astro.request.method === 'POST') { 7 | console.log('success'); 8 | } 9 | --- 10 | 11 | 12 |
13 |
14 | 18 | 19 | 20 | 21 | 29 |
30 | 31 | 68 |
69 | -------------------------------------------------------------------------------- /package/lib/server.ts: -------------------------------------------------------------------------------- 1 | import { TURNSTILE_SECRET_KEY } from "astro:env/server"; 2 | import type { APIRoute } from "astro"; 3 | 4 | // Ensure this route is not prerendered by Astro 5 | export const prerender = false; 6 | 7 | /** 8 | * The Astro-Turnstile API endpoint for verifying tokens. 9 | */ 10 | export const POST: APIRoute = async ({ request }) => { 11 | // Parse the incoming form data 12 | const data = await request.formData(); 13 | 14 | // Get the Turnstile token and the connecting IP 15 | const turnstileToken = data.get("cf-turnstile-response"); 16 | const connectingIP = request.headers.get("CF-Connecting-IP"); 17 | 18 | // Ensure the secret key and token are present 19 | if (!TURNSTILE_SECRET_KEY || !turnstileToken) { 20 | return new Response(null, { 21 | status: 400, 22 | statusText: 23 | "[Astro-Turnstile] Missing secret key or token, please contact the site administrator", 24 | }); 25 | } 26 | 27 | // Validate the token 28 | const formData = new FormData(); 29 | 30 | // Add the secret key and token to the form data 31 | formData.append("secret", TURNSTILE_SECRET_KEY); 32 | formData.append("response", turnstileToken); 33 | 34 | // If there is a connecting IP, add it to the form data 35 | connectingIP && formData.append("remoteip", connectingIP); 36 | 37 | // Send the token to the Turnstile API for verification 38 | const result = await fetch( 39 | "https://challenges.cloudflare.com/turnstile/v0/siteverify", 40 | { 41 | body: formData, 42 | method: "POST", 43 | }, 44 | ); 45 | 46 | // Parse the outcome 47 | const outcome = await result.json(); 48 | 49 | // Return the outcome 50 | if (outcome.success) { 51 | return new Response(null, { 52 | status: 200, 53 | statusText: "[Astro-Turnstile] Token verification successful", 54 | }); 55 | } 56 | 57 | // Return an error message if the token is invalid 58 | return new Response(null, { 59 | status: 400, 60 | statusText: "[Astro-Turnstile] Unable to verify token", 61 | }); 62 | }; 63 | 64 | /** 65 | * The Astro-Turnstile API endpoint for all other requests. 66 | */ 67 | export const ALL: APIRoute = async () => { 68 | // Return a 405 error for all other requests than POST 69 | return new Response( 70 | JSON.stringify({ error: "Method not allowed" }, null, 2), 71 | { 72 | status: 405, 73 | statusText: "method not allowed", 74 | headers: { 75 | "content-type": "application/json", 76 | }, 77 | }, 78 | ); 79 | }; 80 | -------------------------------------------------------------------------------- /playground/README.md: -------------------------------------------------------------------------------- 1 | # Astro Starter Kit: Basics 2 | 3 | ```sh 4 | npm create astro@latest -- --template basics 5 | ``` 6 | 7 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) 8 | [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics) 9 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json) 10 | 11 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 12 | 13 | ![just-the-basics](https://github.com/withastro/astro/assets/2244813/a0a5533c-a856-4198-8470-2d67b1d7c554) 14 | 15 | ## 🚀 Project Structure 16 | 17 | Inside of your Astro project, you'll see the following folders and files: 18 | 19 | ```text 20 | / 21 | ├── public/ 22 | │ └── favicon.svg 23 | ├── src/ 24 | │ ├── components/ 25 | │ │ └── Card.astro 26 | │ ├── layouts/ 27 | │ │ └── Layout.astro 28 | │ └── pages/ 29 | │ └── index.astro 30 | └── package.json 31 | ``` 32 | 33 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 34 | 35 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. 36 | 37 | Any static assets, like images, can be placed in the `public/` directory. 38 | 39 | ## 🧞 Commands 40 | 41 | All commands are run from the root of the project, from a terminal: 42 | 43 | | Command | Action | 44 | | :------------------------ | :----------------------------------------------- | 45 | | `npm install` | Installs dependencies | 46 | | `npm run dev` | Starts local dev server at `localhost:4321` | 47 | | `npm run build` | Build your production site to `./dist/` | 48 | | `npm run preview` | Preview your build locally, before deploying | 49 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | 50 | | `npm run astro -- --help` | Get help using the Astro CLI | 51 | 52 | ## 👀 Want to learn more? 53 | 54 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). 55 | -------------------------------------------------------------------------------- /package/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import type { MiddlewareHandler } from "astro"; 2 | 3 | const COMPONENT_IDENTIFIER = 'data-turnstile-container'; 4 | const TURNSTILE_SCRIPT = ` 5 | `; 11 | 12 | const MAX_SCAN_BYTES = 16384; // 16KB 13 | let bytesScanned = 0; 14 | 15 | export const onRequest: MiddlewareHandler = async (_, next) => { 16 | const response = await next(); 17 | 18 | const contentType = response.headers.get('Content-Type'); 19 | if (!contentType?.includes('text/html') || !response.body) { 20 | return response; 21 | } 22 | 23 | const transformedBody = response.body 24 | .pipeThrough(new TextDecoderStream()) 25 | .pipeThrough(createFastScriptInjectionTransform()) 26 | .pipeThrough(new TextEncoderStream()); 27 | 28 | return new Response(transformedBody, { 29 | status: response.status, 30 | statusText: response.statusText, 31 | headers: response.headers 32 | }); 33 | }; 34 | 35 | function createFastScriptInjectionTransform(): TransformStream { 36 | let hasInjected = false; 37 | let hasFoundComponent = false; 38 | let buffer = ''; 39 | 40 | return new TransformStream({ 41 | transform(chunk: string, controller) { 42 | // Fast path: already injected or scanned too much 43 | if (hasInjected || bytesScanned > MAX_SCAN_BYTES) { 44 | controller.enqueue(chunk); 45 | return; 46 | } 47 | 48 | bytesScanned += chunk.length; 49 | 50 | // Fast path: haven't found component yet 51 | if (!hasFoundComponent) { 52 | // Look for the data attribute 53 | if (chunk.includes(COMPONENT_IDENTIFIER)) { 54 | hasFoundComponent = true; 55 | buffer = chunk; 56 | } else { 57 | controller.enqueue(chunk); 58 | return; 59 | } 60 | } else { 61 | buffer += chunk; 62 | } 63 | 64 | const headCloseIndex = buffer.indexOf(''); 65 | if (headCloseIndex === -1) { 66 | if (buffer.length > MAX_SCAN_BYTES) { 67 | controller.enqueue(buffer); 68 | buffer = ''; 69 | hasInjected = true; 70 | } 71 | return; 72 | } 73 | 74 | const injectedContent = 75 | buffer.slice(0, headCloseIndex) + 76 | TURNSTILE_SCRIPT + 77 | buffer.slice(headCloseIndex); 78 | 79 | controller.enqueue(injectedContent); 80 | hasInjected = true; 81 | buffer = ''; 82 | }, 83 | 84 | flush(controller) { 85 | if (buffer) { 86 | controller.enqueue(buffer); 87 | } 88 | bytesScanned = 0; 89 | } 90 | }); 91 | } -------------------------------------------------------------------------------- /package/src/stubs.ts: -------------------------------------------------------------------------------- 1 | import astroDtsBuilder from '@matthiesenxyz/astrodtsbuilder'; 2 | import {name} from '../package.json'; 3 | 4 | // Create the config DTS file 5 | const config = astroDtsBuilder(); 6 | 7 | // Add a note to the top of the file 8 | config.addSingleLineNote( 9 | `This file is generated by '${name}' and should not be modified manually.` 10 | ); 11 | 12 | // Add the module to the file 13 | config.addModule('virtual:astro-turnstile/config', { 14 | defaultExport: { 15 | typeDef: `import("${name}/schema").AstroTurnstileConfig`, 16 | singleLineDescription: 'The Turnstile configuration options', 17 | }, 18 | }); 19 | 20 | // Create the components DTS file 21 | const components = astroDtsBuilder(); 22 | 23 | // Add a note to the top of the file 24 | components.addSingleLineNote( 25 | `This file is generated by '${name}' and should not be modified manually.` 26 | ); 27 | 28 | // Add the module to the file 29 | components.addModule('astro-turnstile:components/TurnstileWidget', { 30 | defaultExport: { 31 | typeDef: `typeof import('${name}/components/TurnstileWidget.astro').TurnstileWidget`, 32 | multiLineDescription: [ 33 | '# Turnstile Verification Widget', 34 | '@description An [Astro](https://astro.build) component that is used to render a Turnstile verification widget. This widget is used to verify that a user is human.', 35 | `@param {"auto"|"light"|"dark"} theme - The theme for the widget. (default: "auto")`, 36 | `@param {"normal"|"compact"|"flexible"} size - The size for the widget. (default: "normal")`, 37 | `@param {string} margin - The margin for the widget element. (default: '0.5rem')`, 38 | '@example', 39 | '```tsx', 40 | '---', 41 | `import TurnstileWidget from '${name}:components/TurnstileWidget';`, 42 | '---', 43 | "
", 44 | "", 45 | "", 46 | '', 47 | ], 48 | }, 49 | }); 50 | 51 | // Add the module to the file 52 | components.addModule('astro-turnstile:components/TurnstileForm', { 53 | defaultExport: { 54 | typeDef: `typeof import('${name}/components/TurnstileForm.astro').TurnstileForm`, 55 | multiLineDescription: [ 56 | '# Turnstile Verification Form', 57 | '@description An [Astro](https://astro.build) component that is used to render a Turnstile verification form. This form includes a Turnstile verification widget and a submit button.', 58 | '@slot default - Any unassigned content will be placed here.', 59 | "@slot header - `
...
`", 60 | "@slot buttons - `
...
`", 61 | "@slot footer - `
...
`", 62 | `@param {"auto"|"light"|"dark"} theme - The theme for the widget. (default: "auto")`, 63 | `@param {"normal"|"compact"|"flexible"} size - The size for the widget. (default: "normal")`, 64 | `@param {string} margin - The margin for the widget element. (default: '0.5rem')`, 65 | `@param {"multipart/form-data"|"application/x-www-form-urlencoded"|"submit"} enctype - The form enctype. (default: 'application/x-www-form-urlencoded')`, 66 | ], 67 | }, 68 | }); 69 | 70 | // Export the DTS files 71 | export default { 72 | config: config.makeAstroInjectedType('config.d.ts'), 73 | components: components.makeAstroInjectedType('components.d.ts'), 74 | }; 75 | -------------------------------------------------------------------------------- /package/src/strings.ts: -------------------------------------------------------------------------------- 1 | export const loggerStrings = { 2 | setup: 'Turnstile integration setup...', 3 | configSiteMissing: `Astro Config Error: 'site' is not defined, it is recommended to define 'site' in your Astro config. (https://docs.astro.build/en/reference/configuration-reference/#site)\nFalling back to 'window.location.origin'.`, 4 | updateConfig: 'Updating Astro config with Turnstile environment variables...', 5 | injectMiddleware: 'Injecting Turnstile middleware...', 6 | injectRoute: (value: string) => `Injecting Turnstile route at ${value}...`, 7 | virtualImports: 'Adding Virtual Import modules...', 8 | setupComplete: 'Turnstile integration setup complete.', 9 | addDevToolbarApp: 'Adding Turnstile Dev Toolbar App for testing...', 10 | injectTypes: 'Injecting Turnstile types...', 11 | }; 12 | 13 | export const ErrorMessages = { 14 | demoSecret: 15 | 'Turnstile Secret Key is set to a demo value. Please replace it with a valid secret key.', 16 | demoSiteKey: 17 | 'Turnstile Site Key is set to a demo value. Please replace it with a valid site key.', 18 | }; 19 | 20 | type astroHooks = import('astro').HookParameters<'astro:config:setup'>; 21 | 22 | export const envDefaults = { 23 | secretKey: (command: astroHooks['command']): string | undefined => { 24 | if (command === 'dev' || command === 'preview') { 25 | return '1x0000000000000000000000000000000AA'; 26 | } 27 | return undefined; 28 | }, 29 | siteKey: (command: astroHooks['command']): string | undefined => { 30 | if (command === 'dev' || command === 'preview') { 31 | return '1x00000000000000000000AA'; 32 | } 33 | return undefined; 34 | }, 35 | }; 36 | // Define the SVG icons for the toolbar app 37 | export const svgIcons = { 38 | turnstile: ``, 39 | close: ``, 40 | }; 41 | 42 | // Define the raw HTML elements for the app window 43 | export const windowHtmlElements = { 44 | button: ``, 45 | successBanner: `
Token Verification Successful
`, 46 | errorBanner: `
"Unable to verify token"
`, 47 | }; 48 | -------------------------------------------------------------------------------- /package/lib/components/TurnstileForm.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import config from 'virtual:astro-turnstile/config'; 3 | import type {HTMLAttributes} from 'astro/types'; 4 | import Widget, {type Props as WidgetProps} from './TurnstileWidget.astro'; 5 | 6 | interface Props extends WidgetProps, HTMLAttributes<'form'> { 7 | enctype?: 8 | | 'multipart/form-data' 9 | | 'application/x-www-form-urlencoded' 10 | | 'submit' 11 | | undefined; 12 | } 13 | 14 | const { 15 | theme, 16 | size, 17 | margin, 18 | action, 19 | method, 20 | buttonLabel = 'Submit', 21 | enctype = 'application/x-www-form-urlencoded', 22 | ...rest 23 | } = Astro.props; 24 | 25 | const siteUrl = Astro.site; 26 | --- 27 | 28 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 48 | 49 | 50 | 51 | 52 | 53 | 135 | -------------------------------------------------------------------------------- /package/lib/components/TurnstileWidget.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import {TURNSTILE_SITE_KEY} from 'astro:env/client'; 3 | import {AstroError} from 'astro/errors'; 4 | 5 | export interface Props { 6 | /** 7 | * Theme of the Turnstile widget as per the [Turnstile documentation](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#appearance-modes) 8 | * @default "auto" 9 | */ 10 | theme?: 'auto' | 'light' | 'dark' | undefined; 11 | /** 12 | * Size of the Turnstile widget as per the [Turnstile documentation](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#appearance-modes) 13 | * @default "normal" 14 | */ 15 | size?: 'normal' | 'compact' | 'flexible' | undefined; 16 | /** 17 | * Margin of the Turnstile widget (CSS margin property) 18 | * @default "0.5rem" 19 | */ 20 | margin?: string | undefined; 21 | 22 | /** 23 | * Label for Button 24 | * @default "Submit" 25 | */ 26 | buttonLabel?: string | undefined; 27 | } 28 | 29 | const {theme = 'auto', size = 'normal', margin = '0.5rem'} = Astro.props; 30 | 31 | // Define the dimensions type for the different sizes 32 | type Dimensions = {height: string; width: string; minWidth: string}; 33 | 34 | // Define the sizes object with the different style settings 35 | type Sizes = Record; 36 | 37 | // Define the style settings for the different sizes 38 | const sizes: Sizes = { 39 | normal: {height: '65px', width: '300px', minWidth: '300px'}, 40 | flexible: {height: '65px', width: '100%', minWidth: '300px'}, 41 | compact: {height: '140px', width: '150px', minWidth: '150px'}, 42 | }; 43 | 44 | // Define a function to get the style settings based on set the size 45 | const getStyleSettings = (size: string): Dimensions => { 46 | // Get the style settings based on the size 47 | const styleSettings = sizes[size]; 48 | 49 | // If the style settings exist, return the style settings 50 | if (styleSettings) { 51 | return styleSettings; 52 | } 53 | 54 | // If the style settings do not exist, throw an error 55 | throw new AstroError( 56 | `Invalid size: ${size}`, 57 | `'size' must be one of the following: ${Object.keys(sizes).join(', ')}.`, 58 | ); 59 | }; 60 | 61 | // Destructure the height, width, and minWidth from the style settings 62 | const { 63 | height: turnstileHeight, 64 | width: turnstileWidth, 65 | minWidth: turnstileMinWidth, 66 | } = getStyleSettings(size); 67 | --- 68 | 69 |
76 |
77 | 78 | 94 | 95 | 112 | -------------------------------------------------------------------------------- /package/src/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "astro/zod"; 2 | 3 | /** 4 | * Map of error messages for the endpoint path refinement function. 5 | */ 6 | const endpointErrorMessages = { 7 | startsWith: 8 | "The endpoint path must start with a forward slash. (e.g. '/your-path')", 9 | urlSafe: 10 | "The endpoint path must only contain URL-safe characters. (e.g. '/your-path')", 11 | endsWith: 12 | "The endpoint path must not end with a forward slash. (e.g. '/your-path')", 13 | }; 14 | 15 | /** 16 | * Super refine function for the endpoint path. 17 | * @param {string} arg - The value to refine. 18 | * @param {z.RefinementCtx} ctx - The refinement context. 19 | * @returns {void} 20 | */ 21 | function endpointPathSuperRefine(arg: string, ctx: z.RefinementCtx): void { 22 | const code = z.ZodIssueCode.custom; 23 | 24 | if (!arg.startsWith("/")) 25 | ctx.addIssue({ 26 | code, 27 | message: `${endpointErrorMessages.startsWith} Error: ${arg}`, 28 | }); 29 | 30 | if (!/^[a-zA-Z0-9\-_\/]+$/.test(arg)) 31 | ctx.addIssue({ 32 | code, 33 | message: `${endpointErrorMessages.urlSafe} Error: ${arg}`, 34 | }); 35 | 36 | if (arg.endsWith("/")) 37 | ctx.addIssue({ 38 | code, 39 | message: `${endpointErrorMessages.endsWith} Error: ${arg}`, 40 | }); 41 | } 42 | 43 | /** 44 | * Astro-Turnstile configuration options schema. 45 | */ 46 | export const AstroTurnstileOptionsSchema = z 47 | .object({ 48 | /** 49 | * The path to the injected Turnstile API endpoint. 50 | * @type {string} 51 | * @default "/verify" 52 | */ 53 | endpointPath: z 54 | .string() 55 | .optional() 56 | .default("/verify") 57 | .superRefine((arg, ctx) => endpointPathSuperRefine(arg, ctx)) 58 | .describe( 59 | 'The path to the injected Turnstile API endpoint. (default: "/verify")', 60 | ), 61 | /** 62 | * Disable the client-side script injection. 63 | * 64 | * By default, the client-side script is injected into the Astro project on every page. In some cases, you may want to disable this behavior, and manually inject the script where needed. This option allows you to disable the client-side script injection. 65 | * 66 | * **Note:** If you disable the client-side script injection, you will need to manually inject the Turnstile client-side script into your Astro project. 67 | * @example On any Layout that you want to use Turnstile, you can add the following script tag to the head: 68 | * ```html 69 | * 70 | * 71 | * 72 | * 73 | * 74 | * 75 | * ``` 76 | * @see [Cloudflare: Turnstile Explicitly render the Turnstile widget (onloadTurnstileCallback)](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#explicitly-render-the-turnstile-widget) For more information on the Turnstile client-side script. Or if you want to inject their script manually. 77 | * @type {boolean} 78 | * @default false 79 | */ 80 | disableClientScript: z 81 | .boolean() 82 | .optional() 83 | .default(false) 84 | .describe("Disable the client-side script injection. (default: false)"), 85 | /** 86 | * Disable the Astro Turnstile Dev Toolbar App. 87 | * 88 | * @default false 89 | */ 90 | disableDevToolbar: z 91 | .boolean() 92 | .optional() 93 | .default(false) 94 | .describe("Disable the Astro Turnstile Dev Toolbar. (default: false)"), 95 | /** 96 | * Enable verbose logging. 97 | * @type {boolean} 98 | * @default false 99 | */ 100 | verbose: z 101 | .boolean() 102 | .optional() 103 | .default(false) 104 | .describe("Enable verbose logging. (default: false)"), 105 | }) 106 | .optional() 107 | .default({}); 108 | 109 | /** 110 | * Astro-Turnstile configuration options type. 111 | */ 112 | export type AstroTurnstileOptions = typeof AstroTurnstileOptionsSchema._input; 113 | 114 | /** 115 | * Astro-Turnstile configuration options type used by the `virtual:astro-turnstile/config` module. 116 | */ 117 | export type AstroTurnstileConfig = z.infer; 118 | -------------------------------------------------------------------------------- /package/src/toolbar.ts: -------------------------------------------------------------------------------- 1 | import {TURNSTILE_SITE_KEY} from 'astro:env/client'; 2 | import {defineToolbarApp} from 'astro/toolbar'; 3 | import {svgIcons, windowHtmlElements} from './strings'; 4 | 5 | export default defineToolbarApp({ 6 | init(canvas, app, server) { 7 | // Create a new Astro Dev Toolbar Window 8 | const appWindow = document.createElement('astro-dev-toolbar-window'); 9 | 10 | // Set the window's initial size 11 | appWindow.style.width = '600px'; 12 | appWindow.style.height = '300px'; 13 | 14 | // Create a close button for the window 15 | const closeButton = document.createElement('button'); 16 | 17 | // Set the close button's inner HTML to an SVG icon 18 | closeButton.innerHTML = svgIcons.close; 19 | 20 | // Style the close button 21 | closeButton.style.position = 'absolute'; 22 | closeButton.style.top = '0.5rem'; 23 | closeButton.style.right = '0.5rem'; 24 | closeButton.style.padding = '0'; 25 | closeButton.style.width = '2rem'; 26 | closeButton.style.height = '2rem'; 27 | closeButton.style.cursor = 'pointer'; 28 | closeButton.style.border = 'none'; 29 | closeButton.style.backgroundColor = 'transparent'; 30 | closeButton.style.borderRadius = '50%'; 31 | closeButton.style.zIndex = '1000'; 32 | 33 | // Add an event listener to the close button to toggle the app's state 34 | closeButton.onclick = () => { 35 | app.toggleState({state: false}); 36 | }; 37 | 38 | // Create a Header for the app window 39 | const header = document.createElement('astro-dev-overlay-card'); 40 | 41 | // Style the heading 42 | header.style.textAlign = 'center'; 43 | header.style.marginTop = '0'; 44 | header.style.fontSize = '1.5rem'; 45 | header.style.position = 'absolute'; 46 | header.style.width = '90%'; 47 | header.style.height = '100px'; 48 | header.style.top = '0'; 49 | 50 | // Create a Heading for the app window 51 | const heading = document.createElement('h1'); 52 | 53 | // Set the heading's inner HTML 54 | heading.textContent = 'Astro Turnstile'; 55 | 56 | // Style the heading 57 | heading.style.textAlign = 'center'; 58 | heading.style.marginTop = '1rem'; 59 | heading.style.fontWeight = 'bold'; 60 | heading.style.marginBottom = '0'; 61 | heading.style.fontSize = '1.5rem'; 62 | 63 | // Create a Description for the app window 64 | const description = document.createElement('span'); 65 | 66 | // Style the description 67 | description.style.textAlign = 'center'; 68 | description.style.marginTop = '0'; 69 | description.style.fontSize = '1rem'; 70 | 71 | // Set the description's inner HTML 72 | description.textContent = 'Quickly run tests on your Turnstile config.'; 73 | 74 | // Create the testing button and add Turnstile integration 75 | const testElement = document.createElement('form'); 76 | 77 | // Style the form 78 | testElement.style.display = 'flex'; 79 | testElement.style.flexDirection = 'column'; 80 | testElement.style.justifyContent = 'center'; 81 | testElement.style.alignItems = 'center'; 82 | testElement.style.height = '100%'; 83 | testElement.style.width = '100%'; 84 | testElement.style.padding = '1rem'; 85 | testElement.id = 'turnstile-dev-toolbar-form'; 86 | testElement.innerHTML = windowHtmlElements.button; 87 | // Append the script to the form 88 | appWindow.appendChild(testElement); 89 | 90 | // Add an event listener to the form 91 | testElement.addEventListener('submit', async (event) => { 92 | // Prevent the default form submission 93 | event.preventDefault(); 94 | 95 | // Send the verification request to the server 96 | server.send('sendverify', {key: TURNSTILE_SITE_KEY}); 97 | }); 98 | 99 | // Append the headings to the header 100 | header.appendChild(heading); 101 | header.appendChild(description); 102 | 103 | // Append the elements to the app window 104 | appWindow.appendChild(closeButton); 105 | appWindow.appendChild(header); 106 | appWindow.appendChild(testElement); 107 | 108 | // Append the app window to the canvas 109 | canvas.appendChild(appWindow); 110 | 111 | // Listen for the response from the server 112 | server.on('verifyresponse', async (data: {success: boolean}) => { 113 | // Create a new element to display the response 114 | const responseElement = document.createElement('div'); 115 | 116 | // Style the response element 117 | responseElement.innerHTML = data.success 118 | ? windowHtmlElements.successBanner 119 | : windowHtmlElements.errorBanner; 120 | 121 | // Append the response element to the app window 122 | appWindow.appendChild(responseElement); 123 | 124 | if (data.success) { 125 | app.toggleNotification({state: true, level: 'info'}); 126 | } else { 127 | app.toggleNotification({state: true, level: 'error'}); 128 | } 129 | 130 | // Remove the response element after 5 seconds 131 | setTimeout(() => { 132 | responseElement.remove(); 133 | app.toggleNotification({state: false}); 134 | }, 5000); 135 | }); 136 | }, 137 | }); 138 | -------------------------------------------------------------------------------- /package/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # astro-turnstile 2 | 3 | ## 2.1.0 4 | 5 | ### Minor Changes 6 | 7 | - 31d452f: Updated to use data attribute instead of comment 8 | - 9a444e1: Updated script to only inject on pages where component is rendered 9 | 10 | ## 2.0.1 11 | 12 | ### Patch Changes 13 | 14 | - 8de5ce3: Button Update 15 | 16 | ## 2.0.0 17 | 18 | ### Major Changes 19 | 20 | - e5a7b61: Upgrade to Astro V5 21 | 22 | ## 1.2.0 23 | 24 | ### Minor Changes 25 | 26 | - 9921553: Changeset for Type Updates 27 | - 508f6ad: Refactored component imports for TurnstileWidget and TurnstileForm: 28 | BREAKING: Direct package imports from 'astro-turnstile/components/TurnstileWidget' and 'astro-turnstile/components/TurnstileForm'. 29 | 30 | ```ts 31 | // Previously 32 | import { TurnstileForm } from "astro-turnstile/components"; 33 | import { TurnstileWidget } from "astro-turnstile/components"; 34 | 35 | // Virtual modules Before 36 | import { TurnstileForm } from "astro-turnstile:components"; 37 | import { TurnstileWidget } from "astro-turnstile:components"; 38 | 39 | // Now 40 | import TurnstileForm from "astro-turnstile/components/TurnstileForm"; 41 | import TurnstileWidget from "astro-turnstile/components/TurnstileWidget"; 42 | 43 | // Virtual modules now 44 | import TurnstileForm from "astro-turnstile:components/TurnstileForm"; 45 | import TurnstileWidget from "astro-turnstile:components/TurnstileWidget"; 46 | ``` 47 | 48 | - 16ae506: Updated Imports 49 | 50 | ## 1.1.2 51 | 52 | ### Patch Changes 53 | 54 | - 05537b2: [Fix/Docs]: 55 | 56 | - Update Type `AstroTurnstileOptions` to reflect the correct default values by switching from `z.infer` to a `typeof Schema._input` which properly shows the type as it would be used by the enduser 57 | 58 | ```ts 59 | // Previously 60 | type AstroTurnstileOptions = { 61 | endpointPath: string; 62 | disableClientScript: boolean; 63 | disableDevToolbar: boolean; 64 | verbose: boolean; 65 | }; 66 | 67 | // Now 68 | type AstroTurnstileOptions = 69 | | { 70 | endpointPath?: string | undefined; 71 | disableClientScript?: boolean | undefined; 72 | disableDevToolbar?: boolean | undefined; 73 | verbose?: boolean | undefined; 74 | } 75 | | undefined; 76 | ``` 77 | 78 | - Update readme to include instructions and more information about what is available to users from the integration 79 | 80 | ## 1.1.1 81 | 82 | ### Patch Changes 83 | 84 | - 2c91682: Updated readme 85 | 86 | ## 1.1.0 87 | 88 | ### Minor Changes 89 | 90 | - a200211: [refactor components]: Simplify Logic and breakout widget into its own component. 91 | 92 | - Moved Widget to its own component that can be used with custom implementations. 93 | - Refactored Form component to add slots, and adjust the configuration of how logic is handled. 94 | - Updated API endpoint to give responses in the status text to use within the custom Form component. 95 | 96 | ## 1.0.1 97 | 98 | ### Patch Changes 99 | 100 | - b08da43: Updated readme 101 | 102 | ## 1.0.0 103 | 104 | ### Major Changes 105 | 106 | - a30c366: First Release 107 | 108 | ## 0.3.0 109 | 110 | ### Minor Changes 111 | 112 | - 5dbd778: [Refactor]: Update integration logic and handling and create a reusable component 113 | 114 | - BREAKING: Minimum version of Astro is now `v4.14` due to the use of the new injectTypes helper functions. 115 | - NEW: Auto injection of Turnstile Client API script. 116 | - NEW: You can now change the endpoint path of your Astro-Turnstile install for Verifying Tokens. 117 | - NEW: Virtual component module for users with full types `astro-turnstile:components`. 118 | - NEW: Added new Astro DevToolbarApp. 119 | - NEW: Virtual modules `virtual:astro-turnstile/config` and `astro-turnstile:components` automatic `.d.ts` generation providing a fully typed ecosystem. 120 | - CHANGED: The options have changed, the `TURNSTILE_SECRET_KEY` and `TURNSTILE_SITE_KEY` are now environment variables only, and integration options change functionality: 121 | 122 | ```ts 123 | type AstroTurnstileOptions = { 124 | endpointPath: string; 125 | disableClientScript: boolean; 126 | disableDevToolbar: boolean; 127 | verbose: boolean; 128 | }; 129 | ``` 130 | 131 | - NEW: Reusable form component: 132 | 133 | ```tsx 134 | // src/pages/index.astro 135 | --- 136 | import { TurnstileForm } from "astro-turnstile:components"; 137 | --- 138 | 139 | 140 |
141 | 142 | 146 | 147 | {/* 148 | Note: You do not need to place a submit button in 149 | as there is already one present as part of the component. 150 | */} 151 |
152 |
153 | ``` 154 | 155 | ## 0.2.0 156 | 157 | ### Minor Changes 158 | 159 | - 154366f: Playground and Integration Updates 160 | 161 | ## 0.1.1 162 | 163 | ### Patch Changes 164 | 165 | - 23b88a0: Update Readme 166 | 167 | ## 0.1.0 168 | 169 | ### Minor Changes 170 | 171 | - 75f9d29: Inital Release 172 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct - Astro Turnstile 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to a positive environment for our 15 | community include: 16 | 17 | * Demonstrating empathy and kindness toward other people 18 | * Being respectful of differing opinions, viewpoints, and experiences 19 | * Giving and gracefully accepting constructive feedback 20 | * Accepting responsibility and apologizing to those affected by our mistakes, 21 | and learning from the experience 22 | * Focusing on what is best not just for us as individuals, but for the 23 | overall community 24 | 25 | Examples of unacceptable behavior include: 26 | 27 | * The use of sexualized language or imagery, and sexual attention or 28 | advances 29 | * Trolling, insulting or derogatory comments, and personal or political attacks 30 | * Public or private harassment 31 | * Publishing others' private information, such as a physical or email 32 | address, without their explicit permission 33 | * Other conduct which could reasonably be considered inappropriate in a 34 | professional setting 35 | 36 | ## Our Responsibilities 37 | 38 | Project maintainers are responsible for clarifying and enforcing our standards of 39 | acceptable behavior and will take appropriate and fair corrective action in 40 | response to any behavior that they deem inappropriate, 41 | threatening, offensive, or harmful. 42 | 43 | Project maintainers have the right and responsibility to remove, edit, or reject 44 | comments, commits, code, wiki edits, issues, and other contributions that are 45 | not aligned to this Code of Conduct, and will 46 | communicate reasons for moderation decisions when appropriate. 47 | 48 | ## Scope 49 | 50 | This Code of Conduct applies within all community spaces, and also applies when 51 | an individual is officially representing the community in public spaces. 52 | Examples of representing our community include using an official e-mail address, 53 | posting via an official social media account, or acting as an appointed 54 | representative at an online or offline event. 55 | 56 | ## Enforcement 57 | 58 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 59 | reported to the community leaders responsible for enforcement at . 60 | All complaints will be reviewed and investigated promptly and fairly. 61 | 62 | All community leaders are obligated to respect the privacy and security of the 63 | reporter of any incident. 64 | 65 | ## Enforcement Guidelines 66 | 67 | Community leaders will follow these Community Impact Guidelines in determining 68 | the consequences for any action they deem in violation of this Code of Conduct: 69 | 70 | ### 1. Correction 71 | 72 | **Community Impact**: Use of inappropriate language or other behavior deemed 73 | unprofessional or unwelcome in the community. 74 | 75 | **Consequence**: A private, written warning from community leaders, providing 76 | clarity around the nature of the violation and an explanation of why the 77 | behavior was inappropriate. A public apology may be requested. 78 | 79 | ### 2. Warning 80 | 81 | **Community Impact**: A violation through a single incident or series 82 | of actions. 83 | 84 | **Consequence**: A warning with consequences for continued behavior. No 85 | interaction with the people involved, including unsolicited interaction with 86 | those enforcing the Code of Conduct, for a specified period of time. This 87 | includes avoiding interactions in community spaces as well as external channels 88 | like social media. Violating these terms may lead to a temporary or 89 | permanent ban. 90 | 91 | ### 3. Temporary Ban 92 | 93 | **Community Impact**: A serious violation of community standards, including 94 | sustained inappropriate behavior. 95 | 96 | **Consequence**: A temporary ban from any sort of interaction or public 97 | communication with the community for a specified period of time. No public or 98 | private interaction with the people involved, including unsolicited interaction 99 | with those enforcing the Code of Conduct, is allowed during this period. 100 | Violating these terms may lead to a permanent ban. 101 | 102 | ### 4. Permanent Ban 103 | 104 | **Community Impact**: Demonstrating a pattern of violation of community 105 | standards, including sustained inappropriate behavior, harassment of an 106 | individual, or aggression toward or disparagement of classes of individuals. 107 | 108 | **Consequence**: A permanent ban from any sort of public interaction within 109 | the community. 110 | 111 | ## Attribution 112 | 113 | This Code of Conduct is adapted from the [Contributor Covenant](https://contributor-covenant.org/), version 114 | [1.4](https://www.contributor-covenant.org/version/1/4/code-of-conduct/code_of_conduct.md) and 115 | [2.0](https://www.contributor-covenant.org/version/2/0/code_of_conduct/code_of_conduct.md), 116 | and was generated by [contributing-gen](https://github.com/bttger/contributing-gen). -------------------------------------------------------------------------------- /package/README.md: -------------------------------------------------------------------------------- 1 | # `Astro Turnstile` 2 | 3 | This is an [Astro integration](https://docs.astro.build/en/guides/integrations-guide/) that allows you to add a turnstile to your Astro site. 4 | 5 | ## Usage 6 | 7 | ### Prerequisites 8 | 9 | Before you can use this integration, you need to have a Cloudflare account. You can sign up for a free account [here](https://www.cloudflare.com/products/turnstile/). 10 | 11 | ### Getting Started 12 | 13 | First, you need to create a new site in your Cloudflare account. You can do this by following the instructions [here](https://developers.cloudflare.com/turnstile/getting-started/create-site). 14 | 15 | Once you have created a site, you will be given a site key and a secret key. You will need this key to configure the integration. 16 | 17 | ### Installation 18 | 19 | Install the integration **automatically** using the Astro CLI: 20 | 21 | ```bash 22 | pnpm astro add astro-turnstile 23 | ``` 24 | 25 | ```bash 26 | npx astro add astro-turnstile 27 | ``` 28 | 29 | ```bash 30 | yarn astro add astro-turnstile 31 | ``` 32 | 33 | ```bun 34 | bunx astro add astro-turnstile 35 | ``` 36 | 37 | Or install it **manually**: 38 | 39 | 1. Install the required dependencies 40 | 41 | ```bash 42 | pnpm add astro-turnstile 43 | ``` 44 | 45 | ```bash 46 | npm install astro-turnstile 47 | ``` 48 | 49 | ```bash 50 | yarn add astro-turnstile 51 | ``` 52 | 53 | ```bun 54 | bun add astro-turnstile 55 | ``` 56 | 57 | 2. Add the integration to your astro config 58 | 59 | ```diff 60 | +import astroTurnstile from "astro-turnstile"; 61 | 62 | export default defineConfig({ 63 | integrations: [ 64 | + astroTurnstile(), 65 | ], 66 | }); 67 | ``` 68 | 69 | ### Configuration 70 | 71 | #### `.env` File 72 | 73 | You will need to add these 2 values to your `.env` file: 74 | 75 | - `TURNSTILE_SITE_KEY` (required): Your Turnstile site key 76 | - `TURNSTILE_SECRET_KEY` (required): Your Turnstile secret key - this should be kept secret 77 | 78 | #### Astro Config Options 79 | 80 | **`verbose`** 81 | - Type: `boolean` 82 | - Default: `false` 83 | 84 | Enable verbose logging. 85 | 86 | **`disableClientScript`** 87 | - Type: `boolean` 88 | - Default: `false` 89 | 90 | Disable the client-side script injection. 91 | 92 | By default, the client-side script is injected into the Astro project on every page. In some cases, you may want to disable this behavior, and manually inject the script where needed. This option allows you to disable the client-side script injection. 93 | 94 | Note: If you disable the client-side script injection, you will need to manually inject the Turnstile client-side script into your Astro project. 95 | 96 | **`disableDevToolbar`** 97 | - Type: `boolean` 98 | - Default: `false` 99 | 100 | Disable the Astro Turnstile Dev Toolbar App. 101 | 102 | **`endpointPath`** 103 | - Type: `string` 104 | - Default: `/verify` 105 | 106 | The path to the injected Turnstile API endpoint. 107 | 108 | ### Usage 109 | 110 | The following components are made available to the end user: 111 | 112 | - **`TurnstileWidget`** - The main widget component for displaying the Turnstile captcha field in forms 113 | - Available Props: 114 | - **`theme`**: 115 | - Type: `"auto"` | `"light"` | `"dark"` | `undefined` 116 | - Default: `"auto"` 117 | - **`size`**: 118 | - Type: `"normal"` | `"compact"` | `"flexible"` | `undefined` 119 | - Default: `"normal"` 120 | - **`margin`**: 121 | - Type: `string` | `undefined` 122 | - Default: `"0.5rem"` 123 | 124 | - `TurnstileForm` - A helper form element that assists you in building your forms with Turnstile verification built in 125 | - Available Props: 126 | - (All the props from Widget) 127 | - **`enctype`** 128 | - Type: `"multipart/form-data"` | `"application/x-www-form-urlencoded"` | `"submit"` | `undefined` 129 | - Default: `"application/x-www-form-urlencoded"` 130 | - **`action`** 131 | - Type: `string` | `null` | `undefined` 132 | - **`method`** 133 | - Type: `string` | `null` | `undefined` 134 | 135 | These components can be accessed by either of the following methods: 136 | 137 | ```ts 138 | // Option 1: Runtime virtual module 139 | import TurnstileWidget from 'astro-turnstile:components/TurnstileWidget'; 140 | import TurnstileForm from 'astro-turnstile:components/TurnstileForm'; 141 | 142 | // Option 2: Direct package exports 143 | import TurnstileWidget from 'astro-turnstile/components/TurnstileWidget'; 144 | import TurnstileForm from 'astro-turnstile/components/TurnstileForm'; 145 | 146 | ``` 147 | 148 | ## Contributing 149 | 150 | This package is structured as a monorepo: 151 | 152 | - `playground` contains code for testing the package 153 | - `package` contains the actual package 154 | 155 | Install dependencies using pnpm: 156 | 157 | ```bash 158 | pnpm i --frozen-lockfile 159 | ``` 160 | 161 | Start the playground: 162 | 163 | ```bash 164 | pnpm playground:dev 165 | ``` 166 | 167 | You can now edit files in `package`. Please note that making changes to those files may require restarting the playground dev server. 168 | 169 | ## Licensing 170 | 171 | [MIT Licensed](https://github.com/hkbertoson/astro-turnstile/blob/main/LICENSE). Made with ❤️ by [Hunter Bertoson](https://github.com/hkbertoson). 172 | 173 | ## Acknowledgements 174 | 175 | [Astro](https://astro.build/) 176 | [Turnstile](https://www.cloudflare.com/products/turnstile/) 177 | [Florian Lefebvre](https://github.com/florian-lefebvre) 178 | -------------------------------------------------------------------------------- /package/src/integration.ts: -------------------------------------------------------------------------------- 1 | import { 2 | addVirtualImports, 3 | createResolver, 4 | defineIntegration, 5 | } from 'astro-integration-kit'; 6 | import {envField} from 'astro/config'; 7 | import {AstroError} from 'astro/errors'; 8 | import {loadEnv} from 'vite'; 9 | import {name} from '../package.json'; 10 | import {AstroTurnstileOptionsSchema as optionsSchema} from './schema.ts'; 11 | import { 12 | ErrorMessages, 13 | envDefaults, 14 | loggerStrings, 15 | svgIcons, 16 | } from './strings.ts'; 17 | import Dts from './stubs.ts'; 18 | 19 | // Load the Turnstile environment variables for the Server runtime and Verification 20 | // that the environment variables are NOT set to Turnstile demo values during build. 21 | const env = loadEnv('TURNSTILE_', process.cwd()); 22 | 23 | export const astroTurnstile = defineIntegration({ 24 | name, 25 | optionsSchema, 26 | setup({ 27 | name, 28 | options, 29 | options: {endpointPath, verbose, disableClientScript, disableDevToolbar}, 30 | }) { 31 | const {resolve} = createResolver(import.meta.url); 32 | return { 33 | hooks: { 34 | 'astro:config:setup': (params) => { 35 | // Destructure the params object 36 | const { 37 | logger, 38 | updateConfig, 39 | injectScript, 40 | injectRoute, 41 | command, 42 | config, 43 | addDevToolbarApp, 44 | addMiddleware, 45 | } = params; 46 | 47 | // Log startup message 48 | verbose && logger.info(loggerStrings.setup); 49 | 50 | // Update the User's Astro config ('astro:env') with the required Turnstile 51 | // environment variables and Set the 'checkOrigin' security option to true 52 | verbose && logger.info(loggerStrings.updateConfig); 53 | updateConfig({ 54 | env: { 55 | validateSecrets: true, 56 | schema: { 57 | TURNSTILE_SECRET_KEY: envField.string({ 58 | access: 'secret', 59 | context: 'server', 60 | optional: false, 61 | // The default value is only usable in 'dev'/'preview' and should be replaced with the actual secret key. 62 | // See https://developers.cloudflare.com/turnstile/troubleshooting/testing/#dummy-sitekeys-and-secret-keys for more information. 63 | default: envDefaults.secretKey(command), 64 | }), 65 | TURNSTILE_SITE_KEY: envField.string({ 66 | access: 'public', 67 | context: 'client', 68 | optional: false, 69 | // The default value is only usable in 'dev'/'preview' and should be replaced with the actual secret key. 70 | // See https://developers.cloudflare.com/turnstile/troubleshooting/testing/#dummy-sitekeys-and-secret-keys for more information. 71 | default: envDefaults.siteKey(command), 72 | }), 73 | }, 74 | }, 75 | }); 76 | 77 | // Helper function to check if the environment variables are set to Turnstile demo values 78 | function checkKeys(o: { 79 | key: string; 80 | knownKeys: string[]; 81 | error: string; 82 | }) { 83 | if (o.knownKeys.includes(o.key)) { 84 | throw new AstroError(o.error); 85 | } 86 | } 87 | 88 | // If environment variables are set to Turnstile demo values during build, error 89 | if (command === 'build') { 90 | // Check TURNSTILE_SECRET_KEY 91 | if (env.TURNSTILE_SECRET_KEY) { 92 | checkKeys({ 93 | key: env.TURNSTILE_SECRET_KEY, 94 | knownKeys: [ 95 | '1x0000000000000000000000000000000AA', 96 | '2x0000000000000000000000000000000AA', 97 | '3x0000000000000000000000000000000AA', 98 | ], 99 | error: ErrorMessages.demoSecret, 100 | }); 101 | } 102 | 103 | // Check TURNSTILE_SITE_KEY 104 | if (env.TURNSTILE_SITE_KEY) { 105 | checkKeys({ 106 | key: env.TURNSTILE_SITE_KEY, 107 | knownKeys: [ 108 | '1x00000000000000000000AA', 109 | '1x00000000000000000000BB', 110 | '2x00000000000000000000AB', 111 | '2x00000000000000000000BB', 112 | '3x00000000000000000000FF', 113 | ], 114 | error: ErrorMessages.demoSiteKey, 115 | }); 116 | } 117 | } 118 | 119 | // Check if the Astro config has a 'site' property 120 | if (!config.site) { 121 | logger.warn(loggerStrings.configSiteMissing); 122 | } 123 | 124 | // Inject the required Turnstile client-side script if not disabled 125 | if (!disableClientScript) { 126 | verbose && logger.info(loggerStrings.injectMiddleware); 127 | addMiddleware({entrypoint: resolve('./middleware.ts'), order: 'post'}); 128 | } 129 | 130 | // Add Development Toolbar App for Astro Turnstile testing 131 | if (!disableDevToolbar) { 132 | verbose && logger.info(loggerStrings.addDevToolbarApp); 133 | addDevToolbarApp({ 134 | name: 'Astro Turnstile', 135 | id: 'astro-turnstile-dev-toolbar', 136 | icon: svgIcons.turnstile, 137 | entrypoint: resolve('./toolbar.ts'), 138 | }); 139 | } 140 | 141 | // Inject the required Turnstile server-side route 142 | verbose && logger.info(loggerStrings.injectRoute(endpointPath)); 143 | injectRoute({ 144 | pattern: endpointPath, 145 | entrypoint: `${name}/server`, 146 | prerender: false, 147 | }); 148 | 149 | // Add Virtual Imports for resolving the Astro Turnstile Options during runtime 150 | verbose && logger.info(loggerStrings.virtualImports); 151 | addVirtualImports(params, { 152 | name, 153 | imports: { 154 | 'virtual:astro-turnstile/config': `export default ${JSON.stringify( 155 | options 156 | )}`, 157 | 'astro-turnstile:components/TurnstileWidget': `import Widget from '${name}/components/TurnstileWidget'; export default Widget;`, 158 | 'astro-turnstile:components/TurnstileForm': `import Form from '${name}/components/TurnstileForm'; export default Form;`, 159 | }, 160 | }); 161 | 162 | 163 | 164 | // Log completion message 165 | verbose && logger.info(loggerStrings.setupComplete); 166 | }, 167 | 'astro:config:done': ({injectTypes, logger}) => { 168 | // Inject the required Turnstile types for the Astro config and components 169 | verbose && logger.info(loggerStrings.injectTypes); 170 | injectTypes(Dts.config); 171 | injectTypes(Dts.components); 172 | }, 173 | 'astro:server:setup': async ({toolbar, logger}) => { 174 | // Add a Server Event Listener for the 'sendverify' event from the Astro Dev Toolbar app 175 | if (!disableDevToolbar) { 176 | verbose && 177 | logger.info('Adding Server Event Listener Dev Toolbar App...'); 178 | toolbar.on('sendverify', async (data: {key: string}) => { 179 | const formData = new FormData(); 180 | 181 | // Get the Turnstile site token from the environment variables 182 | // or use a dummy token for testing 183 | const siteToken = 184 | env.TURNSTILE_SITE_KEY || '1x00000000000000000000AA'; 185 | 186 | // Add the secret key and token to the form data 187 | formData.append('secret', data.key); 188 | formData.append('response', siteToken); 189 | 190 | // Send the token to the Turnstile API for verification 191 | try { 192 | const response = await fetch( 193 | 'https://challenges.cloudflare.com/turnstile/v0/siteverify', 194 | { 195 | method: 'POST', 196 | body: formData, 197 | headers: { 198 | 'content-type': 'application/x-www-form-urlencoded', 199 | }, 200 | } 201 | ); 202 | 203 | // Send the verification response back to the Astro Dev Toolbar app 204 | toolbar.send('verifyresponse', { 205 | success: response.ok, 206 | }); 207 | } catch (error) { 208 | // Send the verification response back to the Astro Dev Toolbar app 209 | toolbar.send('verifyresponse', {success: false}); 210 | } 211 | }); 212 | } 213 | }, 214 | }, 215 | }; 216 | }, 217 | }); 218 | 219 | export default astroTurnstile; 220 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributing to Astro Turnstile 3 | 4 | First off, thanks for taking the time to contribute! ❤️ 5 | 6 | All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 7 | 8 | > And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: 9 | > - Star the project 10 | > - Tweet about it 11 | > - Refer this project in your project's readme 12 | > - Mention the project at local meetups and tell your friends/colleagues 13 | 14 | 15 | ## Table of Contents 16 | 17 | - [Code of Conduct](#code-of-conduct) 18 | - [I Have a Question](#i-have-a-question) 19 | - [I Want To Contribute](#i-want-to-contribute) 20 | - [Reporting Bugs](#reporting-bugs) 21 | - [Suggesting Enhancements](#suggesting-enhancements) 22 | - [Your First Code Contribution](#your-first-code-contribution) 23 | - [Improving The Documentation](#improving-the-documentation) 24 | - [Styleguides](#styleguides) 25 | - [Commit Messages](#commit-messages) 26 | 27 | 28 | ## Code of Conduct 29 | 30 | This project and everyone participating in it is governed by the 31 | [Astro Turnstile Code of Conduct](https://github.com/hkbertoson/astro-turnstileblob/master/CODE_OF_CONDUCT.md). 32 | By participating, you are expected to uphold this code. Please report unacceptable behavior 33 | to . 34 | 35 | 36 | ## I Have a Question 37 | 38 | > If you want to ask a question, we assume that you have read the available [Documentation](). 39 | 40 | Before you ask a question, it is best to search for existing [Issues](https://github.com/hkbertoson/astro-turnstile/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first. 41 | 42 | If you then still feel the need to ask a question and need clarification, we recommend the following: 43 | 44 | - Open an [Issue](https://github.com/hkbertoson/astro-turnstile/issues/new). 45 | - Provide as much context as you can about what you're running into. 46 | - Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant. 47 | 48 | We will then take care of the issue as soon as possible. 49 | 50 | 64 | 65 | ## I Want To Contribute 66 | 67 | > ### Legal Notice 68 | > When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license. 69 | 70 | ### Reporting Bugs 71 | 72 | 73 | #### Before Submitting a Bug Report 74 | 75 | A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible. 76 | 77 | - Make sure that you are using the latest version. 78 | - Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://github.com/hkbertoson/astro-turnstile). If you are looking for support, you might want to check [this section](#i-have-a-question)). 79 | - To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/hkbertoson/astro-turnstileissues?q=label%3Abug). 80 | - Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue. 81 | - Collect information about the bug: 82 | - Stack trace (Traceback) 83 | - OS, Platform and Version (Windows, Linux, macOS, x86, ARM) 84 | - Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant. 85 | - Possibly your input and the output 86 | - Can you reliably reproduce the issue? And can you also reproduce it with older versions? 87 | 88 | 89 | #### How Do I Submit a Good Bug Report? 90 | 91 | > You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to . 92 | 93 | 94 | We use GitHub issues to track bugs and errors. If you run into an issue with the project: 95 | 96 | - Open an [Issue](https://github.com/hkbertoson/astro-turnstile/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.) 97 | - Explain the behavior you would expect and the actual behavior. 98 | - Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case. 99 | - Provide the information you collected in the previous section. 100 | 101 | Once it's filed: 102 | 103 | - The project team will label the issue accordingly. 104 | - A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. 105 | - If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution). 106 | 107 | 108 | 109 | 110 | ### Suggesting Enhancements 111 | 112 | This section guides you through submitting an enhancement suggestion for Astro Turnstile, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions. 113 | 114 | 115 | #### Before Submitting an Enhancement 116 | 117 | - Make sure that you are using the latest version. 118 | - Read the [documentation](https://github.com/hkbertoson/astro-turnstile) carefully and find out if the functionality is already covered, maybe by an individual configuration. 119 | - Perform a [search](https://github.com/hkbertoson/astro-turnstile/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. 120 | - Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library. 121 | 122 | 123 | #### How Do I Submit a Good Enhancement Suggestion? 124 | 125 | Enhancement suggestions are tracked as [GitHub issues](https://github.com/hkbertoson/astro-turnstile/issues). 126 | 127 | - Use a **clear and descriptive title** for the issue to identify the suggestion. 128 | - Provide a **step-by-step description of the suggested enhancement** in as many details as possible. 129 | - **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you. 130 | - You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. 131 | - **Explain why this enhancement would be useful** to most Astro Turnstile users. You may also want to point out the other projects that solved it better and which could serve as inspiration. 132 | 133 | 134 | 135 | 136 | 140 | 141 | 142 | 146 | 147 | 148 | 149 | 152 | 153 | 154 | ## Attribution 155 | This guide is based on the **contributing-gen**. [Make your own](https://github.com/bttger/contributing-gen)! 156 | --------------------------------------------------------------------------------