Add admin invite only signup field

This commit is contained in:
Salman
2024-02-10 03:26:56 +05:30
parent d4db01bbde
commit 6c1489a87b
9 changed files with 108 additions and 32 deletions
@@ -0,0 +1,20 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
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<void> {
if (await knex.schema.hasColumn(TableName.SuperAdmin, "inviteOnlySignUp")) {
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
t.dropColumn("inviteOnlySignUp");
});
}
}
+1
View File
@@ -11,6 +11,7 @@ export const SuperAdminSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
initialized: z.boolean().default(false).nullable().optional(), initialized: z.boolean().default(false).nullable().optional(),
allowSignUp: z.boolean().default(true).nullable().optional(), allowSignUp: z.boolean().default(true).nullable().optional(),
inviteOnlySignUp: z.boolean().default(false).nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date() updatedAt: z.date()
}); });
+1 -1
View File
@@ -9,7 +9,7 @@ export async function seed(knex: Knex): Promise<void> {
await knex(TableName.Users).del(); await knex(TableName.Users).del();
await knex(TableName.UserEncryptionKey).del(); await knex(TableName.UserEncryptionKey).del();
await knex(TableName.SuperAdmin).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 // Inserts seed entries
const [user] = await knex(TableName.Users) const [user] = await knex(TableName.Users)
.insert([ .insert([
+1 -1
View File
@@ -105,7 +105,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
email, email,
firstName: profile.firstName as string, firstName: profile.firstName as string,
lastName: profile.lastName as string, lastName: profile.lastName as string,
isSignupAllowed: Boolean(serverCfg.allowSignUp), isSignupAllowed: Boolean(serverCfg.allowSignUp && serverCfg.inviteOnlySignUp),
relayState: (req.body as { RelayState?: string }).RelayState, relayState: (req.body as { RelayState?: string }).RelayState,
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string, authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string,
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string
+2 -1
View File
@@ -31,7 +31,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
method: "PATCH", method: "PATCH",
schema: { schema: {
body: z.object({ body: z.object({
allowSignUp: z.boolean().optional() allowSignUp: z.boolean().optional(),
inviteOnlySignUp: z.boolean().optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -33,7 +33,7 @@ export const superAdminServiceFactory = ({
const serverCfg = await serverCfgDAL.findOne({}); const serverCfg = await serverCfgDAL.findOne({});
if (serverCfg) return; 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; return newCfg;
}; };
+1
View File
@@ -1,6 +1,7 @@
export type TServerConfig = { export type TServerConfig = {
initialized: boolean; initialized: boolean;
allowSignUp: boolean; allowSignUp: boolean;
inviteOnlySignUp: boolean;
isMigrationModeOn?: boolean; isMigrationModeOn?: boolean;
}; };
+9 -1
View File
@@ -2,7 +2,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import crypto from "crypto"; import crypto from "crypto";
import { useState } from "react"; import { useEffect,useState } from "react";
import Head from "next/head"; import Head from "next/head";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; 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 issueBackupKey from "@app/components/utilities/cryptography/issueBackupKey";
import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage"; import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage";
import SecurityClient from "@app/components/utilities/SecurityClient"; import SecurityClient from "@app/components/utilities/SecurityClient";
import { useServerConfig } from "@app/context";
import { completeAccountSignupInvite, verifySignupInvite } from "@app/hooks/api/auth/queries"; import { completeAccountSignupInvite, verifySignupInvite } from "@app/hooks/api/auth/queries";
import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries";
@@ -56,6 +57,13 @@ export default function SignupInvite() {
const token = parsedUrl.token as string; const token = parsedUrl.token as string;
const organizationId = parsedUrl.organization_id as string; const organizationId = parsedUrl.organization_id as string;
const email = (parsedUrl.to as string)?.replace(" ", "+").trim(); 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. // Verifies if the information that the users entered (name, workspace) is there, and if the password matched the criteria.
const signupErrorCheck = async () => { const signupErrorCheck = async () => {
@@ -1,7 +1,16 @@
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { useRouter } from "next/router"; 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 { useOrganization, useServerConfig, useUser } from "@app/context";
import { useUpdateServerConfig } from "@app/hooks/api"; import { useUpdateServerConfig } from "@app/hooks/api";
@@ -9,13 +18,18 @@ enum TabSections {
Settings = "settings" Settings = "settings"
} }
type SignUpMode = "disabled" | "invite-only" | "anyone";
export const AdminDashboardPage = () => { export const AdminDashboardPage = () => {
const router = useRouter(); const router = useRouter();
const data = useServerConfig(); const data = useServerConfig();
const [signUpMode, setSignUpMode] = useState<SignUpMode>("invite-only");
const { config } = data; const { config } = data;
const { user, isLoading: isUserLoading } = useUser(); const { user, isLoading: isUserLoading } = useUser();
const { orgs } = useOrganization(); const { orgs } = useOrganization();
const { mutate: updateServerConfig } = useUpdateServerConfig(); const { mutate: updateServerConfig } = useUpdateServerConfig();
const { createNotification } = useNotificationContext();
const isNotAllowed = !user?.superAdmin; const isNotAllowed = !user?.superAdmin;
@@ -28,37 +42,68 @@ export const AdminDashboardPage = () => {
} }
}, [isNotAllowed, isUserLoading]); }, [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 ( return (
<div className="container mx-auto max-w-7xl pb-12 text-white dark:[color-scheme:dark]"> <div className="container mx-auto max-w-7xl px-4 pb-12 text-white dark:[color-scheme:dark]">
<div className="mx-auto mb-6 w-full max-w-7xl py-6 px-6"> <div className="mx-auto mb-6 w-full max-w-7xl pt-6">
<div className="mb-8 flex flex-col items-start justify-between text-xl"> <div className="mb-8 flex flex-col items-start justify-between text-xl">
<h1 className="text-3xl font-semibold">Admin Dashboard</h1> <h1 className="text-3xl font-semibold">Admin Dashboard</h1>
<p className="text-base text-bunker-300">Manage your Infisical instance.</p> <p className="text-base text-bunker-300">Manage your Infisical instance.</p>
</div> </div>
{isUserLoading || isNotAllowed ? (
<ContentLoader text={isNotAllowed ? "Redirecting to org page..." : undefined} />
) : (
<div>
<Tabs defaultValue={TabSections.Settings}>
<TabList>
<div className="flex w-full flex-row border-b border-mineshaft-600">
<Tab value={TabSections.Settings}>General</Tab>
</div>
</TabList>
<TabPanel value={TabSections.Settings}>
<div className="flex items-center space-x-4">
<Switch
id="disable-invite"
isChecked={Boolean(config?.allowSignUp)}
onCheckedChange={(isChecked) => updateServerConfig({ allowSignUp: isChecked })}
/>
<div className="flex-grow">Enable signup or invite</div>
</div>
</TabPanel>
</Tabs>
</div>
)}
</div> </div>
{isUserLoading || isNotAllowed ? (
<ContentLoader text={isNotAllowed ? "Redirecting to org page..." : undefined} />
) : (
<div>
<Tabs defaultValue={TabSections.Settings}>
<TabList>
<div className="flex w-full flex-row border-b border-mineshaft-600">
<Tab value={TabSections.Settings}>General</Tab>
</div>
</TabList>
<TabPanel value={TabSections.Settings}>
<div className="flex items-center justify-between space-x-4">
<div className="label"> Allow user to Sign Up </div>
<Select
className="w-36 bg-mineshaft-700"
dropdownContainerClassName="bg-mineshaft-700"
onValueChange={(state) => handleSignUpModeChange(state as SignUpMode)}
value={signUpMode}
isDisabled={isNotAllowed}
>
<SelectItem value="disabled">Disabled</SelectItem>
<SelectItem value="invite-only">Invite Only</SelectItem>
<SelectItem value="anyone">Anyone</SelectItem>
</Select>
</div>
</TabPanel>
</Tabs>
</div>
)}
</div> </div>
); );
}; };