From 75f1ce7b8629713bdf0d9f0397ec96bbf47abbed Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 23 Apr 2025 02:28:58 +0800 Subject: [PATCH 1/3] feat: enabled sso to bypass org sso --- backend/src/@types/fastify.d.ts | 1 + backend/src/server/routes/v1/sso-router.ts | 50 +++++++++++++++---- .../components/InitialStep/InitialStep.tsx | 38 +++++++------- .../components/PasswordStep/PasswordStep.tsx | 15 ++++-- .../pages/auth/LoginSsoPage/LoginSsoPage.tsx | 1 + .../src/pages/auth/LoginSsoPage/route.tsx | 3 +- 6 files changed, 77 insertions(+), 31 deletions(-) diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index 441d3ce4c..b9c5851db 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -115,6 +115,7 @@ declare module "@fastify/request-context" { declare module "fastify" { interface Session { callbackPort: string; + isAdminLogin: boolean; } interface FastifyRequest { diff --git a/backend/src/server/routes/v1/sso-router.ts b/backend/src/server/routes/v1/sso-router.ts index a222ab172..456f11093 100644 --- a/backend/src/server/routes/v1/sso-router.ts +++ b/backend/src/server/routes/v1/sso-router.ts @@ -166,17 +166,24 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { method: "GET", schema: { querystring: z.object({ - callback_port: z.string().optional() + callback_port: z.string().optional(), + is_admin_login: z + .string() + .optional() + .transform((val) => val === "true") }) }, preValidation: [ async (req, res) => { - const { callback_port: callbackPort } = req.query; + const { callback_port: callbackPort, is_admin_login: isAdminLogin } = req.query; // ensure fresh session state per login attempt await req.session.regenerate(); if (callbackPort) { req.session.set("callbackPort", callbackPort); } + if (isAdminLogin) { + req.session.set("isAdminLogin", isAdminLogin); + } return ( passport.authenticate("google", { scope: ["profile", "email"], @@ -200,10 +207,13 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { // this is due to zod type difference }) as never, handler: async (req, res) => { + const isAdminLogin = req.session.get("isAdminLogin"); await req.session.destroy(); if (req.passportUser.isUserCompleted) { return res.redirect( - `${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}` + `${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent( + req.passportUser.providerAuthToken + )}&isAdminLogin=${isAdminLogin}` ); } return res.redirect( @@ -217,18 +227,26 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { method: "GET", schema: { querystring: z.object({ - callback_port: z.string().optional() + callback_port: z.string().optional(), + is_admin_login: z + .string() + .optional() + .transform((val) => val === "true") }) }, preValidation: [ async (req, res) => { - const { callback_port: callbackPort } = req.query; + const { callback_port: callbackPort, is_admin_login: isAdminLogin } = req.query; // ensure fresh session state per login attempt await req.session.regenerate(); if (callbackPort) { req.session.set("callbackPort", callbackPort); } + if (isAdminLogin) { + req.session.set("isAdminLogin", isAdminLogin); + } + return ( passport.authenticate("github", { session: false, @@ -289,10 +307,13 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { // this is due to zod type difference }) as any, handler: async (req, res) => { + const isAdminLogin = req.session.get("isAdminLogin"); await req.session.destroy(); if (req.passportUser.isUserCompleted) { return res.redirect( - `${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}` + `${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent( + req.passportUser.providerAuthToken + )}&isAdminLogin=${isAdminLogin}` ); } return res.redirect( @@ -306,18 +327,26 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { method: "GET", schema: { querystring: z.object({ - callback_port: z.string().optional() + callback_port: z.string().optional(), + is_admin_login: z + .string() + .optional() + .transform((val) => val === "true") }) }, preValidation: [ async (req, res) => { - const { callback_port: callbackPort } = req.query; + const { callback_port: callbackPort, is_admin_login: isAdminLogin } = req.query; // ensure fresh session state per login attempt await req.session.regenerate(); if (callbackPort) { req.session.set("callbackPort", callbackPort); } + if (isAdminLogin) { + req.session.set("isAdminLogin", isAdminLogin); + } + return ( passport.authenticate("gitlab", { session: false, @@ -342,10 +371,13 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any }) as any, handler: async (req, res) => { + const isAdminLogin = req.session.get("isAdminLogin"); await req.session.destroy(); if (req.passportUser.isUserCompleted) { return res.redirect( - `${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}` + `${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent( + req.passportUser.providerAuthToken + )}&isAdminLogin=${isAdminLogin}` ); } return res.redirect( diff --git a/frontend/src/pages/auth/LoginPage/components/InitialStep/InitialStep.tsx b/frontend/src/pages/auth/LoginPage/components/InitialStep/InitialStep.tsx index ac32816e0..c42ca08ba 100644 --- a/frontend/src/pages/auth/LoginPage/components/InitialStep/InitialStep.tsx +++ b/frontend/src/pages/auth/LoginPage/components/InitialStep/InitialStep.tsx @@ -269,12 +269,14 @@ export const InitialStep = ({ variant="outline_bg" onClick={() => { const callbackPort = queryParams.get("callback_port"); + const queryString = [ + callbackPort && `callback_port=${callbackPort}`, + isAdmin && "is_admin_login=true" + ] + .filter(Boolean) + .join("&"); - window.open( - `/api/v1/sso/redirect/google${ - callbackPort ? `?callback_port=${callbackPort}` : "" - }` - ); + window.open(`/api/v1/sso/redirect/google${queryString ? `?${queryString}` : ""}`); window.close(); }} className="h-10 w-full bg-mineshaft-600" @@ -291,13 +293,14 @@ export const InitialStep = ({ variant="outline_bg" onClick={() => { const callbackPort = queryParams.get("callback_port"); + const queryString = [ + callbackPort && `callback_port=${callbackPort}`, + isAdmin && "is_admin_login=true" + ] + .filter(Boolean) + .join("&"); - window.open( - `/api/v1/sso/redirect/github${ - callbackPort ? `?callback_port=${callbackPort}` : "" - }` - ); - + window.open(`/api/v1/sso/redirect/github${queryString ? `?${queryString}` : ""}`); window.close(); }} className="h-10 w-full bg-mineshaft-600" @@ -314,13 +317,14 @@ export const InitialStep = ({ variant="outline_bg" onClick={() => { const callbackPort = queryParams.get("callback_port"); + const queryString = [ + callbackPort && `callback_port=${callbackPort}`, + isAdmin && "is_admin_login=true" + ] + .filter(Boolean) + .join("&"); - window.open( - `/api/v1/sso/redirect/gitlab${ - callbackPort ? `?callback_port=${callbackPort}` : "" - }` - ); - + window.open(`/api/v1/sso/redirect/gitlab${queryString ? `?${queryString}` : ""}`); window.close(); }} className="h-10 w-full bg-mineshaft-600" diff --git a/frontend/src/pages/auth/LoginPage/components/PasswordStep/PasswordStep.tsx b/frontend/src/pages/auth/LoginPage/components/PasswordStep/PasswordStep.tsx index 20b47599f..529509b49 100644 --- a/frontend/src/pages/auth/LoginPage/components/PasswordStep/PasswordStep.tsx +++ b/frontend/src/pages/auth/LoginPage/components/PasswordStep/PasswordStep.tsx @@ -27,9 +27,16 @@ type Props = { email: string; password: string; setPassword: (password: string) => void; + isAdminLogin?: boolean; }; -export const PasswordStep = ({ providerAuthToken, email, password, setPassword }: Props) => { +export const PasswordStep = ({ + providerAuthToken, + email, + password, + setPassword, + isAdminLogin +}: Props) => { const [isLoading, setIsLoading] = useState(false); const { t } = useTranslation(); const navigate = useNavigate(); @@ -114,7 +121,7 @@ export const PasswordStep = ({ providerAuthToken, email, password, setPassword } // case: user has orgs, so we navigate the user to select an org if (userOrgs.length > 0) { - navigateToSelectOrganization(callbackPort); + navigateToSelectOrganization(callbackPort, isAdminLogin); } // case: no orgs found, so we navigate the user to create an org else { @@ -216,7 +223,7 @@ export const PasswordStep = ({ providerAuthToken, email, password, setPassword } // case: user has orgs, so we navigate the user to select an org if (userOrgs.length > 0) { - navigateToSelectOrganization(callbackPort); + navigateToSelectOrganization(callbackPort, isAdminLogin); } // case: no orgs found, so we navigate the user to create an org else { @@ -249,7 +256,7 @@ export const PasswordStep = ({ providerAuthToken, email, password, setPassword } const userOrgs = await fetchOrganizations(); if (userOrgs.length > 0) { - navigateToSelectOrganization(); + navigateToSelectOrganization(undefined, isAdminLogin); } else { await navigateUserToOrg(navigate); } diff --git a/frontend/src/pages/auth/LoginSsoPage/LoginSsoPage.tsx b/frontend/src/pages/auth/LoginSsoPage/LoginSsoPage.tsx index 5af256d1a..8379fc725 100644 --- a/frontend/src/pages/auth/LoginSsoPage/LoginSsoPage.tsx +++ b/frontend/src/pages/auth/LoginSsoPage/LoginSsoPage.tsx @@ -34,6 +34,7 @@ export const LoginSsoPage = () => { email={username} password={password} setPassword={setPassword} + isAdminLogin={search.isAdminLogin} /> ); default: diff --git a/frontend/src/pages/auth/LoginSsoPage/route.tsx b/frontend/src/pages/auth/LoginSsoPage/route.tsx index 88cc0c539..f66925b93 100644 --- a/frontend/src/pages/auth/LoginSsoPage/route.tsx +++ b/frontend/src/pages/auth/LoginSsoPage/route.tsx @@ -5,7 +5,8 @@ import { z } from "zod"; import { LoginSsoPage } from "./LoginSsoPage"; const LoginSSOQueryParamsSchema = z.object({ - token: z.string() + token: z.string(), + isAdminLogin: z.boolean().optional().catch(false) }); export const Route = createFileRoute("/_restrict-login-signup/login/sso")({ From 1590b528bf0f6162a918cf00e59c4786bf08cffd Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 23 Apr 2025 03:07:50 +0800 Subject: [PATCH 2/3] misc: used url search params --- .../components/InitialStep/InitialStep.tsx | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/frontend/src/pages/auth/LoginPage/components/InitialStep/InitialStep.tsx b/frontend/src/pages/auth/LoginPage/components/InitialStep/InitialStep.tsx index c42ca08ba..820cda786 100644 --- a/frontend/src/pages/auth/LoginPage/components/InitialStep/InitialStep.tsx +++ b/frontend/src/pages/auth/LoginPage/components/InitialStep/InitialStep.tsx @@ -269,12 +269,17 @@ export const InitialStep = ({ variant="outline_bg" onClick={() => { const callbackPort = queryParams.get("callback_port"); - const queryString = [ - callbackPort && `callback_port=${callbackPort}`, - isAdmin && "is_admin_login=true" - ] - .filter(Boolean) - .join("&"); + const searchParams = new URLSearchParams(); + + if (callbackPort) { + searchParams.append("callback_port", callbackPort); + } + + if (isAdmin) { + searchParams.append("is_admin_login", "true"); + } + + const queryString = searchParams.toString(); window.open(`/api/v1/sso/redirect/google${queryString ? `?${queryString}` : ""}`); window.close(); @@ -293,12 +298,17 @@ export const InitialStep = ({ variant="outline_bg" onClick={() => { const callbackPort = queryParams.get("callback_port"); - const queryString = [ - callbackPort && `callback_port=${callbackPort}`, - isAdmin && "is_admin_login=true" - ] - .filter(Boolean) - .join("&"); + const searchParams = new URLSearchParams(); + + if (callbackPort) { + searchParams.append("callback_port", callbackPort); + } + + if (isAdmin) { + searchParams.append("is_admin_login", "true"); + } + + const queryString = searchParams.toString(); window.open(`/api/v1/sso/redirect/github${queryString ? `?${queryString}` : ""}`); window.close(); @@ -317,12 +327,17 @@ export const InitialStep = ({ variant="outline_bg" onClick={() => { const callbackPort = queryParams.get("callback_port"); - const queryString = [ - callbackPort && `callback_port=${callbackPort}`, - isAdmin && "is_admin_login=true" - ] - .filter(Boolean) - .join("&"); + const searchParams = new URLSearchParams(); + + if (callbackPort) { + searchParams.append("callback_port", callbackPort); + } + + if (isAdmin) { + searchParams.append("is_admin_login", "true"); + } + + const queryString = searchParams.toString(); window.open(`/api/v1/sso/redirect/gitlab${queryString ? `?${queryString}` : ""}`); window.close(); From d5888f9de713c243a843dcce49bf01e670e81327 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 23 Apr 2025 03:27:22 +0800 Subject: [PATCH 3/3] misc: only append isAdminLogin query param when relevant --- backend/src/server/routes/v1/sso-router.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/src/server/routes/v1/sso-router.ts b/backend/src/server/routes/v1/sso-router.ts index 456f11093..d056b9bcb 100644 --- a/backend/src/server/routes/v1/sso-router.ts +++ b/backend/src/server/routes/v1/sso-router.ts @@ -211,9 +211,9 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { await req.session.destroy(); if (req.passportUser.isUserCompleted) { return res.redirect( - `${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent( - req.passportUser.providerAuthToken - )}&isAdminLogin=${isAdminLogin}` + `${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}${ + isAdminLogin ? `&isAdminLogin=${isAdminLogin}` : "" + }` ); } return res.redirect( @@ -311,9 +311,9 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { await req.session.destroy(); if (req.passportUser.isUserCompleted) { return res.redirect( - `${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent( - req.passportUser.providerAuthToken - )}&isAdminLogin=${isAdminLogin}` + `${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}${ + isAdminLogin ? `&isAdminLogin=${isAdminLogin}` : "" + }` ); } return res.redirect( @@ -375,9 +375,9 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { await req.session.destroy(); if (req.passportUser.isUserCompleted) { return res.redirect( - `${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent( - req.passportUser.providerAuthToken - )}&isAdminLogin=${isAdminLogin}` + `${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}${ + isAdminLogin ? `&isAdminLogin=${isAdminLogin}` : "" + }` ); } return res.redirect(