diff --git a/.env.sample b/.env.sample index 904bf2c..033fd4a 100644 --- a/.env.sample +++ b/.env.sample @@ -10,7 +10,9 @@ NEXT_PUBLIC_HOST_URL= # See the documentation for all the connection string options: https://pris.ly/d/connection-strings DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public" -# NextAuth.js +# Next Auth AUTH_SECRET= -DISCOUSE_SECRET= \ No newline at end of file +# Discourse SSO +DISCOURSE_HOST= +DISCOURSE_SECRET= \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a3d465b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Tuluobo + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. \ No newline at end of file diff --git a/README.md b/README.md index c403366..294c758 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,153 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). +# Discourse Connect -## Getting Started +这是一个基于 [Next.js](https://nextjs.org/) 的项目,实现了使用 Discourse SSO (Single Sign-On) 用户系统的 OAuth 认证功能。 -First, run the development server: +## 项目概述 + +本项目提供了一个 OAuth 认证系统,允许其他应用程序使用 Discourse 论坛的用户账号进行身份验证。这样可以让用户使用他们已有的 Discourse 账号登录到您的应用程序,无需创建新的账号。 + +主要特性: + +- 基于 Discourse SSO 的用户认证 +- OAuth 2.0 协议支持 +- 使用 Next.js 框架构建,提供良好的性能和开发体验 + +## 开始使用 + +首先,运行开发服务器: ```bash -npm run dev -# or -yarn dev -# or pnpm dev -# or -bun dev +# 或 +pnpm turbo ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +在浏览器中打开 [http://localhost:3000](http://localhost:3000) 查看结果。 -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +您可以通过修改 `app/page.tsx` 来开始编辑页面。当您编辑文件时,页面会自动更新。 -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. +## 配置 -## Learn More +要使用此 OAuth 系统,您需要进行以下配置: -To learn more about Next.js, take a look at the following resources: +1. 在您的 Discourse 论坛中启用 SSO 功能。 +2. 设置环境变量: + - `NEXT_PUBLIC_HOST_URL`: 应用程序的主机 URL(不要在末尾添加 "/") + - `DATABASE_URL`: 数据库连接字符串 + - `AUTH_SECRET`: Next Auth 的密钥 + - `DISCOURSE_HOST`: 您的 Discourse 论坛 URL + - `DISCOURSE_SECRET`: 在 Discourse 中设置的 SSO secret -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +## 部署 -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! +### 使用 Docker 部署 -## Deploy on Vercel +本项目支持使用 Docker 进行部署。以下是使用 Docker Compose 部署的步骤: -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +1. 确保您的系统已安装 Docker 和 Docker Compose。 -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. +2. 在项目根目录下,运行以下命令启动服务: + + ```bash + docker-compose up -d + ``` + + 这将构建并启动 Web 应用和 PostgreSQL 数据库服务。 + +3. 应用将在 http://localhost:3000 上运行。 + +4. 要停止服务,运行: + + ```bash + docker-compose down + ``` + +### 使用 Vercel 部署 + +另一种部署 Next.js 应用程序的简单方法是使用 [Vercel 平台](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme)。 + +查看我们的 [Next.js 部署文档](https://nextjs.org/docs/deployment) 了解更多详情。 + +## OAuth 2.0 接口 + +本项目实现了基于 OAuth 2.0 协议的认证系统。以下是主要的 OAuth 接口及其使用说明: + +### 1. 授权请求 + +**端点:** `/oauth/authorize` + +**方法:** GET + +**参数:** + +- `response_type`: 必须为 "code" +- `client_id`: 您的客户端 ID +- `redirect_uri`: 授权后重定向的 URI +- `scope`: (可选)请求的权限范围 + +**示例:** + +``` +/oauth/authorize?response_type=code&client_id=your_client_id&redirect_uri=https://your-app.com/callback +``` + +### 2. 获取访问令牌 + +**端点:** `/api/oauth/access_token` + +**方法:** POST + +**参数:** + +- `code`: 从授权请求中获得的授权码 +- `redirect_uri`: 必须与授权请求中的 redirect_uri 相同 + +**响应:** + +```json +{ + "access_token": "at_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "expires_in": 604800, + "token_type": "bearer" +} +``` + +### 3. 获取用户信息 + +**端点:** `/api/oauth/user` + +**方法:** GET + +**请求头:** + +- `Authorization: Bearer {access_token}` + +**响应:** + +```json +{ + "id": "user_id", + "email": "user@example.com", + "username": "username", + "admin": false, + "avatar_url": "https://example.com/avatar.jpg", + "name": "User Name" +} +``` + +### 使用流程 + +1. 将用户重定向到授权页面(`/oauth/authorize`)。 +2. 用户授权后,您的应用将收到一个授权码。 +3. 使用授权码请求访问令牌(`/api/oauth/access_token`)。 +4. 使用访问令牌获取用户信息(`/api/oauth/user`)。 + +注意:确保在生产环境中使用 HTTPS 来保护所有的 OAuth 请求和响应。 + +## 贡献 + +欢迎贡献代码、报告问题或提出改进建议。 + +## 许可证 + +本项目采用 MIT 许可证。详情请见 [LICENSE](LICENSE) 文件。 diff --git a/docker-compose.yaml b/docker-compose.yaml index 1644747..0e30806 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -6,7 +6,7 @@ services: - "3000:3000" restart: always environment: - DATABASE_URL: postgresql://next:next@db:5432/next + DATABASE_URL: postgresql://discourse_connect:discourse_connect@db:5432/discourse_connect depends_on: db: condition: service_healthy @@ -19,9 +19,9 @@ services: db: image: postgres:15-alpine environment: - POSTGRES_DB: next - POSTGRES_USER: next - POSTGRES_PASSWORD: next + POSTGRES_DB: discourse_connect + POSTGRES_USER: discourse_connect + POSTGRES_PASSWORD: discourse_connect volumes: - next-db-data:/var/lib/postgresql/data restart: always diff --git a/src/actions/discourse-sso-url.ts b/src/actions/discourse-sso-url.ts index dfce5db..f407607 100644 --- a/src/actions/discourse-sso-url.ts +++ b/src/actions/discourse-sso-url.ts @@ -8,7 +8,8 @@ import WordArray from "crypto-js/lib-typedarrays"; import { AUTH_NONCE } from "@/lib/constants"; const appHost = process.env.NEXT_PUBLIC_HOST_URL; -const oauthSecret = process.env.DISCOUSE_SECRET || ""; +const discourseHost = process.env.DISCOURSE_HOST; +const oauthSecret = process.env.DISCOURSE_SECRET || ""; export async function getDiscourseSSOUrl(searchParams: string) { console.log(`searchParams: ${searchParams}`); @@ -19,5 +20,5 @@ export async function getDiscourseSSOUrl(searchParams: string) { const sig = hmacSHA256(sso, oauthSecret).toString(Hex); cookies().set(AUTH_NONCE, nonce, { maxAge: 60 * 10 }); - return `https://shuzimumin.com/session/sso_provider?sso=${sso}&sig=${sig}`; + return `${discourseHost}/session/sso_provider?sso=${sso}&sig=${sig}`; } diff --git a/src/app/api/auth/discourse/route.ts b/src/app/api/auth/discourse/route.ts index 19b530a..07eb483 100644 --- a/src/app/api/auth/discourse/route.ts +++ b/src/app/api/auth/discourse/route.ts @@ -6,7 +6,8 @@ import WordArray from "crypto-js/lib-typedarrays"; import { AUTH_NONCE } from "@/lib/constants"; const hostUrl = process.env.NEXT_PUBLIC_HOST_URL as string; -const clientSecret = process.env.DISCOUSE_SECRET as string; +const discourseHost = process.env.DISCOURSE_HOST as string; +const clientSecret = process.env.DISCOURSE_SECRET as string; export async function POST(_req: Request) { const nonce = WordArray.random(16).toString(); @@ -16,6 +17,6 @@ export async function POST(_req: Request) { cookies().set(AUTH_NONCE, nonce, { maxAge: 60 * 10 }); return Response.json({ - sso_url: `https://shuzimumin.com/session/sso_provider?sso=${sso}&sig=${sig}`, + sso_url: `${discourseHost}/session/sso_provider?sso=${sso}&sig=${sig}`, }); } diff --git a/src/lib/discourse/verify.ts b/src/lib/discourse/verify.ts index 07604a3..4ef1b35 100644 --- a/src/lib/discourse/verify.ts +++ b/src/lib/discourse/verify.ts @@ -8,7 +8,7 @@ import hmacSHA256 from "crypto-js/hmac-sha256"; import { AUTH_NONCE } from "@/lib/constants"; import { createUser, getUserById, updateUser } from "@/lib/dto/user"; -const DISCOUSE_SECRET = process.env.DISCOUSE_SECRET as string; +const DISCOUSE_SECRET = process.env.DISCOURSE_SECRET as string; export async function discourseCallbackVerify(sso: string, sig: string) { // 校验数据正确性