Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed authentication and added log out #215

Merged
merged 2 commits into from
Feb 12, 2025
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
41 changes: 0 additions & 41 deletions src/client/auth.tsx

This file was deleted.

6 changes: 3 additions & 3 deletions src/client/components/navigation/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const Header: React.FC = () => {
const location = useLocation();
const isHomePage = location.pathname === "/";
const [menuOpen, setMenuOpen] = useState(false);
const { isAuthenticated } = useAuth();
const { isAuthenticated, logout } = useAuth();
const menuRef = useRef<HTMLDivElement>(null);
const hamburgerRef = useRef<HTMLButtonElement>(null);

Expand Down Expand Up @@ -85,14 +85,14 @@ const Header: React.FC = () => {
<div className="ml-auto flex items-center gap-4">
<DesktopMenu navItems={navItems} isHomePage={isHomePage} />
<SearchBar />
<UserButton isLoggedIn={isAuthenticated} />
<UserButton isLoggedIn={isAuthenticated} onLogout={logout} />
</div>
</div>

{/* Mobile Navigation */}
<div className="flex items-center gap-4 md:hidden">
<SearchBar className="w-32 focus:w-64" />
<UserButton isLoggedIn={isAuthenticated} />
<UserButton isLoggedIn={isAuthenticated} onLogout={logout} />
<button ref={hamburgerRef} className="focus:outline-none">
<Hamburger toggled={menuOpen} toggle={setMenuOpen} color={isHomePage ? "#fff" : "#000"} size={22} />
</button>
Expand Down
8 changes: 6 additions & 2 deletions src/client/components/navigation/UserButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import React from "react";

interface UserButtonProps {
isLoggedIn: boolean;
onLogout: () => void;
}

export const UserButton: React.FC<UserButtonProps> = ({ isLoggedIn }) =>
export const UserButton: React.FC<UserButtonProps> = ({ isLoggedIn, onLogout }) =>
isLoggedIn ? (
<Link to="/profile">
<button className="transform rounded-full text-gray-600 duration-300 hover:scale-105 hover:text-[#000000]">
Expand All @@ -14,7 +15,10 @@ export const UserButton: React.FC<UserButtonProps> = ({ isLoggedIn }) =>
</Link>
) : (
<Link to="/login">
<button className="inline-block transform rounded-full bg-[#0f6cb6] px-5 py-2 text-white duration-300 hover:scale-105 hover:bg-[#8dc63f] hover:text-[#000000]">
<button
className="inline-block transform rounded-full bg-[#0f6cb6] px-5 py-2 text-white duration-300 hover:scale-105 hover:bg-[#8dc63f] hover:text-[#000000]"
onClick={onLogout}
>
Log In
</button>
</Link>
Expand Down
2 changes: 1 addition & 1 deletion src/client/router.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { dehydrate, hydrate, QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
import { SuperJSON } from "superjson";
import { AuthProvider } from "./auth";
import { AuthProvider } from "./AuthContext";
import { DefaultCatchBoundary } from "./components/DefaultCatchBoundary";
import { NotFound } from "./components/NotFound";
import { routeTree } from "./routeTree.gen";
Expand Down
26 changes: 24 additions & 2 deletions src/client/routes/profile.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import { createFileRoute } from "@tanstack/react-router";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useEffect, useState } from "react";
import { useAuth } from "../AuthContext";
import { Button } from "../components/ui/button";

export const Route = createFileRoute("/profile")({
component: () => {
const [profile, setProfile] = useState<{ username: string } | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const { logout } = useAuth();
const navigate = useNavigate();

console.log("Profile component rendered");
useEffect(() => {
if (!isLoading) return;
const fetchProfile = async () => {
try {
const response = await fetch("/api/profile", { credentials: "include" });

if (!response.ok) {
throw new Error("Failed to fetch profile");
}

const result = (await response.json()) as { data: { username: string } };
console.log("Profile API Response:", result);
setProfile(result.data);
Expand All @@ -29,6 +34,20 @@ export const Route = createFileRoute("/profile")({
fetchProfile();
}, []);

const handleLogout = async () => {
try {
const response = await fetch("/api/auth/logout", { method: "POST", credentials: "include" });
if (response.ok) {
logout();
navigate({ to: "/" });
} else {
throw new Error("Logout failed");
}
} catch (error) {
console.error("Logout error:", error);
}
};

if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;

Expand All @@ -44,6 +63,9 @@ export const Route = createFileRoute("/profile")({
<span className="text-4xl font-bold">{profile?.username?.charAt(0).toUpperCase()}</span>
</div>
<h2 className="text-xl font-semibold">Welcome, {profile?.username}!</h2>
<Button variant="destructive" className="mt-4" onClick={handleLogout}>
Log Out
</Button>
</div>
</div>
</div>
Expand Down
28 changes: 25 additions & 3 deletions src/server/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@ authRoutes.post("/auth/signup", async (c) => {
const formUsername = formData["username"];
const formPassword = formData["password"];

//TODO VALIDATE USERNAME
//validate username
if (!formUsername || typeof formUsername !== "string") {
return new Response("Invalid username", {
status: 400,
});
}

//TODO VALIDATE PASSWORD
if (!formPassword || typeof formPassword !== "string") {
return new Response("Invalid password", {
status: 400,
Expand Down Expand Up @@ -49,7 +48,7 @@ authRoutes.post("/auth/signup", async (c) => {
} catch (error) {
console.log(error);
// db error, email taken, etc
return new Response("Username already used", {
return new Response("Username already taken", {
status: 400,
});
}
Expand Down Expand Up @@ -95,6 +94,29 @@ authRoutes.post("/auth/login", async (c) => {
}
});

authRoutes.post("/auth/logout", async (c) => {
const sessionId = c.req.header("Cookie")?.match(/sessionId=([^;]*)/)?.[1];

if (!sessionId) {
return new Response("No active session found", { status: 401 });
}

try {
// delete the session id row from the table
await db.delete(Schema.sessions).where(eq(Schema.sessions.id, sessionId));

return new Response("Successfully logged out", {
status: 200,
headers: {
"Set-Cookie": "sessionId=; Path=/; HttpOnly; Secure; Max-Age=0; SameSite=Strict",
},
});
} catch (error) {
console.log(error);
return new Response("Error logging out", { status: 500 });
}
});

async function createSession(sessionID: string, userID: string) {
try {
await db.insert(Schema.sessions).values({
Expand Down
10 changes: 7 additions & 3 deletions src/server/api/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ const profileRoutes = new Hono();
profileRoutes.get("/profile", async (c) => {
try {
const cookie = c.req.header("Cookie") || "";
const sessionID = cookie.startsWith("sessionId=") ? cookie.slice("sessionId=".length) : null;

if (!sessionID) return c.json({ error: { code: 400, message: "Missing or invalid session ID" } }, 400);
console.log(cookie);
const sessionIDMatch = cookie.match(/sessionId=([^;]*)/);
if (!sessionIDMatch) {
return c.json({ error: { code: 400, message: "Missing or invalid session ID" } }, 400);
}
const sessionID = sessionIDMatch[1];
console.log(cookie, sessionID);

const result = await db
.select({ username: Schema.users.username })
Expand Down