diff --git a/apps/web/app/api/auth/saml/well-known/cert/route.ts b/apps/web/app/api/auth/saml/well-known/cert/route.ts new file mode 100644 index 0000000000..61b53f9db8 --- /dev/null +++ b/apps/web/app/api/auth/saml/well-known/cert/route.ts @@ -0,0 +1,3 @@ +import { GET } from "@/modules/ee/auth/saml/api/well-known/cert/route"; + +export { GET }; diff --git a/apps/web/app/api/auth/saml/well-known/sp-metadata/route.ts b/apps/web/app/api/auth/saml/well-known/sp-metadata/route.ts new file mode 100644 index 0000000000..50e487e778 --- /dev/null +++ b/apps/web/app/api/auth/saml/well-known/sp-metadata/route.ts @@ -0,0 +1,3 @@ +import { GET } from "@/modules/ee/auth/saml/api/well-known/sp-metadata/route"; + +export { GET }; diff --git a/apps/web/locales/de-DE.json b/apps/web/locales/de-DE.json index 41d3592e97..3267d28715 100644 --- a/apps/web/locales/de-DE.json +++ b/apps/web/locales/de-DE.json @@ -597,7 +597,6 @@ "contact_deleted_successfully": "Kontakt erfolgreich gelöscht", "contact_not_found": "Kein solcher Kontakt gefunden", "contacts_table_refresh": "Kontakte aktualisieren", - "contacts_table_refresh_error": "Beim Aktualisieren der Kontakte ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.", "contacts_table_refresh_success": "Kontakte erfolgreich aktualisiert", "first_name": "Vorname", "last_name": "Nachname", diff --git a/apps/web/locales/en-US.json b/apps/web/locales/en-US.json index 3e1e24f1eb..d6f15dd9ba 100644 --- a/apps/web/locales/en-US.json +++ b/apps/web/locales/en-US.json @@ -597,7 +597,6 @@ "contact_deleted_successfully": "Contact deleted successfully", "contact_not_found": "No such contact found", "contacts_table_refresh": "Refresh contacts", - "contacts_table_refresh_error": "Something went wrong while refreshing contacts, please try again", "contacts_table_refresh_success": "Contacts refreshed successfully", "first_name": "First Name", "last_name": "Last Name", diff --git a/apps/web/locales/fr-FR.json b/apps/web/locales/fr-FR.json index 50451c2888..d519e9d39c 100644 --- a/apps/web/locales/fr-FR.json +++ b/apps/web/locales/fr-FR.json @@ -597,7 +597,6 @@ "contact_deleted_successfully": "Contact supprimé avec succès", "contact_not_found": "Aucun contact trouvé", "contacts_table_refresh": "Rafraîchir les contacts", - "contacts_table_refresh_error": "Une erreur s'est produite lors de la mise à jour des contacts. Veuillez réessayer.", "contacts_table_refresh_success": "Contacts rafraîchis avec succès", "first_name": "Prénom", "last_name": "Nom de famille", diff --git a/apps/web/locales/pt-BR.json b/apps/web/locales/pt-BR.json index a7bfc84e26..f873a8f53a 100644 --- a/apps/web/locales/pt-BR.json +++ b/apps/web/locales/pt-BR.json @@ -597,7 +597,6 @@ "contact_deleted_successfully": "Contato excluído com sucesso", "contact_not_found": "Nenhum contato encontrado", "contacts_table_refresh": "Atualizar contatos", - "contacts_table_refresh_error": "Ocorreu um erro ao atualizar os contatos. Por favor, tente novamente.", "contacts_table_refresh_success": "Contatos atualizados com sucesso", "first_name": "Primeiro Nome", "last_name": "Sobrenome", diff --git a/apps/web/locales/pt-PT.json b/apps/web/locales/pt-PT.json index b70a8c32ad..88a9f7d082 100644 --- a/apps/web/locales/pt-PT.json +++ b/apps/web/locales/pt-PT.json @@ -597,7 +597,6 @@ "contact_deleted_successfully": "Contacto eliminado com sucesso", "contact_not_found": "Nenhum contacto encontrado", "contacts_table_refresh": "Atualizar contactos", - "contacts_table_refresh_error": "Algo correu mal ao atualizar os contactos, por favor, tente novamente", "contacts_table_refresh_success": "Contactos atualizados com sucesso", "first_name": "Primeiro Nome", "last_name": "Apelido", diff --git a/apps/web/locales/zh-Hant-TW.json b/apps/web/locales/zh-Hant-TW.json index 0881189a5a..588475ee49 100644 --- a/apps/web/locales/zh-Hant-TW.json +++ b/apps/web/locales/zh-Hant-TW.json @@ -597,7 +597,6 @@ "contact_deleted_successfully": "聯絡人已成功刪除", "contact_not_found": "找不到此聯絡人", "contacts_table_refresh": "重新整理聯絡人", - "contacts_table_refresh_error": "重新整理聯絡人時發生錯誤,請再試一次", "contacts_table_refresh_success": "聯絡人已成功重新整理", "first_name": "名字", "last_name": "姓氏", diff --git a/apps/web/modules/auth/lib/authOptions.ts b/apps/web/modules/auth/lib/authOptions.ts index 16ed34fedd..f49faa97db 100644 --- a/apps/web/modules/auth/lib/authOptions.ts +++ b/apps/web/modules/auth/lib/authOptions.ts @@ -187,6 +187,7 @@ export const authOptions: NextAuthOptions = { }, callbacks: { async jwt({ token }) { + console.log("jwt: token", token); const existingUser = await getUserByEmail(token?.email!); if (!existingUser) { @@ -200,6 +201,8 @@ export const authOptions: NextAuthOptions = { }; }, async session({ session, token }) { + console.log("session: session", session); + console.log("session: token", token); // @ts-expect-error session.user.id = token?.id; // @ts-expect-error @@ -223,6 +226,9 @@ export const authOptions: NextAuthOptions = { return true; } if (ENTERPRISE_LICENSE_KEY) { + console.log("signIn: user", user); + console.log("signIn: account", account); + console.log("signIn: callbackUrl", callbackUrl); const result = await handleSsoCallback({ user, account, callbackUrl }); if (result) { diff --git a/apps/web/modules/ee/auth/saml/api/authorize/route.ts b/apps/web/modules/ee/auth/saml/api/authorize/route.ts index 9492508261..1e46f259ed 100644 --- a/apps/web/modules/ee/auth/saml/api/authorize/route.ts +++ b/apps/web/modules/ee/auth/saml/api/authorize/route.ts @@ -20,10 +20,13 @@ export const GET = async (req: NextRequest) => { try { const { redirect_url } = await oauthController.authorize(searchParams as OAuthReq); + console.log("saml/authorize", redirect_url); + if (!redirect_url) { return responses.internalServerErrorResponse("Failed to get redirect URL"); } + console.log("saml/authorize: reached to return redirect url", redirect_url); return NextResponse.redirect(redirect_url); } catch (err: unknown) { const errorMessage = err instanceof Error ? err.message : "An unknown error occurred"; diff --git a/apps/web/modules/ee/auth/saml/api/callback/route.ts b/apps/web/modules/ee/auth/saml/api/callback/route.ts index c598aa751f..020458e4bc 100644 --- a/apps/web/modules/ee/auth/saml/api/callback/route.ts +++ b/apps/web/modules/ee/auth/saml/api/callback/route.ts @@ -15,18 +15,24 @@ export const POST = async (req: Request) => { const { oauthController } = jacksonInstance; const formData = await req.formData(); + console.log("saml/callback: formData", formData); const body = Object.fromEntries(formData.entries()); const { RelayState, SAMLResponse } = body as unknown as SAMLCallbackBody; + console.log("saml/callback: RelayState", RelayState); + console.log("saml/callback: SAMLResponse", SAMLResponse); const { redirect_url } = await oauthController.samlResponse({ RelayState, SAMLResponse, }); + console.log("saml/callback: redirect_url", redirect_url); + if (!redirect_url) { return responses.internalServerErrorResponse("Failed to get redirect URL"); } + console.log("saml/callback: redirecting to", redirect_url); return redirect(redirect_url); }; diff --git a/apps/web/modules/ee/auth/saml/api/token/route.ts b/apps/web/modules/ee/auth/saml/api/token/route.ts index 26ba16800e..508d1acee4 100644 --- a/apps/web/modules/ee/auth/saml/api/token/route.ts +++ b/apps/web/modules/ee/auth/saml/api/token/route.ts @@ -10,9 +10,13 @@ export const POST = async (req: Request) => { const { oauthController } = jacksonInstance; const body = await req.formData(); + console.log("saml/token: body", body); const formData = Object.fromEntries(body.entries()); + console.log("saml/token: formData", formData); const response = await oauthController.token(formData as unknown as OAuthTokenReq); + console.log("saml/token: response", response); + return Response.json(response); }; diff --git a/apps/web/modules/ee/auth/saml/api/userinfo/route.ts b/apps/web/modules/ee/auth/saml/api/userinfo/route.ts index 80f0863b9c..0e73ff90d6 100644 --- a/apps/web/modules/ee/auth/saml/api/userinfo/route.ts +++ b/apps/web/modules/ee/auth/saml/api/userinfo/route.ts @@ -9,8 +9,8 @@ export const GET = async (req: Request) => { } const { oauthController } = jacksonInstance; const token = extractAuthToken(req); - + console.log("saml/userinfo: token", token); const user = await oauthController.userInfo(token); - + console.log("saml/userinfo: user", user); return Response.json(user); }; diff --git a/apps/web/modules/ee/auth/saml/api/well-known/cert/route.ts b/apps/web/modules/ee/auth/saml/api/well-known/cert/route.ts new file mode 100644 index 0000000000..f0ba79cd72 --- /dev/null +++ b/apps/web/modules/ee/auth/saml/api/well-known/cert/route.ts @@ -0,0 +1,20 @@ +import { responses } from "@/app/lib/api/response"; +import jackson from "@/modules/ee/auth/saml/lib/jackson"; + +export async function GET() { + const jacksonInstance = await jackson(); + if (!jacksonInstance) { + return responses.forbiddenResponse("SAML SSO is not enabled in your Formbricks license"); + } + + const { spConfig } = jacksonInstance; + + const config = await spConfig.get(); + + return new Response(config.publicKey, { + status: 200, + headers: { + "Content-Type": "application/x-x509-ca-cert", + }, + }); +} diff --git a/apps/web/modules/ee/auth/saml/api/well-known/sp-metadata/route.ts b/apps/web/modules/ee/auth/saml/api/well-known/sp-metadata/route.ts new file mode 100644 index 0000000000..2703d3e976 --- /dev/null +++ b/apps/web/modules/ee/auth/saml/api/well-known/sp-metadata/route.ts @@ -0,0 +1,22 @@ +import { responses } from "@/app/lib/api/response"; +import jackson from "@/modules/ee/auth/saml/lib/jackson"; +import { NextRequest } from "next/server"; + +export const GET = async (req: NextRequest) => { + const { searchParams } = new URL(req.url); + const encryption = searchParams.get("encryption") === "true"; + + const jacksonInstance = await jackson(); + if (!jacksonInstance) { + return responses.forbiddenResponse("SAML SSO is not enabled in your Formbricks license"); + } + const { spConfig } = jacksonInstance; + const xml = await spConfig.toXMLMetadata(encryption); + + return new Response(xml, { + status: 200, + headers: { + "Content-Type": "text/xml", + }, + }); +}; diff --git a/apps/web/modules/ee/auth/saml/lib/jackson.ts b/apps/web/modules/ee/auth/saml/lib/jackson.ts index 2b883c9316..ea6d1543cf 100644 --- a/apps/web/modules/ee/auth/saml/lib/jackson.ts +++ b/apps/web/modules/ee/auth/saml/lib/jackson.ts @@ -3,7 +3,12 @@ import { SAML_AUDIENCE, SAML_DATABASE_URL, SAML_PATH, WEBAPP_URL } from "@/lib/constants"; import { preloadConnection } from "@/modules/ee/auth/saml/lib/preload-connection"; import { getIsSamlSsoEnabled } from "@/modules/ee/license-check/lib/utils"; -import type { IConnectionAPIController, IOAuthController, JacksonOption } from "@boxyhq/saml-jackson"; +import type { + IConnectionAPIController, + IOAuthController, + ISPSSOConfig, + JacksonOption, +} from "@boxyhq/saml-jackson"; const opts: JacksonOption = { externalUrl: WEBAPP_URL, @@ -19,12 +24,13 @@ const opts: JacksonOption = { declare global { var oauthController: IOAuthController | undefined; var connectionController: IConnectionAPIController | undefined; + var spConfig: ISPSSOConfig | undefined; } const g = global; export default async function init() { - if (!g.oauthController || !g.connectionController) { + if (!g.oauthController || !g.connectionController || !g.spConfig) { const isSamlSsoEnabled = await getIsSamlSsoEnabled(); if (!isSamlSsoEnabled) return; @@ -34,10 +40,12 @@ export default async function init() { g.oauthController = ret.oauthController; g.connectionController = ret.connectionAPIController; + g.spConfig = ret.spConfig; } return { oauthController: g.oauthController, connectionController: g.connectionController, + spConfig: g.spConfig, }; } diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index 27b5843988..ee64ff1d3c 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -403,6 +403,14 @@ const nextConfig = { source: "/api/v1/management/attribute-classes/:id*", destination: "/api/v1/management/contact-attribute-keys/:id*", }, + { + source: "/.well-known/saml.cer", + destination: "/api/auth/saml/well-known/cert", + }, + { + source: "/.well-known/sp-metadata", + destination: "/api/auth/saml/well-known/sp-metadata", + }, ]; }, env: {