mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-24 00:15:26 +00:00
Compare commits
221 Commits
daniel/org
...
fix-secret
Author | SHA1 | Date | |
---|---|---|---|
d702a61586 | |||
1c16f406a7 | |||
90f739caa6 | |||
ede8b6f286 | |||
232c547d75 | |||
fe08bbb691 | |||
2bd06ecde4 | |||
ffc7249c7c | |||
90bcf23097 | |||
5fa4d9029d | |||
7160cf58ee | |||
6b2d757e39 | |||
c075fcceca | |||
e25f5dd65f | |||
3eef023c30 | |||
e63deb0860 | |||
02b2851990 | |||
cb828200e1 | |||
77d068ae2c | |||
8702af671d | |||
31c0fd96ea | |||
2c539697df | |||
ae97b74933 | |||
3e6af2dae5 | |||
3c91e1127f | |||
0e31a9146a | |||
fa1b28b33f | |||
415cf31b2d | |||
9002e6cb33 | |||
1ede551c3e | |||
b7b43858f6 | |||
c91789e6d0 | |||
db0ba4be10 | |||
f73c807aa0 | |||
d1dacd81aa | |||
e8b635ce37 | |||
1d3e03e308 | |||
88e2eff7eb | |||
cd192ee228 | |||
1e657968f6 | |||
b8ba51512c | |||
1ac8ddbd92 | |||
a257743fa5 | |||
b5a7240375 | |||
5c2a108c52 | |||
b78d8d28ed | |||
9c9ade52db | |||
4d229ec745 | |||
605dad29ca | |||
bebdad8159 | |||
b547309ae4 | |||
d1ebdbcc03 | |||
c94caa6fb5 | |||
f53fa46c51 | |||
c42d407cda | |||
80b4bc18ec | |||
1dbf80d4e6 | |||
700a072ec5 | |||
8f42914df5 | |||
831da10073 | |||
6904cd3bda | |||
52fd09b87b | |||
0081bbdf9e | |||
c9e5f2bb75 | |||
73cc97cf17 | |||
0c1d37cc75 | |||
60fbd8ac44 | |||
36efa6ba63 | |||
961a73f712 | |||
6e2f3800d4 | |||
258c9e45d4 | |||
8573263379 | |||
9a724db6ab | |||
60a37e784b | |||
14c60bd075 | |||
de715c03ad | |||
ddb1d5a1ab | |||
41323f205d | |||
771498b817 | |||
22b2fb4c98 | |||
9bbba92768 | |||
46eea972f7 | |||
9eb2a74bdf | |||
b80579fdef | |||
214894c88b | |||
8ff37e3ec9 | |||
926f719967 | |||
c3a56f469a | |||
2bd9914373 | |||
354bac486a | |||
ba22a7fca6 | |||
4aef8ab8ee | |||
e89503f00f | |||
f5f20fbdca | |||
4d4887059a | |||
c11c5ec85e | |||
f0e3c9a4b2 | |||
eace4f1bdc | |||
0bd3f32c6e | |||
ad0504e957 | |||
1e20d780ec | |||
7e2685d604 | |||
92fd2d080d | |||
6d60413593 | |||
f59a75d790 | |||
835c36d161 | |||
e4dba6d5c8 | |||
b9986be387 | |||
5f5d62a285 | |||
667fa7a9e3 | |||
27dcb06083 | |||
9b1a15331a | |||
65776b7ab9 | |||
a9c1f278a1 | |||
900facdb36 | |||
fe638ce2c1 | |||
750a43c978 | |||
08b5975f26 | |||
885d1fbd7f | |||
bb2413d659 | |||
dac5529b6c | |||
bd92e35729 | |||
5b7562a76d | |||
edbf459d04 | |||
560274bde8 | |||
7df614a018 | |||
47287be5bf | |||
6e96f2338c | |||
7fd6b63b5d | |||
995777d76f | |||
2a6032a8cf | |||
ec4d1dd1b2 | |||
143de12d67 | |||
52cf937826 | |||
dbd7561037 | |||
d287c3e152 | |||
8fc081973d | |||
c42bbbea8b | |||
29b2b12ec7 | |||
4f80234afa | |||
a1fa0c652d | |||
8327f41b8e | |||
c2bfeb89e8 | |||
4a0668e92e | |||
716e705c2a | |||
f860fd3abe | |||
30e7fe8a45 | |||
307b89e799 | |||
dbf498b44a | |||
5eb3258311 | |||
bd3cbb3c7b | |||
96abbd9f80 | |||
92441e018f | |||
a9bba02f44 | |||
aaca3ac229 | |||
f0383dd55c | |||
a766329de5 | |||
c0b0c0754b | |||
34618041ca | |||
f36a056c62 | |||
e7b11eac2b | |||
0f14fab915 | |||
12a6fba645 | |||
ce057f44ac | |||
2032063c24 | |||
bbceb37d06 | |||
e917b744f4 | |||
7438c114dd | |||
8e3fc044ca | |||
9473de2212 | |||
744c510a51 | |||
f845749a4d | |||
56fc5a2a8c | |||
7edebbabaa | |||
0e698e9355 | |||
ee215bccfa | |||
00b99e7255 | |||
2b7784718d | |||
5f99e58674 | |||
f77942c702 | |||
2d3fddd0e9 | |||
519b92d592 | |||
c3d5e882f8 | |||
4c354eb3ea | |||
97eff2b480 | |||
c621592807 | |||
bd400a6196 | |||
a93c2d9236 | |||
11dfeda501 | |||
70bd64d54b | |||
0c88a5466c | |||
36266b30d5 | |||
288577b455 | |||
5194be14fd | |||
bab8f95fde | |||
b4f372f883 | |||
b13365ecf5 | |||
bb6e09a895 | |||
715b193a8e | |||
57be493da8 | |||
cc731fe031 | |||
70618420d7 | |||
7feb7ef9c6 | |||
ab1b9fb164 | |||
8c028889a6 | |||
7dc366baf0 | |||
2124d2669f | |||
af83fbea14 | |||
9657b64ab2 | |||
6b92a5f4db | |||
81e961e8bc | |||
6a7a6ce942 | |||
1695412278 | |||
b4fa07334d | |||
29c244c635 | |||
b80a5989a8 | |||
dc696f8932 | |||
c8f0796952 | |||
9282dd08d9 | |||
df459d456a | |||
c8cfb43316 |
.github/workflows
Dockerfile.standalone-infisicalREADME.mdbackend
src
ee
routes/v1
services
license
secret-rotation/secret-rotation-queue
lib
api-docs
config
crypto
knex
server
plugins
routes
v1
identity-access-token-router.tsidentity-router.tsidentity-ua.tsintegration-auth-router.tsintegration-router.tsproject-env-router.tsproject-membership-router.tsproject-router.tssecret-folder-router.tssecret-import-router.ts
v2
identity-org-router.tsidentity-project-router.tsorganization-router.tsproject-membership-router.tsproject-router.ts
v3
services
cli/packages
docs
api-reference/endpoints/folders
documentation/getting-started
images
agent
integrations/github
integrations/cicd
mint.jsonsdks/languages
self-hosting
frontend
.eslintrc.jsi18n.ts
.storybook
src
components
analytics
basic
Error.tsxInputField.tsxListbox.tsx
buttons
dialog
AddProjectMemberDialog.tsxAddUpdateEnvironmentDialog.tsxAddUserDialog.tsxAddWorkspaceDialog.tsxDeleteActionModal.tsxDeleteEnvVar.tsxDeleteUserDialog.tsx
popups
table
context/Notifications
dashboard
navigation
signup
utilities
SecurityClient.tsattemptChangePassword.tsattemptCliLoginMfa.tsattemptLoginMfa.ts
checks/password
cryptography
intercom
isValidHexColor.tssaveTokenToLocalStorage.tstelemetry
v2
Accordion
Card
Checkbox
DeleteActionModal
Drawer
Dropdown
EmailServiceSetupModal
FormControl
HoverCard
HoverCardv2
Menu
Modal
Pagination
Popover
Popoverv2
RadioGroup
Select
Spinner
Stepper
Table
Tabs
context
ee
helpers
hoc/withPermission
hooks/api
apiKeys
auditLogs
auth
bots
identities
index.tsxintegrationAuth
integrations
ldapConfig
organization
scim
serverDetails
serviceTokens
ssoConfig
trustedIps
workspace
layouts
lib/fn
pages
404.tsx
admin
api
cli-redirect.tsxemail-not-verified.tsxindex.tsxintegrations
cloudflare-pages
cloudflare-workers
github
heroku
railway
render
login
org
password-reset.tsxpersonal-settings.tsxproject/[id]
requestnewinvite.tsxsecret-scanning.tsxsignup/sso
verify-email.tsxservices
views
IntegrationsPage
Login
Org
MembersPage/components
OrgIdentityTab
OrgMembersTab
OrgRoleTabSection/OrgRoleModifySection
index.tsxNonePage
Project
AuditLogsPage
IPAllowListPage
MembersPage/components
IdentityTab
MemberListTab
ProjectRoleListTab/components/ProjectRoleModifySection
ServiceTokenTab
index.tsxSecretApprovalPage/components/SecretApprovalRequest/components
SecretOverviewPage/components
CreateSecretForm
ProjectIndexSecretsSection
SecretOverviewTableRow
SecretRotationPage/components/CreateRotationForm
SecretScanning/components
Settings
BillingSettingsPage
BillingSettingsPage.tsx
components
index.tsxOrgSettingsPage
OrgSettingsPage.tsx
components
OrgAuthTab
LDAPModal.tsxOrgGeneralAuthSection.tsxOrgLDAPSection.tsxOrgSCIMSection.tsxOrgSSOSection.tsxScimTokenModal.tsxindex.tsx
OrgDeleteSection
OrgGeneralTab
OrgIncidentContactsSection
OrgNameChangeSection
OrgTabGroup
index.tsxPersonalSettingsPage
APIKeySection
APIKeyV2Section
AuthMethodSection
ChangeLanguageSection
ChangePasswordSection
DeleteAccountSection
EmergencyKitSection
PersonalAPIKeyTab
PersonalAuthTab
PersonalGeneralTab
PersonalSettingsPage.tsxPersonalTabGroup
SecuritySection
SessionsSection
UserNameSection
index.tsxProjectSettingsPage
ProjectSettingsPage.tsx
components
Signup
admin/SignUpPage/components/DownloadBackupKeys
helm-charts/secrets-operator
k8-operator
28
.github/workflows/build-staging-and-deploy.yml
vendored
28
.github/workflows/build-staging-and-deploy.yml
vendored
@ -8,6 +8,15 @@ jobs:
|
||||
steps:
|
||||
- name: ☁️ Checkout source
|
||||
uses: actions/checkout@v3
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_FOR_ECR }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_FOR_ECR }}
|
||||
aws-region: us-east-1
|
||||
- name: Login to Amazon ECR
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@v1
|
||||
- name: 📦 Install dependencies to test all dependencies
|
||||
run: npm ci --only-production
|
||||
working-directory: backend
|
||||
@ -35,17 +44,11 @@ jobs:
|
||||
context: .
|
||||
file: Dockerfile.standalone-infisical
|
||||
tags: infisical/infisical:test
|
||||
# - name: ⏻ Spawn backend container and dependencies
|
||||
# run: |
|
||||
# docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull
|
||||
# - name: 🧪 Test backend image
|
||||
# run: |
|
||||
# ./.github/resources/healthcheck.sh infisical-backend-test
|
||||
# - name: ⏻ Shut down backend container and dependencies
|
||||
# run: |
|
||||
# docker compose -f .github/resources/docker-compose.be-test.yml down
|
||||
- name: 🏗️ Build backend and push
|
||||
- name: 🏗️ Build backend and push to docker hub
|
||||
uses: depot/build-push-action@v1
|
||||
env:
|
||||
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
|
||||
ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPO_NAME }}
|
||||
with:
|
||||
project: 64mmf0n610
|
||||
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
|
||||
@ -55,10 +58,15 @@ jobs:
|
||||
tags: |
|
||||
infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
||||
infisical/staging_infisical:latest
|
||||
${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:latest
|
||||
${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ steps.commit.outputs.short }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: |
|
||||
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
||||
INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
|
||||
|
||||
|
||||
|
||||
postgres-migration:
|
||||
name: Run latest migration files
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -118,9 +118,6 @@ WORKDIR /backend
|
||||
|
||||
ENV TELEMETRY_ENABLED true
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
|
||||
CMD node healthcheck.js
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 443
|
||||
|
||||
|
@ -10,7 +10,8 @@
|
||||
<a href="https://infisical.com/">Infisical Cloud</a> |
|
||||
<a href="https://infisical.com/docs/self-hosting/overview">Self-Hosting</a> |
|
||||
<a href="https://infisical.com/docs/documentation/getting-started/introduction">Docs</a> |
|
||||
<a href="https://www.infisical.com">Website</a>
|
||||
<a href="https://www.infisical.com">Website</a> |
|
||||
<a href="https://infisical.com/careers">Hiring (Remote/SF)</a>
|
||||
</h4>
|
||||
|
||||
<p align="center">
|
||||
|
@ -19,7 +19,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
.min(1)
|
||||
.trim()
|
||||
.refine(
|
||||
(val) => Object.keys(OrgMembershipRole).includes(val),
|
||||
(val) => !Object.keys(OrgMembershipRole).includes(val),
|
||||
"Please choose a different slug, the slug you have entered is reserved"
|
||||
)
|
||||
.refine((v) => slugify(v) === v, {
|
||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
|
||||
import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas";
|
||||
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { AUDIT_LOGS, PROJECTS } from "@app/lib/api-docs";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
@ -19,13 +20,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
workspaceId: z.string().trim().describe(PROJECTS.GET_SNAPSHOTS.workspaceId)
|
||||
}),
|
||||
querystring: z.object({
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
offset: z.coerce.number().default(0),
|
||||
limit: z.coerce.number().default(20)
|
||||
environment: z.string().trim().describe(PROJECTS.GET_SNAPSHOTS.environment),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(PROJECTS.GET_SNAPSHOTS.path),
|
||||
offset: z.coerce.number().default(0).describe(PROJECTS.GET_SNAPSHOTS.offset),
|
||||
limit: z.coerce.number().default(20).describe(PROJECTS.GET_SNAPSHOTS.limit)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -91,16 +92,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
workspaceId: z.string().trim().describe(AUDIT_LOGS.EXPORT.workspaceId)
|
||||
}),
|
||||
querystring: z.object({
|
||||
eventType: z.nativeEnum(EventType).optional(),
|
||||
userAgentType: z.nativeEnum(UserAgentType).optional(),
|
||||
startDate: z.string().datetime().optional(),
|
||||
endDate: z.string().datetime().optional(),
|
||||
offset: z.coerce.number().default(0),
|
||||
limit: z.coerce.number().default(20),
|
||||
actor: z.string().optional()
|
||||
eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType),
|
||||
userAgentType: z.nativeEnum(UserAgentType).optional().describe(AUDIT_LOGS.EXPORT.userAgentType),
|
||||
startDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.startDate),
|
||||
endDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.endDate),
|
||||
offset: z.coerce.number().default(0).describe(AUDIT_LOGS.EXPORT.offset),
|
||||
limit: z.coerce.number().default(20).describe(AUDIT_LOGS.EXPORT.limit),
|
||||
actor: z.string().optional().describe(AUDIT_LOGS.EXPORT.actor)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretSnapshotsSchema, SecretTagsSchema, SecretVersionsSchema } from "@app/db/schemas";
|
||||
import { PROJECTS } from "@app/lib/api-docs";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
@ -66,7 +67,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretSnapshotId: z.string().trim()
|
||||
secretSnapshotId: z.string().trim().describe(PROJECTS.ROLLBACK_TO_SNAPSHOT.secretSnapshotId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -8,6 +8,7 @@ import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { verifyOfflineLicense } from "@app/lib/crypto";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
@ -26,6 +27,7 @@ import {
|
||||
TFeatureSet,
|
||||
TGetOrgBillInfoDTO,
|
||||
TGetOrgTaxIdDTO,
|
||||
TOfflineLicenseContents,
|
||||
TOrgInvoiceDTO,
|
||||
TOrgLicensesDTO,
|
||||
TOrgPlanDTO,
|
||||
@ -96,6 +98,36 @@ export const licenseServiceFactory = ({
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (appCfg.LICENSE_KEY_OFFLINE) {
|
||||
let isValidOfflineLicense = true;
|
||||
const contents: TOfflineLicenseContents = JSON.parse(
|
||||
Buffer.from(appCfg.LICENSE_KEY_OFFLINE, "base64").toString("utf8")
|
||||
);
|
||||
const isVerified = await verifyOfflineLicense(JSON.stringify(contents.license), contents.signature);
|
||||
|
||||
if (!isVerified) {
|
||||
isValidOfflineLicense = false;
|
||||
logger.warn(`Infisical EE offline license verification failed`);
|
||||
}
|
||||
|
||||
if (contents.license.terminatesAt) {
|
||||
const terminationDate = new Date(contents.license.terminatesAt);
|
||||
if (terminationDate < new Date()) {
|
||||
isValidOfflineLicense = false;
|
||||
logger.warn(`Infisical EE offline license has expired`);
|
||||
}
|
||||
}
|
||||
|
||||
if (isValidOfflineLicense) {
|
||||
onPremFeatures = contents.license.features;
|
||||
instanceType = InstanceType.EnterpriseOnPrem;
|
||||
logger.info(`Instance type: ${InstanceType.EnterpriseOnPrem}`);
|
||||
isValidLicense = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// this means this is self hosted oss version
|
||||
// else it would reach catch statement
|
||||
isValidLicense = true;
|
||||
|
@ -6,6 +6,21 @@ export enum InstanceType {
|
||||
Cloud = "cloud"
|
||||
}
|
||||
|
||||
export type TOfflineLicenseContents = {
|
||||
license: TOfflineLicense;
|
||||
signature: string;
|
||||
};
|
||||
|
||||
export type TOfflineLicense = {
|
||||
issuedTo: string;
|
||||
licenseId: string;
|
||||
customerId: string | null;
|
||||
issuedAt: string;
|
||||
expiresAt: string | null;
|
||||
terminatesAt: string | null;
|
||||
features: TFeatureSet;
|
||||
};
|
||||
|
||||
export type TFeatureSet = {
|
||||
_id: null;
|
||||
slug: null;
|
||||
|
@ -9,6 +9,7 @@ import jmespath from "jmespath";
|
||||
import knex from "knex";
|
||||
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { getDbConnectionHost } from "@app/lib/knex";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
||||
import { TAssignOp, TDbProviderClients, TDirectAssignOp, THttpProviderFunction } from "../templates/types";
|
||||
@ -89,7 +90,7 @@ export const secretRotationDbFn = async ({
|
||||
const appCfg = getConfig();
|
||||
|
||||
const ssl = ca ? { rejectUnauthorized: false, ca } : undefined;
|
||||
if (host === "localhost" || host === "127.0.0.1" || appCfg.DB_CONNECTION_URI.includes(host))
|
||||
if (host === "localhost" || host === "127.0.0.1" || getDbConnectionHost(appCfg.DB_CONNECTION_URI) === host)
|
||||
throw new Error("Invalid db host");
|
||||
|
||||
const db = knex({
|
||||
|
287
backend/src/lib/api-docs/constants.ts
Normal file
287
backend/src/lib/api-docs/constants.ts
Normal file
@ -0,0 +1,287 @@
|
||||
export const IDENTITIES = {
|
||||
CREATE: {
|
||||
name: "The name of the identity to create.",
|
||||
organizationId: "The organization ID to which the identity belongs.",
|
||||
role: "The role of the identity. Possible values are 'no-access', 'member', and 'admin'."
|
||||
},
|
||||
UPDATE: {
|
||||
identityId: "The ID of the identity to update.",
|
||||
name: "The new name of the identity.",
|
||||
role: "The new role of the identity."
|
||||
},
|
||||
DELETE: {
|
||||
identityId: "The ID of the identity to delete."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const UNIVERSAL_AUTH = {
|
||||
LOGIN: {
|
||||
clientId: "Your Machine Identity Client ID.",
|
||||
clientSecret: "Your Machine Identity Client Secret."
|
||||
},
|
||||
ATTACH: {
|
||||
identityId: "The ID of the identity to attach the configuration onto.",
|
||||
clientSecretTrustedIps:
|
||||
"A list of IPs or CIDR ranges that the Client Secret can be used from together with the Client ID to get back an access token. You can use 0.0.0.0/0, to allow usage from any network address.",
|
||||
accessTokenTrustedIps:
|
||||
"A list of IPs or CIDR ranges that access tokens can be used from. You can use 0.0.0.0/0, to allow usage from any network address.",
|
||||
accessTokenTTL: "The lifetime for an access token in seconds. This value will be referenced at renewal time.",
|
||||
accessTokenMaxTTL:
|
||||
"The maximum lifetime for an access token in seconds. This value will be referenced at renewal time.",
|
||||
accessTokenNumUsesLimit:
|
||||
"The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses."
|
||||
},
|
||||
RETRIEVE: {
|
||||
identityId: "The ID of the identity to retrieve."
|
||||
},
|
||||
UPDATE: {
|
||||
identityId: "The ID of the identity to update.",
|
||||
clientSecretTrustedIps: "The new list of IPs or CIDR ranges that the Client Secret can be used from.",
|
||||
accessTokenTrustedIps: "The new list of IPs or CIDR ranges that access tokens can be used from.",
|
||||
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
||||
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
||||
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
|
||||
},
|
||||
CREATE_CLIENT_SECRET: {
|
||||
identityId: "The ID of the identity to create a client secret for.",
|
||||
description: "The description of the client secret.",
|
||||
numUsesLimit:
|
||||
"The maximum number of times that the client secret can be used; a value of 0 implies infinite number of uses.",
|
||||
ttl: "The lifetime for the client secret in seconds."
|
||||
},
|
||||
LIST_CLIENT_SECRETS: {
|
||||
identityId: "The ID of the identity to list client secrets for."
|
||||
},
|
||||
REVOKE_CLIENT_SECRET: {
|
||||
identityId: "The ID of the identity to revoke the client secret from.",
|
||||
clientSecretId: "The ID of the client secret to revoke."
|
||||
},
|
||||
RENEW_ACCESS_TOKEN: {
|
||||
accessToken: "The access token to renew."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const ORGANIZATIONS = {
|
||||
LIST_USER_MEMBERSHIPS: {
|
||||
organizationId: "The ID of the organization to get memberships from."
|
||||
},
|
||||
UPDATE_USER_MEMBERSHIP: {
|
||||
organizationId: "The ID of the organization to update the membership for.",
|
||||
membershipId: "The ID of the membership to update.",
|
||||
role: "The new role of the membership."
|
||||
},
|
||||
DELETE_USER_MEMBERSHIP: {
|
||||
organizationId: "The ID of the organization to delete the membership from.",
|
||||
membershipId: "The ID of the membership to delete."
|
||||
},
|
||||
LIST_IDENTITY_MEMBERSHIPS: {
|
||||
orgId: "The ID of the organization to get identity memberships from."
|
||||
},
|
||||
GET_PROJECTS: {
|
||||
organizationId: "The ID of the organization to get projects from."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const PROJECTS = {
|
||||
CREATE: {
|
||||
organizationSlug: "The slug of the organization to create the project in.",
|
||||
projectName: "The name of the project to create.",
|
||||
slug: "An optional slug for the project."
|
||||
},
|
||||
DELETE: {
|
||||
workspaceId: "The ID of the project to delete."
|
||||
},
|
||||
GET: {
|
||||
workspaceId: "The ID of the project."
|
||||
},
|
||||
UPDATE: {
|
||||
workspaceId: "The ID of the project to update.",
|
||||
name: "The new name of the project.",
|
||||
autoCapitalization: "Disable or enable auto-capitalization for the project."
|
||||
},
|
||||
INVITE_MEMBER: {
|
||||
projectId: "The ID of the project to invite the member to.",
|
||||
emails: "A list of organization member emails to invite to the project.",
|
||||
usernames: "A list of usernames to invite to the project."
|
||||
},
|
||||
REMOVE_MEMBER: {
|
||||
projectId: "The ID of the project to remove the member from.",
|
||||
emails: "A list of organization member emails to remove from the project.",
|
||||
usernames: "A list of usernames to remove from the project."
|
||||
},
|
||||
GET_USER_MEMBERSHIPS: {
|
||||
workspaceId: "The ID of the project to get memberships from."
|
||||
},
|
||||
UPDATE_USER_MEMBERSHIP: {
|
||||
workspaceId: "The ID of the project to update the membership for.",
|
||||
membershipId: "The ID of the membership to update.",
|
||||
roles: "A list of roles to update the membership to."
|
||||
},
|
||||
LIST_IDENTITY_MEMBERSHIPS: {
|
||||
projectId: "The ID of the project to get identity memberships from."
|
||||
},
|
||||
UPDATE_IDENTITY_MEMBERSHIP: {
|
||||
projectId: "The ID of the project to update the identity membership for.",
|
||||
identityId: "The ID of the identity to update the membership for.",
|
||||
roles: "A list of roles to update the membership to."
|
||||
},
|
||||
DELETE_IDENTITY_MEMBERSHIP: {
|
||||
projectId: "The ID of the project to delete the identity membership from.",
|
||||
identityId: "The ID of the identity to delete the membership from."
|
||||
},
|
||||
GET_KEY: {
|
||||
workspaceId: "The ID of the project to get the key from."
|
||||
},
|
||||
GET_SNAPSHOTS: {
|
||||
workspaceId: "The ID of the project to get snapshots from.",
|
||||
environment: "The environment to get snapshots from.",
|
||||
path: "The secret path to get snapshots from.",
|
||||
offset: "The offset to start from. If you enter 10, it will start from the 10th snapshot.",
|
||||
limit: "The number of snapshots to return."
|
||||
},
|
||||
ROLLBACK_TO_SNAPSHOT: {
|
||||
secretSnapshotId: "The ID of the snapshot to rollback to."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const ENVIRONMENTS = {
|
||||
CREATE: {
|
||||
workspaceId: "The ID of the project to create the environment in.",
|
||||
name: "The name of the environment to create.",
|
||||
slug: "The slug of the environment to create."
|
||||
},
|
||||
UPDATE: {
|
||||
workspaceId: "The ID of the project to update the environment in.",
|
||||
id: "The ID of the environment to update.",
|
||||
name: "The new name of the environment.",
|
||||
slug: "The new slug of the environment.",
|
||||
position: "The new position of the environment. The lowest number will be displayed as the first environment."
|
||||
},
|
||||
DELETE: {
|
||||
workspaceId: "The ID of the project to delete the environment from.",
|
||||
id: "The ID of the environment to delete."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const FOLDERS = {
|
||||
LIST: {
|
||||
workspaceId: "The ID of the project to list folders from.",
|
||||
environment: "The slug of the environment to list folders from.",
|
||||
path: "The path to list folders from.",
|
||||
directory: "The directory to list folders from. (Deprecated in favor of path)"
|
||||
},
|
||||
CREATE: {
|
||||
workspaceId: "The ID of the project to create the folder in.",
|
||||
environment: "The slug of the environment to create the folder in.",
|
||||
name: "The name of the folder to create.",
|
||||
path: "The path of the folder to create.",
|
||||
directory: "The directory of the folder to create. (Deprecated in favor of path)"
|
||||
},
|
||||
UPDATE: {
|
||||
folderId: "The ID of the folder to update.",
|
||||
environment: "The slug of the environment where the folder is located.",
|
||||
name: "The new name of the folder.",
|
||||
path: "The path of the folder to update.",
|
||||
directory: "The new directory of the folder to update. (Deprecated in favor of path)",
|
||||
workspaceId: "The ID of the project where the folder is located."
|
||||
},
|
||||
DELETE: {
|
||||
folderIdOrName: "The ID or name of the folder to delete.",
|
||||
workspaceId: "The ID of the project to delete the folder from.",
|
||||
environment: "The slug of the environment where the folder is located.",
|
||||
directory: "The directory of the folder to delete. (Deprecated in favor of path)",
|
||||
path: "The path of the folder to delete."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const RAW_SECRETS = {
|
||||
LIST: {
|
||||
workspaceId: "The ID of the project to list secrets from.",
|
||||
workspaceSlug: "The slug of the project to list secrets from. This parameter is only usable by machine identities.",
|
||||
environment: "The slug of the environment to list secrets from.",
|
||||
secretPath: "The secret path to list secrets from.",
|
||||
includeImports: "Weather to include imported secrets or not."
|
||||
},
|
||||
CREATE: {
|
||||
secretName: "The name of the secret to create.",
|
||||
environment: "The slug of the environment to create the secret in.",
|
||||
secretComment: "Attach a comment to the secret.",
|
||||
secretPath: "The path to create the secret in.",
|
||||
secretValue: "The value of the secret to create.",
|
||||
skipMultilineEncoding: "Skip multiline encoding for the secret value.",
|
||||
type: "The type of the secret to create.",
|
||||
workspaceId: "The ID of the project to create the secret in."
|
||||
},
|
||||
GET: {
|
||||
secretName: "The name of the secret to get.",
|
||||
workspaceId: "The ID of the project to get the secret from.",
|
||||
environment: "The slug of the environment to get the secret from.",
|
||||
secretPath: "The path of the secret to get.",
|
||||
version: "The version of the secret to get.",
|
||||
type: "The type of the secret to get.",
|
||||
includeImports: "Weather to include imported secrets or not."
|
||||
},
|
||||
UPDATE: {
|
||||
secretName: "The name of the secret to update.",
|
||||
environment: "The slug of the environment where the secret is located.",
|
||||
secretPath: "The path of the secret to update",
|
||||
secretValue: "The new value of the secret.",
|
||||
skipMultilineEncoding: "Skip multiline encoding for the secret value.",
|
||||
type: "The type of the secret to update.",
|
||||
workspaceId: "The ID of the project to update the secret in."
|
||||
},
|
||||
DELETE: {
|
||||
secretName: "The name of the secret to delete.",
|
||||
environment: "The slug of the environment where the secret is located.",
|
||||
secretPath: "The path of the secret.",
|
||||
type: "The type of the secret to delete.",
|
||||
workspaceId: "The ID of the project where the secret is located."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const SECRET_IMPORTS = {
|
||||
LIST: {
|
||||
workspaceId: "The ID of the project to list secret imports from.",
|
||||
environment: "The slug of the environment to list secret imports from.",
|
||||
path: "The path to list secret imports from."
|
||||
},
|
||||
CREATE: {
|
||||
environment: "The slug of the environment to import into.",
|
||||
path: "The path to import into.",
|
||||
workspaceId: "The ID of the project you are working in.",
|
||||
import: {
|
||||
environment: "The slug of the environment to import from.",
|
||||
path: "The path to import from."
|
||||
}
|
||||
},
|
||||
UPDATE: {
|
||||
secretImportId: "The ID of the secret import to update.",
|
||||
environment: "The slug of the environment where the secret import is located.",
|
||||
import: {
|
||||
environment: "The new environment slug to import from.",
|
||||
path: "The new path to import from.",
|
||||
position: "The new position of the secret import. The lowest number will be displayed as the first import."
|
||||
},
|
||||
path: "The path of the secret import to update.",
|
||||
workspaceId: "The ID of the project where the secret import is located."
|
||||
},
|
||||
DELETE: {
|
||||
workspaceId: "The ID of the project to delete the secret import from.",
|
||||
secretImportId: "The ID of the secret import to delete.",
|
||||
environment: "The slug of the environment where the secret import is located.",
|
||||
path: "The path of the secret import to delete."
|
||||
}
|
||||
} as const;
|
||||
|
||||
export const AUDIT_LOGS = {
|
||||
EXPORT: {
|
||||
workspaceId: "The ID of the project to export audit logs from.",
|
||||
eventType: "The type of the event to export.",
|
||||
userAgentType: "Choose which consuming application to export audit logs for.",
|
||||
startDate: "The date to start the export from.",
|
||||
endDate: "The date to end the export at.",
|
||||
offset: "The offset to start from. If you enter 10, it will start from the 10th audit log.",
|
||||
limit: "The number of audit logs to return.",
|
||||
actor: "The actor to filter the audit logs by."
|
||||
}
|
||||
} as const;
|
1
backend/src/lib/api-docs/index.ts
Normal file
1
backend/src/lib/api-docs/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./constants";
|
@ -106,6 +106,7 @@ const envSchema = z
|
||||
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()),
|
||||
LICENSE_KEY_OFFLINE: zpStr(z.string().optional()),
|
||||
|
||||
// GENERIC
|
||||
STANDALONE_MODE: z
|
||||
|
@ -17,4 +17,5 @@ export {
|
||||
decryptSecrets,
|
||||
decryptSecretVersions
|
||||
} from "./secret-encryption";
|
||||
export { verifyOfflineLicense } from "./signing";
|
||||
export { generateSrpServerKey, srpCheckClientProof } from "./srp";
|
||||
|
8
backend/src/lib/crypto/license_public_key.pem
Normal file
8
backend/src/lib/crypto/license_public_key.pem
Normal file
@ -0,0 +1,8 @@
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEApchBY3BXTu4zWGBguB7nM/pjpVLY3V7VGZOAxmR5ueQTJOwiGM13
|
||||
5HN3EM9fDlQnZu9VSc0OFqRM/bUeUaI1oLPE6WzTHjdHyKjDI/S+TLx3VGEsvhM1
|
||||
uukZpYX+3KX2w4wzRHBaBWyglFy0CVNth9UJhhpD+KKfv7dzcRmsbyoUWi9wGfJu
|
||||
wLYCwaCwZRXIt1sLGmMncPz14vfwdnm2a5Tj1Jbt0GTyBl+1/ZqLbO6SsslLg2G+
|
||||
o7FfGS9z8OUTkvDdu16qxL+p2wCEFZMnOz5BB4oakuT2gS9iOO2l5AOPcT4WzPzy
|
||||
PYbX3d7cN9BkOY9I5z0cX4wzqHjQTvGNLQIDAQAB
|
||||
-----END RSA PUBLIC KEY-----
|
22
backend/src/lib/crypto/signing.ts
Normal file
22
backend/src/lib/crypto/signing.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import crypto, { KeyObject } from "crypto";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
export const verifySignature = (data: string, signature: Buffer, publicKey: KeyObject) => {
|
||||
const verify = crypto.createVerify("SHA256");
|
||||
verify.update(data);
|
||||
verify.end();
|
||||
return verify.verify(publicKey, signature);
|
||||
};
|
||||
|
||||
export const verifyOfflineLicense = async (licenseContents: string, signature: string) => {
|
||||
const publicKeyPem = await fs.readFile(path.join(__dirname, "license_public_key.pem"), "utf8");
|
||||
|
||||
const publicKey = crypto.createPublicKey({
|
||||
key: publicKeyPem,
|
||||
format: "pem",
|
||||
type: "pkcs1"
|
||||
});
|
||||
|
||||
return verifySignature(licenseContents, Buffer.from(signature, "base64"), publicKey);
|
||||
};
|
11
backend/src/lib/knex/connection.ts
Normal file
11
backend/src/lib/knex/connection.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { URL } from "url"; // Import the URL class
|
||||
|
||||
export const getDbConnectionHost = (urlString: string) => {
|
||||
try {
|
||||
const url = new URL(urlString);
|
||||
// Split hostname and port (if provided)
|
||||
return url.hostname.split(":")[0];
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
@ -4,6 +4,7 @@ import { Tables } from "knex/types/tables";
|
||||
|
||||
import { DatabaseError } from "../errors";
|
||||
|
||||
export * from "./connection";
|
||||
export * from "./join";
|
||||
export * from "./select";
|
||||
|
||||
|
@ -14,13 +14,13 @@ export const fastifySwagger = fp(async (fastify) => {
|
||||
version: "0.0.1"
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: "http://localhost:8080",
|
||||
description: "Local server"
|
||||
},
|
||||
{
|
||||
url: "https://app.infisical.com",
|
||||
description: "Production server"
|
||||
},
|
||||
{
|
||||
url: "http://localhost:8080",
|
||||
description: "Local server"
|
||||
}
|
||||
],
|
||||
components: {
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { UNIVERSAL_AUTH } from "@app/lib/api-docs";
|
||||
|
||||
export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/token/renew",
|
||||
@ -7,7 +9,7 @@ export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvid
|
||||
schema: {
|
||||
description: "Renew access token",
|
||||
body: z.object({
|
||||
accessToken: z.string().trim()
|
||||
accessToken: z.string().trim().describe(UNIVERSAL_AUTH.RENEW_ACCESS_TOKEN.accessToken)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
|
||||
import { IdentitiesSchema, OrgMembershipRole } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { IDENTITIES } from "@app/lib/api-docs";
|
||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
@ -20,9 +21,9 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
name: z.string().trim(),
|
||||
organizationId: z.string().trim(),
|
||||
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess)
|
||||
name: z.string().trim().describe(IDENTITIES.CREATE.name),
|
||||
organizationId: z.string().trim().describe(IDENTITIES.CREATE.organizationId),
|
||||
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess).describe(IDENTITIES.CREATE.role)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -79,11 +80,11 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string()
|
||||
identityId: z.string().describe(IDENTITIES.UPDATE.identityId)
|
||||
}),
|
||||
body: z.object({
|
||||
name: z.string().trim().optional(),
|
||||
role: z.string().trim().min(1).optional()
|
||||
name: z.string().trim().optional().describe(IDENTITIES.UPDATE.name),
|
||||
role: z.string().trim().min(1).optional().describe(IDENTITIES.UPDATE.role)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -129,7 +130,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string()
|
||||
identityId: z.string().describe(IDENTITIES.DELETE.identityId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
|
||||
import { IdentityUaClientSecretsSchema, IdentityUniversalAuthsSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { UNIVERSAL_AUTH } from "@app/lib/api-docs";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||
@ -26,8 +27,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
schema: {
|
||||
description: "Login with Universal Auth",
|
||||
body: z.object({
|
||||
clientId: z.string().trim(),
|
||||
clientSecret: z.string().trim()
|
||||
clientId: z.string().trim().describe(UNIVERSAL_AUTH.LOGIN.clientId),
|
||||
clientSecret: z.string().trim().describe(UNIVERSAL_AUTH.LOGIN.clientSecret)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -76,7 +77,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string().trim()
|
||||
identityId: z.string().trim().describe(UNIVERSAL_AUTH.ATTACH.identityId)
|
||||
}),
|
||||
body: z.object({
|
||||
clientSecretTrustedIps: z
|
||||
@ -85,14 +86,16 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
|
||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||
.describe(UNIVERSAL_AUTH.ATTACH.clientSecretTrustedIps),
|
||||
accessTokenTrustedIps: z
|
||||
.object({
|
||||
ipAddress: z.string().trim()
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
|
||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenTrustedIps),
|
||||
accessTokenTTL: z
|
||||
.number()
|
||||
.int()
|
||||
@ -100,15 +103,22 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenTTL must have a non zero number"
|
||||
})
|
||||
.default(2592000),
|
||||
.default(2592000)
|
||||
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenTTL), // 30 days
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
.default(2592000), // 30 days
|
||||
accessTokenNumUsesLimit: z.number().int().min(0).default(0)
|
||||
.default(2592000)
|
||||
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenMaxTTL), // 30 days
|
||||
accessTokenNumUsesLimit: z
|
||||
.number()
|
||||
.int()
|
||||
.min(0)
|
||||
.default(0)
|
||||
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -157,7 +167,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string()
|
||||
identityId: z.string().describe(UNIVERSAL_AUTH.UPDATE.identityId)
|
||||
}),
|
||||
body: z.object({
|
||||
clientSecretTrustedIps: z
|
||||
@ -166,16 +176,23 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
.optional(),
|
||||
.optional()
|
||||
.describe(UNIVERSAL_AUTH.UPDATE.clientSecretTrustedIps),
|
||||
accessTokenTrustedIps: z
|
||||
.object({
|
||||
ipAddress: z.string().trim()
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
.optional(),
|
||||
accessTokenTTL: z.number().int().min(0).optional(),
|
||||
accessTokenNumUsesLimit: z.number().int().min(0).optional(),
|
||||
.optional()
|
||||
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenTrustedIps),
|
||||
accessTokenTTL: z.number().int().min(0).optional().describe(UNIVERSAL_AUTH.UPDATE.accessTokenTTL),
|
||||
accessTokenNumUsesLimit: z
|
||||
.number()
|
||||
.int()
|
||||
.min(0)
|
||||
.optional()
|
||||
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenNumUsesLimit),
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
@ -183,6 +200,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
.optional()
|
||||
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenMaxTTL)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -232,7 +250,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string()
|
||||
identityId: z.string().describe(UNIVERSAL_AUTH.RETRIEVE.identityId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -276,12 +294,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string()
|
||||
identityId: z.string().describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.identityId)
|
||||
}),
|
||||
body: z.object({
|
||||
description: z.string().trim().default(""),
|
||||
numUsesLimit: z.number().min(0).default(0),
|
||||
ttl: z.number().min(0).default(0)
|
||||
description: z.string().trim().default("").describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.description),
|
||||
numUsesLimit: z.number().min(0).default(0).describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.numUsesLimit),
|
||||
ttl: z.number().min(0).default(0).describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.ttl)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -328,7 +346,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string()
|
||||
identityId: z.string().describe(UNIVERSAL_AUTH.LIST_CLIENT_SECRETS.identityId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -371,8 +389,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string(),
|
||||
clientSecretId: z.string()
|
||||
identityId: z.string().describe(UNIVERSAL_AUTH.REVOKE_CLIENT_SECRET.identityId),
|
||||
clientSecretId: z.string().describe(UNIVERSAL_AUTH.REVOKE_CLIENT_SECRET.clientSecretId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -352,6 +352,68 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:integrationAuthId/github/orgs",
|
||||
method: "GET",
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
schema: {
|
||||
params: z.object({
|
||||
integrationAuthId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
orgs: z.object({ name: z.string(), orgId: z.string() }).array()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const orgs = await server.services.integrationAuth.getGithubOrgs({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
id: req.params.integrationAuthId
|
||||
});
|
||||
if (!orgs) throw new Error("No organization found.");
|
||||
|
||||
return { orgs };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:integrationAuthId/github/envs",
|
||||
method: "GET",
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
schema: {
|
||||
params: z.object({
|
||||
integrationAuthId: z.string().trim()
|
||||
}),
|
||||
querystring: z.object({
|
||||
repoOwner: z.string().trim(),
|
||||
repoName: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
envs: z.object({ name: z.string(), envId: z.string() }).array()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const envs = await server.services.integrationAuth.getGithubEnvs({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
id: req.params.integrationAuthId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
repoName: req.query.repoName,
|
||||
repoOwner: req.query.repoOwner
|
||||
});
|
||||
if (!envs) throw new Error("No organization found.");
|
||||
|
||||
return { envs };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
url: "/:integrationAuthId/qovery/orgs",
|
||||
method: "GET",
|
||||
|
@ -33,6 +33,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
||||
secretPrefix: z.string().optional(),
|
||||
secretSuffix: z.string().optional(),
|
||||
initialSyncBehavior: z.string().optional(),
|
||||
shouldAutoRedeploy: z.boolean().optional(),
|
||||
secretGCPLabel: z
|
||||
.object({
|
||||
labelName: z.string(),
|
||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
|
||||
import { ProjectEnvironmentsSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { ENVIRONMENTS } from "@app/lib/api-docs";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
@ -18,11 +19,11 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
workspaceId: z.string().trim().describe(ENVIRONMENTS.CREATE.workspaceId)
|
||||
}),
|
||||
body: z.object({
|
||||
name: z.string().trim(),
|
||||
slug: z.string().trim()
|
||||
name: z.string().trim().describe(ENVIRONMENTS.CREATE.name),
|
||||
slug: z.string().trim().describe(ENVIRONMENTS.CREATE.slug)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -74,13 +75,13 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
id: z.string().trim()
|
||||
workspaceId: z.string().trim().describe(ENVIRONMENTS.UPDATE.workspaceId),
|
||||
id: z.string().trim().describe(ENVIRONMENTS.UPDATE.id)
|
||||
}),
|
||||
body: z.object({
|
||||
slug: z.string().trim().optional(),
|
||||
name: z.string().trim().optional(),
|
||||
position: z.number().optional()
|
||||
slug: z.string().trim().optional().describe(ENVIRONMENTS.UPDATE.slug),
|
||||
name: z.string().trim().optional().describe(ENVIRONMENTS.UPDATE.name),
|
||||
position: z.number().optional().describe(ENVIRONMENTS.UPDATE.position)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -138,8 +139,8 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
id: z.string().trim()
|
||||
workspaceId: z.string().trim().describe(ENVIRONMENTS.DELETE.workspaceId),
|
||||
id: z.string().trim().describe(ENVIRONMENTS.DELETE.id)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
UsersSchema
|
||||
} from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { PROJECTS } from "@app/lib/api-docs";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
|
||||
@ -26,7 +27,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
workspaceId: z.string().trim().describe(PROJECTS.GET_USER_MEMBERSHIPS.workspaceId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -136,8 +137,8 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
membershipId: z.string().trim()
|
||||
workspaceId: z.string().trim().describe(PROJECTS.UPDATE_USER_MEMBERSHIP.workspaceId),
|
||||
membershipId: z.string().trim().describe(PROJECTS.UPDATE_USER_MEMBERSHIP.membershipId)
|
||||
}),
|
||||
body: z.object({
|
||||
roles: z
|
||||
@ -158,6 +159,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
)
|
||||
.min(1)
|
||||
.refine((data) => data.some(({ isTemporary }) => !isTemporary), "At least long lived role is required")
|
||||
.describe(PROJECTS.UPDATE_USER_MEMBERSHIP.roles)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
UserEncryptionKeysSchema,
|
||||
UsersSchema
|
||||
} from "@app/db/schemas";
|
||||
import { PROJECTS } from "@app/lib/api-docs";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import { ProjectFilterType } from "@app/services/project/project-types";
|
||||
@ -128,7 +129,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
method: "GET",
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
workspaceId: z.string().trim().describe(PROJECTS.GET.workspaceId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -157,7 +158,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
method: "DELETE",
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
workspaceId: z.string().trim().describe(PROJECTS.DELETE.workspaceId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -220,11 +221,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
method: "PATCH",
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
workspaceId: z.string().trim().describe(PROJECTS.UPDATE.workspaceId)
|
||||
}),
|
||||
body: z.object({
|
||||
name: z.string().trim().max(64, { message: "Name must be 64 or fewer characters" }).optional(),
|
||||
autoCapitalization: z.boolean().optional()
|
||||
name: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(64, { message: "Name must be 64 or fewer characters" })
|
||||
.optional()
|
||||
.describe(PROJECTS.UPDATE.name),
|
||||
autoCapitalization: z.boolean().optional().describe(PROJECTS.UPDATE.autoCapitalization)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
|
||||
import { SecretFoldersSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { FOLDERS } from "@app/lib/api-docs";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
@ -19,12 +20,12 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
name: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
workspaceId: z.string().trim().describe(FOLDERS.CREATE.workspaceId),
|
||||
environment: z.string().trim().describe(FOLDERS.CREATE.environment),
|
||||
name: z.string().trim().describe(FOLDERS.CREATE.name),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.CREATE.path),
|
||||
// backward compatiability with cli
|
||||
directory: z.string().trim().default("/").transform(removeTrailingSlash)
|
||||
directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.CREATE.directory)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -74,15 +75,15 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
||||
],
|
||||
params: z.object({
|
||||
// old way this was name
|
||||
folderId: z.string()
|
||||
folderId: z.string().describe(FOLDERS.UPDATE.folderId)
|
||||
}),
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
name: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
workspaceId: z.string().trim().describe(FOLDERS.UPDATE.workspaceId),
|
||||
environment: z.string().trim().describe(FOLDERS.UPDATE.environment),
|
||||
name: z.string().trim().describe(FOLDERS.UPDATE.name),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.UPDATE.path),
|
||||
// backward compatiability with cli
|
||||
directory: z.string().trim().default("/").transform(removeTrailingSlash)
|
||||
directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.UPDATE.directory)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -121,6 +122,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
});
|
||||
|
||||
// TODO(daniel): Expose this route in api reference and write docs for it.
|
||||
server.route({
|
||||
url: "/:folderIdOrName",
|
||||
method: "DELETE",
|
||||
@ -133,14 +135,14 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
folderIdOrName: z.string()
|
||||
folderIdOrName: z.string().describe(FOLDERS.DELETE.folderIdOrName)
|
||||
}),
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
workspaceId: z.string().trim().describe(FOLDERS.DELETE.workspaceId),
|
||||
environment: z.string().trim().describe(FOLDERS.DELETE.environment),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.DELETE.path),
|
||||
// keep this here as cli need directory
|
||||
directory: z.string().trim().default("/").transform(removeTrailingSlash)
|
||||
directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.DELETE.directory)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -190,11 +192,11 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
workspaceId: z.string().trim().describe(FOLDERS.LIST.workspaceId),
|
||||
environment: z.string().trim().describe(FOLDERS.LIST.environment),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.LIST.path),
|
||||
// backward compatiability with cli
|
||||
directory: z.string().trim().default("/").transform(removeTrailingSlash)
|
||||
directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.LIST.directory)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
|
||||
import { SecretImportsSchema, SecretsSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { SECRET_IMPORTS } from "@app/lib/api-docs";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
@ -19,12 +20,12 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
workspaceId: z.string().trim().describe(SECRET_IMPORTS.CREATE.workspaceId),
|
||||
environment: z.string().trim().describe(SECRET_IMPORTS.CREATE.environment),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.CREATE.path),
|
||||
import: z.object({
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().transform(removeTrailingSlash)
|
||||
environment: z.string().trim().describe(SECRET_IMPORTS.CREATE.import.environment),
|
||||
path: z.string().trim().transform(removeTrailingSlash).describe(SECRET_IMPORTS.CREATE.import.path)
|
||||
})
|
||||
}),
|
||||
response: {
|
||||
@ -81,20 +82,21 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretImportId: z.string().trim()
|
||||
secretImportId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.secretImportId)
|
||||
}),
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
workspaceId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.workspaceId),
|
||||
environment: z.string().trim().describe(SECRET_IMPORTS.UPDATE.environment),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.UPDATE.path),
|
||||
import: z.object({
|
||||
environment: z.string().trim().optional(),
|
||||
environment: z.string().trim().optional().describe(SECRET_IMPORTS.UPDATE.import.environment),
|
||||
path: z
|
||||
.string()
|
||||
.trim()
|
||||
.optional()
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
||||
position: z.number().optional()
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : val))
|
||||
.describe(SECRET_IMPORTS.UPDATE.import.path),
|
||||
position: z.number().optional().describe(SECRET_IMPORTS.UPDATE.import.position)
|
||||
})
|
||||
}),
|
||||
response: {
|
||||
@ -152,12 +154,12 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretImportId: z.string().trim()
|
||||
secretImportId: z.string().trim().describe(SECRET_IMPORTS.DELETE.secretImportId)
|
||||
}),
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash)
|
||||
workspaceId: z.string().trim().describe(SECRET_IMPORTS.DELETE.workspaceId),
|
||||
environment: z.string().trim().describe(SECRET_IMPORTS.DELETE.environment),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.DELETE.path)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -213,9 +215,9 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash)
|
||||
workspaceId: z.string().trim().describe(SECRET_IMPORTS.LIST.workspaceId),
|
||||
environment: z.string().trim().describe(SECRET_IMPORTS.LIST.environment),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.LIST.path)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
||||
import { ORGANIZATIONS } from "@app/lib/api-docs";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
@ -18,7 +19,7 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
orgId: z.string().trim()
|
||||
orgId: z.string().trim().describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orgId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
ProjectMembershipRole,
|
||||
ProjectUserMembershipRolesSchema
|
||||
} from "@app/db/schemas";
|
||||
import { PROJECTS } from "@app/lib/api-docs";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
|
||||
@ -56,8 +57,8 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
projectId: z.string().trim(),
|
||||
identityId: z.string().trim()
|
||||
projectId: z.string().trim().describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.projectId),
|
||||
identityId: z.string().trim().describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.identityId)
|
||||
}),
|
||||
body: z.object({
|
||||
roles: z
|
||||
@ -77,6 +78,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
])
|
||||
)
|
||||
.min(1)
|
||||
.describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.roles)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -110,8 +112,8 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
projectId: z.string().trim(),
|
||||
identityId: z.string().trim()
|
||||
projectId: z.string().trim().describe(PROJECTS.DELETE_IDENTITY_MEMBERSHIP.projectId),
|
||||
identityId: z.string().trim().describe(PROJECTS.DELETE_IDENTITY_MEMBERSHIP.identityId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -144,7 +146,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
projectId: z.string().trim()
|
||||
projectId: z.string().trim().describe(PROJECTS.LIST_IDENTITY_MEMBERSHIPS.projectId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { OrganizationsSchema, OrgMembershipsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
|
||||
import { ORGANIZATIONS } from "@app/lib/api-docs";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
@ -17,7 +18,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
organizationId: z.string().trim().describe(ORGANIZATIONS.LIST_USER_MEMBERSHIPS.organizationId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -63,7 +64,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
organizationId: z.string().trim().describe(ORGANIZATIONS.GET_PROJECTS.organizationId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -108,9 +109,12 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
|
||||
params: z.object({
|
||||
organizationId: z.string().trim().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.organizationId),
|
||||
membershipId: z.string().trim().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.membershipId)
|
||||
}),
|
||||
body: z.object({
|
||||
role: z.string().trim()
|
||||
role: z.string().trim().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.role)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -145,7 +149,10 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
|
||||
params: z.object({
|
||||
organizationId: z.string().trim().describe(ORGANIZATIONS.DELETE_USER_MEMBERSHIP.organizationId),
|
||||
membershipId: z.string().trim().describe(ORGANIZATIONS.DELETE_USER_MEMBERSHIP.membershipId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
membership: OrgMembershipsSchema
|
||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
|
||||
import { ProjectMembershipsSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { PROJECTS } from "@app/lib/api-docs";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
@ -11,11 +12,11 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
url: "/:projectId/memberships",
|
||||
schema: {
|
||||
params: z.object({
|
||||
projectId: z.string().describe("The ID of the project.")
|
||||
projectId: z.string().describe(PROJECTS.INVITE_MEMBER.projectId)
|
||||
}),
|
||||
body: z.object({
|
||||
emails: z.string().email().array().default([]).describe("Emails of the users to add to the project."),
|
||||
usernames: z.string().array().default([]).describe("Usernames of the users to add to the project.")
|
||||
emails: z.string().email().array().default([]).describe(PROJECTS.INVITE_MEMBER.emails),
|
||||
usernames: z.string().array().default([]).describe(PROJECTS.INVITE_MEMBER.usernames)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -57,12 +58,12 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
url: "/:projectId/memberships",
|
||||
schema: {
|
||||
params: z.object({
|
||||
projectId: z.string().describe("The ID of the project.")
|
||||
projectId: z.string().describe(PROJECTS.REMOVE_MEMBER.projectId)
|
||||
}),
|
||||
|
||||
body: z.object({
|
||||
emails: z.string().email().array().default([]).describe("Emails of the users to remove from the project."),
|
||||
usernames: z.string().array().default([]).describe("Usernames of the users to remove from the project.")
|
||||
emails: z.string().email().array().default([]).describe(PROJECTS.REMOVE_MEMBER.emails),
|
||||
usernames: z.string().array().default([]).describe(PROJECTS.REMOVE_MEMBER.usernames)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -3,6 +3,7 @@ import { z } from "zod";
|
||||
|
||||
import { ProjectKeysSchema, ProjectsSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { PROJECTS } from "@app/lib/api-docs";
|
||||
import { authRateLimit } from "@app/server/config/rateLimiter";
|
||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
@ -38,7 +39,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
workspaceId: z.string().trim().describe(PROJECTS.GET_KEY.workspaceId)
|
||||
}),
|
||||
response: {
|
||||
200: ProjectKeysSchema.merge(
|
||||
@ -140,11 +141,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
projectName: z.string().trim().describe("Name of the project you're creating"),
|
||||
slug: slugSchema
|
||||
projectName: z.string().trim().describe(PROJECTS.CREATE.projectName),
|
||||
slug: z
|
||||
.string()
|
||||
.min(5)
|
||||
.max(36)
|
||||
.refine((v) => slugify(v) === v, {
|
||||
message: "Slug must be a valid slug"
|
||||
})
|
||||
.optional()
|
||||
.describe("An optional slug for the project. If not provided, it will be auto-generated"),
|
||||
organizationSlug: z.string().trim().describe("The slug of the organization to create the project in")
|
||||
.describe(PROJECTS.CREATE.slug)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -159,7 +165,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
orgSlug: req.body.organizationSlug,
|
||||
workspaceName: req.body.projectName,
|
||||
slug: req.body.slug
|
||||
});
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
} from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { CommitType } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
||||
import { RAW_SECRETS } from "@app/lib/api-docs";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||
@ -34,20 +35,15 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
workspaceId: z.string().trim().optional(),
|
||||
workspaceSlug: z
|
||||
.string()
|
||||
.trim()
|
||||
.optional()
|
||||
.describe(
|
||||
"The slug of the workspace. This is only supported when authenticating Machine Identity's. Either the project ID or project slug has to be present in the request."
|
||||
),
|
||||
environment: z.string().trim().optional(),
|
||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceId),
|
||||
workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceSlug),
|
||||
environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment),
|
||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.LIST.secretPath),
|
||||
include_imports: z
|
||||
.enum(["true", "false"])
|
||||
.default("false")
|
||||
.transform((value) => value === "true")
|
||||
.describe(RAW_SECRETS.LIST.includeImports)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -148,18 +144,19 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim()
|
||||
secretName: z.string().trim().describe(RAW_SECRETS.GET.secretName)
|
||||
}),
|
||||
querystring: z.object({
|
||||
workspaceId: z.string().trim().optional(),
|
||||
environment: z.string().trim().optional(),
|
||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
version: z.coerce.number().optional(),
|
||||
type: z.nativeEnum(SecretType).default(SecretType.Shared),
|
||||
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.GET.workspaceId),
|
||||
environment: z.string().trim().optional().describe(RAW_SECRETS.GET.environment),
|
||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.GET.secretPath),
|
||||
version: z.coerce.number().optional().describe(RAW_SECRETS.GET.version),
|
||||
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.GET.type),
|
||||
include_imports: z
|
||||
.enum(["true", "false"])
|
||||
.default("false")
|
||||
.transform((value) => value === "true")
|
||||
.describe(RAW_SECRETS.GET.includeImports)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -239,16 +236,24 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim()
|
||||
secretName: z.string().trim().describe(RAW_SECRETS.CREATE.secretName)
|
||||
}),
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
secretValue: z.string().transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())),
|
||||
secretComment: z.string().trim().optional().default(""),
|
||||
skipMultilineEncoding: z.boolean().optional(),
|
||||
type: z.nativeEnum(SecretType).default(SecretType.Shared)
|
||||
workspaceId: z.string().trim().describe(RAW_SECRETS.CREATE.workspaceId),
|
||||
environment: z.string().trim().describe(RAW_SECRETS.CREATE.environment),
|
||||
secretPath: z
|
||||
.string()
|
||||
.trim()
|
||||
.default("/")
|
||||
.transform(removeTrailingSlash)
|
||||
.describe(RAW_SECRETS.CREATE.secretPath),
|
||||
secretValue: z
|
||||
.string()
|
||||
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
|
||||
.describe(RAW_SECRETS.CREATE.secretValue),
|
||||
secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment),
|
||||
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding),
|
||||
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.CREATE.type)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -317,15 +322,23 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim()
|
||||
secretName: z.string().trim().describe(RAW_SECRETS.UPDATE.secretName)
|
||||
}),
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
secretValue: z.string().transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())),
|
||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
skipMultilineEncoding: z.boolean().optional(),
|
||||
type: z.nativeEnum(SecretType).default(SecretType.Shared)
|
||||
workspaceId: z.string().trim().describe(RAW_SECRETS.UPDATE.workspaceId),
|
||||
environment: z.string().trim().describe(RAW_SECRETS.UPDATE.environment),
|
||||
secretValue: z
|
||||
.string()
|
||||
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
|
||||
.describe(RAW_SECRETS.UPDATE.secretValue),
|
||||
secretPath: z
|
||||
.string()
|
||||
.trim()
|
||||
.default("/")
|
||||
.transform(removeTrailingSlash)
|
||||
.describe(RAW_SECRETS.UPDATE.secretPath),
|
||||
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.UPDATE.skipMultilineEncoding),
|
||||
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.UPDATE.type)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -392,13 +405,18 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim()
|
||||
secretName: z.string().trim().describe(RAW_SECRETS.DELETE.secretName)
|
||||
}),
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
type: z.nativeEnum(SecretType).default(SecretType.Shared)
|
||||
workspaceId: z.string().trim().describe(RAW_SECRETS.DELETE.workspaceId),
|
||||
environment: z.string().trim().describe(RAW_SECRETS.DELETE.environment),
|
||||
secretPath: z
|
||||
.string()
|
||||
.trim()
|
||||
.default("/")
|
||||
.transform(removeTrailingSlash)
|
||||
.describe(RAW_SECRETS.DELETE.secretPath),
|
||||
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.DELETE.type)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -260,20 +260,44 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
|
||||
* Return list of services for Render integration
|
||||
*/
|
||||
const getAppsRender = async ({ accessToken }: { accessToken: string }) => {
|
||||
const res = (
|
||||
await request.get<{ service: { name: string; id: string } }[]>(`${IntegrationUrls.RENDER_API_URL}/v1/services`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json",
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
})
|
||||
).data;
|
||||
const apps: Array<{ name: string; appId: string }> = [];
|
||||
let hasMorePages = true;
|
||||
const perPage = 100;
|
||||
let cursor;
|
||||
|
||||
const apps = res.map((a) => ({
|
||||
name: a.service.name,
|
||||
appId: a.service.id
|
||||
}));
|
||||
interface RenderService {
|
||||
cursor: string;
|
||||
service: { name: string; id: string };
|
||||
}
|
||||
|
||||
while (hasMorePages) {
|
||||
const res: RenderService[] = (
|
||||
await request.get<RenderService[]>(`${IntegrationUrls.RENDER_API_URL}/v1/services`, {
|
||||
params: new URLSearchParams({
|
||||
...(cursor ? { cursor: String(cursor) } : {}),
|
||||
limit: String(perPage)
|
||||
}),
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json",
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
})
|
||||
).data;
|
||||
|
||||
res.forEach((a) => {
|
||||
apps.push({
|
||||
name: a.service.name,
|
||||
appId: a.service.id
|
||||
});
|
||||
});
|
||||
|
||||
if (res.length < perPage) {
|
||||
hasMorePages = false;
|
||||
} else {
|
||||
cursor = res[res.length - 1].cursor;
|
||||
}
|
||||
}
|
||||
|
||||
return apps;
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
|
||||
import { SecretEncryptionAlgo, SecretKeyEncoding, TIntegrationAuths, TIntegrationAuthsInsert } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
@ -24,6 +25,8 @@ import {
|
||||
TIntegrationAuthAppsDTO,
|
||||
TIntegrationAuthBitbucketWorkspaceDTO,
|
||||
TIntegrationAuthChecklyGroupsDTO,
|
||||
TIntegrationAuthGithubEnvsDTO,
|
||||
TIntegrationAuthGithubOrgsDTO,
|
||||
TIntegrationAuthHerokuPipelinesDTO,
|
||||
TIntegrationAuthNorthflankSecretGroupDTO,
|
||||
TIntegrationAuthQoveryEnvironmentsDTO,
|
||||
@ -435,6 +438,75 @@ export const integrationAuthServiceFactory = ({
|
||||
return [];
|
||||
};
|
||||
|
||||
const getGithubOrgs = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TIntegrationAuthGithubOrgsDTO) => {
|
||||
const integrationAuth = await integrationAuthDAL.findById(id);
|
||||
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
integrationAuth.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
|
||||
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
|
||||
|
||||
const octokit = new Octokit({
|
||||
auth: accessToken
|
||||
});
|
||||
|
||||
const { data } = await octokit.request("GET /user/orgs", {
|
||||
headers: {
|
||||
"X-GitHub-Api-Version": "2022-11-28"
|
||||
}
|
||||
});
|
||||
if (!data) return [];
|
||||
|
||||
return data.map(({ login: name, id: orgId }) => ({ name, orgId: String(orgId) }));
|
||||
};
|
||||
|
||||
const getGithubEnvs = async ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
id,
|
||||
repoOwner,
|
||||
repoName
|
||||
}: TIntegrationAuthGithubEnvsDTO) => {
|
||||
const integrationAuth = await integrationAuthDAL.findById(id);
|
||||
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
integrationAuth.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
|
||||
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
|
||||
|
||||
const octokit = new Octokit({
|
||||
auth: accessToken
|
||||
});
|
||||
|
||||
const {
|
||||
data: { environments }
|
||||
} = await octokit.request("GET /repos/{owner}/{repo}/environments", {
|
||||
headers: {
|
||||
"X-GitHub-Api-Version": "2022-11-28"
|
||||
},
|
||||
owner: repoOwner,
|
||||
repo: repoName
|
||||
});
|
||||
if (!environments) return [];
|
||||
return environments.map(({ id: envId, name }) => ({ name, envId: String(envId) }));
|
||||
};
|
||||
|
||||
const getQoveryOrgs = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TIntegrationAuthQoveryOrgsDTO) => {
|
||||
const integrationAuth = await integrationAuthDAL.findById(id);
|
||||
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
|
||||
@ -1061,6 +1133,8 @@ export const integrationAuthServiceFactory = ({
|
||||
getIntegrationApps,
|
||||
getVercelBranches,
|
||||
getApps,
|
||||
getGithubOrgs,
|
||||
getGithubEnvs,
|
||||
getChecklyGroups,
|
||||
getQoveryApps,
|
||||
getQoveryEnvs,
|
||||
|
@ -44,6 +44,16 @@ export type TIntegrationAuthChecklyGroupsDTO = {
|
||||
accountId: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TIntegrationAuthGithubOrgsDTO = {
|
||||
id: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TIntegrationAuthGithubEnvsDTO = {
|
||||
id: string;
|
||||
repoName: string;
|
||||
repoOwner: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TIntegrationAuthQoveryOrgsDTO = {
|
||||
id: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
@ -459,7 +459,7 @@ const syncSecretsAWSParameterStore = async ({
|
||||
|
||||
const params = {
|
||||
Path: integration.path as string,
|
||||
Recursive: true,
|
||||
Recursive: false,
|
||||
WithDecryption: true
|
||||
};
|
||||
|
||||
@ -1110,98 +1110,176 @@ const syncSecretsGitHub = async ({
|
||||
interface GitHubRepoKey {
|
||||
key_id: string;
|
||||
key: string;
|
||||
id?: number | undefined;
|
||||
url?: string | undefined;
|
||||
title?: string | undefined;
|
||||
created_at?: string | undefined;
|
||||
}
|
||||
|
||||
interface GitHubSecret {
|
||||
name: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
interface GitHubSecretRes {
|
||||
[index: string]: GitHubSecret;
|
||||
visibility?: "all" | "private" | "selected";
|
||||
selected_repositories_url?: string | undefined;
|
||||
}
|
||||
|
||||
const octokit = new Octokit({
|
||||
auth: accessToken
|
||||
});
|
||||
|
||||
// const user = (await octokit.request('GET /user', {})).data;
|
||||
const repoPublicKey: GitHubRepoKey = (
|
||||
await octokit.request("GET /repos/{owner}/{repo}/actions/secrets/public-key", {
|
||||
owner: integration.owner as string,
|
||||
repo: integration.app as string
|
||||
})
|
||||
).data;
|
||||
enum GithubScope {
|
||||
Repo = "github-repo",
|
||||
Org = "github-org",
|
||||
Env = "github-env"
|
||||
}
|
||||
|
||||
let repoPublicKey: GitHubRepoKey;
|
||||
|
||||
switch (integration.scope) {
|
||||
case GithubScope.Org: {
|
||||
const { data } = await octokit.request("GET /orgs/{org}/actions/secrets/public-key", {
|
||||
org: integration.owner as string
|
||||
});
|
||||
repoPublicKey = data;
|
||||
break;
|
||||
}
|
||||
case GithubScope.Env: {
|
||||
const { data } = await octokit.request(
|
||||
"GET /repositories/{repository_id}/environments/{environment_name}/secrets/public-key",
|
||||
{
|
||||
repository_id: Number(integration.appId),
|
||||
environment_name: integration.targetEnvironmentId as string
|
||||
}
|
||||
);
|
||||
repoPublicKey = data;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const { data } = await octokit.request("GET /repos/{owner}/{repo}/actions/secrets/public-key", {
|
||||
owner: integration.owner as string,
|
||||
repo: integration.app as string
|
||||
});
|
||||
repoPublicKey = data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get local copy of decrypted secrets. We cannot decrypt them as we dont have access to GH private key
|
||||
let encryptedSecrets: GitHubSecretRes = (
|
||||
await octokit.request("GET /repos/{owner}/{repo}/actions/secrets", {
|
||||
owner: integration.owner as string,
|
||||
repo: integration.app as string
|
||||
})
|
||||
).data.secrets.reduce(
|
||||
(obj, secret) => ({
|
||||
...obj,
|
||||
[secret.name]: secret
|
||||
}),
|
||||
{}
|
||||
);
|
||||
let encryptedSecrets: GitHubSecret[];
|
||||
|
||||
encryptedSecrets = Object.keys(encryptedSecrets).reduce(
|
||||
(
|
||||
result: {
|
||||
[key: string]: GitHubSecret;
|
||||
},
|
||||
key
|
||||
) => {
|
||||
if (
|
||||
(appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) &&
|
||||
(appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)
|
||||
) {
|
||||
result[key] = encryptedSecrets[key];
|
||||
}
|
||||
return result;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
Object.keys(encryptedSecrets).map(async (key) => {
|
||||
if (!(key in secrets)) {
|
||||
return octokit.request("DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}", {
|
||||
switch (integration.scope) {
|
||||
case GithubScope.Org: {
|
||||
encryptedSecrets = (
|
||||
await octokit.request("GET /orgs/{org}/actions/secrets", {
|
||||
org: integration.owner as string
|
||||
})
|
||||
).data.secrets;
|
||||
break;
|
||||
}
|
||||
case GithubScope.Env: {
|
||||
encryptedSecrets = (
|
||||
await octokit.request("GET /repositories/{repository_id}/environments/{environment_name}/secrets", {
|
||||
repository_id: Number(integration.appId),
|
||||
environment_name: integration.targetEnvironmentId as string
|
||||
})
|
||||
).data.secrets;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
encryptedSecrets = (
|
||||
await octokit.request("GET /repos/{owner}/{repo}/actions/secrets", {
|
||||
owner: integration.owner as string,
|
||||
repo: integration.app as string,
|
||||
secret_name: key
|
||||
});
|
||||
repo: integration.app as string
|
||||
})
|
||||
).data.secrets;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for await (const encryptedSecret of encryptedSecrets) {
|
||||
if (
|
||||
!(encryptedSecret.name in secrets) &&
|
||||
!(appendices?.prefix !== undefined && !encryptedSecret.name.startsWith(appendices?.prefix)) &&
|
||||
!(appendices?.suffix !== undefined && !encryptedSecret.name.endsWith(appendices?.suffix))
|
||||
) {
|
||||
switch (integration.scope) {
|
||||
case GithubScope.Org: {
|
||||
await octokit.request("DELETE /orgs/{org}/actions/secrets/{secret_name}", {
|
||||
org: integration.owner as string,
|
||||
secret_name: encryptedSecret.name
|
||||
});
|
||||
break;
|
||||
}
|
||||
case GithubScope.Env: {
|
||||
await octokit.request(
|
||||
"DELETE /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}",
|
||||
{
|
||||
repository_id: Number(integration.appId),
|
||||
environment_name: integration.targetEnvironmentId as string,
|
||||
secret_name: encryptedSecret.name
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
await octokit.request("DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}", {
|
||||
owner: integration.owner as string,
|
||||
repo: integration.app as string,
|
||||
secret_name: encryptedSecret.name
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
Object.keys(secrets).map((key) => {
|
||||
// let encryptedSecret;
|
||||
return sodium.ready.then(async () => {
|
||||
// convert secret & base64 key to Uint8Array.
|
||||
const binkey = sodium.from_base64(repoPublicKey.key, sodium.base64_variants.ORIGINAL);
|
||||
const binsec = sodium.from_string(secrets[key].value);
|
||||
await sodium.ready.then(async () => {
|
||||
for await (const key of Object.keys(secrets)) {
|
||||
// convert secret & base64 key to Uint8Array.
|
||||
const binkey = sodium.from_base64(repoPublicKey.key, sodium.base64_variants.ORIGINAL);
|
||||
const binsec = sodium.from_string(secrets[key].value);
|
||||
|
||||
// encrypt secret using libsodium
|
||||
const encBytes = sodium.crypto_box_seal(binsec, binkey);
|
||||
// encrypt secret using libsodium
|
||||
const encBytes = sodium.crypto_box_seal(binsec, binkey);
|
||||
|
||||
// convert encrypted Uint8Array to base64
|
||||
const encryptedSecret = sodium.to_base64(encBytes, sodium.base64_variants.ORIGINAL);
|
||||
// convert encrypted Uint8Array to base64
|
||||
const encryptedSecret = sodium.to_base64(encBytes, sodium.base64_variants.ORIGINAL);
|
||||
|
||||
await octokit.request("PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}", {
|
||||
owner: integration.owner as string,
|
||||
repo: integration.app as string,
|
||||
secret_name: key,
|
||||
encrypted_value: encryptedSecret,
|
||||
key_id: repoPublicKey.key_id
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
switch (integration.scope) {
|
||||
case GithubScope.Org:
|
||||
await octokit.request("PUT /orgs/{org}/actions/secrets/{secret_name}", {
|
||||
org: integration.owner as string,
|
||||
secret_name: key,
|
||||
visibility: "all",
|
||||
encrypted_value: encryptedSecret,
|
||||
key_id: repoPublicKey.key_id
|
||||
});
|
||||
break;
|
||||
case GithubScope.Env:
|
||||
await octokit.request(
|
||||
"PUT /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}",
|
||||
{
|
||||
repository_id: Number(integration.appId),
|
||||
environment_name: integration.targetEnvironmentId as string,
|
||||
secret_name: key,
|
||||
encrypted_value: encryptedSecret,
|
||||
key_id: repoPublicKey.key_id
|
||||
}
|
||||
);
|
||||
break;
|
||||
default:
|
||||
await octokit.request("PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}", {
|
||||
owner: integration.owner as string,
|
||||
repo: integration.app as string,
|
||||
secret_name: key,
|
||||
encrypted_value: encryptedSecret,
|
||||
key_id: repoPublicKey.key_id
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1229,6 +1307,22 @@ const syncSecretsRender = async ({
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (integration.metadata) {
|
||||
const metadata = z.record(z.any()).parse(integration.metadata);
|
||||
if (metadata.shouldAutoRedeploy === true) {
|
||||
await request.post(
|
||||
`${IntegrationUrls.RENDER_API_URL}/v1/services/${integration.appId}/deploys`,
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -92,7 +92,6 @@ export const projectServiceFactory = ({
|
||||
* Create workspace. Make user the admin
|
||||
* */
|
||||
const createProject = async ({
|
||||
orgSlug,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
@ -100,13 +99,7 @@ export const projectServiceFactory = ({
|
||||
workspaceName,
|
||||
slug: projectSlug
|
||||
}: TCreateProjectDTO) => {
|
||||
if (!orgSlug) {
|
||||
throw new BadRequestError({
|
||||
message: "Must provide organization slug to create project"
|
||||
});
|
||||
}
|
||||
|
||||
const organization = await orgDAL.findOne({ slug: orgSlug });
|
||||
const organization = await orgDAL.findOne({ id: actorOrgId });
|
||||
|
||||
const { permission, membership: orgMembership } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
|
@ -24,7 +24,6 @@ export type TCreateProjectDTO = {
|
||||
actorAuthMethod: ActorAuthMethod;
|
||||
actorId: string;
|
||||
actorOrgId?: string;
|
||||
orgSlug: string;
|
||||
workspaceName: string;
|
||||
slug?: string;
|
||||
};
|
||||
|
@ -23,7 +23,8 @@ export default defineConfig({
|
||||
loader: {
|
||||
".handlebars": "copy",
|
||||
".md": "copy",
|
||||
".txt": "copy"
|
||||
".txt": "copy",
|
||||
".pem": "copy"
|
||||
},
|
||||
external: ["../../../frontend/node_modules/next/dist/server/next-server.js"],
|
||||
outDir: "dist",
|
||||
|
@ -164,6 +164,28 @@ func CallGetAllOrganizations(httpClient *resty.Client) (GetOrganizationsResponse
|
||||
return orgResponse, nil
|
||||
}
|
||||
|
||||
func CallSelectOrganization(httpClient *resty.Client, request SelectOrganizationRequest) (SelectOrganizationResponse, error) {
|
||||
var selectOrgResponse SelectOrganizationResponse
|
||||
|
||||
response, err := httpClient.
|
||||
R().
|
||||
SetBody(request).
|
||||
SetResult(&selectOrgResponse).
|
||||
SetHeader("User-Agent", USER_AGENT).
|
||||
Post(fmt.Sprintf("%v/v3/auth/select-organization", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return SelectOrganizationResponse{}, err
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return SelectOrganizationResponse{}, fmt.Errorf("CallSelectOrganization: Unsuccessful response: [response=%v]", response)
|
||||
}
|
||||
|
||||
return selectOrgResponse, nil
|
||||
|
||||
}
|
||||
|
||||
func CallGetAllWorkSpacesUserBelongsTo(httpClient *resty.Client) (GetWorkSpacesResponse, error) {
|
||||
var workSpacesResponse GetWorkSpacesResponse
|
||||
response, err := httpClient.
|
||||
|
@ -135,6 +135,14 @@ type GetOrganizationsResponse struct {
|
||||
} `json:"organizations"`
|
||||
}
|
||||
|
||||
type SelectOrganizationResponse struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type SelectOrganizationRequest struct {
|
||||
OrganizationId string `json:"organizationId"`
|
||||
}
|
||||
|
||||
type Secret struct {
|
||||
SecretKeyCiphertext string `json:"secretKeyCiphertext,omitempty"`
|
||||
SecretKeyIV string `json:"secretKeyIV,omitempty"`
|
||||
|
@ -74,6 +74,21 @@ var initCmd = &cobra.Command{
|
||||
|
||||
selectedOrganization := organizations[index]
|
||||
|
||||
tokenResponse, err := api.CallSelectOrganization(httpClient, api.SelectOrganizationRequest{OrganizationId: selectedOrganization.ID})
|
||||
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to select organization")
|
||||
}
|
||||
|
||||
// set the config jwt token to the new token
|
||||
userCreds.UserCredentials.JTWToken = tokenResponse.Token
|
||||
err = util.StoreUserCredsInKeyRing(&userCreds.UserCredentials)
|
||||
httpClient.SetAuthToken(tokenResponse.Token)
|
||||
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to store your user credentials")
|
||||
}
|
||||
|
||||
workspaceResponse, err := api.CallGetAllWorkSpacesUserBelongsTo(httpClient)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to pull projects that belong to you")
|
||||
|
@ -301,11 +301,13 @@ func cliDefaultLogin(userCredentialsToBeStored *models.UserCredentials) {
|
||||
log.Debug().Msgf("[decryptedPrivateKey=%s] [email=%s] [loginTwoResponse.Token=%s]", string(decryptedPrivateKey), email, loginTwoResponse.Token)
|
||||
util.PrintErrorMessageAndExit("We were unable to fetch required details to complete your login. Run with -d to see more info")
|
||||
}
|
||||
// Login is successful so ask user to choose organization
|
||||
newJwtToken := GetJwtTokenWithOrganizationId(loginTwoResponse.Token)
|
||||
|
||||
//updating usercredentials
|
||||
userCredentialsToBeStored.Email = email
|
||||
userCredentialsToBeStored.PrivateKey = string(decryptedPrivateKey)
|
||||
userCredentialsToBeStored.JTWToken = loginTwoResponse.Token
|
||||
userCredentialsToBeStored.JTWToken = newJwtToken
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -480,6 +482,44 @@ func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2R
|
||||
return &loginOneResponseResult, &loginTwoResponseResult, nil
|
||||
}
|
||||
|
||||
func GetJwtTokenWithOrganizationId(oldJwtToken string) string {
|
||||
log.Debug().Msg(fmt.Sprint("GetJwtTokenWithOrganizationId: ", "oldJwtToken", oldJwtToken))
|
||||
|
||||
httpClient := resty.New()
|
||||
httpClient.SetAuthToken(oldJwtToken)
|
||||
|
||||
organizationResponse, err := api.CallGetAllOrganizations(httpClient)
|
||||
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to pull organizations that belong to you")
|
||||
}
|
||||
|
||||
organizations := organizationResponse.Organizations
|
||||
|
||||
organizationNames := util.GetOrganizationsNameList(organizationResponse)
|
||||
|
||||
prompt := promptui.Select{
|
||||
Label: "Which Infisical organization would you like to log into?",
|
||||
Items: organizationNames,
|
||||
}
|
||||
|
||||
index, _, err := prompt.Run()
|
||||
if err != nil {
|
||||
util.HandleError(err)
|
||||
}
|
||||
|
||||
selectedOrganization := organizations[index]
|
||||
|
||||
selectedOrgRes, err := api.CallSelectOrganization(httpClient, api.SelectOrganizationRequest{OrganizationId: selectedOrganization.ID})
|
||||
|
||||
if err != nil {
|
||||
util.HandleError(err)
|
||||
}
|
||||
|
||||
return selectedOrgRes.Token
|
||||
|
||||
}
|
||||
|
||||
func userLoginMenu(currentLoggedInUserEmail string) (bool, error) {
|
||||
label := fmt.Sprintf("Current logged in user email: %s on domain: %s", currentLoggedInUserEmail, config.INFISICAL_URL)
|
||||
|
||||
|
@ -419,7 +419,7 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
|
||||
util.HandleError(err, "Unable to parse path flag")
|
||||
}
|
||||
|
||||
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath}, "")
|
||||
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: true}, "")
|
||||
if err != nil {
|
||||
util.HandleError(err, "To fetch all secrets")
|
||||
}
|
||||
@ -477,7 +477,7 @@ func generateExampleEnv(cmd *cobra.Command, args []string) {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath}, "")
|
||||
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: true}, "")
|
||||
if err != nil {
|
||||
util.HandleError(err, "To fetch all secrets")
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Delete"
|
||||
openapi: "DELETE /api/v1/folders/{folderId}"
|
||||
openapi: "DELETE /api/v1/folders/{folderIdOrName}"
|
||||
---
|
||||
|
@ -18,4 +18,4 @@ Follow the instructions for your language use the SDK for it:
|
||||
- [Java SDK](https://infisical.com/docs/sdks/languages/java)
|
||||
- [.NET SDK](https://infisical.com/docs/sdks/languages/csharp)
|
||||
|
||||
Missing a language? [Throw in a request](https://github.com/Infisical/infisical/issues).
|
||||
Missing a language? [Throw in a request here](https://github.com/Infisical/infisical/issues).
|
||||
|
Binary file not shown.
Before ![]() (image error) Size: 733 KiB After ![]() (image error) Size: 739 KiB ![]() ![]() |
Binary file not shown.
After ![]() (image error) Size: 691 KiB |
Binary file not shown.
After ![]() (image error) Size: 709 KiB |
Binary file not shown.
After ![]() (image error) Size: 715 KiB |
Binary file not shown.
Before ![]() (image error) Size: 398 KiB After ![]() (image error) Size: 649 KiB ![]() ![]() |
@ -3,17 +3,14 @@ title: "GitHub Actions"
|
||||
description: "How to sync secrets from Infisical to GitHub Actions"
|
||||
---
|
||||
|
||||
Infisical lets you sync secrets to GitHub at the organization-level, repository-level, and repository environment-level.
|
||||
|
||||
Prerequisites:
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- Ensure that you have admin privileges to the repository you want to sync secrets to.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Usage">
|
||||
<Warning>
|
||||
Infisical can sync secrets to GitHub repo secrets only. If your repo uses environment secrets, then stay tuned with this [issue](https://github.com/Infisical/infisical/issues/54).
|
||||
</Warning>
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- Ensure you have admin privileges to the repo you want to sync secrets to.
|
||||
|
||||
<Steps>
|
||||
<Step title="Authorize Infisical for GitHub">
|
||||
Navigate to your project's integrations tab in Infisical.
|
||||
@ -29,12 +26,27 @@ description: "How to sync secrets from Infisical to GitHub Actions"
|
||||
Although this step breaks E2EE, it's necessary for Infisical to sync the environment variables to the cloud platform.
|
||||
</Info>
|
||||
</Step>
|
||||
<Step title="Start integration">
|
||||
Select which Infisical environment secrets you want to sync to which GitHub repo and press start integration to start syncing secrets to the repo.
|
||||
<Step title="Configure Infisical GitHub integration">
|
||||
Select which Infisical environment secrets you want to sync to which GitHub organization, repository, or repository environment.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Repository">
|
||||

|
||||
</Tab>
|
||||
<Tab title="Organization">
|
||||

|
||||
</Tab>
|
||||
<Tab title="Repository Environment">
|
||||

|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Finally, press create integration to start syncing secrets to GitHub.
|
||||
|
||||

|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
</Tab>
|
||||
<Tab title="Self-Hosted Setup">
|
||||
Using the GitHub integration on a self-hosted instance of Infisical requires configuring an OAuth application in GitHub
|
||||
@ -45,13 +57,13 @@ description: "How to sync secrets from Infisical to GitHub Actions"
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
Create the OAuth application. As part of the form, set the **Homepage URL** to your self-hosted domain `https://your-domain.com`
|
||||
and the **Authorization callback URL** to `https://your-domain.com/integrations/github/oauth2/callback`.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
<Note>
|
||||
If you have a GitHub organization, you can create an OAuth application under it
|
||||
in your organization Settings > Developer settings > OAuth Apps > New Org OAuth App.
|
||||
@ -59,17 +71,17 @@ description: "How to sync secrets from Infisical to GitHub Actions"
|
||||
</Step>
|
||||
<Step title="Add your OAuth application credentials to Infisical">
|
||||
Obtain the **Client ID** and generate a new **Client Secret** for your GitHub OAuth application.
|
||||
|
||||

|
||||
|
||||
|
||||

|
||||
|
||||
Back in your Infisical instance, add two new environment variables for the credentials of your GitHub OAuth application:
|
||||
|
||||
- `CLIENT_ID_GITHUB`: The **Client ID** of your GitHub OAuth application.
|
||||
- `CLIENT_SECRET_GITHUB`: The **Client Secret** of your GitHub OAuth application.
|
||||
|
||||
|
||||
Once added, restart your Infisical instance and use the GitHub integration.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
@ -39,32 +39,32 @@
|
||||
"name": "Start for Free",
|
||||
"url": "https://app.infisical.com/signup"
|
||||
},
|
||||
"anchors": [
|
||||
"tabs": [
|
||||
{
|
||||
"name": "Internals",
|
||||
"icon": "sitemap",
|
||||
"url": "internals"
|
||||
},
|
||||
{
|
||||
"name": "SDKs",
|
||||
"icon": "puzzle-piece",
|
||||
"url": "sdks"
|
||||
"name": "Changelog",
|
||||
"url": "changelog"
|
||||
},
|
||||
{
|
||||
"name": "API Reference",
|
||||
"icon": "cloud",
|
||||
"url": "api-reference"
|
||||
},
|
||||
{
|
||||
"name": "Changelog",
|
||||
"icon": "timer",
|
||||
"url": "changelog"
|
||||
"name": "SDKs",
|
||||
"url": "sdks"
|
||||
},
|
||||
{
|
||||
"name": "Contributing",
|
||||
"url": "contributing"
|
||||
}
|
||||
],
|
||||
"anchors": [
|
||||
|
||||
{
|
||||
"name": "Contributing",
|
||||
"icon": "code",
|
||||
"url": "contributing"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Blog",
|
||||
"icon": "newspaper",
|
||||
@ -79,6 +79,11 @@
|
||||
"name": "GitHub",
|
||||
"icon": "github",
|
||||
"url": "https://github.com/Infisical/infisical"
|
||||
},
|
||||
{
|
||||
"name": "Internals",
|
||||
"icon": "sitemap",
|
||||
"url": "internals"
|
||||
}
|
||||
],
|
||||
"navigation": [
|
||||
@ -191,6 +196,7 @@
|
||||
"self-hosting/guides/mongo-to-postgres"
|
||||
]
|
||||
},
|
||||
"self-hosting/ee",
|
||||
"self-hosting/faq"
|
||||
]
|
||||
},
|
||||
@ -356,7 +362,18 @@
|
||||
},
|
||||
{
|
||||
"group": "Overview",
|
||||
"pages": ["sdks/overview"]
|
||||
"pages": [
|
||||
"sdks/overview"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "SDK's",
|
||||
"pages": [
|
||||
"sdks/languages/node",
|
||||
"sdks/languages/python",
|
||||
"sdks/languages/java",
|
||||
"sdks/languages/csharp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Overview",
|
||||
|
@ -1,6 +1,7 @@
|
||||
---
|
||||
title: "Infisical .NET SDK"
|
||||
icon: "C#"
|
||||
sidebarTitle: ".NET"
|
||||
icon: "bars"
|
||||
---
|
||||
|
||||
If you're working with C#, the official [Infisical C# SDK](https://github.com/Infisical/sdk/tree/main/languages/csharp) package is the easiest way to fetch and work with secrets for your application.
|
||||
|
@ -1,5 +1,6 @@
|
||||
---
|
||||
title: "Infisical Java SDK"
|
||||
sidebarTitle: "Java"
|
||||
icon: "java"
|
||||
---
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
---
|
||||
title: "Infisical Node.js SDK"
|
||||
sidebarTitle: "Node.js"
|
||||
icon: "node"
|
||||
---
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
---
|
||||
title: "Infisical Python SDK"
|
||||
sidebarTitle: "Python"
|
||||
icon: "python"
|
||||
---
|
||||
|
||||
|
28
docs/self-hosting/ee.mdx
Normal file
28
docs/self-hosting/ee.mdx
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
title: "Using Infisical EE"
|
||||
description: "How to activate Infisical Enterprise Edition (EE) features"
|
||||
---
|
||||
|
||||
While most features in Infisical are free to use, others are paid and require purchasing an enterprise license to use them.
|
||||
|
||||
This guide walks through how you can use these paid features in Infisical.
|
||||
|
||||
<Steps>
|
||||
<Step title="Purchase a license">
|
||||
Start by either signing up for a free demo [here](https://infisical.com/schedule-demo) or contacting sales@infisical.com to purchase a license.
|
||||
|
||||
Once purchased, you will be issued a license key.
|
||||
</Step>
|
||||
<Step title="Activate the license">
|
||||
Depending on whether or not the environment where Infisical is deployed has internet access, you may be issued a regular license or an offline license.
|
||||
|
||||
- If using a regular license, you should set the value of the environment variable `LICENSE_KEY` in Infisical to the issued license key.
|
||||
- If using an offline license, you should set the value of the environment variable `LICENSE_KEY_OFFLINE` in Infisical to the issued license key.
|
||||
|
||||
Once your instance starts up, the license key will be validated and you’ll be able to use the paid features.
|
||||
|
||||
<Note>
|
||||
Once the license expires, Infisical will continue to run, but EE features will be disabled until the license is renewed or a new one is purchased.
|
||||
</Note>
|
||||
</Step>
|
||||
</Steps>
|
@ -29,6 +29,7 @@ module.exports = {
|
||||
},
|
||||
plugins: ["react", "prettier", "simple-import-sort", "import"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
quotes: ["error", "double", { avoidEscape: true }],
|
||||
"comma-dangle": ["error", "only-multiline"],
|
||||
"react/react-in-jsx-scope": "off",
|
||||
@ -72,7 +73,6 @@ module.exports = {
|
||||
],
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"simple-import-sort/exports": "warn",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"simple-import-sort/imports": [
|
||||
"warn",
|
||||
{
|
||||
|
@ -1,28 +1,28 @@
|
||||
const path = require('path');
|
||||
const path = require("path");
|
||||
module.exports = {
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
||||
addons: [
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-interactions',
|
||||
'storybook-dark-mode',
|
||||
"@storybook/addon-links",
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/addon-interactions",
|
||||
"storybook-dark-mode",
|
||||
{
|
||||
name: '@storybook/addon-styling',
|
||||
name: "@storybook/addon-styling",
|
||||
options: {
|
||||
postCss: {
|
||||
implementation: require('postcss')
|
||||
implementation: require("postcss")
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/nextjs',
|
||||
name: "@storybook/nextjs",
|
||||
options: {}
|
||||
},
|
||||
core: {
|
||||
disableTelemetry: true
|
||||
},
|
||||
docs: {
|
||||
autodocs: 'tag'
|
||||
autodocs: "tag"
|
||||
}
|
||||
};
|
||||
|
@ -6,7 +6,7 @@ import { ENV, POSTHOG_API_KEY, POSTHOG_HOST } from "../utilities/config";
|
||||
|
||||
export const initPostHog = () => {
|
||||
// @ts-ignore
|
||||
console.log("Hi there 👋")
|
||||
console.log("Hi there 👋");
|
||||
try {
|
||||
if (typeof window !== "undefined") {
|
||||
// @ts-ignore
|
||||
@ -19,7 +19,7 @@ export const initPostHog = () => {
|
||||
|
||||
return posthog;
|
||||
} catch (e) {
|
||||
console.log("posthog err", e)
|
||||
console.log("posthog err", e);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
@ -3,9 +3,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
const Error = ({ text }: { text: string }): JSX.Element => {
|
||||
return (
|
||||
<div className="relative flex flex-row justify-center m-auto items-center w-fit rounded-full">
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} className="text-red mt-1.5 mb-2 mx-2" />
|
||||
{text && <p className="relative top-0 text-red mr-2 text-sm py-1">{text}</p>}
|
||||
<div className="relative m-auto flex w-fit flex-row items-center justify-center rounded-full">
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} className="mx-2 mt-1.5 mb-2 text-red" />
|
||||
{text && <p className="relative top-0 mr-2 py-1 text-sm text-red">{text}</p>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -39,16 +39,16 @@ const InputField = ({
|
||||
|
||||
if (isStatic === true) {
|
||||
return (
|
||||
<div className="flex flex-col my-2 md:my-4 justify-center w-full max-w-md">
|
||||
<p className="text-sm font-semibold text-gray-400 mb-0.5">{label}</p>
|
||||
{text && <p className="text-xs text-gray-400 mb-2">{text}</p>}
|
||||
<div className="my-2 flex w-full max-w-md flex-col justify-center md:my-4">
|
||||
<p className="mb-0.5 text-sm font-semibold text-gray-400">{label}</p>
|
||||
{text && <p className="mb-2 text-xs text-gray-400">{text}</p>}
|
||||
<input
|
||||
onChange={(e) => onChangeHandler(e.target.value)}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
required={isRequired}
|
||||
className="bg-bunker-800 text-gray-400 border border-gray-600 rounded-md text-md p-2 w-full min-w-16 outline-none"
|
||||
className="text-md min-w-16 w-full rounded-md border border-gray-600 bg-bunker-800 p-2 text-gray-400 outline-none"
|
||||
name={name}
|
||||
readOnly
|
||||
autoComplete={autoComplete}
|
||||
@ -58,12 +58,12 @@ const InputField = ({
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="flex-col w-full">
|
||||
<div className="flex flex-row text-mineshaft-300 items-center mb-0.5">
|
||||
<p className="text-sm font-semibold mr-1">{label}</p>
|
||||
<div className="w-full flex-col">
|
||||
<div className="mb-0.5 flex flex-row items-center text-mineshaft-300">
|
||||
<p className="mr-1 text-sm font-semibold">{label}</p>
|
||||
</div>
|
||||
<div
|
||||
className={`group relative flex flex-col justify-center w-full max-w-2xl border ${
|
||||
className={`group relative flex w-full max-w-2xl flex-col justify-center border ${
|
||||
error ? "border-red" : "border-mineshaft-500"
|
||||
} rounded-md`}
|
||||
>
|
||||
@ -75,11 +75,11 @@ const InputField = ({
|
||||
required={isRequired}
|
||||
className={`${
|
||||
blurred
|
||||
? "text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400"
|
||||
? "text-bunker-800 focus:text-gray-400 active:text-gray-400 group-hover:text-gray-400"
|
||||
: ""
|
||||
} ${
|
||||
error ? "focus:ring-red/50" : "focus:ring-primary/50"
|
||||
} relative peer bg-mineshaft-900 rounded-md text-gray-400 text-md p-2 w-full min-w-16 outline-none focus:ring-4 duration-200`}
|
||||
} text-md min-w-16 peer relative w-full rounded-md bg-mineshaft-900 p-2 text-gray-400 outline-none duration-200 focus:ring-4`}
|
||||
name={name}
|
||||
spellCheck="false"
|
||||
autoComplete={autoComplete}
|
||||
@ -91,7 +91,7 @@ const InputField = ({
|
||||
onClick={() => {
|
||||
setPasswordVisible(!passwordVisible);
|
||||
}}
|
||||
className="absolute self-end mr-3 text-gray-400 cursor-pointer"
|
||||
className="absolute mr-3 cursor-pointer self-end text-gray-400"
|
||||
>
|
||||
{passwordVisible ? (
|
||||
<FontAwesomeIcon icon={faEyeSlash} />
|
||||
@ -101,7 +101,7 @@ const InputField = ({
|
||||
</button>
|
||||
)}
|
||||
{blurred && (
|
||||
<div className="peer group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible absolute h-10 w-fit max-w-xl rounded-md flex items-center text-gray-400/50 text-clip overflow-hidden">
|
||||
<div className="peer absolute flex h-10 w-fit max-w-xl items-center overflow-hidden text-clip rounded-md text-gray-400/50 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible">
|
||||
<p className="ml-2" />
|
||||
{value
|
||||
.split("")
|
||||
@ -109,7 +109,7 @@ const InputField = ({
|
||||
.map(() => (
|
||||
<FontAwesomeIcon
|
||||
key={guidGenerator()}
|
||||
className="text-xxs mx-0.5"
|
||||
className="mx-0.5 text-xxs"
|
||||
icon={faCircle}
|
||||
/>
|
||||
))}
|
||||
@ -121,7 +121,7 @@ const InputField = ({
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
{error && <p className="text-red text-xs mt-0.5 mx-0 mb-2 max-w-xs">{errorText}</p>}
|
||||
{error && <p className="mx-0 mt-0.5 mb-2 max-w-xs text-xs text-red">{errorText}</p>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -34,19 +34,19 @@ const ListBox = ({
|
||||
<Listbox value={isSelected} onChange={onChange}>
|
||||
<div className="relative">
|
||||
<Listbox.Button
|
||||
className={`text-gray-400 relative ${
|
||||
className={`relative text-gray-400 ${
|
||||
isFull ? "w-full" : "w-52"
|
||||
} cursor-default rounded-md bg-white/[0.07] hover:bg-white/[0.11] duration-200 py-2.5 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm`}
|
||||
} focus-visible:ring-offset-orange-300 cursor-default rounded-md bg-white/[0.07] py-2.5 pl-3 pr-10 text-left shadow-md duration-200 hover:bg-white/[0.11] focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 sm:text-sm`}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
{text}
|
||||
<span className="ml-1 cursor-pointer block truncate font-semibold text-gray-300">
|
||||
<span className="ml-1 block cursor-pointer truncate font-semibold text-gray-300">
|
||||
{" "}
|
||||
{isSelected}
|
||||
</span>
|
||||
</div>
|
||||
{data && (
|
||||
<div className="cursor-pointer pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<div className="pointer-events-none absolute inset-y-0 right-0 flex cursor-pointer items-center pr-2">
|
||||
<FontAwesomeIcon icon={faAngleDown} className="text-md mr-1.5" />
|
||||
</div>
|
||||
)}
|
||||
@ -58,16 +58,16 @@ const ListBox = ({
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="border border-mineshaft-700 z-[70] p-2 absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-bunker text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm no-scrollbar no-scrollbar::-webkit-scrollbar">
|
||||
<Listbox.Options className="no-scrollbar::-webkit-scrollbar absolute z-[70] mt-1 max-h-60 w-full overflow-auto rounded-md border border-mineshaft-700 bg-bunker p-2 text-base shadow-lg ring-1 ring-black ring-opacity-5 no-scrollbar focus:outline-none sm:text-sm">
|
||||
{data.map((person, personIdx) => (
|
||||
<Listbox.Option
|
||||
key={`${person}.${personIdx + 1}`}
|
||||
className={({ active, selected }) =>
|
||||
`my-0.5 relative cursor-default select-none py-2 pl-10 pr-4 rounded-md ${
|
||||
selected ? "bg-white/10 text-gray-400 font-bold" : ""
|
||||
`relative my-0.5 cursor-default select-none rounded-md py-2 pl-10 pr-4 ${
|
||||
selected ? "bg-white/10 font-bold text-gray-400" : ""
|
||||
} ${
|
||||
active && !selected
|
||||
? "bg-white/5 text-mineshaft-200 cursor-pointer"
|
||||
? "cursor-pointer bg-white/5 text-mineshaft-200"
|
||||
: "text-gray-400"
|
||||
} `
|
||||
}
|
||||
@ -83,7 +83,7 @@ const ListBox = ({
|
||||
{person}
|
||||
</span>
|
||||
{selected ? (
|
||||
<span className="text-primary rounded-lg absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<span className="absolute inset-y-0 left-0 flex items-center rounded-lg pl-3 text-primary">
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md ml-1" />
|
||||
</span>
|
||||
) : null}
|
||||
@ -92,9 +92,9 @@ const ListBox = ({
|
||||
</Listbox.Option>
|
||||
))}
|
||||
{buttonAction && (
|
||||
<button type="button" onClick={buttonAction} className="cursor-pointer w-full">
|
||||
<div className="my-0.5 relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-lime-300 duration-200 hover:text-black hover:font-semibold mt-2">
|
||||
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
|
||||
<button type="button" onClick={buttonAction} className="w-full cursor-pointer">
|
||||
<div className="relative my-0.5 mt-2 flex cursor-pointer select-none justify-start rounded-md py-2 pl-10 pr-4 text-gray-400 duration-200 hover:bg-lime-300 hover:font-semibold hover:text-black">
|
||||
<span className="absolute inset-y-0 left-0 flex items-center rounded-lg pl-3 pr-4">
|
||||
<FontAwesomeIcon icon={faPlus} className="text-lg" />
|
||||
</span>
|
||||
Add Project
|
||||
|
@ -43,7 +43,7 @@ const Button = ({
|
||||
loading,
|
||||
icon,
|
||||
iconDisabled,
|
||||
type = "button",
|
||||
type = "button"
|
||||
}: ButtonProps): JSX.Element => {
|
||||
// Check if the button show always be 'active' - then true;
|
||||
// or if it should switch between 'active' and 'disabled' - then give the status
|
||||
@ -53,9 +53,13 @@ const Button = ({
|
||||
"group m-auto md:m-0 inline-block rounded-md duration-200",
|
||||
|
||||
// Setting background colors and hover modes
|
||||
color === "mineshaft" && activityStatus && "bg-mineshaft-800 border border-mineshaft-600 hover:bg-primary/[0.15] hover:border-primary/60",
|
||||
color === "mineshaft" &&
|
||||
activityStatus &&
|
||||
"bg-mineshaft-800 border border-mineshaft-600 hover:bg-primary/[0.15] hover:border-primary/60",
|
||||
color === "mineshaft" && !activityStatus && "bg-mineshaft",
|
||||
(color === "primary" || !color) && activityStatus && "bg-primary border border-primary-400 opacity-80 hover:opacity-100",
|
||||
(color === "primary" || !color) &&
|
||||
activityStatus &&
|
||||
"bg-primary border border-primary-400 opacity-80 hover:opacity-100",
|
||||
(color === "primary" || !color) && !activityStatus && "bg-primary",
|
||||
color === "red" && "bg-red-800 border border-red",
|
||||
|
||||
@ -78,7 +82,9 @@ const Button = ({
|
||||
color !== "mineshaft" && color !== "red" && color !== "none" && "text-black",
|
||||
color === "red" && "text-gray-200",
|
||||
color === "none" && "text-gray-200 text-xl",
|
||||
activityStatus && color !== "red" && color !== "mineshaft" && color !== "none" ? "group-hover:text-black" : "",
|
||||
activityStatus && color !== "red" && color !== "mineshaft" && color !== "none"
|
||||
? "group-hover:text-black"
|
||||
: "",
|
||||
|
||||
size === "icon" && "flex items-center justify-center"
|
||||
);
|
||||
@ -103,7 +109,7 @@ const Button = ({
|
||||
<div
|
||||
className={`${
|
||||
loading === true ? "opacity-100" : "opacity-0"
|
||||
} absolute flex items-center px-3 bg-primary duration-200 w-full`}
|
||||
} absolute flex w-full items-center bg-primary px-3 duration-200`}
|
||||
>
|
||||
<Image
|
||||
src="/images/loading/loadingblack.gif"
|
||||
@ -116,7 +122,7 @@ const Button = ({
|
||||
{icon && (
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
className={`flex my-auto font-extrabold ${size === "icon-sm" ? "text-sm" : "text-sm"} ${
|
||||
className={`my-auto flex font-extrabold ${size === "icon-sm" ? "text-sm" : "text-sm"} ${
|
||||
(text || textDisabled) && "mr-2"
|
||||
}`}
|
||||
/>
|
||||
@ -124,7 +130,7 @@ const Button = ({
|
||||
{iconDisabled && (
|
||||
<FontAwesomeIcon
|
||||
icon={iconDisabled as IconProp}
|
||||
className={`flex my-auto font-extrabold ${size === "icon-sm" ? "text-sm" : "text-md"} ${
|
||||
className={`my-auto flex font-extrabold ${size === "icon-sm" ? "text-sm" : "text-md"} ${
|
||||
(text || textDisabled) && "mr-2"
|
||||
}`}
|
||||
/>
|
||||
|
@ -64,7 +64,7 @@ const AddProjectMemberDialog = ({
|
||||
) : (
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="z-50 text-lg font-medium text-mineshaft-300 mb-4"
|
||||
className="z-50 mb-4 text-lg font-medium text-mineshaft-300"
|
||||
>
|
||||
{t("section.members.add-dialog.already-all-invited")}
|
||||
</Dialog.Title>
|
||||
@ -127,7 +127,9 @@ const AddProjectMemberDialog = ({
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
onButtonPressed={() => router.push(`/org/${localStorage.getItem("orgData.id")}/members`)}
|
||||
onButtonPressed={() =>
|
||||
router.push(`/org/${localStorage.getItem("orgData.id")}/members`)
|
||||
}
|
||||
color="mineshaft"
|
||||
text={t("section.members.add-dialog.add-user-to-org") as string}
|
||||
size="md"
|
||||
|
@ -28,11 +28,11 @@ export const AddUpdateEnvironmentDialog = ({
|
||||
onCreateSubmit,
|
||||
onEditSubmit,
|
||||
initialValues,
|
||||
isEditMode,
|
||||
isEditMode
|
||||
}: Props) => {
|
||||
const [formInput, setFormInput] = useState<FormFields>({
|
||||
name: "",
|
||||
slug: "",
|
||||
slug: ""
|
||||
});
|
||||
|
||||
// This use effect can be removed when the unmount is happening from outside the component
|
||||
@ -50,7 +50,7 @@ export const AddUpdateEnvironmentDialog = ({
|
||||
e.preventDefault();
|
||||
const data = {
|
||||
name: formInput.name,
|
||||
slug: formInput.slug.toLowerCase(),
|
||||
slug: formInput.slug.toLowerCase()
|
||||
};
|
||||
if (isEditMode) {
|
||||
onEditSubmit(data);
|
||||
@ -62,75 +62,70 @@ export const AddUpdateEnvironmentDialog = ({
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as='div' className='relative z-20' onClose={onClose}>
|
||||
<Dialog as="div" className="relative z-20" onClose={onClose}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0'
|
||||
enterTo='opacity-100'
|
||||
leave='ease-out duration-150'
|
||||
leaveFrom='opacity-100'
|
||||
leaveTo='opacity-0'
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-out duration-150"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className='fixed inset-0 bg-black bg-opacity-70' />
|
||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className='fixed inset-0 overflow-y-auto z-50'>
|
||||
<div className='flex min-h-full items-center justify-center p-4 text-center'>
|
||||
<div className="fixed inset-0 z-50 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0 scale-95'
|
||||
enterTo='opacity-100 scale-100'
|
||||
leave='ease-in duration-200'
|
||||
leaveFrom='opacity-100 scale-100'
|
||||
leaveTo='opacity-0 scale-95'
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className='w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all'>
|
||||
<Dialog.Title
|
||||
as='h3'
|
||||
className='text-lg font-medium leading-6 text-gray-400'
|
||||
>
|
||||
{isEditMode
|
||||
? "Update environment"
|
||||
: "Create a new environment"}
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
|
||||
{isEditMode ? "Update environment" : "Create a new environment"}
|
||||
</Dialog.Title>
|
||||
<form onSubmit={onFormSubmit}>
|
||||
<div className='max-h-28 mt-4'>
|
||||
<div className="mt-4 max-h-28">
|
||||
<InputField
|
||||
label='Environment Name'
|
||||
label="Environment Name"
|
||||
onChangeHandler={(val) => onInputChange("name", val)}
|
||||
type='varName'
|
||||
type="varName"
|
||||
value={formInput.name}
|
||||
placeholder=''
|
||||
placeholder=""
|
||||
isRequired
|
||||
// error={error.length > 0}
|
||||
// errorText={error}
|
||||
/>
|
||||
</div>
|
||||
<div className='max-h-28 mt-4'>
|
||||
<div className="mt-4 max-h-28">
|
||||
<InputField
|
||||
label='Environment Slug'
|
||||
label="Environment Slug"
|
||||
onChangeHandler={(val) => onInputChange("slug", val)}
|
||||
type='varName'
|
||||
type="varName"
|
||||
value={formInput.slug}
|
||||
placeholder=''
|
||||
placeholder=""
|
||||
isRequired
|
||||
// error={error.length > 0}
|
||||
// errorText={error}
|
||||
/>
|
||||
</div>
|
||||
<p className='text-xs text-gray-500 mt-2'>
|
||||
<p className="mt-2 text-xs text-gray-500">
|
||||
Slugs are shorthands used in cli to access environment
|
||||
</p>
|
||||
<div className='mt-4 max-w-min'>
|
||||
<div className="mt-4 max-w-min">
|
||||
<Button
|
||||
onButtonPressed={() => null}
|
||||
type='submit'
|
||||
color='mineshaft'
|
||||
type="submit"
|
||||
color="mineshaft"
|
||||
text={isEditMode ? "Update" : "Create"}
|
||||
active={formInput.name !== "" && formInput.slug !== ""}
|
||||
size='md'
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -13,76 +13,63 @@ type Props = {
|
||||
orgName: string;
|
||||
};
|
||||
|
||||
const AddUserDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
email,
|
||||
setEmail,
|
||||
orgName,
|
||||
}: Props) => {
|
||||
const AddUserDialog = ({ isOpen, closeModal, submitModal, email, setEmail, orgName }: Props) => {
|
||||
const submit = () => {
|
||||
submitModal(email);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='z-50'>
|
||||
<div className="z-50">
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as='div' className='relative' onClose={closeModal}>
|
||||
<Dialog as="div" className="relative" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0'
|
||||
enterTo='opacity-100'
|
||||
leave='ease-in duration-200'
|
||||
leaveFrom='opacity-100'
|
||||
leaveTo='opacity-0'
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className='fixed inset-0 bg-black bg-opacity-70' />
|
||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className='fixed inset-0 overflow-y-auto'>
|
||||
<div className='flex min-h-full items-center justify-center p-4 text-center'>
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0 scale-95'
|
||||
enterTo='opacity-100 scale-100'
|
||||
leave='ease-in duration-200'
|
||||
leaveFrom='opacity-100 scale-100'
|
||||
leaveTo='opacity-0 scale-95'
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className='w-full max-w-lg transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all'>
|
||||
<Dialog.Panel className="w-full max-w-lg transform overflow-hidden rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as='h3'
|
||||
className='text-lg font-medium leading-6 text-gray-400 z-50'
|
||||
as="h3"
|
||||
className="z-50 text-lg font-medium leading-6 text-gray-400"
|
||||
>
|
||||
Invite others to {orgName}
|
||||
</Dialog.Title>
|
||||
<div className='mt-2 mb-4'>
|
||||
<p className='text-sm text-gray-500'>
|
||||
An invite is specific to an email address and expires
|
||||
after 1 day. For security reasons, you will need to
|
||||
separately add members to projects.
|
||||
<div className="mt-2 mb-4">
|
||||
<p className="text-sm text-gray-500">
|
||||
An invite is specific to an email address and expires after 1 day. For
|
||||
security reasons, you will need to separately add members to projects.
|
||||
</p>
|
||||
</div>
|
||||
<div className='max-h-28'>
|
||||
<div className="max-h-28">
|
||||
<InputField
|
||||
label='Email'
|
||||
label="Email"
|
||||
onChangeHandler={setEmail}
|
||||
type='varName'
|
||||
type="varName"
|
||||
value={email}
|
||||
placeholder=''
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className='mt-4 max-w-max'>
|
||||
<Button
|
||||
onButtonPressed={submit}
|
||||
color='mineshaft'
|
||||
text='Invite'
|
||||
size='md'
|
||||
/>
|
||||
<div className="mt-4 max-w-max">
|
||||
<Button onButtonPressed={submit} color="mineshaft" text="Invite" size="md" />
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
{/* <Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||
|
@ -5,7 +5,6 @@ import Button from "../buttons/Button";
|
||||
import InputField from "../InputField";
|
||||
import { Checkbox } from "../table/Checkbox";
|
||||
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
closeModal: () => void;
|
||||
@ -26,8 +25,8 @@ const AddWorkspaceDialog = ({
|
||||
workspaceName,
|
||||
setWorkspaceName,
|
||||
error,
|
||||
loading,
|
||||
}:Props) => {
|
||||
loading
|
||||
}: Props) => {
|
||||
const [addAllUsers, setAddAllUsers] = useState(true);
|
||||
const submit = () => {
|
||||
submitModal(workspaceName, addAllUsers);
|
||||
@ -60,11 +59,8 @@ const AddWorkspaceDialog = ({
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-400"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
|
||||
Create a new project
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
@ -72,7 +68,7 @@ const AddWorkspaceDialog = ({
|
||||
This project will contain your secrets and configs.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-h-28 mt-4">
|
||||
<div className="mt-4 max-h-28">
|
||||
<InputField
|
||||
label="Project Name"
|
||||
onChangeHandler={setWorkspaceName}
|
||||
@ -84,10 +80,7 @@ const AddWorkspaceDialog = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 ml-1">
|
||||
<Checkbox
|
||||
addAllUsers={addAllUsers}
|
||||
setAddAllUsers={setAddAllUsers}
|
||||
/>
|
||||
<Checkbox addAllUsers={addAllUsers} setAddAllUsers={setAddAllUsers} />
|
||||
</div>
|
||||
<div className="mt-4 max-w-min">
|
||||
<Button
|
||||
|
@ -3,89 +3,76 @@ import { Dialog, Transition } from "@headlessui/react";
|
||||
|
||||
import InputField from "../InputField";
|
||||
|
||||
// REFACTOR: Move all these modals into one reusable one
|
||||
// REFACTOR: Move all these modals into one reusable one
|
||||
type Props = {
|
||||
isOpen?: boolean;
|
||||
onClose: ()=>void;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
onSubmit:()=>void;
|
||||
deleteKey?:string;
|
||||
}
|
||||
onSubmit: () => void;
|
||||
deleteKey?: string;
|
||||
};
|
||||
|
||||
const DeleteActionModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
title,
|
||||
onSubmit,
|
||||
deleteKey
|
||||
}:Props) => {
|
||||
const [deleteInputField, setDeleteInputField] = useState("")
|
||||
const DeleteActionModal = ({ isOpen, onClose, title, onSubmit, deleteKey }: Props) => {
|
||||
const [deleteInputField, setDeleteInputField] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setDeleteInputField("");
|
||||
}, [isOpen]);
|
||||
useEffect(() => {
|
||||
setDeleteInputField("");
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as='div' className='relative z-10' onClose={onClose}>
|
||||
<Dialog as="div" className="relative z-10" onClose={onClose}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0'
|
||||
enterTo='opacity-100'
|
||||
leave='ease-in duration-150'
|
||||
leaveFrom='opacity-100'
|
||||
leaveTo='opacity-0'
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-150"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className='fixed inset-0 bg-black bg-opacity-70' />
|
||||
<div className="fixed inset-0 bg-black bg-opacity-70" />
|
||||
</Transition.Child>
|
||||
<div className='fixed inset-0 overflow-y-auto'>
|
||||
<div className='flex min-h-full items-center justify-center p-4 text-center'>
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0 scale-95'
|
||||
enterTo='opacity-100 scale-100'
|
||||
leave='ease-in duration-200'
|
||||
leaveFrom='opacity-100 scale-100'
|
||||
leaveTo='opacity-0 scale-95'
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className='w-full max-w-md transform overflow-hidden rounded-md bg-grey border border-gray-700 p-6 text-left align-middle shadow-xl transition-all'>
|
||||
<Dialog.Title
|
||||
as='h3'
|
||||
className='text-lg font-medium leading-6 text-gray-400'
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-gray-700 bg-grey p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
|
||||
{title}
|
||||
</Dialog.Title>
|
||||
<div className='mt-2'>
|
||||
<p className='text-sm text-gray-500'>
|
||||
This action is irrevertible.
|
||||
</p>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">This action is irrevertible.</p>
|
||||
</div>
|
||||
<div className='mt-2'>
|
||||
<div className="mt-2">
|
||||
<InputField
|
||||
isRequired
|
||||
label={`Type ${deleteKey} to delete the resource`}
|
||||
onChangeHandler={(val) => setDeleteInputField(val)}
|
||||
value={deleteInputField}
|
||||
type='text'
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div className='mt-6'>
|
||||
<div className="mt-6">
|
||||
<button
|
||||
type='button'
|
||||
className='inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:bg-alizarin hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
|
||||
type="button"
|
||||
className="hover:bg-alizarin hover:text-semibold inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={onSubmit}
|
||||
disabled={
|
||||
Boolean(deleteKey) && deleteInputField !== deleteKey
|
||||
}
|
||||
disabled={Boolean(deleteKey) && deleteInputField !== deleteKey}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<button
|
||||
type='button'
|
||||
className='ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:border-white hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
|
||||
type="button"
|
||||
className="hover:text-semibold ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:border-white hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
|
@ -5,13 +5,13 @@ import { Dialog, Transition } from "@headlessui/react";
|
||||
// #TODO: USE THIS. Currently it's not. Kinda complicated to set up because of state.
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onSubmit: () => void
|
||||
}
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: () => void;
|
||||
};
|
||||
|
||||
export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
@ -45,7 +45,7 @@ export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => {
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker border border-mineshaft-600 p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-mineshaft-600 bg-bunker p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-bunker-200">
|
||||
{t("dashboard:sidebar.delete-key-dialog.title")}
|
||||
</Dialog.Title>
|
||||
@ -57,14 +57,14 @@ export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => {
|
||||
<div className="mt-6 flex justify-start">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border border-transparent bg-red-500 opacity-80 hover:opacity-100 px-4 py-2 text-sm font-medium text-bunker-100 text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
className="text-semibold inline-flex justify-center rounded-md border border-transparent bg-red-500 px-4 py-2 text-sm font-medium text-bunker-100 opacity-80 duration-200 hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={onSubmit}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-2 inline-flex justify-center rounded-md border border-transparent bg-bunker-500 px-4 py-2 text-sm font-medium text-gray-400 hover:bg-mineshaft-500 hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
className="hover:text-semibold ml-2 inline-flex justify-center rounded-md border border-transparent bg-bunker-500 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:bg-mineshaft-500 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
|
@ -10,66 +10,55 @@ type Props = {
|
||||
userIdToBeDeleted: string;
|
||||
};
|
||||
|
||||
const DeleteUserDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
submitModal,
|
||||
userIdToBeDeleted,
|
||||
}: Props) => {
|
||||
const DeleteUserDialog = ({ isOpen, closeModal, submitModal, userIdToBeDeleted }: Props) => {
|
||||
const submit = () => {
|
||||
submitModal(userIdToBeDeleted);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as='div' className='relative z-10' onClose={closeModal}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0'
|
||||
enterTo='opacity-100'
|
||||
leave='ease-in duration-200'
|
||||
leaveFrom='opacity-100'
|
||||
leaveTo='opacity-0'
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className='fixed inset-0 bg-black bg-opacity-25' />
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className='fixed inset-0 overflow-y-auto'>
|
||||
<div className='flex min-h-full items-center justify-center p-4 text-center'>
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter='ease-out duration-300'
|
||||
enterFrom='opacity-0 scale-95'
|
||||
enterTo='opacity-100 scale-100'
|
||||
leave='ease-in duration-200'
|
||||
leaveFrom='opacity-100 scale-100'
|
||||
leaveTo='opacity-0 scale-95'
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className='w-full max-w-md transform overflow-hidden rounded-2xl bg-grey border border-gray-700 p-6 text-left align-middle shadow-xl transition-all'>
|
||||
<Dialog.Title
|
||||
as='h3'
|
||||
className='text-lg font-medium leading-6 text-gray-400'
|
||||
>
|
||||
Are you sure you want to remove this user from the
|
||||
workspace?
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl border border-gray-700 bg-grey p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
|
||||
Are you sure you want to remove this user from the workspace?
|
||||
</Dialog.Title>
|
||||
<div className='mt-2'>
|
||||
<p className='text-sm text-gray-500'>
|
||||
This action is irrevertible.
|
||||
</p>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">This action is irrevertible.</p>
|
||||
</div>
|
||||
<div className='mt-6'>
|
||||
<div className="mt-6">
|
||||
<button
|
||||
type='button'
|
||||
className='inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:bg-alizarin hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
|
||||
type="button"
|
||||
className="hover:bg-alizarin hover:text-semibold inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={submit}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<button
|
||||
type='button'
|
||||
className='ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 hover:border-white hover:text-white hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
|
||||
type="button"
|
||||
className="hover:text-semibold ml-2 inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-sm font-medium text-gray-400 duration-200 hover:border-white hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={submit}
|
||||
>
|
||||
Cancel
|
||||
|
@ -34,27 +34,27 @@ const BottonRightPopup = ({
|
||||
}: PopupProps): JSX.Element => {
|
||||
return (
|
||||
<div
|
||||
className="z-[100] drop-shadow-xl border-gray-600/50 border flex flex-col items-start bg-bunker max-w-xl text-gray-200 pt-3 pb-4 rounded-md absolute bottom-0 right-0 mr-6 mb-6"
|
||||
className="absolute bottom-0 right-0 z-[100] mr-6 mb-6 flex max-w-xl flex-col items-start rounded-md border border-gray-600/50 bg-bunker pt-3 pb-4 text-gray-200 drop-shadow-xl"
|
||||
role="alert"
|
||||
>
|
||||
<div className="flex flex-row items-center justify-between w-full border-b border-gray-600/70 pb-3 px-6">
|
||||
<div className="font-bold text-xl mr-2 mt-0.5 flex flex-row">
|
||||
<div className="flex w-full flex-row items-center justify-between border-b border-gray-600/70 px-6 pb-3">
|
||||
<div className="mr-2 mt-0.5 flex flex-row text-xl font-bold">
|
||||
<div>{titleText}</div>
|
||||
<div className="ml-2.5">{emoji}</div>
|
||||
</div>
|
||||
<button className="mt-1" onClick={() => setCheckDocsPopUpVisible(false)} type="button">
|
||||
<FontAwesomeIcon
|
||||
icon={faXmark}
|
||||
className="text-gray-400 text-2xl hover:text-red duration-200 cursor-pointer"
|
||||
className="cursor-pointer text-2xl text-gray-400 duration-200 hover:text-red"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="block sm:inline px-6 mt-4 mb-0.5 text-gray-300">{textLine1}</div>
|
||||
<div className="block sm:inline mb-4 px-6">{textLine2}</div>
|
||||
<div className="flex flex-row px-6 w-full">
|
||||
<div className="mt-4 mb-0.5 block px-6 text-gray-300 sm:inline">{textLine1}</div>
|
||||
<div className="mb-4 block px-6 sm:inline">{textLine2}</div>
|
||||
<div className="flex w-full flex-row px-6">
|
||||
{/* eslint-disable-next-line react/jsx-no-target-blank */}
|
||||
<a
|
||||
className="font-bold p-2 bg-white/10 rounded-md w-full hover:bg-primary duration-200 hover:text-black flex justify-center"
|
||||
className="flex w-full justify-center rounded-md bg-white/10 p-2 font-bold duration-200 hover:bg-primary hover:text-black"
|
||||
href={buttonLink}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
|
@ -9,7 +9,7 @@ export const Checkbox = ({ addAllUsers, setAddAllUsers }: Props) => (
|
||||
{addAllUsers === true ? (
|
||||
<input
|
||||
type="checkbox"
|
||||
className="accent-primary h-4 w-4"
|
||||
className="h-4 w-4 accent-primary"
|
||||
checked
|
||||
readOnly
|
||||
onClick={() => setAddAllUsers(!addAllUsers)}
|
||||
@ -20,12 +20,12 @@ export const Checkbox = ({ addAllUsers, setAddAllUsers }: Props) => (
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label="add all users"
|
||||
className="h-4 w-4 bg-bunker border border-gray-600 rounded-sm"
|
||||
className="h-4 w-4 rounded-sm border border-gray-600 bg-bunker"
|
||||
onClick={() => setAddAllUsers(!addAllUsers)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<label className="ml-2 text-gray-500 text-sm">
|
||||
<label className="ml-2 text-sm text-gray-500">
|
||||
Add all members of my organization to this project.
|
||||
</label>
|
||||
</div>
|
||||
|
@ -36,25 +36,28 @@ const Notification = ({ notification, clearNotification }: NotificationProps) =>
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full flex items-center justify-between px-6 py-4 rounded-md border border-bunker-500 pointer-events-auto bg-mineshaft-700 mb-3 right-3"
|
||||
className="pointer-events-auto relative right-3 mb-3 flex w-full items-center justify-between rounded-md border border-bunker-500 bg-mineshaft-700 px-6 py-4"
|
||||
role="alert"
|
||||
>
|
||||
{notification.type === "error" && (
|
||||
<div className="absolute w-full h-1 bg-red top-0 left-0 rounded-t-md" />
|
||||
<div className="absolute top-0 left-0 h-1 w-full rounded-t-md bg-red" />
|
||||
)}
|
||||
{notification.type === "success" && (
|
||||
<div className="absolute w-full h-1 bg-green top-0 left-0 rounded-t-md" />
|
||||
<div className="absolute top-0 left-0 h-1 w-full rounded-t-md bg-green" />
|
||||
)}
|
||||
{notification.type === "info" && (
|
||||
<div className="absolute w-full h-1 bg-yellow top-0 left-0 rounded-t-md" />
|
||||
<div className="absolute top-0 left-0 h-1 w-full rounded-t-md bg-yellow" />
|
||||
)}
|
||||
<p className="text-bunker-200 text-md font-base mt-0.5">{notification.text}</p>
|
||||
<p className="text-md font-base mt-0.5 text-bunker-200">{notification.text}</p>
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-lg"
|
||||
onClick={() => clearNotification(notification.text)}
|
||||
>
|
||||
<FontAwesomeIcon className="absolute right-2 top-3 text-bunker-300 pl-2 w-4 h-4 hover:text-white" icon={faXmark} />
|
||||
<FontAwesomeIcon
|
||||
className="absolute right-2 top-3 h-4 w-4 pl-2 text-bunker-300 hover:text-white"
|
||||
icon={faXmark}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
@ -11,7 +11,7 @@ const Notifications = ({ notifications, clearNotification }: NoticationsProps) =
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="hidden fixed z-50 md:flex md:flex-col-reverse gap-y-2 w-96 h-full right-2 bottom-2 pointer-events-none">
|
||||
<div className="pointer-events-none fixed right-2 bottom-2 z-[100] hidden h-full w-96 gap-y-2 md:flex md:flex-col-reverse">
|
||||
{notifications.map((notif) => (
|
||||
<Notification key={notif.text} notification={notif} clearNotification={clearNotification} />
|
||||
))}
|
||||
|
@ -30,9 +30,9 @@ const ConfirmEnvOverwriteModal = ({
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className='text-gray-400'>Your file contains the following duplicate secrets:</p>
|
||||
<p className="text-gray-400">Your file contains the following duplicate secrets:</p>
|
||||
<p className="text-sm text-gray-500">{duplicateKeys.join(", ")}</p>
|
||||
<p className='text-md text-gray-400'>Are you sure you want to overwrite these secrets?</p>
|
||||
<p className="text-md text-gray-400">Are you sure you want to overwrite these secrets?</p>
|
||||
</div>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
@ -1,5 +1,10 @@
|
||||
import { memo, SyntheticEvent, useRef } from "react";
|
||||
import { faCircle, faCodeBranch, faExclamationCircle, faEye } from "@fortawesome/free-solid-svg-icons";
|
||||
import {
|
||||
faCircle,
|
||||
faCodeBranch,
|
||||
faExclamationCircle,
|
||||
faEye
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import guidGenerator from "../utilities/randomId";
|
||||
@ -31,8 +36,8 @@ interface DashboardInputFieldProps {
|
||||
* @param {boolean} obj.blurred - whether the input field should be blurred (behind the gray dots) or not; this can be turned on/off in the dashboard
|
||||
* @param {boolean} obj.isDuplicate - if the key name is duplicated
|
||||
* @param {boolean} obj.override - whether a secret/row should be displalyed as overriden
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
|
||||
@ -61,29 +66,31 @@ const DashboardInputField = ({
|
||||
const error = startsWithNumber || isDuplicate;
|
||||
|
||||
return (
|
||||
<div className={`relative flex-col w-full h-10 ${
|
||||
error && value !== "" ? "bg-red/[0.15]" : ""
|
||||
} ${
|
||||
isSideBarOpen && "bg-mineshaft-700 duration-200"
|
||||
}`}>
|
||||
<div
|
||||
className={`relative h-10 w-full flex-col ${error && value !== "" ? "bg-red/[0.15]" : ""} ${
|
||||
isSideBarOpen && "bg-mineshaft-700 duration-200"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`group relative flex flex-col justify-center items-center h-full ${
|
||||
className={`group relative flex h-full flex-col items-center justify-center ${
|
||||
error ? "w-max" : "w-full"
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
onChange={(e) => onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, id)}
|
||||
onChange={(e) =>
|
||||
onChangeHandler(isCapitalized ? e.target.value.toUpperCase() : e.target.value, id)
|
||||
}
|
||||
type={type}
|
||||
value={value}
|
||||
className={`z-10 peer font-mono ph-no-capture bg-transparent h-full caret-bunker-200 text-sm px-2 w-full min-w-16 outline-none ${
|
||||
className={`ph-no-capture min-w-16 peer z-10 h-full w-full bg-transparent px-2 font-mono text-sm caret-bunker-200 outline-none ${
|
||||
error ? "text-red-600 focus:text-red-500" : "text-bunker-300 focus:text-bunker-100"
|
||||
} duration-200`}
|
||||
spellCheck="false"
|
||||
/>
|
||||
</div>
|
||||
{startsWithNumber && (
|
||||
<div className='absolute right-2 top-2 text-red z-50'>
|
||||
<HoverObject
|
||||
<div className="absolute right-2 top-2 z-50 text-red">
|
||||
<HoverObject
|
||||
text="Secret names should not start with a number"
|
||||
icon={faExclamationCircle}
|
||||
color="red"
|
||||
@ -91,33 +98,44 @@ const DashboardInputField = ({
|
||||
</div>
|
||||
)}
|
||||
{isDuplicate && value !== "" && !startsWithNumber && (
|
||||
<div className='absolute right-2 top-2 text-red z-50'>
|
||||
<HoverObject
|
||||
<div className="absolute right-2 top-2 z-50 text-red">
|
||||
<HoverObject
|
||||
text="Secret names should be unique"
|
||||
icon={faExclamationCircle}
|
||||
color="red"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!error && <div className={`absolute right-0 top-0 text-red z-50 bg-mineshaft-800 group-hover:bg-mineshaft-700 ${
|
||||
overrideEnabled ? "visible" : "invisible group-hover:visible"
|
||||
} cursor-pointer duration-0 h-10 flex items-center px-2`}>
|
||||
<button type="button" onClick={() => {
|
||||
if (modifyValueOverride) {
|
||||
if (overrideEnabled === false) {
|
||||
modifyValueOverride("", id);
|
||||
} else {
|
||||
modifyValueOverride(undefined, id);
|
||||
}
|
||||
}
|
||||
}}>
|
||||
<HoverObject
|
||||
text={overrideEnabled ? "This secret is overriden with your personal value" : "You can override this secret with a personal value"}
|
||||
icon={faCodeBranch}
|
||||
color={overrideEnabled ? "primary" : "bunker-400"}
|
||||
/>
|
||||
</button>
|
||||
</div>}
|
||||
{!error && (
|
||||
<div
|
||||
className={`absolute right-0 top-0 z-50 bg-mineshaft-800 text-red group-hover:bg-mineshaft-700 ${
|
||||
overrideEnabled ? "visible" : "invisible group-hover:visible"
|
||||
} duration-0 flex h-10 cursor-pointer items-center px-2`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (modifyValueOverride) {
|
||||
if (overrideEnabled === false) {
|
||||
modifyValueOverride("", id);
|
||||
} else {
|
||||
modifyValueOverride(undefined, id);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<HoverObject
|
||||
text={
|
||||
overrideEnabled
|
||||
? "This secret is overriden with your personal value"
|
||||
: "You can override this secret with a personal value"
|
||||
}
|
||||
icon={faCodeBranch}
|
||||
color={overrideEnabled ? "primary" : "bunker-400"}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -127,20 +145,29 @@ const DashboardInputField = ({
|
||||
|
||||
return (
|
||||
<PopoverObject text={value || ""} onChangeHandler={onChangeHandler} id={id}>
|
||||
<div title={value} className={`relative flex-col w-full h-10 overflow-hidden ${
|
||||
isSideBarOpen && "bg-mineshaft-700 duration-200"
|
||||
}`}>
|
||||
<div
|
||||
title={value}
|
||||
className={`relative h-10 w-full flex-col overflow-hidden ${
|
||||
isSideBarOpen && "bg-mineshaft-700 duration-200"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`group relative flex flex-col justify-center items-center h-full ${
|
||||
className={`group relative flex h-full flex-col items-center justify-center ${
|
||||
error ? "w-max" : "w-full"
|
||||
}`}
|
||||
>
|
||||
{value?.split("\n")[0] ? <span className='ph-no-capture truncate break-all bg-transparent leading-tight text-xs px-2 w-full min-w-16 outline-none text-bunker-300 focus:text-bunker-100 placeholder:text-bunker-400 placeholder:focus:text-transparent placeholder duration-200'>
|
||||
{value?.split("\n")[0]}
|
||||
</span> : <span className='text-bunker-400'>-</span> }
|
||||
{value?.split("\n")[1] && <span className='ph-no-capture truncate break-all bg-transparent leading-tight text-xs px-2 w-full min-w-16 outline-none text-bunker-300 focus:text-bunker-100 placeholder:text-bunker-400 placeholder:focus:text-transparent placeholder duration-200'>
|
||||
{value?.split("\n")[1]}
|
||||
</span>}
|
||||
{value?.split("\n")[0] ? (
|
||||
<span className="ph-no-capture min-w-16 placeholder w-full truncate break-all bg-transparent px-2 text-xs leading-tight text-bunker-300 outline-none duration-200 placeholder:text-bunker-400 focus:text-bunker-100 placeholder:focus:text-transparent">
|
||||
{value?.split("\n")[0]}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-bunker-400">-</span>
|
||||
)}
|
||||
{value?.split("\n")[1] && (
|
||||
<span className="ph-no-capture min-w-16 placeholder w-full truncate break-all bg-transparent px-2 text-xs leading-tight text-bunker-300 outline-none duration-200 placeholder:text-bunker-400 focus:text-bunker-100 placeholder:focus:text-transparent">
|
||||
{value?.split("\n")[1]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</PopoverObject>
|
||||
@ -148,10 +175,10 @@ const DashboardInputField = ({
|
||||
}
|
||||
if (type === "value") {
|
||||
return (
|
||||
<div className="flex-col w-full">
|
||||
<div className="group relative whitespace-pre flex flex-col justify-center w-full">
|
||||
<div className="w-full flex-col">
|
||||
<div className="group relative flex w-full flex-col justify-center whitespace-pre">
|
||||
{overrideEnabled === true && (
|
||||
<div className="bg-primary-500 rounded-sm absolute top-[0.1rem] right-[0.1rem] z-0 w-min text-xxs px-1 text-black opacity-80">
|
||||
<div className="absolute top-[0.1rem] right-[0.1rem] z-0 w-min rounded-sm bg-primary-500 px-1 text-xxs text-black opacity-80">
|
||||
Override enabled
|
||||
</div>
|
||||
)}
|
||||
@ -160,20 +187,20 @@ const DashboardInputField = ({
|
||||
onChange={(e) => onChangeHandler(e.target.value, id)}
|
||||
onScroll={syncScroll}
|
||||
className={`${
|
||||
blurred
|
||||
? "text-transparent focus:text-transparent active:text-transparent"
|
||||
: ""
|
||||
} z-10 peer font-mono ph-no-capture bg-transparent caret-white text-transparent text-sm px-2 py-2 w-full min-w-16 outline-none duration-200 no-scrollbar no-scrollbar::-webkit-scrollbar`}
|
||||
blurred ? "text-transparent focus:text-transparent active:text-transparent" : ""
|
||||
} ph-no-capture min-w-16 no-scrollbar::-webkit-scrollbar peer z-10 w-full bg-transparent px-2 py-2 font-mono text-sm text-transparent caret-white outline-none duration-200 no-scrollbar`}
|
||||
spellCheck="false"
|
||||
/>
|
||||
<div
|
||||
ref={ref}
|
||||
className={`${
|
||||
blurred && !overrideEnabled
|
||||
? "text-bunker-800 group-hover:text-gray-400 peer-focus:text-gray-100 peer-active:text-gray-400 duration-200"
|
||||
? "text-bunker-800 duration-200 group-hover:text-gray-400 peer-focus:text-gray-100 peer-active:text-gray-400"
|
||||
: ""
|
||||
} ${overrideEnabled ? "text-primary-300" : "text-gray-400"}
|
||||
absolute flex flex-row whitespace-pre font-mono z-0 ${blurred ? "invisible" : "visible"} peer-focus:visible mt-0.5 ph-no-capture overflow-x-scroll bg-transparent h-10 text-sm px-2 py-2 w-full min-w-16 outline-none duration-100 no-scrollbar no-scrollbar::-webkit-scrollbar`}
|
||||
absolute z-0 flex flex-row whitespace-pre font-mono ${
|
||||
blurred ? "invisible" : "visible"
|
||||
} ph-no-capture min-w-16 no-scrollbar::-webkit-scrollbar mt-0.5 h-10 w-full overflow-x-scroll bg-transparent px-2 py-2 text-sm outline-none duration-100 no-scrollbar peer-focus:visible`}
|
||||
>
|
||||
{value?.split(REGEX).map((word) => {
|
||||
if (word.match(REGEX) !== null) {
|
||||
@ -203,20 +230,24 @@ const DashboardInputField = ({
|
||||
})}
|
||||
</div>
|
||||
{blurred && (
|
||||
<div className={`absolute flex flex-row justify-between items-center z-0 peer pr-2 ${
|
||||
isSideBarOpen ? "bg-mineshaft-700 duration-200" : "bg-mineshaft-800"
|
||||
} peer-active:hidden peer-focus:hidden group-hover:bg-white/[0.00] duration-100 h-10 w-full text-bunker-400 text-clip`}>
|
||||
<div className="px-2 flex flex-row items-center overflow-x-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
|
||||
<div
|
||||
className={`peer absolute z-0 flex flex-row items-center justify-between pr-2 ${
|
||||
isSideBarOpen ? "bg-mineshaft-700 duration-200" : "bg-mineshaft-800"
|
||||
} h-10 w-full text-clip text-bunker-400 duration-100 group-hover:bg-white/[0.00] peer-focus:hidden peer-active:hidden`}
|
||||
>
|
||||
<div className="no-scrollbar::-webkit-scrollbar flex flex-row items-center overflow-x-scroll px-2 no-scrollbar">
|
||||
{value?.split("").map(() => (
|
||||
<FontAwesomeIcon
|
||||
key={guidGenerator()}
|
||||
className="text-xxs mr-0.5"
|
||||
className="mr-0.5 text-xxs"
|
||||
icon={faCircle}
|
||||
/>
|
||||
))}
|
||||
{value?.split("").length === 0 && <span className='text-bunker-400/80'>EMPTY</span>}
|
||||
{value?.split("").length === 0 && <span className="text-bunker-400/80">EMPTY</span>}
|
||||
</div>
|
||||
<div className="invisible z-[100] cursor-default group-hover:visible">
|
||||
<FontAwesomeIcon icon={faEye} />
|
||||
</div>
|
||||
<div className='invisible group-hover:visible cursor-default z-[100]'><FontAwesomeIcon icon={faEye} /></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from "react"
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@ -8,32 +8,35 @@ import Button from "../basic/buttons/Button";
|
||||
type Props = {
|
||||
onSubmit: () => void;
|
||||
isPlain?: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
export const DeleteActionButton = ({ onSubmit, isPlain }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={`${
|
||||
!isPlain
|
||||
? "bg-[#9B3535] opacity-70 hover:opacity-100 w-[4.5rem] h-[2.5rem] rounded-md duration-200 ml-2"
|
||||
: "cursor-pointer w-[1.5rem] h-[2.35rem] mr-2 flex items-center justfy-center"}`}>
|
||||
{isPlain
|
||||
? <div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={onSubmit}
|
||||
className="invisible group-hover:visible"
|
||||
>
|
||||
<FontAwesomeIcon className="text-bunker-300 hover:text-red pl-2 pr-6 text-lg mt-0.5" icon={faXmark} />
|
||||
</div>
|
||||
: <Button
|
||||
text={String(t("Delete"))}
|
||||
color="red"
|
||||
size="md"
|
||||
onButtonPressed={onSubmit}
|
||||
/>}
|
||||
<div
|
||||
className={`${
|
||||
!isPlain
|
||||
? "ml-2 h-[2.5rem] w-[4.5rem] rounded-md bg-[#9B3535] opacity-70 duration-200 hover:opacity-100"
|
||||
: "justfy-center mr-2 flex h-[2.35rem] w-[1.5rem] cursor-pointer items-center"
|
||||
}`}
|
||||
>
|
||||
{isPlain ? (
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={onSubmit}
|
||||
className="invisible group-hover:visible"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className="mt-0.5 pl-2 pr-6 text-lg text-bunker-300 hover:text-red"
|
||||
icon={faXmark}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Button text={String(t("Delete"))} color="red" size="md" onButtonPressed={onSubmit} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -18,7 +18,7 @@ const DownloadSecretMenu = ({ data, env }: { data: SecretDataProps[]; env: strin
|
||||
<Menu as="div" className="relative inline-block text-left">
|
||||
<Menu.Button
|
||||
as="div"
|
||||
className="inline-flex w-full justify-center text-sm font-medium text-gray-200 rounded-md hover:bg-white/10 duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"
|
||||
className="inline-flex w-full justify-center rounded-md text-sm font-medium text-gray-200 duration-200 hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"
|
||||
>
|
||||
<Button color="mineshaft" size="icon-md" icon={faDownload} onButtonPressed={() => {}} />
|
||||
</Menu.Button>
|
||||
@ -31,7 +31,7 @@ const DownloadSecretMenu = ({ data, env }: { data: SecretDataProps[]; env: strin
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute z-[90] drop-shadow-xl right-0 mt-0.5 w-[12rem] origin-top-right rounded-md bg-bunker border border-mineshaft-500 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none p-2 space-y-2">
|
||||
<Menu.Items className="absolute right-0 z-[90] mt-0.5 w-[12rem] origin-top-right space-y-2 rounded-md border border-mineshaft-500 bg-bunker p-2 shadow-lg ring-1 ring-black ring-opacity-5 drop-shadow-xl focus:outline-none">
|
||||
<Menu.Item>
|
||||
<Button
|
||||
color="mineshaft"
|
||||
|
@ -56,7 +56,7 @@ export default function NavHeader({
|
||||
|
||||
return (
|
||||
<div className="flex flex-row items-center pt-6">
|
||||
<div className="mr-2 flex h-5 w-5 items-center justify-center rounded-md bg-primary text-sm text-black min-w-[1.25rem]">
|
||||
<div className="mr-2 flex h-5 w-5 min-w-[1.25rem] items-center justify-center rounded-md bg-primary text-sm text-black">
|
||||
{currentOrg?.name?.charAt(0)}
|
||||
</div>
|
||||
<Link passHref legacyBehavior href={`/org/${currentOrg?.id}/overview`}>
|
||||
|
@ -6,7 +6,6 @@ import { useOrganization, useWorkspace } from "@app/context";
|
||||
|
||||
import { Select, SelectItem, Tooltip } from "../v2";
|
||||
|
||||
|
||||
/**
|
||||
* This is the component at the top of almost every page.
|
||||
* It shows how to navigate to a certain page.
|
||||
@ -39,10 +38,12 @@ export default function NavHeaderSecrets({
|
||||
}): JSX.Element {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { currentOrg } = useOrganization();
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className={`${!isSnapshot && "absolute"} ml-6 flex flex-row items-center pt-6 cursor-default`}>
|
||||
<div
|
||||
className={`${!isSnapshot && "absolute"} ml-6 flex cursor-default flex-row items-center pt-6`}
|
||||
>
|
||||
<div className="mr-3 flex h-6 w-6 items-center justify-center rounded-md bg-primary-900 text-mineshaft-100">
|
||||
{currentOrg?.name?.charAt(0)}
|
||||
</div>
|
||||
@ -60,31 +61,39 @@ export default function NavHeaderSecrets({
|
||||
</>
|
||||
)}
|
||||
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
|
||||
{pageName === "Secrets"
|
||||
? <a className="text-md font-medium text-primary/80 hover:text-primary" href={`${router.asPath.split("?")[0]}`}>{pageName}</a>
|
||||
: <div className="text-md text-gray-400">{pageName}</div>}
|
||||
{currentEnv &&
|
||||
<>
|
||||
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-sm text-gray-400" />
|
||||
<div className='pl-3 rounded-md hover:bg-bunker-100/10'>
|
||||
<Tooltip content="Select environment">
|
||||
<Select
|
||||
value={userAvailableEnvs?.filter(uae => uae.name === currentEnv)[0]?.slug}
|
||||
onValueChange={(value) => {
|
||||
if (value && onEnvChange) onEnvChange(value);
|
||||
}}
|
||||
className="text-md pl-0 font-medium text-primary/80 hover:text-primary bg-transparent"
|
||||
dropdownContainerClassName="text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 drop-shadow-2xl"
|
||||
>
|
||||
{userAvailableEnvs?.map(({ name, slug }) => (
|
||||
<SelectItem value={slug} key={slug}>
|
||||
{name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</>}
|
||||
{pageName === "Secrets" ? (
|
||||
<a
|
||||
className="text-md font-medium text-primary/80 hover:text-primary"
|
||||
href={`${router.asPath.split("?")[0]}`}
|
||||
>
|
||||
{pageName}
|
||||
</a>
|
||||
) : (
|
||||
<div className="text-md text-gray-400">{pageName}</div>
|
||||
)}
|
||||
{currentEnv && (
|
||||
<>
|
||||
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-sm text-gray-400" />
|
||||
<div className="rounded-md pl-3 hover:bg-bunker-100/10">
|
||||
<Tooltip content="Select environment">
|
||||
<Select
|
||||
value={userAvailableEnvs?.filter((uae) => uae.name === currentEnv)[0]?.slug}
|
||||
onValueChange={(value) => {
|
||||
if (value && onEnvChange) onEnvChange(value);
|
||||
}}
|
||||
className="text-md bg-transparent pl-0 font-medium text-primary/80 hover:text-primary"
|
||||
dropdownContainerClassName="text-bunker-200 bg-mineshaft-800 border border-mineshaft-600 drop-shadow-2xl"
|
||||
>
|
||||
{userAvailableEnvs?.map(({ name, slug }) => (
|
||||
<SelectItem value={slug} key={slug}>
|
||||
{name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -3,9 +3,7 @@ import React, { useState } from "react";
|
||||
import ReactCodeInput from "react-code-input";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import {
|
||||
useSendVerificationEmail
|
||||
} from "@app/hooks/api";
|
||||
import { useSendVerificationEmail } from "@app/hooks/api";
|
||||
|
||||
import Error from "../basic/Error";
|
||||
import { Button } from "../v2";
|
||||
@ -90,8 +88,8 @@ export default function CodeInputStep({
|
||||
return (
|
||||
<div className="mx-auto h-full w-full pb-4 md:px-8">
|
||||
<p className="text-md flex justify-center text-bunker-200">{t("signup.step2-message")}</p>
|
||||
<p className="text-md flex justify-center font-semibold my-1 text-bunker-200">{email} </p>
|
||||
<div className="hidden md:block w-max min-w-[20rem] mx-auto">
|
||||
<p className="text-md my-1 flex justify-center font-semibold text-bunker-200">{email} </p>
|
||||
<div className="mx-auto hidden w-max min-w-[20rem] md:block">
|
||||
<ReactCodeInput
|
||||
name=""
|
||||
inputMode="tel"
|
||||
@ -102,7 +100,7 @@ export default function CodeInputStep({
|
||||
className="mt-6 mb-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="block md:hidden w-max mt-4 mx-auto">
|
||||
<div className="mx-auto mt-4 block w-max md:hidden">
|
||||
<ReactCodeInput
|
||||
name=""
|
||||
inputMode="tel"
|
||||
@ -114,26 +112,29 @@ export default function CodeInputStep({
|
||||
/>
|
||||
</div>
|
||||
{codeError && <Error text={t("signup.step2-code-error")} />}
|
||||
<div className="flex flex-col items-center justify-center lg:w-[19%] w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left">
|
||||
<div className="text-l py-1 text-lg w-full">
|
||||
<div className="mx-auto mt-2 flex w-1/4 min-w-[20rem] max-w-xs flex-col items-center justify-center text-center text-sm md:max-w-md md:text-left lg:w-[19%]">
|
||||
<div className="text-l w-full py-1 text-lg">
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={incrementStep}
|
||||
size="sm"
|
||||
isFullWidth
|
||||
className='h-14'
|
||||
className="h-14"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
isLoading={isCodeInputCheckLoading}
|
||||
> {String(t("signup.verify"))} </Button>
|
||||
>
|
||||
{" "}
|
||||
{String(t("signup.verify"))}{" "}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2">
|
||||
<div className="mx-auto flex max-h-24 w-full max-w-md flex-col items-center justify-center pt-2">
|
||||
<div className="flex flex-row items-baseline gap-1 text-sm">
|
||||
<span className="text-bunker-400">{t("signup.step2-resend-alert")}</span>
|
||||
<div className="mt-2 text-bunker-400 text-md flex flex-row">
|
||||
<div className="text-md mt-2 flex flex-row text-bunker-400">
|
||||
<button disabled={isLoading} onClick={resendVerificationEmail} type="button">
|
||||
<span className='hover:underline hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer'>
|
||||
<span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
|
||||
{isResendingVerificationEmail
|
||||
? t("signup.step2-resend-progress")
|
||||
: t("signup.step2-resend-submit")}
|
||||
@ -141,7 +142,7 @@ export default function CodeInputStep({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-bunker-400 pb-2">{t("signup.step2-spam-alert")}</p>
|
||||
<p className="pb-2 text-sm text-bunker-400">{t("signup.step2-spam-alert")}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -57,19 +57,22 @@ 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 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" />
|
||||
<div className="mx-auto mb-36 flex h-full w-full flex-col items-center md:mb-16 md:px-6">
|
||||
<p className="flex flex-col items-center justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
||||
<FontAwesomeIcon
|
||||
icon={faWarning}
|
||||
className="ml-2 mr-3 mb-6 pt-1 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">
|
||||
<div className="w-full mt-4 md:mt-8 flex flex-row text-center items-center m-2 text-bunker-300 rounded-md lg:w-1/6 lg:w-1/6 w-full md:min-w-[23rem] px-3 mx-auto">
|
||||
<div className="text-md mt-8 flex w-full max-w-md flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 pb-2 text-center text-bunker-300 md:min-w-[24rem] lg:w-1/6">
|
||||
<div className="m-2 mx-auto mt-4 flex w-full w-full flex-row items-center rounded-md px-3 text-center text-bunker-300 md:mt-8 md:min-w-[23rem] lg:w-1/6 lg:w-1/6">
|
||||
<span className="mb-2">
|
||||
{t("signup.step4-description1")} {t("signup.step4-description3")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center px-3 justify-center mt-0 md:mt-4 mb-2 md:mb-4 lg:w-1/6 w-full md:min-w-[20rem] mt-2 md:max-w-md mx-auto text-sm text-center md:text-left">
|
||||
<div className="text-l py-1 text-lg w-full">
|
||||
<div className="mx-auto mt-0 mb-2 mt-2 flex w-full flex-col items-center justify-center px-3 text-center text-sm md:mt-4 md:mb-4 md:min-w-[20rem] md:max-w-md md:text-left lg:w-1/6">
|
||||
<div className="text-l w-full py-1 text-lg">
|
||||
<Button
|
||||
onClick={handleBackupKeyGenerate}
|
||||
size="sm"
|
||||
|
@ -52,13 +52,13 @@ export default function EnterEmailStep({
|
||||
try {
|
||||
await mutateAsync({ email });
|
||||
incrementStep();
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
if (axios.isAxiosError(e)) {
|
||||
const { message = "Something went wrong" } = e.response?.data as { message: string};
|
||||
const { message = "Something went wrong" } = e.response?.data as { message: string };
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: message
|
||||
})
|
||||
text: message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -66,11 +66,11 @@ export default function EnterEmailStep({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="w-full md:px-6 mx-auto">
|
||||
<p className="text-xl font-medium flex justify-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200">
|
||||
<div className="mx-auto w-full md:px-6">
|
||||
<p className="flex justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-xl font-medium text-transparent">
|
||||
{t("signup.step1-start")}
|
||||
</p>
|
||||
<div className="flex flex-col items-center justify-center lg:w-1/6 w-1/4 min-w-[20rem] m-auto rounded-lg mt-8">
|
||||
<div className="m-auto mt-8 flex w-1/4 min-w-[20rem] flex-col items-center justify-center rounded-lg lg:w-1/6">
|
||||
<Input
|
||||
placeholder="Enter your email address..."
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
@ -79,28 +79,35 @@ export default function EnterEmailStep({
|
||||
autoComplete="username"
|
||||
className="h-12"
|
||||
/>
|
||||
{emailError && <p className="text-red-600 text-xs text-left w-full ml-1.5 mt-1.5">Please enter a valid email.</p>}
|
||||
{emailError && (
|
||||
<p className="ml-1.5 mt-1.5 w-full text-left text-xs text-red-600">
|
||||
Please enter a valid email.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center lg:w-1/6 w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left">
|
||||
<div className="text-l py-1 text-lg w-full">
|
||||
<div className="mx-auto mt-2 flex w-1/4 min-w-[20rem] max-w-xs flex-col items-center justify-center text-center text-sm md:max-w-md md:text-left lg:w-1/6">
|
||||
<div className="text-l w-full py-1 text-lg">
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={emailCheck}
|
||||
size="sm"
|
||||
isFullWidth
|
||||
className='h-14'
|
||||
className="h-14"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
isLoading={isLoading}
|
||||
isDisabled={isLoading}
|
||||
> {String(t("signup.step1-submit"))} </Button>
|
||||
>
|
||||
{" "}
|
||||
{String(t("signup.step1-submit"))}{" "}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-auto mb-48 mt-2 flex w-full max-w-md flex-col items-center justify-center pt-2 md:mb-16 md:pb-2">
|
||||
<Link href="/login">
|
||||
<button type="button" className="w-max pb-3 duration-200 hover:opacity-90">
|
||||
<span className="text-sm text-mineshaft-400 hover:underline hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer">
|
||||
<span className="cursor-pointer text-sm text-mineshaft-400 duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
|
||||
{t("signup.already-have-account")}
|
||||
</span>
|
||||
</button>
|
||||
|
@ -16,7 +16,7 @@ export default function TeamInviteStep(): JSX.Element {
|
||||
const router = useRouter();
|
||||
const [emails, setEmails] = useState("");
|
||||
const { data: serverDetails } = useFetchServerStatus();
|
||||
|
||||
|
||||
const { mutateAsync } = useAddUserToOrg();
|
||||
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["setUpEmail"] as const);
|
||||
|
||||
@ -40,55 +40,61 @@ export default function TeamInviteStep(): JSX.Element {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-max mx-auto min-w-lg h-full pb-4 px-8 mb-64 md:mb-32">
|
||||
<p className="text-2xl font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200">
|
||||
<div className="min-w-lg mx-auto mb-64 h-full w-max px-8 pb-4 md:mb-32">
|
||||
<p className="flex justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-2xl font-semibold text-transparent">
|
||||
{t("signup.step5-invite-team")}
|
||||
</p>
|
||||
<p className="text-center flex justify-center text-bunker-400 md:mx-8 mb-6 mt-4">
|
||||
<p className="mb-6 mt-4 flex justify-center text-center text-bunker-400 md:mx-8">
|
||||
{t("signup.step5-subtitle")}
|
||||
</p>
|
||||
<div className="bg-mineshaft-800 border border-mineshaft-500 w-max mx-auto pt-6 pb-4 px-8 rounded-xl drop-shadow-xl mb-6">
|
||||
<div className="mx-auto mb-6 w-max rounded-xl border border-mineshaft-500 bg-mineshaft-800 px-8 pt-6 pb-4 drop-shadow-xl">
|
||||
<div>
|
||||
<div className="text-bunker-300 font-medium pl-1 pb-1 text-sm">
|
||||
<div className="pl-1 pb-1 text-sm font-medium text-bunker-300">
|
||||
<span>Emails</span>
|
||||
</div>
|
||||
<textarea
|
||||
className="bg-mineshaft-900/70 min-w-[30rem] h-20 w-full placeholder:text-bunker-400 py-1 px-2 rounded-md border border-mineshaft-500 text-sm text-bunker-300 outline-none focus:ring-2 ring-primary-800 ring-opacity-70"
|
||||
className="h-20 w-full min-w-[30rem] rounded-md border border-mineshaft-500 bg-mineshaft-900/70 py-1 px-2 text-sm text-bunker-300 outline-none ring-primary-800 ring-opacity-70 placeholder:text-bunker-400 focus:ring-2"
|
||||
value={emails}
|
||||
onChange={(e) => setEmails(e.target.value)}
|
||||
placeholder="email@example.com, email2@example.com..."
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row items-end justify-end mt-0 md:mt-4 md:mb-2 w-full md:min-w-[30rem] mt-2 md:max-w-md mx-auto text-sm">
|
||||
<div className="mx-auto mt-0 mt-2 flex w-full flex-row items-end justify-end text-sm md:mt-4 md:mb-2 md:min-w-[30rem] md:max-w-md">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (serverDetails?.emailConfigured) {
|
||||
inviteUsers({ emails })
|
||||
inviteUsers({ emails });
|
||||
} else {
|
||||
handlePopUpOpen("setUpEmail");
|
||||
}
|
||||
}}
|
||||
size="sm"
|
||||
// isFullWidth
|
||||
className='h-10'
|
||||
className="h-10"
|
||||
colorSchema="primary"
|
||||
variant="solid"
|
||||
> {t("signup.step5-send-invites") ?? ""} </Button>
|
||||
>
|
||||
{" "}
|
||||
{t("signup.step5-send-invites") ?? ""}{" "}
|
||||
</Button>
|
||||
</div>
|
||||
<EmailServiceSetupModal
|
||||
isOpen={popUp.setUpEmail?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("setUpEmail", isOpen)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row max-w-max min-w-28 items-center justify-center md:p-2 min-w-[20rem] max-h-24 mx-auto text-lg px-4 mt-4 mb-2">
|
||||
<div className="min-w-28 mx-auto mt-4 mb-2 flex max-h-24 min-w-[20rem] max-w-max flex-row items-center justify-center px-4 text-lg md:p-2">
|
||||
<Button
|
||||
onClick={redirectToHome}
|
||||
size="sm"
|
||||
isFullWidth
|
||||
className='h-12'
|
||||
className="h-12"
|
||||
colorSchema="secondary"
|
||||
variant="outline"
|
||||
> {t("signup.step5-skip") ?? "Skip"} </Button>
|
||||
>
|
||||
{" "}
|
||||
{t("signup.step5-skip") ?? "Skip"}{" "}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -196,10 +196,8 @@ export default function UserInfoStep({
|
||||
|
||||
const userOrgs = await fetchOrganizations();
|
||||
|
||||
const orgSlug = userOrgs[0]?.slug;
|
||||
const orgId = userOrgs[0]?.id;
|
||||
const project = await ProjectService.initProject({
|
||||
organizationSlug: orgSlug,
|
||||
projectName: "Example Project"
|
||||
});
|
||||
|
||||
|
@ -1,17 +1,11 @@
|
||||
import {
|
||||
getAuthToken,
|
||||
setAuthToken,
|
||||
setMfaTempToken,
|
||||
setSignupTempToken} from "@app/reactQuery";
|
||||
|
||||
import { getAuthToken, setAuthToken, setMfaTempToken, setSignupTempToken } from "@app/reactQuery";
|
||||
|
||||
export const PROVIDER_AUTH_TOKEN_KEY = "infisical__provider-auth-token";
|
||||
|
||||
// depreciated: go for apiRequest module in config/api
|
||||
export default class SecurityClient {
|
||||
|
||||
static setProviderAuthToken(tokenStr: string) {
|
||||
localStorage.setItem(PROVIDER_AUTH_TOKEN_KEY, tokenStr || "")
|
||||
localStorage.setItem(PROVIDER_AUTH_TOKEN_KEY, tokenStr || "");
|
||||
}
|
||||
|
||||
static getProviderAuthToken() {
|
||||
|
@ -3,9 +3,7 @@ import crypto from "crypto";
|
||||
|
||||
import jsrp from "jsrp";
|
||||
|
||||
import {
|
||||
changePassword,
|
||||
srp1} from "@app/hooks/api/auth/queries";
|
||||
import { changePassword, srp1 } from "@app/hooks/api/auth/queries";
|
||||
|
||||
import Aes256Gcm from "./cryptography/aes-256-gcm";
|
||||
import { deriveArgonKey } from "./cryptography/crypto";
|
||||
@ -15,95 +13,95 @@ const clientOldPassword = new jsrp.client();
|
||||
const clientNewPassword = new jsrp.client();
|
||||
|
||||
type Params = {
|
||||
email: string;
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
}
|
||||
email: string;
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
};
|
||||
|
||||
const attemptChangePassword = ({ email, currentPassword, newPassword }: Params): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
clientOldPassword.init({ username: email, password: currentPassword }, async () => {
|
||||
let serverPublicKey; let salt;
|
||||
return new Promise((resolve, reject) => {
|
||||
clientOldPassword.init({ username: email, password: currentPassword }, async () => {
|
||||
let serverPublicKey;
|
||||
let salt;
|
||||
|
||||
try {
|
||||
const clientPublicKey = clientOldPassword.getPublicKey();
|
||||
|
||||
const res = await srp1({ clientPublicKey });
|
||||
|
||||
serverPublicKey = res.serverPublicKey;
|
||||
salt = res.salt;
|
||||
|
||||
clientOldPassword.setSalt(salt);
|
||||
clientOldPassword.setServerPublicKey(serverPublicKey);
|
||||
|
||||
const clientProof = clientOldPassword.getProof();
|
||||
|
||||
clientNewPassword.init({ username: email, password: newPassword }, async () => {
|
||||
clientNewPassword.createVerifier(async (err, result) => {
|
||||
try {
|
||||
const clientPublicKey = clientOldPassword.getPublicKey();
|
||||
const derivedKey = await deriveArgonKey({
|
||||
password: newPassword,
|
||||
salt: result.salt,
|
||||
mem: 65536,
|
||||
time: 3,
|
||||
parallelism: 1,
|
||||
hashLen: 32
|
||||
});
|
||||
|
||||
const res = await srp1({ clientPublicKey });
|
||||
if (!derivedKey) throw new Error("Failed to derive key from password");
|
||||
|
||||
serverPublicKey = res.serverPublicKey;
|
||||
salt = res.salt;
|
||||
const key = crypto.randomBytes(32);
|
||||
|
||||
clientOldPassword.setSalt(salt);
|
||||
clientOldPassword.setServerPublicKey(serverPublicKey);
|
||||
const {
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag
|
||||
} = Aes256Gcm.encrypt({
|
||||
text: localStorage.getItem("PRIVATE_KEY") as string,
|
||||
secret: key
|
||||
});
|
||||
|
||||
const clientProof = clientOldPassword.getProof();
|
||||
const {
|
||||
ciphertext: protectedKey,
|
||||
iv: protectedKeyIV,
|
||||
tag: protectedKeyTag
|
||||
} = Aes256Gcm.encrypt({
|
||||
text: key.toString("hex"),
|
||||
secret: Buffer.from(derivedKey.hash)
|
||||
});
|
||||
|
||||
clientNewPassword.init({ username: email, password: newPassword }, async () => {
|
||||
clientNewPassword.createVerifier(async (err, result) => {
|
||||
try {
|
||||
const derivedKey = await deriveArgonKey({
|
||||
password: newPassword,
|
||||
salt: result.salt,
|
||||
mem: 65536,
|
||||
time: 3,
|
||||
parallelism: 1,
|
||||
hashLen: 32
|
||||
});
|
||||
await changePassword({
|
||||
clientProof,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier
|
||||
});
|
||||
|
||||
if (!derivedKey) throw new Error("Failed to derive key from password");
|
||||
saveTokenToLocalStorage({
|
||||
encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag
|
||||
});
|
||||
|
||||
const key = crypto.randomBytes(32);
|
||||
|
||||
const {
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag
|
||||
} = Aes256Gcm.encrypt({
|
||||
text: localStorage.getItem("PRIVATE_KEY") as string,
|
||||
secret: key
|
||||
});
|
||||
|
||||
const {
|
||||
ciphertext: protectedKey,
|
||||
iv: protectedKeyIV,
|
||||
tag: protectedKeyTag
|
||||
} = Aes256Gcm.encrypt({
|
||||
text: key.toString("hex"),
|
||||
secret: Buffer.from(derivedKey.hash)
|
||||
});
|
||||
|
||||
await changePassword({
|
||||
clientProof,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
encryptedPrivateKey,
|
||||
encryptedPrivateKeyIV,
|
||||
encryptedPrivateKeyTag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier
|
||||
});
|
||||
|
||||
saveTokenToLocalStorage({
|
||||
encryptedPrivateKey,
|
||||
iv: encryptedPrivateKeyIV,
|
||||
tag: encryptedPrivateKeyTag
|
||||
});
|
||||
|
||||
resolve();
|
||||
} catch (err2) {
|
||||
console.error(err2);
|
||||
reject(err2);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
reject(err);
|
||||
resolve();
|
||||
} catch (err2) {
|
||||
console.error(err2);
|
||||
reject(err2);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default attemptChangePassword;
|
||||
export default attemptChangePassword;
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* eslint-disable prefer-destructuring */
|
||||
import jsrp from "jsrp";
|
||||
|
||||
import { login1 , verifyMfaToken } from "@app/hooks/api/auth/queries";
|
||||
import { login1, verifyMfaToken } from "@app/hooks/api/auth/queries";
|
||||
import KeyService from "@app/services/KeyService";
|
||||
|
||||
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
||||
@ -11,11 +11,11 @@ import SecurityClient from "./SecurityClient";
|
||||
const client = new jsrp.client();
|
||||
|
||||
interface IsMfaLoginSuccessful {
|
||||
success: boolean;
|
||||
loginResponse:{
|
||||
privateKey: string;
|
||||
JTWToken: string;
|
||||
}
|
||||
success: boolean;
|
||||
loginResponse: {
|
||||
privateKey: string;
|
||||
JTWToken: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -26,81 +26,84 @@ interface IsMfaLoginSuccessful {
|
||||
* @param {String} obj.mfaToken - MFA code/token
|
||||
*/
|
||||
const attemptLoginMfa = async ({
|
||||
email,
|
||||
password,
|
||||
providerAuthToken,
|
||||
mfaToken
|
||||
email,
|
||||
password,
|
||||
providerAuthToken,
|
||||
mfaToken
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
providerAuthToken?: string,
|
||||
mfaToken: string;
|
||||
email: string;
|
||||
password: string;
|
||||
providerAuthToken?: string;
|
||||
mfaToken: string;
|
||||
}): Promise<IsMfaLoginSuccessful> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
client.init({
|
||||
username: email,
|
||||
password
|
||||
}, async () => {
|
||||
try {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
const { salt } = await login1({
|
||||
email,
|
||||
clientPublicKey,
|
||||
providerAuthToken,
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
client.init(
|
||||
{
|
||||
username: email,
|
||||
password
|
||||
},
|
||||
async () => {
|
||||
try {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
const { salt } = await login1({
|
||||
email,
|
||||
clientPublicKey,
|
||||
providerAuthToken
|
||||
});
|
||||
|
||||
const {
|
||||
encryptionVersion,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
token,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag
|
||||
} = await verifyMfaToken({
|
||||
email,
|
||||
mfaCode: mfaToken
|
||||
});
|
||||
const {
|
||||
encryptionVersion,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
token,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag
|
||||
} = await verifyMfaToken({
|
||||
email,
|
||||
mfaCode: mfaToken
|
||||
});
|
||||
|
||||
// unset temporary (MFA) JWT token and set JWT token
|
||||
SecurityClient.setMfaToken("");
|
||||
SecurityClient.setToken(token);
|
||||
SecurityClient.setProviderAuthToken("");
|
||||
// unset temporary (MFA) JWT token and set JWT token
|
||||
SecurityClient.setMfaToken("");
|
||||
SecurityClient.setToken(token);
|
||||
SecurityClient.setProviderAuthToken("");
|
||||
|
||||
const privateKey = await KeyService.decryptPrivateKey({
|
||||
encryptionVersion,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password,
|
||||
salt,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag
|
||||
});
|
||||
const privateKey = await KeyService.decryptPrivateKey({
|
||||
encryptionVersion,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password,
|
||||
salt,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag
|
||||
});
|
||||
|
||||
saveTokenToLocalStorage({
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
privateKey
|
||||
});
|
||||
saveTokenToLocalStorage({
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
privateKey
|
||||
});
|
||||
|
||||
resolve({
|
||||
success: true,
|
||||
loginResponse:{
|
||||
privateKey,
|
||||
JTWToken: token
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
resolve({
|
||||
success: true,
|
||||
loginResponse: {
|
||||
privateKey,
|
||||
JTWToken: token
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default attemptLoginMfa;
|
||||
export default attemptLoginMfa;
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* eslint-disable prefer-destructuring */
|
||||
import jsrp from "jsrp";
|
||||
|
||||
import { login1 , verifyMfaToken } from "@app/hooks/api/auth/queries";
|
||||
import { login1, verifyMfaToken } from "@app/hooks/api/auth/queries";
|
||||
import KeyService from "@app/services/KeyService";
|
||||
|
||||
import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage";
|
||||
@ -18,75 +18,78 @@ const client = new jsrp.client();
|
||||
* @param {String} obj.mfaToken - MFA code/token
|
||||
*/
|
||||
const attemptLoginMfa = async ({
|
||||
email,
|
||||
password,
|
||||
providerAuthToken,
|
||||
mfaToken
|
||||
email,
|
||||
password,
|
||||
providerAuthToken,
|
||||
mfaToken
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
providerAuthToken?: string,
|
||||
mfaToken: string;
|
||||
email: string;
|
||||
password: string;
|
||||
providerAuthToken?: string;
|
||||
mfaToken: string;
|
||||
}): Promise<Boolean> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
client.init({
|
||||
username: email,
|
||||
password
|
||||
}, async () => {
|
||||
try {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
const { salt } = await login1({
|
||||
email,
|
||||
clientPublicKey,
|
||||
providerAuthToken,
|
||||
});
|
||||
|
||||
const {
|
||||
encryptionVersion,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
token,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag
|
||||
} = await verifyMfaToken({
|
||||
email,
|
||||
mfaCode: mfaToken
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
client.init(
|
||||
{
|
||||
username: email,
|
||||
password
|
||||
},
|
||||
async () => {
|
||||
try {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
const { salt } = await login1({
|
||||
email,
|
||||
clientPublicKey,
|
||||
providerAuthToken
|
||||
});
|
||||
|
||||
// unset temporary (MFA) JWT token and set JWT token
|
||||
SecurityClient.setMfaToken("");
|
||||
SecurityClient.setToken(token);
|
||||
SecurityClient.setProviderAuthToken("");
|
||||
const {
|
||||
encryptionVersion,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
token,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag
|
||||
} = await verifyMfaToken({
|
||||
email,
|
||||
mfaCode: mfaToken
|
||||
});
|
||||
|
||||
const privateKey = await KeyService.decryptPrivateKey({
|
||||
encryptionVersion,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password,
|
||||
salt,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag
|
||||
});
|
||||
|
||||
saveTokenToLocalStorage({
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
privateKey
|
||||
});
|
||||
// unset temporary (MFA) JWT token and set JWT token
|
||||
SecurityClient.setMfaToken("");
|
||||
SecurityClient.setToken(token);
|
||||
SecurityClient.setProviderAuthToken("");
|
||||
|
||||
resolve(true);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
const privateKey = await KeyService.decryptPrivateKey({
|
||||
encryptionVersion,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password,
|
||||
salt,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag
|
||||
});
|
||||
|
||||
export default attemptLoginMfa;
|
||||
saveTokenToLocalStorage({
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
privateKey
|
||||
});
|
||||
|
||||
resolve(true);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default attemptLoginMfa;
|
||||
|
@ -1,5 +1,11 @@
|
||||
import { checkIsPasswordBreached } from "./checkIsPasswordBreached";
|
||||
import { escapeCharRegex, letterCharRegex, lowEntropyRegexes,numAndSpecialCharRegex, repeatedCharRegex } from "./passwordRegexes";
|
||||
import {
|
||||
escapeCharRegex,
|
||||
letterCharRegex,
|
||||
lowEntropyRegexes,
|
||||
numAndSpecialCharRegex,
|
||||
repeatedCharRegex
|
||||
} from "./passwordRegexes";
|
||||
|
||||
interface PasswordCheckProps {
|
||||
password: string;
|
||||
@ -29,40 +35,38 @@ const passwordCheck = async ({
|
||||
{
|
||||
name: "tooShort",
|
||||
validator: (pwd: string) => pwd.length >= 14,
|
||||
setError: setPasswordErrorTooShort,
|
||||
setError: setPasswordErrorTooShort
|
||||
},
|
||||
{
|
||||
name: "tooLong",
|
||||
validator: (pwd: string) => pwd.length < 101,
|
||||
setError: setPasswordErrorTooLong,
|
||||
setError: setPasswordErrorTooLong
|
||||
},
|
||||
{
|
||||
name: "noLetterChar",
|
||||
validator: (pwd: string) => letterCharRegex.test(pwd),
|
||||
setError: setPasswordErrorNoLetterChar,
|
||||
setError: setPasswordErrorNoLetterChar
|
||||
},
|
||||
{
|
||||
name: "noNumOrSpecialChar",
|
||||
validator: (pwd: string) => numAndSpecialCharRegex.test(pwd),
|
||||
setError: setPasswordErrorNoNumOrSpecialChar,
|
||||
setError: setPasswordErrorNoNumOrSpecialChar
|
||||
},
|
||||
{
|
||||
name: "repeatedChar",
|
||||
validator: (pwd: string) => !repeatedCharRegex.test(pwd),
|
||||
setError: setPasswordErrorRepeatedChar,
|
||||
setError: setPasswordErrorRepeatedChar
|
||||
},
|
||||
{
|
||||
name: "escapeChar",
|
||||
validator: (pwd: string) => !escapeCharRegex.test(pwd),
|
||||
setError: setPasswordErrorEscapeChar,
|
||||
setError: setPasswordErrorEscapeChar
|
||||
},
|
||||
{
|
||||
name: "lowEntropy",
|
||||
validator: (pwd: string) => (
|
||||
!lowEntropyRegexes.some(regex => regex.test(pwd))
|
||||
),
|
||||
setError: setPasswordErrorLowEntropy,
|
||||
},
|
||||
validator: (pwd: string) => !lowEntropyRegexes.some((regex) => regex.test(pwd)),
|
||||
setError: setPasswordErrorLowEntropy
|
||||
}
|
||||
];
|
||||
|
||||
const isBreached = await checkIsPasswordBreached(password);
|
||||
@ -73,7 +77,7 @@ const passwordCheck = async ({
|
||||
} else {
|
||||
setPasswordErrorBreached(false);
|
||||
}
|
||||
|
||||
|
||||
tests.forEach((test) => {
|
||||
if (!test.validator(password)) {
|
||||
errorCheck = true;
|
||||
@ -81,7 +85,7 @@ const passwordCheck = async ({
|
||||
} else {
|
||||
test.setError(false);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return errorCheck;
|
||||
};
|
||||
|
@ -17,25 +17,25 @@ function bufferToHex(buffer: ArrayBuffer): string {
|
||||
return hexParts.join("");
|
||||
}
|
||||
|
||||
// see API details here: https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange
|
||||
// in short, the pending password is hashed (SHA-1), the first 5 chars are sliced and compared against a ranged hash table
|
||||
// this hash table is formed from the 5 char hash prefix (ie. 00000-FFFFF) so 16^5 results
|
||||
// returns a hash table of 800-1000 results
|
||||
// padding has been added to prevent MitM attacker determining which hash table was called by the response size
|
||||
// the last 35 chars of the password hash are compared client-side against the table
|
||||
// if there is a match, that password has been involved in a password breach (ie. pwnd) and should NOT be accepted
|
||||
// the database consists of ~700 mln breached passwords and is continuously updated, including with law enforcement ingestion
|
||||
// https://www.troyhunt.com/open-source-pwned-passwords-with-fbi-feed-and-225m-new-nca-passwords-is-now-live/
|
||||
// see API details here: https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange
|
||||
// in short, the pending password is hashed (SHA-1), the first 5 chars are sliced and compared against a ranged hash table
|
||||
// this hash table is formed from the 5 char hash prefix (ie. 00000-FFFFF) so 16^5 results
|
||||
// returns a hash table of 800-1000 results
|
||||
// padding has been added to prevent MitM attacker determining which hash table was called by the response size
|
||||
// the last 35 chars of the password hash are compared client-side against the table
|
||||
// if there is a match, that password has been involved in a password breach (ie. pwnd) and should NOT be accepted
|
||||
// the database consists of ~700 mln breached passwords and is continuously updated, including with law enforcement ingestion
|
||||
// https://www.troyhunt.com/open-source-pwned-passwords-with-fbi-feed-and-225m-new-nca-passwords-is-now-live/
|
||||
|
||||
// The HIBP API follows NIST guidance (pg.14) https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63b.pdf
|
||||
// "When processing requests to establish and change memorized secrets, verifiers SHALL compare
|
||||
// the prospective secrets against a list that contains values known to be commonly-used, expected,
|
||||
// or compromised. For example, the list MAY include, but is not limited to:
|
||||
// • Passwords obtained from previous breach corpuses.
|
||||
// • Dictionary words.
|
||||
// • Repetitive or sequential characters (e.g. ‘aaaaaa’, ‘1234abcd’).
|
||||
// • Context-specific words, such as the name of the service, the username, and derivatives
|
||||
// thereof."
|
||||
// The HIBP API follows NIST guidance (pg.14) https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63b.pdf
|
||||
// "When processing requests to establish and change memorized secrets, verifiers SHALL compare
|
||||
// the prospective secrets against a list that contains values known to be commonly-used, expected,
|
||||
// or compromised. For example, the list MAY include, but is not limited to:
|
||||
// • Passwords obtained from previous breach corpuses.
|
||||
// • Dictionary words.
|
||||
// • Repetitive or sequential characters (e.g. ‘aaaaaa’, ‘1234abcd’).
|
||||
// • Context-specific words, such as the name of the service, the username, and derivatives
|
||||
// thereof."
|
||||
|
||||
export const checkIsPasswordBreached = async (password: string): Promise<boolean> => {
|
||||
const HAVE_I_BEEN_PWNED_API_URL = "https://api.pwnedpasswords.com";
|
||||
@ -66,8 +66,8 @@ export const checkIsPasswordBreached = async (password: string): Promise<boolean
|
||||
response = await axios.get(rangedHashTableUri, {
|
||||
headers: {
|
||||
"Add-Padding": "true", // see https://www.troyhunt.com/enhancing-pwned-passwords-privacy-with-padding/
|
||||
"Content-Type": "text/plain",
|
||||
},
|
||||
"Content-Type": "text/plain"
|
||||
}
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
@ -76,9 +76,8 @@ export const checkIsPasswordBreached = async (password: string): Promise<boolean
|
||||
// check the last 35 hash chars to see if there's a match
|
||||
const isBreachedPassword: boolean = responseData.includes(hashedPwd.slice(5, 40));
|
||||
return isBreachedPassword;
|
||||
}
|
||||
retryAttempt += 1;
|
||||
|
||||
}
|
||||
retryAttempt += 1;
|
||||
} catch (err) {
|
||||
if (!axios.isAxiosError(err)) {
|
||||
throw err;
|
||||
@ -88,14 +87,15 @@ export const checkIsPasswordBreached = async (password: string): Promise<boolean
|
||||
}
|
||||
|
||||
console.error(
|
||||
`Received a non-200 response (${response ? response.status : "unknown"}) from the Pwnd Passwords API`
|
||||
`Received a non-200 response (${
|
||||
response ? response.status : "unknown"
|
||||
}) from the Pwnd Passwords API`
|
||||
);
|
||||
return false;
|
||||
} catch (err: any) {
|
||||
console.error("An unexpected error has occurred:", err.message);
|
||||
return false;
|
||||
} finally {
|
||||
|
||||
// Clear the UTF-8 encoded password from memory
|
||||
|
||||
if (encodedPwd) {
|
||||
|
@ -1,5 +1,11 @@
|
||||
import { checkIsPasswordBreached } from "./checkIsPasswordBreached";
|
||||
import { escapeCharRegex, letterCharRegex, lowEntropyRegexes,numAndSpecialCharRegex, repeatedCharRegex } from "./passwordRegexes";
|
||||
import {
|
||||
escapeCharRegex,
|
||||
letterCharRegex,
|
||||
lowEntropyRegexes,
|
||||
numAndSpecialCharRegex,
|
||||
repeatedCharRegex
|
||||
} from "./passwordRegexes";
|
||||
|
||||
type Errors = {
|
||||
tooShort?: string;
|
||||
@ -44,40 +50,38 @@ const checkPassword = async ({ password, setErrors }: CheckPasswordParams): Prom
|
||||
{
|
||||
name: "tooShort",
|
||||
validator: (pwd: string) => pwd.length >= 14,
|
||||
errorText: "at least 14 characters",
|
||||
errorText: "at least 14 characters"
|
||||
},
|
||||
{
|
||||
name: "tooLong",
|
||||
validator: (pwd: string) => pwd.length < 101,
|
||||
errorText: "at most 100 characters",
|
||||
errorText: "at most 100 characters"
|
||||
},
|
||||
{
|
||||
name: "noLetterChar",
|
||||
validator: (pwd: string) => letterCharRegex.test(pwd),
|
||||
errorText: "at least 1 letter character",
|
||||
errorText: "at least 1 letter character"
|
||||
},
|
||||
{
|
||||
name: "noNumOrSpecialChar",
|
||||
validator: (pwd: string) => numAndSpecialCharRegex.test(pwd),
|
||||
errorText: "at least 1 number or special character",
|
||||
errorText: "at least 1 number or special character"
|
||||
},
|
||||
{
|
||||
name: "repeatedChar",
|
||||
validator: (pwd: string) => !repeatedCharRegex.test(pwd),
|
||||
errorText: "at most 3 repeated, consecutive characters",
|
||||
errorText: "at most 3 repeated, consecutive characters"
|
||||
},
|
||||
{
|
||||
name: "escapeChar",
|
||||
validator: (pwd: string) => !escapeCharRegex.test(pwd),
|
||||
errorText: "No escape characters allowed.",
|
||||
errorText: "No escape characters allowed."
|
||||
},
|
||||
{
|
||||
name: "lowEntropy",
|
||||
validator: (pwd: string) => (
|
||||
!lowEntropyRegexes.some(regex => regex.test(pwd))
|
||||
),
|
||||
errorText: "Password contains personal info.",
|
||||
},
|
||||
validator: (pwd: string) => !lowEntropyRegexes.some((regex) => regex.test(pwd)),
|
||||
errorText: "Password contains personal info."
|
||||
}
|
||||
];
|
||||
|
||||
const isBreached = await checkIsPasswordBreached(password);
|
||||
@ -96,4 +100,4 @@ const checkPassword = async ({ password, setErrors }: CheckPasswordParams): Prom
|
||||
return Object.keys(errors).length > 0;
|
||||
};
|
||||
|
||||
export default checkPassword;
|
||||
export default checkPassword;
|
||||
|
@ -1,6 +1,7 @@
|
||||
// This regex covers letters (case insensitive) for the top 50 most spoken languages
|
||||
/* eslint-disable no-misleading-character-class */
|
||||
export const letterCharRegex = /[A-Za-z\u00C0-\u00D6\u00D8-\u00DE\u00DF-\u00F6\u00F8-\u00FF\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u0600-\u06FF\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u05B0-\u05FF\u0980-\u09FF\u1F00-\u1FFF\u0130\u015E\u011E\u00C7\u00FC\u00FB\u00EB\u00E7]/u;
|
||||
export const letterCharRegex =
|
||||
/[A-Za-z\u00C0-\u00D6\u00D8-\u00DE\u00DF-\u00F6\u00F8-\u00FF\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u0600-\u06FF\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u05B0-\u05FF\u0980-\u09FF\u1F00-\u1FFF\u0130\u015E\u011E\u00C7\u00FC\u00FB\u00EB\u00E7]/u;
|
||||
|
||||
// This regex covers digits, special characters, symbols, and emojis.
|
||||
export const numAndSpecialCharRegex = /[\d!@#$%^&*(),.?":{}|<>]|[^\p{L}\p{N}\s]/u;
|
||||
@ -32,5 +33,5 @@ export const lowEntropyRegexes = [
|
||||
/\b(?:[A-Z0-9]{7,10}|[A-Z0-9]{10,11}|[A-Z0-9]{7,10})\b/,
|
||||
|
||||
// US social security number
|
||||
/\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/,
|
||||
];
|
||||
/\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/
|
||||
];
|
||||
|
@ -3,9 +3,7 @@ import crypto from "crypto";
|
||||
|
||||
import jsrp from "jsrp";
|
||||
|
||||
import { issueBackupPrivateKey ,
|
||||
srp1
|
||||
} from "@app/hooks/api/auth/queries";
|
||||
import { issueBackupPrivateKey, srp1 } from "@app/hooks/api/auth/queries";
|
||||
|
||||
import generateBackupPDF from "../generateBackupPDF";
|
||||
import Aes256Gcm from "./aes-256-gcm";
|
||||
@ -97,7 +95,6 @@ const issueBackupKey = async ({
|
||||
generatedKey
|
||||
});
|
||||
setBackupKeyIssued(true);
|
||||
|
||||
} catch {
|
||||
setBackupKeyError(true);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
/* eslint-disable vars-on-top */
|
||||
/* eslint-disable no-var */
|
||||
/* eslint-disable func-names */
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-nocheck
|
||||
|
||||
import { INTERCOMid as APPid } from "@app/components/utilities/config";
|
||||
|
@ -3,11 +3,7 @@ import { useRouter } from "next/router";
|
||||
|
||||
import { useUser } from "@app/context";
|
||||
|
||||
import {
|
||||
boot as bootIntercom,
|
||||
load as loadIntercom,
|
||||
update as updateIntercom,
|
||||
} from "./intercom";
|
||||
import { boot as bootIntercom, load as loadIntercom, update as updateIntercom } from "./intercom";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const IntercomProvider = ({ children }: { children: any }) => {
|
||||
@ -16,7 +12,11 @@ export const IntercomProvider = ({ children }: { children: any }) => {
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
loadIntercom();
|
||||
bootIntercom({name: `${user?.firstName || ""} ${user?.lastName || ""}`, email: user?.email || "", created_at: Math.floor(((new Date(user?.createdAt))?.getTime() || 0) / 1000)});
|
||||
bootIntercom({
|
||||
name: `${user?.firstName || ""} ${user?.lastName || ""}`,
|
||||
email: user?.email || "",
|
||||
created_at: Math.floor((new Date(user?.createdAt)?.getTime() || 0) / 1000)
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@ -37,4 +37,4 @@ export const IntercomProvider = ({ children }: { children: any }) => {
|
||||
}, [router.events]);
|
||||
|
||||
return children;
|
||||
};
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user