mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-15 09:42:14 +00:00
Compare commits
6 Commits
fix/addres
...
daniel/sso
Author | SHA1 | Date | |
---|---|---|---|
f903e5b3d4 | |||
c6f8915d3f | |||
65b1354ef1 | |||
cda8579ca4 | |||
13d2cbd8b0 | |||
abfc5736fd |
@ -17,6 +17,7 @@ import { z } from "zod";
|
||||
import { LdapGroupMapsSchema } from "@app/db/schemas";
|
||||
import { TLDAPConfig } from "@app/ee/services/ldap-config/ldap-config-types";
|
||||
import { isValidLdapFilter, searchGroups } from "@app/ee/services/ldap-config/ldap-fns";
|
||||
import { ApiDocsTags, LdapSso } from "@app/lib/api-docs";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
@ -132,10 +133,18 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.LdapSso],
|
||||
description: "Get LDAP config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
organizationId: z.string().trim()
|
||||
organizationId: z.string().trim().describe(LdapSso.GET_CONFIG.organizationId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -172,23 +181,32 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.LdapSso],
|
||||
description: "Create LDAP config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
isActive: z.boolean(),
|
||||
url: z.string().trim(),
|
||||
bindDN: z.string().trim(),
|
||||
bindPass: z.string().trim(),
|
||||
uniqueUserAttribute: z.string().trim().default("uidNumber"),
|
||||
searchBase: z.string().trim(),
|
||||
searchFilter: z.string().trim().default("(uid={{username}})"),
|
||||
groupSearchBase: z.string().trim(),
|
||||
organizationId: z.string().trim().describe(LdapSso.CREATE_CONFIG.organizationId),
|
||||
isActive: z.boolean().describe(LdapSso.CREATE_CONFIG.isActive),
|
||||
url: z.string().trim().describe(LdapSso.CREATE_CONFIG.url),
|
||||
bindDN: z.string().trim().describe(LdapSso.CREATE_CONFIG.bindDN),
|
||||
bindPass: z.string().trim().describe(LdapSso.CREATE_CONFIG.bindPass),
|
||||
uniqueUserAttribute: z.string().trim().default("uidNumber").describe(LdapSso.CREATE_CONFIG.uniqueUserAttribute),
|
||||
searchBase: z.string().trim().describe(LdapSso.CREATE_CONFIG.searchBase),
|
||||
searchFilter: z.string().trim().default("(uid={{username}})").describe(LdapSso.CREATE_CONFIG.searchFilter),
|
||||
groupSearchBase: z.string().trim().describe(LdapSso.CREATE_CONFIG.groupSearchBase),
|
||||
groupSearchFilter: z
|
||||
.string()
|
||||
.trim()
|
||||
.default("(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))"),
|
||||
caCert: z.string().trim().default("")
|
||||
.default("(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))")
|
||||
.describe(LdapSso.CREATE_CONFIG.groupSearchFilter),
|
||||
caCert: z.string().trim().default("").describe(LdapSso.CREATE_CONFIG.caCert)
|
||||
}),
|
||||
response: {
|
||||
200: SanitizedLdapConfigSchema
|
||||
@ -214,23 +232,31 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.LdapSso],
|
||||
description: "Update LDAP config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z
|
||||
.object({
|
||||
isActive: z.boolean(),
|
||||
url: z.string().trim(),
|
||||
bindDN: z.string().trim(),
|
||||
bindPass: z.string().trim(),
|
||||
uniqueUserAttribute: z.string().trim(),
|
||||
searchBase: z.string().trim(),
|
||||
searchFilter: z.string().trim(),
|
||||
groupSearchBase: z.string().trim(),
|
||||
groupSearchFilter: z.string().trim(),
|
||||
caCert: z.string().trim()
|
||||
isActive: z.boolean().describe(LdapSso.UPDATE_CONFIG.isActive),
|
||||
url: z.string().trim().describe(LdapSso.UPDATE_CONFIG.url),
|
||||
bindDN: z.string().trim().describe(LdapSso.UPDATE_CONFIG.bindDN),
|
||||
bindPass: z.string().trim().describe(LdapSso.UPDATE_CONFIG.bindPass),
|
||||
uniqueUserAttribute: z.string().trim().describe(LdapSso.UPDATE_CONFIG.uniqueUserAttribute),
|
||||
searchBase: z.string().trim().describe(LdapSso.UPDATE_CONFIG.searchBase),
|
||||
searchFilter: z.string().trim().describe(LdapSso.UPDATE_CONFIG.searchFilter),
|
||||
groupSearchBase: z.string().trim().describe(LdapSso.UPDATE_CONFIG.groupSearchBase),
|
||||
groupSearchFilter: z.string().trim().describe(LdapSso.UPDATE_CONFIG.groupSearchFilter),
|
||||
caCert: z.string().trim().describe(LdapSso.UPDATE_CONFIG.caCert)
|
||||
})
|
||||
.partial()
|
||||
.merge(z.object({ organizationId: z.string() })),
|
||||
.merge(z.object({ organizationId: z.string().trim().describe(LdapSso.UPDATE_CONFIG.organizationId) })),
|
||||
response: {
|
||||
200: SanitizedLdapConfigSchema
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import { z } from "zod";
|
||||
|
||||
import { OidcConfigsSchema } from "@app/db/schemas";
|
||||
import { OIDCConfigurationType, OIDCJWTSignatureAlgorithm } from "@app/ee/services/oidc/oidc-config-types";
|
||||
import { ApiDocsTags, OidcSSo } from "@app/lib/api-docs";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
@ -153,10 +154,18 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.OidcSso],
|
||||
description: "Get OIDC config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
orgSlug: z.string().trim()
|
||||
organizationId: z.string().trim().describe(OidcSSo.GET_CONFIG.organizationId)
|
||||
}),
|
||||
response: {
|
||||
200: SanitizedOidcConfigSchema.pick({
|
||||
@ -180,9 +189,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const { orgSlug } = req.query;
|
||||
const oidc = await server.services.oidc.getOidc({
|
||||
orgSlug,
|
||||
organizationId: req.query.organizationId,
|
||||
type: "external",
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
@ -200,8 +208,16 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.OidcSso],
|
||||
description: "Update OIDC config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z
|
||||
.object({
|
||||
allowedEmailDomains: z
|
||||
@ -216,22 +232,26 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
||||
.split(",")
|
||||
.map((id) => id.trim())
|
||||
.join(", ");
|
||||
}),
|
||||
discoveryURL: z.string().trim(),
|
||||
configurationType: z.nativeEnum(OIDCConfigurationType),
|
||||
issuer: z.string().trim(),
|
||||
authorizationEndpoint: z.string().trim(),
|
||||
jwksUri: z.string().trim(),
|
||||
tokenEndpoint: z.string().trim(),
|
||||
userinfoEndpoint: z.string().trim(),
|
||||
clientId: z.string().trim(),
|
||||
clientSecret: z.string().trim(),
|
||||
isActive: z.boolean(),
|
||||
manageGroupMemberships: z.boolean().optional(),
|
||||
jwtSignatureAlgorithm: z.nativeEnum(OIDCJWTSignatureAlgorithm).optional()
|
||||
})
|
||||
.describe(OidcSSo.UPDATE_CONFIG.allowedEmailDomains),
|
||||
discoveryURL: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.discoveryURL),
|
||||
configurationType: z.nativeEnum(OIDCConfigurationType).describe(OidcSSo.UPDATE_CONFIG.configurationType),
|
||||
issuer: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.issuer),
|
||||
authorizationEndpoint: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.authorizationEndpoint),
|
||||
jwksUri: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.jwksUri),
|
||||
tokenEndpoint: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.tokenEndpoint),
|
||||
userinfoEndpoint: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.userinfoEndpoint),
|
||||
clientId: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.clientId),
|
||||
clientSecret: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.clientSecret),
|
||||
isActive: z.boolean().describe(OidcSSo.UPDATE_CONFIG.isActive),
|
||||
manageGroupMemberships: z.boolean().optional().describe(OidcSSo.UPDATE_CONFIG.manageGroupMemberships),
|
||||
jwtSignatureAlgorithm: z
|
||||
.nativeEnum(OIDCJWTSignatureAlgorithm)
|
||||
.optional()
|
||||
.describe(OidcSSo.UPDATE_CONFIG.jwtSignatureAlgorithm)
|
||||
})
|
||||
.partial()
|
||||
.merge(z.object({ orgSlug: z.string() })),
|
||||
.merge(z.object({ organizationId: z.string().describe(OidcSSo.UPDATE_CONFIG.organizationId) })),
|
||||
response: {
|
||||
200: SanitizedOidcConfigSchema.pick({
|
||||
id: true,
|
||||
@ -267,8 +287,16 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.OidcSso],
|
||||
description: "Create OIDC config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z
|
||||
.object({
|
||||
allowedEmailDomains: z
|
||||
@ -283,23 +311,34 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
||||
.split(",")
|
||||
.map((id) => id.trim())
|
||||
.join(", ");
|
||||
}),
|
||||
configurationType: z.nativeEnum(OIDCConfigurationType),
|
||||
issuer: z.string().trim().optional().default(""),
|
||||
discoveryURL: z.string().trim().optional().default(""),
|
||||
authorizationEndpoint: z.string().trim().optional().default(""),
|
||||
jwksUri: z.string().trim().optional().default(""),
|
||||
tokenEndpoint: z.string().trim().optional().default(""),
|
||||
userinfoEndpoint: z.string().trim().optional().default(""),
|
||||
clientId: z.string().trim(),
|
||||
clientSecret: z.string().trim(),
|
||||
isActive: z.boolean(),
|
||||
orgSlug: z.string().trim(),
|
||||
manageGroupMemberships: z.boolean().optional().default(false),
|
||||
})
|
||||
.describe(OidcSSo.CREATE_CONFIG.allowedEmailDomains),
|
||||
configurationType: z.nativeEnum(OIDCConfigurationType).describe(OidcSSo.CREATE_CONFIG.configurationType),
|
||||
issuer: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.issuer),
|
||||
discoveryURL: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.discoveryURL),
|
||||
authorizationEndpoint: z
|
||||
.string()
|
||||
.trim()
|
||||
.optional()
|
||||
.default("")
|
||||
.describe(OidcSSo.CREATE_CONFIG.authorizationEndpoint),
|
||||
jwksUri: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.jwksUri),
|
||||
tokenEndpoint: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.tokenEndpoint),
|
||||
userinfoEndpoint: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.userinfoEndpoint),
|
||||
clientId: z.string().trim().describe(OidcSSo.CREATE_CONFIG.clientId),
|
||||
clientSecret: z.string().trim().describe(OidcSSo.CREATE_CONFIG.clientSecret),
|
||||
isActive: z.boolean().describe(OidcSSo.CREATE_CONFIG.isActive),
|
||||
organizationId: z.string().trim().describe(OidcSSo.CREATE_CONFIG.organizationId),
|
||||
manageGroupMemberships: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(false)
|
||||
.describe(OidcSSo.CREATE_CONFIG.manageGroupMemberships),
|
||||
jwtSignatureAlgorithm: z
|
||||
.nativeEnum(OIDCJWTSignatureAlgorithm)
|
||||
.optional()
|
||||
.default(OIDCJWTSignatureAlgorithm.RS256)
|
||||
.describe(OidcSSo.CREATE_CONFIG.jwtSignatureAlgorithm)
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.configurationType === OIDCConfigurationType.CUSTOM) {
|
||||
|
@ -13,6 +13,7 @@ import { FastifyRequest } from "fastify";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SamlProviders, TGetSamlCfgDTO } from "@app/ee/services/saml-config/saml-config-types";
|
||||
import { ApiDocsTags, SamlSso } from "@app/lib/api-docs";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
@ -149,8 +150,8 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
firstName,
|
||||
lastName: lastName as string,
|
||||
relayState: (req.body as { RelayState?: string }).RelayState,
|
||||
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string,
|
||||
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string,
|
||||
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider,
|
||||
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId,
|
||||
metadata: userMetadata
|
||||
});
|
||||
cb(null, { isUserCompleted, providerAuthToken });
|
||||
@ -262,25 +263,31 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.SamlSso],
|
||||
description: "Get SAML config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
querystring: z.object({
|
||||
organizationId: z.string().trim()
|
||||
organizationId: z.string().trim().describe(SamlSso.GET_CONFIG.organizationId)
|
||||
}),
|
||||
response: {
|
||||
200: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
organization: z.string(),
|
||||
orgId: z.string(),
|
||||
authProvider: z.string(),
|
||||
isActive: z.boolean(),
|
||||
entryPoint: z.string(),
|
||||
issuer: z.string(),
|
||||
cert: z.string(),
|
||||
lastUsed: z.date().nullable().optional()
|
||||
})
|
||||
.optional()
|
||||
200: z.object({
|
||||
id: z.string(),
|
||||
organization: z.string(),
|
||||
orgId: z.string(),
|
||||
authProvider: z.string(),
|
||||
isActive: z.boolean(),
|
||||
entryPoint: z.string(),
|
||||
issuer: z.string(),
|
||||
cert: z.string(),
|
||||
lastUsed: z.date().nullable().optional()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
@ -302,15 +309,23 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.SamlSso],
|
||||
description: "Create SAML config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
organizationId: z.string(),
|
||||
authProvider: z.nativeEnum(SamlProviders),
|
||||
isActive: z.boolean(),
|
||||
entryPoint: z.string(),
|
||||
issuer: z.string(),
|
||||
cert: z.string()
|
||||
organizationId: z.string().trim().describe(SamlSso.CREATE_CONFIG.organizationId),
|
||||
authProvider: z.nativeEnum(SamlProviders).describe(SamlSso.CREATE_CONFIG.authProvider),
|
||||
isActive: z.boolean().describe(SamlSso.CREATE_CONFIG.isActive),
|
||||
entryPoint: z.string().trim().describe(SamlSso.CREATE_CONFIG.entryPoint),
|
||||
issuer: z.string().trim().describe(SamlSso.CREATE_CONFIG.issuer),
|
||||
cert: z.string().trim().describe(SamlSso.CREATE_CONFIG.cert)
|
||||
}),
|
||||
response: {
|
||||
200: SanitizedSamlConfigSchema
|
||||
@ -341,18 +356,26 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.SamlSso],
|
||||
description: "Update SAML config",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z
|
||||
.object({
|
||||
authProvider: z.nativeEnum(SamlProviders),
|
||||
isActive: z.boolean(),
|
||||
entryPoint: z.string(),
|
||||
issuer: z.string(),
|
||||
cert: z.string()
|
||||
authProvider: z.nativeEnum(SamlProviders).describe(SamlSso.UPDATE_CONFIG.authProvider),
|
||||
isActive: z.boolean().describe(SamlSso.UPDATE_CONFIG.isActive),
|
||||
entryPoint: z.string().trim().describe(SamlSso.UPDATE_CONFIG.entryPoint),
|
||||
issuer: z.string().trim().describe(SamlSso.UPDATE_CONFIG.issuer),
|
||||
cert: z.string().trim().describe(SamlSso.UPDATE_CONFIG.cert)
|
||||
})
|
||||
.partial()
|
||||
.merge(z.object({ organizationId: z.string() })),
|
||||
.merge(z.object({ organizationId: z.string().trim().describe(SamlSso.UPDATE_CONFIG.organizationId) })),
|
||||
response: {
|
||||
200: SanitizedSamlConfigSchema
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import axios from "axios";
|
||||
import jwt from "jsonwebtoken";
|
||||
import * as jwt from "jsonwebtoken";
|
||||
|
||||
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
@ -107,34 +107,26 @@ export const oidcConfigServiceFactory = ({
|
||||
kmsService
|
||||
}: TOidcConfigServiceFactoryDep) => {
|
||||
const getOidc = async (dto: TGetOidcCfgDTO) => {
|
||||
const org = await orgDAL.findOne({ slug: dto.orgSlug });
|
||||
if (!org) {
|
||||
const oidcCfg = await oidcConfigDAL.findOne({
|
||||
orgId: dto.organizationId
|
||||
});
|
||||
if (!oidcCfg) {
|
||||
throw new NotFoundError({
|
||||
message: `Organization with slug '${dto.orgSlug}' not found`,
|
||||
name: "OrgNotFound"
|
||||
message: `OIDC configuration for organization with ID '${dto.organizationId}' not found`
|
||||
});
|
||||
}
|
||||
|
||||
if (dto.type === "external") {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
dto.actor,
|
||||
dto.actorId,
|
||||
org.id,
|
||||
dto.organizationId,
|
||||
dto.actorAuthMethod,
|
||||
dto.actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
|
||||
}
|
||||
|
||||
const oidcCfg = await oidcConfigDAL.findOne({
|
||||
orgId: org.id
|
||||
});
|
||||
|
||||
if (!oidcCfg) {
|
||||
throw new NotFoundError({
|
||||
message: `OIDC configuration for organization with slug '${dto.orgSlug}' not found`
|
||||
});
|
||||
}
|
||||
|
||||
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.Organization,
|
||||
orgId: oidcCfg.orgId
|
||||
@ -465,7 +457,7 @@ export const oidcConfigServiceFactory = ({
|
||||
};
|
||||
|
||||
const updateOidcCfg = async ({
|
||||
orgSlug,
|
||||
organizationId,
|
||||
allowedEmailDomains,
|
||||
configurationType,
|
||||
discoveryURL,
|
||||
@ -484,13 +476,11 @@ export const oidcConfigServiceFactory = ({
|
||||
manageGroupMemberships,
|
||||
jwtSignatureAlgorithm
|
||||
}: TUpdateOidcCfgDTO) => {
|
||||
const org = await orgDAL.findOne({
|
||||
slug: orgSlug
|
||||
});
|
||||
const org = await orgDAL.findOne({ id: organizationId });
|
||||
|
||||
if (!org) {
|
||||
throw new NotFoundError({
|
||||
message: `Organization with slug '${orgSlug}' not found`
|
||||
message: `Organization with ID '${organizationId}' not found`
|
||||
});
|
||||
}
|
||||
|
||||
@ -555,7 +545,7 @@ export const oidcConfigServiceFactory = ({
|
||||
};
|
||||
|
||||
const createOidcCfg = async ({
|
||||
orgSlug,
|
||||
organizationId,
|
||||
allowedEmailDomains,
|
||||
configurationType,
|
||||
discoveryURL,
|
||||
@ -574,12 +564,10 @@ export const oidcConfigServiceFactory = ({
|
||||
manageGroupMemberships,
|
||||
jwtSignatureAlgorithm
|
||||
}: TCreateOidcCfgDTO) => {
|
||||
const org = await orgDAL.findOne({
|
||||
slug: orgSlug
|
||||
});
|
||||
const org = await orgDAL.findOne({ id: organizationId });
|
||||
if (!org) {
|
||||
throw new NotFoundError({
|
||||
message: `Organization with slug '${orgSlug}' not found`
|
||||
message: `Organization with ID '${organizationId}' not found`
|
||||
});
|
||||
}
|
||||
|
||||
@ -639,7 +627,7 @@ export const oidcConfigServiceFactory = ({
|
||||
|
||||
const oidcCfg = await getOidc({
|
||||
type: "internal",
|
||||
orgSlug
|
||||
organizationId: org.id
|
||||
});
|
||||
|
||||
if (!oidcCfg || !oidcCfg.isActive) {
|
||||
|
@ -26,11 +26,11 @@ export type TOidcLoginDTO = {
|
||||
export type TGetOidcCfgDTO =
|
||||
| ({
|
||||
type: "external";
|
||||
orgSlug: string;
|
||||
organizationId: string;
|
||||
} & TGenericPermission)
|
||||
| {
|
||||
type: "internal";
|
||||
orgSlug: string;
|
||||
organizationId: string;
|
||||
};
|
||||
|
||||
export type TCreateOidcCfgDTO = {
|
||||
@ -45,7 +45,7 @@ export type TCreateOidcCfgDTO = {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
isActive: boolean;
|
||||
orgSlug: string;
|
||||
organizationId: string;
|
||||
manageGroupMemberships: boolean;
|
||||
jwtSignatureAlgorithm: OIDCJWTSignatureAlgorithm;
|
||||
} & TGenericPermission;
|
||||
@ -62,7 +62,7 @@ export type TUpdateOidcCfgDTO = Partial<{
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
isActive: boolean;
|
||||
orgSlug: string;
|
||||
organizationId: string;
|
||||
manageGroupMemberships: boolean;
|
||||
jwtSignatureAlgorithm: OIDCJWTSignatureAlgorithm;
|
||||
}> &
|
||||
|
@ -148,10 +148,18 @@ export const samlConfigServiceFactory = ({
|
||||
let samlConfig: TSamlConfigs | undefined;
|
||||
if (dto.type === "org") {
|
||||
samlConfig = await samlConfigDAL.findOne({ orgId: dto.orgId });
|
||||
if (!samlConfig) return;
|
||||
if (!samlConfig) {
|
||||
throw new NotFoundError({
|
||||
message: `SAML configuration for organization with ID '${dto.orgId}' not found`
|
||||
});
|
||||
}
|
||||
} else if (dto.type === "orgSlug") {
|
||||
const org = await orgDAL.findOne({ slug: dto.orgSlug });
|
||||
if (!org) return;
|
||||
if (!org) {
|
||||
throw new NotFoundError({
|
||||
message: `Organization with slug '${dto.orgSlug}' not found`
|
||||
});
|
||||
}
|
||||
samlConfig = await samlConfigDAL.findOne({ orgId: org.id });
|
||||
} else if (dto.type === "ssoId") {
|
||||
// TODO:
|
||||
|
@ -61,20 +61,17 @@ export type TSamlLoginDTO = {
|
||||
export type TSamlConfigServiceFactory = {
|
||||
createSamlCfg: (arg: TCreateSamlCfgDTO) => Promise<TSamlConfigs>;
|
||||
updateSamlCfg: (arg: TUpdateSamlCfgDTO) => Promise<TSamlConfigs>;
|
||||
getSaml: (arg: TGetSamlCfgDTO) => Promise<
|
||||
| {
|
||||
id: string;
|
||||
organization: string;
|
||||
orgId: string;
|
||||
authProvider: string;
|
||||
isActive: boolean;
|
||||
entryPoint: string;
|
||||
issuer: string;
|
||||
cert: string;
|
||||
lastUsed: Date | null | undefined;
|
||||
}
|
||||
| undefined
|
||||
>;
|
||||
getSaml: (arg: TGetSamlCfgDTO) => Promise<{
|
||||
id: string;
|
||||
organization: string;
|
||||
orgId: string;
|
||||
authProvider: string;
|
||||
isActive: boolean;
|
||||
entryPoint: string;
|
||||
issuer: string;
|
||||
cert: string;
|
||||
lastUsed: Date | null | undefined;
|
||||
}>;
|
||||
samlLogin: (arg: TSamlLoginDTO) => Promise<{
|
||||
isUserCompleted: boolean;
|
||||
providerAuthToken: string;
|
||||
|
@ -66,7 +66,10 @@ export enum ApiDocsTags {
|
||||
KmsKeys = "KMS Keys",
|
||||
KmsEncryption = "KMS Encryption",
|
||||
KmsSigning = "KMS Signing",
|
||||
SecretScanning = "Secret Scanning"
|
||||
SecretScanning = "Secret Scanning",
|
||||
OidcSso = "OIDC SSO",
|
||||
SamlSso = "SAML SSO",
|
||||
LdapSso = "LDAP SSO"
|
||||
}
|
||||
|
||||
export const GROUPS = {
|
||||
@ -2427,8 +2430,7 @@ export const SecretSyncs = {
|
||||
keyOcid: "The OCID (Oracle Cloud Identifier) of the encryption key to use when creating secrets in the vault."
|
||||
},
|
||||
ONEPASS: {
|
||||
vaultId: "The ID of the 1Password vault to sync secrets to.",
|
||||
valueLabel: "The label of the entry that holds the secret value."
|
||||
vaultId: "The ID of the 1Password vault to sync secrets to."
|
||||
},
|
||||
HEROKU: {
|
||||
app: "The ID of the Heroku app to sync secrets to.",
|
||||
@ -2651,3 +2653,113 @@ export const SecretScanningConfigs = {
|
||||
content: "The contents of the Secret Scanning Configuration file."
|
||||
}
|
||||
};
|
||||
|
||||
export const OidcSSo = {
|
||||
GET_CONFIG: {
|
||||
organizationId: "The ID of the organization to get the OIDC config for."
|
||||
},
|
||||
UPDATE_CONFIG: {
|
||||
organizationId: "The ID of the organization to update the OIDC config for.",
|
||||
allowedEmailDomains:
|
||||
"A list of allowed email domains that users can use to authenticate with. This field is comma separated. Example: 'example.com,acme.com'",
|
||||
discoveryURL: "The URL of the OIDC discovery endpoint.",
|
||||
configurationType: "The configuration type to use for the OIDC configuration.",
|
||||
issuer:
|
||||
"The issuer for the OIDC configuration. This is only supported when the OIDC configuration type is set to 'custom'.",
|
||||
authorizationEndpoint:
|
||||
"The endpoint to use for OIDC authorization. This is only supported when the OIDC configuration type is set to 'custom'.",
|
||||
jwksUri: "The URL of the OIDC JWKS endpoint.",
|
||||
tokenEndpoint: "The token endpoint to use for OIDC token exchange.",
|
||||
userinfoEndpoint: "The userinfo endpoint to get user information from the OIDC provider.",
|
||||
clientId: "The client ID to use for OIDC authentication.",
|
||||
clientSecret: "The client secret to use for OIDC authentication.",
|
||||
isActive: "Whether to enable or disable this OIDC configuration.",
|
||||
manageGroupMemberships:
|
||||
"Whether to manage group memberships for the OIDC configuration. If enabled, users will automatically be assigned groups when they sign in, based on which groups they are a member of in the OIDC provider.",
|
||||
jwtSignatureAlgorithm: "The algorithm to use for JWT signature verification."
|
||||
},
|
||||
CREATE_CONFIG: {
|
||||
organizationId: "The ID of the organization to create the OIDC config for.",
|
||||
allowedEmailDomains:
|
||||
"A list of allowed email domains that users can use to authenticate with. This field is comma separated.",
|
||||
discoveryURL: "The URL of the OIDC discovery endpoint.",
|
||||
configurationType: "The configuration type to use for the OIDC configuration.",
|
||||
issuer:
|
||||
"The issuer for the OIDC configuration. This is only supported when the OIDC configuration type is set to 'custom'.",
|
||||
authorizationEndpoint:
|
||||
"The authorization endpoint to use for OIDC authorization. This is only supported when the OIDC configuration type is set to 'custom'.",
|
||||
jwksUri: "The URL of the OIDC JWKS endpoint.",
|
||||
tokenEndpoint: "The token endpoint to use for OIDC token exchange.",
|
||||
userinfoEndpoint: "The userinfo endpoint to get user information from the OIDC provider.",
|
||||
clientId: "The client ID to use for OIDC authentication.",
|
||||
clientSecret: "The client secret to use for OIDC authentication.",
|
||||
isActive: "Whether to enable or disable this OIDC configuration.",
|
||||
manageGroupMemberships:
|
||||
"Whether to manage group memberships for the OIDC configuration. If enabled, users will automatically be assigned groups when they sign in, based on which groups they are a member of in the OIDC provider.",
|
||||
jwtSignatureAlgorithm: "The algorithm to use for JWT signature verification."
|
||||
}
|
||||
};
|
||||
|
||||
export const SamlSso = {
|
||||
GET_CONFIG: {
|
||||
organizationId: "The ID of the organization to get the SAML config for."
|
||||
},
|
||||
UPDATE_CONFIG: {
|
||||
organizationId: "The ID of the organization to update the SAML config for.",
|
||||
authProvider: "Authentication provider to use for SAML authentication.",
|
||||
isActive: "Whether to enable or disable this SAML configuration.",
|
||||
entryPoint:
|
||||
"The entry point for the SAML authentication. This is the URL that the user will be redirected to after they have authenticated with the SAML provider.",
|
||||
issuer: "The SAML provider issuer URL or entity ID.",
|
||||
cert: "The certificate to use for SAML authentication."
|
||||
},
|
||||
CREATE_CONFIG: {
|
||||
organizationId: "The ID of the organization to create the SAML config for.",
|
||||
authProvider: "Authentication provider to use for SAML authentication.",
|
||||
isActive: "Whether to enable or disable this SAML configuration.",
|
||||
entryPoint:
|
||||
"The entry point for the SAML authentication. This is the URL that the user will be redirected to after they have authenticated with the SAML provider.",
|
||||
issuer: "The SAML provider issuer URL or entity ID.",
|
||||
cert: "The certificate to use for SAML authentication."
|
||||
}
|
||||
};
|
||||
|
||||
export const LdapSso = {
|
||||
GET_CONFIG: {
|
||||
organizationId: "The ID of the organization to get the LDAP config for."
|
||||
},
|
||||
CREATE_CONFIG: {
|
||||
organizationId: "The ID of the organization to create the LDAP config for.",
|
||||
isActive: "Whether to enable or disable this LDAP configuration.",
|
||||
url: "The LDAP server to connect to such as `ldap://ldap.your-org.com`, `ldaps://ldap.myorg.com:636` (for connection over SSL/TLS), etc.",
|
||||
bindDN:
|
||||
"The distinguished name of the object to bind when performing the user search such as `cn=infisical,ou=Users,dc=acme,dc=com`",
|
||||
bindPass: "The password to use along with Bind DN when performing the user search.",
|
||||
searchBase: "The base DN to use for the user search such as `ou=Users,dc=acme,dc=com`",
|
||||
uniqueUserAttribute:
|
||||
"The attribute to use as the unique identifier of LDAP users such as `sAMAccountName`, `cn`, `uid`, `objectGUID`. If left blank, defaults to uidNumber",
|
||||
searchFilter:
|
||||
"The template used to construct the LDAP user search filter such as `(uid={{username}})` uses literal `{{username}}` to have the given username used in the search. The default is `(uid={{username}})` which is compatible with several common directory schemas.",
|
||||
groupSearchBase: "LDAP search base to use for group membership search such as `ou=Groups,dc=acme,dc=com`",
|
||||
groupSearchFilter:
|
||||
"The template used when constructing the group membership query such as `(&(objectClass=posixGroup)(memberUid={{.Username}}))`. The template can access the following context variables: `[UserDN, UserName]`. The default is `(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))` which is compatible with several common directory schemas.",
|
||||
caCert: "The CA certificate to use when verifying the LDAP server certificate."
|
||||
},
|
||||
UPDATE_CONFIG: {
|
||||
organizationId: "The ID of the organization to update the LDAP config for.",
|
||||
isActive: "Whether to enable or disable this LDAP configuration.",
|
||||
url: "The LDAP server to connect to such as `ldap://ldap.your-org.com`, `ldaps://ldap.myorg.com:636` (for connection over SSL/TLS), etc.",
|
||||
bindDN:
|
||||
"The distinguished name of object to bind when performing the user search such as `cn=infisical,ou=Users,dc=acme,dc=com`",
|
||||
bindPass: "The password to use along with Bind DN when performing the user search.",
|
||||
uniqueUserAttribute:
|
||||
"The attribute to use as the unique identifier of LDAP users such as `sAMAccountName`, `cn`, `uid`, `objectGUID`. If left blank, defaults to uidNumber",
|
||||
searchFilter:
|
||||
"The template used to construct the LDAP user search filter such as `(uid={{username}})` uses literal `{{username}}` to have the given username used in the search. The default is `(uid={{username}})` which is compatible with several common directory schemas.",
|
||||
searchBase: "The base DN to use for the user search such as `ou=Users,dc=acme,dc=com`",
|
||||
groupSearchBase: "LDAP search base to use for group membership search such as `ou=Groups,dc=acme,dc=com`",
|
||||
groupSearchFilter:
|
||||
"The template used when constructing the group membership query such as `(&(objectClass=posixGroup)(memberUid={{.Username}}))`. The template can access the following context variables: `[UserDN, UserName]`. The default is `(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))` which is compatible with several common directory schemas.",
|
||||
caCert: "The CA certificate to use when verifying the LDAP server certificate."
|
||||
}
|
||||
};
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
TOnePassListVariablesResponse,
|
||||
TOnePassSyncWithCredentials,
|
||||
TOnePassVariable,
|
||||
TOnePassVariableDetails,
|
||||
TPostOnePassVariable,
|
||||
TPutOnePassVariable
|
||||
} from "@app/services/secret-sync/1password/1password-sync-types";
|
||||
@ -13,10 +14,7 @@ import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
|
||||
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
|
||||
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
// This should not be changed or it may break existing logic
|
||||
const VALUE_LABEL_DEFAULT = "value";
|
||||
|
||||
const listOnePassItems = async ({ instanceUrl, apiToken, vaultId, valueLabel }: TOnePassListVariables) => {
|
||||
const listOnePassItems = async ({ instanceUrl, apiToken, vaultId }: TOnePassListVariables) => {
|
||||
const { data } = await request.get<TOnePassListVariablesResponse>(`${instanceUrl}/v1/vaults/${vaultId}/items`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
@ -24,49 +22,36 @@ const listOnePassItems = async ({ instanceUrl, apiToken, vaultId, valueLabel }:
|
||||
}
|
||||
});
|
||||
|
||||
const items: Record<string, TOnePassVariable & { value: string; fieldId: string }> = {};
|
||||
const duplicates: Record<string, string> = {};
|
||||
const result: Record<string, TOnePassVariable & { value: string; fieldId: string }> = {};
|
||||
|
||||
for await (const s of data) {
|
||||
// eslint-disable-next-line no-continue
|
||||
if (s.category !== "API_CREDENTIAL") continue;
|
||||
|
||||
if (items[s.title]) {
|
||||
duplicates[s.id] = s.title;
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
const { data: secret } = await request.get<TOnePassVariable>(`${instanceUrl}/v1/vaults/${vaultId}/items/${s.id}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
Accept: "application/json"
|
||||
const { data: secret } = await request.get<TOnePassVariableDetails>(
|
||||
`${instanceUrl}/v1/vaults/${vaultId}/items/${s.id}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
const valueField = secret.fields.find((f) => f.label === valueLabel);
|
||||
const value = secret.fields.find((f) => f.label === "value")?.value;
|
||||
const fieldId = secret.fields.find((f) => f.label === "value")?.id;
|
||||
|
||||
// eslint-disable-next-line no-continue
|
||||
if (!valueField || !valueField.value || !valueField.id) continue;
|
||||
if (!value || !fieldId) continue;
|
||||
|
||||
items[s.title] = {
|
||||
result[s.title] = {
|
||||
...secret,
|
||||
value: valueField.value,
|
||||
fieldId: valueField.id
|
||||
value,
|
||||
fieldId
|
||||
};
|
||||
}
|
||||
|
||||
return { items, duplicates };
|
||||
return result;
|
||||
};
|
||||
|
||||
const createOnePassItem = async ({
|
||||
instanceUrl,
|
||||
apiToken,
|
||||
vaultId,
|
||||
itemTitle,
|
||||
itemValue,
|
||||
valueLabel
|
||||
}: TPostOnePassVariable) => {
|
||||
const createOnePassItem = async ({ instanceUrl, apiToken, vaultId, itemTitle, itemValue }: TPostOnePassVariable) => {
|
||||
return request.post(
|
||||
`${instanceUrl}/v1/vaults/${vaultId}/items`,
|
||||
{
|
||||
@ -78,7 +63,7 @@ const createOnePassItem = async ({
|
||||
tags: ["synced-from-infisical"],
|
||||
fields: [
|
||||
{
|
||||
label: valueLabel,
|
||||
label: "value",
|
||||
value: itemValue,
|
||||
type: "CONCEALED"
|
||||
}
|
||||
@ -100,9 +85,7 @@ const updateOnePassItem = async ({
|
||||
itemId,
|
||||
fieldId,
|
||||
itemTitle,
|
||||
itemValue,
|
||||
valueLabel,
|
||||
otherFields
|
||||
itemValue
|
||||
}: TPutOnePassVariable) => {
|
||||
return request.put(
|
||||
`${instanceUrl}/v1/vaults/${vaultId}/items/${itemId}`,
|
||||
@ -115,10 +98,9 @@ const updateOnePassItem = async ({
|
||||
},
|
||||
tags: ["synced-from-infisical"],
|
||||
fields: [
|
||||
...otherFields,
|
||||
{
|
||||
id: fieldId,
|
||||
label: valueLabel,
|
||||
label: "value",
|
||||
value: itemValue,
|
||||
type: "CONCEALED"
|
||||
}
|
||||
@ -146,18 +128,13 @@ export const OnePassSyncFns = {
|
||||
const {
|
||||
connection,
|
||||
environment,
|
||||
destinationConfig: { vaultId, valueLabel }
|
||||
destinationConfig: { vaultId }
|
||||
} = secretSync;
|
||||
|
||||
const instanceUrl = await getOnePassInstanceUrl(connection);
|
||||
const { apiToken } = connection.credentials;
|
||||
|
||||
const { items, duplicates } = await listOnePassItems({
|
||||
instanceUrl,
|
||||
apiToken,
|
||||
vaultId,
|
||||
valueLabel: valueLabel || VALUE_LABEL_DEFAULT
|
||||
});
|
||||
const items = await listOnePassItems({ instanceUrl, apiToken, vaultId });
|
||||
|
||||
for await (const entry of Object.entries(secretMap)) {
|
||||
const [key, { value }] = entry;
|
||||
@ -171,19 +148,10 @@ export const OnePassSyncFns = {
|
||||
itemTitle: key,
|
||||
itemValue: value,
|
||||
itemId: items[key].id,
|
||||
fieldId: items[key].fieldId,
|
||||
valueLabel: valueLabel || VALUE_LABEL_DEFAULT,
|
||||
otherFields: items[key].fields.filter((field) => field.label !== (valueLabel || VALUE_LABEL_DEFAULT))
|
||||
fieldId: items[key].fieldId
|
||||
});
|
||||
} else {
|
||||
await createOnePassItem({
|
||||
instanceUrl,
|
||||
apiToken,
|
||||
vaultId,
|
||||
itemTitle: key,
|
||||
itemValue: value,
|
||||
valueLabel: valueLabel || VALUE_LABEL_DEFAULT
|
||||
});
|
||||
await createOnePassItem({ instanceUrl, apiToken, vaultId, itemTitle: key, itemValue: value });
|
||||
}
|
||||
} catch (error) {
|
||||
throw new SecretSyncError({
|
||||
@ -195,28 +163,7 @@ export const OnePassSyncFns = {
|
||||
|
||||
if (secretSync.syncOptions.disableSecretDeletion) return;
|
||||
|
||||
// Delete duplicate item entries
|
||||
for await (const [itemId, key] of Object.entries(duplicates)) {
|
||||
// eslint-disable-next-line no-continue
|
||||
if (!matchesSchema(key, environment?.slug || "", secretSync.syncOptions.keySchema)) continue;
|
||||
|
||||
try {
|
||||
await deleteOnePassItem({
|
||||
instanceUrl,
|
||||
apiToken,
|
||||
vaultId,
|
||||
itemId
|
||||
});
|
||||
} catch (error) {
|
||||
throw new SecretSyncError({
|
||||
error,
|
||||
secretKey: key
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Delete item entries that are not in secretMap
|
||||
for await (const [key, item] of Object.entries(items)) {
|
||||
for await (const [key, variable] of Object.entries(items)) {
|
||||
// eslint-disable-next-line no-continue
|
||||
if (!matchesSchema(key, environment?.slug || "", secretSync.syncOptions.keySchema)) continue;
|
||||
|
||||
@ -226,7 +173,7 @@ export const OnePassSyncFns = {
|
||||
instanceUrl,
|
||||
apiToken,
|
||||
vaultId,
|
||||
itemId: item.id
|
||||
itemId: variable.id
|
||||
});
|
||||
} catch (error) {
|
||||
throw new SecretSyncError({
|
||||
@ -240,18 +187,13 @@ export const OnePassSyncFns = {
|
||||
removeSecrets: async (secretSync: TOnePassSyncWithCredentials, secretMap: TSecretMap) => {
|
||||
const {
|
||||
connection,
|
||||
destinationConfig: { vaultId, valueLabel }
|
||||
destinationConfig: { vaultId }
|
||||
} = secretSync;
|
||||
|
||||
const instanceUrl = await getOnePassInstanceUrl(connection);
|
||||
const { apiToken } = connection.credentials;
|
||||
|
||||
const { items } = await listOnePassItems({
|
||||
instanceUrl,
|
||||
apiToken,
|
||||
vaultId,
|
||||
valueLabel: valueLabel || VALUE_LABEL_DEFAULT
|
||||
});
|
||||
const items = await listOnePassItems({ instanceUrl, apiToken, vaultId });
|
||||
|
||||
for await (const [key, item] of Object.entries(items)) {
|
||||
if (key in secretMap) {
|
||||
@ -274,19 +216,12 @@ export const OnePassSyncFns = {
|
||||
getSecrets: async (secretSync: TOnePassSyncWithCredentials) => {
|
||||
const {
|
||||
connection,
|
||||
destinationConfig: { vaultId, valueLabel }
|
||||
destinationConfig: { vaultId }
|
||||
} = secretSync;
|
||||
|
||||
const instanceUrl = await getOnePassInstanceUrl(connection);
|
||||
const { apiToken } = connection.credentials;
|
||||
|
||||
const res = await listOnePassItems({
|
||||
instanceUrl,
|
||||
apiToken,
|
||||
vaultId,
|
||||
valueLabel: valueLabel || VALUE_LABEL_DEFAULT
|
||||
});
|
||||
|
||||
return Object.fromEntries(Object.entries(res.items).map(([key, item]) => [key, { value: item.value }]));
|
||||
return listOnePassItems({ instanceUrl, apiToken, vaultId });
|
||||
}
|
||||
};
|
||||
|
@ -11,8 +11,7 @@ import {
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
const OnePassSyncDestinationConfigSchema = z.object({
|
||||
vaultId: z.string().trim().min(1, "Vault required").describe(SecretSyncs.DESTINATION_CONFIG.ONEPASS.vaultId),
|
||||
valueLabel: z.string().trim().optional().describe(SecretSyncs.DESTINATION_CONFIG.ONEPASS.valueLabel)
|
||||
vaultId: z.string().trim().min(1, "Vault required").describe(SecretSyncs.DESTINATION_CONFIG.ONEPASS.vaultId)
|
||||
});
|
||||
|
||||
const OnePassSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||
|
@ -14,32 +14,29 @@ export type TOnePassSyncWithCredentials = TOnePassSync & {
|
||||
connection: TOnePassConnection;
|
||||
};
|
||||
|
||||
type Field = {
|
||||
id: string;
|
||||
type: string; // CONCEALED, STRING
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type TOnePassVariable = {
|
||||
id: string;
|
||||
title: string;
|
||||
category: string; // API_CREDENTIAL, SECURE_NOTE, LOGIN, etc
|
||||
fields: Field[];
|
||||
};
|
||||
|
||||
export type TOnePassVariableDetails = TOnePassVariable & {
|
||||
fields: {
|
||||
id: string;
|
||||
type: string; // CONCEALED, STRING
|
||||
label: string;
|
||||
value: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type TOnePassListVariablesResponse = TOnePassVariable[];
|
||||
|
||||
type TOnePassBase = {
|
||||
export type TOnePassListVariables = {
|
||||
apiToken: string;
|
||||
instanceUrl: string;
|
||||
vaultId: string;
|
||||
};
|
||||
|
||||
export type TOnePassListVariables = TOnePassBase & {
|
||||
valueLabel: string;
|
||||
};
|
||||
|
||||
export type TPostOnePassVariable = TOnePassListVariables & {
|
||||
itemTitle: string;
|
||||
itemValue: string;
|
||||
@ -50,9 +47,8 @@ export type TPutOnePassVariable = TOnePassListVariables & {
|
||||
fieldId: string;
|
||||
itemTitle: string;
|
||||
itemValue: string;
|
||||
otherFields: Field[];
|
||||
};
|
||||
|
||||
export type TDeleteOnePassVariable = TOnePassBase & {
|
||||
export type TDeleteOnePassVariable = TOnePassListVariables & {
|
||||
itemId: string;
|
||||
};
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Create LDAP SSO Config"
|
||||
openapi: "POST /api/v1/ldap/config"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Get LDAP SSO Config"
|
||||
openapi: "GET /api/v1/ldap/config"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Update LDAP SSO Config"
|
||||
openapi: "PATCH /api/v1/ldap/config"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Create OIDC Config"
|
||||
openapi: "POST /api/v1/sso/oidc/config"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Get OIDC Config"
|
||||
openapi: "GET /api/v1/sso/oidc/config"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Update OIDC Config"
|
||||
openapi: "PATCH /api/v1/sso/oidc/config"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Create SAML SSO Config"
|
||||
openapi: "POST /api/v1/sso/config"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Get SAML SSO Config"
|
||||
openapi: "GET /api/v1/sso/config"
|
||||
---
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Update SAML SSO Config"
|
||||
openapi: "PATCH /api/v1/sso/config"
|
||||
---
|
@ -849,6 +849,30 @@
|
||||
{
|
||||
"group": "Organizations",
|
||||
"pages": [
|
||||
{
|
||||
"group": "OIDC SSO",
|
||||
"pages": [
|
||||
"api-reference/endpoints/organizations/oidc-sso/get-oidc-config",
|
||||
"api-reference/endpoints/organizations/oidc-sso/update-oidc-config",
|
||||
"api-reference/endpoints/organizations/oidc-sso/create-oidc-config"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "LDAP SSO",
|
||||
"pages": [
|
||||
"api-reference/endpoints/organizations/ldap-sso/get-ldap-config",
|
||||
"api-reference/endpoints/organizations/ldap-sso/update-ldap-config",
|
||||
"api-reference/endpoints/organizations/ldap-sso/create-ldap-config"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "SAML SSO",
|
||||
"pages": [
|
||||
"api-reference/endpoints/organizations/saml-sso/get-saml-config",
|
||||
"api-reference/endpoints/organizations/saml-sso/update-saml-config",
|
||||
"api-reference/endpoints/organizations/saml-sso/create-saml-config"
|
||||
]
|
||||
},
|
||||
"api-reference/endpoints/organizations/memberships",
|
||||
"api-reference/endpoints/organizations/update-membership",
|
||||
"api-reference/endpoints/organizations/delete-membership",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 641 KiB After Width: | Height: | Size: 715 KiB |
@ -36,7 +36,6 @@ description: "Learn how to configure a 1Password Sync for Infisical."
|
||||
|
||||
- **1Password Connection**: The 1Password Connection to authenticate with.
|
||||
- **Vault**: The 1Password vault to sync secrets to.
|
||||
- **Value Label**: The label of the 1Password item field that will hold your secret value.
|
||||
</Step>
|
||||
<Step title="Configure sync options">
|
||||
Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
|
||||
@ -95,8 +94,7 @@ description: "Learn how to configure a 1Password Sync for Infisical."
|
||||
"initialSyncBehavior": "overwrite-destination"
|
||||
},
|
||||
"destinationConfig": {
|
||||
"vaultId": "...",
|
||||
"valueLabel": "value"
|
||||
"vaultId": "..."
|
||||
}
|
||||
}'
|
||||
```
|
||||
@ -147,8 +145,7 @@ description: "Learn how to configure a 1Password Sync for Infisical."
|
||||
},
|
||||
"destination": "1password",
|
||||
"destinationConfig": {
|
||||
"vaultId": "...",
|
||||
"valueLabel": "value"
|
||||
"vaultId": "..."
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -163,7 +160,4 @@ description: "Learn how to configure a 1Password Sync for Infisical."
|
||||
Infisical can only perform CRUD operations on the following item types:
|
||||
- API Credentials
|
||||
</Accordion>
|
||||
<Accordion title="What is a 'Value Label'?">
|
||||
It's the label of the 1Password item field which will hold your secret value. For example, if you were to sync Infisical secret 'foo: bar', the 1Password item equivalent would have an item title of 'foo', and a field on that item 'value: bar'. The field label 'value' is what gets changed by this option.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
@ -4,7 +4,7 @@ import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
|
||||
import { FilterableSelect, FormControl, Input, Tooltip } from "@app/components/v2";
|
||||
import { FilterableSelect, FormControl, Tooltip } from "@app/components/v2";
|
||||
import {
|
||||
TOnePassVault,
|
||||
useOnePassConnectionListVaults
|
||||
@ -32,7 +32,6 @@ export const OnePassSyncFields = () => {
|
||||
<SecretSyncConnectionField
|
||||
onChange={() => {
|
||||
setValue("destinationConfig.vaultId", "");
|
||||
setValue("destinationConfig.valueLabel", "");
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -70,22 +69,6 @@ export const OnePassSyncFields = () => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
isOptional
|
||||
label="Value Label"
|
||||
tooltipText="It's the label of the 1Password item field which will hold your secret value. For example, if you were to sync Infisical secret 'foo: bar', the 1Password item equivalent would have an item title of 'foo', and a field on that item 'value: bar'. The field label 'value' is what gets changed by this option."
|
||||
>
|
||||
<Input value={value} onChange={onChange} placeholder="value" />
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="destinationConfig.valueLabel"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -6,15 +6,7 @@ import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
export const OnePassSyncReviewFields = () => {
|
||||
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.OnePass }>();
|
||||
const [vaultId, valueLabel] = watch([
|
||||
"destinationConfig.vaultId",
|
||||
"destinationConfig.valueLabel"
|
||||
]);
|
||||
const vaultId = watch("destinationConfig.vaultId");
|
||||
|
||||
return (
|
||||
<>
|
||||
<GenericFieldLabel label="Vault ID">{vaultId}</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Value Key">{valueLabel || "value"}</GenericFieldLabel>
|
||||
</>
|
||||
);
|
||||
return <GenericFieldLabel label="Vault ID">{vaultId}</GenericFieldLabel>;
|
||||
};
|
||||
|
@ -7,8 +7,7 @@ export const OnePassSyncDestinationSchema = BaseSecretSyncSchema().merge(
|
||||
z.object({
|
||||
destination: z.literal(SecretSync.OnePass),
|
||||
destinationConfig: z.object({
|
||||
vaultId: z.string().trim().min(1, "Vault ID required"),
|
||||
valueLabel: z.string().trim().optional()
|
||||
vaultId: z.string().trim().min(1, "Vault ID required")
|
||||
})
|
||||
})
|
||||
);
|
||||
|
@ -21,7 +21,7 @@ export const useUpdateOIDCConfig = () => {
|
||||
clientId,
|
||||
clientSecret,
|
||||
isActive,
|
||||
orgSlug,
|
||||
organizationId,
|
||||
manageGroupMemberships,
|
||||
jwtSignatureAlgorithm
|
||||
}: {
|
||||
@ -36,7 +36,7 @@ export const useUpdateOIDCConfig = () => {
|
||||
clientSecret?: string;
|
||||
isActive?: boolean;
|
||||
configurationType?: string;
|
||||
orgSlug: string;
|
||||
organizationId: string;
|
||||
manageGroupMemberships?: boolean;
|
||||
jwtSignatureAlgorithm?: OIDCJWTSignatureAlgorithm;
|
||||
}) => {
|
||||
@ -50,7 +50,7 @@ export const useUpdateOIDCConfig = () => {
|
||||
tokenEndpoint,
|
||||
userinfoEndpoint,
|
||||
clientId,
|
||||
orgSlug,
|
||||
organizationId,
|
||||
clientSecret,
|
||||
isActive,
|
||||
manageGroupMemberships,
|
||||
@ -60,7 +60,7 @@ export const useUpdateOIDCConfig = () => {
|
||||
return data;
|
||||
},
|
||||
onSuccess(_, dto) {
|
||||
queryClient.invalidateQueries({ queryKey: oidcConfigKeys.getOIDCConfig(dto.orgSlug) });
|
||||
queryClient.invalidateQueries({ queryKey: oidcConfigKeys.getOIDCConfig(dto.organizationId) });
|
||||
queryClient.invalidateQueries({ queryKey: organizationKeys.getUserOrganizations });
|
||||
}
|
||||
});
|
||||
@ -81,7 +81,7 @@ export const useCreateOIDCConfig = () => {
|
||||
clientId,
|
||||
clientSecret,
|
||||
isActive,
|
||||
orgSlug,
|
||||
organizationId,
|
||||
manageGroupMemberships,
|
||||
jwtSignatureAlgorithm
|
||||
}: {
|
||||
@ -95,7 +95,7 @@ export const useCreateOIDCConfig = () => {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
isActive: boolean;
|
||||
orgSlug: string;
|
||||
organizationId: string;
|
||||
allowedEmailDomains?: string;
|
||||
manageGroupMemberships?: boolean;
|
||||
jwtSignatureAlgorithm?: OIDCJWTSignatureAlgorithm;
|
||||
@ -112,7 +112,7 @@ export const useCreateOIDCConfig = () => {
|
||||
clientId,
|
||||
clientSecret,
|
||||
isActive,
|
||||
orgSlug,
|
||||
organizationId,
|
||||
manageGroupMemberships,
|
||||
jwtSignatureAlgorithm
|
||||
});
|
||||
@ -120,7 +120,7 @@ export const useCreateOIDCConfig = () => {
|
||||
return data;
|
||||
},
|
||||
onSuccess(_, dto) {
|
||||
queryClient.invalidateQueries({ queryKey: oidcConfigKeys.getOIDCConfig(dto.orgSlug) });
|
||||
queryClient.invalidateQueries({ queryKey: oidcConfigKeys.getOIDCConfig(dto.organizationId) });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -5,18 +5,18 @@ import { apiRequest } from "@app/config/request";
|
||||
import { OIDCConfigData } from "./types";
|
||||
|
||||
export const oidcConfigKeys = {
|
||||
getOIDCConfig: (orgSlug: string) => [{ orgSlug }, "organization-oidc"] as const,
|
||||
getOIDCConfig: (orgId: string) => [{ orgId }, "organization-oidc"] as const,
|
||||
getOIDCManageGroupMembershipsEnabled: (orgId: string) =>
|
||||
["oidc-manage-group-memberships", orgId] as const
|
||||
};
|
||||
|
||||
export const useGetOIDCConfig = (orgSlug: string) => {
|
||||
export const useGetOIDCConfig = (orgId: string) => {
|
||||
return useQuery({
|
||||
queryKey: oidcConfigKeys.getOIDCConfig(orgSlug),
|
||||
queryKey: oidcConfigKeys.getOIDCConfig(orgId),
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const { data } = await apiRequest.get<OIDCConfigData>(
|
||||
`/api/v1/sso/oidc/config?orgSlug=${orgSlug}`
|
||||
`/api/v1/sso/oidc/config?organizationId=${orgId}`
|
||||
);
|
||||
|
||||
return data;
|
||||
|
@ -6,7 +6,6 @@ export type TOnePassSync = TRootSecretSync & {
|
||||
destination: SecretSync.OnePass;
|
||||
destinationConfig: {
|
||||
vaultId: string;
|
||||
valueLabel?: string;
|
||||
};
|
||||
connection: {
|
||||
app: AppConnection.OnePass;
|
||||
|
@ -164,10 +164,7 @@ export const MinimizedOrgSidebar = () => {
|
||||
const handleCopyToken = async () => {
|
||||
try {
|
||||
await window.navigator.clipboard.writeText(getAuthToken());
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Copied current login session token to clipboard"
|
||||
});
|
||||
createNotification({ type: "success", text: "Copied current login session token to clipboard" });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
createNotification({ type: "error", text: "Failed to copy user token to clipboard" });
|
||||
|
@ -105,7 +105,7 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele
|
||||
const { mutateAsync: updateMutateAsync, isPending: updateIsLoading } = useUpdateOIDCConfig();
|
||||
const [isDeletePopupOpen, setIsDeletePopupOpen] = useToggle(false);
|
||||
|
||||
const { data } = useGetOIDCConfig(currentOrg?.slug ?? "");
|
||||
const { data } = useGetOIDCConfig(currentOrg?.id ?? "");
|
||||
|
||||
const { control, handleSubmit, reset, setValue, watch } = useForm<OIDCFormData>({
|
||||
resolver: zodResolver(schema),
|
||||
@ -134,7 +134,7 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele
|
||||
clientId: "",
|
||||
clientSecret: "",
|
||||
isActive: false,
|
||||
orgSlug: currentOrg.slug
|
||||
organizationId: currentOrg.id
|
||||
});
|
||||
|
||||
createNotification({
|
||||
@ -196,7 +196,7 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele
|
||||
clientId,
|
||||
clientSecret,
|
||||
isActive: true,
|
||||
orgSlug: currentOrg.slug,
|
||||
organizationId: currentOrg.id,
|
||||
jwtSignatureAlgorithm
|
||||
});
|
||||
} else {
|
||||
@ -212,7 +212,7 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele
|
||||
clientId,
|
||||
clientSecret,
|
||||
isActive: true,
|
||||
orgSlug: currentOrg.slug,
|
||||
organizationId: currentOrg.id,
|
||||
jwtSignatureAlgorithm
|
||||
});
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ export const OrgOIDCSection = (): JSX.Element => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { data, isPending } = useGetOIDCConfig(currentOrg?.slug ?? "");
|
||||
const { data, isPending } = useGetOIDCConfig(currentOrg?.id ?? "");
|
||||
const { mutateAsync } = useUpdateOIDCConfig();
|
||||
const { mutateAsync: updateOrg } = useUpdateOrg();
|
||||
|
||||
@ -41,7 +41,7 @@ export const OrgOIDCSection = (): JSX.Element => {
|
||||
}
|
||||
|
||||
await mutateAsync({
|
||||
orgSlug: currentOrg?.slug,
|
||||
organizationId: currentOrg?.id,
|
||||
isActive: value
|
||||
});
|
||||
|
||||
@ -114,7 +114,7 @@ export const OrgOIDCSection = (): JSX.Element => {
|
||||
}
|
||||
|
||||
await mutateAsync({
|
||||
orgSlug: currentOrg?.slug,
|
||||
organizationId: currentOrg?.id,
|
||||
manageGroupMemberships: value
|
||||
});
|
||||
|
||||
|
@ -38,7 +38,7 @@ export const OrgSsoTab = withPermission(
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { data: oidcConfig, isPending: isLoadingOidcConfig } = useGetOIDCConfig(
|
||||
currentOrg?.slug ?? ""
|
||||
currentOrg?.id ?? ""
|
||||
);
|
||||
const { data: samlConfig, isPending: isLoadingSamlConfig } = useGetSSOConfig(
|
||||
currentOrg?.id ?? ""
|
||||
|
@ -445,7 +445,7 @@ export const ReviewAccessRequestModal = ({
|
||||
onCheckedChange={(checked) => setBypassApproval(checked === true)}
|
||||
isChecked={bypassApproval}
|
||||
id="byPassApproval"
|
||||
className={twMerge("mr-2", bypassApproval ? "!border-red/30 !bg-red/10" : "")}
|
||||
className={twMerge("mr-2", bypassApproval ? "border-red/30 bg-red/10" : "")}
|
||||
>
|
||||
<span className="text-xs text-red">
|
||||
Approve without waiting for requirements to be met (bypass policy protection)
|
||||
|
@ -102,73 +102,43 @@ export const SecretApprovalRequestAction = ({
|
||||
|
||||
if (!hasMerged && status === "open") {
|
||||
return (
|
||||
<div className="flex w-full flex-col items-start justify-between py-4 text-mineshaft-100 transition-all">
|
||||
<div className="flex w-full flex-col justify-between xl:flex-row xl:items-center">
|
||||
<div className="mr-auto flex items-center space-x-4 px-4">
|
||||
<div
|
||||
className={`flex items-center justify-center rounded-full ${isMergable ? "h-8 w-8 bg-green" : "h-10 w-10 bg-red-600"}`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isMergable ? faCheck : faXmark}
|
||||
className={isMergable ? "text-lg text-white" : "text-2xl text-white"}
|
||||
/>
|
||||
</div>
|
||||
<span className="flex flex-col">
|
||||
<p className={`text-md font-medium ${isMergable && "text-lg"}`}>
|
||||
{isMergable ? "Good to merge" : "Merging is blocked"}
|
||||
</p>
|
||||
{!isMergable && (
|
||||
<span className="inline-block text-xs text-mineshaft-300">
|
||||
At least {approvals} approving review{`${approvals > 1 ? "s" : ""}`} required by
|
||||
eligible reviewers.
|
||||
{Boolean(statusChangeByEmail) && `. Reopened by ${statusChangeByEmail}`}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<div className="flex w-full flex-col items-start justify-between py-4 transition-all">
|
||||
<div className="flex items-center space-x-4 px-4">
|
||||
<div
|
||||
className={`flex items-center justify-center rounded-full ${isMergable ? "h-10 w-10 bg-green" : "h-11 w-11 bg-red-600"}`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isMergable ? faCheck : faXmark}
|
||||
className={isMergable ? "text-lg text-black" : "text-2xl text-white"}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 flex items-center justify-end space-x-2 px-4 xl:mt-0">
|
||||
{canApprove || isSoftEnforcement ? (
|
||||
<div className="flex items-center space-x-4">
|
||||
<Button
|
||||
onClick={() => handleSecretApprovalStatusChange("close")}
|
||||
isLoading={isStatusChanging}
|
||||
variant="outline_bg"
|
||||
colorSchema="primary"
|
||||
leftIcon={<FontAwesomeIcon icon={faClose} />}
|
||||
className="hover:border-red/60 hover:bg-red/10"
|
||||
>
|
||||
Close request
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<FontAwesomeIcon icon={!canApprove ? faLandMineOn : faCheck} />}
|
||||
isDisabled={
|
||||
!(
|
||||
(isMergable && canApprove) ||
|
||||
(isSoftEnforcement && byPassApproval && isValidBypassReason(bypassReason))
|
||||
)
|
||||
}
|
||||
isLoading={isMerging}
|
||||
onClick={handleSecretApprovalRequestMerge}
|
||||
colorSchema={isSoftEnforcement && !canApprove ? "danger" : "primary"}
|
||||
variant="outline_bg"
|
||||
>
|
||||
Merge
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-mineshaft-400">Only approvers can merge</div>
|
||||
<span className="flex flex-col">
|
||||
<p className={`text-md font-medium ${isMergable && "text-lg"}`}>
|
||||
{isMergable ? "Good to merge" : "Merging is blocked"}
|
||||
</p>
|
||||
{!isMergable && (
|
||||
<span className="inline-block text-xs text-bunker-200">
|
||||
At least {approvals} approving review{`${approvals > 1 ? "s" : ""}`} required by
|
||||
eligible reviewers.
|
||||
{Boolean(statusChangeByEmail) && `. Reopened by ${statusChangeByEmail}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
{isSoftEnforcement && !isMergable && isBypasser && (
|
||||
<div className="mt-4 w-full border-t border-mineshaft-600 px-5">
|
||||
<div
|
||||
className={`mt-4 w-full border-mineshaft-600 px-5 ${isMergable ? "border-t pb-2" : "border-y pb-4"}`}
|
||||
>
|
||||
<div className="mt-2 flex flex-col space-y-2 pt-2">
|
||||
<Checkbox
|
||||
onCheckedChange={(checked) => setByPassApproval(checked === true)}
|
||||
isChecked={byPassApproval}
|
||||
id="byPassApproval"
|
||||
checkIndicatorBg="text-white"
|
||||
className={twMerge("mr-2", byPassApproval ? "!border-red/30 !bg-red/10" : "")}
|
||||
className={twMerge(
|
||||
"mr-2",
|
||||
byPassApproval ? "border-red bg-red hover:bg-red-600" : ""
|
||||
)}
|
||||
>
|
||||
<span className="text-sm">
|
||||
Merge without waiting for approval (bypass secret change policy)
|
||||
@ -192,18 +162,51 @@ export const SecretApprovalRequestAction = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-2 flex w-full items-center justify-end space-x-2 px-4">
|
||||
{canApprove || isSoftEnforcement ? (
|
||||
<div className="flex items-center space-x-4">
|
||||
<Button
|
||||
onClick={() => handleSecretApprovalStatusChange("close")}
|
||||
isLoading={isStatusChanging}
|
||||
variant="outline_bg"
|
||||
colorSchema="primary"
|
||||
leftIcon={<FontAwesomeIcon icon={faClose} />}
|
||||
className="hover:border-red/60 hover:bg-red/10"
|
||||
>
|
||||
Close request
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<FontAwesomeIcon icon={!canApprove ? faLandMineOn : faCheck} />}
|
||||
isDisabled={
|
||||
!(
|
||||
(isMergable && canApprove) ||
|
||||
(isSoftEnforcement && byPassApproval && isValidBypassReason(bypassReason))
|
||||
)
|
||||
}
|
||||
isLoading={isMerging}
|
||||
onClick={handleSecretApprovalRequestMerge}
|
||||
colorSchema={isSoftEnforcement && !canApprove ? "danger" : "primary"}
|
||||
variant="solid"
|
||||
>
|
||||
Merge
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div>Only approvers can merge</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (hasMerged && status === "close")
|
||||
return (
|
||||
<div className="flex w-full items-center justify-between rounded-md border border-green/60 bg-green/10">
|
||||
<div className="flex items-start space-x-2 p-4">
|
||||
<FontAwesomeIcon icon={faCheck} className="mt-0.5 text-xl text-green" />
|
||||
<div className="flex w-full items-center justify-between rounded-md border border-primary/60 bg-primary/10">
|
||||
<div className="flex items-start space-x-4 p-4">
|
||||
<FontAwesomeIcon icon={faCheck} className="pt-1 text-2xl text-primary" />
|
||||
<span className="flex flex-col">
|
||||
Change request merged
|
||||
<span className="inline-block text-xs text-mineshaft-300">
|
||||
<span className="inline-block text-xs text-bunker-200">
|
||||
Merged by {statusChangeByEmail}.
|
||||
</span>
|
||||
</span>
|
||||
@ -212,26 +215,26 @@ export const SecretApprovalRequestAction = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex w-full items-center justify-between rounded-md border border-yellow/60 bg-yellow/10">
|
||||
<div className="flex items-start space-x-2 p-4">
|
||||
<FontAwesomeIcon icon={faUserLock} className="mt-0.5 text-xl text-yellow" />
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="flex items-start space-x-4">
|
||||
<FontAwesomeIcon icon={faUserLock} className="pt-1 text-2xl text-primary" />
|
||||
<span className="flex flex-col">
|
||||
Secret approval has been closed
|
||||
<span className="inline-block text-xs text-mineshaft-300">
|
||||
<span className="inline-block text-xs text-bunker-200">
|
||||
Closed by {statusChangeByEmail}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => handleSecretApprovalStatusChange("open")}
|
||||
isLoading={isStatusChanging}
|
||||
variant="plain"
|
||||
colorSchema="secondary"
|
||||
className="mr-4 text-yellow/60 hover:text-yellow"
|
||||
leftIcon={<FontAwesomeIcon icon={faLockOpen} />}
|
||||
>
|
||||
Reopen request
|
||||
</Button>
|
||||
<div className="flex items-center space-x-6">
|
||||
<Button
|
||||
onClick={() => handleSecretApprovalStatusChange("open")}
|
||||
isLoading={isStatusChanging}
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faLockOpen} />}
|
||||
>
|
||||
Reopen request
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -3,12 +3,11 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { useState } from "react";
|
||||
import {
|
||||
faCircleCheck,
|
||||
faCircleXmark,
|
||||
faExclamationTriangle,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faInfoCircle,
|
||||
faInfo,
|
||||
faKey
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@ -30,14 +29,14 @@ export type Props = {
|
||||
};
|
||||
|
||||
const generateItemTitle = (op: CommitType) => {
|
||||
let text = { label: "", className: "" };
|
||||
if (op === CommitType.CREATE) text = { label: "create", className: "text-green-600" };
|
||||
else if (op === CommitType.UPDATE) text = { label: "change", className: "text-yellow-600" };
|
||||
else text = { label: "deletion", className: "text-red-600" };
|
||||
let text = { label: "", color: "" };
|
||||
if (op === CommitType.CREATE) text = { label: "create", color: "#60DD00" };
|
||||
else if (op === CommitType.UPDATE) text = { label: "change", color: "#F8EB30" };
|
||||
else text = { label: "deletion", color: "#F83030" };
|
||||
|
||||
return (
|
||||
<div className="text-md pb-2 font-medium">
|
||||
Request for <span className={text.className}>secret {text.label}</span>
|
||||
Request for <span style={{ color: text.color }}>secret {text.label}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -69,15 +68,15 @@ export const SecretApprovalRequestChangeItem = ({
|
||||
<div className="flex items-center px-1 py-1">
|
||||
<div className="flex-grow">{generateItemTitle(op)}</div>
|
||||
{!hasMerged && isStale && (
|
||||
<div className="flex items-center text-mineshaft-300">
|
||||
<FontAwesomeIcon icon={faInfoCircle} className="text-xs" />
|
||||
<span className="ml-1 text-xs">Secret has been changed (stale)</span>
|
||||
<div className="flex items-center">
|
||||
<FontAwesomeIcon icon={faInfo} className="text-sm text-primary-600" />
|
||||
<span className="ml-2 text-xs">Secret has been changed(stale)</span>
|
||||
</div>
|
||||
)}
|
||||
{hasMerged && hasConflict && (
|
||||
<div className="flex items-center space-x-1 text-xs text-bunker-300">
|
||||
<div className="flex items-center space-x-2 text-sm text-bunker-300">
|
||||
<Tooltip content="Merge Conflict">
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} className="text-xs text-red" />
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} className="text-red-700" />
|
||||
</Tooltip>
|
||||
<div>{generateConflictText(op)}</div>
|
||||
</div>
|
||||
@ -96,7 +95,7 @@ export const SecretApprovalRequestChangeItem = ({
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<div className="text-sm font-medium text-mineshaft-300">Key</div>
|
||||
<p className="max-w-lg break-words text-sm">{secretVersion?.secretKey}</p>
|
||||
<div className="text-sm">{secretVersion?.secretKey} </div>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<div className="text-sm font-medium text-mineshaft-300">Value</div>
|
||||
@ -148,7 +147,7 @@ export const SecretApprovalRequestChangeItem = ({
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<div className="text-sm font-medium text-mineshaft-300">Comment</div>
|
||||
<div className="thin-scrollbar max-h-[5rem] max-w-[34rem] overflow-y-auto break-words text-sm xl:max-w-[28rem]">
|
||||
<div className="max-h-[5rem] overflow-y-auto text-sm">
|
||||
{secretVersion?.secretComment || (
|
||||
<span className="text-sm text-mineshaft-300">-</span>
|
||||
)}{" "}
|
||||
@ -187,27 +186,15 @@ export const SecretApprovalRequestChangeItem = ({
|
||||
className="mr-0 flex items-center rounded-r-none border border-mineshaft-500"
|
||||
>
|
||||
<FontAwesomeIcon icon={faKey} size="xs" className="mr-1" />
|
||||
<Tooltip
|
||||
className="max-w-lg whitespace-normal break-words"
|
||||
content={el.key}
|
||||
>
|
||||
<div className="max-w-[125px] overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{el.key}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div>{el.key}</div>
|
||||
</Tag>
|
||||
<Tag
|
||||
size="xs"
|
||||
className="flex items-center rounded-l-none border border-mineshaft-500 bg-mineshaft-900 pl-1"
|
||||
>
|
||||
<Tooltip
|
||||
className="max-w-lg whitespace-normal break-words"
|
||||
content={el.value}
|
||||
>
|
||||
<div className="max-w-[125px] overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{el.value}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div className="max-w-[150px] overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{el.value}
|
||||
</div>
|
||||
</Tag>
|
||||
</div>
|
||||
))}
|
||||
@ -228,13 +215,13 @@ export const SecretApprovalRequestChangeItem = ({
|
||||
<div className="mb-4 flex flex-row justify-between">
|
||||
<span className="text-md font-medium">New Secret</span>
|
||||
<div className="rounded-full bg-green-600 px-2 pb-[0.14rem] pt-[0.2rem] text-xs font-medium">
|
||||
<FontAwesomeIcon icon={faCircleCheck} className="pr-1 text-white" />
|
||||
<FontAwesomeIcon icon={faCircleXmark} className="pr-1 text-white" />
|
||||
New
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<div className="text-sm font-medium text-mineshaft-300">Key</div>
|
||||
<div className="max-w-md break-words text-sm">{newVersion?.secretKey} </div>
|
||||
<div className="text-sm">{newVersion?.secretKey} </div>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<div className="text-sm font-medium text-mineshaft-300">Value</div>
|
||||
@ -286,7 +273,7 @@ export const SecretApprovalRequestChangeItem = ({
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<div className="text-sm font-medium text-mineshaft-300">Comment</div>
|
||||
<div className="thin-scrollbar max-h-[5rem] max-w-[34rem] overflow-y-auto break-words text-sm xl:max-w-[28rem]">
|
||||
<div className="max-h-[5rem] overflow-y-auto text-sm">
|
||||
{newVersion?.secretComment || (
|
||||
<span className="text-sm text-mineshaft-300">-</span>
|
||||
)}{" "}
|
||||
@ -294,15 +281,15 @@ export const SecretApprovalRequestChangeItem = ({
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<div className="text-sm font-medium text-mineshaft-300">Tags</div>
|
||||
<div className="flex flex-wrap gap-y-2">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(newVersion?.tags?.length ?? 0) ? (
|
||||
newVersion?.tags?.map(({ slug, id: tagId, color }) => (
|
||||
<Tag
|
||||
className="flex w-min items-center space-x-1.5 border border-mineshaft-500 bg-mineshaft-800"
|
||||
className="flex w-min items-center space-x-2"
|
||||
key={`${newVersion.id}-${tagId}`}
|
||||
>
|
||||
<div
|
||||
className="h-2.5 w-2.5 rounded-full"
|
||||
className="h-3 w-3 rounded-full"
|
||||
style={{ backgroundColor: color || "#bec2c8" }}
|
||||
/>
|
||||
<div className="text-sm">{slug}</div>
|
||||
@ -324,27 +311,15 @@ export const SecretApprovalRequestChangeItem = ({
|
||||
className="mr-0 flex items-center rounded-r-none border border-mineshaft-500"
|
||||
>
|
||||
<FontAwesomeIcon icon={faKey} size="xs" className="mr-1" />
|
||||
<Tooltip
|
||||
className="max-w-lg whitespace-normal break-words"
|
||||
content={el.key}
|
||||
>
|
||||
<div className="max-w-[125px] overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{el.key}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div>{el.key}</div>
|
||||
</Tag>
|
||||
<Tag
|
||||
size="xs"
|
||||
className="flex items-center rounded-l-none border border-mineshaft-500 bg-mineshaft-900 pl-1"
|
||||
>
|
||||
<Tooltip
|
||||
className="max-w-lg whitespace-normal break-words"
|
||||
content={el.value}
|
||||
>
|
||||
<div className="max-w-[125px] overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{el.value}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div className="max-w-[150px] overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{el.value}
|
||||
</div>
|
||||
</Tag>
|
||||
</div>
|
||||
))}
|
||||
|
@ -3,12 +3,12 @@ import { Controller, useForm } from "react-hook-form";
|
||||
import {
|
||||
faAngleDown,
|
||||
faArrowLeft,
|
||||
faBan,
|
||||
faCheck,
|
||||
faCheckCircle,
|
||||
faCircle,
|
||||
faCodeBranch,
|
||||
faComment,
|
||||
faFolder,
|
||||
faHourglass
|
||||
faXmarkCircle
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
@ -26,7 +26,6 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
EmptyState,
|
||||
FormControl,
|
||||
GenericFieldLabel,
|
||||
IconButton,
|
||||
TextArea,
|
||||
Tooltip
|
||||
@ -82,10 +81,10 @@ export const generateCommitText = (commits: { op: CommitType }[] = [], isReplica
|
||||
|
||||
const getReviewedStatusSymbol = (status?: ApprovalStatus) => {
|
||||
if (status === ApprovalStatus.APPROVED)
|
||||
return <FontAwesomeIcon icon={faCheck} size="xs" className="text-green" />;
|
||||
return <FontAwesomeIcon icon={faCheckCircle} size="xs" style={{ color: "#15803d" }} />;
|
||||
if (status === ApprovalStatus.REJECTED)
|
||||
return <FontAwesomeIcon icon={faBan} size="xs" className="text-red" />;
|
||||
return <FontAwesomeIcon icon={faHourglass} size="xs" className="text-yellow" />;
|
||||
return <FontAwesomeIcon icon={faXmarkCircle} size="xs" style={{ color: "#b91c1c" }} />;
|
||||
return <FontAwesomeIcon icon={faCircle} size="xs" style={{ color: "#c2410c" }} />;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
@ -224,8 +223,8 @@ export const SecretApprovalRequestChanges = ({
|
||||
const hasMerged = secretApprovalRequestDetails?.hasMerged;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-x-6 lg:flex-row">
|
||||
<div className="flex-1 lg:max-w-[calc(100%-17rem)]">
|
||||
<div className="flex space-x-6">
|
||||
<div className="flex-grow">
|
||||
<div className="sticky top-0 z-20 flex items-center space-x-4 bg-bunker-800 pb-6 pt-2">
|
||||
<IconButton variant="outline_bg" ariaLabel="go-back" onClick={onGoBack}>
|
||||
<FontAwesomeIcon icon={faArrowLeft} />
|
||||
@ -243,17 +242,17 @@ export const SecretApprovalRequestChanges = ({
|
||||
: secretApprovalRequestDetails.status}
|
||||
</span>
|
||||
</div>
|
||||
<div className="-mt-0.5 flex-grow flex-col">
|
||||
<div className="flex-grow flex-col">
|
||||
<div className="text-xl">
|
||||
{generateCommitText(
|
||||
secretApprovalRequestDetails.commits,
|
||||
secretApprovalRequestDetails.isReplicated
|
||||
)}
|
||||
</div>
|
||||
<span className="-mt-1 flex items-center space-x-2 text-xs text-gray-400">
|
||||
<div className="flex items-center space-x-2 text-xs text-gray-400">
|
||||
By {secretApprovalRequestDetails?.committerUser?.firstName} (
|
||||
{secretApprovalRequestDetails?.committerUser?.email})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{!hasMerged &&
|
||||
secretApprovalRequestDetails.status === "open" &&
|
||||
@ -263,10 +262,7 @@ export const SecretApprovalRequestChanges = ({
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("reviewChanges", isOpen)}
|
||||
>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
rightIcon={<FontAwesomeIcon className="ml-2" icon={faAngleDown} />}
|
||||
>
|
||||
<Button rightIcon={<FontAwesomeIcon className="ml-2" icon={faAngleDown} />}>
|
||||
Review
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@ -283,87 +279,82 @@ export const SecretApprovalRequestChanges = ({
|
||||
{...field}
|
||||
placeholder="Leave a comment..."
|
||||
reSize="none"
|
||||
className="text-md mt-2 h-40 border border-mineshaft-600 bg-mineshaft-800 placeholder:text-mineshaft-400"
|
||||
className="text-md mt-2 h-40 border border-mineshaft-600 bg-bunker-800"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<Controller
|
||||
control={control}
|
||||
name="status"
|
||||
defaultValue={ApprovalStatus.APPROVED}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className="mb-0"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
<Controller
|
||||
control={control}
|
||||
name="status"
|
||||
defaultValue={ApprovalStatus.APPROVED}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl errorText={error?.message} isError={Boolean(error)}>
|
||||
<RadioGroup
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
className="mb-4 space-y-2"
|
||||
aria-label="Status"
|
||||
>
|
||||
<RadioGroup
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
className="space-y-2"
|
||||
aria-label="Status"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem
|
||||
id="approve"
|
||||
className="h-4 w-4 rounded-full border border-gray-400 text-green focus:ring-2 focus:ring-mineshaft-500"
|
||||
value={ApprovalStatus.APPROVED}
|
||||
aria-labelledby="approve-label"
|
||||
>
|
||||
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
|
||||
</RadioGroupItem>
|
||||
<span
|
||||
id="approve-label"
|
||||
className="cursor-pointer"
|
||||
onClick={() => field.onChange(ApprovalStatus.APPROVED)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
field.onChange(ApprovalStatus.APPROVED);
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
Approve
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem
|
||||
id="reject"
|
||||
className="h-4 w-4 rounded-full border border-gray-400 text-red focus:ring-2 focus:ring-mineshaft-500"
|
||||
value={ApprovalStatus.REJECTED}
|
||||
aria-labelledby="reject-label"
|
||||
>
|
||||
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
|
||||
</RadioGroupItem>
|
||||
<span
|
||||
id="reject-label"
|
||||
className="cursor-pointer"
|
||||
onClick={() => field.onChange(ApprovalStatus.REJECTED)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
field.onChange(ApprovalStatus.REJECTED);
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
Reject
|
||||
</span>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem
|
||||
id="approve"
|
||||
className="h-4 w-4 rounded-full border border-gray-300 text-primary focus:ring-2 focus:ring-mineshaft-500"
|
||||
value={ApprovalStatus.APPROVED}
|
||||
aria-labelledby="approve-label"
|
||||
>
|
||||
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
|
||||
</RadioGroupItem>
|
||||
<span
|
||||
id="approve-label"
|
||||
className="cursor-pointer"
|
||||
onClick={() => field.onChange(ApprovalStatus.APPROVED)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
field.onChange(ApprovalStatus.APPROVED);
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
Approve
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem
|
||||
id="reject"
|
||||
className="h-4 w-4 rounded-full border border-gray-300 text-red focus:ring-2 focus:ring-mineshaft-500"
|
||||
value={ApprovalStatus.REJECTED}
|
||||
aria-labelledby="reject-label"
|
||||
>
|
||||
<RadioGroupIndicator className="flex h-full w-full items-center justify-center after:h-2 after:w-2 after:rounded-full after:bg-current" />
|
||||
</RadioGroupItem>
|
||||
<span
|
||||
id="reject-label"
|
||||
className="cursor-pointer"
|
||||
onClick={() => field.onChange(ApprovalStatus.REJECTED)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
field.onChange(ApprovalStatus.REJECTED);
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
Reject
|
||||
</span>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={isApproving || isRejecting || isSubmitting}
|
||||
variant="outline_bg"
|
||||
className="mt-auto h-min"
|
||||
>
|
||||
Submit Review
|
||||
</Button>
|
||||
@ -380,14 +371,14 @@ export const SecretApprovalRequestChanges = ({
|
||||
<div className="text-sm text-bunker-300">
|
||||
A secret import in
|
||||
<p
|
||||
className="mx-1 inline rounded bg-mineshaft-600/80 text-mineshaft-300"
|
||||
className="mx-1 inline rounded bg-primary-600/40 text-primary-300"
|
||||
style={{ padding: "2px 4px" }}
|
||||
>
|
||||
{secretApprovalRequestDetails?.environment}
|
||||
</p>
|
||||
<div className="mr-1 inline-flex w-min items-center rounded border border-mineshaft-500 pl-1.5 pr-2">
|
||||
<p className="cursor-default border-r border-mineshaft-500 pr-1.5">
|
||||
<FontAwesomeIcon icon={faFolder} className="text-yellow" size="sm" />
|
||||
<div className="mr-2 inline-flex w-min items-center rounded border border-mineshaft-500 pl-1 pr-2">
|
||||
<p className="cursor-default border-r border-mineshaft-500 pr-1">
|
||||
<FontAwesomeIcon icon={faFolder} className="text-primary" size="sm" />
|
||||
</p>
|
||||
<Tooltip content={approvalSecretPath}>
|
||||
<p
|
||||
@ -400,14 +391,14 @@ export const SecretApprovalRequestChanges = ({
|
||||
</div>
|
||||
has pending changes to be accepted from its source at{" "}
|
||||
<p
|
||||
className="mx-1 inline rounded bg-mineshaft-600/80 text-mineshaft-300"
|
||||
className="mx-1 inline rounded bg-primary-600/40 text-primary-300"
|
||||
style={{ padding: "2px 4px" }}
|
||||
>
|
||||
{replicatedImport?.importEnv?.slug}
|
||||
</p>
|
||||
<div className="mr-1 inline-flex w-min items-center rounded border border-mineshaft-500 pl-1.5 pr-2">
|
||||
<p className="cursor-default border-r border-mineshaft-500 pr-1.5">
|
||||
<FontAwesomeIcon icon={faFolder} className="text-yellow" size="sm" />
|
||||
<div className="inline-flex w-min items-center rounded border border-mineshaft-500 pl-1 pr-2">
|
||||
<p className="cursor-default border-r border-mineshaft-500 pr-1">
|
||||
<FontAwesomeIcon icon={faFolder} className="text-primary" size="sm" />
|
||||
</p>
|
||||
<Tooltip content={replicatedImport?.importPath}>
|
||||
<p
|
||||
@ -424,14 +415,14 @@ export const SecretApprovalRequestChanges = ({
|
||||
<div className="text-sm text-bunker-300">
|
||||
<p className="inline">Secret(s) in</p>
|
||||
<p
|
||||
className="mx-1 inline rounded bg-mineshaft-600/80 text-mineshaft-300"
|
||||
className="mx-1 inline rounded bg-primary-600/40 text-primary-300"
|
||||
style={{ padding: "2px 4px" }}
|
||||
>
|
||||
{secretApprovalRequestDetails?.environment}
|
||||
</p>
|
||||
<div className="mr-1 inline-flex w-min items-center rounded border border-mineshaft-500 pl-1.5 pr-2">
|
||||
<p className="cursor-default border-r border-mineshaft-500 pr-1.5">
|
||||
<FontAwesomeIcon icon={faFolder} className="text-yellow" size="sm" />
|
||||
<div className="mr-1 inline-flex w-min items-center rounded border border-mineshaft-500 pl-1 pr-2">
|
||||
<p className="cursor-default border-r border-mineshaft-500 pr-1">
|
||||
<FontAwesomeIcon icon={faFolder} className="text-primary" size="sm" />
|
||||
</p>
|
||||
<Tooltip content={formatReservedPaths(secretApprovalRequestDetails.secretPath)}>
|
||||
<p
|
||||
@ -472,7 +463,7 @@ export const SecretApprovalRequestChanges = ({
|
||||
const reviewer = reviewedUsers?.[requiredApprover.userId];
|
||||
return (
|
||||
<div
|
||||
className="flex w-full flex-col rounded-md bg-mineshaft-800 p-4 text-sm text-mineshaft-100"
|
||||
className="flex w-full flex-col rounded-md bg-mineshaft-800 p-4"
|
||||
key={`required-approver-${requiredApprover.userId}`}
|
||||
>
|
||||
<div>
|
||||
@ -486,16 +477,14 @@ export const SecretApprovalRequestChanges = ({
|
||||
{reviewer?.status === ApprovalStatus.APPROVED ? "approved" : "rejected"}
|
||||
</span>{" "}
|
||||
the request on{" "}
|
||||
{format(
|
||||
new Date(secretApprovalRequestDetails.createdAt),
|
||||
"MM/dd/yyyy h:mm:ss aa"
|
||||
)}
|
||||
.
|
||||
{format(new Date(secretApprovalRequestDetails.createdAt), "PPpp zzz")}.
|
||||
</div>
|
||||
{reviewer?.comment && (
|
||||
<GenericFieldLabel label="Comment" className="mt-2 max-w-4xl break-words">
|
||||
{reviewer?.comment && reviewer.comment}
|
||||
</GenericFieldLabel>
|
||||
<FormControl label="Comment" className="mb-0 mt-4">
|
||||
<TextArea value={reviewer.comment} isDisabled reSize="none">
|
||||
{reviewer?.comment && reviewer.comment}
|
||||
</TextArea>
|
||||
</FormControl>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@ -516,7 +505,7 @@ export const SecretApprovalRequestChanges = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sticky top-0 z-[51] w-1/5 cursor-default pt-2" style={{ minWidth: "240px" }}>
|
||||
<div className="sticky top-0 w-1/5 cursor-default pt-4" style={{ minWidth: "240px" }}>
|
||||
<div className="text-sm text-bunker-300">Reviewers</div>
|
||||
<div className="mt-2 flex flex-col space-y-2 text-sm">
|
||||
{secretApprovalRequestDetails?.policy?.approvers
|
||||
@ -537,17 +526,17 @@ export const SecretApprovalRequestChanges = ({
|
||||
requiredApprover.lastName || ""
|
||||
}`}
|
||||
>
|
||||
<span>{requiredApprover?.email}</span>
|
||||
<span>{requiredApprover?.email} </span>
|
||||
</Tooltip>
|
||||
<span className="text-red">*</span>
|
||||
</div>
|
||||
<div>
|
||||
{reviewer?.comment && (
|
||||
<Tooltip className="max-w-lg break-words" content={reviewer.comment}>
|
||||
<Tooltip content={reviewer.comment}>
|
||||
<FontAwesomeIcon
|
||||
icon={faComment}
|
||||
size="xs"
|
||||
className="mr-1.5 text-mineshaft-300"
|
||||
className="mr-1 text-mineshaft-300"
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
@ -7,13 +7,8 @@ type Props = {
|
||||
|
||||
export const OnePassSyncDestinationSection = ({ secretSync }: Props) => {
|
||||
const {
|
||||
destinationConfig: { vaultId, valueLabel }
|
||||
destinationConfig: { vaultId }
|
||||
} = secretSync;
|
||||
|
||||
return (
|
||||
<>
|
||||
<GenericFieldLabel label="Vault ID">{vaultId}</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Value Key">{valueLabel || "value"}</GenericFieldLabel>
|
||||
</>
|
||||
);
|
||||
return <GenericFieldLabel label="Vault ID">{vaultId}</GenericFieldLabel>;
|
||||
};
|
||||
|
Reference in New Issue
Block a user