mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-19 02:10:33 -05:00
Compare commits
1 Commits
fix/github
...
saml-sso-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fd1cd65d1 |
3
apps/web/app/api/auth/saml/well-known/cert/route.ts
Normal file
3
apps/web/app/api/auth/saml/well-known/cert/route.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { GET } from "@/modules/ee/auth/saml/api/well-known/cert/route";
|
||||||
|
|
||||||
|
export { GET };
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { GET } from "@/modules/ee/auth/saml/api/well-known/sp-metadata/route";
|
||||||
|
|
||||||
|
export { GET };
|
||||||
@@ -597,7 +597,6 @@
|
|||||||
"contact_deleted_successfully": "Kontakt erfolgreich gelöscht",
|
"contact_deleted_successfully": "Kontakt erfolgreich gelöscht",
|
||||||
"contact_not_found": "Kein solcher Kontakt gefunden",
|
"contact_not_found": "Kein solcher Kontakt gefunden",
|
||||||
"contacts_table_refresh": "Kontakte aktualisieren",
|
"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",
|
"contacts_table_refresh_success": "Kontakte erfolgreich aktualisiert",
|
||||||
"first_name": "Vorname",
|
"first_name": "Vorname",
|
||||||
"last_name": "Nachname",
|
"last_name": "Nachname",
|
||||||
|
|||||||
@@ -597,7 +597,6 @@
|
|||||||
"contact_deleted_successfully": "Contact deleted successfully",
|
"contact_deleted_successfully": "Contact deleted successfully",
|
||||||
"contact_not_found": "No such contact found",
|
"contact_not_found": "No such contact found",
|
||||||
"contacts_table_refresh": "Refresh contacts",
|
"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",
|
"contacts_table_refresh_success": "Contacts refreshed successfully",
|
||||||
"first_name": "First Name",
|
"first_name": "First Name",
|
||||||
"last_name": "Last Name",
|
"last_name": "Last Name",
|
||||||
|
|||||||
@@ -597,7 +597,6 @@
|
|||||||
"contact_deleted_successfully": "Contact supprimé avec succès",
|
"contact_deleted_successfully": "Contact supprimé avec succès",
|
||||||
"contact_not_found": "Aucun contact trouvé",
|
"contact_not_found": "Aucun contact trouvé",
|
||||||
"contacts_table_refresh": "Rafraîchir les contacts",
|
"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",
|
"contacts_table_refresh_success": "Contacts rafraîchis avec succès",
|
||||||
"first_name": "Prénom",
|
"first_name": "Prénom",
|
||||||
"last_name": "Nom de famille",
|
"last_name": "Nom de famille",
|
||||||
|
|||||||
@@ -597,7 +597,6 @@
|
|||||||
"contact_deleted_successfully": "Contato excluído com sucesso",
|
"contact_deleted_successfully": "Contato excluído com sucesso",
|
||||||
"contact_not_found": "Nenhum contato encontrado",
|
"contact_not_found": "Nenhum contato encontrado",
|
||||||
"contacts_table_refresh": "Atualizar contatos",
|
"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",
|
"contacts_table_refresh_success": "Contatos atualizados com sucesso",
|
||||||
"first_name": "Primeiro Nome",
|
"first_name": "Primeiro Nome",
|
||||||
"last_name": "Sobrenome",
|
"last_name": "Sobrenome",
|
||||||
|
|||||||
@@ -597,7 +597,6 @@
|
|||||||
"contact_deleted_successfully": "Contacto eliminado com sucesso",
|
"contact_deleted_successfully": "Contacto eliminado com sucesso",
|
||||||
"contact_not_found": "Nenhum contacto encontrado",
|
"contact_not_found": "Nenhum contacto encontrado",
|
||||||
"contacts_table_refresh": "Atualizar contactos",
|
"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",
|
"contacts_table_refresh_success": "Contactos atualizados com sucesso",
|
||||||
"first_name": "Primeiro Nome",
|
"first_name": "Primeiro Nome",
|
||||||
"last_name": "Apelido",
|
"last_name": "Apelido",
|
||||||
|
|||||||
@@ -597,7 +597,6 @@
|
|||||||
"contact_deleted_successfully": "聯絡人已成功刪除",
|
"contact_deleted_successfully": "聯絡人已成功刪除",
|
||||||
"contact_not_found": "找不到此聯絡人",
|
"contact_not_found": "找不到此聯絡人",
|
||||||
"contacts_table_refresh": "重新整理聯絡人",
|
"contacts_table_refresh": "重新整理聯絡人",
|
||||||
"contacts_table_refresh_error": "重新整理聯絡人時發生錯誤,請再試一次",
|
|
||||||
"contacts_table_refresh_success": "聯絡人已成功重新整理",
|
"contacts_table_refresh_success": "聯絡人已成功重新整理",
|
||||||
"first_name": "名字",
|
"first_name": "名字",
|
||||||
"last_name": "姓氏",
|
"last_name": "姓氏",
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ export const authOptions: NextAuthOptions = {
|
|||||||
},
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
async jwt({ token }) {
|
async jwt({ token }) {
|
||||||
|
console.log("jwt: token", token);
|
||||||
const existingUser = await getUserByEmail(token?.email!);
|
const existingUser = await getUserByEmail(token?.email!);
|
||||||
|
|
||||||
if (!existingUser) {
|
if (!existingUser) {
|
||||||
@@ -200,6 +201,8 @@ export const authOptions: NextAuthOptions = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
async session({ session, token }) {
|
async session({ session, token }) {
|
||||||
|
console.log("session: session", session);
|
||||||
|
console.log("session: token", token);
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
session.user.id = token?.id;
|
session.user.id = token?.id;
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
@@ -223,6 +226,9 @@ export const authOptions: NextAuthOptions = {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (ENTERPRISE_LICENSE_KEY) {
|
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 });
|
const result = await handleSsoCallback({ user, account, callbackUrl });
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
|
|||||||
@@ -20,10 +20,13 @@ export const GET = async (req: NextRequest) => {
|
|||||||
try {
|
try {
|
||||||
const { redirect_url } = await oauthController.authorize(searchParams as OAuthReq);
|
const { redirect_url } = await oauthController.authorize(searchParams as OAuthReq);
|
||||||
|
|
||||||
|
console.log("saml/authorize", redirect_url);
|
||||||
|
|
||||||
if (!redirect_url) {
|
if (!redirect_url) {
|
||||||
return responses.internalServerErrorResponse("Failed to get 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);
|
return NextResponse.redirect(redirect_url);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const errorMessage = err instanceof Error ? err.message : "An unknown error occurred";
|
const errorMessage = err instanceof Error ? err.message : "An unknown error occurred";
|
||||||
|
|||||||
@@ -15,18 +15,24 @@ export const POST = async (req: Request) => {
|
|||||||
const { oauthController } = jacksonInstance;
|
const { oauthController } = jacksonInstance;
|
||||||
|
|
||||||
const formData = await req.formData();
|
const formData = await req.formData();
|
||||||
|
console.log("saml/callback: formData", formData);
|
||||||
const body = Object.fromEntries(formData.entries());
|
const body = Object.fromEntries(formData.entries());
|
||||||
|
|
||||||
const { RelayState, SAMLResponse } = body as unknown as SAMLCallbackBody;
|
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({
|
const { redirect_url } = await oauthController.samlResponse({
|
||||||
RelayState,
|
RelayState,
|
||||||
SAMLResponse,
|
SAMLResponse,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("saml/callback: redirect_url", redirect_url);
|
||||||
|
|
||||||
if (!redirect_url) {
|
if (!redirect_url) {
|
||||||
return responses.internalServerErrorResponse("Failed to get redirect URL");
|
return responses.internalServerErrorResponse("Failed to get redirect URL");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("saml/callback: redirecting to", redirect_url);
|
||||||
return redirect(redirect_url);
|
return redirect(redirect_url);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,9 +10,13 @@ export const POST = async (req: Request) => {
|
|||||||
const { oauthController } = jacksonInstance;
|
const { oauthController } = jacksonInstance;
|
||||||
|
|
||||||
const body = await req.formData();
|
const body = await req.formData();
|
||||||
|
console.log("saml/token: body", body);
|
||||||
const formData = Object.fromEntries(body.entries());
|
const formData = Object.fromEntries(body.entries());
|
||||||
|
console.log("saml/token: formData", formData);
|
||||||
|
|
||||||
const response = await oauthController.token(formData as unknown as OAuthTokenReq);
|
const response = await oauthController.token(formData as unknown as OAuthTokenReq);
|
||||||
|
|
||||||
|
console.log("saml/token: response", response);
|
||||||
|
|
||||||
return Response.json(response);
|
return Response.json(response);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ export const GET = async (req: Request) => {
|
|||||||
}
|
}
|
||||||
const { oauthController } = jacksonInstance;
|
const { oauthController } = jacksonInstance;
|
||||||
const token = extractAuthToken(req);
|
const token = extractAuthToken(req);
|
||||||
|
console.log("saml/userinfo: token", token);
|
||||||
const user = await oauthController.userInfo(token);
|
const user = await oauthController.userInfo(token);
|
||||||
|
console.log("saml/userinfo: user", user);
|
||||||
return Response.json(user);
|
return Response.json(user);
|
||||||
};
|
};
|
||||||
|
|||||||
20
apps/web/modules/ee/auth/saml/api/well-known/cert/route.ts
Normal file
20
apps/web/modules/ee/auth/saml/api/well-known/cert/route.ts
Normal file
@@ -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",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -3,7 +3,12 @@
|
|||||||
import { SAML_AUDIENCE, SAML_DATABASE_URL, SAML_PATH, WEBAPP_URL } from "@/lib/constants";
|
import { SAML_AUDIENCE, SAML_DATABASE_URL, SAML_PATH, WEBAPP_URL } from "@/lib/constants";
|
||||||
import { preloadConnection } from "@/modules/ee/auth/saml/lib/preload-connection";
|
import { preloadConnection } from "@/modules/ee/auth/saml/lib/preload-connection";
|
||||||
import { getIsSamlSsoEnabled } from "@/modules/ee/license-check/lib/utils";
|
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 = {
|
const opts: JacksonOption = {
|
||||||
externalUrl: WEBAPP_URL,
|
externalUrl: WEBAPP_URL,
|
||||||
@@ -19,12 +24,13 @@ const opts: JacksonOption = {
|
|||||||
declare global {
|
declare global {
|
||||||
var oauthController: IOAuthController | undefined;
|
var oauthController: IOAuthController | undefined;
|
||||||
var connectionController: IConnectionAPIController | undefined;
|
var connectionController: IConnectionAPIController | undefined;
|
||||||
|
var spConfig: ISPSSOConfig | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const g = global;
|
const g = global;
|
||||||
|
|
||||||
export default async function init() {
|
export default async function init() {
|
||||||
if (!g.oauthController || !g.connectionController) {
|
if (!g.oauthController || !g.connectionController || !g.spConfig) {
|
||||||
const isSamlSsoEnabled = await getIsSamlSsoEnabled();
|
const isSamlSsoEnabled = await getIsSamlSsoEnabled();
|
||||||
if (!isSamlSsoEnabled) return;
|
if (!isSamlSsoEnabled) return;
|
||||||
|
|
||||||
@@ -34,10 +40,12 @@ export default async function init() {
|
|||||||
|
|
||||||
g.oauthController = ret.oauthController;
|
g.oauthController = ret.oauthController;
|
||||||
g.connectionController = ret.connectionAPIController;
|
g.connectionController = ret.connectionAPIController;
|
||||||
|
g.spConfig = ret.spConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
oauthController: g.oauthController,
|
oauthController: g.oauthController,
|
||||||
connectionController: g.connectionController,
|
connectionController: g.connectionController,
|
||||||
|
spConfig: g.spConfig,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -403,6 +403,14 @@ const nextConfig = {
|
|||||||
source: "/api/v1/management/attribute-classes/:id*",
|
source: "/api/v1/management/attribute-classes/:id*",
|
||||||
destination: "/api/v1/management/contact-attribute-keys/: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: {
|
env: {
|
||||||
|
|||||||
Reference in New Issue
Block a user