From 6c1489a87bd8c05118abafd52df2a467ee61633d Mon Sep 17 00:00:00 2001 From: Salman Date: Sat, 10 Feb 2024 03:26:56 +0530 Subject: [PATCH 01/11] Add admin invite only signup field --- .../migrations/20240205200732_invite-only.ts | 20 ++++ backend/src/db/schemas/super-admin.ts | 1 + backend/src/db/seeds/1-user.ts | 2 +- backend/src/ee/routes/v1/saml-router.ts | 2 +- backend/src/server/routes/v1/admin-router.ts | 3 +- .../super-admin/super-admin-service.ts | 2 +- frontend/src/hooks/api/admin/types.ts | 1 + frontend/src/pages/signupinvite.tsx | 10 +- .../admin/DashboardPage/DashboardPage.tsx | 99 ++++++++++++++----- 9 files changed, 108 insertions(+), 32 deletions(-) create mode 100644 backend/src/db/migrations/20240205200732_invite-only.ts diff --git a/backend/src/db/migrations/20240205200732_invite-only.ts b/backend/src/db/migrations/20240205200732_invite-only.ts new file mode 100644 index 000000000..dd4be591b --- /dev/null +++ b/backend/src/db/migrations/20240205200732_invite-only.ts @@ -0,0 +1,20 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const isTablePresent = await knex.schema.hasTable(TableName.SuperAdmin); + if (isTablePresent) { + await knex.schema.alterTable(TableName.SuperAdmin, (t) => { + t.boolean("inviteOnlySignUp").defaultTo(false); + }); + } +} + +export async function down(knex: Knex): Promise { + if (await knex.schema.hasColumn(TableName.SuperAdmin, "inviteOnlySignUp")) { + await knex.schema.alterTable(TableName.SuperAdmin, (t) => { + t.dropColumn("inviteOnlySignUp"); + }); + } +} diff --git a/backend/src/db/schemas/super-admin.ts b/backend/src/db/schemas/super-admin.ts index 13bf45e7b..b43ada8d5 100644 --- a/backend/src/db/schemas/super-admin.ts +++ b/backend/src/db/schemas/super-admin.ts @@ -11,6 +11,7 @@ export const SuperAdminSchema = z.object({ id: z.string().uuid(), initialized: z.boolean().default(false).nullable().optional(), allowSignUp: z.boolean().default(true).nullable().optional(), + inviteOnlySignUp: z.boolean().default(false).nullable().optional(), createdAt: z.date(), updatedAt: z.date() }); diff --git a/backend/src/db/seeds/1-user.ts b/backend/src/db/seeds/1-user.ts index ca0042a98..0da9be9b1 100644 --- a/backend/src/db/seeds/1-user.ts +++ b/backend/src/db/seeds/1-user.ts @@ -9,7 +9,7 @@ export async function seed(knex: Knex): Promise { await knex(TableName.Users).del(); await knex(TableName.UserEncryptionKey).del(); await knex(TableName.SuperAdmin).del(); - await knex(TableName.SuperAdmin).insert([{ initialized: true, allowSignUp: true }]); + await knex(TableName.SuperAdmin).insert([{ initialized: true, allowSignUp: true, inviteOnlySignUp: false }]); // Inserts seed entries const [user] = await knex(TableName.Users) .insert([ diff --git a/backend/src/ee/routes/v1/saml-router.ts b/backend/src/ee/routes/v1/saml-router.ts index f81c21ffd..4c0e43cd6 100644 --- a/backend/src/ee/routes/v1/saml-router.ts +++ b/backend/src/ee/routes/v1/saml-router.ts @@ -105,7 +105,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { email, firstName: profile.firstName as string, lastName: profile.lastName as string, - isSignupAllowed: Boolean(serverCfg.allowSignUp), + isSignupAllowed: Boolean(serverCfg.allowSignUp && serverCfg.inviteOnlySignUp), relayState: (req.body as { RelayState?: string }).RelayState, authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string, orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index e23f68c3a..afe5c4c95 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -31,7 +31,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { method: "PATCH", schema: { body: z.object({ - allowSignUp: z.boolean().optional() + allowSignUp: z.boolean().optional(), + inviteOnlySignUp: z.boolean().optional() }), response: { 200: z.object({ diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index 1144bd414..b15d95e56 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -33,7 +33,7 @@ export const superAdminServiceFactory = ({ const serverCfg = await serverCfgDAL.findOne({}); if (serverCfg) return; - const newCfg = await serverCfgDAL.create({ initialized: false, allowSignUp: true }); + const newCfg = await serverCfgDAL.create({ initialized: false, allowSignUp: true, inviteOnlySignUp: false }); return newCfg; }; diff --git a/frontend/src/hooks/api/admin/types.ts b/frontend/src/hooks/api/admin/types.ts index da6b5ce3c..593191d1f 100644 --- a/frontend/src/hooks/api/admin/types.ts +++ b/frontend/src/hooks/api/admin/types.ts @@ -1,6 +1,7 @@ export type TServerConfig = { initialized: boolean; allowSignUp: boolean; + inviteOnlySignUp: boolean; isMigrationModeOn?: boolean; }; diff --git a/frontend/src/pages/signupinvite.tsx b/frontend/src/pages/signupinvite.tsx index 5eaa48e63..5ede3126c 100644 --- a/frontend/src/pages/signupinvite.tsx +++ b/frontend/src/pages/signupinvite.tsx @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import crypto from "crypto"; -import { useState } from "react"; +import { useEffect,useState } from "react"; import Head from "next/head"; import Image from "next/image"; import Link from "next/link"; @@ -22,6 +22,7 @@ import { deriveArgonKey } from "@app/components/utilities/cryptography/crypto"; import issueBackupKey from "@app/components/utilities/cryptography/issueBackupKey"; import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage"; import SecurityClient from "@app/components/utilities/SecurityClient"; +import { useServerConfig } from "@app/context"; import { completeAccountSignupInvite, verifySignupInvite } from "@app/hooks/api/auth/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; @@ -56,6 +57,13 @@ export default function SignupInvite() { const token = parsedUrl.token as string; const organizationId = parsedUrl.organization_id as string; const email = (parsedUrl.to as string)?.replace(" ", "+").trim(); + const { config } = useServerConfig(); + + useEffect(() => { + if (!config.allowSignUp) { + router.push("/login"); + } + }, [config.allowSignUp]); // Verifies if the information that the users entered (name, workspace) is there, and if the password matched the criteria. const signupErrorCheck = async () => { diff --git a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx index 508dcda1e..240a9d5de 100644 --- a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx +++ b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx @@ -1,7 +1,16 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useRouter } from "next/router"; -import { ContentLoader, Switch, Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; +import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; +import { + ContentLoader, + Select, + SelectItem, + Tab, + TabList, + TabPanel, + Tabs +} from "@app/components/v2"; import { useOrganization, useServerConfig, useUser } from "@app/context"; import { useUpdateServerConfig } from "@app/hooks/api"; @@ -9,13 +18,18 @@ enum TabSections { Settings = "settings" } +type SignUpMode = "disabled" | "invite-only" | "anyone"; + export const AdminDashboardPage = () => { const router = useRouter(); const data = useServerConfig(); + const [signUpMode, setSignUpMode] = useState("invite-only"); + const { config } = data; const { user, isLoading: isUserLoading } = useUser(); const { orgs } = useOrganization(); const { mutate: updateServerConfig } = useUpdateServerConfig(); + const { createNotification } = useNotificationContext(); const isNotAllowed = !user?.superAdmin; @@ -28,37 +42,68 @@ export const AdminDashboardPage = () => { } }, [isNotAllowed, isUserLoading]); + useEffect(() => { + if (!config.allowSignUp) { + setSignUpMode("disabled"); + return; + } + if (config.inviteOnlySignUp) { + setSignUpMode("invite-only"); + return; + } + setSignUpMode("anyone"); + }, [config]); + + function handleSignUpModeChange(newSignUpMode: SignUpMode) { + config.allowSignUp = newSignUpMode !== "disabled"; + config.inviteOnlySignUp = newSignUpMode === "invite-only"; + + createNotification({ + text: "Successfully changed sign up mode.", + type: "success" + }); + + updateServerConfig(config); + setSignUpMode(newSignUpMode); + } + return ( -
-
+
+

Admin Dashboard

Manage your Infisical instance.

- {isUserLoading || isNotAllowed ? ( - - ) : ( -
- - -
- General -
-
- -
- updateServerConfig({ allowSignUp: isChecked })} - /> -
Enable signup or invite
-
-
-
-
- )}
+ {isUserLoading || isNotAllowed ? ( + + ) : ( +
+ + +
+ General +
+
+ +
+
Allow user to Sign Up
+ +
+
+
+
+ )}
); }; From 5cf1ec24003438d575389817685fedf6bb7d5e7c Mon Sep 17 00:00:00 2001 From: Salman Date: Sat, 10 Feb 2024 10:44:39 +0530 Subject: [PATCH 02/11] Add restrict signup based on domain --- .../migrations/20240205200732_invite-only.ts | 7 ++ backend/src/db/schemas/super-admin.ts | 1 + backend/src/server/routes/v1/admin-router.ts | 3 +- backend/src/server/routes/v1/sso-router.ts | 8 +- backend/src/server/routes/v3/signup-router.ts | 12 ++- .../src/services/auth/auth-login-service.ts | 25 +++--- backend/src/services/auth/auth-login-type.ts | 3 + frontend/src/hooks/api/admin/types.ts | 1 + .../admin/DashboardPage/DashboardPage.tsx | 88 ++++++++++++++----- 9 files changed, 109 insertions(+), 39 deletions(-) diff --git a/backend/src/db/migrations/20240205200732_invite-only.ts b/backend/src/db/migrations/20240205200732_invite-only.ts index dd4be591b..d242623c8 100644 --- a/backend/src/db/migrations/20240205200732_invite-only.ts +++ b/backend/src/db/migrations/20240205200732_invite-only.ts @@ -7,6 +7,7 @@ export async function up(knex: Knex): Promise { if (isTablePresent) { await knex.schema.alterTable(TableName.SuperAdmin, (t) => { t.boolean("inviteOnlySignUp").defaultTo(false); + t.string("allowSpecificDomainSignUp"); }); } } @@ -17,4 +18,10 @@ export async function down(knex: Knex): Promise { t.dropColumn("inviteOnlySignUp"); }); } + + if (await knex.schema.hasColumn(TableName.SuperAdmin, "allowSpecificDomainSignUp")) { + await knex.schema.alterTable(TableName.SuperAdmin, (t) => { + t.dropColumn("allowSpecificDomainSignUp"); + }); + } } diff --git a/backend/src/db/schemas/super-admin.ts b/backend/src/db/schemas/super-admin.ts index b43ada8d5..fb373e19b 100644 --- a/backend/src/db/schemas/super-admin.ts +++ b/backend/src/db/schemas/super-admin.ts @@ -12,6 +12,7 @@ export const SuperAdminSchema = z.object({ initialized: z.boolean().default(false).nullable().optional(), allowSignUp: z.boolean().default(true).nullable().optional(), inviteOnlySignUp: z.boolean().default(false).nullable().optional(), + allowSpecificDomainSignUp: z.string().nullable().optional(), createdAt: z.date(), updatedAt: z.date() }); diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index afe5c4c95..ddd95522f 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -32,7 +32,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { schema: { body: z.object({ allowSignUp: z.boolean().optional(), - inviteOnlySignUp: z.boolean().optional() + inviteOnlySignUp: z.boolean().optional(), + allowSpecificDomainSignUp: z.string().optional() }), response: { 200: z.object({ diff --git a/backend/src/server/routes/v1/sso-router.ts b/backend/src/server/routes/v1/sso-router.ts index bfcf2f6ae..da0b12ca5 100644 --- a/backend/src/server/routes/v1/sso-router.ts +++ b/backend/src/server/routes/v1/sso-router.ts @@ -55,7 +55,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { lastName: profile?.name?.familyName || "", authMethod: AuthMethod.GOOGLE, callbackPort: req.query.state as string, - isSignupAllowed: Boolean(serverCfg.allowSignUp) + serverCfg }); cb(null, { isUserCompleted, providerAuthToken }); } catch (error) { @@ -91,7 +91,8 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { lastName: "", authMethod: AuthMethod.GITHUB, callbackPort: req.query.state as string, - isSignupAllowed: Boolean(serverCfg.allowSignUp) + serverCfg + // isSignupAllowed: Boolean(serverCfg.allowSignUp) }); return cb(null, { isUserCompleted, providerAuthToken }); } catch (error) { @@ -127,7 +128,8 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { lastName: "", authMethod: AuthMethod.GITLAB, callbackPort: req.query.state as string, - isSignupAllowed: Boolean(serverCfg.allowSignUp) + serverCfg + // isSignupAllowed: Boolean(serverCfg.allowSignUp) }); return cb(null, { isUserCompleted, providerAuthToken }); diff --git a/backend/src/server/routes/v3/signup-router.ts b/backend/src/server/routes/v3/signup-router.ts index 2a2f50f43..11d79d2b5 100644 --- a/backend/src/server/routes/v3/signup-router.ts +++ b/backend/src/server/routes/v3/signup-router.ts @@ -23,8 +23,16 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { } }, handler: async (req) => { - await server.services.signup.beginEmailSignupProcess(req.body.email); - return { message: `Sent an email verification code to ${req.body.email}` }; + const { email } = req.body; + const config = await server.services.superAdmin.initServerCfg(); + + if (config?.allowSpecificDomainSignUp) { + const domain = email.split("@")[1]; + + if (domain !== config.allowSpecificDomainSignUp) throw new Error(`Unsupported email domain (${domain}).`); + } + await server.services.signup.beginEmailSignupProcess(email); + return { message: `Sent an email verification code to ${email}` }; } }); diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index 6e4d60bba..ccfe619f4 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -261,20 +261,25 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: /* * OAuth2 login for google,github, and other oauth2 provider * */ - const oauth2Login = async ({ - email, - firstName, - lastName, - authMethod, - callbackPort, - isSignupAllowed - }: TOauthLoginDTO) => { + const oauth2Login = async ({ email, firstName, lastName, authMethod, callbackPort, serverCfg }: TOauthLoginDTO) => { let user = await userDAL.findUserByEmail(email); const appCfg = getConfig(); - const isOauthSignUpDisabled = !isSignupAllowed && !user; - if (isOauthSignUpDisabled) throw new BadRequestError({ message: "User signup disabled", name: "Oauth 2 login" }); if (!user) { + // Create a new user based on oAuth + if (!serverCfg?.allowSignUp) + throw new BadRequestError({ message: "User signup disabled", name: "Oauth 2 login" }); + + if (serverCfg?.allowSpecificDomainSignUp) { + const domain = email.split("@")[1]; + + if (domain !== serverCfg.allowSpecificDomainSignUp) + throw new BadRequestError({ + message: `User email domain (${domain}) is not supported`, + name: "Oauth 2 login" + }); + } + user = await userDAL.create({ email, firstName, lastName, authMethods: [authMethod] }); } const isLinkingRequired = !user?.authMethods?.includes(authMethod); diff --git a/backend/src/services/auth/auth-login-type.ts b/backend/src/services/auth/auth-login-type.ts index 67f640bc9..54b27425b 100644 --- a/backend/src/services/auth/auth-login-type.ts +++ b/backend/src/services/auth/auth-login-type.ts @@ -1,3 +1,5 @@ +import { TSuperAdmin } from "@app/db/schemas/super-admin"; + import { AuthMethod } from "./auth-type"; export type TLoginGenServerPublicKeyDTO = { @@ -29,4 +31,5 @@ export type TOauthLoginDTO = { authMethod: AuthMethod; callbackPort?: string; isSignupAllowed?: boolean; + serverCfg?: TSuperAdmin; }; diff --git a/frontend/src/hooks/api/admin/types.ts b/frontend/src/hooks/api/admin/types.ts index 593191d1f..145f22072 100644 --- a/frontend/src/hooks/api/admin/types.ts +++ b/frontend/src/hooks/api/admin/types.ts @@ -2,6 +2,7 @@ export type TServerConfig = { initialized: boolean; allowSignUp: boolean; inviteOnlySignUp: boolean; + allowSpecificDomainSignUp?: string; isMigrationModeOn?: boolean; }; diff --git a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx index 240a9d5de..b0d07b7dc 100644 --- a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx +++ b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx @@ -1,9 +1,14 @@ -import { useEffect, useState } from "react"; +import { FormEvent, useEffect, useState } from "react"; import { useRouter } from "next/router"; +import { faAt } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { + Button, ContentLoader, + FormControl, + Input, Select, SelectItem, Tab, @@ -24,11 +29,13 @@ export const AdminDashboardPage = () => { const router = useRouter(); const data = useServerConfig(); const [signUpMode, setSignUpMode] = useState("invite-only"); + const [allowSpecificDomain, setAllowSpecificDomain] = useState(); const { config } = data; const { user, isLoading: isUserLoading } = useUser(); const { orgs } = useOrganization(); const { mutate: updateServerConfig } = useUpdateServerConfig(); + const { createNotification } = useNotificationContext(); const isNotAllowed = !user?.superAdmin; @@ -49,22 +56,28 @@ export const AdminDashboardPage = () => { } if (config.inviteOnlySignUp) { setSignUpMode("invite-only"); - return; + } else { + setSignUpMode("anyone"); + } + + if (config.allowSpecificDomainSignUp) { + setAllowSpecificDomain(config.allowSpecificDomainSignUp); } - setSignUpMode("anyone"); }, [config]); - function handleSignUpModeChange(newSignUpMode: SignUpMode) { - config.allowSignUp = newSignUpMode !== "disabled"; - config.inviteOnlySignUp = newSignUpMode === "invite-only"; + async function handleSubmit(e: FormEvent) { + e.preventDefault(); + + config.allowSignUp = signUpMode !== "disabled"; + config.inviteOnlySignUp = signUpMode === "invite-only"; + config.allowSpecificDomainSignUp = signUpMode === "anyone" ? allowSpecificDomain : ""; + + await updateServerConfig(config); createNotification({ text: "Successfully changed sign up mode.", type: "success" }); - - updateServerConfig(config); - setSignUpMode(newSignUpMode); } return ( @@ -86,20 +99,49 @@ export const AdminDashboardPage = () => {
-
-
Allow user to Sign Up
- -
+
+
+
+ Allow user to Sign Up +
+ +
+ + {signUpMode === "anyone" && ( +
+
+ Allow email with only specific domain +
+ +
+ } + value={allowSpecificDomain} + onChange={(ev) => setAllowSpecificDomain(ev.target.value)} + /> +
+
+
+ )} + + +
From da377f6fdad2b85392cf5d6f14f1f80574d15200 Mon Sep 17 00:00:00 2001 From: Salman Date: Sat, 10 Feb 2024 23:00:16 +0530 Subject: [PATCH 03/11] Update error handling and refactor --- backend/src/server/routes/v1/sso-router.ts | 15 +++------------ backend/src/server/routes/v3/signup-router.ts | 6 +++++- backend/src/services/auth/auth-login-service.ts | 7 +++++-- backend/src/services/auth/auth-login-type.ts | 4 ---- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/backend/src/server/routes/v1/sso-router.ts b/backend/src/server/routes/v1/sso-router.ts index da0b12ca5..60bbec7db 100644 --- a/backend/src/server/routes/v1/sso-router.ts +++ b/backend/src/server/routes/v1/sso-router.ts @@ -18,7 +18,6 @@ import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { fetchGithubEmails } from "@app/lib/requests/github"; import { AuthMethod } from "@app/services/auth/auth-type"; -import { getServerCfg } from "@app/services/super-admin/super-admin-service"; export const registerSsoRouter = async (server: FastifyZodProvider) => { const appCfg = getConfig(); @@ -42,7 +41,6 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { async (req, _accessToken, _refreshToken, profile, cb) => { try { const email = profile?.emails?.[0]?.value; - const serverCfg = await getServerCfg(); if (!email) throw new BadRequestError({ message: "Email not found", @@ -54,8 +52,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { firstName: profile?.name?.givenName || "", lastName: profile?.name?.familyName || "", authMethod: AuthMethod.GOOGLE, - callbackPort: req.query.state as string, - serverCfg + callbackPort: req.query.state as string }); cb(null, { isUserCompleted, providerAuthToken }); } catch (error) { @@ -84,15 +81,12 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { try { const ghEmails = await fetchGithubEmails(accessToken); const { email } = ghEmails.filter((gitHubEmail) => gitHubEmail.primary)[0]; - const serverCfg = await getServerCfg(); const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({ email, firstName: profile.displayName, lastName: "", authMethod: AuthMethod.GITHUB, - callbackPort: req.query.state as string, - serverCfg - // isSignupAllowed: Boolean(serverCfg.allowSignUp) + callbackPort: req.query.state as string }); return cb(null, { isUserCompleted, providerAuthToken }); } catch (error) { @@ -121,15 +115,12 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { async (req: any, _accessToken: string, _refreshToken: string, profile: any, cb: any) => { try { const email = profile.emails[0].value; - const serverCfg = await getServerCfg(); const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({ email, firstName: profile.displayName, lastName: "", authMethod: AuthMethod.GITLAB, - callbackPort: req.query.state as string, - serverCfg - // isSignupAllowed: Boolean(serverCfg.allowSignUp) + callbackPort: req.query.state as string }); return cb(null, { isUserCompleted, providerAuthToken }); diff --git a/backend/src/server/routes/v3/signup-router.ts b/backend/src/server/routes/v3/signup-router.ts index 11d79d2b5..257a4c173 100644 --- a/backend/src/server/routes/v3/signup-router.ts +++ b/backend/src/server/routes/v3/signup-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { UsersSchema } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; +import { BadRequestError } from "@app/lib/errors"; import { authRateLimit } from "@app/server/config/rateLimiter"; import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; @@ -29,7 +30,10 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { if (config?.allowSpecificDomainSignUp) { const domain = email.split("@")[1]; - if (domain !== config.allowSpecificDomainSignUp) throw new Error(`Unsupported email domain (${domain}).`); + if (domain !== config.allowSpecificDomainSignUp) + throw new BadRequestError({ + message: `User email domain (@${domain}) is not supported` + }); } await server.services.signup.beginEmailSignupProcess(email); return { message: `Sent an email verification code to ${email}` }; diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index ccfe619f4..569b3d374 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -4,6 +4,7 @@ import { TUsers, UserDeviceSchema } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto"; import { BadRequestError } from "@app/lib/errors"; +import { getServerCfg } from "@app/services/super-admin/super-admin-service"; import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service"; import { TokenType } from "../auth-token/auth-token-types"; @@ -261,8 +262,10 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: /* * OAuth2 login for google,github, and other oauth2 provider * */ - const oauth2Login = async ({ email, firstName, lastName, authMethod, callbackPort, serverCfg }: TOauthLoginDTO) => { + const oauth2Login = async ({ email, firstName, lastName, authMethod, callbackPort }: TOauthLoginDTO) => { let user = await userDAL.findUserByEmail(email); + const serverCfg = await getServerCfg(); + const appCfg = getConfig(); if (!user) { @@ -275,7 +278,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: if (domain !== serverCfg.allowSpecificDomainSignUp) throw new BadRequestError({ - message: `User email domain (${domain}) is not supported`, + message: `User email domain (@${domain}) is not supported`, name: "Oauth 2 login" }); } diff --git a/backend/src/services/auth/auth-login-type.ts b/backend/src/services/auth/auth-login-type.ts index 54b27425b..86af5a5f9 100644 --- a/backend/src/services/auth/auth-login-type.ts +++ b/backend/src/services/auth/auth-login-type.ts @@ -1,5 +1,3 @@ -import { TSuperAdmin } from "@app/db/schemas/super-admin"; - import { AuthMethod } from "./auth-type"; export type TLoginGenServerPublicKeyDTO = { @@ -30,6 +28,4 @@ export type TOauthLoginDTO = { lastName?: string; authMethod: AuthMethod; callbackPort?: string; - isSignupAllowed?: boolean; - serverCfg?: TSuperAdmin; }; From 3f96f0a8fb7a20bcf81dfd5934ebf6196fafee22 Mon Sep 17 00:00:00 2001 From: Salman Date: Sun, 11 Feb 2024 00:40:17 +0530 Subject: [PATCH 04/11] Fix dropdown not working on page transition --- .../admin/DashboardPage/DashboardPage.tsx | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx index b0d07b7dc..181dc890a 100644 --- a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx +++ b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx @@ -1,4 +1,4 @@ -import { FormEvent, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import { faAt } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -65,9 +65,7 @@ export const AdminDashboardPage = () => { } }, [config]); - async function handleSubmit(e: FormEvent) { - e.preventDefault(); - + const handleSubmit = async () => { config.allowSignUp = signUpMode !== "disabled"; config.inviteOnlySignUp = signUpMode === "invite-only"; config.allowSpecificDomainSignUp = signUpMode === "anyone" ? allowSpecificDomain : ""; @@ -99,16 +97,13 @@ export const AdminDashboardPage = () => { -
+
Allow user to Sign Up
} @@ -138,10 +133,10 @@ export const AdminDashboardPage = () => {
)} - - +
From 2ef8781378cc40e78a730d71d95fd8a6bb746d86 Mon Sep 17 00:00:00 2001 From: Salman Date: Sun, 11 Feb 2024 10:55:36 +0530 Subject: [PATCH 05/11] Update support multiple domain and error handling --- backend/src/ee/routes/v1/saml-router.ts | 2 +- backend/src/server/routes/v3/signup-router.ts | 12 ++++---- .../src/services/auth/auth-login-service.ts | 6 ++-- .../src/components/signup/EnterEmailStep.tsx | 17 +++++++++-- .../admin/DashboardPage/DashboardPage.tsx | 30 ++++++++++++------- 5 files changed, 45 insertions(+), 22 deletions(-) diff --git a/backend/src/ee/routes/v1/saml-router.ts b/backend/src/ee/routes/v1/saml-router.ts index 4c0e43cd6..f81c21ffd 100644 --- a/backend/src/ee/routes/v1/saml-router.ts +++ b/backend/src/ee/routes/v1/saml-router.ts @@ -105,7 +105,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { email, firstName: profile.firstName as string, lastName: profile.lastName as string, - isSignupAllowed: Boolean(serverCfg.allowSignUp && serverCfg.inviteOnlySignUp), + isSignupAllowed: Boolean(serverCfg.allowSignUp), relayState: (req.body as { RelayState?: string }).RelayState, authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string, orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string diff --git a/backend/src/server/routes/v3/signup-router.ts b/backend/src/server/routes/v3/signup-router.ts index 257a4c173..e0ce21006 100644 --- a/backend/src/server/routes/v3/signup-router.ts +++ b/backend/src/server/routes/v3/signup-router.ts @@ -4,6 +4,7 @@ import { UsersSchema } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "@app/lib/errors"; import { authRateLimit } from "@app/server/config/rateLimiter"; +import { getServerCfg } from "@app/services/super-admin/super-admin-service"; import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; export const registerSignupRouter = async (server: FastifyZodProvider) => { @@ -25,15 +26,16 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { }, handler: async (req) => { const { email } = req.body; - const config = await server.services.superAdmin.initServerCfg(); + const serverCfg = await getServerCfg(); - if (config?.allowSpecificDomainSignUp) { + if (serverCfg?.allowSpecificDomainSignUp) { const domain = email.split("@")[1]; - - if (domain !== config.allowSpecificDomainSignUp) + const allowedDomains = serverCfg.allowSpecificDomainSignUp.split(",").map((e) => e.trim()); + if (!allowedDomains.includes(domain)) { throw new BadRequestError({ - message: `User email domain (@${domain}) is not supported` + message: `Email with a domain (@${domain}) is not supported` }); + } } await server.services.signup.beginEmailSignupProcess(email); return { message: `Sent an email verification code to ${email}` }; diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index 569b3d374..f43fe063c 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -275,10 +275,10 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: if (serverCfg?.allowSpecificDomainSignUp) { const domain = email.split("@")[1]; - - if (domain !== serverCfg.allowSpecificDomainSignUp) + const allowedDomains = serverCfg.allowSpecificDomainSignUp.split(",").map((e) => e.trim()); + if (!allowedDomains.includes(domain)) throw new BadRequestError({ - message: `User email domain (@${domain}) is not supported`, + message: `Email with a domain (@${domain}) is not supported`, name: "Oauth 2 login" }); } diff --git a/frontend/src/components/signup/EnterEmailStep.tsx b/frontend/src/components/signup/EnterEmailStep.tsx index e317a4ea5..058c23106 100644 --- a/frontend/src/components/signup/EnterEmailStep.tsx +++ b/frontend/src/components/signup/EnterEmailStep.tsx @@ -1,7 +1,9 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import Link from "next/link"; +import axios from "axios"; +import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { useSendVerificationEmail } from "@app/hooks/api"; import { Button, Input } from "../v2"; @@ -25,6 +27,7 @@ export default function EnterEmailStep({ setEmail, incrementStep }: DownloadBackupPDFStepProps): JSX.Element { + const { createNotification } = useNotificationContext(); const { mutateAsync } = useSendVerificationEmail(); const [emailError, setEmailError] = useState(false); const { t } = useTranslation(); @@ -46,8 +49,18 @@ export default function EnterEmailStep({ // If everything is correct, go to the next step if (!emailCheckBool) { - await mutateAsync({ email }); - incrementStep(); + try { + await mutateAsync({ email }); + incrementStep(); + } catch(e) { + if (axios.isAxiosError(e)) { + const { message = "Something went wrong" } = e.response?.data as { message: string}; + createNotification({ + type: "error", + text: message + }) + } + } } }; diff --git a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx index 181dc890a..340c1662a 100644 --- a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx +++ b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx @@ -66,17 +66,25 @@ export const AdminDashboardPage = () => { }, [config]); const handleSubmit = async () => { - config.allowSignUp = signUpMode !== "disabled"; - config.inviteOnlySignUp = signUpMode === "invite-only"; - config.allowSpecificDomainSignUp = signUpMode === "anyone" ? allowSpecificDomain : ""; + try { + config.allowSignUp = signUpMode !== "disabled"; + config.inviteOnlySignUp = signUpMode === "invite-only"; + config.allowSpecificDomainSignUp = signUpMode === "anyone" ? allowSpecificDomain : ""; - await updateServerConfig(config); + await updateServerConfig(config); - createNotification({ - text: "Successfully changed sign up mode.", - type: "success" - }); - } + createNotification({ + text: "Successfully changed sign up setting.", + type: "success" + }); + } catch (e) { + console.error(e); + createNotification({ + type: "error", + text: "Failed to update sign up setting." + }); + } + }; return (
@@ -118,12 +126,12 @@ export const AdminDashboardPage = () => { {signUpMode === "anyone" && (
- Allow email with only specific domain + Allow email with only specific domain(s)
} value={allowSpecificDomain} onChange={(ev) => setAllowSpecificDomain(ev.target.value)} From 0fb87ab05fd4df9df5f35763cba8540493c49812 Mon Sep 17 00:00:00 2001 From: Salman Date: Mon, 12 Feb 2024 04:09:46 +0530 Subject: [PATCH 06/11] Update move to react hook form, rename allowedSignUpDomain --- .../migrations/20240205200732_invite-only.ts | 6 +- backend/src/db/schemas/super-admin.ts | 2 +- backend/src/server/routes/v1/admin-router.ts | 2 +- backend/src/server/routes/v3/signup-router.ts | 4 +- .../src/services/auth/auth-login-service.ts | 4 +- frontend/src/hooks/api/admin/types.ts | 2 +- frontend/src/pages/signupinvite.tsx | 2 +- .../components/InitialStep/InitialStep.tsx | 2 +- .../admin/DashboardPage/DashboardPage.tsx | 139 +++++++++++------- 9 files changed, 100 insertions(+), 63 deletions(-) diff --git a/backend/src/db/migrations/20240205200732_invite-only.ts b/backend/src/db/migrations/20240205200732_invite-only.ts index d242623c8..4bebab17f 100644 --- a/backend/src/db/migrations/20240205200732_invite-only.ts +++ b/backend/src/db/migrations/20240205200732_invite-only.ts @@ -7,7 +7,7 @@ export async function up(knex: Knex): Promise { if (isTablePresent) { await knex.schema.alterTable(TableName.SuperAdmin, (t) => { t.boolean("inviteOnlySignUp").defaultTo(false); - t.string("allowSpecificDomainSignUp"); + t.string("allowedSignUpDomain"); }); } } @@ -19,9 +19,9 @@ export async function down(knex: Knex): Promise { }); } - if (await knex.schema.hasColumn(TableName.SuperAdmin, "allowSpecificDomainSignUp")) { + if (await knex.schema.hasColumn(TableName.SuperAdmin, "allowedSignUpDomain")) { await knex.schema.alterTable(TableName.SuperAdmin, (t) => { - t.dropColumn("allowSpecificDomainSignUp"); + t.dropColumn("allowedSignUpDomain"); }); } } diff --git a/backend/src/db/schemas/super-admin.ts b/backend/src/db/schemas/super-admin.ts index fb373e19b..4b410d88b 100644 --- a/backend/src/db/schemas/super-admin.ts +++ b/backend/src/db/schemas/super-admin.ts @@ -12,7 +12,7 @@ export const SuperAdminSchema = z.object({ initialized: z.boolean().default(false).nullable().optional(), allowSignUp: z.boolean().default(true).nullable().optional(), inviteOnlySignUp: z.boolean().default(false).nullable().optional(), - allowSpecificDomainSignUp: z.string().nullable().optional(), + allowedSignUpDomain: z.string().nullable().optional(), createdAt: z.date(), updatedAt: z.date() }); diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index ddd95522f..221014ccc 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -33,7 +33,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { body: z.object({ allowSignUp: z.boolean().optional(), inviteOnlySignUp: z.boolean().optional(), - allowSpecificDomainSignUp: z.string().optional() + allowedSignUpDomain: z.string().optional() }), response: { 200: z.object({ diff --git a/backend/src/server/routes/v3/signup-router.ts b/backend/src/server/routes/v3/signup-router.ts index e0ce21006..45434412e 100644 --- a/backend/src/server/routes/v3/signup-router.ts +++ b/backend/src/server/routes/v3/signup-router.ts @@ -28,9 +28,9 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { const { email } = req.body; const serverCfg = await getServerCfg(); - if (serverCfg?.allowSpecificDomainSignUp) { + if (serverCfg?.allowedSignUpDomain) { const domain = email.split("@")[1]; - const allowedDomains = serverCfg.allowSpecificDomainSignUp.split(",").map((e) => e.trim()); + const allowedDomains = serverCfg.allowedSignUpDomain.split(",").map((e) => e.trim()); if (!allowedDomains.includes(domain)) { throw new BadRequestError({ message: `Email with a domain (@${domain}) is not supported` diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index f43fe063c..e78b4080e 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -273,9 +273,9 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: if (!serverCfg?.allowSignUp) throw new BadRequestError({ message: "User signup disabled", name: "Oauth 2 login" }); - if (serverCfg?.allowSpecificDomainSignUp) { + if (serverCfg?.allowedSignUpDomain) { const domain = email.split("@")[1]; - const allowedDomains = serverCfg.allowSpecificDomainSignUp.split(",").map((e) => e.trim()); + const allowedDomains = serverCfg.allowedSignUpDomain.split(",").map((e) => e.trim()); if (!allowedDomains.includes(domain)) throw new BadRequestError({ message: `Email with a domain (@${domain}) is not supported`, diff --git a/frontend/src/hooks/api/admin/types.ts b/frontend/src/hooks/api/admin/types.ts index 145f22072..e28e7f972 100644 --- a/frontend/src/hooks/api/admin/types.ts +++ b/frontend/src/hooks/api/admin/types.ts @@ -2,7 +2,7 @@ export type TServerConfig = { initialized: boolean; allowSignUp: boolean; inviteOnlySignUp: boolean; - allowSpecificDomainSignUp?: string; + allowedSignUpDomain?: string; isMigrationModeOn?: boolean; }; diff --git a/frontend/src/pages/signupinvite.tsx b/frontend/src/pages/signupinvite.tsx index 5ede3126c..a3b643e29 100644 --- a/frontend/src/pages/signupinvite.tsx +++ b/frontend/src/pages/signupinvite.tsx @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import crypto from "crypto"; -import { useEffect,useState } from "react"; +import { useEffect, useState } from "react"; import Head from "next/head"; import Image from "next/image"; import Link from "next/link"; diff --git a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx index bbef55751..16541c9a0 100644 --- a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx +++ b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx @@ -226,7 +226,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
{!isLoading && loginError && } - {config.allowSignUp ? ( + {config.allowSignUp && !config.inviteOnlySignUp ? (
diff --git a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx index 340c1662a..912a60c18 100644 --- a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx +++ b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx @@ -1,7 +1,10 @@ -import { useEffect, useState } from "react"; +import { useEffect } from "react"; +import { Controller, useForm } from "react-hook-form"; import { useRouter } from "next/router"; import { faAt } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { @@ -23,15 +26,37 @@ enum TabSections { Settings = "settings" } -type SignUpMode = "disabled" | "invite-only" | "anyone"; +const formSchema = yup.object({ + signUpMode: yup + .string() + .oneOf(["disabled", "invite-only", "anyone"]) + .required(), + allowedSignUpDomain: yup.string().optional() +}); + +type TDashboardForm = yup.InferType; export const AdminDashboardPage = () => { const router = useRouter(); const data = useServerConfig(); - const [signUpMode, setSignUpMode] = useState("invite-only"); - const [allowSpecificDomain, setAllowSpecificDomain] = useState(); - const { config } = data; + + const signUpStatus = config.allowSignUp + ? config.inviteOnlySignUp && "invite-only" + : "disabled"; + + const signUpType = signUpStatus || "anyone"; + + const { control, handleSubmit, watch } = useForm({ + resolver: yupResolver(formSchema), + defaultValues: { + signUpMode: signUpType, + allowedSignUpDomain: config.allowedSignUpDomain + } + }); + + const signupMode = watch("signUpMode"); + const { user, isLoading: isUserLoading } = useUser(); const { orgs } = useOrganization(); const { mutate: updateServerConfig } = useUpdateServerConfig(); @@ -49,29 +74,15 @@ export const AdminDashboardPage = () => { } }, [isNotAllowed, isUserLoading]); - useEffect(() => { - if (!config.allowSignUp) { - setSignUpMode("disabled"); - return; - } - if (config.inviteOnlySignUp) { - setSignUpMode("invite-only"); - } else { - setSignUpMode("anyone"); - } - - if (config.allowSpecificDomainSignUp) { - setAllowSpecificDomain(config.allowSpecificDomainSignUp); - } - }, [config]); - - const handleSubmit = async () => { + const onFormSubmit = async (formData: TDashboardForm) => { try { - config.allowSignUp = signUpMode !== "disabled"; - config.inviteOnlySignUp = signUpMode === "invite-only"; - config.allowSpecificDomainSignUp = signUpMode === "anyone" ? allowSpecificDomain : ""; + const { signUpMode, allowedSignUpDomain } = formData; - await updateServerConfig(config); + await updateServerConfig({ + allowSignUp: signUpMode !== "disabled", + inviteOnlySignUp: signUpMode === "invite-only", + allowedSignUpDomain: signUpMode === "anyone" ? allowedSignUpDomain : "" + }); createNotification({ text: "Successfully changed sign up setting.", @@ -86,6 +97,8 @@ export const AdminDashboardPage = () => { } }; + + return (
@@ -105,46 +118,70 @@ export const AdminDashboardPage = () => {
-
+
Allow user to Sign Up
- + ( + + + + )} + />
- {signUpMode === "anyone" && ( + {signupMode === "anyone" && (
Allow email with only specific domain(s)
- -
- } - value={allowSpecificDomain} - onChange={(ev) => setAllowSpecificDomain(ev.target.value)} - /> -
-
+ ( + + } + /> + + )} + />
)} - -
+
From 0ff3ddb0c86be4c8df2f4cf8c402bb5ab5fb4d37 Mon Sep 17 00:00:00 2001 From: Salman Date: Wed, 14 Feb 2024 07:52:46 +0530 Subject: [PATCH 07/11] Update generate schema run --- backend/src/db/schemas/super-admin.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/db/schemas/super-admin.ts b/backend/src/db/schemas/super-admin.ts index 4b410d88b..707ec60e9 100644 --- a/backend/src/db/schemas/super-admin.ts +++ b/backend/src/db/schemas/super-admin.ts @@ -11,10 +11,10 @@ export const SuperAdminSchema = z.object({ id: z.string().uuid(), initialized: z.boolean().default(false).nullable().optional(), allowSignUp: z.boolean().default(true).nullable().optional(), - inviteOnlySignUp: z.boolean().default(false).nullable().optional(), - allowedSignUpDomain: z.string().nullable().optional(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + inviteOnlySignUp: z.boolean().default(false).nullable().optional(), + allowedSignUpDomain: z.string().nullable().optional() }); export type TSuperAdmin = z.infer; From ee69bccb6eee58a1775c8e22f89738bdcd9637db Mon Sep 17 00:00:00 2001 From: Salman Date: Wed, 14 Feb 2024 14:18:44 +0530 Subject: [PATCH 08/11] Update disabled sign up routes --- backend/src/server/routes/v3/signup-router.ts | 27 +++++++++++++++++++ .../src/services/auth/auth-login-service.ts | 3 +-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/backend/src/server/routes/v3/signup-router.ts b/backend/src/server/routes/v3/signup-router.ts index 45434412e..522af7e9f 100644 --- a/backend/src/server/routes/v3/signup-router.ts +++ b/backend/src/server/routes/v3/signup-router.ts @@ -26,7 +26,13 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { }, handler: async (req) => { const { email } = req.body; + const serverCfg = await getServerCfg(); + if (!serverCfg.allowSignUp) { + throw new BadRequestError({ + message: "Sign up is disabled!" + }); + } if (serverCfg?.allowedSignUpDomain) { const domain = email.split("@")[1]; @@ -62,6 +68,13 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { } }, handler: async (req) => { + const serverCfg = await getServerCfg(); + if (!serverCfg.allowSignUp) { + throw new BadRequestError({ + message: "Sign up is disabled!" + }); + } + const { token, user } = await server.services.signup.verifyEmailSignup(req.body.email, req.body.code); return { message: "Successfuly verified email", token, user }; } @@ -104,6 +117,13 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { if (!userAgent) throw new Error("user agent header is required"); const appCfg = getConfig(); + const serverCfg = await getServerCfg(); + if (!serverCfg.allowSignUp) { + throw new BadRequestError({ + message: "Sign up is disabled!" + }); + } + const { user, accessToken, refreshToken } = await server.services.signup.completeEmailAccountSignup({ ...req.body, ip: req.realIp, @@ -167,6 +187,13 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { if (!userAgent) throw new Error("user agent header is required"); const appCfg = getConfig(); + const serverCfg = await getServerCfg(); + if (!serverCfg.allowSignUp) { + throw new BadRequestError({ + message: "Sign up is disabled!" + }); + } + const { user, accessToken, refreshToken } = await server.services.signup.completeAccountInvite({ ...req.body, ip: req.realIp, diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index e78b4080e..04304980a 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -270,8 +270,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: if (!user) { // Create a new user based on oAuth - if (!serverCfg?.allowSignUp) - throw new BadRequestError({ message: "User signup disabled", name: "Oauth 2 login" }); + if (!serverCfg?.allowSignUp) throw new BadRequestError({ message: "Sign Up disabled", name: "Oauth 2 login" }); if (serverCfg?.allowedSignUpDomain) { const domain = email.split("@")[1]; From 97d2a15d3ecc32eca9197464a07c95041dae602c Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Fri, 16 Feb 2024 13:41:18 +0530 Subject: [PATCH 09/11] feat: updated admin dashboard to use zod and ts enum --- .../admin/DashboardPage/DashboardPage.tsx | 79 +++++++++++-------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx index 912a60c18..afaa53d87 100644 --- a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx +++ b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx @@ -3,8 +3,8 @@ import { Controller, useForm } from "react-hook-form"; import { useRouter } from "next/router"; import { faAt } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { yupResolver } from "@hookform/resolvers/yup"; -import * as yup from "yup"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { @@ -26,31 +26,42 @@ enum TabSections { Settings = "settings" } -const formSchema = yup.object({ - signUpMode: yup - .string() - .oneOf(["disabled", "invite-only", "anyone"]) - .required(), - allowedSignUpDomain: yup.string().optional() +enum SignUpModes { + Disabled = "disabled", + InviteOnly = "invite-only", + Anyone = "anyone" +} + +const getSignUpMode = (isSignUpEnabled: boolean, isInviteOnly: boolean) => { + if (isSignUpEnabled) { + if (isInviteOnly) return SignUpModes.InviteOnly; + return SignUpModes.Anyone; + } + return SignUpModes.Disabled; +}; + +const formSchema = z.object({ + signUpMode: z.nativeEnum(SignUpModes), + allowedSignUpDomain: z.string().optional() }); -type TDashboardForm = yup.InferType; - +type TDashboardForm = z.infer; export const AdminDashboardPage = () => { const router = useRouter(); const data = useServerConfig(); const { config } = data; - const signUpStatus = config.allowSignUp - ? config.inviteOnlySignUp && "invite-only" - : "disabled"; - - const signUpType = signUpStatus || "anyone"; - - const { control, handleSubmit, watch } = useForm({ - resolver: yupResolver(formSchema), - defaultValues: { - signUpMode: signUpType, + const { + control, + handleSubmit, + watch, + getValues, + formState: { isSubmitting, isDirty } + } = useForm({ + resolver: zodResolver(formSchema), + values: { + // eslint-disable-next-line + signUpMode: getSignUpMode(config.allowSignUp, config.inviteOnlySignUp), allowedSignUpDomain: config.allowedSignUpDomain } }); @@ -59,12 +70,13 @@ export const AdminDashboardPage = () => { const { user, isLoading: isUserLoading } = useUser(); const { orgs } = useOrganization(); - const { mutate: updateServerConfig } = useUpdateServerConfig(); + const { mutateAsync: updateServerConfig } = useUpdateServerConfig(); const { createNotification } = useNotificationContext(); const isNotAllowed = !user?.superAdmin; + // TODO(akhilmhdh): on nextjs 14 roadmap this will be properly addressed with context split useEffect(() => { if (isNotAllowed && !isUserLoading) { if (orgs?.length) { @@ -77,13 +89,11 @@ export const AdminDashboardPage = () => { const onFormSubmit = async (formData: TDashboardForm) => { try { const { signUpMode, allowedSignUpDomain } = formData; - await updateServerConfig({ - allowSignUp: signUpMode !== "disabled", - inviteOnlySignUp: signUpMode === "invite-only", - allowedSignUpDomain: signUpMode === "anyone" ? allowedSignUpDomain : "" + allowSignUp: signUpMode !== SignUpModes.Disabled, + inviteOnlySignUp: signUpMode === SignUpModes.InviteOnly, + allowedSignUpDomain: signUpMode === SignUpModes.Anyone ? allowedSignUpDomain : "" }); - createNotification({ text: "Successfully changed sign up setting.", type: "success" @@ -97,8 +107,7 @@ export const AdminDashboardPage = () => { } }; - - + console.log(isDirty,getValues()); return (
@@ -142,15 +151,14 @@ export const AdminDashboardPage = () => { onValueChange={(e) => onChange(e)} {...field} > - Disabled - Invite Only - Anyone + Disabled + Invite Only + Anyone )} />
- {signupMode === "anyone" && (
@@ -177,8 +185,11 @@ export const AdminDashboardPage = () => { />
)} - - From fed022ed09c93e5208b2ac22168c26042bff7642 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Thu, 22 Feb 2024 13:09:51 +0530 Subject: [PATCH 10/11] feat: removed unused inviteSignup flag --- ...=> 20240222073748_admin-signup-control.ts} | 7 ------- backend/src/db/schemas/super-admin.ts | 1 - backend/src/db/seeds/1-user.ts | 2 +- backend/src/server/routes/v1/admin-router.ts | 3 +-- backend/src/server/routes/v3/signup-router.ts | 7 ------- .../super-admin/super-admin-service.ts | 2 +- frontend/src/hooks/api/admin/types.ts | 3 +-- frontend/src/hooks/api/serverDetails/types.ts | 3 +-- .../components/InitialStep/InitialStep.tsx | 2 +- .../admin/DashboardPage/DashboardPage.tsx | 20 ++++--------------- 10 files changed, 10 insertions(+), 40 deletions(-) rename backend/src/db/migrations/{20240205200732_invite-only.ts => 20240222073748_admin-signup-control.ts} (70%) diff --git a/backend/src/db/migrations/20240205200732_invite-only.ts b/backend/src/db/migrations/20240222073748_admin-signup-control.ts similarity index 70% rename from backend/src/db/migrations/20240205200732_invite-only.ts rename to backend/src/db/migrations/20240222073748_admin-signup-control.ts index 4bebab17f..c52f753c0 100644 --- a/backend/src/db/migrations/20240205200732_invite-only.ts +++ b/backend/src/db/migrations/20240222073748_admin-signup-control.ts @@ -6,19 +6,12 @@ export async function up(knex: Knex): Promise { const isTablePresent = await knex.schema.hasTable(TableName.SuperAdmin); if (isTablePresent) { await knex.schema.alterTable(TableName.SuperAdmin, (t) => { - t.boolean("inviteOnlySignUp").defaultTo(false); t.string("allowedSignUpDomain"); }); } } export async function down(knex: Knex): Promise { - if (await knex.schema.hasColumn(TableName.SuperAdmin, "inviteOnlySignUp")) { - await knex.schema.alterTable(TableName.SuperAdmin, (t) => { - t.dropColumn("inviteOnlySignUp"); - }); - } - if (await knex.schema.hasColumn(TableName.SuperAdmin, "allowedSignUpDomain")) { await knex.schema.alterTable(TableName.SuperAdmin, (t) => { t.dropColumn("allowedSignUpDomain"); diff --git a/backend/src/db/schemas/super-admin.ts b/backend/src/db/schemas/super-admin.ts index 707ec60e9..b4631195b 100644 --- a/backend/src/db/schemas/super-admin.ts +++ b/backend/src/db/schemas/super-admin.ts @@ -13,7 +13,6 @@ export const SuperAdminSchema = z.object({ allowSignUp: z.boolean().default(true).nullable().optional(), createdAt: z.date(), updatedAt: z.date(), - inviteOnlySignUp: z.boolean().default(false).nullable().optional(), allowedSignUpDomain: z.string().nullable().optional() }); diff --git a/backend/src/db/seeds/1-user.ts b/backend/src/db/seeds/1-user.ts index 0da9be9b1..ca0042a98 100644 --- a/backend/src/db/seeds/1-user.ts +++ b/backend/src/db/seeds/1-user.ts @@ -9,7 +9,7 @@ export async function seed(knex: Knex): Promise { await knex(TableName.Users).del(); await knex(TableName.UserEncryptionKey).del(); await knex(TableName.SuperAdmin).del(); - await knex(TableName.SuperAdmin).insert([{ initialized: true, allowSignUp: true, inviteOnlySignUp: false }]); + await knex(TableName.SuperAdmin).insert([{ initialized: true, allowSignUp: true }]); // Inserts seed entries const [user] = await knex(TableName.Users) .insert([ diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index 9552c10eb..2f26893d8 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -32,8 +32,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { schema: { body: z.object({ allowSignUp: z.boolean().optional(), - inviteOnlySignUp: z.boolean().optional(), - allowedSignUpDomain: z.string().optional() + allowedSignUpDomain: z.string().optional().nullable() }), response: { 200: z.object({ diff --git a/backend/src/server/routes/v3/signup-router.ts b/backend/src/server/routes/v3/signup-router.ts index ad3b3bc8f..b04b88d2f 100644 --- a/backend/src/server/routes/v3/signup-router.ts +++ b/backend/src/server/routes/v3/signup-router.ts @@ -187,13 +187,6 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { if (!userAgent) throw new Error("user agent header is required"); const appCfg = getConfig(); - const serverCfg = await getServerCfg(); - if (!serverCfg.allowSignUp) { - throw new BadRequestError({ - message: "Sign up is disabled!" - }); - } - const { user, accessToken, refreshToken } = await server.services.signup.completeAccountInvite({ ...req.body, ip: req.realIp, diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index b15d95e56..1144bd414 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -33,7 +33,7 @@ export const superAdminServiceFactory = ({ const serverCfg = await serverCfgDAL.findOne({}); if (serverCfg) return; - const newCfg = await serverCfgDAL.create({ initialized: false, allowSignUp: true, inviteOnlySignUp: false }); + const newCfg = await serverCfgDAL.create({ initialized: false, allowSignUp: true }); return newCfg; }; diff --git a/frontend/src/hooks/api/admin/types.ts b/frontend/src/hooks/api/admin/types.ts index e28e7f972..c7022a1d7 100644 --- a/frontend/src/hooks/api/admin/types.ts +++ b/frontend/src/hooks/api/admin/types.ts @@ -1,8 +1,7 @@ export type TServerConfig = { initialized: boolean; allowSignUp: boolean; - inviteOnlySignUp: boolean; - allowedSignUpDomain?: string; + allowedSignUpDomain?: string | null; isMigrationModeOn?: boolean; }; diff --git a/frontend/src/hooks/api/serverDetails/types.ts b/frontend/src/hooks/api/serverDetails/types.ts index 32cc92ae1..af7bc5b28 100644 --- a/frontend/src/hooks/api/serverDetails/types.ts +++ b/frontend/src/hooks/api/serverDetails/types.ts @@ -2,7 +2,6 @@ export type ServerStatus = { date: string; message: string; emailConfigured: boolean; - inviteOnlySignup: boolean; secretScanningConfigured: boolean redisConfigured: boolean -}; \ No newline at end of file +}; diff --git a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx index 16541c9a0..bbef55751 100644 --- a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx +++ b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx @@ -226,7 +226,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
{!isLoading && loginError && } - {config.allowSignUp && !config.inviteOnlySignUp ? ( + {config.allowSignUp ? (
diff --git a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx index afaa53d87..2c7e3662a 100644 --- a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx +++ b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx @@ -28,21 +28,12 @@ enum TabSections { enum SignUpModes { Disabled = "disabled", - InviteOnly = "invite-only", Anyone = "anyone" } -const getSignUpMode = (isSignUpEnabled: boolean, isInviteOnly: boolean) => { - if (isSignUpEnabled) { - if (isInviteOnly) return SignUpModes.InviteOnly; - return SignUpModes.Anyone; - } - return SignUpModes.Disabled; -}; - const formSchema = z.object({ signUpMode: z.nativeEnum(SignUpModes), - allowedSignUpDomain: z.string().optional() + allowedSignUpDomain: z.string().optional().nullable() }); type TDashboardForm = z.infer; @@ -55,13 +46,12 @@ export const AdminDashboardPage = () => { control, handleSubmit, watch, - getValues, formState: { isSubmitting, isDirty } } = useForm({ resolver: zodResolver(formSchema), values: { // eslint-disable-next-line - signUpMode: getSignUpMode(config.allowSignUp, config.inviteOnlySignUp), + signUpMode: config.allowSignUp ? SignUpModes.Anyone : SignUpModes.Disabled, allowedSignUpDomain: config.allowedSignUpDomain } }); @@ -91,8 +81,7 @@ export const AdminDashboardPage = () => { const { signUpMode, allowedSignUpDomain } = formData; await updateServerConfig({ allowSignUp: signUpMode !== SignUpModes.Disabled, - inviteOnlySignUp: signUpMode === SignUpModes.InviteOnly, - allowedSignUpDomain: signUpMode === SignUpModes.Anyone ? allowedSignUpDomain : "" + allowedSignUpDomain: signUpMode === SignUpModes.Anyone ? allowedSignUpDomain : null }); createNotification({ text: "Successfully changed sign up setting.", @@ -107,7 +96,6 @@ export const AdminDashboardPage = () => { } }; - console.log(isDirty,getValues()); return (
@@ -152,7 +140,6 @@ export const AdminDashboardPage = () => { {...field} > Disabled - Invite Only Anyone @@ -177,6 +164,7 @@ export const AdminDashboardPage = () => { > } /> From a831a7d848eca3a57c76788bc26c223bc314f196 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Thu, 22 Feb 2024 15:35:09 -0500 Subject: [PATCH 11/11] small typos and update migration timestamp --- ...up-control.ts => 20240222201806_admin-signup-control.ts} | 0 backend/src/server/routes/v3/signup-router.ts | 6 +++--- backend/src/services/auth/auth-login-service.ts | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) rename backend/src/db/migrations/{20240222073748_admin-signup-control.ts => 20240222201806_admin-signup-control.ts} (100%) diff --git a/backend/src/db/migrations/20240222073748_admin-signup-control.ts b/backend/src/db/migrations/20240222201806_admin-signup-control.ts similarity index 100% rename from backend/src/db/migrations/20240222073748_admin-signup-control.ts rename to backend/src/db/migrations/20240222201806_admin-signup-control.ts diff --git a/backend/src/server/routes/v3/signup-router.ts b/backend/src/server/routes/v3/signup-router.ts index d1ec800f9..24387d2f5 100644 --- a/backend/src/server/routes/v3/signup-router.ts +++ b/backend/src/server/routes/v3/signup-router.ts @@ -30,7 +30,7 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { const serverCfg = await getServerCfg(); if (!serverCfg.allowSignUp) { throw new BadRequestError({ - message: "Sign up is disabled!" + message: "Sign up is disabled" }); } @@ -71,7 +71,7 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { const serverCfg = await getServerCfg(); if (!serverCfg.allowSignUp) { throw new BadRequestError({ - message: "Sign up is disabled!" + message: "Sign up is disabled" }); } @@ -120,7 +120,7 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { const serverCfg = await getServerCfg(); if (!serverCfg.allowSignUp) { throw new BadRequestError({ - message: "Sign up is disabled!" + message: "Sign up is disabled" }); } diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index ee2cc5995..11ce57735 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -270,7 +270,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: if (!user) { // Create a new user based on oAuth - if (!serverCfg?.allowSignUp) throw new BadRequestError({ message: "Sign Up disabled", name: "Oauth 2 login" }); + if (!serverCfg?.allowSignUp) throw new BadRequestError({ message: "Sign up disabled", name: "Oauth 2 login" }); if (serverCfg?.allowedSignUpDomain) { const domain = email.split("@")[1]; @@ -281,9 +281,8 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }: name: "Oauth 2 login" }); } - + user = await userDAL.create({ email, firstName, lastName, authMethods: [authMethod], isGhost: false }); - } const isLinkingRequired = !user?.authMethods?.includes(authMethod); const isUserCompleted = user.isAccepted;