Merge remote-tracking branch 'origin' into groups

This commit is contained in:
Tuan Dang
2024-03-19 10:25:03 -07:00
21 changed files with 90 additions and 116 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
.min(1) .min(1)
.trim() .trim()
.refine( .refine(
(val) => Object.keys(OrgMembershipRole).includes(val), (val) => !Object.keys(OrgMembershipRole).includes(val),
"Please choose a different slug, the slug you have entered is reserved" "Please choose a different slug, the slug you have entered is reserved"
) )
.refine((v) => slugify(v) === v, { .refine((v) => slugify(v) === v, {
@@ -9,6 +9,7 @@ import jmespath from "jmespath";
import knex from "knex"; import knex from "knex";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { getDbConnectionHost } from "@app/lib/knex";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TAssignOp, TDbProviderClients, TDirectAssignOp, THttpProviderFunction } from "../templates/types"; import { TAssignOp, TDbProviderClients, TDirectAssignOp, THttpProviderFunction } from "../templates/types";
@@ -89,7 +90,7 @@ export const secretRotationDbFn = async ({
const appCfg = getConfig(); const appCfg = getConfig();
const ssl = ca ? { rejectUnauthorized: false, ca } : undefined; const ssl = ca ? { rejectUnauthorized: false, ca } : undefined;
if (host === "localhost" || host === "127.0.0.1" || appCfg.DB_CONNECTION_URI.includes(host)) if (host === "localhost" || host === "127.0.0.1" || getDbConnectionHost(appCfg.DB_CONNECTION_URI) === host)
throw new Error("Invalid db host"); throw new Error("Invalid db host");
const db = knex({ const db = knex({
+11
View File
@@ -0,0 +1,11 @@
import { URL } from "url"; // Import the URL class
export const getDbConnectionHost = (urlString: string) => {
try {
const url = new URL(urlString);
// Split hostname and port (if provided)
return url.hostname.split(":")[0];
} catch (error) {
return null;
}
};
+1
View File
@@ -4,6 +4,7 @@ import { Tables } from "knex/types/tables";
import { DatabaseError } from "../errors"; import { DatabaseError } from "../errors";
export * from "./connection";
export * from "./join"; export * from "./join";
export * from "./select"; export * from "./select";
+4 -4
View File
@@ -14,13 +14,13 @@ export const fastifySwagger = fp(async (fastify) => {
version: "0.0.1" version: "0.0.1"
}, },
servers: [ servers: [
{
url: "http://localhost:8080",
description: "Local server"
},
{ {
url: "https://app.infisical.com", url: "https://app.infisical.com",
description: "Production server" description: "Production server"
},
{
url: "http://localhost:8080",
description: "Local server"
} }
], ],
components: { components: {
@@ -150,8 +150,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
message: "Slug must be a valid slug" message: "Slug must be a valid slug"
}) })
.optional() .optional()
.describe(PROJECTS.CREATE.slug), .describe(PROJECTS.CREATE.slug)
organizationSlug: z.string().trim().describe(PROJECTS.CREATE.organizationSlug)
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -166,7 +165,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
orgSlug: req.body.organizationSlug,
workspaceName: req.body.projectName, workspaceName: req.body.projectName,
slug: req.body.slug slug: req.body.slug
}); });
@@ -92,7 +92,6 @@ export const projectServiceFactory = ({
* Create workspace. Make user the admin * Create workspace. Make user the admin
* */ * */
const createProject = async ({ const createProject = async ({
orgSlug,
actor, actor,
actorId, actorId,
actorOrgId, actorOrgId,
@@ -100,13 +99,7 @@ export const projectServiceFactory = ({
workspaceName, workspaceName,
slug: projectSlug slug: projectSlug
}: TCreateProjectDTO) => { }: TCreateProjectDTO) => {
if (!orgSlug) { const organization = await orgDAL.findOne({ id: actorOrgId });
throw new BadRequestError({
message: "Must provide organization slug to create project"
});
}
const organization = await orgDAL.findOne({ slug: orgSlug });
const { permission, membership: orgMembership } = await permissionService.getOrgPermission( const { permission, membership: orgMembership } = await permissionService.getOrgPermission(
actor, actor,
@@ -24,7 +24,6 @@ export type TCreateProjectDTO = {
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorId: string; actorId: string;
actorOrgId?: string; actorOrgId?: string;
orgSlug: string;
workspaceName: string; workspaceName: string;
slug?: string; slug?: string;
}; };
+2 -2
View File
@@ -9,7 +9,7 @@ This guide walks through how you can use these paid features in Infisical.
<Steps> <Steps>
<Step title="Purchase a license"> <Step title="Purchase a license">
Start by either signing up for a free demo [here](https://infisical.com/schedule-demo) or contacting team@infisical.com to purchase a license. Start by either signing up for a free demo [here](https://infisical.com/schedule-demo) or contacting sales@infisical.com to purchase a license.
Once purchased, you will be issued a license key. Once purchased, you will be issued a license key.
</Step> </Step>
@@ -25,4 +25,4 @@ This guide walks through how you can use these paid features in Infisical.
Once the license expires, Infisical will continue to run, but EE features will be disabled until the license is renewed or a new one is purchased. Once the license expires, Infisical will continue to run, but EE features will be disabled until the license is renewed or a new one is purchased.
</Note> </Note>
</Step> </Step>
</Steps> </Steps>
@@ -196,10 +196,8 @@ export default function UserInfoStep({
const userOrgs = await fetchOrganizations(); const userOrgs = await fetchOrganizations();
const orgSlug = userOrgs[0]?.slug;
const orgId = userOrgs[0]?.id; const orgId = userOrgs[0]?.id;
const project = await ProjectService.initProject({ const project = await ProjectService.initProject({
organizationSlug: orgSlug,
projectName: "Example Project" projectName: "Example Project"
}); });
@@ -7,6 +7,7 @@
/* eslint-disable vars-on-top */ /* eslint-disable vars-on-top */
/* eslint-disable no-var */ /* eslint-disable no-var */
/* eslint-disable func-names */ /* eslint-disable func-names */
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck // @ts-nocheck
import { INTERCOMid as APPid } from "@app/components/utilities/config"; import { INTERCOMid as APPid } from "@app/components/utilities/config";
+1
View File
@@ -1,3 +1,4 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck // @ts-nocheck
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable global-require */ /* eslint-disable global-require */
+1 -8
View File
@@ -77,18 +77,11 @@ const secretsToBeAdded = [
* @param {String} obj.projectName - name of new project * @param {String} obj.projectName - name of new project
* @returns {Project} project - new project * @returns {Project} project - new project
*/ */
const initProjectHelper = async ({ const initProjectHelper = async ({ projectName }: { projectName: string }) => {
organizationSlug,
projectName
}: {
organizationSlug: string;
projectName: string;
}) => {
// create new project // create new project
const { const {
data: { project } data: { project }
} = await createWorkspace({ } = await createWorkspace({
organizationSlug,
projectName projectName
}); });
+10 -10
View File
@@ -199,19 +199,17 @@ export const useGetWorkspaceIntegrations = (workspaceId: string) =>
}); });
export const createWorkspace = ({ export const createWorkspace = ({
organizationSlug,
projectName projectName
}: CreateWorkspaceDTO): Promise<{ data: { project: Workspace } }> => { }: CreateWorkspaceDTO): Promise<{ data: { project: Workspace } }> => {
return apiRequest.post("/api/v2/workspace", { projectName, organizationSlug }); return apiRequest.post("/api/v2/workspace", { projectName });
}; };
export const useCreateWorkspace = () => { export const useCreateWorkspace = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation<{ data: { project: Workspace } }, {}, CreateWorkspaceDTO>({ return useMutation<{ data: { project: Workspace } }, {}, CreateWorkspaceDTO>({
mutationFn: async ({ organizationSlug, projectName }) => mutationFn: async ({ projectName }) =>
createWorkspace({ createWorkspace({
organizationSlug,
projectName projectName
}), }),
onSuccess: () => { onSuccess: () => {
@@ -325,7 +323,13 @@ export const useDeleteUserFromWorkspace = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async ({ usernames, workspaceId }: { workspaceId: string; usernames: string[] }) => { mutationFn: async ({
usernames,
workspaceId
}: {
workspaceId: string;
usernames: string[];
}) => {
const { const {
data: { deletedMembership } data: { deletedMembership }
} = await apiRequest.delete(`/api/v2/workspace/${workspaceId}/memberships`, { } = await apiRequest.delete(`/api/v2/workspace/${workspaceId}/memberships`, {
@@ -391,11 +395,7 @@ export const useAddIdentityToWorkspace = () => {
export const useUpdateIdentityWorkspaceRole = () => { export const useUpdateIdentityWorkspaceRole = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async ({ mutationFn: async ({ identityId, workspaceId, roles }: TUpdateWorkspaceIdentityRoleDTO) => {
identityId,
workspaceId,
roles
}:TUpdateWorkspaceIdentityRoleDTO)=> {
const { const {
data: { identityMembership } data: { identityMembership }
} = await apiRequest.patch( } = await apiRequest.patch(
+18 -19
View File
@@ -45,7 +45,6 @@ export type TGetUpgradeProjectStatusDTO = {
// mutation dto // mutation dto
export type CreateWorkspaceDTO = { export type CreateWorkspaceDTO = {
projectName: string; projectName: string;
organizationSlug: string;
}; };
export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string }; export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string };
@@ -82,16 +81,16 @@ export type TUpdateWorkspaceUserRoleDTO = {
workspaceId: string; workspaceId: string;
roles: ( roles: (
| { | {
role: string; role: string;
isTemporary?: false; isTemporary?: false;
} }
| { | {
role: string; role: string;
isTemporary: true; isTemporary: true;
temporaryMode: ProjectUserMembershipTemporaryMode; temporaryMode: ProjectUserMembershipTemporaryMode;
temporaryRange: string; temporaryRange: string;
temporaryAccessStartTime: string; temporaryAccessStartTime: string;
} }
)[]; )[];
}; };
@@ -100,15 +99,15 @@ export type TUpdateWorkspaceIdentityRoleDTO = {
workspaceId: string; workspaceId: string;
roles: ( roles: (
| { | {
role: string; role: string;
isTemporary?: false; isTemporary?: false;
} }
| { | {
role: string; role: string;
isTemporary: true; isTemporary: true;
temporaryMode: ProjectUserMembershipTemporaryMode; temporaryMode: ProjectUserMembershipTemporaryMode;
temporaryRange: string; temporaryRange: string;
temporaryAccessStartTime: string; temporaryAccessStartTime: string;
} }
)[]; )[];
}; };
@@ -4,6 +4,7 @@
/* eslint-disable vars-on-top */ /* eslint-disable vars-on-top */
/* eslint-disable no-var */ /* eslint-disable no-var */
/* eslint-disable func-names */ /* eslint-disable func-names */
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck // @ts-nocheck
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -91,7 +92,7 @@ export const AdminLayout = ({ children }: LayoutProps) => {
console.error(error); console.error(error);
} }
}; };
return ( return (
<> <>
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex"> <div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex">
@@ -236,7 +236,6 @@ export const AppLayout = ({ children }: LayoutProps) => {
project: { id: newProjectId } project: { id: newProjectId }
} }
} = await createWs.mutateAsync({ } = await createWs.mutateAsync({
organizationSlug: currentOrg.slug,
projectName: name projectName: name
}); });
@@ -1,5 +1,3 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
import { useCallback, useEffect } from "react"; import { useCallback, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Head from "next/head"; import Head from "next/head";
@@ -54,42 +52,40 @@ export default function LoginPage() {
const handleSelectOrganization = useCallback( const handleSelectOrganization = useCallback(
async (organization: Organization) => { async (organization: Organization) => {
const callbackPort = queryParams.get("callback_port");
if (organization.authEnforced) { if (organization.authEnforced) {
// org has an org-level auth method enabled (e.g. SAML) // org has an org-level auth method enabled (e.g. SAML)
// -> logout + redirect to SAML SSO // -> logout + redirect to SAML SSO
let samlUrl = `/api/v1/sso/redirect/saml2/organizations/${organization.slug}`;
if (callbackPort) {
samlUrl += `?callback_port=${callbackPort}`;
}
await logout.mutateAsync(); await logout.mutateAsync();
window.open(`/api/v1/sso/redirect/saml2/organizations/${organization.slug}`); window.open(samlUrl);
window.close(); window.close();
return; return;
} }
const { token } = await selectOrg.mutateAsync({ organizationId: organization.id }); const { token } = await selectOrg.mutateAsync({ organizationId: organization.id });
const callbackPort = queryParams.get("callback_port");
if (callbackPort) { if (callbackPort) {
const privateKey = localStorage.getItem("PRIVATE_KEY"); const privateKey = localStorage.getItem("PRIVATE_KEY");
if (!privateKey) { let error: string | null = null;
createNotification({
text: "Private key not found",
type: "error"
});
}
if (!user.email) { if (!privateKey) error = "Private key not found";
createNotification({ if (!user.email) error = "User email not found";
text: "User email not found", if (!token) error = "No token found";
type: "error"
});
}
if (!token) { if (error) {
createNotification({ createNotification({
text: "No token found", text: error,
type: "error" type: "error"
}); });
return;
} }
const payload = { const payload = {
@@ -172,33 +168,22 @@ export default function LoginPage() {
<Spinner /> <Spinner />
) : ( ) : (
organizations.data?.map((org) => ( organizations.data?.map((org) => (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
<div <div
onClick={() => handleSelectOrganization(org)} onClick={() => handleSelectOrganization(org)}
key={org.id} key={org.id}
className="group flex cursor-pointer items-center justify-between rounded-md bg-mineshaft-700 px-4 py-3 capitalize text-gray-200 shadow-md transition-colors hover:bg-mineshaft-600" className="group flex cursor-pointer items-center justify-between rounded-md bg-mineshaft-700 px-4 py-3 capitalize text-gray-200 shadow-md transition-colors hover:bg-mineshaft-600"
> >
<p className="transition-colors">{org.name}</p> <p className="truncate transition-colors">{org.name}</p>
<FontAwesomeIcon <FontAwesomeIcon
icon={faArrowRight} icon={faArrowRight}
className="text-gray-400 transition-colors group-hover:text-primary-500" className="text-gray-400 transition-all group-hover:translate-x-2 group-hover:text-primary-500"
/> />
</div> </div>
)) ))
)} )}
</div> </div>
<Button
className="mt-8"
colorSchema="primary"
isLoading={logout.isLoading}
isDisabled={logout.isLoading}
variant="solid"
size="lg"
onClick={handleLogout}
>
Logout
</Button>
</form> </form>
</div> </div>
+15 -12
View File
@@ -454,7 +454,12 @@ const LearningItemSquare = ({
}; };
const formSchema = yup.object({ const formSchema = yup.object({
name: yup.string().required().label("Project Name").trim().max(64, "Too long, maximum length is 64 characters"), name: yup
.string()
.required()
.label("Project Name")
.trim()
.max(64, "Too long, maximum length is 64 characters"),
addMembers: yup.bool().required().label("Add Members") addMembers: yup.bool().required().label("Add Members")
}); });
@@ -507,7 +512,6 @@ const OrganizationPage = withPermission(
project: { id: newProjectId } project: { id: newProjectId }
} }
} = await createWs.mutateAsync({ } = await createWs.mutateAsync({
organizationSlug: currentOrg.slug,
projectName: name projectName: name
}); });
@@ -631,22 +635,21 @@ const OrganizationPage = withPermission(
{orgWorkspaces {orgWorkspaces
.filter((ws) => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase())) .filter((ws) => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase()))
.map((workspace) => ( .map((workspace) => (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
<div <div
onClick={() => {
router.push(`/project/${workspace.id}/secrets/overview`);
localStorage.setItem("projectData.id", workspace.id);
}}
key={workspace.id} key={workspace.id}
className="min-w-72 flex h-40 flex-col justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4" className="min-w-72 group flex h-40 cursor-pointer flex-col justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
> >
<div className="mt-0 truncate text-lg text-mineshaft-100">{workspace.name}</div> <div className="mt-0 truncate text-lg text-mineshaft-100">{workspace.name}</div>
<div className="mt-0 pb-6 text-sm text-mineshaft-300"> <div className="mt-0 pb-6 text-sm text-mineshaft-300">
{workspace.environments?.length || 0} environments {workspace.environments?.length || 0} environments
</div> </div>
<button <button type="button">
type="button" <div className="group ml-auto w-max cursor-pointer rounded-full border border-mineshaft-600 bg-mineshaft-900 py-2 px-4 text-sm text-mineshaft-300 transition-all group-hover:border-primary-500/80 group-hover:bg-primary-800/20 group-hover:text-mineshaft-200">
onClick={() => {
router.push(`/project/${workspace.id}/secrets/overview`);
localStorage.setItem("projectData.id", workspace.id);
}}
>
<div className="group ml-auto w-max cursor-pointer rounded-full border border-mineshaft-600 bg-mineshaft-900 py-2 px-4 text-sm text-mineshaft-300 hover:border-primary-500/80 hover:bg-primary-800/20 hover:text-mineshaft-200">
Explore{" "} Explore{" "}
<FontAwesomeIcon <FontAwesomeIcon
icon={faArrowRight} icon={faArrowRight}
@@ -693,7 +696,7 @@ const OrganizationPage = withPermission(
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="group ml-auto w-max cursor-default rounded-full border border-mineshaft-600 bg-mineshaft-900 py-2 px-4 text-sm text-mineshaft-300 hover:border-primary-500/80 hover:bg-primary-800/20 hover:text-mineshaft-200" className="group ml-auto w-max cursor-default rounded-full border border-mineshaft-600 bg-mineshaft-900 py-2 px-4 text-sm text-mineshaft-300 transition-all hover:border-primary-500/80 hover:bg-primary-800/20 hover:text-mineshaft-200"
href={feature.link} href={feature.link}
> >
Learn more{" "} Learn more{" "}
+1 -8
View File
@@ -9,15 +9,8 @@ class ProjectService {
* @param {String} obj.projectName - name of new project * @param {String} obj.projectName - name of new project
* @returns {Project} project - new project * @returns {Project} project - new project
*/ */
static async initProject({ static async initProject({ projectName }: { projectName: string }) {
organizationSlug,
projectName
}: {
organizationSlug: string;
projectName: string;
}) {
return initProjectHelper({ return initProjectHelper({
organizationSlug,
projectName projectName
}); });
} }
@@ -189,14 +189,12 @@ export const UserInfoSSOStep = ({
const userOrgs = await fetchOrganizations(); const userOrgs = await fetchOrganizations();
const orgId = userOrgs[0]?.id; const orgId = userOrgs[0]?.id;
const orgSlug = userOrgs[0]?.slug;
await selectOrganization({ await selectOrganization({
organizationId: orgId organizationId: orgId
}); });
const project = await ProjectService.initProject({ const project = await ProjectService.initProject({
organizationSlug: orgSlug,
projectName: "Example Project" projectName: "Example Project"
}); });