diff --git a/document/OAUTH_SETUP_GUIDE.md b/document/OAUTH_SETUP_GUIDE.md deleted file mode 100644 index cc7a5f4..0000000 --- a/document/OAUTH_SETUP_GUIDE.md +++ /dev/null @@ -1,119 +0,0 @@ -# OAuth 设置指南 - -## 问题诊断 - -如果遇到 `Error 400: invalid_request` 错误,通常是 OAuth 配置问题。 - -### 常见错误原因 - -1. **NEXTAUTH_URL 格式错误** - - - 包含多余的引号 - - URL 格式不正确 - - 协议不匹配(http vs https) - -2. **Google OAuth 重定向 URI 配置错误** - - 重定向 URI 未在 Google Cloud Console 中正确配置 - - 重定向 URI 格式不正确 - -## 解决步骤 - -### 1. 检查环境变量 - -在服务器上运行环境变量检查脚本: - -```bash -node scripts/check-env.js -``` - -### 2. 修复 NEXTAUTH_URL - -确保 `.env.production` 文件中的 `NEXTAUTH_URL` 格式正确: - -```bash -# 正确的格式 -NEXTAUTH_URL="https://recorder.zyj.best" - -# 错误的格式(包含多余引号) -NEXTAUTH_URL=""https://recorder.zyj.best"" -``` - -### 3. 配置 Google OAuth - -#### 在 Google Cloud Console 中: - -1. 访问 [Google Cloud Console](https://console.cloud.google.com/) -2. 选择你的项目 -3. 进入 "APIs & Services" > "Credentials" -4. 编辑你的 OAuth 2.0 客户端 ID -5. 在 "Authorized redirect URIs" 中添加: - ``` - https://recorder.zyj.best/api/auth/callback/google - ``` - -### 4. 验证配置 - -重新部署应用并测试: - -```bash -./deploy.sh -``` - -### 5. 调试信息 - -如果问题仍然存在,检查应用日志: - -```bash -docker logs recorder-app -``` - -## 环境变量模板 - -```bash -# Database -DATABASE_URL="file:./prod.db" - -# NextAuth.js -NEXTAUTH_URL="https://recorder.zyj.best" -NEXTAUTH_SECRET="your-secure-secret-here" - -# Google OAuth -GOOGLE_CLIENT_ID="your-google-client-id" -GOOGLE_CLIENT_SECRET="your-google-client-secret" - -# AWS S3 Configuration -AWS_ACCESS_KEY_ID="your-aws-access-key-id" -AWS_SECRET_ACCESS_KEY="your-aws-secret-access-key" -AWS_REGION="us-east-1" -AWS_S3_BUCKET="your-s3-bucket-name" -``` - -## 故障排除 - -### 错误:redirect_uri 格式错误 - -**原因**:NEXTAUTH_URL 包含多余的引号或格式错误 - -**解决**: - -1. 检查 `.env.production` 文件 -2. 确保 NEXTAUTH_URL 格式正确 -3. 重启应用 - -### 错误:redirect_uri 不在授权列表中 - -**原因**:Google OAuth 重定向 URI 未正确配置 - -**解决**: - -1. 在 Google Cloud Console 中添加正确的重定向 URI -2. 确保 URI 格式为:`https://your-domain.com/api/auth/callback/google` - -### 错误:invalid_client - -**原因**:Google OAuth 凭据错误 - -**解决**: - -1. 检查 GOOGLE_CLIENT_ID 和 GOOGLE_CLIENT_SECRET -2. 确保凭据与 Google Cloud Console 中的配置匹配 diff --git a/document/PRODUCTION_OAUTH_SETUP.md b/document/PRODUCTION_OAUTH_SETUP.md new file mode 100644 index 0000000..f77c7ea --- /dev/null +++ b/document/PRODUCTION_OAUTH_SETUP.md @@ -0,0 +1,100 @@ +# 生产环境 OAuth 配置指南 + +## 🚨 问题描述 + +在生产环境中,Google OAuth 登录出现以下错误: + +``` +Error 400: invalid_request +Request details: redirect_uri=https://"https//recorder.zyj.best%22/callback/google +``` + +## 🔍 问题分析 + +### 根本原因 + +1. **重定向 URI 格式错误**:URI 包含了多余的引号和错误的编码 +2. **环境变量配置问题**:`NEXTAUTH_URL` 可能没有正确设置 +3. **Google Cloud Console 配置问题**:授权重定向 URI 可能不正确 + +## ✅ 解决方案 + +### 1. 环境变量配置 + +确保服务器上的 `.env.production` 文件包含正确的配置: + +```bash +# NextAuth.js - 生产环境配置 +NEXTAUTH_URL="https://recorder.zyj.best" +NEXTAUTH_SECRET="your-production-nextauth-secret" + +# Google OAuth - 生产环境配置 +GOOGLE_CLIENT_ID="your-google-client-id" +GOOGLE_CLIENT_SECRET="your-google-client-secret" +``` + +### 2. Google Cloud Console 配置 + +在 Google Cloud Console 中,确保以下重定向 URI 已正确配置: + +#### 授权重定向 URI + +``` +https://recorder.zyj.best/api/auth/callback/google +``` + +#### JavaScript 来源 + +``` +https://recorder.zyj.best +``` + +### 3. 验证步骤 + +1. **检查环境变量**: + + ```bash + # 在服务器上检查环境变量 + docker exec -it recorder-app env | grep NEXTAUTH + ``` + +2. **检查 Google Cloud Console**: + + - 登录 [Google Cloud Console](https://console.cloud.google.com/) + - 进入 "APIs & Services" > "Credentials" + - 检查 OAuth 2.0 客户端 ID 的配置 + +3. **重启应用**: + ```bash + ./deploy.sh + ``` + +### 4. 调试信息 + +应用现在包含调试信息,可以在日志中查看重定向过程: + +```bash +docker logs recorder-app +``` + +## 🔧 常见问题 + +### 问题 1:重定向 URI 不匹配 + +**解决方案**:确保 Google Cloud Console 中的重定向 URI 与 `NEXTAUTH_URL` 一致 + +### 问题 2:HTTPS 证书问题 + +**解决方案**:确保域名有有效的 SSL 证书 + +### 问题 3:环境变量未生效 + +**解决方案**:重启 Docker 容器以重新加载环境变量 + +## 📝 检查清单 + +- [ ] `NEXTAUTH_URL` 设置为 `https://recorder.zyj.best` +- [ ] Google Cloud Console 中配置了正确的重定向 URI +- [ ] 环境变量文件正确加载 +- [ ] 应用已重启 +- [ ] SSL 证书有效 diff --git a/env.example b/env.example index f4412b2..503b5d5 100644 --- a/env.example +++ b/env.example @@ -2,11 +2,8 @@ DATABASE_URL="file:./dev.db" # NextAuth.js -# 重要:确保 URL 不包含多余的引号,格式应为:https://your-domain.com -NEXTAUTH_URL="https://recorder.zyj.best" +NEXTAUTH_URL="http://localhost:3000" NEXTAUTH_SECRET="your-nextauth-secret" -# 可选:设置 Cookie 域名(生产环境) -NEXTAUTH_COOKIE_DOMAIN="recorder.zyj.best" # Google OAuth GOOGLE_CLIENT_ID="your-google-client-id" diff --git a/env.production.example b/env.production.example new file mode 100644 index 0000000..4d27ad1 --- /dev/null +++ b/env.production.example @@ -0,0 +1,19 @@ +# Database +DATABASE_URL="file:./prod.db" + +# NextAuth.js - 生产环境配置 +NEXTAUTH_URL="https://recorder.zyj.best" +NEXTAUTH_SECRET="your-production-nextauth-secret" + +# Google OAuth - 生产环境配置 +GOOGLE_CLIENT_ID="your-google-client-id" +GOOGLE_CLIENT_SECRET="your-google-client-secret" + +# AWS S3 Configuration +AWS_ACCESS_KEY_ID="your-aws-access-key-id" +AWS_SECRET_ACCESS_KEY="your-aws-secret-access-key" +AWS_REGION="us-east-1" +AWS_S3_BUCKET="your-s3-bucket-name" + +# 生产环境设置 +NODE_ENV="production" \ No newline at end of file diff --git a/lib/auth.ts b/lib/auth.ts index d1a94db..3946e87 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -7,42 +7,31 @@ import CredentialsProvider from "next-auth/providers/credentials"; import { UserService } from "./services/user.service"; import { AuthOptions } from "next-auth"; -// 验证和清理 NEXTAUTH_URL -function getValidatedNextAuthUrl(): string { - const url = process.env.NEXTAUTH_URL; - if (!url) { - throw new Error("NEXTAUTH_URL 环境变量未设置"); - } +// 验证环境变量 +function validateAuthConfig() { + const requiredEnvVars = [ + 'GOOGLE_CLIENT_ID', + 'GOOGLE_CLIENT_SECRET', + 'NEXTAUTH_SECRET', + 'NEXTAUTH_URL' + ]; + + const missingVars = requiredEnvVars.filter(varName => !process.env[varName]); - // 清理 URL,移除多余的引号 - let cleanUrl = url.trim(); - if (cleanUrl.startsWith('"') && cleanUrl.endsWith('"')) { - cleanUrl = cleanUrl.slice(1, -1); + if (missingVars.length > 0) { + console.error('Missing required environment variables:', missingVars); + throw new Error(`Missing required environment variables: ${missingVars.join(', ')}`); } - if (cleanUrl.startsWith("'") && cleanUrl.endsWith("'")) { - cleanUrl = cleanUrl.slice(1, -1); - } - - // 确保 URL 格式正确 - try { - new URL(cleanUrl); - } catch (error) { - throw new Error(`无效的 NEXTAUTH_URL: ${cleanUrl}`); - } - - return cleanUrl; + + console.log('Auth configuration validated:', { + NEXTAUTH_URL: process.env.NEXTAUTH_URL, + GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID?.substring(0, 20) + '...', + NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET ? 'Set' : 'Missing' + }); } -// 获取域名用于 Cookie 配置 -function getDomain(): string { - const url = getValidatedNextAuthUrl(); - try { - const urlObj = new URL(url); - return urlObj.hostname; - } catch { - return "recorder.zyj.best"; // 默认域名 - } -} +// 验证配置 +validateAuthConfig(); export const authOptions: AuthOptions = { adapter: PrismaAdapter(prisma), @@ -51,6 +40,13 @@ export const authOptions: AuthOptions = { GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, + authorization: { + params: { + prompt: "consent", + access_type: "offline", + response_type: "code" + } + } }), CredentialsProvider({ @@ -117,10 +113,22 @@ export const authOptions: AuthOptions = { }, async redirect({ url, baseUrl }) { + // 调试信息 + console.log("Redirect callback:", { url, baseUrl }); + // 确保重定向到正确的页面 - if (url.startsWith("/")) return `${baseUrl}${url}`; - else if (new URL(url).origin === baseUrl) return url; - return `${baseUrl}/dashboard`; + if (url.startsWith("/")) { + const redirectUrl = `${baseUrl}${url}`; + console.log("Redirecting to:", redirectUrl); + return redirectUrl; + } else if (new URL(url).origin === baseUrl) { + console.log("Redirecting to same origin:", url); + return url; + } + + const defaultUrl = `${baseUrl}/dashboard`; + console.log("Redirecting to default:", defaultUrl); + return defaultUrl; }, }, @@ -131,69 +139,4 @@ export const authOptions: AuthOptions = { secret: process.env.NEXTAUTH_SECRET, debug: process.env.NODE_ENV === "development", - - // 添加 Cookie 配置 - cookies: { - sessionToken: { - name: `next-auth.session-token`, - options: { - httpOnly: true, - sameSite: "lax", - path: "/", - secure: process.env.NODE_ENV === "production", - domain: process.env.NODE_ENV === "production" ? getDomain() : undefined, - }, - }, - callbackUrl: { - name: `next-auth.callback-url`, - options: { - sameSite: "lax", - path: "/", - secure: process.env.NODE_ENV === "production", - domain: process.env.NODE_ENV === "production" ? getDomain() : undefined, - }, - }, - csrfToken: { - name: `next-auth.csrf-token`, - options: { - httpOnly: true, - sameSite: "lax", - path: "/", - secure: process.env.NODE_ENV === "production", - domain: process.env.NODE_ENV === "production" ? getDomain() : undefined, - }, - }, - pkceCodeVerifier: { - name: `next-auth.pkce.code_verifier`, - options: { - httpOnly: true, - sameSite: "lax", - path: "/", - maxAge: 900, - secure: process.env.NODE_ENV === "production", - domain: process.env.NODE_ENV === "production" ? getDomain() : undefined, - }, - }, - state: { - name: `next-auth.state`, - options: { - httpOnly: true, - sameSite: "lax", - path: "/", - maxAge: 900, - secure: process.env.NODE_ENV === "production", - domain: process.env.NODE_ENV === "production" ? getDomain() : undefined, - }, - }, - nonce: { - name: `next-auth.nonce`, - options: { - httpOnly: true, - sameSite: "lax", - path: "/", - secure: process.env.NODE_ENV === "production", - domain: process.env.NODE_ENV === "production" ? getDomain() : undefined, - }, - }, - }, }; diff --git a/scripts/check-env.js b/scripts/check-env.js deleted file mode 100644 index e8c8eb7..0000000 --- a/scripts/check-env.js +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env node - -// 环境变量检查脚本 -console.log("=== 环境变量检查 ==="); - -const requiredVars = [ - "NEXTAUTH_URL", - "NEXTAUTH_SECRET", - "GOOGLE_CLIENT_ID", - "GOOGLE_CLIENT_SECRET", - "DATABASE_URL", -]; - -console.log("\n必需的环境变量:"); -requiredVars.forEach((varName) => { - const value = process.env[varName]; - if (value) { - console.log( - `✅ ${varName}: ${value.substring(0, 20)}${ - value.length > 20 ? "..." : "" - }` - ); - } else { - console.log(`❌ ${varName}: 未设置`); - } -}); - -console.log("\n=== NEXTAUTH_URL 详细检查 ==="); -const nextAuthUrl = process.env.NEXTAUTH_URL; -if (nextAuthUrl) { - console.log(`原始值: "${nextAuthUrl}"`); - console.log(`长度: ${nextAuthUrl.length}`); - console.log(`包含引号: ${nextAuthUrl.includes('"')}`); - console.log(`包含单引号: ${nextAuthUrl.includes("'")}`); - - // 清理 URL - let cleanUrl = nextAuthUrl.trim(); - if (cleanUrl.startsWith('"') && cleanUrl.endsWith('"')) { - cleanUrl = cleanUrl.slice(1, -1); - console.log(`清理后: "${cleanUrl}"`); - } - - try { - new URL(cleanUrl); - console.log(`✅ URL 格式有效`); - } catch (error) { - console.log(`❌ URL 格式无效: ${error.message}`); - } -} else { - console.log("❌ NEXTAUTH_URL 未设置"); -} - -console.log("\n=== Google OAuth 配置检查 ==="); -const googleClientId = process.env.GOOGLE_CLIENT_ID; -const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET; - -if (googleClientId && googleClientSecret) { - console.log("✅ Google OAuth 凭据已设置"); - console.log(`Client ID 长度: ${googleClientId.length}`); - console.log(`Client Secret 长度: ${googleClientSecret.length}`); -} else { - console.log("❌ Google OAuth 凭据未完全设置"); -} - -console.log("\n=== 建议 ==="); -console.log("1. 确保 NEXTAUTH_URL 不包含多余的引号"); -console.log("2. 确保 Google OAuth 重定向 URI 配置正确"); -console.log("3. 在 Google Cloud Console 中添加正确的重定向 URI"); -console.log( - "4. 重定向 URI 格式应为: https://your-domain.com/api/auth/callback/google" -); diff --git a/scripts/check-oauth.sh b/scripts/check-oauth.sh new file mode 100644 index 0000000..99fd564 --- /dev/null +++ b/scripts/check-oauth.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +echo "🔍 检查 OAuth 配置..." + +# 检查环境变量 +echo "📋 环境变量检查:" +echo "NEXTAUTH_URL: ${NEXTAUTH_URL:-'未设置'}" +echo "NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-'未设置'}" +echo "GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-'未设置'}" + +# 检查容器环境变量 +echo "" +echo "🐳 Docker 容器环境变量:" +docker exec recorder-app env | grep -E "(NEXTAUTH|GOOGLE)" || echo "无法获取容器环境变量" + +# 检查应用日志 +echo "" +echo "📝 最近的认证日志:" +docker logs recorder-app --tail 50 | grep -i "auth\|oauth\|redirect" || echo "未找到相关日志" + +# 检查网络连接 +echo "" +echo "🌐 网络连接检查:" +curl -I https://recorder.zyj.best 2>/dev/null | head -1 || echo "无法连接到网站" + +echo "" +echo "✅ 检查完成!" +echo "" +echo "📋 下一步操作:" +echo "1. 确保 Google Cloud Console 中配置了正确的重定向 URI" +echo "2. 重启应用: ./deploy.sh" +echo "3. 检查日志: docker logs recorder-app" \ No newline at end of file diff --git a/scripts/oauth-debug.js b/scripts/oauth-debug.js deleted file mode 100644 index c519110..0000000 --- a/scripts/oauth-debug.js +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env node - -// OAuth 调试脚本 -console.log("=== OAuth 配置调试 ==="); - -// 模拟服务器环境变量 -const envVars = { - NEXTAUTH_URL: "https://recorder.zyj.best", - GOOGLE_CLIENT_ID: - "1060072115182-l5u59vrbs2lmcpg7pnn72bc8h37eolff.apps.googleusercontent.com", - GOOGLE_CLIENT_SECRET: "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", -}; - -console.log("\n=== 环境变量检查 ==="); -Object.entries(envVars).forEach(([key, value]) => { - console.log(`✅ ${key}: ${value.substring(0, 30)}...`); -}); - -console.log("\n=== 重定向 URI 分析 ==="); -const nextAuthUrl = envVars.NEXTAUTH_URL; -const expectedRedirectUri = `${nextAuthUrl}/api/auth/callback/google`; - -console.log(`NEXTAUTH_URL: ${nextAuthUrl}`); -console.log(`预期的重定向 URI: ${expectedRedirectUri}`); - -// 验证 URL 格式 -try { - new URL(nextAuthUrl); - console.log("✅ NEXTAUTH_URL 格式有效"); -} catch (error) { - console.log(`❌ NEXTAUTH_URL 格式无效: ${error.message}`); -} - -try { - new URL(expectedRedirectUri); - console.log("✅ 重定向 URI 格式有效"); -} catch (error) { - console.log(`❌ 重定向 URI 格式无效: ${error.message}`); -} - -console.log("\n=== Google Cloud Console 配置检查 ==="); -console.log("请在 Google Cloud Console 中验证以下配置:"); -console.log("1. 项目 ID: 检查你的 Google Cloud 项目"); -console.log( - "2. OAuth 2.0 客户端 ID: 1060072115182-l5u59vrbs2lmcpg7pnn72bc8h37eolff.apps.googleusercontent.com" -); -console.log("3. 授权重定向 URI 应包含:"); -console.log(` - ${expectedRedirectUri}`); - -console.log("\n=== 常见问题排查 ==="); -console.log("1. 确保 Google Cloud Console 中的重定向 URI 完全匹配"); -console.log("2. 检查是否有额外的空格或引号"); -console.log("3. 确保协议是 https(不是 http)"); -console.log("4. 检查域名是否正确(recorder.zyj.best)"); - -console.log("\n=== 测试步骤 ==="); -console.log("1. 访问: https://recorder.zyj.best/login"); -console.log("2. 点击 '使用 Google 登录'"); -console.log("3. 观察浏览器地址栏的重定向 URL"); -console.log("4. 检查是否与 Google Cloud Console 中的配置匹配"); - -console.log("\n=== 调试命令 ==="); -console.log("在服务器上运行以下命令查看应用日志:"); -console.log("docker logs recorder-app --tail 50");