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;
}; };
+1 -1
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>
@@ -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";
@@ -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"
}); });