diff --git a/savebook/.env.docker.example b/savebook/.env.docker.example index 2e1cf60..351ddac 100644 --- a/savebook/.env.docker.example +++ b/savebook/.env.docker.example @@ -25,3 +25,8 @@ CLOUDINARY_API_SECRET= # ===================== Node Environment ================================ NODE_ENV=production + +# ===================== Github ============================= +GITHUB_CLIENT_ID=your_github_client_id +GITHUB_CLIENT_SECRET=your_github_client_secret +NEXT_PUBLIC_BASE_URL=http://localhost:3000 \ No newline at end of file diff --git a/savebook/.env.local.example b/savebook/.env.local.example index 07f613a..fba1dbb 100644 --- a/savebook/.env.local.example +++ b/savebook/.env.local.example @@ -8,4 +8,9 @@ ACCESS_TOKEN_EXPIRY=access_token_secret # ===================== Cloudinary ============================= CLOUDINARY_CLOUD_NAME= CLOUDINARY_API_KEY= -CLOUDINARY_API_SECRET= \ No newline at end of file +CLOUDINARY_API_SECRET= + +# ===================== Github ============================= +GITHUB_CLIENT_ID=your_github_client_id +GITHUB_CLIENT_SECRET=your_github_client_secret +NEXT_PUBLIC_BASE_URL=http://localhost:3000 \ No newline at end of file diff --git a/savebook/app/(auth)/login/page.js b/savebook/app/(auth)/login/page.js index d495c24..852c736 100644 --- a/savebook/app/(auth)/login/page.js +++ b/savebook/app/(auth)/login/page.js @@ -168,6 +168,20 @@ const LoginForm = () => { {isLoading ? "Signing in..." : "Sign in"} +
Don’t have an account?{" "} diff --git a/savebook/app/api/auth/github/callback/route.js b/savebook/app/api/auth/github/callback/route.js new file mode 100644 index 0000000..59a0192 --- /dev/null +++ b/savebook/app/api/auth/github/callback/route.js @@ -0,0 +1,61 @@ +import { NextResponse } from "next/server"; +import dbConnect from "@/lib/db/mongodb"; +import User from "@/lib/models/User"; +import { generateAuthToken } from "@/lib/utils/jwtAuth"; + +export async function GET(request) { + const { searchParams } = new URL(request.url); + const code = searchParams.get("code"); + + if (!code) { + return NextResponse.redirect(`${process.env.NEXT_PUBLIC_BASE_URL}/login?error=NoCode`); + } + + try { + const tokenResponse = await fetch("https://github.com/login/oauth/access_token", { + method: "POST", + headers: { "Content-Type": "application/json", Accept: "application/json" }, + body: JSON.stringify({ + client_id: process.env.GITHUB_CLIENT_ID, + client_secret: process.env.GITHUB_CLIENT_SECRET, + code, + }), + }); + const { access_token } = await tokenResponse.json(); + + const userResponse = await fetch("https://api.github.com/user", { + headers: { Authorization: `Bearer ${access_token}` }, + }); + const githubUser = await userResponse.json(); + + await dbConnect(); + + let user = await User.findOne({ username: githubUser.login.toLowerCase() }); + + if (!user) { + user = await User.create({ + username: githubUser.login.toLowerCase(), + password: Math.random().toString(36).slice(-10), + profileImage: githubUser.avatar_url, + firstName: githubUser.name || githubUser.login, + }); + } + + const { authToken } = await generateAuthToken(user._id.toString()); + + const response = NextResponse.redirect(`${process.env.NEXT_PUBLIC_BASE_URL}/notes`); + + response.cookies.set("authToken", authToken, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + maxAge: 60 * 60 * 24 * 7, + path: "/", + }); + + return response; + } catch (error) { + console.error("GitHub OAuth Error:", error); + return NextResponse.redirect(`${process.env.NEXT_PUBLIC_BASE_URL}/login?error=OAuthFailed`); + } +} \ No newline at end of file diff --git a/savebook/app/api/auth/github/route.js b/savebook/app/api/auth/github/route.js new file mode 100644 index 0000000..99420c3 --- /dev/null +++ b/savebook/app/api/auth/github/route.js @@ -0,0 +1,14 @@ +import { NextResponse } from "next/server"; + +export async function GET() { + const rootUrl = "https://github.com/login/oauth/authorize"; + const options = { + client_id: process.env.GITHUB_CLIENT_ID, + redirect_uri: `${process.env.NEXT_PUBLIC_BASE_URL}/api/auth/github/callback`, + scope: "read:user user:email", + state: "github_oauth_state", + }; + + const qs = new URLSearchParams(options); + return NextResponse.redirect(`${rootUrl}?${qs.toString()}`); +} \ No newline at end of file