Compare commits
90 Commits
org-based-
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
ab878e00c9 | |||
6773996d40 | |||
2d088a865f | |||
0a8ec6b9da | |||
01b29c3917 | |||
5439ddeadf | |||
9d17d5277b | |||
c70fc7826a | |||
9ed2bb38c3 | |||
f458cf0d40 | |||
ce3dc86f78 | |||
d1927cb9cf | |||
e80426f72e | |||
f8a8ea2118 | |||
f5cd68168b | |||
1a0a9a7402 | |||
b74ce14d80 | |||
afdc5e8531 | |||
57daeb71e6 | |||
98b5f713a5 | |||
120d7e42bf | |||
c2bd259c12 | |||
242d770098 | |||
1855fc769d | |||
217fef65e8 | |||
8a0fd62785 | |||
c69601c14e | |||
faf6323a58 | |||
b82d1b6a5d | |||
3dcda44c50 | |||
f320b08ca8 | |||
df6e5674cf | |||
6bac143a8e | |||
38b93e499f | |||
a521538010 | |||
8cc2553452 | |||
b1cb9de001 | |||
036256b350 | |||
d3a06b82e6 | |||
87436cfb57 | |||
5c58a4d1a3 | |||
03a91b2c59 | |||
751361bd54 | |||
b4b88daf36 | |||
6546740bd9 | |||
b32558c66f | |||
effd30857e | |||
60998c8944 | |||
3c4d9fd4a9 | |||
ad70c783e8 | |||
7347362738 | |||
4b7f2e808b | |||
57f9d13189 | |||
bd2e8ac922 | |||
79694750af | |||
03db367a4e | |||
b0fb848a92 | |||
4fdfcd50dc | |||
db205b855a | |||
e707f0d235 | |||
27f4225c44 | |||
28a9d8e739 | |||
a1321e4749 | |||
d4db01bbde | |||
39634b8aae | |||
4815ff13ee | |||
fb503756d9 | |||
48a97fb39d | |||
eeaee4409c | |||
8d457bb0bf | |||
d8ea26feb7 | |||
50c0fae557 | |||
70e083bae0 | |||
6a943e275a | |||
526dc6141b | |||
dcab9dcdda | |||
1b0591def8 | |||
4b4305bddc | |||
fcaff76afa | |||
ae9eb20189 | |||
3905d16a7c | |||
ecafdb0d01 | |||
8313245ae1 | |||
dc146d0883 | |||
24dd79b566 | |||
00650df501 | |||
44f087991c | |||
6ff5fb69d4 | |||
9fe2021d9f | |||
fe2f2f972e |
12
.env.example
@ -3,16 +3,18 @@
|
||||
# THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NEVER BE USED FOR PRODUCTION
|
||||
ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
|
||||
|
||||
# Required
|
||||
DB_CONNECTION_URI=postgres://infisical:infisical@db:5432/infisical
|
||||
|
||||
# JWT
|
||||
# Required secrets to sign JWT tokens
|
||||
# THIS IS A SAMPLE AUTH_SECRET KEY AND SHOULD NEVER BE USED FOR PRODUCTION
|
||||
AUTH_SECRET=5lrMXKKWCVocS/uerPsl7V+TX/aaUaI7iDkgl3tSmLE=
|
||||
|
||||
# MongoDB
|
||||
# Backend will connect to the MongoDB instance at connection string MONGO_URL which can either be a ref
|
||||
# to the MongoDB container instance or Mongo Cloud
|
||||
# Required
|
||||
MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin
|
||||
# Postgres creds
|
||||
POSTGRES_PASSWORD=infisical
|
||||
POSTGRES_USER=infisical
|
||||
POSTGRES_DB=infisical
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://redis:6379
|
||||
|
1
.env.migration.example
Normal file
@ -0,0 +1 @@
|
||||
DB_CONNECTION_URI=
|
75
.github/workflows/check-api-for-breaking-changes.yml
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
name: "Check API For Breaking Changes"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
paths:
|
||||
- "backend/src/server/routes/**"
|
||||
|
||||
jobs:
|
||||
check-be-api-changes:
|
||||
name: Check API Changes
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v3
|
||||
# - name: Setup Node 20
|
||||
# uses: actions/setup-node@v3
|
||||
# with:
|
||||
# node-version: "20"
|
||||
# uncomment this when testing locally using nektos/act
|
||||
- uses: KengoTODA/actions-setup-docker-compose@v1
|
||||
if: ${{ env.ACT }}
|
||||
name: Install `docker-compose` for local simulations
|
||||
with:
|
||||
version: "2.14.2"
|
||||
- name: 📦Build the latest image
|
||||
run: docker build --tag infisical-api .
|
||||
working-directory: backend
|
||||
- name: Start postgres and redis
|
||||
run: touch .env && docker-compose -f docker-compose.dev.yml up -d db redis
|
||||
- name: Start the server
|
||||
run: |
|
||||
echo "SECRET_SCANNING_GIT_APP_ID=793712" >> .env
|
||||
echo "SECRET_SCANNING_PRIVATE_KEY=some-random" >> .env
|
||||
echo "SECRET_SCANNING_WEBHOOK_SECRET=some-random" >> .env
|
||||
docker run --name infisical-api -d -p 4000:4000 -e DB_CONNECTION_URI=$DB_CONNECTION_URI -e REDIS_URL=$REDIS_URL -e JWT_AUTH_SECRET=$JWT_AUTH_SECRET --env-file .env --entrypoint '/bin/sh' infisical-api -c "npm run migration:latest && ls && node dist/main.mjs"
|
||||
env:
|
||||
REDIS_URL: redis://172.17.0.1:6379
|
||||
DB_CONNECTION_URI: postgres://infisical:infisical@172.17.0.1:5432/infisical?sslmode=disable
|
||||
JWT_AUTH_SECRET: something-random
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.21.5'
|
||||
- name: Wait for container to be stable and check logs
|
||||
run: |
|
||||
SECONDS=0
|
||||
HEALTHY=0
|
||||
while [ $SECONDS -lt 60 ]; do
|
||||
if docker ps | grep infisical-api | grep -q healthy; then
|
||||
echo "Container is healthy."
|
||||
HEALTHY=1
|
||||
break
|
||||
fi
|
||||
echo "Waiting for container to be healthy... ($SECONDS seconds elapsed)"
|
||||
|
||||
docker logs infisical-api
|
||||
|
||||
sleep 2
|
||||
SECONDS=$((SECONDS+2))
|
||||
done
|
||||
|
||||
if [ $HEALTHY -ne 1 ]; then
|
||||
echo "Container did not become healthy in time"
|
||||
exit 1
|
||||
fi
|
||||
- name: Install openapi-diff
|
||||
run: go install github.com/tufin/oasdiff@latest
|
||||
- name: Running OpenAPI Spec diff action
|
||||
run: oasdiff breaking https://app.infisical.com/api/docs/json http://localhost:4000/api/docs/json --fail-on ERR
|
||||
- name: cleanup
|
||||
run: |
|
||||
docker-compose -f "docker-compose.dev.yml" down
|
||||
docker stop infisical-api
|
||||
docker remove infisical-api
|
43
.github/workflows/check-be-pull-request.yml
vendored
@ -1,43 +0,0 @@
|
||||
name: "Check Backend Pull Request"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
paths:
|
||||
- "backend/**"
|
||||
- "!backend/README.md"
|
||||
- "!backend/.*"
|
||||
- "backend/.eslintrc.js"
|
||||
|
||||
jobs:
|
||||
check-be-pr:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: ☁️ Checkout source
|
||||
uses: actions/checkout@v3
|
||||
- name: 🔧 Setup Node 16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
cache: "npm"
|
||||
cache-dependency-path: backend/package-lock.json
|
||||
- name: 📦 Install dependencies
|
||||
run: npm ci --only-production
|
||||
working-directory: backend
|
||||
# - name: 🧪 Run tests
|
||||
# run: npm run test:ci
|
||||
# working-directory: backend
|
||||
# - name: 📁 Upload test results
|
||||
# uses: actions/upload-artifact@v3
|
||||
# if: always()
|
||||
# with:
|
||||
# name: be-test-results
|
||||
# path: |
|
||||
# ./backend/reports
|
||||
# ./backend/coverage
|
||||
- name: 🏗️ Run build
|
||||
run: npm run build
|
||||
working-directory: backend
|
35
.github/workflows/check-be-ts-and-lint.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
name: "Check Backend PR types and lint"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
paths:
|
||||
- "backend/**"
|
||||
- "!backend/README.md"
|
||||
- "!backend/.*"
|
||||
- "backend/.eslintrc.js"
|
||||
|
||||
jobs:
|
||||
check-be-pr:
|
||||
name: Check TS and Lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: ☁️ Checkout source
|
||||
uses: actions/checkout@v3
|
||||
- name: 🔧 Setup Node 20
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "npm"
|
||||
cache-dependency-path: backend/package-lock.json
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
working-directory: backend
|
||||
- name: Run type check
|
||||
run: npm run type:check
|
||||
working-directory: backend
|
||||
- name: Run lint check
|
||||
run: npm run lint
|
||||
working-directory: backend
|
2
.gitignore
vendored
@ -6,7 +6,7 @@ node_modules
|
||||
.env.gamma
|
||||
.env.prod
|
||||
.env.infisical
|
||||
|
||||
.env.migration
|
||||
*~
|
||||
*.swp
|
||||
*.swo
|
||||
|
10
Makefile
@ -5,16 +5,10 @@ push:
|
||||
docker-compose -f docker-compose.yml push
|
||||
|
||||
up-dev:
|
||||
docker-compose -f docker-compose.dev.yml up --build
|
||||
|
||||
up-pg-dev:
|
||||
docker compose -f docker-compose.pg.yml up --build
|
||||
|
||||
i-dev:
|
||||
infisical run -- docker-compose -f docker-compose.dev.yml up --build
|
||||
docker compose -f docker-compose.dev.yml up --build
|
||||
|
||||
up-prod:
|
||||
docker-compose -f docker-compose.yml up --build
|
||||
docker-compose -f docker-compose.prod.yml up --build
|
||||
|
||||
down:
|
||||
docker-compose down
|
||||
|
@ -84,13 +84,13 @@ To set up and run Infisical locally, make sure you have Git and Docker installed
|
||||
Linux/macOS:
|
||||
|
||||
```console
|
||||
git clone https://github.com/Infisical/infisical && cd "$(basename $_ .git)" && cp .env.example .env && docker-compose -f docker-compose.yml up
|
||||
git clone https://github.com/Infisical/infisical && cd "$(basename $_ .git)" && cp .env.example .env && docker-compose -f docker-compose.prod.yml up
|
||||
```
|
||||
|
||||
Windows Command Prompt:
|
||||
|
||||
```console
|
||||
git clone https://github.com/Infisical/infisical && cd infisical && copy .env.example .env && docker-compose -f docker-compose.yml up
|
||||
git clone https://github.com/Infisical/infisical && cd infisical && copy .env.example .env && docker-compose -f docker-compose.prod.yml up
|
||||
```
|
||||
|
||||
Create an account at `http://localhost:80`
|
||||
|
1270
backend/package-lock.json
generated
@ -81,7 +81,7 @@
|
||||
"@fastify/rate-limit": "^9.0.0",
|
||||
"@fastify/session": "^10.7.0",
|
||||
"@fastify/swagger": "^8.12.0",
|
||||
"@fastify/swagger-ui": "^1.10.1",
|
||||
"@fastify/swagger-ui": "^2.1.0",
|
||||
"@node-saml/passport-saml": "^4.0.4",
|
||||
"@octokit/rest": "^20.0.2",
|
||||
"@octokit/webhooks-types": "^7.3.1",
|
||||
@ -91,7 +91,7 @@
|
||||
"ajv": "^8.12.0",
|
||||
"argon2": "^0.31.2",
|
||||
"aws-sdk": "^2.1532.0",
|
||||
"axios": "^1.6.2",
|
||||
"axios": "^1.6.4",
|
||||
"axios-retry": "^4.0.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bullmq": "^5.1.1",
|
||||
@ -109,7 +109,8 @@
|
||||
"mysql2": "^3.6.5",
|
||||
"nanoid": "^5.0.4",
|
||||
"node-cache": "^5.1.2",
|
||||
"nodemailer": "^6.9.7",
|
||||
"nodemailer": "^6.9.9",
|
||||
"ora": "^7.0.1",
|
||||
"passport-github": "^1.1.0",
|
||||
"passport-gitlab2": "^5.0.0",
|
||||
"passport-google-oauth20": "^2.0.0",
|
||||
@ -117,7 +118,7 @@
|
||||
"picomatch": "^3.0.1",
|
||||
"pino": "^8.16.2",
|
||||
"posthog-node": "^3.6.0",
|
||||
"probot": "^12.3.3",
|
||||
"probot": "^13.0.0",
|
||||
"smee-client": "^2.0.0",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"tweetnacl-util": "^0.15.1",
|
||||
|
@ -3,13 +3,9 @@ import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
import knex from "knex";
|
||||
import { writeFileSync } from "fs";
|
||||
import promptSync from "prompt-sync";
|
||||
|
||||
const prompt = promptSync({ sigint: true });
|
||||
|
||||
dotenv.config({
|
||||
path: path.join(__dirname, "../.env"),
|
||||
debug: true
|
||||
path: path.join(__dirname, "../../.env.migration")
|
||||
});
|
||||
|
||||
const db = knex({
|
||||
@ -94,17 +90,7 @@ const main = async () => {
|
||||
.orderBy("table_name")
|
||||
).filter((el) => !el.tableName.includes("_migrations"));
|
||||
|
||||
console.log("Select a table to generate schema");
|
||||
console.table(tables);
|
||||
console.log("all: all tables");
|
||||
const selectedTables = prompt("Type table numbers comma seperated: ");
|
||||
const tableNumbers =
|
||||
selectedTables !== "all" ? selectedTables.split(",").map((el) => Number(el)) : [];
|
||||
|
||||
for (let i = 0; i < tables.length; i += 1) {
|
||||
// skip if not desired table
|
||||
if (selectedTables !== "all" && !tableNumbers.includes(i)) continue;
|
||||
|
||||
const { tableName } = tables[i];
|
||||
const columns = await db(tableName).columnInfo();
|
||||
const columnNames = Object.keys(columns);
|
||||
@ -124,16 +110,16 @@ const main = async () => {
|
||||
if (colInfo.nullable) {
|
||||
ztype = ztype.concat(".nullable().optional()");
|
||||
}
|
||||
schema = schema.concat(`${!schema ? "\n" : ""} ${columnName}: ${ztype},\n`);
|
||||
schema = schema.concat(
|
||||
`${!schema ? "\n" : ""} ${columnName}: ${ztype}${colNum === columnNames.length - 1 ? "" : ","}\n`
|
||||
);
|
||||
}
|
||||
|
||||
const dashcase = tableName.split("_").join("-");
|
||||
const pascalCase = tableName
|
||||
.split("_")
|
||||
.reduce(
|
||||
(prev, curr) => prev + `${curr.at(0)?.toUpperCase()}${curr.slice(1).toLowerCase()}`,
|
||||
""
|
||||
);
|
||||
.reduce((prev, curr) => prev + `${curr.at(0)?.toUpperCase()}${curr.slice(1).toLowerCase()}`, "");
|
||||
|
||||
writeFileSync(
|
||||
path.join(__dirname, "../src/db/schemas", `${dashcase}.ts`),
|
||||
`// Code generated by automation script, DO NOT EDIT.
|
||||
@ -152,15 +138,6 @@ export type T${pascalCase}Insert = Omit<T${pascalCase}, TImmutableDBKeys>;
|
||||
export type T${pascalCase}Update = Partial<Omit<T${pascalCase}, TImmutableDBKeys>>;
|
||||
`
|
||||
);
|
||||
|
||||
// const file = readFileSync(path.join(__dirname, "../src/db/schemas/index.ts"), "utf8");
|
||||
// if (!file.includes(`export * from "./${dashcase};"`)) {
|
||||
// appendFileSync(
|
||||
// path.join(__dirname, "../src/db/schemas/index.ts"),
|
||||
// `\nexport * from "./${dashcase}";`,
|
||||
// "utf8"
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
|
@ -5,9 +5,9 @@ import dotenv from "dotenv";
|
||||
import type { Knex } from "knex";
|
||||
import path from "path";
|
||||
|
||||
// Update with your config settings.
|
||||
// Update with your config settings. .
|
||||
dotenv.config({
|
||||
path: path.join(__dirname, "../../.env"),
|
||||
path: path.join(__dirname, "../../../.env.migration"),
|
||||
debug: true
|
||||
});
|
||||
export default {
|
||||
|
@ -11,6 +11,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
method: "GET",
|
||||
url: "/:workspaceId/secret-snapshots",
|
||||
schema: {
|
||||
description: "Return project secret snapshots ids",
|
||||
security: [
|
||||
{
|
||||
apiKeyAuth: [],
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
@ -74,6 +81,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
method: "GET",
|
||||
url: "/:workspaceId/audit-logs",
|
||||
schema: {
|
||||
description: "Return audit logs",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
|
@ -19,7 +19,6 @@ import { BadRequestError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||
|
||||
type TSAMLConfig = {
|
||||
callbackUrl: string;
|
||||
@ -28,6 +27,7 @@ type TSAMLConfig = {
|
||||
cert: string;
|
||||
audience: string;
|
||||
wantAuthnResponseSigned?: boolean;
|
||||
disableRequestedAuthnContext?: boolean;
|
||||
};
|
||||
|
||||
export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
@ -67,7 +67,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
throw new BadRequestError({ message: "Failed to authenticate with SAML SSO" });
|
||||
|
||||
const samlConfig: TSAMLConfig = {
|
||||
callbackUrl: `${appCfg.SITE_URL}/api/v1/sso/saml2/${samlConfigId}`,
|
||||
callbackUrl: `${appCfg.SITE_URL}/api/v1/sso/saml2/${ssoConfig.id}`,
|
||||
entryPoint: ssoConfig.entryPoint,
|
||||
issuer: ssoConfig.issuer,
|
||||
cert: ssoConfig.cert,
|
||||
@ -77,7 +77,8 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
samlConfig.wantAuthnResponseSigned = false;
|
||||
}
|
||||
if (ssoConfig.authProvider === SamlProviders.AZURE_SAML) {
|
||||
if (req.body.RelayState && JSON.parse(req.body.RelayState).spIntiaited) {
|
||||
samlConfig.disableRequestedAuthnContext = true;
|
||||
if (req.body?.RelayState && JSON.parse(req.body.RelayState).spInitiated) {
|
||||
samlConfig.audience = `spn:${ssoConfig.issuer}`;
|
||||
}
|
||||
}
|
||||
@ -92,7 +93,6 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
// eslint-disable-next-line
|
||||
async (req, profile, cb) => {
|
||||
try {
|
||||
const serverCfg = await getServerCfg();
|
||||
if (!profile) throw new BadRequestError({ message: "Missing profile" });
|
||||
const { firstName } = profile;
|
||||
const email = profile?.email ?? (profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved
|
||||
@ -105,7 +105,6 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
email,
|
||||
firstName: profile.firstName as string,
|
||||
lastName: profile.lastName as string,
|
||||
isSignupAllowed: Boolean(serverCfg.allowSignUp),
|
||||
relayState: (req.body as { RelayState?: string }).RelayState,
|
||||
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string,
|
||||
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string
|
||||
|
@ -57,6 +57,13 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
||||
method: "POST",
|
||||
url: "/:secretSnapshotId/rollback",
|
||||
schema: {
|
||||
description: "Roll back project secrets to those captured in a secret snapshot version.",
|
||||
security: [
|
||||
{
|
||||
apiKeyAuth: [],
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretSnapshotId: z.string().trim()
|
||||
}),
|
||||
|
@ -44,7 +44,7 @@ type TLicenseServiceFactoryDep = {
|
||||
export type TLicenseServiceFactory = ReturnType<typeof licenseServiceFactory>;
|
||||
|
||||
const LICENSE_SERVER_CLOUD_LOGIN = "/api/auth/v1/license-server-login";
|
||||
const LICENSE_SERVER_ON_PREM_LOGIN = "/api/auth/v1/licence-login";
|
||||
const LICENSE_SERVER_ON_PREM_LOGIN = "/api/auth/v1/license-login";
|
||||
|
||||
const FEATURE_CACHE_KEY = (orgId: string, projectId?: string) => `${orgId}-${projectId || ""}`;
|
||||
export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: TLicenseServiceFactoryDep) => {
|
||||
@ -92,7 +92,7 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
|
||||
// else it would reach catch statement
|
||||
isValidLicense = true;
|
||||
} catch (error) {
|
||||
logger.error(`init-license: encountered an error when init license [error]`, error);
|
||||
logger.error(error, `init-license: encountered an error when init license`);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -300,19 +300,9 @@ export const samlConfigServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const samlLogin = async ({
|
||||
firstName,
|
||||
email,
|
||||
lastName,
|
||||
authProvider,
|
||||
orgId,
|
||||
relayState,
|
||||
isSignupAllowed
|
||||
}: TSamlLoginDTO) => {
|
||||
const samlLogin = async ({ firstName, email, lastName, authProvider, orgId, relayState }: TSamlLoginDTO) => {
|
||||
const appCfg = getConfig();
|
||||
let user = await userDAL.findUserByEmail(email);
|
||||
const isSamlSignUpDisabled = !isSignupAllowed && !user;
|
||||
if (isSamlSignUpDisabled) throw new BadRequestError({ message: "User signup disabled", name: "Saml SSO login" });
|
||||
|
||||
const organization = await orgDAL.findOrgById(orgId);
|
||||
if (!organization) throw new BadRequestError({ message: "Org not found" });
|
||||
|
@ -41,7 +41,6 @@ export type TSamlLoginDTO = {
|
||||
lastName?: string;
|
||||
authProvider: string;
|
||||
orgId: string;
|
||||
isSignupAllowed: boolean;
|
||||
// saml thingy
|
||||
relayState?: string;
|
||||
};
|
||||
|
@ -95,7 +95,7 @@ const envSchema = z
|
||||
SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()),
|
||||
SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()),
|
||||
// LICENCE
|
||||
LICENSE_SERVER_URL: zpStr(z.string().optional()),
|
||||
LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")),
|
||||
LICENSE_SERVER_KEY: zpStr(z.string().optional()),
|
||||
LICENSE_KEY: zpStr(z.string().optional()),
|
||||
STANDALONE_MODE: z
|
||||
|
@ -25,13 +25,13 @@ export const fastifySwagger = fp(async (fastify) => {
|
||||
],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
bearer: {
|
||||
bearerAuth: {
|
||||
type: "http",
|
||||
scheme: "bearer",
|
||||
bearerFormat: "JWT",
|
||||
description: "A service token in Infisical"
|
||||
description: "An access token in Infisical"
|
||||
},
|
||||
apiKey: {
|
||||
apiKeyAuth: {
|
||||
type: "apiKey",
|
||||
in: "header",
|
||||
name: "X-API-Key",
|
||||
|
@ -72,7 +72,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
||||
200: z.object({
|
||||
message: z.string(),
|
||||
user: UsersSchema,
|
||||
token: z.string()
|
||||
token: z.string(),
|
||||
new: z.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
@ -107,7 +108,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
||||
return {
|
||||
message: "Successfully set up admin account",
|
||||
user: user.user,
|
||||
token: token.access
|
||||
token: token.access,
|
||||
new: "123"
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@ -5,6 +5,7 @@ export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvid
|
||||
url: "/token/renew",
|
||||
method: "POST",
|
||||
schema: {
|
||||
description: "Renew access token",
|
||||
body: z.object({
|
||||
accessToken: z.string().trim()
|
||||
}),
|
||||
|
@ -11,6 +11,12 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
||||
url: "/",
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
schema: {
|
||||
description: "Create identity",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
name: z.string().trim(),
|
||||
organizationId: z.string().trim(),
|
||||
@ -52,6 +58,12 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
||||
url: "/:identityId",
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
schema: {
|
||||
description: "Update identity",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string()
|
||||
}),
|
||||
@ -95,6 +107,12 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
||||
url: "/:identityId",
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
schema: {
|
||||
description: "Delete identity",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string()
|
||||
}),
|
||||
|
@ -24,6 +24,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
url: "/universal-auth/login",
|
||||
method: "POST",
|
||||
schema: {
|
||||
description: "Login with Universal Auth",
|
||||
body: z.object({
|
||||
clientId: z.string().trim(),
|
||||
clientSecret: z.string().trim()
|
||||
@ -67,6 +68,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
method: "POST",
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
description: "Attach Universal Auth configuration onto identity",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string().trim()
|
||||
}),
|
||||
@ -141,6 +148,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
method: "PATCH",
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
description: "Update Universal Auth configuration on identity",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string()
|
||||
}),
|
||||
@ -209,6 +222,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
method: "GET",
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
description: "Retrieve Universal Auth configuration on identity",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string()
|
||||
}),
|
||||
@ -246,6 +265,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
method: "POST",
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
description: "Create Universal Auth Client Secret for identity",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string()
|
||||
}),
|
||||
@ -291,6 +316,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
method: "GET",
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
description: "List Universal Auth Client Secrets for identity",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string()
|
||||
}),
|
||||
@ -327,6 +358,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
method: "POST",
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
description: "Revoke Universal Auth Client Secrets for identity",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string(),
|
||||
clientSecretId: z.string()
|
||||
|
@ -10,6 +10,13 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
||||
url: "/:workspaceId/environments",
|
||||
method: "POST",
|
||||
schema: {
|
||||
description: "Create environment",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
@ -58,6 +65,13 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
||||
url: "/:workspaceId/environments/:id",
|
||||
method: "PATCH",
|
||||
schema: {
|
||||
description: "Update environment",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
id: z.string().trim()
|
||||
@ -114,6 +128,13 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
||||
url: "/:workspaceId/environments/:id",
|
||||
method: "DELETE",
|
||||
schema: {
|
||||
description: "Delete environment",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
id: z.string().trim()
|
||||
|
@ -10,6 +10,13 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
url: "/:workspaceId/memberships",
|
||||
method: "GET",
|
||||
schema: {
|
||||
description: "Return project user memberships",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
@ -96,6 +103,13 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
url: "/:workspaceId/memberships/:membershipId",
|
||||
method: "PATCH",
|
||||
schema: {
|
||||
description: "Update project user membership",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
membershipId: z.string().trim()
|
||||
@ -141,6 +155,13 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
url: "/:workspaceId/memberships/:membershipId",
|
||||
method: "DELETE",
|
||||
schema: {
|
||||
description: "Delete project user membership",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
membershipId: z.string().trim()
|
||||
|
@ -11,6 +11,13 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
||||
url: "/",
|
||||
method: "POST",
|
||||
schema: {
|
||||
description: "Create folders",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
@ -57,6 +64,13 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
||||
url: "/:folderId",
|
||||
method: "PATCH",
|
||||
schema: {
|
||||
description: "Update folder",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
// old way this was name
|
||||
folderId: z.string()
|
||||
@ -109,6 +123,13 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
||||
url: "/:folderId",
|
||||
method: "DELETE",
|
||||
schema: {
|
||||
description: "Delete a folder",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
folderId: z.string()
|
||||
}),
|
||||
@ -158,6 +179,13 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
||||
url: "/",
|
||||
method: "GET",
|
||||
schema: {
|
||||
description: "Get folders",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
|
@ -11,6 +11,13 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
||||
url: "/",
|
||||
method: "POST",
|
||||
schema: {
|
||||
description: "Create secret imports",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
@ -65,6 +72,13 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
||||
url: "/:secretImportId",
|
||||
method: "PATCH",
|
||||
schema: {
|
||||
description: "Update secret imports",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretImportId: z.string().trim()
|
||||
}),
|
||||
@ -128,6 +142,13 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
||||
url: "/:secretImportId",
|
||||
method: "DELETE",
|
||||
schema: {
|
||||
description: "Delete secret imports",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretImportId: z.string().trim()
|
||||
}),
|
||||
@ -181,6 +202,13 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
||||
url: "/",
|
||||
method: "GET",
|
||||
schema: {
|
||||
description: "Get secret imports",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
|
@ -10,6 +10,13 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
|
||||
url: "/:orgId/identity-memberships",
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
description: "Return organization identity memberships",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
orgId: z.string().trim()
|
||||
}),
|
||||
|
@ -46,6 +46,12 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
url: "/:projectId/identity-memberships/:identityId",
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
description: "Update project identity memberships",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
projectId: z.string().trim(),
|
||||
identityId: z.string().trim()
|
||||
@ -77,6 +83,12 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
url: "/:projectId/identity-memberships/:identityId",
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
description: "Delete project identity memberships",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
projectId: z.string().trim(),
|
||||
identityId: z.string().trim()
|
||||
@ -104,6 +116,12 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
url: "/:projectId/identity-memberships",
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
description: "Return project identity memberships",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
projectId: z.string().trim()
|
||||
}),
|
||||
|
@ -9,6 +9,13 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
method: "GET",
|
||||
url: "/:organizationId/memberships",
|
||||
schema: {
|
||||
description: "Return organization user memberships",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
}),
|
||||
@ -46,6 +53,13 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
method: "GET",
|
||||
url: "/:organizationId/workspaces",
|
||||
schema: {
|
||||
description: "Return projects in organization that user is part of",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
}),
|
||||
@ -84,6 +98,13 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
method: "PATCH",
|
||||
url: "/:organizationId/memberships/:membershipId",
|
||||
schema: {
|
||||
description: "Update organization user memberships",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
|
||||
body: z.object({
|
||||
role: z.string().trim()
|
||||
@ -113,6 +134,13 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
method: "DELETE",
|
||||
url: "/:organizationId/memberships/:membershipId",
|
||||
schema: {
|
||||
description: "Delete organization user memberships",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -10,6 +10,12 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
url: "/:workspaceId/encrypted-key",
|
||||
method: "GET",
|
||||
schema: {
|
||||
description: "Return encrypted project key",
|
||||
security: [
|
||||
{
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
|
@ -21,6 +21,12 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) =>
|
||||
method: "GET",
|
||||
onRequest: verifyAuth([AuthMode.SERVICE_TOKEN]),
|
||||
schema: {
|
||||
description: "Return Infisical Token data",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
response: {
|
||||
200: ServiceTokensSchema.merge(
|
||||
z.object({
|
||||
|
@ -71,6 +71,12 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
|
||||
method: "GET",
|
||||
url: "/me/organizations",
|
||||
schema: {
|
||||
description: "Return organizations that current user is part of",
|
||||
security: [
|
||||
{
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
response: {
|
||||
200: z.object({
|
||||
organizations: OrganizationsSchema.array()
|
||||
@ -179,6 +185,12 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
|
||||
method: "GET",
|
||||
url: "/me",
|
||||
schema: {
|
||||
description: "Retrieve the current user on the request",
|
||||
security: [
|
||||
{
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
response: {
|
||||
200: z.object({
|
||||
user: UsersSchema.merge(UserEncryptionKeysSchema.omit({ verifier: true }))
|
||||
|
@ -38,6 +38,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
url: "/raw",
|
||||
method: "GET",
|
||||
schema: {
|
||||
description: "List secrets",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
workspaceId: z.string().trim().optional(),
|
||||
environment: z.string().trim().optional(),
|
||||
@ -121,6 +128,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
url: "/raw/:secretName",
|
||||
method: "GET",
|
||||
schema: {
|
||||
description: "Get a secret by name",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim()
|
||||
}),
|
||||
@ -204,6 +218,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
url: "/raw/:secretName",
|
||||
method: "POST",
|
||||
schema: {
|
||||
description: "Create secret",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim()
|
||||
}),
|
||||
@ -274,6 +295,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
url: "/raw/:secretName",
|
||||
method: "PATCH",
|
||||
schema: {
|
||||
description: "Update secret",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim()
|
||||
}),
|
||||
@ -341,6 +369,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
url: "/raw/:secretName",
|
||||
method: "DELETE",
|
||||
schema: {
|
||||
description: "Delete secret",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim()
|
||||
}),
|
||||
|
@ -156,7 +156,19 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
|
||||
const { user, accessToken, refreshToken } = await server.services.signup.completeAccountInvite({
|
||||
...req.body,
|
||||
ip: req.realIp,
|
||||
userAgent
|
||||
userAgent,
|
||||
authorization: req.headers.authorization as string
|
||||
});
|
||||
|
||||
void server.services.telemetry.sendLoopsEvent(user.email, user.firstName || "", user.lastName || "");
|
||||
|
||||
void server.services.telemetry.sendPostHogEvents({
|
||||
event: PostHogEventTypes.UserSignedUp,
|
||||
distinctId: user.email,
|
||||
properties: {
|
||||
email: user.email,
|
||||
attributionSource: "Team Invite"
|
||||
}
|
||||
});
|
||||
|
||||
void res.setCookie("jid", refreshToken, {
|
||||
|
@ -212,13 +212,16 @@ export const authSignupServiceFactory = ({
|
||||
protectedKeyTag,
|
||||
encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag
|
||||
encryptedPrivateKeyTag,
|
||||
authorization
|
||||
}: TCompleteAccountInviteDTO) => {
|
||||
const user = await userDAL.findUserByEmail(email);
|
||||
if (!user || (user && user.isAccepted)) {
|
||||
throw new Error("Failed to complete account for complete user");
|
||||
}
|
||||
|
||||
validateSignUpAuthorization(authorization, user.id);
|
||||
|
||||
const [orgMembership] = await orgDAL.findMembership({
|
||||
inviteEmail: email,
|
||||
status: OrgMembershipStatus.Invited
|
||||
|
@ -34,4 +34,5 @@ export type TCompleteAccountInviteDTO = {
|
||||
verifier: string;
|
||||
ip: string;
|
||||
userAgent: string;
|
||||
authorization: string;
|
||||
};
|
||||
|
@ -35,12 +35,12 @@ export const identityAccessTokenServiceFactory = ({
|
||||
}
|
||||
|
||||
// ttl check
|
||||
if (accessTokenTTL > 0) {
|
||||
if (Number(accessTokenTTL) > 0) {
|
||||
const currentDate = new Date();
|
||||
if (accessTokenLastRenewedAt) {
|
||||
// access token has been renewed
|
||||
const accessTokenRenewed = new Date(accessTokenLastRenewedAt);
|
||||
const ttlInMilliseconds = accessTokenTTL * 1000;
|
||||
const ttlInMilliseconds = Number(accessTokenTTL) * 1000;
|
||||
const expirationDate = new Date(accessTokenRenewed.getTime() + ttlInMilliseconds);
|
||||
|
||||
if (currentDate > expirationDate)
|
||||
@ -50,7 +50,7 @@ export const identityAccessTokenServiceFactory = ({
|
||||
} else {
|
||||
// access token has never been renewed
|
||||
const accessTokenCreated = new Date(accessTokenCreatedAt);
|
||||
const ttlInMilliseconds = accessTokenTTL * 1000;
|
||||
const ttlInMilliseconds = Number(accessTokenTTL) * 1000;
|
||||
const expirationDate = new Date(accessTokenCreated.getTime() + ttlInMilliseconds);
|
||||
|
||||
if (currentDate > expirationDate)
|
||||
@ -61,9 +61,9 @@ export const identityAccessTokenServiceFactory = ({
|
||||
}
|
||||
|
||||
// max ttl checks
|
||||
if (accessTokenMaxTTL > 0) {
|
||||
if (Number(accessTokenMaxTTL) > 0) {
|
||||
const accessTokenCreated = new Date(accessTokenCreatedAt);
|
||||
const ttlInMilliseconds = accessTokenMaxTTL * 1000;
|
||||
const ttlInMilliseconds = Number(accessTokenMaxTTL) * 1000;
|
||||
const currentDate = new Date();
|
||||
const expirationDate = new Date(accessTokenCreated.getTime() + ttlInMilliseconds);
|
||||
|
||||
@ -72,7 +72,7 @@ export const identityAccessTokenServiceFactory = ({
|
||||
message: "Failed to renew MI access token due to Max TTL expiration"
|
||||
});
|
||||
|
||||
const extendToDate = new Date(currentDate.getTime() + accessTokenTTL);
|
||||
const extendToDate = new Date(currentDate.getTime() + Number(accessTokenTTL));
|
||||
if (extendToDate > expirationDate)
|
||||
throw new UnauthorizedError({
|
||||
message: "Failed to renew MI access token past its Max TTL expiration"
|
||||
|
@ -69,9 +69,9 @@ export const identityUaServiceFactory = ({
|
||||
if (!validClientSecretInfo) throw new UnauthorizedError();
|
||||
|
||||
const { clientSecretTTL, clientSecretNumUses, clientSecretNumUsesLimit } = validClientSecretInfo;
|
||||
if (clientSecretTTL > 0) {
|
||||
if (Number(clientSecretTTL) > 0) {
|
||||
const clientSecretCreated = new Date(validClientSecretInfo.createdAt);
|
||||
const ttlInMilliseconds = clientSecretTTL * 1000;
|
||||
const ttlInMilliseconds = Number(clientSecretTTL) * 1000;
|
||||
const currentDate = new Date();
|
||||
const expirationTime = new Date(clientSecretCreated.getTime() + ttlInMilliseconds);
|
||||
|
||||
@ -124,7 +124,10 @@ export const identityUaServiceFactory = ({
|
||||
} as TIdentityAccessTokenJwtPayload,
|
||||
appCfg.AUTH_SECRET,
|
||||
{
|
||||
expiresIn: identityAccessToken.accessTokenMaxTTL === 0 ? undefined : identityAccessToken.accessTokenMaxTTL
|
||||
expiresIn:
|
||||
Number(identityAccessToken.accessTokenMaxTTL) === 0
|
||||
? undefined
|
||||
: Number(identityAccessToken.accessTokenMaxTTL)
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -41,13 +41,12 @@ export const fnSecretsFromImports = async ({
|
||||
environment: importEnv.slug,
|
||||
environmentInfo: importEnv,
|
||||
folderId: importedFolders?.[i]?.id,
|
||||
secrets: importedFolders?.[i]?.id
|
||||
? importedSecsGroupByFolderId[importedFolders?.[i]?.id as string].map((item) => ({
|
||||
...item,
|
||||
environment: importEnv.slug,
|
||||
workspace: "", // This field should not be used, it's only here to keep the older Python SDK versions backwards compatible with the new Postgres backend.
|
||||
_id: item.id // The old Python SDK depends on the _id field being returned. We return this to keep the older Python SDK versions backwards compatible with the new Postgres backend.
|
||||
}))
|
||||
: []
|
||||
// this will ensure for cases when secrets are empty. Could be due to missing folder for a path or when emtpy secrets inside a given path
|
||||
secrets: (importedSecsGroupByFolderId?.[importedFolders?.[i]?.id as string] || []).map((item) => ({
|
||||
...item,
|
||||
environment: importEnv.slug,
|
||||
workspace: "", // This field should not be used, it's only here to keep the older Python SDK versions backwards compatible with the new Postgres backend.
|
||||
_id: item.id // The old Python SDK depends on the _id field being returned. We return this to keep the older Python SDK versions backwards compatible with the new Postgres backend.
|
||||
}))
|
||||
}));
|
||||
};
|
||||
|
@ -581,7 +581,7 @@ export const secretServiceFactory = ({
|
||||
secretType = SecretType.Shared;
|
||||
}
|
||||
|
||||
const secret = await (typeof version === undefined
|
||||
const secret = await (version === undefined
|
||||
? secretDAL.findOne({
|
||||
folderId,
|
||||
type: secretType,
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: '3'
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
nginx:
|
||||
@ -10,36 +10,84 @@ services:
|
||||
volumes:
|
||||
- ./nginx/default.dev.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
depends_on:
|
||||
- frontend
|
||||
- backend
|
||||
networks:
|
||||
- infisical-dev
|
||||
- frontend
|
||||
|
||||
backend:
|
||||
container_name: infisical-dev-backend
|
||||
restart: unless-stopped
|
||||
db:
|
||||
image: postgres:14-alpine
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: infisical
|
||||
POSTGRES_USER: infisical
|
||||
POSTGRES_DB: infisical
|
||||
|
||||
redis:
|
||||
image: redis
|
||||
container_name: infisical-dev-redis
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
ports:
|
||||
- 6379:6379
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
redis-commander:
|
||||
container_name: infisical-dev-redis-commander
|
||||
image: rediscommander/redis-commander
|
||||
restart: always
|
||||
depends_on:
|
||||
- mongo
|
||||
- smtp-server
|
||||
- redis
|
||||
environment:
|
||||
- REDIS_HOSTS=local:redis:6379
|
||||
ports:
|
||||
- "8085:8081"
|
||||
|
||||
db-test:
|
||||
profiles: ["test"]
|
||||
image: postgres:14-alpine
|
||||
ports:
|
||||
- "5430:5432"
|
||||
environment:
|
||||
POSTGRES_PASSWORD: infisical
|
||||
POSTGRES_USER: infisical
|
||||
POSTGRES_DB: infisical-test
|
||||
|
||||
db-migration:
|
||||
container_name: infisical-db-migration
|
||||
depends_on:
|
||||
- db
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile
|
||||
volumes:
|
||||
- ./backend/src:/app/src
|
||||
- ./backend/nodemon.json:/app/nodemon.json
|
||||
- /app/node_modules
|
||||
- ./backend/api-documentation.json:/app/api-documentation.json
|
||||
- ./backend/swagger.ts:/app/swagger.ts
|
||||
command: npm run dev
|
||||
dockerfile: Dockerfile.dev
|
||||
env_file: .env
|
||||
environment:
|
||||
- DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable
|
||||
command: npm run migration:latest
|
||||
|
||||
backend:
|
||||
container_name: infisical-dev-api
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile.dev
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_started
|
||||
redis:
|
||||
condition: service_started
|
||||
db-migration:
|
||||
condition: service_completed_successfully
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- 4000:4000
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin
|
||||
networks:
|
||||
- infisical-dev
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
- DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable
|
||||
volumes:
|
||||
- ./backend/src:/app/src
|
||||
|
||||
frontend:
|
||||
container_name: infisical-dev-frontend
|
||||
@ -55,81 +103,31 @@ services:
|
||||
env_file: .env
|
||||
environment:
|
||||
- NEXT_PUBLIC_ENV=development
|
||||
- INFISICAL_TELEMETRY_ENABLED=${TELEMETRY_ENABLED}
|
||||
networks:
|
||||
- infisical-dev
|
||||
- INFISICAL_TELEMETRY_ENABLED=false
|
||||
|
||||
mongo:
|
||||
image: mongo
|
||||
container_name: infisical-dev-mongo
|
||||
pgadmin:
|
||||
image: dpage/pgadmin4
|
||||
restart: always
|
||||
env_file: .env
|
||||
environment:
|
||||
- MONGO_INITDB_ROOT_USERNAME=root
|
||||
- MONGO_INITDB_ROOT_PASSWORD=example
|
||||
volumes:
|
||||
- mongo-data:/data/db
|
||||
networks:
|
||||
- infisical-dev
|
||||
|
||||
mongo-express:
|
||||
container_name: infisical-dev-mongo-express
|
||||
image: mongo-express
|
||||
restart: always
|
||||
depends_on:
|
||||
- mongo
|
||||
env_file: .env
|
||||
environment:
|
||||
- ME_CONFIG_MONGODB_ADMINUSERNAME=root
|
||||
- ME_CONFIG_MONGODB_ADMINPASSWORD=example
|
||||
- ME_CONFIG_MONGODB_URL=mongodb://root:example@mongo:27017/
|
||||
PGADMIN_DEFAULT_EMAIL: admin@example.com
|
||||
PGADMIN_DEFAULT_PASSWORD: pass
|
||||
ports:
|
||||
- 8081:8081
|
||||
networks:
|
||||
- infisical-dev
|
||||
- 5050:80
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
smtp-server:
|
||||
container_name: infisical-dev-smtp-server
|
||||
image: lytrax/mailhog:latest # https://github.com/mailhog/MailHog/issues/353#issuecomment-821137362
|
||||
restart: always
|
||||
logging:
|
||||
driver: 'none' # disable saving logs
|
||||
driver: "none" # disable saving logs
|
||||
ports:
|
||||
- 1025:1025 # SMTP server
|
||||
- 8025:8025 # Web UI
|
||||
networks:
|
||||
- infisical-dev
|
||||
|
||||
redis:
|
||||
image: redis
|
||||
container_name: infisical-dev-redis
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
ports:
|
||||
- 6379:6379
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
networks:
|
||||
- infisical-dev
|
||||
|
||||
redis-commander:
|
||||
container_name: infisical-dev-redis-commander
|
||||
image: rediscommander/redis-commander
|
||||
restart: always
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
- REDIS_HOSTS=local:redis:6379
|
||||
ports:
|
||||
- "8085:8081"
|
||||
networks:
|
||||
- infisical-dev
|
||||
|
||||
volumes:
|
||||
mongo-data:
|
||||
postgres-data:
|
||||
driver: local
|
||||
redis_data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
infisical-dev:
|
||||
|
@ -1,143 +0,0 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
nginx:
|
||||
container_name: infisical-dev-nginx
|
||||
image: nginx
|
||||
restart: always
|
||||
ports:
|
||||
- 8080:80
|
||||
volumes:
|
||||
- ./nginx/default.dev.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
depends_on:
|
||||
- backend
|
||||
- frontend
|
||||
|
||||
db:
|
||||
image: postgres:14-alpine
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: infisical
|
||||
POSTGRES_USER: infisical
|
||||
POSTGRES_DB: infisical
|
||||
|
||||
redis:
|
||||
image: redis
|
||||
container_name: infisical-dev-redis
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
ports:
|
||||
- 6379:6379
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
redis-commander:
|
||||
container_name: infisical-dev-redis-commander
|
||||
image: rediscommander/redis-commander
|
||||
restart: always
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
- REDIS_HOSTS=local:redis:6379
|
||||
ports:
|
||||
- "8085:8081"
|
||||
|
||||
db-test:
|
||||
profiles: ["test"]
|
||||
image: postgres:14-alpine
|
||||
ports:
|
||||
- "5430:5432"
|
||||
environment:
|
||||
POSTGRES_PASSWORD: infisical
|
||||
POSTGRES_USER: infisical
|
||||
POSTGRES_DB: infisical-test
|
||||
|
||||
backend:
|
||||
container_name: infisical-dev-api
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile.dev
|
||||
depends_on:
|
||||
- db
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable
|
||||
volumes:
|
||||
- ./backend/src:/app/src
|
||||
|
||||
frontend:
|
||||
container_name: infisical-dev-frontend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- backend
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile.dev
|
||||
volumes:
|
||||
- ./frontend/src:/app/src/ # mounted whole src to avoid missing reload on new files
|
||||
- ./frontend/public:/app/public
|
||||
env_file: .env
|
||||
environment:
|
||||
- NEXT_PUBLIC_ENV=development
|
||||
- INFISICAL_TELEMETRY_ENABLED=false
|
||||
|
||||
pgadmin:
|
||||
image: dpage/pgadmin4
|
||||
restart: always
|
||||
environment:
|
||||
PGADMIN_DEFAULT_EMAIL: admin@example.com
|
||||
PGADMIN_DEFAULT_PASSWORD: pass
|
||||
ports:
|
||||
- 5050:80
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
smtp-server:
|
||||
container_name: infisical-dev-smtp-server
|
||||
image: lytrax/mailhog:latest # https://github.com/mailhog/MailHog/issues/353#issuecomment-821137362
|
||||
restart: always
|
||||
logging:
|
||||
driver: "none" # disable saving logs
|
||||
ports:
|
||||
- 1025:1025 # SMTP server
|
||||
- 8025:8025 # Web UI
|
||||
|
||||
# mongo:
|
||||
# image: mongo
|
||||
# container_name: infisical-dev-mongo
|
||||
# restart: always
|
||||
# env_file: .env
|
||||
# environment:
|
||||
# - MONGO_INITDB_ROOT_USERNAME=root
|
||||
# - MONGO_INITDB_ROOT_PASSWORD=example
|
||||
# volumes:
|
||||
# - mongo-data:/data/db
|
||||
# ports:
|
||||
# - 27017:27017
|
||||
#
|
||||
# mongo-express:
|
||||
# container_name: infisical-dev-mongo-express
|
||||
# image: mongo-express
|
||||
# restart: always
|
||||
# depends_on:
|
||||
# - mongo
|
||||
# env_file: .env
|
||||
# environment:
|
||||
# - ME_CONFIG_MONGODB_ADMINUSERNAME=root
|
||||
# - ME_CONFIG_MONGODB_ADMINPASSWORD=example
|
||||
# - ME_CONFIG_MONGODB_URL=mongodb://root:example@mongo:27017/
|
||||
# ports:
|
||||
# - 8081:8081
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
driver: local
|
||||
redis_data:
|
||||
driver: local
|
||||
mongo-data:
|
||||
driver: local
|
@ -1,12 +1,27 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
db-migration:
|
||||
container_name: infisical-db-migration
|
||||
depends_on:
|
||||
- db
|
||||
image: infisical/infisical:latest-postgres
|
||||
env_file: .env
|
||||
command: npm run migration:latest
|
||||
networks:
|
||||
- infisical
|
||||
|
||||
backend:
|
||||
container_name: infisical-backend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- mongo
|
||||
image: infisical/infisical:latest
|
||||
db:
|
||||
condition: service_started
|
||||
redis:
|
||||
condition: service_started
|
||||
db-migration:
|
||||
condition: service_completed_successfully
|
||||
image: infisical/infisical:latest-postgres
|
||||
env_file: .env
|
||||
ports:
|
||||
- 80:8080
|
||||
@ -28,21 +43,18 @@ services:
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
mongo:
|
||||
container_name: infisical-mongo
|
||||
image: mongo
|
||||
db:
|
||||
container_name: infisical-db
|
||||
image: postgres:14-alpine
|
||||
restart: always
|
||||
env_file: .env
|
||||
environment:
|
||||
- MONGO_INITDB_ROOT_USERNAME=${MONGO_USERNAME}
|
||||
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD}
|
||||
volumes:
|
||||
- mongo-data:/data/db
|
||||
- pg_data:/data/db
|
||||
networks:
|
||||
- infisical
|
||||
|
||||
volumes:
|
||||
mongo-data:
|
||||
pg_data:
|
||||
driver: local
|
||||
redis_data:
|
||||
driver: local
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Create"
|
||||
openapi: "POST /api/v2/workspace/{workspaceId}/environments"
|
||||
openapi: "POST /api/v1/workspace/{workspaceId}/environments"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Delete"
|
||||
openapi: "DELETE /api/v2/workspace/{workspaceId}/environments"
|
||||
---
|
||||
openapi: "DELETE /api/v1/workspace/{workspaceId}/environments/{id}"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Update"
|
||||
openapi: "PUT /api/v2/workspace/{workspaceId}/environments"
|
||||
---
|
||||
openapi: "PATCH /api/v1/workspace/{workspaceId}/environments/{id}"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Create"
|
||||
openapi: "POST /api/v1/folders/"
|
||||
---
|
||||
openapi: "POST /api/v1/folders"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Delete"
|
||||
openapi: "DELETE /api/v1/folders/{folderName}"
|
||||
---
|
||||
openapi: "DELETE /api/v1/folders/{folderId}"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "List"
|
||||
openapi: "GET /api/v1/folders/"
|
||||
---
|
||||
openapi: "GET /api/v1/folders"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Update"
|
||||
openapi: "PATCH /api/v1/folders/{folderName}"
|
||||
---
|
||||
openapi: "PATCH /api/v1/folders/{folderId}"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Create"
|
||||
openapi: "POST /api/v1/identities/"
|
||||
---
|
||||
openapi: "POST /api/v1/identities"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Delete"
|
||||
openapi: "DELETE /api/v1/identities/{identityId}"
|
||||
---
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Update"
|
||||
openapi: "PATCH /api/v1/identities/{identityId}"
|
||||
---
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "List Identity Memberships"
|
||||
openapi: "GET /api/v2/organizations/{organizationId}/identity-memberships"
|
||||
---
|
||||
openapi: "GET /api/v2/organizations/{orgId}/identity-memberships"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Create"
|
||||
openapi: "POST /api/v1/secret-imports/"
|
||||
---
|
||||
openapi: "POST /api/v1/secret-imports"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Delete"
|
||||
openapi: "DELETE /api/v1/secret-imports/{id}"
|
||||
---
|
||||
openapi: "DELETE /api/v1/secret-imports/{secretImportId}"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "List"
|
||||
openapi: "GET /api/v1/secret-imports/"
|
||||
---
|
||||
openapi: "GET /api/v1/secret-imports"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Update"
|
||||
openapi: "PUT /api/v1/secret-imports/{id}"
|
||||
---
|
||||
openapi: "PATCH /api/v1/secret-imports/{secretImportId}"
|
||||
---
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "Get"
|
||||
openapi: "GET /api/v2/service-token/"
|
||||
openapi: "GET /api/v2/service-token"
|
||||
---
|
||||
|
||||
<Warning>
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Delete Identity Membership"
|
||||
openapi: "DELETE /api/v2/workspace/{workspaceId}/identity-memberships/{identityId}"
|
||||
---
|
||||
openapi: "DELETE /api/v2/workspace/{projectId}/identity-memberships/{identityId}"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Delete User Membership"
|
||||
openapi: "DELETE /api/v2/workspace/{workspaceId}/memberships/{membershipId}"
|
||||
openapi: "DELETE /api/v1/workspace/{workspaceId}/memberships/{membershipId}"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "List Identity Memberships"
|
||||
openapi: "GET /api/v2/workspace/{workspaceId}/identity-memberships"
|
||||
---
|
||||
openapi: "GET /api/v2/workspace/{projectId}/identity-memberships"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Get User Memberships"
|
||||
openapi: "GET /api/v2/workspace/{workspaceId}/memberships"
|
||||
openapi: "GET /api/v1/workspace/{workspaceId}/memberships"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Roll Back to Snapshot"
|
||||
openapi: "POST /api/v1/workspace/{workspaceId}/secret-snapshots/rollback"
|
||||
openapi: "POST /api/v1/secret-snapshot/{secretSnapshotId}/rollback"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Update Identity Membership"
|
||||
openapi: "PATCH /api/v2/workspace/{workspaceId}/identity-memberships/{identityId}"
|
||||
---
|
||||
openapi: "PATCH /api/v2/workspace/{projectId}/identity-memberships/{identityId}"
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Update User Membership"
|
||||
openapi: "PATCH /api/v2/workspace/{workspaceId}/memberships/{membershipId}"
|
||||
openapi: "PATCH /api/v1/workspace/{workspaceId}/memberships/{membershipId}"
|
||||
---
|
||||
|
82
docs/contributing/platform/backend/folder-structure.mdx
Normal file
@ -0,0 +1,82 @@
|
||||
---
|
||||
title: 'Backend folder structure'
|
||||
---
|
||||
|
||||
```
|
||||
├── scripts
|
||||
├── e2e-test
|
||||
└── src/
|
||||
├── @types/
|
||||
│ ├── knex.d.ts
|
||||
│ └── fastify.d.ts
|
||||
├── db/
|
||||
│ ├── migrations
|
||||
│ ├── schemas
|
||||
│ └── seed
|
||||
├── lib/
|
||||
│ ├── fn
|
||||
│ ├── date
|
||||
│ └── config
|
||||
├── queue
|
||||
├── server/
|
||||
│ ├── routes/
|
||||
│ │ ├── v1
|
||||
│ │ └── v2
|
||||
│ ├── plugins
|
||||
│ └── config
|
||||
├── services/
|
||||
│ ├── auth
|
||||
│ ├── org
|
||||
│ └── project/
|
||||
│ ├── project-service.ts
|
||||
│ ├── project-types.ts
|
||||
│ └── project-dal.ts
|
||||
└── ee/
|
||||
├── routes
|
||||
└── services
|
||||
```
|
||||
|
||||
### `backend/scripts`
|
||||
Contains reusable scripts for backend automation, like running migrations and generating SQL schemas.
|
||||
|
||||
### `backend/e2e-test`
|
||||
Integration tests for the APIs.
|
||||
|
||||
### `backend/src`
|
||||
The source code of the backend.
|
||||
|
||||
- `@types`: Type definitions for libraries like Fastify and Knex.
|
||||
- `db`: Knex.js configuration for the database, including migration, seed files, and SQL type schemas.
|
||||
- `lib`: Stateless, reusable functions used across the codebase.
|
||||
- `queue`: Infisical's queue system based on BullMQ.
|
||||
|
||||
### `src/server`
|
||||
|
||||
- Scope anything related to Fastify/service here.
|
||||
- Includes routes, Fastify plugins, and server configurations.
|
||||
- The routes folder contains various versions of routes separated into v1, v2, etc.
|
||||
|
||||
### `src/services`
|
||||
|
||||
- Handles the core business logic for all operations.
|
||||
- Follows the co-location principle: related components should be kept together.
|
||||
- Each service component typically contains:
|
||||
|
||||
1. **dal**: Database Access Layer functions for database operations
|
||||
2. **service**: The service layer containing business logic.
|
||||
3. **type**: Type definitions used within the service component.
|
||||
4. **fns**: An optional component for sharing reusable functions related to the service.
|
||||
5. **queue**: An optional component for queue-specific logic, like `secret-queue.ts`.
|
||||
|
||||
### `src/ee`
|
||||
|
||||
Follows the same pattern as above, with the exception of a license change from MIT to Infisical Proprietary License.
|
||||
|
||||
### Guidelines and Best Practices
|
||||
|
||||
- All services are interconnected at `/src/server/routes/index.ts`, following the principle of simple dependency injection.
|
||||
- Files should be named in dash-case.
|
||||
- Avoid using classes in the codebase; opt for simple functions instead.
|
||||
- All committed code must be properly linted using `npm run lint:fix` and type-checked with `npm run type:check`.
|
||||
- Minimize shared logic between services as much as possible.
|
||||
- Controllers within a router component should ideally call only one service layer, with exceptions for services like `audit-log` that require access to request object data.
|
@ -0,0 +1,56 @@
|
||||
---
|
||||
title: "Backend development guide"
|
||||
---
|
||||
|
||||
Suppose you're interested in implementing a new feature in Infisical's backend, let's call it "feature-x." Here are the general steps you should follow.
|
||||
|
||||
## Database schema migration
|
||||
In order to run [schema migrations](https://en.wikipedia.org/wiki/Schema_migration#:~:text=A%20schema%20migration%20is%20performed,some%20newer%20or%20older%20version) you need to expose your database connection string. Create a `.env.migration` file to set the database connection URI for migration scripts, or alternatively, export the `DB_CONNECTION_URI` environment variable.
|
||||
|
||||
## Creating new database model
|
||||
If your feature involves a change in the database, you need to first address this by generating the necessary database schemas.
|
||||
|
||||
1. If you're adding a new table, update the `TableName` enum in `/src/db/schemas/models.ts` to include the new table name.
|
||||
2. Create a new migration file by running `npm run migration:new` and give it a relevant name, such as `feature-x`.
|
||||
3. Navigate to `/src/db/migrations/<timestamp>_<feature-x>.ts`.
|
||||
4. Modify both the `up` and `down` functions to create or alter Postgres fields on migration up and to revert these changes on migration down, ensuring idempotency as outlined [here](https://github.com/graphile/migrate/blob/main/docs/idempotent-examples.md).
|
||||
|
||||
### Generating TS Schemas
|
||||
|
||||
While typically you would need to manually write TS types for Knex type-sense, we have automated this process:
|
||||
|
||||
1. Start the server.
|
||||
2. Run `npm run migration:latest` to apply all database changes.
|
||||
3. Execute `npm run generate:schema` to automatically generate types and schemas using [zod](https://github.com/colinhacks/zod) in the `/src/db/schemas` folder.
|
||||
4. Update the barrel export in `schema/index` and include the new tables in `/src/@types/knex.d.ts` to enable type-sensing in Knex.js.
|
||||
|
||||
## Business Logic
|
||||
|
||||
Once the database changes are in place, it's time to create the APIs for `feature-x`:
|
||||
|
||||
1. Execute `npm run generate:component`.
|
||||
2. Choose option 1 for the service component.
|
||||
3. Name the service in dash-case, like `feature-x`. This will create a `feature-x` folder in `/src/services` containing three files.
|
||||
1. `feature-x-dal`: The Database Access Layer functions.
|
||||
2. `feature-x-service`: The service layer where all the business logic is handled.
|
||||
3. `feature-x-type`: The types used by `feature-x`.
|
||||
|
||||
For reusable shared functions, set up a file named `feature-x-fns`.
|
||||
|
||||
Use the custom Infisical function `ormify` in `src/lib/knex` for simple database operations within the DAL.
|
||||
|
||||
## Connecting the Service Layer to the Server Layer
|
||||
|
||||
Server-related logic is handled in `/src/server`. To connect the service layer to the server layer, we use Fastify plugins for dependency injection:
|
||||
|
||||
1. Add the service type in the `fastify.d.ts` file under the `service` namespace of a FastifyServerInstance type.
|
||||
2. In `/src/server/routes/index.ts`, instantiate the required dependencies for `feature-x`, such as the DAL and service layers, and then pass them to `fastify.register("service,{...dependencies})`.
|
||||
3. This makes the service layer accessible within all routes under the Fastify service instance, accessed via `server.services.<registered service name>.<function>`.
|
||||
|
||||
## Writing API Routes
|
||||
|
||||
1. To create a route component, run `npm generate:component`.
|
||||
2. Select option 3, type the router name in dash-case, and provide the version number. This will generate a router file in `src/server/routes/v<version-number>/<router component name>`
|
||||
1. Implement your logic to connect with the service layer as needed.
|
||||
2. Import the router component in the version folder's index.ts. For instance, if it's in v1, import it in `v1/index.ts`.
|
||||
3. Finally, register it under the appropriate prefix for access.
|
@ -4,7 +4,7 @@ description: "Programmatically interact with Infisical"
|
||||
---
|
||||
|
||||
<Note>
|
||||
Currently, identities can only be used to make authenticated requests to the Infisical API and SDKs. They do not work with clients such as CLI, K8s Operator, Terraform Provider, etc.
|
||||
Currently, identities can only be used to make authenticated requests to the Infisical API, SDKs, and Agent. They do not work with clients such as CLI, K8s Operator, Terraform Provider, etc.
|
||||
|
||||
We will be releasing compatibility with it across clients in the coming quarter.
|
||||
</Note>
|
||||
@ -50,4 +50,4 @@ Check out the following authentication method-specific guides for step-by-step i
|
||||
- The identity you are trying to read, update, or delete is more privileged than yourself.
|
||||
- The role you are trying to create an identity for or update an identity to is more privileged than yours.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
</AccordionGroup>
|
||||
|
@ -91,10 +91,17 @@ description: "Configure Azure SAML for Infisical SSO"
|
||||

|
||||
</Step>
|
||||
<Step title="Enable SAML SSO in Infisical">
|
||||
Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Azure.
|
||||
Enabling SAML SSO allows members in your organization to log into Infisical via Azure.
|
||||
|
||||

|
||||
</Step>
|
||||
<Step title="Enforce SAML SSO in Infisical">
|
||||
Enforcing SAML SSO ensures that members in your organization can only access Infisical
|
||||
by logging into the organization via Azure.
|
||||
|
||||
To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one Azure user with Infisical;
|
||||
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Note>
|
||||
|
@ -71,10 +71,17 @@ description: "Configure JumpCloud SAML for Infisical SSO"
|
||||

|
||||
</Step>
|
||||
<Step title="Enable SAML SSO in Infisical">
|
||||
Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via JumpCloud.
|
||||
Enabling SAML SSO allows members in your organization to log into Infisical via JumpCloud.
|
||||
|
||||

|
||||
</Step>
|
||||
<Step title="Enforce SAML SSO in Infisical">
|
||||
Enforcing SAML SSO ensures that members in your organization can only access Infisical
|
||||
by logging into the organization via JumpCloud.
|
||||
|
||||
To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one JumpCloud user with Infisical;
|
||||
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Note>
|
||||
|
@ -79,7 +79,7 @@ description: "Configure Okta SAML 2.0 for Infisical SSO"
|
||||

|
||||
</Step>
|
||||
<Step title="Enforce SAML SSO in Infisical">
|
||||
Enforcing SAML SSO requires members in your organization can only access Infisical
|
||||
Enforcing SAML SSO ensures that members in your organization can only access Infisical
|
||||
by logging into the organization via Okta.
|
||||
|
||||
To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one Okta user with Infisical;
|
||||
|
BIN
docs/images/guides/agent-with-ecs/access-token-deposit.png
Normal file
After Width: | Height: | Size: 134 KiB |
BIN
docs/images/guides/agent-with-ecs/ecs-diagram.png
Normal file
After Width: | Height: | Size: 191 KiB |
BIN
docs/images/guides/agent-with-ecs/file_browser_main.png
Normal file
After Width: | Height: | Size: 117 KiB |
BIN
docs/images/guides/agent-with-ecs/filebrowser_afterlogin.png
Normal file
After Width: | Height: | Size: 135 KiB |
BIN
docs/images/guides/agent-with-ecs/secrets-deposit.png
Normal file
After Width: | Height: | Size: 107 KiB |
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 324 KiB |
Before Width: | Height: | Size: 235 KiB After Width: | Height: | Size: 169 KiB |
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 220 KiB |
Before Width: | Height: | Size: 429 KiB After Width: | Height: | Size: 431 KiB |
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 732 KiB |
@ -3,22 +3,32 @@ title: "Jenkins"
|
||||
description: "How to effectively and securely manage secrets in Jenkins using Infisical"
|
||||
---
|
||||
|
||||
**Objective**: Fetch secrets from Infisical to Jenkins pipelines
|
||||
|
||||
In this guide, we'll outline the steps to deliver secrets from Infisical to Jenkins via the Infisical CLI.
|
||||
At a high level, the Infisical CLI will be executed within your build environment and use a service token to authenticate with Infisical.
|
||||
This token must be added as a Jenkins Credential and then passed to the Infisical CLI as an environment variable, enabling it to access and retrieve secrets within your workflows.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add secrets to [Infisical](https://app.infisical.com).
|
||||
- You have a working Jenkins installation with the [credentials plugin](https://plugins.jenkins.io/credentials/) installed.
|
||||
- You have the Infisical CLI installed on your Jenkins executor nodes or container images.
|
||||
- You have the [Infisical CLI](/cli/overview) installed on your Jenkins executor nodes or container images.
|
||||
|
||||
|
||||
## Add Infisical Service Token to Jenkins
|
||||
|
||||
After setting up your project in Infisical and adding the Infisical CLI to container images, you will need to add the Infisical Service Token to Jenkins. Once you have generated the token, browse to **Manage Jenkins > Manage Credentials** in your Jenkins installation.
|
||||
After setting up your project in Infisical and installing the Infisical CLI to the environment where your Jenkins builds will run, you will need to add the Infisical Service Token to Jenkins.
|
||||
|
||||
To generate a Infisical service token, follow the guide [here](/documentation/platform/token).
|
||||
Once you have generated the token, navigate to **Manage Jenkins > Manage Credentials** in your Jenkins instance.
|
||||
|
||||

|
||||
|
||||
Click on the credential store you want to store the Infisical Service Token in. In this case, we're using the default Jenkins global store.
|
||||
|
||||
<Info>
|
||||
Each of your projects will have a different INFISICAL_SERVICE_TOKEN though.
|
||||
Each of your projects will have a different `INFISICAL_TOKEN`.
|
||||
As a result, it may make sense to spread these out into separate credential domains depending on your use case.
|
||||
</Info>
|
||||
|
||||
@ -28,18 +38,22 @@ Now, click Add Credentials.
|
||||
|
||||

|
||||
|
||||
Choose **Secret text** from the **Kind** dropdown menu, paste the Infisical Service Token into the **Secret** field, enter `INFISICAL_SERVICE_TOKEN` into the **Description** field, and click **OK**.
|
||||
Choose **Secret text** for the **Kind** option from the dropdown list and enter the Infisical Service Token in the **Secret** field.
|
||||
Although the **ID** can be any value, we'll set it to `infisical-service-token` for the sake of this guide.
|
||||
The description is optional and can be any text you prefer.
|
||||
|
||||
|
||||

|
||||
|
||||
When you're done, you should have a credential similar to the one below:
|
||||
When you're done, you should see a credential similar to the one below:
|
||||
|
||||

|
||||
|
||||
|
||||
## Use Infisical in a Freestyle Project
|
||||
|
||||
To use Infisical in a Freestyle Project job, you'll need to expose the credential you created above in an environment variable. First, click New Item from the dashboard navigation sidebar:
|
||||
To fetch secrets with Infisical in a Freestyle Project job, you'll need to expose the credential you created above as an environment variable to the Infisical CLI.
|
||||
To do so, first click **New Item** from the dashboard navigation sidebar:
|
||||
|
||||

|
||||
|
||||
@ -51,7 +65,8 @@ Scroll down to the **Build Environment** section and enable the **Use secret tex
|
||||
|
||||

|
||||
|
||||
Enter INFISICAL_SERVICE_TOKEN in the **Variable** field, select the **Specific credentials** option from the Credentials section and choose INFISICAL_SERVICE_TOKEN from the dropdown menu.
|
||||
Enter `INFISICAL_TOKEN` in the **Variable** field then click the **Specific credentials** option from the Credentials section and select the credential you created earlier.
|
||||
In this case, we saved it as `Infisical service token` so we'll choose that from the dropdown menu.
|
||||
|
||||

|
||||
|
||||
@ -59,15 +74,16 @@ Scroll down to the **Build** section and choose **Execute shell** from the **Add
|
||||
|
||||

|
||||
|
||||
In the command field, enter the following command and click **Save**:
|
||||
In the command field, you can now use the Infisical CLI to fetch secrets.
|
||||
The example command below will print the secrets using the service token passed as a credential. When done, click **Save**.
|
||||
|
||||
```
|
||||
infisical run -- printenv
|
||||
infisical secrets --env=dev --path=/
|
||||
```
|
||||
|
||||

|
||||
|
||||
Finally, click **Build Now** from the navigation sidebar to test your new job.
|
||||
Finally, click **Build Now** from the navigation sidebar to run your new job.
|
||||
|
||||
<Info>
|
||||
Running into issues? Join Infisical's [community Slack](https://infisical.com/slack) for quick support.
|
||||
@ -77,7 +93,8 @@ Finally, click **Build Now** from the navigation sidebar to test your new job.
|
||||
|
||||
## Use Infisical in a Jenkins Pipeline
|
||||
|
||||
To use Infisical in a Pipeline job, you'll need to expose the credential you created above as an environment variable. First, click **New Item** from the dashboard navigation sidebar:
|
||||
To fetch secrets using Infisical in a Pipeline job, you'll need to expose the Jenkins credential you created above as an environment variable.
|
||||
To do so, click **New Item** from the dashboard navigation sidebar:
|
||||
|
||||

|
||||
|
||||
@ -92,31 +109,31 @@ pipeline {
|
||||
agent any
|
||||
|
||||
environment {
|
||||
INFISICAL_SERVICE_TOKEN = credentials('INFISICAL_SERVICE_TOKEN')
|
||||
INFISICAL_TOKEN = credentials('infisical-service-token')
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Run Infisical') {
|
||||
steps {
|
||||
sh("infisical secrets")
|
||||
sh("infisical secrets --env=dev --path=/")
|
||||
|
||||
// doesn't work
|
||||
// sh("docker run --rm test-container infisical secrets")
|
||||
|
||||
// works
|
||||
// sh("docker run -e INFISICAL_SERVICE_TOKEN=${INFISICAL_SERVICE_TOKEN} --rm test-container infisical secrets")
|
||||
// sh("docker run -e INFISICAL_TOKEN=${INFISICAL_TOKEN} --rm test-container infisical secrets --env=dev --path=/")
|
||||
|
||||
// doesn't work
|
||||
// sh("docker-compose up -d")
|
||||
|
||||
// works
|
||||
// sh("INFISICAL_SERVICE_TOKEN=${INFISICAL_SERVICE_TOKEN} docker-compose up -d")
|
||||
// sh("INFISICAL_TOKEN=${INFISICAL_TOKEN} docker-compose up -d")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is a very basic sample that you can work from. Jenkins injects the INFISICAL_SERVICE_TOKEN environment variable defined in the pipeline into the shell the commands execute with, but there are some situations where that won't pass through properly – notably if you're executing docker containers on the executor machine. The examples above should give you some idea for how that will work.
|
||||
|
||||
Finally, click **Build Now** from the navigation sidebar to test your new job.
|
||||
The example provided above serves as an initial guide. It shows how Jenkins adds the `INFISICAL_TOKEN` environment variable, which is configured in the pipeline, into the shell for executing commands.
|
||||
There may be instances where this doesn't work as expected in the context of running Docker commands.
|
||||
However, the list of working examples should provide some insight into how this can be handled properly.
|
||||
|
@ -5,6 +5,19 @@ description: "How to use Infisical for secret management in Ansible"
|
||||
|
||||
The documentation for using Infisical to manage secrets in Ansible is currently available [here](https://galaxy.ansible.com/ui/repo/published/infisical/vault/).
|
||||
|
||||
<Info>
|
||||
Have any questions? Join Infisical's [community Slack](https://infisical.com/slack) for quick support.
|
||||
</Info>
|
||||
## Troubleshoot
|
||||
|
||||
<Accordion title="I'm getting a error related to objc[72832]: +[__NSCFConstantString initialize]">
|
||||
If you get this Python error when you running the lookup plugin:-
|
||||
|
||||
```
|
||||
objc[72832]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.
|
||||
Fatal Python error: Aborted
|
||||
```
|
||||
|
||||
You will need to add this to your shell environment or ansible wrapper script:-
|
||||
|
||||
```
|
||||
export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
|
||||
```
|
||||
</Accordion>
|
||||
|
@ -51,7 +51,7 @@ CMD ["infisical", "run", "--", "npm", "run", "start"]
|
||||
CMD ["infisical", "run", "--command", "npm run start && ..."]
|
||||
```
|
||||
|
||||
## Generate an service token
|
||||
## Generate a service token
|
||||
|
||||
Head to your project settings in the Infisical dashboard to generate an [service token](/documentation/platform/token).
|
||||
This service token will allow you to authenticate and fetch secrets from Infisical.
|
||||
|
287
docs/integrations/platforms/ecs-with-agent.mdx
Normal file
@ -0,0 +1,287 @@
|
||||
---
|
||||
title: 'Amazon ECS'
|
||||
description: "How to deliver secrets to Amazon Elastic Container Service"
|
||||
---
|
||||
|
||||

|
||||
|
||||
This guide will go over the steps needed to access secrets stored in Infisical from Amazon Elastic Container Service (ECS).
|
||||
|
||||
At a high level, the steps involve setting up an ECS task with a [Infisical Agent](/infisical-agent/overview) as a sidecar container. This sidecar container uses [Universal Auth](/documentation/platform/identities/universal-auth) to authenticate with Infisical to fetch secrets/access tokens.
|
||||
Once the secrets/access tokens are retrieved, they are then stored in a shared [Amazon Elastic File System](https://aws.amazon.com/efs/) (EFS) volume. This volume is then made accessible to your application and all of its replicas.
|
||||
|
||||
This guide primarily focuses on integrating Infisical Cloud with Amazon ECS on AWS Fargate and Amazon EFS.
|
||||
However, the principles and steps can be adapted for use with any instance of Infisical (on premise or cloud) and different ECS launch configurations.
|
||||
|
||||
## Prerequisites
|
||||
This guide requires the following prerequisites:
|
||||
- Infisical account
|
||||
- Git installed
|
||||
- Terraform v1.0 or later installed
|
||||
- Access to AWS credentials
|
||||
- Understanding of [Infisical Agent](/infisical-agent/overview)
|
||||
|
||||
## What we will deploy
|
||||
For this demonstration, we'll deploy the [File Browser](https://github.com/filebrowser/filebrowser) application on our ECS cluster.
|
||||
Although this guide focuses on File Browser, the principles outlined here can be applied to any application of your choice.
|
||||
|
||||
File Browser plays a key role in this context because it enables us to view all files attached to a specific volume.
|
||||
This feature is important for our demonstration, as it allows us to verify whether the Infisical agent is depositing the expected files into the designated file volume and if those files are accessible to the application.
|
||||
|
||||
<Warning>
|
||||
Volumes that contain sensitive secrets should not be publicly accessible. The use of File Browser here is solely for demonstration and verification purposes.
|
||||
</Warning>
|
||||
|
||||
|
||||
## Configure Authentication with Infisical
|
||||
In order for the Infisical agent to fetch credentials from Infisical, we'll first need to authenticate with Infisical.
|
||||
While Infisical supports various authentication methods, this guide focuses on using Universal Auth to authenticate the agent with Infisical.
|
||||
|
||||
Follow the documentation to configure and generate a client id and client secret with Universal auth [here](/documentation/platform/identities/universal-auth).
|
||||
Make sure to save these credentials somewhere handy because you'll need them soon.
|
||||
|
||||
## Clone guide assets repository
|
||||
To help you quickly deploy the example application, please clone the guide assets from this [Github repository](https://github.com/Infisical/infisical-guides.git).
|
||||
This repository contains assets for all Infisical guides. The content for this guide can be found within a sub directory called `aws-ecs-with-agent`.
|
||||
The guide will assume that `aws-ecs-with-agent` is your working directory going forward.
|
||||
|
||||
## Deploy example application
|
||||
|
||||
Before we can deploy our full application and its related infrastructure with Terraform, we'll need to first configure our Infisical agent.
|
||||
|
||||
### Agent configuration overview
|
||||
The agent config file defines what authentication method will be used when connecting with Infisical along with where the fetched secrets/access tokens should be saved to.
|
||||
|
||||
Since the Infisical agent will be deployed as a sidecar, the agent configuration file and any secret template files will need to be encoded in base64.
|
||||
This encoding step is necessary as it allows these files to be added into our Terraform configuration file without needing to upload them first.
|
||||
|
||||
#### Secret template file
|
||||
The Infisical agent accepts one or more optional template files. If provided, the agent will fetch secrets using the set authentication method and format the fetched secrets according to the given template file.
|
||||
|
||||
For demonstration purposes, we will create the following secret template file.
|
||||
This template will transform our secrets from Infisical project with the ID `62fd92aa8b63973fee23dec7`, in the `dev` environment, and secrets located in the path `/`, into a `KEY=VALUE` format.
|
||||
|
||||
<Tip>
|
||||
Remember to update the project id, environment slug and secret path to one that exists within your Infisical project
|
||||
</Tip>
|
||||
|
||||
```secrets.template secrets.template
|
||||
{{- with secret "62fd92aa8b63973fee23dec7" "dev" "/" }}
|
||||
{{- range . }}
|
||||
{{ .Key }}={{ .Value }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
Next, we need encode this template file in `base64` so it can be set in the agent configuration file.
|
||||
|
||||
```bash
|
||||
cat secrets.template | base64
|
||||
Cnt7LSB3aXRoIHNlY3JldCAiMWVkMjk2MWQtNDM5NS00MmNlLTlkNzQtYjk2ZGQwYmYzMDg0IiAiZGV2IiAiLyIgfX0Ke3stIHJhbmdlIC4gfX0Ke3sgLktleSB9fT17eyAuVmFsdWUgfX0Ke3stIGVuZCB9fQp7ey0gZW5kIH19
|
||||
```
|
||||
|
||||
#### Full agent configuration file
|
||||
This agent config file will connect with Infisical Cloud using Universal Auth and deposit access tokens at path `/infisical-agent/access-token` and render secrets to file `/infisical-agent/secrets`.
|
||||
|
||||
You'll notice that instead of passing the path to the secret template file as we normally would, we set the base64 encoded template from the previous step under `base64-template-content` property.
|
||||
|
||||
```yaml agent-config.yaml
|
||||
infisical:
|
||||
address: https://app.infisical.com
|
||||
exit-after-auth: true
|
||||
auth:
|
||||
type: universal-auth
|
||||
config:
|
||||
remove_client_secret_on_read: false
|
||||
sinks:
|
||||
- type: file
|
||||
config:
|
||||
path: /infisical-agent/access-token
|
||||
templates:
|
||||
- base64-template-content: Cnt7LSB3aXRoIHNlY3JldCAiMWVkMjk2MWQtNDM5NS00MmNlLTlkNzQtYjk2ZGQwYmYzMDg0IiAiZGV2IiAiLyIgfX0Ke3stIHJhbmdlIC4gfX0Ke3sgLktleSB9fT17eyAuVmFsdWUgfX0Ke3stIGVuZCB9fQp7ey0gZW5kIH19
|
||||
destination-path: /infisical-agent/secrets
|
||||
```
|
||||
|
||||
Again, we'll need to encode the full configuration file in `base64` so it can be easily delivered via Terraform.
|
||||
|
||||
```bash
|
||||
cat agent-config.yaml | base64
|
||||
aW5maXNpY2FsOgogIGFkZHJlc3M6IGh0dHBzOi8vYXBwLmluZmlzaWNhbC5jb20KICBleGl0LWFmdGVyLWF1dGg6IHRydWUKYXV0aDoKICB0eXBlOiB1bml2ZXJzYWwtYXV0aAogIGNvbmZpZzoKICAgIHJlbW92ZV9jbGllbnRfc2VjcmV0X29uX3JlYWQ6IGZhbHNlCnNpbmtzOgogIC0gdHlwZTogZmlsZQogICAgY29uZmlnOgogICAgICBwYXRoOiAvaW5maXNpY2FsLWFnZW50L2FjY2Vzcy10b2tlbgp0ZW1wbGF0ZXM6CiAgLSBiYXNlNjQtdGVtcGxhdGUtY29udGVudDogQ250N0xTQjNhWFJvSUhObFkzSmxkQ0FpTVdWa01qazJNV1F0TkRNNU5TMDBNbU5sTFRsa056UXRZamsyWkdRd1ltWXpNRGcwSWlBaVpHVjJJaUFpTHlJZ2ZYMEtlM3N0SUhKaGJtZGxJQzRnZlgwS2Uzc2dMa3RsZVNCOWZUMTdleUF1Vm1Gc2RXVWdmWDBLZTNzdElHVnVaQ0I5ZlFwN2V5MGdaVzVrSUgxOQogICAgZGVzdGluYXRpb24tcGF0aDogL2luZmlzaWNhbC1hZ2VudC9zZWNyZXRzCg==
|
||||
```
|
||||
|
||||
## Add auth credentials & agent config
|
||||
With the base64 encoded agent config file and Universal Auth credentials in hand, it's time to assign them as values in our Terraform config file.
|
||||
|
||||
To configure these values, navigate to the `ecs.tf` file in your preferred code editor and assign values to `auth_client_id`, `auth_client_secret`, and `agent_config`.
|
||||
|
||||
```hcl ecs.tf
|
||||
...snip...
|
||||
data "template_file" "cb_app" {
|
||||
template = file("./templates/ecs/cb_app.json.tpl")
|
||||
|
||||
vars = {
|
||||
app_image = var.app_image
|
||||
sidecar_image = var.sidecar_image
|
||||
app_port = var.app_port
|
||||
fargate_cpu = var.fargate_cpu
|
||||
fargate_memory = var.fargate_memory
|
||||
aws_region = var.aws_region
|
||||
auth_client_id = "<paste-client-id-string>"
|
||||
auth_client_secret = "<paset-client-secret-string>"
|
||||
agent_config = "<paste-base64-encoded-agent-config-string>"
|
||||
}
|
||||
}
|
||||
...snip...
|
||||
```
|
||||
|
||||
<Warning>
|
||||
To keep this guide simple, `auth_client_id`, `auth_client_secret` have been added directly into the ECS container definition.
|
||||
However, in production, you should securely fetch these values from AWS Secrets Manager or AWS Parameter store and feed them directly to agent sidecar.
|
||||
</Warning>
|
||||
|
||||
After these values have been set, they will be passed to the Infisical agent during startup through environment variables, as configured in the `infisical-sidecar` container below.
|
||||
|
||||
```terraform templates/ecs/cb_app.json.tpl
|
||||
[
|
||||
...snip...
|
||||
{
|
||||
"name": "infisical-sidecar",
|
||||
"image": "${sidecar_image}",
|
||||
"cpu": 1024,
|
||||
"memory": 1024,
|
||||
"networkMode": "bridge",
|
||||
"command": ["agent"],
|
||||
"essential": false,
|
||||
"logConfiguration": {
|
||||
"logDriver": "awslogs",
|
||||
"options": {
|
||||
"awslogs-group": "/ecs/agent",
|
||||
"awslogs-region": "${aws_region}",
|
||||
"awslogs-stream-prefix": "ecs"
|
||||
}
|
||||
},
|
||||
"healthCheck": {
|
||||
"command": ["CMD-SHELL", "agent", "--help"],
|
||||
"interval": 30,
|
||||
"timeout": 5,
|
||||
"retries": 3,
|
||||
"startPeriod": 0
|
||||
},
|
||||
"environment": [
|
||||
{
|
||||
"name": "INFISICAL_UNIVERSAL_AUTH_CLIENT_ID",
|
||||
"value": "${auth_client_id}"
|
||||
},
|
||||
{
|
||||
"name": "INFISICAL_UNIVERSAL_CLIENT_SECRET",
|
||||
"value": "${auth_client_secret}"
|
||||
},
|
||||
{
|
||||
"name": "INFISICAL_AGENT_CONFIG_BASE64",
|
||||
"value": "${agent_config}"
|
||||
}
|
||||
],
|
||||
"mountPoints": [
|
||||
{
|
||||
"containerPath": "/infisical-agent",
|
||||
"sourceVolume": "infisical-efs"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
In the above container definition, you'll notice that that the Infisical agent has a `mountPoints` defined.
|
||||
This mount point is referencing to the already configured EFS volume as shown below.
|
||||
`containerPath` is set to `/infisical-agent` because that is that the folder we have instructed the agent to deposit the credentials to.
|
||||
|
||||
```hcl terraform/efs.tf
|
||||
resource "aws_efs_file_system" "infisical_efs" {
|
||||
tags = {
|
||||
Name = "INFISICAL-ECS-EFS"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_efs_mount_target" "mount" {
|
||||
count = length(aws_subnet.private.*.id)
|
||||
file_system_id = aws_efs_file_system.infisical_efs.id
|
||||
subnet_id = aws_subnet.private[count.index].id
|
||||
security_groups = [aws_security_group.efs_sg.id]
|
||||
}
|
||||
```
|
||||
|
||||
## Configure AWS credentials
|
||||
Because we'll be deploying the example file browser application to AWS via Terraform, you will need to obtain a set of `AWS Access Key` and `Secret Key`.
|
||||
Once you have generated these credentials, export them to your terminal.
|
||||
|
||||
1. Export the AWS Access Key ID:
|
||||
|
||||
```bash
|
||||
export AWS_ACCESS_KEY_ID=<your AWS access key ID>
|
||||
```
|
||||
|
||||
2. Export the AWS Secret Access Key:
|
||||
|
||||
```bash
|
||||
export AWS_SECRET_ACCESS_KEY=<your AWS secret access key>
|
||||
```
|
||||
|
||||
## Deploy terraform configuration
|
||||
With the agent's sidecar configuration complete, we can now deploy our changes to AWS via Terraform.
|
||||
|
||||
1. Change your directory to `terraform`
|
||||
```sh
|
||||
cd terraform
|
||||
```
|
||||
|
||||
2. Initialize Terraform
|
||||
```
|
||||
$ terraform init
|
||||
```
|
||||
|
||||
3. Preview resources that will be created
|
||||
```
|
||||
$ terraform plan
|
||||
```
|
||||
|
||||
4. Trigger resource creation
|
||||
```bash
|
||||
$ terraform apply
|
||||
|
||||
Do you want to perform these actions?
|
||||
Terraform will perform the actions described above.
|
||||
Only 'yes' will be accepted to approve.
|
||||
|
||||
Enter a value: yes
|
||||
```
|
||||
|
||||
```bash
|
||||
|
||||
Apply complete! Resources: 1 added, 1 changed, 1 destroyed.
|
||||
|
||||
Outputs:
|
||||
|
||||
alb_hostname = "cb-load-balancer-1675475779.us-east-1.elb.amazonaws.com:8080"
|
||||
```
|
||||
|
||||
Once the resources have been successfully deployed, Terrafrom will output the host address where the file browser application will be accessible.
|
||||
It may take a few minutes for the application to become fully ready.
|
||||
|
||||
|
||||
## Verify secrets/tokens in EFS volume
|
||||
To verify that the agent is depositing access tokens and rendering secrets to the paths specified in the agent config, navigate to the web address from the previous step.
|
||||
Once you visit the address, you'll be prompted to login. Enter the credentials shown below.
|
||||
|
||||

|
||||
|
||||
Since our EFS volume is mounted to the path of the file browser application, we should see the access token and rendered secret file we defined via the agent config file.
|
||||
|
||||

|
||||
|
||||
As expected, two files are present: `access-token` and `secrets`.
|
||||
The `access-token` file should hold a valid `Bearer` token, which can be used to make HTTP requests to Infisical.
|
||||
The `secrets` file should contain secrets, formatted according to the specifications in our secret template file (presented in key=value format).
|
||||
|
||||

|
||||
|
||||

|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "Infisical",
|
||||
"openapi": "https://app.infisical.com/api/docs/json",
|
||||
"logo": {
|
||||
"dark": "/logo/dark.svg",
|
||||
"light": "/logo/light.svg",
|
||||
@ -233,7 +234,8 @@
|
||||
},
|
||||
"integrations/platforms/kubernetes",
|
||||
"integrations/frameworks/terraform",
|
||||
"integrations/platforms/ansible"
|
||||
"integrations/platforms/ansible",
|
||||
"integrations/platforms/ecs-with-agent"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -462,7 +464,9 @@
|
||||
{
|
||||
"group": "Contributing to platform",
|
||||
"pages": [
|
||||
"contributing/platform/developing"
|
||||
"contributing/platform/developing",
|
||||
"contributing/platform/backend/how-to-create-a-feature",
|
||||
"contributing/platform/backend/folder-structure"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -18,8 +18,8 @@ Other environment variables are listed below to increase the functionality of yo
|
||||
Must be a random 32 byte base64 string. Can be generated with `openssl rand -base64 32`
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="MONGO_URL" type="string" default="none" required>
|
||||
Mongo connection string. *TLS based connection string is not yet supported
|
||||
<ParamField query="DB_CONNECTION_URI" type="string" default="none" required>
|
||||
Postgres database connection string.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="REDIS_URL" type="string" default="none" required>
|
||||
|
@ -2,53 +2,79 @@
|
||||
title: "Docker Compose"
|
||||
description: "Run Infisical with Docker Compose template"
|
||||
---
|
||||
Install Infisical using Docker compose. This self hosting method contains all of the required components needed
|
||||
to run a functional instance of Infisical.
|
||||
|
||||
<Steps>
|
||||
<Step title="Install Docker on your VM">
|
||||
```bash
|
||||
# Example in ubuntu
|
||||
apt-get update
|
||||
apt-get upgrade
|
||||
apt install docker-compose
|
||||
```
|
||||
</Step>
|
||||
<Step title="Download required files">
|
||||
2.1. Run the command below to download the `.env` file template.
|
||||
|
||||
```bash
|
||||
wget -O .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
|
||||
```
|
||||
|
||||
2.2. Run the command below to download the docker compose template.
|
||||
|
||||
```bash
|
||||
wget -O docker-compose.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.yml
|
||||
```
|
||||
|
||||
2.3. Run the command below to download the `nginx` config file.
|
||||
|
||||
```bash
|
||||
mkdir nginx && wget -O ./nginx/default.conf https://raw.githubusercontent.com/Infisical/infisical/main/nginx/default.dev.conf
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Update the .env file">
|
||||
Running Infisical requires a few environment variables to be set.
|
||||
At minimum, Infisical requires that you set the variables `ENCRYPTION_KEY`, `AUTH_SECRET`, `MONGO_URL`, and `REDIS_URL` which you can read more about [here](/self-hosting/configuration/envars).
|
||||
## Prerequisites
|
||||
- [Docker](https://docs.docker.com/engine/install/)
|
||||
- [Docker compose](https://docs.docker.com/compose/install/)
|
||||
|
||||
Tweak the `.env` accordingly.
|
||||
<Warning>
|
||||
This Docker Compose configuration is not designed for high-availability production scenarios.
|
||||
It includes just the essential components needed to set up an Infisical proof of concept (POC).
|
||||
Additional configuration is required to enhance data redundancy and ensure higher availability for production environments.
|
||||
</Warning>
|
||||
|
||||
```bash
|
||||
nano .env
|
||||
```
|
||||
</Step>
|
||||
<Step title="Start Infisical">
|
||||
Finally, run the command below to get Infisical up and running (in detached mode).
|
||||
## Verify prerequisites
|
||||
To verify that Docker compose and Docker are installed on the machine where you plan to install Infisical, run the following commands.
|
||||
|
||||
Check for docker installation
|
||||
```bash
|
||||
docker-compose -f docker-compose.yml up -d
|
||||
docker
|
||||
```
|
||||
|
||||
Your Infisical installation is complete and should be running on port `80` or `http://localhost:80`.
|
||||
</Step>
|
||||
</Steps>
|
||||
Check for docker compose installation
|
||||
```bash
|
||||
docker-compose
|
||||
```
|
||||
|
||||
## Download docker compose file
|
||||
You can obtain the Infisical docker compose file by using a command-line downloader such as `wget` or `curl`.
|
||||
If your system doesn't have either of these, you can use a equivalent command that works with your machine.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="curl">
|
||||
```bash
|
||||
curl -o docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="wget">
|
||||
```bash
|
||||
wget -O docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Configure instance credentials
|
||||
Infisical requires a set of credentials used for connecting to dependent services such as Postgres, Redis, etc.
|
||||
The default credentials can be downloaded using the one of the commands listed below.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="curl">
|
||||
```bash
|
||||
curl -o .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="wget">
|
||||
```bash
|
||||
wget -O .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Once downloaded, the credentials file will be saved to your working directly as `.env` file.
|
||||
View all available configurations [here](/self-hosting/configuration/envars).
|
||||
|
||||
<Warning>
|
||||
The default .env file contains credentials that are intended solely for testing purposes.
|
||||
For production use, please generate a new `ENCRYPTION_KEY` and `AUTH_SECRET`. Instructions to do so, can be found [here](/self-hosting/configuration/envars)
|
||||
</Warning>
|
||||
|
||||
## Start Infisical
|
||||
Run the command below to start Infisical and all related services.
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.prod.yml up
|
||||
```
|
||||
|
||||
Your Infisical instance should now be running on port `80`. To access your instance, visit `http://localhost:80`.
|
5152
docs/spec.yaml
@ -58,8 +58,8 @@ export default function DonwloadBackupPDFStep({
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center w-full h-full md:px-6 mx-auto mb-36 md:mb-16">
|
||||
<p className="text-xl text-center font-medium flex justify-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200">
|
||||
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-3 pt-1 text-2xl text-bunker-200" />
|
||||
<p className="text-xl text-center font-medium flex flex-col justify-center items-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200">
|
||||
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-3 pt-1 mb-6 text-6xl text-bunker-200" />
|
||||
{t("signup.step4-message")}
|
||||
</p>
|
||||
<div className="flex flex-col pb-2 bg-mineshaft-800 border border-mineshaft-600 items-center justify-center text-center lg:w-1/6 w-full md:min-w-[24rem] mt-8 max-w-md text-bunker-300 text-md rounded-md">
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { faClock } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faClock, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
@ -9,20 +9,28 @@ import { z } from "zod";
|
||||
import { Button, FormControl, Input, Modal, ModalContent, TextArea } from "@app/components/v2";
|
||||
|
||||
const ReminderFormSchema = z.object({
|
||||
note: z.string().optional(),
|
||||
note: z.string().optional().nullable(),
|
||||
days: z
|
||||
.number()
|
||||
.min(1, { message: "Must be at least 1 day" })
|
||||
.max(365, { message: "Must be less than 365 days" })
|
||||
.nullable()
|
||||
});
|
||||
export type TReminderFormSchema = z.infer<typeof ReminderFormSchema>;
|
||||
|
||||
interface ReminderFormProps {
|
||||
isOpen: boolean;
|
||||
repeatDays?: number | null;
|
||||
note?: string | null;
|
||||
onOpenChange: (isOpen: boolean, data?: TReminderFormSchema) => void;
|
||||
}
|
||||
|
||||
export const CreateReminderForm = ({ isOpen, onOpenChange }: ReminderFormProps) => {
|
||||
export const CreateReminderForm = ({
|
||||
isOpen,
|
||||
onOpenChange,
|
||||
repeatDays,
|
||||
note
|
||||
}: ReminderFormProps) => {
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
@ -31,32 +39,31 @@ export const CreateReminderForm = ({ isOpen, onOpenChange }: ReminderFormProps)
|
||||
handleSubmit,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<TReminderFormSchema>({
|
||||
defaultValues: {
|
||||
days: repeatDays || undefined,
|
||||
note: note || ""
|
||||
},
|
||||
resolver: zodResolver(ReminderFormSchema)
|
||||
});
|
||||
|
||||
const handleFormSubmit = async (data: TReminderFormSchema) => {
|
||||
console.log(data);
|
||||
onOpenChange(false, data);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
reset();
|
||||
reset({
|
||||
days: repeatDays || undefined,
|
||||
note: note || ""
|
||||
});
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
<ModalContent
|
||||
title="Create secret reminder"
|
||||
// ? QUESTION: Should this specifically say its for secret rotation?
|
||||
// ? Or should we be call it something more generic?
|
||||
subTitle={
|
||||
<div>
|
||||
Set up a reminder for when this secret should be rotated. Everyone with access to this
|
||||
project will be notified when the reminder is triggered.
|
||||
</div>
|
||||
}
|
||||
title={`${repeatDays ? "Update" : "Create"} reminder`}
|
||||
subTitle="Set up a reminder for when this secret should be rotated. Everyone with access to this project will be notified when the reminder is triggered."
|
||||
>
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||
<div className="space-y-2">
|
||||
@ -68,7 +75,7 @@ export const CreateReminderForm = ({ isOpen, onOpenChange }: ReminderFormProps)
|
||||
<>
|
||||
<FormControl
|
||||
className="mb-0"
|
||||
label="How many days between"
|
||||
label="Reminder Interval (in days)"
|
||||
isError={Boolean(fieldState.error)}
|
||||
errorText={fieldState.error?.message || ""}
|
||||
>
|
||||
@ -76,6 +83,7 @@ export const CreateReminderForm = ({ isOpen, onOpenChange }: ReminderFormProps)
|
||||
onChange={(el) => setValue("days", parseInt(el.target.value, 10))}
|
||||
type="number"
|
||||
placeholder="31"
|
||||
value={field.value || undefined}
|
||||
/>
|
||||
</FormControl>
|
||||
<div
|
||||
@ -84,7 +92,8 @@ export const CreateReminderForm = ({ isOpen, onOpenChange }: ReminderFormProps)
|
||||
field.value ? "opacity-60" : "opacity-0"
|
||||
)}
|
||||
>
|
||||
Every {field.value > 1 ? `${field.value} days` : "day"}
|
||||
A reminder will be sent every{" "}
|
||||
{field.value && field.value > 1 ? `${field.value} days` : "day"}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@ -102,17 +111,27 @@ export const CreateReminderForm = ({ isOpen, onOpenChange }: ReminderFormProps)
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className="mt-7 flex items-center">
|
||||
<div className="mt-7 flex items-center space-x-4">
|
||||
<Button
|
||||
isDisabled={isSubmitting}
|
||||
isLoading={isSubmitting}
|
||||
key="layout-create-project-submit"
|
||||
className="mr-4"
|
||||
className=""
|
||||
leftIcon={<FontAwesomeIcon icon={faClock} />}
|
||||
type="submit"
|
||||
>
|
||||
Create reminder
|
||||
{repeatDays ? "Update" : "Create"} reminder
|
||||
</Button>
|
||||
{repeatDays && (
|
||||
<Button
|
||||
key="layout-cancel-create-project"
|
||||
onClick={() => onOpenChange(false, { days: null, note: null })}
|
||||
colorSchema="danger"
|
||||
leftIcon={<FontAwesomeIcon icon={faTrash} />}
|
||||
>
|
||||
Delete reminder
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
key="layout-cancel-create-project"
|
||||
onClick={() => onOpenChange(false)}
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
faCheckCircle,
|
||||
faCircle,
|
||||
faCircleDot,
|
||||
faClock,
|
||||
faPlus,
|
||||
faTag
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
@ -33,9 +34,11 @@ import {
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { useGetSecretVersion } from "@app/hooks/api";
|
||||
import { DecryptedSecret, UserWsKeyPair, WsTag } from "@app/hooks/api/types";
|
||||
|
||||
import { CreateReminderForm } from "./CreateReminderForm";
|
||||
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
|
||||
|
||||
type Props = {
|
||||
@ -147,262 +150,317 @@ export const SecretDetailSidebar = ({
|
||||
await onSaveSecret(secret, { ...secret, ...data }, () => reset());
|
||||
};
|
||||
|
||||
const [createReminderFormOpen, setCreateReminderFormOpen] = useToggle(false);
|
||||
|
||||
const secretReminderRepeatDays = watch("reminderRepeatDays");
|
||||
const secretReminderNote = watch("reminderNote");
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
onOpenChange={(state) => {
|
||||
if (isOpen && isDirty) {
|
||||
if (
|
||||
// eslint-disable-next-line no-alert
|
||||
window.confirm("You have edited the secret. Are you sure you want to reset the change?")
|
||||
) {
|
||||
onToggle(false);
|
||||
reset();
|
||||
} else return;
|
||||
}
|
||||
onToggle(state);
|
||||
}}
|
||||
isOpen={isOpen}
|
||||
>
|
||||
<DrawerContent title="Secret">
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)} className="h-full">
|
||||
<div className="flex h-full flex-col">
|
||||
<FormControl label="Key">
|
||||
<Input isDisabled {...register("key")} />
|
||||
</FormControl>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Controller
|
||||
name="value"
|
||||
key="secret-value"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<FormControl label="Value">
|
||||
<SecretInput
|
||||
isReadOnly={isReadOnly}
|
||||
key="secret-value"
|
||||
isDisabled={isOverridden || !isAllowed}
|
||||
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
|
||||
{...field}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<div className="mb-2 border-b border-mineshaft-600 pb-4">
|
||||
<>
|
||||
<CreateReminderForm
|
||||
repeatDays={secretReminderRepeatDays}
|
||||
note={secretReminderNote}
|
||||
isOpen={createReminderFormOpen}
|
||||
onOpenChange={(_, data) => {
|
||||
setCreateReminderFormOpen.toggle();
|
||||
|
||||
if (data) {
|
||||
setValue("reminderRepeatDays", data.days, { shouldDirty: true });
|
||||
setValue("reminderNote", data.note, { shouldDirty: true });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Drawer
|
||||
onOpenChange={(state) => {
|
||||
if (isOpen && isDirty) {
|
||||
if (
|
||||
// eslint-disable-next-line no-alert
|
||||
window.confirm(
|
||||
"You have edited the secret. Are you sure you want to reset the change?"
|
||||
)
|
||||
) {
|
||||
onToggle(false);
|
||||
reset();
|
||||
} else return;
|
||||
}
|
||||
onToggle(state);
|
||||
}}
|
||||
isOpen={isOpen}
|
||||
>
|
||||
<DrawerContent title="Secret">
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)} className="h-full">
|
||||
<div className="flex h-full flex-col">
|
||||
<FormControl label="Key">
|
||||
<Input isDisabled {...register("key")} />
|
||||
</FormControl>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Switch
|
||||
isDisabled={!isAllowed}
|
||||
id="personal-override"
|
||||
onCheckedChange={handleOverrideClick}
|
||||
isChecked={isOverridden}
|
||||
>
|
||||
Override with a personal value
|
||||
</Switch>
|
||||
<Controller
|
||||
name="value"
|
||||
key="secret-value"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<FormControl label="Value">
|
||||
<SecretInput
|
||||
isReadOnly={isReadOnly}
|
||||
key="secret-value"
|
||||
isDisabled={isOverridden || !isAllowed}
|
||||
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
|
||||
{...field}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
{isOverridden && (
|
||||
<Controller
|
||||
name="valueOverride"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<FormControl label="Value Override">
|
||||
<SecretInput
|
||||
isReadOnly={isReadOnly}
|
||||
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormControl label="Tags" className="">
|
||||
<div className="grid auto-cols-min grid-flow-col gap-2 overflow-hidden pt-2">
|
||||
{fields.map(({ tagColor, id: formId, name, id }) => (
|
||||
<Tag
|
||||
className="flex w-min items-center space-x-2"
|
||||
key={formId}
|
||||
onClose={() => {
|
||||
if (cannotEditSecret) {
|
||||
createNotification({ type: "error", text: "Access denied" });
|
||||
return;
|
||||
}
|
||||
const tag = tags?.find(({ id: tagId }) => id === tagId);
|
||||
if (tag) handleTagSelect(tag);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="h-3 w-3 rounded-full"
|
||||
style={{ backgroundColor: tagColor || "#bec2c8" }}
|
||||
/>
|
||||
<div className="text-sm">{name}</div>
|
||||
</Tag>
|
||||
))}
|
||||
<DropdownMenu>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuTrigger asChild>
|
||||
<IconButton
|
||||
ariaLabel="add"
|
||||
variant="outline_bg"
|
||||
size="xs"
|
||||
className="rounded-md"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlus} />
|
||||
</IconButton>
|
||||
</DropdownMenuTrigger>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<DropdownMenuContent align="end" className="z-[100]">
|
||||
<DropdownMenuLabel>Apply tags to this secrets</DropdownMenuLabel>
|
||||
{tags.map((tag) => {
|
||||
const { id: tagId, name, color } = tag;
|
||||
|
||||
const isSelected = selectedTagsGroupById?.[tagId];
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleTagSelect(tag)}
|
||||
key={tagId}
|
||||
icon={isSelected && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||
iconPos="right"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className="mr-2 h-2 w-2 rounded-full"
|
||||
style={{ background: color || "#bec2c8" }}
|
||||
/>
|
||||
{name}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={ProjectPermissionSub.Tags}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem asChild>
|
||||
<Button
|
||||
size="xs"
|
||||
className="w-full"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faTag} />}
|
||||
onClick={onCreateTag}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Create a tag
|
||||
</Button>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormControl label="Comments & Notes">
|
||||
<TextArea
|
||||
className="border border-mineshaft-600 text-sm"
|
||||
{...register("comment")}
|
||||
readOnly={isReadOnly}
|
||||
rows={5}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="my-2 mb-6 border-b border-mineshaft-600 pb-4">
|
||||
<Controller
|
||||
control={control}
|
||||
name="skipMultilineEncoding"
|
||||
render={({ field: { value, onChange, onBlur } }) => (
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Switch
|
||||
id="skipmultiencoding-option"
|
||||
onCheckedChange={(isChecked) => onChange(!isChecked)}
|
||||
isChecked={!value}
|
||||
onBlur={onBlur}
|
||||
isDisabled={!isAllowed}
|
||||
className="items-center"
|
||||
>
|
||||
Enable multi line encoding
|
||||
<Tooltip
|
||||
content="Infisical encodes multiline secrets by escaping newlines and wrapping in quotes. To disable, enable this option"
|
||||
className="z-[100]"
|
||||
>
|
||||
<FontAwesomeIcon icon={faCircleQuestion} className="ml-1" size="sm" />
|
||||
</Tooltip>
|
||||
</Switch>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="dark mb-4 flex-grow text-sm text-bunker-300">
|
||||
<div className="mb-2">Version History</div>
|
||||
<div className="flex h-48 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-bunker-800 p-2 dark:[color-scheme:dark]">
|
||||
{secretVersion?.map(({ createdAt, value, id }, i) => (
|
||||
<div key={id} className="flex flex-col space-y-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div>
|
||||
<FontAwesomeIcon icon={i === 0 ? faCircleDot : faCircle} size="sm" />
|
||||
</div>
|
||||
<div>{format(new Date(createdAt), "Pp")}</div>
|
||||
</div>
|
||||
<div className="ml-1.5 flex items-center space-x-2 border-l border-bunker-300 pl-4">
|
||||
<div className="self-start rounded-sm bg-primary-500/30 px-1">Value:</div>
|
||||
<div className="break-all font-mono">{value}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="mb-2 border-b border-mineshaft-600 pb-4">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
isFullWidth
|
||||
type="submit"
|
||||
isDisabled={isSubmitting || !isDirty || !isAllowed}
|
||||
isLoading={isSubmitting}
|
||||
<Switch
|
||||
isDisabled={!isAllowed}
|
||||
id="personal-override"
|
||||
onCheckedChange={handleOverrideClick}
|
||||
isChecked={isOverridden}
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button colorSchema="danger" isDisabled={!isAllowed} onClick={onDeleteSecret}>
|
||||
Delete
|
||||
</Button>
|
||||
Override with a personal value
|
||||
</Switch>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
{isOverridden && (
|
||||
<Controller
|
||||
name="valueOverride"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<FormControl label="Value Override">
|
||||
<SecretInput
|
||||
isReadOnly={isReadOnly}
|
||||
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormControl label="Tags" className="">
|
||||
<div className="grid auto-cols-min grid-flow-col gap-2 overflow-hidden pt-2">
|
||||
{fields.map(({ tagColor, id: formId, name, id }) => (
|
||||
<Tag
|
||||
className="flex w-min items-center space-x-2"
|
||||
key={formId}
|
||||
onClose={() => {
|
||||
if (cannotEditSecret) {
|
||||
createNotification({ type: "error", text: "Access denied" });
|
||||
return;
|
||||
}
|
||||
const tag = tags?.find(({ id: tagId }) => id === tagId);
|
||||
if (tag) handleTagSelect(tag);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="h-3 w-3 rounded-full"
|
||||
style={{ backgroundColor: tagColor || "#bec2c8" }}
|
||||
/>
|
||||
<div className="text-sm">{name}</div>
|
||||
</Tag>
|
||||
))}
|
||||
<DropdownMenu>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuTrigger asChild>
|
||||
<IconButton
|
||||
ariaLabel="add"
|
||||
variant="outline_bg"
|
||||
size="xs"
|
||||
className="rounded-md"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlus} />
|
||||
</IconButton>
|
||||
</DropdownMenuTrigger>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<DropdownMenuContent align="end" className="z-[100]">
|
||||
<DropdownMenuLabel>Apply tags to this secrets</DropdownMenuLabel>
|
||||
{tags.map((tag) => {
|
||||
const { id: tagId, name, color } = tag;
|
||||
|
||||
const isSelected = selectedTagsGroupById?.[tagId];
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleTagSelect(tag)}
|
||||
key={tagId}
|
||||
icon={isSelected && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||
iconPos="right"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className="mr-2 h-2 w-2 rounded-full"
|
||||
style={{ background: color || "#bec2c8" }}
|
||||
/>
|
||||
{name}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={ProjectPermissionSub.Tags}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem asChild>
|
||||
<Button
|
||||
size="xs"
|
||||
className="w-full"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faTag} />}
|
||||
onClick={onCreateTag}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Create a tag
|
||||
</Button>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormControl label="Reminder">
|
||||
{secretReminderRepeatDays && secretReminderRepeatDays > 0 ? (
|
||||
<div className="mt-2 ml-1 flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<FontAwesomeIcon className="text-primary-500" icon={faClock} />
|
||||
<span className="text-sm text-bunker-300">
|
||||
Reminder every {secretReminderRepeatDays}{" "}
|
||||
{secretReminderRepeatDays > 1 ? "days" : "day"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
className="px-2 py-1"
|
||||
variant="outline_bg"
|
||||
onClick={() => setCreateReminderFormOpen.on()}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-2 ml-1 flex items-center space-x-2">
|
||||
<Button
|
||||
className="px-2 py-1"
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faClock} />}
|
||||
onClick={() => setCreateReminderFormOpen.on()}
|
||||
>
|
||||
Create Reminder
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</FormControl>
|
||||
<FormControl label="Comments & Notes">
|
||||
<TextArea
|
||||
className="border border-mineshaft-600 text-sm"
|
||||
{...register("comment")}
|
||||
readOnly={isReadOnly}
|
||||
rows={5}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="my-2 mb-6 border-b border-mineshaft-600 pb-4">
|
||||
<Controller
|
||||
control={control}
|
||||
name="skipMultilineEncoding"
|
||||
render={({ field: { value, onChange, onBlur } }) => (
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Switch
|
||||
id="skipmultiencoding-option"
|
||||
onCheckedChange={(isChecked) => onChange(!isChecked)}
|
||||
isChecked={!value}
|
||||
onBlur={onBlur}
|
||||
isDisabled={!isAllowed}
|
||||
className="items-center"
|
||||
>
|
||||
Enable multi line encoding
|
||||
<Tooltip
|
||||
content="Infisical encodes multiline secrets by escaping newlines and wrapping in quotes. To disable, enable this option"
|
||||
className="z-[100]"
|
||||
>
|
||||
<FontAwesomeIcon icon={faCircleQuestion} className="ml-1" size="sm" />
|
||||
</Tooltip>
|
||||
</Switch>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="dark mb-4 flex-grow text-sm text-bunker-300">
|
||||
<div className="mb-2">Version History</div>
|
||||
<div className="flex h-48 flex-col space-y-2 overflow-y-auto overflow-x-hidden rounded-md border border-mineshaft-600 bg-bunker-800 p-2 dark:[color-scheme:dark]">
|
||||
{secretVersion?.map(({ createdAt, value, id }, i) => (
|
||||
<div key={id} className="flex flex-col space-y-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div>
|
||||
<FontAwesomeIcon icon={i === 0 ? faCircleDot : faCircle} size="sm" />
|
||||
</div>
|
||||
<div>{format(new Date(createdAt), "Pp")}</div>
|
||||
</div>
|
||||
<div className="ml-1.5 flex items-center space-x-2 border-l border-bunker-300 pl-4">
|
||||
<div className="self-start rounded-sm bg-primary-500/30 px-1">Value:</div>
|
||||
<div className="break-all font-mono">{value}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
isFullWidth
|
||||
type="submit"
|
||||
isDisabled={isSubmitting || !isDirty || !isAllowed}
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button colorSchema="danger" isDisabled={!isAllowed} onClick={onDeleteSecret}>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</form>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -111,9 +111,11 @@ export const SecretItem = memo(
|
||||
resolver: zodResolver(formSchema)
|
||||
});
|
||||
|
||||
const secretReminderRepeatDays = watch("reminderRepeatDays");
|
||||
const secretReminderNote = watch("reminderNote");
|
||||
|
||||
const overrideAction = watch("overrideAction");
|
||||
const hasComment = Boolean(watch("comment"));
|
||||
const hasReminder = Boolean(watch("reminderRepeatDays"));
|
||||
|
||||
const selectedTags = watch("tags", []);
|
||||
const selectedTagsGroupById = selectedTags.reduce<Record<string, boolean>>(
|
||||
@ -191,6 +193,8 @@ export const SecretItem = memo(
|
||||
return (
|
||||
<>
|
||||
<CreateReminderForm
|
||||
repeatDays={secretReminderRepeatDays}
|
||||
note={secretReminderNote}
|
||||
isOpen={createReminderFormOpen}
|
||||
onOpenChange={(_, data) => {
|
||||
setCreateReminderFormOpen.toggle();
|
||||
@ -380,22 +384,24 @@ export const SecretItem = memo(
|
||||
<IconButton
|
||||
className={twMerge(
|
||||
"w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-6",
|
||||
hasReminder && "w-5 text-primary"
|
||||
Boolean(secretReminderRepeatDays) && "w-5 text-primary"
|
||||
)}
|
||||
variant="plain"
|
||||
size="md"
|
||||
ariaLabel="add-reminder"
|
||||
>
|
||||
<Tooltip content="Reminder">
|
||||
<Tooltip
|
||||
content={
|
||||
secretReminderRepeatDays && secretReminderRepeatDays > 0
|
||||
? `Every ${secretReminderRepeatDays} day${
|
||||
Number(secretReminderRepeatDays) > 1 ? "s" : ""
|
||||
}
|
||||
`
|
||||
: "Reminder"
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
onClick={() => {
|
||||
if (!hasReminder) {
|
||||
setCreateReminderFormOpen.on();
|
||||
} else {
|
||||
setValue("reminderRepeatDays", null, { shouldDirty: true });
|
||||
setValue("reminderNote", null, { shouldDirty: true });
|
||||
}
|
||||
}}
|
||||
onClick={() => setCreateReminderFormOpen.on()}
|
||||
icon={faClock}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
@ -170,7 +170,11 @@ export const AddAPIKeyModal = ({
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
<Button colorSchema="secondary" variant="plain">
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => handlePopUpToggle("addAPIKey", false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -139,7 +139,7 @@ export const WebhooksTab = withProjectPermission(
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-6 max-w-screen-lg 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="flex justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">{t("settings.webhooks.title")}</p>
|
||||
<ProjectPermissionCan
|
||||
|
@ -128,10 +128,10 @@ export const SignUpPage = () => {
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<div className="flex flex-col items-center space-y-4 text-center">
|
||||
<div className="flex flex-col items-center space-y-2 text-center">
|
||||
<img src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical logo" />
|
||||
<div className="text-4xl">Welcome to Infisical</div>
|
||||
<div>Create your first Admin Account</div>
|
||||
<div className="text-4xl pt-4">Welcome to Infisical</div>
|
||||
<div className="text-bunker-300 pb-4">Create your first Super Admin Account</div>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||
<div className="mt-8">
|
||||
@ -145,7 +145,7 @@ export const SignUpPage = () => {
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Input isFullWidth size="lg" {...field} />
|
||||
<Input isFullWidth size="md" {...field} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
@ -158,7 +158,7 @@ export const SignUpPage = () => {
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Input isFullWidth size="lg" {...field} />
|
||||
<Input isFullWidth size="md" {...field} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
@ -168,7 +168,7 @@ export const SignUpPage = () => {
|
||||
name="email"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Email" errorText={error?.message} isError={Boolean(error)}>
|
||||
<Input isFullWidth size="lg" {...field} />
|
||||
<Input isFullWidth size="md" {...field} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
@ -181,7 +181,7 @@ export const SignUpPage = () => {
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Input isFullWidth size="lg" type="password" {...field} />
|
||||
<Input isFullWidth size="md" type="password" {...field} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
@ -194,13 +194,13 @@ export const SignUpPage = () => {
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Input isFullWidth size="lg" type="password" {...field} />
|
||||
<Input isFullWidth size="md" type="password" {...field} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" isFullWidth className="mt-4" isLoading={isSubmitting}>
|
||||
Let's Go
|
||||
<Button type="submit" colorSchema="primary" variant="outline_bg" isFullWidth className="mt-4" isLoading={isSubmitting}>
|
||||
Continue
|
||||
</Button>
|
||||
</form>
|
||||
</motion.div>
|
||||
|
@ -15,8 +15,8 @@ export const DownloadBackupKeys = ({ onGenerate }: Props): JSX.Element => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center w-full h-full md:px-6 mx-auto mb-36 md:mb-16">
|
||||
<p className="text-xl text-center font-medium flex justify-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200">
|
||||
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-3 pt-1 text-2xl text-bunker-200" />
|
||||
<p className="text-xl text-center font-medium flex flex-col justify-center items-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200">
|
||||
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-3 pt-1 mb-6 text-6xl text-bunker-200" />
|
||||
{t("signup.step4-message")}
|
||||
</p>
|
||||
<div className="flex flex-col pb-2 bg-mineshaft-800 border border-mineshaft-600 items-center justify-center text-center lg:w-1/6 w-full md:min-w-[24rem] mt-8 max-w-md text-bunker-300 text-md rounded-md">
|
||||
|