From 1a48600d4ae5367d2f0a67d84a1e2e8a3704bbb9 Mon Sep 17 00:00:00 2001 From: Lucas Ramage Date: Sun, 24 May 2026 19:55:42 -0500 Subject: [PATCH 1/2] fix(server): support browser extension auth via Bearer token - Add chrome-extension:// and moz-extension:// to CORS allowed origin patterns so the extension can make credentialed requests - Fix UserContext.getUserId() to handle JwtAuthenticationToken (set by oauth2ResourceServer for Bearer token requests) in addition to the existing UserAuthenticationToken (set by the cookie filter); the hard cast was causing a ClassCastException and 500 on all extension API calls --- .../java/dev/findfirst/FindFirstApplication.java | 5 +++-- .../security/userauth/context/UserContext.java | 13 +++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/dev/findfirst/FindFirstApplication.java b/server/src/main/java/dev/findfirst/FindFirstApplication.java index ba0d3c1e..5f1427c6 100644 --- a/server/src/main/java/dev/findfirst/FindFirstApplication.java +++ b/server/src/main/java/dev/findfirst/FindFirstApplication.java @@ -47,8 +47,9 @@ public FilterRegistrationBean simpleCorsFilter() { config.setAllowCredentials(true); // *** URL below needs to match the Vue client URL and port *** // Local host and 127.0.0.1 are the same - config.setAllowedOrigins(Arrays.asList("https://localhost:3000", "http://localhost:3000", - "https://findfirst.dev", "http://localhost", "http://127.0.0.1")); + config.setAllowedOriginPatterns(Arrays.asList("https://localhost:3000", "http://localhost:3000", + "https://findfirst.dev", "http://localhost", "http://127.0.0.1", + "chrome-extension://*", "moz-extension://*")); config.setAllowedMethods(Collections.singletonList("*")); config.setAllowedHeaders(Collections.singletonList("*")); source.registerCorsConfiguration("/**", config); diff --git a/server/src/main/java/dev/findfirst/security/userauth/context/UserContext.java b/server/src/main/java/dev/findfirst/security/userauth/context/UserContext.java index 84ca5c2c..0a8dd594 100644 --- a/server/src/main/java/dev/findfirst/security/userauth/context/UserContext.java +++ b/server/src/main/java/dev/findfirst/security/userauth/context/UserContext.java @@ -2,14 +2,23 @@ import dev.findfirst.security.jwt.UserAuthenticationToken; +import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.stereotype.Component; @Component public class UserContext { public int getUserId() { - return ((UserAuthenticationToken) SecurityContextHolder.getContext().getAuthentication()) - .getUserId(); + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth instanceof UserAuthenticationToken uat) { + return uat.getUserId(); + } + if (auth instanceof JwtAuthenticationToken jat) { + Number userId = jat.getToken().getClaim("userId"); + return userId.intValue(); + } + throw new IllegalStateException("Unexpected authentication type: " + auth.getClass()); } } From a4eec29718e3721a4159f2923ae884e49bc2db2b Mon Sep 17 00:00:00 2001 From: Lucas Ramage Date: Sun, 24 May 2026 20:21:26 -0500 Subject: [PATCH 2/2] fix(frontend): resolve failing SonarQube frontend test suite - Mock next/font/google in vitestSetup to fix Libre_Baskerville not-a-function error in app.test.tsx - Always render brand logo in Navbar (remove isMobile conditional that hid it on desktop); drop unused isMobile state and resize effect - Update default avatar test to assert SVG icon presence instead of stale img/src expectation --- frontend/__tests__/Navbar/Navbar.test.tsx | 13 +------- frontend/components/Navbar/Navbar.tsx | 39 +++++++---------------- frontend/vitestSetup.ts | 5 +++ 3 files changed, 18 insertions(+), 39 deletions(-) diff --git a/frontend/__tests__/Navbar/Navbar.test.tsx b/frontend/__tests__/Navbar/Navbar.test.tsx index 4fdfef39..dee9e6e0 100644 --- a/frontend/__tests__/Navbar/Navbar.test.tsx +++ b/frontend/__tests__/Navbar/Navbar.test.tsx @@ -172,20 +172,9 @@ describe("GlobalNavbar", () => { AuthStatus.Authorized, ); - const mock = vi.fn().mockImplementation(authService.getUser); - mock.mockImplementationOnce(() => { - return { - id: 1, - username: "test", - refreshToken: "test", - profileImage: "", - }; - }); - render(); - const avatar = screen.getByAltText("Profile") as HTMLImageElement; + const avatar = document.querySelector(".bi-file-person-fill"); expect(avatar).toBeInTheDocument(); - expect(avatar.src).toContain("/img_avatar.png"); }); it("renders user avatar when authorized and profileImage exists", () => { diff --git a/frontend/components/Navbar/Navbar.tsx b/frontend/components/Navbar/Navbar.tsx index 38a2a3e4..d99ee4d8 100644 --- a/frontend/components/Navbar/Navbar.tsx +++ b/frontend/components/Navbar/Navbar.tsx @@ -9,24 +9,11 @@ import Export from "./Export"; import Image from "next/image"; import Searchbar from "./Searchbar"; import navbarView from "styles/navbar.module.scss"; -import { useEffect, useState } from "react"; import AccountModal from "./AccountModal"; const GlobalNavbar: React.FC = () => { const userAuth = useAuth(); const user = authService.getUser(); - let [isMobile, setIsMobile] = useState(null); - - useEffect(() => { - setIsMobile(window.innerWidth <= 767.98); - - const checkWindowWidth = () => { - setIsMobile(window.innerWidth <= 767.98); - }; - - window.addEventListener("resize", checkWindowWidth); - }, []); - const router = useRouter(); function authButton() { if (userAuth == AuthStatus.Unauthorized || userAuth === undefined) { @@ -75,20 +62,18 @@ const GlobalNavbar: React.FC = () => { className="bg-body-tertiary" > - {isMobile ? ( - router.push("/")} - className={` ${navbarView.navBrand}`} - > - FindFirst Logo - - ) : null} + router.push("/")} + className={` ${navbarView.navBrand}`} + > + FindFirst Logo + {/* Search bar stays visible always */} {userAuth === AuthStatus.Authorized ? : null} diff --git a/frontend/vitestSetup.ts b/frontend/vitestSetup.ts index 6c846768..f46d431e 100644 --- a/frontend/vitestSetup.ts +++ b/frontend/vitestSetup.ts @@ -6,6 +6,11 @@ import "bootstrap/dist/css/bootstrap.min.css"; import "bootstrap-icons/font/bootstrap-icons.min.css"; loadEnvConfig(process.cwd()); +vi.mock("next/font/google", () => ({ + Libre_Baskerville: vi.fn(() => ({ className: "mock-font" })), + Inter: vi.fn(() => ({ className: "mock-font" })), +})); + vi.mock("next/navigation", () => { const actual = vi.importActual("next/navigation"); return {