Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions savebook/.env.docker.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 6 additions & 1 deletion savebook/.env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ ACCESS_TOKEN_EXPIRY=access_token_secret
# ===================== Cloudinary =============================
CLOUDINARY_CLOUD_NAME=
CLOUDINARY_API_KEY=
CLOUDINARY_API_SECRET=
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
14 changes: 14 additions & 0 deletions savebook/app/(auth)/login/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,20 @@ const LoginForm = () => {
{isLoading ? "Signing in..." : "Sign in"}
</button>

<div className="relative my-6">
<div className="absolute inset-0 flex items-center"><span className="w-full border-t border-gray-600"></span></div>
<div className="relative flex justify-center text-xs uppercase"><span className="bg-gray-800 px-2 text-gray-400">Or continue with</span></div>
</div>

<button
type="button"
onClick={() => window.location.href = "/api/auth/github"}
className="w-full flex items-center justify-center gap-3 bg-gray-700 hover:bg-gray-600 text-white py-3 rounded-lg transition-colors"
>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.041-1.412-4.041-1.412-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
Continue with GitHub
</button>

<p className="text-center text-sm text-gray-300">
Don’t have an account?{" "}
<Link href="/register" className="text-blue-400">
Expand Down
61 changes: 61 additions & 0 deletions savebook/app/api/auth/github/callback/route.js
Original file line number Diff line number Diff line change
@@ -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`);
}
}
14 changes: 14 additions & 0 deletions savebook/app/api/auth/github/route.js
Original file line number Diff line number Diff line change
@@ -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()}`);
}