mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 13:27:46 +00:00
Add restrict signup based on domain
This commit is contained in:
@@ -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");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|||||||
@@ -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}` };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.allowSpecificDomainSignUp) {
|
||||||
|
setAllowSpecificDomain(config.allowSpecificDomainSignUp);
|
||||||
}
|
}
|
||||||
setSignUpMode("anyone");
|
|
||||||
}, [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,20 +99,49 @@ 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}
|
||||||
<Select
|
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||||
className="w-36 bg-mineshaft-700"
|
>
|
||||||
dropdownContainerClassName="bg-mineshaft-700"
|
<div className="flex justify-between">
|
||||||
onValueChange={(state) => handleSignUpModeChange(state as SignUpMode)}
|
<div className="mb-4 text-xl font-semibold text-mineshaft-100">
|
||||||
value={signUpMode}
|
Allow user to Sign Up
|
||||||
isDisabled={isNotAllowed}
|
</div>
|
||||||
>
|
<Select
|
||||||
<SelectItem value="disabled">Disabled</SelectItem>
|
className="w-60 bg-mineshaft-700"
|
||||||
<SelectItem value="invite-only">Invite Only</SelectItem>
|
dropdownContainerClassName="bg-mineshaft-700"
|
||||||
<SelectItem value="anyone">Anyone</SelectItem>
|
onValueChange={(state) => setSignUpMode(state as SignUpMode)}
|
||||||
</Select>
|
value={signUpMode}
|
||||||
</div>
|
isDisabled={isNotAllowed}
|
||||||
|
>
|
||||||
|
<SelectItem value="disabled">Disabled</SelectItem>
|
||||||
|
<SelectItem value="invite-only">Invite Only</SelectItem>
|
||||||
|
<SelectItem value="anyone">Anyone</SelectItem>
|
||||||
|
</Select>
|
||||||
|
</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>
|
||||||
|
|||||||
Reference in New Issue
Block a user