Merge branch 'feat/automated-instance-bootstrapping' of https://github.com/Infisical/infisical into feat/automated-instance-bootstrapping

This commit is contained in:
Sheen
2025-03-18 18:37:42 +00:00
9 changed files with 181 additions and 67 deletions
+54 -4
View File
@@ -435,6 +435,42 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
method: "DELETE",
url: "/user-management/users/:userId/admin-access",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
userId: z.string()
}),
response: {
200: z.object({
user: UsersSchema.pick({
username: true,
firstName: true,
lastName: true,
email: true,
id: true
})
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
const user = await server.services.superAdmin.deleteUserSuperAdminAccess(req.params.userId);
return {
user
};
}
});
server.route({ server.route({
method: "POST", method: "POST",
url: "/bootstrap", url: "/bootstrap",
@@ -450,9 +486,23 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
response: { response: {
200: z.object({ 200: z.object({
message: z.string(), message: z.string(),
user: UsersSchema, user: UsersSchema.pick({
organization: OrganizationsSchema, username: true,
identity: IdentitiesSchema.extend({ firstName: true,
lastName: true,
email: true,
id: true,
superAdmin: true
}),
organization: OrganizationsSchema.pick({
id: true,
name: true,
slug: true
}),
identity: IdentitiesSchema.pick({
id: true,
name: true
}).extend({
credentials: z.object({ credentials: z.object({
token: z.string() token: z.string()
}) // would just be Token AUTH for now }) // would just be Token AUTH for now
@@ -478,7 +528,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
}); });
return { return {
message: "Successfully boostrapped instance", message: "Successfully bootstrapped instance",
user: user.user, user: user.user,
organization, organization,
identity: machineIdentity identity: machineIdentity
@@ -27,7 +27,7 @@ import { UserAliasType } from "../user-alias/user-alias-types";
import { TSuperAdminDALFactory } from "./super-admin-dal"; import { TSuperAdminDALFactory } from "./super-admin-dal";
import { import {
LoginMethod, LoginMethod,
TAdminBoostrapInstanceDTO, TAdminBootstrapInstanceDTO,
TAdminGetIdentitiesDTO, TAdminGetIdentitiesDTO,
TAdminGetUsersDTO, TAdminGetUsersDTO,
TAdminSignUpDTO TAdminSignUpDTO
@@ -291,7 +291,7 @@ export const superAdminServiceFactory = ({
return { token, user: userInfo, organization }; return { token, user: userInfo, organization };
}; };
const bootstrapInstance = async ({ email, password, organizationName }: TAdminBoostrapInstanceDTO) => { const bootstrapInstance = async ({ email, password, organizationName }: TAdminBootstrapInstanceDTO) => {
const appCfg = getConfig(); const appCfg = getConfig();
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID); const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
if (serverCfg?.initialized) { if (serverCfg?.initialized) {
@@ -453,6 +453,17 @@ export const superAdminServiceFactory = ({
return identity; return identity;
}; };
const deleteUserSuperAdminAccess = async (userId: string) => {
const user = await userDAL.findById(userId);
if (!user) {
throw new NotFoundError({ name: "User", message: "User not found" });
}
const updatedUser = userDAL.updateById(userId, { superAdmin: false });
return updatedUser;
};
const getIdentities = async ({ offset, limit, searchTerm }: TAdminGetIdentitiesDTO) => { const getIdentities = async ({ offset, limit, searchTerm }: TAdminGetIdentitiesDTO) => {
const identities = await identityDAL.getIdentitiesByFilter({ const identities = await identityDAL.getIdentitiesByFilter({
limit, limit,
@@ -571,6 +582,7 @@ export const superAdminServiceFactory = ({
updateRootEncryptionStrategy, updateRootEncryptionStrategy,
getConfiguredEncryptionStrategies, getConfiguredEncryptionStrategies,
grantServerAdminAccessToUser, grantServerAdminAccessToUser,
deleteIdentitySuperAdminAccess deleteIdentitySuperAdminAccess,
deleteUserSuperAdminAccess
}; };
}; };
@@ -16,7 +16,7 @@ export type TAdminSignUpDTO = {
userAgent: string; userAgent: string;
}; };
export type TAdminBoostrapInstanceDTO = { export type TAdminBootstrapInstanceDTO = {
email: string; email: string;
password: string; password: string;
organizationName: string; organizationName: string;
+3 -3
View File
@@ -601,8 +601,8 @@ func CallGatewayHeartBeatV1(httpClient *resty.Client) error {
return nil return nil
} }
func CallBootstrapInstance(httpClient *resty.Client, request BootstrapInstanceRequest) (*BootstrapInstanceResponse, error) { func CallBootstrapInstance(httpClient *resty.Client, request BootstrapInstanceRequest) (map[string]interface{}, error) {
var resBody BootstrapInstanceResponse var resBody map[string]interface{}
response, err := httpClient. response, err := httpClient.
R(). R().
SetResult(&resBody). SetResult(&resBody).
@@ -618,5 +618,5 @@ func CallBootstrapInstance(httpClient *resty.Client, request BootstrapInstanceRe
return nil, fmt.Errorf("CallBootstrapInstance: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String()) return nil, fmt.Errorf("CallBootstrapInstance: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
} }
return &resBody, nil return resBody, nil
} }
-17
View File
@@ -661,20 +661,3 @@ type BootstrapInstanceRequest struct {
Organization string `json:"organization"` Organization string `json:"organization"`
Domain string `json:"domain"` Domain string `json:"domain"`
} }
type BootstrapInstanceResponseOrganization struct {
ID string `json:"id"`
Name string `json:"name"`
}
type BootstrapInstanceResponseIdentity struct {
ID string `json:"id"`
Name string `json:"name"`
Credentials interface{} `json:"credentials"`
}
type BootstrapInstanceResponse struct {
Message string `json:"message"`
Organization BootstrapInstanceResponseOrganization `json:"organization"`
Identity BootstrapInstanceResponseIdentity `json:"identity"`
}
+2
View File
@@ -70,11 +70,13 @@ var bootstrapCmd = &cobra.Command{
if err != nil { if err != nil {
log.Error().Msgf("Failed to bootstrap instance: %v", err) log.Error().Msgf("Failed to bootstrap instance: %v", err)
return
} }
responseJSON, err := json.MarshalIndent(bootstrapResponse, "", " ") responseJSON, err := json.MarshalIndent(bootstrapResponse, "", " ")
if err != nil { if err != nil {
log.Fatal().Msgf("Failed to convert response to JSON: %v", err) log.Fatal().Msgf("Failed to convert response to JSON: %v", err)
return
} }
fmt.Println(string(responseJSON)) fmt.Println(string(responseJSON))
}, },
+1
View File
@@ -3,6 +3,7 @@ export {
useAdminGrantServerAdminAccess, useAdminGrantServerAdminAccess,
useAdminRemoveIdentitySuperAdminAccess, useAdminRemoveIdentitySuperAdminAccess,
useCreateAdminUser, useCreateAdminUser,
useRemoveUserServerAdminAccess,
useUpdateAdminSlackConfig, useUpdateAdminSlackConfig,
useUpdateServerConfig, useUpdateServerConfig,
useUpdateServerEncryptionStrategy useUpdateServerEncryptionStrategy
+16
View File
@@ -88,6 +88,22 @@ export const useAdminRemoveIdentitySuperAdminAccess = () => {
}); });
}; };
export const useRemoveUserServerAdminAccess = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (userId: string) => {
await apiRequest.delete(`/api/v1/admin/user-management/users/${userId}/admin-access`);
return {};
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [adminStandaloneKeys.getUsers]
});
}
});
};
export const useAdminGrantServerAdminAccess = () => { export const useAdminGrantServerAdminAccess = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
@@ -33,22 +33,26 @@ import {
THead, THead,
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { useSubscription, useUser } from "@app/context"; import { useSubscription } from "@app/context";
import { useDebounce, usePopUp } from "@app/hooks"; import { useDebounce, usePopUp } from "@app/hooks";
import { import {
useAdminDeleteUser, useAdminDeleteUser,
useAdminGetUsers, useAdminGetUsers,
useAdminGrantServerAdminAccess useAdminGrantServerAdminAccess,
useRemoveUserServerAdminAccess
} from "@app/hooks/api"; } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
const addServerAdminUpgradePlanMessage = "Granting another user Server Admin permissions"; const addServerAdminUpgradePlanMessage = "Granting another user Server Admin permissions";
const removeServerAdminUpgradePlanMessage = "Removing Server Admin permissions from user";
const UserPanelTable = ({ const UserPanelTable = ({
handlePopUpOpen handlePopUpOpen
}: { }: {
handlePopUpOpen: ( handlePopUpOpen: (
popUpName: keyof UsePopUpState<["removeUser", "upgradePlan", "upgradeToServerAdmin"]>, popUpName: keyof UsePopUpState<
["removeUser", "upgradePlan", "upgradeToServerAdmin", "removeServerAdmin"]
>,
data?: { data?: {
username: string; username: string;
id: string; id: string;
@@ -58,8 +62,6 @@ const UserPanelTable = ({
}) => { }) => {
const [searchUserFilter, setSearchUserFilter] = useState(""); const [searchUserFilter, setSearchUserFilter] = useState("");
const [adminsOnly, setAdminsOnly] = useState(false); const [adminsOnly, setAdminsOnly] = useState(false);
const { user } = useUser();
const userId = user?.id || "";
const [debouncedSearchTerm] = useDebounce(searchUserFilter, 500); const [debouncedSearchTerm] = useDebounce(searchUserFilter, 500);
const { subscription } = useSubscription(); const { subscription } = useSubscription();
@@ -143,7 +145,6 @@ const UserPanelTable = ({
</Td> </Td>
<Td className="w-5/12">{email}</Td> <Td className="w-5/12">{email}</Td>
<Td> <Td>
{userId !== id && (
<div className="flex justify-end"> <div className="flex justify-end">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg"> <DropdownMenuTrigger asChild className="rounded-lg">
@@ -178,10 +179,27 @@ const UserPanelTable = ({
Make User Server Admin Make User Server Admin
</DropdownMenuItem> </DropdownMenuItem>
)} )}
{superAdmin && (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
if (!subscription?.instanceUserManagement) {
handlePopUpOpen("upgradePlan", {
username,
id,
message: removeServerAdminUpgradePlanMessage
});
return;
}
handlePopUpOpen("removeServerAdmin", { username, id });
}}
>
Remove Server Admin
</DropdownMenuItem>
)}
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div> </div>
)}
</Td> </Td>
</Tr> </Tr>
); );
@@ -212,11 +230,13 @@ export const UserPanel = () => {
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
"removeUser", "removeUser",
"upgradePlan", "upgradePlan",
"upgradeToServerAdmin" "upgradeToServerAdmin",
"removeServerAdmin"
] as const); ] as const);
const { mutateAsync: deleteUser } = useAdminDeleteUser(); const { mutateAsync: deleteUser } = useAdminDeleteUser();
const { mutateAsync: grantAdminAccess } = useAdminGrantServerAdminAccess(); const { mutateAsync: grantAdminAccess } = useAdminGrantServerAdminAccess();
const { mutateAsync: removeAdminAccess } = useRemoveUserServerAdminAccess();
const handleRemoveUser = async () => { const handleRemoveUser = async () => {
const { id } = popUp?.removeUser?.data as { id: string; username: string }; const { id } = popUp?.removeUser?.data as { id: string; username: string };
@@ -256,6 +276,25 @@ export const UserPanel = () => {
handlePopUpClose("upgradeToServerAdmin"); handlePopUpClose("upgradeToServerAdmin");
}; };
const handleRemoveServerAdminAccess = async () => {
const { id } = popUp?.removeServerAdmin?.data as { id: string; username: string };
try {
await removeAdminAccess(id);
createNotification({
type: "success",
text: "Successfully removed server admin access from user"
});
} catch {
createNotification({
type: "error",
text: "Error removing server admin access from user"
});
}
handlePopUpClose("removeServerAdmin");
};
return ( return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4"> <div className="mb-4">
@@ -282,6 +321,17 @@ export const UserPanel = () => {
onDeleteApproved={handleGrantServerAdminAccess} onDeleteApproved={handleGrantServerAdminAccess}
buttonText="Grant Access" buttonText="Grant Access"
/> />
<DeleteActionModal
isOpen={popUp.removeServerAdmin.isOpen}
title={`Are you sure want to remove Server Admin permissions from ${
(popUp?.removeServerAdmin?.data as { id: string; username: string })?.username || ""
}?`}
subTitle=""
onChange={(isOpen) => handlePopUpToggle("removeServerAdmin", isOpen)}
deleteKey="confirm"
onDeleteApproved={handleRemoveServerAdminAccess}
buttonText="Remove Access"
/>
<UpgradePlanModal <UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen} isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}