diff --git a/.env.example b/.env.example index 8a714f77d..8463fea92 100644 --- a/.env.example +++ b/.env.example @@ -4,7 +4,7 @@ ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218 # Required -DB_CONNECTION_URI=postgres://infisical:infisical@db:5432/infisical +DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} # JWT # Required secrets to sign JWT tokens diff --git a/.github/resources/changelog-generator.py b/.github/resources/changelog-generator.py index 1e8c67871..7dd8140ee 100644 --- a/.github/resources/changelog-generator.py +++ b/.github/resources/changelog-generator.py @@ -12,7 +12,7 @@ import uuid REPO_OWNER = "infisical" REPO_NAME = "infisical" TOKEN = os.environ["GITHUB_TOKEN"] -# SLACK_WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"] +SLACK_WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"] OPENAI_API_KEY = os.environ["OPENAI_API_KEY"] SLACK_MSG_COLOR = "#36a64f" @@ -30,6 +30,23 @@ def set_multiline_output(name, value): print(value, file=fh) print(delimiter, file=fh) +def post_changelog_to_slack(changelog, tag): + slack_payload = { + "text": "Hey team, it's changelog time! :wave:", + "attachments": [ + { + "color": SLACK_MSG_COLOR, + "title": f"🗓️Infisical Changelog - {tag}", + "text": changelog, + } + ], + } + + response = requests.post(SLACK_WEBHOOK_URL, json=slack_payload) + + if response.status_code != 200: + raise Exception("Failed to post changelog to Slack.") + def find_previous_release_tag(release_tag:str): previous_tag = subprocess.check_output(["git", "describe", "--tags", "--abbrev=0", f"{release_tag}^"]).decode("utf-8").strip() while not(previous_tag.startswith("infisical/")): @@ -123,20 +140,17 @@ The changelog should: 6. Linear Links: note that the Linear link is optional, include it only if provided. 7. Do not wrap your answer in a codeblock. Just output the text, nothing else Here's a good example to follow, please try to match the formatting as closely as possible, only changing the content of the changelog and have some liberty with the introduction. Notice the importance of the formatting of a changelog item: -``` -- : We optimize our ci to strip comments and minify production builds. ()) -``` +- : We optimize our ci to strip comments and minify production builds. ()) And here's an example of the full changelog: -``` + *Features* -• : We optimize our ci to strip comments and minify production builds. () +• : We optimize our ci to strip comments and minify production builds. () *Fixes & Improvements* -• : We optimize our ci to strip comments and minify production builds. () +• : We optimize our ci to strip comments and minify production builds. () *Technical Updates* -• : We optimize our ci to strip comments and minify production builds. () +• : We optimize our ci to strip comments and minify production builds. () Stay tuned for more exciting updates coming soon! -``` And here are the commits: {} """.format( @@ -166,11 +180,11 @@ if __name__ == "__main__": pr_details = extract_commit_details_from_prs(prs) # Generate changelog - changelog = f"## Infisical - {latest_tag}\n\n{generate_changelog_with_openai(pr_details)}" + changelog = generate_changelog_with_openai(pr_details) + post_changelog_to_slack(changelog,latest_tag) # Print or post changelog to Slack - set_multiline_output("changelog", changelog) + # set_multiline_output("changelog", changelog) except Exception as e: - print(str(e)) - + print(str(e)) \ No newline at end of file diff --git a/.github/workflows/generate-release-changelog.yml b/.github/workflows/generate-release-changelog.yml index 48e35497d..e26a304cf 100644 --- a/.github/workflows/generate-release-changelog.yml +++ b/.github/workflows/generate-release-changelog.yml @@ -2,14 +2,21 @@ name: Generate Changelog permissions: contents: write -on: [workflow_dispatch] +on: + workflow_dispatch: + push: + tags: + - "infisical/v*.*.*-postgres" jobs: generate_changelog: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: @@ -24,13 +31,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - - name: Set git identity - run: | - git config user.name 'github-actions[bot]' - git config user.email 'github-actions[bot]@infisical.noreply.github.com' - - name: Save the changelog to file - run: | - echo "${{ steps.gen-changelog.outputs.changelog }}" >> CHANGELOG.md - git add CHANGELOG.md - git commit -m "chore: changelog update" --no-verify - git push origin main + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 1c5db8f00..054359527 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -197,3 +197,14 @@ dockers: - "infisical/cli:{{ .Major }}.{{ .Minor }}" - "infisical/cli:{{ .Major }}" - "infisical/cli:latest" + + - dockerfile: docker/alpine + goos: linux + goarch: arm64 + ids: + - all-other-builds + image_templates: + - "infisical/cli:{{ .Version }}" + - "infisical/cli:{{ .Major }}.{{ .Minor }}" + - "infisical/cli:{{ .Major }}" + - "infisical/cli:latest" diff --git a/backend/src/db/seeds/1-user.ts b/backend/src/db/seeds/1-user.ts index df689f75c..86cd2be34 100644 --- a/backend/src/db/seeds/1-user.ts +++ b/backend/src/db/seeds/1-user.ts @@ -9,7 +9,12 @@ export async function seed(knex: Knex): Promise { await knex(TableName.Users).del(); await knex(TableName.UserEncryptionKey).del(); await knex(TableName.SuperAdmin).del(); - await knex(TableName.SuperAdmin).insert([{ initialized: true, allowSignUp: true }]); + + await knex(TableName.SuperAdmin).insert([ + // eslint-disable-next-line + // @ts-ignore + { id: "00000000-0000-0000-0000-000000000000", initialized: true, allowSignUp: true } + ]); // Inserts seed entries const [user] = await knex(TableName.Users) .insert([ diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 4e5f749a5..def6e69f8 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -278,6 +278,8 @@ export const registerRoutes = async ( incidentContactDAL, tokenService, projectDAL, + projectMembershipDAL, + projectKeyDAL, smtpService, userDAL, orgBotDAL diff --git a/backend/src/services/integration-auth/integration-sync-secret.ts b/backend/src/services/integration-auth/integration-sync-secret.ts index abb6a7b1e..62a4dcfa7 100644 --- a/backend/src/services/integration-auth/integration-sync-secret.ts +++ b/backend/src/services/integration-auth/integration-sync-secret.ts @@ -441,16 +441,19 @@ const syncSecretsAWSParameterStore = async ({ }) => { if (!accessId) return; - AWS.config.update({ + const config = new AWS.Config({ region: integration.region as string, - accessKeyId: accessId, - secretAccessKey: accessToken + credentials: { + accessKeyId: accessId, + secretAccessKey: accessToken + } }); const ssm = new AWS.SSM({ apiVersion: "2014-11-06", region: integration.region as string }); + ssm.config.update(config); const params = { Path: integration.path as string, @@ -514,12 +517,6 @@ const syncSecretsAWSParameterStore = async ({ } }) ); - - AWS.config.update({ - region: undefined, - accessKeyId: undefined, - secretAccessKey: undefined - }); }; /** @@ -541,12 +538,6 @@ const syncSecretsAWSSecretManager = async ({ try { if (!accessId) return; - AWS.config.update({ - region: integration.region as string, - accessKeyId: accessId, - secretAccessKey: accessToken - }); - secretsManager = new SecretsManagerClient({ region: integration.region as string, credentials: { @@ -575,12 +566,6 @@ const syncSecretsAWSSecretManager = async ({ }) ); } - - AWS.config.update({ - region: undefined, - accessKeyId: undefined, - secretAccessKey: undefined - }); } catch (err) { if (err instanceof ResourceNotFoundException && secretsManager) { await secretsManager.send( @@ -590,11 +575,6 @@ const syncSecretsAWSSecretManager = async ({ }) ); } - AWS.config.update({ - region: undefined, - accessKeyId: undefined, - secretAccessKey: undefined - }); } }; diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index a5beb894b..db6d9654d 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -22,6 +22,8 @@ import { ActorType, AuthMethod, AuthTokenType } from "../auth/auth-type"; import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service"; import { TokenType } from "../auth-token/auth-token-types"; import { TProjectDALFactory } from "../project/project-dal"; +import { TProjectKeyDALFactory } from "../project-key/project-key-dal"; +import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal"; import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service"; import { TUserDALFactory } from "../user/user-dal"; import { TIncidentContactsDALFactory } from "./incident-contacts-dal"; @@ -44,6 +46,8 @@ type TOrgServiceFactoryDep = { orgRoleDAL: TOrgRoleDALFactory; userDAL: TUserDALFactory; projectDAL: TProjectDALFactory; + projectMembershipDAL: Pick; + projectKeyDAL: Pick; incidentContactDAL: TIncidentContactsDALFactory; samlConfigDAL: Pick; smtpService: TSmtpService; @@ -65,6 +69,8 @@ export const orgServiceFactory = ({ permissionService, smtpService, projectDAL, + projectMembershipDAL, + projectKeyDAL, tokenService, orgBotDAL, licenseService, @@ -513,10 +519,50 @@ export const orgServiceFactory = ({ const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorOrgId); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member); - const membership = await orgDAL.deleteMembershipById(membershipId, orgId); + const deletedMembership = await orgDAL.transaction(async (tx) => { + const orgMembership = await orgDAL.deleteMembershipById(membershipId, orgId, tx); - await licenseService.updateSubscriptionOrgMemberCount(orgId); - return membership; + if (!orgMembership.userId) { + await licenseService.updateSubscriptionOrgMemberCount(orgId); + return orgMembership; + } + + // Get all the project memberships of the user in the organization + const projectMemberships = await projectMembershipDAL.findProjectMembershipsByUserId(orgId, orgMembership.userId); + + // Delete all the project memberships of the user in the organization + await projectMembershipDAL.delete( + { + $in: { + id: projectMemberships.map((membership) => membership.id) + } + }, + tx + ); + + // Get all the project keys of the user in the organization + const projectKeys = await projectKeyDAL.find({ + $in: { + projectId: projectMemberships.map((membership) => membership.projectId) + }, + receiverId: orgMembership.userId + }); + + // Delete all the project keys of the user in the organization + await projectKeyDAL.delete( + { + $in: { + id: projectKeys.map((key) => key.id) + } + }, + tx + ); + + await licenseService.updateSubscriptionOrgMemberCount(orgId); + return orgMembership; + }); + + return deletedMembership; }; /* diff --git a/backend/src/services/project-membership/project-membership-dal.ts b/backend/src/services/project-membership/project-membership-dal.ts index 02bf28d01..291eb1045 100644 --- a/backend/src/services/project-membership/project-membership-dal.ts +++ b/backend/src/services/project-membership/project-membership-dal.ts @@ -83,5 +83,25 @@ export const projectMembershipDALFactory = (db: TDbClient) => { } }; - return { ...projectMemberOrm, findAllProjectMembers, findProjectGhostUser, findMembershipsByUsername }; + const findProjectMembershipsByUserId = async (orgId: string, userId: string) => { + try { + const memberships = await db(TableName.ProjectMembership) + .where({ userId }) + .join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`) + .where({ [`${TableName.Project}.orgId` as "orgId"]: orgId }) + .select(selectAllTableCols(TableName.ProjectMembership)); + + return memberships; + } catch (error) { + throw new DatabaseError({ error, name: "Find project memberships by user id" }); + } + }; + + return { + ...projectMemberOrm, + findAllProjectMembers, + findProjectGhostUser, + findMembershipsByUsername, + findProjectMembershipsByUserId + }; }; diff --git a/backend/src/services/project/project-queue.ts b/backend/src/services/project/project-queue.ts index efc795cae..e1d45e96d 100644 --- a/backend/src/services/project/project-queue.ts +++ b/backend/src/services/project/project-queue.ts @@ -102,8 +102,11 @@ export const projectQueueFactory = ({ const oldProjectKey = await projectKeyDAL.findLatestProjectKey(data.startedByUserId, data.projectId); - if (!project || !oldProjectKey) { - throw new Error("Project or project key not found"); + if (!project) { + throw new Error("Project not found"); + } + if (!oldProjectKey) { + throw new Error("Old project key not found"); } if (project.upgradeStatus !== ProjectUpgradeStatus.Failed && project.upgradeStatus !== null) { @@ -267,8 +270,19 @@ export const projectQueueFactory = ({ const user = await userDAL.findUserEncKeyByUserId(key.receiverId); const [orgMembership] = await orgDAL.findMembership({ userId: key.receiverId, orgId: project.orgId }); - if (!user || !orgMembership) { - throw new Error(`User with ID ${key.receiverId} was not found during upgrade, or user is not in org.`); + if (!user) { + throw new Error(`User with ID ${key.receiverId} was not found during upgrade.`); + } + + if (!orgMembership) { + // This can happen. Since we don't remove project memberships and project keys when a user is removed from an org, this is a valid case. + logger.info("User is not in organization", { + userId: key.receiverId, + orgId: project.orgId, + projectId: project.id + }); + // eslint-disable-next-line no-continue + continue; } const [newMember] = assignWorkspaceKeysToMembers({ @@ -532,7 +546,12 @@ export const projectQueueFactory = ({ logger.error("Failed to upgrade project, because no project was found", data); } else { await projectDAL.setProjectUpgradeStatus(data.projectId, ProjectUpgradeStatus.Failed); - logger.error(err, "Failed to upgrade project"); + logger.error("Failed to upgrade project", err, { + extra: { + project, + jobData: data + } + }); } throw err; diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index 4c14edc0c..ecd13dae1 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -375,6 +375,10 @@ export const secretServiceFactory = ({ await projectDAL.checkProjectUpgradeStatus(projectId); + if (inputSecret.newSecretName === "") { + throw new BadRequestError({ message: "New secret name cannot be empty" }); + } + const folder = await folderDAL.findBySecretPath(projectId, environment, path); if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create secret" }); const folderId = folder.id; diff --git a/frontend/src/components/v2/Checkbox/Checkbox.tsx b/frontend/src/components/v2/Checkbox/Checkbox.tsx index 87e278119..dc0e1a491 100644 --- a/frontend/src/components/v2/Checkbox/Checkbox.tsx +++ b/frontend/src/components/v2/Checkbox/Checkbox.tsx @@ -30,7 +30,7 @@ export const Checkbox = ({
-