diff --git a/.env.example b/.env.example index fa432bbca4..2f722a67d2 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,6 @@ # ------------ MANDATORY (CHANGE ACCORDING TO YOUR SETUP) ------------ # ######################################################################## - ############ # BASICS # ############ @@ -51,7 +50,6 @@ SMTP_SECURE_ENABLED=0 SMTP_USER=smtpUser SMTP_PASSWORD=smtpPassword - ######################################################################## # ------------------------------ OPTIONAL -----------------------------# ######################################################################## @@ -99,6 +97,13 @@ AZUREAD_CLIENT_ID= AZUREAD_CLIENT_SECRET= AZUREAD_TENANT_ID= +# OpenID Connect (OIDC) configuration +# OIDC_CLIENT_ID= +# OIDC_CLIENT_SECRET= +# OIDC_ISSUER= +# OIDC_DISPLAY_NAME= +# OIDC_SIGNING_ALGORITHM= + # Cron Secret CRON_SECRET= @@ -118,12 +123,12 @@ NEXT_PUBLIC_FORMBRICKS_API_HOST= NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID= NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID= -# Oauth credentials for Google sheet integration +# Oauth credentials for Google sheet integration GOOGLE_SHEETS_CLIENT_ID= GOOGLE_SHEETS_CLIENT_SECRET= GOOGLE_SHEETS_REDIRECT_URL= -# Oauth credentials for Airtable integration +# Oauth credentials for Airtable integration AIRTABLE_CLIENT_ID= # Enterprise License Key @@ -143,4 +148,4 @@ ENTERPRISE_LICENSE_KEY= # CUSTOMER_IO_SITE_ID= # Ignore Rate Limiting across the Formbricks app -# RATE_LIMITING_DISABLED=1 \ No newline at end of file +# RATE_LIMITING_DISABLED=1 diff --git a/apps/formbricks-com/app/docs/self-hosting/external-auth-providers/page.mdx b/apps/formbricks-com/app/docs/self-hosting/external-auth-providers/page.mdx index 79d7c636ab..92366fa462 100644 --- a/apps/formbricks-com/app/docs/self-hosting/external-auth-providers/page.mdx +++ b/apps/formbricks-com/app/docs/self-hosting/external-auth-providers/page.mdx @@ -76,46 +76,86 @@ GOOGLE_CLIENT_SECRET=your-client-secret-here - Navigate to your Docker setup directory where your `docker-compose.yml` file is located. - Run the following command to bring down your current Docker containers and then bring them back up with the updated environment configuration: +## OpenID Integration + +Integrating your own OIDC (OpenID Connect) instance with your Formbricks instance allows users to log in using their OIDC credentials, ensuring a secure and streamlined user experience. Please follow the steps below to set up OIDC for your Formbricks instance. + +1. Configure your OIDC provider & get the following variables: + - `OIDC_CLIENT_ID` + - `OIDC_CLIENT_SECRET` + - `OIDC_ISSUER` + - `OIDC_SIGNING_ALGORITHM` + + +Make sure the Redirect URI for your OIDC Client is set to `{WEBAPP_URL}/api/auth/callback/openid`. + + +2. Update these environment variables in your `docker-compose.yml` or pass it directly to the running container. + +An example configuration for a FusionAuth OpenID Connect in Formbricks would look like: + + +```yml {{ title: '.env' }} +OIDC_CLIENT_ID=59cada54-56d4-4aa8-a5e7-5823bbe0e5b7 +OIDC_CLIENT_SECRET=4f4dwP0ZoOAqMW8fM9290A7uIS3E8Xg29xe1umhlB_s +OIDC_ISSUER=http://localhost:9011 +OIDC_DISPLAY_NAME=FusionAuth +OIDC_SIGNING_ALGORITHM=HS256 +``` + + + +3. Set an environment variable `OIDC_DISPLAY_NAME` to the display name of your OIDC provider. + +4. Restart your Formbricks instance. + +5. You're all set! Users can now signup & log in using their OIDC credentials. + ## Important Run-time Variables These variables can be provided at the runtime i.e. in your docker-compose file. -| Variable | Description | Required | Default | -| --------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------- | ----------------------- | -| WEBAPP_URL | Base URL of the site. | required | `http://localhost:3000` | -| DATABASE_URL | Database URL with credentials. | required | | -| NEXTAUTH_SECRET | Secret for NextAuth, used for session signing and encryption. | required | (Generated by the user) | -| ENCRYPTION_KEY | Secret for used by Formbricks for data encryption | required | (Generated by the user) | -| NEXTAUTH_URL | Location of the auth server. By default, this is the Formbricks docker instance itself. | required | `http://localhost:3000` | -| PRIVACY_URL | URL for privacy policy. | optional | | -| TERMS_URL | URL for terms of service. | optional | | -| IMPRINT_URL | URL for imprint. | optional | | -| SIGNUP_DISABLED | Disables the ability for new users to create an account if set to `1`. | optional | | -| EMAIL_AUTH_DISABLED | Disables the ability for users to signup or login via email and password if set to `1`. | optional | | -| PASSWORD_RESET_DISABLED | Disables password reset functionality if set to `1`. | optional | | -| EMAIL_VERIFICATION_DISABLED | Disables email verification if set to `1`. | optional | | -| RATE_LIMITING_DISABLED | Disables rate limiting if set to `1`. | optional | | -| INVITE_DISABLED | Disables the ability for invited users to create an account if set to `1`. | optional | | -| MAIL_FROM | Email address to send emails from. | optional (required if email services are to be enabled) | | -| SMTP_HOST | Host URL of your SMTP server. | optional (required if email services are to be enabled) | | -| SMTP_PORT | Host Port of your SMTP server. | optional (required if email services are to be enabled) | | -| SMTP_USER | Username for your SMTP Server. | optional (required if email services are to be enabled) | | -| SMTP_PASSWORD | Password for your SMTP Server. | optional (required if email services are to be enabled) | | -| SMTP_SECURE_ENABLED | SMTP secure connection. For using TLS, set to `1` else to `0`. | optional (required if email services are to be enabled) | | -| GITHUB_ID | Client ID for GitHub. | optional (required if GitHub auth is enabled) | | -| GITHUB_SECRET | Secret for GitHub. | optional (required if GitHub auth is enabled) | | -| GOOGLE_CLIENT_ID | Client ID for Google. | optional (required if Google auth is enabled) | | -| GOOGLE_CLIENT_SECRET | Secret for Google. | optional (required if Google auth is enabled) | | -| CRON_SECRET | API Secret for running cron jobs. | optional | | -| STRIPE_SECRET_KEY | Secret key for Stripe integration. | optional | | -| STRIPE_WEBHOOK_SECRET | Webhook secret for Stripe integration. | optional | | -| TELEMETRY_DISABLED | Disables telemetry if set to `1`. | optional | | -| INSTANCE_ID | Instance ID for Formbricks Cloud to be sent to Telemetry. | optional | | -| INTERNAL_SECRET | Internal Secret (Currently we overwrite the value with a random value). | optional | | -| DEFAULT_BRAND_COLOR | Default brand color for your app (Can be overwritten from the UI as well). | optional | `#64748b` | -| DEFAULT_TEAM_ID | Automatically assign new users to a specific team when joining | optional | | -| DEFAULT_TEAM_ROLE | Role of the user in the default team. | optional | `admin` | -| ONBOARDING_DISABLED | Disables onboarding for new users if set to `1` | optional | | +| Variable | Description | Required | Default | +| --------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ----------------------- | +| WEBAPP_URL | Base URL of the site. | required | `http://localhost:3000` | +| DATABASE_URL | Database URL with credentials. | required | | +| NEXTAUTH_SECRET | Secret for NextAuth, used for session signing and encryption. | required | (Generated by the user) | +| ENCRYPTION_KEY | Secret for used by Formbricks for data encryption | required | (Generated by the user) | +| NEXTAUTH_URL | Location of the auth server. By default, this is the Formbricks docker instance itself. | required | `http://localhost:3000` | +| PRIVACY_URL | URL for privacy policy. | optional | | +| TERMS_URL | URL for terms of service. | optional | | +| IMPRINT_URL | URL for imprint. | optional | | +| SIGNUP_DISABLED | Disables the ability for new users to create an account if set to `1`. | optional | | +| EMAIL_AUTH_DISABLED | Disables the ability for users to signup or login via email and password if set to `1`. | optional | | +| PASSWORD_RESET_DISABLED | Disables password reset functionality if set to `1`. | optional | | +| EMAIL_VERIFICATION_DISABLED | Disables email verification if set to `1`. | optional | | +| RATE_LIMITING_DISABLED | Disables rate limiting if set to `1`. | optional | | +| INVITE_DISABLED | Disables the ability for invited users to create an account if set to `1`. | optional | | +| MAIL_FROM | Email address to send emails from. | optional (required if email services are to be enabled) | | +| SMTP_HOST | Host URL of your SMTP server. | optional (required if email services are to be enabled) | | +| SMTP_PORT | Host Port of your SMTP server. | optional (required if email services are to be enabled) | | +| SMTP_USER | Username for your SMTP Server. | optional (required if email services are to be enabled) | | +| SMTP_PASSWORD | Password for your SMTP Server. | optional (required if email services are to be enabled) | | +| SMTP_SECURE_ENABLED | SMTP secure connection. For using TLS, set to `1` else to `0`. | optional (required if email services are to be enabled) | | +| GITHUB_ID | Client ID for GitHub. | optional (required if GitHub auth is enabled) | | +| GITHUB_SECRET | Secret for GitHub. | optional (required if GitHub auth is enabled) | | +| GOOGLE_CLIENT_ID | Client ID for Google. | optional (required if Google auth is enabled) | | +| GOOGLE_CLIENT_SECRET | Secret for Google. | optional (required if Google auth is enabled) | | +| CRON_SECRET | API Secret for running cron jobs. | optional | | +| STRIPE_SECRET_KEY | Secret key for Stripe integration. | optional | | +| STRIPE_WEBHOOK_SECRET | Webhook secret for Stripe integration. | optional | | +| TELEMETRY_DISABLED | Disables telemetry if set to `1`. | optional | | +| INSTANCE_ID | Instance ID for Formbricks Cloud to be sent to Telemetry. | optional | | +| INTERNAL_SECRET | Internal Secret (Currently we overwrite the value with a random value). | optional | | +| DEFAULT_BRAND_COLOR | Default brand color for your app (Can be overwritten from the UI as well). | optional | `#64748b` | +| DEFAULT_TEAM_ID | Automatically assign new users to a specific team when joining | optional | | +| DEFAULT_TEAM_ROLE | Role of the user in the default team. | optional | `admin` | +| ONBOARDING_DISABLED | Disables onboarding for new users if set to `1` | optional | | +| OIDC_DISPLAY_NAME | Display name for Custom OpenID Connect Provider | optional | | +| OIDC_CLIENT_ID | Client ID for Custom OpenID Connect Provider | optional (required if OIDC auth is enabled) | | +| OIDC_CLIENT_SECRET | Secret for Custom OpenID Connect Provider | optional (required if OIDC auth is enabled) | | +| OIDC_ISSUER | Issuer URL for Custom OpenID Connect Provider (should have `.well-known` configured at this) | optional (required if OIDC auth is enabled) | | +| OIDC_SIGNING_ALGORITHM | Signing Algorithm for Custom OpenID Connect Provider | optional | `RS256` | ## Build-time Variables diff --git a/apps/web/app/(auth)/auth/components/OpenIdButton.tsx b/apps/web/app/(auth)/auth/components/OpenIdButton.tsx new file mode 100644 index 0000000000..ac82b216a8 --- /dev/null +++ b/apps/web/app/(auth)/auth/components/OpenIdButton.tsx @@ -0,0 +1,38 @@ +import { signIn } from "next-auth/react"; +import { useCallback, useEffect } from "react"; + +import { Button } from "@formbricks/ui/Button"; + +export const OpenIdButton = ({ + text = "Continue with OpenId Connect", + inviteUrl, + directRedirect = false, +}: { + text?: string; + inviteUrl?: string | null; + directRedirect?: boolean; +}) => { + const handleLogin = useCallback(async () => { + await signIn("openid", { + redirect: true, + callbackUrl: inviteUrl ? inviteUrl : "/", + }); + }, [inviteUrl]); + + useEffect(() => { + if (directRedirect) { + handleLogin(); + } + }, [directRedirect, handleLogin]); + + return ( + + ); +}; diff --git a/apps/web/app/(auth)/auth/login/components/SigninForm.tsx b/apps/web/app/(auth)/auth/login/components/SigninForm.tsx index 512c0ff81d..235868497d 100644 --- a/apps/web/app/(auth)/auth/login/components/SigninForm.tsx +++ b/apps/web/app/(auth)/auth/login/components/SigninForm.tsx @@ -3,6 +3,7 @@ import { AzureButton } from "@/app/(auth)/auth/components/AzureButton"; import { GithubButton } from "@/app/(auth)/auth/components/GithubButton"; import { GoogleButton } from "@/app/(auth)/auth/components/GoogleButton"; +import { OpenIdButton } from "@/app/(auth)/auth/components/OpenIdButton"; import TwoFactor from "@/app/(auth)/auth/login/components/TwoFactor"; import TwoFactorBackup from "@/app/(auth)/auth/login/components/TwoFactorBackup"; import { XCircleIcon } from "@heroicons/react/24/solid"; @@ -30,6 +31,8 @@ export const SigninForm = ({ googleOAuthEnabled, githubOAuthEnabled, azureOAuthEnabled, + oidcOAuthEnabled, + oidcDisplayName, }: { emailAuthEnabled: boolean; publicSignUpEnabled: boolean; @@ -37,6 +40,8 @@ export const SigninForm = ({ googleOAuthEnabled: boolean; githubOAuthEnabled: boolean; azureOAuthEnabled: boolean; + oidcOAuthEnabled: boolean; + oidcDisplayName?: string; }) => { const router = useRouter(); const searchParams = useSearchParams(); @@ -223,6 +228,12 @@ export const SigninForm = ({ )} + + {oidcOAuthEnabled && !totpLogin && ( + <> + + + )} {publicSignUpEnabled && !totpLogin && ( diff --git a/apps/web/app/(auth)/auth/login/page.tsx b/apps/web/app/(auth)/auth/login/page.tsx index 180f267eaf..8e004281c2 100644 --- a/apps/web/app/(auth)/auth/login/page.tsx +++ b/apps/web/app/(auth)/auth/login/page.tsx @@ -8,6 +8,8 @@ import { EMAIL_AUTH_ENABLED, GITHUB_OAUTH_ENABLED, GOOGLE_OAUTH_ENABLED, + OIDC_DISPLAY_NAME, + OIDC_OAUTH_ENABLED, PASSWORD_RESET_DISABLED, SIGNUP_ENABLED, } from "@formbricks/lib/constants"; @@ -32,6 +34,8 @@ export default function SignInPage() { googleOAuthEnabled={GOOGLE_OAUTH_ENABLED} githubOAuthEnabled={GITHUB_OAUTH_ENABLED} azureOAuthEnabled={AZURE_OAUTH_ENABLED} + oidcOAuthEnabled={OIDC_OAUTH_ENABLED} + oidcDisplayName={OIDC_DISPLAY_NAME} /> diff --git a/apps/web/app/(auth)/auth/signup/components/SignupForm.tsx b/apps/web/app/(auth)/auth/signup/components/SignupForm.tsx index 73d9b0045d..d49c9ae464 100644 --- a/apps/web/app/(auth)/auth/signup/components/SignupForm.tsx +++ b/apps/web/app/(auth)/auth/signup/components/SignupForm.tsx @@ -4,6 +4,7 @@ import { AzureButton } from "@/app/(auth)/auth/components/AzureButton"; import { GithubButton } from "@/app/(auth)/auth/components/GithubButton"; import { GoogleButton } from "@/app/(auth)/auth/components/GoogleButton"; import IsPasswordValid from "@/app/(auth)/auth/components/IsPasswordValid"; +import { OpenIdButton } from "@/app/(auth)/auth/components/OpenIdButton"; import { createUser } from "@/app/lib/users/users"; import { XCircleIcon } from "@heroicons/react/24/solid"; import Link from "next/link"; @@ -23,6 +24,8 @@ interface SignupFormProps { googleOAuthEnabled: boolean; githubOAuthEnabled: boolean; azureOAuthEnabled: boolean; + oidcOAuthEnabled: boolean; + oidcDisplayName?: string; } export const SignupForm = ({ @@ -35,6 +38,8 @@ export const SignupForm = ({ googleOAuthEnabled, githubOAuthEnabled, azureOAuthEnabled, + oidcOAuthEnabled, + oidcDisplayName, }: SignupFormProps) => { const searchParams = useSearchParams(); const router = useRouter(); @@ -213,6 +218,11 @@ export const SignupForm = ({ )} + {oidcOAuthEnabled && ( + <> + + + )} {(termsUrl || privacyUrl) && ( diff --git a/apps/web/app/(auth)/auth/signup/page.tsx b/apps/web/app/(auth)/auth/signup/page.tsx index 7382d66389..03c0889fde 100644 --- a/apps/web/app/(auth)/auth/signup/page.tsx +++ b/apps/web/app/(auth)/auth/signup/page.tsx @@ -10,6 +10,8 @@ import { GITHUB_OAUTH_ENABLED, GOOGLE_OAUTH_ENABLED, INVITE_DISABLED, + OIDC_DISPLAY_NAME, + OIDC_OAUTH_ENABLED, PASSWORD_RESET_DISABLED, PRIVACY_URL, SIGNUP_ENABLED, @@ -56,6 +58,8 @@ export default function SignUpPage({ googleOAuthEnabled={GOOGLE_OAUTH_ENABLED} githubOAuthEnabled={GITHUB_OAUTH_ENABLED} azureOAuthEnabled={AZURE_OAUTH_ENABLED} + oidcOAuthEnabled={OIDC_OAUTH_ENABLED} + oidcDisplayName={OIDC_DISPLAY_NAME} /> )} diff --git a/packages/database/migrations/20240221093753_add_support_for_openid/migration.sql b/packages/database/migrations/20240221093753_add_support_for_openid/migration.sql new file mode 100644 index 0000000000..9b6f9a56b0 --- /dev/null +++ b/packages/database/migrations/20240221093753_add_support_for_openid/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "IdentityProvider" ADD VALUE 'openid'; diff --git a/packages/database/schema.prisma b/packages/database/schema.prisma index 8831b2fcce..d14196fabd 100644 --- a/packages/database/schema.prisma +++ b/packages/database/schema.prisma @@ -495,6 +495,7 @@ enum IdentityProvider { github google azuread + openid } model Account { diff --git a/packages/lib/authOptions.ts b/packages/lib/authOptions.ts index 9c4980a439..fef06126e3 100644 --- a/packages/lib/authOptions.ts +++ b/packages/lib/authOptions.ts @@ -9,7 +9,14 @@ import { prisma } from "@formbricks/database"; import { createAccount } from "./account/service"; import { verifyPassword } from "./auth/util"; -import { EMAIL_VERIFICATION_DISABLED } from "./constants"; +import { + EMAIL_VERIFICATION_DISABLED, + OIDC_CLIENT_ID, + OIDC_CLIENT_SECRET, + OIDC_DISPLAY_NAME, + OIDC_ISSUER, + OIDC_SIGNING_ALGORITHM, +} from "./constants"; import { env } from "./env.mjs"; import { verifyToken } from "./jwt"; import { createMembership } from "./membership/service"; @@ -131,6 +138,28 @@ export const authOptions: NextAuthOptions = { clientSecret: env.AZUREAD_CLIENT_SECRET || "", tenantId: env.AZUREAD_TENANT_ID || "", }), + { + id: "openid", + name: OIDC_DISPLAY_NAME || "OpenId", + type: "oauth", + clientId: OIDC_CLIENT_ID || "", + clientSecret: OIDC_CLIENT_SECRET || "", + wellKnown: `${OIDC_ISSUER}/.well-known/openid-configuration`, + authorization: { params: { scope: "openid email profile" } }, + idToken: true, + client: { + id_token_signed_response_alg: OIDC_SIGNING_ALGORITHM || "RS256", + }, + checks: ["pkce", "state"], + profile: (profile) => { + return { + id: profile.sub, + name: profile.name, + email: profile.email, + image: profile.picture, + }; + }, + }, ], callbacks: { async jwt({ token }) { @@ -161,7 +190,7 @@ export const authOptions: NextAuthOptions = { return true; } - if (!user.email || !user.name || account.type !== "oauth") { + if (!user.email || account.type !== "oauth") { return false; } @@ -214,7 +243,7 @@ export const authOptions: NextAuthOptions = { } const userProfile = await createUser({ - name: user.name, + name: user.name || user.email.split("@")[0], email: user.email, emailVerified: new Date(Date.now()), onboardingCompleted: false, diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts index e2e0fd73e2..06dca54e8e 100644 --- a/packages/lib/constants.ts +++ b/packages/lib/constants.ts @@ -33,6 +33,8 @@ export const GOOGLE_OAUTH_ENABLED = env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SE export const GITHUB_OAUTH_ENABLED = env.GITHUB_ID && env.GITHUB_SECRET ? true : false; export const AZURE_OAUTH_ENABLED = env.AZUREAD_CLIENT_ID && env.AZUREAD_CLIENT_SECRET && env.AZUREAD_TENANT_ID ? true : false; +export const OIDC_OAUTH_ENABLED = + env.OIDC_CLIENT_ID && env.OIDC_CLIENT_SECRET && env.OIDC_ISSUER ? true : false; export const GITHUB_ID = env.GITHUB_ID; export const GITHUB_SECRET = env.GITHUB_SECRET; @@ -43,6 +45,12 @@ export const AZUREAD_CLIENT_ID = env.AZUREAD_CLIENT_ID; export const AZUREAD_CLIENT_SECRET = env.AZUREAD_CLIENT_SECRET; export const AZUREAD_TENANT_ID = env.AZUREAD_TENANT_ID; +export const OIDC_CLIENT_ID = env.OIDC_CLIENT_ID; +export const OIDC_CLIENT_SECRET = env.OIDC_CLIENT_SECRET; +export const OIDC_ISSUER = env.OIDC_ISSUER; +export const OIDC_DISPLAY_NAME = env.OIDC_DISPLAY_NAME; +export const OIDC_SIGNING_ALGORITHM = env.OIDC_SIGNING_ALGORITHM; + export const SIGNUP_ENABLED = env.SIGNUP_DISABLED !== "1"; export const EMAIL_AUTH_ENABLED = env.EMAIL_AUTH_DISABLED !== "1"; export const INVITE_DISABLED = env.INVITE_DISABLED === "1"; diff --git a/packages/lib/env.mjs b/packages/lib/env.mjs index d257599868..b234a990a6 100644 --- a/packages/lib/env.mjs +++ b/packages/lib/env.mjs @@ -72,6 +72,11 @@ export const env = createEnv({ ONBOARDING_DISABLED: z.string().optional(), ENTERPRISE_LICENSE_KEY: z.string().optional(), RATE_LIMITING_DISABLED: z.enum(["1", "0"]).optional(), + OIDC_DISPLAY_NAME: z.string().optional(), + OIDC_CLIENT_ID: z.string().optional(), + OIDC_CLIENT_SECRET: z.string().optional(), + OIDC_ISSUER: z.string().optional(), + OIDC_SIGNING_ALGORITHM: z.string().optional(), }, /* @@ -154,5 +159,10 @@ export const env = createEnv({ ONBOARDING_DISABLED: process.env.ONBOARDING_DISABLED, ENTERPRISE_LICENSE_KEY: process.env.ENTERPRISE_LICENSE_KEY, RATE_LIMITING_DISABLED: process.env.RATE_LIMITING_DISABLED, + OIDC_DISPLAY_NAME: process.env.OIDC_DISPLAY_NAME, + OIDC_CLIENT_ID: process.env.OIDC_CLIENT_ID, + OIDC_CLIENT_SECRET: process.env.OIDC_CLIENT_SECRET, + OIDC_ISSUER: process.env.OIDC_ISSUER, + OIDC_SIGNING_ALGORITHM: process.env.OIDC_SIGNING_ALGORITHM, }, }); diff --git a/packages/types/user.ts b/packages/types/user.ts index 9f3b360964..722afb01b7 100644 --- a/packages/types/user.ts +++ b/packages/types/user.ts @@ -28,7 +28,7 @@ export const ZUser = z.object({ emailVerified: z.date().nullable(), imageUrl: z.string().url().nullable(), twoFactorEnabled: z.boolean(), - identityProvider: z.enum(["email", "google", "github", "azuread"]), + identityProvider: z.enum(["email", "google", "github", "azuread", "openid"]), createdAt: z.date(), updatedAt: z.date(), onboardingCompleted: z.boolean(), @@ -58,7 +58,7 @@ export const ZUserCreateInput = z.object({ onboardingCompleted: z.boolean().optional(), role: ZRole.optional(), objective: ZUserObjective.nullish(), - identityProvider: z.enum(["email", "google", "github", "azuread"]).optional(), + identityProvider: z.enum(["email", "google", "github", "azuread", "openid"]).optional(), identityProviderAccountId: z.string().optional(), });