mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-27 09:40:45 +00:00
Compare commits
123 Commits
daniel/ts-
...
daniel/org
Author | SHA1 | Date | |
---|---|---|---|
eba7b6a3ce | |||
ff44807605 | |||
bfb4e1ac14 | |||
5c2d61432e | |||
7ea0730621 | |||
d9d5ba055e | |||
8d318881b8 | |||
2b7d1a2e36 | |||
5d3b04be29 | |||
42f10b2bfd | |||
80470e96e5 | |||
8cc5c766e3 | |||
a1fe0e9676 | |||
e1bbe17526 | |||
9475755d40 | |||
30ba304382 | |||
974e6e56b4 | |||
b7bbc513e5 | |||
f6956130bb | |||
01f373798f | |||
fe82231574 | |||
d6eee8a7b3 | |||
88827d5060 | |||
9b0299ff9c | |||
f9e59cf35e | |||
afc92d6ec0 | |||
74479fb950 | |||
06eef21e1c | |||
e687de0a97 | |||
7192abfc4c | |||
c7744ca371 | |||
ed45daf34b | |||
3baeddc426 | |||
1e0995e5fc | |||
9c724ff064 | |||
5950369101 | |||
add15bacd3 | |||
a6aa370349 | |||
a50391889a | |||
fb4b35fa09 | |||
4dafe14d90 | |||
8c1745f73e | |||
c829dcf4a3 | |||
973bfe2407 | |||
e2a3dc4a0a | |||
f4df97b968 | |||
278666ca96 | |||
e2f72de2d8 | |||
a24b8a858c | |||
2b7f7e82c7 | |||
9b0cea23c7 | |||
49f6d6f77b | |||
611704d0d4 | |||
e6143dc21f | |||
28caafa248 | |||
41df9880ce | |||
c3ea73e461 | |||
19841bbf7d | |||
748ffb4008 | |||
75338f7c81 | |||
f1fe4f5b5a | |||
53f83b6883 | |||
67e45c086f | |||
7bf57e6d46 | |||
a7d62848f9 | |||
9a6d0d2048 | |||
5bb7e9163a | |||
41af790073 | |||
3e0d4ce6cf | |||
0d22320d61 | |||
fafdff7de1 | |||
bbe245477b | |||
ea9659ba64 | |||
4246a07c1a | |||
d410b85fe1 | |||
d1adc4cfad | |||
2e4a7c5e7f | |||
bf05366c5f | |||
ba51ede553 | |||
4d1b0790d4 | |||
76dd1d9fca | |||
2c7237411e | |||
78f9981139 | |||
fcd810ee07 | |||
e4084facaa | |||
ebbee48adf | |||
5b09212f27 | |||
17a458cc26 | |||
96f381d61b | |||
df39add05a | |||
da50807919 | |||
8652e09546 | |||
6826f9058e | |||
5d932c021f | |||
c74566cefd | |||
9daf73d71c | |||
2ca3e05f37 | |||
ebc03c20ce | |||
29cc94f756 | |||
69e8b3d242 | |||
5d18abc67f | |||
233e12189a | |||
cbdbc8790e | |||
7975e86161 | |||
d767995b45 | |||
98762e53f1 | |||
acee2314f4 | |||
2f23381a80 | |||
2d5006eeb1 | |||
82083f5df8 | |||
ddc4cf5a74 | |||
ff4b7ba52e | |||
e2bf3546a0 | |||
da710c65db | |||
f3ca2c2ba4 | |||
e3aa23a317 | |||
815a497ac9 | |||
0134ffde5e | |||
48444b4df9 | |||
d0a61ffdba | |||
f6dbce3603 | |||
b09398ac75 | |||
7ef22b2715 |
@ -2,7 +2,6 @@ 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";
|
||||
@ -20,13 +19,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim().describe(PROJECTS.GET_SNAPSHOTS.workspaceId)
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
querystring: z.object({
|
||||
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)
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
offset: z.coerce.number().default(0),
|
||||
limit: z.coerce.number().default(20)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -92,16 +91,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim().describe(AUDIT_LOGS.EXPORT.workspaceId)
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
querystring: z.object({
|
||||
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)
|
||||
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()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -1,7 +1,6 @@
|
||||
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";
|
||||
|
||||
@ -67,7 +66,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretSnapshotId: z.string().trim().describe(PROJECTS.ROLLBACK_TO_SNAPSHOT.secretSnapshotId)
|
||||
secretSnapshotId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -8,7 +8,6 @@ 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";
|
||||
@ -27,7 +26,6 @@ import {
|
||||
TFeatureSet,
|
||||
TGetOrgBillInfoDTO,
|
||||
TGetOrgTaxIdDTO,
|
||||
TOfflineLicenseContents,
|
||||
TOrgInvoiceDTO,
|
||||
TOrgLicensesDTO,
|
||||
TOrgPlanDTO,
|
||||
@ -98,36 +96,6 @@ 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,21 +6,6 @@ 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;
|
||||
|
@ -1,287 +0,0 @@
|
||||
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 +0,0 @@
|
||||
export * from "./constants";
|
@ -106,7 +106,6 @@ 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,5 +17,4 @@ export {
|
||||
decryptSecrets,
|
||||
decryptSecretVersions
|
||||
} from "./secret-encryption";
|
||||
export { verifyOfflineLicense } from "./signing";
|
||||
export { generateSrpServerKey, srpCheckClientProof } from "./srp";
|
||||
|
@ -1,8 +0,0 @@
|
||||
-----BEGIN RSA PUBLIC KEY-----
|
||||
MIIBCgKCAQEApchBY3BXTu4zWGBguB7nM/pjpVLY3V7VGZOAxmR5ueQTJOwiGM13
|
||||
5HN3EM9fDlQnZu9VSc0OFqRM/bUeUaI1oLPE6WzTHjdHyKjDI/S+TLx3VGEsvhM1
|
||||
uukZpYX+3KX2w4wzRHBaBWyglFy0CVNth9UJhhpD+KKfv7dzcRmsbyoUWi9wGfJu
|
||||
wLYCwaCwZRXIt1sLGmMncPz14vfwdnm2a5Tj1Jbt0GTyBl+1/ZqLbO6SsslLg2G+
|
||||
o7FfGS9z8OUTkvDdu16qxL+p2wCEFZMnOz5BB4oakuT2gS9iOO2l5AOPcT4WzPzy
|
||||
PYbX3d7cN9BkOY9I5z0cX4wzqHjQTvGNLQIDAQAB
|
||||
-----END RSA PUBLIC KEY-----
|
@ -1,22 +0,0 @@
|
||||
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);
|
||||
};
|
@ -1,7 +1,5 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { UNIVERSAL_AUTH } from "@app/lib/api-docs";
|
||||
|
||||
export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/token/renew",
|
||||
@ -9,7 +7,7 @@ export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvid
|
||||
schema: {
|
||||
description: "Renew access token",
|
||||
body: z.object({
|
||||
accessToken: z.string().trim().describe(UNIVERSAL_AUTH.RENEW_ACCESS_TOKEN.accessToken)
|
||||
accessToken: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -2,7 +2,6 @@ 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";
|
||||
@ -21,9 +20,9 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
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)
|
||||
name: z.string().trim(),
|
||||
organizationId: z.string().trim(),
|
||||
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -80,11 +79,11 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string().describe(IDENTITIES.UPDATE.identityId)
|
||||
identityId: z.string()
|
||||
}),
|
||||
body: z.object({
|
||||
name: z.string().trim().optional().describe(IDENTITIES.UPDATE.name),
|
||||
role: z.string().trim().min(1).optional().describe(IDENTITIES.UPDATE.role)
|
||||
name: z.string().trim().optional(),
|
||||
role: z.string().trim().min(1).optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -130,7 +129,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string().describe(IDENTITIES.DELETE.identityId)
|
||||
identityId: z.string()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -2,7 +2,6 @@ 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";
|
||||
@ -27,8 +26,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
schema: {
|
||||
description: "Login with Universal Auth",
|
||||
body: z.object({
|
||||
clientId: z.string().trim().describe(UNIVERSAL_AUTH.LOGIN.clientId),
|
||||
clientSecret: z.string().trim().describe(UNIVERSAL_AUTH.LOGIN.clientSecret)
|
||||
clientId: z.string().trim(),
|
||||
clientSecret: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -77,7 +76,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string().trim().describe(UNIVERSAL_AUTH.ATTACH.identityId)
|
||||
identityId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
clientSecretTrustedIps: z
|
||||
@ -86,16 +85,14 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||
.describe(UNIVERSAL_AUTH.ATTACH.clientSecretTrustedIps),
|
||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
|
||||
accessTokenTrustedIps: z
|
||||
.object({
|
||||
ipAddress: z.string().trim()
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenTrustedIps),
|
||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
|
||||
accessTokenTTL: z
|
||||
.number()
|
||||
.int()
|
||||
@ -103,22 +100,15 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenTTL must have a non zero number"
|
||||
})
|
||||
.default(2592000)
|
||||
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenTTL), // 30 days
|
||||
.default(2592000),
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
.refine((value) => value !== 0, {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
.default(2592000)
|
||||
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenMaxTTL), // 30 days
|
||||
accessTokenNumUsesLimit: z
|
||||
.number()
|
||||
.int()
|
||||
.min(0)
|
||||
.default(0)
|
||||
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||
.default(2592000), // 30 days
|
||||
accessTokenNumUsesLimit: z.number().int().min(0).default(0)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -167,7 +157,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string().describe(UNIVERSAL_AUTH.UPDATE.identityId)
|
||||
identityId: z.string()
|
||||
}),
|
||||
body: z.object({
|
||||
clientSecretTrustedIps: z
|
||||
@ -176,23 +166,16 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
.optional()
|
||||
.describe(UNIVERSAL_AUTH.UPDATE.clientSecretTrustedIps),
|
||||
.optional(),
|
||||
accessTokenTrustedIps: z
|
||||
.object({
|
||||
ipAddress: z.string().trim()
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
.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),
|
||||
.optional(),
|
||||
accessTokenTTL: z.number().int().min(0).optional(),
|
||||
accessTokenNumUsesLimit: z.number().int().min(0).optional(),
|
||||
accessTokenMaxTTL: z
|
||||
.number()
|
||||
.int()
|
||||
@ -200,7 +183,6 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
message: "accessTokenMaxTTL must have a non zero number"
|
||||
})
|
||||
.optional()
|
||||
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenMaxTTL)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -250,7 +232,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string().describe(UNIVERSAL_AUTH.RETRIEVE.identityId)
|
||||
identityId: z.string()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -294,12 +276,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string().describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.identityId)
|
||||
identityId: z.string()
|
||||
}),
|
||||
body: z.object({
|
||||
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)
|
||||
description: z.string().trim().default(""),
|
||||
numUsesLimit: z.number().min(0).default(0),
|
||||
ttl: z.number().min(0).default(0)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -346,7 +328,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string().describe(UNIVERSAL_AUTH.LIST_CLIENT_SECRETS.identityId)
|
||||
identityId: z.string()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -389,8 +371,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
identityId: z.string().describe(UNIVERSAL_AUTH.REVOKE_CLIENT_SECRET.identityId),
|
||||
clientSecretId: z.string().describe(UNIVERSAL_AUTH.REVOKE_CLIENT_SECRET.clientSecretId)
|
||||
identityId: z.string(),
|
||||
clientSecretId: z.string()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -352,68 +352,6 @@ 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,7 +33,6 @@ 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,7 +2,6 @@ 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";
|
||||
|
||||
@ -19,11 +18,11 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim().describe(ENVIRONMENTS.CREATE.workspaceId)
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
name: z.string().trim().describe(ENVIRONMENTS.CREATE.name),
|
||||
slug: z.string().trim().describe(ENVIRONMENTS.CREATE.slug)
|
||||
name: z.string().trim(),
|
||||
slug: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -75,13 +74,13 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim().describe(ENVIRONMENTS.UPDATE.workspaceId),
|
||||
id: z.string().trim().describe(ENVIRONMENTS.UPDATE.id)
|
||||
workspaceId: z.string().trim(),
|
||||
id: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
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)
|
||||
slug: z.string().trim().optional(),
|
||||
name: z.string().trim().optional(),
|
||||
position: z.number().optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -139,8 +138,8 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim().describe(ENVIRONMENTS.DELETE.workspaceId),
|
||||
id: z.string().trim().describe(ENVIRONMENTS.DELETE.id)
|
||||
workspaceId: z.string().trim(),
|
||||
id: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -9,7 +9,6 @@ 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";
|
||||
@ -27,7 +26,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim().describe(PROJECTS.GET_USER_MEMBERSHIPS.workspaceId)
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -137,8 +136,8 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim().describe(PROJECTS.UPDATE_USER_MEMBERSHIP.workspaceId),
|
||||
membershipId: z.string().trim().describe(PROJECTS.UPDATE_USER_MEMBERSHIP.membershipId)
|
||||
workspaceId: z.string().trim(),
|
||||
membershipId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
roles: z
|
||||
@ -159,7 +158,6 @@ 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,7 +7,6 @@ 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";
|
||||
@ -129,7 +128,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
method: "GET",
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim().describe(PROJECTS.GET.workspaceId)
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -158,7 +157,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
method: "DELETE",
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim().describe(PROJECTS.DELETE.workspaceId)
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -221,16 +220,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
method: "PATCH",
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim().describe(PROJECTS.UPDATE.workspaceId)
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
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)
|
||||
name: z.string().trim().max(64, { message: "Name must be 64 or fewer characters" }).optional(),
|
||||
autoCapitalization: z.boolean().optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -2,7 +2,6 @@ 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";
|
||||
@ -20,12 +19,12 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
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),
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
name: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
// backward compatiability with cli
|
||||
directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.CREATE.directory)
|
||||
directory: z.string().trim().default("/").transform(removeTrailingSlash)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -75,15 +74,15 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
||||
],
|
||||
params: z.object({
|
||||
// old way this was name
|
||||
folderId: z.string().describe(FOLDERS.UPDATE.folderId)
|
||||
folderId: z.string()
|
||||
}),
|
||||
body: z.object({
|
||||
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),
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
name: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
// backward compatiability with cli
|
||||
directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.UPDATE.directory)
|
||||
directory: z.string().trim().default("/").transform(removeTrailingSlash)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -122,7 +121,6 @@ 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",
|
||||
@ -135,14 +133,14 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
folderIdOrName: z.string().describe(FOLDERS.DELETE.folderIdOrName)
|
||||
folderIdOrName: z.string()
|
||||
}),
|
||||
body: z.object({
|
||||
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),
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
// keep this here as cli need directory
|
||||
directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.DELETE.directory)
|
||||
directory: z.string().trim().default("/").transform(removeTrailingSlash)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -192,11 +190,11 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
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),
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
// backward compatiability with cli
|
||||
directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.LIST.directory)
|
||||
directory: z.string().trim().default("/").transform(removeTrailingSlash)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -2,7 +2,6 @@ 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";
|
||||
@ -20,12 +19,12 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
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),
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
import: z.object({
|
||||
environment: z.string().trim().describe(SECRET_IMPORTS.CREATE.import.environment),
|
||||
path: z.string().trim().transform(removeTrailingSlash).describe(SECRET_IMPORTS.CREATE.import.path)
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().transform(removeTrailingSlash)
|
||||
})
|
||||
}),
|
||||
response: {
|
||||
@ -82,21 +81,20 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretImportId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.secretImportId)
|
||||
secretImportId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
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),
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
import: z.object({
|
||||
environment: z.string().trim().optional().describe(SECRET_IMPORTS.UPDATE.import.environment),
|
||||
environment: z.string().trim().optional(),
|
||||
path: z
|
||||
.string()
|
||||
.trim()
|
||||
.optional()
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : val))
|
||||
.describe(SECRET_IMPORTS.UPDATE.import.path),
|
||||
position: z.number().optional().describe(SECRET_IMPORTS.UPDATE.import.position)
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
||||
position: z.number().optional()
|
||||
})
|
||||
}),
|
||||
response: {
|
||||
@ -154,12 +152,12 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretImportId: z.string().trim().describe(SECRET_IMPORTS.DELETE.secretImportId)
|
||||
secretImportId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
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)
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -215,9 +213,9 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
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)
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -1,7 +1,6 @@
|
||||
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";
|
||||
|
||||
@ -19,7 +18,7 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
orgId: z.string().trim().describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orgId)
|
||||
orgId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -7,7 +7,6 @@ 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";
|
||||
@ -57,8 +56,8 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
projectId: z.string().trim().describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.projectId),
|
||||
identityId: z.string().trim().describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.identityId)
|
||||
projectId: z.string().trim(),
|
||||
identityId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
roles: z
|
||||
@ -78,7 +77,6 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
])
|
||||
)
|
||||
.min(1)
|
||||
.describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.roles)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -112,8 +110,8 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
projectId: z.string().trim().describe(PROJECTS.DELETE_IDENTITY_MEMBERSHIP.projectId),
|
||||
identityId: z.string().trim().describe(PROJECTS.DELETE_IDENTITY_MEMBERSHIP.identityId)
|
||||
projectId: z.string().trim(),
|
||||
identityId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -146,7 +144,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
projectId: z.string().trim().describe(PROJECTS.LIST_IDENTITY_MEMBERSHIPS.projectId)
|
||||
projectId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -1,7 +1,6 @@
|
||||
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";
|
||||
|
||||
@ -18,7 +17,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
organizationId: z.string().trim().describe(ORGANIZATIONS.LIST_USER_MEMBERSHIPS.organizationId)
|
||||
organizationId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -64,7 +63,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
organizationId: z.string().trim().describe(ORGANIZATIONS.GET_PROJECTS.organizationId)
|
||||
organizationId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -109,12 +108,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
organizationId: z.string().trim().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.organizationId),
|
||||
membershipId: z.string().trim().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.membershipId)
|
||||
}),
|
||||
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
|
||||
body: z.object({
|
||||
role: z.string().trim().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.role)
|
||||
role: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -149,10 +145,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
apiKeyAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
organizationId: z.string().trim().describe(ORGANIZATIONS.DELETE_USER_MEMBERSHIP.organizationId),
|
||||
membershipId: z.string().trim().describe(ORGANIZATIONS.DELETE_USER_MEMBERSHIP.membershipId)
|
||||
}),
|
||||
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
|
||||
response: {
|
||||
200: z.object({
|
||||
membership: OrgMembershipsSchema
|
||||
|
@ -2,7 +2,6 @@ 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";
|
||||
|
||||
@ -12,11 +11,11 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
url: "/:projectId/memberships",
|
||||
schema: {
|
||||
params: z.object({
|
||||
projectId: z.string().describe(PROJECTS.INVITE_MEMBER.projectId)
|
||||
projectId: z.string().describe("The ID of the project.")
|
||||
}),
|
||||
body: z.object({
|
||||
emails: z.string().email().array().default([]).describe(PROJECTS.INVITE_MEMBER.emails),
|
||||
usernames: z.string().array().default([]).describe(PROJECTS.INVITE_MEMBER.usernames)
|
||||
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.")
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -58,12 +57,12 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
||||
url: "/:projectId/memberships",
|
||||
schema: {
|
||||
params: z.object({
|
||||
projectId: z.string().describe(PROJECTS.REMOVE_MEMBER.projectId)
|
||||
projectId: z.string().describe("The ID of the project.")
|
||||
}),
|
||||
|
||||
body: z.object({
|
||||
emails: z.string().email().array().default([]).describe(PROJECTS.REMOVE_MEMBER.emails),
|
||||
usernames: z.string().array().default([]).describe(PROJECTS.REMOVE_MEMBER.usernames)
|
||||
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.")
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -3,7 +3,6 @@ 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";
|
||||
@ -39,7 +38,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim().describe(PROJECTS.GET_KEY.workspaceId)
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: ProjectKeysSchema.merge(
|
||||
@ -141,17 +140,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
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"
|
||||
})
|
||||
projectName: z.string().trim().describe("Name of the project you're creating"),
|
||||
slug: slugSchema
|
||||
.optional()
|
||||
.describe(PROJECTS.CREATE.slug),
|
||||
organizationSlug: z.string().trim().describe(PROJECTS.CREATE.organizationSlug)
|
||||
.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")
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -10,7 +10,6 @@ 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";
|
||||
@ -35,15 +34,20 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
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),
|
||||
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),
|
||||
include_imports: z
|
||||
.enum(["true", "false"])
|
||||
.default("false")
|
||||
.transform((value) => value === "true")
|
||||
.describe(RAW_SECRETS.LIST.includeImports)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -144,19 +148,18 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim().describe(RAW_SECRETS.GET.secretName)
|
||||
secretName: z.string().trim()
|
||||
}),
|
||||
querystring: z.object({
|
||||
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),
|
||||
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),
|
||||
include_imports: z
|
||||
.enum(["true", "false"])
|
||||
.default("false")
|
||||
.transform((value) => value === "true")
|
||||
.describe(RAW_SECRETS.GET.includeImports)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -236,24 +239,16 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim().describe(RAW_SECRETS.CREATE.secretName)
|
||||
secretName: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
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)
|
||||
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)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -322,23 +317,15 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim().describe(RAW_SECRETS.UPDATE.secretName)
|
||||
secretName: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
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)
|
||||
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)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -405,18 +392,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
secretName: z.string().trim().describe(RAW_SECRETS.DELETE.secretName)
|
||||
secretName: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
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)
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
type: z.nativeEnum(SecretType).default(SecretType.Shared)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -260,44 +260,20 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
|
||||
* Return list of services for Render integration
|
||||
*/
|
||||
const getAppsRender = async ({ accessToken }: { accessToken: string }) => {
|
||||
const apps: Array<{ name: string; appId: string }> = [];
|
||||
let hasMorePages = true;
|
||||
const perPage = 100;
|
||||
let cursor;
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
const apps = res.map((a) => ({
|
||||
name: a.service.name,
|
||||
appId: a.service.id
|
||||
}));
|
||||
|
||||
return apps;
|
||||
};
|
||||
|
@ -1,5 +1,4 @@
|
||||
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";
|
||||
@ -25,8 +24,6 @@ import {
|
||||
TIntegrationAuthAppsDTO,
|
||||
TIntegrationAuthBitbucketWorkspaceDTO,
|
||||
TIntegrationAuthChecklyGroupsDTO,
|
||||
TIntegrationAuthGithubEnvsDTO,
|
||||
TIntegrationAuthGithubOrgsDTO,
|
||||
TIntegrationAuthHerokuPipelinesDTO,
|
||||
TIntegrationAuthNorthflankSecretGroupDTO,
|
||||
TIntegrationAuthQoveryEnvironmentsDTO,
|
||||
@ -438,75 +435,6 @@ 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" });
|
||||
@ -1133,8 +1061,6 @@ export const integrationAuthServiceFactory = ({
|
||||
getIntegrationApps,
|
||||
getVercelBranches,
|
||||
getApps,
|
||||
getGithubOrgs,
|
||||
getGithubEnvs,
|
||||
getChecklyGroups,
|
||||
getQoveryApps,
|
||||
getQoveryEnvs,
|
||||
|
@ -44,16 +44,6 @@ 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: false,
|
||||
Recursive: true,
|
||||
WithDecryption: true
|
||||
};
|
||||
|
||||
@ -1110,176 +1110,98 @@ 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;
|
||||
visibility?: "all" | "private" | "selected";
|
||||
selected_repositories_url?: string | undefined;
|
||||
}
|
||||
|
||||
interface GitHubSecretRes {
|
||||
[index: string]: GitHubSecret;
|
||||
}
|
||||
|
||||
const octokit = new Octokit({
|
||||
auth: accessToken
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
|
||||
// Get local copy of decrypted secrets. We cannot decrypt them as we dont have access to GH private key
|
||||
let encryptedSecrets: GitHubSecret[];
|
||||
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
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
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", {
|
||||
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}", {
|
||||
owner: integration.owner as string,
|
||||
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;
|
||||
}
|
||||
repo: integration.app as string,
|
||||
secret_name: key
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
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
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1307,22 +1229,6 @@ 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"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -23,8 +23,7 @@ export default defineConfig({
|
||||
loader: {
|
||||
".handlebars": "copy",
|
||||
".md": "copy",
|
||||
".txt": "copy",
|
||||
".pem": "copy"
|
||||
".txt": "copy"
|
||||
},
|
||||
external: ["../../../frontend/node_modules/next/dist/server/next-server.js"],
|
||||
outDir: "dist",
|
||||
|
@ -164,28 +164,6 @@ 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,14 +135,6 @@ 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,21 +74,6 @@ 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,13 +301,11 @@ 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 = newJwtToken
|
||||
userCredentialsToBeStored.JTWToken = loginTwoResponse.Token
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -482,44 +480,6 @@ 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)
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: "Delete"
|
||||
openapi: "DELETE /api/v1/folders/{folderIdOrName}"
|
||||
openapi: "DELETE /api/v1/folders/{folderId}"
|
||||
---
|
||||
|
@ -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 here](https://github.com/Infisical/infisical/issues).
|
||||
Missing a language? [Throw in a request](https://github.com/Infisical/infisical/issues).
|
Binary file not shown.
Before Width: | Height: | Size: 691 KiB |
Binary file not shown.
Before Width: | Height: | Size: 709 KiB |
Binary file not shown.
Before Width: | Height: | Size: 715 KiB |
Binary file not shown.
Before Width: | Height: | Size: 649 KiB After Width: | Height: | Size: 398 KiB |
@ -3,14 +3,17 @@ 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.
|
||||
@ -26,27 +29,12 @@ Prerequisites:
|
||||
Although this step breaks E2EE, it's necessary for Infisical to sync the environment variables to the cloud platform.
|
||||
</Info>
|
||||
</Step>
|
||||
<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 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>
|
||||
</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
|
||||
@ -57,13 +45,13 @@ Prerequisites:
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
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.
|
||||
@ -71,17 +59,17 @@ Prerequisites:
|
||||
</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"
|
||||
},
|
||||
"tabs": [
|
||||
"anchors": [
|
||||
{
|
||||
"name": "Changelog",
|
||||
"url": "changelog"
|
||||
},
|
||||
{
|
||||
"name": "API Reference",
|
||||
"url": "api-reference"
|
||||
"name": "Internals",
|
||||
"icon": "sitemap",
|
||||
"url": "internals"
|
||||
},
|
||||
{
|
||||
"name": "SDKs",
|
||||
"icon": "puzzle-piece",
|
||||
"url": "sdks"
|
||||
},
|
||||
{
|
||||
"name": "Contributing",
|
||||
"url": "contributing"
|
||||
}
|
||||
],
|
||||
"anchors": [
|
||||
|
||||
"name": "API Reference",
|
||||
"icon": "cloud",
|
||||
"url": "api-reference"
|
||||
},
|
||||
{
|
||||
"name": "Changelog",
|
||||
"icon": "timer",
|
||||
"url": "changelog"
|
||||
},
|
||||
{
|
||||
"name": "Contributing",
|
||||
"icon": "code",
|
||||
"url": "contributing"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Blog",
|
||||
"icon": "newspaper",
|
||||
@ -79,11 +79,6 @@
|
||||
"name": "GitHub",
|
||||
"icon": "github",
|
||||
"url": "https://github.com/Infisical/infisical"
|
||||
},
|
||||
{
|
||||
"name": "Internals",
|
||||
"icon": "sitemap",
|
||||
"url": "internals"
|
||||
}
|
||||
],
|
||||
"navigation": [
|
||||
@ -196,7 +191,6 @@
|
||||
"self-hosting/guides/mongo-to-postgres"
|
||||
]
|
||||
},
|
||||
"self-hosting/ee",
|
||||
"self-hosting/faq"
|
||||
]
|
||||
},
|
||||
@ -362,18 +356,7 @@
|
||||
},
|
||||
{
|
||||
"group": "Overview",
|
||||
"pages": [
|
||||
"sdks/overview"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "SDK's",
|
||||
"pages": [
|
||||
"sdks/languages/node",
|
||||
"sdks/languages/python",
|
||||
"sdks/languages/java",
|
||||
"sdks/languages/csharp"
|
||||
]
|
||||
"pages": ["sdks/overview"]
|
||||
},
|
||||
{
|
||||
"group": "Overview",
|
||||
|
@ -1,7 +1,6 @@
|
||||
---
|
||||
title: "Infisical .NET SDK"
|
||||
sidebarTitle: ".NET"
|
||||
icon: "bars"
|
||||
icon: "C#"
|
||||
---
|
||||
|
||||
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,6 +1,5 @@
|
||||
---
|
||||
title: "Infisical Java SDK"
|
||||
sidebarTitle: "Java"
|
||||
icon: "java"
|
||||
---
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
---
|
||||
title: "Infisical Node.js SDK"
|
||||
sidebarTitle: "Node.js"
|
||||
icon: "node"
|
||||
---
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
---
|
||||
title: "Infisical Python SDK"
|
||||
sidebarTitle: "Python"
|
||||
icon: "python"
|
||||
---
|
||||
|
||||
|
@ -1,28 +0,0 @@
|
||||
---
|
||||
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 team@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>
|
@ -7,7 +7,6 @@
|
||||
/* 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";
|
||||
|
@ -1,4 +1,3 @@
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-nocheck
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
/* eslint-disable global-require */
|
||||
|
@ -6,8 +6,6 @@ export {
|
||||
useGetIntegrationAuthBitBucketWorkspaces,
|
||||
useGetIntegrationAuthById,
|
||||
useGetIntegrationAuthChecklyGroups,
|
||||
useGetIntegrationAuthGithubEnvs,
|
||||
useGetIntegrationAuthGithubOrgs,
|
||||
useGetIntegrationAuthNorthflankSecretGroups,
|
||||
useGetIntegrationAuthRailwayEnvironments,
|
||||
useGetIntegrationAuthRailwayServices,
|
||||
|
@ -39,10 +39,6 @@ const integrationAuthKeys = {
|
||||
integrationAuthId: string;
|
||||
accountId: string;
|
||||
}) => [{ integrationAuthId, accountId }, "integrationAuthChecklyGroups"] as const,
|
||||
getIntegrationAuthGithubOrgs: (integrationAuthId: string) =>
|
||||
[{ integrationAuthId }, "integrationAuthGithubOrgs"] as const,
|
||||
getIntegrationAuthGithubEnvs: (integrationAuthId: string, repoName: string, repoOwner: string) =>
|
||||
[{ integrationAuthId, repoName, repoOwner }, "integrationAuthGithubOrgs"] as const,
|
||||
getIntegrationAuthQoveryOrgs: (integrationAuthId: string) =>
|
||||
[{ integrationAuthId }, "integrationAuthQoveryOrgs"] as const,
|
||||
getIntegrationAuthQoveryProjects: ({
|
||||
@ -181,32 +177,6 @@ const fetchIntegrationAuthVercelBranches = async ({
|
||||
return branches;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthGithubOrgs = async (integrationAuthId: string) => {
|
||||
const {
|
||||
data: { orgs }
|
||||
} = await apiRequest.get<{ orgs: Org[] }>(
|
||||
`/api/v1/integration-auth/${integrationAuthId}/github/orgs`
|
||||
);
|
||||
|
||||
return orgs;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthGithubEnvs = async (
|
||||
integrationAuthId: string,
|
||||
repoName: string,
|
||||
repoOwner: string
|
||||
) => {
|
||||
if (!repoName || !repoOwner) return [];
|
||||
|
||||
const {
|
||||
data: { envs }
|
||||
} = await apiRequest.get<{ envs: Array<{ name: string; envId: string }> }>(
|
||||
`/api/v1/integration-auth/${integrationAuthId}/github/envs?repoName=${repoName}&repoOwner=${repoOwner}`
|
||||
);
|
||||
|
||||
return envs;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthQoveryOrgs = async (integrationAuthId: string) => {
|
||||
const {
|
||||
data: { orgs }
|
||||
@ -331,6 +301,8 @@ const fetchIntegrationAuthHerokuPipelines = async ({ integrationAuthId }: {
|
||||
`/api/v1/integration-auth/${integrationAuthId}/heroku/pipelines`
|
||||
);
|
||||
|
||||
console.log(99999, pipelines)
|
||||
|
||||
return pipelines;
|
||||
};
|
||||
|
||||
@ -510,30 +482,6 @@ export const useGetIntegrationAuthChecklyGroups = ({
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthGithubOrgs = (integrationAuthId: string) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthGithubOrgs(integrationAuthId),
|
||||
queryFn: () => fetchIntegrationAuthGithubOrgs(integrationAuthId),
|
||||
enabled: true
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthGithubEnvs = (
|
||||
integrationAuthId: string,
|
||||
repoName: string,
|
||||
repoOwner: string
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthGithubEnvs(
|
||||
integrationAuthId,
|
||||
repoName,
|
||||
repoOwner
|
||||
),
|
||||
queryFn: () => fetchIntegrationAuthGithubEnvs(integrationAuthId, repoName, repoOwner),
|
||||
enabled: true
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthQoveryOrgs = (integrationAuthId: string) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthQoveryOrgs(integrationAuthId),
|
||||
|
@ -62,7 +62,6 @@ export const useCreateIntegration = () => {
|
||||
secretPrefix?: string;
|
||||
secretSuffix?: string;
|
||||
initialSyncBehavior?: string;
|
||||
shouldAutoRedeploy?: boolean;
|
||||
}
|
||||
}) => {
|
||||
const { data: { integration } } = await apiRequest.post("/api/v1/integration", {
|
||||
|
@ -11,21 +11,22 @@ export type TCloudIntegration = {
|
||||
|
||||
export type TIntegration = {
|
||||
id: string;
|
||||
isActive: boolean;
|
||||
url?: string;
|
||||
app?: string;
|
||||
appId?: string;
|
||||
targetEnvironment?: string;
|
||||
targetEnvironmentId?: string;
|
||||
targetService?: string;
|
||||
targetServiceId?: string;
|
||||
owner?: string;
|
||||
path?: string;
|
||||
region?: string;
|
||||
scope?: string;
|
||||
integration: string;
|
||||
integrationAuthId: string;
|
||||
projectId: string;
|
||||
envId: string;
|
||||
environment: { slug: string; name: string; id: string };
|
||||
isActive: boolean;
|
||||
url: any;
|
||||
app: string;
|
||||
appId: string;
|
||||
targetEnvironment: string;
|
||||
targetEnvironmentId: string;
|
||||
targetService: string;
|
||||
targetServiceId: string;
|
||||
owner: string;
|
||||
path: string;
|
||||
region: string;
|
||||
integration: string;
|
||||
integrationAuth: string;
|
||||
secretPath: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
@ -44,4 +45,4 @@ export enum IntegrationSyncBehavior {
|
||||
OVERWRITE_TARGET = "overwrite-target",
|
||||
PREFER_TARGET = "prefer-target",
|
||||
PREFER_SOURCE = "prefer-source"
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@
|
||||
/* 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 { useTranslation } from "react-i18next";
|
||||
@ -92,7 +91,7 @@ export const AdminLayout = ({ children }: LayoutProps) => {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex">
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
@ -13,14 +12,11 @@ import {
|
||||
faCircleInfo
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import axios from "axios";
|
||||
import { motion } from "framer-motion";
|
||||
import queryString from "query-string";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { useCreateIntegration } from "@app/hooks/api";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
@ -37,547 +33,263 @@ import {
|
||||
TabList,
|
||||
TabPanel,
|
||||
Tabs
|
||||
} from "@app/components/v2";
|
||||
} from "../../../components/v2";
|
||||
import {
|
||||
useCreateIntegration,
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById,
|
||||
useGetIntegrationAuthGithubEnvs,
|
||||
useGetIntegrationAuthGithubOrgs,
|
||||
useGetWorkspaceById
|
||||
} from "@app/hooks/api";
|
||||
useGetIntegrationAuthById
|
||||
} from "../../../hooks/api/integrationAuth";
|
||||
import { useGetWorkspaceById } from "../../../hooks/api/workspace";
|
||||
|
||||
enum TabSections {
|
||||
Connection = "connection",
|
||||
Options = "options"
|
||||
}
|
||||
|
||||
const targetEnv = ["github-repo", "github-org", "github-env"] as const;
|
||||
type TargetEnv = (typeof targetEnv)[number];
|
||||
|
||||
const schema = yup.object({
|
||||
selectedSourceEnvironment: yup.string().trim().required("Project Environment is required"),
|
||||
secretPath: yup.string().trim().required("Secrets Path is required"),
|
||||
secretSuffix: yup.string().trim().optional(),
|
||||
|
||||
scope: yup.mixed<TargetEnv>().oneOf(targetEnv.slice()).required(),
|
||||
|
||||
repoIds: yup.mixed().when("scope", {
|
||||
is: "github-repo",
|
||||
then: yup.array(yup.string().required()).min(1, "Select at least one repositories")
|
||||
}),
|
||||
|
||||
repoId: yup.mixed().when("scope", {
|
||||
is: "github-env",
|
||||
then: yup.string().required("Repository is required")
|
||||
}),
|
||||
|
||||
repoName: yup.mixed().when("scope", {
|
||||
is: "github-env",
|
||||
then: yup.string().required("Repository is required")
|
||||
}),
|
||||
|
||||
repoOwner: yup.mixed().when("scope", {
|
||||
is: "github-env",
|
||||
then: yup.string().required("Repository is required")
|
||||
}),
|
||||
|
||||
envId: yup.mixed().when("scope", {
|
||||
is: "github-env",
|
||||
then: yup.string().required("Environment is required")
|
||||
}),
|
||||
|
||||
orgId: yup.mixed().when("scope", {
|
||||
is: "github-org",
|
||||
then: yup.string().required("Organization is required")
|
||||
})
|
||||
});
|
||||
|
||||
type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
export default function GitHubCreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const { mutateAsync } = useCreateIntegration();
|
||||
const { createNotification } = useNotificationContext();
|
||||
|
||||
const integrationAuthId =
|
||||
(queryString.parse(router.asPath.split("?")[1]).integrationAuthId as string) ?? "";
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId);
|
||||
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } =
|
||||
useGetIntegrationAuthApps({
|
||||
integrationAuthId
|
||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
|
||||
const { data: integrationAuthOrgs } = useGetIntegrationAuthGithubOrgs(
|
||||
integrationAuthId as string
|
||||
);
|
||||
|
||||
const { control, handleSubmit, watch, setValue } = useForm<FormData>({
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
secretPath: "/",
|
||||
scope: "github-repo",
|
||||
repoIds: []
|
||||
}
|
||||
});
|
||||
|
||||
const scope = watch("scope");
|
||||
const repoId = watch("repoId");
|
||||
const repoIds = watch("repoIds");
|
||||
const repoName = watch("repoName");
|
||||
const repoOwner = watch("repoOwner");
|
||||
|
||||
const { data: integrationAuthGithubEnvs } = useGetIntegrationAuthGithubEnvs(
|
||||
integrationAuthId as string,
|
||||
repoName,
|
||||
repoOwner
|
||||
);
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
const [targetAppIds, setTargetAppIds] = useState<string[]>([]);
|
||||
const [secretSuffix, setSecretSuffix] = useState("");
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setValue("selectedSourceEnvironment", workspace.environments[0].slug);
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthGithubEnvs && integrationAuthGithubEnvs?.length > 0) {
|
||||
setValue("envId", integrationAuthGithubEnvs[0].envId);
|
||||
} else {
|
||||
setValue("envId", undefined);
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetAppIds([String(integrationAuthApps[0].appId)]);
|
||||
} else {
|
||||
setTargetAppIds(["none"]);
|
||||
}
|
||||
}
|
||||
}, [integrationAuthGithubEnvs]);
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
const onFormSubmit = async (data: FormData) => {
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
if (!integrationAuth?.id) return;
|
||||
|
||||
switch (data.scope) {
|
||||
case "github-repo": {
|
||||
const targetApps = integrationAuthApps?.filter((integrationAuthApp) =>
|
||||
data.repoIds?.includes(String(integrationAuthApp.appId))
|
||||
);
|
||||
const targetApps = integrationAuthApps?.filter((integrationAuthApp) =>
|
||||
targetAppIds.includes(String(integrationAuthApp.appId))
|
||||
);
|
||||
|
||||
if (!targetApps) return;
|
||||
if (!targetApps) return;
|
||||
|
||||
await Promise.all(
|
||||
targetApps.map(async (targetApp) => {
|
||||
await mutateAsync({
|
||||
integrationAuthId: integrationAuth?.id,
|
||||
isActive: true,
|
||||
scope: data.scope,
|
||||
secretPath: data.secretPath,
|
||||
sourceEnvironment: data.selectedSourceEnvironment,
|
||||
app: targetApp.name, // repo name
|
||||
owner: targetApp.owner, // repo owner
|
||||
metadata: {
|
||||
secretSuffix: data.secretSuffix
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
case "github-org":
|
||||
await Promise.all(
|
||||
targetApps.map(async (targetApp) => {
|
||||
await mutateAsync({
|
||||
integrationAuthId: integrationAuth?.id,
|
||||
isActive: true,
|
||||
secretPath: data.secretPath,
|
||||
sourceEnvironment: data.selectedSourceEnvironment,
|
||||
scope: data.scope,
|
||||
owner: integrationAuthOrgs?.find((e) => e.orgId === data.orgId)?.name,
|
||||
app: targetApp.name,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
owner: targetApp.owner,
|
||||
secretPath,
|
||||
metadata: {
|
||||
secretSuffix: data.secretSuffix
|
||||
secretSuffix
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case "github-env":
|
||||
await mutateAsync({
|
||||
integrationAuthId: integrationAuth?.id,
|
||||
isActive: true,
|
||||
secretPath: data.secretPath,
|
||||
sourceEnvironment: data.selectedSourceEnvironment,
|
||||
scope: data.scope,
|
||||
app: repoName,
|
||||
appId: data.repoId,
|
||||
owner: repoOwner,
|
||||
targetEnvironmentId: data.envId,
|
||||
metadata: {
|
||||
secretSuffix: data.secretSuffix
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error("Invalid scope");
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
setIsLoading(false);
|
||||
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
let errorMessage: string = "Something went wrong!";
|
||||
if (axios.isAxiosError(err)) {
|
||||
const { message } = err?.response?.data as { message: string };
|
||||
errorMessage = message;
|
||||
}
|
||||
|
||||
createNotification({
|
||||
text: errorMessage,
|
||||
type: "error"
|
||||
});
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return integrationAuth && workspace && integrationAuthApps ? (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center py-4">
|
||||
return integrationAuth &&
|
||||
workspace &&
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps &&
|
||||
targetAppIds ? (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center">
|
||||
<Head>
|
||||
<title>Set Up GitHub Integration</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600 p-0">
|
||||
<form onSubmit={handleSubmit(onFormSubmit)} className="px-6">
|
||||
<CardTitle
|
||||
className="px-0 text-left text-xl"
|
||||
subTitle="Choose which environment in Infisical you want to sync to environment variables in GitHub."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="flex items-center rounded-full bg-mineshaft-200">
|
||||
<Image
|
||||
src="/images/integrations/GitHub.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="GitHub logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">GitHub Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cicd/githubactions" passHref>
|
||||
<a target="_blank" rel="noopener noreferrer">
|
||||
<div className="ml-2 mb-1 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
|
||||
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||
Docs
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="ml-1.5 mb-[0.07rem] text-xxs"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
<CardTitle
|
||||
className="px-6 text-left text-xl"
|
||||
subTitle="Choose which environment in Infisical you want to sync to environment variables in GitHub."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center rounded-full bg-mineshaft-200">
|
||||
<Image
|
||||
src="/images/integrations/GitHub.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="GitHub logo"
|
||||
/>
|
||||
</div>
|
||||
</CardTitle>
|
||||
<Tabs defaultValue={TabSections.Connection}>
|
||||
<TabList>
|
||||
<div className="flex w-full flex-row border-b border-mineshaft-600">
|
||||
<Tab value={TabSections.Connection}>Connection</Tab>
|
||||
<Tab value={TabSections.Options}>Options</Tab>
|
||||
</div>
|
||||
</TabList>
|
||||
<TabPanel value={TabSections.Connection}>
|
||||
<motion.div
|
||||
key="panel-1"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<Controller
|
||||
control={control}
|
||||
name="selectedSourceEnvironment"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Project Environment"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
onValueChange={onChange}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem
|
||||
value={sourceEnvironment.slug}
|
||||
key={`source-environment-${sourceEnvironment.slug}`}
|
||||
>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="secretPath"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Secrets Path"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Input {...field} placeholder="Provide a path, default is /" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="scope"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl label="Scope" errorText={error?.message} isError={Boolean(error)}>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
onValueChange={onChange}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
<SelectItem value="github-org">Organization</SelectItem>
|
||||
<SelectItem value="github-repo">Repository</SelectItem>
|
||||
<SelectItem value="github-env">Repository Environment</SelectItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{scope === "github-repo" && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="repoIds"
|
||||
render={({ field: { onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Repositories"
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
<div className="inline-flex w-full cursor-pointer items-center justify-between rounded-md border border-mineshaft-600 bg-mineshaft-900 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none data-[placeholder]:text-mineshaft-200">
|
||||
{repoIds.length === 1
|
||||
? integrationAuthApps?.reduce(
|
||||
(acc, { appId, name, owner }) =>
|
||||
repoIds[0] === appId ? `${owner}/${name}` : acc,
|
||||
""
|
||||
)
|
||||
: `${repoIds.length} repositories selected`}
|
||||
<FontAwesomeIcon icon={faAngleDown} className="text-xs" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="inline-flex w-full cursor-default items-center justify-between rounded-md border border-mineshaft-600 bg-mineshaft-900 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none data-[placeholder]:text-mineshaft-200">
|
||||
No repositories found
|
||||
</div>
|
||||
)}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
className="thin-scrollbar z-[100] max-h-80 overflow-y-scroll"
|
||||
>
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => {
|
||||
const isSelected = repoIds.includes(
|
||||
String(integrationAuthApp.appId)
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
if (repoIds.includes(String(integrationAuthApp.appId))) {
|
||||
onChange(
|
||||
repoIds.filter(
|
||||
(appId: string) =>
|
||||
appId !== String(integrationAuthApp.appId)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
onChange([...repoIds, String(integrationAuthApp.appId)]);
|
||||
}
|
||||
}}
|
||||
key={`repos-id-${integrationAuthApp.appId}`}
|
||||
icon={
|
||||
isSelected ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckCircle}
|
||||
className="pr-0.5 text-primary"
|
||||
/>
|
||||
) : (
|
||||
<div className="pl-[1.01rem]" />
|
||||
)
|
||||
}
|
||||
iconPos="left"
|
||||
className="w-[28.4rem] text-sm"
|
||||
>
|
||||
{integrationAuthApp.owner}/{integrationAuthApp.name}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</FormControl>
|
||||
)}
|
||||
<span className="ml-2.5">GitHub Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cicd/githubactions" passHref>
|
||||
<a target="_blank" rel="noopener noreferrer">
|
||||
<div className="ml-2 mb-1 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
|
||||
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||
Docs
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="ml-1.5 mb-[0.07rem] text-xxs"
|
||||
/>
|
||||
)}
|
||||
{scope === "github-org" && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="orgId"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Organization"
|
||||
errorText={
|
||||
integrationAuthOrgs?.length ? error?.message : "No organizations found"
|
||||
}
|
||||
isError={Boolean(integrationAuthOrgs?.length && error?.message)}
|
||||
>
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={onChange}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
{integrationAuthOrgs &&
|
||||
integrationAuthOrgs.map(({ name, orgId }) => (
|
||||
<SelectItem key={`github-organization-${orgId}`} value={orgId}>
|
||||
{name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{scope === "github-env" && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="repoId"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Repository"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={(e) => {
|
||||
const selectedRepo = integrationAuthApps.find((app) => app.appId === e);
|
||||
setValue("repoName", selectedRepo?.name);
|
||||
setValue("repoOwner", selectedRepo?.owner);
|
||||
onChange(e);
|
||||
}}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
{integrationAuthApps?.length ? (
|
||||
integrationAuthApps.map((app) => {
|
||||
return (
|
||||
<SelectItem
|
||||
value={app.appId as string}
|
||||
key={`repo-id-${app.appId}`}
|
||||
className="w-[28.4rem] text-sm"
|
||||
>
|
||||
{app.owner}/{app.name}
|
||||
</SelectItem>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{scope === "github-env" && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="envId"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environment"
|
||||
errorText={
|
||||
integrationAuthGithubEnvs?.length
|
||||
? error?.message
|
||||
: "No Environment found"
|
||||
}
|
||||
isError={Boolean(integrationAuthGithubEnvs?.length || error?.message)}
|
||||
>
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={onChange}
|
||||
isDisabled={!repoId}
|
||||
className={twMerge(
|
||||
"w-full border border-mineshaft-500",
|
||||
!repoId && "h-10 cursor-not-allowed"
|
||||
)}
|
||||
>
|
||||
{integrationAuthGithubEnvs?.length ? (
|
||||
integrationAuthGithubEnvs.map((githubEnv) => {
|
||||
return (
|
||||
<SelectItem
|
||||
value={githubEnv.name as string}
|
||||
key={`env-id-${githubEnv.envId}`}
|
||||
className="w-[28.4rem] text-sm"
|
||||
>
|
||||
{githubEnv.name}
|
||||
</SelectItem>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
<TabPanel value={TabSections.Options}>
|
||||
<motion.div
|
||||
key="panel-1"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: -30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<Controller
|
||||
control={control}
|
||||
name="secretSuffix"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Append Secret Names with..."
|
||||
className="pb-[9.75rem]"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="Provide a suffix for secret names, default is no suffix"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
<div className="flex w-full justify-end">
|
||||
<Button
|
||||
type="submit"
|
||||
color="mineshaft"
|
||||
variant="outline_bg"
|
||||
className="mb-6"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
</CardTitle>
|
||||
<Tabs defaultValue={TabSections.Connection} className="px-6">
|
||||
<TabList>
|
||||
<div className="flex w-full flex-row border-b border-mineshaft-600">
|
||||
<Tab value={TabSections.Connection}>Connection</Tab>
|
||||
<Tab value={TabSections.Options}>Options</Tab>
|
||||
</div>
|
||||
</TabList>
|
||||
<TabPanel value={TabSections.Connection}>
|
||||
<motion.div
|
||||
key="panel-1"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<FormControl label="Project Environment">
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem
|
||||
value={sourceEnvironment.slug}
|
||||
key={`azure-key-vault-environment-${sourceEnvironment.slug}`}
|
||||
>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="GitHub Repo">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
<div className="inline-flex w-full cursor-pointer items-center justify-between rounded-md border border-mineshaft-600 bg-mineshaft-900 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none data-[placeholder]:text-mineshaft-200">
|
||||
{targetAppIds.length === 1
|
||||
? integrationAuthApps?.find(
|
||||
(integrationAuthApp) =>
|
||||
targetAppIds[0] === String(integrationAuthApp.appId)
|
||||
)?.name
|
||||
: `${targetAppIds.length} repositories selected`}
|
||||
<FontAwesomeIcon icon={faAngleDown} className="text-xs" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="inline-flex w-full cursor-default items-center justify-between rounded-md border border-mineshaft-600 bg-mineshaft-900 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none data-[placeholder]:text-mineshaft-200">
|
||||
No repositories found
|
||||
</div>
|
||||
)}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
className="thin-scrollbar z-[100] max-h-80 overflow-y-scroll"
|
||||
>
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => {
|
||||
const isSelected = targetAppIds.includes(String(integrationAuthApp.appId));
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
if (targetAppIds.includes(String(integrationAuthApp.appId))) {
|
||||
setTargetAppIds(
|
||||
targetAppIds.filter(
|
||||
(appId) => appId !== String(integrationAuthApp.appId)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setTargetAppIds([
|
||||
...targetAppIds,
|
||||
String(integrationAuthApp.appId)
|
||||
]);
|
||||
}
|
||||
}}
|
||||
key={integrationAuthApp.appId}
|
||||
icon={
|
||||
isSelected ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckCircle}
|
||||
className="pr-0.5 text-primary"
|
||||
/>
|
||||
) : (
|
||||
<div className="pl-[1.01rem]" />
|
||||
)
|
||||
}
|
||||
iconPos="left"
|
||||
className="w-[28.4rem] text-sm"
|
||||
>
|
||||
{integrationAuthApp.name}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</FormControl>
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
<TabPanel value={TabSections.Options}>
|
||||
<motion.div
|
||||
key="panel-1"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: -30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<FormControl label="Append Secret Names with..." className="pb-[9.75rem]">
|
||||
<Input
|
||||
value={secretSuffix}
|
||||
onChange={(evt) => setSecretSuffix(evt.target.value)}
|
||||
placeholder="Provide a suffix for secret names, default is no suffix"
|
||||
/>
|
||||
</FormControl>
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
variant="outline_bg"
|
||||
className="mb-6 ml-auto mr-6"
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0 || targetAppIds.length === 0}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
</Card>
|
||||
<div className="mt-6 w-full max-w-md border-t border-mineshaft-800" />
|
||||
<div className="mt-6 flex w-full max-w-lg flex-col rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4">
|
||||
@ -606,7 +318,7 @@ export default function GitHubCreateIntegrationPage() {
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-max max-w-md flex-col rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6 text-center text-mineshaft-200">
|
||||
<FontAwesomeIcon icon={faBugs} className="li my-2 inline text-6xl" />
|
||||
<FontAwesomeIcon icon={faBugs} className="inlineli my-2 text-6xl" />
|
||||
<p>
|
||||
Something went wrong. Please contact{" "}
|
||||
<a
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
@ -11,9 +10,7 @@ import {
|
||||
faCircleInfo
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import queryString from "query-string";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { useCreateIntegration } from "@app/hooks/api";
|
||||
|
||||
@ -24,8 +21,7 @@ import {
|
||||
FormControl,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem,
|
||||
Switch
|
||||
SelectItem
|
||||
} from "../../../components/v2";
|
||||
import {
|
||||
useGetIntegrationAuthApps,
|
||||
@ -33,28 +29,9 @@ import {
|
||||
} from "../../../hooks/api/integrationAuth";
|
||||
import { useGetWorkspaceById } from "../../../hooks/api/workspace";
|
||||
|
||||
const schema = yup.object({
|
||||
selectedSourceEnvironment: yup.string().required("Source environment is required"),
|
||||
secretPath: yup.string().required("Secret path is required"),
|
||||
targetAppId: yup.string().required("Render service is required"),
|
||||
shouldAutoRedeploy: yup.boolean()
|
||||
});
|
||||
|
||||
type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
export default function RenderCreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const { mutateAsync } = useCreateIntegration();
|
||||
|
||||
const { control, handleSubmit, setValue, watch } = useForm<FormData>({
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
secretPath: "/",
|
||||
shouldAutoRedeploy: false
|
||||
}
|
||||
});
|
||||
|
||||
const selectedSourceEnvironment = watch("selectedSourceEnvironment");
|
||||
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
|
||||
@ -67,29 +44,28 @@ export default function RenderCreateIntegrationPage() {
|
||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||
const [targetApp, setTargetApp] = useState("");
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setValue("selectedSourceEnvironment", workspace.environments[0].slug);
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setValue("targetAppId", integrationAuthApps[0].appId as string);
|
||||
setTargetApp(integrationAuthApps[0].name);
|
||||
} else {
|
||||
setValue("targetAppId", "none");
|
||||
setTargetApp("none");
|
||||
}
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
const onFormSubmit = async ({
|
||||
secretPath,
|
||||
targetAppId,
|
||||
shouldAutoRedeploy
|
||||
}: FormData) => {
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
if (!integrationAuth?.id) return;
|
||||
|
||||
@ -98,15 +74,12 @@ export default function RenderCreateIntegrationPage() {
|
||||
await mutateAsync({
|
||||
integrationAuthId: integrationAuth?.id,
|
||||
isActive: true,
|
||||
app: integrationAuthApps?.find(
|
||||
(integrationAuthApp) => integrationAuthApp.appId === targetAppId
|
||||
)?.name,
|
||||
appId: targetAppId,
|
||||
app: targetApp,
|
||||
appId: integrationAuthApps?.find(
|
||||
(integrationAuthApp) => integrationAuthApp.name === targetApp
|
||||
)?.appId,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
secretPath,
|
||||
metadata: {
|
||||
shouldAutoRedeploy
|
||||
}
|
||||
secretPath
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
@ -115,16 +88,14 @@ export default function RenderCreateIntegrationPage() {
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return integrationAuth &&
|
||||
workspace &&
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps ? (
|
||||
<form
|
||||
onSubmit={handleSubmit(onFormSubmit)}
|
||||
className="flex h-full w-full flex-col items-center justify-center"
|
||||
>
|
||||
integrationAuthApps &&
|
||||
targetApp ? (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center">
|
||||
<Head>
|
||||
<title>Set Up Render Integration</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
@ -158,107 +129,57 @@ export default function RenderCreateIntegrationPage() {
|
||||
</Link>
|
||||
</div>
|
||||
</CardTitle>
|
||||
<div className="px-6">
|
||||
<Controller
|
||||
control={control}
|
||||
name="selectedSourceEnvironment"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Project Environment"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
<FormControl label="Project Environment" className="px-6">
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem
|
||||
value={sourceEnvironment.slug}
|
||||
key={`source-environment-${sourceEnvironment.slug}`}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path" className="px-6">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl label="Render Service" className="px-6">
|
||||
<Select
|
||||
value={targetApp}
|
||||
onValueChange={(val) => setTargetApp(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem
|
||||
value={integrationAuthApp.name}
|
||||
key={`target-app-${integrationAuthApp.name}`}
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem
|
||||
value={sourceEnvironment.slug}
|
||||
key={`source-environment-${sourceEnvironment.slug}`}
|
||||
>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No services found
|
||||
</SelectItem>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="secretPath"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Secrets Path"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="/" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="targetAppId"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
label="Render Service"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
{...field}
|
||||
onValueChange={(e) => {
|
||||
if (e === "") return;
|
||||
onChange(e);
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem
|
||||
value={String(integrationAuthApp.appId as string)}
|
||||
key={`target-app-${String(integrationAuthApp.appId)}`}
|
||||
>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No services found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<div className="mt-8 mb-[2.36rem] ml-1">
|
||||
<Controller
|
||||
control={control}
|
||||
name="shouldAutoRedeploy"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Switch
|
||||
id="redeploy-render"
|
||||
onCheckedChange={(isChecked) => onChange(isChecked)}
|
||||
isChecked={value}
|
||||
>
|
||||
Auto-redeploy service upon secret change
|
||||
</Switch>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
variant="outline_bg"
|
||||
className="mb-8 ml-auto mr-6 w-min mt-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
className="mb-6 mt-2 ml-auto mr-6"
|
||||
isLoading={isLoading}
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
@ -276,7 +197,7 @@ export default function RenderCreateIntegrationPage() {
|
||||
cause an unexpected override of current secrets in Render with secrets from Infisical.
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Head>
|
||||
|
@ -54,25 +54,20 @@ export default function LoginPage() {
|
||||
|
||||
const handleSelectOrganization = useCallback(
|
||||
async (organization: Organization) => {
|
||||
const callbackPort = queryParams.get("callback_port");
|
||||
|
||||
if (organization.authEnforced) {
|
||||
// org has an org-level auth method enabled (e.g. SAML)
|
||||
// -> logout + redirect to SAML SSO
|
||||
let samlUrl = `/api/v1/sso/redirect/saml2/organizations/${organization.slug}`;
|
||||
|
||||
if (callbackPort) {
|
||||
samlUrl += `?callback_port=${callbackPort}`;
|
||||
}
|
||||
|
||||
await logout.mutateAsync();
|
||||
window.open(samlUrl);
|
||||
window.open(`/api/v1/sso/redirect/saml2/organizations/${organization.slug}`);
|
||||
window.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const { token } = await selectOrg.mutateAsync({ organizationId: organization.id });
|
||||
|
||||
const callbackPort = queryParams.get("callback_port");
|
||||
|
||||
if (callbackPort) {
|
||||
const privateKey = localStorage.getItem("PRIVATE_KEY");
|
||||
|
||||
|
@ -60,7 +60,7 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) =>
|
||||
link = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${window.location.origin}/integrations/netlify/oauth2/callback`;
|
||||
break;
|
||||
case "github":
|
||||
link = `https://github.com/login/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=repo,admin:org&redirect_uri=${window.location.origin}/integrations/github/oauth2/callback&state=${state}`;
|
||||
link = `https://github.com/login/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=repo&redirect_uri=${window.location.origin}/integrations/github/oauth2/callback&state=${state}`;
|
||||
break;
|
||||
case "gitlab":
|
||||
link = `${window.location.origin}/integrations/gitlab/authorize`;
|
||||
|
@ -9,8 +9,11 @@ import {
|
||||
AlertDescription,
|
||||
DeleteActionModal,
|
||||
EmptyState,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
IconButton,
|
||||
Select,
|
||||
SelectItem,
|
||||
Skeleton,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
@ -19,7 +22,7 @@ import { usePopUp } from "@app/hooks";
|
||||
import { TIntegration } from "@app/hooks/api/types";
|
||||
|
||||
type Props = {
|
||||
environments: Array<{ name: string; slug: string; id: string }>;
|
||||
environments: Array<{ name: string; slug: string }>;
|
||||
integrations?: TIntegration[];
|
||||
isLoading?: boolean;
|
||||
onIntegrationDelete: (integration: TIntegration, cb: () => void) => void;
|
||||
@ -77,15 +80,29 @@ export const IntegrationsSection = ({
|
||||
<div className="flex flex-col space-y-4 p-6 pt-0">
|
||||
{integrations?.map((integration) => (
|
||||
<div
|
||||
className="max-w-8xl flex justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-3"
|
||||
className="max-w-8xl flex justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-3 pb-0"
|
||||
key={`integration-${integration?.id.toString()}`}
|
||||
>
|
||||
<div className="flex">
|
||||
<div className="ml-2 flex flex-col">
|
||||
<FormLabel label="Environment" />
|
||||
<div className="rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{environments.find((e) => e.id === integration.envId)?.name || "-"}
|
||||
</div>
|
||||
<div>
|
||||
<FormControl label="Environment">
|
||||
<Select
|
||||
value={integration.environment.slug}
|
||||
isDisabled={integration.isActive}
|
||||
className="min-w-[8rem] border border-mineshaft-700"
|
||||
>
|
||||
{environments.map((environment) => {
|
||||
return (
|
||||
<SelectItem
|
||||
value={environment.slug}
|
||||
key={`environment-${environment.slug}`}
|
||||
>
|
||||
{environment.name}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className="ml-2 flex flex-col">
|
||||
<FormLabel label="Secret Path" />
|
||||
@ -125,22 +142,11 @@ export const IntegrationsSection = ({
|
||||
</div>
|
||||
)}
|
||||
<div className="ml-2 flex flex-col">
|
||||
<FormLabel
|
||||
label={
|
||||
(integration.integration === "qovery" && integration?.scope) ||
|
||||
(integration?.scope === "github-org" && "Organization") ||
|
||||
(["github-repo", "github-env"].includes(integration?.scope as string) &&
|
||||
"Repository") ||
|
||||
"App"
|
||||
}
|
||||
/>
|
||||
<div className="min-w-[8rem] max-w-[12rem] overflow-clip text-ellipsis whitespace-nowrap rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{(integration.integration === "hashicorp-vault" &&
|
||||
`${integration.app} - path: ${integration.path}`) ||
|
||||
(integration.scope === "github-org" && `${integration.owner}`) ||
|
||||
(integration.scope?.startsWith("github-") &&
|
||||
`${integration.owner}/${integration.app}`) ||
|
||||
integration.app}
|
||||
<FormLabel label={integration?.metadata?.scope || "App"} />
|
||||
<div className="min-w-[8rem] rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{integration.integration === "hashicorp-vault"
|
||||
? `${integration.app} - path: ${integration.path}`
|
||||
: integration.app}
|
||||
</div>
|
||||
</div>
|
||||
{(integration.integration === "vercel" ||
|
||||
@ -148,31 +154,32 @@ export const IntegrationsSection = ({
|
||||
integration.integration === "railway" ||
|
||||
integration.integration === "gitlab" ||
|
||||
integration.integration === "teamcity" ||
|
||||
integration.integration === "bitbucket" ||
|
||||
(integration.integration === "github" && integration.scope === "github-env")) && (
|
||||
integration.integration === "bitbucket") && (
|
||||
<div className="ml-4 flex flex-col">
|
||||
<FormLabel label="Target Environment" />
|
||||
<div className="overflow-clip text-ellipsis whitespace-nowrap rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{integration.targetEnvironment || integration.targetEnvironmentId}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{integration.integration === "checkly" && integration.targetService && (
|
||||
<div className="ml-2">
|
||||
<FormLabel label="Group" />
|
||||
<div className="rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{integration.targetService}
|
||||
{integration.targetEnvironment}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(integration.integration === "checkly" ||
|
||||
integration.integration === "github") && (
|
||||
<div className="ml-2">
|
||||
<FormLabel label="Secret Suffix" />
|
||||
<div className="rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{integration?.metadata?.secretSuffix || "-"}
|
||||
<>
|
||||
{integration.targetService && (
|
||||
<div className="ml-2">
|
||||
<FormLabel label="Group" />
|
||||
<div className="rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{integration.targetService}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="ml-2">
|
||||
<FormLabel label="Secret Suffix" />
|
||||
<div className="rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{integration?.metadata?.secretSuffix || "-"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex cursor-default items-center">
|
||||
@ -207,7 +214,7 @@ export const IntegrationsSection = ({
|
||||
(popUp?.deleteConfirmation.data as TIntegration)?.integration || " "
|
||||
} integration for ${(popUp?.deleteConfirmation.data as TIntegration)?.app || " "}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteConfirmation", isOpen)}
|
||||
deleteKey={(popUp?.deleteConfirmation?.data as TIntegration)?.app || (popUp?.deleteConfirmation?.data as TIntegration)?.owner || ""}
|
||||
deleteKey={(popUp?.deleteConfirmation?.data as TIntegration)?.app || ""}
|
||||
onDeleteApproved={async () =>
|
||||
onIntegrationDelete(popUp?.deleteConfirmation.data as TIntegration, () =>
|
||||
handlePopUpClose("deleteConfirmation")
|
||||
|
@ -261,9 +261,7 @@ export const MemberListTab = () => {
|
||||
ariaLabel="update"
|
||||
className="ml-4"
|
||||
isDisabled={userId === u?.id || !isAllowed}
|
||||
onClick={() =>
|
||||
handlePopUpOpen("removeMember", { username: u.username })
|
||||
}
|
||||
onClick={() => handlePopUpOpen("removeMember", { email: u.email })}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
|
@ -188,7 +188,7 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
exit={{ x: 10, opacity: 0 }}
|
||||
>
|
||||
<Tooltip content="Copy secret name">
|
||||
<Tooltip content="Copy secret">
|
||||
<IconButton
|
||||
ariaLabel="copy-value"
|
||||
variant="plain"
|
||||
|
@ -2,36 +2,38 @@ import { useTranslation } from "react-i18next";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { Button, Tooltip } from "@app/components/v2";
|
||||
import { Button } from "@app/components/v2";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { AddAPIKeyModal } from "./AddAPIKeyModal";
|
||||
import { APIKeyTable } from "./APIKeyTable";
|
||||
|
||||
export const APIKeySection = () => {
|
||||
const { t } = useTranslation();
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["addAPIKey"] as const);
|
||||
const { t } = useTranslation();
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"addAPIKey"
|
||||
] as const);
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-8 flex justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">
|
||||
{t("settings.personal.api-keys.title")}
|
||||
</p>
|
||||
<Tooltip content="API Keys are deprecated and will be removed in the future.">
|
||||
<Button
|
||||
isDisabled
|
||||
colorSchema="secondary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handlePopUpOpen("addAPIKey")}
|
||||
>
|
||||
Add API Key
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<APIKeyTable />
|
||||
<AddAPIKeyModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div className="mb-6 p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600">
|
||||
<div className="flex justify-between mb-8">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">
|
||||
{t("settings.personal.api-keys.title")}
|
||||
</p>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handlePopUpOpen("addAPIKey")}
|
||||
>
|
||||
Add API Key
|
||||
</Button>
|
||||
</div>
|
||||
<APIKeyTable />
|
||||
<AddAPIKeyModal
|
||||
popUp={popUp}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,199 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
IconButton,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent} from "@app/components/v2";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import {
|
||||
useCreateAPIKeyV2,
|
||||
useUpdateAPIKeyV2
|
||||
} from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const schema = yup.object({
|
||||
name: yup.string().required("API Key V2 name is required")
|
||||
}).required();
|
||||
|
||||
export type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
type Props = {
|
||||
popUp: UsePopUpState<["apiKeyV2"]>;
|
||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["apiKeyV2"]>, state?: boolean) => void;
|
||||
};
|
||||
|
||||
export const APIKeyV2Modal = ({
|
||||
popUp,
|
||||
handlePopUpToggle
|
||||
}: Props) => {
|
||||
const [newAPIKey, setNewAPIKey] = useState("");
|
||||
const [isAPIKeyCopied, setIsAPIKeyCopied] = useToggle(false);
|
||||
|
||||
const { createNotification } = useNotificationContext();
|
||||
|
||||
const { mutateAsync: createMutateAsync } = useCreateAPIKeyV2();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateAPIKeyV2();
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
name: ""
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let timer: NodeJS.Timeout;
|
||||
|
||||
if (isAPIKeyCopied) {
|
||||
timer = setTimeout(() => setIsAPIKeyCopied.off(), 2000);
|
||||
}
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [setIsAPIKeyCopied]);
|
||||
|
||||
useEffect(() => {
|
||||
const apiKeyData = popUp?.apiKeyV2?.data as {
|
||||
apiKeyDataId: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
if (apiKeyData) {
|
||||
reset({
|
||||
name: apiKeyData.name
|
||||
});
|
||||
} else {
|
||||
reset({
|
||||
name: ""
|
||||
});
|
||||
}
|
||||
}, [popUp?.apiKeyV2?.data]);
|
||||
|
||||
const copyTokenToClipboard = () => {
|
||||
navigator.clipboard.writeText(newAPIKey);
|
||||
setIsAPIKeyCopied.on();
|
||||
};
|
||||
|
||||
const onFormSubmit = async ({
|
||||
name
|
||||
}: FormData) => {
|
||||
try {
|
||||
const apiKeyData = popUp?.apiKeyV2?.data as {
|
||||
apiKeyDataId: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
if (apiKeyData) {
|
||||
// update
|
||||
|
||||
await updateMutateAsync({
|
||||
apiKeyDataId: apiKeyData.apiKeyDataId,
|
||||
name
|
||||
});
|
||||
|
||||
handlePopUpToggle("apiKeyV2", false);
|
||||
} else {
|
||||
// create
|
||||
|
||||
const { apiKey } = await createMutateAsync({
|
||||
name
|
||||
});
|
||||
|
||||
setNewAPIKey(apiKey);
|
||||
}
|
||||
|
||||
createNotification({
|
||||
text: `Successfully ${popUp?.apiKeyV2?.data ? "updated" : "created"} API Key`,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
reset();
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: `Failed to ${popUp?.apiKeyV2?.data ? "updated" : "created"} API Key`,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const hasAPIKey = Boolean(newAPIKey);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.apiKeyV2?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("apiKeyV2", isOpen);
|
||||
reset();
|
||||
setNewAPIKey("");
|
||||
}}
|
||||
>
|
||||
<ModalContent title={`${popUp?.apiKeyV2?.data ? "Update" : "Create"} API Key V2`}>
|
||||
{!hasAPIKey ? (
|
||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="name"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Name"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="My API Key"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
{popUp?.apiKeyV2?.data ? "Update" : "Create"}
|
||||
</Button>
|
||||
<Button colorSchema="secondary" variant="plain">
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<div className="mt-2 mb-3 mr-2 flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||
<p className="mr-4 break-all">{newAPIKey}</p>
|
||||
<IconButton
|
||||
ariaLabel="copy icon"
|
||||
colorSchema="secondary"
|
||||
className="group relative"
|
||||
onClick={copyTokenToClipboard}
|
||||
>
|
||||
<FontAwesomeIcon icon={isAPIKeyCopied ? faCheck : faCopy} />
|
||||
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
|
||||
Click to copy
|
||||
</span>
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal
|
||||
} from "@app/components/v2";
|
||||
import { useDeleteAPIKeyV2 } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { APIKeyV2Modal } from "./APIKeyV2Modal";
|
||||
import { APIKeyV2Table } from "./APIKeyV2Table";
|
||||
|
||||
export const APIKeyV2Section = () => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { mutateAsync: deleteMutateAsync } = useDeleteAPIKeyV2();
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"apiKeyV2",
|
||||
"deleteAPIKeyV2"
|
||||
] as const);
|
||||
|
||||
const onDeleteAPIKeyDataSubmit = async (apiKeyDataId: string) => {
|
||||
try {
|
||||
await deleteMutateAsync({
|
||||
apiKeyDataId
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully deleted API Key V2",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
handlePopUpClose("deleteAPIKeyV2");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to delete API Key V2",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-6 p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600">
|
||||
<div className="flex justify-between mb-8">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">
|
||||
API Keys V2 (Beta)
|
||||
</p>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handlePopUpOpen("apiKeyV2")}
|
||||
>
|
||||
Add API Key
|
||||
</Button>
|
||||
</div>
|
||||
<APIKeyV2Table
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
/>
|
||||
<APIKeyV2Modal
|
||||
popUp={popUp}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteAPIKeyV2.isOpen}
|
||||
title={`Are you sure want to delete ${
|
||||
(popUp?.deleteAPIKeyV2?.data as { name: string })?.name || ""
|
||||
}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteAPIKeyV2", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={() =>
|
||||
onDeleteAPIKeyDataSubmit(
|
||||
(popUp?.deleteAPIKeyV2?.data as { apiKeyDataId: string })?.apiKeyDataId
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
import { faKey, faPencil, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
|
||||
import {
|
||||
EmptyState,
|
||||
IconButton,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { useGetMyAPIKeysV2 } from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<["deleteAPIKeyV2", "apiKeyV2"]>,
|
||||
data?: {
|
||||
apiKeyDataId?: string;
|
||||
name?: string;
|
||||
}
|
||||
) => void;
|
||||
};
|
||||
|
||||
export const APIKeyV2Table = ({ handlePopUpOpen }: Props) => {
|
||||
const { data, isLoading } = useGetMyAPIKeysV2();
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th className="">Name</Th>
|
||||
<Th className="">Last Used</Th>
|
||||
<Th className="">Created At</Th>
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isLoading && <TableSkeleton columns={4} innerKey="api-keys-v2" />}
|
||||
{!isLoading &&
|
||||
data &&
|
||||
data.length > 0 &&
|
||||
data.map(({ id, name, lastUsed, createdAt }) => {
|
||||
return (
|
||||
<Tr className="h-10" key={`api-key-v2-${id}`}>
|
||||
<Td>{name}</Td>
|
||||
<Td>{lastUsed ? format(new Date(lastUsed), "yyyy-MM-dd") : "-"}</Td>
|
||||
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
|
||||
<Td className="flex justify-end">
|
||||
<IconButton
|
||||
onClick={async () => {
|
||||
handlePopUpOpen("apiKeyV2", {
|
||||
apiKeyDataId: id,
|
||||
name
|
||||
});
|
||||
}}
|
||||
size="lg"
|
||||
colorSchema="primary"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencil} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
handlePopUpOpen("deleteAPIKeyV2", {
|
||||
apiKeyDataId: id
|
||||
});
|
||||
}}
|
||||
size="lg"
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
className="ml-4"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
{!isLoading && data && data?.length === 0 && (
|
||||
<Tr>
|
||||
<Td colSpan={4}>
|
||||
<EmptyState title="No API key v2 on file" icon={faKey} />
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
</TBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
@ -0,0 +1 @@
|
||||
export { APIKeyV2Section } from "./APIKeyV2Section";
|
@ -1,5 +1,11 @@
|
||||
import { APIKeySection } from "../APIKeySection";
|
||||
// import { APIKeyV2Section } from "../APIKeyV2Section";
|
||||
|
||||
export const PersonalAPIKeyTab = () => {
|
||||
return <APIKeySection />;
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{/* <APIKeyV2Section /> */}
|
||||
<APIKeySection />
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,68 +1,44 @@
|
||||
import { Fragment } from "react";
|
||||
import Link from "next/link";
|
||||
import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { Fragment } from "react"
|
||||
import { Tab } from "@headlessui/react"
|
||||
|
||||
import { PersonalAPIKeyTab } from "../PersonalAPIKeyTab";
|
||||
import { PersonalAuthTab } from "../PersonalAuthTab";
|
||||
import { PersonalGeneralTab } from "../PersonalGeneralTab";
|
||||
|
||||
const tabs = [
|
||||
{ name: "General", key: "tab-account-general" },
|
||||
{ name: "Authentication", key: "tab-account-auth" },
|
||||
{ name: "API Keys", key: "tab-account-api-keys" }
|
||||
{ name: "General", key: "tab-account-general" },
|
||||
{ name: "Authentication", key: "tab-account-auth" },
|
||||
{ name: "API Keys", key: "tab-account-api-keys" }
|
||||
];
|
||||
|
||||
export const PersonalTabGroup = () => {
|
||||
return (
|
||||
<Tab.Group>
|
||||
<Tab.List className="mb-4 w-full border-b-2 border-mineshaft-800">
|
||||
{tabs.map((tab) => (
|
||||
<Tab as={Fragment} key={tab.key}>
|
||||
{({ selected }) => (
|
||||
<button
|
||||
type="button"
|
||||
className={`w-30 mx-2 mr-4 py-2 text-sm font-medium outline-none ${
|
||||
selected ? "border-b border-white text-white" : "text-mineshaft-400"
|
||||
}`}
|
||||
>
|
||||
{tab.name}
|
||||
</button>
|
||||
)}
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
<Tab.Panels>
|
||||
<Tab.Panel>
|
||||
<PersonalGeneralTab />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<PersonalAuthTab />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<div className="space-y-3">
|
||||
<div className="mt-4 flex w-full flex-row items-center rounded-md border border-primary-600/70 bg-primary/[.07] p-4 text-base text-white">
|
||||
<FontAwesomeIcon icon={faWarning} className="pr-6 text-4xl text-white/80" />
|
||||
<div className="flex w-full flex-col text-sm">
|
||||
<span className="mb-4 text-lg font-semibold">Deprecation Notice</span>
|
||||
<p>
|
||||
API Keys are deprecated and will be removed in the future.
|
||||
<br /> Please use Machine Identity authentication for your applications and
|
||||
services.
|
||||
</p>
|
||||
<Link href="https://infisical.com/docs/documentation/platform/identities/overview">
|
||||
<a target="_blank" className="font-semibold text-primary-400">
|
||||
Learn more about Machine Identities
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PersonalAPIKeyTab />
|
||||
</div>
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Tab.Group>
|
||||
<Tab.List className="mb-4 border-b-2 border-mineshaft-800 w-full">
|
||||
{tabs.map((tab) => (
|
||||
<Tab as={Fragment} key={tab.key}>
|
||||
{({ selected }) => (
|
||||
<button
|
||||
type="button"
|
||||
className={`w-30 py-2 mx-2 mr-4 font-medium text-sm outline-none ${selected ? "border-b border-white text-white" : "text-mineshaft-400"}`}
|
||||
>
|
||||
{tab.name}
|
||||
</button>
|
||||
)}
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
<Tab.Panels>
|
||||
<Tab.Panel>
|
||||
<PersonalGeneralTab />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<PersonalAuthTab />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<PersonalAPIKeyTab />
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user