From 07fa75bf28584e5117d623dee77133d6c2f0ca84 Mon Sep 17 00:00:00 2001 From: Levi Date: Thu, 18 Dec 2025 16:04:05 +0800 Subject: [PATCH 1/2] fix: add COOKIE_DOMAIN support for cross-subdomain sharing --- .github/workflows/deploy.yml | 1 + .github/workflows/scripts/deploy.js | 291 +++++++++++++++------------- src/server/api/index.ts | 1 + src/server/lib/auth.ts | 11 ++ wrangler.toml | 3 + 5 files changed, 170 insertions(+), 137 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f48b0a1..ff79c45 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -47,6 +47,7 @@ jobs: AUTH_SOCIAL_GOOGLE_CLIENT_SECRET: ${{ secrets.AUTH_SOCIAL_GOOGLE_CLIENT_SECRET }} AUTH_SOCIAL_GITHUB_CLIENT_ID: ${{ secrets.AUTH_SOCIAL_GITHUB_CLIENT_ID }} AUTH_SOCIAL_GITHUB_CLIENT_SECRET: ${{ secrets.AUTH_SOCIAL_GITHUB_CLIENT_SECRET }} + COOKIE_DOMAIN: ${{ secrets.COOKIE_DOMAIN }} run: | # build application sed -i "s/DATABASE_ID/${{ secrets.CLOUDFLARE_DATABASE_ID }}/g" wrangler.toml diff --git a/.github/workflows/scripts/deploy.js b/.github/workflows/scripts/deploy.js index 4509af2..6a8a36a 100644 --- a/.github/workflows/scripts/deploy.js +++ b/.github/workflows/scripts/deploy.js @@ -9,35 +9,38 @@ import { parse as parseToml, stringify as stringifyToml } from "smol-toml"; const __dirname = process.cwd(); const config = { - preview: { - docs: false, - override: { - name: "typix-preview", - vars: { - MODE: "client", - PROVIDER_CLOUDFLARE_BUILTIN: "true", - }, - }, - }, - production: { - docs: true, - override: { - vars: { - MODE: "mixed", - GOOGLE_ANALYTICS_ID: process.env.GOOGLE_ANALYTICS_ID, - AUTH_EMAIL_VERIFICATION_ENABLED: "true", - AUTH_EMAIL_RESEND_API_KEY: process.env.AUTH_EMAIL_RESEND_API_KEY, - AUTH_EMAIL_RESEND_FROM: "Typix ", - AUTH_SOCIAL_GOOGLE_ENABLED: "true", - AUTH_SOCIAL_GOOGLE_CLIENT_ID: process.env.AUTH_SOCIAL_GOOGLE_CLIENT_ID, - AUTH_SOCIAL_GOOGLE_CLIENT_SECRET: process.env.AUTH_SOCIAL_GOOGLE_CLIENT_SECRET, - AUTH_SOCIAL_GITHUB_ENABLED: "true", - AUTH_SOCIAL_GITHUB_CLIENT_ID: process.env.AUTH_SOCIAL_GITHUB_CLIENT_ID, - AUTH_SOCIAL_GITHUB_CLIENT_SECRET: process.env.AUTH_SOCIAL_GITHUB_CLIENT_SECRET, - PROVIDER_CLOUDFLARE_BUILTIN: "true", - }, - }, - }, + preview: { + docs: false, + override: { + name: "typix-preview", + vars: { + MODE: "client", + PROVIDER_CLOUDFLARE_BUILTIN: "true", + }, + }, + }, + production: { + docs: true, + override: { + vars: { + MODE: "mixed", + GOOGLE_ANALYTICS_ID: process.env.GOOGLE_ANALYTICS_ID, + AUTH_EMAIL_VERIFICATION_ENABLED: "true", + AUTH_EMAIL_RESEND_API_KEY: process.env.AUTH_EMAIL_RESEND_API_KEY, + AUTH_EMAIL_RESEND_FROM: "Typix ", + AUTH_SOCIAL_GOOGLE_ENABLED: "true", + AUTH_SOCIAL_GOOGLE_CLIENT_ID: process.env.AUTH_SOCIAL_GOOGLE_CLIENT_ID, + AUTH_SOCIAL_GOOGLE_CLIENT_SECRET: + process.env.AUTH_SOCIAL_GOOGLE_CLIENT_SECRET, + AUTH_SOCIAL_GITHUB_ENABLED: "true", + AUTH_SOCIAL_GITHUB_CLIENT_ID: process.env.AUTH_SOCIAL_GITHUB_CLIENT_ID, + AUTH_SOCIAL_GITHUB_CLIENT_SECRET: + process.env.AUTH_SOCIAL_GITHUB_CLIENT_SECRET, + COOKIE_DOMAIN: process.env.COOKIE_DOMAIN, + PROVIDER_CLOUDFLARE_BUILTIN: "true", + }, + }, + }, }; /** @@ -45,9 +48,9 @@ const config = { * @returns {any} Parsed TOML configuration object */ function readWranglerConfig() { - const wranglerPath = path.join(__dirname, "wrangler.toml"); - const content = fs.readFileSync(wranglerPath, "utf8"); - return parseToml(content); + const wranglerPath = path.join(__dirname, "wrangler.toml"); + const content = fs.readFileSync(wranglerPath, "utf8"); + return parseToml(content); } /** @@ -55,9 +58,9 @@ function readWranglerConfig() { * @param {any} config - Configuration object */ function writeWranglerConfig(config) { - const wranglerPath = path.join(__dirname, "wrangler.toml"); - const content = stringifyToml(config); - fs.writeFileSync(wranglerPath, content, "utf8"); + const wranglerPath = path.join(__dirname, "wrangler.toml"); + const content = stringifyToml(config); + fs.writeFileSync(wranglerPath, content, "utf8"); } /** @@ -67,17 +70,21 @@ function writeWranglerConfig(config) { * @returns {any} Merged object */ function deepMerge(target, source) { - const result = { ...target }; - - for (const key in source) { - if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) { - result[key] = deepMerge(result[key] || {}, source[key]); - } else { - result[key] = source[key]; - } - } - - return result; + const result = { ...target }; + + for (const key in source) { + if ( + source[key] && + typeof source[key] === "object" && + !Array.isArray(source[key]) + ) { + result[key] = deepMerge(result[key] || {}, source[key]); + } else { + result[key] = source[key]; + } + } + + return result; } /** @@ -86,31 +93,34 @@ function deepMerge(target, source) { * @param {any} baseConfig - Base configuration copy */ function updateWranglerConfig(override, baseConfig) { - const updatedConfig = deepMerge(baseConfig, override); - writeWranglerConfig(updatedConfig); + const updatedConfig = deepMerge(baseConfig, override); + writeWranglerConfig(updatedConfig); } /** * Build and deploy */ function buildAndDeploy(config) { - console.log("šŸ“¦ Building application..."); - execSync("npm run build", { stdio: "inherit" }); - console.log("āœ… Application built successfully"); - - if (config.docs) { - console.log("šŸ“š Building documentation..."); - execSync("cd ./docs && npm install && npm run build && cp -r ./out/ ../dist/home && cd ..", { stdio: "inherit" }); - console.log("āœ… Documentation built successfully"); - } - - console.log("šŸš€ Deploying to Cloudflare..."); - if (config.override.vars.MODE === "client") { - execSync("npm run deploy:no-migrate", { stdio: "inherit" }); - } else { - execSync("npm run deploy", { stdio: "inherit" }); - } - console.log("āœ… Deployed successfully"); + console.log("šŸ“¦ Building application..."); + execSync("npm run build", { stdio: "inherit" }); + console.log("āœ… Application built successfully"); + + if (config.docs) { + console.log("šŸ“š Building documentation..."); + execSync( + "cd ./docs && npm install && npm run build && cp -r ./out/ ../dist/home && cd ..", + { stdio: "inherit" } + ); + console.log("āœ… Documentation built successfully"); + } + + console.log("šŸš€ Deploying to Cloudflare..."); + if (config.override.vars.MODE === "client") { + execSync("npm run deploy:no-migrate", { stdio: "inherit" }); + } else { + execSync("npm run deploy", { stdio: "inherit" }); + } + console.log("āœ… Deployed successfully"); } /** @@ -118,82 +128,89 @@ function buildAndDeploy(config) { * @param {string[]} environments - Environment list */ function deployEnvironments(environments) { - console.log(`šŸ”„ Starting deployment for environments: ${environments.join(", ")}`); - - // Read original configuration as a copy - const baseConfig = readWranglerConfig(); - console.log("šŸ“‹ Base configuration loaded"); - - for (const environment of environments) { - console.log(`\n--- Deploying ${environment} environment ---`); - const envConfig = config[environment]; - try { - updateWranglerConfig(envConfig.override, baseConfig); - buildAndDeploy(envConfig); - } catch (error) { - console.error( - `āŒ Failed to deploy ${environment} environment:`, - error instanceof Error ? error.message : String(error), - ); - process.exit(1); - } - } - - console.log(`\nšŸŽ‰ All environments (${environments.join(", ")}) deployed successfully!`); + console.log( + `šŸ”„ Starting deployment for environments: ${environments.join(", ")}` + ); + + // Read original configuration as a copy + const baseConfig = readWranglerConfig(); + console.log("šŸ“‹ Base configuration loaded"); + + for (const environment of environments) { + console.log(`\n--- Deploying ${environment} environment ---`); + const envConfig = config[environment]; + try { + updateWranglerConfig(envConfig.override, baseConfig); + buildAndDeploy(envConfig); + } catch (error) { + console.error( + `āŒ Failed to deploy ${environment} environment:`, + error instanceof Error ? error.message : String(error) + ); + process.exit(1); + } + } + + console.log( + `\nšŸŽ‰ All environments (${environments.join(", ")}) deployed successfully!` + ); } // Command line interface function main() { - try { - const { values } = parseArgs({ - options: { - preview: { - type: "boolean", - short: "p", - default: false, - }, - production: { - type: "boolean", - short: "P", - default: false, - }, - help: { - type: "boolean", - short: "h", - default: false, - }, - }, - allowPositionals: false, - }); - - const environments = []; - - if (values.preview) { - environments.push("preview"); - } - - if (values.production) { - environments.push("production"); - } - - if (environments.length === 0) { - environments.push("production"); - } - - for (const env of environments) { - if (!(env in config)) { - console.error(`āŒ Unknown environment: ${env}`); - console.log("Available environments: production, preview"); - process.exit(1); - } - } - - deployEnvironments(environments); - } catch (error) { - console.error("āŒ Error parsing arguments:", error instanceof Error ? error.message : String(error)); - console.log("Use --help for usage information"); - process.exit(1); - } + try { + const { values } = parseArgs({ + options: { + preview: { + type: "boolean", + short: "p", + default: false, + }, + production: { + type: "boolean", + short: "P", + default: false, + }, + help: { + type: "boolean", + short: "h", + default: false, + }, + }, + allowPositionals: false, + }); + + const environments = []; + + if (values.preview) { + environments.push("preview"); + } + + if (values.production) { + environments.push("production"); + } + + if (environments.length === 0) { + environments.push("production"); + } + + for (const env of environments) { + if (!(env in config)) { + console.error(`āŒ Unknown environment: ${env}`); + console.log("Available environments: production, preview"); + process.exit(1); + } + } + + deployEnvironments(environments); + } catch (error) { + console.error( + "āŒ Error parsing arguments:", + error instanceof Error ? error.message : String(error) + ); + console.log("Use --help for usage information"); + process.exit(1); + } } main(); diff --git a/src/server/api/index.ts b/src/server/api/index.ts index cdeb328..17ee315 100644 --- a/src/server/api/index.ts +++ b/src/server/api/index.ts @@ -38,6 +38,7 @@ const factory = createFactory({ clientSecret: env(c).AUTH_SOCIAL_GITHUB_CLIENT_SECRET || "", }, }, + cookieDomain: env(c).COOKIE_DOMAIN ? String(env(c).COOKIE_DOMAIN) : undefined, }; c.set("db", db); diff --git a/src/server/lib/auth.ts b/src/server/lib/auth.ts index b59ded7..0fd05fd 100644 --- a/src/server/lib/auth.ts +++ b/src/server/lib/auth.ts @@ -24,6 +24,8 @@ export interface AuthConfig { clientSecret: string; }; }; + // Cookie domain for cross-subdomain sharing (e.g., .xxx.com) + cookieDomain?: string; } export const createAuth = (db: any, config?: AuthConfig) => @@ -31,6 +33,15 @@ export const createAuth = (db: any, config?: AuthConfig) => database: drizzleAdapter(db, { provider: "sqlite", }), + ...(config?.cookieDomain + ? { + advanced: { + defaultCookieAttributes: { + domain: config.cookieDomain, + }, + }, + } + : {}), emailAndPassword: { enabled: true, requireEmailVerification: config?.email?.verification === true, diff --git a/wrangler.toml b/wrangler.toml index f73860f..90a56c2 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -46,6 +46,9 @@ MODE = "client" # AUTH_SOCIAL_GITHUB_CLIENT_ID = "" # AUTH_SOCIAL_GITHUB_CLIENT_SECRET = "" +# Cookie domain for cross-subdomain sharing (e.g., .xxx.com) +# COOKIE_DOMAIN = "" + # Enable Cloudflare built-in AI binding # When set to true, uses Cloudflare Workers AI for image generation without requiring additional API keys PROVIDER_CLOUDFLARE_BUILTIN = "true" From 5f1f5480d5bcb8486e3c3455b98eed94f6114234 Mon Sep 17 00:00:00 2001 From: Levi Date: Thu, 18 Dec 2025 16:15:20 +0800 Subject: [PATCH 2/2] fix --- src/server/lib/auth.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/server/lib/auth.ts b/src/server/lib/auth.ts index 0fd05fd..30438c0 100644 --- a/src/server/lib/auth.ts +++ b/src/server/lib/auth.ts @@ -36,7 +36,8 @@ export const createAuth = (db: any, config?: AuthConfig) => ...(config?.cookieDomain ? { advanced: { - defaultCookieAttributes: { + crossSubDomainCookies: { + enabled: true, domain: config.cookieDomain, }, },