diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index 5b7105f13..34d816b9b 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -118,6 +118,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 5c04f5d1b..09d7cb5bd 100644 --- a/backend/src/server/routes/v1/sso-router.ts +++ b/backend/src/server/routes/v1/sso-router.ts @@ -168,17 +168,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"], @@ -202,10 +209,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=${isAdminLogin}` : "" + }` ); } return res.redirect( @@ -219,18 +229,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, @@ -291,6 +309,7 @@ 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.externalProviderAccessToken) { @@ -305,7 +324,9 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { 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=${isAdminLogin}` : "" + }` ); } return res.redirect( @@ -319,18 +340,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, @@ -355,10 +384,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=${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..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,19 @@ export const InitialStep = ({ variant="outline_bg" onClick={() => { const callbackPort = queryParams.get("callback_port"); + const searchParams = new URLSearchParams(); - window.open( - `/api/v1/sso/redirect/google${ - callbackPort ? `?callback_port=${callbackPort}` : "" - }` - ); + 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(); }} className="h-10 w-full bg-mineshaft-600" @@ -291,13 +298,19 @@ export const InitialStep = ({ variant="outline_bg" onClick={() => { const callbackPort = queryParams.get("callback_port"); + const searchParams = new URLSearchParams(); - window.open( - `/api/v1/sso/redirect/github${ - callbackPort ? `?callback_port=${callbackPort}` : "" - }` - ); + 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(); }} className="h-10 w-full bg-mineshaft-600" @@ -314,13 +327,19 @@ export const InitialStep = ({ variant="outline_bg" onClick={() => { const callbackPort = queryParams.get("callback_port"); + const searchParams = new URLSearchParams(); - window.open( - `/api/v1/sso/redirect/gitlab${ - callbackPort ? `?callback_port=${callbackPort}` : "" - }` - ); + 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(); }} 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")({