Add restrict signup based on domain

This commit is contained in:
Salman
2024-02-10 10:44:39 +05:30
parent 6c1489a87b
commit 5cf1ec2400
9 changed files with 109 additions and 39 deletions
@@ -7,6 +7,7 @@ export async function up(knex: Knex): Promise<void> {
if (isTablePresent) { if (isTablePresent) {
await knex.schema.alterTable(TableName.SuperAdmin, (t) => { await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
t.boolean("inviteOnlySignUp").defaultTo(false); t.boolean("inviteOnlySignUp").defaultTo(false);
t.string("allowSpecificDomainSignUp");
}); });
} }
} }
@@ -17,4 +18,10 @@ export async function down(knex: Knex): Promise<void> {
t.dropColumn("inviteOnlySignUp"); t.dropColumn("inviteOnlySignUp");
}); });
} }
if (await knex.schema.hasColumn(TableName.SuperAdmin, "allowSpecificDomainSignUp")) {
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
t.dropColumn("allowSpecificDomainSignUp");
});
}
} }
+1
View File
@@ -12,6 +12,7 @@ export const SuperAdminSchema = z.object({
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(), inviteOnlySignUp: z.boolean().default(false).nullable().optional(),
allowSpecificDomainSignUp: z.string().nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date() updatedAt: z.date()
}); });
+2 -1
View File
@@ -32,7 +32,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
schema: { schema: {
body: z.object({ body: z.object({
allowSignUp: z.boolean().optional(), allowSignUp: z.boolean().optional(),
inviteOnlySignUp: z.boolean().optional() inviteOnlySignUp: z.boolean().optional(),
allowSpecificDomainSignUp: z.string().optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({
+5 -3
View File
@@ -55,7 +55,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
lastName: profile?.name?.familyName || "", lastName: profile?.name?.familyName || "",
authMethod: AuthMethod.GOOGLE, authMethod: AuthMethod.GOOGLE,
callbackPort: req.query.state as string, callbackPort: req.query.state as string,
isSignupAllowed: Boolean(serverCfg.allowSignUp) serverCfg
}); });
cb(null, { isUserCompleted, providerAuthToken }); cb(null, { isUserCompleted, providerAuthToken });
} catch (error) { } catch (error) {
@@ -91,7 +91,8 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
lastName: "", lastName: "",
authMethod: AuthMethod.GITHUB, authMethod: AuthMethod.GITHUB,
callbackPort: req.query.state as string, callbackPort: req.query.state as string,
isSignupAllowed: Boolean(serverCfg.allowSignUp) serverCfg
// isSignupAllowed: Boolean(serverCfg.allowSignUp)
}); });
return cb(null, { isUserCompleted, providerAuthToken }); return cb(null, { isUserCompleted, providerAuthToken });
} catch (error) { } catch (error) {
@@ -127,7 +128,8 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
lastName: "", lastName: "",
authMethod: AuthMethod.GITLAB, authMethod: AuthMethod.GITLAB,
callbackPort: req.query.state as string, callbackPort: req.query.state as string,
isSignupAllowed: Boolean(serverCfg.allowSignUp) serverCfg
// isSignupAllowed: Boolean(serverCfg.allowSignUp)
}); });
return cb(null, { isUserCompleted, providerAuthToken }); return cb(null, { isUserCompleted, providerAuthToken });
+10 -2
View File
@@ -23,8 +23,16 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
} }
}, },
handler: async (req) => { handler: async (req) => {
await server.services.signup.beginEmailSignupProcess(req.body.email); const { email } = req.body;
return { message: `Sent an email verification code to ${req.body.email}` }; 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}` };
} }
}); });
+15 -10
View File
@@ -261,20 +261,25 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
/* /*
* OAuth2 login for google,github, and other oauth2 provider * OAuth2 login for google,github, and other oauth2 provider
* */ * */
const oauth2Login = async ({ const oauth2Login = async ({ email, firstName, lastName, authMethod, callbackPort, serverCfg }: TOauthLoginDTO) => {
email,
firstName,
lastName,
authMethod,
callbackPort,
isSignupAllowed
}: TOauthLoginDTO) => {
let user = await userDAL.findUserByEmail(email); let user = await userDAL.findUserByEmail(email);
const appCfg = getConfig(); const appCfg = getConfig();
const isOauthSignUpDisabled = !isSignupAllowed && !user;
if (isOauthSignUpDisabled) throw new BadRequestError({ message: "User signup disabled", name: "Oauth 2 login" });
if (!user) { 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] }); user = await userDAL.create({ email, firstName, lastName, authMethods: [authMethod] });
} }
const isLinkingRequired = !user?.authMethods?.includes(authMethod); const isLinkingRequired = !user?.authMethods?.includes(authMethod);
@@ -1,3 +1,5 @@
import { TSuperAdmin } from "@app/db/schemas/super-admin";
import { AuthMethod } from "./auth-type"; import { AuthMethod } from "./auth-type";
export type TLoginGenServerPublicKeyDTO = { export type TLoginGenServerPublicKeyDTO = {
@@ -29,4 +31,5 @@ export type TOauthLoginDTO = {
authMethod: AuthMethod; authMethod: AuthMethod;
callbackPort?: string; callbackPort?: string;
isSignupAllowed?: boolean; isSignupAllowed?: boolean;
serverCfg?: TSuperAdmin;
}; };
+1
View File
@@ -2,6 +2,7 @@ export type TServerConfig = {
initialized: boolean; initialized: boolean;
allowSignUp: boolean; allowSignUp: boolean;
inviteOnlySignUp: boolean; inviteOnlySignUp: boolean;
allowSpecificDomainSignUp?: string;
isMigrationModeOn?: boolean; isMigrationModeOn?: boolean;
}; };
@@ -1,9 +1,14 @@
import { useEffect, useState } from "react"; import { FormEvent, useEffect, useState } from "react";
import { useRouter } from "next/router"; 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 { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { import {
Button,
ContentLoader, ContentLoader,
FormControl,
Input,
Select, Select,
SelectItem, SelectItem,
Tab, Tab,
@@ -24,11 +29,13 @@ export const AdminDashboardPage = () => {
const router = useRouter(); const router = useRouter();
const data = useServerConfig(); const data = useServerConfig();
const [signUpMode, setSignUpMode] = useState<SignUpMode>("invite-only"); const [signUpMode, setSignUpMode] = useState<SignUpMode>("invite-only");
const [allowSpecificDomain, setAllowSpecificDomain] = useState<string | undefined>();
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 { createNotification } = useNotificationContext();
const isNotAllowed = !user?.superAdmin; const isNotAllowed = !user?.superAdmin;
@@ -49,22 +56,28 @@ export const AdminDashboardPage = () => {
} }
if (config.inviteOnlySignUp) { if (config.inviteOnlySignUp) {
setSignUpMode("invite-only"); setSignUpMode("invite-only");
return; } else {
}
setSignUpMode("anyone"); setSignUpMode("anyone");
}
if (config.allowSpecificDomainSignUp) {
setAllowSpecificDomain(config.allowSpecificDomainSignUp);
}
}, [config]); }, [config]);
function handleSignUpModeChange(newSignUpMode: SignUpMode) { async function handleSubmit(e: FormEvent) {
config.allowSignUp = newSignUpMode !== "disabled"; e.preventDefault();
config.inviteOnlySignUp = newSignUpMode === "invite-only";
config.allowSignUp = signUpMode !== "disabled";
config.inviteOnlySignUp = signUpMode === "invite-only";
config.allowSpecificDomainSignUp = signUpMode === "anyone" ? allowSpecificDomain : "";
await updateServerConfig(config);
createNotification({ createNotification({
text: "Successfully changed sign up mode.", text: "Successfully changed sign up mode.",
type: "success" type: "success"
}); });
updateServerConfig(config);
setSignUpMode(newSignUpMode);
} }
return ( return (
@@ -86,12 +99,18 @@ export const AdminDashboardPage = () => {
</div> </div>
</TabList> </TabList>
<TabPanel value={TabSections.Settings}> <TabPanel value={TabSections.Settings}>
<div className="flex items-center justify-between space-x-4"> <form
<div className="label"> Allow user to Sign Up </div> onSubmit={handleSubmit}
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
>
<div className="flex justify-between">
<div className="mb-4 text-xl font-semibold text-mineshaft-100">
Allow user to Sign Up
</div>
<Select <Select
className="w-36 bg-mineshaft-700" className="w-60 bg-mineshaft-700"
dropdownContainerClassName="bg-mineshaft-700" dropdownContainerClassName="bg-mineshaft-700"
onValueChange={(state) => handleSignUpModeChange(state as SignUpMode)} onValueChange={(state) => setSignUpMode(state as SignUpMode)}
value={signUpMode} value={signUpMode}
isDisabled={isNotAllowed} isDisabled={isNotAllowed}
> >
@@ -100,6 +119,29 @@ export const AdminDashboardPage = () => {
<SelectItem value="anyone">Anyone</SelectItem> <SelectItem value="anyone">Anyone</SelectItem>
</Select> </Select>
</div> </div>
{signUpMode === "anyone" && (
<div className="mt-4 flex items-center justify-between">
<div className="mb-4 flex text-mineshaft-100">
Allow email with only specific domain
</div>
<FormControl label="Leave blank to allow any domain handle">
<div className="w-60">
<Input
placeholder="domain.com"
leftIcon={<FontAwesomeIcon icon={faAt} />}
value={allowSpecificDomain}
onChange={(ev) => setAllowSpecificDomain(ev.target.value)}
/>
</div>
</FormControl>
</div>
)}
<Button colorSchema="primary" variant="outline_bg" type="submit">
Save
</Button>
</form>
</TabPanel> </TabPanel>
</Tabs> </Tabs>
</div> </div>