chore: Auth module revamp (#4335)

Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
Dhruwang Jariwala
2024-11-26 13:58:13 +05:30
committed by GitHub
parent 7598a16b75
commit f80d1b32b7
170 changed files with 2484 additions and 2582 deletions

View File

@@ -71,13 +71,15 @@ These variables are present inside your machines docker-compose file. Restart
| OIDC_SIGNING_ALGORITHM | Signing Algorithm for Custom OpenID Connect Provider | optional | RS256 |
| OPENTELEMETRY_LISTENER_URL | URL for OpenTelemetry listener inside Formbricks. | optional | |
| CUSTOM_CACHE_DISABLED | Disables custom cache handler if set to 1 (required for deployment on Vercel) | optional | |
| `<add more>` | | | |
| | | | |
Note: If you want to configure something that is not possible via above, please open an issue on our GitHub repo here or reach out to us on Discord and well try our best to work out a solution with you.
## OAuth Configuration
<Note>
Single Sign-On (SSO) functionality, including OAuth integrations with Google, Microsoft Entra ID, Github and OpenID Connect, requires a valid Formbricks Enterprise License.
</Note>
### Google OAuth
Integrating Google OAuth with your Formbricks instance allows users to log in using their Google credentials, ensuring a secure and streamlined user experience. This guide will walk you through the process of setting up Google OAuth for your Formbricks instance.
@@ -113,10 +115,12 @@ Integrating Google OAuth with your Formbricks instance allows users to log in us
<CodeGroup title="Configuration URLs">
``` {{ title: "Redirect & Origin URLs" }}
Authorized JavaScript origins: {WEBAPP_URL}
Authorized redirect URIs: {WEBAPP_URL}/api/auth/callback/google ```
Authorized redirect URIs: {WEBAPP_URL}/api/auth/callback/google
```
</CodeGroup>
</Col>
5. **Update Environment Variables in Docker**:
- To integrate the Google OAuth, you have two options: either update the environment variables in the docker-compose file or directly add them to the running container.
- In your Docker setup directory, open the `.env` file, and add or update the following lines with the `Client ID` and `Client Secret` obtained from Google Cloud Platform:

View File

@@ -1,11 +1,11 @@
import { InviteOrganizationMember } from "@/app/(app)/(onboarding)/environments/[environmentId]/connect/components/InviteOrganizationMember";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { XIcon } from "lucide-react";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { notFound, redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";

View File

@@ -1,6 +1,6 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { AuthorizationError } from "@formbricks/types/errors";

View File

@@ -1,11 +1,11 @@
import { XMTemplateList } from "@/app/(app)/(onboarding)/environments/[environmentId]/xm-templates/components/XMTemplateList";
import { getOrganizationIdFromEnvironmentId } from "@/lib/utils/helper";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { XIcon } from "lucide-react";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getProductByEnvironmentId, getUserProducts } from "@formbricks/lib/product/service";
import { getUser } from "@formbricks/lib/user/service";

View File

@@ -1,6 +1,6 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { notFound, redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getEnvironments } from "@formbricks/lib/environment/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getUserProducts } from "@formbricks/lib/product/service";

View File

@@ -1,10 +1,10 @@
import { LandingSidebar } from "@/app/(app)/(onboarding)/organizations/[organizationId]/landing/components/landing-sidebar";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getEnterpriseLicense } from "@/modules/ee/license-check/lib/utils";
import { Header } from "@/modules/ui/components/header";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { notFound, redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getOrganization, getOrganizationsByUserId } from "@formbricks/lib/organization/service";
import { getUser } from "@formbricks/lib/user/service";

View File

@@ -1,9 +1,9 @@
import { PosthogIdentify } from "@/app/(app)/environments/[environmentId]/components/PosthogIdentify";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { ToasterClient } from "@/modules/ui/components/toaster-client";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { canUserAccessOrganization } from "@formbricks/lib/organization/auth";
import { getOrganization } from "@formbricks/lib/organization/service";
import { getUser } from "@formbricks/lib/user/service";

View File

@@ -1,6 +1,6 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { notFound, redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";

View File

@@ -1,11 +1,11 @@
import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { GlobeIcon, GlobeLockIcon, LinkIcon, XIcon } from "lucide-react";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getUserProducts } from "@formbricks/lib/product/service";
interface ChannelPageProps {

View File

@@ -1,11 +1,11 @@
import { OnboardingOptionsContainer } from "@/app/(app)/(onboarding)/organizations/components/OnboardingOptionsContainer";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
import { HeartIcon, ListTodoIcon, XIcon } from "lucide-react";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getUserProducts } from "@formbricks/lib/product/service";
interface ModePageProps {

View File

@@ -1,6 +1,7 @@
import { getTeamsByOrganizationId } from "@/app/(app)/(onboarding)/lib/onboarding";
import { getCustomHeadline } from "@/app/(app)/(onboarding)/lib/utils";
import { ProductSettings } from "@/app/(app)/(onboarding)/organizations/[organizationId]/products/new/settings/components/ProductSettings";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getRoleManagementPermission } from "@/modules/ee/license-check/lib/utils";
import { Button } from "@/modules/ui/components/button";
import { Header } from "@/modules/ui/components/header";
@@ -8,7 +9,6 @@ import { XIcon } from "lucide-react";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { DEFAULT_BRAND_COLOR, DEFAULT_LOCALE } from "@formbricks/lib/constants";
import { getOrganization } from "@formbricks/lib/organization/service";
import { getUserProducts } from "@formbricks/lib/product/service";

View File

@@ -23,7 +23,6 @@ export const inviteOrganizationMemberAction = authenticatedActionClient
if (INVITE_DISABLED) {
throw new AuthenticationError("Invite disabled");
}
await checkAuthorizationUpdated({
userId: ctx.user.id,
organizationId: parsedInput.organizationId,
@@ -42,6 +41,7 @@ export const inviteOrganizationMemberAction = authenticatedActionClient
name: "",
role: parsedInput.role,
},
currentUserId: ctx.user.id,
});
if (invite) {

View File

@@ -1,12 +1,12 @@
import { FormbricksClient } from "@/app/(app)/components/FormbricksClient";
import { PosthogIdentify } from "@/app/(app)/environments/[environmentId]/components/PosthogIdentify";
import { ResponseFilterProvider } from "@/app/(app)/environments/[environmentId]/components/ResponseFilterContext";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { DevEnvironmentBanner } from "@/modules/ui/components/dev-environment-banner";
import { ToasterClient } from "@/modules/ui/components/toaster-client";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";

View File

@@ -1,4 +1,5 @@
import { getUserEmail } from "@/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/lib/user";
import { authOptions } from "@/modules/auth/lib/authOptions";
import {
getAdvancedTargetingPermission,
getMultiLanguagePermission,
@@ -11,7 +12,6 @@ import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { getActionClasses } from "@formbricks/lib/actionClass/service";
import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
import { authOptions } from "@formbricks/lib/authOptions";
import {
DEFAULT_LOCALE,
IS_FORMBRICKS_CLOUD,

View File

@@ -1,9 +1,9 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";

View File

@@ -1,4 +1,5 @@
import { PersonSecondaryNavigation } from "@/app/(app)/environments/[environmentId]/(people)/people/components/PersonSecondaryNavigation";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
import { Button } from "@/modules/ui/components/button";
@@ -9,7 +10,6 @@ import { Metadata } from "next";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
import { authOptions } from "@formbricks/lib/authOptions";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";

View File

@@ -1,7 +1,7 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";

View File

@@ -1,8 +1,8 @@
import { ResponseTimeline } from "@/app/(app)/environments/[environmentId]/(people)/people/[personId]/components/ResponseTimeline";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getResponsesByPersonId } from "@formbricks/lib/response/service";
import { getSurveys } from "@formbricks/lib/survey/service";

View File

@@ -104,7 +104,7 @@ const ResponseSurveyCard = ({
return survey.id === response.surveyId;
});
const { membershipRole } = useMembershipRole(survey?.environmentId || "");
const { membershipRole } = useMembershipRole(survey?.environmentId || "", user.id);
const { isMember } = getAccessFlags(membershipRole);
const { hasReadAccess } = getTeamPermissionFlags(productPermission);

View File

@@ -1,6 +1,7 @@
import { AttributesSection } from "@/app/(app)/environments/[environmentId]/(people)/people/[personId]/components/AttributesSection";
import { DeletePersonButton } from "@/app/(app)/environments/[environmentId]/(people)/people/[personId]/components/DeletePersonButton";
import { ResponseSection } from "@/app/(app)/environments/[environmentId]/(people)/people/[personId]/components/ResponseSection";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
@@ -9,7 +10,6 @@ import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { getAttributes } from "@formbricks/lib/attribute/service";
import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
import { authOptions } from "@formbricks/lib/authOptions";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";

View File

@@ -1,5 +1,6 @@
import { PersonDataView } from "@/app/(app)/environments/[environmentId]/(people)/people/components/PersonDataView";
import { PersonSecondaryNavigation } from "@/app/(app)/environments/[environmentId]/(people)/people/components/PersonSecondaryNavigation";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
import { Button } from "@/modules/ui/components/button";
@@ -8,7 +9,6 @@ import { PageHeader } from "@/modules/ui/components/page-header";
import { CircleHelpIcon } from "lucide-react";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { ITEMS_PER_PAGE } from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";

View File

@@ -1,6 +1,7 @@
import { PersonSecondaryNavigation } from "@/app/(app)/environments/[environmentId]/(people)/people/components/PersonSecondaryNavigation";
import { BasicCreateSegmentModal } from "@/app/(app)/environments/[environmentId]/(people)/segments/components/BasicCreateSegmentModal";
import { SegmentTable } from "@/app/(app)/environments/[environmentId]/(people)/segments/components/SegmentTable";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { CreateSegmentModal } from "@/modules/ee/advanced-targeting/components/create-segment-modal";
import { getAdvancedTargetingPermission } from "@/modules/ee/license-check/lib/utils";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
@@ -10,7 +11,6 @@ import { PageHeader } from "@/modules/ui/components/page-header";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
import { authOptions } from "@formbricks/lib/authOptions";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";

View File

@@ -2,6 +2,7 @@ import { ActionClassesTable } from "@/app/(app)/environments/[environmentId]/act
import { ActionClassDataRow } from "@/app/(app)/environments/[environmentId]/actions/components/ActionRowData";
import { ActionTableHeading } from "@/app/(app)/environments/[environmentId]/actions/components/ActionTableHeading";
import { AddActionModal } from "@/app/(app)/environments/[environmentId]/actions/components/AddActionModal";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
@@ -11,7 +12,6 @@ import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { getActionClasses } from "@formbricks/lib/actionClass/service";
import { authOptions } from "@formbricks/lib/authOptions";
import { getEnvironments } from "@formbricks/lib/environment/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";

View File

@@ -1,4 +1,5 @@
import { AirtableWrapper } from "@/app/(app)/environments/[environmentId]/integrations/airtable/components/AirtableWrapper";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
import { GoBackButton } from "@/modules/ui/components/go-back-button";
@@ -9,7 +10,6 @@ import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { getAirtableTables } from "@formbricks/lib/airtable/service";
import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
import { authOptions } from "@formbricks/lib/authOptions";
import { AIRTABLE_CLIENT_ID, WEBAPP_URL } from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getIntegrations } from "@formbricks/lib/integration/service";

View File

@@ -1,7 +1,7 @@
"use server";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { authOptions } from "@formbricks/lib/authOptions";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { getSpreadsheetNameById } from "@formbricks/lib/googleSheet/service";
import { AuthorizationError } from "@formbricks/types/errors";

View File

@@ -1,4 +1,5 @@
import { GoogleSheetWrapper } from "@/app/(app)/environments/[environmentId]/integrations/google-sheets/components/GoogleSheetWrapper";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
import { GoBackButton } from "@/modules/ui/components/go-back-button";
@@ -8,7 +9,6 @@ import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
import { authOptions } from "@formbricks/lib/authOptions";
import {
GOOGLE_SHEETS_CLIENT_ID,
GOOGLE_SHEETS_CLIENT_SECRET,

View File

@@ -1,4 +1,5 @@
import { NotionWrapper } from "@/app/(app)/environments/[environmentId]/integrations/notion/components/NotionWrapper";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
import { GoBackButton } from "@/modules/ui/components/go-back-button";
@@ -8,7 +9,6 @@ import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
import { authOptions } from "@formbricks/lib/authOptions";
import {
NOTION_AUTH_URL,
NOTION_OAUTH_CLIENT_ID,

View File

@@ -7,6 +7,7 @@ import notionLogo from "@/images/notion.png";
import SlackLogo from "@/images/slacklogo.png";
import WebhookLogo from "@/images/webhook.png";
import ZapierLogo from "@/images/zapier-small.png";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
import { Card } from "@/modules/ui/components/integration-card";
@@ -16,7 +17,6 @@ import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import Image from "next/image";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getIntegrations } from "@formbricks/lib/integration/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";

View File

@@ -1,4 +1,5 @@
import { SlackWrapper } from "@/app/(app)/environments/[environmentId]/integrations/slack/components/SlackWrapper";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
import { GoBackButton } from "@/modules/ui/components/go-back-button";
@@ -8,7 +9,6 @@ import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
import { authOptions } from "@formbricks/lib/authOptions";
import { SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, WEBAPP_URL } from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getIntegrationByType } from "@formbricks/lib/integration/service";

View File

@@ -2,6 +2,7 @@ import { AddWebhookButton } from "@/app/(app)/environments/[environmentId]/integ
import { WebhookRowData } from "@/app/(app)/environments/[environmentId]/integrations/webhooks/components/WebhookRowData";
import { WebhookTable } from "@/app/(app)/environments/[environmentId]/integrations/webhooks/components/WebhookTable";
import { WebhookTableHeading } from "@/app/(app)/environments/[environmentId]/integrations/webhooks/components/WebhookTableHeading";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
import { GoBackButton } from "@/modules/ui/components/go-back-button";
@@ -9,7 +10,6 @@ import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper
import { PageHeader } from "@/modules/ui/components/page-header";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";

View File

@@ -1,10 +1,10 @@
import { EnvironmentLayout } from "@/app/(app)/environments/[environmentId]/components/EnvironmentLayout";
import { ResponseFilterProvider } from "@/app/(app)/environments/[environmentId]/components/ResponseFilterContext";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { ToasterClient } from "@/modules/ui/components/toaster-client";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { notFound, redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";

View File

@@ -1,7 +1,7 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";

View File

@@ -1,4 +1,5 @@
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
import { authOptions } from "@/modules/auth/lib/authOptions";
import {
getMultiLanguagePermission,
getRoleManagementPermission,
@@ -10,7 +11,6 @@ import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper
import { PageHeader } from "@/modules/ui/components/page-header";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";

View File

@@ -1,7 +1,7 @@
import { DeleteProductRender } from "@/app/(app)/environments/[environmentId]/product/general/components/DeleteProductRender";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
import { getUserProducts } from "@formbricks/lib/product/service";
import { TProduct } from "@formbricks/types/product";

View File

@@ -1,4 +1,5 @@
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
import { authOptions } from "@/modules/auth/lib/authOptions";
import {
getMultiLanguagePermission,
getRoleManagementPermission,
@@ -11,7 +12,6 @@ import { SettingsId } from "@/modules/ui/components/settings-id";
import packageJson from "@/package.json";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";

View File

@@ -1,5 +1,6 @@
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
import { authOptions } from "@/modules/auth/lib/authOptions";
import {
getMultiLanguagePermission,
getRoleManagementPermission,
@@ -12,7 +13,6 @@ import { PageHeader } from "@/modules/ui/components/page-header";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { notFound } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";
import { getOrganization } from "@formbricks/lib/organization/service";

View File

@@ -1,8 +1,8 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { Metadata } from "next";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";

View File

@@ -1,5 +1,6 @@
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
import { EditLogo } from "@/app/(app)/environments/[environmentId]/product/look/components/EditLogo";
import { authOptions } from "@/modules/auth/lib/authOptions";
import {
getMultiLanguagePermission,
getRemoveInAppBrandingPermission,
@@ -13,7 +14,6 @@ import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper
import { PageHeader } from "@/modules/ui/components/page-header";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { cn } from "@formbricks/lib/cn";
import { DEFAULT_LOCALE, SURVEY_BG_COLORS, UNSPLASH_ACCESS_KEY } from "@formbricks/lib/constants";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";

View File

@@ -1,5 +1,6 @@
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
import { authOptions } from "@/modules/auth/lib/authOptions";
import {
getMultiLanguagePermission,
getRoleManagementPermission,
@@ -10,7 +11,6 @@ import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper
import { PageHeader } from "@/modules/ui/components/page-header";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";

View File

@@ -1,6 +1,6 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";

View File

@@ -1,11 +1,11 @@
import { AccountSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(account)/components/AccountSettingsNavbar";
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { prisma } from "@formbricks/database";
import { authOptions } from "@formbricks/lib/authOptions";
import { getUser } from "@formbricks/lib/user/service";
import { TUserNotificationSettings } from "@formbricks/types/user";
import { EditAlerts } from "./components/EditAlerts";

View File

@@ -2,7 +2,6 @@
import { authenticatedActionClient } from "@/lib/utils/action-client";
import { z } from "zod";
import { disableTwoFactorAuth, enableTwoFactorAuth, setupTwoFactorAuth } from "@formbricks/lib/auth/service";
import { deleteFile } from "@formbricks/lib/storage/service";
import { getFileNameWithIdFromUrl } from "@formbricks/lib/storage/utils";
import { updateUser } from "@formbricks/lib/user/service";
@@ -15,38 +14,6 @@ export const updateUserAction = authenticatedActionClient
return await updateUser(ctx.user.id, parsedInput);
});
const ZSetupTwoFactorAuthAction = z.object({
password: z.string(),
});
export const setupTwoFactorAuthAction = authenticatedActionClient
.schema(ZSetupTwoFactorAuthAction)
.action(async ({ parsedInput, ctx }) => {
return await setupTwoFactorAuth(ctx.user.id, parsedInput.password);
});
const ZEnableTwoFactorAuthAction = z.object({
code: z.string(),
});
export const enableTwoFactorAuthAction = authenticatedActionClient
.schema(ZEnableTwoFactorAuthAction)
.action(async ({ parsedInput, ctx }) => {
return await enableTwoFactorAuth(ctx.user.id, parsedInput.code);
});
const ZDisableTwoFactorAuthAction = z.object({
code: z.string(),
password: z.string(),
backupCode: z.string().optional(),
});
export const disableTwoFactorAuthAction = authenticatedActionClient
.schema(ZDisableTwoFactorAuthAction)
.action(async ({ parsedInput, ctx }) => {
return await disableTwoFactorAuth(ctx.user.id, parsedInput);
});
const ZUpdateAvatarAction = z.object({
avatarUrl: z.string(),
});

View File

@@ -1,13 +1,20 @@
"use client";
import { DisableTwoFactorModal } from "@/app/(app)/environments/[environmentId]/settings/(account)/profile/components/DisableTwoFactorModal";
import { EnableTwoFactorModal } from "@/app/(app)/environments/[environmentId]/settings/(account)/profile/components/EnableTwoFactorModal";
import { DisableTwoFactorModal } from "@/modules/ee/two-factor-auth/components/disable-two-factor-modal";
import { EnableTwoFactorModal } from "@/modules/ee/two-factor-auth/components/enable-two-factor-modal";
import { Switch } from "@/modules/ui/components/switch";
import { UpgradePlanNotice } from "@/modules/ui/components/upgrade-plan-notice";
import { useTranslations } from "next-intl";
import { useState } from "react";
import { TUser } from "@formbricks/types/user";
export const AccountSecurity = ({ user }: { user: TUser }) => {
interface AccountSecurityProps {
user: TUser;
isTwoFactorAuthEnabled: boolean;
environmentId: string;
}
export const AccountSecurity = ({ user, isTwoFactorAuthEnabled, environmentId }: AccountSecurityProps) => {
const t = useTranslations();
const [twoFactorModalOpen, setTwoFactorModalOpen] = useState(false);
const [disableTwoFactorModalOpen, setDisableTwoFactorModalOpen] = useState(false);
@@ -17,6 +24,7 @@ export const AccountSecurity = ({ user }: { user: TUser }) => {
<div className="flex items-center space-x-4">
<Switch
checked={user.twoFactorEnabled}
disabled={!isTwoFactorAuthEnabled && !user.twoFactorEnabled}
onCheckedChange={(checked) => {
if (checked) {
setTwoFactorModalOpen(true);
@@ -35,7 +43,13 @@ export const AccountSecurity = ({ user }: { user: TUser }) => {
</p>
</div>
</div>
{!isTwoFactorAuthEnabled && !user.twoFactorEnabled && (
<UpgradePlanNotice
message={t("environments.settings.profile.to_enable_two_factor_authentication_you_need_an_active")}
textForUrl={t("common.enterprise_license")}
url={`/environments/${environmentId}/settings/enterprise`}
/>
)}
<EnableTwoFactorModal open={twoFactorModalOpen} setOpen={setTwoFactorModalOpen} />
<DisableTwoFactorModal open={disableTwoFactorModalOpen} setOpen={setDisableTwoFactorModalOpen} />
</div>

View File

@@ -1,171 +0,0 @@
"use client";
import { disableTwoFactorAuthAction } from "@/app/(app)/environments/[environmentId]/settings/(account)/profile/actions";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { Modal } from "@/modules/ui/components/modal";
import { OTPInput } from "@/modules/ui/components/otp-input";
import { PasswordInput } from "@/modules/ui/components/password-input";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import React, { useEffect, useState } from "react";
import { Controller, SubmitHandler, useForm } from "react-hook-form";
import toast from "react-hot-toast";
type TDisableTwoFactorFormState = {
password: string;
code: string;
backupCode?: string;
};
type TDisableTwoFactorModalProps = {
open: boolean;
setOpen: (open: boolean) => void;
};
export const DisableTwoFactorModal = ({ open, setOpen }: TDisableTwoFactorModalProps) => {
const t = useTranslations();
const router = useRouter();
const { handleSubmit, control, setValue } = useForm<TDisableTwoFactorFormState>();
const [backupCodeInputVisible, setBackupCodeInputVisible] = useState(false);
useEffect(() => {
setValue("backupCode", "");
setValue("code", "");
}, [backupCodeInputVisible, setValue]);
const resetState = () => {
setBackupCodeInputVisible(false);
setValue("password", "");
setValue("backupCode", "");
setValue("code", "");
setOpen(false);
};
const onSubmit: SubmitHandler<TDisableTwoFactorFormState> = async (data) => {
const { code, password, backupCode } = data;
const disableTwoFactorAuthResponse = await disableTwoFactorAuthAction({ code, password, backupCode });
if (disableTwoFactorAuthResponse?.data) {
toast.success(disableTwoFactorAuthResponse.data.message);
router.refresh();
resetState();
} else {
const errorMessage = getFormattedErrorMessage(disableTwoFactorAuthResponse);
toast.error(errorMessage);
}
};
return (
<Modal open={open} setOpen={() => resetState()} noPadding>
<>
<div>
<div className="p-6">
<h1 className="text-lg font-semibold">
{t("environments.settings.profile.disable_two_factor_authentication")}
</h1>
<p className="text-sm text-slate-700">
{t("environments.settings.profile.disable_two_factor_authentication_description")}
</p>
</div>
<form className="flex flex-col space-y-6" onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col gap-2 px-6">
<label htmlFor="password" className="text-sm font-medium text-slate-700">
{t("common.password")}
</label>
<Controller
name="password"
control={control}
render={({ field, formState: { errors } }) => (
<>
<PasswordInput
id="password"
autoComplete="current-password"
placeholder="*******"
aria-placeholder="password"
required
className="focus:border-brand-dark focus:ring-brand-dark block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
{...field}
/>
{errors.password && (
<p className="mt-2 text-sm text-red-600" id="password-error">
{errors.password.message}
</p>
)}
</>
)}
/>
</div>
<div className="px-6">
<div className="flex flex-col gap-2">
<label htmlFor="code" className="text-sm font-medium text-slate-700">
{backupCodeInputVisible
? t("environments.settings.profile.backup_code")
: t("environments.settings.profile.two_factor_code")}
</label>
<p className="text-sm text-slate-700">
{backupCodeInputVisible
? t(
"environments.settings.profile.each_backup_code_can_be_used_exactly_once_to_grant_access_without_your_authenticator"
)
: t(
"environments.settings.profile.two_factor_authentication_enabled_please_enter_the_six_digit_code_from_your_authenticator_app"
)}
</p>
</div>
{backupCodeInputVisible ? (
<Controller
name="backupCode"
control={control}
render={({ field }) => <Input {...field} placeholder="XXXXX-XXXXX" className="mt-2" />}
/>
) : (
<Controller
name="code"
control={control}
render={({ field }) => (
<OTPInput
value={field.value}
valueLength={6}
onChange={field.onChange}
containerClassName="justify-start mt-4"
/>
)}
/>
)}
</div>
<div className="flex w-full items-center justify-between border-t border-slate-300 p-4">
<div>
<Button
variant="minimal"
size="sm"
type="button"
onClick={() => setBackupCodeInputVisible((prev) => !prev)}>
{backupCodeInputVisible
? t("common.go_back")
: t("environments.settings.profile.lost_access")}
</Button>
</div>
<div className="flex items-center space-x-4">
<Button variant="secondary" size="sm" type="button" onClick={() => setOpen(false)}>
{t("common.cancel")}
</Button>
<Button size="sm">{t("common.disable")}</Button>
</div>
</div>
</form>
</div>
</>
</Modal>
);
};

View File

@@ -27,7 +27,7 @@ export const EditProfileAvatarForm = ({ session, environmentId, imageUrl }: Edit
const inputRef = useRef<HTMLInputElement>(null);
const [isLoading, setIsLoading] = useState(false);
const router = useRouter();
const t = useTranslations("environments.settings.profile");
const t = useTranslations();
const fileSchema =
typeof window !== "undefined"
? z
@@ -136,7 +136,9 @@ export const EditProfileAvatarForm = ({ session, environmentId, imageUrl }: Edit
onClick={() => {
inputRef.current?.click();
}}>
{imageUrl ? t("change_image") : t("upload_image")}
{imageUrl
? t("environments.settings.profile.change_image")
: t("environments.settings.profile.upload_image")}
<input
type="file"
id="hiddenFileInput"

View File

@@ -1,348 +0,0 @@
"use client";
import {
enableTwoFactorAuthAction,
setupTwoFactorAuthAction,
} from "@/app/(app)/environments/[environmentId]/settings/(account)/profile/actions";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { Button } from "@/modules/ui/components/button";
import { Modal } from "@/modules/ui/components/modal";
import { OTPInput } from "@/modules/ui/components/otp-input";
import { PasswordInput } from "@/modules/ui/components/password-input";
import { useTranslations } from "next-intl";
import Image from "next/image";
import { useRouter } from "next/navigation";
import React, { useState } from "react";
import { Controller, SubmitHandler, useForm } from "react-hook-form";
import toast from "react-hot-toast";
type TConfirmPasswordFormState = {
password: string;
};
type TEnterCodeFormState = {
code: string;
};
type TStep = "confirmPassword" | "scanQRCode" | "enterCode" | "backupCodes";
type TEnableTwoFactorModalProps = {
open: boolean;
setOpen: (open: boolean) => void;
};
type TConfirmPasswordFormProps = {
setCurrentStep: (step: TStep) => void;
setBackupCodes: (codes: string[]) => void;
setDataUri: (dataUri: string) => void;
setSecret: (secret: string) => void;
setOpen: (open: boolean) => void;
};
const ConfirmPasswordForm = ({
setBackupCodes,
setCurrentStep,
setDataUri,
setSecret,
setOpen,
}: TConfirmPasswordFormProps) => {
const { control, handleSubmit, setError } = useForm<TConfirmPasswordFormState>();
const t = useTranslations();
const onSubmit: SubmitHandler<TConfirmPasswordFormState> = async (data) => {
const setupTwoFactorAuthResponse = await setupTwoFactorAuthAction({ password: data.password });
if (setupTwoFactorAuthResponse?.data) {
const { backupCodes, dataUri, secret } = setupTwoFactorAuthResponse.data;
setBackupCodes(backupCodes);
setDataUri(dataUri);
setSecret(secret);
setCurrentStep("scanQRCode");
} else {
const errorMessage = getFormattedErrorMessage(setupTwoFactorAuthResponse);
setError("password", { message: errorMessage });
}
};
return (
<div>
<div className="p-6">
<h1 className="text-lg font-semibold">
{t("environments.settings.profile.two_factor_authentication")}
</h1>
<h3 className="text-sm text-slate-700">
{t("environments.settings.profile.confirm_your_current_password_to_get_started")}
</h3>
</div>
<form className="flex flex-col space-y-10" onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col gap-2 px-6">
<label htmlFor="password" className="text-sm font-medium text-slate-700">
{t("common.password")}
</label>
<Controller
name="password"
control={control}
render={({ field, formState: { errors } }) => (
<>
<PasswordInput
id="password"
autoComplete="current-password"
placeholder="*******"
aria-placeholder="password"
required
className="focus:border-brand-dark focus:ring-brand-dark block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
{...field}
/>
{errors.password && (
<p className="mt-2 text-sm text-red-600" id="password-error">
{errors.password.message}
</p>
)}
</>
)}
/>
</div>
<div className="flex w-full items-center justify-end space-x-4 border-t border-slate-300 p-4">
<Button variant="secondary" size="sm" type="button" onClick={() => setOpen(false)}>
{t("common.cancel")}
</Button>
<Button size="sm">{t("common.confirm")}</Button>
</div>
</form>
</div>
);
};
type TScanQRCodeProps = {
setCurrentStep: (step: TStep) => void;
dataUri: string;
secret: string;
setOpen: (open: boolean) => void;
};
const ScanQRCode = ({ dataUri, secret, setCurrentStep, setOpen }: TScanQRCodeProps) => {
const t = useTranslations();
return (
<div>
<div className="p-6">
<h1 className="text-lg font-semibold">
{t("environments.settings.profile.enable_two_factor_authentication")}
</h1>
<h3 className="text-sm text-slate-700">
{t("environments.settings.profile.scan_the_qr_code_below_with_your_authenticator_app")}
</h3>
</div>
<div className="mb-4 flex flex-col items-center justify-center space-y-4">
<Image src={dataUri} alt="QR code" width={200} height={200} />
<p className="text-sm text-slate-700">
{t("environments.settings.profile.or_enter_the_following_code_manually")}
</p>
<p className="text-sm font-medium text-slate-700">{secret}</p>
</div>
<div className="flex w-full items-center justify-end space-x-4 border-t border-slate-300 p-4">
<Button variant="secondary" size="sm" type="button" onClick={() => setOpen(false)}>
{t("common.cancel")}
</Button>
<Button size="sm" onClick={() => setCurrentStep("enterCode")}>
{t("common.next")}
</Button>
</div>
</div>
);
};
type TEnableCodeProps = {
setCurrentStep: (step: TStep) => void;
setOpen: (open: boolean) => void;
refreshData: () => void;
};
const EnterCode = ({ setCurrentStep, setOpen, refreshData }: TEnableCodeProps) => {
const t = useTranslations();
const { control, handleSubmit } = useForm<TEnterCodeFormState>({
defaultValues: {
code: "",
},
});
const onSubmit: SubmitHandler<TEnterCodeFormState> = async (data) => {
try {
const enableTwoFactorAuthResponse = await enableTwoFactorAuthAction({ code: data.code });
if (enableTwoFactorAuthResponse?.data) {
toast.success(enableTwoFactorAuthResponse.data.message);
setCurrentStep("backupCodes");
// refresh data to update the UI
refreshData();
} else {
toast.error(t("environments.settings.profile.the_2fa_otp_is_incorrect_please_try_again"));
}
} catch (err) {
toast.error(err.message);
}
};
return (
<>
<div>
<div className="p-6">
<h1 className="text-lg font-semibold">
{t("environments.settings.profile.enable_two_factor_authentication")}
</h1>
<h3 className="text-sm text-slate-700">
{t("environments.settings.profile.enter_the_code_from_your_authenticator_app_below")}
</h3>
</div>
<form className="flex flex-col space-y-10" onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col gap-2 px-6">
<label htmlFor="code" className="text-sm font-medium text-slate-700">
{t("common.code")}
</label>
<Controller
name="code"
control={control}
render={({ field, formState: { errors } }) => (
<>
<OTPInput
value={field.value}
onChange={field.onChange}
valueLength={6}
containerClassName="justify-start"
/>
{errors.code && (
<p className="mt-2 text-sm text-red-600" id="code-error">
{errors.code.message}
</p>
)}
</>
)}
/>
</div>
<div className="flex w-full items-center justify-end space-x-4 border-t border-slate-300 p-4">
<Button variant="secondary" size="sm" type="button" onClick={() => setOpen(false)}>
{t("common.cancel")}
</Button>
<Button size="sm">{t("common.confirm")}</Button>
</div>
</form>
</div>
</>
);
};
type TDisplayBackupCodesProps = {
backupCodes: string[];
setOpen: (open: boolean) => void;
};
const DisplayBackupCodes = ({ backupCodes, setOpen }: TDisplayBackupCodesProps) => {
const t = useTranslations();
const formatBackupCode = (code: string) => `${code.slice(0, 5)}-${code.slice(5, 10)}`;
const handleDownloadBackupCode = () => {
const formattedCodes = backupCodes.map((code) => formatBackupCode(code)).join("\n");
const blob = new Blob([formattedCodes], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "formbricks-backup-codes.txt";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
return (
<div>
<div className="p-6">
<h1 className="text-lg font-semibold">
{t("environments.settings.profile.enable_two_factor_authentication")}
</h1>
<h3 className="text-sm text-slate-700">
{t("environments.settings.profile.save_the_following_backup_codes_in_a_safe_place")}
</h3>
</div>
<div className="mx-auto mb-6 grid max-w-[60%] grid-cols-2 gap-1 text-center">
{backupCodes.map((code) => (
<p key={code} className="text-sm font-medium text-slate-700">
{formatBackupCode(code)}
</p>
))}
</div>
<div className="flex w-full items-center justify-end space-x-4 border-t border-slate-300 p-4">
<Button variant="secondary" type="button" size="sm" onClick={() => setOpen(false)}>
{t("common.close")}
</Button>
<Button
size="sm"
onClick={() => {
navigator.clipboard.writeText(backupCodes.map((code) => formatBackupCode(code)).join("\n"));
toast.success(t("common.copied_to_clipboard"));
}}>
{t("common.copy")}
</Button>
<Button
size="sm"
onClick={() => {
handleDownloadBackupCode();
}}>
{t("common.download")}
</Button>
</div>
</div>
);
};
export const EnableTwoFactorModal = ({ open, setOpen }: TEnableTwoFactorModalProps) => {
const router = useRouter();
const [currentStep, setCurrentStep] = useState<TStep>("confirmPassword");
const [backupCodes, setBackupCodes] = useState<string[]>([]);
const [dataUri, setDataUri] = useState<string>("");
const [secret, setSecret] = useState<string>("");
const refreshData = () => {
router.refresh();
};
const resetState = () => {
setCurrentStep("confirmPassword");
setBackupCodes([]);
setDataUri("");
setSecret("");
setOpen(false);
};
return (
<Modal open={open} setOpen={() => resetState()} noPadding>
{currentStep === "confirmPassword" && (
<ConfirmPasswordForm
setBackupCodes={setBackupCodes}
setCurrentStep={setCurrentStep}
setDataUri={setDataUri}
setSecret={setSecret}
setOpen={setOpen}
/>
)}
{currentStep === "scanQRCode" && (
<ScanQRCode setCurrentStep={setCurrentStep} dataUri={dataUri} secret={secret} setOpen={setOpen} />
)}
{currentStep === "enterCode" && (
<EnterCode setCurrentStep={setCurrentStep} setOpen={setOpen} refreshData={refreshData} />
)}
{currentStep === "backupCodes" && <DisplayBackupCodes backupCodes={backupCodes} setOpen={resetState} />}
</Modal>
);
};

View File

@@ -1,11 +1,12 @@
import { AccountSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(account)/components/AccountSettingsNavbar";
import { AccountSecurity } from "@/app/(app)/environments/[environmentId]/settings/(account)/profile/components/AccountSecurity";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getIsTwoFactorAuthEnabled } from "@/modules/ee/license-check/lib/utils";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { SettingsId } from "@/modules/ui/components/settings-id";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
@@ -16,6 +17,7 @@ import { EditProfileAvatarForm } from "./components/EditProfileAvatarForm";
import { EditProfileDetailsForm } from "./components/EditProfileDetailsForm";
const Page = async (props: { params: Promise<{ environmentId: string }> }) => {
const isTwoFactorAuthEnabled = await getIsTwoFactorAuthEnabled();
const params = await props.params;
const t = await getTranslations();
const { environmentId } = params;
@@ -65,7 +67,11 @@ const Page = async (props: { params: Promise<{ environmentId: string }> }) => {
<SettingsCard
title={t("common.security")}
description={t("environments.settings.profile.security_description")}>
<AccountSecurity user={user} />
<AccountSecurity
user={user}
isTwoFactorAuthEnabled={isTwoFactorAuthEnabled}
environmentId={environmentId}
/>
</SettingsCard>
)}

View File

@@ -1,4 +1,5 @@
import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getEnterpriseLicense, getRoleManagementPermission } from "@/modules/ee/license-check/lib/utils";
import { Button } from "@/modules/ui/components/button";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
@@ -7,7 +8,6 @@ import { CheckIcon } from "lucide-react";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { notFound } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";

View File

@@ -275,6 +275,7 @@ export const inviteUserAction = authenticatedActionClient
name: parsedInput.name,
role: parsedInput.role,
},
currentUserId: ctx.user.id,
});
if (invite) {

View File

@@ -2,6 +2,7 @@ import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmen
import { AIToggle } from "@/app/(app)/environments/[environmentId]/settings/(organization)/general/components/AIToggle";
import { OrganizationActions } from "@/app/(app)/environments/[environmentId]/settings/(organization)/general/components/EditMemberships/OrganizationActions";
import { getMembershipsByUserId } from "@/app/(app)/environments/[environmentId]/settings/(organization)/general/lib/membership";
import { authOptions } from "@/modules/auth/lib/authOptions";
import {
getIsMultiOrgEnabled,
getIsOrganizationAIReady,
@@ -13,7 +14,6 @@ import { SettingsId } from "@/modules/ui/components/settings-id";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { Suspense } from "react";
import { authOptions } from "@formbricks/lib/authOptions";
import { INVITE_DISABLED, IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";

View File

@@ -1,6 +1,6 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";

View File

@@ -1,6 +1,6 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { Metadata } from "next";
import { getServerSession } from "next-auth";
import { authOptions } from "@formbricks/lib/authOptions";
import { getResponseCountBySurveyId } from "@formbricks/lib/response/service";
import { getSurvey } from "@formbricks/lib/survey/service";

View File

@@ -3,6 +3,7 @@ import { ResponsePage } from "@/app/(app)/environments/[environmentId]/surveys/[
import { EnableInsightsBanner } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/EnableInsightsBanner";
import { SurveyAnalysisCTA } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SurveyAnalysisCTA";
import { needsInsightsGeneration } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/utils";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getIsAIEnabled } from "@/modules/ee/license-check/lib/utils";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
@@ -10,7 +11,6 @@ import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper
import { PageHeader } from "@/modules/ui/components/page-header";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { authOptions } from "@formbricks/lib/authOptions";
import {
MAX_RESPONSES_FOR_INSIGHT_GENERATION,
RESPONSES_PER_PAGE,

View File

@@ -3,6 +3,7 @@ import { EnableInsightsBanner } from "@/app/(app)/environments/[environmentId]/s
import { SummaryPage } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryPage";
import { SurveyAnalysisCTA } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SurveyAnalysisCTA";
import { needsInsightsGeneration } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/utils";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getIsAIEnabled } from "@/modules/ee/license-check/lib/utils";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
@@ -12,7 +13,6 @@ import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { notFound } from "next/navigation";
import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
import { authOptions } from "@formbricks/lib/authOptions";
import {
DEFAULT_LOCALE,
DOCUMENTS_PER_PAGE,

View File

@@ -1,4 +1,5 @@
import { SurveysList } from "@/app/(app)/environments/[environmentId]/surveys/components/SurveyList";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getProductPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
import { TemplateList } from "@/modules/surveys/components/TemplateList";
@@ -10,7 +11,6 @@ import { Metadata } from "next";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { SURVEYS_PER_PAGE, WEBAPP_URL } from "@formbricks/lib/constants";
import { getEnvironment, getEnvironments } from "@formbricks/lib/environment/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";

View File

@@ -1,10 +1,10 @@
import { FormbricksClient } from "@/app/(app)/components/FormbricksClient";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { NoMobileOverlay } from "@/modules/ui/components/no-mobile-overlay";
import { PHProvider, PostHogPageview } from "@/modules/ui/components/post-hog-client";
import { ToasterClient } from "@/modules/ui/components/toaster-client";
import { getServerSession } from "next-auth";
import { Suspense } from "react";
import { authOptions } from "@formbricks/lib/authOptions";
import { getUser } from "@formbricks/lib/user/service";
const AppLayout = async ({ children }) => {

View File

@@ -1,77 +0,0 @@
"use client";
import { Button } from "@/modules/ui/components/button";
import { XCircleIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { forgotPassword } from "@formbricks/lib/utils/users";
export const PasswordResetForm = ({}) => {
const router = useRouter();
const [error, setError] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const t = useTranslations();
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
try {
await forgotPassword(e.target.elements.email.value);
router.push("/auth/forgot-password/email-sent");
} catch (e) {
setError(e.message);
} finally {
setLoading(false);
}
};
return (
<>
{error && (
<div className="absolute top-10 rounded-md bg-red-50 p-4">
<div className="flex">
<div className="flex-shrink-0">
<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800">
{t("auth.forgot-password.an_error_occurred_when_logging")}
</h3>
<div className="mt-2 text-sm text-red-700">
<p className="space-y-1 whitespace-pre-wrap">{error}</p>
</div>
</div>
</div>
</div>
)}
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label htmlFor="email" className="block text-sm font-medium text-slate-800">
{t("common.email")}
</label>
<div className="mt-1">
<input
id="email"
name="email"
type="email"
autoComplete="email"
required
className="focus:border-brand-dark focus:ring-brand-dark block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
/>
</div>
</div>
<div>
<Button type="submit" className="w-full justify-center" loading={loading}>
{t("auth.forgot-password.reset_password")}
</Button>
<div className="mt-3 text-center">
<Button variant="minimal" href="/auth/login" className="w-full justify-center">
{t("auth.forgot-password.back_to_login")}
</Button>
</div>
</div>
</form>
</>
);
};

View File

@@ -1,22 +1,3 @@
import { BackToLoginButton } from "@/app/(auth)/auth/components/BackToLoginButton";
import { FormWrapper } from "@/app/(auth)/auth/components/FormWrapper";
import { getTranslations } from "next-intl/server";
import { EmailSentPage } from "@/modules/auth/forgot-password/email-sent/page";
const Page = async () => {
const t = await getTranslations();
return (
<FormWrapper>
<div>
<h1 className="leading-2 mb-4 text-center font-bold">
{t("auth.forgot-password.email-sent.heading")}
</h1>
<p className="text-center">{t("auth.forgot-password.email-sent.text")}</p>
<div className="mt-5 text-center">
<BackToLoginButton />
</div>
</div>
</FormWrapper>
);
};
export default Page;
export default EmailSentPage;

View File

@@ -1,11 +1,3 @@
import { FormWrapper } from "@/app/(auth)/auth/components/FormWrapper";
import { PasswordResetForm } from "@/app/(auth)/auth/forgot-password/components/PasswordResetForm";
import { ForgotPasswordPage } from "@/modules/auth/forgot-password/page";
const Page = () => {
return (
<FormWrapper>
<PasswordResetForm />
</FormWrapper>
);
};
export default Page;
export default ForgotPasswordPage;

View File

@@ -1,106 +0,0 @@
"use client";
import { IsPasswordValid } from "@/modules/auth/components/SignupOptions/components/IsPasswordValid";
import { Button } from "@/modules/ui/components/button";
import { PasswordInput } from "@/modules/ui/components/password-input";
import { XCircleIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useRouter, useSearchParams } from "next/navigation";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { resetPassword } from "@formbricks/lib/utils/users";
export const ResetPasswordForm = () => {
const t = useTranslations();
const searchParams = useSearchParams();
const router = useRouter();
const [error, setError] = useState<string>("");
const [password, setPassword] = useState<string | null>(null);
const [confirmPassword, setConfirmPassword] = useState<string | null>(null);
const [isValid, setIsValid] = useState(false);
const [loading, setLoading] = useState<boolean>(false);
const handleSubmit = async (e) => {
e.preventDefault();
if (password !== confirmPassword) {
toast.error(t("auth.forgot-password.reset.passwords_do_not_match"));
return;
}
setLoading(true);
const token = searchParams?.get("token");
try {
if (!token) throw new Error(t("auth.forgot-password.reset.no_token_provided"));
await resetPassword(token, e.target.elements.password.value);
router.push("/auth/forgot-password/reset/success");
} catch (e) {
setError(e.message);
} finally {
setLoading(false);
}
};
return (
<>
{error && (
<div className="absolute top-10 rounded-md bg-red-50 p-4">
<div className="flex">
<div className="flex-shrink-0">
<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800">
{t("auth.forgot-password.an_error_occurred_when_logging_you_in")}
</h3>
<div className="mt-2 text-sm text-red-700">
<p className="space-y-1 whitespace-pre-wrap">{error}</p>
</div>
</div>
</div>
</div>
)}
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-slate-800">
{t("auth.forgot-password.reset.new_password")}
</label>
<PasswordInput
id="password"
name="password"
value={password ?? ""}
onChange={(e) => setPassword(e.target.value)}
autoComplete="current-password"
placeholder="*******"
required
className="focus:border-brand-dark focus:ring-brand-dark mt-2 block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-slate-800">
{t("auth.forgot-password.reset.confirm_password")}
</label>
<PasswordInput
id="confirmPassword"
name="confirmPassword"
value={confirmPassword ?? ""}
onChange={(e) => setConfirmPassword(e.target.value)}
autoComplete="current-password"
placeholder="*******"
required
className="focus:border-brand-dark focus:ring-brand-dark mt-2 block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
/>
</div>
<IsPasswordValid password={password} setIsValid={setIsValid} />
</div>
<div>
<Button type="submit" disabled={!isValid} className="w-full justify-center" loading={loading}>
{t("auth.forgot-password.reset_password")}
</Button>
</div>
</form>
</>
);
};

View File

@@ -1,11 +1,3 @@
import { FormWrapper } from "@/app/(auth)/auth/components/FormWrapper";
import { ResetPasswordForm } from "@/app/(auth)/auth/forgot-password/reset/components/ResetPasswordForm";
import { ResetPasswordPage } from "@/modules/auth/forgot-password/reset/page";
const Page = () => {
return (
<FormWrapper>
<ResetPasswordForm />
</FormWrapper>
);
};
export default Page;
export default ResetPasswordPage;

View File

@@ -1,22 +1,3 @@
import { BackToLoginButton } from "@/app/(auth)/auth/components/BackToLoginButton";
import { FormWrapper } from "@/app/(auth)/auth/components/FormWrapper";
import { getTranslations } from "next-intl/server";
import { ResetPasswordSuccessPage } from "@/modules/auth/forgot-password/reset/success/page";
const Page = async () => {
const t = await getTranslations();
return (
<FormWrapper>
<div>
<h1 className="leading-2 mb-4 text-center font-bold">
{t("auth.forgot-password.reset.success.heading")}
</h1>
<p className="text-center">{t("auth.forgot-password.reset.success.text")}</p>
<div className="mt-3 text-center">
<BackToLoginButton />
</div>
</div>
</FormWrapper>
);
};
export default Page;
export default ResetPasswordSuccessPage;

View File

@@ -1,31 +1,3 @@
import { getIsMultiOrgEnabled } from "@/modules/ee/license-check/lib/utils";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { Toaster } from "react-hot-toast";
import { authOptions } from "@formbricks/lib/authOptions";
import { getIsFreshInstance } from "@formbricks/lib/instance/service";
const AuthLayout = async ({ children }: { children: React.ReactNode }) => {
const session = await getServerSession(authOptions);
const isFreshInstance = await getIsFreshInstance();
const isMultiOrgEnabled = await getIsMultiOrgEnabled();
if (session) {
redirect(`/`);
}
if (isFreshInstance && !isMultiOrgEnabled) {
redirect("/setup/intro");
}
return (
<>
<Toaster />
<div className="min-h-screen bg-slate-50">
<div className="isolate bg-white">
<div className="bg-gradient-radial flex min-h-screen from-slate-200 to-slate-50">{children}</div>
</div>
</div>
</>
);
};
import { AuthLayout } from "@/modules/auth/layout";
export default AuthLayout;

View File

@@ -1,48 +1,3 @@
import { FormWrapper } from "@/app/(auth)/auth/components/FormWrapper";
import { Testimonial } from "@/app/(auth)/auth/components/Testimonial";
import { SigninForm } from "@/modules/auth/components/SigninForm";
import { getIsMultiOrgEnabled } from "@/modules/ee/license-check/lib/utils";
import { Metadata } from "next";
import {
AZURE_OAUTH_ENABLED,
EMAIL_AUTH_ENABLED,
GITHUB_OAUTH_ENABLED,
GOOGLE_OAUTH_ENABLED,
OIDC_DISPLAY_NAME,
OIDC_OAUTH_ENABLED,
PASSWORD_RESET_DISABLED,
SIGNUP_ENABLED,
} from "@formbricks/lib/constants";
import { LoginPage } from "@/modules/auth/login/page";
export const metadata: Metadata = {
title: "Login",
description: "Open-source Experience Management. Free & open source.",
};
const Page = async () => {
const isMultiOrgEnabled = await getIsMultiOrgEnabled();
return (
<div className="grid min-h-screen w-full bg-gradient-to-tr from-slate-100 to-slate-50 lg:grid-cols-5">
<div className="col-span-2 hidden lg:flex">
<Testimonial />
</div>
<div className="col-span-3 flex flex-col items-center justify-center">
<FormWrapper>
<SigninForm
emailAuthEnabled={EMAIL_AUTH_ENABLED}
publicSignUpEnabled={SIGNUP_ENABLED}
passwordResetEnabled={!PASSWORD_RESET_DISABLED}
googleOAuthEnabled={GOOGLE_OAUTH_ENABLED}
githubOAuthEnabled={GITHUB_OAUTH_ENABLED}
azureOAuthEnabled={AZURE_OAUTH_ENABLED}
oidcOAuthEnabled={OIDC_OAUTH_ENABLED}
oidcDisplayName={OIDC_DISPLAY_NAME}
isMultiOrgEnabled={isMultiOrgEnabled}
/>
</FormWrapper>
</div>
</div>
);
};
export default Page;
export default LoginPage;

View File

@@ -1,21 +1,3 @@
import { BackToLoginButton } from "@/app/(auth)/auth/components/BackToLoginButton";
import { FormWrapper } from "@/app/(auth)/auth/components/FormWrapper";
import { getTranslations } from "next-intl/server";
import { SignupWithoutVerificationSuccessPage } from "@/modules/auth/signup-without-verification-success/page";
const Page = async () => {
const t = await getTranslations();
return (
<FormWrapper>
<h1 className="leading-2 mb-4 text-center font-bold">
{t("auth.signup_without_verification_success.user_successfully_created")}
</h1>
<p className="text-center text-sm">
{t("auth.signup_without_verification_success.user_successfully_created_description")}
</p>
<hr className="my-4" />
<BackToLoginButton />
</FormWrapper>
);
};
export default Page;
export default SignupWithoutVerificationSuccessPage;

View File

@@ -1,115 +0,0 @@
"use client";
import { SignupOptions } from "@/modules/auth/components/SignupOptions";
import { XCircleIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { useMemo, useState } from "react";
interface SignupFormProps {
webAppUrl: string;
privacyUrl: string | undefined;
termsUrl: string | undefined;
emailVerificationDisabled: boolean;
emailAuthEnabled: boolean;
googleOAuthEnabled: boolean;
githubOAuthEnabled: boolean;
azureOAuthEnabled: boolean;
oidcOAuthEnabled: boolean;
oidcDisplayName?: string;
userLocale: string;
}
export const SignupForm = ({
webAppUrl,
privacyUrl,
termsUrl,
emailVerificationDisabled,
emailAuthEnabled,
googleOAuthEnabled,
githubOAuthEnabled,
azureOAuthEnabled,
oidcOAuthEnabled,
oidcDisplayName,
userLocale,
}: SignupFormProps) => {
const searchParams = useSearchParams();
const [error, setError] = useState<string>("");
const t = useTranslations();
const inviteToken = searchParams?.get("inviteToken");
const callbackUrl = useMemo(() => {
if (inviteToken) {
return webAppUrl + "/invite?token=" + inviteToken;
} else {
return webAppUrl;
}
}, [inviteToken, webAppUrl]);
return (
<>
{error && (
<div className="absolute top-10 rounded-md bg-red-50 p-4">
<div className="flex">
<div className="flex-shrink-0">
<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800">{t("auth.signup.error")}</h3>
<div className="mt-2 text-sm text-red-700">
<p className="space-y-1 whitespace-pre-wrap">{error}</p>
</div>
</div>
</div>
</div>
)}
<div className="text-center">
<h1 className="mb-4 text-slate-700">{t("auth.signup.title")}</h1>
<SignupOptions
emailAuthEnabled={emailAuthEnabled}
emailFromSearchParams={searchParams?.get("email") || ""}
setError={setError}
emailVerificationDisabled={emailVerificationDisabled}
googleOAuthEnabled={googleOAuthEnabled}
githubOAuthEnabled={githubOAuthEnabled}
azureOAuthEnabled={azureOAuthEnabled}
oidcOAuthEnabled={oidcOAuthEnabled}
inviteToken={inviteToken}
callbackUrl={callbackUrl}
oidcDisplayName={oidcDisplayName}
userLocale={userLocale}
/>
{(termsUrl || privacyUrl) && (
<div className="mt-3 text-center text-xs text-slate-500">
{t("auth.signup.terms_of_service")}
<br />
{termsUrl && (
<Link className="font-semibold" href={termsUrl} rel="noreferrer" target="_blank">
{t("auth.signup.terms_of_service")}
</Link>
)}
{termsUrl && privacyUrl && <span> {t("common.and")} </span>}
{privacyUrl && (
<Link className="font-semibold" href={privacyUrl} rel="noreferrer" target="_blank">
{t("auth.signup.privacy_policy")}
</Link>
)}
{/* <br />
We&apos;ll occasionally send you account related emails. */}
<hr className="mx-6 mt-3"></hr>
</div>
)}
<div className="mt-9 text-center text-xs">
<span className="leading-5 text-slate-500">{t("auth.signup.have_an_account")}</span>
<br />
<Link
href={inviteToken ? `/auth/login?callbackUrl=${callbackUrl}` : "/auth/login"}
className="font-semibold text-slate-600 underline hover:text-slate-700">
{t("auth.signup.log_in")}
</Link>
</div>
</div>
</>
);
};

View File

@@ -1,56 +1,3 @@
import { FormWrapper } from "@/app/(auth)/auth/components/FormWrapper";
import { Testimonial } from "@/app/(auth)/auth/components/Testimonial";
import { SignupForm } from "@/app/(auth)/auth/signup/components/SignupForm";
import { getIsMultiOrgEnabled } from "@/modules/ee/license-check/lib/utils";
import { notFound } from "next/navigation";
import {
AZURE_OAUTH_ENABLED,
EMAIL_AUTH_ENABLED,
EMAIL_VERIFICATION_DISABLED,
GITHUB_OAUTH_ENABLED,
GOOGLE_OAUTH_ENABLED,
OIDC_DISPLAY_NAME,
OIDC_OAUTH_ENABLED,
PRIVACY_URL,
SIGNUP_ENABLED,
TERMS_URL,
WEBAPP_URL,
} from "@formbricks/lib/constants";
import { findMatchingLocale } from "@formbricks/lib/utils/locale";
import { SignupPage } from "@/modules/auth/signup/page";
const Page = async (props: { searchParams: Promise<{ [key: string]: string | string[] | undefined }> }) => {
const searchParams = await props.searchParams;
const inviteToken = searchParams["inviteToken"] ?? null;
const isMultOrgEnabled = await getIsMultiOrgEnabled();
const locale = await findMatchingLocale();
if (!inviteToken && (!SIGNUP_ENABLED || !isMultOrgEnabled)) {
notFound();
}
return (
<div className="grid min-h-screen w-full bg-gradient-to-tr from-slate-100 to-slate-50 lg:grid-cols-5">
<div className="col-span-2 hidden lg:flex">
<Testimonial />
</div>
<div className="col-span-3 flex flex-col items-center justify-center">
<FormWrapper>
<SignupForm
webAppUrl={WEBAPP_URL}
termsUrl={TERMS_URL}
privacyUrl={PRIVACY_URL}
emailVerificationDisabled={EMAIL_VERIFICATION_DISABLED}
emailAuthEnabled={EMAIL_AUTH_ENABLED}
googleOAuthEnabled={GOOGLE_OAUTH_ENABLED}
githubOAuthEnabled={GITHUB_OAUTH_ENABLED}
azureOAuthEnabled={AZURE_OAUTH_ENABLED}
oidcOAuthEnabled={OIDC_OAUTH_ENABLED}
oidcDisplayName={OIDC_DISPLAY_NAME}
userLocale={locale}
/>
</FormWrapper>
</div>
</div>
);
};
export default Page;
export default SignupPage;

View File

@@ -1,46 +1,3 @@
import { FormWrapper } from "@/app/(auth)/auth/components/FormWrapper";
import { RequestVerificationEmail } from "@/app/(auth)/auth/verification-requested/components/RequestVerificationEmail";
import { getTranslations } from "next-intl/server";
import { z } from "zod";
import { getEmailFromEmailToken } from "@formbricks/lib/jwt";
import { VerificationRequestedPage } from "@/modules/auth/verification-requested/page";
const VerificationPageSchema = z.string().email();
const Page = async (props) => {
const searchParams = await props.searchParams;
const t = await getTranslations();
const email = getEmailFromEmailToken(searchParams.token);
try {
const parsedEmail = VerificationPageSchema.parse(email).toLowerCase();
return (
<FormWrapper>
<>
<h1 className="leading-2 mb-4 text-center text-lg font-semibold text-slate-900">
{t("auth.verification-requested.please_confirm_your_email_address")}
</h1>
<p className="text-center text-sm text-slate-700">
{t.rich("auth.verification-requested.we_sent_an_email_to", {
email: () => <span className="font-semibold italic">{email}</span>,
})}
{t("auth.verification-requested.please_click_the_link_in_the_email_to_activate_your_account")}
</p>
<hr className="my-4" />
<p className="text-center text-xs text-slate-500">
{t("auth.verification-requested.you_didnt_receive_an_email_or_your_link_expired")}
</p>
<div className="mt-5">
<RequestVerificationEmail email={parsedEmail} />
</div>
</>
</FormWrapper>
);
} catch (error) {
return (
<FormWrapper>
<p className="text-center">{t("auth.verification-requested.invalid_email_address")}</p>
</FormWrapper>
);
}
};
export default Page;
export default VerificationRequestedPage;

View File

@@ -1,18 +1,3 @@
import { FormWrapper } from "@/app/(auth)/auth/components/FormWrapper";
import { SignIn } from "@/app/(auth)/auth/verify/components/SignIn";
import { getTranslations } from "next-intl/server";
import { VerifyPage } from "@/modules/auth/verify/page";
const Page = async (props) => {
const searchParams = await props.searchParams;
const t = await getTranslations();
return searchParams && searchParams.token ? (
<FormWrapper>
<p className="text-center">{t("auth.verify.verifying")}</p>
<SignIn token={searchParams.token} />
</FormWrapper>
) : (
<p className="text-center">{t("auth.verify.no_token_provided")}</p>
);
};
export default Page;
export default VerifyPage;

View File

@@ -1,8 +1,8 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { sendInviteAcceptedEmail } from "@/modules/email";
import { Button } from "@/modules/ui/components/button";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { DEFAULT_LOCALE, WEBAPP_URL } from "@formbricks/lib/constants";
import { deleteInvite, getInvite } from "@formbricks/lib/invite/service";
import { verifyInviteToken } from "@formbricks/lib/jwt";

View File

@@ -1,8 +1,8 @@
import { hasOrganizationAccess } from "@/app/lib/api/apiHelper";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { notFound } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getEnvironments } from "@formbricks/lib/environment/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";

View File

@@ -1,7 +1,7 @@
import { hasOrganizationAccess } from "@/app/lib/api/apiHelper";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { notFound, redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getEnvironments } from "@formbricks/lib/environment/service";
import { getProduct } from "@formbricks/lib/product/service";
import { AuthenticationError, AuthorizationError } from "@formbricks/types/errors";

View File

@@ -1,8 +1,8 @@
import { responses } from "@/app/lib/api/response";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { AsyncParser } from "@json2csv/node";
import { getServerSession } from "next-auth";
import { NextRequest } from "next/server";
import { authOptions } from "@formbricks/lib/authOptions";
export const POST = async (request: NextRequest) => {
const session = await getServerSession(authOptions);

View File

@@ -1,8 +1,8 @@
import { responses } from "@/app/lib/api/response";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { NextRequest } from "next/server";
import * as xlsx from "xlsx";
import { authOptions } from "@formbricks/lib/authOptions";
export const POST = async (request: NextRequest) => {
const session = await getServerSession(authOptions);

View File

@@ -1,5 +1,5 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import NextAuth from "next-auth";
import { authOptions } from "@formbricks/lib/authOptions";
export const fetchCache = "force-no-store";

View File

@@ -1,8 +1,8 @@
import { responses } from "@/app/lib/api/response";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { google } from "googleapis";
import { getServerSession } from "next-auth";
import { NextRequest } from "next/server";
import { authOptions } from "@formbricks/lib/authOptions";
import {
GOOGLE_SHEETS_CLIENT_ID,
GOOGLE_SHEETS_CLIENT_SECRET,

View File

@@ -1,9 +1,9 @@
import { responses } from "@/app/lib/api/response";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { NextRequest } from "next/server";
import * as z from "zod";
import { fetchAirtableAuthToken } from "@formbricks/lib/airtable/service";
import { authOptions } from "@formbricks/lib/authOptions";
import { AIRTABLE_CLIENT_ID, WEBAPP_URL } from "@formbricks/lib/constants";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { createOrUpdateIntegration } from "@formbricks/lib/integration/service";

View File

@@ -1,8 +1,8 @@
import { responses } from "@/app/lib/api/response";
import { authOptions } from "@/modules/auth/lib/authOptions";
import crypto from "crypto";
import { getServerSession } from "next-auth";
import { NextRequest } from "next/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { AIRTABLE_CLIENT_ID, WEBAPP_URL } from "@formbricks/lib/constants";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";

View File

@@ -1,9 +1,9 @@
import { responses } from "@/app/lib/api/response";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { NextRequest } from "next/server";
import * as z from "zod";
import { getTables } from "@formbricks/lib/airtable/service";
import { authOptions } from "@formbricks/lib/authOptions";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { getIntegrationByType } from "@formbricks/lib/integration/service";
import { TIntegrationAirtable } from "@formbricks/types/integration/airtable";

View File

@@ -1,7 +1,7 @@
import { responses } from "@/app/lib/api/response";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { NextRequest } from "next/server";
import { authOptions } from "@formbricks/lib/authOptions";
import {
NOTION_AUTH_URL,
NOTION_OAUTH_CLIENT_ID,

View File

@@ -1,7 +1,7 @@
import { responses } from "@/app/lib/api/response";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { NextRequest } from "next/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { SLACK_AUTH_URL, SLACK_CLIENT_ID, SLACK_CLIENT_SECRET } from "@formbricks/lib/constants";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";

View File

@@ -2,10 +2,10 @@
// body -> should be a valid file object (buffer)
// method -> PUT (to be the same as the signedUrl method)
import { responses } from "@/app/lib/api/response";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { headers } from "next/headers";
import { NextRequest } from "next/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { ENCRYPTION_KEY, UPLOADS_DIR } from "@formbricks/lib/constants";
import { validateLocalSignedUrl } from "@formbricks/lib/crypto";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";

View File

@@ -1,7 +1,7 @@
import { responses } from "@/app/lib/api/response";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { NextRequest } from "next/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { getSignedUrlForPublicFile } from "./lib/getSignedUrl";

View File

@@ -1,28 +0,0 @@
import { sendForgotPasswordEmail } from "@/modules/email";
import { prisma } from "@formbricks/database";
export const POST = async (request: Request) => {
const { email } = await request.json();
try {
const foundUser = await prisma.user.findUnique({
where: {
email: email.toLowerCase(),
},
});
if (foundUser) {
await sendForgotPasswordEmail(foundUser, foundUser.locale);
}
return Response.json({});
} catch (e) {
return Response.json(
{
error: e.message,
errorCode: e.code,
},
{ status: 500 }
);
}
};

View File

@@ -1,146 +0,0 @@
import { getSessionUser } from "@/app/lib/api/apiHelper";
import { OrganizationRole } from "@prisma/client";
import { NextRequest } from "next/server";
import { prisma } from "@formbricks/database";
import { TOrganizationRole } from "@formbricks/types/memberships";
interface Membership {
role: TOrganizationRole;
userId: string;
}
export const GET = async () => {
const sessionUser = await getSessionUser();
if (!sessionUser) {
return new Response("Not authenticated", {
status: 401,
});
}
const user = await prisma.user.findUnique({
where: {
id: sessionUser.id,
},
});
return Response.json(user);
};
export const PUT = async (request: NextRequest) => {
const sessionUser = await getSessionUser();
if (!sessionUser) {
return new Response("Not authenticated", {
status: 401,
});
}
const body = await request.json();
const user = await prisma.user.update({
where: {
id: sessionUser.id,
},
data: body,
});
return Response.json(user);
};
const deleteUser = async (userId: string) => {
await prisma.user.delete({
where: {
id: userId,
},
});
};
const updateUserMembership = async (organizationId: string, userId: string, role: OrganizationRole) => {
await prisma.membership.update({
where: {
userId_organizationId: {
userId,
organizationId,
},
},
data: {
role,
},
});
};
const getManagerMemberships = (memberships: Membership[]) =>
memberships.filter((membership) => membership.role === OrganizationRole.manager);
const getOwnerMemberships = (memberships: Membership[]) =>
memberships.filter((membership) => membership.role === OrganizationRole.owner);
const deleteOrganization = async (organizationId: string) => {
await prisma.organization.delete({
where: {
id: organizationId,
},
});
};
export const DELETE = async () => {
try {
const currentUser = await getSessionUser();
if (!currentUser) {
return new Response("Not authenticated", {
status: 401,
});
}
const currentUserMemberships = await prisma.membership.findMany({
where: {
userId: currentUser.id,
},
include: {
organization: {
select: {
id: true,
name: true,
memberships: {
select: {
userId: true,
role: true,
},
},
},
},
},
});
for (const currentUserMembership of currentUserMemberships) {
const organizationMemberships = currentUserMembership.organization.memberships;
const role = currentUserMembership.role;
const organizationId = currentUserMembership.organizationId;
const organizationManagerMemberships = getManagerMemberships(organizationMemberships);
const organizationOwnerMemberships = getOwnerMemberships(organizationMemberships);
const organizationHasAtLeastOneManager = organizationManagerMemberships.length > 0;
const organizationHasOnlyOneMember = organizationMemberships.length === 1;
const organizationHasMoreThanOneOwner = organizationOwnerMemberships.length > 1;
const currentUserIsOrganizationOwner = role === OrganizationRole.owner;
if (organizationHasOnlyOneMember) {
await deleteOrganization(organizationId);
} else if (
currentUserIsOrganizationOwner &&
organizationHasAtLeastOneManager &&
!organizationHasMoreThanOneOwner
) {
const firstManager = organizationManagerMemberships[0];
await updateUserMembership(organizationId, firstManager.userId, OrganizationRole.owner);
} else if (currentUserIsOrganizationOwner) {
await deleteOrganization(organizationId);
}
}
await deleteUser(currentUser.id);
return Response.json({ deletedUser: currentUser }, { status: 200 });
} catch (error) {
return Response.json({ message: error.message }, { status: 500 });
}
};

View File

@@ -1,38 +0,0 @@
import { sendPasswordResetNotifyEmail } from "@/modules/email";
import { prisma } from "@formbricks/database";
import { verifyToken } from "@formbricks/lib/jwt";
export const POST = async (request: Request) => {
const { token, hashedPassword } = await request.json();
try {
const { id } = await verifyToken(token);
const user = await prisma.user.findUnique({
where: {
id,
},
select: {
id: true,
email: true,
locale: true,
},
});
if (!user) {
return Response.json({ error: "Invalid token provided or no longer valid" }, { status: 409 });
}
await prisma.user.update({
where: { id: user.id },
data: { password: hashedPassword },
});
await sendPasswordResetNotifyEmail(user);
return Response.json({});
} catch (e) {
return Response.json(
{
error: e.message,
errorCode: e.code,
},
{ status: 500 }
);
}
};

View File

@@ -1,163 +0,0 @@
import { getIsMultiOrgEnabled } from "@/modules/ee/license-check/lib/utils";
import { sendInviteAcceptedEmail, sendVerificationEmail } from "@/modules/email";
import { prisma } from "@formbricks/database";
import {
DEFAULT_ORGANIZATION_ID,
DEFAULT_ORGANIZATION_ROLE,
EMAIL_AUTH_ENABLED,
EMAIL_VERIFICATION_DISABLED,
INVITE_DISABLED,
SIGNUP_ENABLED,
} from "@formbricks/lib/constants";
import { getIsFreshInstance } from "@formbricks/lib/instance/service";
import { deleteInvite } from "@formbricks/lib/invite/service";
import { verifyInviteToken } from "@formbricks/lib/jwt";
import { createMembership } from "@formbricks/lib/membership/service";
import { createOrganization, getOrganization } from "@formbricks/lib/organization/service";
import { createUser, updateUser } from "@formbricks/lib/user/service";
export const POST = async (request: Request) => {
let { inviteToken, ...user } = await request.json();
const isMultiOrgEnabled = await getIsMultiOrgEnabled();
const isFreshInstance = await getIsFreshInstance();
if (
!isFreshInstance &&
(!EMAIL_AUTH_ENABLED || inviteToken ? INVITE_DISABLED : !SIGNUP_ENABLED || !isMultiOrgEnabled)
) {
return Response.json({ error: "Signup disabled" }, { status: 403 });
}
let inviteId;
try {
let invite;
let isInviteValid = false;
if (inviteToken) {
let inviteTokenData = await verifyInviteToken(inviteToken);
inviteId = inviteTokenData?.inviteId;
invite = await prisma.invite.findUnique({
where: { id: inviteId },
include: {
creator: true,
},
});
if (!invite) {
return Response.json({ error: "Invalid invite ID" }, { status: 400 });
}
isInviteValid = true;
}
user = {
...user,
...{ email: user.email.toLowerCase() },
};
// create the user
user = await createUser(user);
// User is invited to organization
if (isInviteValid) {
// assign user to existing organization
await createMembership(invite.organizationId, user.id, {
accepted: true,
role: invite.role,
});
await updateUser(user.id, {
notificationSettings: {
alert: {},
weeklySummary: {},
unsubscribedOrganizationIds: [invite.organizationId],
},
});
if (!EMAIL_VERIFICATION_DISABLED) {
await sendVerificationEmail(user);
}
await sendInviteAcceptedEmail(invite.creator.name, user.name, invite.creator.email, user.locale);
await deleteInvite(inviteId);
return Response.json(user);
}
// User signs up without invite
// Default organization assignment is enabled
if (DEFAULT_ORGANIZATION_ID && DEFAULT_ORGANIZATION_ID.length > 0) {
// check if organization exists
let organization = await getOrganization(DEFAULT_ORGANIZATION_ID);
let isNewOrganization = false;
if (!organization) {
// create organization with id from env
organization = await createOrganization({
id: DEFAULT_ORGANIZATION_ID,
name: user.name + "'s Organization",
});
isNewOrganization = true;
}
const role = isNewOrganization ? "owner" : DEFAULT_ORGANIZATION_ROLE || "owner";
await createMembership(organization.id, user.id, { role: role, accepted: true });
const updatedNotificationSettings = {
...user.notificationSettings,
unsubscribedOrganizationIds: Array.from(
new Set([...(user.notificationSettings?.unsubscribedOrganizationIds || []), organization.id])
),
};
await updateUser(user.id, {
notificationSettings: updatedNotificationSettings,
});
}
// Without default organization assignment
else {
if (isMultiOrgEnabled) {
const organization = await createOrganization({ name: user.name + "'s Organization" });
await createMembership(organization.id, user.id, { role: "owner", accepted: true });
const updatedNotificationSettings = {
...user.notificationSettings,
alert: {
...user.notificationSettings?.alert,
},
weeklySummary: {
...user.notificationSettings?.weeklySummary,
},
unsubscribedOrganizationIds: Array.from(
new Set([...(user.notificationSettings?.unsubscribedOrganizationIds || []), organization.id])
),
};
await updateUser(user.id, {
notificationSettings: updatedNotificationSettings,
});
}
}
// send verification email amd return user
if (!EMAIL_VERIFICATION_DISABLED) {
await sendVerificationEmail(user);
}
return Response.json(user);
} catch (e) {
if (e.message === "User with this email already exists") {
return Response.json(
{
error: "user with this email address already exists",
errorCode: e.code,
},
{ status: 409 }
);
} else {
return Response.json(
{
error: e.message,
errorCode: e.code,
},
{ status: 500 }
);
}
}
};

View File

@@ -1,28 +0,0 @@
import { sendVerificationEmail } from "@/modules/email";
import { prisma } from "@formbricks/database";
export const POST = async (request: Request) => {
const { email } = await request.json();
// check for user in DB
try {
const user = await prisma.user.findUnique({
where: { email },
});
if (!user) {
return Response.json({ error: "No user with this email address found" }, { status: 404 });
}
if (user.emailVerified) {
return Response.json({ error: "Email address has already been verified" }, { status: 400 });
}
await sendVerificationEmail(user);
return Response.json(user);
} catch (e) {
return Response.json(
{
error: e.message,
errorCode: e.code,
},
{ status: 500 }
);
}
};

View File

@@ -1,9 +1,9 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { createHash } from "crypto";
import { NextApiRequest, NextApiResponse } from "next";
import type { Session } from "next-auth";
import { getServerSession } from "next-auth";
import { prisma } from "@formbricks/database";
import { authOptions } from "@formbricks/lib/authOptions";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
export const hashApiKey = (key: string): string => createHash("sha256").update(key).digest("hex");

View File

@@ -3,32 +3,27 @@ import {
CLIENT_SIDE_API_RATE_LIMIT,
FORGET_PASSWORD_RATE_LIMIT,
LOGIN_RATE_LIMIT,
RESET_PASSWORD_RATE_LIMIT,
SHARE_RATE_LIMIT,
SIGNUP_RATE_LIMIT,
SYNC_USER_IDENTIFICATION_RATE_LIMIT,
VERIFY_EMAIL_RATE_LIMIT,
} from "@formbricks/lib/constants";
export const signUpLimiter = rateLimit({
export const loginLimiter = rateLimit({
interval: LOGIN_RATE_LIMIT.interval,
allowedPerInterval: LOGIN_RATE_LIMIT.allowedPerInterval,
});
export const signupLimiter = rateLimit({
interval: SIGNUP_RATE_LIMIT.interval,
allowedPerInterval: SIGNUP_RATE_LIMIT.allowedPerInterval,
});
export const forgetPasswordLimiter = rateLimit({
interval: FORGET_PASSWORD_RATE_LIMIT.interval,
allowedPerInterval: FORGET_PASSWORD_RATE_LIMIT.allowedPerInterval,
});
export const resetPasswordLimiter = rateLimit({
interval: RESET_PASSWORD_RATE_LIMIT.interval,
allowedPerInterval: RESET_PASSWORD_RATE_LIMIT.allowedPerInterval,
});
export const verifyEmailLimiter = rateLimit({
interval: VERIFY_EMAIL_RATE_LIMIT.interval,
allowedPerInterval: VERIFY_EMAIL_RATE_LIMIT.allowedPerInterval,
});
export const loginLimiter = rateLimit({
interval: LOGIN_RATE_LIMIT.interval,
allowedPerInterval: LOGIN_RATE_LIMIT.allowedPerInterval,
export const forgotPasswordLimiter = rateLimit({
interval: FORGET_PASSWORD_RATE_LIMIT.interval,
allowedPerInterval: FORGET_PASSWORD_RATE_LIMIT.allowedPerInterval,
});
export const clientSideApiEndpointsLimiter = rateLimit({
interval: CLIENT_SIDE_API_RATE_LIMIT.interval,

View File

@@ -1,14 +1,12 @@
export const loginRoute = (url: string) => url === "/api/auth/callback/credentials";
export const isLoginRoute = (url: string) => url === "/api/auth/callback/credentials";
export const signupRoute = (url: string) => url === "/api/v1/users";
export const isSignupRoute = (url: string) => url === "/auth/signup";
export const resetPasswordRoute = (url: string) => url === "/api/v1/users/reset-password";
export const isVerifyEmailRoute = (url: string) => url === "/auth/verify-email";
export const forgetPasswordRoute = (url: string) => url === "/api/v1/users/forgot-password";
export const isForgotPasswordRoute = (url: string) => url === "/auth/forgot-password";
export const verifyEmailRoute = (url: string) => url === "/api/v1/users/verification-email";
export const clientSideApiRoute = (url: string): boolean => {
export const isClientSideApiRoute = (url: string): boolean => {
if (url.includes("/api/packages/")) return true;
if (url.includes("/api/v1/js/actions")) return true;
if (url.includes("/api/v1/client/storage")) return true;
@@ -16,7 +14,7 @@ export const clientSideApiRoute = (url: string): boolean => {
return regex.test(url);
};
export const shareUrlRoute = (url: string): boolean => {
export const isShareUrlRoute = (url: string): boolean => {
const regex = /\/share\/[A-Za-z0-9]+\/(summary|responses)/;
return regex.test(url);
};
@@ -27,6 +25,7 @@ export const isAuthProtectedRoute = (url: string): boolean => {
return protectedRoutes.some((route) => url.startsWith(route));
};
export const isSyncWithUserIdentificationEndpoint = (
url: string
): { environmentId: string; userId: string } | false => {

View File

@@ -1,10 +1,10 @@
import ClientEnvironmentRedirect from "@/app/ClientEnvironmentRedirect";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { ClientLogout } from "@/modules/ui/components/client-logout";
import type { Session } from "next-auth";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getFirstEnvironmentIdByUserId } from "@formbricks/lib/environment/service";
import { getIsFreshInstance } from "@formbricks/lib/instance/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";

View File

@@ -1,6 +1,6 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { notFound } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { getIsFreshInstance } from "@formbricks/lib/instance/service";
const FreshInstanceLayout = async ({ children }: { children: React.ReactNode }) => {

View File

@@ -1,14 +1,20 @@
import { SignupOptions } from "@/modules/auth/components/SignupOptions";
import { SignupForm } from "@/modules/auth/signup/components/signup-form";
import { getIsSSOEnabled } from "@/modules/ee/license-check/lib/utils";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import {
AZURE_OAUTH_ENABLED,
DEFAULT_ORGANIZATION_ID,
DEFAULT_ORGANIZATION_ROLE,
EMAIL_AUTH_ENABLED,
EMAIL_VERIFICATION_DISABLED,
GITHUB_OAUTH_ENABLED,
GOOGLE_OAUTH_ENABLED,
OIDC_DISPLAY_NAME,
OIDC_OAUTH_ENABLED,
PRIVACY_URL,
TERMS_URL,
WEBAPP_URL,
} from "@formbricks/lib/constants";
import { findMatchingLocale } from "@formbricks/lib/utils/locale";
@@ -19,24 +25,28 @@ export const metadata: Metadata = {
const Page = async () => {
const locale = await findMatchingLocale();
const isSSOEnabled = await getIsSSOEnabled();
const t = await getTranslations();
return (
<div className="flex flex-col items-center">
<h2 className="mb-6 text-xl font-medium">{t("setup.signup.create_administrator")}</h2>
<p className="text-sm text-slate-800">{t("setup.signup.this_user_has_all_the_power")}</p>
<hr className="my-6 w-full border-slate-200" />
<SignupOptions
emailAuthEnabled={EMAIL_AUTH_ENABLED}
emailFromSearchParams={""}
<SignupForm
webAppUrl={WEBAPP_URL}
termsUrl={TERMS_URL}
privacyUrl={PRIVACY_URL}
emailVerificationDisabled={EMAIL_VERIFICATION_DISABLED}
emailAuthEnabled={EMAIL_AUTH_ENABLED}
googleOAuthEnabled={GOOGLE_OAUTH_ENABLED}
githubOAuthEnabled={GITHUB_OAUTH_ENABLED}
azureOAuthEnabled={AZURE_OAUTH_ENABLED}
oidcOAuthEnabled={OIDC_OAUTH_ENABLED}
inviteToken={""}
callbackUrl={""}
oidcDisplayName={OIDC_DISPLAY_NAME}
userLocale={locale}
defaultOrganizationId={DEFAULT_ORGANIZATION_ID}
defaultOrganizationRole={DEFAULT_ORGANIZATION_ROLE}
isSSOEnabled={isSSOEnabled}
/>
</div>
);

View File

@@ -42,6 +42,7 @@ export const inviteOrganizationMemberAction = authenticatedActionClient
name: "",
role: "manager",
},
currentUserId: ctx.user.id,
});
if (invite) {

View File

@@ -1,9 +1,9 @@
import { InviteMembers } from "@/app/setup/organization/[organizationId]/invite/components/InviteMembers";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { Metadata } from "next";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { notFound } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { SMTP_HOST, SMTP_PASSWORD, SMTP_PORT, SMTP_USER } from "@formbricks/lib/constants";
import { verifyUserRoleAccess } from "@formbricks/lib/organization/auth";
import { AuthenticationError } from "@formbricks/types/errors";

View File

@@ -1,11 +1,11 @@
import { RemovedFromOrganization } from "@/app/setup/organization/create/components/RemovedFromOrganization";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getIsMultiOrgEnabled } from "@/modules/ee/license-check/lib/utils";
import { ClientLogout } from "@/modules/ui/components/client-logout";
import { Metadata } from "next";
import { getServerSession } from "next-auth";
import { getTranslations } from "next-intl/server";
import { notFound } from "next/navigation";
import { authOptions } from "@formbricks/lib/authOptions";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { gethasNoOrganizations } from "@formbricks/lib/instance/service";
import { getOrganizationsByUserId } from "@formbricks/lib/organization/service";

View File

@@ -2,9 +2,9 @@ import { authenticateRequest } from "@/app/api/v1/auth";
import { responses } from "@/app/lib/api/response";
import { transformErrorToDetails } from "@/app/lib/api/validator";
import { handleDeleteFile } from "@/app/storage/[environmentId]/[accessType]/[fileName]/lib/deleteFile";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { NextRequest } from "next/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
import { ZStorageRetrievalParams } from "@formbricks/types/storage";
import { getFile } from "./lib/getFile";

View File

@@ -1,6 +1,6 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { getRequestConfig } from "next-intl/server";
import { authOptions } from "@formbricks/lib/authOptions";
import { DEFAULT_LOCALE } from "@formbricks/lib/constants";
import { getUserLocale } from "@formbricks/lib/user/service";
import { findMatchingLocale } from "@formbricks/lib/utils/locale";

View File

@@ -1,6 +1,6 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getServerSession } from "next-auth";
import { DEFAULT_SERVER_ERROR_MESSAGE, createSafeActionClient } from "next-safe-action";
import { authOptions } from "@formbricks/lib/authOptions";
import { getUser } from "@formbricks/lib/user/service";
import {
AuthenticationError,
@@ -18,6 +18,7 @@ export const actionClient = createSafeActionClient({
e instanceof AuthorizationError ||
e instanceof InvalidInputError ||
e instanceof UnknownError ||
e instanceof AuthenticationError ||
e instanceof OperationNotAllowedError
) {
return e.message;

Some files were not shown because too many files have changed in this diff Show More