diff --git a/d1-template/tsconfig.json b/d1-template/tsconfig.json index c4cd57c..c142f21 100644 --- a/d1-template/tsconfig.json +++ b/d1-template/tsconfig.json @@ -3,6 +3,7 @@ "target": "esnext", "lib": ["esnext"], "module": "esnext", + "moduleResolution": "bundler", "types": ["@cloudflare/workers-types", "./worker-configuration.d.ts"], "noEmit": true, "isolatedModules": true, diff --git a/image-classification-template/tsconfig.json b/image-classification-template/tsconfig.json index c4cd57c..c142f21 100644 --- a/image-classification-template/tsconfig.json +++ b/image-classification-template/tsconfig.json @@ -3,6 +3,7 @@ "target": "esnext", "lib": ["esnext"], "module": "esnext", + "moduleResolution": "bundler", "types": ["@cloudflare/workers-types", "./worker-configuration.d.ts"], "noEmit": true, "isolatedModules": true, diff --git a/llm-template/tsconfig.json b/llm-template/tsconfig.json index c4cd57c..c142f21 100644 --- a/llm-template/tsconfig.json +++ b/llm-template/tsconfig.json @@ -3,6 +3,7 @@ "target": "esnext", "lib": ["esnext"], "module": "esnext", + "moduleResolution": "bundler", "types": ["@cloudflare/workers-types", "./worker-configuration.d.ts"], "noEmit": true, "isolatedModules": true, diff --git a/openauth-template/README.md b/openauth-template/README.md new file mode 100644 index 0000000..c816ec3 --- /dev/null +++ b/openauth-template/README.md @@ -0,0 +1,15 @@ +# OpenAuth Server + +Deploy an [OpenAuth](https://openauth.js.org/) server on Cloudflare Workers. + +## Develop Locally + +Use this template with [C3](https://developers.cloudflare.com/pages/get-started/c3/) (the `create-cloudflare` CLI): + +``` +npm create cloudflare@latest -- --template=cloudflare/templates/openauth-template +``` + +## Preview Deployment + +A live public deployment of this template is available at [https://openauth-template.templates.workers.dev](https://openauth-template.templates.workers.dev) diff --git a/openauth-template/migrations/0001_create_user_table.sql b/openauth-template/migrations/0001_create_user_table.sql new file mode 100644 index 0000000..3dfb1d5 --- /dev/null +++ b/openauth-template/migrations/0001_create_user_table.sql @@ -0,0 +1,6 @@ +-- Migration number: 0001 2024-12-27T22:04:18.794Z +CREATE TABLE IF NOT EXISTS user ( + id TEXT PRIMARY KEY NOT NULL DEFAULT (lower(hex(randomblob(16)))), + email TEXT UNIQUE NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/openauth-template/package.json b/openauth-template/package.json new file mode 100644 index 0000000..93c73a8 --- /dev/null +++ b/openauth-template/package.json @@ -0,0 +1,30 @@ +{ + "name": "openauth-template", + "description": "Deploy an OpenAuth server on Cloudflare Workers.", + "cloudflare": { + "label": "OpenAuth Server", + "products": [ + "Workers" + ], + "icon_urls": [ + "https://imagedelivery.net/wSMYJvS3Xw-n339CbDyDIA/5ca0ca32-e897-4699-d4c1-6b680512f000/public" + ], + "preview_image_url": "https://imagedelivery.net/wSMYJvS3Xw-n339CbDyDIA/b2ff10c6-8f7c-419f-8757-e2ccf1c84500/public" + }, + "dependencies": { + "@openauthjs/openauth": "0.2.5", + "valibot": "^1.0.0-beta.9" + }, + "devDependencies": { + "@cloudflare/workers-types": "4.20241216.0", + "typescript": "5.6.3", + "wrangler": "3.97.0" + }, + "scripts": { + "check": "tsc && wrangler --experimental-json-config deploy --dry-run", + "deploy": "wrangler --experimental-json-config deploy", + "dev": "wrangler --experimental-json-config dev", + "predeploy": "wrangler --experimental-json-config d1 migrations apply openauth-template-auth-db --remote", + "types": "wrangler --experimental-json-config types" + } +} diff --git a/openauth-template/src/index.ts b/openauth-template/src/index.ts new file mode 100644 index 0000000..497dca9 --- /dev/null +++ b/openauth-template/src/index.ts @@ -0,0 +1,87 @@ +import { authorizer, createSubjects } from "@openauthjs/openauth"; +import { CloudflareStorage } from "@openauthjs/openauth/storage/cloudflare"; +import { PasswordAdapter } from "@openauthjs/openauth/adapter/password"; +import { PasswordUI } from "@openauthjs/openauth/ui/password"; +import { object, string } from "valibot"; + +// TODO: Explain that this should be a shared type +const subjects = createSubjects({ + user: object({ + id: string(), + }), +}); + +export default { + async fetch(request: Request, env: Env, ctx: ExecutionContext) { + // TODO: Explain this stuff at the top (for demo purposes only) + const url = new URL(request.url); + if (url.pathname === "/") { + return Response.redirect( + url.origin + + `/authorize?client_id=your-client-id&redirect_uri=${encodeURIComponent(url.origin + "/callback")}&response_type=code`, + ); + } else if (url.pathname === "/callback") { + return Response.json({ + message: "OAuth flow complete!", + params: Object.fromEntries(url.searchParams.entries()), + }); + } + + // The real OpenAuth server code starts here: + return authorizer({ + storage: CloudflareStorage({ + namespace: env.AUTH_STORAGE, + }), + subjects, + providers: { + password: PasswordAdapter( + PasswordUI({ + // eslint-disable-next-line @typescript-eslint/require-await + sendCode: async (email, code) => { + // This is where you would email the verification code to the + // user, e.g. using Resend: + // https://resend.com/docs/send-with-cloudflare-workers + console.log(`Sending code ${code} to ${email}`); + }, + copy: { + input_code: "Code (check Worker logs)", + }, + }), + ), + }, + theme: { + title: "myAuth", + primary: "#0051c3", + favicon: "https://workers.cloudflare.com//favicon.ico", + logo: { + dark: "https://imagedelivery.net/wSMYJvS3Xw-n339CbDyDIA/db1e5c92-d3a6-4ea9-3e72-155844211f00/public", + light: + "https://imagedelivery.net/wSMYJvS3Xw-n339CbDyDIA/fa5a3023-7da9-466b-98a7-4ce01ee6c700/public", + }, + }, + success: async (ctx, value) => { + return ctx.subject("user", { + id: await getOrCreateUser(env, value.email), + }); + }, + }).fetch(request, env, ctx); + }, +} satisfies ExportedHandler; + +async function getOrCreateUser(env: Env, email: string): Promise { + const result = await env.AUTH_DB.prepare( + ` + INSERT INTO user (email) + VALUES (?) + ON CONFLICT (email) DO UPDATE SET email = email + RETURNING id; + `, + ) + .bind(email) + .first<{ id: string }>(); + if (!result) { + throw new Error(`Unable to process user: ${email}`); + } + console.log(`Found or created user ${result.id} with email ${email}`); + return result.id; +} diff --git a/openauth-template/tsconfig.json b/openauth-template/tsconfig.json new file mode 100644 index 0000000..c142f21 --- /dev/null +++ b/openauth-template/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["esnext"], + "module": "esnext", + "moduleResolution": "bundler", + "types": ["@cloudflare/workers-types", "./worker-configuration.d.ts"], + "noEmit": true, + "isolatedModules": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "strict": true + }, + "include": ["src"] +} diff --git a/openauth-template/worker-configuration.d.ts b/openauth-template/worker-configuration.d.ts new file mode 100644 index 0000000..de3c81a --- /dev/null +++ b/openauth-template/worker-configuration.d.ts @@ -0,0 +1,6 @@ +// Generated by Wrangler by running `wrangler --experimental-json-config types` + +interface Env { + AUTH_STORAGE: KVNamespace; + AUTH_DB: D1Database; +} diff --git a/openauth-template/wrangler.json b/openauth-template/wrangler.json new file mode 100644 index 0000000..66c15cf --- /dev/null +++ b/openauth-template/wrangler.json @@ -0,0 +1,23 @@ +{ + "compatibility_date": "2024-11-01", + "main": "src/index.ts", + "name": "openauth-template", + "upload_source_maps": true, + "kv_namespaces": [ + { + "binding": "AUTH_STORAGE", + "id": "afec91ff3f7e4b0b9b9323fc6cf5ff85" + } + ], + "d1_databases": [ + { + "binding": "AUTH_DB", + "database_name": "openauth-template-auth-db", + "database_id": "d4dfb2e9-2fd3-4d04-9c83-57b4336a5958" + } + ], + "observability": { + "enabled": true + }, + "compatibility_flags": ["nodejs_compat"] +} diff --git a/package-lock.json b/package-lock.json index cff947d..62940ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2457,6 +2457,58 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@oslojs/asn1": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@oslojs/asn1/-/asn1-1.0.0.tgz", + "integrity": "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@oslojs/binary": "1.0.0" + } + }, + "node_modules/@oslojs/binary": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@oslojs/binary/-/binary-1.0.0.tgz", + "integrity": "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@oslojs/crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@oslojs/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@oslojs/asn1": "1.0.0", + "@oslojs/binary": "1.0.0" + } + }, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@oslojs/jwt": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@oslojs/jwt/-/jwt-0.2.0.tgz", + "integrity": "sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@oslojs/encoding": "0.4.1" + } + }, + "node_modules/@oslojs/jwt/node_modules/@oslojs/encoding": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-0.4.1.tgz", + "integrity": "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q==", + "license": "MIT", + "peer": true + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3389,6 +3441,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0-beta.3.tgz", + "integrity": "sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw==", + "license": "MIT" + }, "node_modules/@types/acorn": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", @@ -4024,6 +4082,18 @@ "node": ">= 8" } }, + "node_modules/arctic": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/arctic/-/arctic-2.3.3.tgz", + "integrity": "sha512-f42+wyM0LKNwUY0TV3fSH1Fnsr/klcZi42XfWFvlNP7Ag8aBX92FaKQIU5xBQzxvvy7jg02tF567LsIlmEujKg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@oslojs/crypto": "1.0.1", + "@oslojs/encoding": "1.1.0", + "@oslojs/jwt": "0.2.0" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -4121,6 +4191,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/aws4fetch": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/aws4fetch/-/aws4fetch-1.0.20.tgz", + "integrity": "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==", + "license": "MIT" + }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -6432,6 +6508,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hono": { + "version": "4.6.14", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.6.14.tgz", + "integrity": "sha512-j4VkyUp2xazGJ8eCCLN1Vm/bxdvm/j5ZuU9AIjLu9vapn2M44p9L3Ktr9Vnb2RN2QtcR/wVjZVMlT5k7GJQgPw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/hosted-git-info": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.3.tgz", @@ -6912,6 +6998,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jose": { + "version": "5.9.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", + "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8619,6 +8714,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openauth-template": { + "resolved": "openauth-template", + "link": true + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -13252,6 +13351,59 @@ "url": "https://github.com/sponsors/wooorm" } }, + "openauth-template": { + "dependencies": { + "@openauthjs/openauth": "0.2.5", + "valibot": "^1.0.0-beta.9" + }, + "devDependencies": { + "@cloudflare/workers-types": "4.20241216.0", + "typescript": "5.6.3", + "wrangler": "3.97.0" + } + }, + "openauth-template/node_modules/@openauthjs/openauth": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@openauthjs/openauth/-/openauth-0.2.5.tgz", + "integrity": "sha512-YdppnBxRPIuse++xBV1mrGu/f/t1ilYPimKkBzypSjcLD+o5t50IeLwm/xg5QzVG+I1chKholT01hK41BBTaRg==", + "dependencies": { + "@standard-schema/spec": "1.0.0-beta.3", + "aws4fetch": "1.0.20", + "jose": "5.9.6" + }, + "peerDependencies": { + "arctic": "^2.2.2", + "hono": "^4.0.0" + } + }, + "openauth-template/node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "openauth-template/node_modules/valibot": { + "version": "1.0.0-beta.9", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.0.0-beta.9.tgz", + "integrity": "sha512-yEX8gMAZ2R1yI2uwOO4NCtVnJQx36zn3vD0omzzj9FhcoblvPukENIiRZXKZwCnqSeV80bMm8wNiGhQ0S8fiww==", + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "speech-to-text-template": { "devDependencies": { "@cloudflare/workers-types": "4.20241216.0", diff --git a/speech-to-text-template/tsconfig.json b/speech-to-text-template/tsconfig.json index c4cd57c..c142f21 100644 --- a/speech-to-text-template/tsconfig.json +++ b/speech-to-text-template/tsconfig.json @@ -3,6 +3,7 @@ "target": "esnext", "lib": ["esnext"], "module": "esnext", + "moduleResolution": "bundler", "types": ["@cloudflare/workers-types", "./worker-configuration.d.ts"], "noEmit": true, "isolatedModules": true, diff --git a/text-classification-template/tsconfig.json b/text-classification-template/tsconfig.json index c4cd57c..c142f21 100644 --- a/text-classification-template/tsconfig.json +++ b/text-classification-template/tsconfig.json @@ -3,6 +3,7 @@ "target": "esnext", "lib": ["esnext"], "module": "esnext", + "moduleResolution": "bundler", "types": ["@cloudflare/workers-types", "./worker-configuration.d.ts"], "noEmit": true, "isolatedModules": true, diff --git a/text-to-image-template/tsconfig.json b/text-to-image-template/tsconfig.json index c4cd57c..c142f21 100644 --- a/text-to-image-template/tsconfig.json +++ b/text-to-image-template/tsconfig.json @@ -3,6 +3,7 @@ "target": "esnext", "lib": ["esnext"], "module": "esnext", + "moduleResolution": "bundler", "types": ["@cloudflare/workers-types", "./worker-configuration.d.ts"], "noEmit": true, "isolatedModules": true, diff --git a/translation-template/tsconfig.json b/translation-template/tsconfig.json index c4cd57c..c142f21 100644 --- a/translation-template/tsconfig.json +++ b/translation-template/tsconfig.json @@ -3,6 +3,7 @@ "target": "esnext", "lib": ["esnext"], "module": "esnext", + "moduleResolution": "bundler", "types": ["@cloudflare/workers-types", "./worker-configuration.d.ts"], "noEmit": true, "isolatedModules": true, diff --git a/vector-embedding-template/tsconfig.json b/vector-embedding-template/tsconfig.json index c4cd57c..c142f21 100644 --- a/vector-embedding-template/tsconfig.json +++ b/vector-embedding-template/tsconfig.json @@ -3,6 +3,7 @@ "target": "esnext", "lib": ["esnext"], "module": "esnext", + "moduleResolution": "bundler", "types": ["@cloudflare/workers-types", "./worker-configuration.d.ts"], "noEmit": true, "isolatedModules": true,